diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd067aa32e60..53ebb0f925d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,12 +13,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + submodules: true - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: - go-version: 1.19 + go-version: 1.20.x - name: Test run: make test @@ -27,12 +29,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + submodules: true - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: - go-version: 1.19 + go-version: 1.20.x - name: Test run: make lint diff --git a/.gitignore b/.gitignore index e24e1d167709..3f27cdc00f07 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,6 @@ profile.cov /dashboard/assets/package-lock.json **/yarn-error.log -logs/ \ No newline at end of file +logs/ + +tests/spec-tests/ diff --git a/.travis.yml b/.travis.yml index 925e23b1a57f..4f5d482c6514 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ jobs: - stage: lint os: linux dist: bionic - go: 1.20.x + go: 1.21.x env: - lint git: @@ -28,7 +28,7 @@ jobs: os: linux arch: amd64 dist: bionic - go: 1.20.x + go: 1.21.x env: - docker services: @@ -45,7 +45,7 @@ jobs: os: linux arch: arm64 dist: bionic - go: 1.20.x + go: 1.21.x env: - docker services: @@ -63,10 +63,9 @@ jobs: os: linux dist: bionic sudo: required - go: 1.20.x + go: 1.21.x env: - azure-linux - - GO111MODULE=on git: submodules: false # avoid cloning ethereum/tests addons: @@ -97,10 +96,9 @@ jobs: - stage: build if: type = push os: osx - go: 1.20.x + go: 1.21.x env: - azure-osx - - GO111MODULE=on git: submodules: false # avoid cloning ethereum/tests script: @@ -112,41 +110,34 @@ jobs: os: linux arch: amd64 dist: bionic - go: 1.20.x - env: - - GO111MODULE=on + go: 1.21.x script: - - go run build/ci.go test $TEST_PACKAGES + - travis_wait 30 go run build/ci.go test $TEST_PACKAGES - stage: build if: type = pull_request os: linux arch: arm64 dist: bionic - go: 1.19.x - env: - - GO111MODULE=on + go: 1.20.x script: - - go run build/ci.go test $TEST_PACKAGES + - travis_wait 30 go run build/ci.go test $TEST_PACKAGES - stage: build os: linux dist: bionic - go: 1.19.x - env: - - GO111MODULE=on + go: 1.20.x script: - - go run build/ci.go test $TEST_PACKAGES + - travis_wait 30 go run build/ci.go test $TEST_PACKAGES # This builder does the Ubuntu PPA nightly uploads - stage: build if: type = cron || (type = push && tag ~= /^v[0-9]/) os: linux dist: bionic - go: 1.20.x + go: 1.21.x env: - ubuntu-ppa - - GO111MODULE=on git: submodules: false # avoid cloning ethereum/tests addons: @@ -167,10 +158,9 @@ jobs: if: type = cron os: linux dist: bionic - go: 1.20.x + go: 1.21.x env: - azure-purge - - GO111MODULE=on git: submodules: false # avoid cloning ethereum/tests script: @@ -181,9 +171,7 @@ jobs: if: type = cron os: linux dist: bionic - go: 1.20.x - env: - - GO111MODULE=on + go: 1.21.x script: - - go run build/ci.go test -race $TEST_PACKAGES + - travis_wait 30 go run build/ci.go test -race $TEST_PACKAGES diff --git a/Dockerfile b/Dockerfile index 1951fed8ef87..ed69a0478967 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ ARG VERSION="" ARG BUILDNUM="" # Build Geth in a stock Go builder container -FROM golang:1.20-alpine as builder +FROM golang:1.21-alpine as builder RUN apk add --no-cache gcc musl-dev linux-headers git diff --git a/Makefile b/Makefile index e6286da7fbae..d736ef61c001 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ GOBIN = ./build/bin GO ?= latest -GORUN = env GO111MODULE=on go run +GORUN = go run geth: $(GORUN) build/ci.go install ./cmd/geth @@ -23,7 +23,7 @@ lint: ## Run linters. $(GORUN) build/ci.go lint clean: - env GO111MODULE=on go clean -cache + go clean -cache rm -fr build/_workspace/pkg/ $(GOBIN)/* # The devtools target installs tools required for 'go generate'. diff --git a/README.md b/README.md index 34bd8b94c98d..77317090c17e 100644 --- a/README.md +++ b/README.md @@ -123,15 +123,6 @@ use separate accounts for play and real money. Unless you manually move accounts, `geth` will by default correctly separate the two networks and will not make any accounts available between them.* -### Full node on the Rinkeby test network - -Go Ethereum also supports connecting to the older proof-of-authority based test network -called [*Rinkeby*](https://www.rinkeby.io) which is operated by members of the community. - -```shell -$ geth --rinkeby console -``` - ### Configuration As an alternative to passing the numerous flags to the `geth` binary, you can also pass a diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 55576070a4ae..6e1075c715fd 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "io" + "math/big" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -246,24 +247,65 @@ func (abi *ABI) HasReceive() bool { // revertSelector is a special function selector for revert reason unpacking. var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4] +// panicSelector is a special function selector for panic reason unpacking. +var panicSelector = crypto.Keccak256([]byte("Panic(uint256)"))[:4] + +// panicReasons map is for readable panic codes +// see this linkage for the deails +// https://docs.soliditylang.org/en/v0.8.21/control-structures.html#panic-via-assert-and-error-via-require +// the reason string list is copied from ether.js +// https://github.com/ethers-io/ethers.js/blob/fa3a883ff7c88611ce766f58bdd4b8ac90814470/src.ts/abi/interface.ts#L207-L218 +var panicReasons = map[uint64]string{ + 0x00: "generic panic", + 0x01: "assert(false)", + 0x11: "arithmetic underflow or overflow", + 0x12: "division or modulo by zero", + 0x21: "enum overflow", + 0x22: "invalid encoded storage byte array accessed", + 0x31: "out-of-bounds array access; popping on an empty array", + 0x32: "out-of-bounds access of an array or bytesN", + 0x41: "out of memory", + 0x51: "uninitialized function", +} + // UnpackRevert resolves the abi-encoded revert reason. According to the solidity // spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert, -// the provided revert reason is abi-encoded as if it were a call to a function -// `Error(string)`. So it's a special tool for it. +// the provided revert reason is abi-encoded as if it were a call to function +// `Error(string)` or `Panic(uint256)`. So it's a special tool for it. func UnpackRevert(data []byte) (string, error) { if len(data) < 4 { return "", errors.New("invalid data for unpacking") } - if !bytes.Equal(data[:4], revertSelector) { + switch { + case bytes.Equal(data[:4], revertSelector): + typ, err := NewType("string", "", nil) + if err != nil { + return "", err + } + unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:]) + if err != nil { + return "", err + } + return unpacked[0].(string), nil + case bytes.Equal(data[:4], panicSelector): + typ, err := NewType("uint256", "", nil) + if err != nil { + return "", err + } + unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:]) + if err != nil { + return "", err + } + pCode := unpacked[0].(*big.Int) + // uint64 safety check for future + // but the code is not bigger than MAX(uint64) now + if pCode.IsUint64() { + if reason, ok := panicReasons[pCode.Uint64()]; ok { + return reason, nil + } + } + return fmt.Sprintf("unknown panic code: %#x", pCode), nil + default: return "", errors.New("invalid data for unpacking") } - typ, err := NewType("string", "", nil) - if err != nil { - return "", err - } - unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:]) - if err != nil { - return "", err - } - return unpacked[0].(string), nil } diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 3486ffd1a517..84175df4bb93 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -1173,6 +1173,8 @@ func TestUnpackRevert(t *testing.T) { {"", "", errors.New("invalid data for unpacking")}, {"08c379a1", "", errors.New("invalid data for unpacking")}, {"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil}, + {"4e487b710000000000000000000000000000000000000000000000000000000000000000", "generic panic", nil}, + {"4e487b7100000000000000000000000000000000000000000000000000000000000000ff", "unknown panic code: 0xff", nil}, } for index, c := range cases { t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) { diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index c16990f395c4..34530a645518 100644 --- a/accounts/abi/bind/backend.go +++ b/accounts/abi/bind/backend.go @@ -29,7 +29,7 @@ import ( var ( // ErrNoCode is returned by call and transact operations for which the requested // recipient contract to operate on does not exist in the state db or does not - // have any code associated with it (i.e. suicided). + // have any code associated with it (i.e. self-destructed). ErrNoCode = errors.New("no contract code at given address") // ErrNoPendingState is raised when attempting to perform a pending state action diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 9201055ab933..31e083c3165c 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -199,7 +199,6 @@ func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address, if err != nil { return nil, err } - return stateDB.GetCode(contract), nil } @@ -212,7 +211,6 @@ func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Addres if err != nil { return nil, err } - return stateDB.GetBalance(contract), nil } @@ -225,7 +223,6 @@ func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address, if err != nil { return 0, err } - return stateDB.GetNonce(contract), nil } @@ -238,7 +235,6 @@ func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Addres if err != nil { return nil, err } - val := stateDB.GetState(contract, key) return val[:], nil } @@ -610,8 +606,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) { return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") } - head := b.blockchain.CurrentHeader() - if !b.blockchain.Config().IsLondon(head.Number) { + if !b.blockchain.Config().IsLondon(header.Number) { // If there's no basefee, then it must be a non-1559 execution if call.GasPrice == nil { call.GasPrice = new(big.Int) @@ -633,13 +628,13 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM // Backfill the legacy gasPrice for EVM execution, unless we're all zeroes call.GasPrice = new(big.Int) if call.GasFeeCap.BitLen() > 0 || call.GasTipCap.BitLen() > 0 { - call.GasPrice = math.BigMin(new(big.Int).Add(call.GasTipCap, head.BaseFee), call.GasFeeCap) + call.GasPrice = math.BigMin(new(big.Int).Add(call.GasTipCap, header.BaseFee), call.GasFeeCap) } } } // Ensure message is initialized properly. if call.Gas == 0 { - call.Gas = 50000000 + call.Gas = 10 * header.GasLimit } if call.Value == nil { call.Value = new(big.Int) @@ -681,7 +676,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa // Get the last block block, err := b.blockByHash(ctx, b.pendingBlock.ParentHash()) if err != nil { - return fmt.Errorf("could not fetch parent") + return errors.New("could not fetch parent") } // Check transaction validity signer := types.MakeSigner(b.blockchain.Config(), block.Number(), block.Time()) @@ -700,8 +695,10 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa } block.AddTxWithChain(b.blockchain, tx) }) - stateDB, _ := b.blockchain.State() - + stateDB, err := b.blockchain.State() + if err != nil { + return err + } b.pendingBlock = blocks[0] b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil) b.pendingReceipts = receipts[0] @@ -815,17 +812,18 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { // Get the last block block := b.blockchain.GetBlockByHash(b.pendingBlock.ParentHash()) if block == nil { - return fmt.Errorf("could not find parent") + return errors.New("could not find parent") } blocks, _ := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { block.OffsetTime(int64(adjustment.Seconds())) }) - stateDB, _ := b.blockchain.State() - + stateDB, err := b.blockchain.State() + if err != nil { + return err + } b.pendingBlock = blocks[0] b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil) - return nil } @@ -892,7 +890,7 @@ func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (typ } func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { - logs := rawdb.ReadLogs(fb.db, hash, number, fb.bc.Config()) + logs := rawdb.ReadLogs(fb.db, hash, number) return logs, nil } diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index 698bfc576573..11900a6cf5c0 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -161,6 +161,7 @@ func TestAdjustTime(t *testing.T) { func TestNewAdjustTimeFail(t *testing.T) { testAddr := crypto.PubkeyToAddress(testKey.PublicKey) sim := simTestBackend(testAddr) + defer sim.blockchain.Stop() // Create tx and send head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 7b0584edaeff..8f620add3889 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/rpc" ) const basefeeWiggleMultiplier = 2 @@ -150,6 +151,48 @@ func DeployContract(opts *TransactOpts, abi abi.ABI, bytecode []byte, backend Co return c.address, tx, c, nil } +// parseError takes in an RPC error and attempts to parse any "execution reverted" +// data as an ABI error, returning an improved error if possible. +func (c *BoundContract) parseError(originalError error) error { + var dataErr rpc.DataError + if !errors.As(originalError, &dataErr) { + return originalError + } + dataString, ok := dataErr.ErrorData().(string) + if !ok { + return originalError + } + data := common.FromHex(dataString) + if len(data) < 4 { + return originalError + } + errAbi, _ := c.abi.ErrorByID([4]byte(data[:4])) + if errAbi == nil { + return originalError + } + vals, decodingErr := errAbi.Unpack(data) + if decodingErr != nil { + return fmt.Errorf("%w: failed to decode error as %v: %w", originalError, errAbi.Name, decodingErr) + } + for i, val := range vals { + bytes, ok := val.([32]byte) + if ok { + vals[i] = common.Hash(bytes) + } + } + fmtStr := "%w: %v(" + for i := range vals { + if i > 0 { + fmtStr += ", " + } + fmtStr += "%v" + } + fmtStr += ")" + fmtArgs := []any{originalError, errAbi.Name} + fmtArgs = append(fmtArgs, vals...) + return fmt.Errorf(fmtStr, fmtArgs...) +} + // Call invokes the (constant) contract method with params as input values and // sets the output to result. The result type might be a single field for simple // returns, a slice of interfaces for anonymous returns and a struct for named @@ -180,7 +223,7 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri } output, err = pb.PendingCallContract(ctx, msg) if err != nil { - return err + return c.parseError(err) } if len(output) == 0 { // Make sure we have a contract to operate on, and bail out otherwise. @@ -193,7 +236,7 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri } else { output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber) if err != nil { - return err + return c.parseError(err) } if len(output) == 0 { // Make sure we have a contract to operate on, and bail out otherwise. @@ -338,7 +381,7 @@ func (c *BoundContract) createLegacyTx(opts *TransactOpts, contract *common.Addr } func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Address, input []byte, gasPrice, gasTipCap, gasFeeCap, value *big.Int) (uint64, error) { - if contract != nil && (contract.Hash().Big().BitLen() > 16) { + if contract != nil && (contract.Big().BitLen() > 16) { // Gas estimation cannot succeed without code for method invocations, unless precompile. if code, err := c.transactor.PendingCodeAt(ensureContext(opts.Context), c.address); err != nil { return 0, err @@ -357,7 +400,7 @@ func (c *BoundContract) estimateGasLimit(opts *TransactOpts, contract *common.Ad } gasLimit, err := c.transactor.EstimateGas(ensureContext(opts.Context), msg) if err != nil { - return 0, err + return 0, c.parseError(err) } // Arbitrum: adjust the estimate adjustedLimit := gasLimit * (10000 + opts.GasMargin) / 10000 diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index c22eb4ae8432..95dc13cc188e 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -325,7 +325,7 @@ var ( if err != nil { return *outstruct, err } - {{range $i, $t := .Normalized.Outputs}} + {{range $i, $t := .Normalized.Outputs}} outstruct.{{.Name}} = *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} return *outstruct, err @@ -335,7 +335,7 @@ var ( } {{range $i, $t := .Normalized.Outputs}} out{{$i}} := *abi.ConvertType(out[{{$i}}], new({{bindtype .Type $structs}})).(*{{bindtype .Type $structs}}){{end}} - + return {{range $i, $t := .Normalized.Outputs}}out{{$i}}, {{end}} err {{end}} } @@ -378,7 +378,7 @@ var ( } {{end}} - {{if .Fallback}} + {{if .Fallback}} // Fallback is a paid mutator transaction binding the contract fallback function. // // Solidity: {{.Fallback.Original.String}} @@ -392,16 +392,16 @@ var ( func (_{{$contract.Type}} *{{$contract.Type}}Session) Fallback(calldata []byte) (*types.Transaction, error) { return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) } - + // Fallback is a paid mutator transaction binding the contract fallback function. - // + // // Solidity: {{.Fallback.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) } {{end}} - {{if .Receive}} + {{if .Receive}} // Receive is a paid mutator transaction binding the contract receive function. // // Solidity: {{.Receive.Original.String}} @@ -415,9 +415,9 @@ var ( func (_{{$contract.Type}} *{{$contract.Type}}Session) Receive() (*types.Transaction, error) { return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) } - + // Receive is a paid mutator transaction binding the contract receive function. - // + // // Solidity: {{.Receive.Original.String}} func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Receive() (*types.Transaction, error) { return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) diff --git a/accounts/abi/error.go b/accounts/abi/error.go index f65f0d2cc278..6cdef70024b8 100644 --- a/accounts/abi/error.go +++ b/accounts/abi/error.go @@ -32,7 +32,7 @@ type Error struct { str string // Sig contains the string signature according to the ABI spec. - // e.g. error foo(uint32 a, int b) = "foo(uint32,int256)" + // e.g. error foo(uint32 a, int b) = "foo(uint32,int256)" // Please note that "int" is substitute for its canonical representation "int256" Sig string @@ -82,12 +82,12 @@ func (e Error) String() string { return e.str } -func (e *Error) Unpack(data []byte) (interface{}, error) { +func (e *Error) Unpack(data []byte) ([]interface{}, error) { if len(data) < 4 { - return "", errors.New("invalid data for unpacking") + return nil, errors.New("invalid data for unpacking") } if !bytes.Equal(data[:4], e.ID[:4]) { - return "", errors.New("invalid data for unpacking") + return nil, errors.New("invalid data for unpacking") } return e.Inputs.Unpack(data[4:]) } diff --git a/accounts/abi/method.go b/accounts/abi/method.go index f69e3ee9b562..b6e1eef3cf3f 100644 --- a/accounts/abi/method.go +++ b/accounts/abi/method.go @@ -127,11 +127,12 @@ func NewMethod(name string, rawName string, funType FunctionType, mutability str state = state + " " } identity := fmt.Sprintf("function %v", rawName) - if funType == Fallback { + switch funType { + case Fallback: identity = "fallback" - } else if funType == Receive { + case Receive: identity = "receive" - } else if funType == Constructor { + case Constructor: identity = "constructor" } str := fmt.Sprintf("%v(%v) %sreturns(%v)", identity, strings.Join(inputNames, ", "), state, strings.Join(outputNames, ", ")) diff --git a/accounts/abi/method_test.go b/accounts/abi/method_test.go index 395a5289654a..9230e307aa41 100644 --- a/accounts/abi/method_test.go +++ b/accounts/abi/method_test.go @@ -84,11 +84,12 @@ func TestMethodString(t *testing.T) { for _, test := range table { var got string - if test.method == "fallback" { + switch test.method { + case "fallback": got = abi.Fallback.String() - } else if test.method == "receive" { + case "receive": got = abi.Receive.String() - } else { + default: got = abi.Methods[test.method].String() } if got != test.expectation { diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go index 1f84b111a3db..48d2ef41ec10 100644 --- a/accounts/abi/reflect.go +++ b/accounts/abi/reflect.go @@ -228,7 +228,7 @@ func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[stri structFieldName := ToCamelCase(argName) if structFieldName == "" { - return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct") + return nil, errors.New("abi: purely underscored output cannot unpack to struct") } // this abi has already been paired, skip it... unless there exists another, yet unassigned diff --git a/accounts/abi/selector_parser.go b/accounts/abi/selector_parser.go index d5472e374f5d..b8ddd7d656b2 100644 --- a/accounts/abi/selector_parser.go +++ b/accounts/abi/selector_parser.go @@ -17,6 +17,7 @@ package abi import ( + "errors" "fmt" ) @@ -40,7 +41,7 @@ func isIdentifierSymbol(c byte) bool { func parseToken(unescapedSelector string, isIdent bool) (string, string, error) { if len(unescapedSelector) == 0 { - return "", "", fmt.Errorf("empty token") + return "", "", errors.New("empty token") } firstChar := unescapedSelector[0] position := 1 @@ -110,7 +111,7 @@ func parseCompositeType(unescapedSelector string) ([]interface{}, string, error) func parseType(unescapedSelector string) (interface{}, string, error) { if len(unescapedSelector) == 0 { - return nil, "", fmt.Errorf("empty type") + return nil, "", errors.New("empty type") } if unescapedSelector[0] == '(' { return parseCompositeType(unescapedSelector) diff --git a/accounts/abi/type.go b/accounts/abi/type.go index 7f74907a8479..2eee11787fda 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -70,7 +70,7 @@ var ( func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) { // check that array brackets are equal if they exist if strings.Count(t, "[") != strings.Count(t, "]") { - return Type{}, fmt.Errorf("invalid arg type in abi") + return Type{}, errors.New("invalid arg type in abi") } typ.stringKind = t @@ -109,7 +109,7 @@ func NewType(t string, internalType string, components []ArgumentMarshaling) (ty } typ.stringKind = embeddedType.stringKind + sliced } else { - return Type{}, fmt.Errorf("invalid formatting of array type") + return Type{}, errors.New("invalid formatting of array type") } return typ, err } @@ -348,7 +348,7 @@ func (t Type) pack(v reflect.Value) ([]byte, error) { } } -// requireLengthPrefix returns whether the type requires any sort of length +// requiresLengthPrefix returns whether the type requires any sort of length // prefixing. func (t Type) requiresLengthPrefix() bool { return t.T == StringTy || t.T == BytesTy || t.T == SliceTy diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 1e778a6ff0db..905b5ce629db 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -18,6 +18,7 @@ package abi import ( "encoding/binary" + "errors" "fmt" "math" "math/big" @@ -125,7 +126,7 @@ func readBool(word []byte) (bool, error) { // readFunctionType enforces that standard by always presenting it as a 24-array (address + sig = 24 bytes) func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { if t.T != FunctionTy { - return [24]byte{}, fmt.Errorf("abi: invalid type in call to make function type byte array") + return [24]byte{}, errors.New("abi: invalid type in call to make function type byte array") } if garbage := binary.BigEndian.Uint64(word[24:32]); garbage != 0 { err = fmt.Errorf("abi: got improperly encoded function type, got %v", word) @@ -138,7 +139,7 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) { // ReadFixedBytes uses reflection to create a fixed array to be read from. func ReadFixedBytes(t Type, word []byte) (interface{}, error) { if t.T != FixedBytesTy { - return nil, fmt.Errorf("abi: invalid type in call to make fixed byte array") + return nil, errors.New("abi: invalid type in call to make fixed byte array") } // convert array := reflect.New(t.GetType()).Elem() @@ -159,14 +160,15 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) // this value will become our slice or our array, depending on the type var refSlice reflect.Value - if t.T == SliceTy { + switch t.T { + case SliceTy: // declare our slice refSlice = reflect.MakeSlice(t.GetType(), size, size) - } else if t.T == ArrayTy { + case ArrayTy: // declare our array refSlice = reflect.New(t.GetType()).Elem() - } else { - return nil, fmt.Errorf("abi: invalid type in array/slice unpacking stage") + default: + return nil, errors.New("abi: invalid type in array/slice unpacking stage") } // Arrays have packed elements, resulting in longer unpack steps. diff --git a/accounts/external/backend.go b/accounts/external/backend.go index d403b7e562d4..6f1581f9b806 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -17,6 +17,7 @@ package external import ( + "errors" "fmt" "math/big" "sync" @@ -98,11 +99,11 @@ func (api *ExternalSigner) Status() (string, error) { } func (api *ExternalSigner) Open(passphrase string) error { - return fmt.Errorf("operation not supported on external signers") + return errors.New("operation not supported on external signers") } func (api *ExternalSigner) Close() error { - return fmt.Errorf("operation not supported on external signers") + return errors.New("operation not supported on external signers") } func (api *ExternalSigner) Accounts() []accounts.Account { @@ -145,7 +146,7 @@ func (api *ExternalSigner) Contains(account accounts.Account) bool { } func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { - return accounts.Account{}, fmt.Errorf("operation not supported on external signers") + return accounts.Account{}, errors.New("operation not supported on external signers") } func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { @@ -242,14 +243,14 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio } func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { - return []byte{}, fmt.Errorf("password-operations not supported on external signers") + return []byte{}, errors.New("password-operations not supported on external signers") } func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { - return nil, fmt.Errorf("password-operations not supported on external signers") + return nil, errors.New("password-operations not supported on external signers") } func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { - return nil, fmt.Errorf("password-operations not supported on external signers") + return nil, errors.New("password-operations not supported on external signers") } func (api *ExternalSigner) listAccounts() ([]common.Address, error) { diff --git a/accounts/keystore/account_cache.go b/accounts/keystore/account_cache.go index 12f92d261951..4ed1439514e1 100644 --- a/accounts/keystore/account_cache.go +++ b/accounts/keystore/account_cache.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "golang.org/x/exp/slices" ) // Minimum amount of time between cache reloads. This limit applies if the platform does @@ -38,11 +39,10 @@ import ( // exist yet, the code will attempt to create a watcher at most this often. const minReloadInterval = 2 * time.Second -type accountsByURL []accounts.Account - -func (s accountsByURL) Len() int { return len(s) } -func (s accountsByURL) Less(i, j int) bool { return s[i].URL.Cmp(s[j].URL) < 0 } -func (s accountsByURL) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +// byURL defines the sorting order for accounts. +func byURL(a, b accounts.Account) int { + return a.URL.Cmp(b.URL) +} // AmbiguousAddrError is returned when attempting to unlock // an address for which more than one file exists. @@ -67,7 +67,7 @@ type accountCache struct { keydir string watcher *watcher mu sync.Mutex - all accountsByURL + all []accounts.Account byAddr map[common.Address][]accounts.Account throttle *time.Timer notify chan struct{} @@ -194,7 +194,7 @@ func (ac *accountCache) find(a accounts.Account) (accounts.Account, error) { default: err := &AmbiguousAddrError{Addr: a.Address, Matches: make([]accounts.Account, len(matches))} copy(err.Matches, matches) - sort.Sort(accountsByURL(err.Matches)) + slices.SortFunc(err.Matches, byURL) return accounts.Account{}, err } } diff --git a/accounts/keystore/account_cache_test.go b/accounts/keystore/account_cache_test.go index aa71c1201821..3847e9daf6ae 100644 --- a/accounts/keystore/account_cache_test.go +++ b/accounts/keystore/account_cache_test.go @@ -17,12 +17,12 @@ package keystore import ( + "errors" "fmt" "math/rand" "os" "path/filepath" "reflect" - "sort" "testing" "time" @@ -30,6 +30,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "golang.org/x/exp/slices" ) var ( @@ -74,7 +75,7 @@ func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error { select { case <-ks.changes: default: - return fmt.Errorf("wasn't notified of new accounts") + return errors.New("wasn't notified of new accounts") } return nil } @@ -202,7 +203,7 @@ func TestCacheAddDeleteOrder(t *testing.T) { // Check that the account list is sorted by filename. wantAccounts := make([]accounts.Account, len(accs)) copy(wantAccounts, accs) - sort.Sort(accountsByURL(wantAccounts)) + slices.SortFunc(wantAccounts, byURL) list := cache.accounts() if !reflect.DeepEqual(list, wantAccounts) { t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts)) diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/keystore_test.go index f90d809b55f4..deb7cae9f9e1 100644 --- a/accounts/keystore/keystore_test.go +++ b/accounts/keystore/keystore_test.go @@ -20,7 +20,6 @@ import ( "math/rand" "os" "runtime" - "sort" "strings" "sync" "sync/atomic" @@ -31,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" + "golang.org/x/exp/slices" ) var testSigData = make([]byte, 32) @@ -397,19 +397,19 @@ func TestImportRace(t *testing.T) { t.Fatalf("failed to export account: %v", acc) } _, ks2 := tmpKeyStore(t, true) - var atom uint32 + var atom atomic.Uint32 var wg sync.WaitGroup wg.Add(2) for i := 0; i < 2; i++ { go func() { defer wg.Done() if _, err := ks2.Import(json, "new", "new"); err != nil { - atomic.AddUint32(&atom, 1) + atom.Add(1) } }() } wg.Wait() - if atom != 1 { + if atom.Load() != 1 { t.Errorf("Import is racy") } } @@ -424,7 +424,7 @@ func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, walle for _, account := range live { liveList = append(liveList, account) } - sort.Sort(accountsByURL(liveList)) + slices.SortFunc(liveList, byURL) for j, wallet := range wallets { if accs := wallet.Accounts(); len(accs) != 1 { t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs)) diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go index 1701fbf53634..8d6ed2b14ef2 100644 --- a/accounts/keystore/passphrase.go +++ b/accounts/keystore/passphrase.go @@ -225,10 +225,13 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) { if err != nil { return nil, err } - key := crypto.ToECDSAUnsafe(keyBytes) + key, err := crypto.ToECDSA(keyBytes) + if err != nil { + return nil, fmt.Errorf("invalid key: %w", err) + } id, err := uuid.FromBytes(keyId) if err != nil { - return nil, err + return nil, fmt.Errorf("invalid UUID: %w", err) } return &Key{ Id: id, diff --git a/accounts/keystore/watch.go b/accounts/keystore/watch.go index 3f64b89c585e..a9f87e7c323e 100644 --- a/accounts/keystore/watch.go +++ b/accounts/keystore/watch.go @@ -20,6 +20,7 @@ package keystore import ( + "os" "time" "github.com/ethereum/go-ethereum/log" @@ -77,7 +78,9 @@ func (w *watcher) loop() { } defer watcher.Close() if err := watcher.Add(w.ac.keydir); err != nil { - logger.Warn("Failed to watch keystore folder", "err", err) + if !os.IsNotExist(err) { + logger.Warn("Failed to watch keystore folder", "err", err) + } return } diff --git a/accounts/scwallet/README.md b/accounts/scwallet/README.md index 4313d9c6b2f8..28079c47435c 100644 --- a/accounts/scwallet/README.md +++ b/accounts/scwallet/README.md @@ -8,7 +8,7 @@ ## Preparing the smartcard - **WARNING: FOILLOWING THESE INSTRUCTIONS WILL DESTROY THE MASTER KEY ON YOUR CARD. ONLY PROCEED IF NO FUNDS ARE ASSOCIATED WITH THESE ACCOUNTS** + **WARNING: FOLLOWING THESE INSTRUCTIONS WILL DESTROY THE MASTER KEY ON YOUR CARD. ONLY PROCEED IF NO FUNDS ARE ASSOCIATED WITH THESE ACCOUNTS** You can use status' [keycard-cli](https://github.com/status-im/keycard-cli) and you should get _at least_ version 2.1.1 of their [smartcard application](https://github.com/status-im/status-keycard/releases/download/2.2.1/keycard_v2.2.1.cap) diff --git a/accounts/scwallet/securechannel.go b/accounts/scwallet/securechannel.go index b1b533eb7243..bbd8b2264796 100644 --- a/accounts/scwallet/securechannel.go +++ b/accounts/scwallet/securechannel.go @@ -24,6 +24,7 @@ import ( "crypto/rand" "crypto/sha256" "crypto/sha512" + "errors" "fmt" "github.com/ethereum/go-ethereum/crypto" @@ -125,7 +126,7 @@ func (s *SecureChannelSession) Pair(pairingPassword []byte) error { // Unpair disestablishes an existing pairing. func (s *SecureChannelSession) Unpair() error { if s.PairingKey == nil { - return fmt.Errorf("cannot unpair: not paired") + return errors.New("cannot unpair: not paired") } _, err := s.transmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{}) @@ -141,7 +142,7 @@ func (s *SecureChannelSession) Unpair() error { // Open initializes the secure channel. func (s *SecureChannelSession) Open() error { if s.iv != nil { - return fmt.Errorf("session already opened") + return errors.New("session already opened") } response, err := s.open() @@ -215,7 +216,7 @@ func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*responseAPDU, error // transmitEncrypted sends an encrypted message, and decrypts and returns the response. func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) { if s.iv == nil { - return nil, fmt.Errorf("channel not open") + return nil, errors.New("channel not open") } data, err := s.encryptAPDU(data) @@ -254,7 +255,7 @@ func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []b return nil, err } if !bytes.Equal(s.iv, rmac) { - return nil, fmt.Errorf("invalid MAC in response") + return nil, errors.New("invalid MAC in response") } rapdu := &responseAPDU{} @@ -319,7 +320,7 @@ func unpad(data []byte, terminator byte) ([]byte, error) { return nil, fmt.Errorf("expected end of padding, got %d", data[len(data)-i]) } } - return nil, fmt.Errorf("expected end of padding, got 0") + return nil, errors.New("expected end of padding, got 0") } // updateIV is an internal method that updates the initialization vector after diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go index 6a40e28ae670..067bda83f145 100644 --- a/accounts/scwallet/wallet.go +++ b/accounts/scwallet/wallet.go @@ -252,7 +252,7 @@ func (w *Wallet) release() error { // with the wallet. func (w *Wallet) pair(puk []byte) error { if w.session.paired() { - return fmt.Errorf("wallet already paired") + return errors.New("wallet already paired") } pairing, err := w.session.pair(puk) if err != nil { @@ -813,7 +813,7 @@ func (s *Session) pair(secret []byte) (smartcardPairing, error) { // unpair deletes an existing pairing. func (s *Session) unpair() error { if !s.verified { - return fmt.Errorf("unpair requires that the PIN be verified") + return errors.New("unpair requires that the PIN be verified") } return s.Channel.Unpair() } @@ -907,7 +907,7 @@ func (s *Session) initialize(seed []byte) error { return err } if status == "Online" { - return fmt.Errorf("card is already initialized, cowardly refusing to proceed") + return errors.New("card is already initialized, cowardly refusing to proceed") } s.Wallet.lock.Lock() diff --git a/accounts/usbwallet/hub.go b/accounts/usbwallet/hub.go index 2139967228f5..e67942dbc107 100644 --- a/accounts/usbwallet/hub.go +++ b/accounts/usbwallet/hub.go @@ -63,9 +63,9 @@ type Hub struct { stateLock sync.RWMutex // Protects the internals of the hub from racey access // TODO(karalabe): remove if hotplug lands on Windows - commsPend int // Number of operations blocking enumeration - commsLock sync.Mutex // Lock protecting the pending counter and enumeration - enumFails uint32 // Number of times enumeration has failed + commsPend int // Number of operations blocking enumeration + commsLock sync.Mutex // Lock protecting the pending counter and enumeration + enumFails atomic.Uint32 // Number of times enumeration has failed } // NewLedgerHub creates a new hardware wallet manager for Ledger devices. @@ -151,7 +151,7 @@ func (hub *Hub) refreshWallets() { return } // If USB enumeration is continually failing, don't keep trying indefinitely - if atomic.LoadUint32(&hub.enumFails) > 2 { + if hub.enumFails.Load() > 2 { return } // Retrieve the current list of USB wallet devices @@ -172,7 +172,7 @@ func (hub *Hub) refreshWallets() { } infos, err := usb.Enumerate(hub.vendorID, 0) if err != nil { - failcount := atomic.AddUint32(&hub.enumFails, 1) + failcount := hub.enumFails.Add(1) if runtime.GOOS == "linux" { // See rationale before the enumeration why this is needed and only on Linux. hub.commsLock.Unlock() @@ -181,7 +181,7 @@ func (hub *Hub) refreshWallets() { "vendor", hub.vendorID, "failcount", failcount, "err", err) return } - atomic.StoreUint32(&hub.enumFails, 0) + hub.enumFails.Store(0) for _, info := range infos { for _, id := range hub.productIDs { diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index 0e399a6d09ab..05add081ab77 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -624,7 +624,7 @@ func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID return signed, nil } -// SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary +// SignTextWithPassphrase implements accounts.Wallet, however signing arbitrary // data is not supported for Ledger wallets, so this method will always return // an error. func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { diff --git a/arbitrum/apibackend.go b/arbitrum/apibackend.go index 9580851f7a0e..8033063402b0 100644 --- a/arbitrum/apibackend.go +++ b/arbitrum/apibackend.go @@ -86,7 +86,7 @@ type SyncProgressBackend interface { FinalizedBlockNumber(ctx context.Context) (uint64, error) } -func createRegisterAPIBackend(backend *Backend, sync SyncProgressBackend, filterConfig filters.Config, fallbackClientUrl string, fallbackClientTimeout time.Duration) (*filters.FilterSystem, error) { +func createRegisterAPIBackend(backend *Backend, filterConfig filters.Config, fallbackClientUrl string, fallbackClientTimeout time.Duration) (*filters.FilterSystem, error) { fallbackClient, err := CreateFallbackClient(fallbackClientUrl, fallbackClientTimeout) if err != nil { return nil, err @@ -94,13 +94,20 @@ func createRegisterAPIBackend(backend *Backend, sync SyncProgressBackend, filter backend.apiBackend = &APIBackend{ b: backend, fallbackClient: fallbackClient, - sync: sync, } filterSystem := filters.NewFilterSystem(backend.apiBackend, filterConfig) backend.stack.RegisterAPIs(backend.apiBackend.GetAPIs(filterSystem)) return filterSystem, nil } +func (a *APIBackend) SetSyncBackend(sync SyncProgressBackend) error { + if a.sync != nil { + return errors.New("sync progress monitor already set") + } + a.sync = sync + return nil +} + func (a *APIBackend) GetAPIs(filterSystem *filters.FilterSystem) []rpc.API { apis := ethapi.GetAPIs(a) @@ -137,7 +144,7 @@ func (a *APIBackend) GetAPIs(filterSystem *filters.FilterSystem) []rpc.API { return apis } -func (a *APIBackend) blockChain() *core.BlockChain { +func (a *APIBackend) BlockChain() *core.BlockChain { return a.b.arb.BlockChain() } @@ -146,7 +153,7 @@ func (a *APIBackend) GetArbitrumNode() interface{} { } func (a *APIBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { - if body := a.blockChain().GetBody(hash); body != nil { + if body := a.BlockChain().GetBody(hash); body != nil { return body, nil } return nil, errors.New("block body not found") @@ -154,11 +161,16 @@ func (a *APIBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.B // General Ethereum API func (a *APIBackend) SyncProgressMap() map[string]interface{} { + if a.sync == nil { + res := make(map[string]interface{}) + res["error"] = "sync object not set in apibackend" + return res + } return a.sync.SyncProgressMap() } func (a *APIBackend) SyncProgress() ethereum.SyncProgress { - progress := a.sync.SyncProgressMap() + progress := a.SyncProgressMap() if len(progress) == 0 { return ethereum.SyncProgress{} @@ -184,7 +196,7 @@ func (a *APIBackend) FeeHistory( } nitroGenesis := rpc.BlockNumber(a.ChainConfig().ArbitrumChainParams.GenesisBlockNum) - newestBlock, latestBlock := a.blockChain().ClipToPostNitroGenesis(newestBlock) + newestBlock, latestBlock := a.BlockChain().ClipToPostNitroGenesis(newestBlock) maxFeeHistory := a.b.config.FeeHistoryMaxBlockCount if blocks > maxFeeHistory { @@ -260,7 +272,7 @@ func (a *APIBackend) FeeHistory( currentTimestampGasUsed = 0 } - receipts := a.blockChain().GetReceiptsByHash(header.ReceiptHash) + receipts := a.BlockChain().GetReceiptsByHash(header.ReceiptHash) for _, receipt := range receipts { if receipt.GasUsed > receipt.GasUsedForL1 { currentTimestampGasUsed += receipt.GasUsed - receipt.GasUsedForL1 @@ -329,17 +341,23 @@ func (a *APIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) } func (a *APIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { - return a.blockChain().GetHeaderByHash(hash), nil + return a.BlockChain().GetHeaderByHash(hash), nil } func (a *APIBackend) blockNumberToUint(ctx context.Context, number rpc.BlockNumber) (uint64, error) { if number == rpc.LatestBlockNumber || number == rpc.PendingBlockNumber { - return a.blockChain().CurrentBlock().Number.Uint64(), nil + return a.BlockChain().CurrentBlock().Number.Uint64(), nil } if number == rpc.SafeBlockNumber { + if a.sync == nil { + return 0, errors.New("block number not supported: object not set") + } return a.sync.SafeBlockNumber(ctx) } if number == rpc.FinalizedBlockNumber { + if a.sync == nil { + return 0, errors.New("block number not supported: object not set") + } return a.sync.FinalizedBlockNumber(ctx) } if number < 0 { @@ -350,13 +368,13 @@ func (a *APIBackend) blockNumberToUint(ctx context.Context, number rpc.BlockNumb func (a *APIBackend) headerByNumberImpl(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { if number == rpc.LatestBlockNumber || number == rpc.PendingBlockNumber { - return a.blockChain().CurrentBlock(), nil + return a.BlockChain().CurrentBlock(), nil } numUint, err := a.blockNumberToUint(ctx, number) if err != nil { return nil, err } - return a.blockChain().GetHeaderByNumber(numUint), nil + return a.BlockChain().GetHeaderByNumber(numUint), nil } func (a *APIBackend) headerByNumberOrHashImpl(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { @@ -366,7 +384,7 @@ func (a *APIBackend) headerByNumberOrHashImpl(ctx context.Context, blockNrOrHash } hash, ishash := blockNrOrHash.Hash() if ishash { - return a.blockChain().GetHeaderByHash(hash), nil + return a.BlockChain().GetHeaderByHash(hash), nil } return nil, errors.New("invalid arguments; neither block nor hash specified") } @@ -376,17 +394,17 @@ func (a *APIBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc } func (a *APIBackend) CurrentHeader() *types.Header { - return a.blockChain().CurrentHeader() + return a.BlockChain().CurrentHeader() } func (a *APIBackend) CurrentBlock() *types.Header { - return a.blockChain().CurrentBlock() + return a.BlockChain().CurrentBlock() } func (a *APIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { if number == rpc.LatestBlockNumber || number == rpc.PendingBlockNumber { - currentHeader := a.blockChain().CurrentBlock() - currentBlock := a.blockChain().GetBlock(currentHeader.Hash(), currentHeader.Number.Uint64()) + currentHeader := a.BlockChain().CurrentBlock() + currentBlock := a.BlockChain().GetBlock(currentHeader.Hash(), currentHeader.Number.Uint64()) if currentBlock == nil { return nil, errors.New("can't find block for current header") } @@ -396,11 +414,11 @@ func (a *APIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) if err != nil { return nil, err } - return a.blockChain().GetBlockByNumber(numUint), nil + return a.BlockChain().GetBlockByNumber(numUint), nil } func (a *APIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - return a.blockChain().GetBlockByHash(hash), nil + return a.BlockChain().GetBlockByHash(hash), nil } func (a *APIBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { @@ -422,10 +440,10 @@ func (a *APIBackend) stateAndHeaderFromHeader(ctx context.Context, header *types if header == nil { return nil, nil, errors.New("header not found") } - if !a.blockChain().Config().IsArbitrumNitro(header.Number) { + if !a.BlockChain().Config().IsArbitrumNitro(header.Number) { return nil, header, types.ErrUseFallback } - bc := a.blockChain() + bc := a.BlockChain() stateFor := func(header *types.Header) (*state.StateDB, error) { return bc.StateAt(header.Root) } @@ -454,7 +472,7 @@ func (a *APIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOr } func (a *APIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) { - if !a.blockChain().Config().IsArbitrumNitro(block.Number()) { + if !a.BlockChain().Config().IsArbitrumNitro(block.Number()) { return nil, nil, types.ErrUseFallback } // DEV: This assumes that `StateAtBlock` only accesses the blockchain and chainDb fields @@ -462,7 +480,7 @@ func (a *APIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexe } func (a *APIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { - if !a.blockChain().Config().IsArbitrumNitro(block.Number()) { + if !a.BlockChain().Config().IsArbitrumNitro(block.Number()) { return nil, vm.BlockContext{}, nil, nil, types.ErrUseFallback } // DEV: This assumes that `StateAtTransaction` only accesses the blockchain and chainDb fields @@ -470,12 +488,12 @@ func (a *APIBackend) StateAtTransaction(ctx context.Context, block *types.Block, } func (a *APIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { - return a.blockChain().GetReceiptsByHash(hash), nil + return a.BlockChain().GetReceiptsByHash(hash), nil } func (a *APIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { - if header := a.blockChain().GetHeaderByHash(hash); header != nil { - return a.blockChain().GetTd(hash, header.Number.Uint64()) + if header := a.BlockChain().GetHeaderByHash(hash); header != nil { + return a.BlockChain().GetTd(hash, header.Number.Uint64()) } return nil } @@ -483,22 +501,28 @@ func (a *APIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { func (a *APIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) { vmError := func() error { return nil } if vmConfig == nil { - vmConfig = a.blockChain().GetVMConfig() + vmConfig = a.BlockChain().GetVMConfig() } txContext := core.NewEVMTxContext(msg) - return vm.NewEVM(*blockCtx, txContext, state, a.blockChain().Config(), *vmConfig), vmError + var context vm.BlockContext + if blockCtx != nil { + context = *blockCtx + } else { + context = core.NewEVMBlockContext(header, a.BlockChain(), nil) + } + return vm.NewEVM(context, txContext, state, a.BlockChain().Config(), *vmConfig), vmError } func (a *APIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { - return a.blockChain().SubscribeChainEvent(ch) + return a.BlockChain().SubscribeChainEvent(ch) } func (a *APIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { - return a.blockChain().SubscribeChainHeadEvent(ch) + return a.BlockChain().SubscribeChainHeadEvent(ch) } func (a *APIBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { - return a.blockChain().SubscribeChainSideEvent(ch) + return a.BlockChain().SubscribeChainSideEvent(ch) } // Transaction pool API @@ -526,7 +550,7 @@ func (a *APIBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { } func (a *APIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { - stateDB, err := a.blockChain().State() + stateDB, err := a.BlockChain().State() if err != nil { return 0, err } @@ -537,11 +561,11 @@ func (a *APIBackend) Stats() (pending int, queued int) { panic("not implemented") // TODO: Implement } -func (a *APIBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { +func (a *APIBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { panic("not implemented") // TODO: Implement } -func (a *APIBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) { +func (a *APIBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { panic("not implemented") // TODO: Implement } @@ -556,7 +580,7 @@ func (a *APIBackend) BloomStatus() (uint64, uint64) { } func (a *APIBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { - return rawdb.ReadLogs(a.ChainDb(), hash, number, a.ChainConfig()), nil + return rawdb.ReadLogs(a.ChainDb(), hash, number), nil } func (a *APIBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { @@ -566,7 +590,7 @@ func (a *APIBackend) ServiceFilter(ctx context.Context, session *bloombits.Match } func (a *APIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { - return a.blockChain().SubscribeLogsEvent(ch) + return a.BlockChain().SubscribeLogsEvent(ch) } func (a *APIBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { @@ -575,15 +599,15 @@ func (a *APIBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Sub } func (a *APIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { - return a.blockChain().SubscribeRemovedLogsEvent(ch) + return a.BlockChain().SubscribeRemovedLogsEvent(ch) } func (a *APIBackend) ChainConfig() *params.ChainConfig { - return a.blockChain().Config() + return a.BlockChain().Config() } func (a *APIBackend) Engine() consensus.Engine { - return a.blockChain().Engine() + return a.BlockChain().Engine() } func (b *APIBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { diff --git a/arbitrum/backend.go b/arbitrum/backend.go index aaf4c0ad0030..b1bb9c89de03 100644 --- a/arbitrum/backend.go +++ b/arbitrum/backend.go @@ -34,7 +34,7 @@ type Backend struct { chanNewBlock chan struct{} //create new L2 block unless empty } -func NewBackend(stack *node.Node, config *Config, chainDb ethdb.Database, publisher ArbInterface, sync SyncProgressBackend, filterConfig filters.Config) (*Backend, *filters.FilterSystem, error) { +func NewBackend(stack *node.Node, config *Config, chainDb ethdb.Database, publisher ArbInterface, filterConfig filters.Config) (*Backend, *filters.FilterSystem, error) { backend := &Backend{ arb: publisher, stack: stack, @@ -51,8 +51,16 @@ func NewBackend(stack *node.Node, config *Config, chainDb ethdb.Database, publis chanNewBlock: make(chan struct{}, 1), } + if len(config.AllowMethod) > 0 { + rpcFilter := make(map[string]bool) + for _, method := range config.AllowMethod { + rpcFilter[method] = true + } + backend.stack.ApplyAPIFilter(rpcFilter) + } + backend.bloomIndexer.Start(backend.arb.BlockChain()) - filterSystem, err := createRegisterAPIBackend(backend, sync, filterConfig, config.ClassicRedirect, config.ClassicRedirectTimeout) + filterSystem, err := createRegisterAPIBackend(backend, filterConfig, config.ClassicRedirect, config.ClassicRedirectTimeout) if err != nil { return nil, nil, err } diff --git a/arbitrum/config.go b/arbitrum/config.go index fbae58588e12..308cf9aee19d 100644 --- a/arbitrum/config.go +++ b/arbitrum/config.go @@ -37,6 +37,8 @@ type Config struct { ClassicRedirect string `koanf:"classic-redirect"` ClassicRedirectTimeout time.Duration `koanf:"classic-redirect-timeout"` MaxRecreateStateDepth int64 `koanf:"max-recreate-state-depth"` + + AllowMethod []string `koanf:"allow-method"` } type ArbDebugConfig struct { @@ -57,6 +59,7 @@ func ConfigAddOptions(prefix string, f *flag.FlagSet) { f.Int(prefix+".filter-log-cache-size", DefaultConfig.FilterLogCacheSize, "log filter system maximum number of cached blocks") f.Duration(prefix+".filter-timeout", DefaultConfig.FilterTimeout, "log filter system maximum time filters stay active") f.Int64(prefix+".max-recreate-state-depth", DefaultConfig.MaxRecreateStateDepth, "maximum depth for recreating state, measured in l2 gas (0=don't recreate state, -1=infinite, -2=use default value for archive or non-archive node (whichever is configured))") + f.StringSlice(prefix+".allow-method", DefaultConfig.AllowMethod, "list of whitelisted rpc methods") arbDebug := DefaultConfig.ArbDebug f.Uint64(prefix+".arbdebug.block-range-bound", arbDebug.BlockRangeBound, "bounds the number of blocks arbdebug calls may return") f.Uint64(prefix+".arbdebug.timeout-queue-bound", arbDebug.TimeoutQueueBound, "bounds the length of timeout queues arbdebug calls may return") @@ -81,6 +84,7 @@ var DefaultConfig = Config{ FeeHistoryMaxBlockCount: 1024, ClassicRedirect: "", MaxRecreateStateDepth: UninitializedMaxRecreateStateDepth, // default value should be set for depending on node type (archive / non-archive) + AllowMethod: []string{}, ArbDebug: ArbDebugConfig{ BlockRangeBound: 256, TimeoutQueueBound: 512, diff --git a/arbitrum/export.go b/arbitrum/export.go index d053c79f0448..92d7d23f83da 100644 --- a/arbitrum/export.go +++ b/arbitrum/export.go @@ -11,8 +11,8 @@ import ( type TransactionArgs = ethapi.TransactionArgs -func EstimateGas(ctx context.Context, b ethapi.Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) { - return ethapi.DoEstimateGas(ctx, b, args, blockNrOrHash, gasCap) +func EstimateGas(ctx context.Context, b ethapi.Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *ethapi.StateOverride, gasCap uint64) (hexutil.Uint64, error) { + return ethapi.DoEstimateGas(ctx, b, args, blockNrOrHash, overrides, gasCap) } func NewRevertReason(result *core.ExecutionResult) error { diff --git a/arbitrum/recordingdb.go b/arbitrum/recordingdb.go index 7c71a7a7b076..701ab3a0ddc0 100644 --- a/arbitrum/recordingdb.go +++ b/arbitrum/recordingdb.go @@ -20,6 +20,7 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/hashdb" flag "github.com/spf13/pflag" ) @@ -187,9 +188,15 @@ type RecordingDatabase struct { } func NewRecordingDatabase(config *RecordingDatabaseConfig, ethdb ethdb.Database, blockchain *core.BlockChain) *RecordingDatabase { + hashConfig := *hashdb.Defaults + hashConfig.CleanCacheSize = config.TrieCleanCache + trieConfig := trie.Config{ + Preimages: false, + HashDB: &hashConfig, + } return &RecordingDatabase{ config: config, - db: state.NewDatabaseWithConfig(ethdb, &trie.Config{Cache: config.TrieCleanCache}), + db: state.NewDatabaseWithConfig(ethdb, &trieConfig), bc: blockchain, } } @@ -235,28 +242,28 @@ func (r *RecordingDatabase) dereferenceRoot(root common.Hash) { r.db.TrieDB().Dereference(root) } -func (r *RecordingDatabase) addStateVerify(statedb *state.StateDB, expected common.Hash) error { +func (r *RecordingDatabase) addStateVerify(statedb *state.StateDB, expected common.Hash, blockNumber uint64) (*state.StateDB, error) { r.mutex.Lock() defer r.mutex.Unlock() - result, err := statedb.Commit(true) + result, err := statedb.Commit(blockNumber, true) if err != nil { - return err + return nil, err } if result != expected { - return fmt.Errorf("bad root hash expected: %v got: %v", expected, result) + return nil, fmt.Errorf("bad root hash expected: %v got: %v", expected, result) } r.referenceRootLockHeld(result) - size, _ := r.db.TrieDB().Size() + _, size, _ := r.db.TrieDB().Size() limit := common.StorageSize(r.config.TrieDirtyCache) * 1024 * 1024 recordingDbSize.Update(int64(size)) if size > limit { log.Info("Recording DB: flushing to disk", "size", size, "limit", limit) r.db.TrieDB().Cap(limit - ethdb.IdealBatchSize) - size, _ = r.db.TrieDB().Size() + _, size, _ = r.db.TrieDB().Size() recordingDbSize.Update(int64(size)) } - return nil + return state.New(result, statedb.Database(), nil) } func (r *RecordingDatabase) PrepareRecording(ctx context.Context, lastBlockHeader *types.Header, logFunc StateBuildingLogFunction) (*state.StateDB, core.ChainContext, *RecordingKV, error) { @@ -326,12 +333,13 @@ func (r *RecordingDatabase) GetOrRecreateState(ctx context.Context, header *type prevHash := currentHeader.Hash() returnedBlockNumber := header.Number.Uint64() for ctx.Err() == nil { - state, block, err := AdvanceStateByBlock(ctx, r.bc, state, header, blockToRecreate, prevHash, logFunc) + var block *types.Block + state, block, err = AdvanceStateByBlock(ctx, r.bc, state, header, blockToRecreate, prevHash, logFunc) if err != nil { return nil, err } prevHash = block.Hash() - err = r.addStateVerify(state, block.Root()) + state, err = r.addStateVerify(state, block.Root(), block.NumberU64()) if err != nil { return nil, fmt.Errorf("failed committing state for block %d : %w", blockToRecreate, err) } diff --git a/arbitrum_types/txoptions.go b/arbitrum_types/txoptions.go index 8ff2b1d6b34a..b6b70897ee82 100644 --- a/arbitrum_types/txoptions.go +++ b/arbitrum_types/txoptions.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/rpc" "github.com/pkg/errors" @@ -70,10 +70,10 @@ func (r RootHashOrSlots) MarshalJSON() ([]byte, error) { type ConditionalOptions struct { KnownAccounts map[common.Address]RootHashOrSlots `json:"knownAccounts"` - BlockNumberMin *hexutil.Uint64 `json:"blockNumberMin,omitempty"` - BlockNumberMax *hexutil.Uint64 `json:"blockNumberMax,omitempty"` - TimestampMin *hexutil.Uint64 `json:"timestampMin,omitempty"` - TimestampMax *hexutil.Uint64 `json:"timestampMax,omitempty"` + BlockNumberMin *math.HexOrDecimal64 `json:"blockNumberMin,omitempty"` + BlockNumberMax *math.HexOrDecimal64 `json:"blockNumberMax,omitempty"` + TimestampMin *math.HexOrDecimal64 `json:"timestampMin,omitempty"` + TimestampMax *math.HexOrDecimal64 `json:"timestampMax,omitempty"` } func (o *ConditionalOptions) Check(l1BlockNumber uint64, l2Timestamp uint64, statedb *state.StateDB) error { @@ -91,14 +91,8 @@ func (o *ConditionalOptions) Check(l1BlockNumber uint64, l2Timestamp uint64, sta } for address, rootHashOrSlots := range o.KnownAccounts { if rootHashOrSlots.RootHash != nil { - trie, err := statedb.StorageTrie(address) - if err != nil { - return err - } - if trie == nil { - return NewRejectedError("Storage trie not found for address key in knownAccounts option") - } - if trie.Hash() != *rootHashOrSlots.RootHash { + storageRoot := statedb.GetStorageRoot(address) + if storageRoot != *rootHashOrSlots.RootHash { return NewRejectedError("Storage root hash condition not met") } } else if len(rootHashOrSlots.SlotValue) > 0 { diff --git a/beacon/engine/errors.go b/beacon/engine/errors.go index 769001b9e3d7..62773a0ea9f6 100644 --- a/beacon/engine/errors.go +++ b/beacon/engine/errors.go @@ -80,6 +80,7 @@ var ( InvalidPayloadAttributes = &EngineAPIError{code: -38003, msg: "Invalid payload attributes"} TooLargeRequest = &EngineAPIError{code: -38004, msg: "Too large request"} InvalidParams = &EngineAPIError{code: -32602, msg: "Invalid parameters"} + UnsupportedFork = &EngineAPIError{code: -38005, msg: "Unsupported fork"} STATUS_INVALID = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: INVALID}, PayloadID: nil} STATUS_SYNCING = ForkChoiceResponse{PayloadStatus: PayloadStatusV1{Status: SYNCING}, PayloadID: nil} diff --git a/beacon/engine/gen_blockparams.go b/beacon/engine/gen_blockparams.go index 0dd2b52597ad..b1f01b50ff8e 100644 --- a/beacon/engine/gen_blockparams.go +++ b/beacon/engine/gen_blockparams.go @@ -20,12 +20,14 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) { Random common.Hash `json:"prevRandao" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` Withdrawals []*types.Withdrawal `json:"withdrawals"` + BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` } var enc PayloadAttributes enc.Timestamp = hexutil.Uint64(p.Timestamp) enc.Random = p.Random enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient enc.Withdrawals = p.Withdrawals + enc.BeaconRoot = p.BeaconRoot return json.Marshal(&enc) } @@ -36,6 +38,7 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error { Random *common.Hash `json:"prevRandao" gencodec:"required"` SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` Withdrawals []*types.Withdrawal `json:"withdrawals"` + BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` } var dec PayloadAttributes if err := json.Unmarshal(input, &dec); err != nil { @@ -56,5 +59,8 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error { if dec.Withdrawals != nil { p.Withdrawals = dec.Withdrawals } + if dec.BeaconRoot != nil { + p.BeaconRoot = dec.BeaconRoot + } return nil } diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go index 336dfc6cc755..6893d64a1626 100644 --- a/beacon/engine/gen_ed.go +++ b/beacon/engine/gen_ed.go @@ -32,6 +32,8 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { 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"` } var enc ExecutableData enc.ParentHash = e.ParentHash @@ -54,6 +56,8 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { } } enc.Withdrawals = e.Withdrawals + enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed) + enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) return json.Marshal(&enc) } @@ -75,6 +79,8 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { 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"` } var dec ExecutableData if err := json.Unmarshal(input, &dec); err != nil { @@ -142,5 +148,11 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { if dec.Withdrawals != nil { e.Withdrawals = dec.Withdrawals } + if dec.BlobGasUsed != nil { + e.BlobGasUsed = (*uint64)(dec.BlobGasUsed) + } + if dec.ExcessBlobGas != nil { + e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) + } return nil } diff --git a/beacon/engine/gen_epe.go b/beacon/engine/gen_epe.go index cc66cef6cd3e..e69f9a5951a2 100644 --- a/beacon/engine/gen_epe.go +++ b/beacon/engine/gen_epe.go @@ -17,10 +17,14 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) { type ExecutionPayloadEnvelope struct { ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` + BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` + Override bool `json:"shouldOverrideBuilder"` } var enc ExecutionPayloadEnvelope enc.ExecutionPayload = e.ExecutionPayload enc.BlockValue = (*hexutil.Big)(e.BlockValue) + enc.BlobsBundle = e.BlobsBundle + enc.Override = e.Override return json.Marshal(&enc) } @@ -29,6 +33,8 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error { type ExecutionPayloadEnvelope struct { ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` BlockValue *hexutil.Big `json:"blockValue" gencodec:"required"` + BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` + Override *bool `json:"shouldOverrideBuilder"` } var dec ExecutionPayloadEnvelope if err := json.Unmarshal(input, &dec); err != nil { @@ -42,5 +48,11 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'blockValue' for ExecutionPayloadEnvelope") } e.BlockValue = (*big.Int)(dec.BlockValue) + if dec.BlobsBundle != nil { + e.BlobsBundle = dec.BlobsBundle + } + if dec.Override != nil { + e.Override = *dec.Override + } return nil } diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 07ebe544b438..67f30d4455aa 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -35,6 +35,7 @@ type PayloadAttributes struct { Random common.Hash `json:"prevRandao" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` Withdrawals []*types.Withdrawal `json:"withdrawals"` + BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` } // JSON type overrides for PayloadAttributes. @@ -61,6 +62,8 @@ type ExecutableData struct { 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"` } // JSON type overrides for executableData. @@ -73,6 +76,8 @@ type executableDataMarshaling struct { ExtraData hexutil.Bytes LogsBloom hexutil.Bytes Transactions []hexutil.Bytes + BlobGasUsed *hexutil.Uint64 + ExcessBlobGas *hexutil.Uint64 } //go:generate go run github.com/fjl/gencodec -type ExecutionPayloadEnvelope -field-override executionPayloadEnvelopeMarshaling -out gen_epe.go @@ -80,6 +85,14 @@ type executableDataMarshaling struct { type ExecutionPayloadEnvelope struct { ExecutionPayload *ExecutableData `json:"executionPayload" gencodec:"required"` BlockValue *big.Int `json:"blockValue" gencodec:"required"` + BlobsBundle *BlobsBundleV1 `json:"blobsBundle"` + Override bool `json:"shouldOverrideBuilder"` +} + +type BlobsBundleV1 struct { + Commitments []hexutil.Bytes `json:"commitments"` + Proofs []hexutil.Bytes `json:"proofs"` + Blobs []hexutil.Bytes `json:"blobs"` } // JSON type overrides for ExecutionPayloadEnvelope. @@ -152,14 +165,15 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { // ExecutableDataToBlock constructs a block from executable data. // It verifies that the following fields: // -// len(extraData) <= 32 -// uncleHash = emptyUncleHash -// difficulty = 0 +// len(extraData) <= 32 +// uncleHash = emptyUncleHash +// difficulty = 0 +// if versionedHashes != nil, versionedHashes match to blob transactions // // and that the blockhash of the constructed block matches the parameters. Nil // Withdrawals value will propagate through the returned block. Empty // Withdrawals value must be passed via non-nil, length 0 value in params. -func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) { +func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (*types.Block, error) { txs, err := decodeTransactions(params.Transactions) if err != nil { return nil, err @@ -174,6 +188,18 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) { if params.BaseFeePerGas != nil && (params.BaseFeePerGas.Sign() == -1 || params.BaseFeePerGas.BitLen() > 256) { return nil, fmt.Errorf("invalid baseFeePerGas: %v", params.BaseFeePerGas) } + var blobHashes []common.Hash + for _, tx := range txs { + blobHashes = append(blobHashes, tx.BlobHashes()...) + } + if len(blobHashes) != len(versionedHashes) { + return nil, fmt.Errorf("invalid number of versionedHashes: %v blobHashes: %v", versionedHashes, blobHashes) + } + for i := 0; i < len(blobHashes); i++ { + if blobHashes[i] != versionedHashes[i] { + return nil, fmt.Errorf("invalid versionedHash at %v: %v blobHashes: %v", i, versionedHashes, blobHashes) + } + } // Only set withdrawalsRoot if it is non-nil. This allows CLs to use // ExecutableData before withdrawals are enabled by marshaling // Withdrawals as the json null value. @@ -183,22 +209,25 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) { withdrawalsRoot = &h } header := &types.Header{ - ParentHash: params.ParentHash, - UncleHash: types.EmptyUncleHash, - Coinbase: params.FeeRecipient, - Root: params.StateRoot, - TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), - ReceiptHash: params.ReceiptsRoot, - Bloom: types.BytesToBloom(params.LogsBloom), - Difficulty: common.Big0, - Number: new(big.Int).SetUint64(params.Number), - GasLimit: params.GasLimit, - GasUsed: params.GasUsed, - Time: params.Timestamp, - BaseFee: params.BaseFeePerGas, - Extra: params.ExtraData, - MixDigest: params.Random, - WithdrawalsHash: withdrawalsRoot, + ParentHash: params.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: params.FeeRecipient, + Root: params.StateRoot, + TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)), + ReceiptHash: params.ReceiptsRoot, + Bloom: types.BytesToBloom(params.LogsBloom), + Difficulty: common.Big0, + Number: new(big.Int).SetUint64(params.Number), + GasLimit: params.GasLimit, + GasUsed: params.GasUsed, + Time: params.Timestamp, + BaseFee: params.BaseFeePerGas, + Extra: params.ExtraData, + MixDigest: params.Random, + WithdrawalsHash: withdrawalsRoot, + ExcessBlobGas: params.ExcessBlobGas, + BlobGasUsed: params.BlobGasUsed, + ParentBeaconRoot: beaconRoot, } block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(params.Withdrawals) if block.Hash() != params.BlockHash { @@ -209,7 +238,7 @@ func ExecutableDataToBlock(params ExecutableData) (*types.Block, error) { // 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) *ExecutionPayloadEnvelope { +func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar) *ExecutionPayloadEnvelope { data := &ExecutableData{ BlockHash: block.Hash(), ParentHash: block.ParentHash(), @@ -226,8 +255,22 @@ func BlockToExecutableData(block *types.Block, fees *big.Int) *ExecutionPayloadE Random: block.MixDigest(), ExtraData: block.Extra(), Withdrawals: block.Withdrawals(), + BlobGasUsed: block.BlobGasUsed(), + ExcessBlobGas: block.ExcessBlobGas(), + } + bundle := BlobsBundleV1{ + Commitments: make([]hexutil.Bytes, 0), + Blobs: make([]hexutil.Bytes, 0), + Proofs: make([]hexutil.Bytes, 0), + } + for _, sidecar := range sidecars { + for j := range sidecar.Blobs { + bundle.Blobs = append(bundle.Blobs, hexutil.Bytes(sidecar.Blobs[j][:])) + bundle.Commitments = append(bundle.Commitments, hexutil.Bytes(sidecar.Commitments[j][:])) + bundle.Proofs = append(bundle.Proofs, hexutil.Bytes(sidecar.Proofs[j][:])) + } } - return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees} + return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees, BlobsBundle: &bundle, Override: false} } // ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1 diff --git a/build/checksums.txt b/build/checksums.txt index 9b038582a844..17fb6dce2766 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,20 +1,25 @@ # This file contains sha256 checksums of optional build dependencies. -e447b498cde50215c4f7619e5124b0fc4e25fb5d16ea47271c47f278e7aa763a go1.20.3.src.tar.gz -c1e1161d6d859deb576e6cfabeb40e3d042ceb1c6f444f617c3c9d76269c3565 go1.20.3.darwin-amd64.tar.gz -86b0ed0f2b2df50fa8036eea875d1cf2d76cefdacf247c44639a1464b7e36b95 go1.20.3.darwin-arm64.tar.gz -340e80abd047c597fdc0f50a6cc59617f06c297d62f7fc77f4a0164e2da6f7aa go1.20.3.freebsd-386.tar.gz -2169fcd8b6c94c5fbe07c0b470ccfb6001d343f6548ad49f3d9ab78e3b5753c7 go1.20.3.freebsd-amd64.tar.gz -e12384311403f1389d14cc1c1295bfb4e0dd5ab919403b80da429f671a223507 go1.20.3.linux-386.tar.gz -979694c2c25c735755bf26f4f45e19e64e4811d661dd07b8c010f7a8e18adfca go1.20.3.linux-amd64.tar.gz -eb186529f13f901e7a2c4438a05c2cd90d74706aaa0a888469b2a4a617b6ee54 go1.20.3.linux-arm64.tar.gz -b421e90469a83671641f81b6e20df6500f033e9523e89cbe7b7223704dd1035c go1.20.3.linux-armv6l.tar.gz -943c89aa1624ea544a022b31e3d6e16a037200e436370bdd5fd67f3fa60be282 go1.20.3.linux-ppc64le.tar.gz -126cf823a5634ef2544b866db107b9d351d3ea70d9e240b0bdcfb46f4dcae54b go1.20.3.linux-s390x.tar.gz -37e9146e1f9d681cfcaa6fee6c7b890c44c64bc50228c9588f3c4231346d33bd go1.20.3.windows-386.zip -143a2837821c7dbacf7744cbb1a8421c1f48307c6fdfaeffc5f8c2f69e1b7932 go1.20.3.windows-amd64.zip -158cb159e00bc979f473e0f5b5a561613129c5e51067967b72b8e072e5a4db81 go1.20.3.windows-arm64.zip +# https://github.com/ethereum/execution-spec-tests/releases +24bac679f3a2d8240d8e08e7f6a70b70c2dabf673317d924cf1d1887b9fe1f81 fixtures.tar.gz +# https://go.dev/dl/ +bfa36bf75e9a1e9cbbdb9abcf9d1707e479bd3a07880a8ae3564caee5711cb99 go1.21.1.src.tar.gz +809f5b0ef4f7dcdd5f51e9630a5b2e5a1006f22a047126d61560cdc365678a19 go1.21.1.darwin-amd64.tar.gz +ffd40391a1e995855488b008ad9326ff8c2e81803a6e80894401003bae47fcf1 go1.21.1.darwin-arm64.tar.gz +9919a9a4dc82371aba3da5b7c830bcb6249fc1502cd26d959eb340a60e41ee01 go1.21.1.freebsd-386.tar.gz +2571f10f6047e04d87c1f5986a05e5e8f7b511faf98803ef12b66d563845d2a1 go1.21.1.freebsd-amd64.tar.gz +b93850666cdadbd696a986cf7b03111fe99db8c34a9aaa113d7c96d0081e1901 go1.21.1.linux-386.tar.gz +b3075ae1ce5dab85f89bc7905d1632de23ca196bd8336afd93fa97434cfa55ae go1.21.1.linux-amd64.tar.gz +7da1a3936a928fd0b2602ed4f3ef535b8cd1990f1503b8d3e1acc0fa0759c967 go1.21.1.linux-arm64.tar.gz +f3716a43f59ae69999841d6007b42c9e286e8d8ce470656fb3e70d7be2d7ca85 go1.21.1.linux-armv6l.tar.gz +eddf018206f8a5589bda75252b72716d26611efebabdca5d0083ec15e9e41ab7 go1.21.1.linux-ppc64le.tar.gz +a83b3e8eb4dbf76294e773055eb51397510ff4d612a247bad9903560267bba6d go1.21.1.linux-s390x.tar.gz +170256c820f466f29d64876f25f4dfa4029ed9902a0a9095d8bd603aecf4d83b go1.21.1.windows-386.zip +10a4f5b63215d11d1770453733dbcbf024f3f74872f84e28d7ea59f0250316c6 go1.21.1.windows-amd64.zip +41135ce6e0ced4bc1e459cb96bd4090c9dc2062e24179c3f337d855af9b560ef go1.21.1.windows-arm64.zip + +# https://github.com/golangci/golangci-lint/releases fba08acc4027f69f07cef48fbff70b8a7ecdfaa1c2aba9ad3fb31d60d9f5d4bc golangci-lint-1.51.1-darwin-amd64.tar.gz 75b8f0ff3a4e68147156be4161a49d4576f1be37a0b506473f8c482140c1e7f2 golangci-lint-1.51.1-darwin-arm64.tar.gz e06b3459aaed356e1667580be00b05f41f3b2e29685d12cdee571c23e1edb414 golangci-lint-1.51.1-freebsd-386.tar.gz diff --git a/build/ci.go b/build/ci.go index 7933724d7416..400c8bdd6874 100644 --- a/build/ci.go +++ b/build/ci.go @@ -120,15 +120,15 @@ var ( // Distros for which packages are created. // Note: vivid is unsupported because there is no golang-1.6 package for it. // Note: the following Ubuntu releases have been officially deprecated on Launchpad: - // wily, yakkety, zesty, artful, cosmic, disco, eoan, groovy, hirsuite, impish + // wily, yakkety, zesty, artful, cosmic, disco, eoan, groovy, hirsuite, impish, + // kinetic debDistroGoBoots = map[string]string{ - "trusty": "golang-1.11", // EOL: 04/2024 - "xenial": "golang-go", // EOL: 04/2026 - "bionic": "golang-go", // EOL: 04/2028 - "focal": "golang-go", // EOL: 04/2030 - "jammy": "golang-go", // EOL: 04/2032 - "kinetic": "golang-go", // EOL: 07/2023 - "lunar": "golang-go", // EOL: 01/2024 + "trusty": "golang-1.11", // EOL: 04/2024 + "xenial": "golang-go", // EOL: 04/2026 + "bionic": "golang-go", // EOL: 04/2028 + "focal": "golang-go", // EOL: 04/2030 + "jammy": "golang-go", // EOL: 04/2032 + "lunar": "golang-go", // EOL: 01/2024 } debGoBootPaths = map[string]string{ @@ -139,7 +139,7 @@ var ( // This is the version of Go that will be downloaded by // // go run ci.go install -dlgo - dlgoVersion = "1.20.3" + dlgoVersion = "1.21.1" // This is the version of Go that will be used to bootstrap the PPA builder. // @@ -148,6 +148,13 @@ var ( // we need to switch over to a recursive builder to jumpt across supported // versions. gobootVersion = "1.19.6" + + // This is the version of execution-spec-tests that we are using. + // When updating, you must also update build/checksums.txt. + executionSpecTestsVersion = "1.0.2" + + // This is where the tests should be unpacked. + executionSpecTestsDir = "tests/spec-tests" ) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) @@ -200,6 +207,7 @@ func doInstall(cmdline []string) { staticlink = flag.Bool("static", false, "Create statically-linked executable") ) flag.CommandLine.Parse(cmdline) + env := build.Env() // Configure the toolchain. tc := build.GoToolchain{GOARCH: *arch, CC: *cc} @@ -207,12 +215,16 @@ func doInstall(cmdline []string) { csdb := build.MustLoadChecksums("build/checksums.txt") tc.Root = build.DownloadGo(csdb, dlgoVersion) } - // Disable CLI markdown doc generation in release builds and enable linking - // the CKZG library since we can make it portable here. - buildTags := []string{"urfave_cli_no_docs", "ckzg"} + + // Disable CLI markdown doc generation in release builds. + buildTags := []string{"urfave_cli_no_docs"} + + // Enable linking the CKZG library since we can make it work with additional flags. + if env.UbuntuVersion != "trusty" { + buildTags = append(buildTags, "ckzg") + } // Configure the build. - env := build.Env() gobuild := tc.Go("build", buildFlags(env, *staticlink, buildTags)...) // arm64 CI builders are memory-constrained and can't handle concurrent builds, @@ -289,16 +301,26 @@ func doTest(cmdline []string) { coverage = flag.Bool("coverage", false, "Whether to record code coverage") verbose = flag.Bool("v", false, "Whether to log verbosely") race = flag.Bool("race", false, "Execute the race detector") + cachedir = flag.String("cachedir", "./build/cache", "directory for caching downloads") ) flag.CommandLine.Parse(cmdline) + // Get test fixtures. + csdb := build.MustLoadChecksums("build/checksums.txt") + downloadSpecTestFixtures(csdb, *cachedir) + // Configure the toolchain. tc := build.GoToolchain{GOARCH: *arch, CC: *cc} if *dlgo { - csdb := build.MustLoadChecksums("build/checksums.txt") tc.Root = build.DownloadGo(csdb, dlgoVersion) } - gotest := tc.Go("test", "-tags=ckzg") + gotest := tc.Go("test") + + // CI needs a bit more time for the statetests (default 10m). + gotest.Args = append(gotest.Args, "-timeout=20m") + + // Enable CKZG backend in CI. + gotest.Args = append(gotest.Args, "-tags=ckzg") // Test a single package at a time. CI builders are slow // and some tests run into timeouts under load. @@ -321,6 +343,21 @@ func doTest(cmdline []string) { build.MustRun(gotest) } +// downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures. +func downloadSpecTestFixtures(csdb *build.ChecksumDB, cachedir string) string { + ext := ".tar.gz" + base := "fixtures" // TODO(MariusVanDerWijden) rename once the version becomes part of the filename + url := fmt.Sprintf("https://github.com/ethereum/execution-spec-tests/releases/download/v%s/%s%s", executionSpecTestsVersion, base, ext) + archivePath := filepath.Join(cachedir, base+ext) + if err := csdb.DownloadFile(url, archivePath); err != nil { + log.Fatal(err) + } + if err := build.ExtractArchive(archivePath, executionSpecTestsDir); err != nil { + log.Fatal(err) + } + return filepath.Join(cachedir, base) +} + // doLint runs golangci-lint on requested packages. func doLint(cmdline []string) { var ( diff --git a/build/deb/ethereum/deb.rules b/build/deb/ethereum/deb.rules index 475e628b2f5e..daca793e555f 100644 --- a/build/deb/ethereum/deb.rules +++ b/build/deb/ethereum/deb.rules @@ -28,7 +28,7 @@ override_dh_auto_build: mv .mod $(GOPATH)/pkg/mod # A fresh Go was built, all dependency downloads faked, hope build works now - ../.go/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}} + ../.go/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}} -ubuntu {{.Distro}} override_dh_auto_test: diff --git a/build/update-license.go b/build/update-license.go index f61536470a19..52a54bf66ab0 100644 --- a/build/update-license.go +++ b/build/update-license.go @@ -46,12 +46,13 @@ import ( "path/filepath" "regexp" "runtime" - "sort" "strconv" "strings" "sync" "text/template" "time" + + "golang.org/x/exp/slices" ) var ( @@ -152,13 +153,6 @@ func (i info) gpl() bool { return false } -// authors implements the sort.Interface for strings in case-insensitive mode. -type authors []string - -func (as authors) Len() int { return len(as) } -func (as authors) Less(i, j int) bool { return strings.ToLower(as[i]) < strings.ToLower(as[j]) } -func (as authors) Swap(i, j int) { as[i], as[j] = as[j], as[i] } - func main() { var ( files = getFiles() @@ -299,7 +293,9 @@ func writeAuthors(files []string) { } } // Write sorted list of authors back to the file. - sort.Sort(authors(list)) + slices.SortFunc(list, func(a, b string) bool { + return strings.ToLower(a) < strings.ToLower(b) + }) content := new(bytes.Buffer) content.WriteString(authorsFileHeader) for _, a := range list { diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index 748113aa4841..5c1635de39a1 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -23,6 +23,7 @@ import ( "fmt" "net" "os" + "time" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/crypto" @@ -107,21 +108,20 @@ func main() { if err != nil { utils.Fatalf("-ListenUDP: %v", err) } + defer conn.Close() - realaddr := conn.LocalAddr().(*net.UDPAddr) - if natm != nil { - if !realaddr.IP.IsLoopback() { - go nat.Map(natm, nil, "udp", realaddr.Port, realaddr.Port, "ethereum discovery") - } - if ext, err := natm.ExternalIP(); err == nil { - realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port} + db, _ := enode.OpenDB("") + ln := enode.NewLocalNode(db, nodeKey) + + listenerAddr := conn.LocalAddr().(*net.UDPAddr) + if natm != nil && !listenerAddr.IP.IsLoopback() { + natAddr := doPortMapping(natm, ln, listenerAddr) + if natAddr != nil { + listenerAddr = natAddr } } - printNotice(&nodeKey.PublicKey, *realaddr) - - db, _ := enode.OpenDB("") - ln := enode.NewLocalNode(db, nodeKey) + printNotice(&nodeKey.PublicKey, *listenerAddr) cfg := discover.Config{ PrivateKey: nodeKey, NetRestrict: restrictList, @@ -148,3 +148,61 @@ func printNotice(nodeKey *ecdsa.PublicKey, addr net.UDPAddr) { fmt.Println("Note: you're using cmd/bootnode, a developer tool.") fmt.Println("We recommend using a regular node as bootstrap node for production deployments.") } + +func doPortMapping(natm nat.Interface, ln *enode.LocalNode, addr *net.UDPAddr) *net.UDPAddr { + const ( + protocol = "udp" + name = "ethereum discovery" + ) + newLogger := func(external int, internal int) log.Logger { + return log.New("proto", protocol, "extport", external, "intport", internal, "interface", natm) + } + + var ( + intport = addr.Port + extaddr = &net.UDPAddr{IP: addr.IP, Port: addr.Port} + mapTimeout = nat.DefaultMapTimeout + log = newLogger(addr.Port, intport) + ) + addMapping := func() { + // Get the external address. + var err error + extaddr.IP, err = natm.ExternalIP() + if err != nil { + log.Debug("Couldn't get external IP", "err", err) + return + } + // Create the mapping. + p, err := natm.AddMapping(protocol, extaddr.Port, intport, name, mapTimeout) + if err != nil { + log.Debug("Couldn't add port mapping", "err", err) + return + } + if p != uint16(extaddr.Port) { + extaddr.Port = int(p) + log = newLogger(extaddr.Port, intport) + log.Info("NAT mapped alternative port") + } else { + log.Info("NAT mapped port") + } + // Update IP/port information of the local node. + ln.SetStaticIP(extaddr.IP) + ln.SetFallbackUDP(extaddr.Port) + } + + // Perform mapping once, synchronously. + log.Info("Attempting port mapping") + addMapping() + + // Refresh the mapping periodically. + go func() { + refresh := time.NewTimer(mapTimeout) + defer refresh.Stop() + for range refresh.C { + addMapping() + refresh.Reset(mapTimeout) + } + }() + + return extaddr +} diff --git a/cmd/clef/README.md b/cmd/clef/README.md index 3891e554029a..85c9c706069b 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -29,7 +29,7 @@ GLOBAL OPTIONS: --loglevel value log level to emit to the screen (default: 4) --keystore value Directory for the keystore (default: "$HOME/.ethereum/keystore") --configdir value Directory for Clef configuration (default: "$HOME/.clef") - --chainid value Chain id to use for signing (1=mainnet, 4=Rinkeby, 5=Goerli) (default: 1) + --chainid value Chain id to use for signing (1=mainnet, 5=Goerli) (default: 1) --lightkdf Reduce key-derivation RAM & CPU usage at some expense of KDF strength --nousb Disables monitoring for and managing USB hardware wallets --pcscdpath value Path to the smartcard daemon (pcscd) socket file (default: "/run/pcscd/pcscd.comm") diff --git a/cmd/clef/main.go b/cmd/clef/main.go index 35f9f50f5532..a8b0bf816a38 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -27,6 +27,7 @@ import ( "fmt" "io" "math/big" + "net" "os" "os/signal" "path/filepath" @@ -99,7 +100,7 @@ var ( chainIdFlag = &cli.Int64Flag{ Name: "chainid", Value: params.MainnetChainConfig.ChainID.Int64(), - Usage: "Chain id to use for signing (1=mainnet, 4=Rinkeby, 5=Goerli)", + Usage: "Chain id to use for signing (1=mainnet, 5=Goerli)", } rpcPortFlag = &cli.IntFlag{ Name: "http.port", @@ -732,6 +733,7 @@ func signer(c *cli.Context) error { cors := utils.SplitAndTrim(c.String(utils.HTTPCORSDomainFlag.Name)) srv := rpc.NewServer() + srv.SetBatchLimits(node.DefaultConfig.BatchRequestLimit, node.DefaultConfig.BatchResponseMaxSize) err := node.RegisterApis(rpcAPI, []string{"account"}, srv) if err != nil { utils.Fatalf("Could not register API: %w", err) @@ -742,7 +744,7 @@ func signer(c *cli.Context) error { port := c.Int(rpcPortFlag.Name) // start http server - httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.HTTPListenAddrFlag.Name), port) + httpEndpoint := net.JoinHostPort(c.String(utils.HTTPListenAddrFlag.Name), fmt.Sprintf("%d", port)) httpServer, addr, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler) if err != nil { utils.Fatalf("Could not start RPC api: %v", err) diff --git a/cmd/devp2p/README.md b/cmd/devp2p/README.md index 0a61304c2be4..5ca7b497a26f 100644 --- a/cmd/devp2p/README.md +++ b/cmd/devp2p/README.md @@ -44,7 +44,7 @@ set to standard output. The following filters are supported: - `-limit ` limits the output set to N entries, taking the top N nodes by score - `-ip ` filters nodes by IP subnet - `-min-age ` filters nodes by 'first seen' time -- `-eth-network ` filters nodes by "eth" ENR entry +- `-eth-network ` filters nodes by "eth" ENR entry - `-les-server` filters nodes by LES server support - `-snap` filters nodes by snap protocol support diff --git a/cmd/devp2p/crawl.go b/cmd/devp2p/crawl.go index 1b964164d882..4288a5feb897 100644 --- a/cmd/devp2p/crawl.go +++ b/cmd/devp2p/crawl.go @@ -17,6 +17,7 @@ package main import ( + "errors" "sync" "sync/atomic" "time" @@ -51,7 +52,14 @@ type resolver interface { RequestENR(*enode.Node) (*enode.Node, error) } -func newCrawler(input nodeSet, disc resolver, iters ...enode.Iterator) *crawler { +func newCrawler(input nodeSet, bootnodes []*enode.Node, disc resolver, iters ...enode.Iterator) (*crawler, error) { + if len(input) == 0 { + input.add(bootnodes...) + } + if len(input) == 0 { + return nil, errors.New("no input nodes to start crawling") + } + c := &crawler{ input: input, output: make(nodeSet, len(input)), @@ -67,7 +75,7 @@ func newCrawler(input nodeSet, disc resolver, iters ...enode.Iterator) *crawler for id, n := range input { c.output[id] = n } - return c + return c, nil } func (c *crawler) run(timeout time.Duration, nthreads int) nodeSet { @@ -87,11 +95,11 @@ func (c *crawler) run(timeout time.Duration, nthreads int) nodeSet { go c.runIterator(doneCh, it) } var ( - added uint64 - updated uint64 - skipped uint64 - recent uint64 - removed uint64 + added atomic.Uint64 + updated atomic.Uint64 + skipped atomic.Uint64 + recent atomic.Uint64 + removed atomic.Uint64 wg sync.WaitGroup ) wg.Add(nthreads) @@ -103,15 +111,15 @@ func (c *crawler) run(timeout time.Duration, nthreads int) nodeSet { case n := <-c.ch: switch c.updateNode(n) { case nodeSkipIncompat: - atomic.AddUint64(&skipped, 1) + skipped.Add(1) case nodeSkipRecent: - atomic.AddUint64(&recent, 1) + recent.Add(1) case nodeRemoved: - atomic.AddUint64(&removed, 1) + removed.Add(1) case nodeAdded: - atomic.AddUint64(&added, 1) + added.Add(1) default: - atomic.AddUint64(&updated, 1) + updated.Add(1) } case <-c.closed: return @@ -138,11 +146,11 @@ loop: break loop case <-statusTicker.C: log.Info("Crawling in progress", - "added", atomic.LoadUint64(&added), - "updated", atomic.LoadUint64(&updated), - "removed", atomic.LoadUint64(&removed), - "ignored(recent)", atomic.LoadUint64(&recent), - "ignored(incompatible)", atomic.LoadUint64(&skipped)) + "added", added.Load(), + "updated", updated.Load(), + "removed", removed.Load(), + "ignored(recent)", recent.Load(), + "ignored(incompatible)", skipped.Load()) } } diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go index 31129bce8b5f..37b139dea292 100644 --- a/cmd/devp2p/discv4cmd.go +++ b/cmd/devp2p/discv4cmd.go @@ -143,7 +143,7 @@ var discoveryNodeFlags = []cli.Flag{ func discv4Ping(ctx *cli.Context) error { n := getNodeArg(ctx) - disc := startV4(ctx) + disc, _ := startV4(ctx) defer disc.Close() start := time.Now() @@ -156,7 +156,7 @@ func discv4Ping(ctx *cli.Context) error { func discv4RequestRecord(ctx *cli.Context) error { n := getNodeArg(ctx) - disc := startV4(ctx) + disc, _ := startV4(ctx) defer disc.Close() respN, err := disc.RequestENR(n) @@ -169,7 +169,7 @@ func discv4RequestRecord(ctx *cli.Context) error { func discv4Resolve(ctx *cli.Context) error { n := getNodeArg(ctx) - disc := startV4(ctx) + disc, _ := startV4(ctx) defer disc.Close() fmt.Println(disc.Resolve(n).String()) @@ -196,10 +196,13 @@ func discv4ResolveJSON(ctx *cli.Context) error { nodeargs = append(nodeargs, n) } - // Run the crawler. - disc := startV4(ctx) + disc, config := startV4(ctx) defer disc.Close() - c := newCrawler(inputSet, disc, enode.IterNodes(nodeargs)) + + c, err := newCrawler(inputSet, config.Bootnodes, disc, enode.IterNodes(nodeargs)) + if err != nil { + return err + } c.revalidateInterval = 0 output := c.run(0, 1) writeNodesJSON(nodesFile, output) @@ -211,14 +214,18 @@ func discv4Crawl(ctx *cli.Context) error { return errors.New("need nodes file as argument") } nodesFile := ctx.Args().First() - var inputSet nodeSet + inputSet := make(nodeSet) if common.FileExist(nodesFile) { inputSet = loadNodesJSON(nodesFile) } - disc := startV4(ctx) + disc, config := startV4(ctx) defer disc.Close() - c := newCrawler(inputSet, disc, disc.RandomNodes()) + + c, err := newCrawler(inputSet, config.Bootnodes, disc, disc.RandomNodes()) + if err != nil { + return err + } c.revalidateInterval = 10 * time.Minute output := c.run(ctx.Duration(crawlTimeoutFlag.Name), ctx.Int(crawlParallelismFlag.Name)) writeNodesJSON(nodesFile, output) @@ -238,14 +245,14 @@ func discv4Test(ctx *cli.Context) error { } // startV4 starts an ephemeral discovery V4 node. -func startV4(ctx *cli.Context) *discover.UDPv4 { +func startV4(ctx *cli.Context) (*discover.UDPv4, discover.Config) { ln, config := makeDiscoveryConfig(ctx) socket := listen(ctx, ln) disc, err := discover.ListenV4(socket, ln, config) if err != nil { exit(err) } - return disc + return disc, config } func makeDiscoveryConfig(ctx *cli.Context) (*enode.LocalNode, discover.Config) { @@ -337,7 +344,7 @@ func listen(ctx *cli.Context, ln *enode.LocalNode) *net.UDPConn { } func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) { - s := params.RinkebyBootnodes + s := params.MainnetBootnodes if ctx.IsSet(bootnodesFlag.Name) { input := ctx.String(bootnodesFlag.Name) if input == "" { diff --git a/cmd/devp2p/discv5cmd.go b/cmd/devp2p/discv5cmd.go index c5e226f0d1fa..0dac94526971 100644 --- a/cmd/devp2p/discv5cmd.go +++ b/cmd/devp2p/discv5cmd.go @@ -81,7 +81,7 @@ var ( func discv5Ping(ctx *cli.Context) error { n := getNodeArg(ctx) - disc := startV5(ctx) + disc, _ := startV5(ctx) defer disc.Close() fmt.Println(disc.Ping(n)) @@ -90,7 +90,7 @@ func discv5Ping(ctx *cli.Context) error { func discv5Resolve(ctx *cli.Context) error { n := getNodeArg(ctx) - disc := startV5(ctx) + disc, _ := startV5(ctx) defer disc.Close() fmt.Println(disc.Resolve(n)) @@ -102,14 +102,18 @@ func discv5Crawl(ctx *cli.Context) error { return errors.New("need nodes file as argument") } nodesFile := ctx.Args().First() - var inputSet nodeSet + inputSet := make(nodeSet) if common.FileExist(nodesFile) { inputSet = loadNodesJSON(nodesFile) } - disc := startV5(ctx) + disc, config := startV5(ctx) defer disc.Close() - c := newCrawler(inputSet, disc, disc.RandomNodes()) + + c, err := newCrawler(inputSet, config.Bootnodes, disc, disc.RandomNodes()) + if err != nil { + return err + } c.revalidateInterval = 10 * time.Minute output := c.run(ctx.Duration(crawlTimeoutFlag.Name), ctx.Int(crawlParallelismFlag.Name)) writeNodesJSON(nodesFile, output) @@ -127,7 +131,7 @@ func discv5Test(ctx *cli.Context) error { } func discv5Listen(ctx *cli.Context) error { - disc := startV5(ctx) + disc, _ := startV5(ctx) defer disc.Close() fmt.Println(disc.Self()) @@ -135,12 +139,12 @@ func discv5Listen(ctx *cli.Context) error { } // startV5 starts an ephemeral discovery v5 node. -func startV5(ctx *cli.Context) *discover.UDPv5 { +func startV5(ctx *cli.Context) (*discover.UDPv5, discover.Config) { ln, config := makeDiscoveryConfig(ctx) socket := listen(ctx, ln) disc, err := discover.ListenV5(socket, ln, config) if err != nil { exit(err) } - return disc + return disc, config } diff --git a/cmd/devp2p/dns_route53.go b/cmd/devp2p/dns_route53.go index 6c97fa937ee8..21a32f9414e4 100644 --- a/cmd/devp2p/dns_route53.go +++ b/cmd/devp2p/dns_route53.go @@ -20,7 +20,6 @@ import ( "context" "errors" "fmt" - "sort" "strconv" "strings" "time" @@ -33,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/dnsdisc" "github.com/urfave/cli/v2" + "golang.org/x/exp/slices" ) const ( @@ -288,11 +288,17 @@ func makeDeletionChanges(records map[string]recordSet, keep map[string]string) [ // sortChanges ensures DNS changes are in leaf-added -> root-changed -> leaf-deleted order. func sortChanges(changes []types.Change) { score := map[string]int{"CREATE": 1, "UPSERT": 2, "DELETE": 3} - sort.Slice(changes, func(i, j int) bool { - if changes[i].Action == changes[j].Action { - return *changes[i].ResourceRecordSet.Name < *changes[j].ResourceRecordSet.Name + slices.SortFunc(changes, func(a, b types.Change) int { + if a.Action == b.Action { + return strings.Compare(*a.ResourceRecordSet.Name, *b.ResourceRecordSet.Name) } - return score[string(changes[i].Action)] < score[string(changes[j].Action)] + if score[string(a.Action)] < score[string(b.Action)] { + return -1 + } + if score[string(a.Action)] > score[string(b.Action)] { + return 1 + } + return 0 }) } diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go index 5e3d9fe98daf..938159ec524d 100644 --- a/cmd/devp2p/internal/ethtest/chain.go +++ b/cmd/devp2p/internal/ethtest/chain.go @@ -77,7 +77,7 @@ func (c *Chain) RootAt(height int) common.Hash { // ForkID gets the fork id of the chain. func (c *Chain) ForkID() forkid.ID { - return forkid.NewID(c.chainConfig, c.blocks[0].Hash(), uint64(c.Len()), c.blocks[0].Time()) + return forkid.NewID(c.chainConfig, c.blocks[0], uint64(c.Len()), c.blocks[0].Time()) } // Shorten returns a copy chain of a desired height from the imported diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go index 67221923a684..de6acfdcda67 100644 --- a/cmd/devp2p/internal/ethtest/chain_test.go +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -145,7 +145,7 @@ func TestChain_GetHeaders(t *testing.T) { }{ { req: GetBlockHeaders{ - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{Number: uint64(2)}, Amount: uint64(5), Skip: 1, @@ -162,7 +162,7 @@ func TestChain_GetHeaders(t *testing.T) { }, { req: GetBlockHeaders{ - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{Number: uint64(chain.Len() - 1)}, Amount: uint64(3), Skip: 0, @@ -177,7 +177,7 @@ func TestChain_GetHeaders(t *testing.T) { }, { req: GetBlockHeaders{ - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{Hash: chain.Head().Hash()}, Amount: uint64(1), Skip: 0, diff --git a/cmd/devp2p/internal/ethtest/helpers.go b/cmd/devp2p/internal/ethtest/helpers.go index bc901bdeb013..a0339b88cbe4 100644 --- a/cmd/devp2p/internal/ethtest/helpers.go +++ b/cmd/devp2p/internal/ethtest/helpers.go @@ -62,7 +62,6 @@ func (s *Suite) dial() (*Conn, error) { } // set default p2p capabilities conn.caps = []p2p.Cap{ - {Name: "eth", Version: 66}, {Name: "eth", Version: 67}, {Name: "eth", Version: 68}, } @@ -237,8 +236,8 @@ func (c *Conn) readAndServe(chain *Chain, timeout time.Duration) Message { return errorf("could not get headers for inbound header request: %v", err) } resp := &BlockHeaders{ - RequestId: msg.ReqID(), - BlockHeadersPacket: eth.BlockHeadersPacket(headers), + RequestId: msg.ReqID(), + BlockHeadersRequest: eth.BlockHeadersRequest(headers), } if err := c.Write(resp); err != nil { return errorf("could not write to connection: %v", err) @@ -267,7 +266,7 @@ func (c *Conn) headersRequest(request *GetBlockHeaders, chain *Chain, reqID uint if !ok { return nil, fmt.Errorf("unexpected message received: %s", pretty.Sdump(msg)) } - headers := []*types.Header(resp.BlockHeadersPacket) + headers := []*types.Header(resp.BlockHeadersRequest) return headers, nil } @@ -379,7 +378,7 @@ func (s *Suite) waitForBlockImport(conn *Conn, block *types.Block) error { conn.SetReadDeadline(time.Now().Add(20 * time.Second)) // create request req := &GetBlockHeaders{ - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{Hash: block.Hash()}, Amount: 1, }, @@ -604,8 +603,8 @@ func (s *Suite) hashAnnounce() error { pretty.Sdump(blockHeaderReq)) } err = sendConn.Write(&BlockHeaders{ - RequestId: blockHeaderReq.ReqID(), - BlockHeadersPacket: eth.BlockHeadersPacket{nextBlock.Header()}, + RequestId: blockHeaderReq.ReqID(), + BlockHeadersRequest: eth.BlockHeadersRequest{nextBlock.Header()}, }) if err != nil { return fmt.Errorf("failed to write to connection: %v", err) diff --git a/cmd/devp2p/internal/ethtest/snap.go b/cmd/devp2p/internal/ethtest/snap.go index f947e4bc9bae..ea528e5e2f2e 100644 --- a/cmd/devp2p/internal/ethtest/snap.go +++ b/cmd/devp2p/internal/ethtest/snap.go @@ -27,8 +27,8 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/internal/utesting" - "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" "golang.org/x/crypto/sha3" ) @@ -530,11 +530,11 @@ func (s *Suite) snapGetAccountRange(t *utesting.T, tc *accRangeTest) error { for i, key := range hashes { keys[i] = common.CopyBytes(key[:]) } - nodes := make(light.NodeList, len(proof)) + nodes := make(trienode.ProofList, len(proof)) for i, node := range proof { nodes[i] = node } - proofdb := nodes.NodeSet() + proofdb := nodes.Set() var end []byte if len(keys) > 0 { diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 815353be7265..0b56c8cf4b6f 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -112,7 +112,7 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { } // write request req := &GetBlockHeaders{ - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{Hash: s.chain.blocks[1].Hash()}, Amount: 2, Skip: 1, @@ -150,7 +150,7 @@ func (s *Suite) TestSimultaneousRequests(t *utesting.T) { // create two requests req1 := &GetBlockHeaders{ RequestId: uint64(111), - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{ Hash: s.chain.blocks[1].Hash(), }, @@ -161,7 +161,7 @@ func (s *Suite) TestSimultaneousRequests(t *utesting.T) { } req2 := &GetBlockHeaders{ RequestId: uint64(222), - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{ Hash: s.chain.blocks[1].Hash(), }, @@ -201,10 +201,10 @@ func (s *Suite) TestSimultaneousRequests(t *utesting.T) { if err != nil { t.Fatalf("failed to get expected headers for request 2: %v", err) } - if !headersMatch(expected1, headers1.BlockHeadersPacket) { + if !headersMatch(expected1, headers1.BlockHeadersRequest) { t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected1, headers1) } - if !headersMatch(expected2, headers2.BlockHeadersPacket) { + if !headersMatch(expected2, headers2.BlockHeadersRequest) { t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected2, headers2) } } @@ -224,7 +224,7 @@ func (s *Suite) TestSameRequestID(t *utesting.T) { reqID := uint64(1234) request1 := &GetBlockHeaders{ RequestId: reqID, - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{ Number: 1, }, @@ -233,7 +233,7 @@ func (s *Suite) TestSameRequestID(t *utesting.T) { } request2 := &GetBlockHeaders{ RequestId: reqID, - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{ Number: 33, }, @@ -270,10 +270,10 @@ func (s *Suite) TestSameRequestID(t *utesting.T) { if err != nil { t.Fatalf("failed to get expected block headers: %v", err) } - if !headersMatch(expected1, headers1.BlockHeadersPacket) { + if !headersMatch(expected1, headers1.BlockHeadersRequest) { t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected1, headers1) } - if !headersMatch(expected2, headers2.BlockHeadersPacket) { + if !headersMatch(expected2, headers2.BlockHeadersRequest) { t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected2, headers2) } } @@ -290,7 +290,7 @@ func (s *Suite) TestZeroRequestID(t *utesting.T) { t.Fatalf("peering failed: %v", err) } req := &GetBlockHeaders{ - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{Number: 0}, Amount: 2, }, @@ -322,7 +322,7 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { // create block bodies request req := &GetBlockBodies{ RequestId: uint64(55), - GetBlockBodiesPacket: eth.GetBlockBodiesPacket{ + GetBlockBodiesRequest: eth.GetBlockBodiesRequest{ s.chain.blocks[54].Hash(), s.chain.blocks[75].Hash(), }, @@ -336,11 +336,11 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { if !ok { t.Fatalf("unexpected: %s", pretty.Sdump(msg)) } - bodies := resp.BlockBodiesPacket + bodies := resp.BlockBodiesResponse t.Logf("received %d block bodies", len(bodies)) - if len(bodies) != len(req.GetBlockBodiesPacket) { + if len(bodies) != len(req.GetBlockBodiesRequest) { t.Fatalf("wrong bodies in response: expected %d bodies, "+ - "got %d", len(req.GetBlockBodiesPacket), len(bodies)) + "got %d", len(req.GetBlockBodiesRequest), len(bodies)) } } @@ -481,8 +481,8 @@ func (s *Suite) TestLargeTxRequest(t *utesting.T) { hashes = append(hashes, hash) } getTxReq := &GetPooledTransactions{ - RequestId: 1234, - GetPooledTransactionsPacket: hashes, + RequestId: 1234, + GetPooledTransactionsRequest: hashes, } if err = conn.Write(getTxReq); err != nil { t.Fatalf("could not write to conn: %v", err) @@ -490,7 +490,7 @@ func (s *Suite) TestLargeTxRequest(t *utesting.T) { // check that all received transactions match those that were sent to node switch msg := conn.waitForResponse(s.chain, timeout, getTxReq.RequestId).(type) { case *PooledTransactions: - for _, gotTx := range msg.PooledTransactionsPacket { + for _, gotTx := range msg.PooledTransactionsResponse { if _, exists := hashMap[gotTx.Hash()]; !exists { t.Fatalf("unexpected tx received: %v", gotTx.Hash()) } @@ -547,8 +547,8 @@ func (s *Suite) TestNewPooledTxs(t *utesting.T) { msg := conn.readAndServe(s.chain, timeout) switch msg := msg.(type) { case *GetPooledTransactions: - if len(msg.GetPooledTransactionsPacket) != len(hashes) { - t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg.GetPooledTransactionsPacket)) + if len(msg.GetPooledTransactionsRequest) != len(hashes) { + t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg.GetPooledTransactionsRequest)) } return diff --git a/cmd/devp2p/internal/ethtest/suite_test.go b/cmd/devp2p/internal/ethtest/suite_test.go index 8a2b132fa3b1..7890c3134811 100644 --- a/cmd/devp2p/internal/ethtest/suite_test.go +++ b/cmd/devp2p/internal/ethtest/suite_test.go @@ -109,19 +109,18 @@ func setupGeth(stack *node.Node) error { } backend, err := eth.New(stack, ðconfig.Config{ - Genesis: &chain.genesis, - NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763 - DatabaseCache: 10, - TrieCleanCache: 10, - TrieCleanCacheJournal: "", - TrieCleanCacheRejournal: 60 * time.Minute, - TrieDirtyCache: 16, - TrieTimeout: 60 * time.Minute, - SnapshotCache: 10, + Genesis: &chain.genesis, + NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763 + DatabaseCache: 10, + TrieCleanCache: 10, + TrieDirtyCache: 16, + TrieTimeout: 60 * time.Minute, + SnapshotCache: 10, }) if err != nil { return err } + backend.SetSynced() _, err = backend.BlockChain().InsertChain(chain.blocks[1:]) return err diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go index afa9a9c8c685..805d7a81b99a 100644 --- a/cmd/devp2p/internal/ethtest/types.go +++ b/cmd/devp2p/internal/ethtest/types.go @@ -99,24 +99,24 @@ func (msg Transactions) Code() int { return 18 } func (msg Transactions) ReqID() uint64 { return 18 } // GetBlockHeaders represents a block header query. -type GetBlockHeaders eth.GetBlockHeadersPacket66 +type GetBlockHeaders eth.GetBlockHeadersPacket func (msg GetBlockHeaders) Code() int { return 19 } func (msg GetBlockHeaders) ReqID() uint64 { return msg.RequestId } -type BlockHeaders eth.BlockHeadersPacket66 +type BlockHeaders eth.BlockHeadersPacket func (msg BlockHeaders) Code() int { return 20 } func (msg BlockHeaders) ReqID() uint64 { return msg.RequestId } // GetBlockBodies represents a GetBlockBodies request -type GetBlockBodies eth.GetBlockBodiesPacket66 +type GetBlockBodies eth.GetBlockBodiesPacket func (msg GetBlockBodies) Code() int { return 21 } func (msg GetBlockBodies) ReqID() uint64 { return msg.RequestId } // BlockBodies is the network packet for block content distribution. -type BlockBodies eth.BlockBodiesPacket66 +type BlockBodies eth.BlockBodiesPacket func (msg BlockBodies) Code() int { return 22 } func (msg BlockBodies) ReqID() uint64 { return msg.RequestId } @@ -128,7 +128,7 @@ func (msg NewBlock) Code() int { return 23 } func (msg NewBlock) ReqID() uint64 { return 0 } // NewPooledTransactionHashes66 is the network packet for the tx hash propagation message. -type NewPooledTransactionHashes66 eth.NewPooledTransactionHashesPacket66 +type NewPooledTransactionHashes66 eth.NewPooledTransactionHashesPacket67 func (msg NewPooledTransactionHashes66) Code() int { return 24 } func (msg NewPooledTransactionHashes66) ReqID() uint64 { return 0 } @@ -139,12 +139,12 @@ type NewPooledTransactionHashes eth.NewPooledTransactionHashesPacket68 func (msg NewPooledTransactionHashes) Code() int { return 24 } func (msg NewPooledTransactionHashes) ReqID() uint64 { return 0 } -type GetPooledTransactions eth.GetPooledTransactionsPacket66 +type GetPooledTransactions eth.GetPooledTransactionsPacket func (msg GetPooledTransactions) Code() int { return 25 } func (msg GetPooledTransactions) ReqID() uint64 { return msg.RequestId } -type PooledTransactions eth.PooledTransactionsPacket66 +type PooledTransactions eth.PooledTransactionsPacket func (msg PooledTransactions) Code() int { return 26 } func (msg PooledTransactions) ReqID() uint64 { return msg.RequestId } @@ -180,25 +180,25 @@ func (c *Conn) Read() Message { case (Status{}).Code(): msg = new(Status) case (GetBlockHeaders{}).Code(): - ethMsg := new(eth.GetBlockHeadersPacket66) + ethMsg := new(eth.GetBlockHeadersPacket) if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { return errorf("could not rlp decode message: %v", err) } return (*GetBlockHeaders)(ethMsg) case (BlockHeaders{}).Code(): - ethMsg := new(eth.BlockHeadersPacket66) + ethMsg := new(eth.BlockHeadersPacket) if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { return errorf("could not rlp decode message: %v", err) } return (*BlockHeaders)(ethMsg) case (GetBlockBodies{}).Code(): - ethMsg := new(eth.GetBlockBodiesPacket66) + ethMsg := new(eth.GetBlockBodiesPacket) if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { return errorf("could not rlp decode message: %v", err) } return (*GetBlockBodies)(ethMsg) case (BlockBodies{}).Code(): - ethMsg := new(eth.BlockBodiesPacket66) + ethMsg := new(eth.BlockBodiesPacket) if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { return errorf("could not rlp decode message: %v", err) } @@ -217,13 +217,13 @@ func (c *Conn) Read() Message { } msg = new(NewPooledTransactionHashes66) case (GetPooledTransactions{}.Code()): - ethMsg := new(eth.GetPooledTransactionsPacket66) + ethMsg := new(eth.GetPooledTransactionsPacket) if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { return errorf("could not rlp decode message: %v", err) } return (*GetPooledTransactions)(ethMsg) case (PooledTransactions{}.Code()): - ethMsg := new(eth.PooledTransactionsPacket66) + ethMsg := new(eth.PooledTransactionsPacket) if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { return errorf("could not rlp decode message: %v", err) } diff --git a/cmd/devp2p/nodeset.go b/cmd/devp2p/nodeset.go index 33c39f4b9e31..7360dc5bcfd9 100644 --- a/cmd/devp2p/nodeset.go +++ b/cmd/devp2p/nodeset.go @@ -21,11 +21,11 @@ import ( "encoding/json" "fmt" "os" - "sort" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/p2p/enode" + "golang.org/x/exp/slices" ) const jsonIndent = " " @@ -77,8 +77,8 @@ func (ns nodeSet) nodes() []*enode.Node { result = append(result, n.N) } // Sort by ID. - sort.Slice(result, func(i, j int) bool { - return bytes.Compare(result[i].ID().Bytes(), result[j].ID().Bytes()) < 0 + slices.SortFunc(result, func(a, b *enode.Node) int { + return bytes.Compare(a.ID().Bytes(), b.ID().Bytes()) }) return result } @@ -103,8 +103,14 @@ func (ns nodeSet) topN(n int) nodeSet { for _, v := range ns { byscore = append(byscore, v) } - sort.Slice(byscore, func(i, j int) bool { - return byscore[i].Score >= byscore[j].Score + slices.SortFunc(byscore, func(a, b nodeJSON) int { + if a.Score > b.Score { + return -1 + } + if a.Score < b.Score { + return 1 + } + return 0 }) result := make(nodeSet, n) for _, v := range byscore[:n] { diff --git a/cmd/devp2p/nodesetcmd.go b/cmd/devp2p/nodesetcmd.go index f7c258637fae..6fbc185ad8ad 100644 --- a/cmd/devp2p/nodesetcmd.go +++ b/cmd/devp2p/nodesetcmd.go @@ -25,6 +25,7 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/params" @@ -228,13 +229,13 @@ func ethFilter(args []string) (nodeFilter, error) { var filter forkid.Filter switch args[0] { case "mainnet": - filter = forkid.NewStaticFilter(params.MainnetChainConfig, params.MainnetGenesisHash) - case "rinkeby": - filter = forkid.NewStaticFilter(params.RinkebyChainConfig, params.RinkebyGenesisHash) + filter = forkid.NewStaticFilter(params.MainnetChainConfig, core.DefaultGenesisBlock().ToBlock()) case "goerli": - filter = forkid.NewStaticFilter(params.GoerliChainConfig, params.GoerliGenesisHash) + filter = forkid.NewStaticFilter(params.GoerliChainConfig, core.DefaultGoerliGenesisBlock().ToBlock()) case "sepolia": - filter = forkid.NewStaticFilter(params.SepoliaChainConfig, params.SepoliaGenesisHash) + filter = forkid.NewStaticFilter(params.SepoliaChainConfig, core.DefaultSepoliaGenesisBlock().ToBlock()) + case "holesky": + filter = forkid.NewStaticFilter(params.HoleskyChainConfig, core.DefaultHoleskyGenesisBlock().ToBlock()) default: return nil, fmt.Errorf("unknown network %q", args[0]) } diff --git a/cmd/evm/README.md b/cmd/evm/README.md index 296215861218..e6c6fe06ad09 100644 --- a/cmd/evm/README.md +++ b/cmd/evm/README.md @@ -342,7 +342,7 @@ To make `t8n` apply these, the following inputs are required: - For ethash, it is `5000000000000000000` `wei`, - If this is not defined, mining rewards are not applied, - A value of `0` is valid, and causes accounts to be 'touched'. -- For each ommer, the tool needs to be given an `addres\` and a `delta`. This +- For each ommer, the tool needs to be given an `address\` and a `delta`. This is done via the `ommers` field in `env`. Note: the tool does not verify that e.g. the normal uncle rules apply, diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go index ffd76b8fb0c9..6612680dc40b 100644 --- a/cmd/evm/blockrunner.go +++ b/cmd/evm/blockrunner.go @@ -22,7 +22,9 @@ import ( "fmt" "os" - "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/tests" "github.com/urfave/cli/v2" ) @@ -38,11 +40,17 @@ func blockTestCmd(ctx *cli.Context) error { if len(ctx.Args().First()) == 0 { return errors.New("path-to-test argument required") } - // Configure the go-ethereum logger - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name))) - log.Root().SetHandler(glogger) + var tracer vm.EVMLogger + // Configure the EVM logger + if ctx.Bool(MachineFlag.Name) { + tracer = logger.NewJSONLogger(&logger.Config{ + EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), + DisableStack: ctx.Bool(DisableStackFlag.Name), + DisableStorage: ctx.Bool(DisableStorageFlag.Name), + EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), + }, os.Stderr) + } // Load the test content from the input file src, err := os.ReadFile(ctx.Args().First()) if err != nil { @@ -53,7 +61,7 @@ func blockTestCmd(ctx *cli.Context) error { return err } for i, test := range tests { - if err := test.Run(false); err != nil { + if err := test.Run(false, rawdb.HashScheme, tracer); err != nil { return fmt.Errorf("test %v: %w", i, err) } } diff --git a/cmd/evm/internal/t8ntool/block.go b/cmd/evm/internal/t8ntool/block.go index 64804a17ff64..5c0e28e284c0 100644 --- a/cmd/evm/internal/t8ntool/block.go +++ b/cmd/evm/internal/t8ntool/block.go @@ -37,33 +37,38 @@ import ( //go:generate go run github.com/fjl/gencodec -type header -field-override headerMarshaling -out gen_header.go type header struct { - ParentHash common.Hash `json:"parentHash"` - OmmerHash *common.Hash `json:"sha3Uncles"` - Coinbase *common.Address `json:"miner"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot"` - ReceiptHash *common.Hash `json:"receiptsRoot"` - Bloom types.Bloom `json:"logsBloom"` - Difficulty *big.Int `json:"difficulty"` - Number *big.Int `json:"number" gencodec:"required"` - GasLimit uint64 `json:"gasLimit" gencodec:"required"` - GasUsed uint64 `json:"gasUsed"` - Time uint64 `json:"timestamp" gencodec:"required"` - Extra []byte `json:"extraData"` - MixDigest common.Hash `json:"mixHash"` - Nonce *types.BlockNonce `json:"nonce"` - BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` - WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + ParentHash common.Hash `json:"parentHash"` + OmmerHash *common.Hash `json:"sha3Uncles"` + Coinbase *common.Address `json:"miner"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash *common.Hash `json:"transactionsRoot"` + ReceiptHash *common.Hash `json:"receiptsRoot"` + Bloom types.Bloom `json:"logsBloom"` + Difficulty *big.Int `json:"difficulty"` + Number *big.Int `json:"number" gencodec:"required"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + GasUsed uint64 `json:"gasUsed"` + Time uint64 `json:"timestamp" gencodec:"required"` + Extra []byte `json:"extraData"` + MixDigest common.Hash `json:"mixHash"` + Nonce *types.BlockNonce `json:"nonce"` + BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"` + ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"` + ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` } type headerMarshaling struct { - Difficulty *math.HexOrDecimal256 - Number *math.HexOrDecimal256 - GasLimit math.HexOrDecimal64 - GasUsed math.HexOrDecimal64 - Time math.HexOrDecimal64 - Extra hexutil.Bytes - BaseFee *math.HexOrDecimal256 + Difficulty *math.HexOrDecimal256 + Number *math.HexOrDecimal256 + GasLimit math.HexOrDecimal64 + GasUsed math.HexOrDecimal64 + Time math.HexOrDecimal64 + Extra hexutil.Bytes + BaseFee *math.HexOrDecimal256 + BlobGasUsed *math.HexOrDecimal64 + ExcessBlobGas *math.HexOrDecimal64 } type bbInput struct { @@ -113,22 +118,25 @@ func (c *cliqueInput) UnmarshalJSON(input []byte) error { // ToBlock converts i into a *types.Block func (i *bbInput) ToBlock() *types.Block { header := &types.Header{ - ParentHash: i.Header.ParentHash, - UncleHash: types.EmptyUncleHash, - Coinbase: common.Address{}, - Root: i.Header.Root, - TxHash: types.EmptyTxsHash, - ReceiptHash: types.EmptyReceiptsHash, - Bloom: i.Header.Bloom, - Difficulty: common.Big0, - Number: i.Header.Number, - GasLimit: i.Header.GasLimit, - GasUsed: i.Header.GasUsed, - Time: i.Header.Time, - Extra: i.Header.Extra, - MixDigest: i.Header.MixDigest, - BaseFee: i.Header.BaseFee, - WithdrawalsHash: i.Header.WithdrawalsHash, + ParentHash: i.Header.ParentHash, + UncleHash: types.EmptyUncleHash, + Coinbase: common.Address{}, + Root: i.Header.Root, + TxHash: types.EmptyTxsHash, + ReceiptHash: types.EmptyReceiptsHash, + Bloom: i.Header.Bloom, + Difficulty: common.Big0, + Number: i.Header.Number, + GasLimit: i.Header.GasLimit, + GasUsed: i.Header.GasUsed, + Time: i.Header.Time, + Extra: i.Header.Extra, + MixDigest: i.Header.MixDigest, + BaseFee: i.Header.BaseFee, + WithdrawalsHash: i.Header.WithdrawalsHash, + BlobGasUsed: i.Header.BlobGasUsed, + ExcessBlobGas: i.Header.ExcessBlobGas, + ParentBeaconRoot: i.Header.ParentBeaconBlockRoot, } // Fill optional values. @@ -150,7 +158,7 @@ func (i *bbInput) ToBlock() *types.Block { if i.Header.Nonce != nil { header.Nonce = *i.Header.Nonce } - if header.Difficulty != nil { + if i.Header.Difficulty != nil { header.Difficulty = i.Header.Difficulty } return types.NewBlockWithHeader(header).WithBody(i.Txs, i.Ommers).WithWithdrawals(i.Withdrawals) @@ -306,7 +314,7 @@ func readInput(ctx *cli.Context) (*bbInput, error) { return inputData, nil } -// dispatchOutput writes the output data to either stderr or stdout, or to the specified +// dispatchBlock writes the output data to either stderr or stdout, or to the specified // files func dispatchBlock(ctx *cli.Context, baseDir string, block *types.Block) error { raw, _ := rlp.EncodeToBytes(block) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 0113620b0e8b..4578a2f6cc96 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -19,12 +19,12 @@ package t8ntool import ( "fmt" "math/big" - "os" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" @@ -47,17 +47,19 @@ type Prestate struct { // ExecutionResult contains the execution status after running a state test, any // error that might have occurred and a dump of the final state if requested. type ExecutionResult struct { - StateRoot common.Hash `json:"stateRoot"` - TxRoot common.Hash `json:"txRoot"` - ReceiptRoot common.Hash `json:"receiptsRoot"` - LogsHash common.Hash `json:"logsHash"` - Bloom types.Bloom `json:"logsBloom" gencodec:"required"` - Receipts types.Receipts `json:"receipts"` - Rejected []*rejectedTx `json:"rejected,omitempty"` - Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` - GasUsed math.HexOrDecimal64 `json:"gasUsed"` - BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` - WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"` + StateRoot common.Hash `json:"stateRoot"` + TxRoot common.Hash `json:"txRoot"` + ReceiptRoot common.Hash `json:"receiptsRoot"` + LogsHash common.Hash `json:"logsHash"` + Bloom types.Bloom `json:"logsBloom" gencodec:"required"` + Receipts types.Receipts `json:"receipts"` + Rejected []*rejectedTx `json:"rejected,omitempty"` + Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` + WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"` + CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"` + CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"` } type ommer struct { @@ -67,37 +69,44 @@ type ommer struct { //go:generate go run github.com/fjl/gencodec -type stEnv -field-override stEnvMarshaling -out gen_stenv.go type stEnv struct { - Coinbase common.Address `json:"currentCoinbase" gencodec:"required"` - Difficulty *big.Int `json:"currentDifficulty"` - Random *big.Int `json:"currentRandom"` - ParentDifficulty *big.Int `json:"parentDifficulty"` - ParentBaseFee *big.Int `json:"parentBaseFee,omitempty"` - ParentGasUsed uint64 `json:"parentGasUsed,omitempty"` - ParentGasLimit uint64 `json:"parentGasLimit,omitempty"` - GasLimit uint64 `json:"currentGasLimit" gencodec:"required"` - Number uint64 `json:"currentNumber" gencodec:"required"` - Timestamp uint64 `json:"currentTimestamp" gencodec:"required"` - ParentTimestamp uint64 `json:"parentTimestamp,omitempty"` - BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` - Ommers []ommer `json:"ommers,omitempty"` - Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` - BaseFee *big.Int `json:"currentBaseFee,omitempty"` - ParentUncleHash common.Hash `json:"parentUncleHash"` + Coinbase common.Address `json:"currentCoinbase" gencodec:"required"` + Difficulty *big.Int `json:"currentDifficulty"` + Random *big.Int `json:"currentRandom"` + ParentDifficulty *big.Int `json:"parentDifficulty"` + ParentBaseFee *big.Int `json:"parentBaseFee,omitempty"` + ParentGasUsed uint64 `json:"parentGasUsed,omitempty"` + ParentGasLimit uint64 `json:"parentGasLimit,omitempty"` + GasLimit uint64 `json:"currentGasLimit" gencodec:"required"` + Number uint64 `json:"currentNumber" gencodec:"required"` + Timestamp uint64 `json:"currentTimestamp" gencodec:"required"` + ParentTimestamp uint64 `json:"parentTimestamp,omitempty"` + BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` + Ommers []ommer `json:"ommers,omitempty"` + Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` + BaseFee *big.Int `json:"currentBaseFee,omitempty"` + ParentUncleHash common.Hash `json:"parentUncleHash"` + ExcessBlobGas *uint64 `json:"currentExcessBlobGas,omitempty"` + ParentExcessBlobGas *uint64 `json:"parentExcessBlobGas,omitempty"` + ParentBlobGasUsed *uint64 `json:"parentBlobGasUsed,omitempty"` + ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` } type stEnvMarshaling struct { - Coinbase common.UnprefixedAddress - Difficulty *math.HexOrDecimal256 - Random *math.HexOrDecimal256 - ParentDifficulty *math.HexOrDecimal256 - ParentBaseFee *math.HexOrDecimal256 - ParentGasUsed math.HexOrDecimal64 - ParentGasLimit math.HexOrDecimal64 - GasLimit math.HexOrDecimal64 - Number math.HexOrDecimal64 - Timestamp math.HexOrDecimal64 - ParentTimestamp math.HexOrDecimal64 - BaseFee *math.HexOrDecimal256 + Coinbase common.UnprefixedAddress + Difficulty *math.HexOrDecimal256 + Random *math.HexOrDecimal256 + ParentDifficulty *math.HexOrDecimal256 + ParentBaseFee *math.HexOrDecimal256 + ParentGasUsed math.HexOrDecimal64 + ParentGasLimit math.HexOrDecimal64 + GasLimit math.HexOrDecimal64 + Number math.HexOrDecimal64 + Timestamp math.HexOrDecimal64 + ParentTimestamp math.HexOrDecimal64 + BaseFee *math.HexOrDecimal256 + ExcessBlobGas *math.HexOrDecimal64 + ParentExcessBlobGas *math.HexOrDecimal64 + ParentBlobGasUsed *math.HexOrDecimal64 } type rejectedTx struct { @@ -157,6 +166,21 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, rnd := common.BigToHash(pre.Env.Random) vmContext.Random = &rnd } + // Calculate the BlobBaseFee + var excessBlobGas uint64 + if pre.Env.ExcessBlobGas != nil { + excessBlobGas := *pre.Env.ExcessBlobGas + vmContext.BlobBaseFee = eip4844.CalcBlobFee(excessBlobGas) + } else { + // If it is not explicitly defined, but we have the parent values, we try + // to calculate it ourselves. + parentExcessBlobGas := pre.Env.ParentExcessBlobGas + parentBlobGasUsed := pre.Env.ParentBlobGasUsed + if parentExcessBlobGas != nil && parentBlobGasUsed != nil { + excessBlobGas = eip4844.CalcExcessBlobGas(*parentExcessBlobGas, *parentBlobGasUsed) + vmContext.BlobBaseFee = eip4844.CalcBlobFee(excessBlobGas) + } + } // If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's // done in StateProcessor.Process(block, ...), right before transactions are applied. if chainConfig.DAOForkSupport && @@ -164,8 +188,21 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, chainConfig.DAOForkBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 { misc.ApplyDAOHardFork(statedb) } - + if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil { + evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig) + core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb) + } + var blobGasUsed uint64 for i, tx := range txs { + if tx.Type() == types.BlobTxType && vmContext.BlobBaseFee == nil { + errMsg := "blob tx used but field env.ExcessBlobGas missing" + log.Warn("rejected tx", "index", i, "hash", tx.Hash(), "error", errMsg) + rejectedTxs = append(rejectedTxs, &rejectedTx{i, errMsg}) + continue + } + if tx.Type() == types.BlobTxType { + blobGasUsed += uint64(params.BlobTxBlobGasPerBlob * len(tx.BlobHashes())) + } msg, err := core.TransactionToMessage(tx, signer, pre.Env.BaseFee) if err != nil { log.Warn("rejected tx", "index", i, "hash", tx.Hash(), "error", err) @@ -243,7 +280,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, if miningReward >= 0 { // Add mining reward. The mining reward may be `0`, which only makes a difference in the cases // where - // - the coinbase suicided, or + // - the coinbase self-destructed, or // - there are only 'bad' transactions, which aren't executed. In those cases, // the coinbase gets no txfee, so isn't created, and thus needs to be touched var ( @@ -270,9 +307,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, statedb.AddBalance(w.Address, amount) } // Commit block - root, err := statedb.Commit(chainConfig.IsEIP158(vmContext.BlockNumber)) + root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber)) if err != nil { - fmt.Fprintf(os.Stderr, "Could not commit state: %v", err) return nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err)) } execRs := &ExecutionResult{ @@ -291,6 +327,16 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, h := types.DeriveSha(types.Withdrawals(pre.Env.Withdrawals), trie.NewStackTrie(nil)) execRs.WithdrawalsRoot = &h } + if vmContext.BlobBaseFee != nil { + execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas) + execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed) + } + // Re-create statedb instance with new root upon the updated database + // for accessing latest states. + statedb, err = state.New(root, statedb.Database(), nil) + if err != nil { + return nil, nil, NewError(ErrorEVM, fmt.Errorf("could not reopen state: %v", err)) + } return statedb, execRs, nil } @@ -306,7 +352,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB } } // Commit and re-open to start with a clean state. - root, _ := statedb.Commit(false) + root, _ := statedb.Commit(0, false) statedb, _ = state.New(root, sdb, nil) return statedb } diff --git a/cmd/evm/internal/t8ntool/gen_header.go b/cmd/evm/internal/t8ntool/gen_header.go index 76228394dc1d..a8c8668978ed 100644 --- a/cmd/evm/internal/t8ntool/gen_header.go +++ b/cmd/evm/internal/t8ntool/gen_header.go @@ -18,23 +18,26 @@ var _ = (*headerMarshaling)(nil) // MarshalJSON marshals as JSON. func (h header) MarshalJSON() ([]byte, error) { type header struct { - ParentHash common.Hash `json:"parentHash"` - OmmerHash *common.Hash `json:"sha3Uncles"` - Coinbase *common.Address `json:"miner"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot"` - ReceiptHash *common.Hash `json:"receiptsRoot"` - Bloom types.Bloom `json:"logsBloom"` - Difficulty *math.HexOrDecimal256 `json:"difficulty"` - Number *math.HexOrDecimal256 `json:"number" gencodec:"required"` - GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` - GasUsed math.HexOrDecimal64 `json:"gasUsed"` - Time math.HexOrDecimal64 `json:"timestamp" gencodec:"required"` - Extra hexutil.Bytes `json:"extraData"` - MixDigest common.Hash `json:"mixHash"` - Nonce *types.BlockNonce `json:"nonce"` - BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` - WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + ParentHash common.Hash `json:"parentHash"` + OmmerHash *common.Hash `json:"sha3Uncles"` + Coinbase *common.Address `json:"miner"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash *common.Hash `json:"transactionsRoot"` + ReceiptHash *common.Hash `json:"receiptsRoot"` + Bloom types.Bloom `json:"logsBloom"` + Difficulty *math.HexOrDecimal256 `json:"difficulty"` + Number *math.HexOrDecimal256 `json:"number" gencodec:"required"` + GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + Time math.HexOrDecimal64 `json:"timestamp" gencodec:"required"` + Extra hexutil.Bytes `json:"extraData"` + MixDigest common.Hash `json:"mixHash"` + Nonce *types.BlockNonce `json:"nonce"` + BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"` + ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"` + ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` } var enc header enc.ParentHash = h.ParentHash @@ -54,29 +57,35 @@ func (h header) MarshalJSON() ([]byte, error) { enc.Nonce = h.Nonce enc.BaseFee = (*math.HexOrDecimal256)(h.BaseFee) enc.WithdrawalsHash = h.WithdrawalsHash + enc.BlobGasUsed = (*math.HexOrDecimal64)(h.BlobGasUsed) + enc.ExcessBlobGas = (*math.HexOrDecimal64)(h.ExcessBlobGas) + enc.ParentBeaconBlockRoot = h.ParentBeaconBlockRoot return json.Marshal(&enc) } // UnmarshalJSON unmarshals from JSON. func (h *header) UnmarshalJSON(input []byte) error { type header struct { - ParentHash *common.Hash `json:"parentHash"` - OmmerHash *common.Hash `json:"sha3Uncles"` - Coinbase *common.Address `json:"miner"` - Root *common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot"` - ReceiptHash *common.Hash `json:"receiptsRoot"` - Bloom *types.Bloom `json:"logsBloom"` - Difficulty *math.HexOrDecimal256 `json:"difficulty"` - Number *math.HexOrDecimal256 `json:"number" gencodec:"required"` - GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` - GasUsed *math.HexOrDecimal64 `json:"gasUsed"` - Time *math.HexOrDecimal64 `json:"timestamp" gencodec:"required"` - Extra *hexutil.Bytes `json:"extraData"` - MixDigest *common.Hash `json:"mixHash"` - Nonce *types.BlockNonce `json:"nonce"` - BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` - WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + ParentHash *common.Hash `json:"parentHash"` + OmmerHash *common.Hash `json:"sha3Uncles"` + Coinbase *common.Address `json:"miner"` + Root *common.Hash `json:"stateRoot" gencodec:"required"` + TxHash *common.Hash `json:"transactionsRoot"` + ReceiptHash *common.Hash `json:"receiptsRoot"` + Bloom *types.Bloom `json:"logsBloom"` + Difficulty *math.HexOrDecimal256 `json:"difficulty"` + Number *math.HexOrDecimal256 `json:"number" gencodec:"required"` + GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` + GasUsed *math.HexOrDecimal64 `json:"gasUsed"` + Time *math.HexOrDecimal64 `json:"timestamp" gencodec:"required"` + Extra *hexutil.Bytes `json:"extraData"` + MixDigest *common.Hash `json:"mixHash"` + Nonce *types.BlockNonce `json:"nonce"` + BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"` + ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"` + ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` } var dec header if err := json.Unmarshal(input, &dec); err != nil { @@ -137,5 +146,14 @@ func (h *header) UnmarshalJSON(input []byte) error { if dec.WithdrawalsHash != nil { h.WithdrawalsHash = dec.WithdrawalsHash } + if dec.BlobGasUsed != nil { + h.BlobGasUsed = (*uint64)(dec.BlobGasUsed) + } + if dec.ExcessBlobGas != nil { + h.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) + } + if dec.ParentBeaconBlockRoot != nil { + h.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot + } return nil } diff --git a/cmd/evm/internal/t8ntool/gen_stenv.go b/cmd/evm/internal/t8ntool/gen_stenv.go index c2cc3a2c8a38..d47db4a8765e 100644 --- a/cmd/evm/internal/t8ntool/gen_stenv.go +++ b/cmd/evm/internal/t8ntool/gen_stenv.go @@ -17,22 +17,26 @@ var _ = (*stEnvMarshaling)(nil) // MarshalJSON marshals as JSON. func (s stEnv) MarshalJSON() ([]byte, error) { type stEnv struct { - Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` - Difficulty *math.HexOrDecimal256 `json:"currentDifficulty"` - Random *math.HexOrDecimal256 `json:"currentRandom"` - ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"` - ParentBaseFee *math.HexOrDecimal256 `json:"parentBaseFee,omitempty"` - ParentGasUsed math.HexOrDecimal64 `json:"parentGasUsed,omitempty"` - ParentGasLimit math.HexOrDecimal64 `json:"parentGasLimit,omitempty"` - GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` - Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` - Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` - ParentTimestamp math.HexOrDecimal64 `json:"parentTimestamp,omitempty"` - BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` - Ommers []ommer `json:"ommers,omitempty"` - Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` - BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` - ParentUncleHash common.Hash `json:"parentUncleHash"` + Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"currentDifficulty"` + Random *math.HexOrDecimal256 `json:"currentRandom"` + ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"` + ParentBaseFee *math.HexOrDecimal256 `json:"parentBaseFee,omitempty"` + ParentGasUsed math.HexOrDecimal64 `json:"parentGasUsed,omitempty"` + ParentGasLimit math.HexOrDecimal64 `json:"parentGasLimit,omitempty"` + GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` + Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` + Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` + ParentTimestamp math.HexOrDecimal64 `json:"parentTimestamp,omitempty"` + BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` + Ommers []ommer `json:"ommers,omitempty"` + Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` + BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` + ParentUncleHash common.Hash `json:"parentUncleHash"` + ExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"` + ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"` + ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"` + ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` } var enc stEnv enc.Coinbase = common.UnprefixedAddress(s.Coinbase) @@ -51,28 +55,36 @@ func (s stEnv) MarshalJSON() ([]byte, error) { enc.Withdrawals = s.Withdrawals enc.BaseFee = (*math.HexOrDecimal256)(s.BaseFee) enc.ParentUncleHash = s.ParentUncleHash + enc.ExcessBlobGas = (*math.HexOrDecimal64)(s.ExcessBlobGas) + enc.ParentExcessBlobGas = (*math.HexOrDecimal64)(s.ParentExcessBlobGas) + enc.ParentBlobGasUsed = (*math.HexOrDecimal64)(s.ParentBlobGasUsed) + enc.ParentBeaconBlockRoot = s.ParentBeaconBlockRoot return json.Marshal(&enc) } // UnmarshalJSON unmarshals from JSON. func (s *stEnv) UnmarshalJSON(input []byte) error { type stEnv struct { - Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` - Difficulty *math.HexOrDecimal256 `json:"currentDifficulty"` - Random *math.HexOrDecimal256 `json:"currentRandom"` - ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"` - ParentBaseFee *math.HexOrDecimal256 `json:"parentBaseFee,omitempty"` - ParentGasUsed *math.HexOrDecimal64 `json:"parentGasUsed,omitempty"` - ParentGasLimit *math.HexOrDecimal64 `json:"parentGasLimit,omitempty"` - GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` - Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` - Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` - ParentTimestamp *math.HexOrDecimal64 `json:"parentTimestamp,omitempty"` - BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` - Ommers []ommer `json:"ommers,omitempty"` - Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` - BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` - ParentUncleHash *common.Hash `json:"parentUncleHash"` + Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"currentDifficulty"` + Random *math.HexOrDecimal256 `json:"currentRandom"` + ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"` + ParentBaseFee *math.HexOrDecimal256 `json:"parentBaseFee,omitempty"` + ParentGasUsed *math.HexOrDecimal64 `json:"parentGasUsed,omitempty"` + ParentGasLimit *math.HexOrDecimal64 `json:"parentGasLimit,omitempty"` + GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` + Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` + Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` + ParentTimestamp *math.HexOrDecimal64 `json:"parentTimestamp,omitempty"` + BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` + Ommers []ommer `json:"ommers,omitempty"` + Withdrawals []*types.Withdrawal `json:"withdrawals,omitempty"` + BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` + ParentUncleHash *common.Hash `json:"parentUncleHash"` + ExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"` + ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"` + ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"` + ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` } var dec stEnv if err := json.Unmarshal(input, &dec); err != nil { @@ -130,5 +142,17 @@ func (s *stEnv) UnmarshalJSON(input []byte) error { if dec.ParentUncleHash != nil { s.ParentUncleHash = *dec.ParentUncleHash } + if dec.ExcessBlobGas != nil { + s.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) + } + if dec.ParentExcessBlobGas != nil { + s.ParentExcessBlobGas = (*uint64)(dec.ParentExcessBlobGas) + } + if dec.ParentBlobGasUsed != nil { + s.ParentBlobGasUsed = (*uint64)(dec.ParentBlobGasUsed) + } + if dec.ParentBeaconBlockRoot != nil { + s.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot + } return nil } diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index a4dc0e985414..c839f3b3a474 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -172,7 +172,7 @@ func Transaction(ctx *cli.Context) error { r.Error = errors.New("gas * maxFeePerGas exceeds 256 bits") } // Check whether the init code size has been exceeded. - if chainConfig.IsShanghai(new(big.Int), 0, 0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { + if chainConfig.IsShanghai(new(big.Int), 0, 0) && tx.To() == nil && len(tx.Data()) > int(chainConfig.MaxInitCodeSize()) { r.Error = errors.New("max initcode size exceeded") } results = append(results, r) diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 0b110c2a5f64..950207916922 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -28,7 +28,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -192,105 +192,20 @@ func Transition(ctx *cli.Context) error { // Set the chain id chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name)) - var txsWithKeys []*txWithKey - if txStr != stdinSelector { - inFile, err := os.Open(txStr) - if err != nil { - return NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err)) - } - defer inFile.Close() - decoder := json.NewDecoder(inFile) - if strings.HasSuffix(txStr, ".rlp") { - var body hexutil.Bytes - if err := decoder.Decode(&body); err != nil { - return err - } - var txs types.Transactions - if err := rlp.DecodeBytes(body, &txs); err != nil { - return err - } - for _, tx := range txs { - txsWithKeys = append(txsWithKeys, &txWithKey{ - key: nil, - tx: tx, - }) - } - } else { - if err := decoder.Decode(&txsWithKeys); err != nil { - return NewError(ErrorJson, fmt.Errorf("failed unmarshaling txs-file: %v", err)) - } - } - } else { - if len(inputData.TxRlp) > 0 { - // Decode the body of already signed transactions - body := common.FromHex(inputData.TxRlp) - var txs types.Transactions - if err := rlp.DecodeBytes(body, &txs); err != nil { - return err - } - for _, tx := range txs { - txsWithKeys = append(txsWithKeys, &txWithKey{ - key: nil, - tx: tx, - }) - } - } else { - // JSON encoded transactions - txsWithKeys = inputData.Txs - } + if txs, err = loadTransactions(txStr, inputData, prestate.Env, chainConfig); err != nil { + return err } - // We may have to sign the transactions. - signer := types.MakeSigner(chainConfig, big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) - - if txs, err = signUnsignedTransactions(txsWithKeys, signer); err != nil { - return NewError(ErrorJson, fmt.Errorf("failed signing transactions: %v", err)) + if err := applyLondonChecks(&prestate.Env, chainConfig); err != nil { + return err } - // Sanity check, to not `panic` in state_transition - if chainConfig.IsLondon(big.NewInt(int64(prestate.Env.Number))) { - if prestate.Env.BaseFee != nil { - // Already set, base fee has precedent over parent base fee. - } else if prestate.Env.ParentBaseFee != nil && prestate.Env.Number != 0 { - parent := &types.Header{ - Number: new(big.Int).SetUint64(prestate.Env.Number - 1), - BaseFee: prestate.Env.ParentBaseFee, - GasUsed: prestate.Env.ParentGasUsed, - GasLimit: prestate.Env.ParentGasLimit, - } - prestate.Env.BaseFee = misc.CalcBaseFee(chainConfig, parent) - } else { - return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section")) - } + if err := applyShanghaiChecks(&prestate.Env, chainConfig); err != nil { + return err } - if chainConfig.IsShanghai(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp, 0) && prestate.Env.Withdrawals == nil { - return NewError(ErrorConfig, errors.New("Shanghai config but missing 'withdrawals' in env section")) + if err := applyMergeChecks(&prestate.Env, chainConfig); err != nil { + return err } - isMerged := chainConfig.TerminalTotalDifficulty != nil && chainConfig.TerminalTotalDifficulty.BitLen() == 0 - env := prestate.Env - if isMerged { - // post-merge: - // - random must be supplied - // - difficulty must be zero - switch { - case env.Random == nil: - return NewError(ErrorConfig, errors.New("post-merge requires currentRandom to be defined in env")) - case env.Difficulty != nil && env.Difficulty.BitLen() != 0: - return NewError(ErrorConfig, errors.New("post-merge difficulty must be zero (or omitted) in env")) - } - prestate.Env.Difficulty = nil - } else if env.Difficulty == nil { - // pre-merge: - // If difficulty was not provided by caller, we need to calculate it. - switch { - case env.ParentDifficulty == nil: - return NewError(ErrorConfig, errors.New("currentDifficulty was not provided, and cannot be calculated due to missing parentDifficulty")) - case env.Number == 0: - return NewError(ErrorConfig, errors.New("currentDifficulty needs to be provided for block number 0")) - case env.Timestamp <= env.ParentTimestamp: - return NewError(ErrorConfig, fmt.Errorf("currentDifficulty cannot be calculated -- currentTime (%d) needs to be after parent time (%d)", - env.Timestamp, env.ParentTimestamp)) - } - prestate.Env.Difficulty = calcDifficulty(chainConfig, env.Number, env.Timestamp, - env.ParentTimestamp, env.ParentDifficulty, env.ParentUncleHash) + if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil { + return err } // Run the test and aggregate the result s, result, err := prestate.Apply(vmConfig, chainConfig, txs, ctx.Int64(RewardFlag.Name), getTracer) @@ -358,33 +273,149 @@ func (t *txWithKey) UnmarshalJSON(input []byte) error { // and secondly to read them with the standard tx json format func signUnsignedTransactions(txs []*txWithKey, signer types.Signer) (types.Transactions, error) { var signedTxs []*types.Transaction - for i, txWithKey := range txs { - tx := txWithKey.tx - key := txWithKey.key - v, r, s := tx.RawSignatureValues() - if key != nil && v.BitLen()+r.BitLen()+s.BitLen() == 0 { - // This transaction needs to be signed - var ( - signed *types.Transaction - err error - ) - if txWithKey.protected { - signed, err = types.SignTx(tx, signer, key) - } else { - signed, err = types.SignTx(tx, types.FrontierSigner{}, key) - } - if err != nil { - return nil, NewError(ErrorJson, fmt.Errorf("tx %d: failed to sign tx: %v", i, err)) - } - signedTxs = append(signedTxs, signed) - } else { + for i, tx := range txs { + var ( + v, r, s = tx.tx.RawSignatureValues() + signed *types.Transaction + err error + ) + if tx.key == nil || v.BitLen()+r.BitLen()+s.BitLen() != 0 { // Already signed - signedTxs = append(signedTxs, tx) + signedTxs = append(signedTxs, tx.tx) + continue + } + // This transaction needs to be signed + if tx.protected { + signed, err = types.SignTx(tx.tx, signer, tx.key) + } else { + signed, err = types.SignTx(tx.tx, types.FrontierSigner{}, tx.key) } + if err != nil { + return nil, NewError(ErrorJson, fmt.Errorf("tx %d: failed to sign tx: %v", i, err)) + } + signedTxs = append(signedTxs, signed) } return signedTxs, nil } +func loadTransactions(txStr string, inputData *input, env stEnv, chainConfig *params.ChainConfig) (types.Transactions, error) { + var txsWithKeys []*txWithKey + var signed types.Transactions + if txStr != stdinSelector { + data, err := os.ReadFile(txStr) + if err != nil { + return nil, NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err)) + } + if strings.HasSuffix(txStr, ".rlp") { // A file containing an rlp list + var body hexutil.Bytes + if err := json.Unmarshal(data, &body); err != nil { + return nil, err + } + // Already signed transactions + if err := rlp.DecodeBytes(body, &signed); err != nil { + return nil, err + } + return signed, nil + } + if err := json.Unmarshal(data, &txsWithKeys); err != nil { + return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshaling txs-file: %v", err)) + } + } else { + if len(inputData.TxRlp) > 0 { + // Decode the body of already signed transactions + body := common.FromHex(inputData.TxRlp) + // Already signed transactions + if err := rlp.DecodeBytes(body, &signed); err != nil { + return nil, err + } + return signed, nil + } + // JSON encoded transactions + txsWithKeys = inputData.Txs + } + // We may have to sign the transactions. + signer := types.LatestSignerForChainID(chainConfig.ChainID) + return signUnsignedTransactions(txsWithKeys, signer) +} + +func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error { + if !chainConfig.IsLondon(big.NewInt(int64(env.Number))) { + return nil + } + // Sanity check, to not `panic` in state_transition + if env.BaseFee != nil { + // Already set, base fee has precedent over parent base fee. + return nil + } + if env.ParentBaseFee == nil || env.Number == 0 { + return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section")) + } + env.BaseFee = eip1559.CalcBaseFee(chainConfig, &types.Header{ + Number: new(big.Int).SetUint64(env.Number - 1), + BaseFee: env.ParentBaseFee, + GasUsed: env.ParentGasUsed, + GasLimit: env.ParentGasLimit, + }) + return nil +} + +func applyShanghaiChecks(env *stEnv, chainConfig *params.ChainConfig) error { + if !chainConfig.IsShanghai(big.NewInt(int64(env.Number)), env.Timestamp, 0) { + return nil + } + if env.Withdrawals == nil { + return NewError(ErrorConfig, errors.New("Shanghai config but missing 'withdrawals' in env section")) + } + return nil +} + +func applyMergeChecks(env *stEnv, chainConfig *params.ChainConfig) error { + isMerged := chainConfig.TerminalTotalDifficulty != nil && chainConfig.TerminalTotalDifficulty.BitLen() == 0 + if !isMerged { + // pre-merge: If difficulty was not provided by caller, we need to calculate it. + if env.Difficulty != nil { + // already set + return nil + } + switch { + case env.ParentDifficulty == nil: + return NewError(ErrorConfig, errors.New("currentDifficulty was not provided, and cannot be calculated due to missing parentDifficulty")) + case env.Number == 0: + return NewError(ErrorConfig, errors.New("currentDifficulty needs to be provided for block number 0")) + case env.Timestamp <= env.ParentTimestamp: + return NewError(ErrorConfig, fmt.Errorf("currentDifficulty cannot be calculated -- currentTime (%d) needs to be after parent time (%d)", + env.Timestamp, env.ParentTimestamp)) + } + env.Difficulty = calcDifficulty(chainConfig, env.Number, env.Timestamp, + env.ParentTimestamp, env.ParentDifficulty, env.ParentUncleHash) + return nil + } + // post-merge: + // - random must be supplied + // - difficulty must be zero + switch { + case env.Random == nil: + return NewError(ErrorConfig, errors.New("post-merge requires currentRandom to be defined in env")) + case env.Difficulty != nil && env.Difficulty.BitLen() != 0: + return NewError(ErrorConfig, errors.New("post-merge difficulty must be zero (or omitted) in env")) + } + env.Difficulty = nil + return nil +} + +func applyCancunChecks(env *stEnv, chainConfig *params.ChainConfig) error { + if !chainConfig.IsCancun(big.NewInt(int64(env.Number)), env.Timestamp, 0) { + env.ParentBeaconBlockRoot = nil // un-set it if it has been set too early + return nil + } + // Post-cancun + // We require EIP-4788 beacon root to be set in the env + if env.ParentBeaconBlockRoot == nil { + return NewError(ErrorConfig, errors.New("post-cancun env requires parentBeaconBlockRoot to be set")) + } + return nil +} + type Alloc map[common.Address]core.GenesisAccount func (g Alloc) OnRoot(common.Hash) {} diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 024be62b9c54..1f6500b78c6c 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -23,107 +23,116 @@ import ( "os" "github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool" + "github.com/ethereum/go-ethereum/internal/debug" "github.com/ethereum/go-ethereum/internal/flags" "github.com/urfave/cli/v2" ) var ( DebugFlag = &cli.BoolFlag{ - Name: "debug", - Usage: "output full trace logs", - } - MemProfileFlag = &cli.StringFlag{ - Name: "memprofile", - Usage: "creates a memory profile at the given path", - } - CPUProfileFlag = &cli.StringFlag{ - Name: "cpuprofile", - Usage: "creates a CPU profile at the given path", + Name: "debug", + Usage: "output full trace logs", + Category: flags.VMCategory, } StatDumpFlag = &cli.BoolFlag{ - Name: "statdump", - Usage: "displays stack and heap memory information", + Name: "statdump", + Usage: "displays stack and heap memory information", + Category: flags.VMCategory, } CodeFlag = &cli.StringFlag{ - Name: "code", - Usage: "EVM code", + Name: "code", + Usage: "EVM code", + Category: flags.VMCategory, } CodeFileFlag = &cli.StringFlag{ - Name: "codefile", - Usage: "File containing EVM code. If '-' is specified, code is read from stdin ", + Name: "codefile", + Usage: "File containing EVM code. If '-' is specified, code is read from stdin ", + Category: flags.VMCategory, } GasFlag = &cli.Uint64Flag{ - Name: "gas", - Usage: "gas limit for the evm", - Value: 10000000000, + Name: "gas", + Usage: "gas limit for the evm", + Value: 10000000000, + Category: flags.VMCategory, } PriceFlag = &flags.BigFlag{ - Name: "price", - Usage: "price set for the evm", - Value: new(big.Int), + Name: "price", + Usage: "price set for the evm", + Value: new(big.Int), + Category: flags.VMCategory, } ValueFlag = &flags.BigFlag{ - Name: "value", - Usage: "value set for the evm", - Value: new(big.Int), + Name: "value", + Usage: "value set for the evm", + Value: new(big.Int), + Category: flags.VMCategory, } DumpFlag = &cli.BoolFlag{ - Name: "dump", - Usage: "dumps the state after the run", + Name: "dump", + Usage: "dumps the state after the run", + Category: flags.VMCategory, } InputFlag = &cli.StringFlag{ - Name: "input", - Usage: "input for the EVM", + Name: "input", + Usage: "input for the EVM", + Category: flags.VMCategory, } InputFileFlag = &cli.StringFlag{ - Name: "inputfile", - Usage: "file containing input for the EVM", - } - VerbosityFlag = &cli.IntFlag{ - Name: "verbosity", - Usage: "sets the verbosity level", + Name: "inputfile", + Usage: "file containing input for the EVM", + Category: flags.VMCategory, } BenchFlag = &cli.BoolFlag{ - Name: "bench", - Usage: "benchmark the execution", + Name: "bench", + Usage: "benchmark the execution", + Category: flags.VMCategory, } CreateFlag = &cli.BoolFlag{ - Name: "create", - Usage: "indicates the action should be create rather than call", + Name: "create", + Usage: "indicates the action should be create rather than call", + Category: flags.VMCategory, } GenesisFlag = &cli.StringFlag{ - Name: "prestate", - Usage: "JSON file with prestate (genesis) config", + Name: "prestate", + Usage: "JSON file with prestate (genesis) config", + Category: flags.VMCategory, } MachineFlag = &cli.BoolFlag{ - Name: "json", - Usage: "output trace logs in machine readable format (json)", + Name: "json", + Usage: "output trace logs in machine readable format (json)", + Category: flags.VMCategory, } SenderFlag = &cli.StringFlag{ - Name: "sender", - Usage: "The transaction origin", + Name: "sender", + Usage: "The transaction origin", + Category: flags.VMCategory, } ReceiverFlag = &cli.StringFlag{ - Name: "receiver", - Usage: "The transaction receiver (execution context)", + Name: "receiver", + Usage: "The transaction receiver (execution context)", + Category: flags.VMCategory, } DisableMemoryFlag = &cli.BoolFlag{ - Name: "nomemory", - Value: true, - Usage: "disable memory output", + Name: "nomemory", + Value: true, + Usage: "disable memory output", + Category: flags.VMCategory, } DisableStackFlag = &cli.BoolFlag{ - Name: "nostack", - Usage: "disable stack output", + Name: "nostack", + Usage: "disable stack output", + Category: flags.VMCategory, } DisableStorageFlag = &cli.BoolFlag{ - Name: "nostorage", - Usage: "disable storage output", + Name: "nostorage", + Usage: "disable storage output", + Category: flags.VMCategory, } DisableReturnDataFlag = &cli.BoolFlag{ - Name: "noreturndata", - Value: true, - Usage: "enable return data output", + Name: "noreturndata", + Value: true, + Usage: "enable return data output", + Category: flags.VMCategory, } ) @@ -183,34 +192,38 @@ var blockBuilderCommand = &cli.Command{ }, } +// vmFlags contains flags related to running the EVM. +var vmFlags = []cli.Flag{ + CodeFlag, + CodeFileFlag, + CreateFlag, + GasFlag, + PriceFlag, + ValueFlag, + InputFlag, + InputFileFlag, + GenesisFlag, + SenderFlag, + ReceiverFlag, +} + +// traceFlags contains flags that configure tracing output. +var traceFlags = []cli.Flag{ + BenchFlag, + DebugFlag, + DumpFlag, + MachineFlag, + StatDumpFlag, + DisableMemoryFlag, + DisableStackFlag, + DisableStorageFlag, + DisableReturnDataFlag, +} + var app = flags.NewApp("the evm command line interface") func init() { - app.Flags = []cli.Flag{ - BenchFlag, - CreateFlag, - DebugFlag, - VerbosityFlag, - CodeFlag, - CodeFileFlag, - GasFlag, - PriceFlag, - ValueFlag, - DumpFlag, - InputFlag, - InputFileFlag, - MemProfileFlag, - CPUProfileFlag, - StatDumpFlag, - GenesisFlag, - MachineFlag, - SenderFlag, - ReceiverFlag, - DisableMemoryFlag, - DisableStackFlag, - DisableStorageFlag, - DisableReturnDataFlag, - } + app.Flags = flags.Merge(vmFlags, traceFlags, debug.Flags) app.Commands = []*cli.Command{ compileCommand, disasmCommand, @@ -221,6 +234,14 @@ func init() { transactionCommand, blockBuilderCommand, } + app.Before = func(ctx *cli.Context) error { + flags.MigrateGlobalFlags(ctx) + return debug.Setup(ctx) + } + app.After = func(ctx *cli.Context) error { + debug.Exit() + return nil + } } func main() { diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 52d7c3fa30f9..45fc9853519f 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -24,7 +24,6 @@ import ( "math/big" "os" goruntime "runtime" - "runtime/pprof" "testing" "time" @@ -34,14 +33,13 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm/runtime" "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/internal/flags" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/hashdb" "github.com/urfave/cli/v2" ) @@ -51,6 +49,7 @@ var runCommand = &cli.Command{ Usage: "run arbitrary evm binary", ArgsUsage: "", Description: `The run command runs arbitrary EVM code.`, + Flags: flags.Merge(vmFlags, traceFlags), } // readGenesis will read the given JSON format genesis file and return @@ -108,9 +107,6 @@ func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) (output []by } func runCmd(ctx *cli.Context) error { - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name))) - log.Root().SetHandler(glogger) logconfig := &logger.Config{ EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), DisableStack: ctx.Bool(DisableStackFlag.Name), @@ -120,14 +116,15 @@ func runCmd(ctx *cli.Context) error { } var ( - tracer vm.EVMLogger - debugLogger *logger.StructLogger - statedb *state.StateDB - chainConfig *params.ChainConfig - sender = common.BytesToAddress([]byte("sender")) - receiver = common.BytesToAddress([]byte("receiver")) - genesisConfig *core.Genesis - preimages = ctx.Bool(DumpFlag.Name) + tracer vm.EVMLogger + debugLogger *logger.StructLogger + statedb *state.StateDB + chainConfig *params.ChainConfig + sender = common.BytesToAddress([]byte("sender")) + receiver = common.BytesToAddress([]byte("receiver")) + preimages = ctx.Bool(DumpFlag.Name) + blobHashes []common.Hash // TODO (MariusVanDerWijden) implement blob hashes in state tests + blobBaseFee = new(big.Int) // TODO (MariusVanDerWijden) implement blob fee in state tests ) if ctx.Bool(MachineFlag.Name) { tracer = logger.NewJSONLogger(logconfig, os.Stdout) @@ -137,19 +134,30 @@ func runCmd(ctx *cli.Context) error { } else { debugLogger = logger.NewStructLogger(logconfig) } + + initialGas := ctx.Uint64(GasFlag.Name) + genesisConfig := new(core.Genesis) + genesisConfig.GasLimit = initialGas if ctx.String(GenesisFlag.Name) != "" { - gen := readGenesis(ctx.String(GenesisFlag.Name)) - genesisConfig = gen - db := rawdb.NewMemoryDatabase() - genesis := gen.MustCommit(db) - sdb := state.NewDatabaseWithConfig(db, &trie.Config{Preimages: preimages}) - statedb, _ = state.New(genesis.Root(), sdb, nil) - chainConfig = gen.Config + genesisConfig = readGenesis(ctx.String(GenesisFlag.Name)) + if genesisConfig.GasLimit != 0 { + initialGas = genesisConfig.GasLimit + } } else { - sdb := state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{Preimages: preimages}) - statedb, _ = state.New(types.EmptyRootHash, sdb, nil) - genesisConfig = new(core.Genesis) + genesisConfig.Config = params.AllEthashProtocolChanges } + + db := rawdb.NewMemoryDatabase() + triedb := trie.NewDatabase(db, &trie.Config{ + Preimages: preimages, + HashDB: hashdb.Defaults, + }) + defer triedb.Close() + genesis := genesisConfig.MustCommit(db, triedb) + sdb := state.NewDatabaseWithNodeDB(db, triedb) + statedb, _ = state.New(genesis.Root(), sdb, nil) + chainConfig = genesisConfig.Config + if ctx.String(SenderFlag.Name) != "" { sender = common.HexToAddress(ctx.String(SenderFlag.Name)) } @@ -203,10 +211,6 @@ func runCmd(ctx *cli.Context) error { } code = common.Hex2Bytes(bin) } - initialGas := ctx.Uint64(GasFlag.Name) - if genesisConfig.GasLimit != 0 { - initialGas = genesisConfig.GasLimit - } runtimeConfig := runtime.Config{ Origin: sender, State: statedb, @@ -217,24 +221,13 @@ func runCmd(ctx *cli.Context) error { Time: genesisConfig.Timestamp, Coinbase: genesisConfig.Coinbase, BlockNumber: new(big.Int).SetUint64(genesisConfig.Number), + BlobHashes: blobHashes, + BlobBaseFee: blobBaseFee, EVMConfig: vm.Config{ Tracer: tracer, }, } - if cpuProfilePath := ctx.String(CPUProfileFlag.Name); cpuProfilePath != "" { - f, err := os.Create(cpuProfilePath) - if err != nil { - fmt.Println("could not create CPU profile: ", err) - os.Exit(1) - } - if err := pprof.StartCPUProfile(f); err != nil { - fmt.Println("could not start CPU profile: ", err) - os.Exit(1) - } - defer pprof.StopCPUProfile() - } - if chainConfig != nil { runtimeConfig.ChainConfig = chainConfig } else { @@ -278,24 +271,10 @@ func runCmd(ctx *cli.Context) error { output, leftOverGas, stats, err := timedExec(bench, execFunc) if ctx.Bool(DumpFlag.Name) { - statedb.Commit(true) - statedb.IntermediateRoot(true) + statedb.Commit(genesisConfig.Number, true) fmt.Println(string(statedb.Dump(nil))) } - if memProfilePath := ctx.String(MemProfileFlag.Name); memProfilePath != "" { - f, err := os.Create(memProfilePath) - if err != nil { - fmt.Println("could not create memory profile: ", err) - os.Exit(1) - } - if err := pprof.WriteHeapProfile(f); err != nil { - fmt.Println("could not write memory profile: ", err) - os.Exit(1) - } - f.Close() - } - if ctx.Bool(DebugFlag.Name) { if debugLogger != nil { fmt.Fprintln(os.Stderr, "#### TRACE ####") diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index ed102e6054da..8a07fccdf886 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -23,10 +23,11 @@ import ( "os" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers/logger" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/tests" "github.com/urfave/cli/v2" ) @@ -50,11 +51,6 @@ type StatetestResult struct { } func stateTestCmd(ctx *cli.Context) error { - // Configure the go-ethereum logger - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name))) - log.Root().SetHandler(glogger) - // Configure the EVM logger config := &logger.Config{ EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), @@ -104,24 +100,23 @@ func runStateTest(fname string, cfg vm.Config, jsonOut, dump bool) error { for _, st := range test.Subtests() { // Run the test and aggregate the result result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true} - _, s, err := test.Run(st, cfg, false) - // print state root for evmlab tracing - if s != nil { - root := s.IntermediateRoot(false) - result.Root = &root - if jsonOut { - fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root) + test.Run(st, cfg, false, rawdb.HashScheme, func(err error, snaps *snapshot.Tree, state *state.StateDB) { + if state != nil { + root := state.IntermediateRoot(false) + result.Root = &root + if jsonOut { + fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root) + } } - } - if err != nil { - // Test failed, mark as so and dump any state to aid debugging - result.Pass, result.Error = false, err.Error() - if dump && s != nil { - dump := s.RawDump(nil) - result.State = &dump + if err != nil { + // Test failed, mark as so and dump any state to aid debugging + result.Pass, result.Error = false, err.Error() + if dump { + dump := state.RawDump(nil) + result.State = &dump + } } - } - + }) results = append(results, *result) } } diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go index 0ef3fac4099d..ad09a6b4d61f 100644 --- a/cmd/evm/t8n_test.go +++ b/cmd/evm/t8n_test.go @@ -259,6 +259,22 @@ func TestT8n(t *testing.T) { output: t8nOutput{alloc: true, result: true}, expOut: "exp.json", }, + { // Cancun tests + base: "./testdata/28", + input: t8nInput{ + "alloc.json", "txs.rlp", "env.json", "Cancun", "", + }, + output: t8nOutput{alloc: true, result: true}, + expOut: "exp.json", + }, + { // More cancun tests + base: "./testdata/29", + input: t8nInput{ + "alloc.json", "txs.json", "env.json", "Cancun", "", + }, + output: t8nOutput{alloc: true, result: true}, + expOut: "exp.json", + }, } { args := []string{"t8n"} args = append(args, tc.output.get()...) diff --git a/cmd/evm/testdata/13/readme.md b/cmd/evm/testdata/13/readme.md index 64f52fc9a910..889975d47e0b 100644 --- a/cmd/evm/testdata/13/readme.md +++ b/cmd/evm/testdata/13/readme.md @@ -1,4 +1,4 @@ ## Input transactions in RLP form -This testdata folder is used to examplify how transaction input can be provided in rlp form. +This testdata folder is used to exemplify how transaction input can be provided in rlp form. Please see the README in `evm` folder for how this is performed. \ No newline at end of file diff --git a/cmd/evm/testdata/2/readme.md b/cmd/evm/testdata/2/readme.md index c116f0e79274..4bcf0f0fa0cf 100644 --- a/cmd/evm/testdata/2/readme.md +++ b/cmd/evm/testdata/2/readme.md @@ -1 +1 @@ -These files examplify a selfdestruct to the `0`-address. \ No newline at end of file +These files exemplify a selfdestruct to the `0`-address. \ No newline at end of file diff --git a/cmd/evm/testdata/23/readme.md b/cmd/evm/testdata/23/readme.md index 85fe8db66c95..f31b64de2fc9 100644 --- a/cmd/evm/testdata/23/readme.md +++ b/cmd/evm/testdata/23/readme.md @@ -1 +1 @@ -These files examplify how to sign a transaction using the pre-EIP155 scheme. +These files exemplify how to sign a transaction using the pre-EIP155 scheme. diff --git a/cmd/evm/testdata/28/alloc.json b/cmd/evm/testdata/28/alloc.json new file mode 100644 index 000000000000..680a89f4edb2 --- /dev/null +++ b/cmd/evm/testdata/28/alloc.json @@ -0,0 +1,16 @@ +{ + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x016345785d8a0000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + }, + "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x016345785d8a0000", + "code" : "0x60004960015500", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/28/env.json b/cmd/evm/testdata/28/env.json new file mode 100644 index 000000000000..82f22ac62f4c --- /dev/null +++ b/cmd/evm/testdata/28/env.json @@ -0,0 +1,22 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentNumber" : "0x01", + "currentTimestamp" : "0x079e", + "currentGasLimit" : "0x7fffffffffffffff", + "previousHash" : "0x3a9b485972e7353edd9152712492f0c58d89ef80623686b6bf947a4a6dce6cb6", + "currentBlobGasUsed" : "0x00", + "parentTimestamp" : "0x03b6", + "parentDifficulty" : "0x00", + "parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "currentRandom" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "withdrawals" : [], + "parentBaseFee" : "0x0a", + "parentGasUsed" : "0x00", + "parentGasLimit" : "0x7fffffffffffffff", + "parentExcessBlobGas" : "0x00", + "parentBlobGasUsed" : "0x00", + "blockHashes" : { + "0" : "0x3a9b485972e7353edd9152712492f0c58d89ef80623686b6bf947a4a6dce6cb6" + }, + "parentBeaconBlockRoot": "0x0000beac00beac00beac00beac00beac00beac00beac00beac00beac00beac00" +} diff --git a/cmd/evm/testdata/28/exp.json b/cmd/evm/testdata/28/exp.json new file mode 100644 index 000000000000..c72b95484129 --- /dev/null +++ b/cmd/evm/testdata/28/exp.json @@ -0,0 +1,48 @@ +{ + "alloc": { + "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": { + "balance": "0x150ca" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x16345785d80c3a9", + "nonce": "0x1" + }, + "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x60004960015500", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x01a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + "balance": "0x16345785d8a0000" + } + }, + "result": { + "stateRoot": "0xa40cb3fab01848e922a48bd24191815df9f721ad4b60376edac75161517663e8", + "txRoot": "0x4409cc4b699384ba5f8248d92b784713610c5ff9c1de51e9239da0dac76de9ce", + "receiptsRoot": "0xbff643da765981266133094092d98c81d2ac8e9a83a7bbda46c3d736f1f874ac", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "gasUsedForL1": "0x0", + "type": "0x3", + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0xa865", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x7508d7139d002a4b3a26a4f12dec0d87cb46075c78bf77a38b569a133b509262", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0xa865", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "currentDifficulty": null, + "gasUsed": "0xa865", + "currentBaseFee": "0x9", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "currentExcessBlobGas": "0x0", + "blobGasUsed": "0x20000" + } +} diff --git a/cmd/evm/testdata/28/txs.rlp b/cmd/evm/testdata/28/txs.rlp new file mode 100644 index 000000000000..8df20e3aa27e --- /dev/null +++ b/cmd/evm/testdata/28/txs.rlp @@ -0,0 +1 @@ +"0xf88bb88903f8860180026483061a8094b94f5374fce5edbc8e2a8697c15331677e6ebf0b8080c00ae1a001a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d801a025e16bb498552165016751911c3608d79000ab89dc3100776e729e6ea13091c7a03acacff7fc0cff6eda8a927dec93ca17765e1ee6cbc06c5954ce102e097c01d2" \ No newline at end of file diff --git a/cmd/evm/testdata/29/alloc.json b/cmd/evm/testdata/29/alloc.json new file mode 100644 index 000000000000..d2c879a45c87 --- /dev/null +++ b/cmd/evm/testdata/29/alloc.json @@ -0,0 +1,16 @@ +{ + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x016345785d8a0000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + }, + "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" : { + "balance" : "0x1", + "code" : "0x3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/29/env.json b/cmd/evm/testdata/29/env.json new file mode 100644 index 000000000000..e752a909ad3f --- /dev/null +++ b/cmd/evm/testdata/29/env.json @@ -0,0 +1,20 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentNumber" : "0x01", + "currentTimestamp" : "0x079e", + "currentGasLimit" : "0x7fffffffffffffff", + "previousHash" : "0x3a9b485972e7353edd9152712492f0c58d89ef80623686b6bf947a4a6dce6cb6", + "currentBlobGasUsed" : "0x00", + "parentTimestamp" : "0x03b6", + "parentDifficulty" : "0x00", + "parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "currentRandom" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "withdrawals" : [ + ], + "parentBaseFee" : "0x0a", + "parentGasUsed" : "0x00", + "parentGasLimit" : "0x7fffffffffffffff", + "parentExcessBlobGas" : "0x00", + "parentBlobGasUsed" : "0x00", + "parentBeaconBlockRoot": "0x0000beac00beac00beac00beac00beac00beac00beac00beac00beac00beac00" +} \ No newline at end of file diff --git a/cmd/evm/testdata/29/exp.json b/cmd/evm/testdata/29/exp.json new file mode 100644 index 000000000000..ac9f7c342c51 --- /dev/null +++ b/cmd/evm/testdata/29/exp.json @@ -0,0 +1,46 @@ +{ + "alloc": { + "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000079e": "0x000000000000000000000000000000000000000000000000000000000000079e", + "0x000000000000000000000000000000000000000000000000000000000001879e": "0x0000beac00beac00beac00beac00beac00beac00beac00beac00beac00beac00" + }, + "balance": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x16345785d871db8", + "nonce": "0x1" + } + }, + "result": { + "stateRoot": "0x19a4f821a7c0a6f4c934f9acb0fe9ce5417b68086e12513ecbc3e3f57e01573c", + "txRoot": "0x248074fabe112f7d93917f292b64932394f835bb98da91f21501574d58ec92ab", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "gasUsedForL1": "0x0", + "type": "0x2", + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x84f70aba406a55628a0620f26d260f90aeb6ccc55fed6ec2ac13dd4f727032ed", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "currentDifficulty": null, + "gasUsed": "0x5208", + "currentBaseFee": "0x9", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "currentExcessBlobGas": "0x0", + "blobGasUsed": "0x0" + } +} diff --git a/cmd/evm/testdata/29/readme.md b/cmd/evm/testdata/29/readme.md new file mode 100644 index 000000000000..ab02ce9cf8d1 --- /dev/null +++ b/cmd/evm/testdata/29/readme.md @@ -0,0 +1,29 @@ +## EIP 4788 + +This test contains testcases for EIP-4788. The 4788-contract is +located at address `0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02`, and this test executes a simple transaction. It also +implicitly invokes the system tx, which sets calls the contract and sets the +storage values + +``` +$ dir=./testdata/29/ && go run . t8n --state.fork=Cancun --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --output.alloc=stdout +INFO [09-27|15:34:53.049] Trie dumping started root=19a4f8..01573c +INFO [09-27|15:34:53.049] Trie dumping complete accounts=2 elapsed="192.759µs" +INFO [09-27|15:34:53.050] Wrote file file=result.json +{ + "alloc": { + "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000079e": "0x000000000000000000000000000000000000000000000000000000000000079e", + "0x000000000000000000000000000000000000000000000000000000000001879e": "0x0000beac00beac00beac00beac00beac00beac00beac00beac00beac00beac00" + }, + "balance": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x16345785d871db8", + "nonce": "0x1" + } + } +} +``` diff --git a/cmd/evm/testdata/29/txs.json b/cmd/evm/testdata/29/txs.json new file mode 100644 index 000000000000..d6743cc4d240 --- /dev/null +++ b/cmd/evm/testdata/29/txs.json @@ -0,0 +1,19 @@ +[ + { + "input" : "0x", + "gas" : "0x10000000", + "nonce" : "0x0", + "to" : "0x1111111111111111111111111111111111111111", + "value" : "0x0", + "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "chainId" : "0x1", + "type" : "0x2", + "v": "0x0", + "r": "0x0", + "s": "0x0", + "maxFeePerGas" : "0xfa0", + "maxPriorityFeePerGas" : "0x0", + "accessList" : [ + ] + } +] \ No newline at end of file diff --git a/cmd/evm/testdata/3/readme.md b/cmd/evm/testdata/3/readme.md index 499f03d7aa7f..246c58ef3bf4 100644 --- a/cmd/evm/testdata/3/readme.md +++ b/cmd/evm/testdata/3/readme.md @@ -1,2 +1,2 @@ -These files examplify a transition where a transaction (excuted on block 5) requests +These files exemplify a transition where a transaction (executed on block 5) requests the blockhash for block `1`. diff --git a/cmd/evm/testdata/4/readme.md b/cmd/evm/testdata/4/readme.md index 08840d37bd9c..eede41a9fd34 100644 --- a/cmd/evm/testdata/4/readme.md +++ b/cmd/evm/testdata/4/readme.md @@ -1,3 +1,3 @@ -These files examplify a transition where a transaction (excuted on block 5) requests +These files exemplify a transition where a transaction (executed on block 5) requests the blockhash for block `4`, but where the hash for that block is missing. It's expected that executing these should cause `exit` with errorcode `4`. diff --git a/cmd/evm/testdata/5/readme.md b/cmd/evm/testdata/5/readme.md index e2b608face9c..1a84afaab64e 100644 --- a/cmd/evm/testdata/5/readme.md +++ b/cmd/evm/testdata/5/readme.md @@ -1 +1 @@ -These files examplify a transition where there are no transcations, two ommers, at block `N-1` (delta 1) and `N-2` (delta 2). \ No newline at end of file +These files exemplify a transition where there are no transactions, two ommers, at block `N-1` (delta 1) and `N-2` (delta 2). \ No newline at end of file diff --git a/cmd/evm/testdata/8/readme.md b/cmd/evm/testdata/8/readme.md index c991fb217799..4dffdab91ebd 100644 --- a/cmd/evm/testdata/8/readme.md +++ b/cmd/evm/testdata/8/readme.md @@ -7,7 +7,7 @@ This test contains testcases for EIP-2930, which uses transactions with access l The alloc portion contains one contract (`0x000000000000000000000000000000000000aaaa`), containing the following code: `0x5854505854`: `PC ;SLOAD; POP; PC; SLOAD`. -Essentialy, this contract does `SLOAD(0)` and `SLOAD(3)`. +Essentially, this contract does `SLOAD(0)` and `SLOAD(3)`. The alloc also contains some funds on `0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b`. diff --git a/cmd/evm/testdata/9/readme.md b/cmd/evm/testdata/9/readme.md index b14464465547..357e200682f6 100644 --- a/cmd/evm/testdata/9/readme.md +++ b/cmd/evm/testdata/9/readme.md @@ -1,13 +1,13 @@ ## EIP-1559 testing -This test contains testcases for EIP-1559, which uses an new transaction type and has a new block parameter. +This test contains testcases for EIP-1559, which uses a new transaction type and has a new block parameter. ### Prestate The alloc portion contains one contract (`0x000000000000000000000000000000000000aaaa`), containing the following code: `0x58585454`: `PC; PC; SLOAD; SLOAD`. -Essentialy, this contract does `SLOAD(0)` and `SLOAD(1)`. +Essentially, this contract does `SLOAD(0)` and `SLOAD(1)`. The alloc also contains some funds on `0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b`. diff --git a/cmd/evm/transition-test.sh b/cmd/evm/transition-test.sh index a2ea5341892c..8cc6aa41de8b 100644 --- a/cmd/evm/transition-test.sh +++ b/cmd/evm/transition-test.sh @@ -280,7 +280,7 @@ To make `t8n` apply these, the following inputs are required: - For ethash, it is `5000000000000000000` `wei`, - If this is not defined, mining rewards are not applied, - A value of `0` is valid, and causes accounts to be 'touched'. -- For each ommer, the tool needs to be given an `addres\` and a `delta`. This +- For each ommer, the tool needs to be given an `address\` and a `delta`. This is done via the `ommers` field in `env`. Note: the tool does not verify that e.g. the normal uncle rules apply, diff --git a/cmd/faucet/README.md b/cmd/faucet/README.md index 6e9b14ebc78a..7e857fa0ee87 100644 --- a/cmd/faucet/README.md +++ b/cmd/faucet/README.md @@ -12,7 +12,6 @@ First things first, the `faucet` needs to connect to an Ethereum network, for wh - `-genesis` is a path to a file containing the network `genesis.json`. or using: - `-goerli` with the faucet with Görli network config - - `-rinkeby` with the faucet with Rinkeby network config - `-sepolia` with the faucet with Sepolia network config - `-network` is the devp2p network id used during connection - `-bootnodes` is a list of `enode://` ids to join the network through diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index b66cc461411e..e4d6ad697750 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -86,7 +86,6 @@ var ( twitterTokenV1Flag = flag.String("twitter.token.v1", "", "Bearer token to authenticate with the v1.1 Twitter API") goerliFlag = flag.Bool("goerli", false, "Initializes the faucet with Görli network config") - rinkebyFlag = flag.Bool("rinkeby", false, "Initializes the faucet with Rinkeby network config") sepoliaFlag = flag.Bool("sepolia", false, "Initializes the faucet with Sepolia network config") ) @@ -140,7 +139,7 @@ func main() { log.Crit("Failed to render the faucet template", "err", err) } // Load and parse the genesis block requested by the user - genesis, err := getGenesis(*genesisFlag, *goerliFlag, *rinkebyFlag, *sepoliaFlag) + genesis, err := getGenesis(*genesisFlag, *goerliFlag, *sepoliaFlag) if err != nil { log.Crit("Failed to parse genesis config", "err", err) } @@ -269,11 +268,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network ui } } // Attach to the client and retrieve and interesting metadatas - api, err := stack.Attach() - if err != nil { - stack.Close() - return nil, err - } + api := stack.Attach() client := ethclient.NewClient(api) return &faucet{ @@ -880,7 +875,7 @@ func authNoAuth(url string) (string, string, common.Address, error) { } // getGenesis returns a genesis based on input args -func getGenesis(genesisFlag string, goerliFlag bool, rinkebyFlag bool, sepoliaFlag bool) (*core.Genesis, error) { +func getGenesis(genesisFlag string, goerliFlag bool, sepoliaFlag bool) (*core.Genesis, error) { switch { case genesisFlag != "": var genesis core.Genesis @@ -888,8 +883,6 @@ func getGenesis(genesisFlag string, goerliFlag bool, rinkebyFlag bool, sepoliaFl return &genesis, err case goerliFlag: return core.DefaultGoerliGenesisBlock(), nil - case rinkebyFlag: - return core.DefaultRinkebyGenesisBlock(), nil case sepoliaFlag: return core.DefaultSepoliaGenesisBlock(), nil default: diff --git a/cmd/geth/attach_test.go b/cmd/geth/attach_test.go index 7c5f951750fb..91007ccf65fb 100644 --- a/cmd/geth/attach_test.go +++ b/cmd/geth/attach_test.go @@ -61,7 +61,7 @@ func TestRemoteDbWithHeaders(t *testing.T) { } func testReceiveHeaders(t *testing.T, ln net.Listener, gethArgs ...string) { - var ok uint32 + var ok atomic.Uint32 server := &http.Server{ Addr: "localhost:0", Handler: &testHandler{func(w http.ResponseWriter, r *http.Request) { @@ -72,12 +72,12 @@ func testReceiveHeaders(t *testing.T, ln net.Listener, gethArgs ...string) { if have, want := r.Header.Get("second"), "two"; have != want { t.Fatalf("missing header, have %v want %v", have, want) } - atomic.StoreUint32(&ok, 1) + ok.Store(1) }}} go server.Serve(ln) defer server.Close() runGeth(t, gethArgs...).WaitExit() - if atomic.LoadUint32(&ok) != 1 { + if ok.Load() != 1 { t.Fatal("Test fail, expected invocation to succeed") } } diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index b27ce557106f..a6bb2c2d2c41 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -39,7 +39,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/trie" "github.com/urfave/cli/v2" ) @@ -49,7 +48,9 @@ var ( Name: "init", Usage: "Bootstrap and initialize a new genesis block", ArgsUsage: "", - Flags: flags.Merge([]cli.Flag{utils.CachePreimagesFlag}, utils.DatabasePathFlags), + Flags: flags.Merge([]cli.Flag{ + utils.CachePreimagesFlag, + }, utils.DatabaseFlags), Description: ` The init command initializes a new genesis block and definition for the network. This is a destructive action and changes the network in which you will be @@ -94,7 +95,9 @@ if one is set. Otherwise it prints the genesis from the datadir.`, utils.MetricsInfluxDBBucketFlag, utils.MetricsInfluxDBOrganizationFlag, utils.TxLookupLimitFlag, - }, utils.DatabasePathFlags), + utils.TransactionHistoryFlag, + utils.StateHistoryFlag, + }, utils.DatabaseFlags), Description: ` The import command imports blocks from an RLP-encoded form. The form can be one file with several RLP-encoded blocks, or several files can be used. @@ -110,7 +113,7 @@ processing will proceed even if an individual RLP-file import failure occurs.`, Flags: flags.Merge([]cli.Flag{ utils.CacheFlag, utils.SyncModeFlag, - }, utils.DatabasePathFlags), + }, utils.DatabaseFlags), Description: ` Requires a first argument of the file to write to. Optional second and third arguments control the first and @@ -126,7 +129,7 @@ be gzipped.`, Flags: flags.Merge([]cli.Flag{ utils.CacheFlag, utils.SyncModeFlag, - }, utils.DatabasePathFlags), + }, utils.DatabaseFlags), Description: ` The import-preimages command imports hash preimages from an RLP encoded stream. It's deprecated, please use "geth db import" instead. @@ -140,7 +143,7 @@ It's deprecated, please use "geth db import" instead. Flags: flags.Merge([]cli.Flag{ utils.CacheFlag, utils.SyncModeFlag, - }, utils.DatabasePathFlags), + }, utils.DatabaseFlags), Description: ` The export-preimages command exports hash preimages to an RLP encoded stream. It's deprecated, please use "geth db export" instead. @@ -159,7 +162,7 @@ It's deprecated, please use "geth db export" instead. utils.IncludeIncompletesFlag, utils.StartKeyFlag, utils.DumpLimitFlag, - }, utils.DatabasePathFlags), + }, utils.DatabaseFlags), Description: ` This command dumps out the state for a given block (or latest, if none provided). `, @@ -195,14 +198,15 @@ func initGenesis(ctx *cli.Context) error { if err != nil { utils.Fatalf("Failed to open database: %v", err) } - triedb := trie.NewDatabaseWithConfig(chaindb, &trie.Config{ - Preimages: ctx.Bool(utils.CachePreimagesFlag.Name), - }) + defer chaindb.Close() + + triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false) + defer triedb.Close() + _, hash, err := core.SetupGenesisBlock(chaindb, triedb, genesis) if err != nil { utils.Fatalf("Failed to write genesis block: %v", err) } - chaindb.Close() log.Info("Successfully wrote genesis state", "database", name, "hash", hash) } return nil @@ -241,7 +245,7 @@ func dumpGenesis(ctx *cli.Context) error { if ctx.IsSet(utils.DataDirFlag.Name) { utils.Fatalf("no existing datadir at %s", stack.Config().DataDir) } - utils.Fatalf("no network preset provided. no exisiting genesis in the default datadir") + utils.Fatalf("no network preset provided, no existing genesis in the default datadir") return nil } @@ -261,16 +265,16 @@ func importChain(ctx *cli.Context) error { defer db.Close() // Start periodically gathering memory profiles - var peakMemAlloc, peakMemSys uint64 + var peakMemAlloc, peakMemSys atomic.Uint64 go func() { stats := new(runtime.MemStats) for { runtime.ReadMemStats(stats) - if atomic.LoadUint64(&peakMemAlloc) < stats.Alloc { - atomic.StoreUint64(&peakMemAlloc, stats.Alloc) + if peakMemAlloc.Load() < stats.Alloc { + peakMemAlloc.Store(stats.Alloc) } - if atomic.LoadUint64(&peakMemSys) < stats.Sys { - atomic.StoreUint64(&peakMemSys, stats.Sys) + if peakMemSys.Load() < stats.Sys { + peakMemSys.Store(stats.Sys) } time.Sleep(5 * time.Second) } @@ -303,8 +307,8 @@ func importChain(ctx *cli.Context) error { mem := new(runtime.MemStats) runtime.ReadMemStats(mem) - fmt.Printf("Object memory: %.3f MB current, %.3f MB peak\n", float64(mem.Alloc)/1024/1024, float64(atomic.LoadUint64(&peakMemAlloc))/1024/1024) - fmt.Printf("System memory: %.3f MB current, %.3f MB peak\n", float64(mem.Sys)/1024/1024, float64(atomic.LoadUint64(&peakMemSys))/1024/1024) + fmt.Printf("Object memory: %.3f MB current, %.3f MB peak\n", float64(mem.Alloc)/1024/1024, float64(peakMemAlloc.Load())/1024/1024) + fmt.Printf("System memory: %.3f MB current, %.3f MB peak\n", float64(mem.Sys)/1024/1024, float64(peakMemSys.Load())/1024/1024) fmt.Printf("Allocations: %.3f million\n", float64(mem.Mallocs)/1000000) fmt.Printf("GC pause: %v\n\n", time.Duration(mem.PauseTotalNs)) @@ -332,7 +336,8 @@ func exportChain(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - chain, _ := utils.MakeChain(ctx, stack, true) + chain, db := utils.MakeChain(ctx, stack, true) + defer db.Close() start := time.Now() var err error @@ -372,6 +377,7 @@ func importPreimages(ctx *cli.Context) error { defer stack.Close() db := utils.MakeChainDatabase(ctx, stack, false) + defer db.Close() start := time.Now() if err := utils.ImportPreimages(db, ctx.Args().First()); err != nil { @@ -390,6 +396,7 @@ func exportPreimages(ctx *cli.Context) error { defer stack.Close() db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() start := time.Now() if err := utils.ExportPreimages(db, ctx.Args().First()); err != nil { @@ -401,6 +408,8 @@ func exportPreimages(ctx *cli.Context) error { func parseDumpConfig(ctx *cli.Context, stack *node.Node) (*state.DumpConfig, ethdb.Database, common.Hash, error) { db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + var header *types.Header if ctx.NArg() > 1 { return nil, nil, common.Hash{}, fmt.Errorf("expected 1 argument (number or hash), got %d", ctx.NArg()) @@ -465,10 +474,10 @@ func dump(ctx *cli.Context) error { if err != nil { return err } - config := &trie.Config{ - Preimages: true, // always enable preimage lookup - } - state, err := state.New(root, state.NewDatabaseWithConfig(db, config), nil) + triedb := utils.MakeTrieDatabase(ctx, db, true, true) // always enable preimage lookup + defer triedb.Close() + + state, err := state.New(root, state.NewDatabaseWithNodeDB(db, triedb), nil) if err != nil { return err } diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 42ded493224b..027dac7bd60a 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -22,16 +22,19 @@ import ( "fmt" "os" "reflect" + "runtime" + "strings" "unicode" - "github.com/urfave/cli/v2" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/external" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/scwallet" "github.com/ethereum/go-ethereum/accounts/usbwallet" "github.com/ethereum/go-ethereum/cmd/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/ethapi" @@ -42,6 +45,7 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/naoina/toml" + "github.com/urfave/cli/v2" ) var ( @@ -170,8 +174,26 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { v := ctx.Uint64(utils.OverrideCancun.Name) cfg.Eth.OverrideCancun = &v } + if ctx.IsSet(utils.OverrideVerkle.Name) { + v := ctx.Uint64(utils.OverrideVerkle.Name) + cfg.Eth.OverrideVerkle = &v + } backend, eth := utils.RegisterEthService(stack, &cfg.Eth) + // Create gauge with geth system and build information + if eth != nil { // The 'eth' backend may be nil in light mode + var protos []string + for _, p := range eth.Protocols() { + protos = append(protos, fmt.Sprintf("%v/%d", p.Name, p.Version)) + } + metrics.NewRegisteredGaugeInfo("geth/info", nil).Update(metrics.GaugeInfoValue{ + "arch": runtime.GOARCH, + "os": runtime.GOOS, + "version": cfg.Node.Version, + "protocols": strings.Join(protos, ","), + }) + } + // Configure log filter RPC API. filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth) @@ -179,15 +201,32 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { if ctx.IsSet(utils.GraphQLEnabledFlag.Name) { utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node) } - // Add the Ethereum Stats daemon if requested. if cfg.Ethstats.URL != "" { utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL) } - // Configure full-sync tester service if requested - if ctx.IsSet(utils.SyncTargetFlag.Name) && cfg.Eth.SyncMode == downloader.FullSync { - utils.RegisterFullSyncTester(stack, eth, ctx.Path(utils.SyncTargetFlag.Name)) + if ctx.IsSet(utils.SyncTargetFlag.Name) { + hex := hexutil.MustDecode(ctx.String(utils.SyncTargetFlag.Name)) + if len(hex) != common.HashLength { + utils.Fatalf("invalid sync target length: have %d, want %d", len(hex), common.HashLength) + } + utils.RegisterFullSyncTester(stack, eth, common.BytesToHash(hex)) + } + // Start the dev mode if requested, or launch the engine API for + // interacting with external consensus client. + if ctx.IsSet(utils.DeveloperFlag.Name) { + simBeacon, err := catalyst.NewSimulatedBeacon(ctx.Uint64(utils.DeveloperPeriodFlag.Name), eth) + if err != nil { + utils.Fatalf("failed to register dev mode catalyst service: %v", err) + } + catalyst.RegisterSimulatedBeaconAPIs(stack, simBeacon) + stack.RegisterLifecycle(simBeacon) + } else if cfg.Eth.SyncMode != downloader.LightSync { + err := catalyst.Register(stack, eth) + if err != nil { + utils.Fatalf("failed to register catalyst service: %v", err) + } } return stack, backend } @@ -272,6 +311,10 @@ func deprecated(field string) bool { return true case "ethconfig.Config.EWASMInterpreter": return true + case "ethconfig.Config.TrieCleanCacheJournal": + return true + case "ethconfig.Config.TrieCleanCacheRejournal": + return true default: return false } diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index aeee6f9c9e40..526ede96192f 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -75,10 +75,7 @@ func localConsole(ctx *cli.Context) error { defer stack.Close() // Attach to the newly started node and create the JavaScript console. - client, err := stack.Attach() - if err != nil { - return fmt.Errorf("failed to attach to the inproc geth: %v", err) - } + client := stack.Attach() config := console.Config{ DataDir: utils.MakeDataDir(ctx), DocRoot: ctx.String(utils.JSpathFlag.Name), diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index b409b19260e0..6f802716c5e8 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -48,7 +48,7 @@ var ( Name: "removedb", Usage: "Remove blockchain and state databases", ArgsUsage: "", - Flags: utils.DatabasePathFlags, + Flags: utils.DatabaseFlags, Description: ` Remove blockchain and state databases`, } @@ -77,7 +77,7 @@ Remove blockchain and state databases`, ArgsUsage: " ", Flags: flags.Merge([]cli.Flag{ utils.SyncModeFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), + }, utils.NetworkFlags, utils.DatabaseFlags), Usage: "Inspect the storage size for each type of data in the database", Description: `This commands iterates the entire database. If the optional 'prefix' and 'start' arguments are provided, then the iteration is limited to the given subset of data.`, } @@ -85,7 +85,7 @@ Remove blockchain and state databases`, Action: checkStateContent, Name: "check-state-content", ArgsUsage: "", - Flags: flags.Merge(utils.NetworkFlags, utils.DatabasePathFlags), + Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags), Usage: "Verify that state data is cryptographically correct", Description: `This command iterates the entire database for 32-byte keys, looking for rlp-encoded trie nodes. For each trie node encountered, it checks that the key corresponds to the keccak256(value). If this is not true, this indicates @@ -97,7 +97,7 @@ a data corruption.`, Usage: "Print leveldb statistics", Flags: flags.Merge([]cli.Flag{ utils.SyncModeFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), + }, utils.NetworkFlags, utils.DatabaseFlags), } dbCompactCmd = &cli.Command{ Action: dbCompact, @@ -107,7 +107,7 @@ a data corruption.`, utils.SyncModeFlag, utils.CacheFlag, utils.CacheDatabaseFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), + }, utils.NetworkFlags, utils.DatabaseFlags), Description: `This command performs a database compaction. WARNING: This operation may take a very long time to finish, and may cause database corruption if it is aborted during execution'!`, @@ -119,7 +119,7 @@ corruption if it is aborted during execution'!`, ArgsUsage: "", Flags: flags.Merge([]cli.Flag{ utils.SyncModeFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), + }, utils.NetworkFlags, utils.DatabaseFlags), Description: "This command looks up the specified database key from the database.", } dbDeleteCmd = &cli.Command{ @@ -129,7 +129,7 @@ corruption if it is aborted during execution'!`, ArgsUsage: "", Flags: flags.Merge([]cli.Flag{ utils.SyncModeFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), + }, utils.NetworkFlags, utils.DatabaseFlags), Description: `This command deletes the specified database key from the database. WARNING: This is a low-level operation which may cause database corruption!`, } @@ -140,7 +140,7 @@ WARNING: This is a low-level operation which may cause database corruption!`, ArgsUsage: " ", Flags: flags.Merge([]cli.Flag{ utils.SyncModeFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), + }, utils.NetworkFlags, utils.DatabaseFlags), Description: `This command sets a given database key to the given value. WARNING: This is a low-level operation which may cause database corruption!`, } @@ -151,7 +151,7 @@ WARNING: This is a low-level operation which may cause database corruption!`, ArgsUsage: " ", Flags: flags.Merge([]cli.Flag{ utils.SyncModeFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), + }, utils.NetworkFlags, utils.DatabaseFlags), Description: "This command looks up the specified database key from the database.", } dbDumpFreezerIndex = &cli.Command{ @@ -161,7 +161,7 @@ WARNING: This is a low-level operation which may cause database corruption!`, ArgsUsage: " ", Flags: flags.Merge([]cli.Flag{ utils.SyncModeFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), + }, utils.NetworkFlags, utils.DatabaseFlags), Description: "This command displays information about the freezer index.", } dbImportCmd = &cli.Command{ @@ -171,7 +171,7 @@ WARNING: This is a low-level operation which may cause database corruption!`, ArgsUsage: " ", Flags: flags.Merge([]cli.Flag{ utils.SyncModeFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), + }, utils.NetworkFlags, utils.DatabaseFlags), Description: "Exports the specified chain data to an RLP encoded stream, optionally gzip-compressed.", } dbMetadataCmd = &cli.Command{ @@ -190,7 +190,7 @@ WARNING: This is a low-level operation which may cause database corruption!`, Usage: "Shows metadata about the chain status.", Flags: flags.Merge([]cli.Flag{ utils.SyncModeFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), + }, utils.NetworkFlags, utils.DatabaseFlags), Description: "Shows metadata about the chain status.", } ) @@ -482,6 +482,9 @@ func dbDumpTrie(ctx *cli.Context) error { db := utils.MakeChainDatabase(ctx, stack, true) defer db.Close() + triedb := utils.MakeTrieDatabase(ctx, db, false, true) + defer triedb.Close() + var ( state []byte storage []byte @@ -515,12 +518,16 @@ func dbDumpTrie(ctx *cli.Context) error { } } id := trie.StorageTrieID(common.BytesToHash(state), common.BytesToHash(account), common.BytesToHash(storage)) - theTrie, err := trie.New(id, trie.NewDatabase(db)) + theTrie, err := trie.New(id, triedb) + if err != nil { + return err + } + trieIt, err := theTrie.NodeIterator(start) if err != nil { return err } var count int64 - it := trie.NewIterator(theTrie.NodeIterator(start)) + it := trie.NewIterator(trieIt) for it.Next() { if max > 0 && count == max { fmt.Printf("Exiting after %d values\n", count) @@ -587,6 +594,7 @@ func importLDBdata(ctx *cli.Context) error { close(stop) }() db := utils.MakeChainDatabase(ctx, stack, false) + defer db.Close() return utils.ImportLDBData(db, fName, int64(start), stop) } @@ -683,6 +691,7 @@ func exportChaindata(ctx *cli.Context) error { close(stop) }() db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() return utils.ExportChaindata(ctx.Args().Get(1), kind, exporter(db), stop) } @@ -690,6 +699,8 @@ func showMetaData(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + ancients, err := db.Ancients() if err != nil { fmt.Fprintf(os.Stderr, "Error accessing ancients: %v", err) diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go index 607b454eadfb..b36c3265a3b8 100644 --- a/cmd/geth/les_test.go +++ b/cmd/geth/les_test.go @@ -107,10 +107,10 @@ func ipcEndpoint(ipcPath, datadir string) string { // but windows require pipes to sit in "\\.\pipe\". Therefore, to run several // nodes simultaneously, we need to distinguish between them, which we do by // the pipe filename instead of folder. -var nextIPC = uint32(0) +var nextIPC atomic.Uint32 func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { - ipcName := fmt.Sprintf("geth-%d.ipc", atomic.AddUint32(&nextIPC, 1)) + ipcName := fmt.Sprintf("geth-%d.ipc", nextIPC.Add(1)) args = append([]string{"--networkid=42", "--port=0", "--authrpc.port", "0", "--ipcpath", ipcName}, args...) t.Logf("Starting %v with rpc: %v", name, args) @@ -146,13 +146,13 @@ func startLightServer(t *testing.T) *gethrpc { t.Logf("Importing keys to geth") runGeth(t, "account", "import", "--datadir", datadir, "--password", "./testdata/password.txt", "--lightkdf", "./testdata/key.prv").WaitExit() account := "0x02f0d131f1f97aef08aec6e3291b957d9efe7105" - server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--miner.etherbase=0x02f0d131f1f97aef08aec6e3291b957d9efe7105", "--mine", "--light.serve=100", "--light.maxpeers=1", "--nodiscover", "--nat=extip:127.0.0.1", "--verbosity=4") + server := startGethWithIpc(t, "lightserver", "--allow-insecure-unlock", "--datadir", datadir, "--password", "./testdata/password.txt", "--unlock", account, "--miner.etherbase=0x02f0d131f1f97aef08aec6e3291b957d9efe7105", "--mine", "--light.serve=100", "--light.maxpeers=1", "--discv4=false", "--nat=extip:127.0.0.1", "--verbosity=4") return server } func startClient(t *testing.T, name string) *gethrpc { datadir := initGeth(t) - return startGethWithIpc(t, name, "--datadir", datadir, "--nodiscover", "--syncmode=light", "--nat=extip:127.0.0.1", "--verbosity=4") + return startGethWithIpc(t, name, "--datadir", datadir, "--discv4=false", "--syncmode=light", "--nat=extip:127.0.0.1", "--verbosity=4") } func TestPriorityClient(t *testing.T) { diff --git a/cmd/geth/main.go b/cmd/geth/main.go index b4a7b5fa95c2..4b26de05a903 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -39,6 +39,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" + "go.uber.org/automaxprocs/maxprocs" // Force-load the tracer engines to trigger registration _ "github.com/ethereum/go-ethereum/eth/tracers/js" @@ -65,6 +66,7 @@ var ( utils.USBFlag, utils.SmartCardDaemonPathFlag, utils.OverrideCancun, + utils.OverrideVerkle, utils.EnablePersonal, utils.TxPoolLocalsFlag, utils.TxPoolNoLocalsFlag, @@ -77,21 +79,23 @@ var ( utils.TxPoolAccountQueueFlag, utils.TxPoolGlobalQueueFlag, utils.TxPoolLifetimeFlag, + utils.BlobPoolDataDirFlag, + utils.BlobPoolDataCapFlag, + utils.BlobPoolPriceBumpFlag, utils.SyncModeFlag, utils.SyncTargetFlag, utils.ExitWhenSyncedFlag, utils.GCModeFlag, utils.SnapshotFlag, utils.TxLookupLimitFlag, + utils.TransactionHistoryFlag, + utils.StateHistoryFlag, utils.LightServeFlag, utils.LightIngressFlag, utils.LightEgressFlag, utils.LightMaxPeersFlag, utils.LightNoPruneFlag, utils.LightKDFFlag, - utils.UltraLightServersFlag, - utils.UltraLightFractionFlag, - utils.UltraLightOnlyAnnounceFlag, utils.LightNoSyncServeFlag, utils.EthRequiredBlocksFlag, utils.LegacyWhitelistFlag, @@ -121,14 +125,16 @@ var ( utils.MinerNewPayloadTimeout, utils.NATFlag, utils.NoDiscoverFlag, + utils.DiscoveryV4Flag, utils.DiscoveryV5Flag, + utils.LegacyDiscoveryV5Flag, utils.NetrestrictFlag, utils.NodeKeyFileFlag, utils.NodeKeyHexFlag, utils.DNSDiscoveryFlag, utils.DeveloperFlag, - utils.DeveloperPeriodFlag, utils.DeveloperGasLimitFlag, + utils.DeveloperPeriodFlag, utils.VMEnableDebugFlag, utils.NetworkIdFlag, utils.EthStatsURLFlag, @@ -138,7 +144,7 @@ var ( utils.GpoMaxGasPriceFlag, utils.GpoIgnoreGasPriceFlag, configFileFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags) + }, utils.NetworkFlags, utils.DatabaseFlags) rpcFlags = []cli.Flag{ utils.HTTPEnabledFlag, @@ -168,6 +174,8 @@ var ( utils.RPCGlobalEVMTimeoutFlag, utils.RPCGlobalTxFeeCapFlag, utils.AllowUnprotectedTxs, + utils.BatchRequestLimit, + utils.BatchResponseMaxSize, } metricsFlags = []cli.Flag{ @@ -235,10 +243,16 @@ func init() { debug.Flags, metricsFlags, ) + flags.AutoEnvVars(app.Flags, "GETH") app.Before = func(ctx *cli.Context) error { + maxprocs.Set() // Automatically set GOMAXPROCS to match Linux container CPU quota. flags.MigrateGlobalFlags(ctx) - return debug.Setup(ctx) + if err := debug.Setup(ctx); err != nil { + return err + } + flags.CheckEnvVars(ctx, app.Flags, "GETH") + return nil } app.After = func(ctx *cli.Context) error { debug.Exit() @@ -259,15 +273,15 @@ func main() { func prepare(ctx *cli.Context) { // If we're running a known preset, log it for convenience. switch { - case ctx.IsSet(utils.RinkebyFlag.Name): - log.Info("Starting Geth on Rinkeby testnet...") - case ctx.IsSet(utils.GoerliFlag.Name): log.Info("Starting Geth on Görli testnet...") case ctx.IsSet(utils.SepoliaFlag.Name): log.Info("Starting Geth on Sepolia testnet...") + case ctx.IsSet(utils.HoleskyFlag.Name): + log.Info("Starting Geth on Holesky testnet...") + case ctx.IsSet(utils.DeveloperFlag.Name): log.Info("Starting Geth in ephemeral dev mode...") log.Warn(`You are running Geth in --dev mode. Please note the following: @@ -292,8 +306,8 @@ func prepare(ctx *cli.Context) { // If we're a full node on mainnet without --cache specified, bump default cache allowance if ctx.String(utils.SyncModeFlag.Name) != "light" && !ctx.IsSet(utils.CacheFlag.Name) && !ctx.IsSet(utils.NetworkIdFlag.Name) { // Make sure we're not on any supported preconfigured testnet either - if !ctx.IsSet(utils.SepoliaFlag.Name) && - !ctx.IsSet(utils.RinkebyFlag.Name) && + if !ctx.IsSet(utils.HoleskyFlag.Name) && + !ctx.IsSet(utils.SepoliaFlag.Name) && !ctx.IsSet(utils.GoerliFlag.Name) && !ctx.IsSet(utils.DeveloperFlag.Name) { // Nope, we're really on mainnet. Bump that cache up! @@ -348,10 +362,7 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isCon stack.AccountManager().Subscribe(events) // Create a client to interact with local geth node. - rpcClient, err := stack.Attach() - if err != nil { - utils.Fatalf("Failed to attach to self: %v", err) - } + rpcClient := stack.Attach() ethClient := ethclient.NewClient(rpcClient) go func() { @@ -412,7 +423,7 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isCon } // Start auxiliary services if enabled - if ctx.Bool(utils.MiningEnabledFlag.Name) || ctx.Bool(utils.DeveloperFlag.Name) { + if ctx.Bool(utils.MiningEnabledFlag.Name) { // Mining only makes sense if a full Ethereum node is running if ctx.String(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support mining") @@ -423,7 +434,7 @@ func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isCon } // Set the gas price to the limits from the CLI and start mining gasprice := flags.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) - ethBackend.TxPool().SetGasPrice(gasprice) + ethBackend.TxPool().SetGasTip(gasprice) if err := ethBackend.StartMining(); err != nil { utils.Fatalf("Failed to start mining: %v", err) } diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 3f668f77db76..a4ae54676b25 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -50,9 +50,8 @@ var ( ArgsUsage: "", Action: pruneState, Flags: flags.Merge([]cli.Flag{ - utils.CacheTrieJournalFlag, utils.BloomFilterSizeFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), + }, utils.NetworkFlags, utils.DatabaseFlags), Description: ` geth snapshot prune-state will prune historical state data with the help of the state snapshot. @@ -62,10 +61,7 @@ two version states are available: genesis and the specific one. The default pruning target is the HEAD-127 state. -WARNING: It's necessary to delete the trie clean cache after the pruning. -If you specify another directory for the trie clean cache via "--cache.trie.journal" -during the use of Geth, please also specify it here for correct deletion. Otherwise -the trie clean cache with default directory will be deleted. +WARNING: it's only supported in hash mode(--state.scheme=hash)". `, }, { @@ -73,7 +69,7 @@ the trie clean cache with default directory will be deleted. Usage: "Recalculate state hash based on the snapshot for verification", ArgsUsage: "", Action: verifyState, - Flags: flags.Merge(utils.NetworkFlags, utils.DatabasePathFlags), + Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags), Description: ` geth snapshot verify-state will traverse the whole accounts and storages set based on the specified @@ -86,7 +82,7 @@ In other words, this command does the snapshot to trie conversion. Usage: "Check that there is no 'dangling' snap storage", ArgsUsage: "", Action: checkDanglingStorage, - Flags: flags.Merge(utils.NetworkFlags, utils.DatabasePathFlags), + Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags), Description: ` geth snapshot check-dangling-storage traverses the snap storage data, and verifies that all snapshot storage data has a corresponding account. @@ -97,7 +93,7 @@ data, and verifies that all snapshot storage data has a corresponding account. Usage: "Check all snapshot layers for the a specific account", ArgsUsage: "
", Action: checkAccount, - Flags: flags.Merge(utils.NetworkFlags, utils.DatabasePathFlags), + Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags), Description: ` geth snapshot inspect-account
checks all snapshot layers and prints out information about the specified address. @@ -108,7 +104,7 @@ information about the specified address. Usage: "Traverse the state with given root hash and perform quick verification", ArgsUsage: "", Action: traverseState, - Flags: flags.Merge(utils.NetworkFlags, utils.DatabasePathFlags), + Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags), Description: ` geth snapshot traverse-state will traverse the whole state from the given state root and will abort if any @@ -123,7 +119,7 @@ It's also usable without snapshot enabled. Usage: "Traverse the state with given root hash and perform detailed verification", ArgsUsage: "", Action: traverseRawState, - Flags: flags.Merge(utils.NetworkFlags, utils.DatabasePathFlags), + Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags), Description: ` geth snapshot traverse-rawstate will traverse the whole state from the given root and will abort if any referenced @@ -144,7 +140,7 @@ It's also usable without snapshot enabled. utils.ExcludeStorageFlag, utils.StartKeyFlag, utils.DumpLimitFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), + }, utils.NetworkFlags, utils.DatabaseFlags), Description: ` This command is semantically equivalent to 'geth dump', but uses the snapshots as the backend data source, making this command a lot faster. @@ -160,15 +156,17 @@ block is used. // Deprecation: this command should be deprecated once the hash-based // scheme is deprecated. func pruneState(ctx *cli.Context) error { - stack, config := makeConfigNode(ctx) + stack, _ := makeConfigNode(ctx) defer stack.Close() chaindb := utils.MakeChainDatabase(ctx, stack, false) defer chaindb.Close() + if rawdb.ReadStateScheme(chaindb) != rawdb.HashScheme { + log.Crit("Offline pruning is not required for path scheme") + } prunerconfig := pruner.Config{ Datadir: stack.ResolvePath(""), - Cachedir: stack.ResolvePath(config.Eth.TrieCleanCacheJournal), BloomSize: ctx.Uint64(utils.BloomFilterSizeFlag.Name), } pruner, err := pruner.NewPruner(chaindb, prunerconfig) @@ -211,13 +209,16 @@ func verifyState(ctx *cli.Context) error { log.Error("Failed to load head block") return errors.New("no head block") } - snapconfig := snapshot.Config{ + triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true) + defer triedb.Close() + + snapConfig := snapshot.Config{ CacheSize: 256, Recovery: false, NoBuild: true, AsyncBuild: false, } - snaptree, err := snapshot.New(snapconfig, chaindb, trie.NewDatabase(chaindb), headBlock.Root()) + snaptree, err := snapshot.New(snapConfig, chaindb, triedb, headBlock.Root()) if err != nil { log.Error("Failed to open snapshot tree", "err", err) return err @@ -248,7 +249,9 @@ func checkDanglingStorage(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() - return snapshot.CheckDanglingStorage(utils.MakeChainDatabase(ctx, stack, true)) + db := utils.MakeChainDatabase(ctx, stack, true) + defer db.Close() + return snapshot.CheckDanglingStorage(db) } // traverseState is a helper function used for pruning verification. @@ -259,6 +262,11 @@ func traverseState(ctx *cli.Context) error { defer stack.Close() chaindb := utils.MakeChainDatabase(ctx, stack, true) + defer chaindb.Close() + + triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true) + defer triedb.Close() + headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") @@ -283,7 +291,6 @@ func traverseState(ctx *cli.Context) error { root = headBlock.Root() log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) } - triedb := trie.NewDatabase(chaindb) t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb) if err != nil { log.Error("Failed to open trie", "root", root, "err", err) @@ -296,7 +303,12 @@ func traverseState(ctx *cli.Context) error { lastReport time.Time start = time.Now() ) - accIter := trie.NewIterator(t.NodeIterator(nil)) + acctIt, err := t.NodeIterator(nil) + if err != nil { + log.Error("Failed to open iterator", "root", root, "err", err) + return err + } + accIter := trie.NewIterator(acctIt) for accIter.Next() { accounts += 1 var acc types.StateAccount @@ -311,9 +323,19 @@ func traverseState(ctx *cli.Context) error { log.Error("Failed to open storage trie", "root", acc.Root, "err", err) return err } - storageIter := trie.NewIterator(storageTrie.NodeIterator(nil)) + storageIt, err := storageTrie.NodeIterator(nil) + if err != nil { + log.Error("Failed to open storage iterator", "root", acc.Root, "err", err) + return err + } + storageIter := trie.NewIterator(storageIt) for storageIter.Next() { slots += 1 + + if time.Since(lastReport) > time.Second*8 { + log.Info("Traversing state", "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + lastReport = time.Now() + } } if storageIter.Err != nil { log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Err) @@ -349,6 +371,11 @@ func traverseRawState(ctx *cli.Context) error { defer stack.Close() chaindb := utils.MakeChainDatabase(ctx, stack, true) + defer chaindb.Close() + + triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true) + defer triedb.Close() + headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") @@ -373,7 +400,6 @@ func traverseRawState(ctx *cli.Context) error { root = headBlock.Root() log.Info("Start traversing the state", "root", root, "number", headBlock.NumberU64()) } - triedb := trie.NewDatabase(chaindb) t, err := trie.NewStateTrie(trie.StateTrieID(root), triedb) if err != nil { log.Error("Failed to open trie", "root", root, "err", err) @@ -389,7 +415,16 @@ func traverseRawState(ctx *cli.Context) error { hasher = crypto.NewKeccakState() got = make([]byte, 32) ) - accIter := t.NodeIterator(nil) + accIter, err := t.NodeIterator(nil) + if err != nil { + log.Error("Failed to open iterator", "root", root, "err", err) + return err + } + reader, err := triedb.Reader(root) + if err != nil { + log.Error("State is non-existent", "root", root) + return nil + } for accIter.Next(true) { nodes += 1 node := accIter.Hash() @@ -397,7 +432,7 @@ func traverseRawState(ctx *cli.Context) error { // Check the present for non-empty hash node(embedded node doesn't // have their own hash). if node != (common.Hash{}) { - blob := rawdb.ReadLegacyTrieNode(chaindb, node) + blob, _ := reader.Node(common.Hash{}, accIter.Path(), node) if len(blob) == 0 { log.Error("Missing trie node(account)", "hash", node) return errors.New("missing account") @@ -426,7 +461,11 @@ func traverseRawState(ctx *cli.Context) error { log.Error("Failed to open storage trie", "root", acc.Root, "err", err) return errors.New("missing storage trie") } - storageIter := storageTrie.NodeIterator(nil) + storageIter, err := storageTrie.NodeIterator(nil) + if err != nil { + log.Error("Failed to open storage iterator", "root", acc.Root, "err", err) + return err + } for storageIter.Next(true) { nodes += 1 node := storageIter.Hash() @@ -434,7 +473,7 @@ func traverseRawState(ctx *cli.Context) error { // Check the presence for non-empty hash node(embedded node doesn't // have their own hash). if node != (common.Hash{}) { - blob := rawdb.ReadLegacyTrieNode(chaindb, node) + blob, _ := reader.Node(common.BytesToHash(accIter.LeafKey()), storageIter.Path(), node) if len(blob) == 0 { log.Error("Missing trie node(storage)", "hash", node) return errors.New("missing storage") @@ -451,6 +490,10 @@ func traverseRawState(ctx *cli.Context) error { if storageIter.Leaf() { slots += 1 } + if time.Since(lastReport) > time.Second*8 { + log.Info("Traversing state", "nodes", nodes, "accounts", accounts, "slots", slots, "codes", codes, "elapsed", common.PrettyDuration(time.Since(start))) + lastReport = time.Now() + } } if storageIter.Error() != nil { log.Error("Failed to traverse storage trie", "root", acc.Root, "err", storageIter.Error()) @@ -494,13 +537,16 @@ func dumpState(ctx *cli.Context) error { if err != nil { return err } + triedb := utils.MakeTrieDatabase(ctx, db, false, true) + defer triedb.Close() + snapConfig := snapshot.Config{ CacheSize: 256, Recovery: false, NoBuild: true, AsyncBuild: false, } - snaptree, err := snapshot.New(snapConfig, db, trie.NewDatabase(db), root) + snaptree, err := snapshot.New(snapConfig, db, triedb, root) if err != nil { return err } @@ -521,14 +567,14 @@ func dumpState(ctx *cli.Context) error { Root common.Hash `json:"root"` }{root}) for accIt.Next() { - account, err := snapshot.FullAccount(accIt.Account()) + account, err := types.FullAccount(accIt.Account()) if err != nil { return err } da := &state.DumpAccount{ Balance: account.Balance.String(), Nonce: account.Nonce, - Root: account.Root, + Root: account.Root.Bytes(), CodeHash: account.CodeHash, SecureKey: accIt.Hash().Bytes(), } diff --git a/cmd/geth/testdata/vcheck/minisig-sigs-new/data.json.minisig b/cmd/geth/testdata/vcheck/minisig-sigs-new/data.json.minisig new file mode 100644 index 000000000000..eaea9f9053f4 --- /dev/null +++ b/cmd/geth/testdata/vcheck/minisig-sigs-new/data.json.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RUQkliYstQBOKLK05Sy5f3bVRMBqJT26ABo6Vbp3BNJAVjejoqYCu4GWE/+7qcDfHBqYIniDCbFIUvYEnOHxV6vZ93wO1xJWDQw= +trusted comment: timestamp:1693986492 file:data.json hashed +6Fdw2H+W1ZXK7QXSF77Z5AWC7+AEFAfDmTSxNGylU5HLT1AuSJQmxslj+VjtUBamYCvOuET7plbXza942AlWDw== diff --git a/cmd/geth/verkle.go b/cmd/geth/verkle.go index e856eaeb9ce1..aa79889e8c18 100644 --- a/cmd/geth/verkle.go +++ b/cmd/geth/verkle.go @@ -45,7 +45,7 @@ var ( Usage: "verify the conversion of a MPT into a verkle tree", ArgsUsage: "", Action: verifyVerkle, - Flags: flags.Merge(utils.NetworkFlags, utils.DatabasePathFlags), + Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags), Description: ` geth verkle verify This command takes a root commitment and attempts to rebuild the tree. @@ -56,7 +56,7 @@ This command takes a root commitment and attempts to rebuild the tree. Usage: "Dump a verkle tree to a DOT file", ArgsUsage: " [ ...]", Action: expandVerkle, - Flags: flags.Merge(utils.NetworkFlags, utils.DatabasePathFlags), + Flags: flags.Merge(utils.NetworkFlags, utils.DatabaseFlags), Description: ` geth verkle dump [ ...] This command will produce a dot file representing the tree, rooted at . @@ -74,7 +74,7 @@ func checkChildren(root verkle.VerkleNode, resolver verkle.NodeResolverFn) error switch node := root.(type) { case *verkle.InternalNode: for i, child := range node.Children() { - childC := child.ComputeCommitment().Bytes() + childC := child.Commit().Bytes() childS, err := resolver(childC[:]) if bytes.Equal(childC[:], zero[:]) { @@ -86,7 +86,7 @@ func checkChildren(root verkle.VerkleNode, resolver verkle.NodeResolverFn) error // depth is set to 0, the tree isn't rebuilt so it's not a problem childN, err := verkle.ParseNode(childS, 0, childC[:]) if err != nil { - return fmt.Errorf("decode error child %x in db: %w", child.ComputeCommitment().Bytes(), err) + return fmt.Errorf("decode error child %x in db: %w", child.Commitment().Bytes(), err) } if err := checkChildren(childN, resolver); err != nil { return fmt.Errorf("%x%w", i, err) // write the path to the erroring node @@ -100,7 +100,7 @@ func checkChildren(root verkle.VerkleNode, resolver verkle.NodeResolverFn) error return nil } } - return errors.New("Both balance and nonce are 0") + return errors.New("both balance and nonce are 0") case verkle.Empty: // nothing to do default: @@ -115,6 +115,7 @@ func verifyVerkle(ctx *cli.Context) error { defer stack.Close() chaindb := utils.MakeChainDatabase(ctx, stack, true) + defer chaindb.Close() headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") @@ -163,6 +164,7 @@ func expandVerkle(ctx *cli.Context) error { defer stack.Close() chaindb := utils.MakeChainDatabase(ctx, stack, true) + defer chaindb.Close() var ( rootC common.Hash keylist [][]byte diff --git a/cmd/geth/version_check_test.go b/cmd/geth/version_check_test.go index bd4d820a7901..4458ab5c0606 100644 --- a/cmd/geth/version_check_test.go +++ b/cmd/geth/version_check_test.go @@ -30,17 +30,24 @@ import ( ) func TestVerification(t *testing.T) { - // Signatures generated with `minisign` - t.Run("minisig", func(t *testing.T) { - // For this test, the pubkey is in testdata/minisign.pub + // Signatures generated with `minisign`. Legacy format, not pre-hashed file. + t.Run("minisig-legacy", func(t *testing.T) { + // For this test, the pubkey is in testdata/vcheck/minisign.pub // (the privkey is `minisign.sec`, if we want to expand this test. Password 'test' ) pub := "RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp" testVerification(t, pub, "./testdata/vcheck/minisig-sigs/") }) + t.Run("minisig-new", func(t *testing.T) { + // For this test, the pubkey is in testdata/vcheck/minisign.pub + // (the privkey is `minisign.sec`, if we want to expand this test. Password 'test' ) + // `minisign -S -s ./minisign.sec -m data.json -x ./minisig-sigs-new/data.json.minisig` + pub := "RWQkliYstQBOKOdtClfgC3IypIPX6TAmoEi7beZ4gyR3wsaezvqOMWsp" + testVerification(t, pub, "./testdata/vcheck/minisig-sigs-new/") + }) // Signatures generated with `signify-openbsd` t.Run("signify-openbsd", func(t *testing.T) { t.Skip("This currently fails, minisign expects 4 lines of data, signify provides only 2") - // For this test, the pubkey is in testdata/signifykey.pub + // For this test, the pubkey is in testdata/vcheck/signifykey.pub // (the privkey is `signifykey.sec`, if we want to expand this test. Password 'test' ) pub := "RWSKLNhZb0KdATtRT7mZC/bybI3t3+Hv/O2i3ye04Dq9fnT9slpZ1a2/" testVerification(t, pub, "./testdata/vcheck/signify-sigs/") @@ -58,6 +65,9 @@ func testVerification(t *testing.T, pubkey, sigdir string) { if err != nil { t.Fatal(err) } + if len(files) == 0 { + t.Fatal("Missing tests") + } for _, f := range files { sig, err := os.ReadFile(filepath.Join(sigdir, f.Name())) if err != nil { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 3aad1c307af3..310a434aba5a 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -18,7 +18,6 @@ package utils import ( - "bytes" "context" "crypto/ecdsa" "encoding/hex" @@ -26,6 +25,7 @@ import ( "fmt" "math" "math/big" + "net" "net/http" "os" "path/filepath" @@ -38,16 +38,14 @@ import ( "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/fdlimit" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/txpool" - "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/eth" - ethcatalyst "github.com/ethereum/go-ethereum/eth/catalyst" + "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/filters" @@ -60,7 +58,6 @@ import ( "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/les" - lescatalyst "github.com/ethereum/go-ethereum/les/catalyst" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics/exp" @@ -72,8 +69,10 @@ import ( "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/hashdb" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" pcsclite "github.com/gballet/go-libpcsclite" gopsutil "github.com/shirou/gopsutil/mem" "github.com/urfave/cli/v2" @@ -133,7 +132,7 @@ var ( } NetworkIdFlag = &cli.Uint64Flag{ Name: "networkid", - Usage: "Explicitly set network id (integer)(For testnets: use --rinkeby, --goerli, --sepolia instead)", + Usage: "Explicitly set network id (integer)(For testnets: use --goerli, --sepolia, --holesky instead)", Value: ethconfig.Defaults.NetworkId, Category: flags.EthCategory, } @@ -142,11 +141,6 @@ var ( Usage: "Ethereum mainnet", Category: flags.EthCategory, } - RinkebyFlag = &cli.BoolFlag{ - Name: "rinkeby", - Usage: "Rinkeby network: pre-configured proof-of-authority test network", - Category: flags.EthCategory, - } GoerliFlag = &cli.BoolFlag{ Name: "goerli", Usage: "Görli network: pre-configured proof-of-authority test network", @@ -157,14 +151,18 @@ var ( Usage: "Sepolia network: pre-configured proof-of-work test network", Category: flags.EthCategory, } - + HoleskyFlag = &cli.BoolFlag{ + Name: "holesky", + Usage: "Holesky network: pre-configured proof-of-stake test network", + Category: flags.EthCategory, + } // Dev mode DeveloperFlag = &cli.BoolFlag{ Name: "dev", Usage: "Ephemeral proof-of-authority network with a pre-funded developer account, mining enabled", Category: flags.DevCategory, } - DeveloperPeriodFlag = &cli.IntFlag{ + DeveloperPeriodFlag = &cli.Uint64Flag{ Name: "dev.period", Usage: "Block period to use in developer mode (0 = mine only if transaction pending)", Category: flags.DevCategory, @@ -223,30 +221,12 @@ var ( } defaultSyncMode = ethconfig.Defaults.SyncMode - SyncModeFlag = &flags.TextMarshalerFlag{ - Name: "syncmode", - Usage: `Blockchain sync mode ("snap", "full" or "light")`, - Value: &defaultSyncMode, - Category: flags.EthCategory, - } - GCModeFlag = &cli.StringFlag{ - Name: "gcmode", - Usage: `Blockchain garbage collection mode ("full", "archive")`, - Value: "full", - Category: flags.EthCategory, - } - SnapshotFlag = &cli.BoolFlag{ + SnapshotFlag = &cli.BoolFlag{ Name: "snapshot", Usage: `Enables snapshot-database mode (default = enable)`, Value: true, Category: flags.EthCategory, } - TxLookupLimitFlag = &cli.Uint64Flag{ - Name: "txlookuplimit", - Usage: "Number of recent blocks to maintain transactions index for (default = about one year, 0 = entire chain)", - Value: ethconfig.Defaults.TxLookupLimit, - Category: flags.EthCategory, - } LightKDFFlag = &cli.BoolFlag{ Name: "lightkdf", Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength", @@ -257,11 +237,6 @@ var ( Usage: "Comma separated block number-to-hash mappings to require for peering (=)", Category: flags.EthCategory, } - LegacyWhitelistFlag = &cli.StringFlag{ - Name: "whitelist", - Usage: "Comma separated block number-to-hash mappings to enforce (=) (deprecated in favor of --eth.requiredblocks)", - Category: flags.DeprecatedCategory, - } BloomFilterSizeFlag = &cli.Uint64Flag{ Name: "bloomfilter.size", Usage: "Megabytes of memory allocated to bloom-filter for pruning", @@ -273,6 +248,40 @@ var ( Usage: "Manually specify the Cancun fork timestamp, overriding the bundled setting", Category: flags.EthCategory, } + OverrideVerkle = &cli.Uint64Flag{ + Name: "override.verkle", + Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting", + Category: flags.EthCategory, + } + SyncModeFlag = &flags.TextMarshalerFlag{ + Name: "syncmode", + Usage: `Blockchain sync mode ("snap", "full" or "light")`, + Value: &defaultSyncMode, + Category: flags.StateCategory, + } + GCModeFlag = &cli.StringFlag{ + Name: "gcmode", + Usage: `Blockchain garbage collection mode, only relevant in state.scheme=hash ("full", "archive")`, + Value: "full", + Category: flags.StateCategory, + } + StateSchemeFlag = &cli.StringFlag{ + Name: "state.scheme", + Usage: "Scheme to use for storing ethereum state ('hash' or 'path')", + Category: flags.StateCategory, + } + StateHistoryFlag = &cli.Uint64Flag{ + Name: "history.state", + Usage: "Number of recent blocks to retain state history for (default = 90,000 blocks, 0 = entire chain)", + Value: ethconfig.Defaults.StateHistory, + Category: flags.StateCategory, + } + TransactionHistoryFlag = &cli.Uint64Flag{ + Name: "history.transactions", + Usage: "Number of recent blocks to maintain transactions index for (default = about one year, 0 = entire chain)", + Value: ethconfig.Defaults.TransactionHistory, + Category: flags.StateCategory, + } // Light server and client settings LightServeFlag = &cli.IntFlag{ Name: "light.serve", @@ -298,23 +307,6 @@ var ( Value: ethconfig.Defaults.LightPeers, Category: flags.LightCategory, } - UltraLightServersFlag = &cli.StringFlag{ - Name: "ulc.servers", - Usage: "List of trusted ultra-light servers", - Value: strings.Join(ethconfig.Defaults.UltraLightServers, ","), - Category: flags.LightCategory, - } - UltraLightFractionFlag = &cli.IntFlag{ - Name: "ulc.fraction", - Usage: "Minimum % of trusted ultra-light servers required to announce a new head", - Value: ethconfig.Defaults.UltraLightFraction, - Category: flags.LightCategory, - } - UltraLightOnlyAnnounceFlag = &cli.BoolFlag{ - Name: "ulc.onlyannounce", - Usage: "Ultra light server sends announcements only", - Category: flags.LightCategory, - } LightNoPruneFlag = &cli.BoolFlag{ Name: "light.nopruning", Usage: "Disable ancient light chain data pruning", @@ -339,18 +331,18 @@ var ( TxPoolJournalFlag = &cli.StringFlag{ Name: "txpool.journal", Usage: "Disk journal for local transaction to survive node restarts", - Value: txpool.DefaultConfig.Journal, + Value: ethconfig.Defaults.TxPool.Journal, Category: flags.TxPoolCategory, } TxPoolRejournalFlag = &cli.DurationFlag{ Name: "txpool.rejournal", Usage: "Time interval to regenerate the local transaction journal", - Value: txpool.DefaultConfig.Rejournal, + Value: ethconfig.Defaults.TxPool.Rejournal, Category: flags.TxPoolCategory, } TxPoolPriceLimitFlag = &cli.Uint64Flag{ Name: "txpool.pricelimit", - Usage: "Minimum gas price limit to enforce for acceptance into the pool", + Usage: "Minimum gas price tip to enforce for acceptance into the pool", Value: ethconfig.Defaults.TxPool.PriceLimit, Category: flags.TxPoolCategory, } @@ -390,7 +382,25 @@ var ( Value: ethconfig.Defaults.TxPool.Lifetime, Category: flags.TxPoolCategory, } - + // Blob transaction pool settings + BlobPoolDataDirFlag = &cli.StringFlag{ + Name: "blobpool.datadir", + Usage: "Data directory to store blob transactions in", + Value: ethconfig.Defaults.BlobPool.Datadir, + Category: flags.BlobPoolCategory, + } + BlobPoolDataCapFlag = &cli.Uint64Flag{ + Name: "blobpool.datacap", + Usage: "Disk space to allocate for pending blob transactions (soft limit)", + Value: ethconfig.Defaults.BlobPool.Datacap, + Category: flags.BlobPoolCategory, + } + BlobPoolPriceBumpFlag = &cli.Uint64Flag{ + Name: "blobpool.pricebump", + Usage: "Price bump percentage to replace an already existing blob transaction", + Value: ethconfig.Defaults.BlobPool.PriceBump, + Category: flags.BlobPoolCategory, + } // Performance tuning settings CacheFlag = &cli.IntFlag{ Name: "cache", @@ -410,18 +420,6 @@ var ( Value: 15, Category: flags.PerfCategory, } - CacheTrieJournalFlag = &cli.StringFlag{ - Name: "cache.trie.journal", - Usage: "Disk journal directory for trie cache to survive node restarts", - Value: ethconfig.Defaults.TrieCleanCacheJournal, - Category: flags.PerfCategory, - } - CacheTrieRejournalFlag = &cli.DurationFlag{ - Name: "cache.trie.rejournal", - Usage: "Time interval to regenerate the trie cache journal", - Value: ethconfig.Defaults.TrieCleanCacheRejournal, - Category: flags.PerfCategory, - } CacheGCFlag = &cli.IntFlag{ Name: "cache.gc", Usage: "Percentage of cache memory allowance to use for trie pruning (default = 25% full mode, 0% archive mode)", @@ -592,9 +590,9 @@ var ( } // MISC settings - SyncTargetFlag = &cli.PathFlag{ + SyncTargetFlag = &cli.StringFlag{ Name: "synctarget", - Usage: `File for containing the hex-encoded block-rlp as sync target(dev feature)`, + Usage: `Hash of the block to full sync to (dev testing feature)`, TakesFile: true, Category: flags.MiscCategory, } @@ -718,6 +716,18 @@ var ( Usage: "Allow for unprotected (non EIP155 signed) transactions to be submitted via RPC", Category: flags.APICategory, } + BatchRequestLimit = &cli.IntFlag{ + Name: "rpc.batch-request-limit", + Usage: "Maximum number of requests in a batch", + Value: node.DefaultConfig.BatchRequestLimit, + Category: flags.APICategory, + } + BatchResponseMaxSize = &cli.IntFlag{ + Name: "rpc.batch-response-max-size", + Usage: "Maximum number of bytes returned from a batched call", + Value: node.DefaultConfig.BatchResponseMaxSize, + Category: flags.APICategory, + } EnablePersonal = &cli.BoolFlag{ Name: "rpc.enabledeprecatedpersonal", Usage: "Enables the (deprecated) personal namespace", @@ -770,8 +780,16 @@ var ( Usage: "Disables the peer discovery mechanism (manual peer addition)", Category: flags.NetworkingCategory, } + DiscoveryV4Flag = &cli.BoolFlag{ + Name: "discovery.v4", + Aliases: []string{"discv4"}, + Usage: "Enables the V4 discovery mechanism", + Category: flags.NetworkingCategory, + Value: true, + } DiscoveryV5Flag = &cli.BoolFlag{ - Name: "v5disc", + Name: "discovery.v5", + Aliases: []string{"discv5"}, Usage: "Enables the experimental RLPx V5 (Topic Discovery) mechanism", Category: flags.NetworkingCategory, } @@ -931,25 +949,26 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. var ( // TestnetFlags is the flag group of all built-in supported testnets. TestnetFlags = []cli.Flag{ - RinkebyFlag, GoerliFlag, SepoliaFlag, + HoleskyFlag, } // NetworkFlags is the flag group of all built-in supported networks. NetworkFlags = append([]cli.Flag{MainnetFlag}, TestnetFlags...) - // DatabasePathFlags is the flag group of all database path flags. - DatabasePathFlags = []cli.Flag{ + // DatabaseFlags is the flag group of all database flags. + DatabaseFlags = []cli.Flag{ DataDirFlag, AncientFlag, RemoteDBFlag, + StateSchemeFlag, HttpHeaderFlag, } ) func init() { if rawdb.PebbleEnabled { - DatabasePathFlags = append(DatabasePathFlags, DBEngineFlag) + DatabaseFlags = append(DatabaseFlags, DBEngineFlag) } } @@ -958,15 +977,15 @@ func init() { // then a subdirectory of the specified datadir will be used. func MakeDataDir(ctx *cli.Context) string { if path := ctx.String(DataDirFlag.Name); path != "" { - if ctx.Bool(RinkebyFlag.Name) { - return filepath.Join(path, "rinkeby") - } if ctx.Bool(GoerliFlag.Name) { return filepath.Join(path, "goerli") } if ctx.Bool(SepoliaFlag.Name) { return filepath.Join(path, "sepolia") } + if ctx.Bool(HoleskyFlag.Name) { + return filepath.Join(path, "holesky") + } return path } Fatalf("Cannot determine default data directory, please set manually (--datadir)") @@ -975,7 +994,7 @@ func MakeDataDir(ctx *cli.Context) string { // setNodeKey creates a node key from set command line flags, either loading it // from a file or as a specified hex value. If neither flags were provided, this -// method returns nil and an emphemeral key is to be generated. +// method returns nil and an ephemeral key is to be generated. func setNodeKey(ctx *cli.Context, cfg *p2p.Config) { var ( hex = ctx.String(NodeKeyHexFlag.Name) @@ -1008,35 +1027,45 @@ func setNodeUserIdent(ctx *cli.Context, cfg *node.Config) { // setBootstrapNodes creates a list of bootstrap nodes from the command line // flags, reverting to pre-configured ones if none have been specified. +// Priority order for bootnodes configuration: +// +// 1. --bootnodes flag +// 2. Config file +// 3. Network preset flags (e.g. --goerli) +// 4. default to mainnet nodes func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { urls := params.MainnetBootnodes - switch { - case ctx.IsSet(BootnodesFlag.Name): + if ctx.IsSet(BootnodesFlag.Name) { urls = SplitAndTrim(ctx.String(BootnodesFlag.Name)) - case ctx.Bool(SepoliaFlag.Name): - urls = params.SepoliaBootnodes - case ctx.Bool(RinkebyFlag.Name): - urls = params.RinkebyBootnodes - case ctx.Bool(GoerliFlag.Name): - urls = params.GoerliBootnodes - } - - // don't apply defaults if BootstrapNodes is already set - if cfg.BootstrapNodes != nil { - return + } else { + if cfg.BootstrapNodes != nil { + return // Already set by config file, don't apply defaults. + } + switch { + case ctx.Bool(HoleskyFlag.Name): + urls = params.HoleskyBootnodes + case ctx.Bool(SepoliaFlag.Name): + urls = params.SepoliaBootnodes + case ctx.Bool(GoerliFlag.Name): + urls = params.GoerliBootnodes + } } + cfg.BootstrapNodes = mustParseBootnodes(urls) +} - cfg.BootstrapNodes = make([]*enode.Node, 0, len(urls)) +func mustParseBootnodes(urls []string) []*enode.Node { + nodes := make([]*enode.Node, 0, len(urls)) for _, url := range urls { if url != "" { node, err := enode.Parse(enode.ValidSchemes, url) if err != nil { log.Crit("Bootstrap URL invalid", "enode", url, "err", err) - continue + return nil } - cfg.BootstrapNodes = append(cfg.BootstrapNodes, node) + nodes = append(nodes, node) } } + return nodes } // setBootstrapNodesV5 creates a list of bootstrap nodes from the command line @@ -1141,6 +1170,14 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) { if ctx.IsSet(AllowUnprotectedTxs.Name) { cfg.AllowUnprotectedTxs = ctx.Bool(AllowUnprotectedTxs.Name) } + + if ctx.IsSet(BatchRequestLimit.Name) { + cfg.BatchRequestLimit = ctx.Int(BatchRequestLimit.Name) + } + + if ctx.IsSet(BatchResponseMaxSize.Name) { + cfg.BatchResponseMaxSize = ctx.Int(BatchResponseMaxSize.Name) + } } // setGraphQL creates the GraphQL listener interface string from the set @@ -1206,19 +1243,6 @@ func setLes(ctx *cli.Context, cfg *ethconfig.Config) { if ctx.IsSet(LightMaxPeersFlag.Name) { cfg.LightPeers = ctx.Int(LightMaxPeersFlag.Name) } - if ctx.IsSet(UltraLightServersFlag.Name) { - cfg.UltraLightServers = strings.Split(ctx.String(UltraLightServersFlag.Name), ",") - } - if ctx.IsSet(UltraLightFractionFlag.Name) { - cfg.UltraLightFraction = ctx.Int(UltraLightFractionFlag.Name) - } - if cfg.UltraLightFraction <= 0 && cfg.UltraLightFraction > 100 { - log.Error("Ultra light fraction is invalid", "had", cfg.UltraLightFraction, "updated", ethconfig.Defaults.UltraLightFraction) - cfg.UltraLightFraction = ethconfig.Defaults.UltraLightFraction - } - if ctx.IsSet(UltraLightOnlyAnnounceFlag.Name) { - cfg.UltraLightOnlyAnnounce = ctx.Bool(UltraLightOnlyAnnounceFlag.Name) - } if ctx.IsSet(LightNoPruneFlag.Name) { cfg.LightNoPrune = ctx.Bool(LightNoPruneFlag.Name) } @@ -1359,13 +1383,17 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { cfg.NoDiscovery = true } - // if we're running a light client or server, force enable the v5 peer discovery - // unless it is explicitly disabled with --nodiscover note that explicitly specifying - // --v5disc overrides --nodiscover, in which case the later only disables v4 discovery - forceV5Discovery := (lightClient || lightServer) && !ctx.Bool(NoDiscoverFlag.Name) - if ctx.IsSet(DiscoveryV5Flag.Name) { - cfg.DiscoveryV5 = ctx.Bool(DiscoveryV5Flag.Name) - } else if forceV5Discovery { + // Disallow --nodiscover when used in conjunction with light mode. + if (lightClient || lightServer) && ctx.Bool(NoDiscoverFlag.Name) { + Fatalf("Cannot use --" + NoDiscoverFlag.Name + " in light client or light server mode") + } + CheckExclusive(ctx, DiscoveryV4Flag, NoDiscoverFlag) + CheckExclusive(ctx, DiscoveryV5Flag, NoDiscoverFlag) + cfg.DiscoveryV4 = ctx.Bool(DiscoveryV4Flag.Name) + cfg.DiscoveryV5 = ctx.Bool(DiscoveryV5Flag.Name) + + // If we're running a light client or server, force enable the v5 peer discovery. + if lightClient || lightServer { cfg.DiscoveryV5 = true } @@ -1464,12 +1492,12 @@ func SetDataDir(ctx *cli.Context, cfg *node.Config) { cfg.DataDir = ctx.String(DataDirFlag.Name) case ctx.Bool(DeveloperFlag.Name): cfg.DataDir = "" // unless explicitly requested, use memory databases - case ctx.Bool(RinkebyFlag.Name) && cfg.DataDir == node.DefaultDataDir(): - cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby") case ctx.Bool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "goerli") case ctx.Bool(SepoliaFlag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "sepolia") + case ctx.Bool(HoleskyFlag.Name) && cfg.DataDir == node.DefaultDataDir(): + cfg.DataDir = filepath.Join(node.DefaultDataDir(), "holesky") } } @@ -1493,7 +1521,7 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) { } } -func setTxPool(ctx *cli.Context, cfg *txpool.Config) { +func setTxPool(ctx *cli.Context, cfg *legacypool.Config) { if ctx.IsSet(TxPoolLocalsFlag.Name) { locals := strings.Split(ctx.String(TxPoolLocalsFlag.Name), ",") for _, account := range locals { @@ -1626,16 +1654,11 @@ func CheckExclusive(ctx *cli.Context, args ...interface{}) { // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { // Avoid conflicting network flags - CheckExclusive(ctx, MainnetFlag, DeveloperFlag, RinkebyFlag, GoerliFlag, SepoliaFlag) + CheckExclusive(ctx, MainnetFlag, DeveloperFlag, GoerliFlag, SepoliaFlag, HoleskyFlag) CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer - if ctx.String(GCModeFlag.Name) == "archive" && ctx.Uint64(TxLookupLimitFlag.Name) != 0 { - ctx.Set(TxLookupLimitFlag.Name, "0") - log.Warn("Disable transaction unindexing for archive node") - } - if ctx.IsSet(LightServeFlag.Name) && ctx.Uint64(TxLookupLimitFlag.Name) != 0 { - log.Warn("LES server cannot serve old transaction status and cannot connect below les/4 protocol version if transaction lookup index is limited") - } + + // Set configurations from CLI flags setEtherbase(ctx, cfg) setGPO(ctx, &cfg.GPO, ctx.String(SyncModeFlag.Name) == "light") setTxPool(ctx, &cfg.TxPool) @@ -1663,7 +1686,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { log.Debug("Sanitizing Go's GC trigger", "percent", int(gogc)) godebug.SetGCPercent(int(gogc)) - if ctx.IsSet(SyncModeFlag.Name) { + if ctx.IsSet(SyncTargetFlag.Name) { + cfg.SyncMode = downloader.FullSync // dev sync target forces full sync + } else if ctx.IsSet(SyncModeFlag.Name) { cfg.SyncMode = *flags.GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode) } if ctx.IsSet(NetworkIdFlag.Name) { @@ -1692,17 +1717,33 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.Preimages = true log.Info("Enabling recording of key preimages since archive mode is used") } - if ctx.IsSet(TxLookupLimitFlag.Name) { - cfg.TxLookupLimit = ctx.Uint64(TxLookupLimitFlag.Name) + if ctx.IsSet(StateHistoryFlag.Name) { + cfg.StateHistory = ctx.Uint64(StateHistoryFlag.Name) } - if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheTrieFlag.Name) { - cfg.TrieCleanCache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheTrieFlag.Name) / 100 + if ctx.IsSet(StateSchemeFlag.Name) { + cfg.StateScheme = ctx.String(StateSchemeFlag.Name) + } + // Parse transaction history flag, if user is still using legacy config + // file with 'TxLookupLimit' configured, copy the value to 'TransactionHistory'. + if cfg.TransactionHistory == ethconfig.Defaults.TransactionHistory && cfg.TxLookupLimit != ethconfig.Defaults.TxLookupLimit { + log.Warn("The config option 'TxLookupLimit' is deprecated and will be removed, please use 'TransactionHistory'") + cfg.TransactionHistory = cfg.TxLookupLimit + } + if ctx.IsSet(TransactionHistoryFlag.Name) { + cfg.TransactionHistory = ctx.Uint64(TransactionHistoryFlag.Name) + } else if ctx.IsSet(TxLookupLimitFlag.Name) { + log.Warn("The flag --txlookuplimit is deprecated and will be removed, please use --history.transactions") + cfg.TransactionHistory = ctx.Uint64(TxLookupLimitFlag.Name) } - if ctx.IsSet(CacheTrieJournalFlag.Name) { - cfg.TrieCleanCacheJournal = ctx.String(CacheTrieJournalFlag.Name) + if ctx.String(GCModeFlag.Name) == "archive" && cfg.TransactionHistory != 0 { + cfg.TransactionHistory = 0 + log.Warn("Disabled transaction unindexing for archive node") } - if ctx.IsSet(CacheTrieRejournalFlag.Name) { - cfg.TrieCleanCacheRejournal = ctx.Duration(CacheTrieRejournalFlag.Name) + if ctx.IsSet(LightServeFlag.Name) && cfg.TransactionHistory != 0 { + log.Warn("LES server cannot serve old transaction status and cannot connect below les/4 protocol version if transaction lookup index is limited") + } + if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheTrieFlag.Name) { + cfg.TrieCleanCache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheTrieFlag.Name) / 100 } if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheGCFlag.Name) { cfg.TrieDirtyCache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheGCFlag.Name) / 100 @@ -1762,28 +1803,18 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { } cfg.Genesis = core.DefaultGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.MainnetGenesisHash) + case ctx.Bool(HoleskyFlag.Name): + if !ctx.IsSet(NetworkIdFlag.Name) { + cfg.NetworkId = 17000 + } + cfg.Genesis = core.DefaultHoleskyGenesisBlock() + SetDNSDiscoveryDefaults(cfg, params.HoleskyGenesisHash) case ctx.Bool(SepoliaFlag.Name): if !ctx.IsSet(NetworkIdFlag.Name) { cfg.NetworkId = 11155111 } cfg.Genesis = core.DefaultSepoliaGenesisBlock() SetDNSDiscoveryDefaults(cfg, params.SepoliaGenesisHash) - case ctx.Bool(RinkebyFlag.Name): - log.Warn("") - log.Warn("--------------------------------------------------------------------------------") - log.Warn("Please note, Rinkeby has been deprecated. It will still work for the time being,") - log.Warn("but there will be no further hard-forks shipped for it.") - log.Warn("The network will be permanently halted in Q2/Q3 of 2023.") - log.Warn("For the most future proof testnet, choose Sepolia as") - log.Warn("your replacement environment (--sepolia instead of --rinkeby).") - log.Warn("--------------------------------------------------------------------------------") - log.Warn("") - - if !ctx.IsSet(NetworkIdFlag.Name) { - cfg.NetworkId = 4 - } - cfg.Genesis = core.DefaultRinkebyGenesisBlock() - SetDNSDiscoveryDefaults(cfg, params.RinkebyGenesisHash) case ctx.Bool(GoerliFlag.Name): if !ctx.IsSet(NetworkIdFlag.Name) { cfg.NetworkId = 5 @@ -1839,17 +1870,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { log.Info("Using developer account", "address", developer.Address) // Create a new developer genesis block or reuse existing one - cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.Int(DeveloperPeriodFlag.Name)), ctx.Uint64(DeveloperGasLimitFlag.Name), developer.Address) + cfg.Genesis = core.DeveloperGenesisBlock(ctx.Uint64(DeveloperGasLimitFlag.Name), developer.Address) if ctx.IsSet(DataDirFlag.Name) { - // If datadir doesn't exist we need to open db in write-mode - // so leveldb can create files. - readonly := true - if !common.FileExist(stack.ResolvePath("chaindata")) { - readonly = false - } - // Check if we have an already initialized chain and fall back to - // that if so. Otherwise we need to generate a new genesis spec. - chaindb := MakeChainDatabase(ctx, stack, readonly) + chaindb := tryMakeReadOnlyDatabase(ctx, stack) if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) { cfg.Genesis = nil // fallback to db content } @@ -1899,9 +1922,6 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend Fatalf("Failed to register the Ethereum service: %v", err) } stack.RegisterAPIs(tracers.APIs(backend.ApiBackend)) - if err := lescatalyst.Register(stack, backend); err != nil { - Fatalf("Failed to register the Engine API service: %v", err) - } return backend.ApiBackend, nil } backend, err := eth.New(stack, cfg) @@ -1914,9 +1934,6 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend Fatalf("Failed to create the LES server: %v", err) } } - if err := ethcatalyst.Register(stack, backend); err != nil { - Fatalf("Failed to register the Engine API service: %v", err) - } stack.RegisterAPIs(tracers.APIs(backend.APIBackend)) return backend.APIBackend, backend } @@ -1950,21 +1967,9 @@ func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconf } // RegisterFullSyncTester adds the full-sync tester service into node. -func RegisterFullSyncTester(stack *node.Node, eth *eth.Ethereum, path string) { - blob, err := os.ReadFile(path) - if err != nil { - Fatalf("Failed to read block file: %v", err) - } - rlpBlob, err := hexutil.Decode(string(bytes.TrimRight(blob, "\r\n"))) - if err != nil { - Fatalf("Failed to decode block blob: %v", err) - } - var block types.Block - if err := rlp.DecodeBytes(rlpBlob, &block); err != nil { - Fatalf("Failed to decode block: %v", err) - } - ethcatalyst.RegisterFullSyncTester(stack, eth, &block) - log.Info("Registered full-sync tester", "number", block.NumberU64(), "hash", block.Hash()) +func RegisterFullSyncTester(stack *node.Node, eth *eth.Ethereum, target common.Hash) { + catalyst.RegisterFullSyncTester(stack, eth, target) + log.Info("Registered full-sync tester", "hash", target) } func SetupMetrics(ctx *cli.Context) { @@ -2019,7 +2024,7 @@ func SetupMetrics(ctx *cli.Context) { } if ctx.IsSet(MetricsHTTPFlag.Name) { - address := fmt.Sprintf("%s:%d", ctx.String(MetricsHTTPFlag.Name), ctx.Int(MetricsPortFlag.Name)) + address := net.JoinHostPort(ctx.String(MetricsHTTPFlag.Name), fmt.Sprintf("%d", ctx.Int(MetricsPortFlag.Name))) log.Info("Enabling stand-alone metrics HTTP endpoint", "address", address) exp.Setup(address) } else if ctx.IsSet(MetricsPortFlag.Name) { @@ -2073,6 +2078,18 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb. return chainDb } +// tryMakeReadOnlyDatabase try to open the chain database in read-only mode, +// or fallback to write mode if the database is not initialized. +func tryMakeReadOnlyDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database { + // If the database doesn't exist we need to open it in write-mode to allow + // the engine to create files. + readonly := true + if rawdb.PreexistingDatabase(stack.ResolvePath("chaindata")) == "" { + readonly = false + } + return MakeChainDatabase(ctx, stack, readonly) +} + func IsNetworkPreset(ctx *cli.Context) bool { for _, flag := range NetworkFlags { bFlag, _ := flag.(*cli.BoolFlag) @@ -2094,7 +2111,7 @@ func DialRPCWithHeaders(endpoint string, headers []string) (*rpc.Client, error) } var opts []rpc.ClientOption if len(headers) > 0 { - var customHeaders = make(http.Header) + customHeaders := make(http.Header) for _, h := range headers { kv := strings.Split(h, ":") if len(kv) != 2 { @@ -2112,10 +2129,10 @@ func MakeGenesis(ctx *cli.Context) *core.Genesis { switch { case ctx.Bool(MainnetFlag.Name): genesis = core.DefaultGenesisBlock() + case ctx.Bool(HoleskyFlag.Name): + genesis = core.DefaultHoleskyGenesisBlock() case ctx.Bool(SepoliaFlag.Name): genesis = core.DefaultSepoliaGenesisBlock() - case ctx.Bool(RinkebyFlag.Name): - genesis = core.DefaultRinkebyGenesisBlock() case ctx.Bool(GoerliFlag.Name): genesis = core.DefaultGoerliGenesisBlock() case ctx.Bool(DeveloperFlag.Name): @@ -2141,6 +2158,10 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh if gcmode := ctx.String(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" { Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name) } + scheme, err := rawdb.ParseStateScheme(ctx.String(StateSchemeFlag.Name), chainDb) + if err != nil { + Fatalf("%v", err) + } cache := &core.CacheConfig{ TrieCleanLimit: ethconfig.Defaults.TrieCleanCache, TrieCleanNoPrefetch: ctx.Bool(CacheNoPrefetchFlag.Name), @@ -2149,6 +2170,8 @@ func MakeChain(ctx *cli.Context, stack *node.Node, readonly bool) (*core.BlockCh TrieTimeLimit: ethconfig.Defaults.TrieTimeout, SnapshotLimit: ethconfig.Defaults.SnapshotCache, Preimages: ctx.Bool(CachePreimagesFlag.Name), + StateScheme: scheme, + StateHistory: ctx.Uint64(StateHistoryFlag.Name), } if cache.TrieDirtyDisabled && !cache.Preimages { cache.Preimages = true @@ -2193,3 +2216,27 @@ func MakeConsolePreloads(ctx *cli.Context) []string { } return preloads } + +// MakeTrieDatabase constructs a trie database based on the configured scheme. +func MakeTrieDatabase(ctx *cli.Context, disk ethdb.Database, preimage bool, readOnly bool) *trie.Database { + config := &trie.Config{ + Preimages: preimage, + } + scheme, err := rawdb.ParseStateScheme(ctx.String(StateSchemeFlag.Name), disk) + if err != nil { + Fatalf("%v", err) + } + if scheme == rawdb.HashScheme { + // Read-only mode is not implemented in hash mode, + // ignore the parameter silently. TODO(rjl493456442) + // please config it if read mode is implemented. + config.HashDB = hashdb.Defaults + return trie.NewDatabase(disk, config) + } + if readOnly { + config.PathDB = pathdb.ReadOnly + } else { + config.PathDB = pathdb.Defaults + } + return trie.NewDatabase(disk, config) +} diff --git a/cmd/utils/flags_legacy.go b/cmd/utils/flags_legacy.go index 930b68fb91d0..6669ff176fdd 100644 --- a/cmd/utils/flags_legacy.go +++ b/cmd/utils/flags_legacy.go @@ -19,6 +19,7 @@ package utils import ( "fmt" + "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/flags" "github.com/urfave/cli/v2" ) @@ -33,15 +34,49 @@ var ShowDeprecated = &cli.Command{ var DeprecatedFlags = []cli.Flag{ NoUSBFlag, + LegacyWhitelistFlag, + CacheTrieJournalFlag, + CacheTrieRejournalFlag, + LegacyDiscoveryV5Flag, + TxLookupLimitFlag, } var ( - // (Deprecated May 2020, shown in aliased flags section) + // Deprecated May 2020, shown in aliased flags section NoUSBFlag = &cli.BoolFlag{ Name: "nousb", Usage: "Disables monitoring for and managing USB hardware wallets (deprecated)", Category: flags.DeprecatedCategory, } + // Deprecated March 2022 + LegacyWhitelistFlag = &cli.StringFlag{ + Name: "whitelist", + Usage: "Comma separated block number-to-hash mappings to enforce (=) (deprecated in favor of --eth.requiredblocks)", + Category: flags.DeprecatedCategory, + } + // Deprecated July 2023 + CacheTrieJournalFlag = &cli.StringFlag{ + Name: "cache.trie.journal", + Usage: "Disk journal directory for trie cache to survive node restarts", + Category: flags.DeprecatedCategory, + } + CacheTrieRejournalFlag = &cli.DurationFlag{ + Name: "cache.trie.rejournal", + Usage: "Time interval to regenerate the trie cache journal", + Category: flags.DeprecatedCategory, + } + LegacyDiscoveryV5Flag = &cli.BoolFlag{ + Name: "v5disc", + Usage: "Enables the experimental RLPx V5 (Topic Discovery) mechanism (deprecated, use --discv5 instead)", + Category: flags.DeprecatedCategory, + } + // Deprecated August 2023 + TxLookupLimitFlag = &cli.Uint64Flag{ + Name: "txlookuplimit", + Usage: "Number of recent blocks to maintain transactions index for (default = about one year, 0 = entire chain) (deprecated, use history.transactions instead)", + Value: ethconfig.Defaults.TransactionHistory, + Category: flags.DeprecatedCategory, + } ) // showDeprecated displays deprecated flags that will be soon removed from the codebase. diff --git a/common/lru/basiclru_test.go b/common/lru/basiclru_test.go index e2d3559f55d3..29812bda157a 100644 --- a/common/lru/basiclru_test.go +++ b/common/lru/basiclru_test.go @@ -170,6 +170,20 @@ func TestBasicLRUContains(t *testing.T) { } } +// Test that Peek doesn't update recent-ness +func TestBasicLRUPeek(t *testing.T) { + cache := NewBasicLRU[int, int](2) + cache.Add(1, 1) + cache.Add(2, 2) + if v, ok := cache.Peek(1); !ok || v != 1 { + t.Errorf("1 should be set to 1") + } + cache.Add(3, 3) + if cache.Contains(1) { + t.Errorf("should not have updated recent-ness of 1") + } +} + func BenchmarkLRU(b *testing.B) { var ( capacity = 1000 diff --git a/common/math/big.go b/common/math/big.go index 1c2afa749abd..013c0ba4b66f 100644 --- a/common/math/big.go +++ b/common/math/big.go @@ -82,7 +82,7 @@ func (i *HexOrDecimal256) MarshalText() ([]byte, error) { // it however accepts either "0x"-prefixed (hex encoded) or non-prefixed (decimal) type Decimal256 big.Int -// NewHexOrDecimal256 creates a new Decimal256 +// NewDecimal256 creates a new Decimal256 func NewDecimal256(x int64) *Decimal256 { b := big.NewInt(x) d := Decimal256(*b) diff --git a/common/math/integer_test.go b/common/math/integer_test.go index b31c7c26c262..209331f25a63 100644 --- a/common/math/integer_test.go +++ b/common/math/integer_test.go @@ -17,6 +17,7 @@ package math import ( + "encoding/json" "testing" ) @@ -114,3 +115,78 @@ func TestMustParseUint64Panic(t *testing.T) { }() MustParseUint64("ggg") } + +type marshalUnMarshalTest struct { + input interface{} + want interface{} + wantErr bool // if true, decoding must fail on any platform +} + +var ( + marshalHexOrDecimal64Tests = []marshalUnMarshalTest{ + {input: uint64(0), want: "0x0"}, + {input: uint64(1), want: "0x1"}, + {input: uint64(16), want: "0x10"}, + {input: uint64(255), want: "0xff"}, + {input: uint64(0xff), want: "0xff"}, + {input: uint64(0x1122334455667788), want: "0x1122334455667788"}, + } + + UnMarshalHexOrDecimal64Tests = []marshalUnMarshalTest{ + // invalid encoding + {input: "", wantErr: true}, + {input: "null", wantErr: true}, + {input: "\"0x\"", wantErr: true}, + {input: "\"0xfffffffffffffffff\"", wantErr: true}, + {input: `"1ab"`, wantErr: true}, + {input: "\"xx\"", wantErr: true}, + {input: "\"0x1zz01\"", wantErr: true}, + + // valid encoding + {input: `""`, want: uint64(0)}, + {input: `"0"`, want: uint64(0)}, + {input: `"10"`, want: uint64(10)}, + {input: `"100"`, want: uint64(100)}, + {input: `"12344678"`, want: uint64(12344678)}, + {input: `"1111111111111111"`, want: uint64(1111111111111111)}, + {input: "\"0x0\"", want: uint64(0)}, + {input: "\"0x2\"", want: uint64(0x2)}, + {input: "\"0x2F2\"", want: uint64(0x2f2)}, + {input: "\"0x1122aaff\"", want: uint64(0x1122aaff)}, + {input: "\"0xbbb\"", want: uint64(0xbbb)}, + {input: "\"0xffffffffffffffff\"", want: uint64(0xffffffffffffffff)}, + } +) + +func TestMarshalHexOrDecimal64(t *testing.T) { + for _, test := range marshalHexOrDecimal64Tests { + in := test.input.(uint64) + out, err := json.Marshal(HexOrDecimal64(in)) + if err != nil { + t.Errorf("%d: %v", in, err) + continue + } + if want := `"` + test.want.(string) + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + } +} + +func TestUnMarshalHexOrDecimal64(t *testing.T) { + for _, test := range UnMarshalHexOrDecimal64Tests { + var v HexOrDecimal64 + err := json.Unmarshal([]byte(test.input.(string)), &v) + if test.wantErr { + if err == nil { + t.Errorf("%s: UnMarshalJSON did not error on invalid encoding: got %q, want ", test.input, err) + } + continue + } + + if uint64(v) != test.want.(uint64) { + t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) + continue + } + } +} diff --git a/common/types.go b/common/types.go index bdfd964237bd..7184b2b11220 100644 --- a/common/types.go +++ b/common/types.go @@ -65,6 +65,11 @@ func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } // If b is larger than len(h), b will be cropped from the left. func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) } +// Cmp compares two hashes. +func (h Hash) Cmp(other Hash) int { + return bytes.Compare(h[:], other[:]) +} + // Bytes gets the byte representation of the underlying hash. func (h Hash) Bytes() []byte { return h[:] } @@ -226,12 +231,14 @@ func IsHexAddress(s string) bool { return len(s) == 2*AddressLength && isHex(s) } +// Cmp compares two addresses. +func (a Address) Cmp(other Address) int { + return bytes.Compare(a[:], other[:]) +} + // Bytes gets the string representation of the underlying address. func (a Address) Bytes() []byte { return a[:] } -// Hash converts an address to a hash by left-padding it with zeros. -func (a Address) Hash() Hash { return BytesToHash(a[:]) } - // Big converts an address to a big integer. func (a Address) Big() *big.Int { return new(big.Int).SetBytes(a[:]) } diff --git a/common/types_test.go b/common/types_test.go index ad892671b5b3..cec689ea39f7 100644 --- a/common/types_test.go +++ b/common/types_test.go @@ -25,6 +25,7 @@ import ( "reflect" "strings" "testing" + "time" ) func TestBytesConversion(t *testing.T) { @@ -583,3 +584,14 @@ func TestAddressEIP55(t *testing.T) { t.Fatal("Unexpected address after unmarshal") } } + +func BenchmarkPrettyDuration(b *testing.B) { + var x = PrettyDuration(time.Duration(int64(1203123912312))) + b.Logf("Pre %s", time.Duration(x).String()) + var a string + b.ResetTimer() + for i := 0; i < b.N; i++ { + a = x.String() + } + b.Logf("Post %s", a) +} diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index c34e46751c10..f160850017f8 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -23,7 +23,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -257,24 +258,36 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa return consensus.ErrInvalidNumber } // Verify the header's EIP-1559 attributes. - if err := misc.VerifyEip1559Header(chain.Config(), parent, header); err != nil { + if err := eip1559.VerifyEIP1559Header(chain.Config(), parent, header); err != nil { return err } + arbosVersion := types.DeserializeHeaderExtraInformation(header).ArbOSFormatVersion // Verify existence / non-existence of withdrawalsHash. - shanghai := chain.Config().IsShanghai(header.Number, header.Time, types.DeserializeHeaderExtraInformation(header).ArbOSFormatVersion) + shanghai := chain.Config().IsShanghai(header.Number, header.Time, arbosVersion) if shanghai && header.WithdrawalsHash == nil { return errors.New("missing withdrawalsHash") } if !shanghai && header.WithdrawalsHash != nil { return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) } - // Verify the existence / non-existence of excessDataGas - cancun := chain.Config().IsCancun(header.Number, header.Time) - if cancun && header.ExcessDataGas == nil { - return errors.New("missing excessDataGas") - } - if !cancun && header.ExcessDataGas != nil { - return fmt.Errorf("invalid excessDataGas: have %d, expected nil", header.ExcessDataGas) + // Verify the existence / non-existence of cancun-specific header fields + cancun := chain.Config().IsCancun(header.Number, header.Time, arbosVersion) + if !cancun { + switch { + case header.ExcessBlobGas != nil: + return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas) + case header.BlobGasUsed != nil: + return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed) + case header.ParentBeaconRoot != nil: + return fmt.Errorf("invalid parentBeaconRoot, have %#x, expected nil", header.ParentBeaconRoot) + } + } else { + if header.ParentBeaconRoot == nil { + return errors.New("header is missing beaconRoot") + } + if err := eip4844.VerifyEIP4844Header(parent, header); err != nil { + return err + } } return nil } diff --git a/consensus/clique/api.go b/consensus/clique/api.go index cb270d321d5a..374b50692d80 100644 --- a/consensus/clique/api.go +++ b/consensus/clique/api.go @@ -205,7 +205,7 @@ func (sb *blockNumberOrHashOrRLP) UnmarshalJSON(data []byte) error { } // GetSigner returns the signer for a specific clique block. -// Can be called with either a blocknumber, blockhash or an rlp encoded blob. +// Can be called with a block number, a block hash or a rlp encoded blob. // The RLP encoded blob can either be a block or a header. func (api *API) GetSigner(rlpOrBlockNr *blockNumberOrHashOrRLP) (common.Address, error) { if len(rlpOrBlockNr.RLP) == 0 { diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index dc996cbae7f6..4508417daa1b 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -33,6 +33,7 @@ import ( lru "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -298,10 +299,11 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H if header.GasLimit > params.MaxGasLimit { return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit) } - if chain.Config().IsShanghai(header.Number, header.Time, types.DeserializeHeaderExtraInformation(header).ArbOSFormatVersion) { + arbosVersion := types.DeserializeHeaderExtraInformation(header).ArbOSFormatVersion + if chain.Config().IsShanghai(header.Number, header.Time, arbosVersion) { return errors.New("clique does not support shanghai fork") } - if chain.Config().IsCancun(header.Number, header.Time) { + if chain.Config().IsCancun(header.Number, header.Time, arbosVersion) { return errors.New("clique does not support cancun fork") } // All basic checks passed, verify cascading fields @@ -343,7 +345,7 @@ func (c *Clique) verifyCascadingFields(chain consensus.ChainHeaderReader, header if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil { return err } - } else if err := misc.VerifyEip1559Header(chain.Config(), parent, header); err != nil { + } else if err := eip1559.VerifyEIP1559Header(chain.Config(), parent, header); err != nil { // Verify the header's EIP-1559 attributes. return err } diff --git a/consensus/clique/clique_test.go b/consensus/clique/clique_test.go index 904b7ab6c8b1..6e2b17c8c86c 100644 --- a/consensus/clique/clique_test.go +++ b/consensus/clique/clique_test.go @@ -30,7 +30,7 @@ import ( ) // This test case is a repro of an annoying bug that took us forever to catch. -// In Clique PoA networks (Rinkeby, Görli, etc), consecutive blocks might have +// In Clique PoA networks (Görli, etc), consecutive blocks might have // the same state root (no block subsidy, empty block). If a node crashes, the // chain ends up losing the recent state and needs to regenerate it from blocks // already in the database. The bug was that processing the block *prior* to an diff --git a/consensus/clique/snapshot.go b/consensus/clique/snapshot.go index e5efa5108fd6..a97115121b82 100644 --- a/consensus/clique/snapshot.go +++ b/consensus/clique/snapshot.go @@ -19,7 +19,6 @@ package clique import ( "bytes" "encoding/json" - "sort" "time" "github.com/ethereum/go-ethereum/common" @@ -29,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "golang.org/x/exp/slices" ) // Vote represents a single vote that an authorized signer made to modify the @@ -62,13 +62,6 @@ type Snapshot struct { Tally map[common.Address]Tally `json:"tally"` // Current vote tally to avoid recalculating } -// signersAscending implements the sort interface to allow sorting a list of addresses -type signersAscending []common.Address - -func (s signersAscending) Len() int { return len(s) } -func (s signersAscending) Less(i, j int) bool { return bytes.Compare(s[i][:], s[j][:]) < 0 } -func (s signersAscending) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - // newSnapshot creates a new snapshot with the specified startup parameters. This // method does not initialize the set of recent signers, so only ever use if for // the genesis block. @@ -315,7 +308,7 @@ func (s *Snapshot) signers() []common.Address { for sig := range s.Signers { sigs = append(sigs, sig) } - sort.Sort(signersAscending(sigs)) + slices.SortFunc(sigs, common.Address.Cmp) return sigs } diff --git a/consensus/clique/snapshot_test.go b/consensus/clique/snapshot_test.go index da33049d75a0..ada234c9f6f5 100644 --- a/consensus/clique/snapshot_test.go +++ b/consensus/clique/snapshot_test.go @@ -21,7 +21,6 @@ import ( "crypto/ecdsa" "fmt" "math/big" - "sort" "testing" "github.com/ethereum/go-ethereum/common" @@ -31,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "golang.org/x/exp/slices" ) // testerAccountPool is a pool to maintain currently active tester accounts, @@ -53,7 +53,7 @@ func (ap *testerAccountPool) checkpoint(header *types.Header, signers []string) for i, signer := range signers { auths[i] = ap.address(signer) } - sort.Sort(signersAscending(auths)) + slices.SortFunc(auths, common.Address.Cmp) for i, auth := range auths { copy(header.Extra[extraVanity+i*common.AddressLength:], auth.Bytes()) } diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 06c5429f88fd..82ef8bd37a1c 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -254,7 +255,7 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa if err := misc.VerifyGaslimit(parent.GasLimit, header.GasLimit); err != nil { return err } - } else if err := misc.VerifyEip1559Header(chain.Config(), parent, header); err != nil { + } else if err := eip1559.VerifyEIP1559Header(chain.Config(), parent, header); err != nil { // Verify the header's EIP-1559 attributes. return err } @@ -262,10 +263,11 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 { return consensus.ErrInvalidNumber } - if chain.Config().IsShanghai(header.Number, header.Time, types.DeserializeHeaderExtraInformation(header).ArbOSFormatVersion) { + arbosVersion := types.DeserializeHeaderExtraInformation(header).ArbOSFormatVersion + if chain.Config().IsShanghai(header.Number, header.Time, arbosVersion) { return errors.New("ethash does not support shanghai fork") } - if chain.Config().IsCancun(header.Number, header.Time) { + if chain.Config().IsCancun(header.Number, header.Time, arbosVersion) { return errors.New("ethash does not support cancun fork") } // Add some fake checks for tests diff --git a/consensus/misc/eip1559.go b/consensus/misc/eip1559/eip1559.go similarity index 92% rename from consensus/misc/eip1559.go rename to consensus/misc/eip1559/eip1559.go index fbaf9eec76b2..84b82c4c492e 100644 --- a/consensus/misc/eip1559.go +++ b/consensus/misc/eip1559/eip1559.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package misc +package eip1559 import ( "errors" @@ -23,20 +23,21 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" ) -// VerifyEip1559Header verifies some header attributes which were changed in EIP-1559, +// VerifyEIP1559Header verifies some header attributes which were changed in EIP-1559, // - gas limit check // - basefee check -func VerifyEip1559Header(config *params.ChainConfig, parent, header *types.Header) error { +func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Header) error { // Verify that the gas limit remains within allowed bounds parentGasLimit := parent.GasLimit if !config.IsLondon(parent.Number) { parentGasLimit = parent.GasLimit * config.ElasticityMultiplier() } - if err := VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil { + if err := misc.VerifyGaslimit(parentGasLimit, header.GasLimit); err != nil { return err } // Verify the header is not malformed diff --git a/consensus/misc/eip1559_test.go b/consensus/misc/eip1559/eip1559_test.go similarity index 98% rename from consensus/misc/eip1559_test.go rename to consensus/misc/eip1559/eip1559_test.go index 1a9f96bc4326..b5afdf0fe5e5 100644 --- a/consensus/misc/eip1559_test.go +++ b/consensus/misc/eip1559/eip1559_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package misc +package eip1559 import ( "math/big" @@ -95,7 +95,7 @@ func TestBlockGasLimits(t *testing.T) { BaseFee: initial, Number: big.NewInt(tc.pNum + 1), } - err := VerifyEip1559Header(config(), parent, header) + err := VerifyEIP1559Header(config(), parent, header) if tc.ok && err != nil { t.Errorf("test %d: Expected valid header: %s", i, err) } diff --git a/consensus/misc/eip4844.go b/consensus/misc/eip4844.go deleted file mode 100644 index 66ca9bd26d86..000000000000 --- a/consensus/misc/eip4844.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2023 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 misc - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/params" -) - -var ( - minDataGasPrice = big.NewInt(params.BlobTxMinDataGasprice) - dataGaspriceUpdateFraction = big.NewInt(params.BlobTxDataGaspriceUpdateFraction) -) - -// CalcBlobFee calculates the blobfee from the header's excess data gas field. -func CalcBlobFee(excessDataGas *big.Int) *big.Int { - // If this block does not yet have EIP-4844 enabled, return the starting fee - if excessDataGas == nil { - return big.NewInt(params.BlobTxMinDataGasprice) - } - return fakeExponential(minDataGasPrice, excessDataGas, dataGaspriceUpdateFraction) -} - -// fakeExponential approximates factor * e ** (numerator / denominator) using -// Taylor expansion. -func fakeExponential(factor, numerator, denominator *big.Int) *big.Int { - var ( - output = new(big.Int) - accum = new(big.Int).Mul(factor, denominator) - ) - for i := 1; accum.Sign() > 0; i++ { - output.Add(output, accum) - - accum.Mul(accum, numerator) - accum.Div(accum, denominator) - accum.Div(accum, big.NewInt(int64(i))) - } - return output.Div(output, denominator) -} diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go new file mode 100644 index 000000000000..2dad9a0cd3de --- /dev/null +++ b/consensus/misc/eip4844/eip4844.go @@ -0,0 +1,98 @@ +// Copyright 2023 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 eip4844 + +import ( + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" +) + +var ( + minBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice) + blobGaspriceUpdateFraction = big.NewInt(params.BlobTxBlobGaspriceUpdateFraction) +) + +// VerifyEIP4844Header verifies the presence of the excessBlobGas field and that +// if the current block contains no transactions, the excessBlobGas is updated +// accordingly. +func VerifyEIP4844Header(parent, header *types.Header) error { + // Verify the header is not malformed + if header.ExcessBlobGas == nil { + return errors.New("header is missing excessBlobGas") + } + if header.BlobGasUsed == nil { + return errors.New("header is missing blobGasUsed") + } + // Verify that the blob gas used remains within reasonable limits. + if *header.BlobGasUsed > params.MaxBlobGasPerBlock { + return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, params.MaxBlobGasPerBlock) + } + if *header.BlobGasUsed%params.BlobTxBlobGasPerBlob != 0 { + return fmt.Errorf("blob gas used %d not a multiple of blob gas per blob %d", header.BlobGasUsed, params.BlobTxBlobGasPerBlob) + } + // Verify the excessBlobGas is correct based on the parent header + var ( + parentExcessBlobGas uint64 + parentBlobGasUsed uint64 + ) + if parent.ExcessBlobGas != nil { + parentExcessBlobGas = *parent.ExcessBlobGas + parentBlobGasUsed = *parent.BlobGasUsed + } + expectedExcessBlobGas := CalcExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed) + if *header.ExcessBlobGas != expectedExcessBlobGas { + return fmt.Errorf("invalid excessBlobGas: have %d, want %d, parent excessBlobGas %d, parent blobDataUsed %d", + *header.ExcessBlobGas, expectedExcessBlobGas, parentExcessBlobGas, parentBlobGasUsed) + } + return nil +} + +// CalcExcessBlobGas calculates the excess blob gas after applying the set of +// blobs on top of the excess blob gas. +func CalcExcessBlobGas(parentExcessBlobGas uint64, parentBlobGasUsed uint64) uint64 { + excessBlobGas := parentExcessBlobGas + parentBlobGasUsed + if excessBlobGas < params.BlobTxTargetBlobGasPerBlock { + return 0 + } + return excessBlobGas - params.BlobTxTargetBlobGasPerBlock +} + +// CalcBlobFee calculates the blobfee from the header's excess blob gas field. +func CalcBlobFee(excessBlobGas uint64) *big.Int { + return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(excessBlobGas), blobGaspriceUpdateFraction) +} + +// fakeExponential approximates factor * e ** (numerator / denominator) using +// Taylor expansion. +func fakeExponential(factor, numerator, denominator *big.Int) *big.Int { + var ( + output = new(big.Int) + accum = new(big.Int).Mul(factor, denominator) + ) + for i := 1; accum.Sign() > 0; i++ { + output.Add(output, accum) + + accum.Mul(accum, numerator) + accum.Div(accum, denominator) + accum.Div(accum, big.NewInt(int64(i))) + } + return output.Div(output, denominator) +} diff --git a/consensus/misc/eip4844_test.go b/consensus/misc/eip4844/eip4844_test.go similarity index 55% rename from consensus/misc/eip4844_test.go rename to consensus/misc/eip4844/eip4844_test.go index 5838cab8e669..ec417380fcb0 100644 --- a/consensus/misc/eip4844_test.go +++ b/consensus/misc/eip4844/eip4844_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package misc +package eip4844 import ( "fmt" @@ -24,22 +24,51 @@ import ( "github.com/ethereum/go-ethereum/params" ) +func TestCalcExcessBlobGas(t *testing.T) { + var tests = []struct { + excess uint64 + blobs uint64 + want uint64 + }{ + // The excess blob gas should not increase from zero if the used blob + // slots are below - or equal - to the target. + {0, 0, 0}, + {0, 1, 0}, + {0, params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob, 0}, + + // If the target blob gas is exceeded, the excessBlobGas should increase + // by however much it was overshot + {0, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 1, params.BlobTxBlobGasPerBlob}, + {1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 1, params.BlobTxBlobGasPerBlob + 1}, + {1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) + 2, 2*params.BlobTxBlobGasPerBlob + 1}, + + // The excess blob gas should decrease by however much the target was + // under-shot, capped at zero. + {params.BlobTxTargetBlobGasPerBlock, params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob, params.BlobTxTargetBlobGasPerBlock}, + {params.BlobTxTargetBlobGasPerBlock, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 1, params.BlobTxTargetBlobGasPerBlock - params.BlobTxBlobGasPerBlob}, + {params.BlobTxTargetBlobGasPerBlock, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 2, params.BlobTxTargetBlobGasPerBlock - (2 * params.BlobTxBlobGasPerBlob)}, + {params.BlobTxBlobGasPerBlob - 1, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 1, 0}, + } + for i, tt := range tests { + result := CalcExcessBlobGas(tt.excess, tt.blobs*params.BlobTxBlobGasPerBlob) + if result != tt.want { + t.Errorf("test %d: excess blob gas mismatch: have %v, want %v", i, result, tt.want) + } + } +} + func TestCalcBlobFee(t *testing.T) { tests := []struct { - excessDataGas int64 + excessBlobGas uint64 blobfee int64 }{ {0, 1}, - {1542706, 1}, - {1542707, 2}, - {10 * 1024 * 1024, 111}, - } - have := CalcBlobFee(nil) - if have.Int64() != params.BlobTxMinDataGasprice { - t.Errorf("nil test: blobfee mismatch: have %v, want %v", have, params.BlobTxMinDataGasprice) + {2314057, 1}, + {2314058, 2}, + {10 * 1024 * 1024, 23}, } for i, tt := range tests { - have := CalcBlobFee(big.NewInt(tt.excessDataGas)) + have := CalcBlobFee(tt.excessBlobGas) if have.Int64() != tt.blobfee { t.Errorf("test %d: blobfee mismatch: have %v want %v", i, have, tt.blobfee) } diff --git a/consensus/misc/gaslimit.go b/consensus/misc/gaslimit.go index 25f35300b94d..dfcabd9a802c 100644 --- a/consensus/misc/gaslimit.go +++ b/consensus/misc/gaslimit.go @@ -17,7 +17,6 @@ package misc import ( - "errors" "fmt" "github.com/ethereum/go-ethereum/params" @@ -36,7 +35,7 @@ func VerifyGaslimit(parentGasLimit, headerGasLimit uint64) error { return fmt.Errorf("invalid gas limit: have %d, want %d +-= %d", headerGasLimit, parentGasLimit, limit-1) } if headerGasLimit < params.MinGasLimit { - return errors.New("invalid gas limit below 5000") + return fmt.Errorf("invalid gas limit below %d", params.MinGasLimit) } return nil } diff --git a/console/bridge.go b/console/bridge.go index 21ef0e8e7b90..c67686d6c3f3 100644 --- a/console/bridge.go +++ b/console/bridge.go @@ -18,6 +18,7 @@ package console import ( "encoding/json" + "errors" "fmt" "io" "reflect" @@ -77,18 +78,18 @@ func (b *bridge) NewAccount(call jsre.Call) (goja.Value, error) { return nil, err } if password != confirm { - return nil, fmt.Errorf("passwords don't match!") + return nil, errors.New("passwords don't match!") } // A single string password was specified, use that case len(call.Arguments) == 1 && call.Argument(0).ToString() != nil: password = call.Argument(0).ToString().String() default: - return nil, fmt.Errorf("expected 0 or 1 string argument") + return nil, errors.New("expected 0 or 1 string argument") } // Password acquired, execute the call and return newAccount, callable := goja.AssertFunction(getJeth(call.VM).Get("newAccount")) if !callable { - return nil, fmt.Errorf("jeth.newAccount is not callable") + return nil, errors.New("jeth.newAccount is not callable") } ret, err := newAccount(goja.Null(), call.VM.ToValue(password)) if err != nil { @@ -102,7 +103,7 @@ func (b *bridge) NewAccount(call jsre.Call) (goja.Value, error) { func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { // Make sure we have a wallet specified to open if call.Argument(0).ToObject(call.VM).ClassName() != "String" { - return nil, fmt.Errorf("first argument must be the wallet URL to open") + return nil, errors.New("first argument must be the wallet URL to open") } wallet := call.Argument(0) @@ -115,7 +116,7 @@ func (b *bridge) OpenWallet(call jsre.Call) (goja.Value, error) { // Open the wallet and return if successful in itself openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet")) if !callable { - return nil, fmt.Errorf("jeth.openWallet is not callable") + return nil, errors.New("jeth.openWallet is not callable") } val, err := openWallet(goja.Null(), wallet, passwd) if err == nil { @@ -198,7 +199,7 @@ func (b *bridge) readPassphraseAndReopenWallet(call jsre.Call) (goja.Value, erro } openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet")) if !callable { - return nil, fmt.Errorf("jeth.openWallet is not callable") + return nil, errors.New("jeth.openWallet is not callable") } return openWallet(goja.Null(), wallet, call.VM.ToValue(input)) } @@ -219,7 +220,7 @@ func (b *bridge) readPinAndReopenWallet(call jsre.Call) (goja.Value, error) { } openWallet, callable := goja.AssertFunction(getJeth(call.VM).Get("openWallet")) if !callable { - return nil, fmt.Errorf("jeth.openWallet is not callable") + return nil, errors.New("jeth.openWallet is not callable") } return openWallet(goja.Null(), wallet, call.VM.ToValue(input)) } @@ -230,13 +231,13 @@ func (b *bridge) readPinAndReopenWallet(call jsre.Call) (goja.Value, error) { // the RPC call. func (b *bridge) UnlockAccount(call jsre.Call) (goja.Value, error) { if len(call.Arguments) < 1 { - return nil, fmt.Errorf("usage: unlockAccount(account, [ password, duration ])") + return nil, errors.New("usage: unlockAccount(account, [ password, duration ])") } account := call.Argument(0) // Make sure we have an account specified to unlock. if goja.IsUndefined(account) || goja.IsNull(account) || account.ExportType().Kind() != reflect.String { - return nil, fmt.Errorf("first argument must be the account to unlock") + return nil, errors.New("first argument must be the account to unlock") } // If password is not given or is the null value, prompt the user for it. @@ -250,7 +251,7 @@ func (b *bridge) UnlockAccount(call jsre.Call) (goja.Value, error) { passwd = call.VM.ToValue(input) } else { if call.Argument(1).ExportType().Kind() != reflect.String { - return nil, fmt.Errorf("password must be a string") + return nil, errors.New("password must be a string") } passwd = call.Argument(1) } @@ -259,7 +260,7 @@ func (b *bridge) UnlockAccount(call jsre.Call) (goja.Value, error) { duration := goja.Null() if !goja.IsUndefined(call.Argument(2)) && !goja.IsNull(call.Argument(2)) { if !isNumber(call.Argument(2)) { - return nil, fmt.Errorf("unlock duration must be a number") + return nil, errors.New("unlock duration must be a number") } duration = call.Argument(2) } @@ -267,7 +268,7 @@ func (b *bridge) UnlockAccount(call jsre.Call) (goja.Value, error) { // Send the request to the backend and return. unlockAccount, callable := goja.AssertFunction(getJeth(call.VM).Get("unlockAccount")) if !callable { - return nil, fmt.Errorf("jeth.unlockAccount is not callable") + return nil, errors.New("jeth.unlockAccount is not callable") } return unlockAccount(goja.Null(), account, passwd, duration) } @@ -277,7 +278,7 @@ func (b *bridge) UnlockAccount(call jsre.Call) (goja.Value, error) { // jeth.sign) with it to actually execute the RPC call. func (b *bridge) Sign(call jsre.Call) (goja.Value, error) { if nArgs := len(call.Arguments); nArgs < 2 { - return nil, fmt.Errorf("usage: sign(message, account, [ password ])") + return nil, errors.New("usage: sign(message, account, [ password ])") } var ( message = call.Argument(0) @@ -286,10 +287,10 @@ func (b *bridge) Sign(call jsre.Call) (goja.Value, error) { ) if goja.IsUndefined(message) || message.ExportType().Kind() != reflect.String { - return nil, fmt.Errorf("first argument must be the message to sign") + return nil, errors.New("first argument must be the message to sign") } if goja.IsUndefined(account) || account.ExportType().Kind() != reflect.String { - return nil, fmt.Errorf("second argument must be the account to sign with") + return nil, errors.New("second argument must be the account to sign with") } // if the password is not given or null ask the user and ensure password is a string @@ -301,13 +302,13 @@ func (b *bridge) Sign(call jsre.Call) (goja.Value, error) { } passwd = call.VM.ToValue(input) } else if passwd.ExportType().Kind() != reflect.String { - return nil, fmt.Errorf("third argument must be the password to unlock the account") + return nil, errors.New("third argument must be the password to unlock the account") } // Send the request to the backend and return sign, callable := goja.AssertFunction(getJeth(call.VM).Get("sign")) if !callable { - return nil, fmt.Errorf("jeth.sign is not callable") + return nil, errors.New("jeth.sign is not callable") } return sign(goja.Null(), message, account, passwd) } @@ -315,11 +316,11 @@ func (b *bridge) Sign(call jsre.Call) (goja.Value, error) { // Sleep will block the console for the specified number of seconds. func (b *bridge) Sleep(call jsre.Call) (goja.Value, error) { if nArgs := len(call.Arguments); nArgs < 1 { - return nil, fmt.Errorf("usage: sleep()") + return nil, errors.New("usage: sleep()") } sleepObj := call.Argument(0) if goja.IsUndefined(sleepObj) || goja.IsNull(sleepObj) || !isNumber(sleepObj) { - return nil, fmt.Errorf("usage: sleep()") + return nil, errors.New("usage: sleep()") } sleep := sleepObj.ToFloat() time.Sleep(time.Duration(sleep * float64(time.Second))) @@ -336,17 +337,17 @@ func (b *bridge) SleepBlocks(call jsre.Call) (goja.Value, error) { ) nArgs := len(call.Arguments) if nArgs == 0 { - return nil, fmt.Errorf("usage: sleepBlocks([, max sleep in seconds])") + return nil, errors.New("usage: sleepBlocks([, max sleep in seconds])") } if nArgs >= 1 { if goja.IsNull(call.Argument(0)) || goja.IsUndefined(call.Argument(0)) || !isNumber(call.Argument(0)) { - return nil, fmt.Errorf("expected number as first argument") + return nil, errors.New("expected number as first argument") } blocks = call.Argument(0).ToInteger() } if nArgs >= 2 { if goja.IsNull(call.Argument(1)) || goja.IsUndefined(call.Argument(1)) || !isNumber(call.Argument(1)) { - return nil, fmt.Errorf("expected number as second argument") + return nil, errors.New("expected number as second argument") } sleep = call.Argument(1).ToInteger() } @@ -421,7 +422,7 @@ func (b *bridge) Send(call jsre.Call) (goja.Value, error) { JSON := call.VM.Get("JSON").ToObject(call.VM) parse, callable := goja.AssertFunction(JSON.Get("parse")) if !callable { - return nil, fmt.Errorf("JSON.parse is not a function") + return nil, errors.New("JSON.parse is not a function") } resultVal, err := parse(goja.Null(), call.VM.ToValue(string(result))) if err != nil { diff --git a/console/console_test.go b/console/console_test.go index cc61f021ce1d..ee5c36be4ac9 100644 --- a/console/console_test.go +++ b/console/console_test.go @@ -94,7 +94,7 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester { t.Fatalf("failed to create node: %v", err) } ethConf := ðconfig.Config{ - Genesis: core.DeveloperGenesisBlock(15, 11_500_000, common.Address{}), + Genesis: core.DeveloperGenesisBlock(11_500_000, common.Address{}), Miner: miner.Config{ Etherbase: common.HexToAddress(testAddress), }, @@ -110,10 +110,11 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester { if err = stack.Start(); err != nil { t.Fatalf("failed to start test stack: %v", err) } - client, err := stack.Attach() - if err != nil { - t.Fatalf("failed to attach to node: %v", err) - } + client := stack.Attach() + t.Cleanup(func() { + client.Close() + }) + prompter := &hookedPrompter{scheduler: make(chan string)} printer := new(bytes.Buffer) diff --git a/core/arbitrum_hooks.go b/core/arbitrum_hooks.go index aaaa36e064eb..7b57ee694fe7 100644 --- a/core/arbitrum_hooks.go +++ b/core/arbitrum_hooks.go @@ -55,6 +55,7 @@ type NodeInterfaceBackendAPI interface { ChainConfig() *params.ChainConfig CurrentBlock() *types.Header BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) + HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) GetEVM(ctx context.Context, msg *Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) } diff --git a/core/asm/asm.go b/core/asm/asm.go index 7c1e14ec01ea..294eb6ffaad7 100644 --- a/core/asm/asm.go +++ b/core/asm/asm.go @@ -34,7 +34,7 @@ type instructionIterator struct { started bool } -// NewInstructionIterator create a new instruction iterator. +// NewInstructionIterator creates a new instruction iterator. func NewInstructionIterator(code []byte) *instructionIterator { it := new(instructionIterator) it.code = code diff --git a/core/asm/compiler.go b/core/asm/compiler.go index 4b1d379206f6..02c589b2c1a9 100644 --- a/core/asm/compiler.go +++ b/core/asm/compiler.go @@ -17,6 +17,8 @@ package asm import ( + "encoding/hex" + "errors" "fmt" "math/big" "os" @@ -30,7 +32,7 @@ import ( // and holds the tokens for the program. type Compiler struct { tokens []token - binary []interface{} + out []byte labels map[string]int @@ -47,15 +49,13 @@ func NewCompiler(debug bool) *Compiler { } } -// Feed feeds tokens in to ch and are interpreted by +// Feed feeds tokens into ch and are interpreted by // the compiler. // -// feed is the first pass in the compile stage as it -// collects the used labels in the program and keeps a -// program counter which is used to determine the locations -// of the jump dests. The labels can than be used in the -// second stage to push labels and determine the right -// position. +// feed is the first pass in the compile stage as it collects the used labels in the +// program and keeps a program counter which is used to determine the locations of the +// jump dests. The labels can than be used in the second stage to push labels and +// determine the right position. func (c *Compiler) Feed(ch <-chan token) { var prev token for i := range ch { @@ -79,7 +79,6 @@ func (c *Compiler) Feed(ch <-chan token) { c.pc++ } } - c.tokens = append(c.tokens, i) prev = i } @@ -88,12 +87,11 @@ func (c *Compiler) Feed(ch <-chan token) { } } -// Compile compiles the current tokens and returns a -// binary string that can be interpreted by the EVM -// and an error if it failed. +// Compile compiles the current tokens and returns a binary string that can be interpreted +// by the EVM and an error if it failed. // -// compile is the second stage in the compile phase -// which compiles the tokens to EVM instructions. +// compile is the second stage in the compile phase which compiles the tokens to EVM +// instructions. func (c *Compiler) Compile() (string, []error) { var errors []error // continue looping over the tokens until @@ -105,16 +103,8 @@ func (c *Compiler) Compile() (string, []error) { } // turn the binary to hex - var bin strings.Builder - for _, v := range c.binary { - switch v := v.(type) { - case vm.OpCode: - bin.WriteString(fmt.Sprintf("%x", []byte{byte(v)})) - case []byte: - bin.WriteString(fmt.Sprintf("%x", v)) - } - } - return bin.String(), errors + h := hex.EncodeToString(c.out) + return h, errors } // next returns the next token and increments the @@ -156,87 +146,114 @@ func (c *Compiler) compileLine() error { return nil } -// compileNumber compiles the number to bytes -func (c *Compiler) compileNumber(element token) { - num := math.MustParseBig256(element.text).Bytes() - if len(num) == 0 { - num = []byte{0} +// parseNumber compiles the number to bytes +func parseNumber(tok token) ([]byte, error) { + if tok.typ != number { + panic("parseNumber of non-number token") + } + num, ok := math.ParseBig256(tok.text) + if !ok { + return nil, errors.New("invalid number") } - c.pushBin(num) + bytes := num.Bytes() + if len(bytes) == 0 { + bytes = []byte{0} + } + return bytes, nil } // compileElement compiles the element (push & label or both) // to a binary representation and may error if incorrect statements // where fed. func (c *Compiler) compileElement(element token) error { - // check for a jump. jumps must be read and compiled - // from right to left. - if isJump(element.text) { - rvalue := c.next() - switch rvalue.typ { - case number: - // TODO figure out how to return the error properly - c.compileNumber(rvalue) - case stringValue: - // strings are quoted, remove them. - c.pushBin(rvalue.text[1 : len(rvalue.text)-2]) - case label: - c.pushBin(vm.PUSH4) - pos := big.NewInt(int64(c.labels[rvalue.text])).Bytes() - pos = append(make([]byte, 4-len(pos)), pos...) - c.pushBin(pos) - case lineEnd: - c.pos-- - default: - return compileErr(rvalue, rvalue.text, "number, string or label") - } - // push the operation - c.pushBin(toBinary(element.text)) + switch { + case isJump(element.text): + return c.compileJump(element.text) + case isPush(element.text): + return c.compilePush() + default: + c.outputOpcode(toBinary(element.text)) return nil - } else if isPush(element.text) { - // handle pushes. pushes are read from left to right. - var value []byte + } +} - rvalue := c.next() - switch rvalue.typ { - case number: - value = math.MustParseBig256(rvalue.text).Bytes() - if len(value) == 0 { - value = []byte{0} - } - case stringValue: - value = []byte(rvalue.text[1 : len(rvalue.text)-1]) - case label: - value = big.NewInt(int64(c.labels[rvalue.text])).Bytes() - value = append(make([]byte, 4-len(value)), value...) - default: - return compileErr(rvalue, rvalue.text, "number, string or label") +func (c *Compiler) compileJump(jumpType string) error { + rvalue := c.next() + switch rvalue.typ { + case number: + numBytes, err := parseNumber(rvalue) + if err != nil { + return err } + c.outputBytes(numBytes) - if len(value) > 32 { - return fmt.Errorf("%d type error: unsupported string or number with size > 32", rvalue.lineno) - } + case stringValue: + // strings are quoted, remove them. + str := rvalue.text[1 : len(rvalue.text)-2] + c.outputBytes([]byte(str)) + + case label: + c.outputOpcode(vm.PUSH4) + pos := big.NewInt(int64(c.labels[rvalue.text])).Bytes() + pos = append(make([]byte, 4-len(pos)), pos...) + c.outputBytes(pos) + + case lineEnd: + // push without argument is supported, it just takes the destination from the stack. + c.pos-- - c.pushBin(vm.OpCode(int(vm.PUSH1) - 1 + len(value))) - c.pushBin(value) - } else { - c.pushBin(toBinary(element.text)) + default: + return compileErr(rvalue, rvalue.text, "number, string or label") } + // push the operation + c.outputOpcode(toBinary(jumpType)) + return nil +} +func (c *Compiler) compilePush() error { + // handle pushes. pushes are read from left to right. + var value []byte + rvalue := c.next() + switch rvalue.typ { + case number: + value = math.MustParseBig256(rvalue.text).Bytes() + if len(value) == 0 { + value = []byte{0} + } + case stringValue: + value = []byte(rvalue.text[1 : len(rvalue.text)-1]) + case label: + value = big.NewInt(int64(c.labels[rvalue.text])).Bytes() + value = append(make([]byte, 4-len(value)), value...) + default: + return compileErr(rvalue, rvalue.text, "number, string or label") + } + if len(value) > 32 { + return fmt.Errorf("%d: string or number size > 32 bytes", rvalue.lineno+1) + } + c.outputOpcode(vm.OpCode(int(vm.PUSH1) - 1 + len(value))) + c.outputBytes(value) return nil } // compileLabel pushes a jumpdest to the binary slice. func (c *Compiler) compileLabel() { - c.pushBin(vm.JUMPDEST) + c.outputOpcode(vm.JUMPDEST) +} + +func (c *Compiler) outputOpcode(op vm.OpCode) { + if c.debug { + fmt.Printf("%d: %v\n", len(c.out), op) + } + c.out = append(c.out, byte(op)) } -// pushBin pushes the value v to the binary stack. -func (c *Compiler) pushBin(v interface{}) { +// output pushes the value v to the binary stack. +func (c *Compiler) outputBytes(b []byte) { if c.debug { - fmt.Printf("%d: %v\n", len(c.binary), v) + fmt.Printf("%d: %x\n", len(c.out), b) } - c.binary = append(c.binary, v) + c.out = append(c.out, b...) } // isPush returns whether the string op is either any of @@ -263,13 +280,13 @@ type compileError struct { } func (err compileError) Error() string { - return fmt.Sprintf("%d syntax error: unexpected %v, expected %v", err.lineno, err.got, err.want) + return fmt.Sprintf("%d: syntax error: unexpected %v, expected %v", err.lineno, err.got, err.want) } func compileErr(c token, got, want string) error { return compileError{ got: got, want: want, - lineno: c.lineno, + lineno: c.lineno + 1, } } diff --git a/core/asm/compiler_test.go b/core/asm/compiler_test.go index ce9df436bd40..3d64c96bc80b 100644 --- a/core/asm/compiler_test.go +++ b/core/asm/compiler_test.go @@ -54,6 +54,14 @@ func TestCompiler(t *testing.T) { `, output: "6300000006565b", }, + { + input: ` + JUMP @label +label: ;; comment + ADD ;; comment +`, + output: "6300000006565b01", + }, } for _, test := range tests { ch := Lex([]byte(test.input), false) diff --git a/core/asm/lex_test.go b/core/asm/lex_test.go index 53e05fbbba0c..1e62d776d401 100644 --- a/core/asm/lex_test.go +++ b/core/asm/lex_test.go @@ -72,6 +72,16 @@ func TestLexer(t *testing.T) { input: "@label123", tokens: []token{{typ: lineStart}, {typ: label, text: "label123"}, {typ: eof}}, }, + // Comment after label + { + input: "@label123 ;; comment", + tokens: []token{{typ: lineStart}, {typ: label, text: "label123"}, {typ: eof}}, + }, + // Comment after instruction + { + input: "push 3 ;; comment\nadd", + tokens: []token{{typ: lineStart}, {typ: element, text: "push"}, {typ: number, text: "3"}, {typ: lineEnd, text: "\n"}, {typ: lineStart, lineno: 1}, {typ: element, lineno: 1, text: "add"}, {typ: eof, lineno: 1}}, + }, } for _, test := range tests { diff --git a/core/asm/lexer.go b/core/asm/lexer.go index d1b79a1fb922..e025c6f363c6 100644 --- a/core/asm/lexer.go +++ b/core/asm/lexer.go @@ -42,6 +42,8 @@ type token struct { // is able to parse and return. type tokenType int +//go:generate go run golang.org/x/tools/cmd/stringer -type tokenType + const ( eof tokenType = iota // end of file lineStart // emitted when a line starts @@ -52,31 +54,13 @@ const ( labelDef // label definition is emitted when a new label is found number // number is emitted when a number is found stringValue // stringValue is emitted when a string has been found - - Numbers = "1234567890" // characters representing any decimal number - HexadecimalNumbers = Numbers + "aAbBcCdDeEfF" // characters representing any hexadecimal - Alpha = "abcdefghijklmnopqrstuwvxyzABCDEFGHIJKLMNOPQRSTUWVXYZ" // characters representing alphanumeric ) -// String implements stringer -func (it tokenType) String() string { - if int(it) > len(stringtokenTypes) { - return "invalid" - } - return stringtokenTypes[it] -} - -var stringtokenTypes = []string{ - eof: "EOF", - lineStart: "new line", - lineEnd: "end of line", - invalidStatement: "invalid statement", - element: "element", - label: "label", - labelDef: "label definition", - number: "number", - stringValue: "string", -} +const ( + decimalNumbers = "1234567890" // characters representing any decimal number + hexNumbers = decimalNumbers + "aAbBcCdDeEfF" // characters representing any hexadecimal + alpha = "abcdefghijklmnopqrstuwvxyzABCDEFGHIJKLMNOPQRSTUWVXYZ" // characters representing alphanumeric +) // lexer is the basic construct for parsing // source code and turning them in to tokens. @@ -200,7 +184,6 @@ func lexLine(l *lexer) stateFn { l.emit(lineEnd) l.ignore() l.lineno++ - l.emit(lineStart) case r == ';' && l.peek() == ';': return lexComment @@ -225,6 +208,7 @@ func lexLine(l *lexer) stateFn { // of the line and discards the text. func lexComment(l *lexer) stateFn { l.acceptRunUntil('\n') + l.backup() l.ignore() return lexLine @@ -234,7 +218,7 @@ func lexComment(l *lexer) stateFn { // the lex text state function to advance the parsing // process. func lexLabel(l *lexer) stateFn { - l.acceptRun(Alpha + "_" + Numbers) + l.acceptRun(alpha + "_" + decimalNumbers) l.emit(label) @@ -253,9 +237,9 @@ func lexInsideString(l *lexer) stateFn { } func lexNumber(l *lexer) stateFn { - acceptance := Numbers + acceptance := decimalNumbers if l.accept("xX") { - acceptance = HexadecimalNumbers + acceptance = hexNumbers } l.acceptRun(acceptance) @@ -265,7 +249,7 @@ func lexNumber(l *lexer) stateFn { } func lexElement(l *lexer) stateFn { - l.acceptRun(Alpha + "_" + Numbers) + l.acceptRun(alpha + "_" + decimalNumbers) if l.peek() == ':' { l.emit(labelDef) diff --git a/core/asm/tokentype_string.go b/core/asm/tokentype_string.go new file mode 100644 index 000000000000..ade76aa360c4 --- /dev/null +++ b/core/asm/tokentype_string.go @@ -0,0 +1,31 @@ +// Code generated by "stringer -type tokenType"; DO NOT EDIT. + +package asm + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[eof-0] + _ = x[lineStart-1] + _ = x[lineEnd-2] + _ = x[invalidStatement-3] + _ = x[element-4] + _ = x[label-5] + _ = x[labelDef-6] + _ = x[number-7] + _ = x[stringValue-8] +} + +const _tokenType_name = "eoflineStartlineEndinvalidStatementelementlabellabelDefnumberstringValue" + +var _tokenType_index = [...]uint8{0, 3, 12, 19, 35, 42, 47, 55, 61, 72} + +func (i tokenType) String() string { + if i < 0 || i >= tokenType(len(_tokenType_index)-1) { + return "tokenType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _tokenType_name[_tokenType_index[i]:_tokenType_index[i+1]] +} diff --git a/core/block_validator.go b/core/block_validator.go index bcb228830d4e..f3d65cea25ff 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -17,6 +17,7 @@ package core import ( + "errors" "fmt" "github.com/ethereum/go-ethereum/consensus" @@ -67,20 +68,48 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash { return fmt.Errorf("transaction root hash mismatch (header value %x, calculated %x)", header.TxHash, hash) } + // Withdrawals are present after the Shanghai fork. if header.WithdrawalsHash != nil { // Withdrawals list must be present in body after Shanghai. if block.Withdrawals() == nil { - return fmt.Errorf("missing withdrawals in block body") + return errors.New("missing withdrawals in block body") } if hash := types.DeriveSha(block.Withdrawals(), trie.NewStackTrie(nil)); hash != *header.WithdrawalsHash { return fmt.Errorf("withdrawals root hash mismatch (header value %x, calculated %x)", *header.WithdrawalsHash, hash) } } else if block.Withdrawals() != nil { - // Withdrawals are not allowed prior to shanghai fork - return fmt.Errorf("withdrawals present in block body") + // Withdrawals are not allowed prior to Shanghai fork + return errors.New("withdrawals present in block body") + } + + // Blob transactions may be present after the Cancun fork. + var blobs int + for i, tx := range block.Transactions() { + // Count the number of blobs to validate against the header's blobGasUsed + blobs += len(tx.BlobHashes()) + + // If the tx is a blob tx, it must NOT have a sidecar attached to be valid in a block. + if tx.BlobTxSidecar() != nil { + return fmt.Errorf("unexpected blob sidecar in transaction at index %d", i) + } + + // The individual checks for blob validity (version-check + not empty) + // happens in StateTransition. + } + + // Check blob gas usage. + if header.BlobGasUsed != nil { + if want := *header.BlobGasUsed / params.BlobTxBlobGasPerBlob; uint64(blobs) != want { // div because the header is surely good vs the body might be bloated + return fmt.Errorf("blob gas used mismatch (header %v, calculated %v)", *header.BlobGasUsed, blobs*params.BlobTxBlobGasPerBlob) + } + } else { + if blobs > 0 { + return errors.New("data blobs present in block body") + } } + // Ancestor block must be known. if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) { return consensus.ErrUnknownAncestor diff --git a/core/block_validator_test.go b/core/block_validator_test.go index 8d5b4cbbeabe..40848fdb0154 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -18,7 +18,6 @@ package core import ( "math/big" - "runtime" "testing" "time" @@ -36,6 +35,11 @@ import ( // Tests that simple header verification works, for both good and bad blocks. func TestHeaderVerification(t *testing.T) { + testHeaderVerification(t, rawdb.HashScheme) + testHeaderVerification(t, rawdb.PathScheme) +} + +func testHeaderVerification(t *testing.T, scheme string) { // Create a simple chain to verify var ( gspec = &Genesis{Config: params.TestChainConfig} @@ -46,7 +50,7 @@ func TestHeaderVerification(t *testing.T) { headers[i] = block.Header() } // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces - chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer chain.Stop() for i := 0; i < len(blocks); i++ { @@ -235,118 +239,6 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) { } } -// Tests that concurrent header verification works, for both good and bad blocks. -func TestHeaderConcurrentVerification2(t *testing.T) { testHeaderConcurrentVerification(t, 2) } -func TestHeaderConcurrentVerification8(t *testing.T) { testHeaderConcurrentVerification(t, 8) } -func TestHeaderConcurrentVerification32(t *testing.T) { testHeaderConcurrentVerification(t, 32) } - -func testHeaderConcurrentVerification(t *testing.T, threads int) { - // Create a simple chain to verify - var ( - gspec = &Genesis{Config: params.TestChainConfig} - _, blocks, _ = GenerateChainWithGenesis(gspec, ethash.NewFaker(), 8, nil) - ) - headers := make([]*types.Header, len(blocks)) - for i, block := range blocks { - headers[i] = block.Header() - } - // Set the number of threads to verify on - old := runtime.GOMAXPROCS(threads) - defer runtime.GOMAXPROCS(old) - - // Run the header checker for the entire block chain at once both for a valid and - // also an invalid chain (enough if one arbitrary block is invalid). - for i, valid := range []bool{true, false} { - var results <-chan error - - if valid { - chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) - _, results = chain.engine.VerifyHeaders(chain, headers) - chain.Stop() - } else { - chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil, nil) - _, results = chain.engine.VerifyHeaders(chain, headers) - chain.Stop() - } - // Wait for all the verification results - checks := make(map[int]error) - for j := 0; j < len(blocks); j++ { - select { - case result := <-results: - checks[j] = result - - case <-time.After(time.Second): - t.Fatalf("test %d.%d: verification timeout", i, j) - } - } - // Check nonce check validity - for j := 0; j < len(blocks); j++ { - want := valid || (j < len(blocks)-2) // We chose the last-but-one nonce in the chain to fail - if (checks[j] == nil) != want { - t.Errorf("test %d.%d: validity mismatch: have %v, want %v", i, j, checks[j], want) - } - if !want { - // A few blocks after the first error may pass verification due to concurrent - // workers. We don't care about those in this test, just that the correct block - // errors out. - break - } - } - // Make sure no more data is returned - select { - case result := <-results: - t.Fatalf("test %d: unexpected result returned: %v", i, result) - case <-time.After(25 * time.Millisecond): - } - } -} - -// Tests that aborting a header validation indeed prevents further checks from being -// run, as well as checks that no left-over goroutines are leaked. -func TestHeaderConcurrentAbortion2(t *testing.T) { testHeaderConcurrentAbortion(t, 2) } -func TestHeaderConcurrentAbortion8(t *testing.T) { testHeaderConcurrentAbortion(t, 8) } -func TestHeaderConcurrentAbortion32(t *testing.T) { testHeaderConcurrentAbortion(t, 32) } - -func testHeaderConcurrentAbortion(t *testing.T, threads int) { - // Create a simple chain to verify - var ( - gspec = &Genesis{Config: params.TestChainConfig} - _, blocks, _ = GenerateChainWithGenesis(gspec, ethash.NewFaker(), 1024, nil) - ) - headers := make([]*types.Header, len(blocks)) - for i, block := range blocks { - headers[i] = block.Header() - } - // Set the number of threads to verify on - old := runtime.GOMAXPROCS(threads) - defer runtime.GOMAXPROCS(old) - - // Start the verifications and immediately abort - chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil, nil) - defer chain.Stop() - - abort, results := chain.engine.VerifyHeaders(chain, headers) - close(abort) - - // Deplete the results channel - verified := 0 - for depleted := false; !depleted; { - select { - case result := <-results: - if result != nil { - t.Errorf("header %d: validation failed: %v", verified, result) - } - verified++ - case <-time.After(50 * time.Millisecond): - depleted = true - } - } - // Check that abortion was honored by not processing too many POWs - if verified > 2*threads { - t.Errorf("verification count too large: have %d, want below %d", verified, 2*threads) - } -} - func TestCalcGasLimit(t *testing.T) { for i, tc := range []struct { pGasLimit uint64 diff --git a/core/blockchain.go b/core/blockchain.go index d21506a66c6a..790a860e3519 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -24,7 +24,6 @@ import ( "math" "math/big" "runtime" - "sort" "strings" "sync" "sync/atomic" @@ -35,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/prque" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state/snapshot" @@ -49,6 +49,9 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/hashdb" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" + "golang.org/x/exp/slices" ) var ( @@ -58,6 +61,8 @@ var ( headFinalizedBlockGauge = metrics.NewRegisteredGauge("chain/head/finalized", nil) headSafeBlockGauge = metrics.NewRegisteredGauge("chain/head/safe", nil) + chainInfoGauge = metrics.NewRegisteredGaugeInfo("chain/info", nil) + accountReadTimer = metrics.NewRegisteredTimer("chain/account/reads", nil) accountHashTimer = metrics.NewRegisteredTimer("chain/account/hashes", nil) accountUpdateTimer = metrics.NewRegisteredTimer("chain/account/updates", nil) @@ -88,6 +93,8 @@ var ( errInsertionInterrupted = errors.New("insertion is interrupted") errChainStopped = errors.New("blockchain is stopped") + errInvalidOldChain = errors.New("invalid old chain") + errInvalidNewChain = errors.New("invalid new chain") ) const ( @@ -126,17 +133,17 @@ const ( ) // CacheConfig contains the configuration values for the trie database -// that's resident in a blockchain. +// and state snapshot these are resident in a blockchain. type CacheConfig struct { TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory - TrieCleanJournal string // Disk journal for saving clean cache entries. - TrieCleanRejournal time.Duration // Time interval to dump clean cache to disk periodically TrieCleanNoPrefetch bool // Whether to disable heuristic state prefetching for followup blocks TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk TrieDirtyDisabled bool // Whether to disable trie write caching and GC altogether (archive node) TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory Preimages bool // Whether to store preimage of trie key to the disk + StateHistory uint64 // Number of blocks from head whose state histories are reserved. + StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top SnapshotRestoreMaxGas uint64 // Rollback up to this much gas to restore snapshot (otherwise snapshot recalculated from nothing) @@ -144,23 +151,55 @@ type CacheConfig struct { TriesInMemory uint64 // Height difference before which a trie may not be garbage-collected TrieRetention time.Duration // Time limit before which a trie may not be garbage-collected + MaxNumberOfBlocksToSkipStateSaving uint32 + MaxAmountOfGasToSkipStateSaving uint64 + SnapshotNoBuild bool // Whether the background generation is allowed SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it } +// triedbConfig derives the configures for trie database. +func (c *CacheConfig) triedbConfig() *trie.Config { + config := &trie.Config{Preimages: c.Preimages} + if c.StateScheme == rawdb.HashScheme { + config.HashDB = &hashdb.Config{ + CleanCacheSize: c.TrieCleanLimit * 1024 * 1024, + } + } + if c.StateScheme == rawdb.PathScheme { + config.PathDB = &pathdb.Config{ + StateHistory: c.StateHistory, + CleanCacheSize: c.TrieCleanLimit * 1024 * 1024, + DirtyCacheSize: c.TrieDirtyLimit * 1024 * 1024, + } + } + return config +} + // defaultCacheConfig are the default caching values if none are specified by the // user (also used during testing). var defaultCacheConfig = &CacheConfig{ // Arbitrum Config Options - TriesInMemory: DefaultTriesInMemory, - TrieRetention: 30 * time.Minute, + TriesInMemory: DefaultTriesInMemory, + TrieRetention: 30 * time.Minute, + MaxNumberOfBlocksToSkipStateSaving: 0, + MaxAmountOfGasToSkipStateSaving: 0, TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 256, SnapshotWait: true, + StateScheme: rawdb.HashScheme, +} + +// DefaultCacheConfigWithScheme returns a deep copied default cache config with +// a provided trie node scheme. +func DefaultCacheConfigWithScheme(scheme string) *CacheConfig { + config := *defaultCacheConfig + config.StateScheme = scheme + return &config } // BlockChain represents the canonical chain given a database with a genesis @@ -236,6 +275,9 @@ type BlockChain struct { processor Processor // Block transaction processor interface forker *ForkChoice vmConfig vm.Config + + numberOfBlocksToSkipStateSaving uint32 + amountOfGasInBlocksToSkipStateSaving uint64 } type trieGcEntry struct { @@ -251,11 +293,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par cacheConfig = defaultCacheConfig } // Open trie database with provided config - triedb := trie.NewDatabaseWithConfig(db, &trie.Config{ - Cache: cacheConfig.TrieCleanLimit, - Journal: cacheConfig.TrieCleanJournal, - Preimages: cacheConfig.Preimages, - }) + triedb := trie.NewDatabase(db, cacheConfig.triedbConfig()) var genesisHash common.Hash var genesisErr error @@ -325,6 +363,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par bc.currentFinalBlock.Store(nil) bc.currentSafeBlock.Store(nil) + // Update chain info data metrics + chainInfoGauge.Update(metrics.GaugeInfoValue{"chain_id": bc.chainConfig.ChainID.String()}) + // If Geth is initialized with an external ancient store, re-initialize the // missing chain indexes and chain flags. This procedure can survive crash // and can be resumed in next restart since chain flags are updated in last step. @@ -335,34 +376,44 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par if err := bc.loadLastState(); err != nil { return nil, err } - // Make sure the state associated with the block is available + // Make sure the state associated with the block is available, or log out + // if there is no available state, waiting for state sync. head := bc.CurrentBlock() if !bc.HasState(head.Root) { - // Head state is missing, before the state recovery, find out the - // disk layer point of snapshot(if it's enabled). Make sure the - // rewound point is lower than disk layer. - var diskRoot common.Hash - if bc.cacheConfig.SnapshotLimit > 0 { - diskRoot = rawdb.ReadSnapshotRoot(bc.db) - } - if diskRoot != (common.Hash{}) { - log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash(), "snaproot", diskRoot) - - snapDisk, diskRootFound, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, diskRoot, true, bc.cacheConfig.SnapshotRestoreMaxGas) - if err != nil { - return nil, err + if head.Number.Uint64() <= bc.genesisBlock.NumberU64() { + // The genesis state is missing, which is only possible in the path-based + // scheme. This situation occurs when the initial state sync is not finished + // yet, or the chain head is rewound below the pivot point. In both scenario, + // there is no possible recovery approach except for rerunning a snap sync. + // Do nothing here until the state syncer picks it up. + log.Info("Genesis state is missing, wait state sync") + } else { + // Head state is missing, before the state recovery, find out the + // disk layer point of snapshot(if it's enabled). Make sure the + // rewound point is lower than disk layer. + var diskRoot common.Hash + if bc.cacheConfig.SnapshotLimit > 0 { + diskRoot = rawdb.ReadSnapshotRoot(bc.db) } - // Chain rewound, persist old snapshot number to indicate recovery procedure - if diskRootFound { - rawdb.WriteSnapshotRecoveryNumber(bc.db, snapDisk) + if diskRoot != (common.Hash{}) { + log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash(), "snaproot", diskRoot) + + snapDisk, diskRootFound, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, diskRoot, true, bc.cacheConfig.SnapshotRestoreMaxGas) + if err != nil { + return nil, err + } + // Chain rewound, persist old snapshot number to indicate recovery procedure + if diskRootFound { + rawdb.WriteSnapshotRecoveryNumber(bc.db, snapDisk) + } else { + log.Warn("Snapshot root not found or too far back. Recreating snapshot from scratch.") + rawdb.DeleteSnapshotRecoveryNumber(bc.db) + } } else { - log.Warn("Snapshot root not found or too far back. Recreating snapshot from scratch.") - rawdb.DeleteSnapshotRecoveryNumber(bc.db) - } - } else { - log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash()) - if _, _, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, common.Hash{}, true, 0); err != nil { - return nil, err + log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash()) + if _, _, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, common.Hash{}, true, 0); err != nil { + return nil, err + } } } } @@ -380,7 +431,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par needRewind = true low = fullBlock.Number.Uint64() } - // In fast sync, it may happen that ancient data has been written to the + // In snap sync, it may happen that ancient data has been written to the // ancient store, but the LastFastBlock has not been updated, truncate the // extra data here. snapBlock := bc.CurrentSnapBlock() @@ -444,18 +495,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par bc.wg.Add(1) go bc.updateFutureBlocks() - // If periodic cache journal is required, spin it up. - if bc.cacheConfig.TrieCleanRejournal > 0 { - if bc.cacheConfig.TrieCleanRejournal < time.Minute { - log.Warn("Sanitizing invalid trie cache journal time", "provided", bc.cacheConfig.TrieCleanRejournal, "updated", time.Minute) - bc.cacheConfig.TrieCleanRejournal = time.Minute - } - bc.wg.Add(1) - go func() { - defer bc.wg.Done() - bc.triedb.SaveCachePeriodically(bc.cacheConfig.TrieCleanJournal, bc.cacheConfig.TrieCleanRejournal, bc.quit) - }() - } // Rewind the chain in case of an incompatible config upgrade. if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat) @@ -520,7 +559,7 @@ func (bc *BlockChain) loadLastState() error { } bc.hc.SetCurrentHeader(headHeader) - // Restore the last known head fast block + // Restore the last known head snap block bc.currentSnapBlock.Store(headBlock.Header()) headFastBlockGauge.Update(int64(headBlock.NumberU64())) @@ -555,21 +594,21 @@ func (bc *BlockChain) loadLastState() error { } log.Info("Loaded most recent local block", "number", headBlock.Number(), "hash", headBlock.Hash(), "td", blockTd, "age", common.PrettyAge(time.Unix(int64(headBlock.Time()), 0))) if headBlock.Hash() != currentSnapBlock.Hash() { - fastTd := bc.GetTd(currentSnapBlock.Hash(), currentSnapBlock.Number.Uint64()) - log.Info("Loaded most recent local snap block", "number", currentSnapBlock.Number, "hash", currentSnapBlock.Hash(), "td", fastTd, "age", common.PrettyAge(time.Unix(int64(currentSnapBlock.Time), 0))) + snapTd := bc.GetTd(currentSnapBlock.Hash(), currentSnapBlock.Number.Uint64()) + log.Info("Loaded most recent local snap block", "number", currentSnapBlock.Number, "hash", currentSnapBlock.Hash(), "td", snapTd, "age", common.PrettyAge(time.Unix(int64(currentSnapBlock.Time), 0))) } if currentFinalBlock != nil { finalTd := bc.GetTd(currentFinalBlock.Hash(), currentFinalBlock.Number.Uint64()) log.Info("Loaded most recent local finalized block", "number", currentFinalBlock.Number, "hash", currentFinalBlock.Hash(), "td", finalTd, "age", common.PrettyAge(time.Unix(int64(currentFinalBlock.Time), 0))) } if pivot := rawdb.ReadLastPivotNumber(bc.db); pivot != nil { - log.Info("Loaded last fast-sync pivot marker", "number", *pivot) + log.Info("Loaded last snap-sync pivot marker", "number", *pivot) } return nil } // SetHead rewinds the local chain to a new head. Depending on whether the node -// was fast synced or full synced and in which state, the method will try to +// was snap synced or full synced and in which state, the method will try to // delete minimal data from disk whilst retaining chain consistency. func (bc *BlockChain) SetHead(head uint64) error { if _, _, err := bc.setHeadBeyondRoot(head, 0, common.Hash{}, false, 0); err != nil { @@ -579,7 +618,7 @@ func (bc *BlockChain) SetHead(head uint64) error { header := bc.CurrentBlock() block := bc.GetBlock(header.Hash(), header.Number.Uint64()) if block == nil { - // This should never happen. In practice, previsouly currentBlock + // This should never happen. In practice, previously currentBlock // contained the entire block whereas now only a "marker", so there // is an ever so slight chance for a race we should handle. log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash()) @@ -590,7 +629,7 @@ func (bc *BlockChain) SetHead(head uint64) error { } // SetHeadWithTimestamp rewinds the local chain to a new head that has at max -// the given timestamp. Depending on whether the node was fast synced or full +// the given timestamp. Depending on whether the node was snap synced or full // synced and in which state, the method will try to delete minimal data from // disk whilst retaining chain consistency. func (bc *BlockChain) SetHeadWithTimestamp(timestamp uint64) error { @@ -601,7 +640,7 @@ func (bc *BlockChain) SetHeadWithTimestamp(timestamp uint64) error { header := bc.CurrentBlock() block := bc.GetBlock(header.Hash(), header.Number.Uint64()) if block == nil { - // This should never happen. In practice, previsouly currentBlock + // This should never happen. In practice, previously currentBlock // contained the entire block whereas now only a "marker", so there // is an ever so slight chance for a race we should handle. log.Error("Current block not found in database", "block", header.Number, "hash", header.Hash()) @@ -638,7 +677,7 @@ func (bc *BlockChain) SetSafe(header *types.Header) { // ignored if it causes rolling back more than rewindLimit Gas (0 meaning infinte). // If the limit was hit, rewind to last block with state. This method is meant to be // used when rewinding with snapshots enabled to ensure that we go back further than -// persistent disk layer. Depending on whether the node was fast synced or full, and +// persistent disk layer. Depending on whether the node was snap synced or full, and // in which state, the method will try to delete minimal data from disk whilst // retaining chain consistency. // @@ -703,7 +742,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha if root != (common.Hash{}) && !rootFound && newHeadBlock.Root() == root { rootFound, blockNumber = true, newHeadBlock.NumberU64() } - if !bc.HasState(newHeadBlock.Root()) { + if !bc.HasState(newHeadBlock.Root()) && !bc.stateRecoverable(newHeadBlock.Root()) { log.Trace("Block state missing, rewinding further", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) if pivot == nil || newHeadBlock.NumberU64() > *pivot { parent := bc.GetBlock(newHeadBlock.ParentHash(), newHeadBlock.NumberU64()-1) @@ -721,30 +760,15 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha lastFullBlock = newHeadBlock.NumberU64() lastFullBlockHash = newHeadBlock.Hash() } - if rootFound || newHeadBlock.NumberU64() <= bc.genesisBlock.NumberU64() { - if newHeadBlock.NumberU64() <= bc.genesisBlock.NumberU64() { - // Recommit the genesis state into disk in case the rewinding destination - // is genesis block and the relevant state is gone. In the future this - // rewinding destination can be the earliest block stored in the chain - // if the historical chain pruning is enabled. In that case the logic - // needs to be improved here. - if !bc.HasState(bc.genesisBlock.Root()) { - // Arbitrum: we have a later block with state; use that instead. - if lastFullBlock != 0 { - blockNumber = lastFullBlock - newHeadBlock = bc.GetBlock(lastFullBlockHash, lastFullBlock) - log.Debug("Rewound to block with state but not snapshot", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) - break - } - if err := CommitGenesisState(bc.db, bc.triedb, bc.genesisBlock.Hash()); err != nil { - log.Crit("Failed to commit genesis state", "err", err) - } - log.Debug("Recommitted genesis state to disk") + if !bc.HasState(newHeadBlock.Root()) && bc.stateRecoverable(newHeadBlock.Root()) { + // Rewind to a block with recoverable state. If the state is + // missing, run the state recovery here. + if err := bc.triedb.Recover(newHeadBlock.Root()); err != nil { + log.Crit("Failed to rollback state", "err", err) // Shouldn't happen } - newHeadBlock = bc.genesisBlock + log.Debug("Rewound to block with state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) } - log.Debug("Rewound to block with state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash()) break } log.Debug("Skipping block with threshold state", "number", newHeadBlock.NumberU64(), "hash", newHeadBlock.Hash(), "root", newHeadBlock.Root()) @@ -759,8 +783,17 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha // to low, so it's safe to update in-memory markers directly. bc.currentBlock.Store(newHeadBlock.Header()) headBlockGauge.Update(int64(newHeadBlock.NumberU64())) + + // The head state is missing, which is only possible in the path-based + // scheme. This situation occurs when the chain head is rewound below + // the pivot point. In this scenario, there is no possible recovery + // approach except for rerunning a snap sync. Do nothing here until the + // state syncer picks it up. + if !bc.HasState(newHeadBlock.Root()) { + log.Info("Chain is stateless, wait state sync", "number", newHeadBlock.Number(), "hash", newHeadBlock.Hash()) + } } - // Rewind the fast block in a simpleton way to the target head + // Rewind the snap block in a simpleton way to the target head if currentSnapBlock := bc.CurrentSnapBlock(); currentSnapBlock != nil && header.Number.Uint64() < currentSnapBlock.Number.Uint64() { newHeadSnapBlock := bc.GetBlock(header.Hash(), header.Number.Uint64()) // If either blocks reached nil, reset to the genesis state @@ -796,7 +829,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha if num+1 <= frozen { // Truncate all relative data(header, total difficulty, body, receipt // and canonical hash) from ancient store. - if err := bc.db.TruncateHead(num); err != nil { + if _, err := bc.db.TruncateHead(num); err != nil { log.Crit("Failed to truncate ancient data", "number", num, "err", err) } // Remove the hash <-> number mapping from the active store. @@ -818,7 +851,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha } } else { // Rewind the chain to the requested head and keep going backwards until a - // block with a state is found or fast sync pivot is passed + // block with a state is found or snap sync pivot is passed if time > 0 { log.Warn("Rewinding blockchain to timestamp", "target", time) bc.hc.SetHeadWithTimestamp(time, updateFn, delFn) @@ -856,7 +889,13 @@ func (bc *BlockChain) SnapSyncCommitHead(hash common.Hash) error { if block == nil { return fmt.Errorf("non existent block [%x..]", hash[:4]) } + // Reset the trie database with the fresh snap synced state. root := block.Root() + if bc.triedb.Scheme() == rawdb.PathScheme { + if err := bc.triedb.Enable(root); err != nil { + return err + } + } if !bc.HasState(root) { return fmt.Errorf("non existent state [%x..]", root[:4]) } @@ -937,7 +976,7 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error { return fmt.Errorf("export failed on #%d: not found", nr) } if nr > first && block.ParentHash() != parentHash { - return fmt.Errorf("export failed: chain reorg during export") + return errors.New("export failed: chain reorg during export") } parentHash = block.Hash() if err := block.EncodeRLP(w); err != nil { @@ -953,7 +992,7 @@ func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error { // writeHeadBlock injects a new head block into the current block chain. This method // assumes that the block is indeed a true head. It will also reset the head -// header and the head fast sync block to this very same block if they are older +// header and the head snap sync block to this very same block if they are older // or if they are on a different side chain. // // Note, this function assumes that the `mu` mutex is held! @@ -1013,7 +1052,7 @@ func (bc *BlockChain) stopWithoutSaving() { func (bc *BlockChain) Stop() { bc.stopWithoutSaving() - // Ensure that the entirety of the state snapshot is journalled to disk. + // Ensure that the entirety of the state snapshot is journaled to disk. var snapBase common.Hash if bc.snaps != nil { var err error @@ -1021,56 +1060,57 @@ func (bc *BlockChain) Stop() { log.Error("Failed to journal state snapshot", "err", err) } } + if bc.triedb.Scheme() == rawdb.PathScheme { + // Ensure that the in-memory trie nodes are journaled to disk properly. + if err := bc.triedb.Journal(bc.CurrentBlock().Root); err != nil { + log.Info("Failed to journal in-memory trie nodes", "err", err) + } + } else { + // Ensure the state of a recent block is also stored to disk before exiting. + // We're writing three different states to catch different restart scenarios: + // - HEAD: So we don't need to reprocess any blocks in the general case + // - HEAD-1: So we don't do large reorgs if our HEAD becomes an uncle + // - HEAD-127: So we have a hard limit on the number of blocks reexecuted + if !bc.cacheConfig.TrieDirtyDisabled { + triedb := bc.triedb + + for _, offset := range []uint64{0, 1, bc.cacheConfig.TriesInMemory - 1, math.MaxUint64} { + if number := bc.CurrentBlock().Number.Uint64(); number > offset || offset == math.MaxUint64 { + var recent *types.Block + if offset == math.MaxUint64 && !bc.triegc.Empty() { + _, latest := bc.triegc.Peek() + recent = bc.GetBlockByNumber(uint64(-latest)) + } else { + recent = bc.GetBlockByNumber(number - offset) + } + if recent == nil || recent.Root() == (common.Hash{}) { + continue + } - // Ensure the state of a recent block is also stored to disk before exiting. - // We're writing three different states to catch different restart scenarios: - // - HEAD: So we don't need to reprocess any blocks in the general case - // - HEAD-1: So we don't do large reorgs if our HEAD becomes an uncle - // - HEAD-127: So we have a hard limit on the number of blocks reexecuted - if !bc.cacheConfig.TrieDirtyDisabled { - triedb := bc.triedb - - for _, offset := range []uint64{0, 1, bc.cacheConfig.TriesInMemory - 1, math.MaxUint64} { - if number := bc.CurrentBlock().Number.Uint64(); number > offset { - var recent *types.Block - if offset == math.MaxUint { - _, latest := bc.triegc.Peek() - recent = bc.GetBlockByNumber(uint64(-latest)) - } else { - recent = bc.GetBlockByNumber(number - offset) - } - if recent.Root() == (common.Hash{}) { - continue + log.Info("Writing cached state to disk", "block", recent.Number(), "hash", recent.Hash(), "root", recent.Root()) + if err := triedb.Commit(recent.Root(), true); err != nil { + log.Error("Failed to commit recent state trie", "err", err) + } } - - log.Info("Writing cached state to disk", "block", recent.Number(), "hash", recent.Hash(), "root", recent.Root()) - if err := triedb.Commit(recent.Root(), true); err != nil { + } + if snapBase != (common.Hash{}) { + log.Info("Writing snapshot state to disk", "root", snapBase) + if err := triedb.Commit(snapBase, true); err != nil { log.Error("Failed to commit recent state trie", "err", err) } } - } - if snapBase != (common.Hash{}) { - log.Info("Writing snapshot state to disk", "root", snapBase) - if err := triedb.Commit(snapBase, true); err != nil { - log.Error("Failed to commit recent state trie", "err", err) + for !bc.triegc.Empty() { + triedb.Dereference(bc.triegc.PopItem().Root) + } + if _, nodes, _ := triedb.Size(); nodes != 0 { // all memory is contained within the nodes return for hashdb + log.Error("Dangling trie nodes after full cleanup") } - } - for !bc.triegc.Empty() { - triedb.Dereference(bc.triegc.PopItem().Root) - } - if size, _ := triedb.Size(); size != 0 { - log.Error("Dangling trie nodes after full cleanup") } } - // Flush the collected preimages to disk - if err := bc.stateCache.TrieDB().Close(); err != nil { + // Close the trie database, release all the held resources as the last step. + if err := bc.triedb.Close(); err != nil { log.Error("Failed to close trie db", "err", err) } - // Ensure all live cached entries be saved into disk, so that we can skip - // cache warmup when node restarts. - if bc.cacheConfig.TrieCleanJournal != "" { - bc.triedb.SaveCache(bc.cacheConfig.TrieCleanJournal) - } log.Info("Blockchain stopped") } @@ -1094,8 +1134,8 @@ func (bc *BlockChain) procFutureBlocks() { } } if len(blocks) > 0 { - sort.Slice(blocks, func(i, j int) bool { - return blocks[i].NumberU64() < blocks[j].NumberU64() + slices.SortFunc(blocks, func(a, b *types.Block) int { + return a.Number().Cmp(b.Number()) }) // Insert one by one as chain insertion needs contiguous ancestry between blocks for i := range blocks { @@ -1126,19 +1166,30 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ ancientReceipts, liveReceipts []types.Receipts ) // Do a sanity check that the provided chain is actually ordered and linked - for i := 0; i < len(blockChain); i++ { + for i, block := range blockChain { if i != 0 { - if blockChain[i].NumberU64() != blockChain[i-1].NumberU64()+1 || blockChain[i].ParentHash() != blockChain[i-1].Hash() { - log.Error("Non contiguous receipt insert", "number", blockChain[i].Number(), "hash", blockChain[i].Hash(), "parent", blockChain[i].ParentHash(), - "prevnumber", blockChain[i-1].Number(), "prevhash", blockChain[i-1].Hash()) - return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", i-1, blockChain[i-1].NumberU64(), - blockChain[i-1].Hash().Bytes()[:4], i, blockChain[i].NumberU64(), blockChain[i].Hash().Bytes()[:4], blockChain[i].ParentHash().Bytes()[:4]) + prev := blockChain[i-1] + if block.NumberU64() != prev.NumberU64()+1 || block.ParentHash() != prev.Hash() { + log.Error("Non contiguous receipt insert", + "number", block.Number(), "hash", block.Hash(), "parent", block.ParentHash(), + "prevnumber", prev.Number(), "prevhash", prev.Hash()) + return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x..], item %d is #%d [%x..] (parent [%x..])", + i-1, prev.NumberU64(), prev.Hash().Bytes()[:4], + i, block.NumberU64(), block.Hash().Bytes()[:4], block.ParentHash().Bytes()[:4]) } } - if blockChain[i].NumberU64() <= ancientLimit { - ancientBlocks, ancientReceipts = append(ancientBlocks, blockChain[i]), append(ancientReceipts, receiptChain[i]) + if block.NumberU64() <= ancientLimit { + ancientBlocks, ancientReceipts = append(ancientBlocks, block), append(ancientReceipts, receiptChain[i]) } else { - liveBlocks, liveReceipts = append(liveBlocks, blockChain[i]), append(liveReceipts, receiptChain[i]) + liveBlocks, liveReceipts = append(liveBlocks, block), append(liveReceipts, receiptChain[i]) + } + + // Here we also validate that blob transactions in the block do not contain a sidecar. + // While the sidecar does not affect the block hash / tx hash, sending blobs within a block is not allowed. + for txIndex, tx := range block.Transactions() { + if tx.Type() == types.BlobTxType && tx.BlobTxSidecar() != nil { + return 0, fmt.Errorf("block #%d contains unexpected blob sidecar in tx at index %d", block.NumberU64(), txIndex) + } } } @@ -1148,7 +1199,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ size = int64(0) ) - // updateHead updates the head fast sync block if the inserted blocks are better + // updateHead updates the head snap sync block if the inserted blocks are better // and returns an indicator whether the inserted blocks are canonical. updateHead := func(head *types.Block) bool { if !bc.chainmu.TryLock() { @@ -1221,7 +1272,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ // a background routine to re-indexed all indices in [ancients - txlookupLimit, ancients) // range. In this case, all tx indices of newly imported blocks should be // generated. - var batch = bc.db.NewBatch() + batch := bc.db.NewBatch() for i, block := range blockChain { if bc.txLookupLimit == 0 || ancientLimit <= bc.txLookupLimit || block.NumberU64() >= ancientLimit-bc.txLookupLimit { rawdb.WriteTxLookupEntriesByBlock(batch, block) @@ -1234,7 +1285,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ size += int64(batch.ValueSize()) if err = batch.Write(); err != nil { snapBlock := bc.CurrentSnapBlock().Number.Uint64() - if err := bc.db.TruncateHead(snapBlock + 1); err != nil { + if _, err := bc.db.TruncateHead(snapBlock + 1); err != nil { log.Error("Can't truncate ancient store after failed insert", "err", err) } return 0, err @@ -1247,12 +1298,12 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ if err := bc.db.Sync(); err != nil { return 0, err } - // Update the current fast block because all block data is now present in DB. + // Update the current snap block because all block data is now present in DB. previousSnapBlock := bc.CurrentSnapBlock().Number.Uint64() if !updateHead(blockChain[len(blockChain)-1]) { // We end up here if the header chain has reorg'ed, and the blocks/receipts // don't match the canonical chain. - if err := bc.db.TruncateHead(previousSnapBlock + 1); err != nil { + if _, err := bc.db.TruncateHead(previousSnapBlock + 1); err != nil { log.Error("Can't truncate ancient store after failed insert", "err", err) } return 0, errSideChainReceipts @@ -1435,16 +1486,47 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. log.Crit("Failed to write block into disk", "err", err) } // Commit all cached state changes into underlying memory database. - root, err := state.Commit(bc.chainConfig.IsEIP158(block.Number())) + root, err := state.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number())) if err != nil { return err } - // If we're running an archive node, always flush - if bc.cacheConfig.TrieDirtyDisabled { - return bc.triedb.Commit(root, false) + // If node is running in path mode, skip explicit gc operation + // which is unnecessary in this mode. + if bc.triedb.Scheme() == rawdb.PathScheme { + return nil + } + // If we're running an archive node, flush + // If MaxNumberOfBlocksToSkipStateSaving or MaxAmountOfGasToSkipStateSaving is not zero, then flushing of some blocks will be skipped: + // * at most MaxNumberOfBlocksToSkipStateSaving block state commits will be skipped + // * sum of gas used in skipped blocks will be at most MaxAmountOfGasToSkipStateSaving + archiveNode := bc.cacheConfig.TrieDirtyDisabled + if archiveNode { + var maySkipCommiting, blockLimitReached, gasLimitReached bool + if bc.cacheConfig.MaxNumberOfBlocksToSkipStateSaving != 0 { + maySkipCommiting = true + if bc.numberOfBlocksToSkipStateSaving > 0 { + bc.numberOfBlocksToSkipStateSaving-- + } else { + blockLimitReached = true + } + } + if bc.cacheConfig.MaxAmountOfGasToSkipStateSaving != 0 { + maySkipCommiting = true + if bc.amountOfGasInBlocksToSkipStateSaving >= block.GasUsed() { + bc.amountOfGasInBlocksToSkipStateSaving -= block.GasUsed() + } else { + gasLimitReached = true + } + } + if !maySkipCommiting || blockLimitReached || gasLimitReached { + bc.numberOfBlocksToSkipStateSaving = bc.cacheConfig.MaxNumberOfBlocksToSkipStateSaving + bc.amountOfGasInBlocksToSkipStateSaving = bc.cacheConfig.MaxAmountOfGasToSkipStateSaving + return bc.triedb.Commit(root, false) + } + // we are skipping saving the trie to diskdb, so we need to keep the trie in memory and garbage collect it later } - // Full but not archive node, do proper garbage collection + // Full node or archive node that's not keeping all states, do proper garbage collection bc.triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive bc.triegc.Push(trieGcEntry{root, block.Header().Time}, -int64(block.NumberU64())) @@ -1454,8 +1536,8 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. if blockLimit > 0 && timeLimit > 0 { // If we exceeded our memory allowance, flush matured singleton nodes to disk var ( - nodes, imgs = bc.triedb.Size() - limit = common.StorageSize(bc.cacheConfig.TrieDirtyLimit) * 1024 * 1024 + _, nodes, imgs = bc.triedb.Size() // all memory is contained within the nodes return for hashdb + limit = common.StorageSize(bc.cacheConfig.TrieDirtyLimit) * 1024 * 1024 ) if nodes > limit || imgs > 4*1024*1024 { bc.triedb.Cap(limit - ethdb.IdealBatchSize) @@ -1477,7 +1559,8 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. } flushInterval := time.Duration(bc.flushInterval.Load()) // If we exceeded out time allowance, flush an entire trie to disk - if bc.gcproc > flushInterval && prevEntry != nil { + // In case of archive node that skips some trie commits we don't flush tries here + if bc.gcproc > flushInterval && prevEntry != nil && !archiveNode { // If the header is missing (canonical chain behind), we're reorging a low // diff sidechain. Suspend committing until this operation is completed. header := bc.GetHeaderByNumber(prevNum) @@ -1685,11 +1768,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) block, err = it.next() } // The remaining blocks are still known blocks, the only scenario here is: - // During the fast sync, the pivot point is already submitted but rollback + // During the snap sync, the pivot point is already submitted but rollback // happens. Then node resets the head full block to a lower height via `rollback` // and leaves a few known blocks in the database. // - // When node runs a fast sync again, it can re-import a batch of known blocks via + // When node runs a snap sync again, it can re-import a batch of known blocks via // `insertChain` while a part of them have higher total difficulty than current // head full block(new pivot point). for block != nil && bc.skipBlock(err, it) { @@ -1899,8 +1982,12 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) stats.processed++ stats.usedGas += usedGas - dirty, _ := bc.triedb.Size() - stats.report(chain, it.index, dirty, setHead) + var snapDiffItems, snapBufItems common.StorageSize + if bc.snaps != nil { + snapDiffItems, snapBufItems = bc.snaps.Size() + } + trieDiffNodes, trieBufNodes, _ := bc.triedb.Size() + stats.report(chain, it.index, snapDiffItems, snapBufItems, trieDiffNodes, trieBufNodes, setHead) if !setHead { // After merge we expect few side chains. Simply count @@ -2042,6 +2129,12 @@ func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (i ) parent := it.previous() for parent != nil && !bc.HasState(parent.Root) { + if bc.stateRecoverable(parent.Root) { + if err := bc.triedb.Recover(parent.Root); err != nil { + return 0, err + } + break + } hashes = append(hashes, parent.Hash()) numbers = append(numbers, parent.Number.Uint64()) @@ -2098,6 +2191,12 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error) parent = block ) for parent != nil && !bc.HasState(parent.Root()) { + if bc.stateRecoverable(parent.Root()) { + if err := bc.triedb.Recover(parent.Root()); err != nil { + return common.Hash{}, err + } + break + } hashes = append(hashes, parent.Hash()) numbers = append(numbers, parent.NumberU64()) parent = bc.GetBlock(parent.ParentHash(), parent.NumberU64()-1) @@ -2134,17 +2233,22 @@ func (bc *BlockChain) recoverAncestors(block *types.Block) (common.Hash, error) // collectLogs collects the logs that were generated or removed during // the processing of a block. These logs are later announced as deleted or reborn. func (bc *BlockChain) collectLogs(b *types.Block, removed bool) []*types.Log { + var blobGasPrice *big.Int + excessBlobGas := b.ExcessBlobGas() + if excessBlobGas != nil { + blobGasPrice = eip4844.CalcBlobFee(*excessBlobGas) + } receipts := rawdb.ReadRawReceipts(bc.db, b.Hash(), b.NumberU64()) - receipts.DeriveFields(bc.chainConfig, b.Hash(), b.NumberU64(), b.Time(), b.BaseFee(), b.Transactions()) - + if err := receipts.DeriveFields(bc.chainConfig, b.Hash(), b.NumberU64(), b.Time(), b.BaseFee(), blobGasPrice, b.Transactions()); err != nil { + log.Error("Failed to derive block receipts fields", "hash", b.Hash(), "number", b.NumberU64(), "err", err) + } var logs []*types.Log for _, receipt := range receipts { for _, log := range receipt.Logs { - l := *log if removed { - l.Removed = true + log.Removed = true } - logs = append(logs, &l) + logs = append(logs, log) } } return logs @@ -2186,10 +2290,10 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { } } if oldBlock == nil { - return errors.New("invalid old chain") + return errInvalidOldChain } if newBlock == nil { - return errors.New("invalid new chain") + return errInvalidNewChain } // Both sides of the reorg are at the same number, reduce both until the common // ancestor is found @@ -2209,11 +2313,11 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Block) error { // Step back with both chains oldBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1) if oldBlock == nil { - return fmt.Errorf("invalid old chain") + return errInvalidOldChain } newBlock = bc.GetBlock(newBlock.ParentHash(), newBlock.NumberU64()-1) if newBlock == nil { - return fmt.Errorf("invalid new chain") + return errInvalidNewChain } } @@ -2441,6 +2545,12 @@ func (bc *BlockChain) skipBlock(err error, it *insertIterator) bool { func (bc *BlockChain) indexBlocks(tail *uint64, head uint64, done chan struct{}) { defer func() { close(done) }() + // If head is 0, it means the chain is just initialized and no blocks are inserted, + // so don't need to indexing anything. + if head == 0 { + return + } + // The tail flag is not existent, it means the node is just initialized // and all blocks(may from ancient store) are not indexed yet. if tail == nil { @@ -2498,6 +2608,15 @@ func (bc *BlockChain) maintainTxIndex() { return } defer sub.Unsubscribe() + log.Info("Initialized transaction indexer", "limit", bc.TxLookupLimit()) + + // Launch the initial processing if chain is not empty. This step is + // useful in these scenarios that chain has no progress and indexer + // is never triggered. + if head := rawdb.ReadHeadBlock(bc.db); head != nil { + done = make(chan struct{}) + go bc.indexBlocks(rawdb.ReadTxIndexTail(bc.db), head.NumberU64(), done) + } for { select { @@ -2583,3 +2702,8 @@ func (bc *BlockChain) SetBlockValidatorAndProcessorForTesting(v Validator, p Pro func (bc *BlockChain) SetTrieFlushInterval(interval time.Duration) { bc.flushInterval.Store(int64(interval)) } + +// GetTrieFlushInterval gets the in-memory tries flush interval +func (bc *BlockChain) GetTrieFlushInterval() time.Duration { + return time.Duration(bc.flushInterval.Load()) +} diff --git a/core/blockchain_arbitrum.go b/core/blockchain_arbitrum.go index 3f28ee24731a..8468dda93b50 100644 --- a/core/blockchain_arbitrum.go +++ b/core/blockchain_arbitrum.go @@ -18,6 +18,7 @@ package core import ( + "errors" "time" "github.com/ethereum/go-ethereum/core/state" @@ -39,7 +40,10 @@ func (bc *BlockChain) WriteBlockAndSetHeadWithTime(block *types.Block, receipts func (bc *BlockChain) ReorgToOldBlock(newHead *types.Block) error { bc.wg.Add(1) defer bc.wg.Done() - bc.chainmu.MustLock() + locked := bc.chainmu.TryLock() + if !locked { + return errors.New("couldn't catch lock to reorg") + } defer bc.chainmu.Unlock() oldHead := bc.CurrentBlock() if oldHead.Hash() == newHead.Hash() { diff --git a/core/blockchain_insert.go b/core/blockchain_insert.go index 8f496e182c9e..9bf662b6b710 100644 --- a/core/blockchain_insert.go +++ b/core/blockchain_insert.go @@ -39,7 +39,7 @@ const statsReportLimit = 8 * time.Second // report prints statistics if some number of blocks have been processed // or more than a few seconds have passed since the last message. -func (st *insertStats) report(chain []*types.Block, index int, dirty common.StorageSize, setHead bool) { +func (st *insertStats) report(chain []*types.Block, index int, snapDiffItems, snapBufItems, trieDiffNodes, triebufNodes common.StorageSize, setHead bool) { // Fetch the timings for the batch var ( now = mclock.Now() @@ -63,7 +63,16 @@ func (st *insertStats) report(chain []*types.Block, index int, dirty common.Stor if timestamp := time.Unix(int64(end.Time()), 0); time.Since(timestamp) > time.Minute { context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) } - context = append(context, []interface{}{"dirty", dirty}...) + if snapDiffItems != 0 || snapBufItems != 0 { // snapshots enabled + context = append(context, []interface{}{"snapdiffs", snapDiffItems}...) + if snapBufItems != 0 { // future snapshot refactor + context = append(context, []interface{}{"snapdirty", snapBufItems}...) + } + } + if trieDiffNodes != 0 { // pathdb + context = append(context, []interface{}{"triediffs", trieDiffNodes}...) + } + context = append(context, []interface{}{"triedirty", triebufNodes}...) if st.queued > 0 { context = append(context, []interface{}{"queued", st.queued}...) diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index fd65cb2db32f..466a86c14415 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -293,10 +293,16 @@ func (bc *BlockChain) HasBlockAndState(hash common.Hash, number uint64) bool { return bc.HasState(block.Root()) } -// TrieNode retrieves a blob of data associated with a trie node -// either from ephemeral in-memory cache, or from persistent storage. -func (bc *BlockChain) TrieNode(hash common.Hash) ([]byte, error) { - return bc.stateCache.TrieDB().Node(hash) +// stateRecoverable checks if the specified state is recoverable. +// Note, this function assumes the state is not present, because +// state is not treated as recoverable if it's available, thus +// false will be returned in this case. +func (bc *BlockChain) stateRecoverable(root common.Hash) bool { + if bc.triedb.Scheme() == rawdb.HashScheme { + return false + } + result, _ := bc.triedb.Recoverable(root) + return result } // ContractCodeWithPrefix retrieves a blob of data associated with a contract @@ -306,9 +312,11 @@ func (bc *BlockChain) TrieNode(hash common.Hash) ([]byte, error) { // new code scheme. func (bc *BlockChain) ContractCodeWithPrefix(hash common.Hash) ([]byte, error) { type codeReader interface { - ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error) + ContractCodeWithPrefix(address common.Address, codeHash common.Hash) ([]byte, error) } - return bc.stateCache.(codeReader).ContractCodeWithPrefix(common.Hash{}, hash) + // TODO(rjl493456442) The associated account address is also required + // in Verkle scheme. Fix it once snap-sync is supported for Verkle. + return bc.stateCache.(codeReader).ContractCodeWithPrefix(common.Address{}, hash) } // State returns a new mutable state based on the current HEAD block. diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index 1adeff47c547..d7a0eba50255 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -22,6 +22,7 @@ package core import ( "math/big" + "path" "testing" "time" @@ -1749,16 +1750,24 @@ func testLongReorgedSnapSyncingDeepRepair(t *testing.T, snapshots bool) { } func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { + for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} { + testRepairWithScheme(t, tt, snapshots, scheme) + } +} + +func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme string) { // It's hard to follow the test case, visualize the input //log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // fmt.Println(tt.dump(true)) // Create a temporary persistent database datadir := t.TempDir() + ancient := path.Join(datadir, "ancient") db, err := rawdb.Open(rawdb.OpenOptions{ Directory: datadir, - AncientsDirectory: datadir, + AncientsDirectory: ancient, + Ephemeral: true, }) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) @@ -1777,6 +1786,7 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 0, // Disable snapshot by default + StateScheme: scheme, // Arbitrum TriesInMemory: 128, @@ -1810,7 +1820,9 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { t.Fatalf("Failed to import canonical chain start: %v", err) } if tt.commitBlock > 0 { - chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), false) + if err := chain.triedb.Commit(canonblocks[tt.commitBlock-1].Root(), false); err != nil { + t.Fatalf("Failed to flush trie state: %v", err) + } if snapshots { if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil { t.Fatalf("Failed to flatten snapshots: %v", err) @@ -1832,21 +1844,22 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { rawdb.WriteLastPivotNumber(db, *tt.pivotBlock) } // Pull the plug on the database, simulating a hard crash + chain.triedb.Close() db.Close() chain.stopWithoutSaving() // Start a new blockchain back up and see where the repair leads us db, err = rawdb.Open(rawdb.OpenOptions{ Directory: datadir, - AncientsDirectory: datadir, + AncientsDirectory: ancient, + Ephemeral: true, }) - if err != nil { t.Fatalf("Failed to reopen persistent database: %v", err) } defer db.Close() - newChain, err := NewBlockChain(db, nil, nil, gspec, nil, engine, vm.Config{}, nil, nil) + newChain, err := NewBlockChain(db, config, nil, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -1889,17 +1902,22 @@ func testRepair(t *testing.T, tt *rewindTest, snapshots bool) { // In this case the snapshot layer of B3 is not created because of existent // state. func TestIssue23496(t *testing.T) { + testIssue23496(t, rawdb.HashScheme) + testIssue23496(t, rawdb.PathScheme) +} + +func testIssue23496(t *testing.T, scheme string) { // It's hard to follow the test case, visualize the input //log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // Create a temporary persistent database datadir := t.TempDir() + ancient := path.Join(datadir, "ancient") db, err := rawdb.Open(rawdb.OpenOptions{ Directory: datadir, - AncientsDirectory: datadir, + AncientsDirectory: ancient, }) - if err != nil { t.Fatalf("Failed to create persistent database: %v", err) } @@ -1912,19 +1930,8 @@ func TestIssue23496(t *testing.T) { BaseFee: big.NewInt(params.InitialBaseFee), } engine = ethash.NewFullFaker() - config = &CacheConfig{ - TrieCleanLimit: 256, - TrieDirtyLimit: 256, - TrieTimeLimit: 5 * time.Minute, - SnapshotLimit: 256, - SnapshotWait: true, - - // Arbitrum - TriesInMemory: 128, - TrieRetention: 30 * time.Minute, - } ) - chain, err := NewBlockChain(db, config, nil, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -1937,7 +1944,7 @@ func TestIssue23496(t *testing.T) { if _, err := chain.InsertChain(blocks[:1]); err != nil { t.Fatalf("Failed to import canonical chain start: %v", err) } - chain.stateCache.TrieDB().Commit(blocks[0].Root(), false) + chain.triedb.Commit(blocks[0].Root(), false) // Insert block B2 and commit the snapshot into disk if _, err := chain.InsertChain(blocks[1:2]); err != nil { @@ -1951,7 +1958,7 @@ func TestIssue23496(t *testing.T) { if _, err := chain.InsertChain(blocks[2:3]); err != nil { t.Fatalf("Failed to import canonical chain start: %v", err) } - chain.stateCache.TrieDB().Commit(blocks[2].Root(), false) + chain.triedb.Commit(blocks[2].Root(), false) // Insert the remaining blocks if _, err := chain.InsertChain(blocks[3:]); err != nil { @@ -1959,20 +1966,22 @@ func TestIssue23496(t *testing.T) { } // Pull the plug on the database, simulating a hard crash + chain.triedb.Close() db.Close() chain.stopWithoutSaving() // Start a new blockchain back up and see where the repair leads us db, err = rawdb.Open(rawdb.OpenOptions{ Directory: datadir, - AncientsDirectory: datadir, + AncientsDirectory: ancient, + Ephemeral: true, }) if err != nil { t.Fatalf("Failed to reopen persistent database: %v", err) } defer db.Close() - chain, err = NewBlockChain(db, nil, nil, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err = NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -1984,8 +1993,12 @@ func TestIssue23496(t *testing.T) { if head := chain.CurrentSnapBlock(); head.Number.Uint64() != uint64(4) { t.Errorf("Head fast block mismatch: have %d, want %d", head.Number, uint64(4)) } - if head := chain.CurrentBlock(); head.Number.Uint64() != uint64(1) { - t.Errorf("Head block mismatch: have %d, want %d", head.Number, uint64(1)) + expHead := uint64(1) + if scheme == rawdb.PathScheme { + expHead = uint64(2) + } + if head := chain.CurrentBlock(); head.Number.Uint64() != expHead { + t.Errorf("Head block mismatch: have %d, want %d", head.Number, expHead) } // Reinsert B2-B4 diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go index 4e777db1c0cd..8c36b84eda8f 100644 --- a/core/blockchain_sethead_test.go +++ b/core/blockchain_sethead_test.go @@ -22,6 +22,7 @@ package core import ( "fmt" "math/big" + "path" "strings" "testing" "time" @@ -29,9 +30,13 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/hashdb" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" ) // rewindTest is a test case for chain rollback upon user request. @@ -1949,16 +1954,24 @@ func testLongReorgedSnapSyncingDeepSetHead(t *testing.T, snapshots bool) { } func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { + for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} { + testSetHeadWithScheme(t, tt, snapshots, scheme) + } +} + +func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme string) { // It's hard to follow the test case, visualize the input // log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) // fmt.Println(tt.dump(false)) // Create a temporary persistent database datadir := t.TempDir() + ancient := path.Join(datadir, "ancient") db, err := rawdb.Open(rawdb.OpenOptions{ Directory: datadir, - AncientsDirectory: datadir, + AncientsDirectory: ancient, + Ephemeral: true, }) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) @@ -1977,6 +1990,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 0, // Disable snapshot + StateScheme: scheme, // Arbitrum TriesInMemory: 128, @@ -2011,7 +2025,7 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { t.Fatalf("Failed to import canonical chain start: %v", err) } if tt.commitBlock > 0 { - chain.stateCache.TrieDB().Commit(canonblocks[tt.commitBlock-1].Root(), false) + chain.triedb.Commit(canonblocks[tt.commitBlock-1].Root(), false) if snapshots { if err := chain.snaps.Cap(canonblocks[tt.commitBlock-1].Root(), 0); err != nil { t.Fatalf("Failed to flatten snapshots: %v", err) @@ -2021,13 +2035,17 @@ func testSetHead(t *testing.T, tt *rewindTest, snapshots bool) { if _, err := chain.InsertChain(canonblocks[tt.commitBlock:]); err != nil { t.Fatalf("Failed to import canonical chain tail: %v", err) } - // Manually dereference anything not committed to not have to work with 128+ tries - for _, block := range sideblocks { - chain.stateCache.TrieDB().Dereference(block.Root()) - } - for _, block := range canonblocks { - chain.stateCache.TrieDB().Dereference(block.Root()) + // Reopen the trie database without persisting in-memory dirty nodes. + chain.triedb.Close() + dbconfig := &trie.Config{} + if scheme == rawdb.PathScheme { + dbconfig.PathDB = pathdb.Defaults + } else { + dbconfig.HashDB = hashdb.Defaults } + chain.triedb = trie.NewDatabase(chain.db, dbconfig) + chain.stateCache = state.NewDatabaseWithNodeDB(chain.db, chain.triedb) + // Force run a freeze cycle type freezer interface { Freeze(threshold uint64) error diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index bff8be67bf00..547ebccfecff 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -24,6 +24,7 @@ import ( "fmt" "math/big" "os" + "path" "strings" "testing" "time" @@ -39,6 +40,7 @@ import ( // snapshotTestBasic wraps the common testing fields in the snapshot tests. type snapshotTestBasic struct { + scheme string // Disk scheme used for storing trie nodes chainBlocks int // Number of blocks to generate for the canonical chain snapshotBlock uint64 // Block number of the relevant snapshot disk layer commitBlock uint64 // Block number for which to commit the state to disk @@ -51,6 +53,7 @@ type snapshotTestBasic struct { // share fields, set in runtime datadir string + ancient string db ethdb.Database genDb ethdb.Database engine consensus.Engine @@ -60,10 +63,12 @@ type snapshotTestBasic struct { func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Block) { // Create a temporary persistent database datadir := t.TempDir() + ancient := path.Join(datadir, "ancient") db, err := rawdb.Open(rawdb.OpenOptions{ Directory: datadir, - AncientsDirectory: datadir, + AncientsDirectory: ancient, + Ephemeral: true, }) if err != nil { t.Fatalf("Failed to create persistent database: %v", err) @@ -75,13 +80,8 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo Config: params.AllEthashProtocolChanges, } engine = ethash.NewFullFaker() - - // Snapshot is enabled, the first snapshot is created from the Genesis. - // The snapshot memory allowance is 256MB, it means no snapshot flush - // will happen during the block insertion. - cacheConfig = defaultCacheConfig ) - chain, err := NewBlockChain(db, cacheConfig, nil, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(basic.scheme), nil, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -102,7 +102,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo startPoint = point if basic.commitBlock > 0 && basic.commitBlock == point { - chain.stateCache.TrieDB().Commit(blocks[point-1].Root(), false) + chain.TrieDB().Commit(blocks[point-1].Root(), false) } if basic.snapshotBlock > 0 && basic.snapshotBlock == point { // Flushing the entire snap tree into the disk, the @@ -121,6 +121,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo // Set runtime fields basic.datadir = datadir + basic.ancient = ancient basic.db = db basic.genDb = genDb basic.engine = engine @@ -210,6 +211,7 @@ func (basic *snapshotTestBasic) teardown() { basic.db.Close() basic.genDb.Close() os.RemoveAll(basic.datadir) + os.RemoveAll(basic.ancient) } // snapshotTest is a test case type for normal snapshot recovery. @@ -226,7 +228,7 @@ func (snaptest *snapshotTest) test(t *testing.T) { // Restart the chain normally chain.Stop() - newchain, err := NewBlockChain(snaptest.db, nil, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -235,7 +237,7 @@ func (snaptest *snapshotTest) test(t *testing.T) { snaptest.verify(t, newchain, blocks) } -// crashSnapshotTest is a test case type for innormal snapshot recovery. +// crashSnapshotTest is a test case type for irregular snapshot recovery. // It can be used for testing that restart Geth after the crash. type crashSnapshotTest struct { snapshotTestBasic @@ -251,13 +253,14 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { db := chain.db db.Close() chain.stopWithoutSaving() + chain.triedb.Close() // Start a new blockchain back up and see where the repair leads us newdb, err := rawdb.Open(rawdb.OpenOptions{ Directory: snaptest.datadir, - AncientsDirectory: snaptest.datadir, + AncientsDirectory: snaptest.ancient, + Ephemeral: true, }) - if err != nil { t.Fatalf("Failed to reopen persistent database: %v", err) } @@ -267,13 +270,13 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { // the crash, we do restart twice here: one after the crash and one // after the normal stop. It's used to ensure the broken snapshot // can be detected all the time. - newchain, err := NewBlockChain(newdb, nil, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } newchain.Stop() - newchain, err = NewBlockChain(newdb, nil, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -300,7 +303,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { // Insert blocks without enabling snapshot if gapping is required. chain.Stop() - gappedBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.genDb, snaptest.gapped, func(i int, b *BlockGen) {}) + gappedBlocks, _ := GenerateChain(snaptest.gspec.Config, blocks[len(blocks)-1], snaptest.engine, snaptest.genDb, snaptest.gapped, func(i int, b *BlockGen) {}) // Insert a few more blocks without enabling snapshot var cacheConfig = &CacheConfig{ @@ -313,6 +316,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 0, + StateScheme: snaptest.scheme, } newchain, err := NewBlockChain(snaptest.db, cacheConfig, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { @@ -322,7 +326,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { newchain.Stop() // Restart the chain with enabling the snapshot - newchain, err = NewBlockChain(snaptest.db, nil, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -350,7 +354,7 @@ func (snaptest *setHeadSnapshotTest) test(t *testing.T) { chain.SetHead(snaptest.setHead) chain.Stop() - newchain, err := NewBlockChain(snaptest.db, nil, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -389,16 +393,17 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 0, + StateScheme: snaptest.scheme, } newchain, err := NewBlockChain(snaptest.db, config, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } - newBlocks, _ := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], snaptest.engine, snaptest.genDb, snaptest.newBlocks, func(i int, b *BlockGen) {}) + newBlocks, _ := GenerateChain(snaptest.gspec.Config, blocks[len(blocks)-1], snaptest.engine, snaptest.genDb, snaptest.newBlocks, func(i int, b *BlockGen) {}) newchain.InsertChain(newBlocks) newchain.Stop() - // Restart the chain, the wiper should starts working + // Restart the chain, the wiper should start working config = &CacheConfig{ // Arbitrum @@ -410,6 +415,7 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 256, SnapshotWait: false, // Don't wait rebuild + StateScheme: snaptest.scheme, } tmp, err := NewBlockChain(snaptest.db, config, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { @@ -417,14 +423,15 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { } // Simulate the blockchain crash. + tmp.triedb.Close() tmp.stopWithoutSaving() - newchain, err = NewBlockChain(snaptest.db, nil, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } - defer newchain.Stop() snaptest.verify(t, newchain, blocks) + newchain.Stop() } // Tests a Geth restart with valid snapshot. Before the shutdown, all snapshot @@ -448,20 +455,23 @@ func TestRestartWithNewSnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : C8 // Expected snapshot disk : G - test := &snapshotTest{ - snapshotTestBasic{ - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 8, - expSnapshotBottom: 0, // Initial disk layer built from genesis - }, - } - test.test(t) - test.teardown() + for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} { + test := &snapshotTest{ + snapshotTestBasic{ + scheme: scheme, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 8, + expSnapshotBottom: 0, // Initial disk layer built from genesis + }, + } + test.test(t) + test.teardown() + } } // Tests a Geth was crashed and restarts with a broken snapshot. In this case the @@ -487,20 +497,23 @@ func TestNoCommitCrashWithNewSnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : G // Expected snapshot disk : C4 - test := &crashSnapshotTest{ - snapshotTestBasic{ - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 0, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 0, - expSnapshotBottom: 4, // Last committed disk layer, wait recovery - }, - } - test.test(t) - test.teardown() + for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} { + test := &crashSnapshotTest{ + snapshotTestBasic{ + scheme: scheme, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 0, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 0, + expSnapshotBottom: 4, // Last committed disk layer, wait recovery + }, + } + test.test(t) + test.teardown() + } } // Tests a Geth was crashed and restarts with a broken snapshot. In this case the @@ -526,20 +539,23 @@ func TestLowCommitCrashWithNewSnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : C2 // Expected snapshot disk : C4 - test := &crashSnapshotTest{ - snapshotTestBasic{ - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 2, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 2, - expSnapshotBottom: 4, // Last committed disk layer, wait recovery - }, - } - test.test(t) - test.teardown() + for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} { + test := &crashSnapshotTest{ + snapshotTestBasic{ + scheme: scheme, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 2, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: 2, + expSnapshotBottom: 4, // Last committed disk layer, wait recovery + }, + } + test.test(t) + test.teardown() + } } // Tests a Geth was crashed and restarts with a broken snapshot. In this case @@ -565,20 +581,27 @@ func TestHighCommitCrashWithNewSnapshot(t *testing.T) { // Expected head fast block: C8 // Expected head block : G // Expected snapshot disk : C4 - test := &crashSnapshotTest{ - snapshotTestBasic{ - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 6, - expCanonicalBlocks: 8, - expHeadHeader: 8, - expHeadFastBlock: 8, - expHeadBlock: 0, - expSnapshotBottom: 4, // Last committed disk layer, wait recovery - }, - } - test.test(t) - test.teardown() + for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} { + expHead := uint64(0) + if scheme == rawdb.PathScheme { + expHead = uint64(4) + } + test := &crashSnapshotTest{ + snapshotTestBasic{ + scheme: scheme, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 6, + expCanonicalBlocks: 8, + expHeadHeader: 8, + expHeadFastBlock: 8, + expHeadBlock: expHead, + expSnapshotBottom: 4, // Last committed disk layer, wait recovery + }, + } + test.test(t) + test.teardown() + } } // Tests a Geth was running with snapshot enabled. Then restarts without @@ -602,21 +625,24 @@ func TestGappedNewSnapshot(t *testing.T) { // Expected head fast block: C10 // Expected head block : C10 // Expected snapshot disk : C10 - test := &gappedSnapshotTest{ - snapshotTestBasic: snapshotTestBasic{ - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 10, - expHeadHeader: 10, - expHeadFastBlock: 10, - expHeadBlock: 10, - expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD - }, - gapped: 2, - } - test.test(t) - test.teardown() + for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} { + test := &gappedSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + scheme: scheme, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 10, + expHeadHeader: 10, + expHeadFastBlock: 10, + expHeadBlock: 10, + expSnapshotBottom: 10, // Rebuilt snapshot from the latest HEAD + }, + gapped: 2, + } + test.test(t) + test.teardown() + } } // Tests the Geth was running with snapshot enabled and resetHead is applied. @@ -640,21 +666,24 @@ func TestSetHeadWithNewSnapshot(t *testing.T) { // Expected head fast block: C4 // Expected head block : C4 // Expected snapshot disk : G - test := &setHeadSnapshotTest{ - snapshotTestBasic: snapshotTestBasic{ - chainBlocks: 8, - snapshotBlock: 0, - commitBlock: 0, - expCanonicalBlocks: 4, - expHeadHeader: 4, - expHeadFastBlock: 4, - expHeadBlock: 4, - expSnapshotBottom: 0, // The initial disk layer is built from the genesis - }, - setHead: 4, - } - test.test(t) - test.teardown() + for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} { + test := &setHeadSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + scheme: scheme, + chainBlocks: 8, + snapshotBlock: 0, + commitBlock: 0, + expCanonicalBlocks: 4, + expHeadHeader: 4, + expHeadFastBlock: 4, + expHeadBlock: 4, + expSnapshotBottom: 0, // The initial disk layer is built from the genesis + }, + setHead: 4, + } + test.test(t) + test.teardown() + } } // Tests the Geth was running with a complete snapshot and then imports a few @@ -678,19 +707,22 @@ func TestRecoverSnapshotFromWipingCrash(t *testing.T) { // Expected head fast block: C10 // Expected head block : C8 // Expected snapshot disk : C10 - test := &wipeCrashSnapshotTest{ - snapshotTestBasic: snapshotTestBasic{ - chainBlocks: 8, - snapshotBlock: 4, - commitBlock: 0, - expCanonicalBlocks: 10, - expHeadHeader: 10, - expHeadFastBlock: 10, - expHeadBlock: 10, - expSnapshotBottom: 10, - }, - newBlocks: 2, - } - test.test(t) - test.teardown() + for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} { + test := &wipeCrashSnapshotTest{ + snapshotTestBasic: snapshotTestBasic{ + scheme: scheme, + chainBlocks: 8, + snapshotBlock: 4, + commitBlock: 0, + expCanonicalBlocks: 10, + expHeadHeader: 10, + expHeadFastBlock: 10, + expHeadBlock: 10, + expSnapshotBottom: 10, + }, + newBlocks: 2, + } + test.test(t) + test.teardown() + } } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index b23685f12348..c6dbcf70ee47 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -52,7 +52,7 @@ var ( // chain. Depending on the full flag, if creates either a full block chain or a // header only chain. The database and genesis specification for block generation // are also returned in case more test blocks are needed later. -func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *Genesis, *BlockChain, error) { +func newCanonical(engine consensus.Engine, n int, full bool, scheme string) (ethdb.Database, *Genesis, *BlockChain, error) { var ( genesis = &Genesis{ BaseFee: big.NewInt(params.InitialBaseFee), @@ -60,7 +60,7 @@ func newCanonical(engine consensus.Engine, n int, full bool) (ethdb.Database, *G } ) // Initialize a fresh chain with only a genesis block - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, genesis, nil, engine, vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, genesis, nil, engine, vm.Config{}, nil, nil) // Create and inject the requested chain if n == 0 { @@ -83,9 +83,9 @@ func newGwei(n int64) *big.Int { } // Test fork of length N starting from block i -func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int)) { +func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, comparator func(td1, td2 *big.Int), scheme string) { // Copy old chain up to #i into a new db - genDb, _, blockchain2, err := newCanonical(ethash.NewFaker(), i, full) + genDb, _, blockchain2, err := newCanonical(ethash.NewFaker(), i, full, scheme) if err != nil { t.Fatal("could not make new canonical in testFork", err) } @@ -176,7 +176,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { blockchain.chainmu.MustLock() rawdb.WriteTd(blockchain.db, block.Hash(), block.NumberU64(), new(big.Int).Add(block.Difficulty(), blockchain.GetTd(block.ParentHash(), block.NumberU64()-1))) rawdb.WriteBlock(blockchain.db, block) - statedb.Commit(false) + statedb.Commit(block.NumberU64(), false) blockchain.chainmu.Unlock() } return nil @@ -198,9 +198,13 @@ func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error } return nil } - func TestLastBlock(t *testing.T) { - genDb, _, blockchain, err := newCanonical(ethash.NewFaker(), 0, true) + testLastBlock(t, rawdb.HashScheme) + testLastBlock(t, rawdb.PathScheme) +} + +func testLastBlock(t *testing.T, scheme string) { + genDb, _, blockchain, err := newCanonical(ethash.NewFaker(), 0, true, scheme) if err != nil { t.Fatalf("failed to create pristine chain: %v", err) } @@ -217,9 +221,9 @@ func TestLastBlock(t *testing.T) { // Test inserts the blocks/headers after the fork choice rule is changed. // The chain is reorged to whatever specified. -func testInsertAfterMerge(t *testing.T, blockchain *BlockChain, i, n int, full bool) { +func testInsertAfterMerge(t *testing.T, blockchain *BlockChain, i, n int, full bool, scheme string) { // Copy old chain up to #i into a new db - genDb, _, blockchain2, err := newCanonical(ethash.NewFaker(), i, full) + genDb, _, blockchain2, err := newCanonical(ethash.NewFaker(), i, full, scheme) if err != nil { t.Fatal("could not make new canonical in testFork", err) } @@ -266,14 +270,20 @@ func testInsertAfterMerge(t *testing.T, blockchain *BlockChain, i, n int, full b // Tests that given a starting canonical chain of a given size, it can be extended // with various length chains. -func TestExtendCanonicalHeaders(t *testing.T) { testExtendCanonical(t, false) } -func TestExtendCanonicalBlocks(t *testing.T) { testExtendCanonical(t, true) } +func TestExtendCanonicalHeaders(t *testing.T) { + testExtendCanonical(t, false, rawdb.HashScheme) + testExtendCanonical(t, false, rawdb.PathScheme) +} +func TestExtendCanonicalBlocks(t *testing.T) { + testExtendCanonical(t, true, rawdb.HashScheme) + testExtendCanonical(t, true, rawdb.PathScheme) +} -func testExtendCanonical(t *testing.T, full bool) { +func testExtendCanonical(t *testing.T, full bool, scheme string) { length := 5 // Make first chain starting from genesis - _, _, processor, err := newCanonical(ethash.NewFaker(), length, full) + _, _, processor, err := newCanonical(ethash.NewFaker(), length, full, scheme) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } @@ -286,41 +296,53 @@ func testExtendCanonical(t *testing.T, full bool) { } } // Start fork from current height - testFork(t, processor, length, 1, full, better) - testFork(t, processor, length, 2, full, better) - testFork(t, processor, length, 5, full, better) - testFork(t, processor, length, 10, full, better) + testFork(t, processor, length, 1, full, better, scheme) + testFork(t, processor, length, 2, full, better, scheme) + testFork(t, processor, length, 5, full, better, scheme) + testFork(t, processor, length, 10, full, better, scheme) } // Tests that given a starting canonical chain of a given size, it can be extended // with various length chains. -func TestExtendCanonicalHeadersAfterMerge(t *testing.T) { testExtendCanonicalAfterMerge(t, false) } -func TestExtendCanonicalBlocksAfterMerge(t *testing.T) { testExtendCanonicalAfterMerge(t, true) } +func TestExtendCanonicalHeadersAfterMerge(t *testing.T) { + testExtendCanonicalAfterMerge(t, false, rawdb.HashScheme) + testExtendCanonicalAfterMerge(t, false, rawdb.PathScheme) +} +func TestExtendCanonicalBlocksAfterMerge(t *testing.T) { + testExtendCanonicalAfterMerge(t, true, rawdb.HashScheme) + testExtendCanonicalAfterMerge(t, true, rawdb.PathScheme) +} -func testExtendCanonicalAfterMerge(t *testing.T, full bool) { +func testExtendCanonicalAfterMerge(t *testing.T, full bool, scheme string) { length := 5 // Make first chain starting from genesis - _, _, processor, err := newCanonical(ethash.NewFaker(), length, full) + _, _, processor, err := newCanonical(ethash.NewFaker(), length, full, scheme) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } defer processor.Stop() - testInsertAfterMerge(t, processor, length, 1, full) - testInsertAfterMerge(t, processor, length, 10, full) + testInsertAfterMerge(t, processor, length, 1, full, scheme) + testInsertAfterMerge(t, processor, length, 10, full, scheme) } // Tests that given a starting canonical chain of a given size, creating shorter // forks do not take canonical ownership. -func TestShorterForkHeaders(t *testing.T) { testShorterFork(t, false) } -func TestShorterForkBlocks(t *testing.T) { testShorterFork(t, true) } +func TestShorterForkHeaders(t *testing.T) { + testShorterFork(t, false, rawdb.HashScheme) + testShorterFork(t, false, rawdb.PathScheme) +} +func TestShorterForkBlocks(t *testing.T) { + testShorterFork(t, true, rawdb.HashScheme) + testShorterFork(t, true, rawdb.PathScheme) +} -func testShorterFork(t *testing.T, full bool) { +func testShorterFork(t *testing.T, full bool, scheme string) { length := 10 // Make first chain starting from genesis - _, _, processor, err := newCanonical(ethash.NewFaker(), length, full) + _, _, processor, err := newCanonical(ethash.NewFaker(), length, full, scheme) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } @@ -333,93 +355,117 @@ func testShorterFork(t *testing.T, full bool) { } } // Sum of numbers must be less than `length` for this to be a shorter fork - testFork(t, processor, 0, 3, full, worse) - testFork(t, processor, 0, 7, full, worse) - testFork(t, processor, 1, 1, full, worse) - testFork(t, processor, 1, 7, full, worse) - testFork(t, processor, 5, 3, full, worse) - testFork(t, processor, 5, 4, full, worse) + testFork(t, processor, 0, 3, full, worse, scheme) + testFork(t, processor, 0, 7, full, worse, scheme) + testFork(t, processor, 1, 1, full, worse, scheme) + testFork(t, processor, 1, 7, full, worse, scheme) + testFork(t, processor, 5, 3, full, worse, scheme) + testFork(t, processor, 5, 4, full, worse, scheme) } // Tests that given a starting canonical chain of a given size, creating shorter // forks do not take canonical ownership. -func TestShorterForkHeadersAfterMerge(t *testing.T) { testShorterForkAfterMerge(t, false) } -func TestShorterForkBlocksAfterMerge(t *testing.T) { testShorterForkAfterMerge(t, true) } +func TestShorterForkHeadersAfterMerge(t *testing.T) { + testShorterForkAfterMerge(t, false, rawdb.HashScheme) + testShorterForkAfterMerge(t, false, rawdb.PathScheme) +} +func TestShorterForkBlocksAfterMerge(t *testing.T) { + testShorterForkAfterMerge(t, true, rawdb.HashScheme) + testShorterForkAfterMerge(t, true, rawdb.PathScheme) +} -func testShorterForkAfterMerge(t *testing.T, full bool) { +func testShorterForkAfterMerge(t *testing.T, full bool, scheme string) { length := 10 // Make first chain starting from genesis - _, _, processor, err := newCanonical(ethash.NewFaker(), length, full) + _, _, processor, err := newCanonical(ethash.NewFaker(), length, full, scheme) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } defer processor.Stop() - testInsertAfterMerge(t, processor, 0, 3, full) - testInsertAfterMerge(t, processor, 0, 7, full) - testInsertAfterMerge(t, processor, 1, 1, full) - testInsertAfterMerge(t, processor, 1, 7, full) - testInsertAfterMerge(t, processor, 5, 3, full) - testInsertAfterMerge(t, processor, 5, 4, full) + testInsertAfterMerge(t, processor, 0, 3, full, scheme) + testInsertAfterMerge(t, processor, 0, 7, full, scheme) + testInsertAfterMerge(t, processor, 1, 1, full, scheme) + testInsertAfterMerge(t, processor, 1, 7, full, scheme) + testInsertAfterMerge(t, processor, 5, 3, full, scheme) + testInsertAfterMerge(t, processor, 5, 4, full, scheme) } // Tests that given a starting canonical chain of a given size, creating longer // forks do take canonical ownership. -func TestLongerForkHeaders(t *testing.T) { testLongerFork(t, false) } -func TestLongerForkBlocks(t *testing.T) { testLongerFork(t, true) } +func TestLongerForkHeaders(t *testing.T) { + testLongerFork(t, false, rawdb.HashScheme) + testLongerFork(t, false, rawdb.PathScheme) +} +func TestLongerForkBlocks(t *testing.T) { + testLongerFork(t, true, rawdb.HashScheme) + testLongerFork(t, true, rawdb.PathScheme) +} -func testLongerFork(t *testing.T, full bool) { +func testLongerFork(t *testing.T, full bool, scheme string) { length := 10 // Make first chain starting from genesis - _, _, processor, err := newCanonical(ethash.NewFaker(), length, full) + _, _, processor, err := newCanonical(ethash.NewFaker(), length, full, scheme) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } defer processor.Stop() - testInsertAfterMerge(t, processor, 0, 11, full) - testInsertAfterMerge(t, processor, 0, 15, full) - testInsertAfterMerge(t, processor, 1, 10, full) - testInsertAfterMerge(t, processor, 1, 12, full) - testInsertAfterMerge(t, processor, 5, 6, full) - testInsertAfterMerge(t, processor, 5, 8, full) + testInsertAfterMerge(t, processor, 0, 11, full, scheme) + testInsertAfterMerge(t, processor, 0, 15, full, scheme) + testInsertAfterMerge(t, processor, 1, 10, full, scheme) + testInsertAfterMerge(t, processor, 1, 12, full, scheme) + testInsertAfterMerge(t, processor, 5, 6, full, scheme) + testInsertAfterMerge(t, processor, 5, 8, full, scheme) } // Tests that given a starting canonical chain of a given size, creating longer // forks do take canonical ownership. -func TestLongerForkHeadersAfterMerge(t *testing.T) { testLongerForkAfterMerge(t, false) } -func TestLongerForkBlocksAfterMerge(t *testing.T) { testLongerForkAfterMerge(t, true) } +func TestLongerForkHeadersAfterMerge(t *testing.T) { + testLongerForkAfterMerge(t, false, rawdb.HashScheme) + testLongerForkAfterMerge(t, false, rawdb.PathScheme) +} +func TestLongerForkBlocksAfterMerge(t *testing.T) { + testLongerForkAfterMerge(t, true, rawdb.HashScheme) + testLongerForkAfterMerge(t, true, rawdb.PathScheme) +} -func testLongerForkAfterMerge(t *testing.T, full bool) { +func testLongerForkAfterMerge(t *testing.T, full bool, scheme string) { length := 10 // Make first chain starting from genesis - _, _, processor, err := newCanonical(ethash.NewFaker(), length, full) + _, _, processor, err := newCanonical(ethash.NewFaker(), length, full, scheme) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } defer processor.Stop() - testInsertAfterMerge(t, processor, 0, 11, full) - testInsertAfterMerge(t, processor, 0, 15, full) - testInsertAfterMerge(t, processor, 1, 10, full) - testInsertAfterMerge(t, processor, 1, 12, full) - testInsertAfterMerge(t, processor, 5, 6, full) - testInsertAfterMerge(t, processor, 5, 8, full) + testInsertAfterMerge(t, processor, 0, 11, full, scheme) + testInsertAfterMerge(t, processor, 0, 15, full, scheme) + testInsertAfterMerge(t, processor, 1, 10, full, scheme) + testInsertAfterMerge(t, processor, 1, 12, full, scheme) + testInsertAfterMerge(t, processor, 5, 6, full, scheme) + testInsertAfterMerge(t, processor, 5, 8, full, scheme) } // Tests that given a starting canonical chain of a given size, creating equal // forks do take canonical ownership. -func TestEqualForkHeaders(t *testing.T) { testEqualFork(t, false) } -func TestEqualForkBlocks(t *testing.T) { testEqualFork(t, true) } +func TestEqualForkHeaders(t *testing.T) { + testEqualFork(t, false, rawdb.HashScheme) + testEqualFork(t, false, rawdb.PathScheme) +} +func TestEqualForkBlocks(t *testing.T) { + testEqualFork(t, true, rawdb.HashScheme) + testEqualFork(t, true, rawdb.PathScheme) +} -func testEqualFork(t *testing.T, full bool) { +func testEqualFork(t *testing.T, full bool, scheme string) { length := 10 // Make first chain starting from genesis - _, _, processor, err := newCanonical(ethash.NewFaker(), length, full) + _, _, processor, err := newCanonical(ethash.NewFaker(), length, full, scheme) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } @@ -432,44 +478,56 @@ func testEqualFork(t *testing.T, full bool) { } } // Sum of numbers must be equal to `length` for this to be an equal fork - testFork(t, processor, 0, 10, full, equal) - testFork(t, processor, 1, 9, full, equal) - testFork(t, processor, 2, 8, full, equal) - testFork(t, processor, 5, 5, full, equal) - testFork(t, processor, 6, 4, full, equal) - testFork(t, processor, 9, 1, full, equal) + testFork(t, processor, 0, 10, full, equal, scheme) + testFork(t, processor, 1, 9, full, equal, scheme) + testFork(t, processor, 2, 8, full, equal, scheme) + testFork(t, processor, 5, 5, full, equal, scheme) + testFork(t, processor, 6, 4, full, equal, scheme) + testFork(t, processor, 9, 1, full, equal, scheme) } // Tests that given a starting canonical chain of a given size, creating equal // forks do take canonical ownership. -func TestEqualForkHeadersAfterMerge(t *testing.T) { testEqualForkAfterMerge(t, false) } -func TestEqualForkBlocksAfterMerge(t *testing.T) { testEqualForkAfterMerge(t, true) } +func TestEqualForkHeadersAfterMerge(t *testing.T) { + testEqualForkAfterMerge(t, false, rawdb.HashScheme) + testEqualForkAfterMerge(t, false, rawdb.PathScheme) +} +func TestEqualForkBlocksAfterMerge(t *testing.T) { + testEqualForkAfterMerge(t, true, rawdb.HashScheme) + testEqualForkAfterMerge(t, true, rawdb.PathScheme) +} -func testEqualForkAfterMerge(t *testing.T, full bool) { +func testEqualForkAfterMerge(t *testing.T, full bool, scheme string) { length := 10 // Make first chain starting from genesis - _, _, processor, err := newCanonical(ethash.NewFaker(), length, full) + _, _, processor, err := newCanonical(ethash.NewFaker(), length, full, scheme) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } defer processor.Stop() - testInsertAfterMerge(t, processor, 0, 10, full) - testInsertAfterMerge(t, processor, 1, 9, full) - testInsertAfterMerge(t, processor, 2, 8, full) - testInsertAfterMerge(t, processor, 5, 5, full) - testInsertAfterMerge(t, processor, 6, 4, full) - testInsertAfterMerge(t, processor, 9, 1, full) + testInsertAfterMerge(t, processor, 0, 10, full, scheme) + testInsertAfterMerge(t, processor, 1, 9, full, scheme) + testInsertAfterMerge(t, processor, 2, 8, full, scheme) + testInsertAfterMerge(t, processor, 5, 5, full, scheme) + testInsertAfterMerge(t, processor, 6, 4, full, scheme) + testInsertAfterMerge(t, processor, 9, 1, full, scheme) } // Tests that chains missing links do not get accepted by the processor. -func TestBrokenHeaderChain(t *testing.T) { testBrokenChain(t, false) } -func TestBrokenBlockChain(t *testing.T) { testBrokenChain(t, true) } +func TestBrokenHeaderChain(t *testing.T) { + testBrokenChain(t, false, rawdb.HashScheme) + testBrokenChain(t, false, rawdb.PathScheme) +} +func TestBrokenBlockChain(t *testing.T) { + testBrokenChain(t, true, rawdb.HashScheme) + testBrokenChain(t, true, rawdb.PathScheme) +} -func testBrokenChain(t *testing.T, full bool) { +func testBrokenChain(t *testing.T, full bool, scheme string) { // Make chain starting from genesis - genDb, _, blockchain, err := newCanonical(ethash.NewFaker(), 10, full) + genDb, _, blockchain, err := newCanonical(ethash.NewFaker(), 10, full, scheme) if err != nil { t.Fatalf("failed to make new canonical chain: %v", err) } @@ -491,19 +549,31 @@ func testBrokenChain(t *testing.T, full bool) { // Tests that reorganising a long difficult chain after a short easy one // overwrites the canonical numbers and links in the database. -func TestReorgLongHeaders(t *testing.T) { testReorgLong(t, false) } -func TestReorgLongBlocks(t *testing.T) { testReorgLong(t, true) } +func TestReorgLongHeaders(t *testing.T) { + testReorgLong(t, false, rawdb.HashScheme) + testReorgLong(t, false, rawdb.PathScheme) +} +func TestReorgLongBlocks(t *testing.T) { + testReorgLong(t, true, rawdb.HashScheme) + testReorgLong(t, true, rawdb.PathScheme) +} -func testReorgLong(t *testing.T, full bool) { - testReorg(t, []int64{0, 0, -9}, []int64{0, 0, 0, -9}, 393280+params.GenesisDifficulty.Int64(), full) +func testReorgLong(t *testing.T, full bool, scheme string) { + testReorg(t, []int64{0, 0, -9}, []int64{0, 0, 0, -9}, 393280+params.GenesisDifficulty.Int64(), full, scheme) } // Tests that reorganising a short difficult chain after a long easy one // overwrites the canonical numbers and links in the database. -func TestReorgShortHeaders(t *testing.T) { testReorgShort(t, false) } -func TestReorgShortBlocks(t *testing.T) { testReorgShort(t, true) } +func TestReorgShortHeaders(t *testing.T) { + testReorgShort(t, false, rawdb.HashScheme) + testReorgShort(t, false, rawdb.PathScheme) +} +func TestReorgShortBlocks(t *testing.T) { + testReorgShort(t, true, rawdb.HashScheme) + testReorgShort(t, true, rawdb.PathScheme) +} -func testReorgShort(t *testing.T, full bool) { +func testReorgShort(t *testing.T, full bool, scheme string) { // Create a long easy chain vs. a short heavy one. Due to difficulty adjustment // we need a fairly long chain of blocks with different difficulties for a short // one to become heavier than a long one. The 96 is an empirical value. @@ -515,12 +585,12 @@ func testReorgShort(t *testing.T, full bool) { for i := 0; i < len(diff); i++ { diff[i] = -9 } - testReorg(t, easy, diff, 12615120+params.GenesisDifficulty.Int64(), full) + testReorg(t, easy, diff, 12615120+params.GenesisDifficulty.Int64(), full, scheme) } -func testReorg(t *testing.T, first, second []int64, td int64, full bool) { +func testReorg(t *testing.T, first, second []int64, td int64, full bool, scheme string) { // Create a pristine chain and database - genDb, _, blockchain, err := newCanonical(ethash.NewFaker(), 0, full) + genDb, _, blockchain, err := newCanonical(ethash.NewFaker(), 0, full, scheme) if err != nil { t.Fatalf("failed to create pristine chain: %v", err) } @@ -588,12 +658,18 @@ func testReorg(t *testing.T, first, second []int64, td int64, full bool) { } // Tests that the insertion functions detect banned hashes. -func TestBadHeaderHashes(t *testing.T) { testBadHashes(t, false) } -func TestBadBlockHashes(t *testing.T) { testBadHashes(t, true) } +func TestBadHeaderHashes(t *testing.T) { + testBadHashes(t, false, rawdb.HashScheme) + testBadHashes(t, false, rawdb.PathScheme) +} +func TestBadBlockHashes(t *testing.T) { + testBadHashes(t, true, rawdb.HashScheme) + testBadHashes(t, true, rawdb.PathScheme) +} -func testBadHashes(t *testing.T, full bool) { +func testBadHashes(t *testing.T, full bool, scheme string) { // Create a pristine chain and database - genDb, _, blockchain, err := newCanonical(ethash.NewFaker(), 0, full) + genDb, _, blockchain, err := newCanonical(ethash.NewFaker(), 0, full, scheme) if err != nil { t.Fatalf("failed to create pristine chain: %v", err) } @@ -622,12 +698,18 @@ func testBadHashes(t *testing.T, full bool) { // Tests that bad hashes are detected on boot, and the chain rolled back to a // good state prior to the bad hash. -func TestReorgBadHeaderHashes(t *testing.T) { testReorgBadHashes(t, false) } -func TestReorgBadBlockHashes(t *testing.T) { testReorgBadHashes(t, true) } +func TestReorgBadHeaderHashes(t *testing.T) { + testReorgBadHashes(t, false, rawdb.HashScheme) + testReorgBadHashes(t, false, rawdb.PathScheme) +} +func TestReorgBadBlockHashes(t *testing.T) { + testReorgBadHashes(t, true, rawdb.HashScheme) + testReorgBadHashes(t, true, rawdb.PathScheme) +} -func testReorgBadHashes(t *testing.T, full bool) { +func testReorgBadHashes(t *testing.T, full bool, scheme string) { // Create a pristine chain and database - genDb, gspec, blockchain, err := newCanonical(ethash.NewFaker(), 0, full) + genDb, gspec, blockchain, err := newCanonical(ethash.NewFaker(), 0, full, scheme) if err != nil { t.Fatalf("failed to create pristine chain: %v", err) } @@ -657,7 +739,7 @@ func testReorgBadHashes(t *testing.T, full bool) { blockchain.Stop() // Create a new BlockChain and check that it rolled back the state. - ncm, err := NewBlockChain(blockchain.db, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ncm, err := NewBlockChain(blockchain.db, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create new chain manager: %v", err) } @@ -677,13 +759,19 @@ func testReorgBadHashes(t *testing.T, full bool) { } // Tests chain insertions in the face of one entity containing an invalid nonce. -func TestHeadersInsertNonceError(t *testing.T) { testInsertNonceError(t, false) } -func TestBlocksInsertNonceError(t *testing.T) { testInsertNonceError(t, true) } +func TestHeadersInsertNonceError(t *testing.T) { + testInsertNonceError(t, false, rawdb.HashScheme) + testInsertNonceError(t, false, rawdb.PathScheme) +} +func TestBlocksInsertNonceError(t *testing.T) { + testInsertNonceError(t, true, rawdb.HashScheme) + testInsertNonceError(t, true, rawdb.PathScheme) +} -func testInsertNonceError(t *testing.T, full bool) { +func testInsertNonceError(t *testing.T, full bool, scheme string) { doTest := func(i int) { // Create a pristine chain and database - genDb, _, blockchain, err := newCanonical(ethash.NewFaker(), 0, full) + genDb, _, blockchain, err := newCanonical(ethash.NewFaker(), 0, full, scheme) if err != nil { t.Fatalf("failed to create pristine chain: %v", err) } @@ -738,6 +826,11 @@ func testInsertNonceError(t *testing.T, full bool) { // Tests that fast importing a block chain produces the same chain data as the // classical full block processing. func TestFastVsFullChains(t *testing.T) { + testFastVsFullChains(t, rawdb.HashScheme) + testFastVsFullChains(t, rawdb.PathScheme) +} + +func testFastVsFullChains(t *testing.T, scheme string) { // Configure and generate a sample block chain var ( key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -770,7 +863,7 @@ func TestFastVsFullChains(t *testing.T) { }) // Import the chain as an archive node for the comparison baseline archiveDb := rawdb.NewMemoryDatabase() - archive, _ := NewBlockChain(archiveDb, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + archive, _ := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer archive.Stop() if n, err := archive.InsertChain(blocks); err != nil { @@ -778,7 +871,7 @@ func TestFastVsFullChains(t *testing.T) { } // Fast import the chain as a non-archive node to test fastDb := rawdb.NewMemoryDatabase() - fast, _ := NewBlockChain(fastDb, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer fast.Stop() headers := make([]*types.Header, len(blocks)) @@ -797,7 +890,8 @@ func TestFastVsFullChains(t *testing.T) { t.Fatalf("failed to create temp freezer db: %v", err) } defer ancientDb.Close() - ancient, _ := NewBlockChain(ancientDb, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer ancient.Stop() if n, err := ancient.InsertHeaderChain(headers); err != nil { @@ -865,6 +959,11 @@ func TestFastVsFullChains(t *testing.T) { // Tests that various import methods move the chain head pointers to the correct // positions. func TestLightVsFastVsFullChainHeads(t *testing.T) { + testLightVsFastVsFullChainHeads(t, rawdb.HashScheme) + testLightVsFastVsFullChainHeads(t, rawdb.PathScheme) +} + +func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) { // Configure and generate a sample block chain var ( key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -910,6 +1009,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { archiveCaching := *defaultCacheConfig archiveCaching.TrieDirtyDisabled = true + archiveCaching.StateScheme = scheme archive, _ := NewBlockChain(archiveDb, &archiveCaching, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) if n, err := archive.InsertChain(blocks); err != nil { @@ -924,7 +1024,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { // Import the chain as a non-archive node and ensure all pointers are updated fastDb := makeDb() defer fastDb.Close() - fast, _ := NewBlockChain(fastDb, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer fast.Stop() headers := make([]*types.Header, len(blocks)) @@ -944,7 +1044,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { // Import the chain as a ancient-first node and ensure all pointers are updated ancientDb := makeDb() defer ancientDb.Close() - ancient, _ := NewBlockChain(ancientDb, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer ancient.Stop() if n, err := ancient.InsertHeaderChain(headers); err != nil { @@ -963,7 +1063,7 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { // Import the chain as a light node and ensure all pointers are updated lightDb := makeDb() defer lightDb.Close() - light, _ := NewBlockChain(lightDb, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + light, _ := NewBlockChain(lightDb, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) if n, err := light.InsertHeaderChain(headers); err != nil { t.Fatalf("failed to insert header %d: %v", n, err) } @@ -976,6 +1076,11 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { // Tests that chain reorganisations handle transaction removals and reinsertions. func TestChainTxReorgs(t *testing.T) { + testChainTxReorgs(t, rawdb.HashScheme) + testChainTxReorgs(t, rawdb.PathScheme) +} + +func testChainTxReorgs(t *testing.T, scheme string) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") @@ -1031,7 +1136,7 @@ func TestChainTxReorgs(t *testing.T) { }) // Import the chain. This runs all block validation rules. db := rawdb.NewMemoryDatabase() - blockchain, _ := NewBlockChain(db, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) if i, err := blockchain.InsertChain(chain); err != nil { t.Fatalf("failed to insert original chain[%d]: %v", i, err) } @@ -1090,6 +1195,11 @@ func TestChainTxReorgs(t *testing.T) { } func TestLogReorgs(t *testing.T) { + testLogReorgs(t, rawdb.HashScheme) + testLogReorgs(t, rawdb.PathScheme) +} + +func testLogReorgs(t *testing.T, scheme string) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr1 = crypto.PubkeyToAddress(key1.PublicKey) @@ -1100,7 +1210,7 @@ func TestLogReorgs(t *testing.T) { signer = types.LatestSigner(gspec.Config) ) - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer blockchain.Stop() rmLogsCh := make(chan RemovedLogsEvent) @@ -1145,13 +1255,18 @@ var logCode = common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd // This test checks that log events and RemovedLogsEvent are sent // when the chain reorganizes. func TestLogRebirth(t *testing.T) { + testLogRebirth(t, rawdb.HashScheme) + testLogRebirth(t, rawdb.PathScheme) +} + +func testLogRebirth(t *testing.T, scheme string) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr1 = crypto.PubkeyToAddress(key1.PublicKey) gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}} signer = types.LatestSigner(gspec.Config) engine = ethash.NewFaker() - blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, engine, vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, engine, vm.Config{}, nil, nil) ) defer blockchain.Stop() @@ -1222,12 +1337,17 @@ func TestLogRebirth(t *testing.T) { // This test is a variation of TestLogRebirth. It verifies that log events are emitted // when a side chain containing log events overtakes the canonical chain. func TestSideLogRebirth(t *testing.T) { + testSideLogRebirth(t, rawdb.HashScheme) + testSideLogRebirth(t, rawdb.PathScheme) +} + +func testSideLogRebirth(t *testing.T, scheme string) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr1 = crypto.PubkeyToAddress(key1.PublicKey) gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000000)}}} signer = types.LatestSigner(gspec.Config) - blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) ) defer blockchain.Stop() @@ -1312,6 +1432,11 @@ func checkLogEvents(t *testing.T, logsCh <-chan []*types.Log, rmLogsCh <-chan Re } func TestReorgSideEvent(t *testing.T) { + testReorgSideEvent(t, rawdb.HashScheme) + testReorgSideEvent(t, rawdb.PathScheme) +} + +func testReorgSideEvent(t *testing.T, scheme string) { var ( key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr1 = crypto.PubkeyToAddress(key1.PublicKey) @@ -1321,7 +1446,7 @@ func TestReorgSideEvent(t *testing.T) { } signer = types.LatestSigner(gspec.Config) ) - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer blockchain.Stop() _, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 3, func(i int, gen *BlockGen) {}) @@ -1392,7 +1517,12 @@ done: // Tests if the canonical block can be fetched from the database during chain insertion. func TestCanonicalBlockRetrieval(t *testing.T) { - _, gspec, blockchain, err := newCanonical(ethash.NewFaker(), 0, true) + testCanonicalBlockRetrieval(t, rawdb.HashScheme) + testCanonicalBlockRetrieval(t, rawdb.PathScheme) +} + +func testCanonicalBlockRetrieval(t *testing.T, scheme string) { + _, gspec, blockchain, err := newCanonical(ethash.NewFaker(), 0, true, scheme) if err != nil { t.Fatalf("failed to create pristine chain: %v", err) } @@ -1436,8 +1566,12 @@ func TestCanonicalBlockRetrieval(t *testing.T) { } pend.Wait() } - func TestEIP155Transition(t *testing.T) { + testEIP155Transition(t, rawdb.HashScheme) + testEIP155Transition(t, rawdb.PathScheme) +} + +func testEIP155Transition(t *testing.T, scheme string) { // Configure and generate a sample block chain var ( key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -1496,7 +1630,7 @@ func TestEIP155Transition(t *testing.T) { } }) - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer blockchain.Stop() if _, err := blockchain.InsertChain(blocks); err != nil { @@ -1546,8 +1680,12 @@ func TestEIP155Transition(t *testing.T) { t.Errorf("have %v, want %v", have, want) } } - func TestEIP161AccountRemoval(t *testing.T) { + testEIP161AccountRemoval(t, rawdb.HashScheme) + testEIP161AccountRemoval(t, rawdb.PathScheme) +} + +func testEIP161AccountRemoval(t *testing.T, scheme string) { // Configure and generate a sample block chain var ( key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -1585,7 +1723,7 @@ func TestEIP161AccountRemoval(t *testing.T) { block.AddTx(tx) }) // account must exist pre eip 161 - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer blockchain.Stop() if _, err := blockchain.InsertChain(types.Blocks{blocks[0]}); err != nil { @@ -1618,6 +1756,11 @@ func TestEIP161AccountRemoval(t *testing.T) { // // https://github.com/ethereum/go-ethereum/pull/15941 func TestBlockchainHeaderchainReorgConsistency(t *testing.T) { + testBlockchainHeaderchainReorgConsistency(t, rawdb.HashScheme) + testBlockchainHeaderchainReorgConsistency(t, rawdb.PathScheme) +} + +func testBlockchainHeaderchainReorgConsistency(t *testing.T, scheme string) { // Generate a canonical chain to act as the main dataset engine := ethash.NewFaker() genesis := &Genesis{ @@ -1638,7 +1781,7 @@ func TestBlockchainHeaderchainReorgConsistency(t *testing.T) { } // Import the canonical and fork chain side by side, verifying the current block // and current header consistency - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -1698,10 +1841,10 @@ func TestTrieForkGC(t *testing.T) { } // Dereference all the recent tries and ensure no past trie is left in for i := 0; i < DefaultTriesInMemory; i++ { - chain.stateCache.TrieDB().Dereference(blocks[len(blocks)-1-i].Root()) - chain.stateCache.TrieDB().Dereference(forks[len(blocks)-1-i].Root()) + chain.TrieDB().Dereference(blocks[len(blocks)-1-i].Root()) + chain.TrieDB().Dereference(forks[len(blocks)-1-i].Root()) } - if nodes, _ := chain.TrieDB().Size(); nodes > 0 { + if _, nodes, _ := chain.TrieDB().Size(); nodes > 0 { // all memory is returned in the nodes return for hashdb t.Fatalf("stale tries still alive after garbase collection") } } @@ -1709,6 +1852,11 @@ func TestTrieForkGC(t *testing.T) { // Tests that doing large reorgs works even if the state associated with the // forking point is not available any more. func TestLargeReorgTrieGC(t *testing.T) { + testLargeReorgTrieGC(t, rawdb.HashScheme) + testLargeReorgTrieGC(t, rawdb.PathScheme) +} + +func testLargeReorgTrieGC(t *testing.T, scheme string) { // Generate the original common chain segment and the two competing forks engine := ethash.NewFaker() genesis := &Genesis{ @@ -1720,7 +1868,10 @@ func TestLargeReorgTrieGC(t *testing.T) { competitor, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*DefaultTriesInMemory+1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{3}) }) // Import the shared chain and the original canonical one - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, genesis, nil, engine, vm.Config{}, nil, nil) + db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) + defer db.Close() + + chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), nil, genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -1733,7 +1884,7 @@ func TestLargeReorgTrieGC(t *testing.T) { t.Fatalf("failed to insert original chain: %v", err) } // Ensure that the state associated with the forking point is pruned away - if node, _ := chain.stateCache.TrieDB().Node(shared[len(shared)-1].Root()); node != nil { + if chain.HasState(shared[len(shared)-1].Root()) { t.Fatalf("common-but-old ancestor still cache") } // Import the competitor chain without exceeding the canonical's TD and ensure @@ -1742,7 +1893,7 @@ func TestLargeReorgTrieGC(t *testing.T) { t.Fatalf("failed to insert competitor chain: %v", err) } for i, block := range competitor[:len(competitor)-2] { - if node, _ := chain.stateCache.TrieDB().Node(block.Root()); node != nil { + if chain.HasState(block.Root()) { t.Fatalf("competitor %d: low TD chain became processed", i) } } @@ -1751,14 +1902,30 @@ func TestLargeReorgTrieGC(t *testing.T) { if _, err := chain.InsertChain(competitor[len(competitor)-2:]); err != nil { t.Fatalf("failed to finalize competitor chain: %v", err) } - for i, block := range competitor[:len(competitor)-DefaultTriesInMemory] { - if node, _ := chain.stateCache.TrieDB().Node(block.Root()); node != nil { + // In path-based trie database implementation, it will keep 128 diff + 1 disk + // layers, totally 129 latest states available. In hash-based it's 128. + states := DefaultTriesInMemory + if scheme == rawdb.PathScheme { + states = states + 1 + } + for i, block := range competitor[:len(competitor)-states] { + if chain.HasState(block.Root()) { + t.Fatalf("competitor %d: unexpected competing chain state", i) + } + } + for i, block := range competitor[len(competitor)-states:] { + if !chain.HasState(block.Root()) { t.Fatalf("competitor %d: competing chain state missing", i) } } } func TestBlockchainRecovery(t *testing.T) { + testBlockchainRecovery(t, rawdb.HashScheme) + testBlockchainRecovery(t, rawdb.PathScheme) +} + +func testBlockchainRecovery(t *testing.T, scheme string) { // Configure and generate a sample block chain var ( key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -1775,7 +1942,7 @@ func TestBlockchainRecovery(t *testing.T) { t.Fatalf("failed to create temp freezer db: %v", err) } defer ancientDb.Close() - ancient, _ := NewBlockChain(ancientDb, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) headers := make([]*types.Header, len(blocks)) for i, block := range blocks { @@ -1795,7 +1962,7 @@ func TestBlockchainRecovery(t *testing.T) { rawdb.WriteHeadFastBlockHash(ancientDb, midBlock.Hash()) // Reopen broken blockchain again - ancient, _ = NewBlockChain(ancientDb, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ancient, _ = NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer ancient.Stop() if num := ancient.CurrentBlock().Number.Uint64(); num != 0 { t.Errorf("head block mismatch: have #%v, want #%v", num, 0) @@ -1810,8 +1977,13 @@ func TestBlockchainRecovery(t *testing.T) { // This test checks that InsertReceiptChain will roll back correctly when attempting to insert a side chain. func TestInsertReceiptChainRollback(t *testing.T) { + testInsertReceiptChainRollback(t, rawdb.HashScheme) + testInsertReceiptChainRollback(t, rawdb.PathScheme) +} + +func testInsertReceiptChainRollback(t *testing.T, scheme string) { // Generate forked chain. The returned BlockChain object is used to process the side chain blocks. - tmpChain, sideblocks, canonblocks, gspec, err := getLongAndShortChains() + tmpChain, sideblocks, canonblocks, gspec, err := getLongAndShortChains(scheme) if err != nil { t.Fatal(err) } @@ -1842,7 +2014,7 @@ func TestInsertReceiptChainRollback(t *testing.T) { } defer ancientDb.Close() - ancientChain, _ := NewBlockChain(ancientDb, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ancientChain, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer ancientChain.Stop() // Import the canonical header chain. @@ -1887,6 +2059,11 @@ func TestInsertReceiptChainRollback(t *testing.T) { // - https://github.com/ethereum/go-ethereum/issues/18977 // - https://github.com/ethereum/go-ethereum/pull/18988 func TestLowDiffLongChain(t *testing.T) { + testLowDiffLongChain(t, rawdb.HashScheme) + testLowDiffLongChain(t, rawdb.PathScheme) +} + +func testLowDiffLongChain(t *testing.T, scheme string) { // Generate a canonical chain to act as the main dataset engine := ethash.NewFaker() genesis := &Genesis{ @@ -1901,11 +2078,14 @@ func TestLowDiffLongChain(t *testing.T) { }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, genesis, nil, engine, vm.Config{}, nil, nil) + diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) + defer diskdb.Close() + + chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), nil, genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } - defer chain.stopWithoutSaving() + defer chain.Stop() if n, err := chain.InsertChain(blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) @@ -2085,11 +2265,20 @@ func TestPrunedImportSideWithMerging(t *testing.T) { testSideImport(t, 1, -10, 1) } -func TestInsertKnownHeaders(t *testing.T) { testInsertKnownChainData(t, "headers") } -func TestInsertKnownReceiptChain(t *testing.T) { testInsertKnownChainData(t, "receipts") } -func TestInsertKnownBlocks(t *testing.T) { testInsertKnownChainData(t, "blocks") } +func TestInsertKnownHeaders(t *testing.T) { + testInsertKnownChainData(t, "headers", rawdb.HashScheme) + testInsertKnownChainData(t, "headers", rawdb.PathScheme) +} +func TestInsertKnownReceiptChain(t *testing.T) { + testInsertKnownChainData(t, "receipts", rawdb.HashScheme) + testInsertKnownChainData(t, "receipts", rawdb.PathScheme) +} +func TestInsertKnownBlocks(t *testing.T) { + testInsertKnownChainData(t, "blocks", rawdb.HashScheme) + testInsertKnownChainData(t, "blocks", rawdb.PathScheme) +} -func testInsertKnownChainData(t *testing.T, typ string) { +func testInsertKnownChainData(t *testing.T, typ string, scheme string) { engine := ethash.NewFaker() genesis := &Genesis{ Config: params.TestChainConfig, @@ -2112,7 +2301,7 @@ func testInsertKnownChainData(t *testing.T, typ string) { } defer chaindb.Close() - chain, err := NewBlockChain(chaindb, nil, nil, genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(chaindb, DefaultCacheConfigWithScheme(scheme), nil, genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2385,7 +2574,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i } // getLongAndShortChains returns two chains: A is longer, B is heavier. -func getLongAndShortChains() (*BlockChain, []*types.Block, []*types.Block, *Genesis, error) { +func getLongAndShortChains(scheme string) (*BlockChain, []*types.Block, []*types.Block, *Genesis, error) { // Generate a canonical chain to act as the main dataset engine := ethash.NewFaker() genesis := &Genesis{ @@ -2397,7 +2586,7 @@ func getLongAndShortChains() (*BlockChain, []*types.Block, []*types.Block, *Gene genDb, longChain, _ := GenerateChainWithGenesis(genesis, engine, 80, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { return nil, nil, nil, nil, fmt.Errorf("failed to create tester chain: %v", err) } @@ -2443,7 +2632,12 @@ func getLongAndShortChains() (*BlockChain, []*types.Block, []*types.Block, *Gene // 3. Then there should be no canon mapping for the block at height X // 4. The forked block should still be retrievable by hash func TestReorgToShorterRemovesCanonMapping(t *testing.T) { - chain, canonblocks, sideblocks, _, err := getLongAndShortChains() + testReorgToShorterRemovesCanonMapping(t, rawdb.HashScheme) + testReorgToShorterRemovesCanonMapping(t, rawdb.PathScheme) +} + +func testReorgToShorterRemovesCanonMapping(t *testing.T, scheme string) { + chain, canonblocks, sideblocks, _, err := getLongAndShortChains(scheme) if err != nil { t.Fatal(err) } @@ -2481,7 +2675,12 @@ func TestReorgToShorterRemovesCanonMapping(t *testing.T) { // as TestReorgToShorterRemovesCanonMapping, but applied on headerchain // imports -- that is, for fast sync func TestReorgToShorterRemovesCanonMappingHeaderChain(t *testing.T) { - chain, canonblocks, sideblocks, _, err := getLongAndShortChains() + testReorgToShorterRemovesCanonMappingHeaderChain(t, rawdb.HashScheme) + testReorgToShorterRemovesCanonMappingHeaderChain(t, rawdb.PathScheme) +} + +func testReorgToShorterRemovesCanonMappingHeaderChain(t *testing.T, scheme string) { + chain, canonblocks, sideblocks, _, err := getLongAndShortChains(scheme) if err != nil { t.Fatal(err) } @@ -2624,6 +2823,11 @@ func TestTransactionIndices(t *testing.T) { } func TestSkipStaleTxIndicesInSnapSync(t *testing.T) { + testSkipStaleTxIndicesInSnapSync(t, rawdb.HashScheme) + testSkipStaleTxIndicesInSnapSync(t, rawdb.PathScheme) +} + +func testSkipStaleTxIndicesInSnapSync(t *testing.T, scheme string) { // Configure and generate a sample block chain var ( key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -2682,7 +2886,7 @@ func TestSkipStaleTxIndicesInSnapSync(t *testing.T) { // Import all blocks into ancient db, only HEAD-32 indices are kept. l := uint64(32) - chain, err := NewBlockChain(ancientDb, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l) + chain, err := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2816,6 +3020,11 @@ func BenchmarkBlockChain_1x1000Executions(b *testing.B) { // 2. Downloader starts to sync again // 3. The blocks fetched are all known and canonical blocks func TestSideImportPrunedBlocks(t *testing.T) { + testSideImportPrunedBlocks(t, rawdb.HashScheme) + testSideImportPrunedBlocks(t, rawdb.PathScheme) +} + +func testSideImportPrunedBlocks(t *testing.T, scheme string) { // Generate a canonical chain to act as the main dataset engine := ethash.NewFaker() genesis := &Genesis{ @@ -2825,7 +3034,7 @@ func TestSideImportPrunedBlocks(t *testing.T) { // Generate and import the canonical chain _, blocks, _ := GenerateChainWithGenesis(genesis, engine, 2*DefaultTriesInMemory, nil) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, genesis, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2834,15 +3043,20 @@ func TestSideImportPrunedBlocks(t *testing.T) { if n, err := chain.InsertChain(blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } - - lastPrunedIndex := len(blocks) - DefaultTriesInMemory - 1 + // In path-based trie database implementation, it will keep 128 diff + 1 disk + // layers, totally 129 latest states available. In hash-based it's 128. + states := DefaultTriesInMemory + if scheme == rawdb.PathScheme { + states = DefaultTriesInMemory + 1 + } + lastPrunedIndex := len(blocks) - states - 1 lastPrunedBlock := blocks[lastPrunedIndex] // Verify pruning of lastPrunedBlock if chain.HasBlockAndState(lastPrunedBlock.Hash(), lastPrunedBlock.NumberU64()) { t.Errorf("Block %d not pruned", lastPrunedBlock.NumberU64()) } - firstNonPrunedBlock := blocks[len(blocks)-DefaultTriesInMemory] + firstNonPrunedBlock := blocks[len(blocks)-states] // Verify firstNonPrunedBlock is not pruned if !chain.HasBlockAndState(firstNonPrunedBlock.Hash(), firstNonPrunedBlock.NumberU64()) { t.Errorf("Block %d pruned", firstNonPrunedBlock.NumberU64()) @@ -2864,6 +3078,11 @@ func TestSideImportPrunedBlocks(t *testing.T) { // each transaction, so this works ok. The rework accumulated writes in memory // first, but the journal wiped the entire state object on create-revert. func TestDeleteCreateRevert(t *testing.T) { + testDeleteCreateRevert(t, rawdb.HashScheme) + testDeleteCreateRevert(t, rawdb.PathScheme) +} + +func testDeleteCreateRevert(t *testing.T, scheme string) { var ( aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") @@ -2915,7 +3134,7 @@ func TestDeleteCreateRevert(t *testing.T) { b.AddTx(tx) }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -2934,6 +3153,11 @@ func TestDeleteCreateRevert(t *testing.T) { // Expected outcome is that _all_ slots are cleared from A, due to the selfdestruct, // and then the new slots exist func TestDeleteRecreateSlots(t *testing.T) { + testDeleteRecreateSlots(t, rawdb.HashScheme) + testDeleteRecreateSlots(t, rawdb.PathScheme) +} + +func testDeleteRecreateSlots(t *testing.T, scheme string) { var ( engine = ethash.NewFaker() @@ -3023,7 +3247,7 @@ func TestDeleteRecreateSlots(t *testing.T) { b.AddTx(tx) }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, engine, vm.Config{ + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, engine, vm.Config{ Tracer: logger.NewJSONLogger(nil, os.Stdout), }, nil, nil) if err != nil { @@ -3057,6 +3281,11 @@ func TestDeleteRecreateSlots(t *testing.T) { // regular value-transfer // Expected outcome is that _all_ slots are cleared from A func TestDeleteRecreateAccount(t *testing.T) { + testDeleteRecreateAccount(t, rawdb.HashScheme) + testDeleteRecreateAccount(t, rawdb.PathScheme) +} + +func testDeleteRecreateAccount(t *testing.T, scheme string) { var ( engine = ethash.NewFaker() @@ -3100,7 +3329,7 @@ func TestDeleteRecreateAccount(t *testing.T) { b.AddTx(tx) }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, engine, vm.Config{ + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, engine, vm.Config{ Tracer: logger.NewJSONLogger(nil, os.Stdout), }, nil, nil) if err != nil { @@ -3130,6 +3359,11 @@ func TestDeleteRecreateAccount(t *testing.T) { // Expected outcome is that _all_ slots are cleared from A, due to the selfdestruct, // and then the new slots exist func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { + testDeleteRecreateSlotsAcrossManyBlocks(t, rawdb.HashScheme) + testDeleteRecreateSlotsAcrossManyBlocks(t, rawdb.PathScheme) +} + +func testDeleteRecreateSlotsAcrossManyBlocks(t *testing.T, scheme string) { var ( engine = ethash.NewFaker() @@ -3270,7 +3504,7 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { current = exp }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, engine, vm.Config{ + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, engine, vm.Config{ //Debug: true, //Tracer: vm.NewJSONLogger(nil, os.Stdout), }, nil, nil) @@ -3328,7 +3562,14 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { // to the destructset in case something is created "onto" an existing item. // We need to either roll back the snapDestructs, or not place it into snapDestructs // in the first place. +// + func TestInitThenFailCreateContract(t *testing.T) { + testInitThenFailCreateContract(t, rawdb.HashScheme) + testInitThenFailCreateContract(t, rawdb.PathScheme) +} + +func testInitThenFailCreateContract(t *testing.T, scheme string) { var ( engine = ethash.NewFaker() @@ -3401,7 +3642,7 @@ func TestInitThenFailCreateContract(t *testing.T) { }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, engine, vm.Config{ + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, engine, vm.Config{ //Debug: true, //Tracer: vm.NewJSONLogger(nil, os.Stdout), }, nil, nil) @@ -3439,6 +3680,11 @@ func TestInitThenFailCreateContract(t *testing.T) { // checking that the gas usage of a hot SLOAD and a cold SLOAD are calculated // correctly. func TestEIP2718Transition(t *testing.T) { + testEIP2718Transition(t, rawdb.HashScheme) + testEIP2718Transition(t, rawdb.PathScheme) +} + +func testEIP2718Transition(t *testing.T, scheme string) { var ( aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") engine = ethash.NewFaker() @@ -3486,7 +3732,7 @@ func TestEIP2718Transition(t *testing.T) { }) // Import the canonical chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3516,6 +3762,11 @@ func TestEIP2718Transition(t *testing.T) { // gasFeeCap - gasTipCap < baseFee. // 6. Legacy transaction behave as expected (e.g. gasPrice = gasFeeCap = gasTipCap). func TestEIP1559Transition(t *testing.T) { + testEIP1559Transition(t, rawdb.HashScheme) + testEIP1559Transition(t, rawdb.PathScheme) +} + +func testEIP1559Transition(t *testing.T, scheme string) { var ( aa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") engine = ethash.NewFaker() @@ -3526,8 +3777,9 @@ func TestEIP1559Transition(t *testing.T) { addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) + config = *params.AllEthashProtocolChanges gspec = &Genesis{ - Config: params.AllEthashProtocolChanges, + Config: &config, Alloc: GenesisAlloc{ addr1: {Balance: funds}, addr2: {Balance: funds}, @@ -3574,7 +3826,7 @@ func TestEIP1559Transition(t *testing.T) { b.AddTx(tx) }) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3656,6 +3908,11 @@ func TestEIP1559Transition(t *testing.T) { // Tests the scenario the chain is requested to another point with the missing state. // It expects the state is recovered and all relevant chain markers are set correctly. func TestSetCanonical(t *testing.T) { + testSetCanonical(t, rawdb.HashScheme) + testSetCanonical(t, rawdb.PathScheme) +} + +func testSetCanonical(t *testing.T, scheme string) { //log.Root().SetHandler(log.LvlFilterHandler(log.LvlDebug, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) var ( @@ -3678,7 +3935,10 @@ func TestSetCanonical(t *testing.T) { } gen.AddTx(tx) }) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, engine, vm.Config{}, nil, nil) + diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) + defer diskdb.Close() + + chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -3735,6 +3995,11 @@ func TestSetCanonical(t *testing.T) { // TestCanonicalHashMarker tests all the canonical hash markers are updated/deleted // correctly in case reorg is called. func TestCanonicalHashMarker(t *testing.T) { + testCanonicalHashMarker(t, rawdb.HashScheme) + testCanonicalHashMarker(t, rawdb.PathScheme) +} + +func testCanonicalHashMarker(t *testing.T, scheme string) { var cases = []struct { forkA int forkB int @@ -3782,7 +4047,7 @@ func TestCanonicalHashMarker(t *testing.T) { _, forkB, _ := GenerateChainWithGenesis(gspec, engine, c.forkB, func(i int, gen *BlockGen) {}) // Initialize test chain - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), nil, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -4129,6 +4394,7 @@ func testCreateThenDelete(t *testing.T, config *params.ChainConfig) { if err != nil { t.Fatalf("failed to create tester chain: %v", err) } + defer chain.Stop() // Import the blocks for _, block := range blocks { if _, err := chain.InsertChain([]*types.Block{block}); err != nil { @@ -4137,6 +4403,116 @@ func testCreateThenDelete(t *testing.T, config *params.ChainConfig) { } } +func TestDeleteThenCreate(t *testing.T) { + var ( + engine = ethash.NewFaker() + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + factoryAddr = crypto.CreateAddress(address, 0) + funds = big.NewInt(1000000000000000) + ) + /* + contract Factory { + function deploy(bytes memory code) public { + address addr; + assembly { + addr := create2(0, add(code, 0x20), mload(code), 0) + if iszero(extcodesize(addr)) { + revert(0, 0) + } + } + } + } + */ + factoryBIN := common.Hex2Bytes("608060405234801561001057600080fd5b50610241806100206000396000f3fe608060405234801561001057600080fd5b506004361061002a5760003560e01c80627743601461002f575b600080fd5b610049600480360381019061004491906100d8565b61004b565b005b6000808251602084016000f59050803b61006457600080fd5b5050565b600061007b61007684610146565b610121565b905082815260208101848484011115610097576100966101eb565b5b6100a2848285610177565b509392505050565b600082601f8301126100bf576100be6101e6565b5b81356100cf848260208601610068565b91505092915050565b6000602082840312156100ee576100ed6101f5565b5b600082013567ffffffffffffffff81111561010c5761010b6101f0565b5b610118848285016100aa565b91505092915050565b600061012b61013c565b90506101378282610186565b919050565b6000604051905090565b600067ffffffffffffffff821115610161576101606101b7565b5b61016a826101fa565b9050602081019050919050565b82818337600083830152505050565b61018f826101fa565b810181811067ffffffffffffffff821117156101ae576101ad6101b7565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f830116905091905056fea2646970667358221220ea8b35ed310d03b6b3deef166941140b4d9e90ea2c92f6b41eb441daf49a59c364736f6c63430008070033") + + /* + contract C { + uint256 value; + constructor() { + value = 100; + } + function destruct() public payable { + selfdestruct(payable(msg.sender)); + } + receive() payable external {} + } + */ + contractABI := common.Hex2Bytes("6080604052348015600f57600080fd5b5060646000819055506081806100266000396000f3fe608060405260043610601f5760003560e01c80632b68b9c614602a576025565b36602557005b600080fd5b60306032565b005b3373ffffffffffffffffffffffffffffffffffffffff16fffea2646970667358221220ab749f5ed1fcb87bda03a74d476af3f074bba24d57cb5a355e8162062ad9a4e664736f6c63430008070033") + contractAddr := crypto.CreateAddress2(factoryAddr, [32]byte{}, crypto.Keccak256(contractABI)) + + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{ + address: {Balance: funds}, + }, + } + nonce := uint64(0) + signer := types.HomesteadSigner{} + _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 2, func(i int, b *BlockGen) { + fee := big.NewInt(1) + if b.header.BaseFee != nil { + fee = b.header.BaseFee + } + b.SetCoinbase(common.Address{1}) + + // Block 1 + if i == 0 { + tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: nonce, + GasPrice: new(big.Int).Set(fee), + Gas: 500000, + Data: factoryBIN, + }) + nonce++ + b.AddTx(tx) + + data := common.Hex2Bytes("00774360000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a76080604052348015600f57600080fd5b5060646000819055506081806100266000396000f3fe608060405260043610601f5760003560e01c80632b68b9c614602a576025565b36602557005b600080fd5b60306032565b005b3373ffffffffffffffffffffffffffffffffffffffff16fffea2646970667358221220ab749f5ed1fcb87bda03a74d476af3f074bba24d57cb5a355e8162062ad9a4e664736f6c6343000807003300000000000000000000000000000000000000000000000000") + tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: nonce, + GasPrice: new(big.Int).Set(fee), + Gas: 500000, + To: &factoryAddr, + Data: data, + }) + b.AddTx(tx) + nonce++ + } else { + // Block 2 + tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: nonce, + GasPrice: new(big.Int).Set(fee), + Gas: 500000, + To: &contractAddr, + Data: common.Hex2Bytes("2b68b9c6"), // destruct + }) + nonce++ + b.AddTx(tx) + + data := common.Hex2Bytes("00774360000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a76080604052348015600f57600080fd5b5060646000819055506081806100266000396000f3fe608060405260043610601f5760003560e01c80632b68b9c614602a576025565b36602557005b600080fd5b60306032565b005b3373ffffffffffffffffffffffffffffffffffffffff16fffea2646970667358221220ab749f5ed1fcb87bda03a74d476af3f074bba24d57cb5a355e8162062ad9a4e664736f6c6343000807003300000000000000000000000000000000000000000000000000") + tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: nonce, + GasPrice: new(big.Int).Set(fee), + Gas: 500000, + To: &factoryAddr, // re-creation + Data: data, + }) + b.AddTx(tx) + nonce++ + } + }) + // Import the canonical chain + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + for _, block := range blocks { + if _, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", block.NumberU64(), err) + } + } +} + // TestTransientStorageReset ensures the transient storage is wiped correctly // between transactions. func TestTransientStorageReset(t *testing.T) { @@ -4215,6 +4591,7 @@ func TestTransientStorageReset(t *testing.T) { if err != nil { t.Fatalf("failed to create tester chain: %v", err) } + defer chain.Stop() // Import the blocks if _, err := chain.InsertChain(blocks); err != nil { t.Fatalf("failed to insert into chain: %v", err) @@ -4309,6 +4686,7 @@ func TestEIP3651(t *testing.T) { if err != nil { t.Fatalf("failed to create tester chain: %v", err) } + defer chain.Stop() if n, err := chain.InsertChain(blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } diff --git a/core/bloombits/matcher.go b/core/bloombits/matcher.go index d8f932041bc0..6a4cfb23db19 100644 --- a/core/bloombits/matcher.go +++ b/core/bloombits/matcher.go @@ -58,7 +58,7 @@ type partialMatches struct { // bit with the given number of fetch elements, or a response for such a request. // It can also have the actual results set to be used as a delivery data struct. // -// The contest and error fields are used by the light client to terminate matching +// The context and error fields are used by the light client to terminate matching // early if an error is encountered on some path of the pipeline. type Retrieval struct { Bit uint @@ -389,7 +389,7 @@ func (m *Matcher) distributor(dist chan *request, session *MatcherSession) { shutdown = session.quit // Shutdown request channel, will gracefully wait for pending requests ) - // assign is a helper method fo try to assign a pending bit an actively + // assign is a helper method to try to assign a pending bit an actively // listening servicer, or schedule it up for later when one arrives. assign := func(bit uint) { select { @@ -630,13 +630,16 @@ func (s *MatcherSession) Multiplex(batch int, wait time.Duration, mux chan chan request <- &Retrieval{Bit: bit, Sections: sections, Context: s.ctx} result := <-request + + // Deliver a result before s.Close() to avoid a deadlock + s.deliverSections(result.Bit, result.Sections, result.Bitsets) + if result.Error != nil { s.errLock.Lock() s.err = result.Error s.errLock.Unlock() s.Close() } - s.deliverSections(result.Bit, result.Sections, result.Bitsets) } } } diff --git a/core/bloombits/matcher_test.go b/core/bloombits/matcher_test.go index 36764c3f174b..7f3d5f279ca3 100644 --- a/core/bloombits/matcher_test.go +++ b/core/bloombits/matcher_test.go @@ -85,7 +85,7 @@ func TestMatcherRandom(t *testing.T) { } // Tests that the matcher can properly find matches if the starting block is -// shifter from a multiple of 8. This is needed to cover an optimisation with +// shifted from a multiple of 8. This is needed to cover an optimisation with // bitset matching https://github.com/ethereum/go-ethereum/issues/15309. func TestMatcherShifted(t *testing.T) { t.Parallel() @@ -106,7 +106,7 @@ func TestWildcardMatcher(t *testing.T) { testMatcherBothModes(t, nil, 0, 10000, 0) } -// makeRandomIndexes generates a random filter system, composed on multiple filter +// makeRandomIndexes generates a random filter system, composed of multiple filter // criteria, each having one bloom list component for the address and arbitrarily // many topic bloom list components. func makeRandomIndexes(lengths []int, max int) [][]bloomIndexes { diff --git a/core/chain_indexer.go b/core/chain_indexer.go index 23ab23ef0fb9..f5fce7258831 100644 --- a/core/chain_indexer.go +++ b/core/chain_indexer.go @@ -19,6 +19,7 @@ package core import ( "context" "encoding/binary" + "errors" "fmt" "sync" "sync/atomic" @@ -403,7 +404,7 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com if header == nil { return common.Hash{}, fmt.Errorf("block #%d [%x..] not found", number, hash[:4]) } else if header.ParentHash != lastHead { - return common.Hash{}, fmt.Errorf("chain reorged during section processing") + return common.Hash{}, errors.New("chain reorged during section processing") } if err := c.backend.Process(c.ctx, header); err != nil { return common.Hash{}, err diff --git a/core/chain_makers.go b/core/chain_makers.go index 30472b11b09e..7c892d58e424 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -23,6 +23,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -104,6 +106,9 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti } b.txs = append(b.txs, tx) b.receipts = append(b.receipts, receipt) + if b.header.BlobGasUsed != nil { + *b.header.BlobGasUsed += receipt.BlobGasUsed + } } // AddTx adds a transaction to the generated block. If no coinbase has @@ -202,7 +207,7 @@ func (b *BlockGen) AddUncle(h *types.Header) { // The gas limit and price should be derived from the parent h.GasLimit = parent.GasLimit if b.config.IsLondon(h.Number) { - h.BaseFee = misc.CalcBaseFee(b.config, parent) + h.BaseFee = eip1559.CalcBaseFee(b.config, parent) if !b.config.IsLondon(parent.Number) { parentGasLimit := parent.GasLimit * b.config.ElasticityMultiplier() h.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit) @@ -282,7 +287,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse } blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n) chainreader := &fakeChainReader{config: config} - genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) { + genblock := func(i int, parent *types.Block, triedb *trie.Database, statedb *state.StateDB) (*types.Block, types.Receipts) { b := &BlockGen{i: i, chain: blocks, parent: parent, statedb: statedb, config: config, engine: engine} b.header = makeHeader(chainreader, parent, statedb, b.engine) @@ -321,23 +326,27 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse } // Write state changes to db - root, err := statedb.Commit(config.IsEIP158(b.header.Number)) + root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number)) if err != nil { panic(fmt.Sprintf("state write error: %v", err)) } - if err := statedb.Database().TrieDB().Commit(root, false); err != nil { + if err = triedb.Commit(root, false); err != nil { panic(fmt.Sprintf("trie write error: %v", err)) } return block, b.receipts } return nil, nil } + // Forcibly use hash-based state scheme for retaining all nodes in disk. + triedb := trie.NewDatabase(db, trie.HashDefaults) + defer triedb.Close() + for i := 0; i < n; i++ { - statedb, err := state.New(parent.Root(), state.NewDatabase(db), nil) + statedb, err := state.New(parent.Root(), state.NewDatabaseWithNodeDB(db, triedb), nil) if err != nil { panic(err) } - block, receipt := genblock(i, parent, statedb) + block, receipt := genblock(i, parent, triedb, statedb) blocks[i] = block receipts[i] = receipt parent = block @@ -350,7 +359,9 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse // then generate chain on top. func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (ethdb.Database, []*types.Block, []types.Receipts) { db := rawdb.NewMemoryDatabase() - _, err := genesis.Commit(db, trie.NewDatabase(db)) + triedb := trie.NewDatabase(db, trie.HashDefaults) + defer triedb.Close() + _, err := genesis.Commit(db, triedb) if err != nil { panic(err) } @@ -380,12 +391,27 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S Time: time, } if chain.Config().IsLondon(header.Number) { - header.BaseFee = misc.CalcBaseFee(chain.Config(), parent.Header()) + header.BaseFee = eip1559.CalcBaseFee(chain.Config(), parent.Header()) if !chain.Config().IsLondon(parent.Number()) { parentGasLimit := parent.GasLimit() * chain.Config().ElasticityMultiplier() header.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit) } } + prevArbosVersion := types.DeserializeHeaderExtraInformation(parent.Header()).ArbOSFormatVersion + if chain.Config().IsCancun(header.Number, header.Time, prevArbosVersion) { + var ( + parentExcessBlobGas uint64 + parentBlobGasUsed uint64 + ) + if parent.ExcessBlobGas() != nil { + parentExcessBlobGas = *parent.ExcessBlobGas() + parentBlobGasUsed = *parent.BlobGasUsed() + } + excessBlobGas := eip4844.CalcExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed) + header.ExcessBlobGas = &excessBlobGas + header.BlobGasUsed = new(uint64) + header.ParentBeaconRoot = new(common.Hash) + } return header } diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 13e3789e663e..bd9390a3312e 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" ) func TestGenerateWithdrawalChain(t *testing.T) { @@ -74,8 +75,7 @@ func TestGenerateWithdrawalChain(t *testing.T) { Storage: storage, Code: common.Hex2Bytes("600154600354"), } - - genesis := gspec.MustCommit(gendb) + genesis := gspec.MustCommit(gendb, trie.NewDatabase(gendb, trie.HashDefaults)) chain, _ := GenerateChain(gspec.Config, genesis, beacon.NewFaker(), gendb, 4, func(i int, gen *BlockGen) { tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(address), address, big.NewInt(1000), params.TxGas, new(big.Int).Add(gen.BaseFee(), common.Big1), nil), signer, key) @@ -146,6 +146,7 @@ func ExampleGenerateChain() { addr2 = crypto.PubkeyToAddress(key2.PublicKey) addr3 = crypto.PubkeyToAddress(key3.PublicKey) db = rawdb.NewMemoryDatabase() + genDb = rawdb.NewMemoryDatabase() ) // Ensure that key1 has some funds in the genesis block. @@ -153,13 +154,13 @@ func ExampleGenerateChain() { Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}}, } - genesis := gspec.MustCommit(db) + genesis := gspec.MustCommit(genDb, trie.NewDatabase(genDb, trie.HashDefaults)) // This call generates a chain of 5 blocks. The function runs for // each block and adds different features to gen based on the // block index. signer := types.HomesteadSigner{} - chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, 5, func(i int, gen *BlockGen) { + chain, _ := GenerateChain(gspec.Config, genesis, ethash.NewFaker(), genDb, 5, func(i int, gen *BlockGen) { switch i { case 0: // In block 1, addr1 sends addr2 some ether. @@ -188,7 +189,7 @@ func ExampleGenerateChain() { }) // Import the chain. This runs all block validation rules. - blockchain, _ := NewBlockChain(db, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.HashScheme), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer blockchain.Stop() if i, err := blockchain.InsertChain(chain); err != nil { diff --git a/core/dao_test.go b/core/dao_test.go index 6438060cee1b..8383db7f97a7 100644 --- a/core/dao_test.go +++ b/core/dao_test.go @@ -83,7 +83,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { if _, err := bc.InsertChain(blocks); err != nil { t.Fatalf("failed to import contra-fork chain for expansion: %v", err) } - if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, false); err != nil { + if err := bc.triedb.Commit(bc.CurrentHeader().Root, false); err != nil { t.Fatalf("failed to commit contra-fork head for expansion: %v", err) } bc.Stop() @@ -106,7 +106,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { if _, err := bc.InsertChain(blocks); err != nil { t.Fatalf("failed to import pro-fork chain for expansion: %v", err) } - if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, false); err != nil { + if err := bc.triedb.Commit(bc.CurrentHeader().Root, false); err != nil { t.Fatalf("failed to commit pro-fork head for expansion: %v", err) } bc.Stop() @@ -131,7 +131,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { if _, err := bc.InsertChain(blocks); err != nil { t.Fatalf("failed to import contra-fork chain for expansion: %v", err) } - if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, false); err != nil { + if err := bc.triedb.Commit(bc.CurrentHeader().Root, false); err != nil { t.Fatalf("failed to commit contra-fork head for expansion: %v", err) } blocks, _ = GenerateChain(&proConf, conBc.GetBlockByHash(conBc.CurrentBlock().Hash()), ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {}) @@ -149,7 +149,7 @@ func TestDAOForkRangeExtradata(t *testing.T) { if _, err := bc.InsertChain(blocks); err != nil { t.Fatalf("failed to import pro-fork chain for expansion: %v", err) } - if err := bc.stateCache.TrieDB().Commit(bc.CurrentHeader().Root, false); err != nil { + if err := bc.triedb.Commit(bc.CurrentHeader().Root, false); err != nil { t.Fatalf("failed to commit pro-fork head for expansion: %v", err) } blocks, _ = GenerateChain(&conConf, proBc.GetBlockByHash(proBc.CurrentBlock().Hash()), ethash.NewFaker(), genDb, 1, func(i int, gen *BlockGen) {}) diff --git a/core/error.go b/core/error.go index 872ba8d365d8..4214ed207a91 100644 --- a/core/error.go +++ b/core/error.go @@ -100,4 +100,8 @@ var ( // ErrSenderNoEOA is returned if the sender of a transaction is a contract. ErrSenderNoEOA = errors.New("sender not an eoa") + + // ErrBlobFeeCapTooLow is returned if the transaction fee cap is less than the + // blob gas fee of the block. + ErrBlobFeeCapTooLow = errors.New("max fee per blob gas less than block blob gas fee") ) diff --git a/core/evm.go b/core/evm.go index 31e634edee88..1219e8ac7dbe 100644 --- a/core/evm.go +++ b/core/evm.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" ) @@ -40,6 +41,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common var ( beneficiary common.Address baseFee *big.Int + blobBaseFee *big.Int random *common.Hash ) @@ -52,6 +54,9 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common if header.BaseFee != nil { baseFee = new(big.Int).Set(header.BaseFee) } + if header.ExcessBlobGas != nil { + blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas) + } if header.Difficulty.Cmp(common.Big0) == 0 { random = &header.MixDigest } @@ -69,6 +74,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common Time: header.Time, Difficulty: new(big.Int).Set(header.Difficulty), BaseFee: baseFee, + BlobBaseFee: blobBaseFee, GasLimit: header.GasLimit, Random: random, ArbOSVersion: arbOsVersion, @@ -78,8 +84,9 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common // NewEVMTxContext creates a new transaction context for a single transaction. func NewEVMTxContext(msg *Message) vm.TxContext { return vm.TxContext{ - Origin: msg.From, - GasPrice: new(big.Int).Set(msg.GasPrice), + Origin: msg.From, + GasPrice: new(big.Int).Set(msg.GasPrice), + BlobHashes: msg.BlobHashes, } } diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index f536019dac12..76825d3befc1 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -24,13 +24,12 @@ import ( "math" "math/big" "reflect" - "sort" "strings" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "golang.org/x/exp/slices" ) var ( @@ -73,12 +72,12 @@ type ID struct { type Filter func(id ID) error // NewID calculates the Ethereum fork ID from the chain config, genesis hash, head and time. -func NewID(config *params.ChainConfig, genesis common.Hash, head, time uint64) ID { +func NewID(config *params.ChainConfig, genesis *types.Block, head, time uint64) ID { // Calculate the starting checksum from the genesis hash - hash := crc32.ChecksumIEEE(genesis[:]) + hash := crc32.ChecksumIEEE(genesis.Hash().Bytes()) // Calculate the current fork checksum and the next fork block - forksByBlock, forksByTime := gatherForks(config) + forksByBlock, forksByTime := gatherForks(config, genesis.Time()) for _, fork := range forksByBlock { if fork <= head { // Fork already passed, checksum the previous hash and the fork number @@ -104,7 +103,7 @@ func NewIDWithChain(chain Blockchain) ID { return NewID( chain.Config(), - chain.Genesis().Hash(), + chain.Genesis(), head.Number.Uint64(), head.Time, ) @@ -115,7 +114,7 @@ func NewIDWithChain(chain Blockchain) ID { func NewFilter(chain Blockchain) Filter { return newFilter( chain.Config(), - chain.Genesis().Hash(), + chain.Genesis(), func() (uint64, uint64) { head := chain.CurrentHeader() return head.Number.Uint64(), head.Time @@ -124,7 +123,7 @@ func NewFilter(chain Blockchain) Filter { } // NewStaticFilter creates a filter at block zero. -func NewStaticFilter(config *params.ChainConfig, genesis common.Hash) Filter { +func NewStaticFilter(config *params.ChainConfig, genesis *types.Block) Filter { head := func() (uint64, uint64) { return 0, 0 } return newFilter(config, genesis, head) } @@ -132,14 +131,14 @@ func NewStaticFilter(config *params.ChainConfig, genesis common.Hash) Filter { // newFilter is the internal version of NewFilter, taking closures as its arguments // instead of a chain. The reason is to allow testing it without having to simulate // an entire blockchain. -func newFilter(config *params.ChainConfig, genesis common.Hash, headfn func() (uint64, uint64)) Filter { +func newFilter(config *params.ChainConfig, genesis *types.Block, headfn func() (uint64, uint64)) Filter { // Calculate the all the valid fork hash and fork next combos var ( - forksByBlock, forksByTime = gatherForks(config) + forksByBlock, forksByTime = gatherForks(config, genesis.Time()) forks = append(append([]uint64{}, forksByBlock...), forksByTime...) sums = make([][4]byte, len(forks)+1) // 0th is the genesis ) - hash := crc32.ChecksumIEEE(genesis[:]) + hash := crc32.ChecksumIEEE(genesis.Hash().Bytes()) sums[0] = checksumToBytes(hash) for i, fork := range forks { hash = checksumUpdate(hash, fork) @@ -240,7 +239,7 @@ func checksumToBytes(hash uint32) [4]byte { // gatherForks gathers all the known forks and creates two sorted lists out of // them, one for the block number based forks and the second for the timestamps. -func gatherForks(config *params.ChainConfig) ([]uint64, []uint64) { +func gatherForks(config *params.ChainConfig, genesis uint64) ([]uint64, []uint64) { // Gather all the fork block numbers via reflection kind := reflect.TypeOf(params.ChainConfig{}) conf := reflect.ValueOf(config).Elem() @@ -270,8 +269,8 @@ func gatherForks(config *params.ChainConfig) ([]uint64, []uint64) { } } } - sort.Slice(forksByBlock, func(i, j int) bool { return forksByBlock[i] < forksByBlock[j] }) - sort.Slice(forksByTime, func(i, j int) bool { return forksByTime[i] < forksByTime[j] }) + slices.Sort(forksByBlock) + slices.Sort(forksByTime) // Deduplicate fork identifiers applying multiple forks for i := 1; i < len(forksByBlock); i++ { @@ -290,7 +289,8 @@ func gatherForks(config *params.ChainConfig) ([]uint64, []uint64) { if len(forksByBlock) > 0 && forksByBlock[0] == 0 { forksByBlock = forksByBlock[1:] } - if len(forksByTime) > 0 && forksByTime[0] == 0 { + // Skip any forks before genesis. + for len(forksByTime) > 0 && forksByTime[0] <= genesis { forksByTime = forksByTime[1:] } return forksByBlock, forksByTime diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index ab59a454d8c9..db634bc14b90 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -18,10 +18,14 @@ package forkid import ( "bytes" + "hash/crc32" "math" + "math/big" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" ) @@ -36,13 +40,13 @@ func TestCreation(t *testing.T) { } tests := []struct { config *params.ChainConfig - genesis common.Hash + genesis *types.Block cases []testcase }{ // Mainnet test cases { params.MainnetChainConfig, - params.MainnetGenesisHash, + core.DefaultGenesisBlock().ToBlock(), []testcase{ {0, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Unsynced {1149999, 0, ID{Hash: checksumToBytes(0xfc64ec04), Next: 1150000}}, // Last Frontier block @@ -74,34 +78,10 @@ func TestCreation(t *testing.T) { {30000000, 2000000000, ID{Hash: checksumToBytes(0xdce96c2d), Next: 0}}, // Future Shanghai block }, }, - // Rinkeby test cases - { - params.RinkebyChainConfig, - params.RinkebyGenesisHash, - []testcase{ - {0, 0, ID{Hash: checksumToBytes(0x3b8e0691), Next: 1}}, // Unsynced, last Frontier block - {1, 0, ID{Hash: checksumToBytes(0x60949295), Next: 2}}, // First and last Homestead block - {2, 0, ID{Hash: checksumToBytes(0x8bde40dd), Next: 3}}, // First and last Tangerine block - {3, 0, ID{Hash: checksumToBytes(0xcb3a64bb), Next: 1035301}}, // First Spurious block - {1035300, 0, ID{Hash: checksumToBytes(0xcb3a64bb), Next: 1035301}}, // Last Spurious block - {1035301, 0, ID{Hash: checksumToBytes(0x8d748b57), Next: 3660663}}, // First Byzantium block - {3660662, 0, ID{Hash: checksumToBytes(0x8d748b57), Next: 3660663}}, // Last Byzantium block - {3660663, 0, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // First Constantinople block - {4321233, 0, ID{Hash: checksumToBytes(0xe49cab14), Next: 4321234}}, // Last Constantinople block - {4321234, 0, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // First Petersburg block - {5435344, 0, ID{Hash: checksumToBytes(0xafec6b27), Next: 5435345}}, // Last Petersburg block - {5435345, 0, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // First Istanbul block - {8290927, 0, ID{Hash: checksumToBytes(0xcbdb8838), Next: 8290928}}, // Last Istanbul block - {8290928, 0, ID{Hash: checksumToBytes(0x6910c8bd), Next: 8897988}}, // First Berlin block - {8897987, 0, ID{Hash: checksumToBytes(0x6910c8bd), Next: 8897988}}, // Last Berlin block - {8897988, 0, ID{Hash: checksumToBytes(0x8E29F2F3), Next: 0}}, // First London block - {10000000, 0, ID{Hash: checksumToBytes(0x8E29F2F3), Next: 0}}, // Future London block - }, - }, // Goerli test cases { params.GoerliChainConfig, - params.GoerliGenesisHash, + core.DefaultGoerliGenesisBlock().ToBlock(), []testcase{ {0, 0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block {1561650, 0, ID{Hash: checksumToBytes(0xa3f5ab08), Next: 1561651}}, // Last Petersburg block @@ -118,7 +98,7 @@ func TestCreation(t *testing.T) { // Sepolia test cases { params.SepoliaChainConfig, - params.SepoliaGenesisHash, + core.DefaultSepoliaGenesisBlock().ToBlock(), []testcase{ {0, 0, ID{Hash: checksumToBytes(0xfe3366e7), Next: 1735371}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople, Petersburg, Istanbul, Berlin and first London block {1735370, 0, ID{Hash: checksumToBytes(0xfe3366e7), Next: 1735371}}, // Last London block @@ -127,6 +107,16 @@ func TestCreation(t *testing.T) { {1735372, 1677557088, ID{Hash: checksumToBytes(0xf7f9bc08), Next: 0}}, // First Shanghai block }, }, + // Holesky test cases + { + params.HoleskyChainConfig, + core.DefaultHoleskyGenesisBlock().ToBlock(), + []testcase{ + {0, 0, ID{Hash: checksumToBytes(0xc61a6098), Next: 1696000704}}, // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople, Petersburg, Istanbul, Berlin, London, Paris block + {123, 0, ID{Hash: checksumToBytes(0xc61a6098), Next: 1696000704}}, // First MergeNetsplit block + {123, 1696000704, ID{Hash: checksumToBytes(0xfd4f016b), Next: 0}}, // Last MergeNetsplit block + }, + }, } for i, tt := range tests { for j, ttt := range tt.cases { @@ -377,7 +367,7 @@ func TestValidation(t *testing.T) { //{params.MainnetChainConfig, 20999999, 1677999999, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, ErrLocalIncompatibleOrStale}, } for i, tt := range tests { - filter := newFilter(tt.config, params.MainnetGenesisHash, func() (uint64, uint64) { return tt.head, tt.time }) + filter := newFilter(tt.config, core.DefaultGenesisBlock().ToBlock(), func() (uint64, uint64) { return tt.head, tt.time }) if err := filter(tt.id); err != tt.err { t.Errorf("test %d: validation error mismatch: have %v, want %v", i, err, tt.err) } @@ -406,3 +396,55 @@ func TestEncoding(t *testing.T) { } } } + +// Tests that time-based forks which are active at genesis are not included in +// forkid hash. +func TestTimeBasedForkInGenesis(t *testing.T) { + var ( + time = uint64(1690475657) + genesis = types.NewBlockWithHeader(&types.Header{Time: time}) + forkidHash = checksumToBytes(crc32.ChecksumIEEE(genesis.Hash().Bytes())) + config = func(shanghai, cancun uint64) *params.ChainConfig { + return ¶ms.ChainConfig{ + ChainID: big.NewInt(1337), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: true, + 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), + TerminalTotalDifficulty: big.NewInt(0), + TerminalTotalDifficultyPassed: true, + MergeNetsplitBlock: big.NewInt(0), + ShanghaiTime: &shanghai, + CancunTime: &cancun, + Ethash: new(params.EthashConfig), + } + } + ) + tests := []struct { + config *params.ChainConfig + want ID + }{ + // Shanghai active before genesis, skip + {config(time-1, time+1), ID{Hash: forkidHash, Next: time + 1}}, + + // Shanghai active at genesis, skip + {config(time, time+1), ID{Hash: forkidHash, Next: time + 1}}, + + // Shanghai not active, skip + {config(time+1, time+2), ID{Hash: forkidHash, Next: time + 1}}, + } + for _, tt := range tests { + if have := NewID(tt.config, genesis, 0, time); have != tt.want { + t.Fatalf("incorrect forkid hash: have %x, want %x", have, tt.want) + } + } +} diff --git a/core/gen_genesis.go b/core/gen_genesis.go index 4e0844e889ab..38614252a380 100644 --- a/core/gen_genesis.go +++ b/core/gen_genesis.go @@ -18,19 +18,21 @@ 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]GenesisAccount `json:"alloc" gencodec:"required"` - Number math.HexOrDecimal64 `json:"number"` - GasUsed math.HexOrDecimal64 `json:"gasUsed"` - ParentHash common.Hash `json:"parentHash"` - BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` + 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]GenesisAccount `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"` } var enc Genesis enc.Config = g.Config @@ -51,25 +53,29 @@ func (g Genesis) MarshalJSON() ([]byte, error) { enc.GasUsed = math.HexOrDecimal64(g.GasUsed) enc.ParentHash = g.ParentHash enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee) + enc.ExcessBlobGas = (*math.HexOrDecimal64)(g.ExcessBlobGas) + enc.BlobGasUsed = (*math.HexOrDecimal64)(g.BlobGasUsed) return json.Marshal(&enc) } // 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]GenesisAccount `json:"alloc" gencodec:"required"` - Number *math.HexOrDecimal64 `json:"number"` - GasUsed *math.HexOrDecimal64 `json:"gasUsed"` - ParentHash *common.Hash `json:"parentHash"` - BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` + 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]GenesisAccount `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"` } var dec Genesis if err := json.Unmarshal(input, &dec); err != nil { @@ -120,5 +126,11 @@ func (g *Genesis) UnmarshalJSON(input []byte) error { if dec.BaseFee != nil { g.BaseFee = (*big.Int)(dec.BaseFee) } + if dec.ExcessBlobGas != nil { + g.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) + } + if dec.BlobGasUsed != nil { + g.BlobGasUsed = (*uint64)(dec.BlobGasUsed) + } return nil } diff --git a/core/genesis.go b/core/genesis.go index 44c2dd5d8497..b0591867abd1 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -59,10 +59,12 @@ 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"` + 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 } func ReadGenesis(db ethdb.Database) (*Genesis, error) { @@ -73,7 +75,7 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) { } blob := rawdb.ReadGenesisStateSpec(db, stored) if blob == nil { - return nil, fmt.Errorf("genesis state missing from db") + return nil, errors.New("genesis state missing from db") } if len(blob) != 0 { if err := genesis.Alloc.UnmarshalJSON(blob); err != nil { @@ -82,11 +84,11 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) { } genesis.Config = rawdb.ReadChainConfig(db, stored) if genesis.Config == nil { - return nil, fmt.Errorf("genesis config missing from db") + return nil, errors.New("genesis config missing from db") } genesisBlock := rawdb.ReadBlock(db, stored, 0) if genesisBlock == nil { - return nil, fmt.Errorf("genesis block missing from db") + return nil, errors.New("genesis block missing from db") } genesisHeader := genesisBlock.Header() genesis.Nonce = genesisHeader.Nonce.Uint64() @@ -96,6 +98,9 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) { genesis.Difficulty = genesisHeader.Difficulty genesis.Mixhash = genesisHeader.MixDigest genesis.Coinbase = genesisHeader.Coinbase + genesis.BaseFee = genesisHeader.BaseFee + genesis.ExcessBlobGas = genesisHeader.ExcessBlobGas + genesis.BlobGasUsed = genesisHeader.BlobGasUsed return &genesis, nil } @@ -115,8 +120,8 @@ func (ga *GenesisAlloc) UnmarshalJSON(data []byte) error { return nil } -// deriveHash computes the state root according to the genesis specification. -func (ga *GenesisAlloc) deriveHash() (common.Hash, error) { +// hash computes the state root according to the genesis specification. +func (ga *GenesisAlloc) hash() (common.Hash, error) { // Create an ephemeral in-memory database for computing hash, // all the derived states will be discarded to not pollute disk. db := state.NewDatabase(rawdb.NewMemoryDatabase()) @@ -125,33 +130,37 @@ func (ga *GenesisAlloc) deriveHash() (common.Hash, error) { return common.Hash{}, err } for addr, account := range *ga { - statedb.AddBalance(addr, account.Balance) + if account.Balance != nil { + statedb.AddBalance(addr, account.Balance) + } statedb.SetCode(addr, account.Code) statedb.SetNonce(addr, account.Nonce) for key, value := range account.Storage { statedb.SetState(addr, key, value) } } - return statedb.Commit(false) + return statedb.Commit(0, false) } -// flush is very similar with deriveHash, but the main difference is -// all the generated states will be persisted into the given database. -// Also, the genesis state specification will be flushed as well. +// flush is very similar with hash, but the main difference is all the generated +// states will be persisted into the given database. Also, the genesis state +// specification will be flushed as well. func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhash common.Hash) error { statedb, err := state.New(types.EmptyRootHash, state.NewDatabaseWithNodeDB(db, triedb), nil) if err != nil { return err } for addr, account := range *ga { - statedb.AddBalance(addr, account.Balance) + if account.Balance != nil { + statedb.AddBalance(addr, account.Balance) + } statedb.SetCode(addr, account.Code) statedb.SetNonce(addr, account.Nonce) for key, value := range account.Storage { statedb.SetState(addr, key, value) } } - root, err := statedb.Commit(false) + root, err := statedb.Commit(0, false) if err != nil { return err } @@ -170,41 +179,6 @@ func (ga *GenesisAlloc) flush(db ethdb.Database, triedb *trie.Database, blockhas return nil } -// CommitGenesisState loads the stored genesis state with the given block -// hash and commits it into the provided trie database. -func CommitGenesisState(db ethdb.Database, triedb *trie.Database, blockhash common.Hash) error { - var alloc GenesisAlloc - blob := rawdb.ReadGenesisStateSpec(db, blockhash) - if len(blob) != 0 { - if err := alloc.UnmarshalJSON(blob); err != nil { - return err - } - } else { - // Genesis allocation is missing and there are several possibilities: - // the node is legacy which doesn't persist the genesis allocation or - // the persisted allocation is just lost. - // - supported networks(mainnet, testnets), recover with defined allocations - // - private network, can't recover - var genesis *Genesis - switch blockhash { - case params.MainnetGenesisHash: - genesis = DefaultGenesisBlock() - case params.RinkebyGenesisHash: - genesis = DefaultRinkebyGenesisBlock() - case params.GoerliGenesisHash: - genesis = DefaultGoerliGenesisBlock() - case params.SepoliaGenesisHash: - genesis = DefaultSepoliaGenesisBlock() - } - if genesis != nil { - alloc = genesis.Alloc - } else { - return errors.New("not found") - } - } - return alloc.flush(db, triedb, blockhash) -} - // GenesisAccount is an account in the state of the genesis block. type GenesisAccount struct { Code []byte `json:"code,omitempty"` @@ -216,15 +190,17 @@ type GenesisAccount struct { // field type overrides for gencodec type genesisSpecMarshaling struct { - Nonce math.HexOrDecimal64 - Timestamp math.HexOrDecimal64 - ExtraData hexutil.Bytes - GasLimit math.HexOrDecimal64 - GasUsed math.HexOrDecimal64 - Number math.HexOrDecimal64 - Difficulty *math.HexOrDecimal256 - BaseFee *math.HexOrDecimal256 - Alloc map[common.UnprefixedAddress]GenesisAccount + Nonce math.HexOrDecimal64 + Timestamp math.HexOrDecimal64 + ExtraData hexutil.Bytes + GasLimit math.HexOrDecimal64 + GasUsed math.HexOrDecimal64 + Number math.HexOrDecimal64 + Difficulty *math.HexOrDecimal256 + Alloc map[common.UnprefixedAddress]GenesisAccount + BaseFee *math.HexOrDecimal256 + ExcessBlobGas *math.HexOrDecimal64 + BlobGasUsed *math.HexOrDecimal64 } type genesisAccountMarshaling struct { @@ -268,6 +244,7 @@ func (e *GenesisMismatchError) Error() string { // ChainOverrides contains the changes to chain config. type ChainOverrides struct { OverrideCancun *uint64 + OverrideVerkle *uint64 } // SetupGenesisBlock writes or updates the genesis block in db. @@ -296,6 +273,9 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen if overrides != nil && overrides.OverrideCancun != nil { config.CancunTime = overrides.OverrideCancun } + if overrides != nil && overrides.OverrideVerkle != nil { + config.VerkleTime = overrides.OverrideVerkle + } } } // Just commit the new block if there is no stored genesis block. @@ -314,10 +294,12 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen applyOverrides(genesis.Config) return genesis.Config, block.Hash(), nil } - // We have the genesis block in database(perhaps in ancient database) - // but the corresponding state is missing. + // The genesis block is present(perhaps in ancient database) while the + // state database is not initialized yet. It can happen that the node + // is initialized with an external ancient store. Commit genesis state + // in this case. header := rawdb.ReadHeader(db, stored, 0) - if header.Root != types.EmptyRootHash && !rawdb.HasLegacyTrieNode(db, header.Root) { + if header.Root != types.EmptyRootHash && !triedb.Initialized(header.Root) { if genesis == nil { genesis = DefaultGenesisBlock() } @@ -366,7 +348,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen // are returned to the caller unless we're already at block zero. head := rawdb.ReadHeadHeader(db) if head == nil { - return newcfg, stored, fmt.Errorf("missing head header") + return newcfg, stored, errors.New("missing head header") } compatErr := storedcfg.CheckCompatible(newcfg, head.Number.Uint64(), head.Time) if compatErr != nil && ((head.Number.Uint64() != 0 && compatErr.RewindToBlock != 0) || (head.Time != 0 && compatErr.RewindToTime != 0)) { @@ -420,8 +402,6 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { return params.MainnetChainConfig case ghash == params.SepoliaGenesisHash: return params.SepoliaChainConfig - case ghash == params.RinkebyGenesisHash: - return params.RinkebyChainConfig case ghash == params.GoerliGenesisHash: return params.GoerliChainConfig default: @@ -431,7 +411,7 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { // ToBlock returns the genesis block according to genesis specification. func (g *Genesis) ToBlock() *types.Block { - root, err := g.Alloc.deriveHash() + root, err := g.Alloc.hash() if err != nil { panic(err) } @@ -463,9 +443,28 @@ func (g *Genesis) ToBlock() *types.Block { } } var withdrawals []*types.Withdrawal - if g.Config != nil && g.Config.IsShanghai(big.NewInt(int64(g.Number)), g.Timestamp, types.DeserializeHeaderExtraInformation(head).ArbOSFormatVersion) { - head.WithdrawalsHash = &types.EmptyWithdrawalsHash - withdrawals = make([]*types.Withdrawal, 0) + if conf := g.Config; conf != nil { + num := big.NewInt(int64(g.Number)) + arbosVersion := types.DeserializeHeaderExtraInformation(head).ArbOSFormatVersion + if conf.IsShanghai(num, g.Timestamp, arbosVersion) { + head.WithdrawalsHash = &types.EmptyWithdrawalsHash + withdrawals = make([]*types.Withdrawal, 0) + } + if conf.IsCancun(num, g.Timestamp, arbosVersion) { + // EIP-4788: The parentBeaconBlockRoot of the genesis block is always + // the zero hash. This is because the genesis block does not have a parent + // by definition. + head.ParentBeaconRoot = new(common.Hash) + // EIP-4844 fields + head.ExcessBlobGas = g.ExcessBlobGas + head.BlobGasUsed = g.BlobGasUsed + if head.ExcessBlobGas == nil { + head.ExcessBlobGas = new(uint64) + } + if head.BlobGasUsed == nil { + head.BlobGasUsed = new(uint64) + } + } } return types.NewBlock(head, nil, nil, nil, trie.NewStackTrie(nil)).WithWithdrawals(withdrawals) } @@ -513,10 +512,8 @@ func WriteHeadBlock(db ethdb.Database, block *types.Block, prevDifficulty *big.I // MustCommit writes the genesis block and state to db, panicking on error. // The block is committed as the canonical head block. -// Note the state changes will be committed in hash-based scheme, use Commit -// if path-scheme is preferred. -func (g *Genesis) MustCommit(db ethdb.Database) *types.Block { - block, err := g.Commit(db, trie.NewDatabase(db)) +func (g *Genesis) MustCommit(db ethdb.Database, triedb *trie.Database) *types.Block { + block, err := g.Commit(db, triedb) if err != nil { panic(err) } @@ -535,18 +532,6 @@ func DefaultGenesisBlock() *Genesis { } } -// DefaultRinkebyGenesisBlock returns the Rinkeby network genesis block. -func DefaultRinkebyGenesisBlock() *Genesis { - return &Genesis{ - Config: params.RinkebyChainConfig, - Timestamp: 1492009146, - ExtraData: hexutil.MustDecode("0x52657370656374206d7920617574686f7269746168207e452e436172746d616e42eb768f2244c8811c63729a21a3569731535f067ffc57839b00206d1ad20c69a1981b489f772031b279182d99e65703f0076e4812653aab85fca0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), - GasLimit: 4700000, - Difficulty: big.NewInt(1), - Alloc: decodePrealloc(rinkebyAllocData), - } -} - // DefaultGoerliGenesisBlock returns the Görli network genesis block. func DefaultGoerliGenesisBlock() *Genesis { return &Genesis{ @@ -572,22 +557,29 @@ func DefaultSepoliaGenesisBlock() *Genesis { } } +// DefaultHoleskyGenesisBlock returns the Holesky network genesis block. +func DefaultHoleskyGenesisBlock() *Genesis { + return &Genesis{ + Config: params.HoleskyChainConfig, + Nonce: 0x1234, + GasLimit: 0x17d7840, + Difficulty: big.NewInt(0x01), + Timestamp: 1695902100, + Alloc: decodePrealloc(holeskyAllocData), + } +} + // DeveloperGenesisBlock returns the 'geth --dev' genesis block. -func DeveloperGenesisBlock(period uint64, gasLimit uint64, faucet common.Address) *Genesis { +func DeveloperGenesisBlock(gasLimit uint64, faucet common.Address) *Genesis { // Override the default period to the user requested one - config := *params.AllCliqueProtocolChanges - config.Clique = ¶ms.CliqueConfig{ - Period: period, - Epoch: config.Clique.Epoch, - } + config := *params.AllDevChainProtocolChanges // Assemble and return the genesis with the precompiles and faucet pre-funded return &Genesis{ Config: &config, - ExtraData: append(append(make([]byte, 32), faucet[:]...), make([]byte, crypto.SignatureLength)...), GasLimit: gasLimit, BaseFee: big.NewInt(params.InitialBaseFee), - Difficulty: big.NewInt(1), + Difficulty: big.NewInt(0), Alloc: map[common.Address]GenesisAccount{ common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256 @@ -604,13 +596,34 @@ func DeveloperGenesisBlock(period uint64, gasLimit uint64, faucet common.Address } func decodePrealloc(data string) GenesisAlloc { - var p []struct{ Addr, Balance *big.Int } + var p []struct { + Addr *big.Int + Balance *big.Int + Misc *struct { + Nonce uint64 + Code []byte + Slots []struct { + Key common.Hash + Val common.Hash + } + } `rlp:"optional"` + } if err := rlp.NewStream(strings.NewReader(data), 0).Decode(&p); err != nil { panic(err) } ga := make(GenesisAlloc, len(p)) for _, account := range p { - ga[common.BigToAddress(account.Addr)] = GenesisAccount{Balance: account.Balance} + acc := GenesisAccount{Balance: account.Balance} + if account.Misc != nil { + acc.Nonce = account.Misc.Nonce + acc.Code = account.Misc.Code + + acc.Storage = make(map[common.Hash]common.Hash) + for _, slot := range account.Misc.Slots { + acc.Storage[slot.Key] = slot.Val + } + } + ga[common.BigToAddress(account.Addr)] = acc } return ga } diff --git a/core/genesis_alloc.go b/core/genesis_alloc.go index 542dcc4120c2..94c4ec535889 100644 --- a/core/genesis_alloc.go +++ b/core/genesis_alloc.go @@ -22,871 +22,6 @@ package core // nolint: misspell const mainnetAllocData = "\xfa\x04]X\u0793\r\x83b\x011\x8e\u0189\x9agT\x06\x908'\x80t2\x80\x89\n\u05ce\xbcZ\xc6 \x00\x00\u0793\x17bC\x0e\xa9\u00e2nWI\xaf\xdbp\xda_x\u077b\x8c\x89\n\u05ce\xbcZ\xc6 \x00\x00\u0793\x1d\x14\x80K9\x9cn\xf8\x0edWoev`\x80O\xec\v\x89\u3bb5sr@\xa0\x00\x00\u07932@5\x87\x94{\x9f\x15b*h\xd1\x04\xd5M3\xdb\xd1\u0349\x043\x87Oc,\xc6\x00\x00\u0793I~\x92\xcd\xc0\xe0\xb9c\xd7R\xb2)j\u02c7\u0682\x8b$\x89\n\x8fd\x9f\xe7\xc6\x18\x00\x00\u0793K\xfb\xe1Tk\xc6\xc6[\\~\xaaU0K8\xbb\xfe\xc6\u04c9lk\x93[\x8b\xbd@\x00\x00\u0793Z\x9c\x03\xf6\x9d\x17\xd6l\xbb\x8a\xd7!\x00\x8a\x9e\xbb\xb86\xfb\x89lk\x93[\x8b\xbd@\x00\x00\u0793]\x0e\xe8\x15^\xc0\xa6\xffh\bU,\xa5\xf1k\xb5\xbe2:\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u0793v\"\xd8J#K\xb8\xb0x#\x0f\u03c4\xb6z\u9a2c\xae\x89%\xe1\xccQ\x99R\xf8\x00\x00\u0793{\x9f\xc3\x19\x05\xb4\x99K\x04\xc9\xe2\xcf\xdc^'pP?B\x89l]\xb2\xa4\xd8\x15\xdc\x00\x00\u0793\u007fJ#\xca\x00\xcd\x04=%\u0088\x8c\x1a\xa5h\x8f\x81\xa3D\x89)\xf0\xa9[\xfb\xf7)\x00\x00\u0793\x869\u06bb\u3bac\x88{]\xc0\xe4>\x13\xbc\u0487\xd7l\x89\x10\xd0\xe3\xc8}n,\x00\x00\u0793\x89P\x86y\xab\xf8\xc7\x1b\xf6x\x16\x87\x12\x0e>j\x84XM\x89a\x94\x04\x9f0\xf7 \x00\x00\u0793\x8f\xc7\u02ed\xff\xbd\r\u007f\xe4O\x8d\xfd`\xa7\x9dr\x1a\x1c\x9c\x8965\u026d\xc5\u07a0\x00\x00\u0793\x95`\xa3\xdebxh\xf9\x1f\xa8\xbf\xe1\xc1\xb7\xaf\xaf\b\x18k\x89\x1cg\xf5\xf7\xba\xa0\xb0\x00\x00\u0793\x96\x97G\xf7\xa5\xb3\x06E\xfe\x00\xe4I\x01CZ\xce$\xcc7\x89\\(=A\x03\x94\x10\x00\x00\u0793\x9am}\xb3&g\x9bw\xc9\x03\x91\xa7Gm#\x8f;\xa3>\x89\n\xdaUGK\x814\x00\x00\u0793\x9e\xef\n\b\x86\x05n?i!\x18S\xb9\xb7E\u007f7\x82\u4262\xa8x\x06\x9b(\xe0\x00\x00\u0793\x9f\xdb\xf4N\x1fJcb\xb7i\u00daG_\x95\xa9l+\u01c9\x1e\x93\x12\x83\xcc\xc8P\x00\x00\u07d3\xa5y\u007fR\xc9\u054f\x18\x9f6\xb1\xd4]\x1b\xf6\x04\x1f/k\x8a\x01'\u0473F\x1a\xcd\x1a\x00\x00\u0793\xaaS\x81\xb2\x13\x8e\xbe\xff\xc1\x91\xd5\xd8\u00d1u;p\x98\u04895\xab\xb0\x9f\xfe\u07b6\x80\x00\u0793\xaa\xda%\xea\"\x86p\x9a\xbbB-A\x92?\u04c0\xcd\x04\u01c9#=\xf3)\x9far\x00\x00\u0793\xac\xbf\xb2\xf2ZT\x85\xc79\xefp\xa4N\xee\xeb|e\xa6o\x89\x05k\xc7^-c\x10\x00\x00\u07d3\xac\xc6\xf0\x82\xa4B\x82\x87d\xd1\x1fX\u0589J\xe4\b\xf0s\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u0793\xb2w\xb0\x99\xa8\xe8f\xca\x0e\xc6[\u02c7(O\xd1B\xa5\x82\x89j\xcb=\xf2~\x1f\x88\x00\x00\u0753\xbd\xd4\x01:\xa3\x1c\x04al+\xc9x_'\x88\xf9\x15g\x9b\x88\xb9\xf6]\x00\xf6<\x00\x00\u0793\xc2}c\xfd\xe2K\x92\xee\x8a\x1e~\xd5\xd2m\x8d\xc5\xc8;\x03\x89lk\x93[\x8b\xbd@\x00\x00\u0553\xc4\x0f\xe2\tT#P\x9b\x9f\u0677T21X\xaf#\x10\xf3\x80\u0793\xd7^\xd6\fwO\x8b:ZQs\xfb\x183\xadq\x05\xa2\u0649l\xb7\xe7Hg\xd5\xe6\x00\x00\u07d3\u05cd\x89\xb3_G'\x16\xec\xea\xfe\xbf`\x05'\u04e1\xf9i\x8a\x05\xe0T\x9c\x962\xe1\xd8\x00\x00\u0793\xda\xe2{5\v\xae \xc5e!$\xaf]\x8b\\\xba\x00\x1e\xc1\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u0793\xdc\x01\xcb\xf4Ix\xa4.\x8d\xe8\xe46\xed\xf9B\x05\u03f6\xec\x89O\x0f\xeb\xbc\u068c\xb4\x00\x00\u07d3\u607c-\x10\xdbb\u0785\x84\x83$I\"P4\x8e\x90\xbf\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d3\xf4c\xe17\xdc\xf6%\xfb\U000fc8de\u0298\u04b9h\xcf\u007f\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4\x01\x00\a9K\x8bue\xa1e\x8a\xf8\x8c\xe4cI\x915\u05b7\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x01\r\xf1\xdfK\xed#v\r-\x1c\x03x\x15\x86\xdd\xf7\x91\x8eT\x89\x03@\xaa\xd2\x1b;p\x00\x00\xe0\x94\x01\x0fJ\x98\u07e1\xd9y\x9b\xf5\u01d6\xfbU\x0e\xfb\xe7\xec\xd8w\x8a\x01\xb2\xf2\x92#b\x92\xc7\x00\x00\u07d4\x01\x15PW\x00/k\r\x18\xac\xb98\x8d;\xc8\x12\x9f\x8fz \x89H\xa4\xa9\x0f\xb4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x01k`\xbbmg\x92\x8c)\xfd\x03\x13\xc6f\u068f\x16\x98\xd9\u0149lk\x93[\x8b\xbd@\x00\x00\u07d4\x01l\x85\xe1a;\x90\x0f\xa3W\xb8(;\x12\x0ee\xae\xfc\xdd\b\x89+]\x97\x84\xa9|\xd5\x00\x00\u07d4\x01\x84\x92H\x8b\xa1\xa2\x924\"G\xb3\x18U\xa5Y\x05\xfe\xf2i\x89\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4\x01\x8f \xa2{'\xecD\x1a\xf7#\xfd\x90\x99\xf2\u02f7\x9dbc\x89uy*\x8a\xbd\xef|\x00\x00\u07d4\x01\x91\xebT~{\xf6\x97k\x9b\x1bWuFv\x1d\xe6V\"\xe2\x89lkLM\xa6\u077e\x00\x00\u07d4\x01\x9dp\x95y\xffK\xc0\x9f\xdc\xdd\xe41\xdc\x14G\xd2\xc2`\xbc\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x01\xa2Z_Z\xf0\x16\x9b0\x86L;\xe4\xd7V<\xcdD\xf0\x9e\x89M\x85<\x8f\x89\b\x98\x00\x00\xe0\x94\x01\xa7\xd9\xfa}\x0e\xb1\x18\\g\xe5M\xa8<.u\xdbi\u37ca\x01\x9dJ\xdd\xd0\u063c\x96\x00\x00\u07d4\x01\xa8\x18\x13ZAB\x10\xc3|b\xb6%\xac\xa1\xa5F\x11\xac6\x89\x0e\x189\x8ev\x01\x90\x00\x00\u07d4\x01\xb1\xca\xe9\x1a;\x95Y\xaf\xb3<\xdcmh\x94B\xfd\xbf\xe07\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x01\xb5\xb5\xbcZ\x11\u007f\xa0\x8b4\xed\x1d\xb9D\x06\bYz\xc5H\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x01\xbb\xc1Og\xaf\x069\xaa\xb1D\x1ej\b\xd4\xceqb\t\x0f\x89F\xfc\xf6\x8f\xf8\xbe\x06\x00\x00\xe0\x94\x01\xd08\x15\xc6\x1fAkq\xa2a\n-\xab\xa5\x9f\xf6\xa6\xde[\x8a\x02\x05\xdf\xe5\v\x81\xc8.\x00\x00\u07d4\x01\u0559\xee\r_\x8c8\xab-9.,e\xb7L<\xe3\x18 \x89\x1b\xa5\xab\xf9\xe7y8\x00\x00\u07d4\x01\xe4\x05!\x12%0\u066c\x91\x11<\x06\xa0\x19\vmc\x85\v\x89Hz\x9a0E9D\x00\x00\u07d4\x01\xe6A]X{\x06T\x90\xf1\xed\u007f!\xd6\xe0\xf3\x86\xeegG\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x01\xe8d\xd3Tt\x1bB>oB\x85\x17$F\x8ct\xf5\xaa\x9c\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x01\xed_\xba\x8d.\xabg:\xec\x04-0\xe4\xe8\xa6\x11\xd8\xc5Z\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x01\xfb\x8e\xc1$%\xa0O\x81>F\xc5L\x05t\x8c\xa6\xb2\x9a\xa9\x89\x0e\x15s\x03\x85F|\x00\x00\u07d4\x01\xff\x1e\xb1\u07adP\xa7\xf2\xf9c\x8f\xde\xe6\xec\xcf:{*\u0209 \x86\xac5\x10R`\x00\x00\u07d4\x02\x03b\u00ed\xe8x\u0290\u05b2\u0609\xa4\xccU\x10\xee\xd5\xf3\x898\x88\xe8\xb3\x11\xad\xb3\x80\x00\u07d4\x02\x03\xae\x01\xd4\xc4\x1c\xae\x18e\xe0K\x1f[S\xcd\xfa\xec\xae1\x896\x89\xcd\u03b2\x8c\xd7\x00\x00\u07d4\x02\b\x93a\xa3\xfetQ\xfb\x1f\x87\xf0\x1a-\x86fS\xdc\v\a\x89\x02*\xc7H2\xb5\x04\x00\x00\u07d4\x02\x1fi\x04=\xe8\x8cI\x17\xca\x10\xf1\x84(\x97\xee\xc0X\x9c|\x89kD\u03f8\x14\x87\xf4\x00\x00\u07d4\x02)\x0f\xb5\xf9\xa5\x17\xf8(E\xac\xde\xca\x0f\xc8F\x03\x9b\xe23\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x029\xb4\xf2\x1f\x8e\x05\xcd\x01Q++\xe7\xa0\xe1\x8am\x97F\a\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x02Gr\x12\xff\xddu\xe5\x15VQ\xb7e\x06\xb1dfq\xa1\xeb\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\x02J\t\x8a\xe7\x02\xbe\xf5@l\x9c\"\xb7\x8b\xd4\xeb,\u01e2\x93\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x02K\xdd,{\xfdP\x0e\xe7@O\u007f\xb3\xe9\xfb1\xdd \xfb\u0449\t\xc2\x00vQ\xb2P\x00\x00\u07d4\x02Sg\x96\x03\x04\xbe\xee4Y\x11\x18\xe9\xac-\x13X\xd8\x02\x1a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x02V\x14\x9f[Pc\xbe\xa1N\x15f\x1f\xfbX\xf9\xb4Y\xa9W\x89&)\xf6n\fS\x00\x00\x00\u07d4\x02`=z;\xb2\x97\xc6|\x87~]4\xfb\u0579\x13\xd4\xc6:\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x02a\xad:\x17*\xbf\x13\x15\xf0\xff\xec2p\x98j\x84\t\xcb%\x89\v\b!;\u03cf\xfe\x00\x00\u07d4\x02d2\xaf7\xdcQ\x13\xf1\xf4mH\nM\u0c80R#~\x89\x13I\xb7\x86\xe4\v\xfc\x00\x00\u07d4\x02f\xab\x1ck\x02\x16#\v\x93\x95D=_\xa7^hEh\u018965\u026d\xc5\u07a0\x00\x00\u07d4\x02u\x1d\u018c\xb5\xbdsp'\xab\xf7\u0777s\x90\xcdw\xc1k\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x02w\x8e9\x0f\xa1u\x10\xa3B\x8a\xf2\x87\fBsT}8l\x8a\x03lw\x80\x18\x8b\xf0\xef\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x037|\x0eUkd\x01\x03(\x9aa\x89\u1baecI4g\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x03IcM\u00a9\xe8\f?w!\xee+PF\xae\xaa\xed\xfb\xb5\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x03U\xbc\xac\xbd!D\x1e\x95\xad\xee\xdc0\xc1r\x18\u0224\b\u0389\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x03n\xef\xf5\xba\x90\xa6\x87\x9a\x14\xdf\xf4\xc5\x04;\x18\xca\x04`\u0249\x05k\xc7^-c\x10\x00\x00\xe0\x94\x03qKA\u04a6\xf7Q\x00\x8e\xf8\xddM+)\xae\u02b8\xf3n\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\x03r\xe8RX.\t44J\x0f\xed!x0M\xf2]F(\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x03r\xeeU\b\xbf\x81c\xed(N^\xef\x94\xceMsg\xe5\"\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x03}\xd0V\xe7\xfd\xbdd\x1d\xb5\xb6\xbe\xa2\xa8x\n\x83\xfa\u1009\a\x96\xe3\xea?\x8a\xb0\x00\x00\xe0\x94\x03\x83#\xb1\x84\xcf\xf7\xa8*\xe2\u1f67y?\xe41\x9c\xa0\xbf\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x03\x87y\xca-\xbef>c\xdb?\xe7V\x83\xea\x0e\xc6.#\x83\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\xe0\x94\x03\x8eE\xea\xdd=\x88\xb8\u007f\xe4\u06b0fh\x05\"\xf0\xdf\xc8\xf9\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x03\x92T\x9ar\u007f\x81eT)\u02d2\x8bR\x9f%\xdfM\x13\x85\x89\x01lC\xa0\xee\xa0t\x00\x00\u07d4\x03\x94\xb9\x0f\xad\xb8`O\x86\xf4?\xc1\xe3]1$\xb3*Y\x89\x89)j\xa1@'\x8ep\x00\x00\u0794\x03\x9ezN\xbc(N,\xcdB\xb1\xbd\xd6\v\xd6Q\x1c\x0fw\x06\x88\xf0\x15\xf2W6B\x00\x00\u07d4\x03\x9e\xf1\xceR\xfeyc\xf1f\u0562u\u0131\x06\x9f\xe3\xa82\x89\x15\xaf9\u4ab2t\x00\x00\u07d4\x03\xa2l\xfcL\x181op\u055e\x9e\x1ay\xee>\x8b\x96/L\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x03\xaab(\x81#m\xd0\xf4\x94\f$\xc3$\xff\x8b{~!\x86\x89\xadx\xeb\u016cb\x00\x00\x00\u07d4\x03\xafz\xd9\xd5\"<\xf7\xc8\xc1? \xdfg\xeb\xe5\xff\u017bA\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x03\xb0\xf1|\xd4F\x9d\xdc\u03f7\xdai~\x82\xa9\x1a_\x9ewt\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x03\xb4\x1bQ\xf4\x1d\xf2\r\xd2y\xba\xe1\x8c\x12w_w\xadw\x1c\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x03\xbe[F)\xae\xfb\xbc\xab\x9d\xe2m9Wl\xb7\xf6\x91\xd7d\x89\n\xdf0\xbap\u0217\x00\x00\u07d4\x03\xc6G\xa9\xf9)\xb0x\x1f\xe9\xae\x01\u02a3\xe1\x83\xe8vw~\x89\x18*\xb7\xc2\f\xe5$\x00\x00\u07d4\x03\xc9\x1d\x92\x946\x03\xe7R >\x054\x0eV`\x13\xb9\x00E\x89+|\xc2\xe9\xc3\"\\\x00\x00\u07d4\x03\xcbLOE\x16\xc4\xffy\xa1\xb6$O\xbfW.\x1c\u007f\xeay\x89\x94\x89#z\u06daP\x00\x00\u07d4\x03\u02d8\u05ec\xd8\x17\u079d\x88m\"\xfa\xb3\xf1\xb5}\x92\xa6\b\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\x03\u031d-!\xf8k\x84\xac\x8c\xea\xf9q\u06e7\x8a\x90\xe6%p\x89WG=\x05\u06ba\xe8\x00\x00\u07d4\x03\xd1rO\xd0\x0eT\xaa\xbc\xd2\xde*\x91\xe8F+\x10I\xdd:\x89\x8f\x1d\\\x1c\xae7@\x00\x00\u07d4\x03\xde\xdf\xcd\v<.\x17\xc7\x05\xda$\x87\x90\uf626\xbdWQ\x89Hz\x9a0E9D\x00\x00\u07d4\x03\u8c04SuW\xe7\t\xea\xe2\xe1\u1966\xbc\xe1\xef\x83\x14\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x03\xeam&\u0400\xe5z\xee9&\xb1\x8e\x8e\xd7:N[(&\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x03\xeb<\xb8`\xf6\x02\x8d\xa5T\xd3D\xa2\xbbZP\n\xe8\xb8o\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x03\xeb\xc6?\xdaf`\xa4e\x04^#_\xben\\\xf1\x95s_\x89\a\xb0l\xe8\u007f\xddh\x00\x00\xe0\x94\x03\xefj\xd2\x0f\xf7\xbdO\x00+\xacX\xd4uD\u03c7\x9a\xe7(\x8a\x01u\xc7X\u0439n\\\x00\x00\u07d4\x03\xf7\xb9 \b\x81:\xe0\xa6v\xeb!(\x14\xaf\xab5\"\x10i\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x04\x11p\xf5\x81\u0780\xe5\x8b*\x04\\\x8f|\x14\x93\xb0\x01\xb7\u02c90\xc8\xeca2\x89\nZ\xa8P\t\xe3\x9c\x00\x00\u07d4\x04i\xe8\xc4@E\v\x0eQ&&\xfe\x81~gT\xa8\x15(0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x04m'K\x1a\xf6\x15\xfbPZvJ\xd8\u0767p\xb1\xdb/=\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x04}Z&\u05ed\x8f\x8ep`\x0fp\xa3\x98\u076a\x1c-\xb2o\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x04~\x87\xc8\xf7\xd1\xfc\xe3\xb0\x13S\xa8Xb\xa9H\xac\x04\x9f>\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4\x04\u007f\x9b\xf1R\x9d\xaf\x87\xd4\a\x17^o\x17\x1b^Y\xe9\xff>\x89#<\x8f\xe4'\x03\xe8\x00\x00\xe0\x94\x04\x85'2\xb4\xc6R\xf6\xc2\u53b3e\x87\xe6\nb\xda\x14\u06ca\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x04\x8a\x89p\xeaAE\xc6MU\x17\xb8\xde[F\xd0YZ\xad\x06\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x04\x9c]K\xc6\xf2]NEli{R\xa0x\x11\xcc\u045f\xb1\x89\x10D\x00\xa2G\x0eh\x00\x00\u07d4\x04\xa1\xca\xda\x1c\xc7Q\b/\xf8\u0692\x8e<\xfa\x00\b \xa9\xe9\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x04\xa8\n\xfa\xd5>\xf1\xf8Ae\xcf\xd8R\xb0\xfd\xf1\xb1\xc2K\xa8\x89\x03$\xe9d\xb3\xec\xa8\x00\x00\u07d4\x04\xaa\xfc\x8a\xe5\xceoI\x03\u021d\u007f\xac\x9c\xb1\x95\x12\"Gw\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x04\xbaK\xb8q@\x02,!Jo\xacB\xdbZ\x16\u0755@E\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x04\xba\x8a?\x03\xf0\x8b\x89P\x95\x99M\xdaa\x9e\u06ac\xee>z\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x04\xc2\xc6K\xb5L>\xcc\xd0U\x85\xe1\x0e\xc6\xf9\x9a\f\xdb\x01\xa3\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x04\xceE\xf6\x00\xdb\x18\xa9\u0405\x1b)\xd99>\xbd\xaa\xfe=\u0149\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x04\u05b8\xd4\u0686t\a\xbb\x99wI\u07bb\xcd\xc0\xb3XS\x8a\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x04\xd78\x96\xcfe\x93\xa6\x91\x97*\x13\xa6\xe4\x87\x1f\xf2\xc4+\x13\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x04\xd8*\xf9\xe0\x1a\x93m\x97\xf8\xf8Y@\xb9p\xf9\xd4\u06d96\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x04\xe5\xf5\xbc|\x92?\xd1\xe3\x175\xe7.\xf9h\xfdg\x11\fn\x89WU\x1d\xbc\x8ebL\x00\x00\u07d4\x04\xec\xa5\x01c\n\xbc\xe3R\x18\xb1t\x95k\x89\x1b\xa2^\xfb#\x8966\x9e\xd7t}&\x00\x00\u07d4\x05\x05\xa0\x8e\"\xa1\t\x01Z\"\xf6\x850STf*U1\u0549\x8c\xf2?\x90\x9c\x0f\xa0\x00\x00\u07d4\x05\x14\x95L\xe8\x81\xc807\x03d\x00\x89lO\xd1\xee$nx\x00\x00\u07d4\x05\x1dBBv\xb2\x129fQ\x86\x13=e;\xb8\xb1\x86/\x89\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x05!\xbc:\x9f\x87\x11\xfe\xcb\x10\xf5\a\x97\xd7\x10\x83\xe3A\ub749\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x05#mL\x90\xd0e\xf9\u34c3X\xaa\xff\xd7w\xb8j\xecI\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x05*X\xe05\xf1\xfe\x9c\xdd\x16\x9b\xcf \x97\x03E\xd1+\x9cQ\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4\x05.\xab\x1fa\xb6\xd4U\x17(?A\xd1D\x18$\x87\x87I\u0409\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x053n\x9ar'(\xd9c\xe7\xa1\xcf'Y\xfd\x02tS\x0f\u02891\xa2D?\x88\x8ay\x80\x00\u07d4\x054q\u035aA\x92[9\x04\xa5\xa8\xff\xca6Y\xe04\xbe#\x89\n\xd2\x01\xa6yO\xf8\x00\x00\u07d4\x056\x1d\x8e\xb6\x94\x1dN\x90\xfb~\x14\x18\xa9Z2\xd5%w2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x05B:T\xc8\xd0\xf9p~pAs\xd9#\xb9F\xed\xc8\xe7\x00\x89\x06\xea\x03\u00bf\x8b\xa5\x80\x00\u07d4\x05D\f[\a;R\x9bH) \x9d\xff\x88\t\x0e\a\xc4\xf6\xf5\x89E\u04977\xe2/ \x00\x00\u07d4\x05Z\xb6X\xc6\xf0\xedO\x87^\xd6t.K\xc7)-\x1a\xbb\xf0\x89\x04\x86\u02d7\x99\x19\x1e\x00\x00\u07d4\x05[\xd0,\xaf\x19\xd6 +\xbc\u0703m\x18{\xd1\xc0\x1c\xf2a\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x05^\xacO\x1a\xd3\xf5\x8f\v\xd0$\u058e\xa6\r\xbe\x01\u01af\xb3\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x05fQU\xccI\xcb\xf6\xaa\xbd\u056e\x92\xcb\xfa\xad\x82\xb8\xc0\xc1\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x05f\x86\a\x8f\xb6\xbc\xf9\xba\n\x8a\x8d\xc6:\x90o_\xea\xc0\xea\x89\x1b\x18\x1eK\xf24<\x00\x00\u07d4\x05iks\x91k\xd3\x03>\x05R\x1e2\x11\xdf\xec\x02n\x98\xe4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x05k\x15F\x89O\x9a\x85\xe2\x03\xfb3m\xb5i\xb1l%\xe0O\x89\t.\xdb\t\xff\b\u0600\x00\u07d4\x05yI\xe1\xca\x05pF\x9eL\xe3\u0190\xaea:k\x01\xc5Y\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x05}\u049f-\x19\xaa=\xa4#'\xeaP\xbc\xe8o\xf5\xc9\x11\u0649\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x05\u007f\u007f\x81\xcdz@o\xc4Y\x94@\x8bPI\x91,Vdc\x89\\(=A\x03\x94\x10\x00\x00\u07d4\x05\x91]N\"Zf\x81b\xae\xe7\xd6\xc2_\xcf\xc6\xed\x18\xdb\x03\x89\x03\x98\xc3ry%\x9e\x00\x00\u07d4\x05\x96\xa2}\xc3\xee\x11_\xce/\x94\xb4\x81\xbc z\x9e&\x15%\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x05\xa80rC\x02\xbc\x0fn\xbd\xaa\x1e\xbe\xee\xb4nl\xe0\v9\x89\x05V\xf6L\x1f\xe7\xfa\x00\x00\u07d4\x05\xae\u007f\u053b\u0300\xca\x11\xa9\n\x1e\u01e3\x01\xf7\xcc\u0303\u06c91T\xc9r\x9d\x05x\x00\x00\u07d4\x05\xbbd\xa9\x16\xbef\xf4`\xf5\xe3\xb6C2\x11\r \x9e\x19\xae\x89\u3bb5sr@\xa0\x00\x00\xe0\x94\x05\xbfO\xcf\xe7r\xe4[\x82dC\x85.l5\x13P\xcer\xa2\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94\x05\xc6@\x04\xa9\xa8&\xe9N^N\xe2g\xfa*v2\xddNo\x8a\x03m\xc4.\xbf\xf9\v\u007f\x80\x00\xe0\x94\x05\xc76\xd3e\xaa7\xb5\xc0\xbe\x9c\x12\u022d\\\xd9\x03\xc3,\xf9\x8a\x01E^{\x80\n\x86\x88\x00\x00\xe0\x94\x05\xcbl;\x00r\xd3\x11ga\xb52\xb2\x18D;S\xe8\xf6\u014a\x1e\x02\xc3\xd7\xfc\xa9\xb6(\x00\x00\u07d4\x05\xd0\xf4\xd7(\xeb\xe8.\x84\xbfYu\x15\xadA\xb6\v\xf2\x8b9\x89\u3bb5sr@\xa0\x00\x00\u07d4\x05\u058d\xada\u04fb\u07f3\xf7y&\\IGJ\xff?\xcd0\x89\x02\"\xc5]\xc1Q\x9d\x80\x00\u07d4\x05\xe6q\xdeU\xaf\xec\x96K\aM\xe5t\xd5\x15\x8d]!\xb0\xa3\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x05\xe9{\tI,\u058fc\xb1+\x89.\xd1\xd1\x1d\x15,\x0e\u02897\b\xba\xed=h\x90\x00\x00\u07d4\x05\xf3c\x1fVd\xbd\xad]\x012\xc88\x8d6\xd7\u0612\t\x18\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x06\t\xd8:l\xe1\xff\u0276\x90\xf3\xe9\xa8\x1e\x98>\x8b\xdcM\x9d\x8a\x0e\u04b5%\x84\x1a\xdf\xc0\x00\x00\u07d4\x06\x1e\xa4\x87|\u0409D\xebd\u0096n\x9d\xb8\xde\xdc\xfe\xc0k\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x06%\xd0`V\x96\x8b\x00\"\x06\xff\x91\x98\x01@$+\xfa\xa4\x99\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x06(\xbf\xbeU5x/\xb5\x88@k\xc9f`\xa4\x9b\x01\x1a\xf5\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d4\x061\u044b\xbb\xbd0\xd9\xe1s+\xf3n\xda\xe2\u0389\x01\xab\x80\x89\xa3\xf9\x88U\xec9\x90\x00\x00\u07d4\x061\xdc@\xd7NP\x95\xe3r\x9e\xdd\xf4\x95D\xec\xd49og\x89\b\xacr0H\x9e\x80\x00\x00\xe0\x94\x067Y\xdd\x1cN6.\xb1\x93\x98\x95\x1f\xf9\xf8\xfa\xd1\xd3\x10h\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x06_\xf5u\xfd\x9c\x16\xd3\xcbo\u058f\xfc\x8fH?\xc3.\xc85\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x06a\x8e\x9dWb\xdfb\x02\x86\x01\xa8\x1dD\x87\u05a0\xec\xb8\x0e\x89Hz\x9a0E9D\x00\x00\xe0\x94\x06fG\xcf\xc8]#\xd3v\x05W= \x8c\xa1T\xb2D\xd7l\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x06xeJ\xc6v\x1d\xb9\x04\xa2\xf7\xe8Y^\xc1\xea\xacsC\b\x89/\x98\xb2\x9c(\x18\xf8\x00\x00\u07d4\x06\x86\n\x93RYU\xffbI@\xfa\xdc\xff\xb8\xe1I\xfdY\x9c\x89lh\xcc\u041b\x02,\x00\x00\xe0\x94\x06\x8c\xe8\xbdn\x90*E\u02c3\xb5\x15A\xb4\x0f9\xc4F\x97\x12\x8a\x01\x1c\x0f\x9b\xadJF\xe0\x00\x00\u07d4\x06\x8e)\xb3\xf1\x91\xc8\x12\xa699\x18\xf7\x1a\xb93\xaehG\xf2\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x06\x8eeWf\xb9D\xfb&6\x19e\x87@\xb8P\xc9J\xfa1\x89\x01\xe8\u007f\x85\x80\x9d\xc0\x00\x00\u0794\x06\x96N-\x17\xe9\x18\x9f\x88\xa8 96\xb4\n\xc9nS<\x06\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x06\x99L\xd8:\xa2d\n\x97\xb2`\vA3\x9d\x1e\r>\xdel\x89\r\x8drkqw\xa8\x00\x00\u07d4\x06\x9e\u042bz\xa7}\xe5q\xf1a\x06\x05\x1d\x92\xaf\xe1\x95\xf2\u0409\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x06\xac&\xad\x92\u02c5\x9b\u0550]\xdc\xe4&j\xa0\xecP\xa9\u0149*\x03I\x19\u07ff\xbc\x00\x00\u07d4\x06\xb0\xc1\xe3\u007fZ^\u013b\xf5\b@T\x8f\x9d:\xc0(\x88\x97\x89\xd8\u0602\u148e}\x00\x00\u07d4\x06\xb0\xff\x83@s\xcc\xe1\xcb\xc9\xeaU~\xa8{`Yc\u8d09\x10CV\x1a\x88)0\x00\x00\xe0\x94\x06\xb1\x06d\x9a\xa8\xc4!\xdd\xcd\x1b\x8c2\xcd\x04\x18\xcf0\xda\x1f\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\x06\xb5\xed\xe6\xfd\xf1\xd6\xe9\xa3G!7\x9a\xea\xa1|q=\xd8*\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x06\xcb\xfa\b\xcd\xd4\xfb\xa77\xba\xc4\a\xbe\x82$\xf4\xee\xf3X(\x89 +\xe5\xe88.\x8b\x80\x00\u07d4\x06\xd6\xcb0\x84\x81\xc36\xa6\xe1\xa2%\xa9\x12\xf6\xe65Y@\xa1\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\x06\xdc\u007f\x18\xce\xe7\xed\xab[yS7\xb1\xdfj\x9e\x8b\u062eY\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x06\xf6\x8d\xe3\xd79\xdbA\x12\x1e\xac\xf7y\xaa\xda=\xe8v!\a\x89\x01\x84\x93\xfb\xa6N\xf0\x00\x00\u07d4\x06\xf7\u070d\x1b\x94b\xce\xf6\xfe\xb13h\xa7\xe3\x97K\t\u007f\x9f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\a\x01\xf9\xf1G\xecHhV\xf5\xe1\xb7\x1d\xe9\xf1\x17\xe9\x9e!\x05\x89\te\xdaq\u007f\u0578\x00\x00\u07d4\a\r]6L\xb7\xbb\xf8\"\xfc,\xa9\x1a5\xbd\xd4A\xb2\x15\u0549lk\x93[\x8b\xbd@\x00\x00\xe0\x94\a\x1d\xd9\r\x14\xd4\x1fO\xf7\xc4\x13\xc2B8\xd35\x9c\xd6\x1a\a\x8a\a\xb5?y\xe8\x88\xda\xc0\x00\x00\u07d4\a&\xc4.\x00\xf4T\x04\x83n\xb1\xe2\x80\xd0s\xe7\x05\x96\x87\xf5\x89X\x00>?\xb9G\xa3\x80\x00\xe0\x94\a'\xbe\n*\x00! H\xb5R\x0f\xbe\xfb\x95>\xbc\x9dT\xa0\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\a)\xa8\xa4\xa5\xba#\xf5y\xd0\x02[\x1a\xd0\xf8\xa0\xd3\\\xdf\u048a\x02\r\u058a\xaf2\x89\x10\x00\x00\u07d4\a)\xb4\xb4|\t\xeb\x16\x15\x84d\u022a\u007f\xd9i\vC\x889\x89lh\xcc\u041b\x02,\x00\x00\u0794\a4\xa0\xa8\x1c\x95b\xf4\xd9\xe9\xe1\n\x85\x03\xda\x15\xdbF\xd7n\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94\a\xa7\xef[G\x00\x00\xe0\x94\ap\xc6\x1b\xe7\x87r#\f\xb5\xa3\xbb$)\xa7&\x14\xa0\xb36\x8a\x01n\u0899\xb7\x13A\x80\x00\u07d4\ar><0\xe8\xb71\xeeEj)\x1e\xe0\u7630 Jw\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\as\xee\xac\xc0P\xf7G \xb4\xa1\xbdW\x89[\x1c\xce\xebI]\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\a\x80\r/\x80h\xe4H\u01daOi\xb1\xf1^\xf6\x82\xaa\xe5\xf6\x8a\x04\x1b\xad\x15^e\x12 \x00\x00\u07d4\a\xa8\xda\xde\xc1BW\x1a}S\xa4)pQxm\a,\xbaU\x89\x01;m\xa1\x13\x9b\u0680\x00\u07d4\a\xaf\x93\x8c\x127\xa2|\x900\tM\xcf$\aP$n=,\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\a\xb1\xa3\x06\xcbC\x12\xdffH,,\xaer\xd1\xe0a@\x0f\u034a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\a\xb7\xa5p3\xf8\xf1\x130\xe4f^\x18]#N\x83\xec\x14\v\x89\xea~\xe9*\f\x9a\v\x80\x00\u07d4\a\xbc,\xc8\xee\xdc\x01\x97\a\x00\xef\xc9\xc4\xfb6s^\x98\xcdq\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\a\xd4\x12\x17\xba\u0725\xe0\xe6\x03'\xd8E\xa3FO\x0f'\xf8J\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\a\xd43N\u00c5\xe8\xaaT\xee\xda\xea\xdb0\x02/\f\u07e4\xab\x89\x8e\x91\xd5 \xf2\xeby\x00\x00\u07d4\a\xda\xe6\"c\r\x1168\x193\u04adk\"\xb89\xd8!\x02\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\a\xdc+\xf8;\u01af\x19\xa8B\xff\xeaf\x1a\xf5\xb4\x1bg\xfd\xa1\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\a\u070c\x8b\x92z\xdb\xed\xfa\x8f]c\x9bCR5\x1f/6\u0489\x11\n\xed;U0\xdb\x00\x00\u07d4\a\xdd\xd0B,\x86\xefe\xbf\f\u007f\xc3E(b\xb1\"\x8b\b\xb8\x89o\xf5\u04aa\x8f\x9f\xcf\x00\x00\u07d4\a\xe1\x16,\xea\xe3\xcf!\xa3\xf6-\x10Y\x900.0\u007fN;\x89R\xf1\x03\xed\xb6k\xa8\x00\x00\u07d4\a\xe2\xb4\xcd\xee\xd9\u0407\xb1.Um\x9ew\f\x13\xc0\x99a_\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\a\xfe\xefT\xc16\x85\b)\xba\xdcKI\xc3\xf2\xa7<\x89\xfb\x9e\x89\x06hZ\xc1\xbf\xe3,\x00\x00\u07d4\b\x05FP\x8a=&\x82\u0239\x88O\x13c{\x88G\xb4M\xb3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\b\t\bv\xba\xad\xfe\xe6\\=6;\xa5S\x12t\x8c\xfa\x87=\x89\\*\x997\x1c\xff\xe1\x00\x00\u07d4\b\x16o\x021?\xea\u12f0D\xe7\x87|\x80\x8bU\xb5\xbfX\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\b)\xd0\xf7\xbb|Dl\xfb\xb0\u07ad\xb29M\x9d\xb7$\x9a\x87\x89\x02,\xa3X|\xf4\xeb\x00\x00\u07d4\b0m\xe5\x19\x81\u7b21\x85hY\xb7\xc7xijki\xf9\x89\xadx\xeb\u016cb\x00\x00\x00\xe0\x94\b7S\x9b_jR*H,\xdc\u04e9\xbbpC\xaf9\xbd\u048a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\b8\xa7v\x8d\x9c*\u028b\xa2y\xad\xfe\xe4\xb1\xf4\x91\xe3&\xf1\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\bA\x16R\xc8qq6\t\xaf\x00b\xa8\xa1(\x1b\xf1\xbb\xcf\u0649K\xe4\xe7&{j\xe0\x00\x00\xe0\x94\bM\x102Tu\x9b4<\xb2\xb9\xc2\xd8\xff\x9e\x1a\xc5\xf1E\x96\x8a\x01\x9b\xff/\xf5yh\xc0\x00\x00\u07d4\bPO\x05d?\xabY\x19\xf5\xee\xa5Y%\u05e3\xed}\x80z\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\b[J\xb7]\x83b\xd9\x14C\\\xed\xee\x1d\xaa+\x1e\xe1\xa2;\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4\b[\xa6_\xeb\xe2>\xef\xc2\xc8\x02fj\xb1&#\x82\xcf\u0114\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\bt\x98\xc0FFh\xf3\x11P\xf4\xd3\u013c\u0765\"\x1b\xa1\x02\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\bw\uebabx\xd5\xc0\x0e\x83\xc3+-\x98\xfay\xadQH/\x89\x17\xd2-q\xdab&\x00\x00\u0794\b\x93j7\u07c5\xb3\xa1X\xca\xfd\x9d\xe0!\xf5\x817h\x13G\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\b\xa9\xa4N\x1fA\xde=\xbb\xa7\xa3c\xa3\xabA,\x12L\xd1^\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\b\xb7\xbd\u03d4MUp\x83\x8b\xe7\x04`$:\x86\x94HXX\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\b\xb8E6\xb7L\x8c\x01T=\xa8\x8b\x84\u05cb\xb9WG\xd8\"\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\b\xc2\xf26\xacJ\xdc\xd3\xfd\xa9\xfb\xc6\xe4S\"S\xf9\xda;\xec\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\b\xc8\x02\xf8wX4\x9f\xa0>k\xc2\xe2\xfd\a\x91\x19~\ua689lk\x93[\x8b\xbd@\x00\x00\u07d4\b\xc9\U0007fd89\xfd\xf8\x04\xd7i\xf8!#6\x02\x15\xaf\xf9;\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\b\xca\u0215&A\xd8\xfcRn\xc1\xabO-\xf8&\xa5\xe7q\x0f\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94\b\xcc\xdaP\xe4\xb2j\x0f\xfc\x0e\xf9.\x92\x051\a\x06\xbe\xc2\u01ca\x01Iul8W\xc6\x00\x00\x00\u07d4\b\u0406M\xc3/\x9a\xcb6\xbfN\xa4G\xe8\xddg&\x90j\x15\x89lnY\xe6|xT\x00\x00\u07d4\b\xd4&\u007f\xeb\x15\u0697\x00\xf7\xcc\xc3\xc8J\x89\x18\xbf\x17\xcf\u0789a\t=|,m8\x00\x00\xe0\x94\b\xd41\x1c\x9c\x1b\xba\xf8\u007f\xab\xe1\xa1\xd0\x14c\x82\x8d]\x98\u038a\x13\x0e\xe8\xe7\x17\x90D@\x00\x00\u07d4\b\xd5N\x83\xadHj\x93L\xfa\xea\u20e3>\xfd\"|\x0e\x99\x898S\x05\x83$^\xdc\x00\x00\u07d4\b\xd9~\xad\xfc\xb7\xb0d\xe1\xcc\xd9\u0217\x9f\xbe\xe5\xe7z\x97\x19\x89\x0el]\xa8\xd6z\xc1\x80\x00\u07d4\b\xda:z\x0fE!a\u03fc\xec1\x1b\xb6\x8e\xbf\xde\xe1~\x88\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\b\xe3\x8e\xe0\xceH\xc9\xcad\\\x10\x19\xf7;SUX\x1cV\xe6\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\b\xef?\xa4\xc4<\xcd\xc5{\"\xa4\xb9\xb23\x1a\x82\xe58\x18\xf2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\t\td\x8c\x18\xa3\xce[\xaez\x04~\xc2\xf8h\xd2L\u0768\x1d\x89\xcf\x15&@\xc5\xc80\x00\x00\u07d4\t\f\xd6{`\xe8\x1dT\xe7\xb5\xf6\a\x8f>\x02\x1b\xa6[\x9a\x1e\x8965\u026d\xc5\u07a0\x00\x00\u07d4\t\f\xeb\xef),>\xb0\x81\xa0_\u062a\xf7\u04db\xf0{\x89\u0509\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\t\x0f\xa96{\xdaW\xd0\xd3%:\n\x8f\xf7l\xe0\xb8\xe1\x9as\x8965\u026d\xc5\u07a0\x00\x00\u07d4\t\x14n\xa3\x88Qv\xf0w\x82\xe1\xfe0\xdc\xe3\xce$\u011e\x1f\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\t!`_\x99\x16N;\xcc(\xf3\x1c\xae\xcex\x971\x82V\x1d\x89+\ai*\x90e\xa8\x00\x00\xe0\x94\t&\x1f\x9a\xcbE\x1c7\x88\x84O\f\x14Q\xa3[\xadP\x98\xe3\x8a\x01\u056d'P) `\x00\x00\xe0\x94\t'\"\x04\x92\x19K.\u069f\u013b\xe3\x8f%\u0581\xdf\xd3l\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u0794\t*\xcbbK\b\xc0U\x10\x18\x9b\xbb\xe2\x1ee$\xd6D\u032d\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\t.\x81UX@-g\xf9\rk\xfem\xa0\xb2\xff\xfa\x91EZ\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\tP0\xe4\xb8&\x92\xdc\xf8\xb8\u0411$\x94\xb9\xb3x\xec\x93(\x89H\xa4zu\x80\x00\u07d4\t\x89\xc2\x00D\v\x87\x89\x91\xb6\x9d`\x95\xdf\xe6\x9e3\xa2.p\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\t\x90\xe8\x1c\u05c5Y\x9e\xa26\xbd\x19f\xcfRc\x02\xc3[\x9c\x8965\u026d\xc5\u07a0\x00\x00\u07d4\t\x98\xd8'1\x15\xb5j\xf4%\xff\xc8>!\x8c\x1e\n\xfe\x89(\u01c8\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\t\xaeI\xe3\u007f\x12\x1d\xf5\xdc\x15\x8c\xfd\xe8\x06\xf1s\xa0k\f\u007f\x89\xd80\x9e&\xab\xa1\xd0\x00\x00\u07d4\t\xaf\xa7;\xc0G\xefF\xb9w\xfd\x97c\xf8r\x86\xa6\xbeh\u0189\x1b/\xb5\xe8\xf0jf\x00\x00\u07d4\t\xb4f\x86\x96\xf8j\b\x0f\x8b\xeb\xb9\x1d\xb8\xe6\xf8p\x15\x91Z\x89#\x8f\xf7\xb3O`\x01\x00\x00\xe0\x94\t\xb5\x9b\x86\x98\xa7\xfb\xd3\xd2\xf8\xc7:\x00\x89\x88\xde>@k+\x8a\bxg\x83&\xea\xc9\x00\x00\x00\xe0\x94\t\xb7\xa9\x88\xd1?\xf8\x91\x86so\x03\xfd\xf4au\xb5=\x16\xe0\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\t\xc1w\xf1\xaeD$\x11\u076c\xf1\x87\xd4m\xb9V\x14\x83`\xe7\x8a\x01\xe5.3l\xde\"\x18\x00\x00\xe0\x94\t\u020f\x91~Mj\xd4s\xfa\x12\u93a3\xc4G*^\xd6\u068a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\t\u0438\xcd\a|i\xd9\xf3-\x9c\xcaC\xb3\xc2\b\xa2\x1e\u050b\x89\b!\xd2!\xb5)\x1f\x80\x00\xe0\x94\t\xd6\xce\xfdu\xb0\u0133\xf8\xf1\u0587\xa5\"\xc9a#\xf1\xf59\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\t\xe47\xd4H\x86\x12(\xa22\xb6.\xe8\xd3ye\xa9\x04\ud70a\x04\x98\xcf@\x1d\xf8\x84.\x80\x00\u07d4\t\xee\x12\xb1\xb4+\x05\xaf\x9c\xf2\a\xd5\xfc\xac%[.\xc4\x11\xf2\x89\x031\xcd\xddG\xe0\xfe\x80\x00\u07d4\t\xf3\xf6\x01\xf6\x05D\x11@Xl\xe0eo\xa2J\xa5\xb1\u066e\x89Sswo\xe8\xc4T\x00\x00\u07d4\t\xf9W[\xe5}\x00G\x93\u01e4\ub137\x15\x87\xf9|\xbbj\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\n\x06P\x86\x1fx^\xd8\xe4\xbf\x10\x05\xc4P\xbb\xd0n\xb4\x8f\xb6\x89\xa6A;y\x14N~\x00\x00\u07d4\n\x06\xfa\xd7\xdc\u05e4\x92\xcb\xc0S\xee\xab\xdei4\xb3\x9d\x867\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\n\a}\xb1?\xfe\xb0\x94\x84\xc2\x17p\x9dX\x86\xb8\xbf\x9cZ\x8b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\n\x0e\u0366cow\x16\xef\x19saF\x87\xfd\x89\xa8 \xa7\x06\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\n)\xa8\xa4\xd5\xfd\x95\x00u\xff\xb3Mw\xaf\xeb-\x82;\u0589\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\n*\u0795\xb2\xe8\xc6m\x8a\xe6\xf0\xbad\xcaW\u05c3\xbemD\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\n+O\xc5\xd8\x1a\xceg\xdcK\xba\x03\xf7\xb4UA=F\xfe=\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\n-\xcbzg\x17\x01\u06f8\xf4\x95r\x80\x88&Xs5l\x8e\x89\b?\x16\xce\b\xa0l\x00\x00\u07d4\n=\xe1U\xd5\xec\xd8\xe8\x1c\x1f\xf9\xbb\xf07\x83\x01\xf8\xd4\xc6#\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\nG\xad\x90Y\xa2I\xfc\x93k&b5=\xa6\x90_u\u00b9\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\nH)ov1p\x8c\x95\u04b7Iu\xbcJ\xb8\x8a\xc19*\x8a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x94\nJ\x01\x19\x95\u0181\xbc\x99\x9f\xddyuN\x9a2J\xe3\xb3y\x8a\b\xc1\x9a\xb0n\xb8\x9a\xf6\x00\x00\u07d4\nX\xfd\xddq\x89\x8d\xe7s\xa7O\xda\xe4^{\xd8N\xf46F\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\n[y\xd8\xf2;d\x83\xdb\u2f6ab\xb1\x06L\xc7cf\xae\x89j\u0202\x10\tR\u01c0\x00\u07d4\ne.*\x8bw\xbd\x97\xa7\x90\xd0\xe9\x13a\u0248\x90\u06f0N\x8965\u026d\xc5\u07a0\x00\x00\u07d4\nn\xber;n\xd1\xf9\xa8ji\xdd\xdah\xdcGF\\+\x1b\x89@=-\xb5\x99\xd5\xe4\x00\x00\u07d4\nw\xe7\xf7+C{WO\x00\x12\x8b!\xf2\xac&Q3R\x8c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\n\x91\u007f;\\\xb0\xb8\x83\x04\u007f\u0676Y=\xbc\xd5W\xf4S\xb9\x8965\u026d\xc5\u07a0\x00\x00\u07d4\n\x93\x1bD\x9e\xa8\xf1,\xdb\xd5\xe2\xc8\xccv\xba\xd2\xc2|\x069\x89\x01?\x9e\x8cy\xfe\x05\x80\x00\u0794\n\x98\x04\x13x\x03\xbahh\xd9:U\xf9\x98_\xcdT\x04Q\u4239\x8b\xc8)\xa6\xf9\x00\x00\u07d4\n\x9a\xb2c\x8b\x1c\xfdeM%\u06b0\x18\xa0\xae\xbd\u07c5\xfdU\x89\x01.\x8c\xb5\xfeLJ\x80\x00\u07d4\n\xb3f\xe6\xe7\u056b\xbc\xe6\xb4JC\x8di\xa1\u02bb\x90\xd13\x89\x11X\xe4`\x91=\x00\x00\x00\u07d4\n\xb4(\x1e\xbb1\x85\x90\xab\xb8\x9a\x81\xdf\a\xfa:\xf9\x04%\x8a\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u0794\n\xb5\x9d9\a\x02\xc9\xc0Y\xdb\x14\x8e\xb4\xf3\xfc\xfa}\x04\xc7\xe7\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94\n\xbf\xb3\x9b\x11HmyW(f\x19[\xa2lc\vg\x84\u06ca\x19\xba\x877\xf9i(\xf0\x00\x00\u07d4\n\u029aV&\x91;\b\xcf\u0266m@P\x8d\xceR\xb6\x0f\x87\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\n\xd3\xe4M<\x00\x1f\xa2\x90\xb3\x93ap0TA\b\xacn\xb9\x89j\xbd\xa0\xbc0\xb2\u07c0\x00\u07d4\n\xec.Bn\xd6\xcc\f\xf3\xc2I\xc1\x89~\xacG\xa7\xfa\xa9\xbd\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\n\xf6_\x14xNU\xa6\xf9Vg\xfds%*\x1c\x94\a-*\x89\nv;\x8e\x02\xd4O\x80\x00\u07d4\n\xf6\xc8\xd59\xc9mP%\x9e\x1b\xa6q\x9e\x9c\x80`\xf3\x88\u008965\u026d\xc5\u07a0\x00\x00\u07d4\v\x069\x0f$7\xb2\x0e\u0123\xd3C\x1b2y\xc6X>^\u05c9\n\x84Jt$\xd9\xc8\x00\x00\u07d4\v\v8b\x11*\xee\u00e04\x92\xb1\xb0_D\x0e\xcaT%n\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\v\x0e\x05[(\xcb\xd0=\xc5\xffD\xaad\xf3\xdc\xe0O^c\xfb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v\x11\x9d\xf9\x9ck\x8d\xe5\x8a\x1e,?)zgD\xbfU\"w\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v\x14\x89\x19\x99\xa6\\\x9e\xf73\b\xef\xe3\x10\f\xa1\xb2\x0e\x81\x92\x89+^:\xf1k\x18\x80\x00\x00\u07d4\v!\x13PE4d*\x1d\xaf\x10.\xee\x10\xb9\xeb\xdev\xe2a\x89\x94,\xdd|\x95\xf2\xbd\x80\x00\xe0\x94\v(\x8aZ\x8bu\xf3\xdcA\x91\xeb\x04W\xe1\xc8=\xbd M%\x8a\x01\a\x14\xe7{\xb4:\xb4\x00\x00\u07d4\v6\x9e\x00.\x1bLy\x13\xfc\xf0\x0f-^\x19\u0141eG\x8f\x89\x03\u007fe\x16(\x8c4\x00\x00\u07d4\vC\xbd#\x91\x02U\x81\u0615l\xe4*\a%y\u02ff\xcb\x14\x89\x01\x04\xe7\x04d\xb1X\x00\x00\u07d4\vP|\xf5SV\x8d\xaa\xf6U\x04\xaeN\xaa\x17\xa8\xea<\xdb\xf5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v]f\xb1<\x87\xb3\x92\xe9M\x91\xd5\xf7l\rE\nU(C\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v^ \x11\xeb\xc2Z\x00\u007f!6)`I\x8a\xfb\x8a\xf2\x80\xfb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\vd\x9d\xa3\xb9j\x10,\xdcm\xb6R\xa0\xc0}e\xb1\xe4C\xe6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\vi \xa6K6;\x8d]\x90\x80$\x94\xcfVKT|C\r\x89A\rXj \xa4\xc0\x00\x00\u07d4\vp\x11\x01\xa4\x10\x9f\x9c\xb3`\xdcW\xb7tBg=^Y\x83\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\vq\xf5T\x12$i\uf5ce/\x1f\xef\xd7\u02f4\x10\x98'r\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\xe0\x94\v{\xb3B\xf0\x1b\u0248\x8ej\x9a\xf4\xa8\x87\xcb\xf4\xc2\xdd,\xaf\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\v}3\x93q\xe5\xbeg'\xe6\xe31\xb5\x82\x1f\xa2K\u06ddZ\x89.\u007f\x81\x86\x82b\x01\x00\x00\u07d4\v\u007f\xc9\xdd\xf7\x05v\xf63\x06i\xea\xaaq\xb6\xa81\xe9\x95(\x89\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4\v\x80\xfcp(,\xbd\xd5\xfd\xe3[\xf7\x89\x84\xdb;\xdb\x12\x01\x88\x8968\x02\x1c\xec\u06b0\x00\x00\u07d4\v\x92M\xf0\a\xe9\xc0\x87\x84\x17\xcf\xe6;\x97n\xa1\xa3\x82\xa8\x97\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\v\x93\xfc\xa4\xa4\xf0\x9c\xac \xdb`\xe0e\xed\xcc\xcc\x11\u0976\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\v\x9d\xf8\x0f\xbe# \t\xda\xcf\n\xa8\xca\u0153v\xe2Gb\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v\xa6\xe4j\xf2Z\x13\xf5qi%Z4\xa4\xda\xc7\xce\x12\xbe\x04\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\v\xa8p[\xf5\\\xf2\x19\xc0\x95k^?\xc0\x1cDt\xa6\xcd\xc1\x89\x05%\xe0Y]Mk\x80\x00\u07d4\v\xafn\u0379\x1a\xcb6\x06\xa85|\v\xc4\xf4\\\xfd-~o\x8965\u026d\xc5\u07a0\x00\x00\u07d4\v\xb0_r$\xbbX\x04\x85eV\xc0~\xea\xdb\ud1fa\x8f|\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\v\xb0\xc1&\x82\xa2\xf1\\\x9bWA\xb28\\\xbeA\xf04\x06\x8e\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\v\xb2\\\xa7\u0448\xe7\x1eMi={\x17\a\x17\xd6\xf8\xf0\xa7\n\x89\x12C\x02\xa8/\xad\xd7\x00\x00\u07d4\v\xb2e\x0e\xa0\x1a\xcau[\xc0\xc0\x17\xb6K\x1a\xb5\xa6m\x82\xe3\x89Hz\x9a0E9D\x00\x00\u07d4\v\xb5Lr\xfdf\x10\xbf\xa463\x97\xe0 8K\x02+\fI\x89Hz\x9a0E9D\x00\x00\u07d4\v\xb7\x16\n\xba)7b\xf8sO>\x03&\xff\u0264\xca\xc1\x90\x8965\u026d\xc5\u07a0\x00\x00\u07d4\v\xc9\\\xb3-\xbbWL\x83/\xa8\x17J\x815m8\xbc\x92\xac\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v\xd6}\xbd\xe0z\x85n\xbd\x89;^\xdcO:[\xe4 &\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v\xdb\xc5L\u023d\xbb\xb4\x02\xa0\x89\x11\xe2#*T`\u0386k\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\v\xddX\xb9n|\x91m\xd2\xfb05o*\xeb\xfa\xaf\x1d\x860\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\v\u1f39\x03C\xfa\xe501s\xf4a\xbd\x91JH9\x05l\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\v\xe1\xfd\xf6&\xeea\x89\x10-p\xd1;1\x01,\x95\xcd\x1c\u0589lk\x93[\x8b\xbd@\x00\x00\u07d4\v\xe2\xb9J\xd9P\xa2\xa6&@\xc3[\xfc\xcdlg\xda\xe4P\xf6\x89i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94\v\u681eC\a\xfeH\xd4\x12\xb8\u0461\xa8(M\xceHba\x8a\x04\x0f\xbf\xf8\\\x0180\x00\x00\u07d4\v\xef\xb5G\a\xf6\x1b,\x9f\xb0G\x15\xab\x02n\x1b\xb7 B\xbd\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\v\xf0dB\x8f\x83bg\"\xa7\xb5\xb2j\x9a\xb2\x04!\xa7r>\x89\a?u\u0460\x85\xba\x00\x00\u07d4\v\xfb\xb6\x92]\xc7^R\xcf&\x84\"K\xbe\x05P\xfe\xa6\x85\u04c9j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\f\b\x80\x06\xc6K0\xc4\u076f\xbc6\xcb_\x05F\x9e\xb6(4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\f s\xbaD\xd3\u077d\xb69\xc0N\x19\x109\xa7\x17\x16#\u007f\x89M\x85<\x8f\x89\b\x98\x00\x00\xe0\x94\f\",|A\u0270H\xef\xcc\xe0\xa22CCb\xe1-g;\x8a\x02\x1e\x83Yivw8\x00\x00\xe0\x94\f(\b\xb9Q\ud787-{2y\x0f\xccY\x94\xaeA\xff\u070a\x15\x99n[<\u05b3\xc0\x00\x00\u07d4\f(\x84~O\t\xdf\xce_\x9b%\xaf|NS\x0fY\u0200\xfe\x8965\u026d\xc5\u07a0\x00\x00\u07d4\f-\\\x92\x058\xe9S\u02af$\xf0s\u007fUL\u0192wB\x8965\u026d\xc5\u07a0\x00\x00\u07d4\f0\xca\xcc?r&\x9f\x8bO\x04\xcf\a=+\x05\xa8=\x9a\u0449lyt\x12?d\xa4\x00\x00\u07d4\f29\xe2\xe8A$-\xb9\x89\xa6\x15\x18\xc2\"G\xe8\xc5R\b\x89\x0eJ\xf6G\x174d\x00\x00\xe0\x94\fH\r\xe9\xf7F\x10\x02\x90\x8bI\xf6\x0f\xc6\x1e+b\xd3\x14\v\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\fH\xaeb\xd1S\x97\x88\xeb\xa0\x13\xd7^\xa6\vd\xee\xbaN\x80\x89w\xfb\xdcC\xe00\x99\x80\x00\u07d4\fU\x89\xa7\xa8\x9b\x9a\xd1[\x02u\x190AYH\xa8u\xfb\xef\x89\x06\u0519\xeclc8\x00\x00\u07d4\fg\x03=\xd8\xee\u007f\f\x8a\xe54\xd4*Q\xf7\xd9\xd4\xf7\x97\x8f\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\fhE\xbfA\xd5\xee'<>\u6d70\u059fo\xd5\xea\xbb\xf7\x89\xa2\xa1\xb9h.X\t\x00\x00\xe0\x94\f\u007f\x86\x9f\x8e\x90\xd5?\xdc\x03\u8c81\x9b\x01k\x9d\x18\xeb&\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\f\x86\x92\xee\xff*S\xd6\xd1h\x8e\xd5j\x9d\u06fdh\u06bb\xa1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\f\x8ff\xc6\x01{\xce[ 4r\x04\xb6\x02\xb7C\xba\u05cd`\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\f\x8f\xd7w^T\xa6\xd9\u0263\xbf\x89\x0ev\x1fewi?\xf0\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\f\x92Z\xd5\xeb5,\x8e\xf7m\f\"-\x11[\a\x91\xb9b\xa1\x89\xacc]\u007f\xa3N0\x00\x00\u07d4\f\x96~0a\xb8zu>\x84P~\xb6\t\x86x,\x8f0\x13\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\f\xa1*\xb0\xb9fl\xf0\xce\xc6g\x1a\x15)/&SGj\xb2\x8a,x'\xc4-\"\xd0|\x00\x00\u07d4\f\xa6p\xeb,\x8b\x96\u02e3y!\u007fY)\u00b8\x92\xf3\x9e\xf6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\f\xae\x10\x8em\xb9\x9b\x9ecxv\xb0d\xc60>\u068ae\u0209\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\f\xbd\x92\x1d\xbe\x12\x15c\xb9\x8ahq\xfe\xcb\x14\xf1\xcc~\x88\u05c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\f\xbf\x87p\xf0\xd1\b.\\ \u016e\xad4\xe5\xfc\xa9\xaez\xe2\x8965\u026d\xc5\u07a0\x00\x00\u07d4\f\xc6\u007f\x82s\xe1\xba\xe0\x86\u007f\xd4.\x8b\x81\x93\xd7&y\xdb\xf8\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\f\u05a1A\x91\x8d\x12k\x10m\x9f.\xbfi\xe1\x02\xdeM2w\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\f\xda\x12\xbfr\xd4a\xbb\xc4y\xeb\x92\xe6I\x1d\x05~kZ\u044a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\f\u0716\v\x99\x8c\x14\x19\x98\x16\r\xc1y\xb3l\x15\u0484p\xed\x89\x1b\x1bk\u05efd\xc7\x00\x00\xe0\x94\f\xfb\x17#5\xb1l\x87\xd5\x19\xcd\x14uS\r W\u007f^\x0e\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\r\x1f*Wq>\xbcn\x94\xde)\x84n\x88D\xd3vfWc\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\r2e\xd3\u7f79=^\x8e\x8b\x1c\xa4\u007f!\ny>\u030e\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\r5@\x8f\"ef\x11o\xb8\xac\u06a9\xe2\xc9\u055bvh?\x892\xf5\x1e\u06ea\xa30\x00\x00\u07d4\rU\x1e\xc1\xa2\x13<\x98\x1d_\u01a8\xc8\x17?\x9e|OG\xaf\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\r]\x98V\\d|\xa5\xf1w\xa2\xad\xb9\xd3\x02/\xac(\u007f!\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\re\x80\x14\xa1\x99\x06\x1c\xf6\xb3\x943\x14\x03\x03\xc2\x0f\xfdNZ\x8a\x01\xbc\x85\xdc*\x89\xbb \x00\x00\u07d4\rg\x87\x06\xd07\x18\u007f>\"\xe6\xf6\x9b\x99\xa5\x92\xd1\x1e\xbcY\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\ri\x10\f9\\\xe6\xc5\xea\xad\xf9]\x05\xd8r\x83~\xde\xdd!\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\rt~\u559b\xf7\x9dW8\x1do\xe3\xa2@l\xd0\xd8\xce'\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\r\x80#\x92\x9d\x91r4\xae@Q+\x1a\xab\xb5\xe8\xa4Q'q\x89\b\x05\xe9\x9f\xdc\xc5\xd0\x00\x00\xe0\x94\r\x8a\xab\x8ft\xea\x86,\xdfvh\x05\x00\x9d?>B\xd8\xd0\v\x8a\x01;\x80\xb9\x9cQ\x85p\x00\x00\u07d4\r\x8c@\xa7\x9e\x18\x99O\xf9\x9e\xc2Q\xee\x10\u0408\u00d1.\x80\x89\x066d\xfc\u04bb\xc4\x00\x00\u07d4\r\x8e\xd7\xd0\xd1V83\x0e\xd7\xe4\xea\u032b\x8aE\x8dus~\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\r\x92X/\u06e0^\xab\xc3\xe5\x158\xc5m\xb8\x817\x85\xb3(\x89\nZ\xa8P\t\xe3\x9c\x00\x00\u07d4\r\x94C\xa7\x94h\xa5\xbb\xf7\xc1\xe5\xb9\x15\xb3d\x87\xf9\x16\x1f\x19\x84m\x10\x1431\x8a\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\r\xbdA|7+\x8b\r\x01\xbc\xd9Dpk\xd3.`\xae(\u0449\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\r\xc1\x00\xb1\a\x01\x1c\u007f\xc0\xa13\x96\x12\xa1l\xce\xc3(R\b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\r\u03dd\x8c\x98\x04E\x9fd|\x14\x13\x8e\xd5\x0f\xadV;AT\x89\t`\xdbwh\x1e\x94\x00\x00\u07d4\r\xcf\xe87\xea\x1c\xf2\x8ce\xfc\xce\u00fe\xf1\xf8NY\xd1P\xc0\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\r\xd4\xe6t\xbb\xad\xb1\xb0\u0702D\x98q=\xce;QV\xda)\x89\t79SM(h\x00\x00\u07d4\r\xfb\u0501pP\xd9\x1d\x9db\\\x02\x05<\xf6\x1a>\xe2\x85r\x89\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\x0e\x02N\u007f\x02\x9cj\xaf:\x8b\x91\x0f^\b\bs\xb8W\x95\xaa\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x0e\tdl\x99\xafC\x8e\x99\xfa'L\xb2\xf9\xc8V\xcbe\xf76\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x0e\f\x9d\x00^\xa0\x16\u0095\xcdy\\\xc9!>\x87\xfe\xbc3\xeb\x89\n\xbb\xcdN\xf3wX\x00\x00\u07d4\x0e\rf3\xdb\x1e\f\u007f#Jm\xf1c\xa1\x0e\n\xb3\x9c \x0f\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x0e\x11\xd7z\x89w\xfa\xc3\r&\x84E\xe51\x14\x9b1T\x1a$\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x0e\x12=}\xa6\xd1\xe6\xfa\xc2\u072d\xd2p)$\v\xb3\x90R\xfe\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x0e\x18\x01\xe7\vbb\x86\x1b\x114\u033c9\x1fV\x8a\xfc\x92\xf7\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x0e \x94\xac\x16T\xa4k\xa1\xc4\u04e4\v\xb8\xc1}\xa7\U000d6209\x13h?\u007f<\x15\xd8\x00\x00\u07d4\x0e!\xaf\x1b\x8d\xbf'\xfc\xf6?7\xe0G\xb8z\x82\\\xbe|'\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x0e.PJ-\x11\"\xb5\xa9\xfe\xee\\\xb1E\x1b\xf4\u00ac\xe8{\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x0e/\x8e(\xa6\x81\xf7|X;\xd0\xec\xde\x16cK\xdd~\x00\u0349\x05'8\xf6Y\xbc\xa2\x00\x00\u07d4\x0e2\x02\x19\x83\x8e\x85\x9b/\x9f\x18\xb7.=@s\xcaP\xb3}\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x0e3\xfc\xbb\xc0\x03Q\v\xe3W\x85\xb5*\x9c]!k\xc0\x05\xf4\x89e\xea=\xb7UF`\x00\x00\u07d4\x0e6\x96\xcf\x1fB\x17\xb1c\u047c\x12\xa5\xeas\x0f\x1c2\xa1J\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x0e9\x0fD\x05=\xdf\xce\xf0\xd6\b\xb3^M\x9c,\xbe\x98q\xbb\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x0e:(\xc1\u07ef\xb0P[\xdc\xe1\x9f\xe0%\xf5\x06\xa6\xd0\x1c\xeb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x0e=\xd7\xd4\xe4)\xfe90\xa6A@5\xf5+\xdcY\x9dxM\x89\x02,\xa3X|\xf4\xeb\x00\x00\u07d4\x0eGey\x03Rek\xc6Vh,$\xfc^\xf3\xe7j#\u01c9\x02\x86\xd7\xfc\f\xb4\xf5\x00\x00\u07d4\x0eI\x88\x00Dqw\xb8\u022f\xc3\xfd\xfa\u007fi\xf4\x05\x1b\xb6)\x89t\x05\xb6\x9b\x8d\xe5a\x00\x00\u07d4\x0ek\xaa\xa3\u07b9\x89\xf2\x89b\x00vf\x86\x18\xe9\xac3(e\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x0el\xd6d\xad\x9c\x1e\xd6K\xf9\x87I\xf4\x06D\xb6&\xe3y,\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\xe0\x94\x0em\xfdU;.\x87=*\xec\x15\xbd_\xbb?\x84r\xd8\u04d4\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\x0en\xc3\x137bq\xdf\xf5T#\xabT\"\xcc:\x8b\x06\xb2+\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x0en\u0399\x11\x1c\xad\x19a\xc7H\xed=\xf5\x1e\xddi\u04a3\xb1\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\x0e\x83\xb8PH\x1a\xb4MI\xe0\xa2)\xa2\xe4d\x90,iS\x9b\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x0e\x89\xed\xdd?\xa0\xd7\x1d\x8a\xb0\xff\x8d\xa5X\x06\x86\xe3\xd4\xf7O\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x0e\x90\x96\xd3C\xc0`\xdbX\x1a\x12\x01\x12\xb2x`~\xc6\xe5+\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x0e\x9cQ\x18d\xa1w\xf4\x9b\xe7\x82\x02w?`H\x9f\xe0NR\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\x0e\xa2\xa2\x101+>\x86~\xe0\xd1\xcch,\xe1\xd6f\xf1\x8e\u054a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x0e\xb1\x89\xef,-Wb\xa9c\u05b7\xbd\xf9i\x8e\xa8\u7d0a\x89Hz\x9a0E9D\x00\x00\xe0\x94\x0e\xb5\xb6b\xa1\xc7\x18`\x8f\xd5/\f%\xf97\x880\x17\x85\x19\x8a\x01J7(\x1aa.t\x00\x00\xe0\x94\x0e\xc4f\x96\xff\xac\x1fX\x00_\xa8C\x98$\xf0\x8e\xed\x1d\xf8\x9b\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x0e\xc5\n\xa8#\xf4e\xb9FK\v\xc0\u0125w$\xa5U\xf5\u058a\f\x83\xd1Bj\u01f1\xf0\x00\x00\u07d4\x0e\xc50\x8b1(.!\x8f\xc9\xe7Y\xd4\xfe\xc5\xdb7\b\xce\u01096C\xaady\x86\x04\x00\x00\u07d4\x0e\xcc\xf6\x17\x84O\xd6\x1f\xbab\xcb\x0eD[z\u018b\xcc\x1f\xbe\x89\x14\xfeO\xe65e\xc6\x00\x00\u07d4\x0e\u04fb:N\xb5T\xcf\u0297\x94}WU\a\xcd\xfdm!\u0609\x1d\xb3 _\xcc#\u0540\x00\u07d4\x0e\xd7l,;]P\xff\x8f\xb5\v>\xea\xcdh\x15\x90\xbe\x1c-\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x0e\u0680\xf4\xed\aJ\xeaiz\xed\xdf(;c\xdb\xca=\xc4\u0689lk\x93[\x8b\xbd@\x00\x00\u07d4\x0e\xddKX\x0f\xf1\x0f\xe0lJ\x03\x11b9\xef\x96b+\xae5\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\x0e\xe3\x91\xf0^\u038a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x0f\x92\x9c\xf8\x95\xdb\x01z\xf7\x9f>\xad\"\x16\xb1\xbdi\xc3}\u01c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x0f\xa0\x10\xce\fs\x1d;b\x8e6\xb9\x1fW\x13\x00\u477e\xab\x8963\x03\"\xd5#\x8c\x00\x00\u07d4\x0f\xa5\xd8\u0173\xf2\x94\xef\u0515\xabi\xd7h\xf8\x18rP\x85H\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x0f\xa6\u01f0\x97=\v\xae)@T\x0e$}6'\xe3|\xa3G\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x0f\xad\x05P|\u070f$\xb2\xbeL\xb7\xfa]\x92}\u06d1\x1b\x88\x89\xa2\xdf\x13\xf4A\xf0\t\x80\x00\u07d4\x0f\xb5\xd2\xc6s\xbf\xb1\xdd\xca\x14\x1b\x98\x94\xfdm?\x05\xdag \x89\x05k\xc7^-c\x10\x00\x00\u07d4\x0f\u0260\xe3AE\xfb\xfd\xd2\xc9\u04a4\x99\xb6\x17\u05e0)i\xb9\x89\t\xc2\x00vQ\xb2P\x00\x00\xe0\x94\x0f\xcf\xc4\x06P\b\xcf\xd3#0_b\x86\xb5zM\xd7\xee\xe2;\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x0f\xdde@#\x95\u07db\u045f\xeeE\a\xefSE\xf7E\x10L\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x0f\xecN\xe0\xd7\xca\x18\x02\x90\xb6\xbd \xf9\x99#B\xf6\x0f\xf6\x8d\x89\x12 \u007f\x0e\xdc\xe9q\x80\x00\u07d4\x0f\ue06c3\x1e\xfd\x8f\x81\x16\x1cW8+\xb4P{\xb9\xeb\xec\x89\x15\xaf\x88\r\x8c\u06c3\x00\x00\u07d4\x0f\xfe\xa0mq\x13\xfbj\xec(i\xf4\xa9\u07f0\x90\a\xfa\xce\xf4\x89\f8F\x81\xb1\xe1t\x00\x00\u07d4\x10\tq\x98\xb4\xe7\xee\x91\xff\x82\xcc/;\xd9_\xeds\xc5@\xc0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x10\vM\tw\xfc\xba\xd4\u07bd^d\xa0Iz\xea\xe5\x16\x8f\xab\x89\x11\f\x90s\xb5$Z\x00\x00\xe0\x94\x10\x1a\nd\xf9\xaf\xccD\x8a\x8a\x13\rM\xfc\xbe\xe8\x957\xd8T\x8a\x037\xfe_\xea\xf2\u0440\x00\x00\u07d4\x10,G}i\xaa\u06e9\xa0\xb0\xf6+tY\xe1\u007f\xbb\x1c\x15a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x101\xe0\xec\xb5I\x85\xae!\xaf\x17\x93\x95\r\xc8\x11\x88\x8f\xde|\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x104d\x14\xbe\xc6\xd3\xdc\xc4NP\xe5MT\u00b8\xc3sN>\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x108\x98X\xb8\x00\xe8\xc0\xec2\xf5\x1e\xd6\x1a5YF\xcc@\x9b\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x10Y\xcb\xc6>6\xc4>\x88\xf3\x00\b\xac\xa7\xce\x05\x8e\ua816\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\x10n\xd5\xc7\x19\xb5&\x14w\x89\x04%\xaeuQ\xdcY\xbd%\\\x8a\x02\x89jX\xc9[\xe5\x88\x00\x00\u07d4\x10q\x1c=\xda21x\x85\xf0\xa2\xfd\x8a\xe9.\x82\x06\x9b\r\v\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x10sy\xd4\xc4gFO#[\xc1\x8eU\x93\x8a\xad>h\x8a\u05c9\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94\x10v!-Ou\x8c\x8e\xc7\x12\x1c\x1c}t%I&E\x92\x84\x8a\ai[Y\xb5\xc1{L\x00\x00\u07d4\x10x\xd7\xf6\x1b\x0eV\xc7N\xe6c[.\x18\x19\xef\x1e=\x87\x85\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x10z\x03\xcf\bB\xdb\u07b0a\x8f\xb5\x87\xcai\x18\x9e\xc9/\xf5\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x10\x80\xc1\xd85\x8a\x15\xbc\x84\xda\xc8%\x89\u0392\xb9\x81\x89t\xc1\xfa\xb8\xad\xb4T\x00\x00\u07d4\x10\xe1\xe37x\x85\xc4-}\xf2\x18R.\xe7vh\x87\xc0^j\x89\x10C\xc4<\xde\x1d9\x80\x00\u07d4\x10\u342d+\xa3=\x82\xb3s\x88\u041cED\u01b0\"]\xe5\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x10\xf4\xbf\xf0\u02a5\x02|\nj-\xcf\xc9R\x82M\xe2\x94\t\t\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x11\x00\x1b\x89\xed\x87>:\xae\xc1\x15V4\xb4h\x16C\x98c#\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x11\x027\u03d1\x17\xe7g\x92/\u0121\xb7\x8dyd\u0682\xdf \x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x11\x11\xe5\xdb\xf4^o\x90mb\x86o\x17\b\x10\x17\x88\xdd\xd5q\x89F{\xe6S>\xc2\xe4\x00\x00\xe0\x94\x11\x17+'\x8d\xddD\xee\xa2\xfd\xf4\xcb\x1d\x16\x96#\x91\xc4S\u064a\xc6/=\x9b\xfdH\x95\xf0\x00\x00\u07d4\x11&4\xb4\xec0\xffxn\x02AY\xf7\x96\xa5y9\xea\x14N\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x110l}WX\x867x\x0f\xc9\xfd\xe8\xe9\x8e\xcb\x00\x8f\x01d\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\x116\x12\xbc;\xa0\xeeH\x98\xb4\x9d\xd2\x023\x90_/E\x8fb\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\x11A_\xaba\xe0\xdf\u0539\x06v\x14\x1aUz\x86\x9b\xa0\xbd\xe9\x89o\x05\xb5\x9d; \x00\x00\x00\u07d4\x11L\xbb\xbfo\xb5*\xc4\x14\xbe~\xc6\x1f{\xb7\x14\x95\xce\x1d\xfa\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x11L\xfe\xfeP\x17\r\xd9z\xe0\x8f\nDTIx\u0159T\x8d\x89.\u0207\xe7\xa1J\x1c\x00\x00\u07d4\x11a\b\xc1 \x84a.\xed\xa7\xa9=\xdc\xf8\xd2`.'\x9e\\\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x11d\u02aa\x8c\u0157z\xfe\x1f\xad\x8a}`(\xce-W)\x9b\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x11gZ%UF\a\xa3\xb6\xc9*\x9e\xe8\xf3ou\xed\xd3\xe36\x89\b\xa9\xab\xa5W\xe3l\x00\x00\u07d4\x11j\t\xdff\xcb\x15\x0e\x97W\x8e)\u007f\xb0n\x13\x04\f\x89<\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x11o\xef^`\x16B\xc9\x18\u02c9\x16\x0f\xc2);\xa7\x1d\xa96\x89+|\xc2\xe9\xc3\"\\\x00\x00\u07d4\x11xP\x1f\xf9J\xdd\x1cX\x81\xfe\x88a6\xf6\xdf\xdb\xe6\x1a\x94\x89\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4\x11y\xc6\r\xbd\x06\x8b\x15\v\aM\xa4\xbe#\x03; \u0185X\x89$\xdc\xe5M4\xa1\xa0\x00\x00\u07d4\x11}\x9a\xa3\xc4\xd1;\xee\x12\xc7P\x0f\t\xf5\xdd\x1cf\xc4e\x04\x89\v*\xd3\x04\x90\xb2x\x00\x00\xe0\x94\x11}\xb867\u007f\xe1TU\xe0,.\xbd\xa4\v\x1c\xebU\x1b\x19\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\x11\x8c\x18\xb2\xdc\xe1p\xe8\xf4Eu;\xa5\xd7Q<\xb7cm-\x8a\x01\xdd\f\x88_\x9a\r\x80\x00\x00\u07d4\x11\x8f\xbdu;\x97\x929Z\xefzMx\xd2c\xcd\u02ab\xd4\xf7\x8963\x03\"\xd5#\x8c\x00\x00\xe0\x94\x11\x92\x83x\xd2}U\xc5 \xce\xed\xf2L\xeb\x1e\x82-\x89\r\xf0\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x11\x9a\xa6M[}\x18\x1d\xae\x9d<\xb4I\x95\\\x89\xc1\xf9c\xfa\x89%\xf2s\x93=\xb5p\x00\x00\xe0\x94\x11\xc05\x8a\xa6G\x9d\xe2\x18f\xfe!\a\x19$\xb6^p\xf8\xb9\x8a\a\xb5?y\xe8\x88\xda\xc0\x00\x00\xe0\x94\x11\xd2$z\"\x1ep\xc2\xd6m\x17\xee\x13\x8d8\xc5_\xfb\x86@\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x11\u05c4JG\x1e\xf8\x9a\x8d\x87uUX<\xee\xbd\x149\xea&\x8a\x02#i\u6e80\u0188\x00\x00\u07d4\x11\xdda\x85\u0668\xd7=\xdf\u06a7\x1e\x9bwtC\x1cM\xfe\u008965\u026d\xc5\u07a0\x00\x00\u07d4\x11\xe7\x99~\u0750E\x03\xd7}\xa6\x03\x8a\xb0\xa4\xc84\xbb\xd5c\x89\x15\b\x94\xe8I\xb3\x90\x00\x00\u07d4\x11\xec\x00\xf8I\xb61\x9c\xf5\x1a\xa8\u074ff\xb3U)\xc0\xbew\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x11\ufe22\x04Q\x16\x1bdJ\x8c\u03bb\xc1\xd3C\xa3\xbb\xcbR\x89\xadx\xeb\u016cb\x00\x00\x00\xe0\x94\x11\xfe\xfb]\xc1\xa4Y\x8a\xa7\x12d\fQwu\u07e1\xd9\x1f\x8c\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x12\x0f\x9d\xe6\xe0\xaf~\xc0*\a\xc6\t\u0284G\xf1W\xe64L\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4\x12\x10\xf8\v\u06c2l\x17Tb\xab\a\x16\xe6\x9eF\xc2J\xd0v\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x12\x13N\u007fk\x01{\xf4\x8e\x85Z9\x9c\xa5\x8e.\x89/\xa5\u020965\u026d\xc5\u07a0\x00\x00\u07d4\x12\x170t\x98\x01S\xae\xaaK\r\xcb\xc7\x13.\xad\xce\xc2\x1bd\x89\r\x02\xabHl\xed\xc0\x00\x00\u07d4\x12\x1f\x85[p\x14\x9a\xc84s\xb9po\xb4MG\x82\x8b\x98;\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\x12'\xe1\nM\xbf\x9c\xac\xa3\x1b\x17\x80#\x9fUv\x15\xfc5\xc1\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x12-\xcf\xd8\x1a\u0779}\x1a\x0eI%\u0135I\x80n\x9f;\xeb\x89R 5\xccn\x01!\x00\x00\u07d4\x12/V\x12%I\xd1h\xa5\xc5\xe2g\xf5&b\xe5\xc5\xcc\xe5\u0209\n\ad\a\xd3\xf7D\x00\x00\xe0\x94\x121o\xc7\xf1x\xea\xc2.\xb2\xb2Z\xed\xea\xdf=u\xd0\x01w\x8a\x04<3\xbe\x05\xf6\xbf\xb9\x80\x00\xe0\x94\x127Y\xf33\xe1>0i\xe2\x03KO\x059\x89\x18\x11\x9d6\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x12\\\xc5\xe4\xd5k+\xcc.\xe1\xc7\t\xfb\x9eh\xfb\x17t@\xbd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x12c#\x88\xb2v^\xe4E+P\x16\x1d\x1f\xff\xd9\x1a\xb8\x1fJ\x89(\x1d\x90\x1fO\xdd\x10\x00\x00\u07d4\x12h\x97\xa3\x11\xa1J\xd4;x\xe0\x92\x01\x00\xc4Bk\xfdk\u07494\xc7&\x89?-\x94\x80\x00\u07d4\x12m\x91\xf7\xad\x86\u07bb\x05W\xc6\x12\xca'n\xb7\xf9m\x00\xa1\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x12}?\xc5\x00;\xf6<\r\x83\xe99W\x83e\x15\xfd'\x90E\x89\x06\x10\xc9\".nu\x00\x00\xe0\x94\x12}\xb1\xca\xdf\x1bw\x1c\xbdtu\xe1\xb2ri\x0fU\x8c\x85e\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\x12\x84\xf0\xce\xe9\xd2\xff)\x89\xb6Ut\xd0o\xfd\x9a\xb0\xf7\xb8\x05\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x12\x8b\x90\x8f\xe7C\xa44 =\xe2\x94\xc4A\xc7\xe2\n\x86\xeag\x89&\xab\x14\xe0\xc0\xe1<\x00\x00\xe0\x94\x12\x93\u01cc}jD;\x9dt\xb0\xba^\xe7\xbbG\xfdA\x85\x88\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\x12\x96\xac\xde\xd1\xe0c\xaf9\xfe\x8b\xa0\xb4\xb6=\xf7\x89\xf7\x05\x17\x89\x05k\xf9\x1b\x1ae\xeb\x00\x00\u07d4\x12\xaa}\x86\xdd\xfb\xad0\x16\x92\xfe\xac\x8a\b\xf8A\xcb!\\7\x89\amA\xc6$\x94\x84\x00\x00\xe0\x94\x12\xaf\xbc\xba\x14'\xa6\xa3\x9e{\xa4\x84\x9fz\xb1\xc45\x8a\xc3\x1b\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x12\xb5\xe2\x89E\xbb)i\xf9\xc6Lc\xcc\x05\xb6\xf1\xf8\xd6\xf4\u054a\x01\xa2\x9e\x86\x91;t\x05\x00\x00\u0794\x12\u03cb\x0eFR\x13!\x1a[S\u07f0\xdd'\x1a(,\x12\u0248\xd2\xf1?w\x89\xf0\x00\x00\u07d4\x12\xd2\a\x90\xb7\xd3\xdb\u060c\x81\xa2y\xb8\x12\x03\x9e\x8a`;\u0409V\xf9\x85\u04c6D\xb8\x00\x00\xe0\x94\x12\xd6\re\xb7\xd9\xfcH\x84\v\xe5\xf8\x91\xc7E\xcev\xeeP\x1e\x8a\x04\x85\xe58\x8d\fv\x84\x00\x00\u0794\x12\xd9\x1a\x92\xd7O\xc8a\xa7)dm\xb1\x92\xa1%\xb7\x9fSt\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94\x12\u992d*\xd5t\x84\xddp\x05e\xbd\xdbFB;\u067d1\x8a\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4\x12\xf3,\n\x1f-\xaa\xb6v\xfei\xab\xd9\xe0\x185-L\xcdE\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\x12\xf4`\xaedl\xd2x\x0f\xd3\\P\xa6\xafK\x9a\xcc\xfa\x85\u018965\u026d\xc5\u07a0\x00\x00\u07d4\x12\xff\xc1\x12\x86\x05\xcb\f\x13p\x9ar\x90Po&\x90\x97q\x93\x89\xb5\x0f\u03ef\xeb\xec\xb0\x00\x00\u07d4\x13\x03$F\xe7\xd6\x10\xaa\x00\xec\x8cV\u0275t\xd3l\xa1\xc0\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x13\x1cy,\x19}\x18\xbd\x04]p$\x93|\x1f\x84\xb6\x0fD8\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x13\x1d\xf8\xd30\xeb|\xc7\x14}\nUWo\x05\u078d&\xa8\xb7\x89\n1\x06+\xee\xedp\x00\x00\u07d4\x13\x1f\xae\xd1%a\xbbz\xee\x04\xe5\x18Z\xf8\x02\xb1\xc3C\x8d\x9b\x89\v\xdf\x0e\u0733\x90\xc9\xc8V\b\xb7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x13!\xcc\xf2\x979\xb9t\xe5\xa5\x16\xf1\x8f:\x846q\xe3\x96B\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x13'\xd7Y\xd5n\n\xb8z\xf3~\xcfc\xfe\x01\xf3\x10\xbe\x10\n\x89#\xbc<\xdbh\xa1\x80\x00\x00\u07d4\x13)\xdd\x19\xcdK\xaa\x9f\xc6C\x10\xef\xec\xea\xb2!\x17%\x1f\x12\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x137\x1f\x92\xa5n\xa88\x1eC\x05\x9a\x95\x12\x8b\xdcMC\u0166\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x13O\x15\xe1\xe3\x9cSCY0\xaa\xed\xf3\xe0\xfeV\xfd\xe8C\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x13Ac\xbe\x9f\xbb\xe1\xc5in\xe2U\xe9\v\x13%C\x95\xc3\x18\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x13\\\xec\xd9U\xe5y\x83pv\x920\x15\x93\x03\u0671\x83\x9ff\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x13]\x17\x19\xbf\x03\xe3\xf8f1$y\xfe3\x81\x18\xcd8~p\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x13^\xb8\xc0\xe9\xe1\x01\xde\xed\xec\x11\xf2\xec\xdbf\xae\x1a\xae\x88g\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x13`\xe8}\xf2Li\xeemQ\xc7nsv\u007f\xfe\x19\xa2\x13\x1c\x89\x04\xfc\xc1\xa8\x90'\xf0\x00\x00\u07d4\x13l\x83K\xf1\x112m s\x95)[.X>\xa7\xf35r\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x13mKf+\xbd\x10\x80\xcf\xe4D[\x0f\xa2\x13\x86D5\xb7\xf1\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x13oI\a\u02b4\x1e'\bK\x98E\x06\x9f\xf2\xfd\f\x9a\xdey\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x13t\xfa\xcd{?\x8dhd\x9d`\xd4U\x0e\xe6\x9f\xf0HA3\x89\x0e\x9e\xd6\xe1\x11r\xda\x00\x00\u07d4\x13|\xf3A\xe8Ql\x81X\x14\xeb\xcds\xe6V\x9a\xf1L\xf7\xbc\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x13\x84\x8bF\xeau\xbe\xb7\xea\xa8_Y\xd8f\xd7\u007f\xd2L\xf2\x1a\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\x13\x9d51\u0252*\xd5bi\xf60\x9a\xa7\x89\xfb$\x85\xf9\x8c\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x13\x9eG\x97d\xb4\x99\xd6f \x8cJ\x8a\x04z\x97\x041c\u0749 w!*\xffm\xf0\x00\x00\u07d4\x13\xa5\xee\xcb80]\xf9Iq\xef-\x9e\x17\x9a\xe6\u03ba\xb37\x89\x11\u3ac3\x95\xc6\xe8\x00\x00\u07d4\x13\xac\xad\xa8\x98\n\xff\xc7PI!\xbe\x84\xebID\xc8\xfb\xb2\xbd\x89V\u04aa:\\\t\xa0\x00\x00\u07d4\x13\xb9\xb1\a\x15qL\t\xcf\xd6\x10\u03dc\x98F\x05\x1c\xb1\xd5\x13\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x13\xce3-\xffe\xa6\xab\x938\x97X\x8a\xa2>\x00\t\x80\xfa\x82\x89\x0e\x02\x056\xf0(\xf0\x00\x00\u07d4\x13\xd6z~%\xf2\xb1,\u06c5XP\t\xf8\xac\u011b\x96s\x01\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x13\xde\xe0>7\x99\x95-\a8\x84=K\xe8\xfc\n\x80?\xb2\x0e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x13\xe0/\xb4H\xd6\xc8J\xe1}\xb3\x10\xad(m\x05a`\u0695\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x13\xe3!r\x8c\x9cWb\x80X\xe9?\xc8f\xa02\xdd\v\u0690\x89&\xbc\xca#\xfe.\xa2\x00\x00\u07d4\x13\xec\x81\"\x84\x02n@\x9b\xc0f\xdf\xeb\xf9\u0564\xa2\xbf\x80\x1e\x89WG=\x05\u06ba\xe8\x00\x00\xe0\x94\x14\x01)\xea\xa7f\xb5\xa2\x9f[:\xf2WND\t\xf8\xf6\xd3\xf1\x8a\x01Z\xf1\u05cbX\xc4\x00\x00\x00\u07d4\x14\x05\x18\xa3\x19K\xad\x13P\xb8\x94\x9ee\x05e\u07bem\xb3\x15\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x14\x06\x85M\x14\x9e\b\x1a\xc0\x9c\xb4\xcaV\r\xa4c\xf3\x120Y\x89Hz\x9a0E9D\x00\x00\u07d4\x14\f\xa2\x8f\xf3;\x9ff\xd7\xf1\xfc\x00x\xf8\xc1\xee\xf6\x9a\x1b\xc0\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\x14\x0f\xbaX\xdb\xc0H\x03\xd8L!0\xf0\x19x\xf9\xe0\xc71)\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\x14\x1a^9\xee/h\n`\x0f\xbfo\xa2\x97\u0790\xf3\"\\\u074a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x14%N\xa1&\xb5-\x01B\xda\n~\x18\x8c\xe2U\xd8\xc4qx\x89*\x03I\x19\u07ff\xbc\x00\x00\u07d4\x14+\x87\xc5\x04?\xfbZ\x91\xdf\x18\xc2\xe1\t\xce\xd6\xfeJq\u06c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x14\x87\xf5\xa5$\u0288Q^\x8a\x01\xab,\xf7\xc9\xf8~ \x00\x00\u07d4\x14\xa75 f6D\x04\xdbP\xf0\xd0\u05cduJ\"\x19\x8e\xf4\x89e\xea=\xb7UF`\x00\x00\u07d4\x14\xab\x16K;RL\x82\u05ab\xfb\xc0\u0783\x11&\xae\x8d\x13u\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x14\xb1`>\xc6+ \x02 3\xee\xc4\xd6\xd6eZ\xc2J\x01Z\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94\x14\xc6;\xa2\u0731\xddM\xf3=\u06b1\x1cO\x00\a\xfa\x96\xa6-\x8a\x03HA\xb6\x05z\xfa\xb0\x00\x00\xe0\x94\x14\xcd\u077c\x8b\t\xe6gZ\x9e\x9e\x05\t\x1c\xb9\"8\u00de\x1e\x8a\x01\x14x\xb7\xc3\n\xbc0\x00\x00\u07d4\x14\xd0\n\xad9\xa0\xa7\u045c\xa0SP\xf7\xb07'\xf0\x8d\xd8.\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x14\xee\xc0\x9b\xf0>5+\xd6\xff\x1b\x1e\x87k\xe6d\xce\xff\xd0\u03c9\x01\x16\xdc:\x89\x94\xb3\x00\x00\u07d4\x14\xf2!\x15\x95\x18x;\u0127\x06go\xc4\xf3\xc5\xee@X)\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x14\xfc\xd19\x1e}s/Avl\xda\u0344\xfa\x1d\xeb\x9f\xfd\u0489lk\x93[\x8b\xbd@\x00\x00\u07d4\x15\x0e=\xbc\xbc\xfc\x84\xcc\xf8\x9bsBwc\xa5e\xc2>`\u0409\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94\x15\x18b{\x885\x1f\xed\xe7\x96\xd3\xf3\b3d\xfb\u0508{\f\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u0794\x15\"J\xd1\xc0\xfa\xceF\xf9\xf5V\xe4wJ0%\xad\x06\xbdR\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\x15/+\xd2)\xdd\xf3\xcb\x0f\xda\xf4U\xc1\x83 \x9c\x0e\x1e9\xa2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x15/N\x86\x0e\xf3\xee\x80jP'w\xa1\xb8\xdb\xc9\x1a\x90vh\x89 \x86\xac5\x10R`\x00\x00\u07d4\x15<\b\xaa\x8b\x96\xa6\x11\xefc\xc0%>*C4\x82\x9eW\x9d\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\x15<\xf2\x84,\xb9\u0787l'o\xa6Gg\u0468\xec\xf5s\xbb\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x15>\xf5\x8a\x1e.z>\xb6\xb4Y\xa8\n\xb2\xa5G\xc9A\x82\xa2\x8a\x14T+\xa1*3|\x00\x00\x00\u07d4\x15DY\xfa/!1\x8e44D\x97\x89\xd8&\xcd\xc1W\f\xe5\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x15G\xb9\xbfz\xd6bt\xf3A8'#\x1b\xa4\x05\ue308\xc1\x8a\x03\xa9\u057a\xa4\xab\xf1\xd0\x00\x00\u07d4\x15H\xb7p\xa5\x11\x8e\u0787\u06e2\xf6\x903\u007fam\u60eb\x89\x1c\x99V\x85\u0fc7\x00\x00\u07d4\x15R\x83P\xe0\xd9g\n.\xa2\u007f{J3\xb9\xc0\xf9b\x1d!\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\x15[7y\xbbmV4./\u0681{[-\x81\xc7\xf4\x13'\x89\x02\xb8\xaa:\al\x9c\x00\x00\u07d4\x15e\xaf\x83~\xf3\xb0\xbdN+#V\x8dP#\xcd4\xb1d\x98\x89\x15Q\xe9rJ\u013a\x00\x00\u07d4\x15f\x91\x80\xde\u2558\x86\x9b\b\xa7!\xc7\xd2LL\x0e\xe6?\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x15r\xcd\xfa\xb7*\x01\u0396\x8ex\xf5\xb5D\x8d\xa2\x98S\xfb\u074a\x01\x12blI\x06\x0f\xa6\x00\x00\xe0\x94\x15uY\xad\xc5Wd\xccm\xf7\x93#\t%4\xe3\xd6dZf\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x15x\xbd\xbc7\x1bM$8E3\x05V\xff\xf2\xd5\xefM\xffg\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x15~\xb3\xd3\x11;\u04f5\x97qM:\x95N\xdd\x01\x89\x82\xa5\u02c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x15\x84\xa2\xc0f\xb7\xa4U\xdb\u05ae(\a\xa73N\x83\xc3_\xa5\x89\a\f\x1c\xc7;\x00\xc8\x00\x00\u07d4\x15\x87F\x86\xb6s=\x10\xd7\x03\xc9\xf9\xbe\xc6\xc5.\xb8b\x8dg\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x15\x8a\ra\x92S\xbfD2\xb5\xcd\x02\u01f8b\xf7\u00b7V6\x89\a[\xac|[\x12\x18\x80\x00\u07d4\x15\x98\x12y\x82\xf2\xf8\xad;k\x8f\xc3\xcf'\xbfax\x01\xba+\x89\t`\xdbwh\x1e\x94\x00\x00\xe0\x94\x15\x9a\xdc\xe2z\xa1\vG#d)\xa3JZ\xc4,\xad[d\x16\x8a\x06\xbf\x90\xa9n\xdb\xfaq\x80\x00\u07d4\x15\xa0\xae\xc3\u007f\xf9\xff=T\t\xf2\xa4\xf0\xc1!*\xac\xcb\x02\x96\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x15\xaaS\r\xc3iX\xb4\xed\xb3\x8e\xeem\xd9\xe3\xc7}L\x91E\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x15\xac\xb6\x15h\xecJ\xf7\xea(\x198a\x81\xb1\x16\xa6\xc5\xeep\x8a\x06\x90\x83l\n\xf5\xf5`\x00\x00\u07d4\x15\xb9o0\xc2;\x86d\xe7I\x06Q\x06k\x00\xc49\x1f\xbf\x84\x89\x16B\xe9\xdfHv)\x00\x00\u07d4\x15\xc7\xed\xb8\x11\x8e\xe2{4\"\x85\xebY&\xb4z\x85[\u01e5\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x15\u0654hPz\xa0A?\xb6\r\xca*\xdc\u007fV\x9c\xb3kT\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x15\u06f4\x8c\x980\x97d\xf9\x9c\xed6\x92\xdc\xca5\xee0k\xac\x8a\x1f\u00c4+\xd1\xf0q\xc0\x00\x00\xe0\x94\x15\u072f\xcc+\xac\xe7\xb5[T\xc0\x1a\x1cQF&\xbfa\xeb\u060a\x01\xfd\x934\x94\xaa_\xe0\x00\x00\u07d4\x15\u3d44\x05kb\xc9s\xcf^\xb0\x96\xf1s>T\xc1\\\x91\x892\xc7Z\x02#\xdd\xf3\x00\x00\u07d4\x15\xeb\xd1\xc7\xca\u04af\xf1\x92u\xc6W\xc4\xd8\b\xd0\x10\xef\xa0\xf5\x89\n\xdf0\xbap\u0217\x00\x00\u07d4\x15\xee\x0f\xc6>\xbf\x1b\x1f\u011d{\xb3\x8f\x88c\x82:.\x17\u0489g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x15\xf1\xb3R\x11\rh\x90\x1d\x8fg\xaa\xc4jl\xfa\xfe\x03\x14w\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x15\xf2\xb7\xb1d2\xeeP\xa5\xf5[A#/c4\xedX\xbd\xc0\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x16\x01\x9aM\xaf\xabC\xf4\u067fAc\xfa\xe0\x84}\x84\x8a\xfc\xa2\x89\x01[\xc7\x019\xf7J\x00\x00\u07d4\x16\x02&\xef\xe7\xb5:\x8a\xf4b\xd1\x17\xa0\x10\x80\x89\xbd\xec\xc2\u0449\n\xdf0\xbap\u0217\x00\x00\u07d4\x16\f\xebo\x98\x0e\x041_S\xc4\xfc\x98\x8b+\xf6\x9e(M}\x89\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\xe0\x94\x16\x1c\xafZ\x97*\u0383y\xa6\u0420J\xe6\xe1c\xfe!\xdf+\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\x16\x1d&\xefgY\xba[\x9f \xfd\xcdf\xf1a2\xc3RA^\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x16!\x10\xf2\x9e\xac_}\x02\xb5C\xd8\xdc\u057bY\xa5\xe3;s\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x16+\xa5\x03'b\x14\xb5\t\xf9u\x86\xbd\x84!\x10\xd1\x03\xd5\x17\x8a\x01\xe7\xff\u0609\\\"h\x00\x00\u07d4\x16-v\xc2\xe6QJ:\xfbo\xe3\xd3\u02d3\xa3\\Z\xe7\x83\xf1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x16;\xadJ\x12+E}d\xe8\x15\nA>\xaeM\a\x02>k\x89\x01\x04\xe7\x04d\xb1X\x00\x00\u07d4\x16<\u023e\"vF\xcb\tq\x91Y\xf2\x8e\u041c]\xc0\xdc\xe0\x89Hz\x9a0E9D\x00\x00\u07d4\x16=\xcas\xd7\xd6\xea?>`b2*\x874\x18\f\vx\uf25ft \x03\xcb}\xfc\x00\x00\u07d4\x16Mz\xac>\xec\xba\uc86dQ\x91\xb7S\xf1s\xfe\x12\xec3\x89(VR\xb8\xa4hi\x00\x00\u07d4\x16Rl\x9e\u07d4>\xfaOm\x0f\v\xae\x81\xe1\x8b1\xc5@y\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\x16S\x05\xb7\x872.%\xdcj\xd0\xce\xfelo3Fx\xd5i\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x16e\xab\x179\xd7\x11\x19\xeea2\xab\xbd\x92j'\x9f\xe6yH\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\x16k\xf6\u06b2-\x84\x1bHl8\xe7\xbaj\xb3:\x14\x87\ud30a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x16v\x99\xf4\x8ax\xc6\x15Q%\x15s\x99X\x993\x12WO\a\x89\x02\x1d;\xd5^\x80<\x00\x00\u07d4\x16x\xc5\xf2\xa5\"92%\x19ca\x89OS\xccu/\xe2\xf3\x89h\xf3e\xae\xa1\xe4@\x00\x00\u07d4\x16|\xe7\xdee\xe8G\bYZRT\x97\xa3\xeb^ZfPs\x89\x1f1Gsfo\xc4\x00\x00\u07d4\x16~>:\xe2\x003HE\x93\x92\xf7\xdf\xceD\xaf|!\xadY\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x16\x80\xce\xc5\x02\x1e\xe90P\xf8\xae\x12rQ\x83\x9et\xc1\xf1\xfd\x8a\x02\xc6\x14a\xe5\xd7C\u0580\x00\u07d4\x16\x81j\xac\x0e\xde\r-<\xd4B\xday\xe0c\x88\x0f\x0f\x1dg\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x16\x8bP\x19\xb8\x18i\x16D\x83_\xe6\x9b\xf2)\xe1q\x12\xd5,\x8a\x05\xed\xe2\x0f\x01\xa4Y\x80\x00\x00\u07d4\x16\x8b\xde\xc8\x18\xea\xfcm)\x92\xe5\xefT\xaa\x0e\x16\x01\xe3\xc5a\x8967Pz0\xab\xeb\x00\x00\u07d4\x16\x8d0\xe5?\xa6\x81\t+R\xe9\xba\xe1Z\r\xcbA\xa8\u027b\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x16\x9b\xbe\xfcA\xcf\xd7\xd7\u02f8\xdf\xc60 \xe9\xfb\x06\u0515F\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x16\xa5\x8e\x98]\xcc\xd7\a\xa5\x94\u0453\xe7\u0327\x8b]\x02xI\x89I\xb9\u029aiC@\x00\x00\u07d4\x16\xa9\xe9\xb7:\u92c6M\x17(y\x8b\x87f\xdb\xc6\xea\x8d\x12\x893\xe7\xb4K\r\xb5\x04\x00\x00\u07d4\x16\xaaR\xcb\vUG#\xe7\x06\x0f!\xf3'\xb0\xa6\x83\x15\xfe\xa3\x89\r\x8drkqw\xa8\x00\x00\u07d4\x16\xab\xb8\xb0!\xa7\x10\xbd\u01ce\xa54\x94\xb2\x06\x14\xffN\xaf\xe8\x89\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4\x16\xaf\xa7\x87\xfc\x9f\x94\xbd\xffiv\xb1\xa4/C\n\x8b\xf6\xfb\x0f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x16\xba\xe5\xd2N\xff\x91w\x8c\u064bM:\x1c\xc3\x16/D\xaaw\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\x16\xbc@!Z\xbb\u066e](\v\x95\xb8\x01\vE\x14\xff\x12\x92\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x16\xbeu\u9299Z9R\"\xd0\v\u05df\xf4\xb6\xe68\u144a\a\x9f\x90\\o\xd3N\x80\x00\x00\u07d4\x16\xc1\xbf[}\xc9\xc8<\x17\x9e\xfa\xcb\xcf.\xb1t\xe3V\x1c\xb3\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x16\u01f3\x1e\x8c7b\x82\xac\"qr\x8c1\xc9^5\xd9R\u00c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x16\xf3\x13\u03ca\xd0\x00\x91J\n\x17m\u01a44+y\xec%8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x16\xff\xac\x84\x03)@\xf0\x12\x1a\tf\x8b\x85\x8a~y\xff\xa3\xbb\x89\xd2J\xdan\x10\x87\x11\x00\x00\xe0\x94\x17\x03\xb4\xb2\x92\xb8\xa9\xde\xdd\xed\xe8\x1b\xb2]\x89\x17\x9fdF\xb6\x8a\x04+e\xa4U\xe8\xb1h\x00\x00\u07d4\x17\x04\x93\x11\x10\x1d\x81~\xfb\x1de\x91\x0ff6b\xa6\x99\u024c\x89lh\xcc\u041b\x02,\x00\x00\u07d4\x17\x04\xce\xfc\xfb\x131\xeczx8\x8b)9>\x85\xc1\xafy\x16\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x17\n\x88\xa8\x99\u007f\x92\xd287\x0f\x1a\xff\xde\xe64pP\xb0\x13\x89\xa2\xacw5\x14\x880\x00\x00\u07d4\x17\x10\x8d\xab,P\xf9\x9d\xe1\x10\u1cf3\xb4\u0342\xf5\xdf(\xe7\x895 ;g\xbc\xca\xd0\x00\x00\xe0\x94\x17\x12[Y\xacQ\xce\xe0)\xe4\xbdx\xd7\xf5\x94}\x1e\xa4\x9b\xb2\x8a\x04\xa8\x9fT\xef\x01!\xc0\x00\x00\u07d4\x17\x1a\u0660K\xed\u0238a\xe8\xedK\xdd\xf5qx\x13\xb1\xbbH\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x17\x1c\xa0*\x8bmb\xbfL\xa4~\x90i\x14\a\x98a\x97,\xb2\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x17\"\xc4\xcb\xe7\n\x94\xb6U\x9dBP\x84\xca\xee\xd4\xd6\xe6n!\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x17X\vvotSR\\\xa4\u01a8\x8b\x01\xb5\x05p\xea\b\x8c\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x17X\x9al\x00jT\xca\xd7\x01\x03\x12:\xae\n\x82\x13_\u07b4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x17Z\x18::#_\xfb\xb0;\xa85gRg\"\x94\x17\xa0\x91\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\x17_\xee\xea*\xa4\xe0\xef\xda\x12\xe1X\x8d/H2\x90\xed\xe8\x1a\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x17e6\x1c.\xc2\xf86\x16\u0383c\xaa\xe2\x10%\xf2Vo@\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x17gR\\_Z\"\xed\x80\xe9\xd4\xd7q\x0f\x03b\u049e\xfa3\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x17v%`\xe8*\x93\xb3\xf5\"\xe0\xe5$\xad\xb8a,:tp\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x17}\xaex\xbc\x01\x13\xd8\u04dcD\x02\xf2\xa6A\xae*\x10Z\xb8\x89b\x92BV \xb4H\x00\x00\xe0\x94\x17\x84\x94\x8b\xf9\x98H\u021eDV8PM\u0598'\x1bY$\x8a\x01GLA\r\x87\xba\xee\x00\x00\u07d4\x17\x88\u069bW\xfd\x05\xed\xc4\xff\x99\xe7\xfe\xf3\x01Q\x9c\x8a\n\x1e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x17\x8e\xafk\x85T\xc4]\xfd\xe1kx\xce\f\x15\u007f.\xe3\x13Q\x89\x11X\xe4`\x91=\x00\x00\x00\u07d4\x17\x96\x1dc;\xcf \xa7\xb0)\xa7\xd9K}\xf4\xda.\xc5B\u007f\x89\fo\xf0p\U000532c0\x00\u07d4\x17\x96\xbc\xc9{\x8a\xbcq\u007fKJ|k\x106\xea!\x82c\x9f\x89\x13A\xf9\x1c\xd8\xe3Q\x00\x00\u07d4\x17\x99=1*\xa1\x10iW\x86\x8fjU\xa5\xe8\xf1/w\xc8C\x89\x18e\xe8\x14\xf4\x14.\x80\x00\u07d4\x17\x9a\x82^\x0f\x1fn\x98S\tf\x84e\xcf\xfe\xd46\xf6\xae\xa9\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x17\xb2\xd6\xcfe\xc6\xf4\xa3G\xdd\xc6W&U5M\x8aA+)\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x17\xb8\a\xaf\xa3\xdd\xd6G\xe7#T.{R\xfe\xe3\x95'\xf3\x06\x89\x15\xaf@\xff\xa7\xfc\x01\x00\x00\u07d4\x17\xc0G\x86W\xe1\xd3\xd1z\xaa3\x1d\xd4)\xce\u03d1\xf8\xae]\x8964\xfb\x9f\x14\x89\xa7\x00\x00\u07d4\x17\xc0\xfe\xf6\x98l\xfb.@A\xf9\x97\x9d\x99@\xb6\x9d\xff=\xe2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x17\u0511\x8d\xfa\xc1]w\xc4\u007f\x9e\xd4\x00\xa8P\x19\rd\xf1Q\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x17\xd5!\xa8\xd9w\x90#\xf7\x16M#<;d \xff\xd2#\xed\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x17\xd91\xd4\xc5b\x94\u073ew\xc8e[\xe4i_\x00mJ<\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x17\xdfIQ\x8ds\xb1)\xf0\xda6\xb1\u0274\f\xb6d \xfd\u01ca\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x17\xe4\xa0\xe5+\xac>\xe4N\xfe\tT\xe7S\u0538]dN\x05\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x17\xe5\x84\xe8\x10\xe5gp,a\xd5]CK4\u0375\xee0\xf6\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x17\xe8.px\xdcO\xd9\xe8y\xfb\x8aPf\u007fS\xa5\xc5E\x91\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x17\xe8o;[0\xc0\xbaY\xf2\xb2\xe8XB[\xa8\x9f\n\x10\xb0\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x17\xee\x9fT\xd4\xdd\xc8Mg\x0e\xff\x11\xe5Je\x9f\xd7/DU\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\xe0\x94\x17\xefJ\xcc\x1b\xf1G\xe3&t\x9d\x10\xe6w\xdc\xff\xd7o\x9e\x06\x8a\bwQ\xf4\xe0\xe1\xb50\x00\x00\u07d4\x17\xf1F2\xa7\xe2\x82\v\xe6\xe8\xf6\u07c25X(=\xad\xab-\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x17\xf5#\xf1\x17\xbc\x9f\xe9x\xaaH\x1e\xb4\xf5V\x17\x117\x1b\u0209li\xf7>)\x13N\x00\x00\u07d4\x17\xfd\x9bU\x1a\x98\xcba\xc2\xe0\u007f\xbfA\xd3\xe8\u02650\u02e5\x89\x01v\x8c0\x81\x93\x04\x80\x00\u07d4\x18\x04x\xa6U\u05cd\x0f;\fO +aH[\xc4\x00/\u0549lk\x93[\x8b\xbd@\x00\x00\u07d4\x18\x13l\x9d\xf1g\xaa\x17\xb6\xf1\x8e\"\xa7\x02\u020fK\u0082E\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x18\x15'\x9d\xff\x99R\xda;\xe8\xf7rI\xdb\xe2\"C7{\xe7\x8a\x01\x01|\xb7n{&d\x00\x00\u07d4\x18\x1f\xbb\xa8R\xa7\xf5\x01x\xb1\xc7\xf0>\xd9\xe5\x8dT\x16))\x89$\x1a\x9bOaz(\x00\x00\xe0\x94\x18'\x03\x9f\tW\x02\x94\b\x8f\xdd\xf0G\x16\\3\u65a4\x92\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\x18-\xb8R\x93\xf6\x06\u8248\xc3pL\xb3\xf0\xc0\xbb\xbf\xcaZ\x89\a?u\u0460\x85\xba\x00\x00\u07d4\x18H\x00<%\xbf\u052a\x90\xe7\xfc\xb5\u05f1k\xcd\f\xff\xc0\u060965\u026d\xc5\u07a0\x00\x00\xe0\x94\x18JO\v\xebq\xff\xd5X\xa6\xb6\xe8\xf2(\xb7\x87\x96\xc4\xcf>\x8a\x02\x8a\x85t%Fo\x80\x00\x00\xe0\x94\x18M\x86\xf3Fj\xe6h;\x19r\x99\x82\xe7\xa7\u1903G\xb2\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x18Q\xa0c\xcc\xdb0T\x90w\xf1\xd19\xe7-\xe7\x97\x11\x97\u0549lk\x93[\x8b\xbd@\x00\x00\u07d4\x18UF\xe8v\x8dPhs\x81\x8a\xc9u\x1c\x1f\x12\x11j;\xef\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x18X\xcf\x11\xae\xa7\x9fS\x98\xad+\xb2\"g\xb5\xa3\xc9R\xeat\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\xe0\x94\x18Z\u007f\u012c\xe3h\xd23\xe6 \xb2\xa4Y5f\x12\x92\xbd\xf2\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x18d\xa3\u01f4\x81UD\x8cT\u020cp\x8f\x16g\tsm1\x89\a?u\u0460\x85\xba\x00\x00\u07d4\x18j\xfd\xc0\x85\xf2\xa3\xdc\xe4a^\xdf\xfb\xad\xf7\x1a\x11x\x0fP\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x18k\x95\xf8\xe5\xef\xfd\xdc\xc9O\x1a1[\xf0)];\x1e\xa5\x88\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x18}\x9f\f\a\xf8\xebt\xfa\xaa\xd1^\xbc{\x80Dt\x17\xf7\x82\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x18\x95\xa0\xebJCrr/\xcb\u016f\xe6\x93o(\x9c\x88\xa4\x19\x891T\xc9r\x9d\x05x\x00\x00\u07d4\x18\x99\xf6\x9fe;\x05\xa5\xa6\xe8\x1fH\a\x11\u041b\xbf\x97X\x8c\x89i\xfb\x13=\xf7P\xac\x00\x00\u07d4\x18\xa6\xd2\xfcR\xbes\b@#\xc9\x18\x02\xf0[\xc2JK\xe0\x9f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x18\xb0@|\xda\xd4\xceR`\x06#\xbd^\x1fj\x81\xaba\xf0&\x89\x11Q\xcc\xf0\xc6T\u0180\x00\u07d4\x18\xb8\xbc\xf9\x83!\xdaa\xfbN>\xac\xc1\xecT\x17'-\xc2~\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4\x18\xc6r:gS)\x9c\xb9\x14G}\x04\xa3\xbd!\x8d\xf8\xc7u\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x18\xe1\x13\xd8\x17|i\x1aa\xbexXR\xfa[\xb4z\uef6f\x89Hz\x9a0E9D\x00\x00\xe0\x94\x18\xe4\xceGH;S\x04\n\u06eb5\x17,\x01\xefdPn\f\x8a\x01\xe7\xe4\x17\x1b\xf4\u04e0\x00\x00\xe0\x94\x18\xe52C\x98\x1a\xab\xc8v}\xa1\fsD\x9f\x13\x91V\x0e\xaa\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x18\xfa\x86%\xc9\u0704>\x00\x15\x9e\x892\xf5\x1e\u06ea\xa30\x00\x00\xe0\x94\x193\xe34\xc4\x0f:\u02ed\f\v\x85\x11X i$\xbe\xca:\x8a\x01\x99^\xaf\x01\xb8\x96\x18\x80\x00\xe0\x94\x197\xc5\xc5\x15\x05uS\u033dF\u0546dU\xcef)\x02\x84\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\u07d4\x19:\xc6Q\x83e\x18\x00\xe25\x80\xf8\xf0\xea\u04fbY~\xb8\xa4\x89\x02\xb6*\xbc\xfb\x91\n\x00\x00\u07d4\x19=7\xed4}\x1c/N55\r\x9aDK\xc5|\xa4\xdbC\x89\x03@\xaa\xd2\x1b;p\x00\x00\xe0\x94\x19@\u0713d\xa8R\x16_GAN'\xf5\x00$E\xa4\xf1C\x8a\x02L-\xffj<|H\x00\x00\u07d4\x19E\xfe7\u007f\xe6\u0537\x1e>y\x1fo\x17\xdb$<\x9b\x8b\x0f\x89vy\u7fb9\x886\x00\x00\u07d4\x19Jk\xb3\x02\xb8\xab\xa7\xa5\xb5y\u07d3\xe0\xdf\x15t\x96v%\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x19L\ubd12\x98\x82\xbf;K\xf9\x86L+\x1b\x0fb\u0083\xf9\x89\x1e\xf8aS\x1ft\xaa\x00\x00\u07d4\x19O\xf4J\xef\xc1{\xd2\x0e\xfdz LG\xd1b\f\x86\xdb]\x89\xa2\x99\th\u007fj\xa4\x00\x00\xe0\x94\x19O\xfex\xbb\xf5\xd2\r\u044a\x1f\x01\xdaU.\x00\xb7\xb1\x1d\xb1\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\x19S1>*\xd7F#\x9c\xb2'\x0fH\xaf4\u063b\x9cDe\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x19W\x1a+\x8f\x81\u01bc\xf6j\xb3\xa1\x00\x83)V\x17\x15\x00\x03\x89\x1a\xb2\xcf|\x9f\x87\xe2\x00\x00\xe0\x94\x19h}\xaa9\xc3h\x13\x9bn{\xe6\r\xc1u:\x9f\f\xbe\xa3\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x19l\x02!\nE\n\xb0\xb3cpe_qz\xa8{\xd1\xc0\x04\x89\x0e\x10\xac\xe1W\xdb\xc0\x00\x00\u07d4\x19n\x85\xdf~s+J\x8f\x0e\xd06#\xf4\u06dd\xb0\xb8\xfa1\x89\x01%\xb9/\\\xef$\x80\x00\u07d4\x19s+\xf9s\x05]\xbd\x91\xa4S:\u06a2\x14\x9a\x91\u04c3\x80\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x19vr\xfd9\xd6\xf2F\xcef\xa7\x90\xd1:\xa9\"\xd7\x0e\xa1\t\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x19y\x8c\xbd\xa7\x15\ua69b\x9dj\xab\x94,U\x12\x1e\x98\xbf\x91\x89A\rXj \xa4\xc0\x00\x00\u07d4\x19\x8b\xfc\xf1\xb0z\xe3\b\xfa,\x02\x06\x9a\xc9\xda\xfeq5\xfbG\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x19\x8e\xf1\xec2Z\x96\xcc5Lrf\xa08\xbe\x8b\\U\x8fg\x8a\x80\xd1\xe47>\u007f!\xda\x00\x00\xe0\x94\x19\x91\x8a\xa0\x9e}IN\x98\xff\xa5\xdbP5\b\x92\xf7\x15j\u018a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x19\xb3k\f\x87\xeafN\xd8\x03\x18\xdcw\xb6\x88\xdd\xe8}\x95\xa5\x89i\x9fI\x98\x020=\x00\x00\u07d4\x19\u07d4E\xa8\x1c\x1b=\x80J\xea\xebon NB6f?\x89\x02\x06\xd9NjI\x87\x80\x00\u07d4\x19\xe5\u07a37\n,tj\xae4\xa3|S\x1fA\xda&N\x83\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x19\xe7\xf3\xeb{\xf6\u007f5\x99 \x9e\xbe\b\xb6*\xd32\u007f\x8c\u0789lk\x93[\x8b\xbd@\x00\x00\u07d4\x19\xe9Nb\x00P\xaa\xd7f\xb9\xe1\xba\xd91#\x83\x12\u053fI\x89\x81\xe3-\xf9r\xab\xf0\x00\x00\u07d4\x19\xec\xf2\xab\xf4\f\x9e\x85{%/\xe1\xdb\xfd=L]\x8f\x81n\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x19\xf5\xca\xf4\xc4\x0ei\b\x81<\aE\xb0\xae\xa9Xm\x9d\xd91\x89#\xfe\xd9\xe1\xfa+`\x00\x00\u07d4\x19\xf6C\xe1\xa8\xfa\x04\xae\x16\x00`(\x13\x833\xa5\x9a\x96\u0787\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x19\xf9\x9f,\vF\u0389\x06\x87]\xc9\xf9\n\xe1\x04\xda\xe3U\x94\x89\xf4WZ]M\x16*\x00\x00\u07d4\x19\xff$O\xcf\xe3\xd4\xfa/O\u065f\x87\xe5[\xb3\x15\xb8\x1e\xb6\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1a\x04\xce\xc4 \xadC\"\x15$mw\xfe\x17\x8d3\x9e\u0435\x95\x89\x11!a\x85\u009fp\x00\x00\xe0\x94\x1a\x04\xd58\x9e\xb0\x06\xf9\u0388\f0\xd1SS\xf8\xd1\x1cK1\x8a\x03\x9d\x84\xb2\x18m\xc9\x10\x00\x00\u07d4\x1a\bA\xb9*\u007fpuV\x9d\xc4b~kv\u02b0Z\u0791\x89Rf<\u02b1\xe1\xc0\x00\x00\xe0\x94\x1a\b]C\xec\x92AN\xa2{\x91O\xe7g\xb6\xd4k\x1e\xefD\x8a\x06A\xe8\xa15c\xd8\xf8\x00\x00\u07d4\x1a\t\xfd\xc2\u01e2\x0e#WK\x97\u019e\x93\u07bag\xd3r \x89lO\xd1\xee$nx\x00\x00\u07d4\x1a\n\x1d\u07f01\xe5\xc8\xcc\x1dF\xcf\x05\x84-P\xfd\xdcq0\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x1a\x1c\x9a&\xe0\xe0$\x18\xa5\xcfh}\xa7Z'\\b,\x94@\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x1a \x1bC'\u03a7\xf3\x99\x04bF\xa3\xc8~n\x03\xa3\u0368\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x1a$4\xccwD\"\u050dS\u055c]V,\u0384\a\xc9K\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4\x1a%\xe1\u017c~_P\xec\x16\xf8\x88_!\x0e\xa1\xb98\x80\x0e\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x1a&\x94\xec\a\xcf^Mh\xba@\xf3\xe7\xa1LS\xf3\x03\x8cn\x8966\xcd\x06\xe2\xdb:\x80\x00\u07d4\x1a5 E5\x82\xc7\x18\xa2\x1cB7[\xc5\as%RS\xe1\x89*\xd3s\xcef\x8e\x98\x00\x00\xe0\x94\x1a7n\x1b-/Y\ai\xbb\x85\x8dEu2\rN\x14\x99p\x8a\x01\x06q%v9\x1d\x18\x00\x00\u07d4\x1a:3\x0eO\xcbi\xdb\xef^i\x01x;\xf5\x0f\xd1\xc1SB\x89\u3bb5sr@\xa0\x00\x00\u07d4\x1aN\u01a0\xae\u007fZ\x94'\xd2=\xb9rL\r\f\xff\xb2\xab/\x89\t\xb4\x1f\xbf\x9e\n\xec\x00\x00\u07d4\x1aP^b\xa7N\x87\xe5wG>O:\xfa\x16\xbe\xdd<\xfaR\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x1a^\xe53\xac\xbf\xb3\xa2\xd7m[hRw\xb7\x96\xc5j\x05+\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1adJP\xcb\u00ae\xe8#\xbd+\xf2C\xe8%\xbeMG\xdf\x02\x89\x05k\xe0<\xa3\xe4}\x80\x00\u07d4\x1apD\xe28?\x87\b0[I[\xd1\x17k\x92\xe7\xef\x04:\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1ay\xc7\xf4\x03\x9cg\xa3\x9du\x13\x88L\xdc\x0e,4\"$\x90\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x1a\x89\x89\x9c\xbe\xbd\xbbd\xbb&\xa1\x95\xa6<\bI\x1f\u035e\xee\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x1a\x8a\\\xe4\x14\u079c\xd1r\x93~7\xf2\u055c\xffq\xceW\xa0\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x1a\x95\xa8\xa8\b.FR\xe4\x17\r\xf9'\x1c\xb4\xbbC\x05\xf0\xb2\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\x1a\x95\u0277Tk]\x17\x86\u00c5\x8f\xb1#dF\xbc\f\xa4\u0389j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x1a\x98~?\x83\xdeu\xa4/\x1b\xde|\x99|\x19!{J_$\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1a\x9ep/8]\xcd\x10^\x8b\x9f\xa4(\xee\xa2\x1cW\xffR\x8a\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\x1a\xa1\x02\x1fU\n\xf1X\xc7Gf\x8d\xd1;F1`\xf9Z@\x89O\xb0Y\x1b\x9b08\x00\x00\u07d4\x1a\xa2v\x99\xca\u068d\u00e7oy3\xaaf\xc7\x19\x19\x04\x0e\x88\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x1a\xa4\x02p\xd2\x1e\\\u0786\xb61m\x1a\xc3\xc53IKy\xed\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x1a\xb5:\x11\xbc\xc6=\u07ea@\xa0+\x9e\x18d\x96\u037b\x8a\xff\x89l?*\xac\x80\f\x00\x00\x00\u07d4\x1a\xbcN%;\b\n\xebCy\x84\xab\x05\xbc\xa0\x97\x9a\xa4>\x1c\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x1a\xc0\x89\u00fcM\x82\xf0j \x05\x1a\x9ds-\xc0\xe74\xcba\x89%\xf6\x9dc\xa6\xce\x0e\x00\x00\xe0\x94\x1a\xd4V>\xa5xk\xe1\x15\x995\xab\xb0\xf1\u0547\x9c>sr\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x1a\xd7- \xa7n\u007f\xcckv@X\xf4\x8dA}Io\xa6\u0349lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x1a\xda\xf4\xab\xfa\x86}\xb1\u007f\x99\xafj\xbe\xbfpz<\xf5]\xf6\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x1a\xf6\x03C6\x0e\v-u%R\x107W \xdf!\xdb\\}\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x1a\xfc\u0145\x89l\xd0\xed\xe1)\xee-\xe5\xc1\x9e\xa8\x11T\vd\x89\xaf*\xba\f\x8e[\xef\x80\x00\u07d4\x1b\x05\xeajj\u022f|\xb6\xa8\xb9\x11\xa8\xcc\xe8\xfe\x1a*\xcf\u0209lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b\v1\xaf\xffKm\xf3e:\x94\xd7\xc8yx\xae5\xf3J\xae\x89\x139\x10E?\xa9\x84\x00\x00\u07d4\x1b\r\ah\x17\xe8\u058e\xe2\xdfN\x1d\xa1\xc1\x14-\x19\x8cD5\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\x1b\x13\ro\xa5\x1d\\H\xec\x8d\x1dR\u070a\"{\xe8s\\\x8a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b#\u02c6cUHq\xfb\xbe\r\x9e`9~\xfbo\xae\xdc>\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1b&9X\x8bU\xc3D\xb0#\xe8\xde_\xd4\b{\x1f\x04\x03a\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\x1b9 \xd0\x01\xc4>r\xb2N|\xa4o\x0f\xd6\xe0\xc2\n_\xf2\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x1b<\xb8\x1eQ\x01\x1bT\x9dx\xbfr\v\r\x92J\xc7c\xa7\u008av\x95\xa9, \xd6\xfe\x00\x00\x00\u07d4\x1bC#,\xcdH\x80\xd6\xf4o\xa7Q\xa9l\xd8$s1XA\x89\x04V9\x18$O@\x00\x00\u07d4\x1bK\xbc\xb1\x81e!\x1b&[(\a\x16\xcb?\x1f!!v\xe8\x89\x19\x9a\xd3}\x03\xd0`\x80\x00\u07d4\x1bM\a\xac\u04c1\x83\xa6\x1b\xb2x=+{\x17\x8d\xd5\x02\xac\x8d\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1bckzIo\x04MsYYn5:\x10F\x16Cok\x89\x13\x88\xea\x95\xc3?\x1d\x00\x00\u07d4\x1bd\x95\x89\x12@\xe6NYD\x93\xc2f!q\xdb^0\xce\x13\x89\tX\x87\u0595\xedX\x00\x00\u07d4\x1bf\x10\xfbh\xba\xd6\xed\x1c\xfa\xa0\xbb\xe3:$\xeb.\x96\xfa\xfb\x89\b=lz\xabc`\x00\x00\u07d4\x1by\x903\xefm\xc7\x12x\"\xf7EB\xbb\"\xdb\xfc\t\xa3\b\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x1b~\xd9t\xb6\xe24\u0381$t\x98B\x9a[\u0520\xa2\xd19\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b\x82o\xb3\xc0\x12\xb0\xd1Y\u253a[\x8aI\x9f\xf3\xc0\xe0<\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b\x8a\xa0\x16\f\u05df\x00_\x88Q\nqI\x13\xd7\n\u04fe3\x89\n\xef\xfb\x83\a\x9a\xd0\x00\x00\xe0\x94\x1b\x8b\xd6\xd2\xec\xa2\x01\x85\xa7\x8e}\x98\xe8\xe1\x85g\x8d\xacH0\x8a\x03\x89O\x0eo\x9b\x9fp\x00\x00\u07d4\x1b\x9b-\u0096\x0eL\xb9@\x8ft\x05\x82|\x9bY\a\x16\x12\xfd\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x1b\xa9\"\x8d8\x87'\xf3\x89\x15\x0e\xa0;s\xc8-\xe8\xeb.\t\x8a\x01\x89t\xfb\xe1w\xc9(\x00\x00\u07d4\x1b\xa9\xf7\x99~S\x87\xb6\xb2\xaa\x015\xac$R\xfe6\xb4\xc2\r\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\u07d4\x1b\xba\x03\xffkJ\u057f\x18\x18J\xcb!\xb1\x88\xa3\x99\xe9\xebJ\x89a\t=|,m8\x00\x00\u07d4\x1b\xbc\x19\x9eXg\x90\xbe\x87\xaf\xed\xc8I\xc0G&t\\]{\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x1b\xbc`\xbc\xc8\x0e\\\xdc5\xc5Aj\x1f\n@\xa8=\xae\x86{\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b\xc4L\x87a#\x1b\xa1\xf1\x1f_\xaa@\xfaf\x9a\x01>\x12\u0389\v\tR\xc4Z\xea\xad\x00\x00\u07d4\x1b\xcf4A\xa8f\xbd\xbe\x960\t\xce3\xc8\x1c\xbb\x02a\xb0,\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\x1b\u048c\xd5\u01ca\xeeQ5|\x95\xc1\xef\x925\xe7\xc1\x8b\xc8T\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b\xd8\xeb\xaavt\xbb\x18\u1458\xdb$OW\x03\x13\a_C\x89\b!\xab\rD\x14\x98\x00\x00\u07d4\x1b\xd9\t\xac\rJ\x11\x02\xec\x98\xdc\xf2\u0329j\n\xdc\u05e9Q\x89\x01\x16Q\xac>zu\x80\x00\u07d4\x1b\xe3T,6\x13hte\xf1Zp\xae\xeb\x81f+e\u0328\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b\xeaM\xf5\x12/\xaf\u07b3`~\xdd\xda\x1e\xa4\xff\u06da\xbf*\x89\x12\xc1\xb6\xee\xd0=(\x00\x00\u07d4\x1b\xecM\x02\u0385\xfcH\xfe\xb6$\x89\x84\x1d\x85\xb1pXj\x9b\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4\x1b\xf9t\u0650OE\u0381\xa8E\xe1\x1e\xf4\xcb\xcf'\xafq\x9e\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\x1c\x04VI\xcdS\xdc#T\x1f\x8e\xd4\xd3A\x81(\b\xd5\u075c\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\x1c\x12\x8b\xd6\u0365\xfc\xa2uu\xe4\xb4;2S\xc8\xc4\x17*\xfe\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1c\x13\u04c67\xb9\xa4|\xe7\x9d7\xa8oP\xfb@\x9c\x06\a(\x89Hz\x9a0E9D\x00\x00\u07d4\x1c \x10\xbdf-\xf4\x17\xf2\xa2q\x87\x9a\xfb\x13\xefL\x88\xa3\xae\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x1c%z\u0525Q\x05\xea;X\xed7K\x19\x8d\xa2f\xc8_c\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x1c.6\a\xe1'\xca\xca\x0f\xbd\\YH\xad\xad}\xd80\xb2\x85\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4\x1c5l\xfd\xb9_\xeb\xb7\x14c;(\xd5\xc12\u0744\xa9\xb46\x89\x01Z\xf1\u05cbX\xc4\x00\x00\u07d4\x1c5\xaa\xb6\x88\xa0\u034e\xf8.vT\x1b\xa7\xac9R\u007ft;\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x1c>\xf0]\xae\x9d\xcb\u0509\xf3\x02D\bf\x9d\xe2D\xc5*\x02\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x1cJ\xf0\xe8c\xd2el\x865\xbco\xfe\xc8\u0759(\x90\x8c\xb5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1c`\x19\x93x\x92\a\xf9e\xbb\x86\\\xbbL\xd6W\xcc\xe7o\xc0\x89\x05T\x1ap7P?\x00\x00\u07d4\x1cc\xfa\x9e,\xbb\xf21a\xda3\xa1\xda}\xf7\r\x1b\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1c\xb6\xb2\xd7\xcf\xc5Y\xb7\xf4\x1eoV\xab\x95\xc7\xc9X\xcd\x0eL\x89Hz\x9a0E9D\x00\x00\u07d4\x1c\xc1\xd3\xc1O\x0f\xb8d\x0e6rM\xc42)\xd2\xeaz\x1eH\x89\\(=A\x03\x94\x10\x00\x00\u07d4\x1c\xc9\bv\x00A\t\xcdy\xa3\u07a8f\u02c4\n\xc3d\xba\x1b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1c\xd1\xf0\xa3\x14\u02f2\x00\xde\n\f\xb1\xef\x97\xe9 p\x9d\x97\u0089lk\x93[\x8b\xbd@\x00\x00\u0794\x1c\xdaA\x1b\xd5\x16;\xae\xca\x1eU\x85c`\x1c\xe7 \xe2N\xe1\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x1c\xe8\x1d1\xa7\x920\"\xe1%\xbfH\xa3\xe06\x93\xb9\x8d\xc9\u0749lk\x93[\x8b\xbd@\x00\x00\u07d4\x1c\xeb\xf0\x98]\u007fh\n\xaa\x91\\D\xccb\xed\xb4\x9e\xab&\x9e\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x1c\xedg\x15\xf8b\xb1\xff\x86\x05\x82\x01\xfc\xceP\x82\xb3nb\xb2\x8a\x01j^`\xbe\xe2s\xb1\x00\x00\u07d4\x1c\xf0L\xb1C\x80\x05\x9e\xfd?#\x8be\u057e\xb8j\xfa\x14\u0609\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x1c\xf1\x05\xab#\x02;ULX>\x86\u05d2\x11y\xee\x83\x16\x9f\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x1c\xf2\xebz\x8c\xca\u00ad\xea\xef\x0e\xe8sG\xd55\u04f9@X\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1c\xfc\xf7Q\u007f\f\bE\x97 \x94+dz\u0452\xaa\x9c\x88(\x89+^:\xf1k\x18\x80\x00\x00\xe0\x94\x1d\t\xad$\x12i\x1c\u0141\xc1\xab6\xb6\xf9CL\xd4\xf0\x8bT\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\x1d\x15|Xv\xc5\xca\xd5S\xc9\x12\xca\xf6\xce-Rw\xe0\\s\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x1d&\x15\xf8\xb6\xcaP\x12\xb6c\xbd\u0414\xb0\xc5\x13|w\x8d\u07ca\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x1d)\u01ea\xb4+ H\u04b2R%\u0518\u06e6z\x03\xfb\xb2\x89\n\u05ce\xbcZ\xc6 \x00\x00\u0794\x1d4\x1f\xa5\xa3\xa1\xbd\x05\x1f}\xb8\a\xb6\xdb/\u01faO\x9bE\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x1d4N\x96%g\xcb'\xe4M\xb9\xf2\xfa\u01f6\x8d\xf1\xc1\xe6\xf7\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\x1d6h0c\xb7\xe9\xeb\x99F-\xab\xd5i\xbd\xdc\xe7\x16\x86\xf2\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x1d7aky?\x94\x91\x188\xac\x8e\x19\xee\x94I\u07d2\x1e\u0109QP\xae\x84\xa8\xcd\xf0\x00\x00\xe0\x94\x1d9[0\xad\xda\x1c\xf2\x1f\t\x1aOJ{u3q\x18\x94A\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\x1dEXn\xb8\x03\xca!\x90e\v\xf7H\xa2\xb1t1+\xb5\a\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\x1dW.\xdd-\x87\xca'\x1ag\x14\xc1Z;7v\x1d\u0320\x05\x89\x06\xeb\xd5*\x8d\xdd9\x00\x00\u07d4\x1dc0\x97\xa8R%\xa1\xffC!\xb1)\x88\xfd\xd5\\+8D\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x1di\xc8=(\xff\x04t\xce\xeb\xea\xcb:\xd2'\xa1D\xec\u78ca\x01(\xcc\x03\x92\nb\u0480\x00\u07d4\x1d\x96\xbc\u0544W\xbb\xf1\xd3\u00a4o\xfa\xf1m\xbf}\x83hY\x89\tIr\t\xd8F~\x80\x00\u07d4\x1d\x9ej\xaf\x80\x19\xa0_#\x0e]\xef\x05\xaf]\x88\x9b\xd4\xd0\xf2\x89\a?u\u0460\x85\xba\x00\x00\u07d4\x1d\xab\x17.\xff\xa6\xfb\xeeSL\x94\xb1~yN\xda\xc5OU\xf8\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x1d\xb9\xac\x9a\x9e\xae\xec\nR7W\x05\fq\xf4rx\xc7-P\x89Hz\x9a0E9D\x00\x00\u07d4\x1d\xbe\x8e\x1c+\x8a\x00\x9f\x85\xf1\xad<\xe8\r.\x055\x0e\u3709\aW\rn\x9e\xbb\xe4\x00\x00\u07d4\x1d\xc7\xf7\xda\xd8]\xf5?\x12q\x15$\x03\xf4\xe1\xe4\xfd\xb3\xaf\xa0\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1d\u03bc\xb7em\xf5\u072a3h\xa0U\xd2/\x9e\xd6\xcd\xd9@\x89\x1b\x18\x1eK\xf24<\x00\x00\xe0\x94\x1d\xd7tA\x84J\xfe\x9c\xc1\x8f\x15\xd8\xc7{\xcc\xfbe^\xe04\x8a\x01\x06\xebEW\x99D\x88\x00\x00\u07d4\x1d\xde\xfe\xfd5\xab\x8fe\x8b$q\xe5G\x90\xbc\x17\xaf\x98\u07a4\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x1d\xee\xc0\x1a\xbe\\\r\x95-\xe9\x10l=\xc3\x069\xd8P\x05\u0589lk\x93[\x8b\xbd@\x00\x00\u07d4\x1d\xf6\x91\x16rg\x9b\xb0\xef5\t\x03\x8c\f'\xe3\x94\xfd\xfe0\x89\x1dF\x01b\xf5\x16\xf0\x00\x00\u07d4\x1d\xfa\xee\ar\x12\xf1\xbe\xaf\x0eo/\x18@Sz\xe1T\xad\x86\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x1e\x06\r\xc6\xc5\xf1\u02cc\xc7\xe1E.\x02\xee\x16u\b\xb5eB\x8a\x02\xb1O\x02\xc8d\xc7~\x00\x00\xe0\x94\x1e\x13\xecQ\x14,\ubde2`\x83A,<\xe3QD\xbaV\xa1\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x1e\x1aH(\x11\x9b\xe3\t\xbd\x88#nMH+PM\xc5W\x11\x89\xa00\xdc\xeb\xbd/L\x00\x00\u07d4\x1e\x1a\ud178leb\u02cf\xa1\xebo\x8f;\xc9\u072eny\x89\xf4\xd2\u0744%\x9b$\x00\x00\u07d4\x1e\x1ccQwj\xc3\x10\x919~\xcf\x16\x00-\x97\x9a\x1b-Q\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\x1e\x1dz_$h\xb9N\xa8&\x98-\xbf!%yR*\xb7\xdf\n\u02ac\x9e\xee\xd3Y09\xe5\xacuy\x8a+\x14F\xddj\xef\xe4\x1c\x00\x00\u07d4\x1e{^M\x1fW+\xec\xf2\xc0\x0f\xc9\f\xb4v{Jn3\u0509\x06\x1f\xc6\x10u\x93\xe1\x00\x00\u07d4\x1e\x8eh\x9b\x02\x91|\xdc)$]\f\x9ch\xb0\x94\xb4\x1a\x9e\u0589lk\x93[\x8b\xbd@\x00\x00\u07d4\x1e\xa34\xb5u\b\a\xeat\xaa\u016b\x86\x94\xec_(\xaaw\u03c9\x1a\xb2\xcf|\x9f\x87\xe2\x00\x00\u07d4\x1e\xa4qU\x04\u01af\x10{\x01\x94\xf4\xf7\xb1\xcbo\xcc\xcdoK\x89 \x041\x97\xe0\xb0'\x00\x00\u07d4\x1e\xa4\x92\xbc\xe1\xad\x10~3\u007fK\u0527\xac\x9a{\xab\xcc\u036b\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x1e\xa6\xbf/\x15\xae\x9c\x1d\xbcd\u06a7\xf8\xeaM\r\x81\xaa\xd3\xeb\x89\u3bb5sr@\xa0\x00\x00\u07d4\x1e\xb4\xbfs\x15j\x82\xa0\xa6\x82 \x80\xc6\xed\xf4\x9cF\x9a\xf8\xb9\x89g\x8a\x93 b\xe4\x18\x00\x00\xe0\x94\x1e\xba\xcbxD\xfd\xc3\"\xf8\x05\x90O\xbf\x19b\x80-\xb1S|\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x1e\xc4\xecKw\xbf\x19\u0411\xa8h\xe6\xf4\x91T\x18\x05A\xf9\x0e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1e\xd0n\xe5\x16b\xa8lcE\x88\xfbb\xdcC\xc8\xf2~|\x17\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1e\u063b?\x06w\x8b\x03\x9e\x99a\xd8\x1c\xb7\x1as\xe6x|\x8e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1e\xda\bNye\x00\xba\x14\xc5\x12\x1c\r\x90\x84of\xe4\xbeb\x89\x1c\xfd\xd7F\x82\x16\xe8\x00\x00\u07d4\x1e\xeel\xbe\xe4\xfe\x96\xadaZ\x9c\xf5\x85zdy@\u07ccx\x89\x01\r:\xa56\xe2\x94\x00\x00\u07d4\x1e\xf2\u073f\xe0\xa5\x00A\x1d\x95n\xb8\u0213\x9c=l\xfef\x9d\x89*\x11)\u0413g \x00\x00\xe0\x94\x1e\xf5\xc9\xc76P\u03fb\xde\\\x88U1\xd4'\xc7\xc3\xfeUD\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x1f\x04\x12\xbf\xed\u0356N\x83}\t,q\xa5\xfc\xba\xf3\x01&\xe2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x1f\x17O@\xa0Dr4\xe6fS\x91Mu\xbc\x00>V\x90\u0709\b\xacr0H\x9e\x80\x00\x00\u07d4\x1f!\x86\xde\xd2>\f\xf9R\x16\x94\xe4\xe1dY>i\n\x96\x85\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x1f*\xfc\n\xed\x11\xbf\xc7\x1ew\xa9\ae{6\xeav\xe3\xfb\x99\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u0794\x1f9Y\xfc)\x11\x10\xe8\x822\xc3kvg\xfcx\xa3ya?\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x1f=\xa6\x8f\xe8~\xafC\xa8)\xabm~\u0166\xe0\t\xb2\x04\xfb\x89\x1e\x16\x01u\x8c,~\x00\x00\u07d4\x1fI\xb8m\r9EY\x06\x98\xa6\xaa\xf1g<7u\\\xa8\r\x89%\xf2s\x93=\xb5p\x00\x00\u07d4\x1f_;4\xbd\x13K'\x81\xaf\xe5\xa0BJ\u0144l\xde\xfd\x11\x89\x05]\xe6\xa7y\xbb\xac\x00\x00\u07d4\x1fo\x0004\x97R\x06\x1c\x96\a+\xc3\xd6\xeb5I \x8dk\x89\x01K\x8d\xe1\xeb\x88\u06c0\x00\u07d4\x1f}\x8e\x86\xd6\xee\xb0%E\xaa\xd9\x0e\x912{\xd3i\xd7\xd2\xf3\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x1f\x81\x16\xbd\n\xf5W\x0e\xaf\fV\u011cz\xb5\xe3zX\x04X\x89lk\x93[\x8b\xbd@\x00\x00\u0794\x1f\x88\xf8\xa13\x8f\xc7\xc1\tv\xab\xcd?\xb8\u04c5T\xb5\uc708\xb9\xf6]\x00\xf6<\x00\x00\u07d4\x1f\x9c2hE\x8d\xa3\x01\xa2\xbeZ\xb0\x82W\xf7{\xb5\xa9\x8a\xa4\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1f\xa21\x9f\xed\x8c-F*\xdf.\x17\xfe\xecjo0Qn\x95\x89\x06\xca\xe3\x06!\xd4r\x00\x00\u07d4\x1f\xb4c\xa08\x99\x83\xdf}Y?{\xddmxI\u007f\xed\x88y\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x1f\xb7\xbd1\r\x95\xf2\xa6\u067a\xaf\x8a\x8aC\n\x9a\x04E:\x8b\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x1f\xcc|\xe6\xa8HX\x95\xa3\x19\x9e\x16H\x1fr\xe1\xf7b\xde\xfe\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x1f\xcf\xd1\xd5\u007f\x87\"\x90V\f\xb6-`\x0e\x1d\xef\xbe\xfc\xcc\x1c\x89P\xc5\xe7a\xa4D\b\x00\x00\u0794\x1f\u0496\xbe\x03\xads|\x92\xf9\u0186\x9e\x8d\x80\xa7\x1cW\x14\xaa\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\x1f\xdd\xd8_\u024b\xe9\xc4\x04Ya\xf4\x0f\x93\x80^\xccEI\xe5\x89\b\xe3\xf5\v\x17<\x10\x00\x00\u07d4 \x01\xbe\xf7{f\xf5\x1e\x15\x99\xb0/\xb1\x10\x19J\x00\x99\xb7\x8d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4 \x02d\xa0\x9f\x8ch\xe3\xe6b\x97\x95(\x0fV%O\x86@\u0409\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4 \x03qy\a\xa7%`\xf40\u007f\x1b\xee\xccT6\xf4=!\xe7\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4 \r\xfc\vq\xe3Y\xb2\xb4eD\n6\xa6\xcd\xc3Rw0\a\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4 \x13L\xbf\xf8\x8b\xfa\xdcFkR\xec\ua9d8W\x89\x1d\x83\x1e\x8965\u026d\xc5\u07a0\x00\x00\u07d4 \x14&\x1f\x01\b\x9fSyV0\xba\x9d\xd2O\x9a4\xc2\xd9B\x89Hz\x9a0E9D\x00\x00\u07d4 \x16\x89]\xf3,\x8e\xd5G\x82iF\x84#\xae\xa7\xb7\xfb\xceP\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4 \x18\x1cKA\xf6\xf9r\xb6iX!_\x19\xf5p\xc1]\xdf\xf1\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4 \x18d\xa8\xf7\x84\xc2'{\v|\x9e\xe74\xf7\xb3w\xea\xb6H\x89\xf2(\x14\x00\xd1\xd5\xec\x00\x00\u07d4 \xb8\x1a\xe59&\xac\xe9\xf7\xd7AZ\x05\f\x03\x1dX_ \x89\x12\u007f\x19\xe8>\xb3H\x00\x00\xe0\x94 \x1d\x9e\xc1\xbc\v\x89-C\xf3\xeb\xfa\xfb,\x00\x00\u07d4 \xa1RV\xd5\f\xe0X\xbf\x0e\xacC\xaaS:\xa1n\u0273\x80\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4 \xa2\x9cPy\xe2k?\x181\x8b\xb2\xe5\x0e\x8e\x8b4n[\xe8\x89\x1b\x1a\xb3\x19\xf5\xecu\x00\x00\u07d4 \xa8\x16\x80\xe4e\xf8\x87\x90\xf0\aO`\xb4\xf3_]\x1ej\xa5\x89Ea\x80'\x8f\fw\x80\x00\u07d4 \xb9\xa9\u6f48\x80\u0659J\xe0\r\u0439(*\v\xea\xb8\x16\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4 \u0084\xba\x10\xa2\b0\xfc=i\x9e\xc9}-\xfa'\xe1\xb9^\x89lk\x93[\x8b\xbd@\x00\x00\u07d4 \xd1A\u007f\x99\xc5i\u3fb0\x95\x85e0\xfe\x12\xd0\xfc\uaa89@\x15\xf9K\x11\x83i\x80\x00\u07d4 \u074f\u02f4n\xa4o\u3066\x8b\x8c\xa0\xea[\xe2\x1f\u9949lk\x93[\x8b\xbd@\x00\x00\xe0\x94 \xff>\u078c\xad\xb5\xc3{H\xcb\x14X\x0f\xb6^#\t\n{\x8a\b\xe4\xd3\x16\x82v\x86@\x00\x00\xe0\x94!\x008\x1d`\xa5\xb5J\xdc\t\u0456\x83\xa8\xf6\u057bK\xfb\u02ca\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94!\x18\xc1\x16\xab\f\xdfo\xd1\x1dT\xa40\x93\a\xb4w\xc3\xfc\x0f\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94!\x1b)\xce\xfcy\xae\x97gD\xfd\xeb\u03bd<\xbb2\xc5\x13\x03\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4! l\xe2.\xa4\x80\xe8Y@\xd3\x13\x14\xe0\xd6ONM:\x04\x8965\u026d\xc5\u07a0\x00\x00\u07d4!2\xc0Qj.\x17\x17J\xc5G\xc4;{\x00 \xd1\xebLY\x895e\x9e\xf9?\x0f\xc4\x00\x00\xe0\x94!@\x8bMz,\x0en\xcaAC\xf2\xca\u037b\u033a\x12\x1b\u060a\x04<3\xc1\x93ud\x80\x00\x00\u07d4!Kt9U\xa5\x12\xden\r\x88j\x8c\xbd\x02\x82\xbe\xe6\u04a2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4!L\x89\u017d\x8e}\"\xbcWK\xb3^H\x95\x02\x11\xc6\xf7v\x89\x01\x06T\xf2X\xfd5\x80\x00\xe0\x94!Ti\x14\xdf\u04ef*\xddA\xb0\xff>\x83\xff\xdat\x14\xe1\xe0\x8a\x01C\x95\xe78ZP.\x00\x00\u07d4!X.\x99\xe5\x02\xcb\xf3\xd3\xc2;\xdf\xfbv\xe9\x01\xacmV\xb2\x89\x05k\xc7^-c\x10\x00\x00\u07d4!Y$\b\x13\xa70\x95\xa7\xeb\xf7\u00f3t>\x80(\xae_\t\x89lk\x93[\x8b\xbd@\x00\x00\u07d4!`\xb4\xc0,\xac\n\x81\u0791\b\xdeCE\x90\xa8\xbf\xe6\x875\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94!nA\x86N\xf9\x8f\x06\r\xa0\x8e\xca\xe1\x9a\xd1\x16j\x17\xd06\x8a\x016\x9f\xb9a(\xacH\x00\x00\u07d4!\x84o/\xdfZA\xed\x8d\xf3n^\xd8TM\xf7Y\x88\xec\xe3\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94!\xa6\xdbe'F{\xc6\xda\xd5K\xc1n\x9f\xe2\x95;g\x94\xed\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4!\xa6\xfe\xb6\xab\x11\xc7f\xfd\xd9w\xf8\xdfA!\x15_G\xa1\xc0\x89\x03\x19\xcf8\xf1\x00X\x00\x00\u07d4!\xb1\x82\xf2\xda+8D\x93\xcf_5\xf8=\x9d\x1e\xe1O*!\x89lk\x93[\x8b\xbd@\x00\x00\u07d4!\xbf\xe1\xb4\\\xac\xdebt\xfd\x86\b\u0661x\xbf>\xebn\u0709l\xee\x06\u077e\x15\xec\x00\x00\u07d4!\xc0s\x80HOl\xbc\x87$\xad2\xbc\x86L;Z\xd5\x00\xb7\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94!\u00e8\xbb\xa2g\xc8\u0322{\x1a\x9a\xfa\xba\xd8o`z\xf7\b\x8a\x01\xe4\xa3lI\u06580\x00\x00\u07d4!\xcem[\x90\x18\xce\xc0J\u0596yD\xbe\xa3\x9e\x800\xb6\xb8\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4!\xd0'\x05\xf3\xf6I\x05\xd8\x0e\xd9\x14y\x13\xea\x8cs\a\u0595\x89I\xed\xb1\xc0\x98\x876\x00\x00\u07d4!\xd1?\f@$\xe9g\xd9G\a\x91\xb5\x0f\"\xde:\xfe\xcf\x1b\x89\xf1Z\xd3^.1\xe5\x00\x00\xe0\x94!\xdb\u06c1z\r\x84\x04\u01bd\xd6\x15\x047N\x9cC\xc9!\x0e\x8a\x02\x1e\x18\xb9\xe9\xabE\xe4\x80\x00\xe0\x94!\xdf\x1e\xc2KNK\xfey\xb0\xc0\x95\u03ba\xe1\x98\xf2\x91\xfb\u044a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94!\xdf-\u036ft\xb2\xbf\x804\x04\xddM\xe6\xa3^\xab\xec\x1b\xbd\x8a\x01w\"J\xa8D\xc7 \x00\x00\u07d4!\xe2\x19\u021c\xa8\xac\x14\xaeL\xbaa0\xee\xb7}\x9em9b\x89*\u035f\xaa\xa08\xee\x00\x00\u07d4!\xe5\u04ba\xe9\x95\xcc\xfd\b\xa5\xc1k\xb5$\xe1\xf60D\x8f\x82\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4!\xe5\xd7s 0L \x1c\x1eS\xb2a\xa1#\u0421\x06>\x81\x89\x04\xb6\xfa\x9d3\xddF\x00\x00\xe0\x94!\xea\xe6\xfe\xff\xa9\xfb\xf4\u0347OG9\xac\xe50\u033eY7\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4!\xec\xb2\u07e6Wy\xc7Y-\x04\x1c\xd2\x10Z\x81\xf4\xfdNF\x8965\u026d\xc5\u07a0\x00\x00\u07d4!\uff20\x9b5\x80\xb9\x8es\xf5\xb2\xf7\xf4\xdc\v\xf0,R\x9c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4!\xfd\v\xad\xe5\xf4\xeftt\xd0X\xb7\xf3\xd8T\xcb\x13\x00RN\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94!\xfdG\xc5%`\x12\x19\x8f\xa5\xab\xf11\xc0mj\xa1\x96_u\x8a\x01\xab,\xf7\xc9\xf8~ \x00\x00\u07d4!\xfdl]\x97\xf9\xc6\x00\xb7h!\xdd\xd4\xe7v5\x0f\xce+\xe0\x89lj\u04c2\xd4\xfba\x00\x00\u07d4\"\r\u018d\xf0\x19\xb6\xb0\u033f\xfbxKZZ\xb4\xb1]@`\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\"\x0e+\x92\xc0\xf6\xc9\x02\xb5\x13\xd9\xf1\xe6\xfa\xb6\xa8\xb0\xde\xf3\u05c9+^:\xf1k\x18\x80\x00\x00\u07d4\"V\x1cY1\x14560\x9c\x17\xe82X{b\\9\v\x9a\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\"W\xfc\xa1jn\\*d|<)\xf3l\xe2)\xab\x93\xb1~\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\"]5\xfa\xed\xb3\x91\u01fc-\xb7\xfa\x90q\x16\x04\x05\x99m\x00\x89\t\x18T\xfc\x18bc\x00\x00\u07d4\"_\x9e\xb3\xfbo\xf3\xe9\xe3\xc8D~\x14\xa6n\x8dO7y\xf6\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\"r\x18n\xf2}\xcb\xe2\xf5\xfc70P\xfd\xae\u007f*\xce#\x16\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d4\"s\xba\u05fcNHv\"\xd1u\xefzf\x98\x8bj\x93\xc4\xee\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\"v&K\xec\x85&\xc0\xc0\xf2pgz\xba\xf4\xf0\xe4A\xe1g\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\"\x82B\xf83n\xec\xd8$.\x1f\x00\x0fA\x93~q\xdf\xfb\xbf\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\"\x84*\xb80\xdaP\x99\x13\xf8\x1d\xd1\xf0O\x10\xaf\x9e\xdd\x1cU\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\"\x94O\xbc\xa9\xb5yc\bN\xb8M\xf7\xc8_\xb9\xbc\u07f8V\x89\xfc\x11\x8f\uf43a8\x80\x00\u07d4\"\x9c\xc4q\x1bbu^\xa2\x96DZ\u00f7\u007f\xc63\x82\x1c\xf2\x89\x02#\xe8\xb0R\x192\x80\x00\u0794\"\x9eC\r\xe2\xb7OD&Q\xdd\u0377\x01v\xbc\x05L\xadT\x88\xbb\xf9\x81\xbcJ\xaa\x80\x00\u07d4\"\x9fO\x1a*OT\atP[G\a\xa8\x1d\xe4D\x10%[\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\"\x9f\xf8\v\xf5p\x80\t\xa9\xf79\xe0\xf8\xb5`\x91@\x16\u0566\x89\x12\x11\xec\xb5m\x13H\x80\x00\u07d4\"\xa2X\x12\xabV\xdc\xc4#\x17^\xd1\u062d\xac\xce3\xcd\x18\x10\x89dI\xe8NG\xa8\xa8\x00\x00\xe0\x94\"\xb9j\xb2\xca\xd5]\xb1\x00\xb50\x01\xf9\xe4\xdb7\x81\x04\xc8\a\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\"\xbd\xff\xc2@\xa8\x8f\xf7C\x1a\xf3\xbf\xf5\x0e\x14\xda7\xd5\x18>\x8965\u026d\xc5\u07a0\x00\x00\u07d4\"\xce4\x91Y\xee\xb1D\xef\x06\xff&6X\x8a\xefy\xf6(2\x89\n1\x06+\xee\xedp\x00\x00\u07d4\"\xdbU\x9f,<\x14u\xa2\xe6\xff\xe8:YyY\x91\x96\xa7\xfa\x8965\u026d\xc5\u07a0\x00\x00\u07d4\"\xe1QX\xb5\xee>\x86\xeb\x032\xe3\u6a6cl\u0675^\u0349\b\xacr0H\x9e\x80\x00\x00\u07d4\"\xe2H\x8e-\xa2jI\xae\x84\xc0\x1b\xd5K!\xf2\x94x\x91\u0189]\u0212\xaa\x111\xc8\x00\x00\u07d4\"\xe5\x12\x14\x9a\x18\xd3i\xb7\x86\xc9\xed\xab\xaf\x1d\x89N\xe0.g\x14a\\\x00\x00\u07d4\"\xeb}\xb0\xbaV\xb0\xf8\xb8\x16\u0332\x06\xe6\x15\xd9)\x18[\r\x89\x04])s~\"\xf2\x00\x00\u07d4\"\xee\xd3'\xf8\xeb\x1d\x138\xa3\xcb{\x0f\x8aK\xaaY\a\u0355\x89\x01E]_Hw\b\x80\x00\xe0\x94\"\xf0\x04\u07cd\xe9\xe6\xeb\xf5#\u032c\xe4W\xac\xcb&\xf9r\x81\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u0794\"\xf2\xdc\xffZ\u05cc>\xb6\x85\v\\\xb9Q\x12{e\x95\"\u623e -j\x0e\xda\x00\x00\u07d4\"\xf3\xc7y\xddy\x02>\xa9*x\xb6\\\x1a\x17\x80\xf6-\\J\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\"\xfe\x88M\x907)\x1bMR\xe6(Z\xe6\x8d\xea\v\xe9\xff\xb5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#\x06\u07d3\x1a\x94\rX\xc0\x16e\xfaM\b\x00\x80,\x02\xed\xfe\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94#\t\xd3@\x91D[22Y\v\xd7\x0fO\x10\x02[,\x95\t\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4#\x12\x00F\xf6\x83!\x02\xa7R\xa7fVi\x1c\x86>\x17\u5709\x11\xe0\xe4\xf8\xa5\v\xd4\x00\x00\u07d4#\x1a\x15\xac\xc1\x99\u021f\xa9\xcb\"D\x1c\xc7\x030\xbd\xcc\xe6\x17\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4#\x1d\x94\x15]\xbc\xfe*\x93\xa3\x19\xb6\x17\x1fc\xb2\v\u04b6\xfa\x89\xcf\x14{\xb9\x06\xe2\xf8\x00\x00\u07d4#(2\xcdYw\xe0\nL0\xd0\x16?.$\xf0\x88\xa6\xcb\t\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4#,m\x03\xb5\xb6\xe6q\x1e\xff\xf1\x90\xe4\x9c(\xee\xf3l\x82\xb0\x89Hz\x9a0E9D\x00\x00\xe0\x94#,\xb1\xcdI\x99<\x14J?\x88\xb3a\x1e#5i\xa8k\u058a\x03L`lB\u042c`\x00\x00\u07d4#,\xe7\x82Pb%\xfd\x98`\xa2\xed\xc1Jz0Gsm\xa2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4#/R]U\x85\x9b}N`\x8d H\u007f\xaa\xdb\x00)15\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94#4\u0150\u01e4\x87i\x100E\u0176SL\x8a4i\xf4J\x8a\x03\xb1\x99\a=\xf7-\xc0\x00\x00\u07d4#7n\u02bftl\xe53!\xcfB\xc8fI\xb9+g\xb2\xff\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#7\x8fB\x92m\x01\x84\xb7\x93\xb0\xc8'\xa6\xdd>=3O\u0349\x03\t'\xf7L\x9d\xe0\x00\x00\u07d4#8B\xb1\xd0i/\xd1\x11@\xcfZ\u0364\xbf\x960\xba\xe5\xf8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#9\xe9I(p\xaf\xea%7\xf3\x89\xac/\x83\x83\x02\xa3<\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#;\xdd\xdd]\xa9HR\xf4\xad\xe8\xd2\x12\x88V\x82\xd9\ak\u0189\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4#OF\xba\xb7?\xe4]1\xbf\x87\xf0\xa1\xe0Fa\x99\xf2\ubb09\x1aJ\xba\"\\ t\x00\x00\u07d4#U\x1fV\x97_\xe9+1\xfaF\x9cI\xeaf\xeefb\xf4\x1e\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4#V\x95B\xc9}V`\x18\xc9\a\xac\xfc\xf3\x91\xd1@g\xe8~\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94#_\xa6l\x02^\xf5T\x00p\xeb\xcf\r7-\x81w\xc4g\xab\x8a\a\x12\x9e\x1c\xdf7>\xe0\x00\x00\xe0\x94#r\xc4\xc1\u0253\x9fz\xafl\xfa\xc0@\x90\xf0\x04t\x84\n\t\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4#s\f5z\x91\x02nD\xb1\xd0\xe2\xfc*Q\xd0q\xd8\xd7{\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4#v\xad\xa9\x033\xb1\u0441\bL\x97\xe6E\xe8\x10\xaa[v\xf1\x89(\xa8WBTf\xf8\x00\x00\u07d4#x\xfdC\x82Q\x1e\x96\x8e\u0452\x10g7\xd3$\xf4T\xb55\x8965\u026d\xc5\u07a0\x00\x00\u07d4#\x82\xa9\u050e\xc8>\xa3e(\x90\xfd\x0e\u7710{[-\xc1\x89\a?u\u0460\x85\xba\x00\x00\u07d4#\x83\xc2\"\xe6~\x96\x91\x90\xd3!\x9e\xf1M\xa3xP\xe2lU\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#\x8akv5%/RDHl\n\xf0\xa7: s\x85\xe09\x89JD\x91\xbdm\xcd(\x00\x00\u07d4#\x9as>k\x85Z\u0152\xd6c\x15a\x86\xa8\xa1t\xd2D\x9e\x89X\xbe7X\xb2A\xf6\x00\x00\xe0\x94#\xab\t\xe7?\x87\xaa\x0f;\xe0\x13\x9d\xf0\xc8\xebk\xe5cO\x95\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94#\xab\xd9\xe9>yW\xe5\xb66\xbeey\x05\x1c\x15\xe5\xce\v\x0e\x8a\x03\xa3\xc8\xf7\xcb\xf4,8\x00\x00\u07d4#\xb1\u0111\u007f\xbd\x93\xee=H8\x93\x06\x95s\x84\xa5Il\xbf\x89\xd8\xd8X?\xa2\xd5/\x00\x00\xe0\x94#\xba8d\xdaX=\xabV\xf4 \x87<7g\x96\x90\xe0/\x00\x8a\x02\x13BR\r_\xec \x00\x00\u07d4#\xc5Z\xebW9\x87o\n\xc8\xd7\xeb\xea\x13\xber\x96\x85\xf0\x00\x89Hz\x9a0E9D\x00\x00\u07d4#\u025b\xa0\x87D\x8e\x19\xc9p\x1d\xf6n\f\xabR6\x831\xfa\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#\xcc\xc3\u01ac\xd8\\.F\fO\xfd\xd8+\xc7]\xc8I\xea\x14\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4#\xcd%\x98\xa2\x0e\x14\x9e\xad*\u0593yWn\xce\xdb`\u3389lk\x93[\x8b\xbd@\x00\x00\u07d4#\u07cfH\xee\x00\x92V\xeay~\x1f\xa3i\xbe\xeb\xcfk\xc6c\x89|\xd3\xfa\xc2m\x19\x81\x80\x00\u07d4#\xe2\u01a8\xbe\x8e\n\u03e5\xc4\xdf^6\x05\x8b\xb7\u02ecZ\x81\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#\xeaf\x9e5d\x81\x9a\x83\xb0\xc2l\x00\xa1m\x9e\x82olF\x89M\x8dl\xa9h\xca\x13\x00\x00\u07d4#\xebo\xd8Vq\xa9\x06:\xb7g\x8e\xbe&Z \xf6\x1a\x02\xb3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#\xf9\xec\xf3\xe5\xdd\u0723\x88\x15\xd3\xe5\x9e\xd3K[\x90\xb4\xa3S\x89\v\x17\x81\xa3\xf0\xbb \x00\x00\u07d4#\xfa~\xb5\x1aH\"\x95\x98\xf9~v+\xe0\x86\x96R\xdf\xfcf\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94$\x03\x05rs\x13\xd0\x1esT,w_\xf5\x9d\x11\xcd5\xf8\x19\x8a\x01A\x88Vf\x80\u007f\\\x80\x00\u07d4$\x04k\x91\u069ba\xb6)\u02cb\x8e\xc0\xc3Q\xa0~\a\x03\xe4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4$\x0eU\x9e'J\xae\xf0\xc2X\x99\x8c\x97\x9fg\x1d\x11s\xb8\x8b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94$\x13aU\x9f\xee\xf8\x0e\xf170!S\xbd\x9e\xd2\xf2]\xb3\xef\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94$;;\xcaj)\x93Y\xe8\x86\xce3\xa3\x03A\xfa\xfeMW=\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4$<\x84\xd1$ W\f\xc4\xef;\xab\xa1\xc9Y\u0083$\x95 \x89\u007f\x1fi\x93\xa8S\x04\x00\x00\xe0\x94$CJ>2\xe5N\xcf'/\xe3G\v_oQ/gU \x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4$HYo\x91\xc0\x9b\xaa0\xbc\x96\x10j-7\xb5p^](\x89lk\x93[\x8b\xbd@\x00\x00\u0794$Xn\xc5E\x175\xee\xaa\xebG\r\xc8sj\xaeu/\x82\xe5\x88\xf4?\xc2\xc0N\xe0\x00\x00\u07d4$X\xd6U_\xf9\x8a\x12\x9c\xce@7\x95=\x00 n\xffB\x87\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4$b\x91\x16[Y3-\xf5\xf1\x8c\xe5\u0248V\xfa\xe9X\x97\u0589\\(=A\x03\x94\x10\x00\x00\u07d4$g\u01a5\u0196\xed\xe9\xa1\xe5B\xbf\x1a\xd0k\xccK\x06\xac\xa0\x89\x01\x00\xbd3\xfb\x98\xba\x00\x00\u07d4$v\xb2\xbbu\x1c\xe7H\xe1\xa4\xc4\xff{#\v\xe0\xc1]\"E\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4$z\n\x11\xc5\u007f\x03\x83\xb9I\xdeT\vf\xde\xe6\x86\x04\xb0\xa1\x899\xfb\xae\x8d\x04-\xd0\x00\x00\u07d4$\x87\xc3\u013e\x86\xa2r=\x91|\x06\xb4XU\x01p\xc3\xed\xba\x8965\u026d\xc5\u07a0\x00\x00\u07d4$\x89\xac\x12i4\xd4\u05a9M\xf0\x87C\xda{v\x91\xe9y\x8e\x8965\u026d\xc5\u07a0\x00\x00\u07d4$\x9d\xb2\x9d\xbc\x19\xd1#]\xa7)\x8a\x04\b\x1c1WB\u9b09a\xac\xff\x81\xa7\x8a\xd4\x00\x00\u07d4$\xa4\xeb6\xa7\xe4\x98\xc3o\x99\x97\\\x1a\x8dr\x9f\u05b3\x05\u05c9\r\xfcx!\x0e\xb2\xc8\x00\x00\u07d4$\xa7P\xea\xe5\x87G\x11\x11m\xd7\xd4{q\x86\u0399\r1\x03\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4$\xaa\x11Q\xbbv_\xa3\xa8\x9c\xa5\x0e\xb6\xe1\xb1\xc7\x06A\u007f\u0509\xa8\r$g~\xfe\xf0\x00\x00\u0794$\xac\xa0\x8d[\xe8^\xbb\x9f12\xdf\xc1\xb6 \x82N\xdf\xed\xf9\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4$\xb2\xbe\x11\x8b\x16\u0632\x17Gi\xd1{L\xf8O\a\u0294m\x89lk\x93[\x8b\xbd@\x00\x00\u07d4$\xb8\xb4F\u07bd\x19G\x95]\u0404\xf2\xc5D\x933F\u04ed\x89\xeaim\x90@9\xbd\x80\x00\u07d4$\xb9^\xbe\xf7\x95\x00\xba\xa0\xed\xa7.w\xf8wA]\xf7\\3\x891T\xc9r\x9d\x05x\x00\x00\u07d4$\xb9\xe6dOk\xa4\xcd\xe1&'\r\x81\xf6\xab`\xf2\x86\xdf\xf4\x89\a?u\u0460\x85\xba\x00\x00\u07d4$\xbdY\x04\x05\x90\x91\xd2\xf9\xe1-j&\xa0\x10\xca\"\xab\x14\xe8\x89e\xea=\xb7UF`\x00\x00\u07d4$\xc0\u020bT\xa3TG\t\x82\x8a\xb4\xab\x06\x84\x05Y\xf6\xc5\u2250\xf54`\x8ar\x88\x00\x00\u07d4$\xc1\x17\xd1\u04b3\xa9z\xb1\x1aFy\u025awJ\x9e\xad\xe8\u044965\u026d\xc5\u07a0\x00\x00\u07d4$\xcf\xf0\xe93j\x9f\x80\xf9\xb1\u02d6\x8c\xafk\x1d\x1cI2\xa4\x89\n\xdaUGK\x814\x00\x00\u07d4$\u06aa\xdd\xf7\xb0k\xbc\ua6c0Y\x00\x85\xa8\x85gh+N\x89\x11K \x15\u04bb\xd0\x00\x00\u07d4$\xdc\xc2K\xd9\xc7!\f\xea\u03f3\r\xa9\x8a\xe0JM{\x8a\xb9\x8965\u026d\xc5\u07a0\x00\x00\u07d4$\xf7E\r\xdb\xf1\x8b\x02\x0f\xeb\x1a 2\xd9\xd5Kc>\xdf7\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4$\xfcs\xd2\a\x93\t\x8e\t\u076bW\x98Pb$\xfa\x1e\x18P\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4$\xfd\x9al\x87L/\xab?\xf3n\x9a\xfb\xf8\xce\r2\xc7\u0792\x89Hz\x9a0E9D\x00\x00\u07d4%\n@\xce\xf3 #\x97\xf2@F\x95H\xbe\xb5bj\xf4\xf2<\x89\x05\x03\xb2\x03\xe9\xfb\xa2\x00\x00\u07d4%\niC\av\xf64w\x03\xf9R\x97\x83\x95Za\x97\xb6\x82\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4%\x0e\xb7\xc6o\x86\x9d\xdfI\u0685\xf39>\x98\f\x02\x9a\xa44\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4%\x10j\xb6u]\xf8mkc\xa1\x87p;\f\xfe\xa0\u5520\x89\x01|@Z\xd4\x1d\xb4\x00\x00\xe0\x94%\x18_2Z\xcf-dP\x06\x98\xf6\\v\x9d\xdfh0\x16\x02\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4%\x1c\x12r,hy\"y\x92\xa3\x04\xeb5v\xcd\x18CN\xa5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4%\x1eh8\xf7\xce\u0173\x83\xc1\xd9\x01F4\x12t\xda\xf8\xe5\x02\x89\a\xff\x1c\xcbua\xdf\x00\x00\u07d4%%\x9d\x97Z!\xd8:\xe3\x0e3\xf8\x00\xf5?7\u07e0\x198\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4%({\x81_\\\x828\ns\xb0\xb1?\xba\xf9\x82\xbe$\xc4\u04c9\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94%+eU\xaf\u0700\xf2\xd9m\x97-\x17\u06c4\xeaZ\xd5!\xac\x8a\x01\xab,\xf7\xc9\xf8~ \x00\x00\u07d4%8S)6\x81<\x91\xe6S(O\x01|\x80\u00f8\xf8\xa3o\x89l\x87T\xc8\xf3\f\b\x00\x00\xe0\x94%>2\xb7N\xa4I\n\xb9&\x06\xfd\xa0\xaa%{\xf2=\u02cb\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94%?\x1et*,\uc1b0\u05f3\x06\xe5\xea\xcbl\xcb/\x85T\x8a\x04>^\xde\x1f\x87\x8c \x00\x00\u07d4%A1J\v@\x8e\x95\xa6\x94DIwq*Pq5\x91\xab\x89X\x9e\x1a]\xf4\u05f5\x00\x00\u07d4%L\x1e\xccc\f(w\u0780\x95\xf0\xa8\u06e1\xe8\xbf\x1fU\f\x89\\(=A\x03\x94\x10\x00\x00\u07d4%Z\xbc\x8d\b\xa0\x96\xa8\x8f=j\xb5_\xbcsR\xbd\u0739\u0389\x04t6\x821>\u0780\x00\u07d4%[\xdddt\u0302b\xf2j\"\u00cfE\x94\x0e\x1c\ue99b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4%`\xb0\x9b\x89\xa4\xaehI\xedZ<\x99XBf1qDf\x89\\(=A\x03\x94\x10\x00\x00\u07d4%a\xa18\xdc\xf8;\xd8\x13\xe0\xe7\xf1\bd+\xe3\xde=o\x05\x8964\xf4\x84\x17@\x1a\x00\x00\u0794%a\xec\x0f7\x92\x18\xfe^\xd4\xe0(\xa3\xf7D\xaaAuLr\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u0794%b\x92\xa1\x91\xbd\xda4\xc4\xdakk\u0591G\xbfu\u2a6b\x88\xc2\xff.\r\xfb\x03\x80\x00\u07d4%i~\xf2\f\u032ap\xd3-7o\x82r\xd9\xc1\a\f=x\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4%o\xa1P\u0307\xb5\x05j\a\xd0\x04\xef\xc8E$s\x9eb\xb5\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4%r\x1c\x87\xb0\xdc!7|r\x00\xe5$\xb1J\"\xf0\xafi\xfb\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4%\x899\xbb\xf0\f\x9d\xe9\xafS8\xf5\xd7\x14\xab\xf6\xd0\xc1\xc6q\x89T\x06\x923\xbf\u007fx\x00\x00\xe0\x94%\x90\x12hp\xe0\xbd\xe8\xa6c\xab\x04\nr\xa5W=\x8dA\u008a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4%\x9e\xc4\xd2e\xf3\xabSk|p\xfa\x97\xac\xa1Bi,\x13\xfc\x89\x01\x1b\x1b[\xea\x89\xf8\x00\x00\xe0\x94%\xa5\x00\xee\xeczf*\x84\x15R\xb5\x16\x8bp{\r\xe2\x1e\x9e\x8a\x02\x1f/o\x0f\xc3\xc6\x10\x00\x00\xe0\x94%\xa5\xa4M8\xa2\xf4Lj\x9d\xb9\u037ck\x1e.\x97\xab\xb5\t\x8a\x03\x99\x92d\x8a#\u0220\x00\x00\u07d4%\xa7L*\xc7]\u023a\xa8\xb3\x1a\x9c|\xb4\xb7\x82\x9b$V\u0689lk\x93[\x8b\xbd@\x00\x00\xe0\x94%\xad\xb8\xf9o9I,\x9b\xb4|^\u0708bNF\aV\x97\x8a\x05\xa9\x94\v\xc5hyP\x00\x00\u07d4%\xae\xe6\x8d\t\xaf\xb7\x1d\x88\x17\xf3\xf1\x84\xecV/x\x97\xb74\x89lk\x93[\x8b\xbd@\x00\x00\u07d4%\xb0S;\x81\xd0*a{\x92)\xc7\xec]o/g.[Z\x8965\u026d\xc5\u07a0\x00\x00\u07d4%\xb7\x8c\x9f\xad\x85\xb43C\xf0\xbf\xcd\x0f\xac\x11\u0254\x9c\xa5\xeb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4%\xbcI\xef(\x8c\xd1e\xe5%\xc6a\xa8\x12\u03c4\xfb\xec\x8f3\x89\x12Y!\xae\xbd\xa9\xd0\x00\x00\u07d4%\xbd\xfa>\xe2o8Ia{#\x00bX\x8a\x97\xe3\xca\xe7\x01\x8965\xe6\x19\xbb\x04\xd4\x00\x00\u07d4%\xc1\xa3~\xe5\xf0\x82e\xa1\xe1\r=\x90\xd5G)U\xf9x\x06\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4%\xc6\xe7O\xf1\xd9(\u07d8\x13z\xf4\u07c40\xdf$\xf0|\u05c9\x15$VU\xb1\x02X\x00\x00\xe0\x94%\xcf\xc4\xe2\\5\xc1;i\xf7\xe7}\xbf\xb0\x8b\xafXuk\x8d\x8a\bxg\x83&\xea\xc9\x00\x00\x00\xe0\x94%\xda\u0515\xa1\x1a\x86\xb9\xee\xec\xe1\xee\xec\x80^W\xf1W\xfa\xff\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\xe0\x94%\xe07\xf0\n\x18'\v\xa5\xec4 \"\x9d\xdb\n,\u33e2\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4%\xe6a\xc99\x86:\xcc\x04No\x17\xb5i\x8c\xce7\x9e\xc3\u0309JD\x91\xbdm\xcd(\x00\x00\u07d4&\x04\x8f\xe8M\x9b\x01\nb\xe71b~I\xbc.\xb7?@\x8f\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4&\x06\u00f3\xb4\xca\x1b\t\x14\x98`,\xb1\x97\x8b\xf3\xb9R!\xc0\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4&\n#\x0eDe\a~\v\x14\xeeDB\xa4\x82\u0570\xc9\x14\xbf\x89Z\xf6\x06\xa0k[\x11\x80\x00\u07d4&\r\xf8\x94:\x8c\x9a]\xbayE2\u007f\xd7\xe0\x83|\x11\xad\a\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4&\x14\xf4-]\xa8D7ux\xe6\xb4H\xdc$0[\xef+\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4&\x15\x10\x0e\xa7\xe2[\xba\x9b\xcat`X\xaf\xbb\xb4\xff\xbeBD\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4&\x15u\xe9\xcfY\xc8\"o\xa7\xaa\xf9\x1d\xe8o\xb7\x0fZ\u00ee\x89\x10C\xa4CjR?\x00\x00\xe0\x94&\x1e\x0f\xa6LQ\x13te\xee\xcf[\x90\xf1\x97\xf7\x93\u007f\xdb\x05\x8a\x03\xcf\xc8.7\xe9\xa7@\x00\x00\u07d4&*\x8b\xfd}\x9d\xc5\xdd:\u05c1a\xb6\xbbV\b$76U\x89?j\x83\x84\a+v\x00\x00\xe0\x94&*\xedK\xc0\xf4\xa4\xb2\xc6\xfb5y>\x83ZI\x18\x9c\xdf\xec\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94&-\xc16L\xcfm\xf8\\C&\x8e\xe1\x82UM\xaei.)\x8a\x01\v /\xect\xce\xd8\x00\x00\u07d4&8\x140\x9d\xe4\xe65\xcfX^\r6Tw\xfc@\xe6l\xf7\x89\a\xea(2uw\b\x00\x00\u07d4&9\xee\xe9\x87<\xee\xc2o\u0314T\xb5H\xb9\xe7\xc5J\xa6\\\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94&>W\xda\xcb\xe0\x14\x9f\x82\xfee\xa2fH\x98\x86o\xf5\xb4c\x8a\b\v\xfb\xef\xcb_\v\xc0\x00\x00\u07d4>\x19\xc0m_\x14z\xa5\x97$\x8e\xb4l\xf7\xbe\xfad\xa5\x89X\xe7\x92n\xe8X\xa0\x00\x00\u07d4&L\xc8\bj\x87\x10\xf9\x1b!r\t\x05\x91,\u05d6J\xe8h\x89\x01s\x17\x90SM\xf2\x00\x00\xe0\x94&S\x83\u058bR\xd04\x16\x1b\xfa\xb0\x1a\xe1\xb0G\x94/\xbc2\x8a\x04rq\xde\xe2\rt\\\x00\x00\u07d4&Y\xfa\xcb\x1e\x83CeS\xb5\xb4)\x89\xad\xb8\a_\x99S\xed\x89\x01\x97evw\x1a^\x00\x00\xe0\x94&o-\xa7\xf0\b^\xf3\xf3\xfa\t\xba\xee#+\x93\xc7D\xdb.\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4&qH\xfdr\xc5Ob\nY/\xb9'\x991\x9c\xc4S+\\\x89\x169\u46fa\x16(\x00\x00\xe0\x94&xJ\u0791\u0228:\x8e9e\x8c\x8d\x82wA<\u0319T\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4&z~n\x82\xe1\xb9\x1dQ\xde\u0776D\xf0\xe9m\xbb\x1f\u007f~\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4&\x80q=@\x80\x8e*P\xed\x011P\xa2\xa6\x94\xb9j\u007f\x1d\x89a\t=|,m8\x00\x00\u07d4&\x97\xb39\x81;\f-\x96K$q\xeb\x1c`oN\u02d6\x16\x89>\x8e\xf7\x95\u0610\xc8\x00\x00\u07d4&\xa6\x8e\xab\x90Z\x8b=\xce\x00\xe3\x170\x82%\u06b1\xb9\xf6\xb8\x89kV\x05\x15\x82\xa9p\x00\x00\u07d4&\xb1\x1d\x06e\x88\xcet\xa5r\xa8Zc(s\x92\x12\xaa\x8b@\x89lk\x93[\x8b\xbd@\x00\x00\u07d4&\xba\xbfB\xb2g\xfd\xcf8a\xfd\xd4#j^GHH\xb3X\x8965\u026d\xc5\u07a0\x00\x00\u07d4&\xc0\x05Kp\r:|-\xcb\xe2uh\x9dOL\xad\x16\xa35\x89lk\x93[\x8b\xbd@\x00\x00\u07d4&\xc2\xff\xc3\x0e\xfd\xc5'>v\x18:\x16\xc2i\x8dnS\x12\x86\x89*\x11)\u0413g \x00\x00\u07d4&\u025f\x88I\u0240+\x83\xc8a!\u007f\xd0z\x9e\x84\u0377\x9d\x89\x10CV\x1a\x88)0\x00\x00\u07d4&\xcf\xff\xd0R\x15+\xb3\xf9W\xb4x\xd5\xf9\x8b#:|+\x92\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4&\u0521h\x91\xf5)\"x\x92\x17\xfc\u0606\xf7\xfc\xe2\x96\xd4\x00\x89lk\x93[\x8b\xbd@\x00\x00\u07d4&\xd4\xec\x17\xd5\u03b2\u0214\xbd\u015d\nji]\xad+C\u0309\x9f\x1fxv\x1d4\x1a\x00\x00\u07d4&\xe8\x01\xb6,\x82q\x91\xddh\xd3\x1a\x01\x19\x90\x94\u007f\xd0\xeb\xe0\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4&\xe9\xe2\xadr\x97\x02bd\x17\xef%\xde\r\xc8\x00\xf7\xa7y\xb3\x8965\u026d\xc5\u07a0\x00\x00\u07d4&\xf9\xf7\xce\xfd~9K\x9d9$A+\xf2\u0083\x1f\xaf\x1f\x85\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94&\xfe\x17L\xbfRfP\xe0\xcd\x00\x9b\xd6\x12e\x02\u038ehM\x8a\x02w\x01s8\xa3\n\xe0\x00\x00\xe0\x94&\xff\nQ\xe7\xce\u0384\x00'ix\xdb\xd6#n\xf1b\xc0\xe6\x8a\x15.\x18V'T\nP\x00\x00\u07d4'\x10\x1a\x0fV\u04da\x88\u0168O\x9b2L\xdd\xe3>\\\xb6\x8c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4'\x14L\xa9\xa7w\x1a\x83j\xd5\x0f\x80?d\xd8i\xb2\xae+ \x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4'\x14i\x13V:\xa7E\xe2X\x840\xd94\x8e\x86\xea|5\x10\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4'\x1d=H\x1c\xb8\x8evq\xad!iI\xb66^\x060=\xe0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4' \xf9\xcaBn\xf2\xf2\xcb\xd2\xfe\xcd9\x92\fO\x1a\x89\xe1m\x89lk\x93[\x8b\xbd@\x00\x00\u07d4'*\x13\x1aZejz:\xca5\u023d \"\"\xa7Y\"X\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4'D\xffgFA!\xe3Z\xfc)\"\x17qd\xfa/\xcb\x02g\x89\x05k\xc7^-c\x10\x00\x00\u07d4'J=w\x1a=p\x97\x96\xfb\xc4\xd5\xf4\x8f\xce/\xe3\x8cy\u0589\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4'Mi\x17\x0f\xe7\x14\x14\x01\x88+\x88j\xc4a\x8cj\xe4\x0e\u06c93\xc5I\x901r\f\x00\x00\u07d4'R\x1d\xeb;n\xf1An\xa4\u01c1\xa2\xe5\u05f3n\xe8\x1ca\x89lk\x93[\x8b\xbd@\x00\x00\u07d4'Xu\xffO\xbb\f\xf3\xa40!1'H\u007fv\b\xd0L\xba\x89\x1b\x1c\x01\x0evmX\x00\x00\u07d4'j\x00n0(\xec\xd4L\xdbb\xba\nw\u0394\xeb\xd9\xf1\x0f\x89a\x94\x04\x9f0\xf7 \x00\x00\u07d4'k\x05!\xb0\xe6\x8b'}\xf0\xbb2\xf3\xfdH2cP\xbf\xb2\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4'o\xd7\xd2O\x8f\x88?Zz()[\xf1qQ\u01e8K\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4'p\xf1N\xfb\x16]\u07bay\xc1\v\xb0\xaf1\xc3\x1eY3L\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4'vw\xab\xa1\xe5,;S\xbf\xa2\a\x1dN\x85\x9a\n\xf7\xe8\xe1\x8965\u026d\xc5\u07a0\x00\x00\u07d4'\x82Ff\xd2x\xd7\x04#\xf0=\xfe\x1d\u01e3\xf0/C\u2d4966\xc2^f\xec\xe7\x00\x00\u07d4'\x83\f_`#\xaf\xaa\xf7\x97Egl J\x0f\xac\u0360\xba\x89\r\x02\xabHl\xed\xc0\x00\x00\xe0\x94'\x84\x90?\x1d|\x1b\\\xd9\x01\xf8\x87]\x14\xa7\x9b<\xbe*V\x8a\x04\xbd\xa7\xe9\xd7J\xd5P\x00\x00\u07d4'\x8c\v\xdec\x0e\u00d3\xb1\xe7&\u007f\xc9\xd7\xd9p\x19\xe4\x14[\x89lk\x93[\x8b\xbd@\x00\x00\u07d4'\x98q\x10\"\x1a\x88\b&\xad\xb2\xe7\xab^\xcax\xc6\xe3\x1a\xec\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94'\xac\a;\xe7\x9c\xe6W\xa9:\xa6\x93\xeeC\xbf\x0f\xa4\x1f\xef\x04\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4'\xb1iN\xaf\xa1e\xeb\xd7\xcc{\u025et\x81J\x95\x14\x19\u0709+^:\xf1k\x18\x80\x00\x00\u07d4'\xb6(\x16\xe1\xe3\xb8\u045by\xd1Q=]\xfa\x85[\f:*\x89\x05j\xf5\xc1\xfdiP\x80\x00\u07d4'\xbf\x94<\x163\xfe2\xf8\xbc\xcc\xdbc\x02\xb4\a\xa5rND\x892\xf8Lm\xf4\b\xc0\x80\x00\u07d4'\xbf\x9fD\xba}\x05\xc35@\u00e5;\xb0,\xbb\xff\xe7\xc3\u0189lk\x93[\x8b\xbd@\x00\x00\u07d4'\xc2\xd7\xcaPM\xaa=\x90f\xdc\t\x13}\xc4/:\xaa\xb4R\x89 \x86\xac5\x10R`\x00\x00\u07d4'\xd1X\xac=>\x11\t\xabnW\x0e\x90\xe8]8\x92\xcdv\x80\x89\x05k\xc7^-c\x10\x00\x00\u07d4'\xe69\x89\xca\x1e\x90;\xc6 \xcf\x1b\x9c?g\xb9\xe2\xaee\x81\x89Hz\x9a0E9D\x00\x00\xe0\x94'\xf0<\xf1\xab\xc5\xe1\xb5\x1d\xbcDK(\x9eT,\x9d\u07f0\xe6\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4'\xfc\x85\xa4\x9c\xff\x90\xdb\xcf\xda\u071d\xdd@\u05b9\xa2!\nl\x89\x05k\xc7^-c\x10\x00\x00\u07d4(\x05A^\x1d\u007f\xde\xc6\xde\u07f8\x9eR\x1d\x10Y-t<\x10\x89\x05k\xc7^-c\x10\x00\x00\u07d4(\a>\xfc\x17\xd0\\\xab1\x95\xc2\xdb3+a\x98Gw\xa6\x12\x8965\u026d\xc5\u07a0\x00\x00\u07d4(\x12P\xa2\x91!'\nN\xe5\u05cd$\xfe\xaf\xe8,p\xba:\x8965\u026d\xc5\u07a0\x00\x00\u07d4(\x13\xd2c\xfc_\xf2G\x9e\x97\x05\x95\u05b6\xb5`\xf8\xd6\xd6\u0449lk\x93[\x8b\xbd@\x00\x00\u07d4(.\x80\xa5T\x87ZVy\x9f\xa0\xa9\u007fU\x10\u7557LN\x8965\u026d\xc5\u07a0\x00\x00\u07d4(3\x96\xce<\xac9\x8b\xcb\xe7\"\u007f2>x\xff\x96\u0407g\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4(4\x9f~\xf9t\xeaU\xfe6\xa1X;4\xce\xc3\xc4Pe\xf0\x89\f\xb63\u051eeY\x00\x00\u07d4(6\x120F\xb2\x84\xe5\xef\x10+\xfd\"\xb1v^P\x81\x16\xad\x89\x16S\xfb\xb5\xc4'\xe4\x00\x00\u07d4(<#\x14(<\x92\u0530d\xf0\xae\xf9\xbbRF\xa7\x00\u007f9\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4(>\x11 7I\xb1\xfaO2\xfe\xbbq\xe4\x9d\x13Y\x198*\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94(>bR\xb4\xef\xcfFT9\x1a\xcbu\xf9\x03\u015bx\xc5\xfb\x8a\x02\x8a\x85t%Fo\x80\x00\x00\xe0\x94(Q\x0en\xff\x1f\xc8)\xb6WoC(\xbc98\xecze\x80\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4(X\xac\xac\xaf!\xea\x81\u02b7Y\x8f\xdb\xd8kE.\x9e\x8e\x15\x89$\x1a\x9bOaz(\x00\x00\u07d4(Z\xe5\x1b\x95\x00\u014dT\x13e\xd9ui\xf1K\xb2\xa3p\x9b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4(f\xb8\x1d\xec\xb0.\xe7\n\xe2P\xce\xe5\xcd\xc7{Y\u05f6y\x89lk\x93[\x8b\xbd@\x00\x00\u07d4(i\x06\xb6\xbdIr\xe3\xc7\x16U\xe0K\xaf6&\f|\xb1S\x89\x12nr\xa6\x9aP\xd0\x00\x00\u07d4(k\x18ma\xea\x1f\u05cd\x990\xfe\x12\xb0e7\xb0\\=Q\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94(t\xf3\xe2\x98]_{@f'\xe1{\xaaw+\x01\xab\u031e\x8a\x01F\x05\x04\x10v_8\x00\x00\xe0\x94(|\xf9\u0410.\xf8\x19\xa7\xa5\xf1ID[\xf1w^\xe8\xc4|\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4(\x81\x8e\x18\xb6\x10\x00\x13!\xb3\x1d\xf6\xfe}(\x15\u036d\xc9\xf5\x8965\u026d\xc5\u07a0\x00\x00\u07d4(\x86\x83$3~\x11\xba\x10l\xb4\x81\u0696/:\x84S\x80\x8d\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94(\x90K\xb7\xc40)C\xb7\t\xb1Myp\xe4+\x83$\u184a\x02\x1f\x97\x84j\a-~\x00\x00\u07d4(\x95\xe8\t\x99\xd4\x06\xadY.+&'7\xd3_}\xb4\xb6\x99\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4(\x96r\x80!N!\x8a\x12\f]\xda7\x04\x1b\x11\x1e\xa3mt\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4(\xa3\xda\t\xa8\x19H\x19\xae\x19\x9f.m\x9d\x13\x04\x81~(\xa5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4(\xab\x16_\xfbi\xed\xa0\xc5I\xae8\xe9\x82o_\u007f\x92\xf8S\x89FM\xf6\xd7\xc8DY\x00\x00\u07d4(\xb7u\x85\xcb=U\xa1\x99\xab)\x1d:\x18\u018f\u8684\x8a\x89j@v\xcfy\x95\xa0\x00\x00\xe0\x94(\xd4\xeb\xf4\x1e=\x95\xf9\xbb\x9a\x89u#\\\x1d\x009>\x80\x00\u07d4)\nV\xd4\x1fn\x9e\xfb\xdc\xea\x03B\u0dd2\x9a\x8c\xdf\xcb\x05\x89\x12\xa5\xf5\x81h\xee`\x00\x00\u07d4)\x15bK\xcbg\x917\xb8\xda\xe9\xabW\xd1\x1bI\x05\xea\xeeK\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4)\x1e\xfe\x00\x81\xdc\xe8\xc1G\x99\xf7\xb2\xa46\x19\xc0\u00f3\xfc\x1f\x89A\rXj \xa4\xc0\x00\x00\u07d4)\x1f\x92\x9c\xa5\x9bT\xf8D>=Mu\xd9]\xee$<\xefx\x89\x1b\x1a\b\x927\a=\x00\x00\xe0\x94))\x8c\xcb\xdf\xf6\x89\xf8\u007f\xe4\x1a\xa6\xe9\x8f\u07f5=\xea\xf3z\x8a\x041\\2\xd7\x1a\x9e`\x00\x00\u07d4)/\"\x8b\n\x94t\x8c\x8e\xeca-$o\x98\x93c\xe0\x8f\b\x89\n\ad\a\xd3\xf7D\x00\x00\u07d4)3\x84\xc4+o\x8f)\x05\xceR\xb7 \\\"t7la+\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4)4\xc0\xdf{\xbc\x17+l\x18k\vrTz\u038b\xf7TT\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4)<#\x06\xdf6\x04\xaeO\xda\r z\xbasog\xde\a\x92\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94)I\xfd\x1d\xef\\v\xa2\x86\xb3\x87$$\x80\x9a\a\xdb9f\xf3\x8a\x01\x1b\xd9\x06\u06a0\xc9C\x80\x00\u07d4)OIK?.\x14\xa3\xf8\xab\x00\x00\x00\u07d4)U\xc3W\xfd\x8fu\xd5\x15\x9a=\xfai\u0178z5\x9d\ua309lk\x93[\x8b\xbd@\x00\x00\u07d4)a\xfb9\x1ca\x95|\xb5\xc9\xe4\a\u0762\x938\u04f9,\x80\x8964\xfb\x9f\x14\x89\xa7\x00\x00\u07d4)h\x1d\x99\x12\xdd\xd0~\xaa\xbb\x88\xd0]\x90\xf7f\xe8bA}\x8965\u026d\xc5\u07a0\x00\x00\u07d4)kq\xc0\x01X\x19\xc2B\xa7\x86\x1eo\xf7\xed\xed\x8a_q\xe3\x89lh\xcc\u041b\x02,\x00\x00\u07d4)mf\xb5!W\x1aNA\x03\xa7\xf5b\xc5\x11\xe6\xaas-\x81\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4)o\x00\xde\x1d\u00fb\x01\xd4z\x8c\xcd\x1e]\x1d\u0661\xebw\x91\x8965\u026d\xc5\u07a0\x00\x00\u07d4)s\x85\xe8\x864FV\x85\xc21\xa3\x14\xa0\xd5\xdc\xd1F\xaf\x01\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4)v=\xd6\u069a|\x16\x11s\x88\x83!\ub9b6<\x8f\xb8E\x89\x11\xc7\xea\x16.x \x00\x00\u07d4)yt\x11t\xa8\xc1\xea\v\u007f\x9e\xdfe\x81w\x85\x94\x17\xf5\x12\x89\x19\x01\x96l\x84\x96\x83\x80\x00\u07d4)z\x88\x92\x1b_\xca\x10\u5edd\xed`\x02T7\xae\"\x16\x94\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94)}]\xbe\"//\xb5%1\xac\xbd\v\x01=\xc4F\xacsh\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4)\x82N\x94\xccCH\xbc\x962y\xdc\xdfG9\x17\x152L\u04c9i*\xe8\x89p\x81\xd0\x00\x00\u07d4)\x82\xd7j\x15\xf8G\xddA\xf1\x92*\xf3h\xfeg\x8d\x0eh\x1e\x89\x05k\xc7^-c\x10\x00\x00\u07d4)\x88\x87\xba\xb5|[\xa4\xf0aR)\xd7R_\xa1\x13\xb7\ua249\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94)\x8e\xc7kD\r\x88\a\xb3\xf7\x8b_\x90\x97\x9b\xeeB\xedC\u06ca\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4)\x93h`\x90B\xa8X\xd1\xec\xdf\x1f\xc0\xad\xa5\xea\xce\xca)\u03c9lk\x93[\x8b\xbd@\x00\x00\u07d4)\x9e\v\xcaU\xe0i\u0785\x04\xe8\x9a\xcan\xca!\u04ca\x9a]\x89\x03\x027\x9b\xf2\xca.\x00\x00\u07d4)\xac+E\x84T\xa3l~\x96\xc7:\x86g\"*\x12$,q\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94)\xad\u03c3\xb6\xb2\n\u01a44\xab\xb1\x99<\xbd\x05\xc6\x0e\xa2\xe4\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94)\xae\xf4\x8d\xe8\xc9\xfb\xadK\x9eL\xa9pyzU3\xebr-\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4)\xb3\xf5a\xeezn%\x94\x1e\x98\xa52[x\xad\u01d7\x85\xf3\x89\x05k\xc7^-c\x10\x00\x00\u07d4)\xbd\xc4\xf2\x8d\xe0\x18\x0fC<&\x94\xebt\xf5PL\xe9C7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4)\u0300M\x92+\xe9\x1fY\t\xf3H\xb0\xaa\xa5\xd2\x1b`x0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94)\xda>5\xb2;\xb1\xf7/\x8e\"X\xcf\u007fU3Y\xd2K\xac\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94)\xe6y\x90\xe1\xb6\xd5.\x10U\xff\xe0I\xc51\x95\xa8\x15B\u03ca\x04<3\xc1\x93ud\x80\x00\x00\u07d4)\uab82v\x17b\xf4\xd2\xdbS\xa9\u018b\x0fk\vmNf\x89lk\x93[\x8b\xbd@\x00\x00\u07d4)\xeb~\xef\xda\xe9\xfe\xb4I\xc6?\xf5\xf2y\xd6u\x10\xeb\x14\"\x89\x01\r:\xa56\xe2\x94\x00\x00\u07d4)\xf0\xed\xc6\x038\xe7\x11 \x85\xa1\xd1\x14\u068cB\u038fU\u0589\xa0Z\u007f\x0f\xd8%x\x00\x00\u07d4)\xf8\xfb\xa4\xc3\ar\xb0W\xed\xbb\xe6*\xe7B\f9\x05r\xe1\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94)\xf9(l\x0es\x8d\x17!\xa6\x91\u01b9Z\xb3\u0667\x97\xed\xe8\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4*\b^%\xb6Hb\xf5\xe6\x8dv\x8e+\x0fz\x85)\x85\x8e\xee\x89k\x88:\xcdWf\xcd\x00\x00\u07d4**\xb6\xb7Lz\xf1\xd9Gk\xb5\xbc\xb4RG\x97\xbe\xdc5R\x8965\u026d\xc5\u07a0\x00\x00\u07d4*9\x19\nO\u0783\u07f3\xdd\xcbL_\xbb\x83\xaclIu\\\x8965\u026d\xc5\u07a0\x00\x00\u07d4*@\r\xff\x85\x94\xder(\xb4\xfd\x15\xc3#\"\xb7[\xb8}\xa8\x89\x051\xa1\u007f`z-\x00\x00\xe0\x94*D\xa7!\x8f\xe4Me\xa1\xb4\xb7\xa7\u0671\xc2\xc5,\x8c>4\x8a\r-\x06\xc3\x05\xa1\xebW\x80\x00\u07d4*F\xd3Swqv\xff\x8e\x83\xff\xa8\x00\x1fOp\xf9s:\xa5\x89\x05\xbf\v\xa6cOh\x00\x00\u07d4*Y_\x16\xee\xe4\xcb\f\x17\u0662\xd99\xb3\xc1\x0flgrC\x89;\xa1\x91\v\xf3A\xb0\x00\x00\u07d4*Y\xe4~\xa5\xd8\xf0\xe7\xc0(\xa3\xe8\xe0\x93\xa4\x9c\x1bP\xb9\xa3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4*[\xa9\xe3L\u054d\xa5L\x9a'\x12f:;\xe2t\xc8\xe4{\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94*^:@\xd2\xcd\x03%vm\xe7:=g\x18\x96\xb3b\xc7;\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94*cY\x0e\xfe\x99\x86\xc3\xfe\xe0\x9b\n\n3\x8b\x15\xbe\xd9\x1f!\x8a\x01^\x1cN\x05\xee&\xd0\x00\x00\u07d4*gf\n\x13h\xef\xcdbn\xf3k+\x1b`\x19\x80\x94\x1c\x05\x89\a?u\u0460\x85\xba\x00\x00\u07d4*t+\x89\x10\x94\x1e\t2\x83\n\x1d\x96\x92\xcf\u0484\x94\xcf@\x89\x1b\x1a\xb3\x19\xf5\xecu\x00\x00\u07d4*tl\xd4@'\xaf>\xbd7\xc3x\xc8^\xf7\xf7T\xab_(\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4*\x81\xd2|\xb6\xd4w\x0f\xf4\xf3\u0123\xba\x18\xe5\xe5\u007f\aQ|\x89lk\x93[\x8b\xbd@\x00\x00\u07d4*\x91\xa9\xfe\xd4\x1b}\x0e\\\xd2\xd81X\xd3\xe8\xa4\x1a\x9a-q\x89i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94*\x9cW\xfe{k\x13\x8a\x92\rgo{\x1a%\x10\x80\xff\xb9\x8a4\xf0\x86\xf3\xb3;h@\x00\x00\u07d4+p\x1d\x16\xc0\xd3\xcc\x1eL\xd8TE\xe6\xad\x02\ue92c\x01-\x89 \x86\xac5\x10R`\x00\x00\xe0\x94+q|\xd42\xa3#\xa4e\x909\x84\x8d;\x87\xde&\xfc\x95F\x8ai\xe1\r\xe7fv\u0400\x00\x00\u07d4+t\xc3s\xd0K\xfb\x0f\xd6\n\x18\xa0\x1a\x88\xfb\xe8Gp\u5309\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4+w\xa4\u060c\rV\xa3\xdb\xe3\xba\xe0J\x05\xf4\xfc\u0477W\xe1\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94+\x84\x88\xbd-<\x19z=&\x15\x18\x15\xb5\xa7\x98\xd2qh\u070a\x01j\x1f\x9f_\xd7\xd9`\x00\x00\u07d4+\x8a\r\xee\\\xb0\xe1\xe9~\x15\xcf\xcan\x19\xad!\xf9\x95\ufb49\x1bUC\x8d\x9a$\x9b\x00\x00\xe0\x94+\x8f\xe4\x16n#\xd1\x19c\xc0\x93+\x8a\u078e\x01E\xea\ap\x8a\t(\x96R\x9b\xad\u0708\x00\x00\xe0\x94+\x99\xb4.OBa\x9e\xe3k\xaa~J\xf2\xd6^\xac\xfc\xba5\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4+\xab\x0f\xbe(\u0544 \xb5 6w\n\x12\xf9\x95*\xeai\x11\x89\xcf\x15&@\xc5\xc80\x00\x00\u07d4+\xad\xe9\x1d\x15E\x17b\x0f\u05349\xac\x97\x15zA\x02\xa9\xf7\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4+\xaf\x8dn\"\x11t\x12H \xeeI+\x94Y\xecO\xad\xaf\xbb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4+\xaf\xbf\x9e\x9e\xd2\xc2\x19\xf7\xf2y\x13t\xe7\xd0\\\xb0gw\xe7\x89\v\xed\x1d\x02c\xd9\xf0\x00\x00\xe0\x94+\xb3f\xb9\xed\xcb\r\xa6\x80\xf0\xe1\v;n(t\x81\x90\xd6\u00ca\x01:b\u05f5v@d\x00\x00\xe0\x94+\xb6\xf5x\xad\xfb\u7ca1\x16\xb3UO\xac\xf9\x96\x98\x13\xc3\x19\x8a\x01\x91'\xa19\x1e\xa2\xa0\x00\x00\u07d4+\xbeb\xea\xc8\f\xa7\xf4\xd6\xfd\xee~}\x8e(\xb6:\xcfw\x0e\x89\x81\xe3-\xf9r\xab\xf0\x00\x00\u07d4+\xbeg*\x18WP\x8fc\x0f*^\xdbV=\x9e\x9d\xe9(\x15\x89lk\x93[\x8b\xbd@\x00\x00\u07d4+\xc4)\xd6\x18\xa6jL\xf8-\xbb-\x82N\x93V\xef\xfa\x12j\x89lj\xccg\u05f1\xd4\x00\x00\u07d4+\xd2R\xe0\xd72\xff\x1d|x\xf0\xa0.l\xb2T#\xcf\x1b\x1a\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4+\xdd\x03\xbe\xbb\xee';l\xa1\x05\x9b4\x99\x9a[\xbda\xbby\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4,\x04\x11\\>R\x96\x1b\r\xc0\xb0\xbf1\xfb\xa4ToYf\xfd\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94,\x06\u0752+aQJ\xaf\xed\xd8D\x88\xc0\u008em\xcf\x0e\x99\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94,\f\xc3\xf9QH,\u0222\x92X\x15hN\xb9\xf9N\x06\x02\x00\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4,\x0e\xe14\u0633aE\xb4{\xee\u7bcd'8\xdb\xda\b\xe8\x89\n\xe5os\x0em\x84\x00\x00\u07d4,\x0f[\x9d\xf46%y\x8e~\x03\xc1\xa5\xfdjm\t\x1a\xf8+\x89\x01\xb0\xfc\xaa\xb2\x000\x00\x00\u07d4,\x12\x8c\x95\xd9W!Q\x01\xf0C\u074f\u0142EmA\x01m\x89-C\xf3\xeb\xfa\xfb,\x00\x00\u07d4,\x18\x00\xf3_\xa0->\xb6\xff[%(_^J\xdd\x13\xb3\x8d\x891\"\u04ed\xaf\xde\x10\x00\x00\u07d4,\x1c\x19\x11N=m\xe2xQHK\x8d'\x15\xe5\x0f\x8a\x10e\x89\x05k\xc7^-c\x10\x00\x00\u07d4,\x1c\xc6\xe1\x8c\x15$\x88\xba\x11\xc2\xcc\x1b\xce\xfa-\xf3\x06\xab\u0449Z\x87\xe7\xd7\xf5\xf6X\x00\x00\xe0\x94,\x1d\xf8\xa7oH\xf6\xb5K\u03dc\xafV\xf0\xee\x1c\xf5z\xb3=\x8a\x02$\u007fu\x00\x89\xdaX\x00\x00\u07d4,!G\x94z\xe3?\xb0\x98\xb4\x89\xa5\xc1k\xff\xf9\xab\xcdN*\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4,#OP\\\xa8\xdc\xc7}\x9b~\x01\xd2W\xc3\x18\xcc\x199m\x89\x05k\xc7^-c\x10\x00\x00\u07d4,$(\xe4\xa6it\xed\xc8\"\xd5\xdb\xfb$\x1b'(\aQX\x89lk\x93[\x8b\xbd@\x00\x00\u07d4,-\x15\xff9V\x1c\x1br\xed\xa1\xcc\x02\u007f\xfe\xf27C\xa1D\x89\u0500\xed\x9e\xf3+@\x00\x00\u07d4,-\xb2\x8c3\t7^\xea1\x82\x1b\x84\xd4\b\x93\x0e\xfa\x1a\u01c9lk\x93[\x8b\xbd@\x00\x00\u07d4,Z-\n\xbd\xa0;\xbe!W\x81\xb4\xff)l\x8ca\xbd\xba\xf6\x89\x01\xa8\xe5oH\xc0\"\x80\x00\u07d4,[}{\x19Z7\x1b\xf9\xab\u0774/\xe0O/\x1d\x9a\x99\x10\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4,]\xf8ffj\x19K&\u03bb@~J\x1f\xd7> \x8d^\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94,`?\xf0\xfe\x93alCW>\xf2y\xbf\xea@\x88\x8dj\xe7\x8a\x01\x00\xf4\xb6\xd6gW\x90\x00\x00\xe0\x94,hF\xa1\xaa\x99\x9a\"F\xa2\x87\x05`\x00\xbaM\u02e8\xe6=\x8a\x02\x1f/o\x0f\xc3\xc6\x10\x00\x00\u0794,j\xfc\xd4\x03|\x1e\xd1O\xa7O\xf6u\x8e\tE\xa1\x85\xa8\xe8\x88\xf4?\xc2\xc0N\xe0\x00\x00\u07d4,ki\x9d\x9e\xad4\x9f\x06\u007fEq\x1a\aJd\x1d\xb6\xa8\x97\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4,o\\\x12L\u01c9\xf8\xbb9\x8e?\x88\x97Q\xbcK`-\x9e\x89\x01Y\xf2\v\xed\x00\xf0\x00\x00\u07d4,\x83\xae\xb0/\xcf\x06}e\xa4p\x82\xfd\x97x3\xab\x1c\uc449\b'8#%\x8a\xc0\x00\x00\xe0\x94,\x89\xf5\xfd\xca=\x15T\t\xb68\xb9\x8at.U\xebFR\xb7\x8a\x14\u06f2\x19\\\xa2(\x90\x00\x00\u07d4,\x96HI\xb1\xf6\x9c\xc7\u03a4D%8\xed\x87\xfd\xf1l\xfc\x8f\x89lk\x93[\x8b\xbd@\x00\x00\u0794,\x9f\xa7,\x95\xf3}\b\xe9\xa3`\t\u7930\u007f)\xba\xd4\x1a\x88\xdfn\xb0\xb2\xd3\xca\x00\x00\u07d4,\xafk\xf4\xec}Z\x19\xc5\xe0\x89z^\xeb\x01\x1d\xce\xceB\x10\x89\a\x93H5\xa01\x16\x00\x00\u07d4,\xb4\xc3\xc1k\xb1\xc5^|kz\x19\xb1'\xa1\xac\x93\x90\xcc\t\x89\xb8'\x94\xa9$O\f\x80\x00\xe0\x94,\xb5IZPS6\xc2FT\x10\xd1\xca\xe0\x95\xb8\xe1\xba\\\u074a\x04<3\xc1\x93ud\x80\x00\x00\u07d4,\xb6\x15\a:@\xdc\u06d9\xfa\xa8HW.\x98{;\x05n\xfb\x89+X\xad\u06c9\xa2X\x00\x00\u07d4,\xbam]\r\xc2\x04\xea\x8a%\xad\xa2\xe2oVu\xbd_/\u0709H#\xef}\u06da\xf3\x80\x00\u07d4,\xbb\fs\u07d1\xb9\x17@\xb6i;wJ}\x05\x17~\x8eX\x89dI\xe8NG\xa8\xa8\x00\x00\u07d4,\xcbfIM\n\xf6\x89\xab\xf9H=6]x$D\xe7\u07ad\x8965\u026d\xc5\u07a0\x00\x00\u07d4,\xcc\x1f\x1c\xb5\xf4\xa8\x00.\x18k \x88]\x9d\xbc\x03\f\b\x94\x89lk\x93[\x8b\xbd@\x00\x00\u07d4,\u03c0\xe2\x18\x98\x12^\xb4\xe8\a\u0342\xe0\x9b\x9d(Y/n\x89lk\x93[\x8b\xbd@\x00\x00\u07d4,\u0456\x94\u0452j\x0f\xa9\x18\x9e\u07ba\xfcg\x1c\xf1\xb2\u02a5\x8965\u026d\xc5\u07a0\x00\x00\u07d4,\u04d34\xac~\xacyrW\xab\xe3sa\x95\xf5\xb4\xb5\xce\x0f\x89\x05kGx^7&\x00\x00\u07d4,\u05de\xb5 '\xb1,\x18\x82\x8e>\xaa\xb2\x96\x9b\xfc\u0487\xe9\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4,\xd8xfV\x8d\xd8\x1a\xd4}\x9d:\u0404nZePss\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4,\xdb9De\x06\x16\xe4|\xb1\x82\xe0`2/\xa1Hyx\u0389b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4,\xe1\x1a\x92\xfa\xd0$\xff+>\x87\xe3\xb5B\xe6\xc6\r\xcb\u0656\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4-\x03&\xb2?\x04\t\xc0\xc0\xe9#hc\xa13\aZ\x94\xba\x18\x89\vg\x9b\xe7[\xe6\xae\x00\x00\u07d4-\r\xecQ\xa6\xe8s0\xa6\xa8\xfa*\x0fe\u060dJ\xbc\xdfs\x89\n\ad\a\xd3\xf7D\x00\x00\u07d4-#vkok\x05s}\xad\x80\xa4\x19\xc4\x0e\xdaMw\x10>\x89\xcf\x15&@\xc5\xc80\x00\x00\u07d4-+\x03#Y\xb3c\x96O\xc1\x1aQ\x82c\xbf\xd0T1\xe8g\x89\b\x1c\x1d\xf7b\x9ep\x00\x00\u07d4-4\x80\xbf\be\aJr\xc7u\x9e\xe5\x13{Mp\xc5\x1c\xe9\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4-5\xa9\xdfbu\u007f\u007f\xfa\xd1\x04\x9a\xfb\x06\xcaJ\xfcFLQ\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4-@U\x8b\x06\xf9\n9#\x14U\x92\x12;gt\xe4n1\xf4\x8965\u026d\xc5\u07a0\x00\x00\u07d4-Bi\x12\xd0Y\xfa\xd9t\v.9\n.\xea\xc0To\xf0\x1b\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4-S-\xf4\xc69\x11\xd1\u0391\xf6\xd1\xfc\xbf\xf7\x96\x0fx\xa8\x85\x89Z\x85\x96\x8aXx\u0680\x00\u07d4-S\x91\xe98\xb3HX\u03d6[\x84\x051\xd5\xef\xdaA\v\t\x89K\xe4\xe7&{j\xe0\x00\x00\xe0\x94-[B\xfcY\xeb\xda\r\xfdf\xae\x91K\u008c\x1b\nn\xf8:\x8a+\u0235\x9f\xdc\xd86c\x80\x00\u07d4-]s5\xac\xb06+G\u07e3\xa8\xa4\xd3\xf5\x94\x95D\u04c0\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94-a\xbf\xc5hs\x92<+\x00\t]\xc3\xea\xa0\xf5\x90\u062e\x0f\x8a\x04ef\xdf\xf8\xceU`\x00\x00\u07d4-e\x11\xfdz8\x00\xb2hT\xc7\xec9\xc0\u0735\xf4\xc4\xe8\xe8\x89\x15\xad\u077a/\x9ew\x00\x00\u07d4-}\\@\u076f\xc4P\xb0Jt\xa4\u06bc+\xb5\xd6e\x00.\x89lk\x93[\x8b\xbd@\x00\x00\u07d4-\x89\xa8\x00jO\x13z \xdc+\xecF\xfe.\xb3\x12\xea\x96T\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4-\x8cR2\x9f8\u04a2\xfa\x9c\xba\xf5\u0143\xda\xf1I\v\xb1\x1c\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4-\x8e\x06\x18\x92\xa5\xdc\xce!\x96j\xe1\xbb\a\x88\xfd>\x8b\xa0Y\x89\r\x8e\\\xe6\x17\xf2\xd5\x00\x00\u07d4-\x8e[\xb8\xd3R\x16\x95\xc7~|\x83N\x02\x91\xbf\xac\xeet\b\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4-\x90\xb4\x15\xa3\x8e.\x19\xcd\xd0/\U000ed069z\xf7\xcb\xf6r\x89\x05\xf3\xc7\xf6A1\xe4\x00\x00\u07d4-\x9b\xado\x1e\xe0*p\xf1\xf1=\xef\\\u0332z\x9a'@1\x89a\t=|,m8\x00\x00\u07d4-\x9c_\xec\u04b4O\xbbj\x1e\xc72\xea\x05\x9fO\x1f\x9d+\\\x896\xca2f\x1d\x1a\xa7\x00\x00\xe0\x94-\xa6\x17iP\t\xccW\xd2j\u0510\xb3*]\xfb\xeb\x93N^\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4-\xa7k|9\xb4 \u323a,\x10 \xb0\x85k\x02pd\x8a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4-\u01ddn\u007fU\xbc\xe2\xe2\xd0\xc0*\xd0|\uca3bR\x93T\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\xe0\x94-\xca\x0eD\x9a\xb6F\xdb\xdf\u04d3\xa9fb\x96\v\u02b5\xae\x1e\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4-\xd3%\xfd\xff\xb9{\x19\x99R\x84\xaf\xa5\xab\xdbWJ\x1d\xf1j\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4-\xd5x\xf7@}\xfb\xd5H\xd0^\x95\xcc\u00dcHT)bj\x89\u3bb5sr@\xa0\x00\x00\u07d4-\xd8\xee\xef\x87\x19J\xbc,\xe7X]\xa1\xe3[|\xeax\f\xb7\x8965\xc6 G9\u0640\x00\u07d4-\xdf@\x90Wi\xbc\xc4&\xcb,)8\xff\xe0w\xe1\u8758\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4-\xe0\x96D\x00\u0082\xbd\u05ca\x91\x9ck\xf7|k_yay\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94-\xe3\x1a\xfd\x18\x9a\x13\xa7o\xf6\xfes\xea\xd9\xf7K\xb5\u0126)\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4-\xec\x982\x9d\x1f\x96\u00e5\x9c\xaay\x81uTR\xd4\xdaI\u0549\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94-\ue422\x8f\x19-gj\x87s#+V\xf1\x8f#\x9e/\xad\x8a\x03\xef\xa7\xe7G\xb6\u046d\x00\x00\xe0\x94.\b\x80\xa3E\x96#\a \xf0Z\xc8\xf0e\xaf\x86\x81\u0736\u008a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4.\fW\xb4qP\xf9Z\xa6\xa7\xe1j\xb9\xb1\xcb\xf5C(\x97\x9a\x89\x05k\xc7^-c\x10\x00\x00\u07d4.\x10\x91\v\xa6\xe0\xbc\x17\xe0UUf\x14\u02c7\t\x0fM~[\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94.$\xb5\x97\x87;\xb1A\xbd\xb27\xea\x8aZ\xb7Gy\x9a\xf0-\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4.(\x10\xde\xe4J\xe4\xdf\xf3\xd8cB\xab\x12fW\xd6S\xc36\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4.,\xbdz\xd8%G\xb4\xf5\xff\x8b:\xb5o\x94*dE\xa3\xb0\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4.-~\xa6k\x9fG\xd8\xccR\xc0\x1cR\xb6\u147c}G\x86\x89\xd8\xd4`,&\xbfl\x00\x00\u07d4.C\x93H\u07caBw\xb2*v\x84W\xd1\x15\x8e\x97\xc4\t\x04\x89*\x1e\x9f\xf2o\xbfA\x00\x00\xe0\x94.F\xfc\xeej;\xb1E\xb5\x94\xa2C\xa3\x91?\xce]\xado\xba\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u0794.G\xf2\x87\xf4\x98#7\x13\x85\r1&\x82<\xc6}\xce\xe2U\x88\u029d\x9e\xa5X\xb4\x00\x00\u07d4.N\u1b99j\xa0\xa1\xd9$(\xd0fR\xa6\xbe\xa6\xd2\xd1]\x89lk\x93[\x8b\xbd@\x00\x00\u07d4.R\x91+\xc1\x0e\xa3\x9dT\xe2\x93\xf7\xae\u05b9\x9a\x0fLs\xbe\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4.a\x9fW\xab\xc1\u91ea\x93j\xe3\xa2&Ib\xe7\xeb-\x9a\x89(\xfb\x9b\x8a\x8aSP\x00\x00\u07d4.d\xa8\xd7\x11\x11\xa2/L]\xe1\xe09\xb36\xf6\x8d9\x8a|\x89lk\x93[\x8b\xbd@\x00\x00\u07d4.i3T=O,\xc0\vSP\xbd\x80h\xba\x92C\u05be\xb0\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94.~\x05\xe2\x9e\u0767\xe4\xae%\xc5\x175C\xef\xd7\x1fm=\x80\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4.\u007fFU \xec5\xcc#\u058eue\x1b\xb6h\x95D\xa1\x96\x898\xec[r\x1a\x1a&\x80\x00\u07d4.\x8e\xb3\nqn_\xe1\\t#>\x03\x9b\xfb\x11\x06\xe8\x1d\x12\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94.\x98$\xb5\xc12\x11\x1b\xca$\xdd\xfb\xa7\xe5u\xa5\xcdr\x96\xc1\x8a\x03\xa4\x84Qnm\u007f\xfe\x00\x00\u07d4.\xa5\xfe\xe6?3z7nK\x91\x8e\xa8!H\xf9MH\xa6&\x89e\x0f\x8e\r\u0493\xc5\x00\x00\u07d4.\xafN*F\xb7\x89\xcc\u0088\xc8\xd1\xd9)N?\xb0\x858\x96\x89lk\x93[\x8b\xbd@\x00\x00\u07d4.\xaf\xf9\xf8\xf8\x110d\u04d5z\xc6\xd6\xe1\x1e\xeeB\xc8\x19]\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4.\xba\fn\xe5\xa1\x14\\\x1cW9\x84\x96:`]\x88\nz \x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4.\xc9X\"\xeb\x88{\xc1\x13\xb4q*M\xfd\u007f\x13\xb0\x97\xb5\xe7\x8965\u026d\xc5\u07a0\x00\x00\u07d4.\xcaj<]\x9fD\x9d\tV\xbdC\xfa{M{\xe8CYX\x89lk\xdaip\x9c\xc2\x00\x00\xe0\x94.\xca\xc5\x04\xb23\x86n\xb5\xa4\xa9\x9e{\u0490\x13Y\xe4;=\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4.\xeb\xf5\x942\xb5(\x92\xf98\v\xd1@\xaa\x99\xdc\xf8\xad\f\x0f\x89\b=lz\xabc`\x00\x00\u07d4.\xee\xd5\x04q\xa1\xa2\xbfS\xee0\xb1#.n\x9d\x80\xef\x86m\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4.\xefk\x14\x17\u05f1\x0e\xcf\xc1\x9b\x12:\x8a\x89\xe7>RlX\x89 \x86\xac5\x10R`\x00\x00\u07d4.\xf8i\xf05\vW\xd54x\xd7\x01\xe3\xfe\xe5)\xbc\x91\x1cu\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4.\xf9\xe4eqj\xca\u03f8\xc8%/\xa8\xe7\xbcyi\xeb\xf6\u4255\x9e\xb1\xc0\xe4\xae \x00\x00\xe0\x94.\xfcLd}\xacj\xca\xc3Uw\xad\"\x17X\xfe\xf6ao\xaa\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4/\x13eu&\xb1w\xca\xd5G\u00d0\x8c\x84\x0e\xffd{E\u0649?v\x84\x9c\xf1\xee,\x80\x00\u07d4/\x18}ZpMZ3\x8c[(v\xa0\x90\xdc\xe9d(N)\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94/%#\u0303O\x00\x86\x05$\x02bb\x96gQ\x86\xa8\u508a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4/(*\xbb\xb6\u0523\xc3\xcd;\\\xa8\x12\xf7d>\x800_\x06\x89dI\xe8NG\xa8\xa8\x00\x00\u07d4/+\xba\x1b\x17\x96\x82\x1avo\xced\xb8O(\xech\xf1Z\xea\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4/1]\x90\x16\xe8\xee_Sf\x81 /\x90\x84\xb02TMM\x898<\xd1+\x9e\x86<\x00\x00\u07d4/M\xa7SC\x0f\xc0\x9es\xac\xbc\xcd\xcd\xe9\xdad\u007f+]7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4/P\x80\xb8?~-\xc0\xa1\xdd\x11\xb0\x92\xad\x04+\xffx\x8fL\x89\xb4\xf8\xfby#\x1d+\x80\x00\u07d4/a\uf941\x9dp_+\x1eN\xe7T\xae\xb8\xa8\x19Pju\x89O%\x91\xf8\x96\xa6P\x00\x00\xe0\x94/f\xbf\xbf\"b\xef\u030d+\xd0DO\u0170ib\x98\xff\x1e\x8a\x02\x1a\xd95\xf7\x9fv\xd0\x00\x00\u07d4/m\xce\x130\u015e\xf9!`!TW-MK\xac\xbd\x04\x8a\x8965\u026d\xc5\u07a0\x00\x00\u07d4/}2\x90\x85\x1b\xe5\u01b4\xb4?}Et2\x9fa\xa7\x92\u00c9\x05k\xc7^-c\x10\x00\x00\u07d4/\x858\x17\xaf\u04f8\xf3\xb8n\x9f`\xeew\xb5\xd9ws\xc0\xe3\x89N\xae\xeaD\xe3h\xb9\x00\x00\u07d4/\xa4\x91\xfbY \xa6WN\xbd(\x9f9\xc1\xb2C\r-\x9aj\x89lk\x93[\x8b\xbd@\x00\x00\u07d4/\xb5f\xc9K\xbb\xa4\xe3\xcbg\xcd\xda}_\xadq1S\x91\x02\x89lk\x93[\x8b\xbd@\x00\x00\u07d4/\xbbPJ]\xc5'\xd3\xe3\xeb\x00\x85\xe2\xfc<}\xd58\xcbz\x89C\u00b1\x8a\xec<\n\x80\x00\u07d4/\xbc\x85y\x8aX5\x98\xb5\"\x16mn\x9d\xda\x12\x1db}\xbc\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4/\xbc\xef3\x84\xd4 \xe4\xbfa\xa0f\x99\x90\xbcpT\U00065bc9lk\x93[\x8b\xbd@\x00\x00\xe0\x94/\xc8.\xf0v\x93#A&Oaz\f\x80\xddW\x1ej\xe99\x8a\x01\x84$\xf5\xf0\xb1\xb4\xe0\x00\x00\u07d4/\u075by\u07cd\xf50\xadc\xc2\x0eb\xafC\x1a\xe9\x92\x16\xb8\x89\x01#n\xfc\xbc\xbb4\x00\x00\u07d4/\xe0\x02?W\"e\x0f:\x8a\xc0\x10\t\x12^t\xe3\xf8.\x9b\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4/\xe0\xccBKS\xa3\x1f\t\x16\xbe\b\xec\x81\xc5\v\xf8\xea\xb0\xc1\x89 \x86\xac5\x10R`\x00\x00\u07d4/\xe1:\x8d\a\x85\u0787X\xa5\xe4\x18v\xc3n\x91l\xf7Pt\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4/\xea\x1b/\x83O\x02\xfcT3?\x8a\x80\x9f\x048\xe5\x87\n\xa9\x89\x01\x18T\xd0\xf9\xce\xe4\x00\x00\u07d4/\xee6\xa4\x9e\xe5\x0e\xcfqo\x10G\x91VFw\x9f\x8b\xa0?\x899B\"\xc4\u0686\xd7\x00\x00\u07d4/\xef\x81G\x8aK.\x80\x98\xdb_\xf3\x87\xba!S\xf4\xe2+y\x896'\xe8\xf7\x127<\x00\x00\u07d4/\xf1`\xc4Or\xa2\x99\xb5\xec-q\xe2\x8c\xe5Dm/\u02ef\x89\x13\x84\x00\xec\xa3d\xa0\x00\x00\u07d4/\xf1\xcaU\xfd\x9c\xec\x1b\x1f\xe9\U00029af7LQ<\x1e*\xaa\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94/\xf5\u02b1,\r\x95\u007f\xd33\xf3\x82\xee\xb7Q\a\xa6L\xb8\xe8\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94/\xf80\xcfU\xfb\x00\u0560\xe05\x14\xfe\xcdD1K\xd6\xd9\xf1\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4/\xfe\x93\xec\x1aV6\xe9\xee4\xafp\xdf\xf5&\x82\xe6\xffpy\x89lk\x93[\x8b\xbd@\x00\x00\u07d40\x03y\x88p&q\xac\xbe\x89,\x03\xfeW\x88\xaa\x98\xaf(z\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d40$\x8dX\xe4\x14\xb2\x0f\xed:lH+Y\xd9\xd8\xf5\xa4\xb7\xe2\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4019\xbcYd\x03\xd5\u04d3\x1fwLf\u013aFtT\u06c9\\%\xe1J\xea(?\x00\x00\u079408\x00\x87xie\x14\x9e\x81B;\x15\xe3\x13\xba2\xc5\u01c3\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d40:0\xacB\x86\xae\x17\xcfH=\xad{\x87\fk\xd6M{J\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d40?\xba\xeb\xbeF\xb3[n[t\x94j_\x99\xbc\x15\x85\xca\xe7\x89/\x9a\xc0i_[\xba\x00\x00\u07d40ADZ3\xba\x15\x87A\x16\r\x9c4N\xb8\x8e\\0o\x94\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d40H\x01d\xbc\xd8It\xeb\xc0\xd9\f\x9b\x9a\xfa\xb6&\xcd\x1cs\x89+^:\xf1k\x18\x80\x00\x00\u07d40N\u019atTW!\xd71j\xefM\u03f4\x1a\u015e\xe2\xf0\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x940Q\x182\x91\x8d\x804\xa7\xbe\xe7.\xf2\xbf\xeeD\x0e\u02fc\xf6\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d40Q?\u029f6\xfdx\x8c\xfe\xa7\xa3@\xe8m\xf9\x82\x94\xa2D\x89\x18;_\x03\xb1G\x9c\x00\x00\u07d40U\xef\xd2`)\xe0\xd1\x1b\x93\r\xf4\xf5;\x16,\x8c?\xd2\u0389\x1b\x1a\b\x927\a=\x00\x00\u07d40]&\xc1\v\xdc\x10?k\x9c!'.\xb7\xcb-\x91\b\xc4~\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d40_x\xd6\x18\xb9\x90\xb4)[\xac\x8a-\xfa&(\x84\xf8\x04\xea\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x940d\x89\x9a\x96\x1a>\x1d\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d40\x98\xb6]\xb9>\xca\xca\xf75\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d40\uc4d2$J!\b\u0247\xbc\\\xdd\xe0\ud7c3z\x81{\x89T\x99%\xf6\xc9\xc5%\x00\x00\xe0\x940\xed\x11\xb7{\xc1~^f\x94\u023c[nG\x98\xf6\x8d\x9c\xa7\x8a\x1eo\xb3B\x1f\xe0)\x9e\x00\x00\u07d40\xf7\xd0%\xd1o{\xee\x10U\x80Ho\x9fV\x1c{\xae?\xef\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x940\xfb\xe5\x88_\x9f\xcc\xe9\xea^\u06c2\xedJ\x11\x96\xdd%\x9a\xed\x8a\x01\x19\xe4\u007f!8\x1f@\x00\x00\u07d41\x04}p?c\xb94$\xfb\xbdn/\x1f\x9et\xde\x13\xe7\t\x89\x9a\x81f\xf7\u6ca7\x80\x00\u07d411?\xfdc[\xf2\xf32HA\xa8\x8c\a\xed\x14aD\xce\xeb\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d41Y\xe9\fH\xa9\x15\x90J\xdf\u24b2/\xa5\xfd^ryk\x896\xaf\xe9\x8f&\x06\x10\x00\x00\u07d41]\xb7C\x9f\xa1\u0574#\xaf\xa7\xddq\x98\xc1\xcft\xc9\x18\xbc\x89 \x86\xac5\x10R`\x00\x00\u07d41^\xf2\xdab\x0f\xd30\xd1.\xe5]\xe5\xf3)\xa6\x96\xe0\xa9h\x89\b!\xab\rD\x14\x98\x00\x00\u07d41n\x92\xa9\x1b\xbd\xa6\x8b\x9e/\x98\xb3\xc0H\x93N<\xc0\xb4\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d41n\xb4\xe4}\xf7\x1bB\xe1mo\xe4h%\xb72{\xaf1$\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d41q\x87~\x9d\x82\f\xc6\x18\xfc\t\x19\xb2\x9e\xfd3?\xdaI4\x8965\u026d\xc5\u07a0\x00\x00\u07d41|\xf4\xa2<\xb1\x91\xcd\xc5c\x12\u009d\x15\xe2\x10\xb3\xb9\xb7\x84\x89\a\xcef\xc5\x0e(@\x00\x00\u07d41\x8b.\xa5\xf0\xaa\xa8y\xc4\xd5\xe5H\xac\x9d\x92\xa0\xc6t\x87\xb7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d41\x8cv\xec\xfd\x8a\xf6\x8dpUSR\xe1\xf6\x01\xe3Y\x88\x04-\x89\x1b1\x19.h\xc7\xf0\x00\x00\u07d41\x8f\x1f\x8b\xd2 \xb0U\x8b\x95\xfb3\x10\x0f\xfd\xbbd\r|\xa6\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d41\xaa;\x1e\xbe\x8cM\xbc\xb6\xa7\b\xb1\xd7H1\xe6\x0eIv`\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d41\xab\b\x89f\xec\xc7\"\x92X\xf6\t\x8f\xceh\xcf9\xb3\x84\x85\x8965\u026d\xc5\u07a0\x00\x00\xe0\x941\xadM\x99F\xef\t\xd8\xe9\x88\xd9F\xb1\"\u007f\x91A\x90\x176\x8a\x04\xd8S\xc8\xf8\x90\x89\x80\x00\x00\xe0\x941\xb4;\x01]\x00\x81d~h\x00\x00\u07d424\x86\xcad\xb3uGO\xb2\xb7Y\xa9\xe7\xa15\x85\x9b\xd9\xf6\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d427I\xa3\xb9q\x95\x9eF\u0234\x82-\xca\xfa\xf7\xaa\xf9\xbdn\x89\x01\x16q\xa5\xb2Ep\x00\x00\u07d42:\xadA\xdfKo\xc8\xfe\u038c\x93\x95\x8a\xa9\x01\xfah\bC\x894\x95tD\xb8@\xe8\x00\x00\xe0\x942;<\xfe>\xe6+\xbd\xe2\xa2a\xe5<\xb3\xec\xc0X\x10\xf2\u018a\x02\ub3b1\xa1r\u0738\x00\x00\u07d42?\xca^\xd7\u007fi\x9f\x9d\x990\xf5\xce\xef\xf8\xe5oY\xf0<\x89Hz\x9a0E9D\x00\x00\u07d42H\\\x81\x87(\xc1\x97\xfe\xa4\x87\xfb\xb6\xe8)\x15\x9e\xba\x83p\x899!\xb4\x13\xbcN\xc0\x80\x00\xe0\x942P\xe3\xe8X\xc2j\xde\u032d\xf3jVc\xc2*\xa8LAp\x8a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x942Y\xbd/\xdd\xfb\xbco\xba\u04f6\xe8t\xf0\xbb\xc0,\xda\x18\xb5\x8a\x02\x84`VI[\r\x18\x80\x00\u07d42uIo\xd4\u07491\xfdi\xfb\n\v\x04\xc4\xd1\xff\x87\x9e\xf5\x89\x18-~L\xfd\xa08\x00\x00\u07d42{\xb4\x9euOo\xb4\xf73\xc6\xe0o9\x89\xb4\xf6]K\xee\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d42\x82y\x1do\xd7\x13\xf1\xe9OK\xfdV^\xaax\xb3\xa0Y\x9d\x89Hz\x9a0E9D\x00\x00\u07d42\x83\xeb\u007f\x917\xdd9\xbe\xd5_\xfek\x8d\xc8E\xf3\xe1\xa0y\x89\x03\x97\n\xe9!Ux\x00\x00\u07d42\x86\t\x97\xd70\xb2\xd8;s$\x1a%\xd3f}Q\xc9\b\xef\x89\x1b\x1a\b\x927\a=\x00\x00\xe0\x942\x86\u047cez1,\x88G\xd9<\xb3\xcbyP\xf2\xb0\xc6\xe3\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x942\xa2\r\x02\x8e,b\x18\xb9\xd9[D\\w\x15$cj\"\xef\x8a\x02\x02\xfe\xfb\xf2\xd7\xc2\xf0\x00\x00\u07d42\xa7\x06\x91%\\\x9f\xc9y\x1aOu\u0238\x1f8\x8e\n%\x03\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d42\xb7\xfe\xeb\xc5\u015b\xf6^\x86\x1cL\v\xe4*v\x11\xa5T\x1a\x89w\u9aa8R\\\x10\x00\x00\xe0\x942\xba\x9a}\x04#\xe0:R_\xe2\xeb\xebf\x1d \x85w\x8b\u060a\x04<3\xc1\x93ud\x80\x00\x00\u07d42\xbb.\x96\x93\xe4\xe0\x854M/\r\xbdF\xa2\x83\u3807\xfd\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x942\xc2\xfd\u2daa\xbb\x80\u5ba2\xb9I\xa2\x17\xf3\xcb\t\"\x83\x8a\x010a`\xaf\xdf 7\x80\x00\u07d42\xd9P\xd5\xe9>\xa1\u0574\x8d\xb4qO\x86{\x03 \xb3\x1c\x0f\x897\b\xba\xed=h\x90\x00\x00\u07d42\u06f6qlT\xe81e\x82\x9aJ\xbb6uxI\xb6\xe4}\x8965\u026d\xc5\u07a0\x00\x00\u07d42\xebd\xbe\x1b]\xed\xe4\b\u01bd\xef\xben@\\\x16\xb7\xed\x02\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d42\xef\\\xdcg\x1d\xf5V*\x90\x1a\xee]\xb7\x16\xb9\xbev\xdc\xf6\x89lk\x93[\x8b\xbd@\x00\x00\u07d42\xf2\x9e\x87'\xa7LkC\x01\xe3\xff\xff\x06\x87\xc1\xb8p\xda\xe9\x8965\u026d\xc5\u07a0\x00\x00\u07d42\xfa\x0e\x86\xcd\b}\u058di1\x90\xf3-\x931\t\t\xedS\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d42\xfb\xee\xd6\xf6&\xfc\xdf\xd5\x1a\xca\xfbs\v\x9e\xef\xf6\x12\xf5d\x89lk\x93[\x8b\xbd@\x00\x00\u07943\x00\xfb\x14\x9a\xde\xd6[\u02e6\xc0N\x9c\u05b7\xa0;\x89;\xb1\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x943\x01\xd9\xca/;\xfe\x02by\xcdh\x19\xf7\x9a)=\x98\x15n\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\xe0\x943\b\xb04f\xc2z\x17\xdf\xe1\xaa\xfc\xeb\x81\xe1m)4Vo\x8a\x03\x99\x92d\x8a#\u0220\x00\x00\u07943\x1a\x1c&\xcci\x94\xcd\xd3\xc1K\xec\xe2v\xff\xffK\x9d\xf7|\x88\xfaz\xed\xdfO\x06\x80\x00\xe0\x943&\xb8\x8d\xe8\x06\x18DT\xc4\v'\xf3\t\xd9\xddm\u03f9x\x8a\x03\xca\\f\u067cD0\x00\x00\xe0\x943)\xeb;\xafCE\xd6\x00\xce\xd4\x0en\x99ueo\x117B\x8a\x01\x0f\b\xed\xa8\xe5U\t\x80\x00\u07d432\r\xd9\x0f+\xaa\x11\r\xd34\x87*\x99\x8f\x14\x84&E<\x8965f3\xeb\xd8\xea\x00\x00\u07d436\xc3\xefn\x8bP\xee\x90\xe07\xb1d\xb7\xa8\xea_\xaa\xc6]\x89\x0e\u0223\xa7\x1c\"T\x00\x00\xe0\x9438\fo\xffZ\xcd&Q0\x96)\u06daq\xbf? \u017a\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d43:\xd1Yd\x01\xe0Z\xea-6\xcaG1\x8e\xf4\xcd,\xb3\u07c9\x9d\xc0\\\xce(\u00b8\x00\x00\u07d43C@\xeeK\x9c\u0701\xf8P\xa7Q\x16\xd5\x0e\u9d98%\xbf\x89lk\x93[\x8b\xbd@\x00\x00\u07d43H\x1e\x85n\xbe\u050e\xa7\b\xa2t&\xef(\xe8g\xf5|\u0449\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x943V[\xa9\xda,\x03\xe7x\xce\x12)O\b\x1d\xfe\x81\x06M$\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07943X\x1c\xee#0\x88\xc0\x86\r\x94N\f\xf1\u03ab\xb8&\x1c.\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d43XX\xf7I\xf1i\u02bc\xfeR\xb7\x96\xe3\xc1\x1e\xc4~\xa3\u0089\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x943^\"\x02[zw\u00e0t\u01cb\x8e=\xfe\a\x13A\x94n\x8a\x02'\xcas\n\xb3\xf6\xac\x00\x00\u07d43b\x9b\xd5/\x0e\x10{\xc0q\x17ld\xdf\x10\x8fdw}I\x89\x01\xcf\xddth!n\x80\x00\u07d43{;\u07c6\xd7\x13\xdb\xd0{]\xbf\xcc\x02+z{\x19F\xae\x89\xd7\xc1\x98q\x0ef\xb0\x00\x00\u07d43|\xfe\x11W\xa5\u0191 \x10\xddV\x153y\x17i\u00b6\xa6\x8965\u026d\xc5\u07a0\x00\x00\u07d43\xb36\xf5\xba^\xdb{\x1c\xcc~\xb1\xa0\u0644\xc1#\x1d\x0e\u0709lk\x93[\x8b\xbd@\x00\x00\u07d43\xc4\a\x13;\x84\xb3\xcaL=\xed\x1fFX\x90\f8\x10\x16$\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\xe0\x943\xd1r\xab\a\\Q\xdb\x1c\xd4\n\x8c\xa8\xdb\xff\r\x93\xb8C\xbb\x8a\x016x\x05\x10\xd1-\xe3\x80\x00\u07d43\xe9\xb7\x18#\x95.\x1ff\x95\x8c'\x8f\u008b\x11\x96\xa6\u0164\x89\x05k\xc7^-c\x10\x00\x00\u07d43\xeakxU\xe0[\a\xab\x80\u06b1\xe1M\xe9\xb6I\xe9\x9bl\x89\x1c\xd6\xfb\xadW\xdb\xd0\x00\x00\u07d43\xf1R#1\rD\u078bf6h_:L=\x9cVU\xa5\x89\r\x94b\xc6\xcbKZ\x00\x00\u07d43\xf4\xa6G\x1e\xb1\xbc\xa6\xa9\xf8[;Hr\xe1\aU\xc8+\xe1\x89lk\x93[\x8b\xbd@\x00\x00\u07d43\xfbWzM!O\xe0\x10\xd3,\xca|>\xed\xa6?\x87\xce\xef\x8965\u026d\xc5\u07a0\x00\x00\u07d43\xfdq\x8f\v\x91\xb5\xce\u020a]\xc1^\xec\xf0\xec\xef\xa4\xef=\x89\x17r$\xaa\x84Lr\x00\x00\u07d44\x14\x80\u030c\xb4v\xf8\xd0\x1f\xf3\b\x12\xe7\xc7\x0e\x05\xaf\xaf]\x89lk\x93[\x8b\xbd@\x00\x00\u07d44'-^ut1]\xca\u9afd1{\xac\x90(\x9dGe\x89b\xa9\x92\xe5:\n\xf0\x00\x00\xe0\x9440\xa1c\x81\xf8i\xf6\xeaT#\x91XU\xe8\x00\x885%\xa9\x8a\x03\xca\\f\u067cD0\x00\x00\u07d441\x86%\x81\x8e\xc1?\x11\x83Z\xe9sS\xce7}oY\n\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d449<]\x91\xb9\xdeYr\x03\xe7[\xacC\t\xb5\xfa=(\u00c9\n\x84Jt$\xd9\xc8\x00\x00\u07d449\x99\x8b$|\xb4\xbf\x8b\xc8\nm+5'\xf1\xdf\xe9\xa6\u0489\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d44C}\x14ed\v\x13l\xb5\x84\x1c?\x93O\x9b\xa0\xb7\t}\x89\t`\xdbwh\x1e\x94\x00\x00\u07d44J\x8d\xb0\x86\xfa\xedN\xfc7\x13\x1b:\"\xb0x-\xadp\x95\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x944fM\"\x0f\xa7\xf3yX\x02J32\u0584\xbc\xc6\xd4\u023d\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d44f\xf6~9cl\x01\xf4;:!\xa0\xe8R\x93%\xc0\x86$\x89-\xb1\x16vP\xac\xd8\x00\x00\u07d44\x856\x1e\xe6\xbf\x06\xefe\b\xcc\xd2=\x94d\x1f\x81M>/\x89lk\x93[\x8b\xbd@\x00\x00\u07d44\x85\xf6!%d3\xb9\x8aB\x00\xda\xd8W\xef\xe5Y7\uc609lk\x93[\x8b\xbd@\x00\x00\u07d44\x95\x8aF\xd3\x0e0\xb2s\xec\xc6\xe5\xd3X\xa2\x12\xe50~\x8c\x89lk\x93[\x8b\xbd@\x00\x00\u07d44\x97\xddf\xfd\x11\x80q\xa7\x8c,\xb3n@\xb6e\x1c\xc8%\x98\x89\x05\xf1\x01kPv\xd0\x00\x00\xe0\x944\x9a\x81k\x17\xab='\xbb\xc0\xae\x00Q\xf6\xa0p\xbe\x1f\xf2\x9d\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d44\x9d,\x91\x8f\u041e(\a1\x8ef\xceC)\t\x17k\xd5\v\x89<\xb7\x1fQ\xfcU\x80\x00\x00\u07d44\xa0C\x1f\xff^\xad\x92\u007f\xb6`\f\x1e\xa8\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d44\xff&\xeb`\xa8\u0469ZH\x9f\xae\x13n\xe9\x1dNX\bL\x89 \x86\xac5\x10R`\x00\x00\u07d44\xffX)R\xff$E\x8f{\x13\xd5\x1f\vO\x98p\"\xc1\xfe\x89\x98\x06\xde=\xa6\xe9x\x00\x00\u07d45\x10k\xa9N\x85c\u0533\xcb<\\i,\x10\xe6\x04\xb7\xce\u0609lk\x93[\x8b\xbd@\x00\x00\xe0\x945\x14_b\x03\x97\u019c\xb8\xe0\tb\x96\x1f\x0fH\x86d9\x89\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d45\x14t0\xc3\x10e\x00\u77e2\xf5\x02F.\x94p<#\xb1\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x945\x17\x87\x845\x05\xf8\xe4\xef\xf4ef\xcc\u695fM\x1c_\xe7\x8a\x01\xf5q\x89\x87fKH\x00\x00\xe0\x945\x1f\x16\xe5\xe0sZ\xf5gQ\xb0\xe2%\xb2B\x11q9@\x90\x8a\x02\xd4\xca\x05\xe2\xb4<\xa8\x00\x00\xe0\x945$\xa0\x00#N\xba\xaf\a\x89\xa14\xa2\xa4\x178<\xe5(*\x8a\x011yU\x94}\x8e,\x00\x00\u07d45&\xee\xce\x1ak\xdc>\xe7\xb4\x00\xfe\x93[HF?1\xbe\u05c9\x04w\x87\x9bm\x140\x00\x00\u07d45*x_J\x92\x162PL\xe5\xd0\x15\xf8\xd7FO\xa3\xdb\x14\xe7r\x92\x13\u03aa7\x8c\t^\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d45\xaf\x04\n\f\xc23zv\xaf(\x81T\xc7V\x1e\x1a#3I\x8965\u026d\xc5\u07a0\x00\x00\u07d45\xb0>\xa4$W6\xf5{\x85\xd2\xebyb\x8f\x03m\xdc\xd7\x05\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d45\xbd$he\xfa\xb4\x90\xac\bz\xc1\xf1\xd4\xf2\xc1\r\f\xda\x03\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x945\xbff\x88R/5Fz\u007fu0#\x14\xc0+\xa1v\x80\x0e\x8a\x03\xafA\x82\x02\xd9T\xe0\x00\x00\u07d45\u022d\xc1\x11%C+;w\xac\xd6F%\xfeX\xeb\xee\x9df\x89lk\x93[\x8b\xbd@\x00\x00\u07d45\u0497\x0fI\xdc\xc8\x1e\xa9\xeep~\x9c\x8a\n\xb2\xa8\xbbtc\x89N\x10\x03\xb2\x8d\x92\x80\x00\x00\u07d45\xe0\x96\x12\r\xea\xa5\xc1\xec\xb1d^,\u02cbN\xdb\xd9)\x9a\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d45\xea!c\xa3\x8c\u07da\x12?\x82\xa5\xec\x00%\x8d\xae\v\xc7g\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d45\xf1\xda\x12{\x837o\x1b\x88\xc8*3Y\xf6z^g\xddP\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d45\xf2\x94\x9c\xf7\x8b\xc2\x19\xbbO\x01\x90|\xf3\xb4\xb3\u04c6T\x82\x89\x0f\xb5\xc8l\x92\xe44\x00\x00\u07d45\xf5\x86\x01I\xe4\xbb\xc0K\x8a\u0172r\xbeU\xad\x1a\xcaX\xe0\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d46\x02E\x8d\xa8omj\x9d\x9e\xb0=\xaf\x97\xfeV\x19\xd4B\xfa\x89lk\x93[\x8b\xbd@\x00\x00\u07d46\x057-\x93\xa9\x01\t\x88\x01\x8f\x9f1]\x03.\u0448\x0f\xa1\x89\x1b\x1b\xcfQ\x89j}\x00\x00\u07d46\x16\xd4H\x98_]2\xae\xfa\x8b\x93\xa9\x93\xe0\x94\xbd\x85I\x86\x89\v\"\u007fc\xbe\x81<\x00\x00\u07d46\x16\xfbF\xc8\x15x\xc9\xc8\xebM;\xf8\x80E\x1a\x887\x9d}\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d46\x1cu\x93\x16\x96\xbc=B}\x93\xe7lw\xfd\x13\xb2A\xf6\xf4\x89\x1d\xc5\xd8\xfc&m\xd6\x00\x00\u07d46\x1d\x9e\xd8\v[\xd2|\xf9\xf1\"o&u2X\xee_\x9b?\x89\xbfi\x14\xba}r\xc2\x00\x00\u07d46\x1f;\xa9\xed\x95kw\x0f%}6r\xfe\x1f\xf9\xf7\xb0$\f\x89 \x86\xac5\x10R`\x00\x00\u07d46\"|\u07e0\xfd;\x9d~jtF\x85\xf5\xbe\x9a\xa3f\xa7\xf0\x89\n\xc2s\x0e\xe9\xc6\xc1\x80\x00\u07d46/\xbc\xb1\x06b7\n\x06\x8f\xc2e&\x02\xa2Wy7\xcc\xe6\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d460\xc5\xe5e\u03aa\x8a\x0f\x0f\xfe2\x87^\xae*l\xe6<\x19\x89\t7r+7t\xd0\x00\x00\u07d463\x9f\x84\xa5\u00b4L\xe5=\xfd\xb6\xd4\xf9}\xf7\x82\x12\xa7\u07c9\x11o\x18\xb8\x17\x15\xa0\x00\x00\u07d464:\xec\xa0{n\u054a\x0eb\xfaN\xcbI\x8a\x12O\xc9q\x89\x10CV\x1a\x88)0\x00\x00\u07d46au@4\x81\xe0\xab\x15\xbbQF\x15\u02f9\x89\xeb\u018f\x82\x89lk\x93[\x8b\xbd@\x00\x00\u07d46ro;\x88Z$\xf9)\x96\u0681b^\u022d\x16\xd8\xcb\xe6\x89S\xafu\u0441HW\x80\x00\xe0\x946s\x95C\x99\xf6\u07feg\x18\x18%\x9b\xb2x\xe2\xe9.\xe3\x15\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d46u\x8e\x04\x9c\u064b\u03a1\"w\xa6v\xf9)sb\x89\x00#\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d46\u007fY\u0302yS)8NA\xe1(1\x15\xe7\x91\xf2j\x01\x89lk\x93[\x8b\xbd@\x00\x00\u07d46\x81\x0f\xf9\xd2\x13\xa2q\xed\xa2\xb8\xaay\x8b\xe6T\xfaK\xbe\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d46\x8cT\x14\xb5k\x84U\x17\x1f\xbf\ab \xc1\u02e4\xb5\xca1\x89\x1e>\xf9\x11\xe8=r\x00\x00\xe0\x946\x90$k\xa3\xc8\x06y\xe2.\xacD\x12\xa1\xae\xfc\xe6\xd7\u0342\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d46\x92\x8bU\xbc\x86\x15\t\xd5\x1c\x8c\xf1\xd5F\xbf\xecn>\x90\xaf\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d46\x98\"\xf5W\x8b@\xdd\x1fDqpk\"\u0357\x13R\xdak\x89\x12\xc1\xb6\xee\xd0=(\x00\x00\u07d46\x9e\xf7a\x19_:7>$\xec\xe6\xcd\"R\x0f\xe0\xb9\xe8n\x89\x1c\xff\xaf\xc9M\xb2\b\x80\x00\u07d46\xa0\x8f\xd6\xfd\x1a\xc1|\xe1^\xd5~\xef\xb1*+\u2048\xbf\x89Hz\x9a0E9D\x00\x00\u07d46\xa0\xe6\x1e\x1b\xe4\u007f\xa8~0\xd3(\x88\xee\x030\x90\x1c\xa9\x91\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x946\xb2\xc8^:\xee\xeb\xb7\rc\u0124s\f\xe2\xe8\xe8\x8a6$\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x946\xbfC\xff5\u07d0\x90\x88$3l\x9b1\xce3\x06~/P\x8aIr\x15\x10\xc1\xc1\xe9H\x00\x00\u07d46\xbf\xe1\xfa;{p\xc1r\xeb\x04/h\x19\xa8\x97%\x95A>\x8965\u026d\xc5\u07a0\x00\x00\xe0\x946\xc5\x10\xbf\x8dnV\x9b\xf2\xf3}G&]\xbc\xb5\x02\xff+\u038a\x06ZM\xa2]0\x16\xc0\x00\x00\xe0\x946\xd8]\xc3h1V\xe6;\xf8\x80\xa9\xfa\xb7x\x8c\xf8\x14:'\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d46\u07cf\x88<\x12s\xec\x8a\x17\x1fz3\xcf\xd6I\xb1\xfe`u\x89\fRHJ\xc4\x16\x89\x00\x00\xe0\x946\xe1Va\f\xd8\xffd\xe7\x80\u061d\x00T8\\\xa7gU\xaa\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d46\xfe\xc6,,B^!\x9b\x18D\x8a\xd7W\x00\x9d\x8cT\x02o\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d47\x00\xe3\x02t$\xd99\xdb\xde]B\xfbx\xf6\xc4\xdb\xec\x1a\x8f\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d47\x02\xe7\x04\xcc!at9\xadN\xa2zW\x14\xf2\xfd\xa1\xe92\x8965\u026d\xc5\u07a0\x00\x00\u07d47\x035\fMo\xe374,\xdd\xc6[\xf1\xe28k\xf3\xf9\xb2\x89m\x81!\xa1\x94\xd1\x10\x00\x00\xe0\x947\b\xe5\x9d\xe6\xb4\x05P\x88x)\x02\xe0W\x9cr\x01\xa8\xbfP\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d47\x126~^U\xa9mZ\x19\x16\x8fn\xb2\xbc~\x99q\xf8i\x8965\u026d\xc5\u07a0\x00\x00\u07d47\x19Zc]\xccb\xf5jq\x80I\xd4~\x8f\x9f\x96\x83(\x91\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d47'4\x1f&\xc1 \x01\xe3x@^\xe3\x8b-\x84d\xecq@\x89lk\x93[\x8b\xbd@\x00\x00\u07d47.E:kb\x9f'g\x8c\u022e\xb5\xe5|\xe8^\xc0\xae\xf9\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d474\xcb\x18t\x91\xed\xe7\x13\xae[;-\x12(J\xf4k\x81\x01\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d477!n\xe9\x1f\x17w2\xfbX\xfa@\x97&r\a\xe2\xcfU\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d47M;\xbb\x057Q\xf9\xf6\x8d\xdb\a\xa0\x89lk\x93[\x8b\xbd@\x00\x00\u07d48r\xf4\x8d\xc5\xe3\xf8\x17\xbck*\xd2\xd00\xfc^\x04q\x19=\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d48~\xea\xfdk@\t\u07af\x8b\u0578Zr\x98:\x8d\xcc4\x87\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d48\x81\xde\xfa\xe1\xc0{<\xe0Lx\xab\xe2k\f\u070ds\xf0\x10\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d48\x83\xbe\xcc\b\xb9\xbeh\xad;\b6\xaa\u00f6 \xdc\x00\x17\xef\x89lk\x93[\x8b\xbd@\x00\x00\u07d48\x85\xfe\xe6q\a\xdc:\xa9\x8a\x1d\x99:t\xdf\\\xd7T\xb9\x8dR\x9a\x89a\t=|,m8\x00\x00\u07d48\xe4m\xe4E<8\xe9A\xe7\x93\x0fC0O\x94\xbb{+\xe8\x89l\xb7\xe7Hg\xd5\xe6\x00\x00\u07d48\xe7\u06e8\xfdO\x1f\x85\r\xbc&I\xd8\xe8O\tR\xe3\xeb<\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d48\xe8\xa3\x1a\xf2\xd2e\xe3\x1a\x9f\xff-\x8fF(m\x12E\xa4g\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d48\xeao[Z{\x88AuQ\xb4\x12=\xc1'\xdf\xe94-\xa6\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d48\xee\xc6\xe2\x17\xf4\xd4\x1a\xa9 \xe4$\xb9RQ\x97\x04\x1c\xd4\u0189\xf0\r%\xeb\x92.g\x00\x00\xe0\x948\xf3\x87\xe1\xa4\xedJs\x10n\xf2\xb4b\xe4t\xe2\xe3\x14:\u040a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d49\x11a\xb0\xe4<0 f\u898d,\xe7\xe1\x99\xec\xdb\x1dW\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x949\x15\uad6b.Yw\xd0u\xde\xc4}\x96\xb6\x8bK\\\xf5\x15\x8a\r\a\x01\x81\x85\x12\x0f@\x00\x00\u07d49\x1aw@\\\t\xa7+^\x846#z\xaa\xf9]h\xda\x17\t\x89\x02\xa9&J\xf3\u0479\x00\x00\u07d49\x1f \x17m\x126\rrMQG\n\x90p6uYJM\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d49$3\xd2\u0383\xd3\xfbJv\x02\u0323\xfa\xcaN\xc1@\xa4\xb0\x89\x02\xc3\xc4e\xcaX\xec\x00\x00\xe0\x949?x;\\\u06c6\"\x1b\xf0)O\xb7\x14\x95\x9c{E\x89\x9c\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d49?\xf4%^\\e\x8f.\u007f\x10\xec\xbd)%rg\x1b\xc2\u0489lk\x93[\x8b\xbd@\x00\x00\u07d49A2`\x0fAU\xe0\u007fME\xbc>\xb8\xd9\xfbr\xdc\u05c4\x89\x9fn\x92\xed\xea\a\xd4\x00\x00\u07d49Q\xe4\x8e<\x86\x9ekr\xa1C\xb6\xa4Ph\u0379\xd4f\u0409\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x949T\xbd\xfe\v\xf5\x87\u0195\xa3\x05\xd9$L=[\xdd\xda\u027b\x8a\x04\x10'\x83'\xf9\x85`\x80\x00\u07d49]m%U \xa8\xdb)\xab\xc4}\x83\xa5\u06ca\x1a}\xf0\x87\x89\x05k\xc7^-c\x10\x00\x00\u07d49ck%\x81\x1b\x17j\xbf\xcf\xee\xcad\xbc\x87E/\x1f\xdf\xf4\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d49i\xb4\xf7\x1b\xb8u\x1e\xdeC\xc0\x166:zaOv\x11\x8e\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x949x/\xfe\x06\xacx\x82*<:\x8a\xfe0^P\xa5a\x88\u038a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d49zn\xf8v:\x18\xf0\x0f\xac!~\x05\\\r0\x94\x10\x10\x11\x89lk\x93[\x8b\xbd@\x00\x00\u07d49|\u06cc\x80\xc6yP\xb1\x8deB)a\x0e\x93\xbf\xa6\xee\x1a\x89?\x95\xc8\xe0\x82\x15!\x00\x00\u07d49\x82O\x8b\xce\xd1v\xfd>\xa2.\u01a4\x93\xd0\xcc\xc3?\xc1G\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d49\x93l'\x19E\v\x94 \xcc%\"\u03d1\xdb\x01\xf2'\xc1\xc1\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d49\x95\xe0\x96\xb0\x8aZrh\x00\xfc\xd1}\x9cd\xc6N\b\x8d+\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d49\x9a\xa6\xf5\xd0x\xcb\tp\x88+\u0259 \x06\xf8\xfb\xdf4q\x8965\u026d\xc5\u07a0\x00\x00\u07d49\xaa\x05\xe5m}28T!\u03d36\xe9\r=\x15\xa9\xf8Y\x89\x01h\u048e?\x00(\x00\x00\u07d49\xaa\xf0\x85M\xb6\xeb9\xbc{.C\x84jv\x17\x1c\x04E\u0789dI\xe8NG\xa8\xa8\x00\x00\u07d49\xb1\xc4q\xae\x94\xe1!dE.\x81\x1f\xbb\xe2\xb3\xcdru\xac\x89lk\x93[\x8b\xbd@\x00\x00\u07d49\xb2\x992t\x90\xd7/\x9a\x9e\xdf\xf1\x1b\x83\xaf\xd0\xe9\xd3\xc4P\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d49\xba\u018d\x94xY\xf5\x9e\x92&\b\x9c\x96\xd6.\x9f\xbe<\u0789\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x949\xbf\xd9xh\x9b\xec\x04\x8f\xc7v\xaa\x15$\u007f^\x1d|9\xa2\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d49\xc7s6|\x88%\xd3YlhoB\xbf\r\x141\x9e?\x84\x89\a?u\u0460\x85\xba\x00\x00\u07d49\u05291@,\fy\xc4W\x18o$\u07c7)\u03d5p1\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d49\xd6\xca\xca\"\xbc\xcdjr\xf8~\xe7\u05b5\x9e\v\xde!\xd7\x19\x89l\x87T\xc8\xf3\f\b\x00\x00\u07d49\xe0\xdbM`V\x8c\x80\v\x8cU\x00\x02l%\x94\xf5v\x89`\x8965\u026d\xc5\u07a0\x00\x00\xe0\x949\xeeO\xe0\x0f\xbc\xeddph\xd4\xf5|\x01\xcb\"\xa8\v\xcc\u044a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d49\xf1\x983\x1eK!\xc1\xb7`\xa3\x15_J\xb2\xfe\x00\xa7F\x19\x89lk\x93[\x8b\xbd@\x00\x00\u07d49\xf4Fc\xd9%a\t\x1b\x82\xa7\r\xcfY=u@\x05\x97:\x89\n\u05cb.\xdc!Y\x80\x00\u07d4:\x03U\x94\xc7GGmB\xd1\xee\x96l6\"L\xdd\"I\x93\x89\x13J\xf7Ei\xf9\xc5\x00\x00\u07d4:\x04W(G\xd3\x1e\x81\xf7v\\\xa5\xbf\xc9\xd5W\x15\x9f6\x83\x89\a6-\r\xab\xea\xfd\x80\x00\xe0\x94:\x06\xe3\xbb\x1e\xdc\xfd\fD\xc3\aM\xe0\xbb`k\x04\x98\x94\xa2\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u0794:\x10\x88\x8b~\x14\x9c\xae',\x010,2}\n\xf0\x1a\v$\x88\xeb\xec!\xee\x1d\xa4\x00\x00\u07d4:1\b\xc1\u6023;3l!\x13\x134@\x9d\x97\xe5\xad\xec\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4:6\x8e\xfeJ\u05c6\xe2c\x95\xec\x9f\u01adi\x8c\xae)\xfe\x01\x89\"E\x89\x96u\xf9\xf4\x00\x00\u07d4:=\xd1\x04\xcd~\xb0O!\x93/\xd43\xeaz\xff\u04d3i\xf5\x89\x13aO#\xe2B&\x00\x00\u07d4:B\x97\xda\xc4.\x1eO\xb8\xcb1\xec\xddC\xaew<\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94:\x99`&m\xf6I cS\x8a\x99\xf4\x87\xc9P\xa3\xa5\uc78a\x05\x15\n\xe8J\x8c\xdf\x00\x00\x00\u07d4:\x9b\x11\x10)\xce\x1f \xc9\x10\x9czt\xee\xee\xf3OO.\xb2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4:\x9eTA\xd4K$;\xe5[u\x02z\x1c\ub7ac\xf5\r\xf2\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94:\xa0z4\xa1\xaf\u0216}=\x13\x83\xb9kb\u03d6\xd5\xfa\x90\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94:\xa4,!\xb9\xb3\x1c>'\xcc\xd1~\t\x9a\xf6y\xcd\xf5i\a\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4:\xa9H\xea\x029wU\xef\xfb/\x9d\xc99-\xf1\x05\x8f~3\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\u07d4:\xad\xf9\x8ba\xe5\u0216\xe7\xd1\x00\xa39\x1d2P\"]a\u07c9\f\xafg\x007\x01h\x00\x00\u07d4:\xaeHr\xfd\x90\x93\xcb\xca\xd1@o\x1e\x80x\xba\xb5\x03Y\xe2\x89\x02\"\xc8\xeb?\xf6d\x00\x00\u07d4:\xbb\x8a\xdf\xc6\x04\xf4\x8dY\x84\x81\x1d\u007f\x1dR\xfe\xf6u\x82p\x89\xf2\x97\x19\xb6o\x11\f\x00\x00\u07d4:\xc2\xf0\xff\x16\x12\xe4\xa1\xc3F\xd53\x82\xab\xf6\u0622[\xaaS\x89lk\x93[\x8b\xbd@\x00\x00\u07d4:\xc9\xdczCj\xe9\x8f\xd0\x1cz\x96!\xaa\x8e\x9d\v\x8bS\x1d\x89a\t=|,m8\x00\x00\xe0\x94:\xd0aI\xb2\x1cU\xff\x86|\xc3\xfb\x97@\u04bc\xc7\x10\x121\x8a)\xb7d2\xb9DQ \x00\x00\u07d4:\xd7\x02C\u060b\xf0@\x0fW\xc8\xc1\xfdW\x81\x18H\xaf\x16*\x89.\x9e\xe5\u00c6S\xf0\x00\x00\u07d4:\xd9\x15\xd5P\xb7#AV \xf5\xa9\xb5\xb8\x8a\x85\xf3\x82\xf05\x8965\u026d\xc5\u07a0\x00\x00\u07d4:\xe1`\xe3\xcd`\xae1\xb9\xd6t-h\xe1Nv\xbd\x96\xc5\x17\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4:\xe6+\xd2q\xa7`c\u007f\xady\xc3\x1c\x94\xffb\xb4\xcd\x12\xf7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4:\xeaN\x82\xd2@\x02H\xf9\x98q\xa4\x1c\xa2W\x06\r:\"\x1b\x8965\u026d\xc5\u07a0\x00\x00\u07d4:\xf6[>(\x89ZJ\x00\x11S9\x1d\x1ei\xc3\x1f\xb9\xdb9\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4;\a\xdbZ5\u007fZ\xf2HL\xbc\x9dw\xd7;\x1f\xd0Q\x9f\u01c9\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4;\n\u032fK`|\xfea\xd1s4\xc2\x14\xb7\\\xde\xfd\xbd\x89\x89lk\x93[\x8b\xbd@\x00\x00\u07d4;\x13c\x1a\x1b\x89\xcbVeH\x89\x9a\x1d`\x91\\\xdc\xc4 [\x89lk\x93[\x8b\xbd@\x00\x00\u07d4;\x15\x90\x99\aR\a\u0180vc\xb1\xf0\xf7\xed\xa5J\xc8\xcc\xe3\x89j\xc4\xe6[i\xf9-\x80\x00\u07d4;\x197\xd5\u74f8\x9bc\xfb\x8e\xb5\xf1\xb1\xc9\xcak\xa0\xfa\x8e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4;\"\xda*\x02q\xc8\xef\xe1\x02S'scji\xb1\xc1~\t\x89\x1b6\xa6DJ>\x18\x00\x00\u07d4;\"\u07a3\xc2_\x1bY\u01fd'\xbb\x91\u04e3\xea\xec\xef9\x84\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94;#g\xf8IK_\xe1\x8dh<\x05]\x89\x99\x9c\x9f=\x1b4\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4;,E\x99\x0e!GDQ\xcfOY\xf0\x19U\xb31\xc7\xd7\u0249lk\x93[\x8b\xbd@\x00\x00\xe0\x94;A\x00\xe3\ns\xb0\xc74\xb1\x8f\xfa\x84&\u045b\x191/\x1a\x8a\v\xb5\u046ap\n\xfd\x90\x00\x00\u07d4;B\xa6m\x97\x9fX(4tz\x8b`B\x8e\x9bN\xec\xcd#\x89!\xa1\u01d0\xfa\xdcX\x00\x00\u07d4;Gh\xfdq\xe2\xdb,\xbe\u007f\xa0PH<'\xb4\xeb\x93\x1d\xf3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4;Vj\x8a\xfa\u0456\x82\xdc,\xe8g\x9a<\xe4D\xa5\xb0\xfdO\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94;\\%\x1d\u007f\u05c9;\xa2\t\xfeT\x1c\xec\xd0\xce%:\x99\r\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4;^\x8b\x17w\xca\x18A\x896\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94;\x93\xb1a6\xf1\x1e\xaf\x10\x99l\x95\x99\r;'9\xcc\xea_\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4;\xabK\x01\xa7\xc8K\xa1?\uea70\xbb\x19\x1bw\xa3\xaa\u0723\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4;\xb55\x98\xcc \xe2\x05]\xc5S\xb0I@J\u0277\xdd\x1e\x83\x89!W\x1d\xf7|\x00\xbe\x00\x00\u07d4;\xbc\x13\xd0J\xcc\xc0pz\xeb\u072e\xf0\x87\u0438~\v^\u327e\xd1\xd0&=\x9f\x00\x00\x00\u07d4;\xc6\xe3\xeezV\u038f\x14\xa3u2Y\x0fcqk\x99f\xe8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4;\xc8]ls[\x9c\xdaK\xba_H\xb2K\x13\xe7\x0600{\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4;\xd6$\xb5H\xcbe\x976\x90~\u062a<\fp^$\xb5u\x89lk\x93[\x8b\xbd@\x00\x00\u07d4;\u0660m\x1b\xd3lN\xdd'\xfc\r\x1f[\b\x8d\xda\xe3\xc7*\x89\x1b\x1azB\v\xa0\r\x00\x00\u0794;\u077c\x814\xf7}UY\u007f\xc9|&\xd2f\x98\t\x06\x04\ub23e -j\x0e\xda\x00\x00\xe0\x94;\xf8n\u0623\x15>\xc93xj\x02\xac\t\x03\x01\x85^Wk\x8a_J\x8c\x83u\xd1U@\x00\x00\u07d4;\xfb\u04c4|\x17\xa6\x1c\xf3\xf1{R\xf8\ub879`\xb3\U000df262\xa1]\tQ\x9b\xe0\x00\x00\u07d4<\x03\xbb\xc0#\xe1\xe9?\xa3\xa3\xa6\xe4(\xcf\f\xd8\xf9^\x1e\u0189Rf<\u02b1\xe1\xc0\x00\x00\u07d4<\f=\ufb1c\xeaz\xcc1\x9a\x96\xc3\v\x8e\x1f\xed\xabEt\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4<\x15\xb3Q\x1d\xf6\xf04.sH\u0309\xaf9\xa1h\xb7s\x0f\x8965\u026d\xc5\u07a0\x00\x00\u07d4<\x1f\x91\xf3\x01\xf4\xb5e\xbc\xa2GQ\xaa\x1fv\x13\"p\x9d\u0749a\t=|,m8\x00\x00\xe0\x94<(l\xfb0\x14n_\u05d0\xc2\xc8T\x15RW\x8d\xe34\u060a\x02)\x1b\x11\xaa0n\x8c\x00\x00\u07d4<2.a\x1f\u06c2\rG\xc6\xf8\xfcd\xb6\xfa\xd7L\xa9_^\x89\r%\x8e\xce\x1b\x13\x15\x00\x00\u07d4\xa5\xe5\xbfb\xbb\u0309\x05V\xf6L\x1f\xe7\xfa\x00\x00\u07d4<\x86\x9c\tie#\xce\xd8$\xa0pAF\x05\xbbv#\x1f\xf2\x8965\u026d\xc5\u07a0\x00\x00\u07d4<\x92V\x19\u02731DF?\x057\u06165\x87\x06\xc5 \xb0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4<\x98YK\xf6\x8bW5\x1e\x88\x14\xae\x9em\xfd-%J\xa0o\x89\x10CV\x1a\x88)0\x00\x00\u07d4<\xad\xeb=>\xed?b1\x1dRU>p\xdfJ\xfc\xe5o#\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94<\xae\xdbS\x19\xfe\x80eC\xc5nP!\xd3r\xf7\x1b\xe9\x06.\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4<\xaf\xaf^bPV\x15\x06\x8a\xf8\xeb\"\xa1:\u0629\xe5Pp\x89lf\x06E\xaaG\x18\x00\x00\u07d4<\xb1y\xcbH\x01\xa9\x9b\x95\u00f0\xc3$\xa2\xbd\xc1\x01\xa6S`\x89\x01h\u048e?\x00(\x00\x00\u07d4<\xb5a\u0386BK5\x98\x91\xe3d\xec\x92_\xfe\xff'}\xf7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4<\xcbq\xaah\x80\xcb\v\x84\x01-\x90\xe6\a@\xec\x06\xac\u05cf\x89lk\x93[\x8b\xbd@\x00\x00\u07d4<\xce\xf8\x86yW9G\xe9I\x97y\x8a\x1e2~\b`:e\x89+\xc9\x16\u059f;\x02\x00\x00\xe0\x94<\xd1\xd9s\x1b\xd5H\xc1\xddo\u03a6\x1b\xebu\xd9\x17T\xf7\u04ca\x01\x16\x1d\x01\xb2\x15\xca\xe4\x80\x00\u07d4<\u04e6\xe95y\xc5mIAq\xfcS>z\x90\xe6\xf5\x94d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4<\u05b7Y<\xbe\xe7x0\xa8\xb1\x9d\b\x01\x95\x8f\xcdK\xc5z\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4<\xd7\xf7\xc7\xc257\x80\xcd\xe0\x81\xee\xecE\x82+%\xf2\x86\f\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4<\xe1\u0717\xfc\u05f7\xc4\u04e1\x8aI\xd6\xf2\xa5\xc1\xb1\xa9\x06\u05c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4<\xea0*G*\x94\x03y\xdd9\x8a$\xea\xfd\xba\u07c8\xady\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94<\xec\xa9k\xb1\xcd\xc2\x14\x02\x9c\xbc^\x18\x1d9\x8a\xb9M=A\x8a\x10\xf0\xcf\x06M\u0552\x00\x00\x00\u07d4<\xf4\x84RO\xbd\xfa\xda\xe2m\xc1\x85\xe3++c\x0f\xd2\xe7&\x89\x18TR\xcb*\x91\xc3\x00\x00\u07d4<\xf9\xa1\xd4e\xe7\x8bp9\xe3iDx\xe2b{6\xfc\xd1A\x89J`S*\xd5\x1b\xf0\x00\x00\u07d4<\xfb\xf0fVYpc\x9e\x13\r\xf2\xa7\xd1k\x0e\x14\xd6\t\x1c\x89\\(=A\x03\x94\x10\x00\x00\xe0\x94=\th\x8d\x93\xad\a\xf3\xab\xe6\x8cr'#\xcdh\t\x90C^\x8a\x06ZL\xe9\x9fv\x9en\x00\x00\u07d4=1X{_\u0546\x98Ex\x87%\xa6c)\nI\xd3g\x8c\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4=?\xadI\xc9\xe5\xd2u\x9c\x8e\x8eZzM`\xa0\xdd\x13V\x92\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4=WO\xcf\x00\xfa\xe1\u064c\u023f\x9d\u07e1\xb3\x95;\x97A\xbc\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4=Z\x8b+\x80\xbe\x8b5\xd8\xec\xf7\x89\xb5\xedz\au\xc5\al\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4=f\xcdK\xd6M\\\x8c\x1b^\xea(\x1e\x10m\x1cZ\xad#s\x89i\xc4\xf3\xa8\xa1\x10\xa6\x00\x00\u0794=j\xe0S\xfc\xbc1\x8do\xd0\xfb\xc3S\xb8\xbfT.h\r'\x88\xc6s\xce<@\x16\x00\x00\u07d4=o\xf8,\x93w\x05\x9f\xb3\r\x92\x15r?`\xc7u\u0211\xfe\x89\r\x8e\\\xe6\x17\xf2\xd5\x00\x00\u07d4=y\xa8S\xd7\x1b\xe0b\x1bD\xe2\x97Yel\xa0u\xfd\xf4\t\x89lk\x93[\x8b\xbd@\x00\x00\u07d4=~\xa5\xbf\x03R\x81\x00\xed\x8a\xf8\xae\xd2e>\x92\x1bng%\x8965\u026d\xc5\u07a0\x00\x00\u07d4=\x81?\xf2\xb6\xedW\xb97\u06bf+8\x1d\x14\x8aA\x1f\xa0\x85\x89\x05k\xc7^-c\x10\x00\x00\u07d4=\x88\x143\xf0J}\r'\xf8ID\xe0\x8aQ-\xa3UR\x87\x89A\rXj \xa4\xc0\x00\x00\u07d4=\x89\xe5\x05\xcbF\xe2\x11\xa5?2\xf1g\xa8w\xbe\xc8\u007fK\n\x89\x01[5W\xf1\x93\u007f\x80\x00\xe0\x94=\x8d\a#r\x1es\xa6\xc0\xd8`\xaa\x05W\xab\xd1L\x1e\xe3b\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4=\x8f9\x88\x1b\x9e\xdf\xe9\x12'\xc3?\xa4\xcd\xd9\x1eg\x85D\xb0\x89\x04\xab\a\xbaC\xad\xa9\x80\x00\u07d4=\x9dk\xe5\u007f\xf8>\x06Y\x85fO\x12VD\x83\xf2\xe6\x00\xb2\x89n\xac\xe4?#\xbd\x80\x00\x00\u07d4=\xa3\x9c\xe3\xefJz9f\xb3.\xe7\xeaN\xbc#5\xa8\xf1\x1f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4=\xaa\x01\u03b7\x0e\xaf\x95\x91\xfaR\x1b\xa4\xa2~\xa9\xfb\x8e\xdeJ\x89Zc\xd2\u027cvT\x00\x00\u07d4=\xb5\xfejh\xbd6\x12\xac\x15\xa9\x9aa\xe5U\x92\x8e\xec\xea\xf3\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4=\xb9\xed\u007f\x02L~&7/\xea\xcf+\x05\b\x03D^8\x10\x89E\xb1H\xb4\x99j0\x00\x00\u07d4=\xbf\r\xbf\xd7x\x90\x80\x053\xf0\x9d\xea\x83\x01\xb9\xf0%\u04a6\x8965\u026d\xc5\u07a0\x00\x00\u07d4=\xce\U0005c18b\x15\xd3N\xdaBn\xc7\xe0K\x18\xb6\x01p\x02\x89lh\xcc\u041b\x02,\x00\x00\xe0\x94=\xd1.Uj`76\xfe\xbaJo\xa8\xbdJ\xc4]f*\x04\x8a#u{\x91\x83\xe0x(\x00\x00\u07d4=\u078b\x15\xb3\u033a\xa5x\x01\x12\xc3\xd6t\xf3\x13\xbb\xa6\x80&\x89`\x1dQZ>O\x94\x00\x00\xe0\x94=\xde\xdb\xe4\x89#\xfb\xf9\xe56\xbf\x9f\xfb\aG\xc9\xcd\u04de\xef\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d4=\xea\xe43'\x91?b\x80\x8f\xaa\x1bbv\xa2\xbdch\xea\u0649lk\x93[\x8b\xbd@\x00\x00\u07d4=\xf7b\x04\x9e\u068a\u0192}\x90Lz\xf4/\x94\xe5Q\x96\x01\x89lk\x93[\x8b\xbd@\x00\x00\u07d4>\x04\r@\u02c0\xba\x01%\xf3\xb1_\xde\xfc\xc8?0\x05\xda\x1b\x898E$\xccp\xb7x\x00\x00\u07d4>\v\x8e\xd8n\xd6i\xe1'#\xafur\xfb\xac\xfe\x82\x9b\x1e\x16\x89QM\xe7\xf9\xb8\x12\xdc\x00\x00\xe0\x94>\f\xbejm\xcba\xf1\x10\xc4[\xa2\xaa6\x1d\u007f\xca\xd3\xdas\x8a\x01\xb2\u07dd!\x9fW\x98\x00\x00\u07d4>\x19KN\xce\xf8\xbbq\x1e\xa2\xff$\xfe\xc4\xe8{\xd02\xf7\u0449\x8b\x9d\xc1\xbc\x1a\x03j\x80\x00\xe0\x94>\x1b\"0\xaf\xbb\xd3\x10\xb4\x92jLwmZ\u705cf\x1d\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4>\x1cS0\x0eL\x16\x89\x12\x16<~\x99\xb9]\xa2h\xad(\n\x896b2\\\u044f\xe0\x00\x00\u07d4>\x1c\x96 c\xe0\xd5)YA\xf2\x10\u0723\xabS\x1e\xec\x88\t\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4>,\xa0\xd24\xba\xf6\a\xadFj\x1b\x85\xf4\xa6H\x8e\xf0\n\xe7\x89\x04\xda!\xa3H=V\x80\x00\u07d4>/&#^\x13zs$\xe4\xdc\x15K]\xf5\xafF\xea\x1aI\x89\x017\xaa\xd8\x03-\xb9\x00\x00\xe0\x94>1a\xf1\xea/\xbf\x12ny\xda\x18\x01\u0695\x12\xb3y\x88\u024a\nm\xd9\f\xaeQ\x14H\x00\x00\xe0\x94>6\xc1rS\xc1\x1c\xf3\x89t\xed\r\xb1\xb7Y\x16\r\xa67\x83\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4><\u04fe\xc0e\x91\xd64o%Kb\x1e\xb4\x1c\x89\x00\x8d1\x895\u07fe\u069f74\x00\x00\u07d4>E\xbdU\u06d0`\xec\xed\x92;\xb9\xcbs<\xb3W?\xb51\x89X\xe7\x92n\xe8X\xa0\x00\x00\u07d4>M\x13\xc5Z\x84\xe4n\xd7\xe9\u02d0\xfd5^\x8a\u0651\u33c965\u026d\xc5\u07a0\x00\x00\u07d4>N\x92e\"<\x9782L\xf2\v\xd0`\x06\xd0\a>\u06cc\x89\a?u\u0460\x85\xba\x00\x00\xe0\x94>O\xbdf\x10\x15\xf6F\x1e\xd6s\\\xef\xef\x01\xf3\x14E\xde:\x8a\x03n4)\x98\xb8\xb0 \x00\x00\xe0\x94>S\xff!\a\xa8\u07be3(I:\x92\xa5\x86\xa7\xe1\xf4\x97X\x8a\x04\xe6\x9c*q\xa4\x05\xab\x00\x00\u07d4>Z9\xfd\xdap\xdf\x11&\xab\r\u011asx1\x1aSz\x1f\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\xe0\x94>Z\xbd\t\xceZ\xf7\xba\x84\x87\xc3Y\xe0\xf2\xa9:\x98k\v\x18\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4>\\\xb8\x92\x8cAx%\xc0:;\xfc\xc5!\x83\xe5\xc9\x1eB\u05c9\xe71\xd9\xc5,\x96/\x00\x00\u07d4>^\x93\xfbL\x9c\x9d\x12F\xf8\xf2G5\x8e\"\xc3\xc5\xd1{j\x89\b!\xab\rD\x14\x98\x00\x00\u07d4>a\x83P\xfa\x01ez\xb0\xef>\xba\xc8\xe3p\x12\xf8\xfc+o\x89\x98\x06\xde=\xa6\xe9x\x00\x00\u07d4>c\xce;$\xca(e\xb4\u0166\x87\xb7\xae\xa3Y~\xf6\xe5H\x89lk\x93[\x8b\xbd@\x00\x00\u07d4>f\xb8GiVj\xb6yE\xd5\xfa\x8175V\xbc\u00e1\xfa\x89\b=lz\xabc`\x00\x00\xe0\x94>v\xa6-\xb1\x87\xaat\xf68\x17S;0l\xea\xd0\xe8\u03be\x8a\x06\x9bZ\xfa\xc7P\xbb\x80\x00\x00\u07d4>z\x96k]\xc3W\xff\xb0~\x9f\xe0g\xc4W\x91\xfd\x8e0I\x89\x034-`\xdf\xf1\x96\x00\x00\xe0\x94>\x81w!u#~\xb4\xcb\xe0\xfe-\xca\xfd\xad\xff\xebj\x19\x99\x8a\x01\xdd\f\x88_\x9a\r\x80\x00\x00\u07d4>\x83I\xb6\u007fWED\x9fe\x93g\u066dG\x12\xdb[\x89Z\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4>\x83TO\x00\x82U%r\u01c2\xbe\xe5\xd2\x18\xf1\xef\x06J\x9d\x89\x05l\xd5_\xc6M\xfe\x00\x00\u07d4>\x84\xb3\\[\"ePpa\xd3\vo\x12\xda\x03?\xe6\xf8\xb9\x89a\t=|,m8\x00\x00\u07d4>\x86A\xd4\x87E\xba2/_\xd6\xcbP\x12N\xc4f\x88\u01e6\x9a\u007f\xae\x8a\x01\n\xfc\x1a\xde;N\xd4\x00\x00\u07d4>\x91N0\x18\xac\x00D\x93A\u011d\xa7\x1d\x04\xdf\xee\xedb!\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4>\x94\x10\u04f9\xa8~\xd5\xe4Q\xa6\xb9\x1b\xb8\x92?\xe9\x0f\xb2\xb5\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4>\x94\xdfS\x13\xfaR\x05p\xef#+\xc31\x1d_b/\xf1\x83\x89lk\x93[\x8b\xbd@\x00\x00\u0794>\x9b4\xa5\u007f3u\xaeY\xc0\xa7^\x19\u0136A\"\x8d\x97\x00\x88\xf8i\x93)g~\x00\x00\u07d4>\xad\xa8\xc9/V\x06~\x1b\xb7<\xe3x\xdaV\xdc,\xdf\xd3e\x89w\xcd\xe9:\xeb\rH\x00\x00\xe0\x94>\xaf\by\xb5\xb6\xdb\x15\x9bX\x9f\x84W\x8bjt\xf6\xc1\x03W\x8a\x01\x898\xb6q\xfae\xa2\x80\x00\u07d4>\xaf1k\x87a]\x88\xf7\xad\xc7|X\xe7\x12\xedMw\x96k\x89\x05m\xbcL\xee$d\x80\x00\u07d4>\xb8\xb3;!\xd2<\u0686\xd8(\x88\x84\xabG\x0e\x16F\x91\xb5\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4>\xb9\xef\x06\xd0\xc2Y\x04\x03\x19\x94~\x8czh\x12\xaa\x02S\u0609\t\r\x97/22<\x00\x00\u07d4>\u030e\x16h\xdd\xe9\x95\xdcW\x0f\xe4\x14\xf4B\x11\xc54\xa6\x15\x89lk\x93[\x8b\xbd@\x00\x00\u07d4>\u03752\xe3\x97W\x96b\xb2\xa4aA\u73c25\x93j_\x89\x03\x9f\xba\xe8\xd0B\xdd\x00\x00\u07d4>\xeeo\x1e\x966\vv\x89\xb3\x06\x9a\xda\xf9\xaf\x8e\xb6\f\u404965\u026d\xc5\u07a0\x00\x00\xe0\x94?\b\u066d\x89O\x81>\x8e!H\xc1`\xd2K5:\x8et\xb0\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4?\f\x83\xaa\xc5qybsN\\\xea\xea\xec\u04db(\xad\x06\xbe\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94?\x10\x80\x02\x82\u0477\xdd\u01cf\xa9-\x820\aN\x1b\xf6\xae\xae\x8a\x01\n\xfc\x1a\xde;N\xd4\x00\x00\u07d4?\x123qO M\xe9\xdeN\xe9m\a;6\x8d\x81\x97\x98\x9f\x89\x02\x17\xc4\x10t\xe6\xbb\x00\x00\u07d4?\x17:\xa6\xed\xf4i\u0445\xe5\x9b\xd2j\xe4#k\x92\xb4\xd8\xe1\x89\x11X\xe4`\x91=\x00\x00\x00\u07d4?\x1b\xc4 \xc5<\x00,\x9e\x90\x03|D\xfej\x8e\xf4\xdd\xc9b\x89\t`\xdbwh\x1e\x94\x00\x00\u07d4?#a\b\xee\xc7\"\x89\xba\u00e6\\\u0483\xf9^\x04\x1d\x14L\x8964\xbf9\xab\x98x\x80\x00\u07d4?-\xa0\x93\xbb\x16\xeb\x06O\x8b\xfa\x9e0\xb9)\xd1_\x8e\x1cL\x89lk\x93[\x8b\xbd@\x00\x00\u07d4?-\xd5]\xb7\xea\xb0\xeb\xeee\xb3>\xd8 ,\x1e\x99.\x95\x8b\x89,s\xc97t,P\x00\x00\u07d4?/8\x14\x91y|\xc5\xc0\u0502\x96\xc1O\xd0\xcd\x00\xcd\xfa-\x89+\x95\xbd\xcc9\xb6\x10\x00\x00\u07d4?0\u04fc\x9f`\"2\xbcrB\x88\xcaF\xcd\v\a\x88\xf7\x15\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4?<\x8ea\xe5`L\xef\x06\x05\xd46\xdd\"\xac\u0346\"\x17\xfc\x89Hz\x9a0E9D\x00\x00\u07d4??F\xb7\\\xab\xe3{\xfa\u0307`(\x1fCA\xca\u007fF=\x89 \xacD\x825\xfa\xe8\x80\x00\u07d4?G)c\x19x\x83\xbb\xdaZ\x9b}\xfc\xb2-\xb1\x14@\xad1\x89\x1a\x19d<\xb1\xef\xf0\x80\x00\u07d4?L\xd19\x9f\x8a4\xed\u06da\x17\xa4q\xfc\x92+Xp\xaa\xfc\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4?U\x1b\xa9<\xd5F\x93\xc1\x83\xfb\x9a\xd6\re\xe1`\x96s\u0249lk\x93[\x8b\xbd@\x00\x00\xe0\x94?bzv\x9ej\x95\x0e\xb8p\x17\xa7\u035c\xa2\bq\x13h1\x8a\x02\ub3b1\xa1r\u0738\x00\x00\u07d4?m\xd3e\x0e\xe4(\u0737u\x95S\xb0\x17\xa9j\x94(j\u0249Hz\x9a0E9D\x00\x00\u07d4?tr7\x80o\xed?\x82\x8ahR\xeb\bg\xf7\x90'\xaf\x89\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4?u\xaea\xcc\x1d\x80Be;[\xae\xc4D>\x05\x1c^z\xbd\x89\x05-T(\x04\xf1\xce\x00\x00\u07d4?\xb7\u0457\xb3\xbaO\xe0E\xef\xc2=P\xa1E\x85\xf5X\u0672\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94?\xbc\x1eE\x18\xd74\x00\xc6\xd0F5\x949\xfbh\xea\x1aI\xf4\x8a\x03y\v\xb8U\x13v@\x00\x00\u07d4?\xbe\xd6\xe7\xe0\u029c\x84\xfb\xe9\xeb\u03ddN\xf9\xbbIB\x81e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4?\u043bGy\x8c\xf4L\u07feM3=\xe67\xdfJ\x00\xe4\\\x89\x05lUy\xf7\"\x14\x00\x00\xe0\x94?\xe4\x0f\xbd\x91\x9a\xad(\x18\xdf\x01\xeeM\xf4lF\x84*\xc59\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4?\xe8\x01\xe6\x135\xc5\x14\r\xc7\xed\xa2\xefR\x04F\nP\x120\x89lk\x93[\x8b\xbd@\x00\x00\u07d4?\xf86\xb6\xf5{\x90\x1bD\f0\xe4\xdb\xd0e\xcf7\xd3\u050c\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4?\xfc\xb8p\xd4\x02=%]Qg\u0625\a\xce\xfc6kh\xba\x89#4^\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4@s\xfaI\xb8q\x17\u02d0\x8c\xf1\xabQ-\xa7T\xa92\xd4w\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4@\x8ai\xa4\a\x15\xe1\xb3\x13\xe15N`\b\x00\xa1\xe6\xdc\x02\xa5\x89\x01\u7e11\u0312T\x00\x00\u07d4@\x9b\xd7P\x85\x82\x1c\x1d\xe7\f\xdc;\x11\xff\xc3\xd9#\xc7@\x10\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4@\x9dZ\x96.\xde\uefa1x\x01\x8c\x0f8\xb9\u0372\x13\xf2\x89\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4@\xa31\x19[\x97s%\u00aa(\xfa/B\xcb%\xec<%<\x89lk\x93[\x8b\xbd@\x00\x00\u07d4@\xa7\xf7(g\xa7\u0706w\v\x16+uW\xa44\xedP\xcc\xe9\x8965\u026d\xc5\u07a0\x00\x00\u07d4@\xab\n>\x83\xd0\u022c\x93f\x91\x05 \xea\xb1w+\xac;\x1a\x894\xf1\f-\xc0^|\x00\x00\u07d4@\xabf\xfe!>\xa5l:\xfb\x12\xc7[\xe3?\x8e2\xfd\b]\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94@\xadt\xbc\v\xce*E\xe5/6\xc3\u07bb\x1b:\xda\x1bv\x19\x8a\x01p\x16-\xe1\t\xc6X\x00\x00\u07d4@\u03c9\x05\x91\xea\u484f\x81*)T\xcb)_c3'\xe6\x89\x02\x9b\xf76\xfcY\x1a\x00\x00\u07d4@\u03d0\xef[v\x8c]\xa5\x85\x00,\xcb\xe6avP\xd8\xe87\x8963\x03\"\xd5#\x8c\x00\x00\xe0\x94@\xd4]\x9dv%\xd1QV\xc92\xb7q\xca{\x05'\x13\tX\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4@\xdb\x1b\xa5\x85\xce4S\x1e\xde\xc5IHI9\x13\x81\xe6\xcc\u04c9a\t=|,m8\x00\x00\xe0\x94@\xdfI^\xcf?\x8bL\xef*l\x18\x99W$\x8f\u813c+\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4@\xe0\xdb\xf3\xef\uf404\xea\x1c\xd7\xe5\x03\xf4\v;J\x84C\xf6\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4@\xe2D\n\xe1B\u02006j\x12\xc6\xd4\x10/K\x844\xb6*\x8965\u026d\xc5\u07a0\x00\x00\u07d4@\xe3\u0083\xf7\xe2M\xe0A\f\x12\x1b\xee`\xa5`\u007f>)\xa6\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94@\xeaPD\xb2\x04\xb20v\xb1\xa5\x80;\xf1\xd3\f\x0f\x88\x87\x1a\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\xe0\x94@\xed\xdbD\x8di\x0e\xd7.\x05\xc2%\xd3O\xc85\x0f\xa1\xe4\u014a\x01{x\x83\xc0i\x16`\x00\x00\xe0\x94@\xf4\xf4\xc0ls,\xd3[\x11\x9b\x89;\x12~}\x9d\aq\xe4\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4A\x01\x0f\u023a\xf8C}\x17\xa0Ci\x80\x9a\x16\x8a\x17\xcaV\xfb\x89\x05k\xc7^-c\x10\x00\x00\u07d4A\x03)\x96q\xd4gc\x97\x8f\xa4\xaa\x19\xee4\xb1\xfc\x95'\x84\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4A\x03<\x1bm\x05\xe1\u0289\xb0\x94\x8f\xc6DS\xfb\xe8z\xb2^\x89Hz\x9a0E9D\x00\x00\u07d4A\t\x8a\x81E#\x17\xc1\x9e>\xef\v\xd1#\xbb\xe1x\xe9\xe9\u0289\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4A\x16\x10\xb1x\xd5a}\xfa\xb94\u0493\xf5\x12\xa9>\\\x10\xe1\x89\t79SM(h\x00\x00\u07d4A\x1c\x83\x1c\xc6\xf4O\x19e\xecWW\xabN[<\xa4\xcf\xfd\x1f\x89\x17\n\x0fP@\xe5\x04\x00\x00\xe0\x94A*h\xf6\xc6EU\x9c\xc9w\xfcId\x04z \x1d\x1b\xb0\xe2\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4A?K\x02f\x9c\xcf\xf6\x80k\xc8&\xfc\xb7\xde\xca;\x0e\xa9\xbc\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4AE\x99\t.\x87\x9a\xe2Sr\xa8MsZ\xf5\xc4\xe5\x10\xcdm\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4AHV\x12\xd04F\xecL\x05\xe5$NV?\x1c\xba\xe0\xf1\x97\x894\x95tD\xb8@\xe8\x00\x00\u07d4A]\tj\xb0b\x93\x18?<\x03=%\xf6\xcfqx\xac;\u01c9\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4Af\xfc\b\u0285\xf7f\xfd\xe81F\x0e\x9d\xc9<\x0e!\xaal\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94Ag\x84\xaf`\x960\xb0p\u051a\x8b\xcd\x12#\\d(\xa4\b\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4Ag\xcdH\xe73A\x8e\x8f\x99\xff\xd14\x12\x1cJJ\xb2x\u0109\xc5S%\xcat\x15\xe0\x00\x00\u07d4Al\x86\xb7 \x83\xd1\xf8\x90}\x84\xef\xd2\xd2\u05c3\xdf\xfa>\xfb\x89lj\xccg\u05f1\xd4\x00\x00\u07d4AsA\x9d\\\x9fc)U\x1d\xc4\xd3\xd0\u03ac\x1bp\x1b\x86\x9e\x89\x04\xc5>\xcd\xc1\x8a`\x00\x00\u07d4At\xfa\x1b\xc1*;q\x83\u02eb\xb7z\vYU{\xa5\xf1\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4Axj\x10\xd4G\xf4\x84\xd32D\u0337\xfa\u034bB{[\x8c\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94Az<\u0454\x96S\nmB\x04\u00f5\xa1|\xe0\xf2\a\xb1\xa5\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4A~N&\x88\xb1\xfdf\xd8!R\x9eF\xedOB\xf8\xb3\xdb=\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94A\x9aq\xa3l\x11\xd1\x05\xe0\xf2\xae\xf5\xa3\xe5\x98\a\x8e\x85\xc8\v\x8a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x94A\x9b\xdes\x16\xcc\x1e\u0495\u0205\xac\xe3B\u01db\xf7\xee3\xea\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4A\xa2\xf2\xe6\xec\xb8c\x94\xec\x0e3\x8c\x0f\xc9~\x9cU\x83\xde\u0489l\xee\x06\u077e\x15\xec\x00\x00\u07d4A\xa8\u0083\x00\x81\xb1\x02\xdfn\x011e|\a\xabc[T\u0389lj\xccg\u05f1\xd4\x00\x00\u07d4A\xa8\xe26\xa3\x0emc\xc1\xffdM\x13*\xa2\\\x89S~\x01\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4A\xa9\xa4\x04\xfc\x9f[\xfe\xe4\x8e\xc2e\xb1%#3\x8e)\xa8\xbf\x89\x15\b\x94\xe8I\xb3\x90\x00\x00\u07d4A\xad6\x9fu\x8f\xef8\xa1\x9a\xa3\x14\x93y\x83,\x81\x8e\xf2\xa0\x8966\x9e\xd7t}&\x00\x00\u07d4A\xb2\xd3O\xde\v\x10)&+Ar\xc8\x1c\x15\x90@[\x03\xae\x8965\u026d\xc5\u07a0\x00\x00\u07d4A\xb2\xdb\u05dd\u069b\x86Ojp0'T\x19\u00dd>\xfd;\x89\xadx\xeb\u016cb\x00\x00\x00\u07d4A\xc3\xc26u4\xd1;\xa2\xb3?\x18\\\xdb\xe6\xacC\xc2\xfa1\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4A\u02d8\x96D_p\xa1\n\x14!R\x96\xda\xf6\x14\xe3,\xf4\u0549g\x8a\x93 b\xe4\x18\x00\x00\u07d4A\xcey\x95\t5\xcf\xf5[\xf7\x8eL\xce\xc2\xfec\x17\x85\u06d5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4A\u04f71\xa3&\xe7hX\xba\xa5\xf4\xbd\x89\xb5{6\x93#C\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\xe0\x94A\xe4\xa2\x02u\xe3\x9b\xdc\xef\xebe\\\x03\"tKvQ@\u008a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4A\xed-\x8ep\x81H,\x91\x9f\xc2=\x8f\x00\x91\xb3\xc8,F\x85\x89F:\x1ev[\u05ca\x00\x00\xe0\x94A\xf2~tK\u049d\xe2\xb0Y\x8f\x02\xa0\xbb\x9f\x98\xe6\x81\ua90a\x01\xa4\xab\xa2%\xc2\a@\x00\x00\u07d4A\xf4\x89\xa1\xect{\u009c>_\x9d\x8d\xb9xw\xd4\u0474\xe9\x89\a?u\u0460\x85\xba\x00\x00\u07d4B\x0f\xb8n}+Q@\x1f\xc5\xe8\xc7 \x15\xde\xcbN\xf8\xfc.\x8965\u026d\xc5\u07a0\x00\x00\u07d4B\x16\x84\xba\xa9\xc0\xb4\xb5\xf5S8\xe6\xf6\xe7\xc8\xe1F\xd4\x1c\xb7\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4B9\x96Y\xac\xa6\xa5\xa8c\xea\"E\xc93\xfe\x9a5\xb7\x88\x0e\x89n\xce2\xc2l\x82p\x00\x00\xe0\x94B;\xcaG\xab\xc0\fpW\xe3\xad4\xfc\xa6>7_\xbd\x8bJ\x8a\x03\xcf\xc8.7\xe9\xa7@\x00\x00\u07d4B<1\a\xf4\xba\xceANI\x9cd9\nQ\xf7F\x15\xca^\x89lk\x93[\x8b\xbd@\x00\x00\u07d4B<\xc4YL\xf4\xab\xb66\x8d\xe5\x9f\u04b1#\a4a!C\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94BD\xf13\x11X\xb9\xce&\xbb\xe0\xb9#k\x92\x03\xca5\x144\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u0794BQw\xebt\xad\n\x9d\x9aWR\"\x81G\xeemcV\xa6\u6239\x8b\xc8)\xa6\xf9\x00\x00\u07d4BW%\xc0\xf0\x8f\b\x11\xf5\xf0\x06\xee\xc9\x1c\\\\\x12k\x12\xae\x89\b!\xab\rD\x14\x98\x00\x00\xe0\x94BX\xfdf/\xc4\xce2\x95\xf0\xd4\xed\x8f{\xb1D\x96\x00\xa0\xa9\x8a\x01lE.\xd6\b\x8a\xd8\x00\x00\xe0\x94B\\\x18\x16\x86\x8fww\xcc+\xa6\xc6\u048c\x9e\x1eylR\xb3\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4B\\3\x8a\x13%\xe3\xa1W\x8e\xfa)\x9eW\u0646\xebGO\x81\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94BbY\xb0\xa7Vp\x1a\x8bf5(R!V\xc0(\x8f\x0f$\x8a\x02\x18\xae\x19k\x8dO0\x00\x00\u07d4Bm\x15\xf4\a\xa0\x115\xb1:kr\xf8\xf2R\v51\xe3\x02\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4Box\xf7\r\xb2Y\xac\x854\x14[)4\xf4\xef\x10\x98\xb5\u0609\x13\x84\x00\xec\xa3d\xa0\x00\x00\u07d4Bs-\x8e\xf4\x9f\xfd\xa0K\x19x\x0f\xd3\xc1\x84i\xfb7A\x06\x89\x17\v\x00\xe5\u4a7e\x00\x00\u07d4Bt\x17\xbd\x16\xb1\xb3\xd2-\xbb\x90-\x8f\x96W\x01o$\xa6\x1c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Btj\xee\xa1O'\xbe\xff\f\r\xa6BS\xf1\xe7\x97\x18\x90\xa0\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4B{F*\xb8NP\x91\xf4\x8aF\xeb\f\u0712\xdd\xcb&\xe0x\x89lk\x93[\x8b\xbd@\x00\x00\u07d4B~GQ\u00fa\xbex\xcf\xf8\x83\b\x86\xfe\xbc\x10\xf9\x90\x8dt\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94B~\xc6h\xac\x94\x04\xe8\x95\u0306\x15\x11\xd1b\nI\x12\xbe\x98\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4B\x80\xa5\x8f\x8b\xb1\v\x94@\u0794\xf4+OY! \x82\x01\x91\x89lk\x93[\x8b\xbd@\x00\x00\u07d4B\x8a\x1e\xe0\xed3\x1dyR\u033e\x1cyt\xb2\x85+\u0453\x8a\x89w\xb7JN\x8d\xe5e\x00\x00\u0794B\x9c\x06\xb4\x87\xe8Tj\xbd\xfc\x95\x8a%\xa3\xf0\xfb\xa5?o\x00\x88\xbbdJ\xf5B\x19\x80\x00\xe0\x94B\xa9\x8b\xf1`'\xceX\x9cN\xd2\xc9X1\xe2rB\x05\x06N\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4B\xc6\xed\xc5\x15\xd3UW\x80\x8d\x13\xcdD\xdc\xc4@\v%\x04\xe4\x89\n\xba\x14\u015b\xa72\x00\x00\u07d4B\xce\xcf\u0492\x10y\xc2\xd7\xdf?\b\xb0z\xa3\xbe\xee^!\x9a\x8965\u026d\xc5\u07a0\x00\x00\u07d4B\u04669\x9b0\x16\xa8Y\u007f\x8bd\t'\xb8\xaf\xbc\xe4\xb2\x15\x89\xa1\x8b\xce\xc3H\x88\x10\x00\x00\u07d4B\xd3I@\xed\xd2\xe7\x00]F\xe2\x18\x8eL\xfe\u0383\x11\xd7M\x89\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4B\u04e5\xa9\x01\xf2\xf6\xbd\x93V\xf1\x12\xa7\x01\x80\xe5\xa1U\v`\x892$\xf4'#\xd4T\x00\x00\u07d4B\u05b2c\xd9\xe9\xf4\x11lA\x14$\xfc\x99Ux;\xa1\xc5\x1b\x81\x0f\xc4g\u057aM\xeaB\xf7\xa9\x88^i\x8a\bxg\x83&\xea\xc9\x00\x00\x00\xe0\x94C>\xb9J3\x90\x86\xed\x12\u067d\xe9\xcd\x1dE\x86\x03\xc9}\u058a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4CI\"Zb\xf7\n\xeaH\n\x02\x99\x15\xa0\x1eSy\xe6O\xa5\x89\x8c\xd6~#4\xc0\xd8\x00\x00\u07d4CT\"\x1eb\xdc\t\xe6@d6\x16:\x18^\xf0m\x11J\x81\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94CTC\xb8\x1d\xfd\xb9\xbd\x8cg\x87\xbc%\x18\xe2\xd4~W\xc1_\x8a\x01C\x8d\x93\x97\x88\x1e\xf2\x00\x00\u07d4Ca\u0504o\xaf\xb3w\xb6\xc0\xeeI\xa5\x96\xa7\x8d\xdf5\x16\xa3\x89\xc2\x12z\xf8X\xdap\x00\x00\xe0\x94Cd0\x9a\x9f\xa0p\x95`\x0fy\xed\xc6Q \xcd\xcd#\xdcd\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4Cg\xaeK\f\xe9d\xf4\xa5J\xfdK\\6\x84\x96\xdb\x16\x9e\x9a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Ct\x89(\xe8\xc3\xecD6\xa1\u0412\xfb\xe4:\xc7I\xbe\x12Q\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4Cv{\xf7\xfd*\xf9[r\xe91-\xa9D<\xb1h\x8eCC\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94Cy\x838\x8a\xb5\x9aO\xfc!_\x8e\x82iF\x10)\xc3\xf1\xc1\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4C\x89\x8cI\xa3MP\x9b\xfe\xd4\xf7`A\xee\x91\xca\xf3\xaaj\xa5\x89\x10CV\x1a\x88)0\x00\x00\u07d4C\x8c/T\xff\x8eb\x9b\xab6\xb1D+v\v\x12\xa8\x8f\x02\xae\x89lk\x93[\x8b\xbd@\x00\x00\u07d4C\x98b\x8e\xa6c-9>\x92\x9c\xbd\x92\x84d\xc5h\xaaJ\f\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4C\x9d//Q\x10\xa4\u054b\x17W\x93P\x15@\x87@\xfe\xc7\xf8\x89\u03e5\xc5\x15\x0fL\x88\x80\x00\u07d4C\x9d\xee?vy\xff\x100s?\x93@\xc0\x96hkI9\v\x89lk\x93[\x8b\xbd@\x00\x00\u07d4C\xb0y\xba\xf0ry\x99\xe6k\xf7C\u057c\xbfwl;\t\"\x89lk\x93[\x8b\xbd@\x00\x00\u07d4C\xbc-M\xdc\xd6X;\xe2\u01fc\tK(\xfbr\xe6+\xa8;\x89lk\x93[\x8b\xbd@\x00\x00\u07d4C\xc7\xeb\u0173\xe7\xaf\x16\xf4}\xc5az\xb1\x0e\x0f9\xb4\xaf\xbb\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4C\u02d6R\x81\x8coMg\x96\xb0\xe8\x94\t0ly\xdbcI\x89lk\x93[\x8b\xbd@\x00\x00\u07d4C\xcc\b\xd0s*\xa5\x8a\xde\xf7a\x9b\xedFU\x8a\xd7wAs\x89\xf0\xe7\u0730\x12*\x8f\x00\x00\xe0\x94C\u0567\x1c\xe8\xb8\xf8\xae\x02\xb2\xea\xf8\xea\xf2\xca(@\xb9?\xb6\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u0794C\xdb\u007f\xf9Z\bm(\ubff8/\xb8\xfb_#\n^\xbc\u0348\xdfn\xb0\xb2\xd3\xca\x00\x00\u07d4C\xe7\xec\x84cX\xd7\xd0\xf97\xad\x1c5\v\xa0i\u05ffr\xbf\x89\x06p\xaeb\x92\x14h\x00\x00\u07d4C\xf1o\x1eu\xc3\xc0j\x94x\xe8\u0157\xa4\n<\xb0\xbf\x04\u0309\x9d\xf7\u07e8\xf7`H\x00\x00\u07d4C\xf4p\xede\x9e)\x91\xc3u\x95~]\xde\u017d\x1d8\"1\x89\x05k\xc7^-c\x10\x00\x00\u07d4C\xf7\xe8n8\x1e\xc5\x1e\u0110m\x14v\u02e9z=\xb5\x84\xe4\x8965\u026d\xc5\u07a0\x00\x00\u07d4C\xff8t>\xd0\xcdC0\x8c\x06e\t\u030e~r\xc8b\xaa\x89i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94C\xff\x88S\xe9\x8e\xd8@k\x95\x00\n\u0684\x83b\u05a09*\x8a\x04\xae\v\x1cM.\x84\xd0\x00\x00\u07d4D\t\x88f\xa6\x9bh\xc0\xb6\xbc\x16\x82)\xb9`5\x87\x05\x89g\x89\n1\x06+\xee\xedp\x00\x00\u07d4D\x19\xaca\x8d]\xea|\xdc`w o\xb0}\xbd\xd7\x1c\x17\x02\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4D\x1aR\x00\x16a\xfa\xc7\x18\xb2\u05f3Q\xb7\xc6\xfbR\x1az\xfd\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94D\x1a\u0282c\x13$\xac\xbf\xa2F\x8b\xda2[\xbdxG{\xbf\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4D\x1f7\xe8\xa0)\xfd\x02H/(\x9cI\xb5\xd0m\x00\xe4\b\xa4\x89\x12\x11\xec\xb5m\x13H\x80\x00\u07d4D \xaa5F[\xe6\x17\xad$\x98\xf3p\xde\n<\xc4\xd20\xaf\x89lk\x93[\x8b\xbd@\x00\x00\u07d4D#/\xf6m\xda\xd1\xfd\x84\x12f8\x006\xaf\xd7\xcf}\u007fB\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4D%\rGn\x06$\x84\xe9\b\n9g\xbf:Js*\xd7?\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4D)\xa2\x9f\xee\x19\x84Pg,\f\x1d\a1b%\v\xecdt\x896*\xaf\x82\x02\xf2P\x00\x00\u07d4D5RS\xb2wH\xe3\xf3O\xe9\xca\xe1\xfbq\x8c\x8f$\x95)\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4D8\xe8\x80\xcb'f\xb0\xc1\u03ae\xc9\xd2A\x8f\u03b9R\xa0D\x89\a?\xa0s\x90?\b\x00\x00\u07d4DL\xafy\xb7\x138\ue6a7\xc73\xb0*\u02a7\xdc\x02YH\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4D\\\xb8\xde^=\xf5 \xb4\x99\xef\u0240\xf5+\xff@\xf5\\v\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94Dj\x809\xce\u03dd\xceHy\xcb\xca\xf3I;\xf5E\xa8\x86\x10\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4Dt)\x9d\x0e\xe0\x90\u0710x\x9a\x14\x86H\x9c=\rd^m\x8965\u026d\xc5\u07a0\x00\x00\u07d4D\x8b\xf4\x10\xad\x9b\xbc/\xec\xc4P\x8d\x87\xa7\xfc.K\x85a\xad\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4D\x90\x1e\r\x0e\b\xac=^\x95\xb8\xec\x9d^\x0f\xf5\xf1.\x03\x93\x89\x16\xa1\xf9\xf5\xfd}\x96\x00\x00\xe0\x94D\x93\x12<\x02\x1e\xce;3\xb1\xa4R\xc9&\x8d\xe1@\a\xf9\u04ca\x01je\x02\xf1Z\x1eT\x00\x00\xe0\x94D\x9a\xc4\xfb\xe3\x83\xe3g8\x85^6JW\xf4q\xb2\xbf\xa11\x8a)\xb7d2\xb9DQ \x00\x00\u07d4D\xa0\x1f\xb0J\xc0\xdb,\xce]\xbe(\x1e\x1cF\xe2\x8b9\xd8x\x89lj\xccg\u05f1\xd4\x00\x00\u07d4D\xa6=\x18BE\x87\xb9\xb3\a\xbf\xc3\xc3d\xae\x10\xcd\x04\xc7\x13\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94D\xa8\x98\x9e20\x81!\xf7$f\x97\x8d\xb3\x95\xd1\xf7l:K\x8a\x01\x88P)\x9fB\xb0j\x00\x00\u07d4D\xc1\x11\v\x18\x87\x0e\xc8\x11x\xd9=!X8\xc5Q\u050ed\x89\n\xd6\xf9\x85\x93\xbd\x8f\x00\x00\u07d4D\xc1Ge\x12|\xde\x11\xfa\xb4l],\xf4\u0532\x89\x00#\xfd\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94D\xc5N\xaa\x8a\xc9@\xf9\xe8\x0f\x1et\xe8/\xc1O\x16v\x85j\x8a\x01\xab,\xf7\xc9\xf8~ \x00\x00\u07d4D\xcdwSZ\x89?\xa7\xc4\xd5\xeb:$\x0ey\u0419\xa7--\x89,s\xc97t,P\x00\x00\u07d4D\u07faP\xb8)\xbe\xcc_O\x14\u0470J\xab3 \xa2\x95\xe5\x8965\u026d\xc5\u07a0\x00\x00\u07d4D\xe2\xfd\xc6y\xe6\xbe\xe0\x1e\x93\xefJ:\xb1\xbc\xce\x01*\xbc|\x89\x16=\x19I\x00\xc5E\x80\x00\xe0\x94D\xf6/*\xaa\xbc)\xad:k\x04\xe1\xffo\x9c\xe4R\xd1\xc1@\x8a\x03\x99\x92d\x8a#\u0220\x00\x00\u07d4D\xff\xf3{\xe0\x1a8\x88\u04f8\xb8\u1200\xa7\xdd\xef\xee\xea\u04c9\x0e\f[\xfc}\xae\x9a\x80\x00\u07d4E\x06\xfe\x19\xfaK\x00k\xaa9\x84R\x9d\x85\x16\xdb++P\xab\x89lk\x93[\x8b\xbd@\x00\x00\u07d4E\x1b6\x99G[\xed]y\x05\xf8\x90Z\xa3Eo\x1e\u05c8\xfc\x89\x8a\xc7#\x04\x89\xe8\x00\x00\x00\u0794E\x1bpp%\x9b\u06e2q\x00\xe3n#B\x8aS\xdf\xe3\x04\u9239\x8b\xc8)\xa6\xf9\x00\x00\u07d4E'+\x8fb\xe9\xf9\xfa\x8c\xe0D \u1ba3\xeb\xa9hn\xac\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94E+d\u06ce\xf7\xd6\u07c7\u01c8c\x9c\"\x90\xbe\x84\x82\xd5u\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4E>5\x9a3\x97\x94LZ'Z\xb1\xa2\xf7\n^Z?i\x89\x89\r\x02\xabHl\xed\xc0\x00\x00\u07d4EI\xb1Yy%_~e\xe9\x9b\rV\x04\u06d8\xdf\xca\u023f\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4EKa\xb3D\xc0\xef\x96Qy#\x81U\xf2w\u00c2\x9d\v8\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94EO\x01A\xd7!\xd3<\xbd\xc4\x10\x18\xbd\x01\x11\x9a\xa4xH\x18\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4ES3\x90\xe3@\xfe\r\xe3\xb3\xcf_\xb9\xfc\x8e\xa5R\xe2\x9eb\x89O%\x91\xf8\x96\xa6P\x00\x00\u07d4ES\x96\xa4\xbb\u067a\u8bdf\xb7\xc4\xd6MG\x1d\xb9\xc2E\x05\x89\b\xbaR\xe6\xfcE\xe4\x00\x00\u07d4E[\x92\x96\x92\x1at\xd1\xfcAa\u007fC\xb80>o>\xd7l\x89\u3bb5sr@\xa0\x00\x00\u07d4E\\\xb8\xee9\xff\xbcu#1\xe5\xae\xfcX\x8e\xf0\xeeY4T\x8965F:x\r\xef\x80\x00\u07d4Ej\u0b24\x8e\xbc\xfa\xe1f\x06\x02PR_c\x96^v\x0f\x89\x10CV\x1a\x88)0\x00\x00\u07d4Eo\x8dtf\x82\xb2$g\x93I\x06M\x1b6\x8c|\x05\xb1v\x89\u0213\u041c\x8fQP\x00\x00\u07d4Ep)\xc4i\xc4T\x8d\x16\x8c\xec>e\x87.D(\xd4+g\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Eq\xdeg+\x99\x04\xba\xd8t6\x92\xc2\x1cO\xdc\xeaL.\x01\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4Ex\x1b\xbew\x14\xa1\xc8\xf7;\x1cty!\xdfO\x84'\x8bp\x89lk\x93[\x8b\xbd@\x00\x00\u07d4E{\xce\xf3}\xd3\xd6\v-\xd0\x19\xe3\xfea\xd4k?\x1erR\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94E\x8e<\u025e\x94xD\xa1\x8ejB\x91\x8f\xef~\u007f_^\xb3\x8a\a\xb5?y\xe8\x88\xda\xc0\x00\x00\u07d4E\x93\x93\xd6:\x06>\xf3r\x1e\x16\xbd\x9f\xdeE\ue77dw\xfb\x89j\xba\u05a3\xc1S\x05\x00\x00\u07d4E\xa5p\xdc\xc2\t\f\x86\xa6\xb3\xea)\xa6\bc\xdd\xe4\x1f\x13\xb5\x89\f\x9a\x95\xee)\x86R\x00\x00\u07d4E\xa8 \xa0g/\x17\xdct\xa0\x81\x12\xbcd?\xd1\x16w6\u00c9\n\xd6\xc4;(\x15\xed\x80\x00\u07d4E\xb4q\x05\xfeB\xc4q-\xcen*!\xc0[\xff\xd5\xeaG\xa9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4E\xbb\x82\x96R\u063f\xb5\x8b\x85'\xf0\xec\xb6!\u009e!.\u00c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94E\xc0\u045f\v\x8e\x05O\x9e\x8986\xd5\xec\xaey\x01\xaf(\x12\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4E\xc4\xec\xb4\xee\x89\x1e\xa9\x84\xa7\xc5\xce\xfd\x8d\xfb\x001\v(P\x89kV\x05\x15\x82\xa9p\x00\x00\u07d4E\u028d\x95f\b\xf9\xe0\n/\x99t\x02\x86@\x88\x84ef\x8f\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94E\u0298b\x00;N@\xa3\x17\x1f\xb5\xca\xfa\x90(\xca\xc8\xde\x19\x8a\x02\ub3b1\xa1r\u0738\x00\x00\u07d4E\xd1\xc9\xee\xdf|\xabA\xa7y\x05{y9_T(\xd8\x05(\x89lk\x93[\x8b\xbd@\x00\x00\u07d4E\u0535M7\xa8\xcfY\x98!#_\x06/\xa9\xd1p\xed\u8909\x11\x90g;_\u0690\x00\x00\xe0\x94E\xdb\x03\xbc\xcf\u05a5\xf4\xd0&k\x82\xa2*6\x87\x92\xc7}\x83\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4E\xe3\xa9>r\x14J\u0686\f\xbcV\xff\x85\x14Z\xda8\xc6\u0689WG=\x05\u06ba\xe8\x00\x00\u07d4E\u6378\u06fa\xba_\xc2\xcb3|b\xbc\xd0\xd6\x1b\x05\x91\x89\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94E\u6379L}\n\xb7\xacA\x85zq\xd6qG\x87\x0fNq\x8aT\xb4\v\x1f\x85+\xda\x00\x00\x00\u07d4E\xf4\xfc`\xf0\x8e\xac\xa1\x05\x98\xf03c)\x80\x1e<\x92\xcbF\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4F\rSU\xb2\xce\xebnb\x10}\x81\xe5\x12p\xb2k\xf4V \x89l\xb7\xe7Hg\xd5\xe6\x00\x00\xe0\x94F\"O2\xf4\xec\xe5\u0206p\x90\xd4@\x9dU\xe5\v\x18C-\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4F'\xc6\x06\x84&q\xab\u0782\x95\xee]\xd9L\u007fT\x954\xf4\x89\x0f\x89_\xbd\x872\xf4\x00\x00\u07d4F+g\x8bQ\xb5\x84\xf3\xedz\xda\a\v\\\u065c\v\xf7\xb8\u007f\x89\x05k\xc7^-c\x10\x00\x00\u07d4FM\x9c\x89\xcc\xe4\x84\xdf\x00\x02w\x19\x8e\xd8\a_\xa65r\u0449\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4FPNj!Z\xc8;\xcc\xf9V\xbe\xfc\x82\xabZg\x93q\u0209\x1c!(\x05\u00b4\xa5\x00\x00\xe0\x94FQ\xdcB\x0e\b\xc3);'\xd2Ix\x90\xebP\":\xe2\xf4\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4FS\x1e\x8b\x1b\xde\t\u007f\u07c4\x9dm\x11\x98\x85`\x8a\x00\x8d\xf7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4Fb\x92\xf0\xe8\rC\xa7\x87t'u\x90\xa9\xebE\x96\x12\x14\xf4\x894\x95tD\xb8@\xe8\x00\x00\xe0\x94Fb\xa1v^\xe9!\x84-\u0708\x89\x8d\x1d\xc8bu\x97\xbd~\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4Fe\xe4s\x96\xc7\u06d7\xeb*\x03\xd9\bc\xd5\u053a1\x9a\x94\x89 \x86\xac5\x10R`\x00\x00\u07d4Fo\xdak\x9bX\xc5S'P0j\x10\xa2\xa8\xc7h\x10;\a\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4Fq$\xae\u007fE/&\xb3\xd5t\xf6\b\x88\x94\xfa]\x1c\xfb;\x89\x92^\x06\xee\xc9r\xb0\x00\x00\u0794Fr*6\xa0\x1e\x84\x1d\x03\xf7\x80\x93^\x91}\x85\u0566z\xbd\x88\xce\xc7o\x0eqR\x00\x00\u07d4Fw\x9aVV\xff\x00\xd7>\xac:\xd0\u00cbl\x850\x94\xfb@\x89\f\x82S\xc9lj\xf0\x00\x00\u07d4Fw\xb0N\x03C\xa3!1\xfdj\xbb9\xb1\xb6\x15k\xba=[\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4F}Y\x88$\x9ahaG\x16e\x98@\xed\n\xe6\xf6\xf4W\xbc\x89\x15\x01\xa4\x8c\xef\xdf\xde\x00\x00\u07d4F~\x0e\xd5O;v\xae\x066\x17n\aB\b\x15\xa0!sn\x89lk\x93[\x8b\xbd@\x00\x00\u07d4F~\xa1\x04E\x82~\xf1\xe5\x02\xda\xf7k\x92\x8a \x9e\r@2\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94F\u007f\xbfAD\x16\x00u\u007f\xe1X0\xc8\xcd_O\xfb\xbb\xd5`\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94F\x93Xp\x932\xc8+\x88~ \xbc\xdd\xd0\"\x0f\x8e\u06e7\u040a\x03\xa9\u057a\xa4\xab\xf1\xd0\x00\x00\u07d4F\x97\xba\xaf\x9c\xcb`?\xd3\x040h\x9dCTE\xe9\u024b\xf5\x89\n\xd2\x01\xa6yO\xf8\x00\x00\u07d4F\xa3\v\x8a\x80\x891!tE\xc3\xf5\xa9>\x88,\x03E\xb4&\x89\r\x8d\xb5\xeb\u05f2c\x80\x00\u07d4F\xa40\xa2\u0528\x94\xa0\u062a?\xea\xc6\x156\x14\x15\xc3\xf8\x1f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4F\xaaP\x18pg~\u007f\nPHv\xb4\xe8\x80\x1a\n\xd0\x1cF\x89+^:\xf1k\x18\x80\x00\x00\u07d4F\xbf\u0172\a\xeb \x13\xe2\xe6\x0fw_\xec\xd7\x18\x10\u0159\f\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4F\xc1\xaa\"D\xb9\u0229W\u028f\xacC\x1b\x05\x95\xa3\xb8h$\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4F\xd8\x061(B\x03\xf6(\x8e\xcdNWX\xbb\x9dA\xd0]\xbe\x89lk\x93[\x8b\xbd@\x00\x00\u07d4G\n\xc5\xd1\xf3\xef\xe2\x8f8\x02\xaf\x92[W\x1ec\x86\x8b9}\x89lk\x93[\x8b\xbd@\x00\x00\u07d4G\x10\x10\xdaI/@\x18\x83;\b\x8d\x98r\x90\x1e\x06\x12\x91t\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4G\x12T\x02e\xcb\xee\u00c4p\"\u015f\x1b1\x8dC@\n\x9e\x89\xbd\xbcA\xe04\x8b0\x00\x00\xe0\x94G\x14\u03e4\xf4k\u05bdps}u\x87\x81\x97\xe0\x8f\x88\xe61\x8a\x02\u007f>\u07f3Nn@\x00\x00\u07d4G H\xcc`\x9a\xeb$!e\uaa87\x05\x85\f\xf3\x12]\xe0\x8965\u026d\xc5\u07a0\x00\x00\u07d4G!\x92)\xe8\xcdVe\x9ae\u00a9C\xe2\u075a\x8fK\xfd\x89\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d4G7\xd0B\xdcj\xe7>\xc7:\xe2Qz\u03a2\xfd\xd9d\x87\u014965\u026d\xc5\u07a0\x00\x00\u07d4GAX\xa1\xa9\xdci<\x13?e\xe4{\\:\xe2\xf7s\xa8o\x89\n\xdaUGK\x814\x00\x00\u07d4GE\xab\x18\x1a6\xaa\x8c\xbf\"\x89\xd0\xc4Qe\xbc~\xbe#\x81\x89\x02\"\xc8\xeb?\xf6d\x00\x00\u07d4GPf\xf9\xad&eQ\x96\xd5SS'\xbb\xeb\x9by)\xcb\x04\x89\xa4\xccy\x95c\u00c0\x00\x00\xe0\x94GR!\x8eT\xdeB?\x86\xc0P\x193\x91z\xea\b\xc8\xfe\u054a\x04<3\xc1\x93ud\x80\x00\x00\u07d4GZa\x93W-JNY\u05fe\t\u02d6\r\u074cS\x0e/\x89$,\xf7\x8c\xdf\a\xff\x80\x00\u07d4Gd\x8b\xed\x01\xf3\xcd2I\bNc]\x14\u06a9\xe7\xec<\x8a\x89\n\x84Jt$\xd9\xc8\x00\x00\u07d4Gh\x84\x10\xff%\xd6T\xd7.\xb2\xbc\x06\xe4\xad$\xf83\xb0\x94\x89\b\xb2\x8da\xf3\u04ec\x00\x00\u07d4GkU\x99\b\x9a?\xb6\xf2\x9clr\xe4\x9b.G@\ua00d\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4Gs\x0f_\x8e\xbf\x89\xacr\xef\x80\xe4l\x12\x19P8\xec\xdcI\x89\xabM\xcf9\x9a:`\x00\x00\xe0\x94G{$\xee\u80deO\u045d\x12P\xbd\vfEyJa\u028a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4G\x81\xa1\nM\xf5\uef02\xf4\xcf\xe1\a\xba\x1d\x8av@\xbdf\x89a\t=|,m8\x00\x00\u07d4G\x88Z\xba\xbe\xdfM\x92\x8e\x1c\x88\x83\xa6a\x9cl(\x11\x84\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94G\xe2]\xf8\x82%8\xa8Yk(\xc67\x89kM\x14<5\x1d\x8a\x11\v\xe9\xeb$\xb8\x81P\x00\x00\u07d4G\xf4ik\xd4b\xb2\r\xa0\x9f\xb8>\xd2\x03\x98\x18\xd7v%\xb3\x89\b\x13\xcaV\x90m4\x00\x00\u07d4G\xfe\xf5\x85\x84FRH\xa0\x81\r`F>\xe9>Zn\xe8\u04c9\x0fX\xcd>\x12i\x16\x00\x00\u07d4G\xffo\xebC! `\xbb\x15\x03\u05e3\x97\xfc\b\xf4\xe7\x03R\x89lk\x93[\x8b\xbd@\x00\x00\u07d4G\xff\xf4,g\x85Q\xd1A\xebu\xa6\xee9\x81\x17\xdf>J\x8d\x89\x05k\xea\xe5\x1f\xd2\xd1\x00\x00\u07d4H\x01\x0e\xf3\xb8\xe9^?0\x8f0\xa8\xcb\u007fN\xb4\xbf`\xd9e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4H\n\xf5 v\x00\x9c\xa77\x81\xb7\x0eC\xb9Y\x16\xa6\"\x03\xab\x892\x19r\xf4\b=\x87\x80\x00\u07d4H\x0f1\xb9\x891\x1eA$\u01a7F_ZD\tM6\xf9\u04097\x90\xbb\x85Q7d\x00\x00\xe0\x94H\x11\x15)j\xb7\xdbRI/\xf7\xb6G\xd63)\xfb\\\xbck\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d4H\x1e:\x91\xbf\xdc/\x1c\x84(\xa0\x11\x9d\x03\xa4\x16\x01A~\x1c\x8965\u026d\xc5\u07a0\x00\x00\u07d4H(\xe4\xcb\xe3N\x15\x10\xaf\xb7,+\ueb0aE\x13\xea\xeb\u0649\u0556{\xe4\xfc?\x10\x00\x00\xe0\x94H)\x82\xac\x1f\x1cm\x17!\xfe\xec\u0679\xc9l\xd9I\x80PU\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4H0,1\x1e\xf8\xe5\xdcfAX\xddX<\x81\x19Mn\rX\x89\xb6gl\xe0\xbc\xcb\\\x00\x00\u07d4H;\xa9\x904\xe9\x00\xe3\xae\xdfaI\x9d;+\xce9\xbe\xb7\xaa\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4HT\x8bK\xa6+\xcb/\r4\xa8\x8d\u019ah\x0eS\x9c\xf0F\x89\x05l\xf1\u02fbt2\x00\x00\u07d4Hc\x84\x979&Zc\xb0\xa2\xbf#jY\x13\xe6\xf9Y\xce\x15\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d4He\x9d\x8f\x8c\x9a/\xd4Oh\u06a5]#\xa6\b\xfb\xe5\x00\u0709lk\x93[\x8b\xbd@\x00\x00\xe0\x94Hf\x9e\xb5\xa8\x01\u0637_\xb6\xaaX\xc3E\x1bpX\xc2C\xbf\x8a\x06\x8dB\xc18\u06b9\xf0\x00\x00\u07d4Hjl\x85\x83\xa8D\x84\xe3\xdfC\xa1#\x83\u007f\x8c~#\x17\u0409\x11\x87\xc5q\xab\x80E\x00\x00\u07d4Hz\xdf}p\xa6t\x0f\x8dQ\xcb\xddh\xbb?\x91\u0125\xceh\x89\x03\x9f\xba\xe8\xd0B\xdd\x00\x00\u07d4H~\x10\x85\x02\xb0\xb1\x89\uf70cm\xa4\xd0\xdbba\xee\xc6\xc0\x89g\x8a\x93 b\xe4\x18\x00\x00\xe0\x94H\x88\xfb%\xcdP\u06f9\xe0H\xf4\x1c\xa4}x\xb7\x8a'\xc7\u064a\x03\xa9\u057a\xa4\xab\xf1\xd0\x00\x00\u0794H\x934\u00b6\x95\xc8\xee\a\x94\xbd\x86B\x17\xfb\x9f\xd8\xf8\xb15\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4H\xa3\r\xe1\xc9\x19\xd3\xfd1\x80\xe9}_+*\x9d\xbd\x96M-\x89\x02b\x9ff\xe0\xc50\x00\x00\u07d4H\xbf\x14\u05f1\xfc\x84\xeb\xf3\xc9k\xe1/{\xce\x01\xaai\xb0>\x89\x06\x81U\xa46v\xe0\x00\x00\u07d4H\xc2\ue465\aV\xd8\u039a\xbe\xebu\x89\xd2,o\xee]\xfb\x89\xae\x8ez\v\xb5u\xd0\x00\x00\u07d4H\xc5\u0197\v\x91a\xbb\x1c{z\xdf\xed\x9c\xde\u078a\x1b\xa8d\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94H\xd2CKz}\xbb\xff\b\";c\x87\xb0]\xa2\xe5\t1&\x8a\x03\xcf\xc8.7\xe9\xa7@\x00\x00\u07d4H\xd4\xf2F\x8f\x96?\u05da\x00a\x98\xbbg\x89]-Z\xa4\u04c9K\xe4\xe7&{j\xe0\x00\x00\u07d4H\xe0\xcb\xd6\u007f\x18\xac\xdbzb\x91\xe1%M\xb3.\trs\u007f\x89\x05k\xe0<\xa3\xe4}\x80\x00\u07d4H\xf6\n5HO\xe7y+\u030a{c\x93\xd0\u0761\xf6\xb7\x17\x89\xc3(\t>a\xee@\x00\x00\u07d4H\xf8\x83\xe5g\xb46\xa2{\xb5\xa3\x12M\xbc\x84\xde\xc7u\xa8\x00\x89)\xd7n\x86\x9d\u0340\x00\x00\xe0\x94I\x01E\xaf\xa8\xb5E\"\xbb!\xf3R\xf0m\xa5\xa7\x88\xfa\x8f\x1d\x8a\x01\xf4lb\x90\x1a\x03\xfb\x00\x00\u07d4I\t\xb3\x19\x98\xea\xd4\x14\xb8\xfb\x0e\x84k\xd5\xcb\xde995\xbe\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4I\x12\xd9\x02\x93\x16v\xff9\xfc4\xfe<<\xc8\xfb!\x82\xfaz\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4I\x13o\xe6\xe2\x8btS\xfc\xb1kk\xbb\u9aac\xba\x837\xfd\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94I\x15a\u06cbo\xaf\xb9\x00~b\xd0P\u0082\xe9,Kk\u020a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4I\x18]\xd7\xc262\xf4lu\x94s\ubb96`\b\xcd5\x98\x89\r\xc5_\xdb\x17d{\x00\x00\u07d4I,\xb5\xf8a\xb1\x87\xf9\xdf!\xcdD\x85\xbe\xd9\vP\xff\xe2-\x89\x1b\x19\xe5\vD\x97|\x00\x00\u07d4I-\xe4j\xaf\x8f\x1dp\x8dY\u05da\xf1\xd0:\xd2\xcb`\x90/\x89lk\x93[\x8b\xbd@\x00\x00\u07d4I.p\xf0M\x18@\x8c\xb4\x1e%`70Pk5\xa2\x87k\x89\x02\"\xc8\xeb?\xf6d\x00\x00\u07d4I:g\xfe#\xde\xccc\xb1\r\xdau\xf3(v\x95\xa8\x1b\u056b\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4I=H\xbd\xa0\x15\xa9\xbf\xcf\x16\x03\x93n\xabh\x02L\xe5Q\xe0\x89\x018\xa3\x88\xa4<\x00\x00\x00\xe0\x94IBV\xe9\x9b\x0f\x9c\xd6\xe5\xeb\xca8\x99\x862R\x90\x01e\u020a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4IM\xecM^\xe8\x8a'q\xa8\x15\xf1\xeerd\x94/\xb5\x8b(\x89lk\x93[\x8b\xbd@\x00\x00\u07d4I[d\x1b\x1c\u07a3b\u00f4\u02fd\x0f\\\xc5\v\x1e\x17k\x9c\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94Ih\xa2\xce\xdbEuU\xa19)Z\xea(wnT\x00<\x87\x8a\x02#\x1a\xef\u0266b\x8f\x00\x00\u07d4Im6U4S\n_\xc1W|\nRA\u02c8\xc4\xdapr\x89a\t=|,m8\x00\x00\xe0\x94In1\x95\x92\xb3A\xea\xcc\xd7x\u0767\xc8\x19mT\xca\xc7u\x8a\x01\xf5q\x89\x87fKH\x00\x00\u07d4IoXC\xf6\xd2L\u064d%^L#\xd1\xe1\xf0#\"uE\x89_\x17\x9f\u0526\xee\t\x80\x00\xe0\x94Ip\u04ec\xf7+[\x1f2\xa7\x00<\xf1\x02\xc6N\xe0TyA\x8a\x1d\xa5jK\b5\xbf\x80\x00\x00\u07d4Iw\xa7\x93\x9d\t9h\x94U\xce&9\xd0\xeeZL\xd9\x10\xed\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4Iy\x19N\xc9\xe9}\xb9\xbe\xe84;|w\xd9\xd7\xf3\xf1\u071f\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4Iy4c\xe1h\x10\x83\u05ab\xd6\xe7%\u057b\xa7E\xdc\xcd\xe8\x89\x1d\x98\xe9LNG\x1f\x00\x00\u07d4I\x81\xc5\xfff\xccN\x96\x80%\x1f\xc4\xcd/\xf9\a\xcb2xe\x89(\xa8WBTf\xf8\x00\x00\u07d4I\x89\u007f\xe92\xbb\xb3\x15L\x95\u04fc\xe6\xd9;ms)\x04\u0749\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4I\x89\xe1\xab^|\xd0\aF\xb3\x93\x8e\xf0\xf0\xd0d\xa2\x02[\xa5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4I\x8a\xbd\xeb\x14\xc2k{r4\xd7\x0f\u03ae\xf3a\xa7m\xffr\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4I\xa6E\xe0f}\xfd{2\xd0u\xcc$g\u074ch\t\a\u0109\a\x06\x01\x95\x8f\u02dc\x00\x00\xe0\x94I\xb7N\x16\x92e\xf0\x1a\x89\xecL\x90r\u0164\xcdr\xe4\xe85\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d4I\xbd\xbc{\xa5\xab\xeb\xb68\x9e\x91\xa3(R \xd3E\x1b\xd2S\x8965\u026d\xc5\u07a0\x00\x00\u07d4I\xc9A\xe0\xe5\x01\x87&\xb7)\x0f\xc4s\xb4q\xd4\x1d\xae\x80\u0449\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94I\xc9w\x1f\xca\x19\u0579\xd2E\u0211\xf8\x15\x8f\xe4\x9fG\xa0b\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4I\xcf\x1eT\xbe61\x06\xb9 r\x9d-\v\xa4o\bg\x98\x9a\x89\x0e\x87?D\x13<\xb0\x00\x00\u07d4I\xd2\u008e\xe9\xbcT^\xaa\xf7\xfd\x14\xc2|@s\xb4\xbb_\x1a\x89O\xe9\xb8\x06\xb4\r\xaf\x00\x00\u07d4I\xdd\xee\x90.\x1d\f\x99\u0471\x1a\xf3\u030a\x96\xf7\x8eM\xcf\x1a\x89\n\u03a5\xe4\xc1\x8cS\x00\x00\u07d4I\xf0(9[Z\x86\xc9\xe0\u007fwxc\x0eL.=7:w\x89\x06\xa7JP8\u06d1\x80\x00\xe0\x94J\x19 5\xe2a\x9b$\xb0p\x9dVY\x0e\x91\x83\xcc\xf2\xc1\u064a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4J@S\xb3\x1d\x0e\xe5\u06ef\xb1\xd0k\u05ec\u007f\xf3\",G\u0589K\xe4\xe7&{j\xe0\x00\x00\u07d4JC\x01p\x15-\xe5\x17&3\u0742b\xd1\a\xa0\xaf\xd9j\x0f\x89\xabM\xcf9\x9a:`\x00\x00\u07d4JG\xfc>\x17\u007fVz\x1e8\x93\xe0\x00\xe3k\xba#R\n\xb8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4JR\xba\xd2\x03W\"\x8f\xaa\x1e\x99k\xedy\f\x93gK\xa7\u0409Hz\x9a0E9D\x00\x00\u07d4JS\xdc\xdbV\xceL\xdc\xe9\xf8.\xc0\xeb\x13\xd6sR\xe7\u020b\x89\u3bb5sr@\xa0\x00\x00\u07d4J_\xae;\x03r\xc20\xc1%\xd6\xd4p\x14\x037\xab\x91VV\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4Jq\x90a\xf5(T\x95\xb3{\x9d~\xf8\xa5\x1b\a\xd6\u6b2c\x89\n\xd4\xc81j\v\f\x00\x00\u07d4Js8\x92\x98\x03\x1b\x88\x16\u0329FB\x1c\x19\x9e\x18\xb3C\u0589\"8h\xb8y\x14o\x00\x00\u07d4Js]\"G\x927m3\x13g\xc0\x93\xd3\x1c\x87\x944\x15\x82\x89f\xff\xcb\xfd^Z0\x00\x00\u07d4Jt\x94\xcc\xe4HU\u0300X(B\xbe\x95\x8a\r\x1c\x00r\ue242\x1a\xb0\xd4AI\x80\x00\x00\u07d4Ju\xc3\xd4\xfao\u033d]\u0567\x03\xc1Sy\xa1\xe7\x83\u9dc9b\xa9\x92\xe5:\n\xf0\x00\x00\xe0\x94J\x81\xab\xe4\x98L|k\xefc\u0598 \xe5WC\xc6\x1f \x1c\x8a\x03d\x01\x00N\x9a\xa3G\x00\x00\u07d4J\x82iO\xa2\x9d\x9e!2\x02\xa1\xa2\t(]\xf6\xe7E\xc2\t\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4J\x83\\%\x82LG\xec\xbf\u01d49\xbf?\\4\x81\xaau\u0349K\xe4\xe7&{j\xe0\x00\x00\u07d4J\x91\x802C\x91Y\xbb1[g%\xb6\x83\r\xc86\x97s\x9f\x89\x12\xa3.\xf6x3L\x00\x00\u07d4J\x97\xe8\xfc\xf4c^\xa7\xfc^\x96\xeeQu.\u00c8qk`\x89\x1d\x99E\xab+\x03H\x00\x00\u07d4J\x9a&\xfd\n\x8b\xa1\x0f\x97}\xa4\xf7|1\x90\x8d\xabJ\x80\x16\x89a\t=|,m8\x00\x00\u07d4J\xa1H\xc2\xc34\x01\xe6j+Xnew\u0132\x92\xd3\xf2@\x89\v\xb8`\xb2\x85\xf7t\x00\x00\u07d4J\xa6\x93\xb1\"\xf3\x14H*G\xb1\x1c\xc7|h\xa4\x97\x87ab\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4J\xb2\xd3O\x04\x83O\xbftyd\x9c\xab\x92=,G%\xc5S\x89\xbe\xd1\xd0&=\x9f\x00\x00\x00\u07d4J\xc0vs\xe4/d\xc1\xa2^\xc2\xfa-\x86\xe5\xaa+4\xe09\x89lk\x93[\x8b\xbd@\x00\x00\u07d4J\u016c\xad\x00\v\x88w!L\xb1\xae\x00\xea\u0263}Y\xa0\xfd\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4J\u0250ZL\xb6\xab\x1c\xfdbTn\xe5\x91s\x00\xb8|O\u07897\b\xba\xed=h\x90\x00\x00\u07d4J\u03e9\xd9N\xdaf%\xc9\u07e5\xf9\xf4\xf5\xd1\a\xc4\x03\x1f\u07c9\x02\"\xc8\xeb?\xf6d\x00\x00\u07d4J\xd0G\xfa\xe6~\xf1b\xfeh\xfe\xdb\xc2};e\xca\xf1\f6\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4J\xd9]\x18\x8dddp\x9a\xdd%U\xfbM\x97\xfe\x1e\xbf1\x1f\x89\x12\xc1\xb6\xee\xd0=(\x00\x00\u07d4J\xdb\xf4\xaa\xe0\xe3\xefD\xf7\xddM\x89\x85\u03ef\tn\u010e\x98\x89\b!\xab\rD\x14\x98\x00\x00\u07d4J\xe2\xa0M9\t\xefENTL\xcf\xd6\x14\xbf\xef\xa7\x10\x89\xae\x89\x18\x01\x15\x9d\xf1\xee\xf8\x00\x00\xe0\x94J\xe90\x82\xe4Q\x87\xc2a`\xe6g\x92\xf5\u007f\xad5Q\xc7:\x8a\x04\x96\x15 \xda\xff\x82(\x00\x00\u07d4J\xf0\xdb\a{\xb9\xba^D>!\xe1H\xe5\x9f7\x91\x05\u0152\x89 \x86\xac5\x10R`\x00\x00\u07d4K\x06\x19\xd9\u062a1:\x951\xac}\xbe\x04\xca\rjZ\u0476\x89lk\x93[\x8b\xbd@\x00\x00\u07d4K\v\u062c\xfc\xbcS\xa6\x01\v@\xd4\u040d\xdd-\x9dib-\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4K\x19\xeb\f5K\xc199`\xeb\x06\x06;\x83\x92o\rg\xb2\x89\x01\x92t\xb2Y\xf6T\x00\x00\u07d4K)C|\x97\xb4\xa8D\xbeq\u0323\xb6H\xd4\xca\x0f\u075b\xa4\x89\b$q\x984\u03ec\x00\x00\u07d4K1\xbfA\xab\xc7\\\x9a\xe2\u034f\u007f5\x16;n+tPT\x89\x14\xb5P\xa0\x13\xc78\x00\x00\u07d4K:|\u00e7\u05f0\x0e\xd5(\"!\xa6\x02Y\xf2[\xf6S\x8a\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94K:\xab3^\xbb\xfa\xa8p\xccM`^}.t\xc6h6\x9f\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4K\xcd\xc1\x8a`\x00\x00\u07d4K`\xa3\xe2S\xbf8\xc8\xd5f \x10\xbb\x93\xa4s\xc9e\xc3\xe5\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4Kt\xf5\xe5\x8e.\xdfv\xda\xf7\x01Q\x96J\v\x8f\x1d\xe0f<\x89\x11\x90\xaeID\xba\x12\x00\x00\u07d4Kv!f\xdd\x11\x18\xe8Ci\xf8\x04\xc7_\x9c\xd6W\xbfs\f\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4Ky.)h>\xb5\x86\u353b3Rl`\x01\xb3\x97\x99\x9e\x89 \x86\xac5\x10R`\x00\x00\u07d4K\x90N\x93K\xd0\u030b p_\x87\x9e\x90[\x93\xea\f\xcc0\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94K\x92\x06\xbakT\x9a\x1a\u007f\x96\x9e\x1d]\xba\x86u9\xd1\xfag\x8a\x01\xab,\xf7\xc9\xf8~ \x00\x00\u07d4K\x98N\xf2lWn\x81Z.\xae\xd2\xf5\x17\u007f\a\u06f1\xc4v\x89T\x91YV\xc4\t`\x00\x00\u07d4K\x9e\x06\x8f\xc4h\tv\xe6\x15\x04\x91)\x85\xfd\\\xe9K\xab\r\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4K\xa0\xd9\xe8\x96\x01w+IhG\xa2\xbbC@\x18g\x87\xd2e\x8965\u026d\xc5\u07a0\x00\x00\u07d4K\xa5:\xb5I\xe2\x01m\xfa\"<\x9e\u0563\x8f\xad\x91(\x8d\a\x89K\xe4\xe7&{j\xe0\x00\x00\xe0\x94K\xa8\xe0\x11\u007f\xc0\xb6\xa3\xe5k$\xa3\xa5\x8f\xe6\xce\xf4B\xff\x98\x8a\x011\xbe\xb9%\xff\xd3 \x00\x00\u07d4K\xac\x84j\xf4\x16\x9f\x1d\x95C\x1b4\x1d\x88\x00\xb2!\x80\xaf\x1a\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4K\xb6\xd8k\x83\x14\xc2-\x8d7\xeaQm\x00\x19\xf1V\xaa\xe1-\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94K\xb9e\\\xfb*6\xea|cz{\x85\x9bJ1T\xe2n\xbe\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\xe0\x94K\xbc\xbf8\xb3\xc9\x01c\xa8K\x1c\u04a9;X\xb2\xa34\x8d\x87\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94K\xd6\xdd\f\xff#@\x0e\x170\xba{\x89E\x04W}\x14\xe7J\x8a+\xa0\xcc\xdd\xd0\xdfs\xb0\x00\x00\u07d4K\xe8b\x8a\x81T\x87N\x04\x8d\x80\xc1B\x18\x10\"\xb1\x80\xbc\xc1\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4K\xe9\rA!)\u0564\xd0BCa\xd6d\x9dNG\xa6#\x16\x897\b\xba\xed=h\x90\x00\x00\xe0\x94K\xea(\x8e\xeaB\u0115^\xb9\xfa\xad*\x9f\xafG\x83\xcb\u076c\x8a\x06\x18\xbe\x16c\u012fI\x00\x00\u07d4K\xf4G\x97\x99\xef\x82\xee\xa2\tC7OV\xa1\xbfT\x00\x1e^\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4K\xf8\xbf\x1d5\xa211Wd\xfc\x80\x01\x80\x9a\x94\x92\x94\xfcI\x89\x03\x9f\xba\xe8\xd0B\xdd\x00\x00\u07d4K\xf8\xe2oL'\x90\xdae3\xa2\xac\x9a\xba\xc3\u019a\x19\x943\x89\n\u05ce\xbcZ\xc6 \x00\x00\u0794L\n\xcaP\x8b<\xaf^\xe0(\xbcp}\xd1\xe8\x00\xb88\xf4S\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94L\v\x15\x15\xdf\xce\u05e1>\x13\xee\x12\xc0\xf5#\xaePO\x03+\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4L\x13\x98\f2\xdc\xf3\x92\vx\xa4\xa7\x903\x12\x90|\x1b\x12?\x89\x03A\x00\x15\xfa\xae\f\x00\x00\u07d4L\x15y\xaf3\x12\xe4\xf8\x8a\xe9\x95\xcc9W\xd2R\xce\v\xf0\xc8}[O\"4g.p\x89\x87\x86x2n\xac\x90\x00\x00\u07d4LB1y\x82i\x1d\x10\x89\x05k\xc7^-c\x10\x00\x00\u07d4LZ\xfe@\xf1\x8f\xfcH\u04e1\xae\xc4\x1f\u009d\xe1y\xf4\u0497\x89lk\x93[\x8b\xbd@\x00\x00\u07d4L[=\xc0\xe2\xb96\x0f\x91(\x9b\x1f\xe1<\xe1,\x0f\xbd\xa3\xe1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Lfk\x86\xf1\xc5\ue324\x12\x85\xf5\xbd\xe4\xf7\x90R\b\x14\x06\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4Lik\xe9\x9f:i\x04@\xc3CjY\xa7\xd7\xe97\u05ba\r\x89\xbb\x91%T\"c\x90\x00\x00\u07d4Lj$\x8f\xc9}p]\xefI\\\xa2\aY\x16\x9e\xf0\xd3dq\x89)3\x1eeX\xf0\xe0\x00\x00\u07d4Lj\x9d\xc2\u02b1\n\xbb.|\x13p\x06\xf0\x8f\ucd77y\xe1\x89\x1b\r\x04 /G\xec\x00\x00\u07d4Lk\x93\xa3\xbe\xc1cIT\f\xbf\xca\xe9l\x96!\xd6dP\x10\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Lu\x98\x13\xad\x13\x86\xbe\xd2\u007f\xfa\xe9\xe4\x81^60\u0323\x12\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94Lv\f\xd9\xe1\x95\xeeO-k\xce%\x00\xff\x96\xda|C\ue44a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4Lv{e\xfd\x91\x16\x1fO\xbd\xccji\xe2\xf6\xadq\x1b\xb9\x18\x89'\b\x01\xd9F\xc9@\x00\x00\u07d4L~.+w\xad\f\xd6\xf4J\xcb(a\xf0\xfb\x8b(u\x0e\xf9\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4L\x85\xed6/$\xf6\xb9\xf0L\xdf\xcc\xd0\"\xaeSQG\u02f9\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4L\x93[\xb2Pw\x8b0\x9b==\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4L\xee\x90\x1bJ\u0231V\xc5\xe2\xf8\xa6\xf1\xbe\xf5r\xa7\xdc\xeb~\x8965\u026d\xc5\u07a0\x00\x00\u07d4L\xef\xbe#\x98\xe4}R\u73743L\x8bivu\U00053b89\xd9o\u0390\u03eb\xcc\x00\x00\u07d4L\xf5S{\x85\x84/\x89\xcf\xee5\x9e\xaeP\x0f\xc4I\xd2\x11\x8f\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94M\bG\x1dh\x00z\xff*\xe2y\xbc^?\xe4\x15o\xbb\xe3\u078a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4M \x01\x10\x12@\b\xd5ov\x98\x12VB\f\x94jo\xf4\\\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4M$\xb7\xacG\xd2\xf2}\xe9\tt\xba=\xe5\xea\xd2\x03TK\u0349\x05k\xc7^-c\x10\x00\x00\u0794M)\xfcR:,\x16)S!!\u0699\x98\u9d6b\x9d\x1bE\x88\xdbD\xe0I\xbb,\x00\x00\u07d4M8\xd9\x0f\x83\xf4Q\\\x03\xccx2j\x15M5\x8b\u0602\xb7\x89\n\ad\a\xd3\xf7D\x00\x00\u07d4ML\xf5\x80t)a^0\xcd\xfa\xce\x1eZ\xaeM\xad0U\xe6\x89 \x86\xac5\x10R`\x00\x00\u07d4MW\xe7\x16\x87l\f\x95\xef^\xae\xbd5\xc8\xf4\x1b\x06\x9bk\xfe\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94Mg\U000ab159\xfe\xf5\xfcA9\x99\xaa\x01\xfd\u007f\xcep\xb4=\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4Mn\x8f\xe1\t\xcc\xd2\x15\x8eM\xb1\x14\x13/\xe7_\xec\u023e[\x89\x01[5W\xf1\x93\u007f\x80\x00\xe0\x94Mq\xa6\xeb=\u007f2~\x184'\x8e(\v\x03\x9e\xdd\xd3\x1c/\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4M|\xfa\xa8L\xb31\x06\x80\n\x8c\x80/\xb8\xaaF8\x96\u0159\x89a\t=|,m8\x00\x00\u07d4M\x80\x10\x93\xc1\x9c\xa9\xb8\xf3B\xe3<\xc9\xc7{\xbdL\x83\x12\u03c9\x12\xb3\xe7\xfb\x95\u0364\x80\x00\u07d4M\x82\x88\x94u/o%\x17]\xaf!w\tD\x87\x95Ko\x9f\x89O!+\xc2\u011c\x83\x80\x00\xe0\x94M\x82\xd7p\f\x12;\xb9\x19A\x9b\xba\xf0Fy\x9ck\x0e,f\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4M\x83m\x9d;\x0e,\xbdM\xe0PYo\xaaI\f\xff\xb6\r]\x89\x10CV\x1a\x88)0\x00\x00\u07d4M\x86\x97\xaf\x0f\xbf,\xa3n\x87h\xf4\xaf\"\x135phZ`\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4M\x92y\x96 )\xa8\xbdEc\x977\xe9\x8bQ\x1e\xff\aL!\x89Hz\x9a0E9D\x00\x00\u07d4M\x93io\xa2HY\xf5\u0493\x9a\xeb\xfaT\xb4\xb5\x1a\xe1\xdc\u0309\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\u07d4M\x9cw\xd0u\f^o\xbc$\u007f/\u05d2thl\xb3S\u0589\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4M\xa5\xed\u0188\xb0\xcbb\xe1@=\x17\x00\xd9\u0739\x9f\xfe?\u04c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94M\xa8\x03\ai\x84K\xc3A\x86\xb8\\\xd4\xc74\x88I\xffI\xe9\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4M\xb1\xc4:\x0f\x83M}\x04x\xb8\x96\ag\xec\x1a\xc4L\x9a\xeb\x89/Q\x810V'7\x00\x00\u07d4M\xb2\x12\x84\xbc\xd4\xf7\x87\xa7Ue\x00\xd6\xd7\xd8\xf3f#\xcf5\x89i(7Ow\xa3c\x00\x00\u07d4M\xc3\xda\x13\xb2\xb4\xaf\xd4O]\r1\x89\xf4D\xd4\xdd\xf9\x1b\x1b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4M\u013f^u\x89\xc4{(7\x8du\x03\u03d6H\x80a\u06fd\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4M\xc9\u057bK\x19\xce\u0354\xf1\x9e\xc2] \x0e\xa7/%\xd7\xed\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94M\xcd\x11\x81X\x18\xae)\xb8]\x016sI\xa8\xa7\xfb\x12\xd0k\x8a\x01\xacB\x86\x10\x01\x91\xf0\x00\x00\u07d4M\xcfb\xa3\xde?\x06\x1d\xb9\x14\x98\xfda\x06\x0f\x1fc\x98\xffs\x89lj\xccg\u05f1\xd4\x00\x00\u07d4M\xd11\xc7J\x06\x8a7\xc9\n\xde\xd4\xf3\t\xc2@\x9fdx\u04c9\x15\xaf9\u4ab2t\x00\x00\xe0\x94M\u0767Xk\"7\xb0S\xa7\xf3(\x9c\xf4`\xdcW\xd3z\t\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4M\xe3\xfe4\xa6\xfb\xf64\xc0Q\x99\u007fG\xcc\u007fHy\x1fX$\x89l]\xb2\xa4\xd8\x15\xdc\x00\x00\u07d4M\xf1@\xbaye\x85\xddT\x891[\xcaK\xbah\n\u06f8\x18\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4N\x02\ay\xb5\xdd\xd3\xdf\"\x8a\x00\xcbH\xc2\xfc\x97\x9d\xa6\xae8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4N\v\xd3$s\xc4\xc5\x1b\xf2VT\xde\xf6\x9fy|k)\xa22\x89V\xc9]\xe8\xe8\xca\x1d\x00\x00\u07d4N\"%\xa1\xbbY\xbc\x88\xa21ft\xd33\xb9\xb0\xaf\xcafU\x89\bg\x0e\x9e\xc6Y\x8c\x00\x00\u07d4N#\x10\x19\x1e\xad\x8d;\xc6H\x98s\xa5\xf0\xc2\xeck\x87\u1f8965\u026d\xc5\u07a0\x00\x00\u07d4N#-S\xb3\u6f8f\x89Sa\xd3\x1c4\xd4v+\x12\xc8.\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4N+\xfaJFo\x82g\x1b\x80\x0e\xeeBj\xd0\f\a\x1b\xa1p\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4N>\xda\u0506M\xabd\xca\xe4\xc5Azvw@S\xdcd2\x89 \b\xfbG\x8c\xbf\xa9\x80\x00\u07d4NC\x18\xf5\xe1>\x82JT\xed\xfe0\xa7\xedO&\xcd=\xa5\x04\x89lk\x93[\x8b\xbd@\x00\x00\u07d4N[w\xf9\x06aY\xe6\x15\x93?-\xdatw\xfaNG\xd6H\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94Nf\x00\x80b\x89EJ\u03630\xa2\xa3U`\x10\u07ec\xad\xe6\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4Ns\xcf#y\xf1$\x86\x0fs\xd6\xd9\x1b\xf5\x9a\xcc\\\xfc\x84[\x89\x02,\xa3X|\xf4\xeb\x00\x00\xe0\x94Nz\xa6~\x12\x18>\xf9\xd7F\x8e\xa2\x8a\xd29\xc2\xee\xf7\x1bv\x8a\x01\n\xfc\x1a\xde;N\xd4\x00\x00\xe0\x94N{TGM\x01\xfe\xfd8\x8d\xfc\xd5;\x9ff&$A\x8a\x05\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94N\x89.\x80\x81\xbf6\xe4\x88\xfd\xdb;&0\xf3\xf1\xe8\xda0\u048a\x02\x8a\xba0u$Q\xfc\x00\x00\xe0\x94N\x8amcH\x9c\xcc\x10\xa5\u007f\x88_\x96\xeb\x04\xec\xbbT`$\x8a\x03\xea\xe3\x13\x0e\u0316\x90\x00\x00\u07d4N\x8eG\xae;\x1e\xf5\f\x9dT\xa3\x8e\x14 \x8c\x1a\xbd6\x03\u0089y(\xdb\x12vf\f\x00\x00\u0794N\x90\u03312X\xac\xaa\x9fO\xeb\xc0\xa3B\x92\xf9Y\x91\xe20\x88\xdbD\xe0I\xbb,\x00\x00\u07d4N\xa5n\x11\x12d\x1c\x03\x8d\x05e\xa9\u0096\xc4c\xaf\xef\xc1~\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\xe0\x94N\xa7\x0f\x041?\xaee\xc3\xff\"J\x05\\=-\xab(\xdd\u07ca\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4N\xb1EKW8\x05\u022c\xa3~\xde\xc7\x14\x9aA\xf6\x12\x02\xf4\x89\x10CV\x1a\x88)0\x00\x00\u07d4N\xb8{\xa8x\x8e\xba\r\xf8~[\x9b\xd5\n\x8eE6\x80\x91\xc1\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4N\xbcV)\xf9\xa6\xa6k,\xf36:\u0109\\\x03H\u8fc7\x8967\tlK\xcci\x00\x00\u07d4N\xc7h)^\xea\xba\xfcB\x95\x84\x15\xe2+\xe2\x16\xcd\xe7v\x18\x89\x03;\x1d\xbc9\xc5H\x00\x00\u07d4N\xcc\x19\x94\x8d\xd9\u0347\xb4\xc7 \x1a\xb4\x8eu\x8f(\xe7\xccv\x89\x1b\x1d\xaba\u04ead\x00\x00\u07d4N\xd1M\x81\xb6\v#\xfb%\x05M\x89%\u07e5s\u072eah\x89\x12nr\xa6\x9aP\xd0\x00\x00\xe0\x94N\xe1<\rA \vF\u045d\xee\\K\xce\xc7\x1d\x82\xbb\x8e8\x8a\x01\xab\xee\x13\u033e\ufbc0\x00\u07d4N\xea\xd4\n\xad\x8cs\xef\b\xfc\x84\xbc\n\x92\xc9\t/j6\xbf\x89\x01s\x17\x90SM\xf2\x00\x00\u07d4N\xeb\xe8\f\xb6\xf3\xaeY\x04\xf6\xf4\xb2\x8d\x90\u007f\x90q\x89\xfc\xab\x89lj\xccg\u05f1\xd4\x00\x00\u07d4N\xeb\xf1 ]\f\xc2\f\xeel\u007f\x8f\xf3\x11_V\u050f\xba&\x89\x01\r:\xa56\xe2\x94\x00\x00\u07d4N\xf1\xc2\x14c:\xd9\xc0p;N#t\xa2\xe3>>B\x92\x91\x89Hz\x9a0E9D\x00\x00\u07d4N\xfc\xd9\u01df\xb43L\xa6${\n3\xbd\x9c\xc32\b\xe2r\x89Hz\x9a0E9D\x00\x00\xe0\x94O\x06$k\x8dK\u0496a\xf4>\x93v\"\x01\u0486\x93Z\xb1\x8a\x01\x059O\xfcF6\x11\x00\x00\u07d4O\x15+/\xb8e\x9dCwn\xbb\x1e\x81g:\xa8Ai\xbe\x96\x89lk\x93[\x8b\xbd@\x00\x00\u07d4O\x17\u007f\x9dV\x95=\xedq\xa5a\x1f93\"\xc3\x02y\x89\\\x89\rU\uf422\xda\x18\x00\x00\u07d4O\x1a-\xa5JLm\xa1\x9d\x14$\x12\xe5n\x81WA\xdb#%\x89\x05k\xc7^-c\x10\x00\x00\u07d4O#\xb6\xb8\x17\xff\xa5\xc6d\xac\xda\u05db\xb7\xb7&\xd3\n\xf0\xf9\x89_h\xe8\x13\x1e\u03c0\x00\x00\xe0\x94O&i\f\x99+z1*\xb1.\x13\x85\xd9J\xcdX(\x8e{\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4O+G\xe2wZ\x1f\xa7\x17\x8d\xad\x92\x98Z[\xbeI;\xa6\u0589\n\u05ce\xbcZ\xc6 \x00\x00\u07d4O:HT\x91\x11E\xea\x01\xc6D\x04K\xdb.Z\x96\n\x98/\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4O?,g0i\xac\x97\xc2\x026\a\x15)\x81\xf5\xcd`c\xa0\x89 \x86\xac5\x10R`\x00\x00\xe0\x94OJ\x9b\xe1\f\xd5\xd3\xfb]\xe4\x8c\x17\xbe)o\x89V\x90d[\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4OR\xadap\xd2[*.\x85\x0e\xad\xbbRA?\xf20>\u007f\x89\xa4\xccy\x95c\u00c0\x00\x00\u07d4OX\x01\xb1\xeb0\xb7\x12\u0620WZ\x9aq\xff\x96]O4\xeb\x89\x10CV\x1a\x88)0\x00\x00\u07d4O]\xf5\xb9CW\u0794\x86\x04\xc5\x1bx\x93\xcd\xdf`v\xba\xad\x89\xcb\xd4{n\xaa\x8c\xc0\x00\x00\u07d4Od\xa8^\x8e\x9a@I\x8c\fu\xfc\xeb\x037\xfbI\b>^\x8965\u026d\xc5\u07a0\x00\x00\u07d4Og9m%S\xf9\x98x_pN\a\xa69\x19}\u0454\x8d\x89\x10DrR\x1b\xa78\x00\x00\u07d4OmG7\u05e9@8$\x87&H\x86i|\xf7c\u007f\x80\x15\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4Os0\toy\xed&N\xe0\x12\u007f]0\xd2\xf7?!\xcb\u007f\x04\x89\x04\x82\xfe&\f\xbc\xa9\x00\x00\u07d4O\xeeP\xc5\xf9\x88 k\t\xa5sF\x9f\xb1\u0434.\xbbm\u0389l\xee\x06\u077e\x15\xec\x00\x00\u07d4O\xf6v\xe2\u007fh\x1a\x98-\x8f\xd9\xd2\x0ed\x8b=\xce\x05\xe9E\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4O\xf6\u007f\xb8\u007fn\xfb\xa9'\x990\u03fd\x1bz4L\u057a\x8bN\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94PFf\u03891\x17^\x11\xa5\xed\x11\xc1\u072a\x06\xe5\u007fNf\x8a\x02\u007f>\u07f3Nn@\x00\x00\u0794PXM\x92\x06\xa4l\xe1\\0\x11\x17\xee(\xf1\\0\xe6\x0eu\x88\xb9\xf6]\x00\xf6<\x00\x00\xe0\x94PZ3\xa1\x864\xddH\x00i)\x13N\x00\x00\u07d4P\u0286\xb5\xeb\x1d\x01\x87M\xf8\xe5\xf3IE\u051cl\x1a\xb8H\x8965\u026d\xc5\u07a0\x00\x00\u07d4P\u0357\xe97\x8b\\\xf1\x8f\x179c#l\x99Q\xeft8\xa5\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4P\u073c'\xbc\xad\x98@\x93\xa2\x12\xa9\xb4\x17\x8e\xab\xe9\x01ua\x89\a\xe3by\v\\\xa4\x00\x00\u07d4P\xe10#\xbd\x9c\xa9j\xd4\xc5?\xdf\xd4\x10\xcbk\x1fB\v\u07c9\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94P\xe1\xc8\xec\x98A[\xefD&\x18p\x87\x99C{\x86\xe6\xc2\x05\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4P\xf8\xfaK\xb9\xe2g|\x99\nN\xe8\xcep\xdd\x15#%\x1eO\x89\x01i=#\x16Ok\x00\x00\u07d4P\xfb6\xc2q\a\xee,\xa9\xa3#n'F\u0321\x9a\xcekI\x89lk\x93[\x8b\xbd@\x00\x00\u07d4P\xfe\xf2\x96\x95U\x88\u02aet\xc6.\xc3*#\xa4T\xe0\x9a\xb8\x89A\x1d\xff\xab\xc5\a8\x00\x00\u07d4Q\x02\xa4\xa4 w\xe1\x1cX\xdfGs\u3b14F#\xa6m\x9f\x89lp\x15\xfdR\xed@\x80\x00\u07d4Q\x03\x93w\xee\xd0\xc5s\xf9\x86\xc5\xe8\xa9_\xb9\x9aY\xe93\x0f\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4Q\x03\xbc\t\x93>\x99!\xfdS\xdcSo\x11\xf0]\rG\x10}\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94Q\x04\xec\xc0\xe30\xdd\x1f\x81\xb5\x8a\xc9\u06f1\xa9\xfb\xf8\x8a<\x85\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4Q\r\x81Y\u0314Wh\xc7E\a\x90\xba\a>\xc0\xd9\xf8\x9e0\x89\x8a\xc7#\x04\x89\xe8\x00\x00\x00\u07d4Q\x0e\xdaV\x01I\x9a\r^\x1a\x00k\xff\xfd\x836r\xf2\xe2g\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Q\x12dF\xab=\x802U~\x8e\xbaeY}u\xfa\u0701\\\x89\x11t\xa5\xcd\xf8\x8b\xc8\x00\x00\xe0\x94Q\x18U}`\r\x05\xc2\xfc\xbf8\x06\xff\xbd\x93\xd0 %\xd70\x8a\x02g\u04ebd#\xf5\x80\x00\x00\u07d4Q\x1e\x0e\xfb\x04\xacN?\xf2\xe6U\x0eI\x82\x95\xbf\xcdV\xff\u0549$=M\x18\"\x9c\xa2\x00\x00\u07d4Q!\x16\x81{\xa9\xaa\xf8C\xd1P|e\xa5\xead\n{\x9e\xec\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4Q&F\ri,q\u026fo\x05WM\x93\x99\x83h\xa27\x99\x89\x02\u0465\x1c~\x00P\x00\x00\u07d4Q'\u007f\xe7\xc8\x1e\xeb\xd2R\xa0=\xf6\x9ak\x9f2n'\"\a\x89\x03@.y\u02b4L\x80\x00\u07d4Q)oPD'\r\x17pvF\x12\x9c\x86\xaa\xd1d^\xad\xc1\x89H|r\xb3\x10\xd4d\x80\x00\xe0\x94Q+\x91\xbb\xfa\xa9\xe5\x81\xefh?\xc9\r\x9d\xb2*\x8fI\xf4\x8b\x8aA\xa5\"8m\x9b\x95\xc0\x00\x00\u07d4Q5\xfb\x87W`\f\xf4tTbR\xf7M\xc0tm\x06&,\x89lk\x93[\x8b\xbd@\x00\x00\u07d4QF2\xef\xbdd,\x04\xdel\xa3B1]@\u0750\xa2\u06e6\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4QKu\x12\u026e^\xa6<\xbf\x11q[c\xf2\x1e\x18\u0496\xc1\x89lj\xccg\u05f1\xd4\x00\x00\u07d4QS\xa0\xc3\u0211(\x81\xbf\x1c5\x01\xbfd\xb4VI\xe4\x82\"\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94QVQ\xd6\xdbO\xaf\x9e\xcd\x10:\x92\x1b\xbb\xbej\xe9p\xfd\u050a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94Q_0\xbc\x90\xcd\xf4W~\xe4}e\u05c5\xfb\xe2\xe87\u01bc\x8a\x02'\x1b^\x01\x8b\xa0X\x00\x00\u07d4Q`\xeda.\x1bH\xe7??\xc1[\xc42\x1b\x8f#\xb8\xa2K\x89\x1e\x82kB(e\xd8\x00\x00\u07d4Qa\xfdI\xe8G\xf6tU\xf1\u023bz\xbb6\xe9\x85&\r\x03\x89A\rXj \xa4\xc0\x00\x00\u07d4QiT\x02_\xca&\b\xf4}\xa8\x1c!^\xed\xfd\x84J\t\xff\x89\x14\xb5P\xa0\x13\xc78\x00\x00\u07d4Qi\xc6\n\xeeL\xee\u0444\x9a\xb3mfL\xff\x97\x06\x1e\x8e\xa8\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4Q|uC\r\xe4\x01\xc3A\x03&\x86\x11'\x90\xf4mM6\x9e\x89\x15\b\x94\xe8I\xb3\x90\x00\x00\u07d4Q|\xd7`\x8e]\r\x83\xa2kq\u007f6\x03\xda\xc2'}\u00e4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Q\x86]\xb1H\x88\x19Q\xf5\x12Qq\x0e\x82\xb9\xbe\r~\xad\xb2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Q\x89\x1b,\xcd\xd2\xf5\xa4K*\x8b\u011a]\x9b\xcadw%\x1c\x89\x10\xce\x1d=\x8c\xb3\x18\x00\x00\u07d4Q\x8c\xef'\xb1\x05\x82\xb6\xd1OiH=\u06a0\xdd<\x87\xbb\\\x89 \x86\xac5\x10R`\x00\x00\u07d4Q\xa6\xd6'\xf6j\x89#\u060d`\x94\xc4qS\x80\xd3\x05|\xb6\x89>s\xd2z5\x94\x1e\x00\x00\u07d4Q\xa8\xc2\x166\x02\xa3.\xe2L\xf4\xaa\x97\xfd\x9e\xa4\x14QiA\x89\x03h\xf7\xe6\xb8g,\x00\x00\u07d4Q\xb4u\x8e\x9e\x14P\xe7\xafBh\xc3\u01f1\xe7\xbdo\\uP\x8965\u026d\xc5\u07a0\x00\x00\u07d4Q\u028b\xd4\xdcdO\xacG\xafgUc\u0540J\r\xa2\x1e\xeb\x89*\xb7\xb2`\xff?\xd0\x00\x00\u07d4Q\xd2K\xc3so\x88\xddc\xb7\" &\x88f0\xb6\ub1cd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Q\u05cb\x17\x8dp~9n\x87\x10\x96\\OA\xb1\xa1\xd9\x17\x9d\x89\x05\xfe\xe2\"\x04\x1e4\x00\x00\u07d4Q\xe3/\x14\xf4\xca^(|\xda\xc0W\xa7y^\xa9\xe0C\x99S\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4Q\xe4?\xe0\xd2\\x(`\xaf\x81\xea\x89\xddy<\x13\xf0\u02f1\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4Q\xe7\xb5\\/\x98 \xee\xd78\x846\x1bPf\xa5\x9boE\u0189lk\x93[\x8b\xbd@\x00\x00\xe0\x94Q\xea\x1c\t4\xe3\xd0@\"\ud715\xa0\x87\xa1P\xefp^\x81\x8a\x01Tp\x81\xe7\"M \x00\x00\u07d4Q\xee\f\xca;\xcb\x10\xcd>\x987\"\xce\xd8I=\x92l\bf\x8965f3\xeb\xd8\xea\x00\x00\xe0\x94Q\xf4f:\xb4O\xf7\x93E\xf4'\xa0\xf6\xf8\xa6\u0225?\xf24\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4Q\xf5^\xf4~dV\xa4\x18\xab2\xb9\"\x1e\xd2}\xbaf\b\xee\x89\u3bb5sr@\xa0\x00\x00\xe0\x94Q\xf9\xc42\xa4\xe5\x9a\xc8b\x82\u05ad\xabL.\xb8\x91\x91`\xeb\x8ap;[\x89\u00e6\xe7@\x00\x00\u07d4R\x0ff\xa0\xe2e\u007f\xf0\xacA\x95\xf2\xf0d\xcf/\xa4\xb2BP\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4R\x10#T\xa6\xac\xa9]\x8a.\x86\xd5\u07bd\xa6\xdei4`v\x89lk\x93[\x8b\xbd@\x00\x00\u07d4R\x13\xf4Y\xe0x\xad:\xb9Z\t #\x9f\xcf\x163\xdc\x04\u0289\x8c\xf2\x18|*\xfb\x18\x80\x00\u07d4R\x15\x18;\x8f\x80\xa9\xbc\x03\xd2l\xe9\x12\a\x83*\r9\xe6 \x8965\u026d\xc5\u07a0\x00\x00\xe0\x94R!Cx\xb5@\x04\x05j|\xc0\x8c\x89\x13'y\x8a\u01b2H\x8a\x037\xfe_\xea\xf2\u0440\x00\x00\xe0\x94R##\xaa\xd7\x1d\xbc\x96\xd8Z\xf9\x0f\bK\x99\xc3\xf0\x9d\ucdca\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4R>\x14\r\xc8\x11\xb1\x86\xde\xe5\xd6\u020b\xf6\x8e\x90\xb8\xe0\x96\xfd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4R?mdi\x0f\xda\u0354(SY\x1b\xb0\xff \xd3em\x95\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4RO\xb2\x10R,^#\xbbg\u07ff\x8c&\xaaam\xa4\x99U\x8965b\xa6m4#\x80\x00\u07d4RU\xdci\x15ZE\xb9p\xc6\x04\xd3\x00G\xe2\xf50i\x0e\u007f\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4R`\xdcQ\xee\a\xbd\u06ab\xab\xb9\xeetK9<\u007fG\x93\xa6\x89\x01\xd8f_\xa5\xfaL\x00\x00\u07d4Rg\xf4\xd4\x12\x92\xf3p\x86<\x90\u05d3)i\x03\x846%\u01c9K\xe4\xe7&{j\xe0\x00\x00\u07d4Rk\xb53\xb7n \xc8\xee\x1e\xbf\x12?\x1e\x9f\xf4\x14\x8e@\xbe\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4Rl\xb0\x9c\u3b63g.\xec\x1d\xebF [\xe8\x9aKV>\x89\x85\xcaa[\xf9\xc0\x10\x00\x00\u07d4Rs\x8c\x90\xd8`\xe0L\xb1/I\x8d\x96\xfd\xb5\xbf6\xfc4\x0e\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4Rz\x8c\xa1&\x863\xa6\xc99\xc5\xde\x1b\x92\x9a\ue4ae\xac\x8d\x890\xca\x02O\x98{\x90\x00\x00\u07d4R\x81\x01\xceF\xb7 \xa2!M\u036ef\x18\xa51w\xff\xa3w\x89\x1b\x96\x12\xb9\xdc\x01\xae\x00\x00\xe0\x94R\x81s4s\xe0\r\x87\xf1\x1e\x99U\u5275\x9fJ\u008ez\x8a\x8b\xd6/\xf4\xee\xc5Y \x00\x00\u07d4R\x98\xab\x18*\x195\x9f\xfc\xec\xaf\xd7\u0475\xfa!-\xed\xe6\u0749\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4R\x9a\xa0\x02\u0196*:\x85E\x02\u007f\u0630_\"\xb5\xbf\x95d\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4R\x9e\x82O\xa0rX+@2h:\xc7\xee\xcc\x1c\x04\xb4\xca\xc1\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94R\xa5\xe4\xdeC\x93\xee\xcc\xf0X\x1a\xc1\x1bR\u0183\xc7n\xa1]\x8a\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4R\xb4%|\xf4\x1bn(\x87\x8dP\xd5{\x99\x91O\xfa\x89\x87:\x89\xd5\r\u026a,Aw\x00\x00\u07d4R\xb8\xa9Y&4\xf70\v|\\Y\xa34[\x83_\x01\xb9\\\x89lk\x93[\x8b\xbd@\x00\x00\u07d4R\xbd\u066fYx\x85\v\xc2A\x10q\x8b7#u\x9bC~Y\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4R\xcd @;\xa7\xed\xa6\xbc0z=c\xb5\x91\x1b\x81|\x12c\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u0794R\u04c0Q\x1d\xf1\x9d^\u0080{\xbc\xb6vX\x1bg\xfd7\xa3\x88\xb9\xf6]\x00\xf6<\x00\x00\xe0\x94R\xe1s\x13P\xf9\x83\xcc,A\x89\x84/\xde\x06\x13\xfa\xd5\f\xe1\x8a\x02w\x01s8\xa3\n\xe0\x00\x00\u07d4R\xe4g\x832\x9av\x93\x01\xb1u\x00\x9d4gh\xf4\xc8~\xe4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4R\xf0X\xd4aG\xe9\x00m)\xbf,\t0J\xd1\xcd\xddn\x15\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4R\xf1T#2<$\xf1\x9a\xe2\xabg7\x17\"\x9d?t}\x9b\x897\xa04\xcb\xe8\xe3\xf3\x80\x00\u07d4R\xf8\xb5\t\xfe\xe1\xa8t\xabo\x9d\x876\u007f\xbe\xaf\x15\xac\x13\u007f\x8965\u026d\xc5\u07a0\x00\x00\u07d4R\xfbF\xac]\x00\xc3Q\x8b,:\x1c\x17}D/\x81eU_\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4S\x00w\xc9\xf7\xb9\a\xff\x9c\xec\fw\xa4\x1ap\xe9\x02\x9a\xddJ\x89lk\x93[\x8b\xbd@\x00\x00\u07d4S\x03\x19\xdb\n\x8f\x93\xe5\xbb}M\xbfH\x161O\xbe\xd86\x1b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4S\x04}\u022c\x90\x83\xd9\x06r\xe8\xb3G<\x10\f\xcd'\x83#\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4S\va\xe4/9Bm$\b\xd4\bR\xb9\xe3J\xb5\xeb\xeb\u0149\x0e~\xeb\xa3A\vt\x00\x00\u07d4S\x0f\xfa\u00fc4\x12\xe2\xec\x0e\xa4{y\x81\xc7p\xf5\xbb/5\x89\a?u\u0460\x85\xba\x00\x00\u07d4S\x17\xec\xb0#\x05,\xa7\xf5e+\xe2\xfa\x85L\xfeEc\xdfM\x89\x1b\x1a\xb3\x19\xf5\xecu\x00\x00\u07d4S\x19M\x8a\xfa>\x885\x02v~\xdb\xc3\x05\x86\xaf3\xb1\x14\u04c9lk\x93[\x8b\xbd@\x00\x00\u07d4S*}\xa0\xa5\xadt\aF\x8d;\xe8\xe0~i\xc7\xddd\xe8a\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4S-2\xb0\x0f0[\xcc$\xdc\xefV\x81}b/4\xfb,$\x89a\x94\x04\x9f0\xf7 \x00\x00\u07d4S4DX@\x82\xeb\xa6T\xe1\xad0\xe1Is\\o{\xa9\"\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4S8\xefp\xea\xc9\u075a\xf5\xa0P;^\xfa\xd1\x03\x9eg\xe7%\x89\x90\xf54`\x8ar\x88\x00\x00\xe0\x94S9oJ&\u00b4`D\x960lTB\xe7\xfc\xba'.6\x8a\x04?/\b\xd4\x0eZ\xfc\x00\x00\xe0\x94S:s\xa4\xa2\"\x8e\xee\x05\xc4\xff\xd7\x18\xbb\xf3\xf9\xc1\xb1)\xa7\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4S<\x06\x92\x8f\x19\u0429V\xcc(\x86k\xf6\xc8\xd8\xf4\x19\x1a\x94\x89\x0f\xd8\xc1C8\xe60\x00\x00\u07d4S@e6\x1c\xb8T\xfa\xc4+\xfb\\\x9f\xcd\xe0`J\xc9\x19\u0689lk\x93[\x8b\xbd@\x00\x00\u07d4SC\u007f\xec\xf3J\xb9\xd45\xf4\u07b8\xca\x18\x15\x19\xe2Y 5\x89\n1\x06+\xee\xedp\x00\x00\u07d4SR\x01\xa0\xa1\xd74\"\x80\x1fU\xde\xd4\u07ee\xe4\xfb\xaan;\x89\x02&!\x1fy\x15B\x80\x00\xe0\x94S`\x81\x05\xceK\x9e\x11\xf8k\xf4\x97\xff\xca;x\x96{_\x96\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4SnM\x80)\xb7?Uy\u0723>p\xb2N\xba\x89\xe1\x1d~\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4Sp\rS%MC\x0f\"x\x1aJv\xa4c\x93;]k\b\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94S\u007f\x9dM1\xefp\x83\x9d\x84\xb0\xd9\u0377+\x9a\xfe\xdb\xdf5\x8a\x0e\u04b5%\x84\x1a\xdf\xc0\x00\x00\xe0\x94S\x81D\x85\x03\xc0\xc7\x02T+\x1d\xe7\xcc_\xb5\xf6\xab\x1c\xf6\xa5\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94S\x94.yI\xd6x\x8b\xb7\x80\xa7\xe8\xa0y'\x81\xb1aK\x84\x8a\x03]\xebFhO\x10\xc8\x00\x00\u07d4S\x95\xa4E]\x95\xd1x\xb4S*\xa4r[\x19?\xfeQ)a\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94S\x98\x9e\xd30V?\xd5}\xfe\u027d4<7`\xb0y\x93\x90\x8a\x01P\x89N\x84\x9b9\x00\x00\x00\u07d4S\xa2Dg(\x95H\x0fJ+\x1c\xdf}\xa5\xe5\xa2B\xecM\xbc\x8965\u026d\xc5\u07a0\x00\x00\u07d4S\xa7\x14\xf9\x9f\xa0\x0f\xefu\x8e#\xa2\xe7F2m\xad$|\xa7\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4S\xaf2\xc2/\uf640?\x17\x8c\xf9\v\x80/\xb5q\xc6\x1c\xb9\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4S\xc0\xbb\u007f\u020e\xa4\"\xd2\xef~T\x0e-\x8f(\xb1\xbb\x81\x83\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94S\xc5\xfe\x01\x19\xe1\xe8Hd\f\xee0\xad\ua594\x0f*]\x8b\x8a\x04\x9a\xda_\xa8\xc1\f\x88\x00\x00\u07d4S\xc9\xec\xa4\ts\xf6;\xb5\x92{\xe0\xbcj\x8a\x8b\xe1\x95\x1ft\x89lk\x93[\x8b\xbd@\x00\x00\u07d4S\u0388\xe6lZ\xf2\U0009bf4fY*V\xa3\xd1_ l2\x89\a\xa2\x8c1\xcc6\x04\x00\x00\u07d4S\xce\xc6\u0200\x92\xf7V\xef\xe5o}\xb1\x12(\xa2\xdbE\xb1\"\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4S\xe3[\x12#\x1f\x19\xc3\xfdwL\x88\xfe\xc8\xcb\xee\xdf\x14\b\xb2\x89\x1b\xc1mgN\xc8\x00\x00\x00\u07d4S\xe4\xd9im\xcb?M{?p\u072aN\xec\xb7\x17\x82\xff\\\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4S\xfa\xf1e\xbe\x03\x1e\xc1\x830\xd9\xfc\xe5\xbd\x12\x81\xa1\xaf\b\u06c9\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4T\n\x18\x19\xbd|5\x86\x1ey\x18\x04\xe5\xfb\xb3\xbc\x97\u026b\xb1\x89N\xd7\xda\xc6B0 \x00\x00\xe0\x94T\f\a(\x02\x01N\xf0\xd5a4Z\xecH\x1e\x8e\x11\xcb5p\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94T\f\xf2=\xd9\\MU\x8a'\x9dw\x8d+75\xb3\x16A\x91\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4T\x10`\xfcX\xc7P\xc4\x05\x12\xf83i\xc0\xa63@\xc1\"\xb6\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4T\x13\xc9\u007f\xfaJn*{\xba\x89a\u071f\u03850\xa7\x87\u05c965\u026d\xc5\u07a0\x00\x00\u07d4T\x1d\xb2\n\x80\xcf;\x17\xf1b\x1f\x1b?\xf7\x9b\x88/P\xde\xf3\x8965\u026d\xc5\u07a0\x00\x00\u07d4T.\x80\x96\xba\xfb\x88\x16&\x06\x00.\x8c\x8a>\u0458\x14\xae\xac\x89lk\x93[\x8b\xbd@\x00\x00\u07d4T1\v:\xa8\x87\x03\xa7%\u07e5}\xe6\xe6F\x93Qd\x80,\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4T1\xb1\u0447Q\xb9\x8f\xc9\u220a\xc7u\x9f\x155\xa2\xdbG\x89lk\x93[\x8b\xbd@\x00\x00\u07d4T1\xcaB~ae\xa6D\xba\xe3&\xbd\tu\n\x17\x8ce\r\x89lk\x93[\x8b\xbd@\x00\x00\u07d4T5\xc6\xc1y3\x17\xd3,\xe1;\xbaLO\xfe\xb9s\xb7\x8a\u0709\r\x8ek\x1c\x12\x85\xef\x00\x00\xe0\x94T6)\xc9\\\xde\xf4(\xad7\xd4S\u02958\xa9\xf9\t\x00\xac\x8a\t(\x96R\x9b\xad\u0708\x00\x00\u07d4T9\x1bM\x17mGl\xea\x16N_\xb55\u0197\x00\xcb%5\x89\x05l\xd5_\xc6M\xfe\x00\x00\xe0\x94T:\x8c\x0e\xfb\x8b\xcd\x15\xc5C\u29a4\xf8\aYv1\xad\xef\x8a\x01?\x80\xe7\xe1O-D\x00\x00\u07d4T?\x8cgN$b\xd8\xd5\u06a0\xe8\x01\x95\xa8p\x8e\x11\xa2\x9e\x89\x03wX\x83;:z\x00\x00\xe0\x94TK[5\x1d\x1b\xc8.\x92\x97C\x99H\xcfHa\xda\u026e\x11\x8a\x04\xa8\x9fT\xef\x01!\xc0\x00\x00\u07d4TM\xdaB\x1d\xc1\xebs\xbb$\xe3\xe5j$\x80\x13\xb8|\x0fD\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4TW\\1\x14u\x1e\x14o\xfe\u00c7nE\xf2\x0e\xe8AJ\u07ba\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4T\xb4B\x9b\x18/\x03w\xbe~bi9\xc5\xdbd@\xf7]z\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4T\xbc\xb8\xe7\xf7<\xda=s\xf4\u04cb-\bG\xe6\x00\xba\r\xf8\x89:pAX\x82\xdf\x18\x00\x00\u07d4T\xc9>\x03\xa9\xb2\xe8\xe4\xc3g(5\xa9\xeev\xf9a[\xc1N\x89\x01\r:\xa56\xe2\x94\x00\x00\u07d4T\u0388'YV\xde\xf5\xf9E\x8e;\x95\xde\xca\xcdH@!\xa0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4T\xdb^\x06\xb4\x81]1\xcbV\xa8q\x9b\xa3:\xf2\xd7>rR\x89$R\x1e*0\x17\xb8\x00\x00\xe0\x94T\xe0\x12\x83\u030b8E8\xdddgp\xb3W\xc9`\xd6\xca\u034a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4T\xecs\x00\xb8\x1a\xc8C3\xed\x1b\x03<\xd5\u05e39r\xe24\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4T\xfe\xbc\xce \xfez\x90\x98\xa7U\xbd\x90\x98\x86\x02\xa4\x8c\b\x9e\x89\"\xb1\xc8\xc1\"z\x00\x00\x00\u07d4U\n\xad\xae\x12!\xb0z\xfe\xa3\x9f\xba.\xd6.\x05\u5df5\xf9\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4U\f0o\x81\xef]\x95\x80\xc0l\xb1\xab \x1b\x95\xc7H\xa6\x91\x89$\x17\xd4\xc4p\xbf\x14\x00\x00\xe0\x94U\x19\x99\xdd\xd2\x05V3'\xb9\xb50xZ\xcf\xf9\xbcs\xa4\xba\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4U\x1ew\x84w\x8e\xf8\xe0H\xe4\x95\xdfI\xf2aO\x84\xa4\xf1\u0709 \x86\xac5\x10R`\x00\x00\xe0\x94U)\x83\na\xc1\xf1<\x19~U\v\xed\xdf\u05bd\x19\\\x9d\x02\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4U)\x87\xf0e\x1b\x91[.\x1eS(\xc1!\x96\rK\xddj\xf4\x89a\t=|,m8\x00\x00\u07d4U;k\x1cW\x05\x0e\x88\xcf\f1\x06{\x8dL\xd1\xff\x80\xcb\t\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4U?7\xd9$fU\x0e\x9f\xd7u\xaet6-\xf00\x17\x912\x89lk\x93[\x8b\xbd@\x00\x00\u07d4UC6\xeeN\xa1U\xf9\xf2O\x87\xbc\xa9\xcar\xe2S\xe1,\u0489\x05k\xc7^-c\x10\x00\x00\u0794UC\xddm\x16\x9e\xec\x8a!;\xbfz\x8a\xf9\xff\xd1]O\xf7Y\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4UG\xfd\xb4\xae\x11\x95>\x01)+x\a\xfa\x92#\xd0\xe4`j\x89\x05]\x11}\xcb\x1d&\x00\x00\u07d4UR\xf4\xb3\xed>\x1d\xa7\x9a/x\xbb\x13\xe8\xaeZh\xa9\xdf;\x8965\u026d\xc5\u07a0\x00\x00\u07d4U\\\xa9\xf0\\\xc14\xabT\xae\x9b\xea\x1c?\xf8z\xa8Q\x98\u0289\x05k\xc7^-c\x10\x00\x00\xe0\x94U]\x8d<\xe1y\x8a\u0290'T\xf1d\xb8\xbe*\x022\x9cl\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4U]\xf1\x93\x90\xc1m\x01)\x87r\xba\xe8\xbc:\x11R\x19\x9c\xbd\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4U^\xbe\x84\u06a4+\xa2V\xeax\x91\x05\xce\u0136\x93\xf1/\x18\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94U\u007f^e\xe0\xda3\x99\x82\x19\xadN\x99W\x05E\xb2\xa9\xd5\x11\x8a\x02U\x9c\xbb\x98XB@\x00\x00\u07d4U\x83` h\x83\xdd\x1bmJYc\x9eV)\xd0\xf0\xc6u\u0409lk\x93[\x8b\xbd@\x00\x00\u07d4U\x84B0P\xe3\xc2\x05\x1f\v\xbd\x8fD\xbdm\xbc'\xec\xb6,\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4U\x85)CI)p\xf8\xd6)\xa1Sf\xcd\xda\x06\xa9OE\x13\x89lk\x93[\x8b\xbd@\x00\x00\u0794U\x86d\x86\xec\x16\x8fy\xdb\xe0\u1af1\x88d\u0649\x91\xae,\x88\xdfn\xb0\xb2\xd3\xca\x00\x00\u07d4U\x8cTd\x9a\x8an\x94r+\xd6\xd2\x1d\x14qOqx\x054\x89lk\x93[\x8b\xbd@\x00\x00\u07d4U\x91\x940O\x14\xb1\xb9:\xfeDO\x06$\xe0S\xc2:\x00\t\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4U\x93\xc9\u0536ds\x0f\xd9<\xa6\x01Q\xc2\\.\xae\xd9<;\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4U\x97\x06\xc32\xd2\ay\xc4_\x8am\x04ji\x91Y\xb7I!\x89\x14\x9bD.\x85\xa3\u03c0\x00\u07d4U\x98\xb3\xa7\x9aH\xf3+\x1f_\xc9\x15\xb8{d]\x80]\x1a\xfe\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4U\xa3\xdfW\xb7\xaa\xec\x16\xa1b\xfdS\x16\xf3[\xec\b(!\u03c9j\xcb=\xf2~\x1f\x88\x00\x00\u07d4U\xa4\xca\xc0\u02cbX-\x9f\xef8\xc5\xc9\xff\xf9\xbdS\t=\x1f\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4U\xa6\x1b\x10\x94\x80\xb5\xb2\xc4\xfc\xfd\xef\x92\xd9\x05\x84\x16\f\r5\x89\x02lVM+S\xf6\x00\x00\u07d4U\xaa]1>\xbb\bM\xa0\xe7\x80\x10\x91\u2792\xc5\xde\u00ea\x89lk\x93[\x8b\xbd@\x00\x00\u07d4U\xab\x99\xb0\xe0\xe5]{\xb8t\xb7\xcf\xe84\xdec\x1c\x97\xec#\x897\xe9\x8c\xe3h\x99\xe4\x00\x00\u07d4U\xaf\t/\x94\xbajy\x91\x8b\f\xf99\xea\xb3\xf0\x1b?Q\u01c9\b \xd5\xe3\x95v\x12\x00\x00\u07d4U\xc5dfAf\xa1\xed\xf3\x91>\x01i\xf1\xcdE\x1f\xdb]\f\x89\x82\x17\xeaIP\x8el\x00\x00\xe0\x94U\xcaj\xbey\xea$\x97\xf4o\u06f804`\x10\xfeF\x9c\xbe\x8a\x016\x9f\xb9a(\xacH\x00\x00\u07d4U\xca\xffK\xba\x04\xd2 \u0265\xd2\x01\x86r\xec\x85\xe3\x1e\xf8>\x89lk\x93[\x8b\xbd@\x00\x00\u07d4U\xd0W\xbc\xc0K\xd0\xf4\xaf\x96BQ:\xa5\t\v\xb3\xff\x93\xfe\x89;\xfeE,\x8e\xddL\x00\x00\u07d4U\xd4.\xb4\x95\xbfF\xa64\x99{_.\xa3b\x81I\x18\u2c09\x05\xc0\xd2e\xb5\xb2\xa8\x00\x00\u07d4U\u069d\xcd\xcaa\xcb\xfe\x1f\x13<{\xce\xfc\x86{\x9c\x81\"\xf9\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4U\xe2 \x87bb\xc2\x18\xafOVxG\x98\xc7\xe5]\xa0\x9e\x91\x89\a=\x99\xc1VE\xd3\x00\x00\u07d4U\xfd\b\u0440d\xbd ,\x0e\xc3\xd2\xcc\xe0\xce\v\x9d\x16\x9cM\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4V\x00s\nU\xf6\xb2\x0e\xbd$\x81\x1f\xaa=\xe9m\x16b\xab\xab\x89e\xea=\xb7UF`\x00\x00\u07d4V\x03$\x1e\xb8\xf0\x8fr\x1e4\x8c\x9d\x9a\xd9/H\u342a$\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4V\x056yJ\x9e+\x00I\xd1\x023\xc4\x1a\xdc_A\x8a&J\x8965\u026d\xc5\u07a0\x00\x00\u07d4V\aY\x00Y\xa9\xfe\xc1\x88\x11I\xa4K6\x94\x9a\xef\x85\xd5`\x89lk\x93[\x8b\xbd@\x00\x00\u07d4V\v\xec\xdfR\xb7\x1f=\x88'\xd9'a\x0f\x1a\x98\x0f3qo\x89\x17GMp_V\u0400\x00\xe0\x94V\r\xa3~\x95m\x86/\x81\xa7_\u0540\xa7\x13\\\x1b$cR\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94V\x0f\xc0\x8d\a\x9f\x04~\xd8\xd7\xdfuU\x1a\xa55\x01\xf5p\x13\x8a\x01\x9b\xff/\xf5yh\xc0\x00\x00\u07d4V\x1b\xe9)\x9b>k>c\xb7\x9b\t\x16\x9d\x1a\x94\x8a\xe6\xdb\x01\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94V \xe3\xedy-/\x185\xfe_UA}Q\x11F\fj\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4V \xf4m\x14Q\xc25=bC\xa5\u0534'\x13\v\xe2\xd4\a\x89\x03@\xaa\xd2\x1b;p\x00\x00\xe0\x94V!\x05\xe8+\t\x975\xdeI\xf6&\x92\u0307\xcd8\xa8\xed\u034a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94V*\x8d\u02fe\xee\xf7\xb3`h]'0;\u059e\tJ\xcc\xf6\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4V+\xce\u04ca\xb2\xabl\b\x0f;\x05A\xb8Enp\x82K?\x89\"\xca5\x87\xcfN\xb0\x00\x00\xe0\x94V+\xe9Z\xba\x17\xc57\x1f\u2e82\x87\x99\xb1\xf5]!w\u058a\b\x16\xd3~\x87\xb9\xd1\xe0\x00\x00\u07d4V/\x16\u05da\xbf\xce\u00d4>4\xb2\x0f\x05\xf9{\xdf\u0366\x05\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4V7=\xaa\xb4c\x16\xfd~\x15v\xc6\x1ej\xff\xcbeY\xdd\u05c9\v\xacq]\x14l\x9e\x00\x00\u07d4V9v8\xbb<\xeb\xf1\xf6 byK^\xb9B\xf9\x16\x17\x1d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4V:\x03\xab\x9cV\xb6\x00\xf6\xd2[f\f!\xe1c5Qzu\x8965\u026d\xc5\u07a0\x00\x00\u07d4V<\xb8\x80<\x1d2\xa2['\xb6A\x14\x85+\xd0M\x9c \u0349\v\x14\x9e\xad\n\xd9\xd8\x00\x00\u07d4VXc\x91\x04\fW\xee\xc6\xf5\xaf\xfd\x8c\u052b\xde\x10\xb5\n\u0309\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4Vl\x10\xd68\u8e0bG\xd6\xe6\xa4\x14Iz\xfd\xd0\x06\x00\u0509\x05k9Bc\xa4\f\x00\x00\u07d4Vl(\xe3L8\b\xd9vo\xe8B\x1e\xbfO+\x1cO}w\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4V\x8d\xf3\x18Vi\x9b\xb5\xac\xfc\x1f\xe1\u0580\u07d9`\xcaCY\x89J\xcfUR\xf3\xb2I\x80\x00\u07d4V\x91\xdd/gE\xf2\x0e\"\xd2\xe1\u0479U\xaa)\x03\xd6VV\x89j\xc5\xc6-\x94\x86\a\x00\x00\u07d4V\xa1\xd6\r@\xf5\u007f0\x8e\xeb\xf0\x87\xde\xe3\xb3\u007f\x1e|,\xba\x89>\u072e\xc8-\x06\xf8\x00\x00\u07d4V\xac \xd6;\xd8\x03Y\\\xec\x03m\xa7\xed\x1d\xc6n\n\x9e\a\x89\x03w*S\xcc\xdce\x80\x00\u07d4V\xb6\xc2=\xd2\uc434r\x8f;\xb2\xe7d\xc3\xc5\f\x85\xf1D\x8965\u026d\xc5\u07a0\x00\x00\u07d4V\xdf\x05\xba\xd4l?\x00\xaeGn\xcf\x01{\xb8\xc8w8?\xf1\x89\n\xb1]\xaa\xefp@\x00\x00\u07d4V\xee\x19\u007fK\xbf\x9f\x1b\x06b\xe4\x1c+\xbd\x9a\xa1\xf7\x99\xe8F\x8965\u026d\xc5\u07a0\x00\x00\u07d4V\xf4\x93\xa3\xd1\b\xaa\xa2\u044d\x98\x92/\x8e\xfe\x16b\u03f7=\x89m\x81!\xa1\x94\xd1\x10\x00\x00\u07d4V\xfc\x1a{\xad@G#|\xe1\x16\x14b\x96#\x8e\a\x8f\x93\xad\x89\t\xa6?\b\xeac\x88\x00\x00\u07d4V\xfe\xbf\x9e\x10\x03\xaf\x15\xb1\xbdI\a\xec\b\x9aJ\x1b\x91\xd2h\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4W\x17\u0313\x01Q\x1dJ\x81\xb9\xf5\x83\x14\x8b\xee\xd3\xd3\u0303\t\x89\x8c\xf2?\x90\x9c\x0f\xa0\x00\x00\u07d4W\x17\xf2\xd8\xf1\x8f\xfc\xc0\xe5\xfe$}:B\x19\x03|:d\x9c\x89\u063beI\xb0+\xb8\x00\x00\u07d4W\x19P\xea,\x90\xc1B}\x93\x9da\xb4\xf2\xdeL\xf1\u03ff\xb0\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4W\x19\xf4\x9br\r\xa6\x88V\xf4\xb9\xe7\b\xf2VE\xbd\xbcKA\x89\"\xb1\xc8\xc1\"z\x00\x00\x00\u07d4W*\xc1\xab\xa0\xde#\xaeA\xa7\xca\xe1\xdc\bB\u062b\xfc\x10;\x89g\x8a\x93 b\xe4\x18\x00\x00\xe0\x94W-\xd8\xcd?\xe3\x99\xd1\xd0\xec(\x121\xb7\xce\xfc \xb9\u4eca\x023\xc8\xfeBp>\x80\x00\x00\xe0\x94WI!\x83\x8c\xc7}l\x98\xb1}\x90::\xe0\xee\r\xa9[\u040a\vS(\x17\x8a\xd0\xf2\xa0\x00\x00\u07d4WJ\xd95S\x90\u421e\xf4*\xcd\x13\x8b*'\xe7\x8c\x00\xae\x89Tg\xb72\xa9\x134\x00\x00\u07d4WM\xe1\xb3\xf3\x8d\x91XF\xae7\x18VJZ\xda \xc2\xf3\xed\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94W\\\x00\u0081\x82\x10\u0085U\xa0\xff)\x01\x02\x89\xd3\xf8#\t\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94Ws\xb6\x02g!\xa1\xdd\x04\xb7\x82\x8c\xd6+Y\x1b\xfb4SL\x8a\x05\xb7\xacES\xdez\xe0\x00\x00\xe0\x94WwD\x1c\x83\xe0?\v\xe8\xdd4\v\xdechP\x84|b\v\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4Wx\xff\u071b\x94\u0165\x9e\"N\xb9e\xb6\u0790\xf2\"\xd1p\x89\x12-\u007f\xf3f\x03\xfc\x00\x00\u07d4Wz\xee\xe8\u053c\b\xfc\x97\xab\x15n\xd5\u007f\xb9p\x92Sf\xbe\x89\x12\r\xf1\x14rX\xbf\x00\x00\u07d4W{-\a\xe9\xcfRJ\x18\u04c9\x15Vak\x96\x06g\x00\x00\u07d4W\xd5\xfd\x0e=0I3\x0f\xfc\xdc\xd0 Ei\x17e{\xa2\u0689k\xf2\x01\x95\xf5T\xd4\x00\x00\u07d4W\u0754q\xcb\xfa&'\t\xf5\U00106f37t\xc5\xf5'\xb8\xf8\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4W\xdf#\xbe\xbd\xc6^\xb7_\ub732\xfa\xd1\xc0si++\xaf\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4X\x00\u03410\x83\x9e\x94I]-\x84\x15\xa8\xea,\x90\xe0\xc5\u02c9\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94X\x03\xe6\x8b4\xda\x12\x1a\xef\b\xb6\x02\xba\u06ef\xb4\xd1$\x81\u028a\x03\xcf\xc8.7\xe9\xa7@\x00\x00\xe0\x94X\x16\xc2hww\xb6\xd7\u04a2C-Y\xa4\x1f\xa0Y\xe3\xa4\x06\x8a\x1cO\xe4:\xdb\n^\x90\x00\x00\u07d4X\x1a:\xf2\x97\xef\xa4Cj)\xaf\x00r\x92\x9a\xbf\x98&\xf5\x8b\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94X\x1b\x9f\xd6\xea\xe3r\xf3P\x1fB\xeb\x96\x19\xee\xc8 \xb7\x8a\x84\x8a\x04+\xe2\xc0\f\xa5;\x8d\x80\x00\u07d4X\x1b\xdf\x1b\xb2v\xdb\u0746\xae\xdc\xdb9z\x01\xef\xc0\xe0\f[\x8965\u026d\xc5\u07a0\x00\x00\u07d4X\x1f4\xb5#\xe5\xb4\x1c\t\xc8|)\x8e)\x9c\xbc\x0e)\xd0f\x89=X3\xaa\xfd9u\x80\x00\xe0\x94X$\xa7\xe2(8'q40\x8c_KP\u06b6^C\xbb1\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4X+pf\x9c\x97\xaa\xb7\u0581H\xd8\xd4\xe9\x04\x11\xe2\x81\rV\x8965f3\xeb\xd8\xea\x00\x00\u07d4X.|\xc4o\x1d{Nn\x9d\x95\x86\x8b\xfd7\x05s\x17\x8fL\x89lk\x93[\x8b\xbd@\x00\x00\u07d4X>\x83\xbaU\xe6~\x13\xe0\xe7o\x83\x92\xd8s\xcd!\xfb\xf7\x98\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4Xi\xfb\x86}q\xf18\u007f\x86;i\x8d\t\xfd\xfb\x87\u011b\\\x89\u01bb\xf8X\xb3\x16\b\x00\x00\u07d4X}hI\xb1h\xf6\xc33+z\xba\xe7\xeblB\xc3\u007fH\xbf\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4X\x87\xdcj3\xdf\xedZ\xc1\xed\xef\xe3^\xf9\x1a!b1\xac\x96\x89\r\x8drkqw\xa8\x00\x00\xe0\x94X\x8e\u0650\xa2\xaf\xf4J\x94\x10]X\xc3\x05%w5\xc8h\xac\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d4X\xae-\xdc_L\x8a\u0697\xe0l\x00\x86\x17\x17g\xc4#\xf5\u05c9WG=\x05\u06ba\xe8\x00\x00\u07d4X\xae\xd6gJ\xff\xd9\xf6B3'*W\x8d\xd98k\x99\xc2c\x89\xb8Pz\x82\a( \x00\x00\xe0\x94X\xb8\b\xa6[Q\xe63\x89i\xaf\xb9^\xc7\a5\xe4Q\xd5&\x8a\bxK\xc1\xb9\x83z8\x00\x00\u07d4X\xb8\xae\x8fc\xef5\xed\ab\xf0\xb6#=J\xc1Nd\xb6M\x89lk\x93[\x8b\xbd@\x00\x00\u07d4X\xba\x15ie\x0e[\xbb\xb2\x1d5\xd3\xe1u\xc0\u05b0\xc6Q\xa9\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4X\xc5U\xbc)<\xdb\x16\xc66.\xd9z\xe9U\v\x92\xea\x18\x0e\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4X\xc6P\xce\xd4\v\xb6VA\xb8\xe8\xa9$\xa09\xde\xf4hT\u07c9\x01\x00\xbd3\xfb\x98\xba\x00\x00\u07d4X\xc9\aT\xd2\xf2\n\x1c\xb1\xdd3\x06%\xe0KE\xfaa\x9d\\\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94X\xe2\xf1\x12#\xfc\x827\xf6\x9d\x99\xc6(\x9c\x14\x8c\x06\x04\xf7B\x8a\x05\x15\n\xe8J\x8c\xdf\x00\x00\x00\u07d4X\xe5T\xaf=\x87b\x96 \xdaa\xd58\xc7\xf5\xb4\xb5LJ\xfe\x89FP\x9diE4r\x80\x00\u07d4X\xe5\xc9\xe3D\xc8\x06e\r\xac\xfc\x90M3\xed\xbaQ\a\xb0\u0789\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\u07d4X\xe6a\u043as\xd6\xcf$\t\x9aUb\xb8\b\xf7\xb3g;h\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94X\xf0[&%`P<\xa7a\xc6\x18\x90\xa4\x03_Lsr\x80\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4X\xfb\x94sd\xe7iWe6\x1e\xbb\x1e\x80\x1f\xfb\x8b\x95\xe6\u0409\n\u05ce\xbcZ\xc6 \x00\x00\u07d4Y\x01\x81\xd4E\x00{\u0407Z\xaf\x06\x1c\x8dQ\x159\x00\x83j\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Y\x02\xe4J\xf7i\xa8rF\xa2\x1e\a\x9c\b\xbf6\xb0n\xfe\xb3\x8965\u026d\xc5\u07a0\x00\x00\u07d4Y\n\xcb\xda7)\f\r>\xc8O\xc2\x00\rv\x97\xf9\xa4\xb1]\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94Y\f\xcbY\x11\xcfx\xf6\xf6\"\xf55\xc4t7_J\x12\xcf\u03ca\x04<3\xc1\x93ud\x80\x00\x00\u07d4Y\x10\x10m\xeb\u0491\xa1\u0340\xb0\xfb\xbb\x8d\x8d\x9e\x93\xa7\xcc\x1e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Y\x16\x17I\xfe\xdc\xf1\xc7!\xf2 -\x13\xad\xe2\xab\xcfF\v=\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94Y\x1b\xef1q\xd1\u0155w\x17\xa4\xe9\x8d\x17\xeb\x14,!NV\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4Y <\xc3u\x99\xb6H1*|\xc9\xe0m\xac\xb5\x89\xa9\xaej\x89\b\x0fyq\xb6@\x0e\x80\x00\u07d4Y&\x81q\xb83\xe0\xaa\x13\xc5KR\xcc\xc0B.O\xa0:\ub262\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94Y'w&\x1e;\xd8R\u010e\u0295\xb3\xa4L[\u007f-B,\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4Y0Dg\x0f\xae\xff\x00\xa5[Z\xe0Q\xeb{\xe8p\xb1\x16\x94\x89\a?u\u0460\x85\xba\x00\x00\xe0\x94Y;E\xa1\x86J\xc5\xc7\xe8\xf0\u02ae\xba\r\x87<\xd5\xd1\x13\xb2\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4Y_^\xdajV\xf1N%\xe0\xc6\xf3\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4Z\x1a3ib\xd6\xe0\xc601\u0303\u01a5\u01a6\xf4G\x8e\u02c965\u026d\xc5\u07a0\x00\x00\u07d4Z\x1d--\x1dR\x03\x04\xb6 \x88IW\x047\xeb0\x91\xbb\x9f\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4Z&s1\xfa\xcb&-\xaa\xec\xd9\xddc\xa9p\f_RY\u07c9\x05k\xc7^-c\x10\x00\x00\xe0\x94Z(WU9\x1e\x91NX\x02_\xaaH\xcch_O\xd4\xf5\xb8\x8a\x05\x81v{\xa6\x18\x9c@\x00\x00\u07d4Z)\x16\xb8\xd2\xe8\xcc\x12\xe2\a\xabFMC>#p\xd8#\u0649lk\x93[\x8b\xbd@\x00\x00\u07d4Z+\x1c\x85:\xeb(\xc4U9\xafv\xa0\n\xc2\u0628$(\x96\x89\x01Z\xf1\u05cbX\xc4\x00\x00\u07d4Z-\xaa\xb2\\1\xa6\x1a\x92\xa4\xc8,\x99%\xa1\xd2\xefXX^\x89\f8\r\xa9\u01d5\f\x00\x00\u07d4Z0\xfe\xac7\xac\x9fr\u05f4\xaf\x0f+\xc79R\xc7O\xd5\u00c9lk\x93[\x8b\xbd@\x00\x00\u07d4ZTh\xfa\\\xa2&\xc7S.\xcf\x06\xe1\xbc\x1cE\"]~\u0249g\x8a\x93 b\xe4\x18\x00\x00\u07d4ZVR\x857JI\xee\xddPL\x95}Q\bt\xd0\x04U\xbc\x89\x05k\xc7^-c\x10\x00\x00\u07d4Z^\xe8\xe9\xbb\x0e\x8a\xb2\xfe\xcbK3\u0494x\xbeP\xbb\xd4K\x89*\x11)\u0413g \x00\x00\xe0\x94Z_\x85\b\xda\x0e\xbe\xbb\x90\xbe\x903\xbdM\x9e'A\x05\xae\x00\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4Z`q\xbc\xeb\xfc\xbaJ\xb5\u007fM\xb9o\u01e6\x8b\xec\xe2\xba[\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Z`\xc9$\x16(s\xfc~\xa4\xda\u007f\x97.5\x01g7`1\x89\x04\x87\xf2w\xa8\x85y\x80\x00\u07d4Zf\x86\xb0\xf1~\a\xed\xfcY\xb7Y\xc7}[\xef\x16M8y\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4Zp\x10o \xd6?\x87Re\xe4\x8e\r5\xf0\x0e\x17\xd0+\u0249\x01\x15\x8eF\t\x13\xd0\x00\x00\u0794Zt\xbab\xe7\xc8\x1a4t\xe2}\x89O\xed3\xdd$\xad\x95\xfe\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94Zw5\x00}p\xb0hD\u0699\x01\xcd\xfa\xdb\x11\xa2X,/\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4Z\x82\xf9l\u0537\xe2\xd9=\x10\xf3\x18]\xc8\xf4=Ku\xaai\x89lc?\xba\xb9\x8c\x04\x00\x00\u07d4Z\x87\xf04\xe6\xf6\x8fNt\xff\xe6\fd\x81\x946\x03l\xf7\u05c9\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94Z\x89\x11U\xf5\x0eB\aCt\xc79\xba\xad\xf7\xdf&Q\x15:\x8a\x01\x02\xdao\xd0\xf7:<\x00\x00\u07d4Z\x9c\x8bi\xfcaMiVI\x99\xb0\r\xcbB\xdbg\xf9~\x90\x89\xb9\xe6\x15\xab\xad:w\x80\x00\xe0\x94Z\xaf\x1c1%Jn\x00_\xba\u007fZ\xb0\xecy\xd7\xfc+c\x0e\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4Z\xb1\xa5aSH\x00\x1c|w]\xc7WHf\x9b\x8b\xe4\xde\x14\x89%jr\xfb)\xe6\x9c\x00\x00\xe1\x94Z\xbf\xec%\xf7L\u06047c\x1aw1\x90i2wcV\xf9\x8b\t\xd8<\xc0\u07e1\x11w\xff\x80\x00\u07d4Z\u0090\x8b\x0f9\x8c\r\xf5\xba\xc2\xcb\x13\xcas\x14\xfb\xa8\xfa=\x89\n\xd4\xc81j\v\f\x00\x00\xe0\x94Z\u025a\u05c1j\xe9\x02\x0f\xf8\xad\xf7\x9f\xa9\x86\x9b|\xeaf\x01\x8a\x04ri\x8bA;C \x00\x00\u07d4Z\xd1,^\xd4\xfa\x82~!P\u03e0\u058c\n\xa3{\x17i\xb8\x89+^:\xf1k\x18\x80\x00\x00\xe0\x94Z\xd5\xe4 uV\x13\x88o5\xaaV\xac@>\xeb\xdf\xe4\xb0\u040a\x10\xf0\xcf\x06M\u0552\x00\x00\x00\u07d4Z\xdew\xfd\x81\xc2\\\n\xf7\x13\xb1\a\x02v\x8c\x1e\xb2\xf9u\xe7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4Z\xe6N\x85;\xa0\xa5\x12\x82\u02cd\xb5.Aa^|\x9fs?\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Z\xed\x0el\xfe\x95\xf9\u0580\xc7dr\xa8\x1a+h\n\u007f\x93\xe2\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4Z\xef\x16\xa2&\xddh\a\x1f$\x83\xe1\xdaBY\x83\x19\xf6\x9b,\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Z\xf4j%\xac\t\xcbsakS\xb1O\xb4/\xf0\xa5\x1c\u0772\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4Z\xf7\xc0r\xb2\u016c\xd7\x1cv\xad\xdc\xceS\\\xf7\xf8\xf95\x85\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94Z\xfd\xa9@\\\x8e\x976QEt\u0692\x8d\xe6tV\x01\t\x18\x8a\x01E\xb8\xb0#\x9aF\x92\x00\x00\u07d4[\x06\xd1\xe6\x93\f\x10Ti+y\xe3\xdb\xe6\xec\xceS\x96d \x89\v\"\u007fc\xbe\x81<\x00\x00\u07d4[%\xca\xe8m\xca\xfa*`\xe7r61\xfc_\xa4\x9c\x1a\xd8}\x89\x87\fXQ\x0e\x85 \x00\x00\u07d4[(|~sB\x99\xe7'bo\x93\xfb\x11\x87\xa6\rPW\xfe\x89\x05|\xd94\xa9\x14\xcb\x00\x00\u07d4[)\f\x01\x96|\x81.M\xc4\xc9\v\x17L\x1b@\x15\xba\xe7\x1e\x89\b \xeb4\x8dR\xb9\x00\x00\u07d4[+d\xe9\xc0X\u30a8\xb2\x99\"N\xec\xaa\x16\xe0\x9c\x8d\x92\x89\b\xbaR\xe6\xfcE\xe4\x00\x00\xe0\x94[./\x16\x18U.\xab\r\xb9\x8a\xddUc|)Q\xf1\xfb\x19\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4[0`\x8cg\x8e\x1a\xc4d\xa8\x99L;3\xe5\xcd\xf3Iq\x12\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4[36\x96\xe0L\xca\x16\x92\xe7\x19\x86W\x9c\x92\rk)\x16\xf9\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94[C\rw\x96\x96\xa3e?\xc6\x0et\xfb\u02ec\xf6\xb9\u00ba\xf1\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4[Cse\xae:\x9a/\xf9|h\xe6\xf9\nv \x18\x8c}\x19\x89l\x87T\xc8\xf3\f\b\x00\x00\u07d4[I\xaf\xcduDx8\xf6\xe7\xce\u068d!w}O\xc1\xc3\xc0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4[L\f`\xf1\x0e\u0489K\xdbB\xd9\xdd\x1d!\x05\x87\x81\n\r\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4[N\xa1m\xb6\x80\x9b\x03R\u0536\xe8\x1c9\x13\xf7jQ\xbb2\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4[[\xe0\xd8\xc6rv\xba\xab\xd8\xed\xb3\rH\xeaud\v\x8b)\x89,\xb1\xf5_\xb7\xbe\x10\x00\x00\u07d4[]Qp)2\x15b\x11\x1bC\bm\v\x045\x91\x10\x9ap\x89\x8c\xf2?\x90\x9c\x0f\xa0\x00\x00\xe0\x94[]\x8c\x8e\xedl\x85\xac!Va\xde\x02fv\x82?\xaa\n\f\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4[mU\xf6q)g@\\e\x91)\xf4\xb1\xde\t\xac\xf2\xcb{\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4[p\u011c\u024b=\xf3\xfb\xe2\xb1Y\u007f\\\x1bcG\xa3\x88\xb7\x894\x95tD\xb8@\xe8\x00\x00\u07d4[sn\xb1\x83Sb\x9b\u0796v\xda\xdd\x16P4\xce^\xcch\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4[u\x9f\xa1\x10\xa3\x1c\x88F\x9fT\xd4K\xa3\x03\xd5}\xd3\xe1\x0f\x89[F\xdd/\x0e\xa3\xb8\x00\x00\u07d4[w\x84\xca\xea\x01y\x9c\xa3\x02'\x82vg\xce |\\\xbcv\x89lk\x93[\x8b\xbd@\x00\x00\u07d4[x\xec\xa2\u007f\xbd\xeao&\xbe\xfb\xa8\x97+)^x\x146K\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94[\x80\v\xfd\x1b>\u0525}\x87Z\xed&\xd4/\x1aw\b\xd7*\x8a\x01Z\x82\xd1\u057b\x88\xe0\x00\x00\u07d4[\x85\xe6\x0e*\xf0TO/\x01\xc6N 2\x90\x0e\xbd8\xa3\u01c9lk\x93[\x8b\xbd@\x00\x00\u07d4[\xa2\xc6\xc3]\xfa\xec)h&Y\x19\x04\xd5DFJ\xea\xbd^\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94[\xafmt\x96 \x80>\x83H\xaf7\x10\xe5\xc4\xfb\xf2\x0f\u0214\x8a\x01\x0f@\x02a]\xfe\x90\x00\x00\u07d4[\xc1\xf9U\a\xb1\x01\x86B\xe4\\\xd9\xc0\xe2'3\xb9\xb1\xa3&\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94[\xd25GG\u007fm\t\u05f2\xa0\x05\xc5\xeee\fQ\fV\u05ca\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4[\xd2J\xac6\x12\xb2\f`\x9e\xb4gy\xbf\x95i\x84\a\xc5|\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4[\u0586-Q}M\xe4U\x9dN\xec\n\x06\xca\xd0^/\x94n\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4[\xe0EQ*\x02n?\x1c\xeb\xfdZ~\xc0\xcf\xc3o-\xc1k\x89\x06\x81U\xa46v\xe0\x00\x00\xe0\x94[\xf9\xf2\"nZ\xea\xcf\x1d\x80\xae\nY\xc6\xe3\x808\xbc\x8d\xb5\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4[\xfa\xfe\x97\xb1\xdd\x1dq+\xe8mA\xdfy\x89SE\x87Z\x87\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\\\x0f.Q7\x8fk\r{\xabas1X\vn9\xad<\xa5\x8a\x02\bj\xc3Q\x05&\x00\x00\x00\u07d4\\)\xf9\xe9\xa5#\xc1\xf8f\x94H\xb5\\H\xcb\xd4|%\xe6\x10\x894F\xa0\xda\xd0L\xb0\x00\x00\xe0\x94\\0\x8b\xacHW\xd3;\xae\xa0t\xf3\x95m6!\xd9\xfa(\xe1\x8a\x01\x0f\b\xed\xa8\xe5U\t\x80\x00\u07d4\\1*V\u01c4\xb1\"\t\x9bvM\x05\x9c!\xec\xe9^\x84\u0289\x05&c\u032b\x1e\x1c\x00\x00\u07d4\\1\x99m\xca\xc0\x15\xf9\xbe\x98[a\x1fF\x870\xef$M\x90\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\\24W\xe1\x87v\x1a\x82v\xe3Y\xb7\xb7\xaf?;n=\xf6\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\\<\x1cd[\x91uC\x11;>l\x1c\x05M\xa1\xfet+\x9a\x89+^:\xf1k\x18\x80\x00\x00\u0794\\=\x19D\x1d\x19l\xb4Cf \xfc\xad\u007f\xbby\xb2\x9ex\x88\xc6s\xce<@\x16\x00\x00\u07d4\\?V\u007f\xaf\xf7\xba\u0475\x12\x00\"\xe8\xcb\u02a8+I\x17\xb3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\\Ch\x91\x8a\xced\t\u01de\u0280\u036a\xe49\x1d+bN\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\\FA\x97y\x1c\x8a=\xa3\xc9%Co'z\xb1;\xf2\xfa\xa2\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\\H\x81\x16\\\xb4+\xb8.\x979l\x8e\xf4J\xdb\xf1s\xfb\x99\x89\x05\xfe\xe2\"\x04\x1e4\x00\x00\xe0\x94\\H\x92\x90z\a \xdfo\xd3A>c\xffv}k9\x80#\x8a\x02\xcb\x00\x9f\u04f5y\x0f\x80\x00\u07d4\\O$\xe9\x94\ud3c5\x0e\xa7\x81\x8fG\x1c\x8f\xac;\xcf\x04R\x89]\x80h\x8d\x9e1\xc0\x00\x00\u07d4\\T\x19V\\:\xadNqN\a92\x8e5!\u024f\x05\u0309\x1c\x9fx\u0489>@\x00\x00\u07d4\\a6\xe2\x18\xde\na\xa17\xb2\xb3\x96-*a\x12\xb8\t\u05c9\x0f\xf3\u06f6_\xf4\x86\x80\x00\xe0\x94\\a\xaby\xb4\b\xdd2)\xf6bY7\x05\xd7/\x1e\x14{\xb8\x8a\x04\xd0$=4\x98\u0344\x00\x00\u07d4\\m\x04\x1d\xa7\xafD\x87\xb9\xdcH\xe8\xe1\xf6\af\u0425m\xbc\x89O\a\n\x00>\x9ct\x00\x00\u07d4\\o6\xaf\x90\xab\x1aeln\xc8\xc7\xd5!Q'b\xbb\xa3\xe1\x89lh\xcc\u041b\x02,\x00\x00\u07d4\\{\x9e\u01e2C\x8d\x1eD*\x86\x0f\x8a\x02\x1e\x18\x99\xf07z\xea\x00\x00\u07d4\\\xcc\xf1P\x8b\xfd5\xc2\x050\xaad%\x00\xc1\r\xeee\xea\xed\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\u07d4\\\xcer\xd0h\xc7\xc3\xf5[\x1d(\x19T^w1|\xae\x82@\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\\\xd0\xe4u\xb5D!\xbd\xfc\f\x12\xea\x8e\b+\u05e5\xaf\nj\x89\x032\xca\x1bg\x94\f\x00\x00\u07d4\\\u0548\xa1N\xc6H\xcc\xf6G)\xf9\x16z\xa7\xbf\x8b\xe6\xeb=\x8965\u026d\xc5\u07a0\x00\x00\u07d4\\\u062f`\xdee\xf2M\xc3\xceW0\xba\x92e0\"\xdcYc\x89a\t=|,m8\x00\x00\u07d4\\\xdcG\b\xf1O@\xdc\xc1Zy_}\xc8\xcb\v\u007f\xaa\x9en\x89\x1d\x1c_>\xda \xc4\x00\x00\u07d4\\\u0d86,\u0391b\xe8~\bI\xe3\x87\xcb]\xf4\xf9\x11\x8c\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\xe0\x94\\\xe2\xe7\u03aa\xa1\x8a\xf0\xf8\xaa\xfa\u007f\xba\xd7L\u021e<\xd46\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\\\xe4@h\xb8\xf4\xa3\xfey\x9ej\x83\x11\xdb\xfd\xed\xa2\x9d\xee\x0e\x89lk\x93[\x8b\xbd@\x00\x00\u0794\\\xeb\xe3\v*\x95\xf4\xae\xfd\xa6ee\x1d\xc0\xcf~\xf5u\x81\x99\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\\\xf1\x8f\xa7\u0227\xc0\xa2\xb3\xd5\xef\u0459\x0fd\xdd\xc5i$,\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\\\xf4N\x10T\reqd#\xb1\xbc\xb5B\xd2\x1f\xf8:\x94\u034a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\\\xf8\xc0>\xb3\xe8r\xe5\x0f|\xfd\f/\x8d;?,\xb5\x18:\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\\\xfa\x8dV\x85ue\x8c\xa4\xc1\xa5\x93\xacL]\x0eD\xc6\aE\x89\x0f\xc6o\xae7F\xac\x00\x00\u07d4\\\xfa\x98w\xf7\x19\u01dd\x9eIJ\b\xd1\xe4\x1c\xf1\x03\xfc\x87\u0249\n\u05ce\xbcZ\xc6 \x00\x00\u07d4]\x1d\xc38{G\xb8E\x1eU\x10l\f\xc6}m\xc7+\u007f\v\x89lk\x93[\x8b\xbd@\x00\x00\u07d4]#\x1ap\xc1\xdf\xeb6\n\xbd\x97\xf6\x16\xe2\xd1\r9\xf3\u02b5\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4]$\xbd\xbc\x1cG\xf0\xeb\x83\xd1(\xca\xe4\x8a\xc3\xf4\xb5\x02bt\a\xda'/g\x81Jk\xec\u0509\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4]\x83\xb2\x1b\xd2q#`Ckg\xa5\x97\xee3x\xdb>z\xe4\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94]\x87+\x12.\x99N\xf2|q\xd7\u07b4W\xbfeB\x9e\xcal\x8a\x01\xb1\xad\xed\x81\u04d4\x10\x80\x00\xe0\x94]\x8d1\xfa\xa8d\xe2!Y\xcdoQu\xcc\xec\xc5?\xa5Mr\x8a\x05\xb6\x96\xb7\r\xd5g\x10\x00\x00\xe0\x94]\x95\x8a\x9b\u0449\u0098_\x86\u014a\x8ci\xa7\xa7\x88\x06\xe8\u068a\x02(\xf1o\x86\x15x`\x00\x00\u07d4]\xa2\xa9\xa4\xc2\xc0\xa4\xa9$\xcb\xe0\xa5:\xb9\xd0\xc6'\xa1\u03e0\x89'\xbf8\xc6TM\xf5\x00\x00\u07d4]\xa4\u0288\x93\\'\xf5\\1\x10H\x84\x0eX\x9e\x04\xa8\xa0I\x89\x04V9\x18$O@\x00\x00\u07d4]\xa5G\x85\u027d0W\\\x89\u07b5\x9d A\xd2\n9\xe1{\x89j\xa2\t\xf0\xb9\x1de\x80\x00\xe0\x94]\xb6\x9f\xe9>o\xb6\xfb\xd4P\x96k\x97#\x8b\x11\n\xd8'\x9a\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4]\xb7\xbb\xa1\xf9W?$\x11]\x8c\x8cb\xe9\u0388\x95\x06\x8e\x9f\x89\x02\xb5\xaa\xd7,e \x00\x00\xe0\x94]\xb8D\x00W\x00i\xa9W<\xab\x04\xb4\u6d955\xe2\x02\xb8\x8a\x02\r\u058a\xaf2\x89\x10\x00\x00\u07d4]\xc3m\xe55\x94P\xa1\xec\t\xcb\fD\xcf+\xb4+:\xe45\x89<\x94m\x89;3\x06\x00\x00\u07d4]\xc6\xf4_\xef&\xb0n3\x021?\x88M\xafH\xe2to\xb9\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94]\u0376\xb8zP\xa9\xde\x02C\x80\x00\x00\u07d4^Q\xb8\xa3\xbb\t\xd3\x03\xea|\x86\x05\x15\x82\xfd`\x0f\xb3\xdc\x1a\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u0794^X\xe2U\xfc\x19\x87\n\x040_\xf2\xa0F1\xf2\xff)K\xb1\x88\xf4?\xc2\xc0N\xe0\x00\x00\u07d4^ZD\x19t\xa8=t\u0187\xeb\xdcc?\xb1\xa4\x9e{\x1a\u05c9\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4^eE\x8b\xe9d\xaeD\x9fqw7\x04\x97\x97f\xf8\x89\x87a\x89\x1c\xa7\xccs[o|\x00\x00\u07d4^g\u07c9i\x10\x1a\u06bd\x91\xac\xcdk\xb1\x99\x12t\xaf\x8d\xf2\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4^n\x97G\xe1b\xf8\xb4\\en\x0fl\xaez\x84\xba\xc8\x0eN\x89lk\x93[\x8b\xbd@\x00\x00\u07d4^s\x1bU\xce\xd4R\xbb??\xe8q\xdd\xc3\xed~\xe6Q\n\x8f\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4^t\xed\x80\xe9eW\x88\xe1\xbb&\x97R1\x96g\xfeuNZ\x89\x03\t'\xf7L\x9d\xe0\x00\x00\u07d4^w.'\xf2\x88\x00\xc5\r\u0697;\xb3>\x10v.n\xea \x89a\t=|,m8\x00\x00\u07d4^{\x8cT\xdcW\xb0@ bq\x9d\xee~\xf5\xe3~\xa3]b\x89\x9b\xf9\x81\x0f\xd0\\\x84\x00\x00\u07d4^\u007fp7\x87uX\x9f\xc6j\x81\xd3\xf6S\xe9T\xf5U`\ub243\xf2\x89\x18\x1d\x84\xc8\x00\x00\xe0\x94^\x80n\x84W0\xf8\a>l\xc9\x01\x8e\xe9\x0f\\\x05\xf9\t\xa3\x8a\x02\x01\xe9m\xac\u03af \x00\x00\u07d4^\x8eM\xf1\x8c\xf0\xafw\tx\xa8\u07cd\xac\x90\x93\x15\x10\xa6y\x89lk\x93[\x8b\xbd@\x00\x00\u07d4^\x90\xc8Xw\x19\x87V\xb06l\x0e\x17\xb2\x8eR\xb4FPZ\x89\x14JJ\x18\xef\xebh\x00\x00\u07d4^\x95\xfe_\xfc\xf9\x98\xf9\xf9\xac\x0e\x9a\x81\u06b8>\xadw\x00=\x89\x1dB\xc2\r2y\u007f\x00\x00\u07d4^\xad)\x03z\x12\x89dx\xb1)j\xb7\x14\xe9\u02d5B\x8c\x81\x89\x03\xe0C\a-@n\x00\x00\u07d4^\xb3q\xc4\a@lB{;}\xe2q\xad<\x1e\x04&\x95y\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4^\u037a\xea\xb9\x10o\xfe]{Q\x96\x96`\x9a\x05\xba\ub16d\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4^\xd0\xd63\x85Y\xefD\xdcza\xed\xeb\x89?\xa5\xd8?\xa1\xb5\x89\v\xed\x1d\x02c\xd9\xf0\x00\x00\xe0\x94^\u04fb\xc0R@\xe0\u04d9\xebm\xdf\xe6\x0fb\xdeM\x95\t\xaf\x8a)\x14\xc0$u\xf9\xd6\xd3\x00\x00\u0594^\xd3\xf1\xeb\xe2\xaegV\xb5\xd8\xdc\x19\xca\xd0,A\x9a\xa5w\x8b\x80\u07d4^\xd5a\x15\xbde\x05\xa8\x82s\xdf\\V\x83\x94p\xd2J-\xb7\x89\x03\x8ee\x91\xeeVf\x80\x00\xe0\x94^\xf8\xc9a\x86\xb3y\x84\xcb\xfe\x04\u0158@n;\n\xc3\x17\x1f\x8a\x01\xfd\x934\x94\xaa_\xe0\x00\x00\u07d4^\xfb\xdf\xe58\x99\x99c<&`Z[\xfc,\x1b\xb5\x95\x93\x93\x89\x03\xc0W\xc9\\\xd9\b\x00\x00\xe0\x94_\x13\x15F1Fm\xcb\x13S\u0210\x93*|\x97\xe0\x87\x8e\x90\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4_\x16z\xa2B\xbcL\x18\x9a\xde\xcb:\u0127\xc4R\xcf\x19/\u03c9lkLM\xa6\u077e\x00\x00\xe0\x94_\x1c\x8a\x04\xc9\rs[\x8a\x15)\t\xae\xaeco\xb0\xce\x16e\x8a\x01{x'a\x8cZ7\x00\x00\u07d4_#\xba\x1f7\xa9lE\xbcI\x02YS\x8aT\u008b\xa3\xb0\u0549A\rXj \xa4\xc0\x00\x00\u07d4_&\xcf4Y\x9b\xc3n\xa6{\x9ez\x9f\x9bC0\xc9\xd5B\xa3\x8965\u026d\xc5\u07a0\x00\x00\u07d4_)\xc9\xdev]\xde%\x85*\xf0}3\xf2\xceF\x8f\xd2\t\x82\x89lk\x93[\x8b\xbd@\x00\x00\u07d4_/\a\xd2\u0597\xe8\xc5g\xfc\xfd\xfe\x02\x0fI\xf3`\xbe!9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4_2\x1b=\xaa\xa2\x96\xca\xdf)C\x9f\x9d\xab\x06*K\xff\xed\u0589\x04p%\x90>\xa7\xae\x00\x00\u07d4_3:;#\x10vZ\r\x182\xb9\xbeL\n\x03pL\x1c\t\x8965\u026d\xc5\u07a0\x00\x00\u07d4_4K\x01\xc7\x19\x1a2\xd0v*\xc1\x88\xf0\xec-\xd4`\x91\x1d\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94_6>\n\xb7G\xe0-\x1b;f\xab\xb6\x9e\xa5<{\xafR:\x8a\x02w\x01s8\xa3\n\xe0\x00\x00\u07d4_7[\x86`\f@\u0328\xb2gkz\x1a\x1d\x16D\xc5\xf5,\x89\x04F\x18\xd7Lb?\x00\x00\u07d4_>\x1eg9\xb0\xc6\"\x00\xe0\n\x006\x91\xd9\xef\xb28\u061f\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4_H?\xfb\x8fh\n\xed\xf2\xa3\x8fx3\xaf\xdc\xdeY\xb6\x1eK\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94_J\xceL\x1c\xc13\x91\xe0\x1f\x00\xb1\x98\xe1\xf2\v_\x91\xcb\xf5\x8a\x01\x0f\x0f\xa8\xb9\u04c1\x1a\x00\x00\xe0\x94_R\x12\x82\xe9\xb2x\u070c\x03Lr\xafS\xee)\xe5D=x\x8a\x01as-/\x8f:\xe0\x00\x00\u07d4_h\xa2L~\xb4\x11vgs{39?\xb3\xc2\x14\x8aS\xb6\x89\x02\xce\u0791\x8dE<\x00\x00\u07d4_p\x8e\xaf9\xd8#\x94lQ\xb3\xa3\u9df3\xc0\x03\xe2cA\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4_t.H~:\xb8\x1a\xf2\xf9J\xfd\xbe\x1b\x9b\x8f\\\u0301\xbc\x89u\xc4E\xd4\x11c\xe6\x00\x00\u07d4_t\xed\x0e$\xff\x80\u0672\u0124K\xaa\x99uB\x8c\u05b95\x89\xa1\x8b\xce\xc3H\x88\x10\x00\x00\u07d4_v\xf0\xa3\x06&\x9cx0k=e\r\xc3\xe9\xc3p\x84\xdba\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4_w\xa1\a\xab\x12&\xb3\xf9_\x10\ue0ee\xfcl]\xff>\u0709\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4_{;\xba\xc1m\xab\x83\x1aJ\x0f\xc5;\fT\x9d\xc3l1\u0289i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94_\x93\xff\x83't\xdbQ\x14\xc5[\xb4\xbfD\xcc\U000f53d0?\x8a(\xa9\xc9\x1a&4X)\x00\x00\u07d4_\x96\x16\xc4{Jg\xf4\x06\xb9Z\x14\xfeo\xc2h9o\x17!\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4_\x98\x109\xfc\xf5\x02%\xe2\xad\xf7bu!\x12\xd1\xcc&\xb6\xe3\x89\x1b\x1aAj!S\xa5\x00\x00\u07d4_\x99\u070eI\xe6\x1dW\xda\xef`j\xcd\xd9\x1bMp\a2j\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4_\xa6\x1f\x15-\xe6\x125\x16\xc7Q$)y(_yj\u01d1\x89\v\x0f\x11\x97)c\xb0\x00\x00\u07d4_\xa7\xbf\xe0C\x88a'\xd4\x01\x1d\x83V\xa4~\x94yc\xac\xa8\x89b\xa9\x92\xe5:\n\xf0\x00\x00\xe0\x94_\xa8\xa5Nh\x17lO\xe2\xc0\x1c\xf6q\xc5\x15\xbf\xbd\xd5(\xa8\x8aE\xe1U\xfa\x01\x10\xfa@\x00\x00\u07d4_\xad\x96\x0fk,\x84V\x9c\x9fMG\xbf\x19\x85\xfc\xb2\xc6]\xa6\x8965f3\xeb\xd8\xea\x00\x00\u07d4_\xc6\xc1\x14&\xb4\xa1\xea\xe7\xe5\x1d\xd5\x12\xad\x10\x90\xc6\xf1\xa8[\x89\x93\xfe\\W\xd7\x10h\x00\x00\u07d4_\u0344Th\x96\xdd\b\x1d\xb1\xa3 \xbdM\x8c\x1d\xd1R\x8cL\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4_\u0368G\xaa\xf8\xd7\xfa\x8b\xca\b\x02\x9c\xa2\x84\x91f\xaa\x15\xa3\x89!\u02b8\x12Y\xa3\xbf\x00\x00\u07d4_\xd1\xc3\xe3\x17x'l\xb4.\xa7@\xf5\xea\xe9\xc6A\xdb\xc7\x01\x89\n\x84Jt$\xd9\xc8\x00\x00\u07d4_\xd3\xd6w~\xc2b\n\xe8:\x05R\x8e\xd4%\a-<\xa8\xfd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4_\xd9s\xaf6j\xa5\x15|Te\x9b\u03f2|\xbf\xa5\xac\x15\u0589\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4_\xe7w\x03\x80\x8f\x82>l9\x93R\x10\x8b\xdb,R|\xb8|\x89j@v\xcfy\x95\xa0\x00\x00\xe0\x94_\xecI\xc6e\xe6N\xe8\x9d\xd4A\xeet\x05n\x1f\x01\xe9(p\x8a\x01V\x9b\x9es4t\xc0\x00\x00\u07d4_\xf3&\xcd`\xfd\x13k$^)\xe9\bzj\u04e6R\u007f\r\x89e\xea=\xb7UF`\x00\x00\u07d4_\xf9=\xe6\xee\x05L\xadE\x9b-^\xb0\xf6\x87\x03\x89\xdf\xcbt\x89\v\xed\x1d\x02c\xd9\xf0\x00\x00\u07d4`\x06\xe3m\x92\x9b\xf4]\x8f\x16#\x1b\x12j\x01\x1a\xe2\x83\xd9%\x89\t\x8a}\x9b\x83\x14\xc0\x00\x00\u07d4`!\xe8Z\x88\x14\xfc\xe1\xe8*A\xab\xd1\u04f2\xda\xd2\xfa\xef\xe0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4`8t\n\xe2\x8df\xba\x93\xb0\xbe\bH+2\x05\xa0\xf7\xa0{\x89\x11!a\x85\u009fp\x00\x00\u07d4`?/\xabz\xfbn\x01{\x94v`i\xa4\xb4;8\x96I#\x89Y\xd2\xdb$\x14\u0699\x00\x00\u07d4`B'm\xf2\x98?\xe2\xbcGY\xdc\x19C\xe1\x8f\xdb\xc3Ow\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4`B\xc6D\xba\xe2\xb9o%\xf9M1\xf6x\xc9\r\xc9f\x90\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4`L\xdf\x18b\x8d\xbf\xa82\x91\x94\xd4x\xddR\x01\xee\xccK\xe7\x89\x01?0j$\t\xfc\x00\x00\u07d4`N\x94w\xeb\xf4r|t[\u02bb\xed\xcbl\xcf)\x99@\"\x8966\x9e\xd7t}&\x00\x00\u07d4`gm\x1f\xa2\x1f\xca\x05\"\x97\xe2K\xf9c\x89\u0171*p\u05c9\r\x17|Zzh\xd6\x00\x00\u07d4`gn\x92\u044b\x00\x05\t\xc6\x1d\xe5@\xe6\xc5\u0776v\xd5\t\x89A\rXj \xa4\xc0\x00\x00\u07d4`o\x17q!\xf7\x85\\!\xa5\x06#0\xc8v\"d\xa9{1\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4`\x86B6\x93\r\x04\xd8@+]\xcb\xeb\x80\u007f<\xafa\x1e\xa2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4`\xabq\xcd&\xeamnY\xa7\xa0\xf6'\xee\a\x9c\x88^\xbb\xf6\x89\x01s\x17\x90SM\xf2\x00\x00\u07d4`\xaf\x0e\xe1\x18D<\x9b7\xd2\xfe\xadw\xf5\xe5!\u07be\x15s\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4`\xb3X\xcb=\xbe\xfa7\xf4}\xf2\xd76X@\u068e;\u024c\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4`\xb8\u05b7;ySO\xb0\x8b\xb8\xcb\xce\xfa\xc7\xf3\x93\xc5{\xfe\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4`\xbeo\x95?*M%\xb6%o\xfd$#\xac\x148%.N\x89\b!\xab\rD\x14\x98\x00\x00\u0794`\xc3qO\xdd\xdbcFY\u48b1\xeaB\xc4r\x8c\u01f8\xba\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4`\xcc=D^\xbd\xf7j}z\xe5q\u0197\x1d\xffh\u0305\x85\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94`\xd5fq@\xd1&\x14\xb2\x1c\x8e^\x8a3\b.2\xdf\xcf#\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4`\xde\"\xa1Pt2\xa4{\x01\xcch\xc5*\v\xf8\xa2\xe0\u0418\x89\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\u07d4`\xe0\xbd\u0422Y\xbb\x9c\xb0\x9d?7\xe5\u034b\x9d\xac\uafca\x89JD\x91\xbdm\xcd(\x00\x00\u07d4`\xe3\xccC\xbc\xdb\x02j\xadu\x9cpf\xf5U\xbb\xf2\xacf\xf5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4a\x04+\x80\xfd`\x95\u0478{\xe2\xf0\x0f\x10\x9f\xab\xaf\xd1W\xa6\x89\x05k\xc7^-c\x10\x00\x00\u07d4a\a\xd7\x1d\xd6\xd0\xee\xfb\x11\xd4\xc9\x16@L\xb9\x8cu>\x11}\x89lk\x93[\x8b\xbd@\x00\x00\u07d4a\x0f\xd6\xeeN\xeb\xab\x10\xa8\xc5]\vK\xd2\xe7\xd6\xef\x81qV\x89\x01\x15\x95a\x06]]\x00\x00\u07d4a\x14\xb0\xea\xe5Wi\x03\xf8\v\xfb\x98\x84-$\xed\x92#\u007f\x1e\x89\x05k\xc7^-c\x10\x00\x00\u07d4a!\xaf9\x8a[-\xa6\x9fe\xc68\x1a\xec\x88\u039c\xc6D\x1f\x89\"\xb1\xc8\xc1\"z\x00\x00\x00\u07d4a&g\xf1r\x13[\x95\v,\xd1\xde\x10\xaf\xde\xcehW\xb8s\x8965\u026d\xc5\u07a0\x00\x00\u07d4a,\xed\x8d\xc0\u071e\x89\x9e\xe4oyb33\x15\xf3\xf5^D\x89\x12^5\xf9\xcd=\x9b\x00\x00\u07d4a4\xd9B\xf07\xf2\xcc=BJ#\f`=g\xab\xd3\xed\xf7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4a:\xc5;\xe5e\xd4e6\xb8 q[\x9b\x8d:\xe6\x8aK\x95\x89\xcb\xd4{n\xaa\x8c\xc0\x00\x00\u07d4a?\xabD\xb1k\xbeUMD\xaf\xd1x\xab\x1d\x02\xf3z\ua949lk\x93[\x8b\xbd@\x00\x00\u07d4aN\x8b\xef=\xd2\u015bY\xa4\x14Vt@\x10\x185\x18\x84\xea\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4aQ\x84d\xfd\u0637<\x1b\xb6\xacm\xb6\x00eI8\xdb\xf1z\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4aT}7nSi\xbc\xf9x\xfc\x16,1\xc9\b\"3\xb8%\xd0%\xbe?{\x10V\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4a\x91\xdd\u0276J\x8e\b\x90\xb427\t\u05e0|H\xb9*d\x89*\x03I\x19\u07ff\xbc\x00\x00\u07d4a\x96\xc3\xd3\xc0\x90\x8d%Cf\xb7\xbc\xa5WE\"-\x9dM\xb1\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4a\x9f\x17\x14E\xd4+\x02\xe2\xe0p\x04\xad\x8a\xfeiO\xa5=j\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4a\xad\xf5\x92\x9a^)\x81hN\xa2C\xba\xa0\x1f}\x1f^\x14\x8a\x89\x05\xfa\xbfl\x98O#\x00\x00\u07d4a\xb1\xb8\xc0\x12\xcdLx\xf6\x98\xe4p\xf9\x02V\xe6\xa3\x0fH\u0749\n\u05ce\xbcZ\xc6 \x00\x00\u07d4a\xb3\xdf.\x9e\x9f\xd9h\x13\x1f\x1e\x88\xf0\xa0\xeb[\xd7eFM\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4a\xb9\x02\u0166s\x88X&\x82\r\x1f\xe1EI\xe4\x86_\xbd\u0089\x12$\xef\xed*\u1440\x00\u07d4a\xb9\x05\xdef?\xc1s\x86R;:(\xe2\xf7\xd07\xa6U\u0349\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4a\xba\x87\xc7~\x9bYm\xe7\xba\x0e2o\xdd\xfe\xec!c\xeff\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4a\xbf\x84\u056b\x02oX\xc8s\xf8o\xf0\xdf\u0282\xb5W3\xae\x89lk\x93[\x8b\xbd@\x00\x00\u07d4a\xc4\xee|\x86LMk^7\xea\x131\xc2\x03s\x9e\x82k/\x89\x01\xa15;8*\x91\x80\x00\u07d4a\xc80\xf1eG\x18\xf0u\u032b\xa3\x16\xfa\xac\xb8[}\x12\v\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4a\xc8\xf1\xfaC\xbf\x84i\x99\xec\xf4{+2M\xfbkc\xfe:\x89+^:\xf1k\x18\x80\x00\x00\u07d4a\xc9\xdc\u8c98\x1c\xb4\x0e\x98\xb0@+\xc3\xeb(4\x8f\x03\xac\x89\n\xac\xac\u0679\xe2+\x00\x00\u07d4a\u03a7\x1f\xa4d\xd6*\a\x06?\x92\v\f\xc9\x17S\x973\u0609Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4a\xd1\x01\xa03\xee\x0e.\xbb1\x00\xed\xe7f\xdf\x1a\xd0$IT\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4a\xedU\x96\u0197 \u007f=U\xb2\xa5\x1a\xa7\xd5\x0f\a\xfa\t\xe8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4a\xff\x8eg\xb3M\x9e\xe6\xf7\x8e\xb3o\xfe\xa1\xb9\xf7\xc1W\x87\xaf\x89X\xe7\x92n\xe8X\xa0\x00\x00\u07d4b\x05\xc2\xd5dtp\x84\x8a8@\xf3\x88~\x9b\x01]4u\\\x89a\x94\x04\x9f0\xf7 \x00\x00\u07d4b(\xad\xe9^\x8b\xb1}\x1a\xe2;\xfb\x05\x18AMI~\x0e\xb8\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94b)\xdc\xc2\x03\xb1\xed\xcc\xfd\xf0n\x87\x91\fE*\x1fMzr\x8a\x06\xe1\xd4\x1a\x8f\x9e\xc3P\x00\x00\u0794b+\xe4\xb4T\x95\xfc\xd91C\xef\xc4\x12\u0599\xd6\xcd\xc2=\u0148\xf0\x15\xf2W6B\x00\x00\u07d4b3\x1d\xf2\xa3\xcb\xee5 \xe9\x11\u07a9\xf7>\x90_\x89%\x05\x89lk\x93[\x8b\xbd@\x00\x00\u07d4bVD\xc9Z\x87>\xf8\xc0l\u06de\x9fm\x8dv\x80\x04=b\x89a\x94\x04\x9f0\xf7 \x00\x00\u07d4be\xb2\xe7s\x0f6\xb7v\xb5-\f\x9d\x02\xad\xa5]\x8e<\xb6\x8965\u026d\xc5\u07a0\x00\x00\u07d4bh\n\x15\xf8\u0338\xbd\xc0/s`\xc2Z\xd8\u03f5{\x8c\u034965\u026d\xc5\u07a0\x00\x00\u07d4b\x94\xea\xe6\xe4 \xa3\xd5`\n9\xc4\x14\x1f\x83\x8f\xf8\xe7\xccH\x89\xa00\xdc\xeb\xbd/L\x00\x00\u07d4b\x97\x1b\xf2cL\xee\v\xe3\u0249\x0fQ\xa5`\x99\u06f9Q\x9b\x89#\x8f\xd4,\\\xf0@\x00\x00\u07d4b\x9b\xe7\xab\x12jS\x98\xed\xd6\u069f\x18D~x\u0192\xa4\xfd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4b\xb4\xa9\"nah\a\x1el\xbea\x11\xfe\xf0\xbcc\x8a\x03\xba\x19\x10\xbf4\x1b\x00\x00\x00\xe0\x94c\n\x91:\x901\xc9I*\xbdLA\u06f1PT\xcf\xecD\x16\x8a\x014X\xdbg\xaf5\xe0\x00\x00\xe0\x94c\fRs\x12mQ|\xe6q\x01\x81\x1c\xab\x16\xb8SL\xf9\xa8\x8a\x01\xfe\xcc\xc6%s\xbb\u04c0\x00\u07d4c\x100\xa5\xb2{\a(\x8aEio\x18\x9e\x11\x14\xf1*\x81\xc0\x89\x1b\x1azB\v\xa0\r\x00\x00\u07d4c\x10\xb0 \xfd\x98\x04IW\x99P\x92\t\x0f\x17\xf0NR\xcd\xfd\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4c+\x91I\xd7\x01x\xa7364'^\x82\u0555?'\x96{\x89%\xf2s\x93=\xb5p\x00\x00\u07d4c,\xec\xb1\f\xfc\xf3\x8e\u0246\xb4;\x87p\xad\xec\xe9 \x02!\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4c1\x02\x8c\xbbZ!H[\xc5\x1bVQB\x99;\xdb%\x82\xa9\x89\x1c\xfd\xd7F\x82\x16\xe8\x00\x00\u07d4c3O\xcf\x17E\x84\x0eK\tJ;\xb4\v\xb7o\x96\x04\xc0L\x89\u05e5\xd7\x03\xa7\x17\xe8\x00\x00\u07d4c4\nWqk\xfac\xebl\xd13r\x12\x02W[\xf7\x96\xf0\x89\va\xe0\xa2\f\x12q\x80\x00\u07d4cN\xfc$7\x11\a\xb4\xcb\xf0?y\xa9=\xfd\x93\xe41\xd5\xfd\x89B5\x82\xe0\x8e\xdc\\\x80\x00\xe0\x94c\\\x00\xfd\xf05\xbc\xa1_\xa3a\r\xf38N\x0f\xb7\x90h\xb1\x8a\x01\xe7\xe4\x17\x1b\xf4\u04e0\x00\x00\u07d4ca.xb\xc2{X|\xfbm\xaf\x99\x12\xcb\x05\x1f\x03\n\x9f\x89\x02[\x19\u053f\xe8\xed\x00\x00\u07d4cfgU\xbdA\xb5\x98i\x97x<\x13\x040\b$+<\xb5\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4c{\xe7\x1b:\xa8\x15\xffE=VB\xf70tE\vd\xc8*\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94c}g\xd8\u007fXo\nZG\x9e \xee\x13\xea1\n\x10\xb6G\x8a\n:Y&\xaf\xa1\xe70\x00\x00\u07d4c\u007fXi\xd6\xe4i_\x0e\xb9\xe2s\x11\u0107\x8a\xff33\x80\x89j\xc0Nh\xaa\xec\x86\x00\x00\u07d4c\x97|\xad}\r\xcd\xc5+\x9a\xc9\xf2\xff\xa16\xe8d(\x82\xb8\x89\x04\x10\u0546\xa2\nL\x00\x00\u07d4c\xa6\x1d\xc3\n\x8e;0\xa7c\xc4!<\x80\x1c\xbf\x98s\x81x\x8965\u026d\xc5\u07a0\x00\x00\u07d4c\xacT\\\x99\x12C\xfa\x18\xae\xc4\x1dOoY\x8eUP\x15\u0709 \x86\xac5\x10R`\x00\x00\u07d4c\xb9uMu\xd1-8@9\xeci\x06<\v\xe2\x10\xd5\xe0\u3252\v\x86\f\xc8\xec\xfd\x80\x00\u07d4c\xbbfO\x91\x17\x03v(YM\xa7\xe3\xc5\b\x9f\xd6\x18\xb5\xb5\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4c\u00a3\xd25\xe5\xee\xab\xd0\u0526\xaf\u06c9\xd9F'9d\x95\x89CN\xf0[\x9d\x84\x82\x00\x00\u07d4c\xc8\xdf\xde\v\x8e\x01\xda\xdc.t\x8c\x82L\xc06\x9d\U00010cc9\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4c\xd5Z\u065b\x917\xfd\x1b \xcc+O\x03\xd4,\xba\xdd\xf34\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4c\xd8\x00H\x87u\x96\xe0\u0084\x89\xe6P\xcdJ\xc1\x80\tjI\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\xe0\x94c\xe4\x14`>\x80\xd4\xe5\xa0\xf5\xc1\x87t FB%\x82\b\xe4\x8a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x94c\xe8\x8e.S\x9f\xfbE\x03\x86\xb4\xe4g\x89\xb2#\xf5GlE\x8a\x01U\x17\nw\x8e%\xd0\x00\x00\u07d4c\xef/\xbc=\xaf^\xda\xf4\xa2\x95b\x9c\xcf1\xbc\xdf@8\xe5\x89O%\x91\xf8\x96\xa6P\x00\x00\u07d4c\xf0\xe5\xa7R\xf7\x9fg\x12N\xedc:\xd3\xfd'\x05\xa3\x97\u0509\u0556{\xe4\xfc?\x10\x00\x00\xe0\x94c\xf5\xb5=y\xbf.A\x14\x89Re0\"8E\xfa\xc6\xf6\x01\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4c\xfc\x93\x00\x13\x05\xad\xfb\u0278])\xd9)\x1a\x05\xf8\xf1A\v\x8965\u026d\xc5\u07a0\x00\x00\u0794c\xfek\xccK\x8a\x98P\xab\xbeu\x8070\xc92%\x1f\x14[\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4d\x03\xd0bT\x96\x90\xc8\xe8\xb6>\xaeA\xd6\xc1\tGn%\x88\x89lk\x93[\x8b\xbd@\x00\x00\u07d4d\x04+\xa6\x8b\x12\xd4\xc1Qe\x1c\xa2\x81;sR\xbdV\xf0\x8e\x89 \x86\xac5\x10R`\x00\x00\u0794d\x05\xdd\x13\xe9:\xbc\xff7~p\x0e<\x1a\x00\x86\xec\xa2})\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94d\n\xbam\xe9\x84\xd9E\x177x\x03p^\xae\xa7\t_J\x11\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4d\v\xf8t\x15\xe0\xcf@s\x01\xe5Y\x9ah6m\xa0\x9b\xba\u0209\x1a\xbc\x9fA`\x98\x15\x80\x00\u07d4d \xf8\xbc\xc8\x16JaR\xa9\x9dk\x99i0\x05\xcc\xf7\xe0S\x8965f3\xeb\xd8\xea\x00\x00\u07d4d$\x1axD)\x0e\n\xb8U\xf1\u052au\xb5SE\x03\"$\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4d&J\xed\xd5-\xca\xe9\x18\xa0\x12\xfb\xcd\f\x03\x0e\xe6\xf7\x18!\x8965\u026d\xc5\u07a0\x00\x00\u07d4d7\x0e\x87 &E\x12Z5\xb2\a\xaf\x121\xfb`r\xf9\xa7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4d=\x9a\xee\u0531\x80\x94~\u04b9 |\xceL=\xdcU\xe1\xf7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4dC\xb8\xaec\x9d\xe9\x1c\xf7\xf0p\xa5G\x03\xb7\x18NH'l\\\x00w\xefK4\x89\x11X\xe4`\x91=\x00\x00\x00\xe0\x94d\xe2\xde! \v\x18\x99\u00e0\xc0e;P@\x13m\r\xc8B\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4d\xec\x8a[t?4y\xe7\a\xda\xe9\xee \u076aO@\xf1\u0649\n\u05ce\xbcZ\xc6 \x00\x00\u07d4e\x03\x86\v\x19\x10\b\xc1U\x83\xbf\u0201X\t\x93\x01v((\x8965\u026d\xc5\u07a0\x00\x00\u07d4e\x051\x911\x9e\x06z%\xe66\x1dG\xf3\u007fc\x18\xf84\x19\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4e\t;#\x9b\xbf\xba#\xc7w\\\xa7\xdaZ\x86H\xa9\xf5L\xf7\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4e\t\xee\xb14~\x84/\xfbA>7\x15^,\xbcs\x82s\xfd\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94e\vBUU\xe4\xe4\xc5\x17\x18\x14h6\xa2\xc1\xeew\xa5\xb4!\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4e\f\xf6}\xb0`\xcc\xe1uh\xd5\xf2\xa4#h|Idv\t\x89\x05k\xc7^-c\x10\x00\x00\u07d4e\x10\xdfB\xa5\x99\xbc\xb0\xa5\x19\u0329a\xb4\x88u\x9aogw\x89lk\x93[\x8b\xbd@\x00\x00\u07d4e6u\xb8B\xd7\u0634a\xf7\"\xb4\x11|\xb8\x1d\xac\x8ec\x9d\x89\x01\xae6\x1f\xc1E\x1c\x00\x00\u07d4eK~\x80\x87\x99\xa8=r\x87\xc6w\x06\xf2\xab\xf4\x9aId\x04\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94eORHG\xb3\xa6\xac\xc0\xd3\xd5\xf1\xf3b\xb6\x03\xed\xf6_\x96\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4eY4\u068etN\xaa=\xe3M\xbb\xc0\x89LN\xda\va\xf2\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4e]\\\xd7H\x96)\xe2ANIb?\xabb\xa1~M6\x11\x89\x05\fL\xb2\xa1\f`\x00\x00\u07d4e\xaf\x8d\x8b[\x1d\x1e\xed\xfaw\xbc\xbc\x96\xc1\xb13\xf83\x06\u07c9\x05P\x05\xf0\xc6\x14H\x00\x00\u07d4e\xaf\x90\x87\xe0QgqT\x97\u0265\xa7I\x18\x94\x89\x00M\xef\x89-C\xf3\xeb\xfa\xfb,\x00\x00\u0794e\xb4/\xae\xcc\x1e\u07f1B\x83\u0297\x9a\xf5E\xf6;0\xe6\f\x88\xfc\x93c\x92\x80\x1c\x00\x00\u0794e\xd3>\xb3\x9c\xdadS\xb1\x9ea\xc1\xfeM\xb91p\xef\x9d4\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4e\xd8\xddN%\x1c\xbc\x02\x1f\x05\xb0\x10\xf2\xd5\xdcR\f8r\xe0\x89-CW\x9a6\xa9\x0e\x00\x00\u07d4e\xea&\xea\xbb\xe2\xf6L\xcc\xcf\xe0h)\xc2]F7R\x02%\x89%\xf2s\x93=\xb5p\x00\x00\u07d4e\xeag\xad?\xb5j\xd5\xfb\x948}\u04ce\xb3\x83\x00\x1d|h\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94e\xeb\xae\xd2~\u06dd\xcc\x19W\xae\xe5\xf4R\xac!\x05\xa6\\\x0e\x8a\t7\u07ed\xae%\u26c0\x00\u07d4e\xee \xb0m\x9a\u0549\xa7\xe7\xce\x04\xb9\xf5\xf7\x95\xf4\x02\xae\u0389lk\x93[\x8b\xbd@\x00\x00\u07d4e\xf544m/\xfbx\u007f\xa9\xcf\x18]t[\xa4)\x86\xbdn\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94e\xf5\x87\x0f&\xbc\xe0\x89g}\xfc#\xb5\x00\x1e\xe4\x92H4(\x8a\x01\x12\xb1\xf1U\xaa2\xa3\x00\x00\u07d4e\xfd\x02\xd7\x04\xa1*M\xac\xe9G\x1b\x06E\xf9b\xa8\x96q\u0209\x01\x8d\x1c\xe6\xe4'\u0340\x00\u07d4e\xff\x87O\xaf\xceM\xa3\x18\xd6\xc9=W\xe2\u00ca\rs\xe8 \x8968\x02\x1c\xec\u06b0\x00\x00\xe0\x94f\x05W\xbbC\xf4\xbe:\x1b\x8b\x85\xe7\xdf{<[\xcdT\x80W\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4f\b,u\xa8\xde1\xa59\x13\xbb\xd4M\xe3\xa07O\u007f\xaaA\x89O%\x91\xf8\x96\xa6P\x00\x00\u07d4f\x11\xceY\xa9\x8b\a*\xe9Y\xdcI\xadQ\x1d\xaa\xaa\xa1\x9dk\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4f \x1b\xd2'\xaem\u01bd\xfe\xd5\xfb\u0781\x1f\xec\xfe^\x9d\u0649 >\x9e\x84\x92x\x8c\x00\x00\u07d4f#4\x81G$\x93[y1\xdd\xcaa\x00\xe0\rFw'\u0349\"\x88&\x9d\a\x83\xd4\x00\x00\u07d4f'O\xea\x82\xcd0\xb6\u009b#5\x0eOO=1\nX\x99\x89p7\x05P\xab\x82\x98\x00\x00\u07d4f,\xfa\x03\x8f\xab7\xa0\x17E\xa3d\u1e41'\xc5\x03tm\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4f5\xb4oq\x1d-\xa6\xf0\xe1cp\u034e\xe4>\xfb,-R\x89lk\x93[\x8b\xbd@\x00\x00\u07d4f6\x04\xb0P0F\xe6$\xcd&\xa8\xb6\xfbGB\xdc\xe0*o\x89\x03\x8b\x9by~\xf6\x8c\x00\x00\u07d4f6\u05ecczH\xf6\x1d8\xb1L\xfdHe\xd3m\x14(\x05\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4f@\xcc\xf0SU\\\x13\n\xe2\xb6Vd~\xa6\xe3\x167\xb9\xab\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4fBK\xd8x[\x8c\xb4a\x10*\x90\x02\x83\xc3]\xfa\a\xefj\x89\x02.-\xb2ff\xfc\x80\x00\u07d4fL\xd6}\xcc\u026c\x82(\xb4\\U\u06cdvU\ve\x9c\u0709\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4fNC\x11\x98p\xaf\x10zD\x8d\xb1'\x8b\x04H8\xff\u036f\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4fQso\xb5\x9b\x91\xfe\xe9\xc9:\xa0\xbdn\xa2\xf7\xb2Pa\x80\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4f[\x00\x0f\vw'P\xcc\x89k\x91\x8a\xacIK\x16\x80\x00\xe0\x94g]\\\xaa`\x9b\xf7\n\x18\xac\xa5\x80F]\x8f\xb71\r\x1b\xbb\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4gc F\u0732ZT\x93i(\xa9oB?3 \xcb\ud489lk\x93[\x8b\xbd@\x00\x00\u07d4ge\xdf%(\x0e\x8eO8\u0531\xcfDo\xc5\xd7\xebe\x9e4\x89\x05k\xc7^-c\x10\x00\x00\u07d4gv\xe13\xd9\xdc5L\x12\xa9Q\b{c\x96P\xf59\xa43\x89\x06\x81U\xa46v\xe0\x00\x00\u07d4g\x85Q<\xf72\xe4~\x87g\ap\xb5A\x9b\xe1\f\xd1\xfct\x89lk\x93[\x8b\xbd@\x00\x00\u07d4g\x947\xea\xcfCxx\xdc)=H\xa3\x9c\x87\xb7B\x1a!l\x89\x03\u007f\x81\x82\x1d\xb2h\x00\x00\u07d4g\x9b\x9a\x10\x990Q~\x89\x99\t\x9c\xcf*\x91LL\x8d\xd94\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4g\xa8\x0e\x01\x90r\x1f\x949\rh\x02r\x9d\xd1,1\xa8\x95\xad\x89lk\x13u\xbc\x91V\x00\x00\u07d4g\xb8\xa6\xe9\x0f\xdf\n\x1c\xacD\x17\x930\x1e\x87P\xa9\xfayW\x890\x84\x9e\xbe\x166\x9c\x00\x00\u07d4g\xbc\x85\xe8}\xc3LN\x80\xaa\xfa\x06k\xa8\u049d\xbb\x8eC\x8e\x89\x15\xd1\xcfAv\xae\xba\x00\x00\u07d4g\xc9&\t>\x9b\x89'\x938\x10\u0642\"\xd6.+\x82\x06\xbb\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4g\xcf\xdanp\xbfvW\u04d0Y\xb5\x97\x90\xe5\x14Z\xfd\xbea\x89#\x05\r\tXfX\x00\x00\u07d4g\u0582\xa2\x82\xefs\xfb\x8dn\x90q\xe2aOG\xab\x1d\x0f^\x8965\u026d\xc5\u07a0\x00\x00\u07d4g\u05a8\xaa\x1b\xf8\xd6\xea\xf78N\x99=\xfd\xf1\x0f\n\xf6\x8aa\x89\n\xbc\xbbW\x18\x97K\x80\x00\u07d4g\u0692.\xff\xa4r\xa6\xb1$\xe8N\xa8\xf8k$\xe0\xf5\x15\xaa\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4g\xdf$-$\r\u0538\a\x1dr\xf8\xfc\xf3[\xb3\x80\x9dq\xe8\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4g\xee@n\xa4\xa7\xaej:8\x1e\xb4\xed\xd2\xf0\x9f\x17KI(\x898)c_\th\xb0\x00\x00\u07d4g\xf2\xbbx\xb8\xd3\xe1\x1f|E\x8a\x10\xb5\xc8\xe0\xa1\xd3tF}\x89a\t=|,m8\x00\x00\u07d4g\xfcR}\xce\x17\x85\xf0\xfb\x8b\xc7\xe5\x18\xb1\xc6i\xf7\xec\u07f5\x89\r\x02\xabHl\xed\xc0\x00\x00\u07d4h\x02}\x19U\x8e\xd73\x9a\b\xae\xe8\xde5Y\xbe\x06>\xc2\xea\x89lk\x93[\x8b\xbd@\x00\x00\u07d4h\x06@\x83\x8b\xd0zD{\x16\x8dm\x92;\x90\xcflC\xcd\u0289]\u0212\xaa\x111\xc8\x00\x00\u07d4h\a\xdd\u020d\xb4\x89\xb03\xe6\xb2\xf9\xa8\x15SW\x1a\xb3\xc8\x05\x89\x01\x9f\x8euY\x92L\x00\x00\xe0\x94h\rY\x11\xed\x8d\xd9\xee\xc4\\\x06\f\"?\x89\xa7\xf6 \xbb\u054a\x04<3\xc1\x93ud\x80\x00\x00\u07d4h\x11\xb5L\u0456c\xb1\x1b\x94\xda\x1d\xe2D\x82\x85\u035fh\u0649;\xa1\x91\v\xf3A\xb0\x00\x00\u07d4h\x19\f\xa8\x85\xdaB1\x87L\x1c\xfbB\xb1X\n!s\u007f8\x89\xcf\x15&@\xc5\xc80\x00\x00\xe0\x94h(\x97\xbcO\x8e\x89\x02\x91 \xfc\xff\xb7\x87\xc0\x1a\x93\xe6A\x84\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4h)^\x8e\xa5\xaf\xd9\t?\xc0\xa4e\xd1W\x92+]*\xe24\x89\x01\x15NS!}\xdb\x00\x00\u07d4h.\x96'oQ\x8d1\xd7\xe5n0\u07f0\t\xc1!\x82\x01\xbd\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4h5\xc8\xe8\xb7J,\xa2\xae?J\x8d\x0fk\x95J>*\x83\x92\x89\x03B\x9c3]W\xfe\x00\x00\u07d4h63\x01\n\x88hk\xeaZ\x98\xeaS\xe8y\x97\xcb\xf7>i\x89\x05k9Bc\xa4\f\x00\x00\u07d4h=\xba6\xf7\xe9O@\xeaj\xea\ry\xb8\xf5!\xdeU\an\x89\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4hA\x9cm\xd2\xd3\xceo\u02f3\xc7>/\xa0y\xf0`Q\xbd\xe6\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4hG;z}\x96Y\x04\xbe\u06e5V\u07fc\x17\x13l\xd5\xd44\x89\x05k\xc7^-c\x10\x00\x00\u07d4hG\x82[\xde\xe8$\x0e(\x04,\x83\xca\xd6B\U000868fd\u0709QP\xae\x84\xa8\xcd\xf0\x00\x00\xe0\x94hJD\xc0i3\x9d\b\xe1\x9auf\x8b\u06e3\x03\xbe\x85S2\x8a\x0e\u04b5%\x84\x1a\xdf\xc0\x00\x00\u07d4hS\x1fM\u0680\x8fS vz\x03\x114(\xca\f\xe2\xf3\x89\x89\x01\r:\xa56\xe2\x94\x00\x00\u07d4hy'\xe3\x04\x8b\xb5\x16*\xe7\xc1\\\xf7k\xd1$\xf9I{\x9e\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94h\x80\x9a\xf5\xd52\xa1\x1c\x1aMn2\xaa\xc7\\LR\xb0\x8e\xad\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4h\x86\xad\xa7\xbb\xb0a{\u0684!\x91\u018c\x92.\xa3\xa8\xac\x82\x89>\xe2;\xde\x0e} \x00\x00\xe0\x94h\x88>\x15.V`\xfe\xe5\x96&\xe7\xe3\xb4\xf0Q\x10\xe6\"/\x8a\v\x94c;\xe9u\xa6*\x00\x00\u07d4h\x8aV\x9e\x96U$\xeb\x1d\n\xc3\xd3s>\xab\x90\x9f\xb3\xd6\x1e\x89G\x8e\xae\x0eW\x1b\xa0\x00\x00\xe0\x94h\x8e\xb3\x85;\xbc\xc5\x0e\xcf\xee\x0f\xa8\u007f\n\xb6\x93\u02bd\xef\x02\x8a\x06\xb1\n\x18@\x06G\xc0\x00\x00\u07d4h\xa7B_\xe0\x9e\xb2\x8c\xf8n\xb1y>A\xb2\x11\xe5{\u058d\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4h\xa8l@#\x88\xfd\xdcY\x02\x8f\xecp!\u933f\x83\x0e\xac\x89\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\xe0\x94h\xac\u06a9\xfb\x17\xd3\xc3\t\x91\x1aw\xb0_S\x91\xfa\x03N\xe9\x8a\x01\xe5.3l\xde\"\x18\x00\x00\u07d4h\xad\xdf\x01\x9dk\x9c\xabp\xac\xb1?\v1\x17\x99\x9f\x06.\x12\x89\x02\xb5\x12\x12\xe6\xb7\u0200\x00\u07d4h\xb3\x186\xa3\n\x01j\xda\x15{c\x8a\xc1]\xa7?\x18\xcf\u0789\x01h\u048e?\x00(\x00\x00\xe0\x94h\xb6\x85G\x88\xa7\xc6Il\xdb\xf5\xf8K\x9e\xc5\xef9+x\xbb\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4h\xc0\x84\x90\u021b\xf0\u05b6\xf3 \xb1\xac\xa9\\\x83\x12\xc0\x06\b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4h\xc7\xd1q\x1b\x01\x1a3\xf1o\x1fU\xb5\xc9\x02\xcc\xe9p\xbd\u05c9\b=lz\xabc`\x00\x00\u07d4h\xc8y\x1d\xc3B\xc3sv\x9e\xa6\x1f\xb7\xb5\x10\xf2Q\xd3 \x88\x8965\u026d\xc5\u07a0\x00\x00\u07d4h\u07d4|I[\ubbb8\u8273\xf9S\xd53\x87K\xf1\x06\x89\x1d\x99E\xab+\x03H\x00\x00\u07d4h\xe8\x02'@\xf4\xaf)\xebH\xdb2\xbc\xec\xdd\xfd\x14\x8d=\xe3\x8965\u026d\xc5\u07a0\x00\x00\u07d4h\xecy\u057eqUql@\x94\x1cy\u05cd\x17\u079e\xf8\x03\x89\x1b#8w\xb5 \x8c\x00\x00\u07d4h\xee\xc1\u222c1\xb6\xea\xba~\x1f\xbdO\x04\xadW\x9ak]\x89lk\x93[\x8b\xbd@\x00\x00\u07d4h\xf5%\x92\x1d\xc1\x1c2\x9buO\xbf>R\x9f\xc7#\xc84\u0349WG=\x05\u06ba\xe8\x00\x00\u07d4h\xf7\x19\xae4+\xd7\xfe\xf1\x8a\x05\u02f0/pZ\u04ce\u0572\x898\xeb\xad\\\u0710(\x00\x00\xe0\x94h\xf7W<\xd4W\xe1L\x03\xfe\xa4>0-04|\x10p\\\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4h\xf8\xf4QU\xe9\x8cP)\xa4\xeb\u0175'\xa9.\x9f\xa81 \x89\xf0{D\xb4\a\x93 \x80\x00\u07d4h\xfe\x13W!\x8d\tXI\xcdW\x98B\u012a\x02\xff\x88\x8d\x93\x89lk\x93[\x8b\xbd@\x00\x00\u07d4i\x02(\xe4\xbb\x12\xa8\u0535\u09d7\xb0\xc5\xcf*u\t\x13\x1e\x89e\xea=\xb7UF`\x00\x00\u07d4i\x05\x94\xd3\x06a<\xd3\xe2\xfd$\xbc\xa9\x99J\u064a=s\xf8\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94i\a2ir\x9ed\x14\xb2n\xc8\xdc\x0f\xd95\xc7;W\x9f\x1e\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\xe0\x94i\x19\xdd^]\xfb\x1a\xfa@G\x03\xb9\xfa\xea\x8c\xee5\xd0\rp\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4i4\x92\xa5\xc5\x13\x96\xa4\x82\x88\x16i\xcc\xf6\xd8\xd7y\xf0\tQ\x89\x12\xbfPP:\xe3\x03\x80\x00\u07d4i=\x83\xbe\tE\x9e\xf89\v.0\xd7\xf7\u008d\xe4\xb4(N\x89lk\x93[\x8b\xbd@\x00\x00\u07d4iQp\x83\xe3\x03\xd4\xfb\xb6\xc2\x11E\x14!]i\xbcF\xa2\x99\x89\x05k\xc7^-c\x10\x00\x00\u07d4iUPel\xbf\x90\xb7]\x92\xad\x91\"\xd9\r#\xcah\xcaM\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94iX\xf8;\xb2\xfd\xfb'\xce\x04\t\xcd\x03\xf9\xc5\xed\xbfL\xbe\u074a\x04<3\xc1\x93ud\x80\x00\x00\u0794i[\x0fRBu7\x01\xb2d\xa6pq\xa2\u0708\b6\xb8\u06c8\u3601\x1b\xech\x00\x00\xe0\x94i[L\xce\bXV\xd9\xe1\xf9\xff>y\x94 #5\x9e_\xbc\x8a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x94if\x06:\xa5\xde\x1d\xb5\xc6q\xf3\xddi\x9dZ\xbe!>\xe9\x02\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4it\u0224\x14\u03ae\xfd<.M\xfd\xbe\xf40V\x8d\x9a\x96\v\x89\x12\x1e\xa6\x8c\x11NQ\x00\x00\xe0\x94iximQP\xa9\xa2cQ?\x8ft\u0196\xf8\xb19|\xab\x8a\x01g\xf4\x82\xd3\u0171\xc0\x00\x00\xe0\x94iy{\xfb\x12\u027e\u0582\xb9\x1f\xbcY5\x91\xd5\xe4\x027(\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94i\u007fUSk\xf8Z\xdaQ\x84\x1f\x02\x87b:\x9f\x0e\u041a\x17\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4i\x82\xfe\x8a\x86~\x93\xebJ\v\xd0QX\x93\x99\xf2\xec\x9aR\x92\x89lk\x93[\x8b\xbd@\x00\x00\u07d4i\x8a\x8ao\x01\xf9\xabh/c|yi\xbe\x88_lS\x02\xbf\x89\x01\r:\xa56\xe2\x94\x00\x00\u07d4i\x8a\xb9\xa2\xf33\x81\xe0|\fGC=\r!\xd6\xf36\xb1'\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4i\x94\xfb21\xd7\xe4\x1dI\x1a\x9dh\xd1\xfaL\xae,\xc1Y`\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4i\x9c\x9e\xe4q\x95Q\x1f5\xf8b\xcaL\"\xfd5\xae\x8f\xfb\xf4\x89\x04V9\x18$O@\x00\x00\u07d4i\x9f\xc6\u058aGuW<\x1d\u036e\xc80\xfe\xfdP9|N\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4i\xaf(\xb0tl\xac\r\xa1p\x84\xb99\x8c^6\xbb:\r\xf2\x896w\x03n\xdf\n\xf6\x00\x00\u07d4i\xb8\x0e\xd9\x0f\x84\x83J\xfa?\xf8.\xb9dp;V\tw\u0589\x01s\x17\x90SM\xf2\x00\x00\xe0\x94i\xb8\x1dY\x81\x14\x1e\u01e7\x14\x10`\xdf\u03cf5\x99\xff\xc6>\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4i\xbc\xfc\x1dC\xb4\xba\x19\xde{'K\xdf\xfb5\x13\x94\x12\xd3\u05c95e\x9e\xf9?\x0f\xc4\x00\x00\u07d4i\xbd%\xad\xe1\xa34lY\xc4\xe90\xdb*\x9dq^\xf0\xa2z\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4i\xc0\x8dtGT\xdep\x9c\xe9n\x15\xae\r\x1d9[:\"c\x8965\u026d\xc5\u07a0\x00\x00\u07d4i\xc2\xd85\xf1>\xe9\x05\x80@\x8ej2\x83\xc8\u0326\xa44\xa2\x89#\x8f\xd4,\\\xf0@\x00\x00\u07d4i\xc9N\a\u0129\xbe3\x84\xd9]\xfa<\xb9)\x00Q\x87;{\x89\x03\xcbq\xf5\x1f\xc5X\x00\x00\u07d4i\xcb>!S\x99\x8d\x86\xe5\xee \xc1\xfc\u0466\xba\uec86?\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4i\u04ddQ\b\x89\xe5R\xa3\x96\x13[\xfc\xdb\x06\xe3~8v3\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94i\u064f8\xa3\xba=\xbc\x01\xfa\\,\x14'\xd8b\x83//p\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94i\xe2\xe2\xe7\x040|\xcc[\\\xa3\xf1d\xfe\xce.\xa7\xb2\xe5\x12\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4i\xffB\x90t\u02dblc\xbc\x91B\x84\xbc\xe5\xf0\xc8\xfb\xf7\u0409\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4i\xff\x89\x01\xb5Av?\x81|_)\x98\xf0-\xcf\xc1\xdf)\x97\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4j\x02:\xf5}XM\x84^i\x876\xf10\u06dd\xb4\r\xfa\x9a\x89\x05[ \x1c\x89\x00\x98\x00\x00\u07d4j\x04\xf5\xd5?\xc0\xf5\x15\xbe\x94+\x8f\x12\xa9\xcbz\xb0\xf3\x97x\x89\xa9\xaa\xb3E\x9b\xe1\x94\x00\x00\u07d4j\x05\xb2\x1cO\x17\xf9\xd7?_\xb2\xb0\u02c9\xffSV\xa6\xcc~\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\xe0\x94j\x0f\x05`f\xc2\xd5f(\x85\x02s\xd7\xec\xb7\xf8\xe6\xe9\x12\x9e\x8a\x01\x0f\r)<\u01e5\x88\x00\x00\u07d4j\x13\xd5\xe3,\x1f\xd2m~\x91\xffn\x051`\xa8\x9b,\x8a\xad\x89\x02\xe6/ \xa6\x9b\xe4\x00\x00\u07d4j.\x86F\x9a[\xf3|\xee\x82\xe8\x8bL8c\x89](\xfc\xaf\x89\x1c\"\x92f8[\xbc\x00\x00\u07d4j6\x94BL|\u01b8\xbc\u067c\u02baT\f\xc1\xf5\xdf\x18\u05c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94jB\u0297\x1cex\u056d\xe2\x95\xc3\xe7\xf4\xad3\x1d\xd3BN\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4jD\xaf\x96\xb3\xf02\xaed\x1b\xebg\xf4\xb6\xc83B\xd3|]\x89\x01\x92t\xb2Y\xf6T\x00\x00\u07d4jL\x89\a\xb6\x00$\x80W\xb1\xe4cT\xb1\x9b\u0705\x9c\x99\x1a\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4jQNbB\xf6\xb6\x8c\x13~\x97\xfe\xa1\u73b5U\xa7\xe5\xf7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4jS\xd4\x1a\xe4\xa7R\xb2\x1a\xbe\xd57FI\x95:Q=\xe5\xe5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4jaY\aJ\xb5s\xe0\xeeX\x1f\x0f=\xf2\u05a5\x94b\x9bt\x89\x10\xce\x1d=\x8c\xb3\x18\x00\x00\u07d4jc7\x83?\x8fjk\xf1\f\xa7\xec!\xaa\x81\x0e\xd4D\xf4\u02c97\xbd$4\\\xe8\xa4\x00\x00\u07d4jcS\xb9qX\x9f\x18\xf2\x95\\\xba(\xab\xe8\xac\xcejWa\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4jc\xfc\x89\xab\xc7\xf3n(-\x80x{{\x04\xaf\xd6U>q\x89\b\xacr0H\x9e\x80\x00\x00\u07d4jg\x9e7\x8f\xdc\xe6\xbf\xd9\u007f\xe6/\x04)Z$\xb9\x8965\u026d\xc5\u07a0\x00\x00\u07d4j\x8c\xea-\xe8J\x8d\xf9\x97\xfd?\x84\xe3\b=\x93\xdeW\u0369\x89\x05k\xe0<\xa3\xe4}\x80\x00\xe0\x94j\x97Xt;`>\xea:\xa0RKB\x88\x97#\xc4\x159H\x8a\x02#\x85\xa8'\xe8\x15P\x00\x00\u07d4j\xa5s/;\x86\xfb\x8c\x81\xef\xbek[G\xb5cs\v\x06\u020965\u026d\xc5\u07a0\x00\x00\u07d4j\xb3#\xaePV\xed\nE0r\u016b\xe2\xe4/\xcf]q9\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4j\xb5\xb4\xc4\x1c\u0778)i\f/\xda\u007f \xc8^b\x9d\xd5\u0549d\u052fqL2\x90\x00\x00\u07d4j\xc4\x0fS-\xfe\xe5\x11\x81\x17\u04ad5-\xa7}Om\xa2\u0209\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4j\xc4\u053e-\xb0\u065d\xa3\xfa\xaa\xf7RZ\xf2\x82\x05\x1dj\x90\x89\x04X\xcaX\xa9b\xb2\x80\x00\u07d4j\xcd\u0723\xcd+I\x90\xe2\\\xd6\\$\x14\x9d\t\x12\t\x9ey\x89\xa2\xa1\xe0|\x9fl\x90\x80\x00\u07d4j\xd9\v\xe2R\xd9\xcdFM\x99\x81%\xfa\xb6\x93\x06\v\xa8\xe4)\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4j\u0753!\x93\xcd8IJ\xa3\xf0:\xec\xccKz\xb7\xfa\xbc\xa2\x89\x04\xdbs%Gc\x00\x00\x00\xe0\x94j\xe5\u007f'\x91|V*\x13*M\x1b\xf7\xec\n\u01c5\x83)&\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4j\xeb\x9ftt.\xa4\x91\x81=\xbb\xf0\xd6\xfc\xde\x1a\x13\x1dM\xb3\x89\x17\xe5T0\x8a\xa00\x00\x00\u07d4j\xf25\u04bb\xe0P\xe6)\x16\x15\xb7\x1c\xa5\x82\x96X\x81\x01B\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4j\xf6\xc7\xee\x99\xdf'\x1b\xa1[\xf3\x84\xc0\xb7d\xad\xcbM\xa1\x82\x8965f3\xeb\xd8\xea\x00\x00\u07d4j\xf8\xe5Yih,q_H\xadO\xc0\xfb\xb6~\xb5\x97\x95\xa3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4j\xf9@\xf6>\u0278\xd8v'*\u0296\xfe\xf6\\\xda\xce\xcd\ua262\xa1]\tQ\x9b\xe0\x00\x00\u07d4j\xf9\xf0\xdf\uebbb_d\xbf\x91\xabw\x16i\xbf\x05)US\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4j\xff\x14f\xc2b6u\xe3\xcb\x0eu\xe4#\xd3z%\xe4B\xeb\x89]\u0212\xaa\x111\xc8\x00\x00\xe0\x94k\r\xa2Z\xf2g\u05c3l\"k\xca\xe8\xd8r\xd2\xceR\xc9A\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4k\x10\xf8\xf8\xb3\xe3\xb6\r\xe9\n\xa1-\x15_\x9f\xf5\xff\xb2,P\x89lk\x93[\x8b\xbd@\x00\x00\u07d4k\x17Y\x8a\x8e\xf5Oyz\xe5\x15\u0336Q}\x18Y\xbf\x80\x11\x89\x05k\xc7^-c\x10\x00\x00\u07d4k \xc0\x80`jy\xc7;\xd8\xe7[\x11qzN\x8d\xb3\xf1\u00c9\x10?sX\x03\xf0\x14\x00\x00\u07d4k\"\x84D\x02!\xce\x16\xa88-\xe5\xff\x02)G\"i\xde\xec\x8965\u026d\xc5\u07a0\x00\x00\u07d4k0\xf1\x829\x10\xb8m:\xcbZj\xfc\x9d\xef\xb6\xf3\xa3\v\xf8\x89\u3bb5sr@\xa0\x00\x00\u07d4k8\u0784\x1f\xad\u007fS\xfe\x02\xda\x11[\xd8j\xaff$f\xbd\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4kK\x99\xcb?\xa9\xf7\xb7L\u3903\x17\xb1\xcd\x13\t\n\x1az\x89\x03\x1b2~i]\xe2\x00\x00\u07d4kZ\xe7\xbfx\xecu\xe9\f\xb5\x03\xc7x\xcc\u04f2KO\x1a\xaf\x89+^:\xf1k\x18\x80\x00\x00\u07d4kc\xa2\u07f2\xbc\xd0\xca\xec\x00\"\xb8\x8b\xe3\f\x14Q\xeaV\xaa\x89+\xdbk\xf9\x1f\u007fL\x80\x00\u07d4kew\xf3\x90\x9aMm\xe0\xf4\x11R-Ep8d\x004\\\x89e\xea=\xb7UF`\x00\x00\u07d4kr\xa8\xf0a\xcf\xe6\x99j\xd4G\xd3\xc7,(\xc0\xc0\x8a\xb3\xa7\x89\xe7\x8cj\u01d9\x12b\x00\x00\u07d4kv\rHw\xe6\xa6'\xc1\xc9g\xbe\xe4Q\xa8P}\xdd\u06eb\x891T\xc9r\x9d\x05x\x00\x00\u07d4k\x83\xba\xe7\xb5e$EXU[\xcfK\xa8\xda \x11\x89\x1c\x17\x89lk\x93[\x8b\xbd@\x00\x00\u07d4k\x92]\xd5\xd8\xeda2\xabm\b`\xb8,D\xe1\xa5\x1f\x1f\xee\x89P; >\x9f\xba \x00\x00\xe0\x94k\x94a]\xb7Pej\u00cc~\x1c\xf2\x9a\x9d\x13g\u007fN\x15\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4k\x95\x1aC'N\xea\xfc\x8a\t\x03\xb0\xaf.\xc9+\xf1\xef\xc89\x89\x05k\xc7^-c\x10\x00\x00\u07d4k\x99%!\xec\x85#p\x84\x8a\u0597\xcc-\xf6Nc\xcc\x06\xff\x8965\u026d\xc5\u07a0\x00\x00\u07d4k\xa8\xf7\xe2_\xc2\xd8qa\x8e$\xe4\x01\x84\x19\x917\xf9\xf6\xaa\x89\x15\xafd\x86\x9ak\xc2\x00\x00\u07d4k\xa9\xb2\x1b5\x10k\xe1Y\xd1\xc1\xc2ez\xc5l\u049f\xfdD\x89\xf2\xdc}G\xf1V\x00\x00\x00\u07d4k\xafz*\x02\xaex\x80\x1e\x89\x04\xadz\xc0Q\b\xfcV\xcf\xf6\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94k\xb2\xac\xa2?\xa1bm\x18\xef\xd6w\u007f\xb9}\xb0-\x8e\n\xe4\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4k\xb4\xa6a\xa3:q\xd4$\u051b\xb5\xdf(b.\xd4\xdf\xfc\xf4\x89\",\x8e\xb3\xfff@\x00\x00\u07d4k\xb5\b\x13\x14j\x9a\xddB\xee\"\x03\x8c\x9f\x1fti\xd4\u007fG\x89\n\xdaUGK\x814\x00\x00\u07d4k\xbc?5\x8af\x8d\u0461\x1f\x03\x80\xf3\xf71\bBj\xbdJ\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4k\xbd\x1eq\x93\x90\xe6\xb9\x10C\xf8\xb6\xb9\u07c9\x8e\xa8\x00\x1b4\x89llO\xa6\xc3\xdaX\x80\x00\u07d4k\xc8Z\xcdY(r.\xf5\tS1\xee\x88\xf4\x84\xb8\u03c3W\x89\t\xc2\x00vQ\xb2P\x00\x00\u07d4k\xd3\xe5\x9f#\x9f\xaf\xe4wk\xb9\xbd\xddk\ue0fa]\x9d\x9f\x8965\u026d\xc5\u07a0\x00\x00\u07d4k\xd4W\xad\xe0Qy]\xf3\xf2F\\89\xae\xd3\xc5\xde\xe9x\x8964\xbf9\xab\x98x\x80\x00\u07d4k\xe1c\x13d>\xbc\x91\xff\x9b\xb1\xa2\xe1\x16\xb8T\xea\x93:E\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4k\xe7Y^\xa0\xf0hH\x9a'\x01\xecFI\x15\x8d\xdcC\xe1x\x89lk\x93[\x8b\xbd@\x00\x00\u07d4k\xe9\x03\x0e\xe6\xe2\xfb\u0111\xac\xa3\xde@\"\xd3\x01w+{}\x89\x01s\x17\x90SM\xf2\x00\x00\xe0\x94k\xec1\x1a\xd0P\b\xb4\xaf5<\x95\x8c@\xbd\x06s\x9a?\xf3\x8a\x03w\xf6*\x0f\nbp\x00\x00\u07d4k\xf7\xb3\xc0e\xf2\xc1\xe7\xc6\xeb\t+\xa0\xd1Pf\xf3\x93\u0478\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4k\xf8o\x1e/+\x802\xa9\\Mw8\xa1\t\xd3\xd0\xed\x81\x04\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4l\x05\xe3N^\xf2\xf4.\u041d\xef\xf1\x02l\xd6k\xcbi`\xbb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4l\b\xa6\xdc\x01s\xc74)U\xd1\xd3\xf2\xc0e\xd6/\x83\xae\u01c9\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4l\n\xe9\xf0C\xc84\xd4Bq\xf14\x06Y=\xfe\tO8\x9f\x89RD*\xe13\xb6*\x80\x00\u07d4l\f\xc9\x17\xcb\xee}|\t\x97c\xf1Nd\xdf}4\xe2\xbf\t\x89\r\x8drkqw\xa8\x00\x00\xe0\x94l\x0eq/@\\Yr_\xe8)\xe9wK\xf4\xdf\u007fM\xd9e\x8a\f(h\x88\x9c\xa6\x8aD\x00\x00\xe0\x94l\x10\x12\x05\xb3#\xd7uD\xd6\xdcR\xaf7\xac\xa3\xce\xc6\xf7\xf1\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4l\x15\xec5 \xbf\x8e\xbb\xc8 \xbd\x0f\xf1\x97x7T\x94\u03dd\x89l\xb7\xe7Hg\xd5\xe6\x00\x00\xe0\x94l\x1d\xdd3\xc8\x19f\u0706!w`q\xa4\x12\x94\x82\xf2\xc6_\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4l%2\u007f\x8d\u02f2\xf4^V\x1e\x86\xe3]\x88P\xe5:\xb0Y\x89;\xcd\xf9\xba\xfe\xf2\xf0\x00\x00\u07d4l.\x9b\xe6\u052bE\x0f\xd1%1\xf3?\x02\x8caFt\xf1\x97\x89\xc2\x12z\xf8X\xdap\x00\x00\u07d4l5\x9eX\xa1=Ex\xa93\x8e3\\g\xe7c\x9f_\xb4\u05c9\v\xd1[\x94\xfc\x8b(\x00\x00\u07d4l=\x18pA&\xaa\x99\xee3B\xce`\xf5\xd4\xc8_\x18g\u0349\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4lGK\xc6jTx\x00f\xaaOQ.\xef\xa7s\xab\xf9\x19\u01c9\x05\x18\x83\x15\xf7v\xb8\x00\x00\u07d4lNBn\x8d\xc0\x05\u07e3Ql\xb8\xa6\x80\xb0.\ua56e\x8e\x89Hz\x9a0E9D\x00\x00\u07d4lR\xcf\b\x95\xbb5\xe6V\x16\x1eM\xc4j\xe0\xe9m\xd3\xe6,\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4lT\"\xfbK\x14\xe6\u064b`\x91\xfd\xecq\xf1\xf0\x86@A\x9d\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4l\\:T\u0367\xc2\xf1\x18\xed\xbaCN\xd8\x1en\xbb\x11\xddz\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4lc\xf8EV\u0490\xbf\u0359\xe44\ue657\xbf\xd7yWz\x89lk\x93[\x8b\xbd@\x00\x00\u07d4lc\xfc\x85\x02\x9a&T\u05db+\xeaM\xe3I\xe4REw\u0149#\xc7W\a+\x8d\xd0\x00\x00\u07d4led\xe5\xc9\xc2N\xaa\xa7D\xc9\xc7\xc9h\xc9\xe2\xc9\xf1\xfb\xae\x89I\x9bB\xa2\x119d\x00\x00\xe0\x94lg\xd6\xdb\x1d\x03Ql\x12\x8b\x8f\xf24\xbf=I\xb2m)A\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4lg\xe0\u05f6.*\bPiE\xa5\xdf\xe3\x82c3\x9f\x1f\"\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4lj\xa0\xd3\vdr\x19\x90\xb9PJ\x86?\xa0\xbf\xb5\xe5}\xa7\x89\x92^\x06\xee\xc9r\xb0\x00\x00\u07d4lqJX\xff\xf6\xe9}\x14\xb8\xa5\xe3\x05\xeb$@eh\x8b\xbd\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4l\x80\rKI\xba\a%\x04`\xf9\x93\xb8\xcb\xe0\v&j%S\x89\x1a\xb2\xcf|\x9f\x87\xe2\x00\x00\u07d4l\x80\x8c\xab\xb8\xff_\xbbc\x12\xd9\xc8\xe8J\xf8\xcf\x12\xef\bu\x89\xd8\xd8X?\xa2\xd5/\x00\x00\xe0\x94l\x82 )!\x8a\xc8\xe9\x8a&\f\x1e\x06@)4\x889\x87[\x8a\x01\x0f\x97\xb7\x87\xe1\xe3\b\x00\x00\u07d4l\x84\u02e7|m\xb4\xf7\xf9\x0e\xf1=^\xe2\x1e\x8c\xfc\u007f\x83\x14\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94l\x86\x87\xe3Aw\x10\xbb\x8a\x93U\x90!\xa1F\x9ej\x86\xbcw\x8a\x02[-\xa2x\xd9k{\x80\x00\xe0\x94l\x88,'s,\xef\\|\x13\xa6\x86\xf0\xa2\xeawUZ\u0089\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4l\xa5\xde\x00\x81}\xe0\xce\xdc\xe5\xfd\x00\x01(\xde\xde\x12d\x8b<\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4l\xa6\xa12\xce\x1c\u0488\xbe\xe3\x0e\xc7\xcf\xef\xfb\x85\xc1\xf5\nT\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94l\xb1\x1e\xcb2\xd3\u0382\x96\x011\x066\xf5\xa1\f\xf7\u03db_\x8a\x04?\u851c8\x01\xf5\x00\x00\u07d4l\xc1\xc8x\xfal\u078a\x9a\v\x83\x11$~t\x1eFB\xfem\x895e\x9e\xf9?\x0f\xc4\x00\x00\xe0\x94l\xcb\x03\xac\xf7\xf5<\xe8z\xad\xcc!\xa9\x93-\xe9\x15\xf8\x98\x04\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4l\xd2\x12\xae\xe0N\x01?=*\xba\u04a0#`k\xfb\\j\u01c9lj\xccg\u05f1\xd4\x00\x00\u07d4l\xd2(\xdcq!i0\u007f\xe2|\xebtw\xb4\x8c\xfc\x82r\xe5\x89\x044\xea\x94\u06caP\x00\x00\u07d4l\xe1\xb0\xf6\xad\xc4pQ\xe8\xab8\xb3\x9e\xdbA\x86\xb0;\xab\u0309Ay\x97\x94\xcd$\xcc\x00\x00\u07d4l\xea\xe3s=\x8f\xa4=l\xd8\f\x1a\x96\xe8\xeb\x93\x10\x9c\x83\xb7\x89\x10'\x94\xad \xdah\x00\x00\u07d4m\x05i\xe5U\x8f\xc7\xdf'f\xf2\xba\x15\u070a\xef\xfc[\xebu\x89\xd8\xe6\x00\x1el0+\x00\x00\u07d4m\x12\x0f\f\xaa\xe4O\xd9K\xca\xfeU\xe2\xe2y\uf5ba\\z\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4m\x14V\xff\xf0\x10N\xe8D\xa31G7\x8438\xd2L\xd6l\x89\a\xb0l\xe8\u007f\xddh\x00\x00\u07d4m \xef\x97\x04g\nP\v\xb2i\xb5\x83.\x85\x98\x02\x04\x9f\x01\x89\a\f\x1c\xc7;\x00\xc8\x00\x00\xe0\x94m/\x97g4\xb9\xd0\a\r\x18\x83\xcfz\u02b8\xb3\xe4\x92\x0f\xc1\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4m9\xa9\u93c1\xf7i\xd7:\xad,\xea\xd2v\xac\x13\x87\xba\xbe\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4m;x6\xa2\xb9\u0619r\x1aM#{R#\x85\xdc\xe8\xdf\u034966\xc2^f\xec\xe7\x00\x00\u07d4m?+\xa8V\u033b\x027\xfava\x15k\x14\xb0\x13\xf2\x12@\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94m@\b\xb4\xa8\x88\xa8&\xf2H\xeej\v\r\xfd\xe9\xf92\x10\xb9\x8a\x01'\xfc\xb8\xaf\xae \xd0\x00\x00\u07d4m@\xca'\x82m\x97s\x1b>\x86\xef\xfc\u05f9*Aa\xfe\x89\x89lk\x93[\x8b\xbd@\x00\x00\u07d4mD\x97J1\u0447\xed\xa1m\xddG\xb9\xc7\xecP\x02\xd6\x1f\xbe\x892\xf5\x1e\u06ea\xa30\x00\x00\xe0\x94mK\\\x05\xd0j \x95~\x17H\xabm\xf2\x06\xf3C\xf9/\x01\x8a\x02\x1f6\x06\x99\xbf\x82_\x80\x00\xe0\x94mL\xbf=\x82\x84\x83:\xe9\x93D0>\b\xb4\xd6\x14\xbf\xda;\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4mY\xb2\x1c\xd0\xe2t\x88\x04\u066b\xe0d\xea\u00be\xf0\xc9_'\x89lk\x93[\x8b\xbd@\x00\x00\u07d4mc\u04ce\xe8\xb9\x0e\x0en\xd8\xf1\x92\xed\xa0Q\xb2\u05a5\x8b\xfd\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4mf4\xb5\xb8\xa4\x01\x95\xd9I\x02z\xf4\x82\x88\x02\t,\ued89\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94m}\x1c\x94\x95\x11\xf8\x83\x03\x80\x8c`\xc5\xea\x06@\xfc\xc0&\x83\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4m\x84m\xc1&W\xe9\x1a\xf2P\bQ\x9c>\x85\u007fQp}\u0589\xf8\xd3\v\xc9#B\xf8\x00\x00\u07d4m\x91\x93\x99k\x19F\x17!\x11\x06\xd1c^\xb2l\u0136ll\x89\x15\xaa\x1e~\x9d\xd5\x1c\x00\x00\u07d4m\x99\x97P\x98\x82\x02~\xa9G#\x14$\xbe\xde\xde)e\u043a\x89l\x81\u01f3\x11\x95\xe0\x00\x00\u07d4m\xa0\xed\x8f\x1di3\x9f\x05\x9f*\x0e\x02G\x1c\xb4O\xb8\u00fb\x892\xbc8\xbbc\xa8\x16\x00\x00\u07d4m\xb7+\xfdC\xfe\xf4e\xcaV2\xb4Z\xabra@N\x13\xbf\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94m\xbe\x8a\xbf\xa1t(\x06&9\x817\x1b\xf3\xd3U\x90\x80kn\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4m\xc3\xf9+\xaa\x1d!\u06b78+\x892a\xa05o\xa7\xc1\x87\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4m\xc7\x05:q\x86\x16\xcf\u01cb\xeec\x82\xeeQ\xad\xd0\xc7\x030\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94m\xcc~d\xfc\xaf\xcb\xc2\xdcl\x0e^f,\xb3G\xbf\xfc\xd7\x02\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4m\xda_x\x8alh\x8d\u07d2\x1f\xa3\x85.\xb6\xd6\xc6\xc6)f\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4m\xdb`\x92w\x9dXB\xea\xd3x\xe2\x1e\x81 \xfdLk\xc12\x89lk\x93[\x8b\xbd@\x00\x00\u07d4m\xdf\xefc\x91U\u06ab\n\\\xb4\x95:\xa8\u016f\xaa\x88\x04S\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4m\xe0/-\xd6~\xfd\xb794\x02\xfa\x9e\xaa\xcb\xcfX\x9d.V\x89@\x13\x8b\x91~\u07f8\x00\x00\u07d4m\u4d418\\\xf7\xfc\x9f\xe8\xc7}\x13\x1f\xe2\xeew$\xc7j\x89})\x97s=\xcc\xe4\x00\x00\u07d4m\xe4\xd1R\x19\x18/\xaf:\xa2\xc5\xd4\xd2Y_\xf20\x91\xa7'\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4m\xed\xf6.t?M,*K\x87\xa7\x87\xf5BJz\xeb9<\x89\t\xc2\x00vQ\xb2P\x00\x00\u07d4m\xf2Of\x85\xa6/y\x1b\xa37\xbf?\xf6~\x91\xf3\u053c:\x89ukI\xd4\nH\x18\x00\x00\u07d4m\xf5\xc8O{\x90\x9a\xab>a\xfe\x0e\xcb\x1b;\xf2`\"*\u0489\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4m\xff\x90\xe6\xdc5\x9d%\x90\x88+\x14\x83\xed\xbc\xf8\x87\xc0\xe4#\x8965\u026d\xc5\u07a0\x00\x00\u07d4n\x01\xe4\xadV\x9c\x95\xd0\a\xad\xa3\r^-\xb1(\x88I\"\x94\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n\a;f\u0478\xc6gD\u0600\x96\xa8\u0759\xec~\x02(\u0689\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n\x0e\xe7\x06\x12\xc9v(}I\x9d\u07e6\xc0\xdc\xc1,\x06\xde\xea\x89\a\v\u0579V!F\x00\x00\xe0\x94n\x12\xb5\x1e\"[JCr\xe5\x9a\u05e2\xa1\xa1>\xa3\u04e17\x8a\x03\x00F\xc8\xccw_\x04\x00\x00\u07d4n\x1a\x04l\xaf[JW\xf4\xfdK\xc1sb!&\xb4\xe2\xfd\x86\x89a\t=|,m8\x00\x00\u07d4n\x1e\xa4\xb1\x83\xe2R\u027bwg\xa0\x06\u05346\x96\u02ca\xe9\x89\x0f\xf3x<\x85\xee\u0400\x00\u07d4n%[p\n\xe7\x13\x8aK\xac\xf2(\x88\xa9\xe2\xc0\n(^\xec\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n'\n\xd5)\xf1\xf0\xb8\xd9\xcbm$'\xec\x1b~-\xc6Jt\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4n.\xab\x85\u0709\xfe)\xdc\n\xa1\x852G\u06b4:R=V\x89\x04V9\x18$O@\x00\x00\u07d4n:Q\xdbt=3M/\xe8\x82$\xb5\xfe|\x00\x8e\x80\xe6$\x89\x05\xbf\v\xa6cOh\x00\x00\u07d4nL*\xb7\xdb\x02i9\xdb\u04fch8J\xf6`\xa6\x18\x16\xb2\x89\t\r\x97/22<\x00\x00\u07d4nM.9\u0203f)\u5d07\xb1\x91\x8af\x9a\xeb\u07556\x8965\u026d\xc5\u07a0\x00\x00\u07d4n\\-\x9b\x1cTj\x86\xee\xfd]\nQ \xc9\xe4\xe70\x19\x0e\x89\n\xd2\x01\xa6yO\xf8\x00\x00\u07d4n`\xae\u19cf\x8e\u068bBLs\xe3S5J\xe6|0B\x89\xbd5\xa4\x8d\x99\x19\xe6\x00\x00\u07d4nd\xe6\x12\x9f\"N7\x8c\x0ensj~z\x06\xc2\x11\xe9\xec\x8965\u026d\xc5\u07a0\x00\x00\u07d4nm[\xbb\xb9\x05;\x89\xd7D\xa2s\x16\u00a7\xb8\xc0\x9bT}\x891Rq\n\x02>m\x80\x00\u07d4nr\xb2\xa1\x18j\x8e)\x16T;\x1c\xb3jh\x87\x0e\xa5\u0457\x89\n\x15D\xbe\x87\x9e\xa8\x00\x00\u07d4nv\x1e\xaa\x0f4_w{TA\xb7:\x0f\xa5\xb5k\x85\xf2-\x89lk\x93[\x8b\xbd@\x00\x00\u07d4ny\xed\u0504[\anL\u060d\x18\x8bnC-\xd9?5\xaa\x893\xc5I\x901r\f\x00\x00\u07d4n\x82\x12\xb7\"\xaf\xd4\b\xa7\xa7>\xd3\xe29^\xe6EJ\x030\x89\b\x9e\x91y\x94\xf7\x1c\x00\x00\u07d4n\x84\x87m\xbb\x95\xc4\vfV\xe4+\xa9\xae\xa0\x8a\x99;T\u0709;\xbc`\xe3\xb6\u02fe\x00\x00\u07d4n\x84\xc2\xfd\x18\xd8\tW\x14\xa9h\x17\x18\x9c\xa2\x1c\xcab\xba\xb1\x89\x12{lp&!\u0340\x00\u07d4n\x86m\x03-@Z\xbd\xd6\\\xf6QA\x1d\x807\x96\xc2#\x11\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94n\x89\x9eY\xa9\xb4\x1a\xb7\xeaA\xdfu\x17\x86\x0f*\xcbY\xf4\xfd\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4n\x89\xc5\x1e\xa6\xde\x13\xe0l\xdct\x8bg\xc4A\x0f\u9f2b\x03\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n\x8a&h\x9fz/\xde\xfd\x00\x9c\xba\xaaS\x10%4P\u06ba\x89o!7\x17\xba\xd8\xd3\x00\x00\u07d4n\x96\xfa\xed\xa3\x05C\x02\xc4_X\xf1a2L\x99\xa3\xee\xbbb\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4n\xb0\xa5\xa9\xae\x96\xd2,\xf0\x1d\x8f\xd6H;\x9f8\xf0\x8c,\x8b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n\xb3\x81\x96\x17@@X&\x8f\f<\xff5\x96\xbf\xe9\x14\x8c\x1c\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\xe0\x94n\xb5W\x8ak\xb7\xc3!S\x19[\r\x80 \xa6\x91HR\xc0Y\x8a\x8b\u00ab\xf4\x02!\xf4\x80\x00\x00\u07d4n\xbb^iW\xaa\x82\x1e\xf6Y\xb6\x01\x8a9:PL\xaeDP\x89lk\x93[\x8b\xbd@\x00\x00\u07d4n\xbc\xf9\x95\u007f_\xc5\u916d\xd4u\";\x04\xb8\xc1Jz\xed\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4n\xc3e\x95q\xb1\x1f\x88\x9d\xd49\xbc\xd4\xd6u\x10\xa2[\xe5~\x89\x06\xaa\xf7\xc8Qm\f\x00\x00\u07d4n\u021b9\xf9\xf5'jU>\x8d\xa3\x0en\xc1z\xa4~\xef\u01c9\x18BO_\v\x1bN\x00\x00\u07d4n\xc9m\x13\xbd\xb2M\u01e5W)?\x02\x9e\x02\xddt\xb9zU\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n\xca\xef\xa6\xfc>\xe54bm\xb0,o\x85\xa0\u00d5W\x1ew\x89 \x86\xac5\x10R`\x00\x00\u07d4n\u04a1+\x02\xf8\u0188\u01f5\u04e6\xea\x14\xd66\x87\u06b3\xb6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4n\u0604E\x9f\x80\x9d\xfa\x10\x16\xe7p\xed\xaf>\x9f\xefF\xfa0\x89\xb8R\xd6x \x93\xf1\x00\x00\xe0\x94n\xdf\u007fR\x83r\\\x95>\xe6C\x17\xf6a\x88\xaf\x11\x84\xb03\x8a\x01\xb4d1\x1dE\xa6\x88\x00\x00\u07d4n\xe8\xaa\xd7\xe0\xa0e\u0605-|;\x9an_\xdcK\xf5\f\x00\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4n\xef\u0705\x0e\x87\xb7\x15\xc7'\x91w<\x03\x16\xc3U\x9bX\xa4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n\xf9\xe8\u0276!}Vv\x9a\xf9}\xbb\x1c\x8e\x1b\x8b\xe7\x99\u0489\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4n\xfb\xa8\xfb*\u0176s\a)\xa9r\xec\"D&\xa2\x87\u00ed\x89\x0fY\x85\xfb\xcb\xe1h\x00\x00\xe0\x94n\xfd\x90\xb55\xe0\v\xbd\x88\x9f\xda~\x9c1\x84\xf8y\xa1Q\u06ca\x02#\x85\xa8'\xe8\x15P\x00\x00\u07d4o\x05\x16f\xcbO{\u04b1\x90r!\xb8)\xb5U\u05e3\xdbt\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4o\x0e\xdd#\xbc\xd8_`\x15\xf9(\x9c(\x84\x1f\xe0L\x83\xef\xeb\x89\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\u07d4o\x13zq\xa6\xf1\x97\xdf,\xbb\xf0\x10\u073d\x89a\t=|,m8\x00\x00\u07d4p\x10\xbe-\xf5{\u042b\x9a\xe8\x19l\xd5\n\xb0\xc5!\xab\xa9\xf9\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4p#\xc7\tV\xe0J\x92\xd7\x00%\xaa\u0497\xb59\xaf5Xi\x89lk\x93[\x8b\xbd@\x00\x00\u07d4p%\x96]+\x88\xda\x19}DY\xbe=\xc98cD\xcc\x1f1\x89l\xb7\xe7Hg\xd5\xe6\x00\x00\u07d4p(\x02\xf3m\x00%\x0f\xabS\xad\xbc\u0596\xf0\x17oc\x8aI\x89lk\x93[\x8b\xbd@\x00\x00\u07d4pH\x19\xd2\xe4Mn\xd1\xda%\xbf\u0384\u011f\u0322V\x13\xe5\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4pJn\xb4\x1b\xa3O\x13\xad\xdd\xe7\xd2\xdb}\xf0I\x15\u01e2!\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4pJ\xb1\x15\r^\x10\xf5\xe3I\x95\b\xf0\xbfpe\x0f\x02\x8dK\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4pJ\xe2\x1dv-n\x1d\xde(\xc25\xd11\x04Yr6\xdb\x1a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4pM$<)x\xe4l,\x86\xad\xbe\xcd$n;)_\xf63\x89m\x12\x1b\xeb\xf7\x95\xf0\x00\x00\u07d4pM]\xe4\x84m9\xb5<\xd2\x1d\x1cI\xf0\x96\xdb\\\x19\xba)\x89\b=lz\xabc`\x00\x00\u07d4p]\xdd85T\x82\xb8\xc7\u04f5\x15\xbd\xa1P\r\xd7\u05e8\x17\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94pan(\x92\xfa&\x97\x05\xb2\x04k\x8f\xe3\xe7/\xa5X\x16\u04ca\x04<3\xc1\x93ud\x80\x00\x00\u07d4pg\x0f\xbb\x05\xd30\x14DK\x8d\x1e\x8ew\x00%\x8b\x8c\xaam\x89lk\x93[\x8b\xbd@\x00\x00\u07d4p\x81\xfak\xaa\xd6\u03f7\xf5\x1b,\xca\x16\xfb\x89p\x99\x1ad\xba\x89\f\xae\xc0\x05\xf6\xc0\xf6\x80\x00\xe0\x94p\x85\xae~~M\x93!\x97\xb5\u01c5\x8c\x00\xa3gF&\xb7\xa5\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4p\x86\xb4\xbd\xe3\xe3]J\xeb$\xb8%\xf1\xa2\x15\xf9\x9d\x85\xf7E\x89lh\xcc\u041b\x02,\x00\x00\u07d4p\x8a*\xf4%\u03b0\x1e\x87\xff\xc1\xbeT\xc0\xf52\xb2\x0e\xac\u0589\aE\u0503\xb1\xf5\xa1\x80\x00\u07d4p\x8e\xa7\a\xba\xe45\u007f\x1e\xbe\xa9Y\u00e2P\xac\u05aa!\xb3\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u0794p\x8f\xa1\x1f\xe3=\x85\xad\x1b\xef\u02ee8\x18\xac\xb7\x1fj}~\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4p\x9101\x16\xd5\xf28\x9b##\x8bMej\x85\x96\u0644\u04c9;N~\x80\xaaX3\x00\x00\u07d4p\x99\xd1/n\xc6V\x89\x9b\x04\x9avW\x06]b\x99h\x92\u0209\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4p\x9f\xe9\xd2\xc1\xf1\xceB |\x95\x85\x04J`\x89\x9f5\x94/\x89lk\x93[\x8b\xbd@\x00\x00\u07d4p\xa05I\xaaah\xe9~\x88\xa5\b3\nZ\v\xeatq\x1a\x89Hz\x9a0E9D\x00\x00\u07d4p\xa4\x06}D\x8c\xc2]\xc8\xe7\x0ee\x1c\xea|\xf8N\x92\x10\x9e\x89\t\x8a}\x9b\x83\x14\xc0\x00\x00\u07d4p\xab4\xbc\x17\xb6o\x9c;c\xf1Q'O*r|S\x92c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4p\xc2\x13H\x8a\x02\f<\xfb9\x01N\xf5\xbad\x04rK\u02a3\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4p\xd2^\xd2\u022d\xa5\x9c\b\x8c\xf7\r\xd2+\xf2\u06d3\xac\xc1\x8a\x899GEE\u4b7c\x00\x00\u07d4p\xe5\xe9\xdas_\xf0w$\x9d\u02da\xaf=\xb2\xa4\x8d\x94\x98\xc0\x8965\u026d\xc5\u07a0\x00\x00\u07d4p\xfe\xe0\x8b\x00\xc6\xc2\xc0Jp\xc0\xce=\x92\u03ca\x01Z\xf1\u05cbX\xc4\x00\x00\x00\u0794q\v\xe8\xfd^)\x18F\x8b\u2abe\xa8\r\x82\x845\u05d6\x12\x88\xf4?\xc2\xc0N\xe0\x00\x00\u07d4q\x13]\x8f\x05\x96<\x90ZJ\a\x92)\t#Z\x89jR\ua262\xa1]\tQ\x9b\xe0\x00\x00\u07d4q\x1e\xcfw\xd7\x1b=\x0e\xa9\\\xe4u\x8a\xfe\u0379\xc11\a\x9d\x89)3\x1eeX\xf0\xe0\x00\x00\u07d4q!?\xca14\x04 N\u02e8q\x97t\x1a\xa9\xdf\xe9c8\x89\x03@\xaa\xd2\x1b;p\x00\x00\xe0\x94q+vQ\x02\x14\xdcb\x0fl:\x1d\u049a\xa2+\xf6\xd2\x14\xfb\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4q/\xf77\n\x13\xed6\ts\xfe\u071f\xf5\xd2\xc9:P^\x9e\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4q3\x84:x\xd99\u019dD\x86\xe1\x0e\xbc{`*4\x9f\xf7\x89\x11\xd5\xca\xcc\xe2\x1f\x84\x00\x00\u07d4qH\xae\xf32a\xd8\x03\x1f\xac?q\x82\xff5\x92\x8d\xafT\u0649\xdeB\xee\x15D\u0750\x00\x00\u07d4qcu\x8c\xbblLR^\x04\x14\xa4\n\x04\x9d\xcc\xcc\xe9\x19\xbb\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4qh\xb3\xbb\x8c\x16s!\u067d\xb0#\xa6\xe9\xfd\x11\xaf\u026f\u0649a\t=|,m8\x00\x00\u07d4qirN\xe7\"q\xc54\xca\xd6B\x0f\xb0N\xe6D\u02c6\xfe\x89\x16<+@\u06e5R\x00\x00\u07d4qj\xd3\xc3:\x9b\x9a\n\x18\x96sW\x96\x9b\x94\xee}*\xbc\x10\x89\x1a!\x17\xfeA*H\x00\x00\xe0\x94qk\xa0\x1e\xad*\x91'\x065\xf9_%\xbf\xaf-\xd6\x10\xca#\x8a\ty\xe7\x01 V\xaax\x00\x00\u07d4qmP\u0320\x1e\x93\x85\x00\xe6B\x1c\xc0p\xc3P|g\u04c7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4qv,cg\x8c\x18\xd1\xc67\x8c\xe0h\xe6f8\x13\x15\x14~\x89lk\x93[\x8b\xbd@\x00\x00\u07d4qxL\x10Q\x17\xc1\xf6\x895y\u007f\xe1Y\xab\xc7NC\xd1j\x89l\x81\u01f3\x11\x95\xe0\x00\x00\xe0\x94qyro\\q\xae\x1bm\x16\xa6\x84(\x17Nk4\xb26F\x8a\x01\x8e\xa2P\t|\xba\xf6\x00\x00\xe0\x94q|\xf9\xbe\xab680\x8d\xed~\x19^\f\x86\x13-\x16?\xed\x8a\x032n\xe6\xf8e\xf4\"\x00\x00\u07d4q\x80\xb8>\xe5WC\x17\xf2\x1c\x80r\xb1\x91\u0615\xd4aS\u00c9\x18\xef\xc8J\xd0\u01f0\x00\x00\u07d4q\x94kq\x17\xfc\x91^\xd1\a8_B\u065d\xda\xc62I\u0089lk\x93[\x8b\xbd@\x00\x00\xe0\x94q\x9e\x89\x1f\xbc\xc0\xa3>\x19\xc1-\xc0\xf0 9\xca\x05\xb8\x01\u07ca\x01OU8F:\x1bT\x00\x00\u07d4q\xc7#\n\x1d5\xbd\u0581\x9e\u0539\xa8\x8e\x94\xa0\xeb\a\x86\u0749\uc80b5=$\x14\x00\x00\u07d4q\xd2\xccm\x02W\x8ce\xf7\r\xf1\x1bH\xbe\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4r\x83\xcdFu\xdaX\u0116UaQ\xda\xfd\x80\xc7\xf9\x95\xd3\x18\x89)3\x1eeX\xf0\xe0\x00\x00\u07d4r\x86\xe8\x9c\xd9\u078fz\x8a\x00\xc8o\xfd\xb59\x92\u0752Q\u0449i*\xe8\x89p\x81\xd0\x00\x00\u07d4r\x8f\x9a\xb0\x80\x15}\xb3\a1V\xdb\xca\x1a\x16\x9e\xf3\x17\x94\a\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4r\x94\xc9\x18\xb1\xae\xfbM%\x92~\xf9\u05d9\xe7\x1f\x93\xa2\x8e\x85\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94r\x94\uc763\x10\xbckK\xbd\xf5C\xb0\xefE\xab\xfc>\x1bM\x8a\x04\xa8\x9fT\xef\x01!\xc0\x00\x00\u07d4r\x9a\xadF'tNS\xf5\xd6c\t\xaatD\x8b:\xcd\xf4o\x89lk\x93[\x8b\xbd@\x00\x00\u07d4r\xa2\xfc\x86u\xfe\xb9r\xfaA\xb5\r\xff\u06fa\xe7\xfa*\u07f7\x89\x9a\xb4\xfcg\xb5(\xc8\x00\x00\u07d4r\xa8&\b&)G&\xa7[\xf3\x9c\u066a\x9e\a\xa3\xea\x14\u0349lk\x93[\x8b\xbd@\x00\x00\u07d4r\xb0Yb\xfb*\u0549\xd6Z\xd1j\"U\x9e\xba\x14X\xf3\x87\x89\a?u\u0460\x85\xba\x00\x00\u07d4r\xb5c?\xe4w\xfeT.t/\xac\xfdi\f\x13xT\xf2\x16\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4r\xb7\xa0=\xda\x14\u029cf\x1a\x1dF\x9f\xd376\xf6s\xc8\xe8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4r\xb9\x04D\x0e\x90\xe7 \u05ac\x1c*\u05dc2\x1d\xcc\x1c\x1a\x86\x89T\x06\x923\xbf\u007fx\x00\x00\xe0\x94r\xb9\nM\xc0\x97#\x94\x92\u0179w}\xcd\x1eR\xba+\xe2\u008a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4r\xbb'\u02d9\xf3\xe2\xc2\u03d0\xa9\x8fp}0\xe4\xa2\x01\xa0q\x89X\xe7\x92n\xe8X\xa0\x00\x00\xe0\x94r\xc0\x83\xbe\xad\xbd\xc2'\xc5\xfbC\x88\x15\x97\xe3.\x83\xc2`V\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4r\xcd\x04\x8a\x11\x05tH)\x83I-\xfb\x1b\xd2yB\xa6\x96\xba\x89lk\x93[\x8b\xbd@\x00\x00\u07d4r\xd0=M\xfa\xb3P\f\xf8\x9b\x86\x86o\x15\xd4R\x8e\x14\xa1\x95\x89\xf3K\x82\xfd\x8e\x91 \x00\x00\u07d4r\u06bb[n\ud799\xbe\x91X\x88\xf6V\x80V8\x16\b\xf8\x89\vL\x96\xc5,\xb4\xfe\x80\x00\u07d4r\xfbI\u009d#\xa1\x89P\u0132\xdc\r\xdfA\x0fS-oS\x89lk\x93[\x8b\xbd@\x00\x00\u07d4r\xfe\xaf\x12EyR9Td[\u007f\xaf\xff\x03x\xd1\xc8$.\x8965\u026d\xc5\u07a0\x00\x00\u07d4s\x01\xdcL\xf2mq\x86\xf2\xa1\x1b\xf8\xb0\x8b\xf2)F?d\xa3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4s\x04G\xf9|\xe9\xb2_\"\xba\x1a\xfb6\xdf'\xf9Xk\ub6c9,s\xc97t,P\x00\x00\u07d4s\x06\xde\x0e(\x8bV\xcf\u07d8~\xf0\xd3\xcc)f\a\x93\xf6\u0749\x1b\x8a\xbf\xb6.\xc8\xf6\x00\x00\xe0\x94s\r\x87c\u01a4\xfd\x82J\xb8\xb8Y\x16\x1e\xf7\xe3\xa9j\x12\x00\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4s\x12\x81sH\x95(\x01.v\xb4\x1a^(\u018b\xa4\xe3\xa9\u050965\u026d\xc5\u07a0\x00\x00\u07d4s\x13F\x12\bETUFTE\xa4Y\xb0l7s\xb0\xeb0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4s/\xea\xd6\x0f{\xfd\u05a9\xde\u0101%\xe3s]\xb1\xb6eO\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4sB#\xd2\u007f\xf2>Y\x06\xca\xed\"YW\x01\xbb4\x83\f\xa1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4sG>r\x11Q\x10\xd0\xc3\xf1\x17\b\xf8nw\xbe+\xb0\x98<\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4sRXm\x02\x1a\xd0\xcfw\xe0\xe9(@JY\xf3t\xffE\x82\x89\xb8Pz\x82\a( \x00\x00\u07d4sU\v\xebs+\xa9\u076f\xdaz\xe4\x06\xe1\x8f\u007f\xeb\x0f\x8b\xb2\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4s[\x97\xf2\xfc\x1b\xd2K\x12\an\xfa\xf3\xd1(\x80s\xd2\f\x8c\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4s^2\x86f\xedV7\x14+3\x06\xb7|\xccT`\xe7,=\x89j\xb8\xf3xy\u0251\x00\x00\u07d4sc\u0350\xfb\xab[\xb8\u011a\xc2\x0f\xc6,9\x8f\xe6\xfbtL\x89lk\x93[\x8b\xbd@\x00\x00\u07d4skDP=\xd2\xf6\xddTi\xffL[-\xb8\xeaO\xece\u0409\x11\x04\xeeu\x9f!\xe3\x00\x00\xe0\x94sk\xf1@,\x83\x80\x0f\x89>X1\x92X*\x13N\xb52\xe9\x8a\x02\x1e\x19\u0493\xc0\x1f&\x00\x00\xe0\x94s\x8c\xa9M\xb7\u038b\xe1\xc3\x05l\u0598\x8e\xb3v5\x9f3S\x8a\x05f[\x96\xcf5\xac\xf0\x00\x00\u07d4s\x91K\"\xfc/\x13\x15\x84$}\x82\xbeO\ucfd7\x8a\u053a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4s\x93'\t\xa9\u007f\x02\u024eQ\xb0\x911(e\x12#\x85\xae\x8e\x89M\x85<\x8f\x89\b\x98\x00\x00\u07d4s\x93\xcb\xe7\xf9\xba!e\xe5\xa7U5\x00\xb6\xe7]\xa3\xc3:\xbf\x89\x05k\xc7^-c\x10\x00\x00\u07d4s\xb4\u0519\xde?8\xbf5\xaa\xf7i\xa6\xe3\x18\xbcm\x126\x92\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94s\xbe\xddo\xda{\xa3'!\x85\b{cQ\xfc\x13=HN7\x8a\x01\x12&\xbf\x9d\xceYx\x00\x00\u07d4s\xbf\xe7q\x0f1\u02b9I\xb7\xa2`O\xbfR9\xce\xe7\x90\x15\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94s\u03c0\xae\x96\x88\xe1X\x0eh\xe7\x82\xcd\b\x11\xf7\xaaIM,\x8a\x01\xa4\xab\xa2%\xc2\a@\x00\x00\xe0\x94s\xd7&\x9f\xf0l\x9f\xfd3uL\xe5\x88\xf7J\x96j\xbb\xbb\xba\x8a\x01e\xc9fG\xb3\x8a \x00\x00\u07d4s\xd8\xfe\xe3\u02c6M\xce\"\xbb&\u029c/\bm^\x95\xe6;\x8965\u026d\xc5\u07a0\x00\x00\u07d4s\xdf<>yU\xf4\xf2\xd8Y\x83\x1b\xe3\x80\x00\xb1\ak8\x84\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4s\u48b6\f\U0010e2ef+w~\x17Z[\x1eM\f-\x8f\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94t\n\xf1\xee\xfd3e\u05cb\xa7\xb1,\xb1\xa6s\xe0j\arF\x8a\x04+\xf0kx\xed;P\x00\x00\xe0\x94t\v\xfdR\xe0\x16g\xa3A\x9b\x02\x9a\x1b\x8eEWj\x86\xa2\u06ca\x03\x8e\xba\xd5\xcd\xc9\x02\x80\x00\x00\u07d4t\x0fd\x16\x14w\x9d\u03e8\x8e\xd1\xd4%\xd6\r\xb4*\x06\f\xa6\x896\"\xc6v\b\x10W\x00\x00\u07d4t\x12\u027c0\xb4\xdfC\x9f\x021\x00\xe69$\x06j\xfdS\xaf\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4t\x16\x93\xc3\x03vP\x85\x13\b \xcc+c\xe9\xfa\x92\x13\x1b\x89A\rXj \xa4\xc0\x00\x00\u07d4t!\xce[\xe3\x81s\x8d\u0703\xf0&!\x97O\xf0hly\xb8\x89Xx\x8c\xb9K\x1d\x80\x00\x00\u07d4t1j\xdf%7\x8c\x10\xf5v\u0574\x1aoG\xfa\x98\xfc\xe3=\x89\x128\x13\x1e\\z\xd5\x00\x00\u07d4t6Q\xb5^\xf8B\x9d\xf5\f\xf8\x198\xc2P\x8d\xe5\u0207\x0f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4t=\xe5\x00&\xcag\xc9M\xf5O\x06b`\xe1\xd1J\xcc\x11\xac\x89lk\x93[\x8b\xbd@\x00\x00\u07d4tE /\ft)z\x00N\xb3rj\xa6\xa8-\xd7\xc0/\xa1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4tK\x03\xbb\xa8X*\xe5I\x8e-\xc2-\x19\x94\x94g\xabS\xfc\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4tL\fw\xba\u007f#i \xd1\xe44\xde]\xa3>H\xeb\xf0,\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4tP\xff\u007f\x99\xea\xa9\x11bu\u07ach\xe4(\xdf[\xbc\u0639\x89lk\x93[\x8b\xbd@\x00\x00\u07d4tV\u0172\xc5Cn>W\x10\b\x93?\x18\x05\xcc\xfe4\xe9\xec\x8965\u026d\xc5\u07a0\x00\x00\u07d4tZ\u04eb\xc6\xee\xeb$qh\x9bS\x9ex\x9c\xe2\xb8&\x83\x06\x89=A\x94\xbe\xa0\x11\x92\x80\x00\xe0\x94tZ\xec\xba\xf9\xbb9\xb7Jg\xea\x1c\xe6#\xde6\x84\x81\xba\xa6\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4t\\\xcf-\x81\x9e\u06fd\u07a8\x11{\\I\xed<*\x06n\x93\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4tb\u021c\xaa\x9d\x8dx\x91\xb2T]\xef!otd\u057b!\x89\x05\xea\xedT\xa2\x8b1\x00\x00\u07d4td\x8c\xaa\xc7H\xdd\x13\\\xd9\x1e\xa1L(\xe1\xbdM\u007f\xf6\xae\x89\xa8\r$g~\xfe\xf0\x00\x00\xe0\x94tq\xf7.\xeb0\x06$\xeb(.\xabM\x03r\x00\x00\x00\xe0\x94t\x84\xd2k\xec\xc1\xee\xa8\xc61^\xc3\xee\nE\x01\x17\u0706\xa0\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4t\x86:\xce\xc7]\x03\xd5>\x86\x0ed\x00/,\x16^S\x83w\x8965\u026d\xc5\u07a0\x00\x00\u07d4t\x89\u030a\xbeu\u0364\xef\r\x01\xce\xf2`^G\xed\xa6z\xb1\x89\a?u\u0460\x85\xba\x00\x00\u07d4t\x8c(^\xf1#?\xe4\xd3\x1c\x8f\xb17\x833r\x1c\x12\xe2z\x89lk\x93[\x8b\xbd@\x00\x00\u07d4t\x90\x87\xac\x0fZ\x97\xc6\xfa\xd0!S\x8b\xf1\xd6\u0361\x8e\r\xaa\x8965\u026d\xc5\u07a0\x00\x00\u07d4t\x95\xaex\xc0\xd9\x02a\xe2\x14\x0e\xf2\x061\x04s\x1a`\xd1\xed\x89\x01\xdbPq\x89%!\x00\x00\u07d4t\x9aJv\x8b_#rH\x93\x8a\x12\xc6#\x84{\xd4\xe6\x88\u0709\x03\xe73b\x87\x14 \x00\x00\u07d4t\x9a\xd6\xf2\xb5pk\xbe/h\x9aD\u0136@\xb5\x8e\x96\xb9\x92\x89\x05k\xc7^-c\x10\x00\x00\u07d4t\xa1\u007f\x06K4N\x84\xdbce\u0695\x91\xff\x16(%vC\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4t\xae\xec\x91]\xe0\x1c\u019b,\xb5\xa65o\xee\xa1FX\xc6\u0149\f\x9a\x95\xee)\x86R\x00\x00\u07d4t\xaf\xe5I\x02\xd6\x15x%v\xf8\xba\xac\x13\xac\x97\f\x05\x0fn\x89\t\xa1\xaa\xa3\xa9\xfb\xa7\x00\x00\u07d4t\xb7\xe0\"\x8b\xae\xd6YW\xae\xbbM\x91m3:\xae\x16O\x0e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4t\xbcJ^ E\xf4\xff\x8d\xb1\x84\xcf:\x9b\f\x06Z\xd8\a\u0489lk\x93[\x8b\xbd@\x00\x00\u07d4t\xbc\xe9\xec86-l\x94\u032c&\xd5\xc0\xe1:\x8b;\x1d@\x8965&A\x04B\xf5\x00\x00\u07d4t\xbfzZ\xb5\x92\x93\x14\x9b\\`\xcf6Bc\xe5\xeb\xf1\xaa\r\x89\x06G\f>w\x1e<\x00\x00\xe0\x94t\xc7<\x90R\x8a\x15s6\xf1\xe7\xea b\n\xe5?\xd2G(\x8a\x01\xe6:.S\x8f\x16\xe3\x00\x00\u07d4t\u0464\xd0\xc7RN\x01\x8dN\x06\xed;d\x80\x92\xb5\xb6\xaf,\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94t\xd3f\xb0{/VG}|pw\xaco\xe4\x97\xe0\xebeY\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4t\xd3zQt{\xf8\xb7q\xbf\xbfC\x9493\xd1\x00\xd2\x14\x83\x8965\u026d\xc5\u07a0\x00\x00\u07d4t\xd6q\u065c\xbe\xa1\xabW\x90cu\xb6?\xf4+PE\x1d\x17\x8965\u026d\xc5\u07a0\x00\x00\u07d4t\xeb\xf4BVF\xe6\u03c1\xb1\t\xce{\xf4\xa2\xa6=\x84\x81_\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4t\xed3\xac\xf4?5\xb9\x8c\x920\xb9\xe6d.\xcbS0\x83\x9e\x89$\xf6\xdf\xfbI\x8d(\x00\x00\u07d4t\xef(i\xcb\xe6\b\x85`E\xd8\xc2\x04\x11\x18W\x9f\"6\xea\x89\x03<\xd6E\x91\x95n\x00\x00\u07d4t\xfcZ\x99\xc0\xc5F\x05\x03\xa1;\x05\tE\x9d\xa1\x9c\xe7\u0350\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4u\v\xbb\x8c\x06\xbb\xbf$\bC\xccux.\xe0/\b\xa9tS\x89-C\xf3\xeb\xfa\xfb,\x00\x00\u07d4u\x14\xad\xbd\xc6?H?0M\x8e\x94\xb6\u007f\xf30\x9f\x18\v\x82\x89!\u0120n-\x13Y\x80\x00\u0794u\x17\xf1l(\xd12\xbb@\xe3\xba6\u01ae\xf11\xc4b\xda\x17\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4u\x1a,\xa3Nq\x87\xc1c\u048e6\x18\xdb(\xb1<\x19m&\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94u\x1a\xbc\xb6\xcc\x030Y\x91\x18\x15\xc9o\u04516\n\xb0D-\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4u&\xe4\x82R\x9f\n\x14\xee\u0248q\xdd\xdd\x0er\x1b\f\u0662\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4u)\xf3y{\xb6\xa2\x0f~\xa6I$\x19\xc8L\x86vA\xd8\x1c\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94u*^\xe22a,\xd3\x00_\xb2n[Y}\xe1\x9fwk\xe6\x8a\x01'\xfc\xb8\xaf\xae \xd0\x00\x00\u07d4u,\x9f\xeb\xf4/f\xc4x{\xfa~\xb1|\xf53;\xbaPp\x89j\x99\xf2\xb5O\xddX\x00\x00\u07d4u930F\u07b1\xef\x8e\u07b9\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94u\xc1\xad#\xd2?$\xb3\x84\xd0\xc3\x14\x91w\xe8f\x97a\r!\x8a\x01\\[\xcdl(\x8b\xbd\x00\x00\u07d4u\xc2\xff\xa1\xbe\xf5I\x19\xd2\t\u007fz\x14-.\x14\xf9\xb0JX\x89\x90\xf3XP@2\xa1\x00\x00\u07d4u\xd6|\xe1N\x8d)\xe8\xc2\xff\u3051{\x93\v\x1a\xff\x1a\x87\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4u\xde~\x93R\xe9\v\x13\xa5\x9aXx\xff\xec\u01c3\x1c\xacM\x82\x89\x94\x89#z\u06daP\x00\x00\u07d4u\xf7S\x9d0\x9e\x909\x98\x9e\xfe.\x8b-\xbd\x86Z\r\xf0\x88\x89\x85[[\xa6\\\x84\xf0\x00\x00\u07d4v\b\xf47\xb3\x1f\x18\xbc\vd\u04c1\xae\x86\xfd\x97\x8e\u05f3\x1f\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94v\x0f\xf35N\x0f\u0793\x8d\x0f\xb5\xb8,\xef[\xa1\\=)\x16\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4v\x1an6,\x97\xfb\xbd|Yw\xac\xba-\xa7F\x876_I\x89\t\xf7J\xe1\xf9S\xd0\x00\x00\u07d4v\x1el\xae\xc1\x89\xc20\xa1b\xec\x00e0\x19>g\u03dd\x19\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94v\x1f\x8a:*\U00028f7e\x1d\xa0\t2\x1f\xb2\x97d\xebb\xa1\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4v)\x98\xe1\xd7R'\xfc\xedzp\xbe\x10\x9aL\vN\xd8d\x14\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4v-o0\u06b9\x915\xe4\xec\xa5\x1dRC\xd6\xc8b\x11\x02\u0549\x0fI\x89A\xe6d(\x00\x00\u07d4v3\x1e0yl\xe6d\xb2p\x0e\rASp\x0e\u0706\x97w\x89lk\x93[\x8b\xbd@\x00\x00\u07d4v8\x86\xe33\xc5o\xef\xf8[\xe3\x95\x1a\xb0\xb8\x89\xce&.\x95\x89lk\x93[\x8b\xbd@\x00\x00\u07d4v:|\xba\xb7\rzd\u0427\xe5)\x80\xf6\x81G%\x93I\f\x89 \x86\xac5\x10R`\x00\x00\u07d4v>\xec\u0c0a\u021e2\xbf\xa4\xbe\xcev\x95\x14\xd8\xcb[\x85\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4v@\xa3\u007f\x80R\x98\x15\x15\xbc\xe0x\u0693\xaf\xa4x\x9bW4\x89lk\x93[\x8b\xbd@\x00\x00\u0794vA\xf7\xd2j\x86\xcd\xdb+\xe10\x81\x81\x0e\x01\xc9\xc8E\x89dI\xe8NG\xa8\xa8\x00\x00\xe0\x94vO\xc4mB\x8bm\xbc\"\x8a\x0f_U\xc9P\x8cw.\xab\x9f\x8a\x05\x81v{\xa6\x18\x9c@\x00\x00\u07d4vPn\xb4\xa7\x80\xc9Q\xc7J\x06\xb0=;\x83b\xf0\x99\x9dq\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94v[\xe2\xe1/b\x9ecI\xb9}!\xb6*\x17\xb7\xc80\xed\xab\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94vb\x81P\xe2\x99[['\x9f\xc8>\r\xd5\xf1\x02\xa6q\xdd\x1c\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4vk7Y\xe8yN\x92m\xacG=\x91:\x8f\xb6\x1a\xd0\xc2\u0249\x04\xb0m\xbb\xb4\x0fJ\x00\x00\u07d4vp\xb0/,<\xf8\xfdOG0\xf38\x1aq\xeaC\x1c3\u01c9\x0e~\xeb\xa3A\vt\x00\x00\u07d4vz\x03eZ\xf3`\x84\x1e\x81\r\x83\xf5\xe6\x1f\xb4\x0fL\xd1\x13\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4vz\u0190y\x1c.#E\x10\x89\xfelp\x83\xfeU\u07b6+\x89,s\xc97t,P\x00\x00\u07d4v\u007f\xd7y}Qi\xa0_sd2\x1c\x19\x84:\x8c4\x8e\x1e\x89\x01\x04\xe7\x04d\xb1X\x00\x00\u0794v\x84o\r\xe0;Zv\x97\x1e\xad)\x8c\xdd\b\x84:K\xc6\u0188\xd7\x1b\x0f\u088e\x00\x00\xe0\x94v\x84\x98\x93N7\xe9\x05\xf1\xd0\xe7{D\xb5t\xbc\xf3\xecJ\xe8\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4v\x8c\xe0\u06a0)\xb7\xde\xd0\"\xe5\xfcWM\x11\xcd\xe3\xec\xb5\x17\x89\x11t\xa5\xcd\xf8\x8b\xc8\x00\x00\xe0\x94v\x93\xbd\xebo\xc8+[\xcar\x13U\"1u\xd4z\bKM\x8a\x04\xa8\x9fT\xef\x01!\xc0\x00\x00\u07d4v\xaa\xf8\xc1\xac\x01/\x87R\xd4\xc0\x9b\xb4f\a\xb6e\x1d\\\xa8\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4v\xab\x87\xddZ\x05\xad\x83\x9aN/\xc8\xc8Z\xa6\xba\x05d\x170\x89lk\x93[\x8b\xbd@\x00\x00\u07d4v\xaf\xc2%\xf4\xfa0}\xe4\x84U+\xbe\x1d\x9d?\x15\aLJ\x89\xa2\x90\xb5\u01ed9h\x00\x00\xe0\x94v\xbe\xca\xe4\xa3\x1d6\xf3\xcbW\u007f*CYO\xb1\xab\xc1\xbb\x96\x8a\x05C\xa9\xce\x0e\x132\xf0\x00\x00\u07d4v\xc2u5\xbc\xb5\x9c\xe1\xfa-\x8c\x91\x9c\xab\xebJk\xba\x01\u0449lk\x93[\x8b\xbd@\x00\x00\u07d4v\xca\"\xbc\xb8y\x9eS'\u012a*}\tI\xa1\xfc\xce_)\x89R\xa0?\"\x8cZ\xe2\x00\x00\u07d4v\xca\u0108\x11\x1aO\u0555\xf5h\xae:\x85\x87p\xfc\x91]_\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94v\u02dc\x8bi\xf48vu\u0102S\xe24\xcb~\rt\xa4&\x8a\x01\x90\xf4H.\xb9\x1d\xae\x00\x00\u07d4v\xf8:\xc3\xda0\xf7\t&(\xc73\x9f \x8b\xfc\x14,\xb1\ue25a\x18\xff\xe7B}d\x00\x00\xe0\x94v\xf9\xad=\x9b\xbd\x04\xae\x05\\\x14w\xc0\xc3^u\x92\xcb* \x8a\b\x83?\x11\xe3E\x8f \x00\x00\u07d4v\xff\xc1W\xadk\xf8\xd5m\x9a\x1a\u007f\u077c\x0f\xea\x01\n\xab\xf4\x8965\u026d\xc5\u07a0\x00\x00\u07d4w\x02\x8e@\x9c\xc4:;\xd3=!\xa9\xfcS\xec`n\x94\x91\x0e\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4w\f/\xb2\u0128\x17S\xac\x01\x82\xeaF\x0e\xc0\x9c\x90\xa5\x16\xf8\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4w\r\x98\xd3\x1bCS\xfc\xee\xe8V\fL\u03c0>\x88\xc0\xc4\xe0\x89 \x86\xac5\x10R`\x00\x00\xe0\x94w\x13\xab\x807A\x1c\t\xbah\u007fo\x93d\xf0\xd3#\x9f\xac(\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4w\x15\a\xae\xeej%]\xc2\u035d\xf5QT\x06-\b\x97\xb2\x97\x89\x12\x1e\xa6\x8c\x11NQ\x00\x00\u07d4w\x19\x88\x87\x95\xadtY$\xc7W`\u0771\x82}\xff\xd8\u0368\x89lkLM\xa6\u077e\x00\x00\u07d4w'\xaf\x10\x1f\n\xab\xa4\xd2:\x1c\xaf\xe1|n\xb5\u06b1\xc6\u0709lk\x93[\x8b\xbd@\x00\x00\u07d4w,)\u007f\n\u0454H.\xe8\xc3\xf06\xbd\xeb\x01\xc2\x01\xd5\u0309\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94w0o\xfe.J\x8f<\xa8&\xc1\xa2I\xf7!-\xa4:\xef\xfd\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4w1A\x12}\x8c\xf3\x18\xae\xbf\x886Z\xdd=U'\xd8[j\x8966\u05ef^\u024e\x00\x00\u07d4wF\xb6\xc6i\x9c\x8f4\xca'h\xa8 \xf1\xff\xa4\xc2\a\xfe\x05\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4wQ\xf3c\xa0\xa7\xfd\x053\x19\b\t\u076f\x93@\xd8\xd1\x12\x91\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4wW\xa4\xb9\xcc=\x02G\u032a\xeb\x99\t\xa0\xe5n\x1d\xd6\xdc\u0089\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4w\\\x10\xc9>\r\xb7 [&CE\x823\xc6O\xc3?\xd7[\x89lk\x93[\x8b\xbd@\x00\x00\u07d4wa~\xbcK\xeb\xc5\xf5\xdd\xeb\x1bzp\xcd\xebj\xe2\xff\xa0$\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4wiC\xff\xb2\xef\\\xdd5\xb8<(\xbc\x04k\xd4\xf4gp\x98\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94wp\x1e,I=\xa4|\x1bX\xf4!\xb5I]\xeeE\xbe\xa3\x9b\x8a\x01H\xf6I\xcfaB\xa5\x80\x00\u07d4wy\x8f \x12W\xb9\xc3R\x04\x95pW\xb5Ft\xae\xfaQ\u07c9\b\x13\xcaV\x90m4\x00\x00\u07d4w\x8cC\xd1\x1a\xfe;Xo\xf3t\x19-\x96\xa7\xf2=+\x9b\u007f\x89\x8b\xb4\xfc\xfa;}k\x80\x00\u07d4w\x8cy\xf4\xde\x19S\xeb\u0398\xfe\x80\x06\xd5:\x81\xfbQ@\x12\x8963\x03\"\xd5#\x8c\x00\x00\u07d4w\x92t\xbf\x18\x03\xa36\xe4\u04f0\r\u0753\xf2\xd4\xf5\xf4\xa6.\x8965\u026d\xc5\u07a0\x00\x00\u07d4w\xa1q\"\xfa1\xb9\x8f\x17\x11\xd3*\x99\xf0>\xc3&\xf3=\b\x89\\(=A\x03\x94\x10\x00\x00\u07d4w\xa3I\a\xf3\x05\xa5L\x85\xdb\t\xc3c\xfd\xe3\xc4~j\xe2\x1f\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4w\xa7i\xfa\xfd\xec\xf4\xa68v-[\xa3\x96\x9d\xf61 \xa4\x1d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4w\xbekd\xd7\xc73\xa46\xad\xec^\x14\xbf\x9a\xd7@+\x1bF\x8965\u026d\xc5\u07a0\x00\x00\u07d4w\xbf\xe9<\u0367P\x84~A\xa1\xaf\xfe\xe6\xb2\u0696\xe7!N\x89\x10CV\x1a\x88)0\x00\x00\u07d4w\u0126\x97\xe6\x03\xd4+\x12\x05l\xbb\xa7a\xe7\xf5\x1d\x04C\xf5\x89$\xdc\xe5M4\xa1\xa0\x00\x00\u07d4w\xcc\x02\xf6#\xa9\u03d8S\t\x97\xeag\xd9\\;I\x18Y\xae\x89Is\x03\xc3n\xa0\xc2\x00\x00\u07d4w\xd4?\xa7\xb4\x81\xdb\xf3\xdbS\f\xfb\xf5\xfd\xce\xd0\xe6W\x181\x89lk\x93[\x8b\xbd@\x00\x00\u07d4w\xda^lr\xfb6\xbc\xe1\xd9y\x8f{\xcd\xf1\u044fE\x9c.\x89\x016\x95\xbbl\xf9>\x00\x00\u07d4w\xf4\xe3\xbd\xf0V\x88<\xc8r\x80\xdb\xe6@\xa1\x8a\r\x02\xa2\a\x89\n\x81\x99:+\xfb[\x00\x00\u0794w\xf6\t\u0287 \xa0#&,U\xc4o-&\xfb90\xaci\x88\xf0\x15\xf2W6B\x00\x00\u07d4w\xf8\x1b\x1b&\xfc\x84\xd6\u0797\uf2df\xbdr\xa310\xccJ\x8965\u026d\xc5\u07a0\x00\x00\u07d4x\x19\xb0E\x8e1N+S\xbf\xe0\f8I_\u0539\xfd\xf8\u0589\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4x\x1b\x15\x01dz.\x06\xc0\xedC\xff\x19\u007f\xcc\xec5\xe1p\v\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4x/R\xf0\xa6v\xc7w\x16\xd5t\xc8\x1e\xc4hO\x9a\x02\n\x97\x89.\x14\xe2\x06\xb70\xad\x80\x00\u07d4x5]\xf0\xa20\xf8=\x03,p1TAM\xe3\xee\u06b5W\x89lk\x93[\x8b\xbd@\x00\x00\u07d4x6\xf7\xefk\u01fd\x0f\xf3\xac\xafD\x9c\x84\xddk\x1e,\x93\x9f\x89\xe0\x8d\xe7\xa9,\xd9|\x00\x00\u07d4x7\xfc\xb8v\xda\x00\xd1\xeb;\x88\xfe\xb3\xdf?\xa4\x04/\xac\x82\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4x>\uc2a5\xda\xc7{.f#\xedQ\x98\xa41\xab\xba\xee\a\x89\x17\xda:\x04\u01f3\xe0\x00\x00\u07d4x\\\x8e\xa7t\xd70D\xa74\xfay\n\x1b\x1et>w\xed|\x89\f\xf1Rd\f\\\x83\x00\x00\u07d4x`\xa3\xde8\xdf8*\xe4\xa4\xdc\xe1\x8c\f\a\xb9\x8b\xce=\xfa\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94xcCq\xe1s\x04\xcb\xf39\xb1E*L\xe48\xdcvL\u038a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4xd\u0719\x9f\xe4\xf8\xe0\x03\xc0\xf4=\xec\u00da\xae\x15\"\xdc\x0f\x89\x05\x1e\x10+\xd8\xec\xe0\x00\x00\u07d4xtj\x95\x8d\xce\xd4\xc7d\xf8vP\x8cAJh4,\uce49\x02\xbe7O\xe8\xe2\xc4\x00\x00\xe0\x94x}1?\xd3k\x05>\xee\xae\xdb\xcet\xb9\xfb\x06x32\x89\x8a\x05\xc0X\xb7\x84'\x19`\x00\x00\u07d4x\x85\x9c[T\x8bp\r\x92\x84\xce\xe4\xb6c=GJ\x8a\x04{\x92\xc4\x15B$-\n\b\xc7\x0f\x99\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4x\u03c36\xb3(\xdb=\x87\x81:G+\x9e\x89\xb7^\f\xf3\xbc\x8965\u026d\xc5\u07a0\x00\x00\u07d4x\xd4\xf8\xc7\x1c\x1eh\xa6\x9a\x98\xf5/\xcbE\u068a\xf5n\xa1\xa0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4x\xdf&\x81\xd6\xd6\x02\xe2!B\xd5A\x16\u07a1]EIW\xaa\x89\x10'\x94\xad \xdah\x00\x00\u07d4x\xe0\x8b\xc53A<&\u2473\x14?\xfa|\u026f\xb9{x\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4x\xe8?\x80\xb3g\x8cz\nN>\x8c\x84\xdc\xcd\xe0dBbw\x89a\t=|,m8\x00\x00\u07d4x\xf5\xc7G\x85\xc5f\x8a\x83\x80r\x04\x8b\xf8\xb4SYM\u06ab\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4y\x0f\x91\xbd]\x1c\\\xc4s\x9a\xe9\x13\x00\u06c9\xe1\xc10<\x93\x89lk\x93[\x8b\xbd@\x00\x00\u07d4y\x17\u5f42\xa9y\x0f\xd6P\xd0C\xcd\xd90\xf7y\x963\u06c9\xd8\xd4`,&\xbfl\x00\x00\u07d4y\x19\xe7b\u007f\x9b}T\xea;\x14\xbbM\xd4d\x9fO9\xde\xe0\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4y\x1f`@\xb4\xe3\xe5\r\xcf5S\xf1\x82\u0357\xa9\x060\xb7]\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4y0\xc2\xd9\xcb\xfa\x87\xf5\x10\xf8\xf9\x87w\xff\x8a\x84H\xcaV)\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4yE)\u041d\x01rq5\x970\x02pu\xb8z\xd8=\xaen\x89\x10\xce\x1d=\x8c\xb3\x18\x00\x00\u07d4yKQ\u00deS\xd9\xe7b\xb0a;\x82\x9aD\xb4r\xf4\xff\xf3\x89$5\xe0dxA\u0300\x00\xe0\x94yU\x1c\xed\xe3v\xf7G\xe3ql\x8dy@\rvm.\x01\x95\x8a\t\xcb7\xaf\xa4\xffxh\x00\x00\u07d4y^\xbc&&\xfc9\xb0\xc8b\x94\xe0\xe87\xdc\xf5#U0\x90\x8965\u026d\xc5\u07a0\x00\x00\u07d4yn\xbb\xf4\x9b>6\xd6v\x94\xady\xf8\xff6vz\xc6\xfa\xb0\x89\x03K\xc4\xfd\xde'\xc0\x00\x00\u07d4yo\x87\xbaaz)0\xb1g\v\xe9.\xd1(\x1f\xb0\xb3F\xe1\x89\x06\xf5\xe8o\xb5((\x00\x00\u07d4yt'\xe3\xdb\xf0\xfe\xaez%\x06\xf1-\xf1\xdc@2n\x85\x05\x8965\u026d\xc5\u07a0\x00\x00\u07d4yu\x10\xe3\x86\xf5c\x93\xce\xd8\xf4w7\x8aDLHO}\xad\x8965\u026d\xc5\u07a0\x00\x00\u07d4y{\xb7\xf1W\xd9\xfe\xaa\x17\xf7m\xa4\xf7\x04\xb7M\xc1\x03\x83A\x89\xb5\x0f\u03ef\xeb\xec\xb0\x00\x00\u07d4y\x88\x90\x131\xe3\x87\xf7\x13\xfa\u03b9\x00\\\xb9\xb6Q6\xeb\x14\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4y\x89\u041f8&\xc3\u5bccu*\x81\x15r:\x84\xd8\tp\x89\x16\x86\xf8aL\xf0\xad\x00\x00\xe0\x94y\x95\xbd\x8c\xe2\xe0\xc6{\xf1\u01e51\xd4w\xbc\xa1\xb2\xb9ua\x8a\x01BH\xd6\x17\x82\x9e\xce\x00\x00\u07d4y\xae\xb3Ef\xb9t\xc3ZX\x81\xde\xc0 \x92}\xa7\xdf]%\x89lk\x93[\x8b\xbd@\x00\x00\u07d4y\xb1 \xeb\x88\x06s#!(\x8fgZ'\xa9\"_\x1c\xd2\ub245\xa0\xbf7\xde\xc9\xe4\x00\x00\u07d4y\xb4\x8d-a7\u00c5Ma\x1c\x01\xeaBBz\x0fY{\xb7\x89\nZ\xa8P\t\xe3\x9c\x00\x00\u07d4y\xb8\xaa\xd8y\xdd0V~\x87x\xd2\xd21\xc8\xf3z\xb8sN\x89lk\x93[\x8b\xbd@\x00\x00\u07d4y\xbf/{n2\x8a\xaf&\xe0\xbb\t?\xa2-\xa2\x9e\xf2\xf4q\x89a\t=|,m8\x00\x00\u07d4y\xc10\xc7b\xb8v[\x19\u04ab\u0260\x83\xab\x8f:\xady@\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4y\xc1\xbe\x19q\x1fs\xbe\xe4\xe61j\xe7T\x94Y\xaa\u03a2\xe0\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4y\xc6\x00/\x84R\xca\x15\u007f\x13\x17\xe8\n/\xaf$GUY\xb7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4y\xca\xc6IO\x11\xef'\x98t\x8c\xb52\x85\xbd\x8e\"\xf9|\u0689lk\x93[\x8b\xbd@\x00\x00\u07d4y\u03e9x\n\xe6\xd8{,1\x88?\t'i\x86\u021ag5\x8965\u026d\xc5\u07a0\x00\x00\u07d4y\u06e2VG-\xb4\xe0X\xf2\xe4\xcd\xc3\xeaN\x8aBw83\x89O%\x91\xf8\x96\xa6P\x00\x00\u07d4y\xed\x10\xcf\x1fm\xb4\x82\x06\xb5\t\x19\xb9\xb6\x97\b\x1f\xbd\xaa\xf3\x89lk\x93[\x8b\xbd@\x00\x00\u0794y\xf0\x8e\x01\xce\t\x88\xe6<\u007f\x8f)\b\xfa\xdeC\xc7\xf9\xf5\u0248\xfc\x93c\x92\x80\x1c\x00\x00\u07d4y\xfdmH1Pf\xc2\x04\xf9e\x18i\xc1\tl\x14\xfc\x97\x81\x89lk\x93[\x8b\xbd@\x00\x00\u0794y\xff\xb4\xac\x13\x81*\vx\u0123{\x82u\">\x17k\xfd\xa5\x88\xf0\x15\xf2W6B\x00\x00\u07d4z\x05\x89\xb1C\xa8\xe5\xe1\a\u026cf\xa9\xf9\xf8Yz\xb3\u7ac9Q\xe92\xd7n\x8f{\x00\x00\u07d4z\nx\xa9\xcc9?\x91\xc3\xd9\xe3\x9ak\x8c\x06\x9f\a^k\xf5\x89Hz\x9a0E9D\x00\x00\u07d4z\x13p\xa7B\xec&\x87\xe7a\xa1\x9a\u0167\x942\x9e\xe6t\x04\x89\xa2\xa12ga\xe2\x92\x00\x00\xe0\x94z-\xfcw\x0e$6\x811\xb7\x84w\x95\xf2\x03\xf3\xd5\r[V\x8a\x02i\xfe\xc7\xf06\x1d \x00\x00\u07d4z3\x83N\x85\x83s>-R\xae\xadX\x9b\u046f\xfb\x1d\xd2V\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94z6\xab\xa5\xc3\x1e\xa0\xca~'{\xaa2\xecF\u0393\xcfu\x06\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94z8\x11\"\xba\xday\x1az\xb1\xf6\x03}\xac\x80C'S\xba\xad\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94zH\xd8w\xb6:\x8f\x8f\x93\x83\xe9\xd0\x1eS\xe8\fR\x8e\x95_\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4zO\x9b\x85\x06\x90\xc7\xc9F\x00\xdb\xee\f\xa4\xb0\xa4\x11\xe9\xc2!\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4zc\x86\x9f\xc7g\xa4\u01b1\xcd\x0e\x06I\xf3cL\xb1!\xd2K\x89\x043\x87Oc,\xc6\x00\x00\u07d4zg\xdd\x04:PO\xc2\xf2\xfcq\x94\xe9\xbe\xcfHL\xec\xb1\xfb\x89\r\x8drkqw\xa8\x00\x00\xe0\x94zk&\xf48\u0663RD\x91U\xb8\x87l\xbd\x17\xc9\u065bd\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4zmx\x1cw\u013a\x1f\xca\xdfhsA\xc1\xe3\x17\x99\xe9='\x89\x0e\u0683\x8cI)\b\x00\x00\u07d4zph\xe1\xc37\\\x0eY\x9d\xb1\xfb\xe6\xb2\xea#\xb8\xf4\a\u0489lk\x93[\x8b\xbd@\x00\x00\u07d4zt\xce\xe4\xfa\x0fcp\xa7\x89O\x11l\xd0\f\x11G\xb8>Y\x89+^:\xf1k\x18\x80\x00\x00\u07d4zy\xe3\x0f\xf0W\xf7\n=\x01\x91\xf7\xf5?v\x157\xaf}\xff\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94zzO\x80sW\xa4\xbb\xe6\x8e\x1a\xa8\x0692\x10\xc4\x11\u0333\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4z\x85c\x86y\x01 o?+\xf0\xfa>\x1c\x81\t\u02bc\u0345\x89\amA\xc6$\x94\x84\x00\x00\xe0\x94z\x87\x97i\n\xb7{Tp\xbf|\f\x1b\xbaa%\b\xe1\xac}\x8a\x01\xe0\x92\x96\xc37\x8d\xe4\x00\x00\u07d4z\x8c\x89\xc0\x14P\x9dV\u05f6\x810f\x8f\xf6\xa3\xec\xecsp\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94z\x94\xb1\x99\x92\u03b8\xcec\xbc\x92\xeeKZ\xde\xd1\fM\x97%\x8a\x03\x8d\x1a\x80d\xbbd\xc8\x00\x00\u07d4z\xa7\x9a\xc0C\x16\u030d\b\xf2\x00e\xba\xa6\xd4\x14(\x97\xd5N\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4z\xadM\xbc\u04ec\xf9\x97\u07d3XiV\xf7+d\u062d\x94\xee\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94z\xb2V\xb2\x04\x80\n\xf2\x017\xfa\xbc\xc9\x16\xa22Xu%\x01\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4z\xbaV\xf6:H\xbc\b\x17\u05b9p9\x03\x9az\xd6/\xae.\x89 \x86\xac5\x10R`\x00\x00\xe0\x94z\xbb\x10\xf5\xbd\x9b\xc3;\x8e\xc1\xa8-d\xb5[k\x18wuA\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4z\u010d@\xc6d\u031am\x89\xf1\xc5\xf5\xc8\n\x1cp\xe7D\u6263\x10b\xbe\xee\xd7\x00\x00\x00\u07d4z\u014fo\xfcO\x81\a\xaen07\x8eN\x9f\x99\xc5\u007f\xbb$\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4z\xd3\xf3\aao\x19\u0731C\xe6DM\xab\x9c<3a\x1fR\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4z\xd8,\xae\xa1\xa8\xb4\xed\x051\x9b\x9c\x98p\x17<\x81N\x06\xee\x89!d\xb7\xa0J\u0220\x00\x00\u07d4z\xde]f\xb9D\xbb\x86\f\x0e\xfd\xc8bv\u054fFS\xf7\x11\x89lk\x93[\x8b\xbd@\x00\x00\u07d4z\xdf\xed\xb0m\x91\xf3\xccs\x90E\v\x85U\x02p\x88<{\xb7\x89\x11x\xfa@Q]\xb4\x00\x00\u07d4z\xe1\xc1\x9eS\xc7\x1c\xeeLs\xfa\xe2\xd7\xfcs\xbf\x9a\xb5\u348965\u026d\xc5\u07a0\x00\x00\u07d4z\xe6Y\xeb;\xc4hR\xfa\x86\xfa\xc4\xe2\x1cv\x8dP8\x89E\x89\x0f\x81\f\x1c\xb5\x01\xb8\x00\x00\u07d4z\xea%\xd4+&\x12(n\x99\xc56\x97\u01bcA\x00\xe2\u06ff\x89lk\x93[\x8b\xbd@\x00\x00\u07d4z\xef{U\x1f\v\x9cF\xe7U\xc0\xf3\x8e[:s\xfe\x11\x99\xf5\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4{\v1\xffn$t^\xad\x8e\u067b\x85\xfc\v\xf2\xfe\x1dU\u0509+^:\xf1k\x18\x80\x00\x00\xe0\x94{\x0f\xea\x11v\xd5!Y3:\x14<)IC\xda6\xbb\u0774\x8a\x01\xfc}\xa6N\xa1L\x10\x00\x00\u07d4{\x11g<\xc0\x19bk)\f\xbd\xce&\x04o~m\x14\x1e!\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4{\x12!b\xc9\x13\xe7\x14l\xad\v~\xd3z\xff\xc9*\v\xf2\u007f\x89Q\xaf\tk#\x01\u0440\x00\u07d4{\x1b\xf5:\x9c\xbe\x83\xa7\u07a44W\x9f\xe7*\xac\x8d*\f\u0409\n\xd4\xc81j\v\f\x00\x00\u07d4{\x1d\xaf\x14\x89\x1b\x8a\x1e\x1b\xd4)\u0633k\x9aJ\xa1\u066f\xbf\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4{\x1f\xe1\xabM\xfd\x00\x88\xcd\xd7\xf6\x01c\xefY\xec*\xee\x06\xf5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4{%\xbb\x9c\xa8\xe7\x02!~\x933\"RP\xe5<6\x80MH\x89e\xea=\xb7UF`\x00\x00\u07d4{'\xd0\xd1\xf3\xdd<\x14\x02\x94\xd0H\x8bx>\xbf@\x15'}\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94{@\a\xc4^ZW?\u06f6\xf8\xbdtk\xf9J\xd0J<&\x8a\x038!\xf5\x13]%\x9a\x00\x00\u07d4{C\xc7\xee\xa8\xd6#U\xb0\xa8\xa8\x1d\xa0\x81\xc6Dk3\xe9\xe0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4{M*8&\x90i\xc1\x85Ww\rY\x1d$\xc5\x12\x1f^\x83\x89%\xf2s\x93=\xb5p\x00\x00\xe0\x94{au\xec\x9b\xef\xc78$\x955\xdd\xde4h\x8c\xd3n\xdf%\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94{f\x12hy\x84M\xfa4\xfee\xc9\xf2\x88\x11\u007f\xef\xb4I\xad\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4{j\x84q\x8d\xd8nc3\x84)\xac\x81\x1d|\x8a\x86\x0f!\xf1\x89a\t=|,m8\x00\x00\xe0\x94{q,z\xf1\x16v\x00jf\xd2\xfc\\\x1a\xb4\xc4y\xce`7\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4{s$-u\u029a\xd5X\xd6P)\r\xf1v\x92\xd5L\u0638\x89lnY\xe6|xT\x00\x00\u07d4{v\x1f\xeb\u007f\u03e7\xde\xd1\xf0\xeb\x05\x8fJ`\v\xf3\xa7\b\u02c9\xf9]\xd2\xec'\xcc\xe0\x00\x00\xe0\x94{\x82|\xae\u007f\xf4t\t\x18\xf2\xe00\xab&\u02d8\xc4\xf4l\xf5\x8a\x01\x94hL\v9\xde\x10\x00\x00\xe0\x94{\x892\x86B~r\xdb!\x9a!\xfcM\xcd_\xbfY(<1\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4{\x92&\xd4o\xe7Q\x94\v\xc4\x16\xa7\x98\xb6\x9c\xcf\r\xfa\xb6g\x89\u3bb5sr@\xa0\x00\x00\u07d4{\x98\xe2<\xb9k\xee\xe8\n\x16\x80i\ube8f \xed\xd5\\\u03c9\v\xa0\xc9\x15\x87\xc1J\x00\x00\u07d4{\xb0\xfd\xf5\xa6c\xb5\xfb\xa2\x8d\x9c\x90*\xf0\xc8\x11\xe2R\xf2\x98\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4{\xb9W\x1f9K\v\x1a\x8e\xbaVd\xe9\u0635\xe8@g{\xea\x89\x01\x11du\x9f\xfb2\x00\x00\xe0\x94{\xb9\x84\xc6\u06f9\xe2y\x96j\xfa\xfd\xa5\x9c\x01\xd0&'\xc8\x04\x8a\x01\xb4d1\x1dE\xa6\x88\x00\x00\u07d4{\xbb\xec^p\xbd\xea\u063b2\xb4(\x05\x98\x8e\x96H\xc0\xaa\x97\x8966\u05ef^\u024e\x00\x00\u07d4{\xca\x1d\xa6\xc8\nf\xba\xa5\xdbZ\u0245A\u013e'kD}\x89$\xcf\x04\x96\x80\xfa<\x00\x00\u07d4{\u0772\xee\x98\xde\x19\xeeL\x91\xf6a\xee\x8eg\xa9\x1d\x05K\x97\x8965\u026d\xc5\u07a0\x00\x00\u0794{\xe2\xf7h\f\x80-\xa6\x15L\x92\xc0\x19J\xe72Qzqi\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4{\xe7\xf2Eiq\x88;\x9a\x8d\xbeL\x91\xde\xc0\x8a\xc3N\x88b\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4{\xe8\u0334\xf1\x1bf\xcan\x1dW\xc0\xb59b!\xa3\x1b\xa5:\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94{\xeb\x81\xfb/^\x91Rk*\xc9y^v\u019b\xcf\xf0K\xc0\x8a\x0e\xb2.yO\n\x8d`\x00\x00\u07d4|\b\x83\x05L-\x02\xbcz\x85+\x1f\x86\xc4'w\xd0\xd5\xc8V\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4|\x0f^\a C\xc9\xeet\x02B\x19~x\xccK\x98\xcd\xf9`\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4|\x1d\xf2JO\u007f\xb2\u01f4r\xe0\xbb\x00l\xb2}\xcd\x16AV\x8965\u026d\xc5\u07a0\x00\x00\u07d4|)\xd4}W\xa73\xf5k\x9b!pc\xb5\x13\xdc;1Y#\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4|+\x96\x03\x88JO.FN\u03b9}\x17\x93\x8d\x82\x8b\xc0,\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4|8,\x02\x96a.N\x97\xe4@\xe0-8q';U\xf5;\x89\n\xb6@9\x12\x010\x00\x00\u07d4|>\xb7\x13\xc4\xc9\xe08\x1c\xd8\x15L|\x9a}\xb8d\\\xde\x17\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4|D\x01\xae\x98\xf1.\xf6\xde9\xae$\u03df\xc5\x1f\x80\xeb\xa1k\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4|E\xf0\xf8D*V\xdb\u04dd\xbf\x15\x99\x95A\\R\xedG\x9b\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94|S-\xb9\xe0\xc0l&\xfd@\xac\xc5j\xc5\\\x1e\xe9-<:\x8a?\x87\bW\xa3\xe0\xe3\x80\x00\x00\u07d4|`\xa0_zJ_\x8c\xf2xC\x916.uZ\x83A\xefY\x89f\x94\xf0\x18*7\xae\x00\x00\u07d4|`\xe5\x1f\v\xe2(\xe4\xd5o\xdd)\x92\xc8\x14\xdaw@\u01bc\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4|i$\xd0|>\xf5\x89\x19f\xfe\nxV\xc8{\xef\x9d 4\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94|\x8b\xb6Zo\xbbI\xbdA3\x96\xa9\xd7\xe3\x10S\xbb\xb3z\xa9\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94|\x9a\x11\f\xb1\x1f%\x98\xb2\xb2\x0e,\xa4\x002^A\xe9\xdb3\x8a\x05\x81v{\xa6\x18\x9c@\x00\x00\u07d4|\xbc\xa8\x8f\xcaj\x00`\xb9`\x98\\\x9a\xa1\xb0%4\xdc\"\b\x89\x19\x12z\x13\x91\xea*\x00\x00\u07d4|\xbe\xb9\x992\xe9~n\x02\x05\x8c\xfcb\u0432k\xc7\u0325+\x89lk\x93[\x8b\xbd@\x00\x00\u07d4|\xc2Jj\x95\x8c \xc7\xd1$\x96`\xf7Xb&\x95\v\r\x9a\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4|\xd2\x0e\u0335\x18\xb6\f\xab\t[r\x0fW\x15p\u02aaD~\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4|\xd5\xd8\x1e\xab7\xe1\x1ebv\xa3\xa1\t\x12Q`~\r~8\x89\x03hM^\xf9\x81\xf4\x00\x00\u07d4|\xdft!9E\x95=\xb3\x9a\xd0\xe8\xa9x\x1a\xddy.M\x1d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4|\xe4hdF\U000547be\xd6r\x15\xeb\rZ\x1d\xd7,\x11\xb8\x89x9\xd3!\xb8\x1a\xb8\x00\x00\u07d4|\xefMC\xaaA\u007f\x9e\xf8\xb7\x87\xf8\xb9\x9dS\xf1\xfe\xa1\ue209g\x8a\x93 b\xe4\x18\x00\x00\u07d4}\x03P\xe4\v3\x8d\xdasfa\x87+\xe3?\x1f\x97R\xd7U\x89\x02\xb4\xf5\xa6\U00051500\x00\xe0\x94}\x04\xd2\xed\xc0X\xa1\xaf\xc7a\xd9\u025a\xe4\xfc\\\x85\xd4\u0226\x8aB\xa9\xc4g\\\x94g\xd0\x00\x00\u07d4}\v%^\xfbW\xe1\x0fp\b\xaa\"\xd4\x0e\x97R\xdf\xcf\x03x\x89\x01\x9f\x8euY\x92L\x00\x00\xe0\x94}\x13\xd6pX\x84\xab!W\u074d\xccpF\xca\xf5\x8e\xe9K\xe4\x8a\x1d\r\xa0|\xbb>\xe9\xc0\x00\x00\u07d4}'>c~\xf1\xea\u0101\x11\x94\x13\xb9\x1c\x98\x9d\xc5\xea\xc1\"\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4}*R\xa7\xcf\f\x846\xa8\xe0\a\x97kl&\xb7\"\x9d\x1e\x15\x89\x17\xbf\x06\xb3*$\x1c\x00\x00\u07d4}4\x805i\xe0\v\u05b5\x9f\xff\b\x1d\xfa\\\n\xb4\x19zb\x89\\\xd8|\xb7\xb9\xfb\x86\x00\x00\u07d4}4\xffY\xae\x84\nt\x13\u01baL[\xb2\xba,u\xea\xb0\x18\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4}9(R\xf3\xab\xd9/\xf4\xbb[\xb2l\xb6\bt\xf2\xbeg\x95\x8966\xc2^f\xec\xe7\x00\x00\u07d4}DRg\u015a\xb8\u04a2\xd9\xe7\t\x99\x0e\th%\x80\u011f\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94}U\x13\x97\xf7\x9a)\x88\xb0d\xaf\xd0\xef\xeb\xee\x80,w!\xbc\x8a\bW\xe0\xd6\xf1\xdav\xa0\x00\x00\u07d4}Z\xa3?\xc1KQ\x84\x1a\x06\x90n\xdb+\xb4\x9c*\x11ri\x89\x10D\x00\xa2G\x0eh\x00\x00\xe0\x94}]/s\x94\x9d\xad\xda\bV\xb2\x06\x98\x9d\xf0\a\x8dQ\xa1\xe5\x8a\x02\xc4:H\x1d\xf0M\x01wb\xed\xcb\\\xaab\x9bZ\x89\x02\"\xc8\xeb?\xf6d\x00\x00\u07d4~\x8f\x96\xcc)\xf5{\tu\x12\f\xb5\x93\xb7\u0743=`kS\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4~\x97*\x8a|*D\xc9;!Cl8\xd2\x1b\x92R\xc3E\xfe\x89a\t=|,m8\x00\x00\u07d4~\x99\u07fe\x98\x9d;\xa5)\u0457Q\xb7\xf41\u007f\x89S\xa3\xe2\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4~\xa0\xf9n\xe0\xa5s\xa30\xb5h\x97v\x1f=L\x010\xa8\xe3\x89Hz\x9a0E9D\x00\x00\u0794~\xa7\x91\xeb\xab\x04E\xa0\x0e\xfd\xfcNJ\x8e\x9a~ue\x13m\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4~\xab\xa05\xe2\xaf7\x93\xfdtgK\x10%@\xcf\x19\n\u0779\x89E\x02l\x83[`D\x00\x00\xe0\x94~\xb4\xb0\x18\\\x92\xb6C\x9a\b\xe72!h\xcb5<\x8awJ\x8a\x02'\x19l\xa0I\x83\xca\x00\x00\xe0\x94~\xbd\x95\xe9\xc4p\xf7(5\x83\xdcn\x9d,M\xce\v\ua3c4\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4~\u0425\xa8G\xbe\xf9\xa9\xda|\xba\x1dd\x11\xf5\xc3\x161&\x19\x89\x02(\xeb7\xe8u\x1d\x00\x00\u07d4~\xda\xfb\xa8\x98K\xafc\x1a\x82\vk\x92\xbb\xc2\xc56U\xf6\xbd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4~\xdb\x02\xc6\x1a\"r\x87a\x1a\xd9Pici\xccNdzh\x89\x0e\u0683\x8cI)\b\x00\x00\u07d4~\xe5\u0280]\xce#\xaf\x89\xc2\xd4D\xe7\xe4\af\xc5Lt\x04\x89\r\v\xd4\x12\xed\xbd\x82\x00\x00\xe0\x94~\xe6\x04\u01e9\xdc)\t\xce2\x1d\u6e72OWgWuU\x8a\x01+\xf9\u01d8\\\xf6-\x80\x00\u07d4~\xf1o\xd8\xd1[7\x8a\x0f\xba0k\x8d\x03\u0758\xfc\x92a\x9f\x89%\xf2s\x93=\xb5p\x00\x00\u07d4~\xf9\x8bR\xbe\xe9S\xbe\xf9\x92\xf3\x05\xfd\xa0'\xf8\x91\x1cXQ\x89\x1b\xe7\" i\x96\xbc\x80\x00\u07d4~\xfc\x90vj\x00\xbcR7,\xac\x97\xfa\xbd\x8a<\x83\x1f\x8e\u0349\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4~\xfe\xc0\xc6%<\xaf9\u007fq(|\x1c\a\xf6\xc9X+[\x86\x89\x1a,\xbc\xb8O0\u0540\x00\u07d4\u007f\x01\xdc|7G\xca`\x8f\x98=\xfc\x8c\x9b9\xe7U\xa3\xb9\x14\x89\v8l\xad_zZ\x00\x00\u07d4\u007f\x06b\xb4\x10)\x8c\x99\xf3\x11\u04e1EJ\x1e\xed\xba/\xeav\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\u007f\x06\u021dY\x80\u007f\xa6\v\xc6\x016\xfc\xf8\x14\u02ef%C\xbd\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u007f\v\x90\xa1\xfd\u050f'\xb2h\xfe\xb3\x83\x82\xe5]\xdbP\xef\x0f\x892\xf5\x1e\u06ea\xa30\x00\x00\u07d4\u007f\x0e\xc3\u06c0F\x92\xd4\xd1\xea2E6Z\xab\x05\x90\a[\u0109\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u007f\x0f\x04\xfc\xf3zS\xa4\xe2N\xden\x93\x10Nx\xbe\x1d<\x9e\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\u007f\x13\xd7`I\x8dq\x93\xcahY\xbc\x95\xc9\x018d#\xd7l\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\u007f\x15\n\xfb\x1aw\u00b4Y(\xc2h\xc1\u9f74d\x1dG\u0609lk\x93[\x8b\xbd@\x00\x00\u07d4\u007f\x16\x19\x98\x8f7\x15\xe9O\xf1\xd2S&-\xc5X\x1d\xb3\xde\x1c\x890\xca\x02O\x98{\x90\x00\x00\u07d4\u007f\x1c\x81\xee\x16\x97\xfc\x14K|\v\xe5I;V\x15\xae\u007f\xdd\u0289\x1b\x1d\xaba\u04ead\x00\x00\u07d4\u007f#\x82\xff\xd8\xf89VFy7\xf9\xbar7F#\xf1\x1b8\x89 \x86\xac5\x10R`\x00\x00\u07d4\u007f7\t9\x1f?\xbe\xba5\x92\xd1u\xc7@\xe8z\tT\x1d\x02\x89\x1a\x05V\x90\xd9\u06c0\x00\x00\u07d4\u007f8\x9c\x12\xf3\xc6\x16OdFVlwf\x95\x03\xc2y%'\x89\x05V\xf6L\x1f\xe7\xfa\x00\x00\xe0\x94\u007f:\x1eE\xf6~\x92\u0200\xe5s\xb43y\xd7\x1e\xe0\x89\xdbT\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\u007f=r\x03\u0224G\xf7\xbf6\u060a\xe9\xb6\x06*^\xeex\xae\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\u007fF\xbb%F\r\xd7\xda\xe4!\x1c\xa7\xf1Z\xd3\x12\xfc}\xc7\\\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\u007fI\xe7\xa4&\x98\x82\xbd\x87\"\u0526\xf5f4v)b@y\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\u007fI\xf2\a&G\x1a\xc1\u01e8>\xf1\x06\xe9w\\\xebf%f\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4\u007fK^'\x85x\xc0F\xcc\xea\xf6W0\xa0\xe0h2\x9e\u0576\x89e\xea=\xb7UF`\x00\x00\u07d4\u007fOY;a\x8c3\v\xa2\xc3\xd5\xf4\x1e\xce\xeb\x92\xe2~Bl\x89\x96n\xdcuk|\xfc\x00\x00\u07d4\u007fT\x14\x91\u04ac\x00\xd2a/\x94\xaa\u007f\v\xcb\x01FQ\xfb\u0509\x14b\fW\xdd\xda\xe0\x00\x00\u07d4\u007fZ\xe0Z\xe0\xf8\xcb\xe5\xdf\xe7!\xf0D\u05e7\xbe\xf4\xc2y\x97\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\u007f`:\xec\x17Y\xea_\a\xc7\xf8\xd4\x1a\x14(\xfb\xba\xf9\xe7b\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u007falo\x00\x8a\u07e0\x82\xf3M\xa7\xd0e\x04`6\x80u\xfb\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u007fa\xfal\xf5\xf8\x98\xb4@\xda\u016b\xd8`\rmi\x1f\xde\xf9\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\xe0\x94\u007fe\\g\x89\xed\xdfE\\\xb4\xb8\x80\x99r\x0698\x9e\ubb0a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\u007fk(\u0204!\xe4\x85~E\x92\x81\u05c4ai$\x89\xd3\xfb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u007fn\xfboC\x18\x87m.\xe6$\xe2u\x95\xf4DF\xf6\x8e\x93\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\u007fq\x92\xc0\xdf\x1c}\xb6\xd9\xede\xd7\x11\x84\xd8\xe4\x15Z\x17\xba\x89\x04Sr\x8d3\x94,\x00\x00\u07d4\u007fz:!\xb3\xf5\xa6]\x81\xe0\xfc\xb7\xd5-\xd0\n\x1a\xa3m\xba\x89\x05k\xc7^-c\x10\x00\x00\u07d4\u007f\x8d\xbc\xe1\x80\xed\x9cV65\xaa\xd2\xd9{L\xbcB\x89\x06\u0649\x90\xf54`\x8ar\x88\x00\x00\xe0\x94\u007f\x99=\xdb~\x02\u0082\xb8\x98\xf6\x15_h\x0e\xf5\xb9\xaf\xf9\a\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\u007f\x9f\x9bV\xe4(\x9d\xfbX\xe7\x0f\xd5\xf1*\x97\xb5m5\u01a5\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\u007f\xa3~\xd6x\x87u\x1aG\x1f\x0e\xb3\x06\xbeD\xe0\xdb\xcd`\x89\x899vt\u007f\xe1\x1a\x10\x00\x00\u07d4\u007f\xaa0\xc3\x15\x19\xb5\x84\xe9rP\xed*<\xf38^\xd5\xfdP\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u007f\xcf[\xa6fo\x96lTH\xc1{\xf1\xcb\v\xbc\xd8\x01\x9b\x06\x89\x05k\xc3\u042e\xbeI\x80\x00\xe0\x94\u007f\xd6y\xe5\xfb\r\xa2\xa5\xd1\x16\x19M\xcbP\x83\x18\xed\u0140\xf3\x8a\x01c\x9eI\xbb\xa1b\x80\x00\x00\u07d4\u007f\u06e01\u01cf\x9c\tmb\xd0Z6\x9e\uac3c\xccU\u5257\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\u007f\xdb\u00e8D\xe4\r\x96\xb2\xf3\xa652.`e\xf4\xca\x0e\x84\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u007f\xdf\u020dx\xbf\x1b(Z\xc6O\x1a\xdb5\xdc\x11\xfc\xb09Q\x89|\x06\xfd\xa0/\xb06\x00\x00\u07d4\u007f\xea\x19b\xe3]b\x05\x97h\xc7I\xbe\u0756\u02b90\xd3x\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u007f\xef\x8c8w\x9f\xb3\a\xeco\x04K\xeb\xe4\u007f<\xfa\xe7\x96\xf1\x89\t#@\xf8l\xf0\x9e\x80\x00\u07d4\u007f\xf0\xc6?p$\x1b\xec\xe1\x9bs~SA\xb1+\x10\x901\u0609\x12\xc1\xb6\xee\xd0=(\x00\x00\xe0\x94\u007f\xfa\xbf\xbc9\f\xbeC\u0389\x18\x8f\bh\xb2}\xcb\x0f\f\xad\x8a\x01YQ\x82\"K&H\x00\x00\xe0\x94\u007f\xfd\x02\xed7\fp`\xb2\xaeS\xc0x\xc8\x01!\x90\u07fbu\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u0794\x80\x02*\x12\a\xe9\x10\x91\x1f\xc9(I\xb0i\xab\f\xda\xd0C\u04c8\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\x80\t\xa7\xcb\u0452\xb3\xae\u052d\xb9\x83\xd5(ER\xc1ltQ\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x80\x0e}c\x1cnW:\x903/\x17\xf7\x1f_\u045bR\x8c\xb9\x89\b=lz\xabc`\x00\x00\u07d4\x80\x15m\x10\ufa320\u0254\x10c\r7\xe2i\xd4\t<\xea\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x80\x172\xa4\x81\u00c0\xe5~\xd6-l)\u0799\x8a\xf3\xfa;\x13\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x80\x1de\xc5\x18\xb1\x1d\x0e?OG\x02!Ap\x13\xc8\xe5>\u0149\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x80&CZ\xacr\x8dI{\x19\xb3\xe7\xe5|(\xc5c\x95O+\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\x80-\xc3\xc4\xff-}\x92^\u215fJ\x06\u05fa`\xf10\x8c\x89\x05P\x94\f\x8f\xd3L\x00\x00\u07d4\x800\xb1\x11\u0198?\x04\x85\u076c\xa7b$\xc6\x18\x064x\x9f\x89\x04V9\x18$O@\x00\x00\u07d4\x805\xbc\xff\xae\xfd\xee\xea5\x83\fI}\x14(\x9d6 #\u0789\x10CV\x1a\x88)0\x00\x00\u07d4\x805\xfeNkj\xf2z\u44a5xQ^\x9d9\xfao\xa6[\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x80C\xed\"\xf9\x97\u58a4\xc1n6D\x86\xaed\x97V\x92\u0109=I\x04\xff\xc9\x11.\x80\x00\u07d4\x80C\xfd\u043cL\x97=\x16c\xd5_\xc15P\x8e\xc5\xd4\xf4\xfa\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x80L\xa9IrcOc:Q\xf3V\v\x1d\x06\xc0\xb2\x93\xb3\xb1\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x80R-\u07d4N\xc5.'\xd7$\xedL\x93\xe1\xf7\xbe`\x83\u0589\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x80Y\x1aB\x17\x9f4\xe6M\x9d\xf7]\xcdF;(hoUt\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x80\\\xe5\x12\x97\xa0y;\x81 g\xf0\x17\xb3\xe7\xb2\u07db\xb1\xf9\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\x80]\x84o\xb0\xbc\x02\xa73r&\u0585\xbe\x9e\xe7s\xb9\x19\x8a\x8a\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4\x80c7\x9a{\xf2\u02d2:\x84\xc5\t>h\xda\xc7\xf7T\x81\u0149\x11v\x10.n2\xdf\x00\x00\u07d4\x80hTX\x8e\xcc\xe5AI_\x81\u008a)\x03s\xdf\x02t\xb2\x89\x1f\x8c\xdf\\n\x8dX\x00\x00\u07d4\x80oD\xbd\xebh\x807\x01^\x84\xff!\x80I\xe3\x823*3\x89l]\xb2\xa4\xd8\x15\xdc\x00\x00\u07d4\x80tF\x18\xde9jT1\x97\xeeH\x94\xab\xd0c\x98\xdd|'\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x80w\xc3\xe4\xc4EXn\tL\xe1\x02\x93\u007f\xa0[s{V\x8c\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x80\x90\u007fY1H\xb5|F\xc1w\xe2=%\xab\u012a\xe1\x83a\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x80\x97s\x16\x94NYB\xe7\x9b\x0e:\xba\u04cd\xa7F\be\x19\x89\x02\x1auJm\xc5(\x00\x00\xe0\x94\x80\xa0\xf6\xcc\x18l\xf6 \x14\x00sn\x06Z9\x1fR\xa9\xdfJ\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x80\xab\xecZ\xa3n\\\x9d\t\x8f\x1b\x94(\x81\xbdZ\xca\u0196=\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x80\xb2=8\v\x82\\F\xe098\x99\xa8UVF-\xa0\u1309lk\x93[\x8b\xbd@\x00\x00\u07d4\x80\xb4-\xe1p\xdb\xd7#\xf4T\xe8\x8fw\x16E-\x92\x98P\x92\x89\x10F#\xc0v-\xd1\x00\x00\u07d4\x80\xb7\x9f3\x83\x90\u047a\x1b77\xa2\x9a\x02W\xe5\xd9\x1e\a1\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x80\xbf\x99^\u063a\x92p\x1d\x10\xfe\u011f\x9e}\x01M\xbe\xe0&\x89\x1f\x047\xca\x1a~\x12\x80\x00\u07d4\x80\xc0N\xfd1\x0fD\x04\x83\xc7?tK[\x9edY\x9c\xe3\xec\x89A\rXj \xa4\xc0\x00\x00\u07d4\x80\u00e9\xf6\x95\xb1m\xb1Yr\x86\u0473\xa8\xb7il9\xfa'\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x80\xc5>\xe7\xe35\u007f\x94\xce\rxh\x00\x9c \x8bJ\x13\x01%\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x80\xcc!\xbd\x99\xf3\x90\x05\u014f\xe4\xa4H\x90\x92 !\x8ff\u02c966\xc9yd6t\x00\x00\u07d4\x80\xd5\xc4\fY\xc7\xf5N\xa3\xa5_\xcf\xd1uG\x1e\xa3P\x99\xb3\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x80\xda/\u0762\x9a\x9e'\xf9\xe1\x15\x97^i\xae\x9c\xfb\xf3\xf2~\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x80\xe7\xb3 R0\xa5f\xa1\xf0a\xd9\"\x81\x9b\xb4\xd4\u04a0\xe1\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\x80\xea\x1a\xcc\x13n\xcaKh\xc8B\xa9Z\xdfk\u007f\xee~\xb8\xa2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x80\xf0z\xc0\x9e{,<\n=\x1e\x94\x13\xa5D\xc7:A\xbe\u02c9\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x81\r\xb2Vu\xf4^\xa4\xc7\xf3\x17\u007f7\xce)\xe2-g\x99\x9c\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x81\x13\x9b\xfd\u0326V\xc40 ?r\x95\x8cT;e\x80\xd4\f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x81\x14a\xa2\xb0\u0290\xba\xda\xc0j\x9e\xa1nx{3\xb1\x96\u0309\b\xe3\xf5\v\x17<\x10\x00\x00\u07d4\x81\x16M\xeb\x10\x81J\xe0\x83\x91\xf3,\bf{bH\xc2}z\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\x81\x18i1\x18A7\xd1\x19*\u020c\xd3\xe1\xe5\xd0\xfd\xb8jt\x89\x9d5\x95\xab$8\xd0\x00\x00\u0794\x81*U\xc4<\xae\xdcYr\x187\x90\x00\xceQ\rT\x886\xfd\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x81.\xa7\xa3\xb2\xc8n\xed2\xffO,sQL\xc6;\xac\xfb\u038965\u026d\xc5\u07a0\x00\x00\u07d4\x814\xdd\x1c\x9d\xf0\xd6\u0225\x81$&\xbbU\xc7a\u0283\x1f\b\x89\x06\xa2\x16\v\xb5|\xcc\x00\x00\u07d4\x81A5\u068f\x98\x11\aW\x83\xbf\x1a\xb6pb\xaf\x8d>\x9f@\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x81I\x8c\xa0{\x0f/\x17\xe8\xbb\xc7\xe6\x1a\u007fJ\xe7\xbef\xb7\x8b\x89\x05\x81\xfb\xb5\xb3;\xb0\x00\x00\u07d4\x81Um\xb2sI\xab\x8b'\x00ID\xedP\xa4n\x94\x1a\x0f_\x89\u063beI\xb0+\xb8\x00\x00\u07d4\x81U\xfalQ\xeb1\xd8\bA-t\x8a\xa0\x86\x10P\x18\x12/\x89e\xea=\xb7UF`\x00\x00\xe0\x94\x81V6\v\xbd7\ta\xce\xcakf\x91\xd7P\x06\xad L\xf2\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\x81a\xd9@\xc3v\x01\x00\xb9\b\x05)\xf8\xa6\x03%\x03\x0fn\u0709\x10CV\x1a\x88)0\x00\x00\xe0\x94\x81d\xe7\x83\x14\xae\x16\xb2\x89&\xccU=,\xcb\x16\xf3V'\r\x8a\x01\xca\x13N\x95\xfb2\xc8\x00\x00\u07d4\x81e\u02b0\xea\xfbZ2\x8f\xc4\x1a\xc6M\xaeq[.\xef,e\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x81h\xed\xce\u007f)a\xcf)[\x9f\xcdZE\xc0l\xde\xdan\xf5\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x81m\x97r\xcf\x119\x91\x16\xcc\x1er\xc2lgt\xc9\xed\xd79\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x81s\xc85djg.\x01R\xbe\x10\xff\xe8Ab\xdd%nL\x89\x1a\xab\xdf!E\xb40\x00\x00\u07d4\x81t\x93\u035b\xc6#p*$\xa5o\x9f\x82\xe3\xfdH\xf3\xcd1\x89\x9eK#\xf1-L\xa0\x00\x00\u07d4\x81y\xc8\tp\x18,\u0177\xd8*M\xf0n\xa9M\xb6:%\xf3\x89'o%\x9d\xe6k\xf4\x00\x00\u07d4\x81z\xc3;\xd8\xf8GVsr\x95\x1fJ\x10\u05e9\x1c\xe3\xf40\x89\n\xd7\xc4\x06\xc6m\xc1\x80\x00\xe0\x94\x81\x8f\xfe'\x1f\u00d75e\xc3\x03\xf2\x13\xf6\xd2\u0689\x89~\xbd\x8a\x016\xe0SB\xfe\u1e40\x00\u07d4\x81\x97\x94\x81!s.c\xd9\xc1H\x19N\xca\xd4n0\xb7I\u0209\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x81\x9a\xf9\xa1\xc2s2\xb1\xc3i\xbb\xda\x1b=\xe1\xc6\xe93\xd6@\x89\x11\t\xe6T\xb9\x8fz\x00\x00\xe0\x94\x81\x9c\u06a506x\xef|\xecY\u050c\x82\x16:\xcc`\xb9R\x8a\x03\x13QT_y\x81l\x00\x00\u07d4\x81\x9e\xb4\x99\vZ\xbaUG\t=\xa1+k<\x10\x93\xdfmF\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x81\xa8\x81\x96\xfa\xc5\xf2<>\x12\xa6\x9d\xecK\x88\x0e\xb7\xd9s\x10\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x81\xbc\xcb\xff\x8fD4~\xb7\xfc\xa9['\xce|\x95$\x92\xaa\xad\x89\b@\xc1!e\xddx\x00\x00\u07d4\x81\xbdu\xab\xd8e\xe0\xc3\xf0J\vO\xdb\xcbt\xd3@\x82\xfb\xb7\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x81\xc1\x8c*#\x8d\xdcL\xba#\n\a-\xd7\xdc\x10\x1eb\x02s\x89Hz\x9a0E9D\x00\x00\u07d4\x81\xc9\xe1\xae\xe2\xd36]S\xbc\xfd\u0356\xc7\xc58\xb0\xfd~\xec\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\x81\u03edv\t\x13\xd3\xc3\"\xfc\xc7{I\u00ae9\a\xe7On\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94\x81\xd6\x19\xffW&\xf2@_\x12\x90Lr\xeb\x1e$\xa0\xaa\xeeO\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x81\xef\u25aev\xc8`\xd1\xc5\xfb\xd3=G\xe8\u0399\x96\xd1W\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x81\xf8\xde,(=_\u052f\xbd\xa8]\xed\xf9v\x0e\xab\xbb\xb5r\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x82\f\x19)\x11\x96P[e\x05\x9d\x99\x14\xb7\t\v\xe1\u06c7\u0789\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4\x82\x1c\xb5\xcd\x05\xc7\uf41f\xe1\xbe`s=\x89c\xd7`\xdcA\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x82\x1dy\x8a\xf1\x99\x89\u00ee[\x84\xa7\xa7(<\xd7\xfd\xa1\xfa\xbe\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x82\x1e\xb9\t\x94\xa2\xfb\xf9K\xdc23\x91\x02\x96\xf7o\x9b\xf6\xe7\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x82$\x9f\xe7\x0fa\u01b1o\x19\xa3$\x84\x0f\xdc\x02\x021\xbb\x02\x8a\x02\x036\xb0\x8a\x93c[\x00\x00\u07d4\x82(\xeb\xc0\x87H\x0f\xd6EG\xca(\x1f^\xac\xe3\x04\x14S\xb9\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94\x82)\u03b9\xf0\xd7\b9I\x8dD\xe6\xab\xed\x93\xc5\xca\x05\x9f]\x8a\x1a\x1c\x1b<\x98\x9a \x10\x00\x00\u07d4\x82.\xdf\xf66V:a\x06\xe5.\x9a%\x98\xf7\xe6\xd0\xef'\x82\x89\x01\xf4\xf9i=B\u04c0\x00\u07d4\x822\x19\xa2Yv\xbb*\xa4\xaf\x8b\xadA\xac5&\xb4\x936\x1f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x822\xd1\xf9t.\u07cd\xd9'\xda5;*\xe7\xb4\xcb\xceu\x92\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\x824\xf4c\u0444\x85P\x1f\x8f\x85\xac\xe4\x97,\x9bc-\xbc\u0309lk\x93[\x8b\xbd@\x00\x00\u07d4\x827htg7\xcem\xa3\x12\xd5>TSN\x10o\x96|\xf3\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x82;\xa7dr8\xd1\x13\xbc\xe9\x96JC\u0420\x98\x11\x8b\xfeM\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x82@t1(\x06\xdaGHCBf\xee\x00!@\u305a\u0089Q\xb1\u04c3\x92a\xac\x00\x00\u07d4\x82C\x8f\u04b3*\x9b\xddgKI\xd8\xcc_\xa2\xef\xf9x\x18G\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x82HW(\xd0\xe2\x81V7X\xc7Z\xb2~\xd9\u80a0\x00-\x89\a\xf8\b\xe9)\x1el\x00\x00\u07d4\x82K<\x19)]~\xf6\xfa\xa7\xf3t\xa4y\x84\x86\xa8\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x82Q5\x8c\xa4\xe0`\u0775Y\xcaX\xbc\v\u077e\xb4\a\x02\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x82Q5\xb1\xa7\xfc\x16\x05aL\x8a\xa4\u042cm\xba\u040fH\x0e\x89M\x85<\x8f\x89\b\x98\x00\x00\u07d4\x82S\t\xa7\xd4]\x18\x12\xf5\x1en\x8d\xf5\xa7\xb9ol\x90\x88\x87\x89\x804\xf7\u0671f\xd4\x00\x00\u07d4\x82Z\u007fN\x10\x94\x9c\xb6\xf8\x96Bh\xf1\xfa_W\xe7\x12\xb4\u0109\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x82a\xfa#\f\x90\x1dC\xffW\x9fG\x80\u04d9\xf3\x1e`v\xbc\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x82b\x16\x9baXp\x13N\xb4\xacl_G\x1ck\xf2\xf7\x89\xfc\x89\x19\x12z\x13\x91\xea*\x00\x00\u07d4\x82c\xec\xe5\xd7\t\xe0\u05eeq\u0328h\xed7\xcd/\xef\x80{\x895\xab\x02\x8a\xc1T\xb8\x00\x00\xe0\x94\x82l\xe5y\x052\xe0T\x8ca\x02\xa3\r>\xac\x83k\xd68\x8f\x8a\x03\xcf\xc8.7\xe9\xa7@\x00\x00\u07d4\x82n\xb7\xcds\x19\xb8-\xd0z\x1f;@\x90q\xd9n9g\u007f\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x82u1\xa6\u0141z\xe3_\x82\xb0\v\x97T\xfc\xf7LU\xe22\x89\xc3(\t>a\xee@\x00\x00\u0794\x82u\xcdhL6y\u0548}\x03fN3\x83E\xdc<\xdd\xe1\x88\xdbD\xe0I\xbb,\x00\x00\u07d4\x82\x84\x92;b\u62ff|+\x9f4\x14\xd1>\xf6\xc8\x12\xa9\x04\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4\x82\x8b\xa6Q\u02d3\x0e\xd9xqV)\x9a=\xe4L\u040br\x12\x89Hz\x9a0E9D\x00\x00\u07d4\x82\xa1\\\xef\x1dl\x82`\xea\xf1Y\xea?\x01\x80\xd8g}\xce\x1c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x82\xa8\xb9kl\x9e\x13\xeb\xec\x1e\x9f\x18\xac\x02\xa6\x0e\xa8\x8aH\xff\x89lk\x8c@\x8es\xb3\x00\x00\u07d4\x82\xa8\u02ff\xdf\xf0+.8\xaeK\xbf\xca\x15\xf1\xf0\xe8;\x1a\xea\x89\x04\x9b\x99\x1c'\xefm\x80\x00\u07d4\x82\xe4F\x1e\xb9\xd8I\xf0\x04\x1c\x14\x04!\x9eBr\u0110\n\xb4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x82\xe5w\xb5\x15\xcb+\b`\xaa\xfe\x1c\xe0\x9aY\xe0\x9f\xe7\xd0@\x89 \x86\xac5\x10R`\x00\x00\u07d4\x82\xea\x01\xe3\xbf.\x83\x83nqpN\"\xa2q\x93w\xef\xd9\u00c9\xa4\xccy\x95c\u00c0\x00\x00\u07d4\x82\xf2\xe9\x91\xfd2L_]\x17v\x8e\x9fa3]\xb61\x9dl\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x82\xf3\x9b'X\xaeB'{\x86\u059fu\xe6(\xd9X\xeb\u02b0\x8a\bxg\x83&\xea\xc9\x00\x00\x00\xe0\x94\x82\xf8T\xc9\xc2\xf0\x87\xdf\xfa\x98Z\xc8 \x1ebl\xa5Fv\x86\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\x82\xffqo\xdf\x03>\xc7\xe9B\xc9\t\u0643\x18g\xb8\xb6\xe2\xef\x89a\t=|,m8\x00\x00\u07d4\x83\b\xed\n\xf7\xf8\xa3\xc1u\x1f\xaf\xc8w\xb5\xa4*\xf7\xd3X\x82\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x83\x1cD\xb3\b@G\x18K*\xd2\x18h\x06@\x907P\xc4]\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x83!\x05\x83\xc1jN\x1e\x1d\xac\x84\xeb\xd3~=\x0f|W\ub909lk\x93[\x8b\xbd@\x00\x00\u07d4\x83,T\x17k\xdfC\xd2\u027c\u05f8\b\xb8\x95V\xb8\x9c\xbf1\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x833\x16\x98]Gt+\xfe\xd4\x10`J\x91\x95<\x05\xfb\x12\xb0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x834vK{9zNW\x8fP6M`\xceD\x89\x9b\xff\x94\x89\x05\x03\xb2\x03\xe9\xfb\xa2\x00\x00\xe0\x94\x83;j\x8e\xc8\xda@\x81\x86\xac\x8a}*m\xd6\x15#\xe7\u0384\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\x83=?\xaeT*\xd5\xf8\xb5\f\xe1\x9b\xde+\xecW\x91\x80\u020c\x89\x12\xc1\xb6\xee\xd0=(\x00\x00\xe0\x94\x83=\xb4,\x14\x16<{\xe4\u02b8j\u0153\xe0bf\u0599\u054a$\xe4\r+iC\xef\x90\x00\x00\xe0\x94\x83V;\xc3d\ud060\xc6\xda;V\xffI\xbb\xf2g\x82z\x9c\x8a\x03\xab\x91\xd1{ \xdeP\x00\x00\u07d4\x83zd]\xc9\\IT\x9f\x89\x9cN\x8b\u03c7S$\xb2\xf5|\x89 \x8c9J\xf1\u0208\x00\x00\u07d4\x83\x8b\xd5e\xf9\x9f\xdeH\x05?y\x17\xfe3<\xf8J\xd5H\xab\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x83\x90\x8a\xa7G\x8am\x1c\x9b\x9b\x02\x81\x14\x8f\x8f\x9f$+\x9f\u0709lk\x93[\x8b\xbd@\x00\x00\u07d4\x83\x92\xe57vq5x\x01[\xffI@\xcfC\x84\x9d}\u02e1\x89\bM\xf05]V\x17\x00\x00\xe0\x94\x83\x97\xa1\xbcG\xac\xd6GA\x81Y\xb9\x9c\xeaW\xe1\xe6S-n\x8a\x01\xf1\x0f\xa8'\xb5P\xb4\x00\x00\u07d4\x83\x98\xe0~\xbc\xb4\xf7_\xf2\x11m\xe7|\x1c*\x99\xf3\x03\xa4\u03c9\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x83\xa3\x14\x883\xd9dI\x84\xf7\xc4u\xa7\x85\a\x16\ufd00\xff\x89\xb8Pz\x82\a( \x00\x00\u07d4\x83\xa4\x02C\x8e\x05\x19w=TH2k\xfba\xf8\xb2\f\xf5-\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d4\x83\xa9;[\xa4\x1b\xf8\x87 \xe4\x15y\f\xdc\vg\xb4\xaf4\u0109\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x83\xc2=\x8aP!$\xee\x15\x0f\b\xd7\x1d\xc6rt\x10\xa0\xf9\x01\x8a\a3\x1f;\xfef\x1b\x18\x00\x00\u07d4\x83\u0217\xa8Ki^\xeb\xe4fy\xf7\xda\x19\xd7vb\x1c&\x94\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x83\xd52\u04cdm\xee?`\xad\u018b\x93a3\u01e2\xa1\xb0\u0749\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x83\xdb\xf8\xa1(S\xb4\n\xc6\x19\x96\xf8\xbf\x1d\xc8\xfd\xba\xdd\xd3)\x894\x95tD\xb8@\xe8\x00\x00\u07d4\x83\xdb\xfd\x8e\xda\x01\xd0\u078e\x15\x8b\x16\u0413_\xc28\n]\u01c9 \x86\xac5\x10R`\x00\x00\u07d4\x83\xe4\x80U2|(\xb5\x93o\xd9\xf4D~s\xbd\xb2\xdd3v\x89\x90\xf54`\x8ar\x88\x00\x00\xe0\x94\x83\xfeZ\x1b2\x8b\xaeD\a\x11\xbe\xafj\xad`&\xed\xa6\xd2 \x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x84\x00\x8ar\xf8\x03o?\xeb\xa5B\xe3Px\xc0W\xf3*\x88%\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x84\x0e\xc8>\xa96!\xf04\xe7\xbb7b\xbb\x8e)\xde\xd4\xc4y\x89\x87\x86x2n\xac\x90\x00\x00\xe0\x94\x84\x11E\xb4H@\xc9F\xe2\x1d\xbc\x19\x02d\xb8\xe0\xd5\x02\x93i\x8a?\x87\bW\xa3\xe0\xe3\x80\x00\x00\u07d4\x84#!\a\x93+\x12\xe01\x86X5%\xce\x02:p>\xf8\u0649lk\x93[\x8b\xbd@\x00\x00\u07d4\x84$O\xc9ZiW\xed|\x15\x04\xe4\x9f0\xb8\xc3^\xcaKy\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x841'}{\xdd\x10E}\xc0\x17@\x8c\x8d\xbb\xbdAJ\x8d\xf3\x89\x02\"\xc8\xeb?\xf6d\x00\x00\u07d4\x847Z\xfb\xf5\x9b:\x1da\xa1\xbe2\xd0u\xe0\xe1ZO\xbc\xa5\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x84;\xd3P/E\xf8\xbcM\xa3p\xb3#\xbd\xac?\xcf_\x19\xa6\x89P\x03\x9dc\xd1\x1c\x90\x00\x00\u07d4\x84P34c\rw\xf7AG\xf6\x8b.\bf\x13\xc8\xf1\xad\xe9\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\x84R\x03u\x0fqH\xa9\xaa&)!\xe8mC\xbfd\x19t\xfd\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x84a\xec\u0126\xa4^\xb1\xa5\xb9G\xfb\x86\xb8\x80i\xb9\x1f\xcdo\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x84g^\x91wrmE\xea\xa4k9\x92\xa3@\xba\u007fq\f\x95\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x84hl{\xadv,T\xb6g\u055f\x90\x94<\xd1M\x11z&\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x84\x89\xf6\xad\x1d\x9a\x94\xa2\x97x\x91V\x89\x9d\xb6AT\xf1\u06f5\x89\x13t\a\xc0<\x8c&\x80\x00\u07d4\x84\x8c\x99Jy\x00?\xe7\xb7\xc2l\xc62\x12\xe1\xfc/\x9c\x19\xeb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x84\x8f\xbd)\xd6|\xf4\xa0\x13\xcb\x02\xa4\xb1v\xef$N\x9e\u6349\x01\x17*ck\xbd\xc2\x00\x00\u07d4\x84\x94\x9d\xbaU\x9ac\xbf\xc8E\xde\xd0n\x9f-\x9b\u007f\x11\xef$\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x84\x9a\xb8\a\x90\xb2\x8f\xf1\xff\u05ba9N\xfctc\x10\\6\xf7\x89\x01\xe0+\xe4\xael\x84\x00\x00\u07d4\x84\x9b\x11oYc\x01\xc5\u063bb\xe0\xe9z\x82H\x12n9\xf3\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x84\xa7L\xee\xcf\xf6\\\xb9;/\x94\x9dw>\xf1\xad\u007f\xb4\xa2E\x89\x05\n\x9bDF\x85\xc7\x00\x00\u07d4\x84\xaa\xc7\xfa\x19\u007f\xf8\\0\xe0;zS\x82\xb9W\xf4\x1f:\xfb\x89\b\x8b#\xac\xff\u0650\x00\x00\u07d4\x84\xaf\x1b\x15sB\xd5Ch&\r\x17\x87b0\xa54\xb5K\x0e\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\x84\xb0\xeek\xb87\u04e4\xc4\xc5\x01\x1c:\"\x8c\x0e\u06b4cJ\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x84\xb4\xb7Nf#\xba\x9d\x15\x83\xe0\u03feId?\x168AI\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x84\xb6\xb6\xad\xbe/[>-h,f\xaf\x1b\u0110S@\xc3\xed\x89!\x92\xf8\xd2\"\x15\x00\x80\x00\xe0\x94\x84\xb9\x1e.)\x02\xd0^+Y\x1bA\b;\u05fe\xb2\xd5,t\x8a\x02\x15\xe5\x12\x8bE\x04d\x80\x00\u07d4\x84\xbc\xbf\"\xc0\x96\a\xac\x844\x1d.\xdb\xc0;\xfb\x179\xd7D\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x84\xbf\xce\xf0I\x1a\n\xe0iK7\u03ac\x02E\x84\xf2\xaa\x04g\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x84\xcb}\xa0P-\xf4\\\xf5a\x81{\xbd#b\xf4Q\xbe\x02\u0689Hz\x9a0E9D\x00\x00\u07d4\x84\xccxx\xda`_\xdb\x01\x9f\xab\x9bL\xcf\xc1Wp\x9c\u0765\x89Hy\x85\x13\xaf\x04\xc9\x00\x00\u07d4\x84\xdb\x14Y\xbb\x00\x81.\xa6~\xcb=\xc1\x89\xb7!\x87\xd9\xc5\x01\x89\b\x11\xb8\xfb\u0685\xab\x80\x00\u07d4\x84\u9516\x80\xbe\xcehA\xb9\xa7\xe5%\r\b\xac\xd8}\x16\u0349\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x84\xe9\u03c1f\xc3j\xbf\xa4\x90S\xb7\xa1\xad@6 &\x81\xef\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x84\xec\x06\xf2G\x00\xfeBAL\xb9\x89|\x15L\x88\xde/a2\x89Hz\x9a0E9D\x00\x00\xe0\x94\x84\xf5\"\xf0R\x0e\xbaR\xdd\x18\xad!\xfaK\x82\x9f+\x89\u02d7\x8a\x01\fQ\x06\xd5\x13O\x13\x00\x00\u07d4\x85\v\x9d\xb1\x8f\xf8K\xf0\xc7\xdaI\xea7\x81\xd9 \x90\xad~d\x89\x8c\xf2?\x90\x9c\x0f\xa0\x00\x00\u07d4\x85\x10\xee\x93O\f\xbc\x90\x0e\x10\a\xeb8\xa2\x1e*Q\x01\xb8\xb2\x89\x05\xbf\v\xa6cOh\x00\x00\u07d4\x85\x16\xfc\xafw\u0213\x97\x0f\xcd\x1a\x95\x8b\xa9\xa0\x0eI\x04@\x19\x89\n\xa3\xeb\x16\x91\xbc\xe5\x80\x00\u07d4\x85\x1a\xa9\x1c\x82\xf4/\xad]\xd8\xe8\xbb^\xa6\x9c\x8f:Yw\u0449\b\x0eV\x1f%xy\x80\x00\u07d4\x85\x1c\rb\xbeF5\xd4w~\x805\xe3~K\xa8Q|a2\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x85\x1d\u00ca\xdbE\x93r\x9av\xf3:\x86\x16\u06b6\xf5\xf5\x9aw\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x852I\b\x97\xbb\xb4\u038b\u007fk\x83~L\xba\x84\x8f\xbe\x99v\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x85>j\xba\xf4Di\xc7/\x15\x1dN\"8\x19\xac\xedN7(\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x85F\x91\xceqO2\\\xedU\xceY(\u039b\xa1/\xac\u0478\x89\xedp\xb5\xe9\xc3\xf2\xf0\x00\x00\u07d4\x85L\fF\x9c$k\x83\xb5\u0473\xec\xa4C\xb3\x9a\xf5\xee\x12\x8a\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\x85]\x9a\xef,9\xc6#\r\t\u025e\xf6II\x89\xab\u61c5\x89\b\xbaR\xe6\xfcE\xe4\x00\x00\u07d4\x85c\u0113a\xb6%\xe7hw\x1c\x96\x15\x1d\xbf\xbd\x1c\x90iv\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x85fa\t\x01\xaa\xce8\xb82D\xf3\xa9\xc810jg\xb9\u0709\xb0\x82\x13\xbc\xf8\xff\xe0\x00\x00\xe0\x94\x85j\xa2<\x82\xd7![\xec\x8dW\xf6\n\xd7^\xf1O\xa3_D\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x85nZ\xb3\xf6L\x9a\xb5k\x00\x93\x93\xb0\x16d\xfc\x03$\x05\x0e\x89a\t=|,m8\x00\x00\u07d4\x85n\xb2\x04$\x1a\x87\x83\x0f\xb2)\x03\x13C\xdc0\x85OX\x1a\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x85s,\x06\\\xbdd\x11\x99A\xae\xd40\xacYg\vlQ\u0109'\xa5sb\xab\n\x0e\x80\x00\xe0\x94\x85x\xe1\x02\x12\xca\x14\xff\a2\xa8$\x1e7F}\xb8V2\xa9\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x85y\xda\xdf\x1a9Z4q\xe2\vov=\x9a\x0f\xf1\x9a?o\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x85\u007f\x10\v\x1aY0\"^\xfc~\x90 \u05c3'\xb4\x1c\x02\u02c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x85\x94mV\xa4\xd3q\xa93hS\x96\x90\xb6\x0e\xc8%\x10tT\x89]\u0212\xaa\x111\xc8\x00\x00\xe0\x94\x85\x99\xcb\u0566\xa9\xdc\u0539f\xbe8}iw]\xa5\xe3C'\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x86$_Yf\x91\t>\xce?=<\xa2&>\xac\xe8\x19A\u0649\n1\x06+\xee\xedp\x00\x00\u07d4\x86%i!\x1e\x8cc'\xb5A^:g\xe5s\x8b\x15\xba\xafn\x89\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4\x86)}s\x0f\xe0\xf7\xa9\xee$\xe0\x8f\xb1\b{1\xad\xb3\x06\xa7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x86D\xcc(\x1b\xe32\xcc\xce\xd3m\xa4\x83\xfb*\aF\u067a.\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x86I\x9a\x12(\xff-~\xe3\au\x93dPo\x8e\x8c\x83\a\xa5\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x86K\xecPi\xf8U\xa4\xfdX\x92\xa6\xc4I\x1d\xb0|\x88\xff|\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x86W\n\xb2Y\u0271\xc3,\x97) /w\xf5\x90\xc0}\xd6\x12\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x86c\xa2A\xa0\xa8\x9ep\xe1\x82\xc8E\xe2\x10\\\x8a\xd7&K\u03ca\x03#\xb1=\x83\x98\xf3#\x80\x00\u07d4\x86g\xfa\x11U\xfe\xd72\u03f8\u0725\xa0\xd7e\xce\r\a\x05\xed\x89\x04n\xc9e\u00d3\xb1\x00\x00\u07d4\x86h\xaf\x86\x8a\x1e\x98\x88_\x93\u007f&\x15\xde\xd6u\x18\x04\xeb-\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x86t\nFd\x8e\x84Z]\x96F\x1b\x18\t\x1f\xf5{\xe8\xa1o\x8a\x14\xc0\x974\x85\xbf9@\x00\x00\xe0\x94\x86~\xbaVt\x8aY\x045\r,\xa2\xa5\u039c\xa0\vg\n\x9b\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x86\x80dt\xc3X\x04}\x94\x06\xe6\xa0\u007f@\x94[\xc82\x8eg\x8a\x01u.\xb0\xf7\x01=\x10\x00\x00\u07d4\x86\x88=T\xcd9\x15\xe5I\tU0\xf9\xab\x18\x05\xe8\xc5C-\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x86\x8c#\xbe\x874f\xd4\xc7L\"\n\x19\xb2E\xd1x~\x80\u007f\x89J\x13\xbb\xbd\x92\u020e\x80\x00\xe0\x94\x86\x92O\xb2\x11\xaa\xd2<\xf5\xce`\x0e\n\xae\x80c\x96D@\x87\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x86\x93\u9e3e\x94B^\xefyi\xbci\xf9\xd4/|\xadg\x1e\x8967\tlK\xcci\x00\x00\xe0\x94\x86\x9f\x1a\xa3\x0eDU\xbe\xb1\x82 \x91\xde\\\xad\xecy\xa8\xf9F\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x86\xa1\xea\xde\xeb0F\x13E\xd9\xefk\xd0R\x16\xfa$|\r\f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x86\xa5\xf8%\x9e\u0570\x9e\x18\x8c\xe3F\xee\x92\xd3J\xa5\u0753\xfa\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x86\xb7\xbdV<\uad86\xf9bD\xf9\xdd\xc0*\u05f0\xb1K\u008a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x86\u008bVx\xaf7\xd7'\xec\x05\xe4Dw\x90\xf1_q\xf2\xea\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x86\xc4\xce\x06\u066c\x18[\xb1H\xd9o{z\xbes\xf4A\x00m\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x86\xc8\xd0\u0642\xb59\xf4\x8f\x980\xf9\x89\x1f\x9d`z\x94&Y\x8a\x02\xce\xd3wa\x82O\xb0\x00\x00\u07d4\x86\xc94\xe3\x8eS\xbe;3\xf2t\xd0S\x9c\xfc\xa1Y\xa4\xd0\u04494\x95tD\xb8@\xe8\x00\x00\xe0\x94\x86\xca\x01E\x95~k\r\xfe6\x87_\xbez\r\xecU\xe1z(\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x86\u02af\xac\xf3*\xa01|\x03*\xc3k\xab\xed\x97G\x91\xdc\x03\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\x86\u0377\xe5\x1a\xc4Gr\xbe6\x90\xf6\x1d\x0eYvn\x8b\xfc\x18\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x86\xdfs\xbd7\u007f,\t\xdec\xc4]g\xf2\x83\xea\xef\xa0\xf4\xab\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x86\xe3\xfe\x86\xe9=\xa4\x86\xb1Bf\xea\xdf\x05l\xbf\xa4\xd9\x14C\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x86\xe8g\x0e'Y\x8e\xa0\x9c8\x99\xabw\x11\u04f9\xfe\x90\x1c\x17\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x86\xefd&!\x19I\xcc7\xf4\xc7^xP6\x9d\f\xf5\xf4y\x8a\x02\xd6_2\xea\x04Z\xf6\x00\x00\u07d4\x86\xf0]\x19\x06>\x93i\xc6\x00N\xb3\xf1#\x94:|\xffN\xab\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x86\xf2>\x9c\n\xaf\u01cb\x9c@M\xcd`3\x9a\x92[\xff\xa2f\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x86\xf4\xf4\n\u0644\xfb\xb8\t3\xaebn\x0eB\xf93?\xddA\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x86\xf9\\[\x11\xa2\x93\x94\x0e5\xc0\xb8\x98\u0637_\b\xaa\xb0m\x8a\x06D\xe3\xe8u\xfc\xcft\x00\x00\u07d4\x86\xff\xf2 \xe5\x93\x05\xc0\x9fH8`\xd6\xf9N\x96\xfb\xe3/W\x89\x02S[j\xb4\xc0B\x00\x00\u07d4\x87\a\x96\xab\xc0\u06c4\xaf\x82\xdaR\xa0\xedhsM\xe7\xe66\xf5\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x87\x0f\x15\xe5\u07cb\x0e\xab\xd0%iSz\x8e\xf9;Vx\\B\x89\x15\b\x94\xe8I\xb3\x90\x00\x00\u07d4\x87\x181`\xd1r\xd2\xe0\x84\xd3'\xb8k\xcb|\x1d\x8eg\x84\xef\x89\xd8\xd8X?\xa2\xd5/\x00\x00\xe0\x94\x87\x1b\x8a\x8bQ\u07a1\x98\x9aY!\xf1>\xc1\xa9U\xa5\x15\xadG\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x87%\xe8\xc7S\xb3\xac\xbf\u0725_I\x13\\3\x91\x99\x10`)\n\xa7\xf6\u0338\xf8Zx\u06c9\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x87Pa\xee\x12\xe8 \x04\x1a\x01\x94,\xb0\xe6[\xb4'\xb0\x00`\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\x87XJ?a;\xd4\xfa\xc7L\x1ex\v\x86\xd6\xca\xeb\x89\f\xb2\x89\\(=A\x03\x94\x10\x00\x00\u07d4\x87d\xd0'\"\x00\t\x96\xec\xd4u\xb43)\x8e\x9fT\v\x05\xbf\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x87l?!\x8bGv\xdf<\xa9\xdb\xfb'\r\xe1R\xd9N\xd2R\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x87u\xa6\x10\xc5\x02\xb9\xf1\xe6\xadL\xda\u06cc\xe2\x9b\xffu\xf6\xe4\x89 \x86\xac5\x10R`\x00\x00\u07d4\x87vN6w\xee\xf6\x04\xcb\u015a\xed$\xab\xdcVk\t\xfc%\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94\x87\x87\xd1&w\xa5\xec)\x1eW\xe3\x1f\xfb\xfa\xd1\x05\xc32K\x87\x8a\x02\xa2N\xb52\b\xf3\x12\x80\x00\u07d4\x87\x94\xbfG\xd5E@\xec\xe5\xc7\"7\xa1\xff\xb5\x11\u0777Gb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x87\xa5>\xa3\x9fY\xa3[\xad\xa85%!dU\x94\xa1\xa7\x14\u02c9g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x87\xa7\xc5\b\xefqX-\u0665Cr\xf8\x9c\xb0\x1f%/\xb1\x80\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x87\xaf%\xd3\xf6\xf8\xee\xa1S\x13\xd5\xfeEW\xe8\x10\xc5$\xc0\x83\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4\x87\xb1\x0f\x9c(\x00\x98\x17\x9a+v\xe9\u0390\xbea\xfc\x84M\r\x89Hz\x9a0E9D\x00\x00\u07d4\x87\xbf|\xd5\u0629)\xe1\u01c5\xf9\xe5D\x91\x06\xac#$c\u0249\x047\xb1\x1f\xccEd\x00\x00\u07d4\x87\u0118\x17\t4\xb8#=\x1a\xd1\xe7i1}\\G_/@\x897\b\xba\xed=h\x90\x00\x00\u07d4\x87\xcf6\xad\x03\xc9\xea\xe9\x05:\xbbRB\u0791\x17\xbb\x0f*\v\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x87\u05ec\x06S\xcc\xc6z\xa9\xc3F\x9e\xefCR\x19?}\xbb\x86\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\xe0\x94\x87\xe3\x06+#!\xe9\u07f0\x87\\\u311c\x9b.5\"\xd5\n\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x87\xe6\x03N\xcf#\xf8\xb5c\x9d_\x0e\xa7\n\"S\x8a\x92\x04#\x89\x11\xc7\xea\x16.x \x00\x00\u07d4\x87\xefm\x8bj|\xbf\x9b\\\x8c\x97\xf6~\xe2\xad\u00a7;?w\x89\n\xdd\x1b\xd2<\x00L\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x88F\x92\x8dh2\x89\xa2\xd1\x1d\xf8\xdbz\x94t\x98\x8e\xf0\x13H\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x88I\x80\xebEe\xc1\x04\x83\x17\xa8\xf4\u007f\u06f4a\x96[\u4049\xd8\xd6\x11\x9a\x81F\x05\x00\x00\xe0\x94\x88Jz9\u0411n\x05\xf1\xc2B\xdfU`\u007f7\u07cc_\u068a\x04\xf4\x84<\x15|\x8c\xa0\x00\x00\u07d4\x88T\x93\xbd\xa3j\x042\x97eF\xc1\xdd\xceq\xc3\xf4W\x00!\x89\v\xbfQ\r\xdf\xcb&\x00\x00\xe0\x94\x88`\x9e\nF[n\x99\xfc\xe9\a\x16mW\xe9\xda\b\x14\xf5\u020a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x88m\n\x9e\x17\xc9\xc0\x95\xaf.\xa25\x8b\x89\xecpR\x12\ue509\x01\x84\x93\xfb\xa6N\xf0\x00\x00\u07d4\x88y~Xg^\xd5\xccL\x19\x98\a\x83\xdb\xd0\xc9V\bQS\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x88|\xacA\xcdpo3E\xf2\xd3J\xc3N\x01u*nY\t\x89 F\\\ue7617\x00\x00\u07d4\x88\x88\x8aW\xbd\x96\x87\xcb\xf9P\xae\xea\u03d7@\xdc\xc4\xd1\xefY\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u0794\x88\x89D\x83\x16\xcc\xf1N\xd8m\xf8\xe2\xf4x\xdcc\xc43\x83@\x88\xd2\xf1?w\x89\xf0\x00\x00\u07d4\x88\x8c\x16\x14I3\x19|\xac&PM\xd7n\x06\xfdf\x00\u01c9\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x88\x8e\x94\x91p\x83\xd1R +S\x1699\x86\x9d'\x11u\xb4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x88\x90\x87\xf6o\xf2\x84\xf8\xb5\xef\xbd)I;pg3\xab\x14G\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\x88\x95\xebrb&\xed\xc3\xf7\x8c\u01a5\x15\a{2\x96\xfd\xb9^\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x88\x97Z_\x1e\xf2R\x8c0\v\x83\xc0\xc6\a\xb8\xe8}\u0593\x15\x89\x04\x86\u02d7\x99\x19\x1e\x00\x00\u07d4\x88\x9d\xa4\x0f\xb1\xb6\x0f\x9e\xa9\xbdzE>XL\xf7\xb1\xb4\xd9\xf7\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x88\x9d\xa6b\xebJ\n*\x06\x9d+\xc2K\x05\xb4\xee.\x92\xc4\x1b\x89Z,\x8cTV\xc9\xf2\x80\x00\u07d4\x88\xa1\"\xa28,R91\xfbQ\xa0\u032d;\xeb[rY\u00c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x88\xa2\x15D0\xc0\xe4\x11G\xd3\xc1\xfe\u3cf0\x06\xf8Q\xed\xbd\x8965f3\xeb\xd8\xea\x00\x00\u07d4\x88\xb2\x17\u0337\x86\xa2T\xcfM\xc5\u007f]\x9a\xc3\xc4U\xa3\x04\x83\x892$\xf4'#\xd4T\x00\x00\xe0\x94\x88\xbcC\x01.\xdb\x0e\xa9\xf0b\xacCxC%\n9\xb7\x8f\xbb\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x88\xc2Qj|\xdb\t\xa6'mr\x97\xd3\x0fZM\xb1\xe8K\x86\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x88\xc3ad\rki7;\b\x1c\xe0\xc43\xbdY\x02\x87\xd5\xec\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\x88\xd5A\xc8@\xceC\xce\xfb\xafm\x19\xafk\x98Y\xb5s\xc1E\x89\t79SM(h\x00\x00\u07d4\x88\xde\x13\xb0\x991\x87|\x91\rY1e\xc3d\u0221d\x1b\u04c9\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x88\xde\u017d?N\xba-\x18\xb8\xaa\xce\xfa{r\x15H\xc3\x19\xba\x89JD\x91\xbdm\xcd(\x00\x00\u07d4\x88\xe6\xf9\xb2G\xf9\x88\xf6\xc0\xfc\x14\xc5o\x1d\xe5>\u019dC\u0309\x05k\xc7^-c\x10\x00\x00\u07d4\x88\xee\u007f\x0e\xfc\x8fw\x8ckh~\xc3+\xe9\xe7\xd6\xf0 \xb6t\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x88\xf1\x04_\x19\xf2\xd3\x19\x18\x16\xb1\xdf\x18\xbbn\x145\xad\x1b8\x89\r\x02\xabHl\xed\xc0\x00\x00\xe0\x94\x89\x00\x9e\a\xe3\xfahc\xa7x\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x89Wr~r\xcfb\x90 \xf4\xe0^\xdfy\x9a\xa7E\x80b\u0409wC\"\x17\xe6\x83`\x00\x00\u07d4\x89]iN\x88\v\x13\xcc\u0404\x8a\x86\xc5\xceA\x1f\x88Gk\xbf\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4\x89^\xc5TVD\u0dc30\xff\xfa\xb8\xdd\xea\xc9\xe83\x15l\x89 \x86\xac5\x10R`\x00\x00\u07d4\x89`\tRj,{\f\t\xa6\xf6:\x80\xbd\U0009d707\u079c\x89\xbb\xb8k\x82#\xed\xeb\x00\x00\u07d4\x89g\u05f9\xbd\xb7\xb4\xae\xd2.e\xa1]\xc8\x03\xcbz!?\x10\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x89n3\\\xa4z\xf5yb\xfa\x0fM\xbf>E\xe6\x88\u02e5\x84\x89J/\xc0\xab`R\x12\x00\x00\u07d4\x89s\xae\xfd^\xfa\xee\x96\t]\x9e(\x8fj\x04l\x977KC\x89\a\xa4\u0120\xf32\x14\x00\x00\u07d4\x89\x8cr\xddseX\xef\x9eK\xe9\xfd\xc3O\xefT\xd7\xfc~\b\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x89\x9b<$\x9f\fK\x81\xdfu\xd2\x12\x00M=m\x95/\xd2#\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x89\xab\x13\xee&mw\x9c5\xe8\xbb\x04\u034a\x90\xcc!\x03\xa9[\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4\x89\xc43\xd6\x01\xfa\xd7\x14\xdaci0\x8f\xd2l\x1d\u0254+\xbf\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x89\xd7[\x8e\b1\xe4o\x80\xbc\x17A\x88\x18N\x00o\xde\x0e\xae\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x89\u3d5a\x15\x86G7\u0513\xc1\xd2<\xc5=\xbf\x8d\xcb\x13b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x89\xfc\x8eM8k\r\v\xb4\xa7\a\xed\xf3\xbdV\r\xf1\xad\x8fN\x89\xa00\xdc\xeb\xbd/L\x00\x00\u07d4\x89\xfe\xe3\r\x17(\xd9l\xec\xc1\u06b3\xda.w\x1a\xfb\u03eaA\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\x8a\x1c\u016c\x11\x1cI\xbf\xcf\xd8H\xf3}\xd7h\xaae\u0208\x02\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x8a \xe5\xb5\xce\xe7\xcd\x1fU\x15\xba\xce;\xf4\xf7\u007f\xfd\xe5\xcc\a\x89\x04V9\x18$O@\x00\x00\xe0\x94\x8a!}\xb3\x8b\xc3_!_\xd9)\x06\xbeBCo\xe7\xe6\xed\x19\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x8a$:\n\x9f\xeaI\xb89TwE\xff-\x11\xaf?K\x05\"\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\x8a$}\x18e\x10\x80\x9fq\xcf\xfcEYG\x1c9\x10\x85\x81!\x89a\t=|,m8\x00\x00\u07d4\x8a4p(-^**\xef\u05e7P\x94\xc8\"\xc4\xf5\xae\uf289\r(\xbc`dx\xa5\x80\x00\u07d4\x8a6\x86\x9a\xd4x\x99|\xbfm\x89$\xd2\n<\x80\x18\xe9\x85[\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x8aC\x14\xfba\u0353\x8f\xc3>\x15\xe8\x16\xb1\x13\U000ac267\xfb\x89\x17vNz\xede\x10\x00\x00\u07d4\x8aOJ\u007fR\xa3U\xba\x10_\xca r\xd3\x06_\xc8\xf7\x94K\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x8aX1(,\xe1Jezs\r\xc1\x88&\xf7\xf9\xb9\x9d\xb9h\x89\uaf8a[A\xc16\x00\x00\u07d4\x8a_\xb7W\x93\xd0C\xf1\xbc\xd48\x85\xe07\xbd0\xa5(\xc9'\x89\x13Snm.\x9a\xc2\x00\x00\u07d4\x8af\xab\xbc-0\xce!\xa83\xb0\u06ceV\x1dQ\x05\xe0\xa7,\x89%\xf1\xde\\v\xac\xdf\x00\x00\u07d4\x8atl]g\x06G\x11\xbf\xcah[\x95\xa4\xfe)\x1a'\x02\x8e\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x8ax\n\xb8z\x91E\xfe\x10\xed`\xfaGjt\n\xf4\u02b1\u0489\x12\x1b.^ddx\x00\x00\u07d4\x8az\x06\xbe\x19\x9a:X\x01\x9d\x84j\xc9\xcb\xd4\xd9]\xd7W\u0789\xa2\xa4#\x94BV\xf4\x00\x00\u07d4\x8a\x81\x01\x14\xb2\x02]\xb9\xfb\xb5\x00\x99\xa6\xe0\u02de.\xfak\u0709g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x8a\x86\xe4\xa5\x1c\x01;\x1f\xb4\xc7k\xcf0f|x\xd5.\xed\xef\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8a\x9e\u029cZ\xba\x8e\x13\x9f\x80\x03\xed\xf1\x16:\xfbp\xaa:\xa9\x89#\xc7W\a+\x8d\xd0\x00\x00\u07d4\x8a\xb89\xae\xaf*\xd3|\xb7\x8b\xac\xbb\xb63\xbc\xc5\xc0\x99\xdcF\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8a\u021b\u06780\x1ek\x06w\xfa%\xfc\xf0\xf5\x8f\f\u01f6\x11\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x8a\xdcS\xef\x8c\x18\xed0Qx]\x88\xe9\x96\xf3\xe4\xb2\x0e\xcdQ\x8a\b\xe4\xd3\x16\x82v\x86@\x00\x00\u07d4\x8a\xe6\xf8\vp\xe1\xf2<\x91\xfb\u0569f\xb0\xe4\x99\xd9]\xf82\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\x8a\xe9\uf30a\x8a\u07e6\xaby\x8a\xb2\xcd\xc4\x05\b*\x1b\xbbp\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8a\xf6&\xa5\xf3'\xd7Pe\x89\xee\xb7\x01\x0f\xf9\xc9D` \u0489K\xe4\xe7&{j\xe0\x00\x00\xe0\x94\x8b\x01\xda4\xd4p\xc1\xd1\x15\xac\xf4\xd8\x11\xe1\x01\xdb\x1e\x14\xec\xc7\xd3\"\xc7+\x8c\x04s\x89\x18\xb2j1>\x8a\xe9\x00\x00\xe0\x94\x8bH\xe1\x9d9\xdd5\xb6nn\x1b\xb6\xb9\xc6W\xcb,\xf5\x9d\x04\x8a\x03\xc7U\xac\x9c\x02J\x01\x80\x00\xe0\x94\x8bP^(q\xf7\u07b7\xa68\x95 \x8e\x82'\u072a\x1b\xff\x05\x8a\f\xf6\x8e\xfc0\x8dy\xbc\x00\x00\u07d4\x8bW\xb2\xbc\x83\u030dM\xe31 N\x89?/;\x1d\xb1\a\x9a\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x8b\\\x91K\x12\x8b\xf1i\\\b\x89#\xfaF~y\x11\xf3Q\xfa\x89\x05V\xf6L\x1f\xe7\xfa\x00\x00\xe0\x94\x8b_)\xcc/\xaa&,\xde\xf3\x0e\xf5T\xf5\x0e\xb4\x88\x14n\xac\x8a\x01;hp\\\x97 \x81\x00\x00\u07d4\x8bpV\xf6\xab\xf3\xb1\x18\xd0&\xe9D\xd5\xc0sC<\xa4Q\u05c965\xc6 G9\u0640\x00\u07d4\x8bqE\"\xfa(9b\x04p\xed\xcf\fD\x01\xb7\x13f=\xf1\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x8bt\xa7\xcb\x1b\xb8\u014f\xce&tf\xa3\x03X\xad\xafR\u007fa\x8a\x02\xe2WxN%\xb4P\x00\x00\u07d4\x8b~\x9fo\x05\xf7\xe3dv\xa1n>q\x00\xc9\x03\x1c\xf4\x04\xaf\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x8b\x81\x15ni\x869\x94<\x01\xa7Rr\xad=5\x85\x1a\xb2\x82\x89\x12\xb3\x16_e\xd3\xe5\x00\x00\u07d4\x8b\x95w\x92\x00S\xb1\xa0\x01\x890M\x88\x80\x10\xd9\xef,\xb4\xbf\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x8b\x98A\x86.w\xfb\xbe\x91\x94p\x93U\x83\xa9<\xf0'\xe4P\x89llS4B\u007f\x1f\x00\x00\u07d4\x8b\x99}\xbc\a\x8a\xd0)a5]\xa0\xa1Y\xf2\x92~\xd4=d\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94\x8b\x9f\xda}\x98\x1f\xe9\xd6B\x87\xf8\\\x94\xd8?\x90t\x84\x9f\u030a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\x8b\xb0!/2\x95\xe0)\u02b1\xd9a\xb0A3\xa1\x80\x9e{\x91\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8b\xbe\xac\xfc)\xcf\xe94\x02\xdb\xd6j\x1a\xcbvv\x85c7\xb9;\xf0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8b\xf3s\xd0v\x81L\xbcW\xe1\xc6\xd1j\x82\u017e\x13\xc7=7\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x8c\x10#\xfd\xe1WM\xb8\xbbT\xf1s\x96p\x15|\xa4}\xa6R\x8a\x01y\u03da\u00e1\xb1w\x00\x00\u07d4\x8c\x1f\xbe_\n\xea5\x9cZ\xa1\xfa\b\u0209T\x12\u028e\x05\xa6\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x8c\"B`U\xb7o\x11\xf0\xa2\xde\x1a\u007f\x81\x9aa\x96\x85\xfe`\x89kV\x05\x15\x82\xa9p\x00\x00\u07d4\x8c+}\x8b`\x8d(\xb7\u007f\\\xaa\x9c\xd6E$*\x82>L\u0649b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\x8c/\xbe\ue3ac\xc5\xc5\xd7|\x16\xab\xd4b\ue701E\xf3K\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\x8c:\x9e\xe7\x1fr\x9f#l\xba8g\xb4\u05dd\x8c\xee\xe2]\xbc\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x8cP\xaa*\x92\x12\xbc\xdeVA\x8a\xe2a\xf0\xb3^z\x9d\xbb\x82\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x8cT\xc7\xf8\xb9\x89nu\xd7\xd5\xf5\xc7`%\x86\x99\x95qB\xad\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x8c]\x16\xede\xe3\xed~\x8b\x96\u0297+\xc8as\xe3P\v\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8cj\xa8\x82\xee2,\xa8HW\x8c\x06\xcb\x0f\xa9\x11\xd3`\x83\x05\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\x8cj\xe7\xa0Z\x1d\xe5u\x82\xae'h Bv\xc0\xffG\xed\x03\x8a,\v\xb3\xdd0\xc4\xe2\x00\x00\x00\u07d4\x8co\x9fN[z\xe2v\xbfXI{\u05ff*}%$_d\x89\x93\xfe\\W\xd7\x10h\x00\x00\u07d4\x8cu\x95n\x8f\xedP\xf5\xa7\xdd|\xfd'\xda \x0fgF\xae\xa6\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x8c|\xb4\xe4\x8b%\x03\x1a\xa1\xc4\xf9)%\xd61\xa8\xc3\xed\xc7a\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x8c\u007f\xa5\xca\xe8/\xed\xb6\x9a\xb1\x89\xd3\xff'\xae \x92\x93\xfb\x93\x89\x15\xaf\x88\r\x8c\u06c3\x00\x00\xe0\x94\x8c\x81A\x0e\xa85L\xc5\xc6\\A\xbe\x8b\xd5\xdes<\v\x11\x1d\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\x8c\x83\xd4$\xa3\xcf$\xd5\x1f\x01\x92=\xd5J\x18\u05b6\xfe\xde{\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8c\x90\n\x826\xb0\x8c+e@]9\xd7_ \x06*ua\xfd\x89X\xe7\x92n\xe8X\xa0\x00\x00\u07d4\x8c\x93\xc3\xc6\u06dd7q}\xe1e\u00e1\xb4\xfeQ\x95,\b\u0789\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x8c\x99\x95\x91\xfdr\xefq\x11\xef\xcaz\x9e\x97\xa25k;\x00\n\x89\xddd\xe2\xaa\ngP\x00\x00\u07d4\x8c\xa6\x98\x97F\xb0n2\xe2Hta\xb1\u0399j':\xcf\u05c9\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x8c\xb3\xaa?\xcd!(T\xd7W\x8f\xcc0\xfd\xed\xe6t*1*\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x8c\xc0\xd7\xc0\x16\xfaz\xa9P\x11J\xa1\xdb\tH\x82\xed\xa2t\xea\x89\b\xa9\xab\xa5W\xe3l\x00\x00\u07d4\x8c\xc6R\xdd\x13\xe7\xfe\x14\u06bb\xb3m]2\r\xb9\xff\xee\x8aT\x89a\t=|,m8\x00\x00\u07d4\x8c\u02bf%\a\u007f:\xa4\x15E4MS\xbe\x1b+\x9c3\x90\x00\x89[\xe8f\xc5b\xc5D\x00\x00\u07d4\x8c\xcf:\xa2\x1a\xb7BWj\xd8\xc4\"\xf7\x1b\xb1\x88Y\x1d\ua28965\u026d\xc5\u07a0\x00\x00\u07d4\x8c\xd0\xcd\"\xe6 \xed\xa7\x9c\x04a\xe8\x96\xc9\xd1b)\x12K_z\xfb\xec\x89\a?u\u0460\x85\xba\x00\x00\u07d4\x8c\xe2/\x9f\xa3rD\x9aB\x06\x10\xb4z\xe0\xc8\xd5eH\x122\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x8c\u451d\x8a\x16T-B<\x17\x98Ng9\xfar\u03b1w\x8a\x05K@Y&\xf4\xa6=\x80\x00\u07d4\x8c\xe5\xe3\xb5\xf5\x91\xd5\uc8ca\xbf\"\x8f.<5\x13K\xda\xc0\x89}\xc3[\x84\x89|8\x00\x00\xe0\x94\x8c\xee8\xd6YW\x88\xa5n?\xb9F4\xb3\xff\xe1\xfb\xdb&\u058a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x8c\xee\xa1^\xec;\xda\xd8\x02?\x98\xec\xf2[+\x8f\xef'\xdb)\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8c\xf3To\xd1\u0363=X\x84_\xc8\xfc\xfe\u02bc\xa7\xc5d*\x89\x1f\x1e9\x93,\xb3'\x80\x00\u07d4\x8c\xf6\xda\x02\x04\xdb\u0106\vF\xad\x97?\xc1\x11\x00\x8d\x9e\fF\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x8c\xfe\xde\xf1\x98\xdb\n\x91C\xf0\x91)\xb3\xfdd\u073b\x9bIV\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8d\x04\xa5\xeb\xfb]\xb4\t\xdb\x06\x17\xc9\xfaV1\xc1\x92\x86\x1fJ\x894\x95tD\xb8@\xe8\x00\x00\u07d4\x8d\x06\xe4d$\\\xadaI9\xe0\xaf\bE\xe6\xd70\xe2\x03t\x89\n\u070a(\xf3\xd8}\x80\x00\u07d4\x8d\a\xd4-\x83\x1c-|\x83\x8a\xa1\x87+:\xd5\xd2w\x17h#\x89\x12\xee\x1f\x9d\xdb\xeeh\x00\x00\u07d4\x8d\v\x9e\xa5?\xd2cA^\xac\x119\x1f|\xe9\x12V\xb9\xfb\x06`\xf6\xf0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8dy\\_JV\x89\xadb\u0696\x16q\xf0(\x06R\x86\xd5T\x89o\x05\xb5\x9d; \x00\x00\x00\u07d4\x8d\u007f>a)\x9c-\xb9\xb9\xc0H|\xf6'Q\x9e\xd0\n\x91#\x89^t\xa8P^\x80\xa0\x00\x00\xe0\x94\x8d\x89\x17\v\x92\xb2\xbe,\b\xd5|H\xa7\xb1\x90\xa2\xf1Fr\x0f\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4\x8d\x93\xda\u01c5\xf8\x8f\x1a\x84\xbf\x92}Se+E\xa1T\xcc\u0749\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4\x8d\x99R\u043bN\xbf\xa0\xef\xd0\x1a:\xa9\xe8\xe8\u007f\x05%t.\x89\xbb\x91%T\"c\x90\x00\x00\u07d4\x8d\x9a\fp\xd2& B\xdf\x10\x17\xd6\xc3\x03\x13 $w'\x12\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8d\x9e\xd7\xf4U0X\xc2ox6\xa3\x80-0d\xeb\x1b6=\x89\x04\xe1\x00;(\xd9(\x00\x00\u07d4\x8d\xa1\x17\x8fU\xd9wr\xbb\x1d$\x11\x1a@JO\x87\x15\xb9]\x89/\x9a\xc3\xf6\xde\x00\x80\x80\x00\u07d4\x8d\xa1\xd3Y\xbal\xb4\xbc\xc5}zCw \xd5]\xb2\xf0\x1cr\x89\x04V9\x18$O@\x00\x00\u07d4\x8d\xab\x94\x8a\xe8\x1d\xa3\x01\xd9r\xe3\xf6\x17\xa9\x12\xe5\xa7Sq.\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x8d\xad\xdfR\xef\xbdt\u0695\xb9i\xa5GoO\xbb\xb5c\xbf\u0489-C\xf3\xeb\xfa\xfb,\x00\x00\u07d4\x8d\xb1\x85\xfe\x1bp\xa9Jj\b\x0e~#\xa8\xbe\xdcJ\xcb\xf3K\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\x8d\xb5\x8e@n -\xf9\xbcpl\xb43\xe1\x94\xf4\x0f\x82\xb4\x0f\xaa\xdb\x1f\x8b\x85a\x16\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x8d\xc1\xd5\x11\x1d\t\xaf%\xfd\xfc\xacE\\|\xec(>mgu\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8d\u0504\xff\x8a0sd\xebf\xc5%\xa5q\xaa\xc7\x01\xc5\xc3\x18\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8d\u05a9\xba\xe5\u007fQ\x85I\xad\xa6wFo\ua2b0O\u0674\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8d\xde<\xb8\x11\x85h\xefE\x03\xfe\x99\x8c\xcd\xf56\xbf\x19\xa0\x98\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8d\xde`\xeb\b\xa0\x99\xd7\u06a3V\u06aa\xb2G\r{\x02Zk\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94\x8d\xf39!Kj\u0472Fc\xceq`4t\x9dn\xf88\u064a\x02TO\xaaw\x80\x90\xe0\x00\x00\xe0\x94\x8d\xf5=\x96\x19\x14q\xe0Y\xdeQ\xc7\x18\xb9\x83\xe4\xa5\x1d*\xfd\x8a\x06\u01b95\xb8\xbb\xd4\x00\x00\x00\u07d4\x8d\xfb\xaf\xbc\x0e[\\\x86\xcd\x1a\u0597\xfe\xea\x04\xf41\x88\u0796\x89\x15%+\u007f_\xa0\xde\x00\x00\u07d4\x8e\a;\xad%\xe4\"\x18a_J\x0ek.\xa8\xf8\xde\"0\xc0\x89\x82=b\x9d\x02k\xfa\x00\x00\u07d4\x8e\x0f\xee8hZ\x94\xaa\xbc\xd7\u0385{k\x14\t\x82Ou\xb8\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x8e#\xfa\xcd\x12\xc7e\xc3j\xb8\x1am\xd3M\x8a\xa9\xe6\x89\x18\xae\x89\t\x11\u418d\xba\x9b\x00\x00\xe0\x94\x8e/\x904\xc9%G\x19\u00ceP\u026ad0^\u0596\xdf\x1e\x8a\x01\x00N.E\xfb~\xe0\x00\x00\u07d4\x8e2@\xb0\x81\x0e\x1c\xf4\a\xa5\x00\x80G@\u03cdad2\xa4\x89\x02/fU\xef\v8\x80\x00\u07d4\x8eHj\x04B\xd1q\xc8`[\xe3H\xfe\xe5~\xb5\b^\xff\r\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8eaV3k\xe2\u037e2\x14\r\xf0\x8a+\xa5_\u0425\x84c\x89\x04\t\x9e\x1dcW\x18\x00\x00\u07d4\x8eg\b\x15\xfbg\xae\xae\xa5{\x86SN\xdc\x00\xcd\xf5d\xfe\u5272\xe4\xb3#\xd9\xc5\x10\x00\x00\u07d4\x8emt\x85\xcb\u942c\xc1\xad\x0e\xe9\xe8\xcc\xf3\x9c\f\x93D\x0e\x893\xc5I\x901r\f\x00\x00\xe0\x94\x8et\xe0\u0477~\xbc\x82:\xca\x03\xf1\x19\x85L\xb1 '\xf6\u05ca\x16\xb3R\xda^\x0e\xd3\x00\x00\x00\u07d4\x8ex\xf3QE}\x01oJ\xd2u^\xc7BN\\!\xbamQ\x89\a\xea(2uw\b\x00\x00\u07d4\x8ey6\u0552\x00\x8f\xdcz\xa0N\xde\xebuZ\xb5\x13\u06f8\x9d\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x8e\u007f\xd28H\xf4\xdb\a\x90j}\x10\xc0K!\x80;\xb0\x82'\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x8e\x92\xab\xa3\x8er\xa0\x98\x17\v\x92\x95\x92FSz.UV\xc0\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4\x8e\x98ve$\xb0\xcf'G\xc5\r\xd4;\x95gYM\x971\u0789lD\xb7\xc2a\x82(\x00\x00\u07d4\x8e\x9b5\xadJ\n\x86\xf7XDo\xff\xde4&\x9d\x94\f\xea\u0349\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8e\x9c\b\xf78f\x1f\x96v#n\xff\x82\xbaba\xdd?H\"\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x8e\x9cB\x92f\xdf\x05~\xfax\xdd\x1d_w\xfc@t*\xd4f\x89\x10D.\u0475l|\x80\x00\u07d4\x8e\xa6V\xe7\x1e\xc6Q\xbf\xa1|ZWY\xd8`1\xcc5\x99w\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x8e\xae)CU\x98\xba\x8f\x1c\x93B\x8c\xdb>+M1\a\x8e\x00\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8e\xb1\xfb\xe4\xe5\xd3\x01\x9c\xd7\xd3\r\xae\x9c\r[Lv\xfbc1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8e\xb5\x17t\xaf k\x96k\x89\t\xc4Z\xa6r'H\x80,\f\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x8e\xb8\xc7\x19\x82\xa0\x0f\xb8Bu)2S\xf8\x04ED\xb6kI\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\x8e\xcb\u03ec\xbf\xaf\xe9\xf0\f9\"\xa2N,\xf0\x02gV\xca \x8a\x011\xbe\xb9%\xff\xd3 \x00\x00\u07d4\x8e\u03b2\xe1$Sl[_\xfcd\x0e\xd1O\xf1^\u0668\xcbq\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8e\u042f\x11\xff(p\xda\x06\x81\x00J\xfe\x18\xb0\x13\xf7\xbd8\x82\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u0794\x8e\xd1Cp\x1f/r(\x0f\xd0J{Ad(\x19y\xea\x87\u0248\xc2I\xfd\xd3'x\x00\x00\u07d4\x8e\xd1R\x8bD~\xd4)y\x02\xf69\xc5\x14\u0414J\x88\xf8\u0209\n\xc6\xe7z\xb6c\xa8\x00\x00\u07d4\x8e\xd4(L\x0fGD\x9c\x15\xb8\u0673$]\u8fb6\u0380\xbf\x89+^:\xf1k\x18\x80\x00\x00\xe0\x94\x8e\xde~=\xc5\aI\xc6\xc5\x0e.(\x16\x84x\xc3M\xb8\x19F\x8a\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4\x8e\xe5\x843}\xdb\xc8\x0f\x9e4\x98\xdfU\xf0\xa2\x1e\xac\xb5\u007f\xb1\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x8e\xeb\xec\x1ab\xc0\x8b\x05\xa7\xd1\u0551\x80\xaf\x9f\xf0\u044e?6\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x8e\xf4\u0622\xc2o\xf7\x14\xb6u\x89\x19\x80\x1c\x83\xb6\xc7\xc0\x00\x00\u07d4\x8fM\x1dAi>F,\xf9\x82\xfd\x81\u042ap\x1d:St\u0249\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8fM\x1e~Ea(J4\xfe\xf9g<\r4\xe1*\xf4\xaa\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8fO\xb1\xae\xa7\xcd\x0fW\x0e\xa5\xe6\x1b@\xa4\xf4Q\vbd\xe4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8fV\x1bA\xb2\t\xf2H\u0229\x9f\x85\x87\x887bP`\x9c\xf3\x89\\(=A\x03\x94\x10\x00\x00\xe0\x94\x8fX\xd84\x8f\xc1\xdcN\r\xd84;eC\xc8W\x04^\xe9@\x8a\x02\xe3\x03\x8d\xf4s\x03(\x00\x00\u07d4\x8f`\x89_\xbe\xbb\xb5\x01\u007f\xcb\xff<\u0763\x97)+\xf2[\xa6\x89\x17D\x06\xff\x9fo\u0480\x00\u07d4\x8fd\xb9\xc1$m\x85x1d1\a\xd3U\xb5\xc7_\xef]O\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\x8ff\x0f\x8b.L|\u00b4\xac\x9cG\xed(P\x8d_\x8f\x86P\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x8fi\xea\xfd\x023\xca\xdb@Y\xabw\x9cF\xed\xf2\xa0PnH\x89`\xf0f \xa8IE\x00\x00\xe0\x94\x8fq~\xc1U/LD\x00\x84\xfb\xa1\x15J\x81\xdc\x00>\xbd\xc0\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x8f\x8a\xcb\x10v\a8\x84y\xf6K\xaa\xab\xea\x8f\xf0\a\xad\xa9}\x8a\x05\xc6\xf3\b\n\xd4#\xf4\x00\x00\u07d4\x8f\x8c\xd2n\x82\xe7\xc6\xde\xfd\x02\u07ed\a\x97\x90!\xcb\xf7\x15\f\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x8f\x8f7\u042d\x8f3]*q\x01\xb4\x11V\xb6\x88\xa8\x1a\x9c\xbe\x89\x03\xcbq\xf5\x1f\xc5X\x00\x00\u07d4\x8f\x92\x84O(*\x92\x99\x9e\u5d28\xd7s\xd0kiM\xbd\x9f\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\x8f\xact\x8fxJ\x0f\xedh\u06e43\x19\xb4*u\xb4d\x9cn\x891T\xc9r\x9d\x05x\x00\x00\u07d4\x8f\u0665\xc3:}\x9e\xdc\xe0\x99{\xdfw\xab0d$\xa1\x1e\xa9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8f\xef\xfa\xdb8z\x15G\xfb(M\xa9\xb8\x14\u007f>|m\xc6\u0689-b{\xe4S\x05\b\x00\x00\u07d4\x8f\xf4`Ehw#\xdc3\xe4\u0419\xa0i\x04\xf1\ubd44\u0709lk\x93[\x8b\xbd@\x00\x00\u07d4\x8f\xfa\x06!\"\xac0t\x18\x82\x1a\u06d3\x11\aZ7\x03\xbf\xa3\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x8f\xfe2)\x97\xb8\xe4\x04B-\x19\xc5J\xad\xb1\x8f[\xc8\u9dc9\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x90\x01\x94\u0131\aC\x05\u045d\xe4\x05\xb0\xacx(\x0e\xca\xf9g\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x90\x03\xd2p\x89\x1b\xa2\xdfd=\xa84\x15\x83\x195E\xe3\xe0\x00\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x90\x05z\xf9\xaaf0~\xc9\xf03\xb2\x97$\u04f2\xf4\x1e\xb6\xf9\x8a\x19\xd1\u05aa\xdb,R\xe8\x00\x00\u07d4\x90\x0f\v\x8e5\xb6h\xf8\x1e\xf2R\xb18U\xaaP\a\xd0\x12\xe7\x89\x17\n\x0fP@\xe5\x04\x00\x00\u07d4\x90\x18\xcc\x1fH\xd20\x8e%*\xb6\b\x9f\xb9\x9a|\x1dV\x94\x10\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x90\x1d\x99\xb6\x99\xe5\u0191\x15\x19\xcb v\xb4\xc7c0\xc5M\"\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x90-t\xa1W\xf7\u04b9\xa37\x8b\x1fVp70\xe0:\x17\x19\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x904\x13\x87\x8a\xea;\xc1\bc\t\xa3\xfev\x8beU\x9e\x8c\xab\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x90If\xcc\"\x13\xb5\xb8\xcb[\xd6\b\x9e\xf9\xcd\xdb\xef~\xdf\u0309lk\x93[\x8b\xbd@\x00\x00\u07d4\x90L\xaaB\x9ca\x9d\x94\x0f\x8egA\x82j\r\xb6\x92\xb1\x97(\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x90R\xf2\xe4\xa3\xe3\xc1-\xd1\xc7\x1b\xf7\x8aN\xc3\x04=\u020b~\x89\x0e~\xeb\xa3A\vt\x00\x00\u0794\x90U&V\x8a\xc1#\xaf\xc0\xe8J\xa7\x15\x12O\xeb\xe8=\xc8|\x88\xf8i\x93)g~\x00\x00\u07d4\x90\x92\x91\x87\a\xc6!\xfd\xbd\x1d\x90\xfb\x80\xebx\u007f\xd2osP\x89\x85[[\xa6\\\x84\xf0\x00\x00\u07d4\x90\x9b^v:9\xdc\u01d5\"=s\xa1\u06f7\xd9L\xa7Z\u0209lk\x93[\x8b\xbd@\x00\x00\u07d4\x90\xac\xce\xd7\xe4\x8c\b\u01b94dm\xfa\n\xdf)\u0714\aO\x89\x03\vK\x15{\xbdI\x00\x00\u07d4\x90\xb1\xf3p\xf9\xc1\xeb\v\xe0\xfb\x8e+\x8a\xd9jAcq\u074a\x890\xca\x02O\x98{\x90\x00\x00\u07d4\x90\xb6/\x13\x1a_)\xb4UqQ>\xe7\xa7J\x8f\v#\"\x02\x89\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4\x90\xbdb\xa0P\x84Ra\xfaJ\x9f|\xf2A\xeac\v\x05\ufe09\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x90\xc4\x1e\xba\x00\x8e \xcb\xe9'\xf3F`?\u0206\x98\x12Yi\x89\x02F\xdd\xf9yvh\x00\x00\u07d4\x90\u0480\x9a\xe1\xd1\xff\xd8\xf6>\xda\x01\xdeI\xddU-\xf3\u047c\x89\u063beI\xb0+\xb8\x00\x00\u07d4\x90\xdc\t\xf7\x17\xfc*[i\xfd`\xba\b\xeb\xf4\v\xf4\xe8$l\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\x90\xe3\x00\xacqE\x1e@\x1f\x88\u007fnw(\x85\x16G\xa8\x0e\a\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x90\xe3Z\xab\xb2\xde\xef@\x8b\xb9\xb5\xac\xefqDW\xdf\xdebr\x89\x05l\xd5_\xc6M\xfe\x00\x00\u07d4\x90\xe7\a\x0fM\x03?\xe6\x91\f\x9e\xfeZ'\x8e\x1f\xc6#M\xef\x89\x05q8\b\x19\xb3\x04\x00\x00\u07d4\x90\xe9>M\xc1q!HyR36\x14\x00+\xe4#VI\x8e\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x90\u9a68.\u06a8\x14\u0084\xd22\xb6\u9e90p\x1dIR\x89\x05k\xe0<\xa3\xe4}\x80\x00\u07d4\x90\xf7t\xc9\x14}\u0790\x85=\xdcC\xf0\x8f\x16\xd4U\x17\x8b\x8c\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x90\xfcS{!\x06Xf\n\x83\xba\xa9\xacJ\x84\x02\xf6WF\xa8\x89e\xea=\xb7UF`\x00\x00\u07d4\x91\x05\n\\\xff\xad\xed\xb4\xbbn\xaa\xfb\xc9\xe5\x014(\xe9l\x80\x89\\(=A\x03\x94\x10\x00\x00\u07d4\x91\x05\x17d\xafk\x80\x8eB\x12\xc7~0\xa5W.\xaa1pp\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x91\v}Wz~9\xaa#\xac\xf6*\xd7\xf1\xef4)4\xb9h\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x91\x0e\x99eC4Lh\x15\xfb\x97\u0367\xafK\x86\x98vZ[\x89\x05\x9a\xf6\x98)\xcfd\x00\x00\u07d4\x91\x1f\xee\xa6\x1f\xe0\xedP\u0179\xe5\xa0\xd6`q9\x9d(\xbd\u0189\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\x91\x1f\xf23\xe1\xa2\x11\xc0\x17,\x92\xb4l\xf9\x97\x03\x05\x82\xc8:\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x91 \xe7\x11s\xe1\xba\x19\xba\x8f\x9fO\xdb\u072a4\xe1\u05bbx\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x91!\x17\x12q\x9f+\bM;8u\xa8Pi\xf4f61A\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x91#\x04\x11\x8b\x80G=\x9e\x9f\xe3\xeeE\x8f\xbea\x0f\xfd\xa2\xbb\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x91Tky\xec\xf6\x9f\x93kZV\x15\b\xb0\xd7\xe5\f\u0159/\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4\x91V\u0440)5\x0eG\x04\b\xf1_\x1a\xa3\xbe\x9f\x04\ng\u018965\u026d\xc5\u07a0\x00\x00\u07d4\x91b\x0f>\xb3\x04\xe8\x13\u048b\x02\x97Ume\xdcN]\u5a89\xcf\x15&@\xc5\xc80\x00\x00\xe0\x94\x91k\xf7\xe3\xc5E\x92\x1d2\x06\xd9\x00\xc2O\x14\x12|\xbd^p\x8a\x03\xd0\u077c}\xf2\xbb\x10\x00\x00\u0794\x91l\xf1}qA(\x05\xf4\xaf\xc3DJ\v\x8d\xd1\xd93\x9d\x16\x88\xc6s\xce<@\x16\x00\x00\u07d4\x91{\x8f\x9f:\x8d\t\xe9 ,R\u009erA\x96\xb8\x97\xd3^\x89\b\xbaR\xe6\xfcE\xe4\x00\x00\u07d4\x91\x89g\x91\x8c\u0617\xdd\x00\x05\xe3m\xc6\u0203\xefC\x8f\xc8\u01c9\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4\x91\x89\x8e\xab\x8c\x05\xc0\"(\x83\xcdM\xb2;w\x95\xe1\xa2J\u05c9lk\x93[\x8b\xbd@\x00\x00\u0794\x91\x91\xf9F\x98!\x05\x16\xcfc!\xa1B\a\x0e Yvt\xed\x88\xee\x9d[\xe6\xfc\x11\x00\x00\u07d4\x91\xa4\x14\x9a,{\x1b:g\xea(\xaf\xf3G%\u0fcdu$\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\x91\xa7\x87\xbcQ\x96\xf3HW\xfe\f7/M\xf3v\xaa\xa7f\x13\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x91\xa8\xba\xae\xd0\x12\xea.c\x80;Y=\r\f*\xabL[\n\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\x91\xac\\\xfeg\xc5J\xa7\xeb\xfb\xa4HflF\x1a;\x1f\xe2\xe1\x89\x15\xc94\x92\xbf\x9d\xfc\x00\x00\u07d4\x91\xbb?y\x02+\xf3\xc4S\xf4\xff%n&\x9b\x15\xcf,\x9c\xbd\x89RX\\\x13\xfe:\\\x00\x00\u07d4\x91\xc7^<\xb4\xaa\x89\xf3F\x19\xa1d\xe2\xa4x\x98\xf5gM\x9c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x91\xc8\f\xaa\b\x1b85\x1d*\x0e\x0e\x00\xf8\n4\xe5dt\xc1\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x91\xccF\xaa7\x9f\x85jf@\xdc\xcdZd\x8ay\x02\xf8I\u0649\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x91\u04a9\xee\x1am\xb2\x0fS\x17\u0327\xfb\xe218\x95\u06ce\xf8\x8a\x01\xcc\u00e5/0n(\x00\x00\u07d4\x91\xd6n\xa6(\x8f\xaaK=`l*\xa4\\{k\x8a%'9\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x91\u06f6\xaa\xad\x14\x95\x85\xbeG7\\]m\xe5\xff\t\x19\x15\x18\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x91\xe8\x81\x06R\xe8\xe6\x16\x15%\xd6;\xb7u\x1d\xc2\x0fg`v\x89'Mej\xc9\x0e4\x00\x00\u07d4\x91\xf5\x16\x14l\xda (\x17\x19\x97\x80`\u01beAI\x06|\x88\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x91\xf6$\xb2J\x1f\xa5\xa0V\xfeW\x12)\xe77\x9d\xb1K\x9a\x1e\x8a\x02\x8a\x85\x17\xc6i\xb3W\x00\x00\xe0\x94\x91\xfe\x8aLad\u07cf\xa6\x06\x99]k\xa7\xad\xca\xf1\u0213\u038a\x03\x99\x92d\x8a#\u0220\x00\x00\u07d4\x92\x1fRa\xf4\xf6\x12v\a\x06\x89&%\xc7^{\u0396\xb7\b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92!\xc9\xce\x01#&et\x10\x96\xac\a#Y\x03\xad\x1f\xe2\xfc\x89\x06\xdbc3U\"b\x80\x00\u07d4\x92%\x988`\xa1\xcbF#\xc7$\x80\xac\x16'+\f\x95\xe5\xf5\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x92%\xd4jZ\x80\x949$\xa3\x9e[\x84\xb9m\xa0\xacE\x05\x81\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\x92* \u01da\x1d:&\xdd8)g{\xf1\xd4\\\x8fg+\xb6\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x92C\x8eR\x03\xb64o\xf8\x86\xd7\xc3b\x88\xaa\xcc\xccx\xce\u028965\u026d\xc5\u07a0\x00\x00\u07d4\x92C\xd7v-w({\x12c\x86\x88\xb9\x85N\x88\xa7i\xb2q\x8965\u026d\xc5\u07a0\x00\x00\u0794\x92K\xcez\x85<\x97\v\xb5\xec{\xb7Y\xba\xeb\x9ct\x10\x85{\x88\xbe -j\x0e\xda\x00\x00\u07d4\x92N\xfam\xb5\x95\xb7\x93\x13'~\x881\x96%\akX\n\x10\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92U\x82&\xb3\x84bl\xadH\xe0\x9d\x96k\xf19^\xe7\xea]\x89\x12\x1e\xa6\x8c\x11NQ\x00\x00\u07d4\x92`\x82\xcb~\xedK\x19\x93\xad$ZGrg\xe1\xc3<\xd5h\x89\x14Jt\xba\u07e4\xb6\x00\x00\u07d4\x92b\t\xb7\xfd\xa5N\x8d\u06dd\x9eM=\x19\xeb\u070e\x88\u009f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92h\xd6&FV6\x11\xdc;\x83*0\xaa#\x94\xc6F\x13\xe3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92i\x8e4Sx\xc6-\x8e\xda\x18M\x946j\x14K\f\x10[\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\x92y:\u0173rhwJq0\xde+\xbd3\x04\x05f\x17s\x89\x02,\xa3X|\xf4\xeb\x00\x00\xe0\x94\x92y\xb2\"\x8c\xec\x8f{M\xda?2\x0e\x9a\x04f\xc2\xf5\x85\u028a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x92|\xb7\xdc\x18p6\xb5B{\xc7\xe2\x00\xc5\xecE\f\x1d'\u0509\v\xb5\x9a'\x95<`\x00\x00\u07d4\x92|\u00bf\xda\x0e\b\x8d\x02\xef\xf7\v8\xb0\x8a\xa5<\xc3\tA\x89do`\xa1\xf9\x866\x00\x00\xe0\x94\x92\x84\xf9m\xdbG\xb5\x18n\xe5X\xaa12M\xf56\x1c\x0fs\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\x92\x9d6\x8e\xb4j-\x1f\xbd\xc8\xff\xa0`~\xdeK\xa8\x8fY\xad\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92\xa7\u0166Cb\xe9\xf8B\xa2=\xec\xa2\x105\x85\u007f\x88\x98\x00\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x92\xa8\x98\xd4o\x19q\x9c8\x12j\x8a<'\x86z\xe2\xce\u5589lk\x93[\x8b\xbd@\x00\x00\u07d4\x92\xa9q\xa79y\x9f\x8c\xb4\x8e\xa8G]r\xb2\xd2GAr\xe6\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x92\xaa\xe5\x97h\xed\xdf\xf8<\xfe`\xbbQ.s\n\x05\xa1a\u05c9\\\x97xA\fv\u0440\x00\u07d4\x92\xad\x1b=u\xfb\xa6}Tf=\xa9\xfc\x84\x8a\x8a\xde\x10\xfag\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92\xae[|~\xb4\x92\xff\x1f\xfa\x16\xddB\xad\x9c\xad@\xb7\xf8\u0709.\xe4IU\b\x98\xe4\x00\x00\u07d4\x92\xc0\xf5s\xec\xcfb\xc5H\x10\xeek\xa8\xd1\xf1\x13T+0\x1b\x89\xb7ro\x16\u0331\xe0\x00\x00\u07d4\x92\xc1?\xe0\xd6\u0387\xfdP\xe0=\uf7e6@\x05\t\xbdps\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x92\xc9L( \xdf\xcfqV\xe6\xf10\x88\xec\u754b6v\xfd\x89\x05-T(\x04\xf1\xce\x00\x00\u07d4\x92\xcf\xd6\x01\x88\xef\u07f2\xf8\xc2\xe7\xb1i\x8a\xbb\x95&\xc1Q\x1f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92\u062d\x9aMah;\x80\u0526g.\x84\xc2\rbB\x1e\x80\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x92\u0725\xe1\x02\xb3\xb8\x1b`\xf1\xa5\x04cIG\xc3t\xa8\x8c\u02c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x92\xe454\x0e\x9d%<\x00%c\x89\xf5+\x06}U\x97Nv\x89\x0e\x87?D\x13<\xb0\x00\x00\xe0\x94\x92\xe49(\x16\xe5\xf2\xef_\xb6X7\xce\xc2\xc22\\\xc6I\"\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x92\xe6X\x1e\x1d\xa1\xf9\xb8F\xe0\x93G3=\xc8\x18\xe2\u04acf\x89\xc5S%\xcat\x15\xe0\x00\x00\u07d4\x93\x1d\xf3M\x12%\xbc\xd4\"Nch\r\\L\t\xbc\xe75\xa6\x89\x03\xaf\xb0\x87\xb8v\x90\x00\x00\u07d4\x93\x1f\xe7\x12\xf6B\a\xa2\xfdP\"r\x88CT\x8b\xfb\x8c\xbb\x05\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x93#_4\r(c\xe1\x8d/LR\x99e\x16\x13\x8d\"\x02g\x89\x04\x00.D\xfd\xa7\xd4\x00\x00\u07d4\x93%\x82U\xb3|\u007fX\xf4\xb1\x06s\xa92\xdd:\xfd\x90\xf4\xf2\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x93(\xd5\\\xcb?\xceS\x1f\x19\x93\x823\x9f\x0eWn\xe8@\xa3\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x93)\xff\xdc&\x8b\xab\u0788t\xb3f@l\x81D[\x9b-5\x89\x16\xe6/\x8cs\f\xa1\x80\x00\u07d4\x93+\x9c\x04\xd4\r*\xc80\x83\xd9B\x98\x16\x9d\xae\x81\xab.\u0409lk\x93[\x8b\xbd@\x00\x00\u07d4\x9346\xc8G&U\xf6L:\xfa\xaf|Lb\x1c\x83\xa6+8\x8965\u026d\xc5\u07a0\x00\x00\u0794\x93;\xf3?\x82\x99p+:\x90&B\xc3>\v\xfa\xea\\\x1c\xa3\x88\xd2\xf1?w\x89\xf0\x00\x00\u07d4\x93@4\\\xa6\xa3\uaf77sc\xf2X`C\xf2\x948\xce\v\x89\x1c\xc8\x05\xda\r\xff\xf1\x00\x00\xe0\x94\x93@\xb5\xf6x\xe4^\xe0^\xb7\b\xbbz\xbbn\xc8\xf0\x8f\x1bk\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\x93J\xf2\x1b~\xbf\xa4g\xe2\xce\xd6Z\xa3N\xdd:\x0e\xc7\x132\x8a\a\x80\x1f>\x80\xcc\x0f\xf0\x00\x00\xe0\x94\x93PiDJj\x98M\xe2\bNFi*\xb9\x9fg\x1f\xc7'\x8a\x01\xe7\xe4\x17\x1b\xf4\u04e0\x00\x00\xe0\x94\x93P~\x9e\x81\x19\xcb\xce\u068a\xb0\x87\xe7\xec\xb0q8=i\x81\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\x93g\x8a\x00\x00\xe0\x94\x93m\xcf\x00\x01\x94\xe3\xbf\xf5\n\u0174$:;\xa0\x14\xd6a\u060a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x93o8\x13\xf5\xf6\xa1;\x8eO\xfe\xc8?\xe7\xf8&\x18jq\u0349\x1c0s\x1c\xec\x03 \x00\x00\u07d4\x93t\x86\x9dJ\x99\x11\xee\x1e\xafU\x8b\xc4\u00b6>\xc6:\xcf\u074965\u026d\xc5\u07a0\x00\x00\u07d4\x93uc\u0628\x0f\u05657\xb0\xe6m \xa0%%\xd5\u0606`\x89\x87\x86x2n\xac\x90\x00\x00\u07d4\x93v\xdc\xe2\xaf.\xc8\xdc\xdat\x1b~sEfF\x81\xd96h\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x93\x86\x8d\xdb*yM\x02\xeb\xda/\xa4\x80|v\xe3`\x98X\u0709m\xee\x15\xfc|$\xa7\x80\x00\xe0\x94\x93\x9cC\x13\xd2(\x0e\xdf^\a\x1b\xce\xd8F\x06?\n\x97]T\x8a\x19i6\x89t\xc0[\x00\x00\x00\xe0\x94\x93\xa6\xb3\xabB0\x10\xf9\x81\xa7H\x9dJ\xad%\xe2b\\WA\x8a\x04F\x80\xfej\x1e\xdeN\x80\x00\u07d4\x93\xaa\x8f\x92\xeb\xff\xf9\x91\xfc\x05^\x90ne\x1a\xc7h\xd3+\u02092\xf5\x1e\u06ea\xa30\x00\x00\u07d4\x93\xb4\xbf?\xdf\xf6\xde?NV\xbamw\x99\xdcK\x93\xa6T\x8f\x89\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\u07d4\x93\xbc}\x9aJ\xbdD\u023b\xb8\xfe\x8b\xa8\x04\xc6\x1a\xd8\xd6Wl\x89\xd8\xd6\x11\x9a\x81F\x05\x00\x00\u07d4\x93\xc2\xe6N]\xe5X\x9e\xd2P\x06\xe8C\x19n\xe9\xb1\xcf\v>\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\xe0\x94\x93\u020e-\x88b\x1e0\xf5\x8a\x95\x86\xbe\xd4\t\x89\x99\xebg\u074a\x06\x9bZ\xfa\xc7P\xbb\x80\x00\x00\u07d4\x93\xe0\xf3~\xcd\xfb\x00\x86\xe3\xe8b\xa9p4D{\x1eM\xec\x1a\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\xe0\x94\x93\xe3\x03A\x1a\xfa\xf6\xc1\a\xa4A\x01\u026c[6\xe9\xd6S\x8b\x8a\r\xf9\xdd\xfe\xcd\x03e@\x00\x00\u07d4\x93\xf1\x8c\xd2R`@v\x14\x88\xc5\x13\x17M\x1eycv\x8b,\x89\x82\xff\xac\x9a\u0553r\x00\x00\u07d4\x94\x0fqQ@P\x9f\xfa\xbf\x97EF\xfa\xb3\x90\"\xa4\x19R\u0489K\xe4\xe7&{j\xe0\x00\x00\u07d4\x94,k\x8c\x95[\xc0\u0608\x12g\x8a#g%\xb3'9\xd9G\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\x94=7\x86JJS}5\xc8\u0657#\xcdd\x06\xce%b\xe6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x94C\x9c\xa9\xcc\x16\x9ay\u0520\x9c\xae^gvJo\x87\x1a!\x89\r\x02\xabHl\xed\xc0\x00\x00\xe0\x94\x94D\x9c\x01\xb3*\u007f\xa5Z\xf8\x10OB\xcd\xd8D\xaa\x8c\xbc@\x8a\x03\x81\x11\xa1\xf4\xf0<\x10\x00\x00\xe0\x94\x94E\xba\\0\xe9\x89a\xb8`$a\xd08]@\xfb\xd8\x03\x11\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x94O\a\xb9o\x90\xc5\xf0\xd7\xc0\u0140S1I\xf3\xf5\x85\xa0x\x89\x04\x02\xf4\xcf\xeeb\xe8\x00\x00\u07d4\x94T\xb3\xa8\xbf\xf9p\x9f\xd0\u1407~l\xb6\u0219t\xdb\u0589\x90\xf54`\x8ar\x88\x00\x00\u07d4\x94]\x96\xeaW>\x8d\xf7&+\xbf\xa5r\"\x9bK\x16\x01k\x0f\x89\vX\x9e\xf9\x14\xc1B\x00\x00\u07d4\x94^\x18v\x9d~\xe7'\xc7\x01?\x92\xde$\xd1\x17\x96\u007f\xf3\x17\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x94a'\x81\x03;W\xb1F\xeet\xe7S\xc6r\x01\u007fS\x85\xe4\x89\xc3(\t>a\xee@\x00\x00\xe0\x94\x94dJ\xd1\x16\xa4\x1c\xe2\xca\u007f\xbe\xc6\t\xbd\xefs\x8a*\xc7\u01ca\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x94p\xcc6YE\x86\x82\x18!\xc5\u0256\xb6\xed\xc8;mZ2\x89\x01M\x11 \u05f1`\x00\x00\xe0\x94\x94u\xc5\x10\xec\x9a&\x97\x92GtL=\x8c;\x0e\v_D\u04ca\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x94~\x11\xe5\xea)\ro\u00f3\x80H\x97\x9e\f\xd4N\xc7\xc1\u007f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x94\x83\u064f\x14\xa3?\xdc\x11\x8d@9U\u00995\xed\xfc_p\x89\x18\xea;4\xefQ\x88\x00\x00\u07d4\x94\x911\xf2\x89C\x92\\\xfc\x97\xd4\x1e\f\xea\v&)s\xa70\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\x94\x9f\x84\xf0\xb1\xd7\u0127\xcfI\xee\u007f\x8b,J\x13M\xe3(x\x89%\"H\u07b6\xe6\x94\x00\x00\u07d4\x94\x9f\x8c\x10{\xc7\xf0\xac\xea\xa0\xf1pR\xaa\xdb\xd2\xf9s+.\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x94\xa7\u0368\xf4\x81\xf9\u061dB\xc3\x03\xae\x162\xb3\xb7\t\xdb\x1d\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x94\xa9\xa7\x16\x911| d'\x1bQ\xc95?\xbd\xed5\x01\xa8\x89\xb5\x0f\u03ef\xeb\xec\xb0\x00\x00\u07d4\x94\xadK\xad\x82K\xd0\ub7a4\x9cX\u03bc\xc0\xff^\b4k\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\x94\xbb\xc6}\x13\xf8\x9e\xbc\xa5\x94\xbe\x94\xbcQp\x92\f0\xd9\xf3\x89\x04X\xff\xa3\x15\nT\x00\x00\u07d4\x94\xbe:\xe5Ob\xd6c\xb0\xd4\u031e\x1e\xa8\xfe\x95V\ua7bf\x89\x01C\x13,\xa8C\x18\x00\x00\xe0\x94\x94\xc0U\xe8X5z\xaa0\xcf A\xfa\x90Y\xce\x16J\x1f\x91\x8a\x04<%\xe0\xdc\xc1\xbd\x1c\x00\x00\xe0\x94\x94\xc7B\xfdz\x8by\x06\xb3\xbf\xe4\xf8\x90O\xc0\xbe\\v\x803\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x94\xcaV\xdew\u007f\xd4S\x17\u007f^\x06\x94\xc4x\xe6j\xff\x8a\x84\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x94\xd8\x10t\xdbZ\xe1\x97\u04bb\x13s\xab\x80\xa8}\x12\x1cK\u04ca\x01\xfd\x934\x94\xaa_\xe0\x00\x00\u07d4\x94\u06c0xs\x86\n\xac=Z\xea\x1e\x88^R\xbf\xf2\x86\x99T\x89\xae\x8ez\v\xb5u\xd0\x00\x00\u07d4\x94\xe1\xf5\u02db\x8a\xba\xce\x03\xa1\xa6B\x82VU;i\f#U\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x94\xef\x8b\xe4Pw\xc7\xd4\xc5e'@\u0794jbbOq?\x89\x05l\xf5Y:\x18\xf8\x80\x00\u07d4\x94\xf1?\x9f\b6\xa3\xee$7\xa8I\"\u0498M\xc0\xf7\xd5;\x89\xa2\xa02\x9b\u00ca\xbe\x00\x00\u07d4\x94\xf8\xf0W\xdb~`\xe6u\xad\x94\x0f\x15X\x85\u0464w4\x8e\x89\x15\xbeat\xe1\x91.\x00\x00\xe0\x94\x94\xfc\u03ad\xfe\\\x10\x9c^\xae\xafF-C\x871B\u020e\"\x8a\x01\x045a\xa8\x82\x93\x00\x00\x00\u07d4\x95\x03N\x16!\x86Q7\xcdG9\xb3F\xdc\x17\xda:'\xc3N\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\x95\fh\xa4\t\x88\x15M#\x93\xff\xf8\xda|\u0369\x96\x14\xf7,\x89\xf9AF\xfd\x8d\xcd\xe5\x80\x00\xe0\x94\x95\x0f\xe9\xc6\xca\xd5\f\x18\xf1\x1a\x9e\xd9\xc4W@\xa6\x18\x06\x12\u040a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x95!\x83\xcf\u04ce5.W\x9d6\xde\xce\u0171\x84P\xf7\xfb\xa0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x95'\x8b\b\xde\xe7\xc0\xf2\xc8\xc0\xf7\"\xf9\xfc\xbb\xb9\xa5$\x1f\u0689\x82\x93\t\xf6O\r\xb0\x00\x00\u07d4\x95,W\xd2\xfb\x19Q\a\xd4\xcd\\\xa3\x00wA\x19\u07ed/x\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x955r\xf0\xeam\xf9\xb1\x97\xca\xe4\x0eK\x8e\xcc\x05lCq\u014965\u026d\xc5\u07a0\x00\x00\u07d4\x95>\xf6R\xe7\xb7i\xf5=nxjX\x95/\xa9>\xe6\xab\u725b\ny\x1f\x12\x110\x00\x00\u07d4\x95DpF1;/:^\x19\xb9H\xfd;\x8b\xed\xc8,q|\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x95]\xb3\xb7C`\xb9\xa2hg~s\u03a8!f\x8a\xf6\xfa\u038a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4\x95`\xe8\xacg\x18\xa6\xa1\xcd\xcf\xf1\x89\xd6\x03\xc9\x06>A=\xa6\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x95g\xa0\u0781\x1d\xe6\xff\t[~\xe6N\u007f\x1b\x83\xc2a[\x80\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4\x95h\x1c\xda\xe6\x9b I\xce\x10\x1e2\\u\x98\x92\xca\xc3\xf8\x11\x89\x9a\xe9*\x9b\xc9L@\x00\x00\xe0\x94\x95h\xb7\xdeuV(\xaf5\x9a\x84T=\xe25\x04\xe1^A\xe6\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\x95i\xc6:\x92\x84\xa8\x05bm\xb3\xa3.\x9d#c\x93GaQ\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x95\x80\x9e\x8d\xa3\xfb\xe4\xb7\xf2\x81\xf0\xb8\xb1q_B\x0f}}c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x95\x9fW\xfd\xedj\xe3y\x13\xd9\x00\xb8\x1e_H\xa7\x93\"\xc6'\x89\r\xdb&\x10GI\x11\x80\x00\u07d4\x95\x9f\xf1\u007f\x1dQ\xb4s\xb4@\x10\x05'U\xa7\xfa\x8cu\xbdT\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x95\xa5w\xdc.\xb3\xael\xb9\xdf\xc7z\xf6\x97\xd7\xef\xdf\xe8\x9a\x01\x89\a_a\x0fp\xed \x00\x00\u07d4\x95\xcbm\x8acy\xf9J\xba\x8b\x88ViV,MD\x8eV\xa7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x95\xd5PB{ZQLu\x1ds\xa0\xf6\u049f\xb6]\"\xed\x10\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x95\u064d\f\x10i\x90\x8f\x06zR\xac\xac+\x8bSM\xa3z\xfd\x89oY\xb60\xa9)p\x80\x00\xe0\x94\x95\xdfN4E\xd7f&$\u010e\xbat\u03de\nS\xe9\xf72\x8a\v\xdb\xc4\x1e\x03H\xb3\x00\x00\x00\u07d4\x95\xe6\xa5K-_g\xa2JHu\xafu\x10|\xa7\xea\x9f\xd2\xfa\x89Hz\x9a0E9D\x00\x00\xe0\x94\x95\xe6\xf9=\xac\"\x8b\xc7XZ%sZ\xc2\xd0v\xcc:@\x17\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x95\xe7ad$\xcd\ta\xa7\x17'$t7\xf0\x06\x92r(\x0e\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x95\xe8\n\x82\xc2\f\xbe= `$,\xb9-sX\x10\xd04\xa2\x89\x01\xc3.F?\u0539\x80\x00\u07d4\x95\xf6-\x02C\xed\xe6\x1d\xad\x9a1e\xf59\x05'\rT\xe2B\x89WG=\x05\u06ba\xe8\x00\x00\u07d4\x95\xfbZ\xfb\x14\xc1\uf6b7\xd1y\xc5\xc3\x00P?\xd6j^\xe2\x89\x01\xda\xf7\xa0+\r\xbe\x80\x00\u07d4\x96\x10Y\"\x02\u0082\xab\x9b\u0628\x84Q\x8b>\v\xd4u\x817\x89\x0e\x87?D\x13<\xb0\x00\x00\xe0\x94\x96\x1cY\xad\xc7E\x05\u0446M\x1e\xcf\u02ca\xfa\x04\x12Y<\x93\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\x96,\r\xec\x8a=FK\xf3\x9b\x12\x15\xea\xfd&H\n\xe4\x90\u0349l\x82\xe3\xea\xa5\x13\xe8\x00\x00\u07d4\x96,\xd2*\x8e\xdf\x1eONU\xb4\xb1]\xdb\xfb]\x9dT\x19q\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x963K\xfe\x04\xff\xfaY\x02\x13\xea\xb3e\x14\xf38\xb8d\xb76\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x967\xdc\x12r=\x9cxX\x85B\uac02fO?\x03\x8d\x9d\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x96N\xabK'kL\u0618>\x15\xcar\xb1\x06\x90\x0f\xe4\x1f\u0389\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x96b\xee\x02\x19&h+1\xc5\xf2\x00\xceEz\xbe\xa7ll\xe9\x89$Y\x0e\x85\x89\xebj\x00\x00\xe0\x94\x96l\x04x\x1c\xb5\xe6}\xde25\xd7\xf8b\x0e\x1a\xb6c\xa9\xa5\x8a\x10\r P\xdacQ`\x00\x00\u07d4\x96pv\xa8w\xb1\x8e\xc1ZA[\xb1\x16\xf0n\xf3&E\u06e3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x96{\xfa\xf7bC\u0379@\t\xae<\x8d5\x05\xe9\xc0\x80EK\xe0\xe8\x19\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x96\x92A\x91\xb7\xdfe[3\x19\xdcma7\xf4\x81\xa7:\x0f\xf3\x89\xd9\xec\xb4\xfd \x8eP\x00\x00\u07d4\x96\x96\x05!83\x8cr/\x11@\x81\\\xf7t\x9d\r;:t\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x96\xa5_\x00\xdf\xf4\x05\xdcM\xe5\xe5\x8cW\xf6\xf6\xf0\xca\xc5]/\x89jf\x167\x9c\x87\xb5\x80\x00\u07d4\x96\xaaW?\xed/#4\x10\u06eeQ\x80\x14[#\xc3\x1a\x02\xf0\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\x96\xadW\x9b\xbf\xa8\u06ce\xbe\xc9\u0486\xa7.Fa\xee\xd8\xe3V\x89:\v\xa4+\xeca\x83\x00\x00\u07d4\x96\xb44\xfe\x06W\xe4*\u0302\x12\xb6\x86Q9\xde\xde\x15\x97\x9c\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x96\xb9\x06\xear\x9fFU\xaf\xe3\xe5}5'|\x96}\xfa\x15w\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x96\xd6-\xfdF\b\u007fb@\x9d\x93\xdd`a\x88\xe7\x0e8\x12W\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x96\xd9\u0328\xf5^\xea\x00@\xecn\xb3H\xa1wK\x95\xd9>\xf4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x96\xe7\xc0\xc9\u057f\x10\x82\x1b\xf1@\xc5X\xa1E\xb7\xca\xc2\x13\x97\x899>\xf1\xa5\x12|\x80\x00\x00\u07d4\x96\xeaj\u021a+\xac\x954{Q\u06e6=\x8b\xd5\xeb\xde\xdc\xe1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x96\xea\xfb\xf2\xfboM\xb9\xa46\xa7LE\xb5eDR\xe28\x19\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x96\xebR>\x83/P\n\x01}\xe1>\xc2\u007f]6lV\x0e\xff\x89\x10\xac\u03baC\xee(\x00\x00\u07d4\x96\xf0F*\xe6\xf8\xb9`\x88\xf7\xe9\u018ct\xb9\u062d4\xb3G\x89a\t=|,m8\x00\x00\u07d4\x96\xf8 P\vp\xf4\xa3\xe3#\x9da\x9c\xff\x8f\" u\xb15\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x96\xfeY\xc3\u06f3\xaa|\xc8\xcbbH\fe\xe5nb\x04\xa7\xe2\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x96\xffoP\x99h\xf3l\xb4,\xbaH\xdb2\xf2\x1fVv\xab\xf8\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x97\t8R*\xfb^\x8f\x99Hs\xc9\xfb\xdc&\xe3\xb3~1L\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x97\n\xbdS\xa5O\xcaJd) |\x18-MW\xbb9\u0520\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x97\r\x8b\x8a\x00\x16\xd1C\x05O\x14\x9f\xb3\xb8\xe5P\xdc\a\x97\u01c965\u026d\xc5\u07a0\x00\x00\u07d4\x97,/\x96\xaa\x00\u03ca/ Z\xbc\xf8\x93|\fu\xf5\xd8\u0649\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x97?N6\x1f\xe5\xde\u0358\x9dL\x8f}|\xc9y\x908]\xaf\x89\x15\x0f\x85C\xa3\x87B\x00\x00\u07d4\x97M\x05A\xabJG\xec\u007fu6\x9c\x00i\xb6J\x1b\x81w\x10\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u0794\x97M/\x17\x89_)\x02\x04\x9d\xea\xae\xcf\t\xc3\x04e\a@-\x88\xcc\x19\u00947\xab\x80\x00\u07d4\x97R\xd1O^\x10\x93\xf0qq\x1c\x1a\xdb\xc4\xe3\xeb\x1e\\W\xf3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x97V\xe1v\xc9\xefi>\xe1\xee\u01b9\xf8\xb1Q\xd3\x13\xbe\xb0\x99\x89A\rXj \xa4\xc0\x00\x00\u07d4\x97_7d\xe9{\xbc\xcfv|\xbd;y[\xa8m\x8b\xa9\x84\x0e\x89\x12\xc1\xb6\xee\xd0=(\x00\x00\xe0\x94\x97j\x18Sj\xf4\x18tBc\b\x87\x1b\xcd\x15\x12\xa7u\xc9\xf8\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x97n<\xea\xf3\xf1\xafQ\xf8\u009a\xff]\u007f\xa2\x1f\x03\x86\xd8\xee\x89\r\x02\xabHl\xed\xc0\x00\x00\xe0\x94\x97w\xcca\xcfuk\xe3\xb3\xc2\f\xd4I\x1ci\xd2u\xe7\xa1 \x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x97\x81\v\xaf\xc3~\x840c2\xaa\xcb5\xe9*\xd9\x11\xd2=$\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x97\x8cC\f\xe45\x9b\x06\xbc,\xdf\\)\x85\xfc\x95\x0eP\xd5\u0209\x1a\x05V\x90\xd9\u06c0\x00\x00\u07d4\x97\x95\xf6C\x19\xfc\x17\xdd\x0f\x82a\xf9\xd2\x06\xfbf\xb6L\xd0\u0249\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x97\x99\xca!\xdb\xcfi\xbf\xa1\xb3\xf7+\xacQ\xb9\xe3\xcaX|\xf9\x89\\(=A\x03\x94\x10\x00\x00\u07d4\x97\x9c\xbf!\xdf\xec\x8a\xce?\x1c\x19m\x82\u07d6%4\xdf9O\x89\x99\x91\xd4x\xddM\x16\x00\x00\u07d4\x97\x9dh\x1ca}\xa1o!\xbc\xac\xa1\x01\xed\x16\xed\x01Z\xb6\x96\x89e\xea=\xb7UF`\x00\x00\u07d4\x97\x9f0\x15\x8bWK\x99\x9a\xab4\x81\a\xb9\xee\xd8[\x1f\xf8\xc1\x894\x95tD\xb8@\xe8\x00\x00\u07d4\x97\xa8o\x01\xce?|\xfdDA3\x0e\x1c\x9b\x19\xe1\xb1\x06\x06\xef\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x97\xb9\x1e\xfesP\xc2\xd5~~@k\xab\x18\xf3a{\xcd\xe1J\x8a\x02\x1e\x19\x99\xbb\xd5\u04be\x00\x00\u07d4\x97\xd0\xd9r^;p\xe6u\x841s\x93\x8e\xd3q\xb6,\u007f\xac\x89\t79SM(h\x00\x00\u07d4\x97\xd9\xe4jv\x04\u05f5\xa4\xeaN\xe6\x1aB\xb3\xd25\x0f\xc3\xed\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x97\xdc&\xecg\n1\xe0\"\x1d*u\xbc]\xc9\xf9\f\x1fo\u0509\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94\x97\xde!\xe4!\xc3\u007f\xe4\xb8\x02_\x9aQ\xb7\xb3\x90\xb5\xdfx\x04\x8a\x10\xf0\xcf\x06M\u0552\x00\x00\x00\u07d4\x97\xe2\x89s\xb8`\xc5g@(\x00\xfb\xb6<\xe3\x9a\x04\x8a=y\x89\x05B%:\x12l\xe4\x00\x00\u07d4\x97\xe5\xcca'\xc4\xf8\x85\xbe\x02\xf4KB\xd1\u0230\xac\x91\u44c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x97\xf1\xfeL\x80\x83\xe5\x96!*\x18w(\xdd\\\xf8\n1\xbe\u0149\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x97\xf7v\x06W\xc1\xe2\x02u\x90\x86\x96>\xb4!\x1c_\x819\xb9\x8a\n\x8a\t\u007f\xcb=\x17h\x00\x00\xe0\x94\x97\xf9\x9bk\xa3\x13F\u0358\xa9\xfeL0\x8f\x87\u0165\x8cQQ\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x98\n\x84\xb6\x86\xfc1\xbd\xc8<\"\x10XTjq\xb1\x1f\x83\x8a\x89*AUH\xaf\x86\x81\x80\x00\u07d4\x98\x10\xe3J\x94\xdbn\xd1V\xd08\x9a\x0e+\x80\xf4\xfdk\n\x8a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x98\x1d\xdf\x04\x04\xe4\xd2-\xdaUj\a&\xf0\v-\x98\xab\x95i\x8965f3\xeb\xd8\xea\x00\x00\xe0\x94\x98\x1fq'u\xc0\xda\xd9u\x18\xff\xed\xcbG\xb9\xad\x1dl'b\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\x984h!\x80\xb9\x82\xd1f\xba\u06dd\x9d\x1d\x9b\xbf\x01m\x87\xee\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x986\xb4\xd3\x04sd\x1a\xb5j\xee\xe1\x92Bv\x1drrQx\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x989sB\xec_=L\xb8w\xe5N\xf5\xd6\xf1\xd3fs\x1b\u050a\x01@a\xb9\xd7z^\x98\x00\x00\xe0\x94\x98Fd\x886\xa3\a\xa0W\x18O\xd5\x1fb\x8a_\x8c\x12B|\x8a\x04\vi\xbfC\xdc\xe8\xf0\x00\x00\xe0\x94\x98Jy\x85\xe3\xcc~\xb5\xc96\x91\xf6\xf8\xcc{\x8f$]\x01\xb2\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\x98]p\xd2\a\x89+\xed9\x85\x90\x02N$!\xb1\xcc\x11\x93Y\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x98m\xf4~v\xe4\u05e7\x89\xcd\xee\x91<\u0243\x16P\x93l\x9d\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x98t\x80?\xe1\xf3\xa06^y\"\xb1Bp\xea\xeb\x03,\xc1\xb5\x89<\xf5\x92\x88$\xc6\xc2\x00\x00\u07d4\x98ub4\x95\xa4l\xdb\xf2YS\x0f\xf88\xa1y\x9e\u00c9\x91\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x98v\x18\xc8VV |{\xac\x15\a\xc0\xff\xef\xa2\xfbd\xb0\x92\x89\x03}\xfeC1\x89\xe3\x80\x00\u07d4\x98|\x9b\xcdn?9\x90\xa5+\xe3\xed\xa4q\f'Q\x8fOr\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x98\x82\x96|\xeeh\u04a89\xfa\u062bJ|=\xdd\xf6\xc0\xad\u0209Hx\xbe\x1f\xfa\xf9]\x00\x00\u07d4\x98\x85\\}\xfb\xee3SD\x90J\x12\xc4\fs\x17\x95\xb1:T\x899\xfb\xae\x8d\x04-\xd0\x00\x00\u07d4\x98\x9c\f\xcf\xf6T\xda\x03\xae\xb1\x1a\xf7\x01\x05Ea\xd6)~\x1d\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x98\xa0\xe5Lm\x9d\u023e\x96'l\xeb\xf4\xfe\xc4`\xf6#]\x85\x89j\u0202\x10\tR\u01c0\x00\u07d4\x98\xb7i\xcc0\\\xec\xfbb\x9a\x00\xc9\a\x06\x9d~\xf9\xbc:\x12\x89\x01h\u048e?\x00(\x00\x00\xe0\x94\x98\xbaN\x9c\xa7/\xdd\xc2\fi\xb49ov\xf8\x18?z*N\x8a\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\x00\u07d4\x98\xbeimQ\xe3\x90\xff\x1cP\x1b\x8a\x0fc1\xb6(\xdd\u016d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x98\xbe\u04e7.\xcc\xfb\xaf\xb9#H\x92\x93\xe4)\xe7\x03\xc7\xe2[\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x98\xbfJ\xf3\x81\v\x84#\x87\xdbp\xc1MF\t\x96&\x00=\x10\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x98\xc1\x0e\xbf,O\x97\u02e5\xa1\xab?*\xaf\xe1\xca\xc4#\xf8\u02c9\x10CV\x1a\x88)0\x00\x00\u07d4\x98\xc1\x9d\xba\x81\v\xa6\x11\xe6\x8f/\x83\xee\x16\xf6\xe7tO\f\x1f\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x98\xc5IJ\x03\xac\x91\xa7h\xdf\xfc\x0e\xa1\xdd\u0b3f\x88\x90\x19\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\x98\xd2\x04\xf9\b_\x8c\x8e}\xe2>X\x9bd\xc6\xef\xf6\x92\xccc\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x98\xd3s\x19\x92\xd1\xd4\x0e\x12\x11\xc7\xf75\xf2\x18\x9a\xfa\a\x02\xe0\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x98\xe2\xb6\xd6\x06\xfd-i\x91\xc9\xd6\xd4\a\u007f\xdf?\xddE\x85\u06890\xdf\x1ao\x8a\xd6(\x00\x00\u07d4\x98\xe3\xe9\v(\xfc\xca\ue087y\xb8\xd4\nUh\xc4\x11n!\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x98\xe6\xf5G\u06c8\xe7_\x1f\x9c\x8a\xc2\xc5\xcf\x16'\xbaX\v>\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x98\xf4\xaf:\xf0\xae\xde_\xaf\xdcB\xa0\x81\xec\xc1\xf8\x9e<\xcf \x8a\x01\xfd\x934\x94\xaa_\xe0\x00\x00\u07d4\x98\xf6\xb8\xe6!=\xbc\x9aU\x81\xf4\xcc\xe6e_\x95%+\xdb\a\x89\x11Xr\xb0\xbc\xa40\x00\x00\u07d4\x99\te\r\u05719{\x8b\x8b\x0e\xb6\x94\x99\xb2\x91\xb0\xad\x12\x13\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x99\x11s`\x19G\xc2\bJb\xd69R~\x96\x15\x12W\x9a\xf9\x89 \x86\xac5\x10R`\x00\x00\u07d4\x99\x12\x9d[<\f\xdeG\xea\r\xefM\xfc\a\r\x1fJY\x95'\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x99\x17\u058dJ\xf3A\xd6Q\xe7\xf0\a\\m\xe6\xd7\x14Nt\t\x8a\x012\xd4Gl\b\xe6\xf0\x00\x00\u07d4\x99\x1a\xc7\xcap\x97\x11_& ^\xee\x0e\xf7\xd4\x1e\xb4\xe3\x11\xae\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u0794\x99#e\xd7d\xc5\xce5@9\xdd\xfc\x91.\x02:u\xb8\xe1h\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x99&F\xac\x1a\u02ab\xf5\u076b\xa8\xf9B\x9a\xa6\xa9Nt\x96\xa7\x8967Pz0\xab\xeb\x00\x00\u07d4\x99&\x83'\xc3s3.\x06\xc3\xf6\x16B\x87\xd4U\xb9\xd5\xfaK\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x99(\xffqZ\xfc:+`\xf8\xebL\u013aN\xe8\u06b6\u5749\x17\xda:\x04\u01f3\xe0\x00\x00\u07d4\x992\xef\x1c\x85\xb7Z\x9b*\x80\x05}P\x874\xc5\x10\x85\xbe\u0309\x02\xb8?\xa50\x1dY\x00\x00\xe0\x94\x99?\x14ax`^f\xd5\x17\xbex.\xf0\xb3\xc6\x1aN\x19%\x8a\x01|\x1f\x055\u05e5\x83\x00\x00\xe0\x94\x99A7\x04\xb1\xa3.p\xf3\xbc\ri\u0748\x1c8VkT\u02ca\x05\xcckiF1\xf7\x12\x00\x00\u07d4\x99AR\xfc\x95\xd5\xc1\u028b\x88\x11:\xbb\xadMq\x0e@\xde\xf6\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x99D\xfe\xe9\xd3JJ\x88\x00#\u01c92\xc0\vY\xd5\xc8*\x82\x89(\xa8\xa5k6\x90\a\x00\x00\u07d4\x99L\u00b5\"~\xc3\xcf\x04\x85\x12F|A\xb7\xb7\xb7H\x90\x9f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x99q\xdf`\xf0\xaef\xdc\xe9\xe8\xc8N\x17\x14\x9f\t\xf9\xc5/d\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x99v\x94~\xff_j\xe5\xda\b\xddT\x11\x92\xf3x\xb4(\xff\x94\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x99}e\x92\xa3\x15\x89\xac\xc3\x1b\x99\x01\xfb\xeb<\xc3\xd6[2\x15\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x99\x82\xa5\x89\x0f\xfbT\x06\u04ec\xa8\u04bf\xc1\xddp\xaa\xa8\n\xe0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x99\x87\x8f\x9dn\n~\u066e\u01c2\x97\xb78y\xa8\x01\x95\xaf\xe0\x89\xd7\xc1\x98q\x0ef\xb0\x00\x00\u07d4\x99\x8c\x1f\x93\xbc\xdbo\xf2<\x10\xd0\u0712G(\xb7;\xe2\xff\x9f\x896[\xf3\xa43\xea\xf3\x00\x00\u07d4\x99\x91aL[\xaaG\xddl\x96\x87FE\xf9z\xdd,=\x83\x80\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x99\x92J\x98\x16\xbb}\xdf?\xec\x18D\x82\x8e\x9a\xd7\xd0k\xf4\xe6\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\x99\x99vh\xf7\xc1\xa4\xff\x9e1\xf9\x97z\xe3\"K\u02c8z\x85\x89\x0f\xc969(\x01\xc0\x00\x00\u07d4\x99\x9cI\xc1t\xca\x13\xbc\x83l\x1e\n\x92\xbf\xf4\x8b'\x15C\u0289\xb1\xcf$\xdd\u0431@\x00\x00\u07d4\x99\xa4\xde\x19\xde\u05d0\b\xcf\xdc\xd4]\x01M.XK\x89\x14\xa8\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\x99\xa9k\xf2$.\xa1\xb3\x9e\xceo\xcc\r\x18\xae\xd0\f\x01y\xf3\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x99\xb0\x18\x93+\xca\xd3U\xb6y+%]\xb6p-\xec\x8c\xe5\u0749\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\x99\xb7C\xd1\xd9\xef\xf9\r\x9a\x194\xb4\xdb!\xd5\x19\u061bJ8\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x99\xb8\xc8$\x86\x9d\xe9\xed$\xf3\xbf\xf6\x85L\xb6\xddE\xcc?\x9f\x89e\xea=\xb7UF`\x00\x00\u07d4\x99\xc0\x17L\xf8N\a\x83\xc2 \xb4\xebj\xe1\x8f\xe7\x03\x85J\u04c9py\xa2W=\fx\x00\x00\u07d4\x99\xc1\xd9\xf4\fj\xb7\xf8\xa9/\xce/\xdc\xe4zT\xa5\x86\xc5?\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\x99\xc26\x14\x1d\xae\xc87\xec\xe0O\xda\xee\x1d\x90\u03cb\xbd\xc1\x04\x89ve\x16\xac\xac\r \x00\x00\u07d4\x99\xc3\x1f\xe7HX7\x87\xcd\xd3\xe5%\xb2\x81\xb2\x18\x96\x179\xe3\x897\b\xba\xed=h\x90\x00\x00\u07d4\x99\xc4u\xbf\x02\xe8\xb9!J\xda_\xad\x02\xfd\xfd\x15\xba6\\\f\x89 \t\xc5\u023fo\xdc\x00\x00\u07d4\x99\u0203%\x85F\xcc~N\x97\x1fR.8\x99\x18\xda^\xa6:\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x99\xc9\xf9>E\xfe<\x14\x18\xc3S\xe4\u016c8\x94\xee\xf8\x12\x1e\x89\x05\x85\xba\xf1E\x05\v\x00\x00\xe0\x94\x99\xd1W\x9c\xd4&\x82\xb7dN\x1dOq(D\x1e\xef\xfe3\x9d\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x99\u0475\x85\x96_@jB\xa4\x9a\x1c\xa7\x0fv\x9evZ?\x98\x8a\x03\x89O\x0eo\x9b\x9fp\x00\x00\u07d4\x99\xdf\xd0PL\x06\xc7C\xe4e4\xfd{U\xf1\xf9\xc7\xec3)\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x99\xf4\x14|\xcck\u02c0\u0304.i\xf6\xd0\x0e0\xfaA3\u0649\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x99\xf7\u007f\x99\x8b \xe0\xbc\xdc\xd9\xfc\x83\x86ARl\xf2Y\x18\xef\x89a\t=|,m8\x00\x00\u07d4\x99\xfa\xd5\x008\xd0\xd9\xd4\xc3\xfb\xb4\xbc\xe0V\x06\xec\xad\xcdQ!\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x99\xfe\r \x12(\xa7S\x14VU\xd4(\xeb\x9f\xd9I\x85\xd3m\x89i \xbf\xf3QZ:\x00\x00\u07d4\x9a\a\x9c\x92\xa6)\xca\x15\xc8\xca\xfa.\xb2\x8d[\xc1z\xf8(\x11\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x9a\r<\xee=\x98\x92\xea;7\x00\xa2\u007f\xf8A@\xd9\x02T\x93\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\x9a$\u038dH\\\xc4\xc8nI\u07b3\x90\"\xf9,t0\xe6~\x89Fy\x1f\xc8N\a\xd0\x00\x00\u07d4\x9a,\xe4;]\x89\u0593k\x8e\x8c5G\x91\xb8\xaf\xff\x96$%\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9a9\x01bS^9\x88w\xe4\x16x}b9\xe0uN\x93|\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9a=\xa6P#\xa10 \xd2!E\xcf\xc1\x8b\xab\x10\xbd\x19\xceN\x89\x18\xbfn\xa3FJ:\x00\x00\xe0\x94\x9a>+\x1b\xf3F\xdd\a\v\x02sW\xfe\xacD\xa4\xb2\xc9}\xb8\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9aL\xa8\xb8!\x17\x89NC\xdbr\xb9\xfax\xf0\xb9\xb9:\xce\t\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\x9aR.R\xc1\x95\xbf\xb7\xcf_\xfa\xae\u06d1\xa3\xbath\x16\x1d\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9aZ\xf3\x1c~\x063\x9a\u0234b\x8d|M\xb0\xce\x0fE\u0224\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u0794\x9ac?\xcd\x11,\xce\xebv_\xe0A\x81ps*\x97\x05\u7708\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x9ac\u0445\xa7\x91)\xfd\xab\x19\xb5\x8b\xb61\xea6\xa4 TN\x89\x02F\xdd\xf9yvh\x00\x00\u07d4\x9ag\b\u0778\x90<(\x9f\x83\xfe\x88\x9c\x1e\xdc\xd6\x1f\x85D#\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9ao\xf5\xf6\xa7\xaf{z\xe0\xed\x9c \xec\xecP#\u0481\xb7\x86\x89\x8a\x12\xb9\xbdjg\xec\x00\x00\xe0\x94\x9a\x82\x82m<)H\x1d\xcc+\u0495\x00G\xe8\xb6\x04\x86\xc38\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x9a\x8e\xcaA\x89\xffJ\xa8\xff~\u0536\xb7\x03\x9f\t\x02!\x9b\x15\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x9a\x95;[\xccp\x93y\xfc\xb5Y\u05f9\x16\xaf\u06a5\f\xad\u0309\x05k\xc7^-c\x10\x00\x00\u07d4\x9a\x99\v\x8a\xebX\x8d~\xe7\xec.\xd8\xc2\xe6Os\x82\xa9\xfe\xe2\x89\x01\xd1'\xdbi\xfd\x8b\x00\x00\u07d4\x9a\x9d\x1d\xc0\xba\xa7}n \xc3\xd8I\u01c8b\xdd\x1c\x05L\x87\x89/\xb4t\t\x8fg\xc0\x00\x00\xe0\x94\x9a\xa4\x8cf\xe4\xfbJ\u0419\x93N2\x02.\x82t'\xf2w\xba\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9a\xa80\x8fB\x91\x0eZ\xde\t\xc1\xa5\xe2\x82\xd6\xd9\x17\x10\xbd\xbf\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9a\xaa\xfa\x00gd~\u0659\x06kzL\xa5\xb4\xb3\xf3\xfe\xaao\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9a\xb9\x88\xb5\x05\xcf\xee\x1d\xbe\x9c\u044e\x9bTs\xb9\xa2\xd4\xf56\x89\x11X\xe4`\x91=\x00\x00\x00\u07d4\x9a\xb9\x8dm\xbb\x1e\xaa\xe1mE\xa0EhT\x1a\xd3\xd8\xfe\x06\u0309\x0e\xc5\x04d\xfe#\xf3\x80\x00\xe0\x94\x9a\xba+^'\xffx\xba\xaa\xb5\xcd\u0248\xb7\xbe\x85\\\xeb\xbd\u038a\x02\x1e\f\x00\x13\a\n\xdc\x00\x00\u07d4\x9a\xc4\xdaQ\xd2x\"\xd1\xe2\b\xc9n\xa6J\x1e[U)\x97#\x89\x05lUy\xf7\"\x14\x00\x00\u0794\x9a\xc8S\x97y*i\u05cf(k\x86C*\a\xae\u03b6\x0ed\x88\xc6s\xce<@\x16\x00\x00\xe0\x94\x9a\xc9\a\xee\x85\xe6\xf3\xe2#E\x99\x92\xe2V\xa4?\xa0\x8f\xa8\xb2\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9a\xd4\u007f\xdc\xf9\u0354-(\xef\xfd[\x84\x11[1\xa6X\xa1>\x89\xb2Y\xec\x00\xd5;(\x00\x00\u07d4\x9a\xdb\u04fc{\n\xfc\x05\xd1\xd2\xed\xa4\x9f\xf8c\x93\x9cH\xdbF\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4\x9a\xdfE\x8b\xff5\x99\xee\xe1\xa2c\x98\x85\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x9a\xf9\xdb\xe4t\"\xd1w\xf9E\xbd\xea\xd7\xe6\xd8)05b0\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x9a\xfaSkLf\xbc8\xd8u\u0133\x00\x99\xd9&\x1f\xdb8\xeb\x89\v*\x8f\x84*w\xbc\x80\x00\u07d4\x9b\x06\xad\x84\x1d\xff\xbeL\xcfF\xf1\x03\x9f\u00c6\xf3\xc3!Dn\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9b\x11h\u078a\xb6KGU/3\x89\x80\n\x9c\xc0\x8bFf\u03c9]\u0212\xaa\x111\xc8\x00\x00\u07d4\x9b\x18\x11\xc3\x05\x1fF\xe6d\xaeK\xc9\xc8$\u0445\x92\xc4WJ\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4\x9b\x18G\x86U\xa4\x85\x1c\xc9\x06\xe6`\xfe\xaca\xf7\xf4\u023f\xfc\x89\xe2G\x8d8\x90}\x84\x00\x00\u07d4\x9b\"\xa8\r\\{3t\xa0[D`\x81\xf9}\n4\a\x9e\u007f\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x9b+\xe7\xf5gT\xf5\x05\xe3D\x1a\x10\xf7\xf0\xe2\x0f\xd3\xdd\xf8I\x89\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\x9b2\xcfOQ\x15\xf4\xb3J\x00\xa6La}\xe0c\x875C#\x89\x05\xb8\x1e\u0608 |\x80\x00\u07d4\x9bC\u0739_\xde1\x80u\xa5g\xf1\xe6\xb5v\x17\x05^\xf9\xe8\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x9bDO\xd37\xe5\xd7R\x93\xad\xcf\xffp\xe1\xea\x01\xdb\x022\"\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x9bH$\xff\x9f\xb2\xab\xdaUM\xeeO\xb8\xcfT\x91eW\x061\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x9bL'\x15x\f\xa4\xe9\x9e`\xeb\xf2\x19\xf1Y\f\x8c\xadP\n\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\x9bY\xeb!;\x1eue\xe4PG\xe0N\xa07O\x10v-\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9b\\9\xf7\xe0\xac\x16\x8c\x8e\xd0\xed4\x04w\x11}\x1bh.\xe9\x89\x05P\x05\xf0\xc6\x14H\x00\x00\u07d4\x9b^\xc1\x8e\x83\x13\x88}\xf4a\u0490.\x81\xe6z\x8f\x11;\xb1\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x9bd\xd3\u034d+s\xf6hA\xb5\xc4k\xb6\x95\xb8\x8a\x9a\xb7]\x89\x01 :Ov\f\x16\x80\x00\u07d4\x9be\x8f\xb3a\xe0F\xd4\xfc\xaa\x8a\xefm\x02\xa9\x91\x11\"6%\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9bfA\xb1>\x17/\xc0r\xcaK\x83'\xa3\xbc(\xa1[f\xa9\x89\x06\x81U\xa46v\xe0\x00\x00\xe0\x94\x9bh\xf6t\x16\xa6;\xf4E\x1a1\x16L\x92\xf6r\xa6\x87Y\xe9\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4\x9bw6i\xe8}v\x01\x8c\t\x0f\x82U\xe5D\t\xb9\u0728\xb2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x9bw\xeb\xce\xd7\xe2\x15\xf0\x92\x0e\x8c+\x87\x00$\xf6\xec\xb2\xff1\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9b|\x88\x10\xcc|\u021e\x80Nm>8\x12\x18PG(w\xfe\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9b\xa5=\xc8\xc9^\x9aG/\xeb\xa2\xc4\xe3,\x1d\xc4\xdd{\xabF\x89Hz\x9a0E9D\x00\x00\xe0\x94\x9b\xac\xd3\xd4\x0f;\x82\xac\x91\xa2d\xd9\u060d\x90\x8e\xac\x86d\xb9\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x9b\xb7`\xd5\u0089\xa3\xe1\xdb\x18\xdb\tSE\xcaA;\x9aC\u0089\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\x9b\xb7b\x04\x18j\xf2\xf6;\xe7\x91h`\x16\x87\xfc\x9b\xadf\x1f\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x9b\xb9\xb0*&\xbf\xe1\xcc\xc3\xf0\xc6!\x9e&\x1c9\u007f\xc5\xcax\x89Hz\x9a0E9D\x00\x00\u07d4\x9b\xc5s\xbc\xda#\xb8\xb2o\x90s\xd9\f#\x0e\x8eq\xe0'\v\x896/u\xa40]\f\x00\x00\u07d4\x9b\xd7\u00caB\x100JMe>\xde\xff\x1b<\xe4_\xcexC\x89\x0fI\x89A\xe6d(\x00\x00\xe0\x94\x9b\u0600h\xe10u\xf3\xa8\xca\xc4d\xa5\xf9I\xd6\xd8\x18\xc0\xf6\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x9b\xd9\x05\xf1q\x9f\u01ec\xd0\x15\x9dM\xc1\xf8\xdb/!G#8\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9b\xdb\u071b\x9741\xd1<\x89\xa3\xf9u~\x9b;bu\xbf\u01c9\x1b\x1a}\u03caD\u04c0\x00\u07d4\x9b\xe3\xc3)\xb6*(\xb8\xb0\x88l\xbd\x8b\x99\xf8\xbc\x93\f\xe3\xe6\x89\x04\t\xe5+H6\x9a\x00\x00\xe0\x94\x9b\xf5\x8e\xfb\xea\a\x84\xeb\x06\x8a\xde\u03e0\xbb!P\x84\xc7:5\x8a\x01:k+VHq\xa0\x00\x00\u07d4\x9b\xf6r\xd9y\xb3fR\xfcR\x82Tzjk\xc2\x12\xaeCh\x89#\x8f\xd4,\\\xf0@\x00\x00\xe0\x94\x9b\xf7\x03\xb4\x1c6$\xe1_@T\x96#\x90\xbc\xba0R\xf0\xfd\x8a\x01H>\x01S<.<\x00\x00\u07d4\x9b\xf7\x1f\u007f\xb57\xacT\xf4\xe5\x14\x94\u007f\xa7\xffg(\xf1m/\x89\x01\u03c4\xa3\n\n\f\x00\x00\u07d4\x9b\xf9\xb3\xb2\xf2<\xf4a\xebY\x1f(4\v\xc7\x19\x93\x1c\x83d\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x9b\xfce\x9c\x9c`\x1e\xa4*k!\xb8\xf1p\x84\xec\x87\xd7\x02\x12\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9b\xff\xf5\r\xb3jxUU\xf0vR\xa1S\xb0\xc4+\x1b\x8bv\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9c\x05\xe9\xd0\xf0u\x8eyS\x03q~1\xda!<\xa1W\u618965\u026d\xc5\u07a0\x00\x00\u07d4\x9c\x1bw\x1f\t\xaf\x88*\xf0d0\x83\xde*\xa7\x9d\xc0\x97\xc4\x0e\x89\x86p\xe9\xece\x98\xc0\x00\x00\u07d4\x9c(\xa2\xc4\b`\x91\xcb]\xa2&\xa6W\xce2H\xe8\xea{o\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\u07d4\x9c/\xd5@\x89\xaff]\xf5\x97\x1ds\xb8\x04a`9dsu\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9c4@\x98\xbaaZ9\x8f\x11\xd0\t\x90[\x17|D\xa7\xb6\x02\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9c=\x06\x92\xce\xee\xf8\n\xa4\x96\\\xee\xd2b\xff\xc7\xf0i\xf2\u0709\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9c@\\\xf6\x97\x95a8\x06^\x11\xc5\xf7U\x9eg$[\u0465\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x9cE *%\xf6\xad\x00\x11\xf1\x15\xa5\xa7\"\x04\xf2\xf2\x19\x88f\x8a\x01\x0f\xcf:b\xb0\x80\x98\x00\x00\xe0\x94\x9cI\xde\xffG\b_\xc0\x97\x04\u02a2\u0728\u0087\xa9\xa17\u068a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x9cK\xbc\xd5\xf1dJo\aX$\xdd\xfe\x85\xc5q\u05ab\xf6\x9c\x89a\x94\x04\x9f0\xf7 \x00\x00\u07d4\x9cRj\x14\x06\x83\xed\xf1C\x1c\xfa\xa1(\xa95\xe2\xb6\x14\u060b\x89\x06\x04o7\xe5\x94\\\x00\x00\xe0\x94\x9cT\xe4\xedG\x9a\x85h)\u01bbB\u069f\vi*u\xf7(\x8a\x01\x97\xa8\xf6\xddU\x19\x80\x00\x00\xe0\x94\x9cX\x1a`\xb6\x10(\xd94\x16y)\xb2-p\xb3\x13\xc3O\u040a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\x9c\\\xc1\x11\t,\x12!\x16\xf1\xa8_N\xe3\x14\bt\x1a}/\x89\x1a\xb2\xcf|\x9f\x87\xe2\x00\x00\u07d4\x9ck\u0264k\x03\xaeT\x04\xf0C\xdf\xcf!\x88>A\x10\xcc3\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x9cx\x96?\xbc&<\t\xbdr\xe4\xf8\xde\xf7J\x94u\xf7\x05\\\x8a\x02\ub3b1\xa1r\u0738\x00\x00\u07d4\x9cx\xfb\xb4\xdfv\x9c\xe2\xc1V\x92\f\xfe\xdf\xda\x03:\x0e%J\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x9c{m\xc5\x19\x0f\xe2\x91)c\xfc\xd5yh>\xc79Q\x16\xb0\x89*\x11)\u0413g \x00\x00\u07d4\x9c\x80\xbc\x18\xe9\xf8\u0516\x8b\x18]\xa8\u01df\xa6\xe1\x1f\xfc>#\x89\r\x02\xabHl\xed\xc0\x00\x00\xe0\x94\x9c\x98\xfd\xf1\xfd\u034b\xa8\xf4\u0170L:\xe8X~\xfd\xf0\xf6\xe6\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\x9c\x99\xa1\u0691\u0552\v\xc1N\f\xb9\x14\xfd\xf6+\x94\u02c3X\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x9c\x99\xb6&\x06(\x1b\\\xef\xab\xf3aV\xc8\xfeb\x83\x9e\xf5\xf3\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x9c\x9a\a\xa8\xe5|1r\xa9\x19\xefdx\x94tI\x0f\r\x9fQ\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9c\x9d\xe4G$\xa4\x05M\xa0\xea\xa6\x05\xab\u0300&hw\x8b\xea\x89\n\xd7\xd5\xca?\xa5\xa2\x00\x00\u07d4\x9c\x9f;\x8a\x81\x1b!\xf3\xff?\xe2\x0f\xe9p\x05\x1c\xe6j\x82O\x89>\xc2\u07bc\a\u053e\x00\x00\xe0\x94\x9c\x9f\x89\xa3\x91\x0fj*\xe8\xa9\x10G\xa1z\xb7\x88\xbd\xde\xc1p\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9c\xa0B\x9f\x87O\x8d\xce\xe2\xe9\xc0b\xa9\x02\n\x84*Xz\xb9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9c\xa4.\u7838\x98\xf6\xa5\xcc`\xb5\xa5\u05f1\xbf\xa3\xc321\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9c\xb2\x8a\xc1\xa2\n\x10o\u007f76\x92\xc5\xceLs\xf172\xa1\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9c\xcd\u0732\xcf\u00b2[\br\x9a\n\x98\xd9\xe6\xf0 .\xa2\xc1\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x9c\xe2\u007f$^\x02\xd1\xc3\x12\xc1\xd5\x00x\x8c\x9d\xefv\x90E;\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9c\xe56;\x13\xe8#\x8a\xa4\xdd\x15\xac\u0432\xe8\xaf\xe0\x872G\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x9c\xf2\x92\x8b\xee\xf0\x9a@\xf9\xbf\xc9S\xbe\x06\xa2Q\x11a\x82\xfb\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x9d\x06\x91\x97\xd1\xdeP\x04Z\x18o^\xc7D\xac@\u8bd1\u0189lk\x93[\x8b\xbd@\x00\x00\u07d4\x9d\x0e}\x92\xfb0XS\u05d8&;\xf1^\x97\xc7+\xf9\xd7\xe0\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9d\x0f4~\x82k}\u03aa\xd2y\x06\n5\xc0\x06\x1e\xcf3K\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9d u\x17B,\xc0\xd6\r\xe7\xc27\tzMO\xce \x94\f\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x9d%\n\xe4\xf1\x10\xd7\x1c\xaf\u01f0\xad\xb5.\x8d\x9a\xcbfy\xb8\x8a\x02\x15mn\x99r\x13\xc0\x00\x00\xe0\x94\x9d+\xfc6\x10o\x03\x82P\xc0\x18\x01hW\x85\xb1l\x86\xc6\r\x8aPw\xd7]\xf1\xb6u\x80\x00\x00\xe0\x94\x9d0\xcb#{\xc0\x96\xf1p6\xfc\x80\xdd!\xcah\x99,\xa2\u064a\x06n\xe71\x8f\u070f0\x00\x00\u07d4\x9d2\x96.\xa9\x97\x00\xd92(\xe9\xdb\xda\xd2\xcc7\xbb\x99\xf0~\x89\xb4c+\xed\xd4\xde\xd4\x00\x00\u07d4\x9d4\xda\xc2[\xd1X(\xfa\xef\xaa\xf2\x8fq\aS\xb3\x9e\x89\u0709;\x1cV\xfe\xd0-\xf0\x00\x00\u07d4\x9d6\x91e\xfbp\xb8\x1a:v_\x18\x8f\xd6\f\xbe^{\th\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9d@\xe0\x12\xf6\x04%\xa3@\xd8-\x03\xa1\xc7W\xbf\xab\xc7\x06\xfb\x89\t4o:\xdd\u020d\x80\x00\u07d4\x9dAt\xaaj\xf2\x84v\xe2)\xda\xdbF\x18\b\b\xc6u\x05\xc1\x89B\x1a\xfd\xa4.\u0597\x00\x00\u07d4\x9dB\x133\x9a\x01U\x18avL\x87\xa9<\xe8\xf8_\x87\x95\x9a\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9dF\f\x1b7\x9d\xdb\x19\xa8\xc8[LgG\x05\r\xdf\x17\xa8u\x89\xb5\x0f\u03ef\xeb\xec\xb0\x00\x00\u07d4\x9dG\xba[L\x85\x05\xad\x8d\xa4)4(\va\xa0\xe1\xe8\xb9q\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x9dM2\x11w%n\xbd\x9a\xfb\xda0A5\xd5\x17\xc3\xdcV\x93\x89!d\xb7\xa0J\u0220\x00\x00\u07d4\x9dO\xf9\x89\xb7\xbe\u066b\x10\x9d\x10\xc8\xc7\xe5_\x02\xd7g4\xad\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x9dQ\x15C\xb3\xd9\xdc`\xd4\u007f\t\u051d\x01\xb6\u0118\xd8 x\x8a\x02a\x97\xb9Qo\u00d4\x00\x00\u07d4\x9dn\u03e0:\xf2\xc6\xe1D\xb7\xc4i*\x86\x95\x1e\x90.\x9e\x1f\x89\xa2\xa5\xaa`\xad$?\x00\x00\u07d4\x9dvU\xe9\xf3\xe5\xba]n\x87\xe4\x12\xae\xbe\x9e\xe0\u0512G\ue24e\t1\x1c\x1d\x80\xfa\x00\x00\u07d4\x9dx1\xe84\xc2\v\x1b\xaaiz\xf1\xd8\xe0\xc6!\u016f\xff\x9a\x89\x04\xb0m\xbb\xb4\x0fJ\x00\x00\u07d4\x9dx\xa9u\xb7\xdb^M\x8e(\x84\\\xfb\xe7\xe3\x14\x01\xbe\r\u0649H\xa40k\xa2\u5e5c\x8ahX\u02f5,\f\xf75\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94\x9d\u007f\xdapp\xbf>\xe9\xbb\u0664\x1fU\xca\u0505J\xe6\xc2,\x8a\x02U\u02e3\xc4o\xcf\x12\x00\x00\u07d4\x9d\x81\xae\xa6\x9a\xedj\xd0p\x89\xd6\x14E4\x8c\x17\xf3K\xfc[\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x9d\x91\x1f6\x82\xf3/\xe0y.\x9f\xb6\xff<\xfcG\xf5\x89\xfc\xa5\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9d\x91;]3\x9c\x95\xd8wEV%c\xfe\xa9\x8b#\xc6\f\u0109\tA0,\u007fM#\x00\x00\u07d4\x9d\x93\xfa\xb6\xe2(E\xf8\xf4Z\aIo\x11\xdeqS\r\xeb\u01c9lO\xd1\xee$nx\x00\x00\u07d4\x9d\x99\xb1\x89\xbb\u0664\x8f\xc2\xe1n\x8f\u0363;\xb9\x9a1{\xbb\x89=\x16\xe1\vm\x8b\xb2\x00\x00\u07d4\x9d\x9cN\xfe\x9fC9\x89\xe2;\xe9@I!S)\xfaU\xb4\u02c9\r\u3c89\x03\u01b5\x80\x00\u07d4\x9d\x9eW\xfd\xe3\x0ePh\xc0>I\x84\x8e\xdc\xe3C\xb7\x02\x83X\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\x9d\xa30\"@\xaf\x05\x11\xc6\xfd\x18W\xe6\u07779Ow\xabk\x89\xa8\r$g~\xfe\xf0\x00\x00\u07d4\x9d\xa4\xec@pw\xf4\xb9p{-\x9d.\xde^\xa5(+\xf1\u07c9\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9d\xa6\t\xfa:~l\xf2\xcc\x0ep\u036b\xe7\x8d\xc4\xe3\x82\xe1\x1e\x89A\rXj \xa4\xc0\x00\x00\xe0\x94\x9d\xa6\x1c\xcdb\xbf\x86\x06V\xe02]qW\xe2\xf1`\xd9;\xb5\x8a\x01\x0f\f\xa9V\xf8y\x9e\x00\x00\xe0\x94\x9d\xa6\xe0u\x98\x9ct\x19\tL\xc9\xf6\xd2\u44d3\xbb\x19\x96\x88\x8a\x02Y\xbbq\u056d\xf3\xf0\x00\x00\u07d4\x9d\xa8\xe2,\xa1\x0eg\xfe\xa4NR^GQ\xee\xac6\xa3\x11\x94\x89\x0e\x189\x8ev\x01\x90\x00\x00\u07d4\x9d\xb2\xe1\\\xa6\x81\xf4\xc6`H\xf6\xf9\xb7\x94\x1e\u040b\x1f\xf5\x06\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9d\xc1\x0f\xa3\x8f\x9f\xb0h\x10\xe1\x1f`\x17>\xc3\xd2\xfdju\x1e\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x9d\xd2\x19f$\xa1\xdd\xf1J\x9d7^_\a\x15+\xaf\"\xaf\xa2\x89A\xb0^$c\xa5C\x80\x00\u07d4\x9d\xd4k\x1cm?\x05\u279co\x03~\xed\x9aYZ\xf4\xa9\xaa\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x9d\xdd5^cN\xe9\x92~K\u007fl\x97\xe7\xbf:/\x1ehz\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\x9d\xe2\n\xe7j\xa0\x82c\xb2\x05\xd5\x14$a\x96\x1e$\b\xd2f\x89\r\xa93\xd8\xd8\xc6p\x00\x00\u07d4\x9d\xe2\v\xc3~\u007fH\xa8\x0f\xfdz\xd8O\xfb\xf1\xa1\xab\xe1s\x8c\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9d\xe78m\xde@\x1c\xe4\xc6{q\xb6U?\x8a\xa3N\xa5\xa1}\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\x9d\xeb9\x02z\xf8w\x99+\x89\xf2\xecJ\x1f\x82.\xcd\xf1&\x93\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x9d\xef\xe5j\x0f\xf1\xa1\x94}\xba\t#\xf7\xdd%\x8d\x8f\x12\xfaE\x8a\x05\xb1*\ufbe8\x04\x00\x00\x00\u07d4\x9d\xf0W\xcd\x03\xa4\xe2~\x8e\x03/\x85y\x85\xfd\u007f\x01\xad\xc8\u05c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x9d\xf3*P\x1c\vx\x1c\x02\x81\x02/B\xa1)?\xfd{\x89*\x8a\x01\xe7\xe4\x17\x1b\xf4\u04e0\x00\x00\u07d4\x9e\x01vZ\xff\b\xbc\"\x05P\xac\xa5\xea.\x1c\xe8\u5c19#\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9e \xe5\xfd6\x1e\xab\xcfc\x89\x1f[\x87\xb0\x92h\xb8\xeb7\x93\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x9e#,\b\xc1M\xc1\xa6\xed\v\x8a;(h\x97{\xa5\xc1}\x10\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x9e#\xc5\u4dc2\xb0\n_\xad\U0006eb47\xda\xcf[\x03g\xa1\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x9e59\x90q\xa4\xa1\x01\xe9\x19M\xaa?\t\xf0J\v_\x98p\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9e>\xb5\t'\x8f\xe0\xdc\xd8\xe0\xbb\xe7\x8a\x19N\x06\xb6\x809C\x892\xf5\x1e\u06ea\xa30\x00\x00\u07d4\x9eBrrQk>g\xd4\xfc\xbf\x82\xf5\x93\x90\xd0L\x8e(\xe5\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x9eL\xec5:\xc3\u3043^<\t\x91\xf8\xfa\xa5\xb7\u0428\xe6\x8a\x02\x1e\x18\xb9\xe9\xabE\xe4\x80\x00\u07d4\x9eX\x11\xb4\v\xe1\xe2\xa1\xe1\u048c;\at\xac\xde\n\t`=\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x9eZ1\x1d\x9fi\x89\x8a|j\x9dc`h\x048\xe6z{/\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4\x9e| P\xa2'\xbb\xfd`\x93~&\x8c\xea>h\xfe\xa8\xd1\xfe\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x9e\u007fe\xa9\x0e\x85\b\x86{\xcc\xc9\x14%j\x1e\xa5t\xcf\a\xe3\x89C8t\xf62\xcc`\x00\x00\xe0\x94\x9e\x81D\xe0\x8e\x89dx\x11\xfekr\xd4E\u05a5\xf8\n\xd2D\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9e\x8fd\xdd\xcd\u9e34Q\xba\xfa\xa25\xa9\xbfQ\x1a%\xac\x91\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4\x9e\x95\x1fm\xc5\xe3R\xaf\xb8\xd0B\x99\xd2G\x8aE\x12Y\xbfV\x89\x03\xe7A\x98\x81\xa7:\x00\x00\u07d4\x9e\x96\r\xcd\x03\u057a\x99\xcb\x11]\x17\xffL\t$\x8a\xd4\u043e\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9e\xafj2\x8a@v\x02N\xfakg\xb4\x8b!\xee\xdc\xc0\xf0\xb8\x89\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4\x9e\xb1\xffqy\x8f(\xd6\xe9\x89\xfa\x1e\xa0X\x8e'\xba\x86\xcb}\x89\a\xa1\xfe\x16\x02w\x00\x00\x00\u07d4\x9e\xb2\x81\xc3'\x19\xc4\x0f\xdb>!m\xb0\xf3\u007f\xbcs\xa0&\xb7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x9e\xb3\xa7\xcb^g&Bz:6\x1c\xfa\x8dad\xdb\u043a\x16\x89+\x95\xbd\xcc9\xb6\x10\x00\x00\u07d4\x9e\xb7\x83N\x17\x1dA\xe0i\xa7yG\xfc\xa8v\"\xf0\xbaNH\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\x9e\xc0>\x02\u51f7v\x9d\xefS\x84\x13\xe9\u007f~U\xbeq\u060a\x04+\xf0kx\xed;P\x00\x00\u07d4\x9e\u02eb\xb0\xb2'\x82\xb3uD)\xe1uz\xab\xa0K\x81\x18\x9f\x89,\xa7\xbb\x06\x1f^\x99\x80\x00\u07d4\x9e\xce\x14\x00\x80\t6\xc7\xc6H_\xcd\xd3b`\x17\u041a\xfb\xf6\x89\x10\xce\x1d=\x8c\xb3\x18\x00\x00\u07d4\x9e\xd4\xe6?ReB\xd4O\xdd\xd3MY\xcd%8\x8f\xfdk\u0689\u049b4\xa4cH\x94\x00\x00\u07d4\x9e\xd8\x0e\xda\u007fU\x05M\xb9\xfbR\x82E\x16\x88\xf2k\xb3t\xc1\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x9e\u0710\xf4\xbe!\be!J\xb5\xb3^Z\x8d\xd7t\x15'\x9d\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9e\u07acL\x02k\x93\x05M\u0171\xd6a\fo9`\xf2\xads\x89A\rXj \xa4\xc0\x00\x00\u07d4\x9e\xe9?3\x9eg&\xece\xee\xa4O\x8aK\xfe\x10\xda=2\x82\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x9e\xe9v\f\xc2s\xd4pj\xa0\x83u\xc3\xe4o\xa20\xaf\xf3\u054a\x01\xe5.3l\xde\"\x18\x00\x00\u07d4\x9e\xeb\a\xbd+x\x90\x19^}F\xbd\xf2\a\x1bf\x17QM\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x9e\xefD-)\x1aD}t\xc5\xd2S\u011e\xf3$\xea\xc1\xd8\xf0\x89\xb9f\b\xc8\x10;\xf0\x00\x00\u07d4\x9e\xf1\x89k\x00|2\xa1Q\x14\xfb\x89\xd7=\xbdG\xf9\x12+i\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9f\x01w\x06\xb80\xfb\x9c0\ufc20\x9fPk\x91WEu4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9f\x10\xf2\xa0F;e\xae0\xb0p\xb3\xdf\x18\xcfF\xf5\x1e\x89\xbd\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x9f\x19\xfa\u0223$7\xd8\n\u0183z\v\xb7\x84\x17)\xf4\x97.\x89#=\xf3)\x9far\x00\x00\u07d4\x9f\x1a\xa8\xfc\xfc\x89\xa1\xa52\x8c\xbdcD\xb7\x1f'\x8a,\xa4\xa0\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x9f!0,\xa5\tk\xeat\x02\xb9\x1b\x0f\xd5\x06%O\x99\x9a=\x89C\x97E\x1a\x00=\xd8\x00\x00\u07d4\x9f'\x1d(U\x00\xd78F\xb1\x8fs>%\u074bO]J\x8b\x89'#\xc3F\xae\x18\b\x00\x00\u07d4\x9f4\x97\xf5\xef_\xe60\x95\x83l\x00N\xb9\xce\x02\xe9\x01;K\x89\"V\x86\x1b\xf9\xcf\b\x00\x00\xe0\x94\x9f:t\xfd^~\xdc\xc1\x16)\x93\x17\x13\x81\u02f62\xb7\xcf\xf0\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9fF\xe7\xc1\xe9\a\x8c\xae\x860Z\xc7\x06\v\x01F}f\x85\xee\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\x9fIl\xb2\x06\x95c\x14M\b\x11g{\xa0\xe4q:\nAC\x89<\xd2\xe0\xbfc\xa4H\x00\x00\u07d4\x9fJq\x95\xac|\x15\x1c\xa2X\xca\xfd\xa0\u02b0\x83\xe0I\xc6\x02\x89SS\x8c2\x18\\\xee\x00\x00\u07d4\x9fJ\xc9\xc9\xe7\xe2L\xb2DJ\x04T\xfa[\x9a\xd9\xd9-8S\x89-C\xf3\xeb\xfa\xfb,\x00\x00\u07d4\x9f_D\x02kWjJ\xdbA\xe9YaV\x1dA\x03\x9c\xa3\x91\x89\r\x8drkqw\xa8\x00\x00\u07d4\x9f`{?\x12F\x9fDa!\u03bf4u5kq\xb42\x8c\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9fa\xbe\xb4o^\x85=\n\x85!\xc7Dnh\xe3L}\ts\x89\x1e[\x8f\xa8\xfe*\xc0\x00\x00\u07d4\x9fd\xa8\xe8\xda\xcfJ\xde0\xd1\x0fMY\xb0\xa3\u056b\xfd\xbft\x8966\x9e\xd7t}&\x00\x00\u07d4\x9ff.\x95'A!\xf1wVncm#\x96L\xf1\xfdho\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9fj2*mF\x99\x81Bj\xe8D\x86]~\xe0\xbb\x15\u01f3\x89\x02\xb5\xeeW\x92\x9f\u06c0\x00\u07d4\x9fy\x86\x92J\xeb\x02h|\xd6A\x89\x18\x9f\xb1g\xde\xd2\xdd\\\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\x9fz\x03\x92\xf8Ws.0\x04\xa3u\xe6\xb1\x06\x8dI\xd801\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9f\x82E\u00eb}\x171d\x86\x1c\u04d9\x1b\x94\xf1\xba@\xa9:\x89\x9b\ny\x1f\x12\x110\x00\x00\u07d4\x9f\x83\xa2\x93\xc3$\xd4\x10l\x18\xfa\xa8\x88\x8fd\u0499\x05L\xa0\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9f\x86\xa0f\xed\xb6\x1f\xcbXV\u0793\xb7\\\x8cy\x18d\xb9{\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9f\x98\xeb4\xd4iy\xb0\xa6\u078b\x05\xaaS:\x89\xb8%\xdc\xf1\x89\x04\xb0m\xbb\xb4\x0fJ\x00\x00\xe0\x94\x9f\x9f\xe0\xc9_\x10\xfe\xe8z\xf1\xaf r6\xc8\xf3aN\xf0/\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x9f\xae\xa1\xc5\xe8\x1ez\xcb?\x17\xf1\xc3Q\xee.\u0649\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xa0\b\x01\x98c\xc1\xa7|\x14\x99\xeb9\xbb\u05ff-\u05e3\x1c\xb9\x89\amA\xc6$\x94\x84\x00\x00\u07d4\xa0\t\xbf\ao\x1b\xa3\xfaW\u04a7!r\x18\xbe\xd5VZzz\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xa0\x1e\x94v\u07c4C\x18%\xc86\xe8\x80:\x97\xe2/\xa5\xa0\u034a\x01EB\xba\x12\xa37\xc0\x00\x00\u0794\xa0\x1f\x12\xd7\x0fD\xaa{\x11;(\\\"\xdc\xdbE\x874T\xa7\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xa0\x1f\u0450j\x90\x85\x06\xde\xda\xe1\xe2\b\x12\x88r\xb5n\u7489\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xa0\"\x82@\xf9\x9e\x1d\xe9\xcb2\xd8,\x0f/\xa9\xa3\xd4K\v\xf3\x89V\xbcu\xe2\xd61\x00\x00\x00\xe0\x94\xa0+\xdedahn\x19\xace\f\x97\r\x06r\xe7m\xcbO\u008a\x01\xe0\x92\x96\xc37\x8d\xe4\x00\x00\u07d4\xa0,\x1e4\x06O\x04u\xf7\xfa\x83\x1c\xcb%\x01L:\xa3\x1c\xa2\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\xa0-\u01aa2\x8b\x88\r\u97acTh#\xfc\xcfw@G\xfb\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xa0.?\x8fYY\xa7\xaa\xb7A\x86\x12\x12\x9bp\x1c\xa1\xb8\x00\x10\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa04\u007f\n\x98wc\x90\x16\\\x16m2\x96;\xf7M\xcd\n/\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa05\xa3e$x\xf8-\xbdm\x11_\xaa\x8c\xa9F\xec\x9eh\x1d\x89\x05\xf4\xe4-\u052f\xec\x00\x00\u07d4\xa0:=\xc7\xc53\xd1tB\x95\xbe\x95]a\xaf?R\xb5\x1a\xf5\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xa0E\x9e\xf3i:\xac\xd1d|\xd5\u0612\x989 L\xefS\xbe\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xa0O*\xe0*\xdd\x14\xc1/\xafe\xcb%\x90\"\u0403\n\x8e&\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xa0l\xd1\xf3\x969l\ndFFQ\xd7\xc2\x05\xef\xaf8|\xa3\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xa0ri\x1c\x8d\xd7\xcdB7\xffr\xa7\\\x1a\x95\x06\xd0\xce[\x9e\x89\x14\x0e\xc8\x0f\xa7\xee\x88\x00\x00\u07d4\xa0r\u03beb\xa9\xe9\xf6\x1c\xc3\xfb\xf8\x8a\x9e\xfb\xfe>\x9a\x8dp\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xa0v\x82\x00\v\x1b\xcf0\x02\xf8\\\x80\xc0\xfa)I\xbd\x1e\x82\xfd\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xa0z\xa1mt\xae\u8a63(\x8dR\xdb\x15Q\u0553\x882\x97\x89 \x86\xac5\x10R`\x00\x00\u07d4\xa0\x8d![[j\xacHa\xa2\x81\xac~@\vx\xfe\xf0L\xbf\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa0\x95\x19p\xdf\u0403/\xb8;\xda\x12\xc25E\xe7\x90Aul\x89 \x86\xac5\x10R`\x00\x00\u07d4\xa0\x9fM^\xaae\xa2\xf4\xcbu\nI\x924\x01\xda\u5410\xaf\x89\a\x96\xe3\xea?\x8a\xb0\x00\x00\xe0\x94\xa0\xa0\xe6R\x04T\x1f\u029b/\xb2\x82\u0355\x13\x8f\xae\x16\xf8\t\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa0\xaa_\x02\x01\xf0M;\xbe\xb8\x98\x13/|\x11g\x94f\xd9\x01\x89\x01\xfb\xedR\x15\xbbL\x00\x00\u07d4\xa0\xaa\xdb\xd9P\x97\"p_m#X\xa5\u01df7\x97\x0f\x00\xf6\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa0\xb7q\x95\x1c\xe1\xde\xee6:\xe2\xb7q\xb7>\a\u0135\xe8\x00\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xa0\xde\\`\x1eif5\u0198\xb7\xae\x9c\xa4S\x9f\u01f9A\xec\x89\x12\xc3\xcb\xd7\x04\xc9w\x00\x00\u07d4\xa0\xe8\xbaf\x1bH\x15L\xf8C\xd4\u00a5\xc0\xf7\x92\xd5(\xee)\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xa0\xfc~S\xc5\xeb\xd2z*\xbd\xacE&\x1f\x84\xab;Q\xae\xfb\x89\xa3\x13\xda\xec\x9b\xc0\xd9\x00\x00\xe0\x94\xa0\xff[L\xf0\x16\x02~\x83#I}D(\xd3\xe5\xa8;\x87\x95\x8a\x01e\x98\xd3\xc8>\xc0B\x00\x00\u07d4\xa1\x06F[\xbd\x19\u1dbc\xe5\r\x1b\x11W\xdcY\tZ60\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xa1\x06\xe6\x92>\xddS\u028e\xd6P\x96\x8a\x91\b\xd6\xcc\xfd\x96p\x8a\x02\x02\xfe\x15\x05\xaf\uc240\x00\u07d4\xa1\t\u12f0\xa3\x9c\x9e\xf8/\xa1\x95\x97\xfc^\xd8\xe9\xebmX\x89X\xe7\x92n\xe8X\xa0\x00\x00\u07d4\xa1\x1a\x03\u013b&\xd2\x1e\xffg}]U\\\x80\xb2TS\xeez\x89\x03\xcb'Y\xbcA\x0f\x80\x00\u07d4\xa1\x1e\xff\xabl\xf0\xf5\x97,\xff\xe4\xd5e\x96\xe9\x89h\x14J\x8f\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4\xa1 M\xad_V\a(\xa3\\\r\x8f\u01d4\x81\x05{\xf7s\x86\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xa1&#\xe6)\u07d3\tg\x04\xb1`\x84\xbe,\u061dV-\xa4\x8a\x01\xcc\xc92E\x11\xe4P\x00\x00\xe0\x94\xa1*l-\x98]\xaf\x0eO_ z\xe8Q\xaa\xf7)\xb32\u034a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\xa13m\xfb\x96\xb6\xbc\xbeK>\xdf2\x05\xbeW#\xc9\x0f\xadR\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xa1;\x9d\x82\xa9\x9b<\x9b\xbaZ\xe7.\xf2\x19\x9e\xdc};\xb3l\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\xa1<\xfe\x82mm\x18A\u072eD;\xe8\u00c7Q\x816\xb5\xe8\x8a\x1d\xa5jK\b5\xbf\x80\x00\x00\xe0\x94\xa1C.\xd2\u01b7wz\x88\xe8\xd4m8\x8epG\u007f \x8c\xa5\x8a\x01\xb1\xa7\xe4\x13\xa1\x96\xc5\x00\x00\u07d4\xa1D\xf6\xb6\x0fr\xd6J!\xe30\xda\xdbb\u0619\n\xde+\t\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa1P%\xf5\x95\xac\xdb\xf3\x11\x0fw\u017f$G~eH\xf9\xe8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa1X\x14\x8a.\x0f>\x92\xdc,\xe3\x8f\xeb\xc2\x01\a\xe3%<\x96\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa1a`\x85\x1d+\x9c4\x9b\x92\xe4o\x82\x9a\xbf\xb2\x10\x945\x95\x89a\t=|,m8\x00\x00\u07d4\xa1f\xf9\x11\xc6D\xac2\x13\u049e\x0e\x1a\xe0\x10\xf7\x94\u056d&\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa1m\x9e=c\x98aY\xa8\x00\xb4h7\xf4^\x8b\xb9\x80\xee\v\x89n\x11u\xdaz\xd1 \x00\x00\u07d4\xa1pp\xc2\xe9\u0169@\xa4\xec\x0eIT\xc4\xd7\xd6C\xbe\x8fI\x89lk\x17\x03;6\x1c\x80\x00\u07d4\xa1|\x9eC#\x06\x95\x18\x18\x9dR\a\xa0r\x8d\u02d20j?\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa1\x83`\xe9\x85\xf2\x06.\x8f\x8e\xfe\x02\xad,\xbc\x91\xad\x9aZ\xad\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xa1\x91\x14\x05\xcfn\x99\x9e\xd0\x11\xf0\xdd\xcd*O\xf7\u008f%&\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xa1\x92i\x80\a\xcc\x11\xaa`=\"\x1d_\xee\xa0v\xbc\xf7\xc3\r\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa1\x92\xf0j\xb0R\xd5\xfd\u007f\x94\xee\xa81\x8e\x82x\x15\xfegz\x89\a\x1f\x8a\x93\xd0\x1eT\x00\x00\u07d4\xa1\x99\x81D\x96\x8a\\p\xa6AUT\xce\xfe\u0082F\x90\u0125\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa1\xa1\xf0\xfam \xb5\nyO\x02\xefR\b\\\x9d\x03j\xa6\u028965\u026d\xc5\u07a0\x00\x00\u07d4\xa1\xae\x8dE@\xd4\xdbo\xdd\xe7\x14oA[C\x1e\xb5\\y\x83\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xa1\xb4|M\x0e\xd6\x01\x88B\xe6\xcf\xc8c\n\u00e3\x14.^k\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xa1\xc4\xf4Z\x82\xe1\xc4x\xd8E\b.\xb1\x88u\xc4\xeae9\xab\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\xa1\xdc\xd0\xe5\xb0Z\x97|\x96#\xe5\xae/Y\xb9\xad\xa2\xf3>1\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xa1\xe48\n;\x1ft\x96s\xe2p\"\x99\x93\xeeU\xf3Vc\xb4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa1\xf1\x93\xa0Y/\x1f\xeb\x9f\xdf\xc9\n\xa8\x13xN\xb8\x04q\u0249K\xe4\xe7&{j\xe0\x00\x00\u07d4\xa1\xf2\x85@P\xf8re\x8e\xd8.R\xb0\xad{\xbc\x1c\xb9!\xf6\x89m\x03\x17\xe2\xb3&\xf7\x00\x00\u07d4\xa1\xf5\xb8@\x14\rZ\x9a\xce\xf4\x02\xac<\u00c8jh\xca\xd2H\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa1\xf7e\xc4O\xe4_y\x06w\x94HD\xbeO-B\x16_\xbd\x89\xc7\xe9\xcf\xdev\x8e\xc7\x00\x00\u07d4\xa1\xf7\xdd\xe1\xd78\xd8\xcdg\x9e\xa1\xee\x96[\xee\"K\xe7\xd0M\x89=\x18DP\xe5\xe9<\x00\x00\u07d4\xa1\xf8\u063c\xf9\x0ew\u007f\x19\xb3\xa6Iu\x9a\xd9P'\xab\xdf\u00c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa2\x02TrB\x80onp\xe7@X\xd6\xe5)-\xef\xc8\xc8\u0509l\x87T\xc8\xf3\f\b\x00\x00\u07d4\xa2\r\a\x1b\x1b\x000cI}y\x90\xe1$\x9d\xab\xf3l5\xf7\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa2\r\x8f\xf6\f\xaa\xe3\x1d\x02\xe0\xb6e\xfaC]v\xf7|\x94B\x89\x1a\x8a\x90\x9d\xfc\xef@\x00\x00\u07d4\xa2\x11\xda\x03\xcc\x0e1\xec\xceS\t\x99\x87\x18QU(\xa0\x90\u07c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa2\x14B\xab\x054\n\xdeh\xc9\x15\xf3\xc39\x9b\x99U\xf3\xf7\xeb\x89*\x03I\x19\u07ff\xbc\x00\x00\u07d4\xa2\"\"Y\u075c>=\xed\x12p\x84\xf8\b\xe9*\x18\x870,\x89\b\xc83\x9d\xaf\xedH\x00\x00\u07d4\xa2*\xde\r\xdb\\n\xf8\xd0\u034d\xe9M\x82\xb1\x10\x82\xcb.\x91\x897KW\xf3\xce\xf2p\x00\x00\u07d4\xa2L:\xb6!\x81\xe9\xa1[x\xc4b\x1eL|X\x81'\xbe&\x89\b\xcd\xe4:\x83\xd31\x00\x00\u07d4\xa2W\xadYK\u0603(\xa7\xd9\x0f\xc0\xa9\a\u07d5\xee\xca\xe3\x16\x89\x1c7\x86\xff8F\x93\x00\x00\u07d4\xa2[\bd7\xfd!\x92\u0420\xf6On\xd0D\xf3\x8e\xf3\xda2\x89\x12)\x0f\x15\x18\v\xdc\x00\x00\u07d4\xa2v\xb0X\u02d8\u060b\xee\xdbg\xe5CPl\x9a\r\x94p\u0609\x90\xaa\xfcv\xe0/\xbe\x00\x00\u07d4\xa2\x82\xe9i\xca\xc9\xf7\xa0\xe1\xc0\u0350\xf5\xd0\xc48\xacW\r\xa3\x89\"\a\xeb\x89\xfc'8\x00\x00\xe0\x94\xa2\x91\xe9\u01d9\rU-\u046e\x16\u03bc?\xca4,\xba\xf1\u044a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xa2\x93\x19\xe8\x10i\xe5\xd6\r\xf0\x0f=\xe5\xad\xee5\x05\xec\xd5\xfb\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xa2\x96\x8f\xc1\xc6K\xac\vz\xe0\u058b\xa9I\x87Mm\xb2S\xf4\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xa2\x9d[\xdat\xe0\x03GHr\xbdX\x94\xb8\x853\xffd\u00b5\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa2\x9df\x1acv\xf6m\vt\xe2\xfe\x9d\x8f&\xc0$~\xc8L\x89\xdf3\x04\a\x9c\x13\xd2\x00\x00\u07d4\xa2\xa45\xdeD\xa0\x1b\xd0\ucc9eD\xe4vD\xe4j\f\xdf\xfb\x89\x1b\x1dDZz\xff\xe7\x80\x00\u07d4\xa2\xac\xe4\u0253\xbb\x1eS\x83\xf8\xact\xe1y\x06n\x81O\x05\x91\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xa2\xb7\x01\xf9\xf5\xcd\u041eK\xa6+\xae\xba\u3a02W\x10X\x85\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa2\u0145O\xf1Y\x9f\x98\x89,W%\xd2b\xbe\x1d\xa9\x8a\xad\xac\x89\x11\t\xff30\x10\xe7\x80\x00\u07d4\xa2\xc7\xea\xff\xdc,\x9d\x93sE l\x90\x9aR\u07f1LG\x8f\x89\a\xc0\x86\x0eZ\x80\xdc\x00\x00\u07d4\xa2\u04aabk\t\xd6\xd4\xe4\xb1?\u007f\xfcZ\x88\xbdz\xd3gB\x89\xfb\x80xPuS\x83\x00\x00\u07d4\xa2\u04cd\xe1\xc79\x06\xf6\xa7\xcan\xfe\xb9|\xf6\xf6\x9c\xc4!\xbe\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xa2\xdce\xee%kY\xa5\xbdy)wO\x90K5\x8d\U000ed84a\x04\x83\xbc\xe2\x8b\xeb\t\xf8\x00\x00\u07d4\xa2\xe0h:\x80]\xe6\xa0^\xdb/\xfb\xb5\xe9o\x05p\xb67\u00c9\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa2\u1e2a\x90\x0e\x9c\x13\x9b?\xa1\"5OaV\xd9*\x18\xb1\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xa2\u2d54\x1e\f\x01\x94K\xfe\x1d_\xb4\xe8\xa3K\x92,\u03f1\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa2\xe4`\xa9\x89\xcb\x15V_\x9e\u0327\xd1!\xa1\x8eN\xb4\x05\xb6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa2\xec\xce,I\xf7*\t\x95\xa0\xbd\xa5z\xac\xf1\xe9\xf0\x01\xe2*\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xa2\xf4r\xfeO\"\xb7}\xb4\x89!\x9e\xa4\x02=\x11X*\x93)\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xa2\xf7\x98\xe0w\xb0}\x86\x12N\x14\a\xdf2\x89\r\xbbKcy\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa2\xf8k\xc0a\x88N\x9e\xef\x05d\x0e\xddQ\xa2\xf7\xc0Yli\x89llD\xfeG\xec\x05\x00\x00\u07d4\xa2\xfa\x17\xc0\xfbPl\xe4\x94\x00\x8b\x95W\x84\x1c?d\x1b\x8c\xae\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa3\x04X\x8f\r\x85\f\xd8\u04cfv\xe9\xe8<\x1b\xf6>3>\u0789\x02(V\x01!l\x8c\x00\x00\u07d4\xa3\x05\x8cQszN\x96\xc5_.\xf6\xbd{\xb3X\x16~\u00a7\x89 \xdb:\xe4H\x1a\u0500\x00\u07d4\xa3\t\xdfT\u02bc\xe7\f\x95\xec03\x14\x9c\xd6g\x8ao\xd4\u03c9\f\x1f\x12\xc7Q\x01X\x00\x00\u07d4\xa3\nER\x0eR\x06\xd9\x00@p\xe6\xaf>{\xb2\xe8\xddS\x13\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xa3\x0e\n\xcbSL\x9b0\x84\xe8P\x1d\xa0\x90\xb4\xeb\x16\xa2\xc0\u0349lk\x93[\x8b\xbd@\x00\x00\u07d4\xa3 0\x95\xed\xb7\x02\x8ehq\xce\n\x84\xf5HE\x9f\x830\n\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xa3!\t\x1d0\x18\x06By\xdb9\x9d+*\x88\xa6\xf4@\xae$\x89\xadx\xeb\u016cb\x00\x00\x00\u07d4\xa3#-\x06\x8dP\x06I\x03\xc9\xeb\xc5c\xb5\x15\xac\u0237\xb0\x97\x89l\x87T\xc8\xf3\f\b\x00\x00\xe0\x94\xa3$\x1d\x89\n\x92\xba\xf5)\b\xdcJ\xa0Irk\xe4&\xeb\u04ca\x04<-\xa6a\xca/T\x00\x00\u07d4\xa3)F&\xec)\x84\xc4;C\xdaM]\x8eFi\xb1\x1dKY\x896\xa4\xcfcc\x19\xc0\x00\x00\u07d4\xa3,\xf7\xdd\xe2\f=\xd5g\x9f\xf5\xe3%\x84\\p\u0156&b\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa39\xa3\xd8\xca(\x0e'\xd2A[&\xd1\xfcy2(\xb6`C\x896\U00086577\x8f\xf0\x00\x00\u07d4\xa3<\xb4P\xf9[\xb4n%\xaf\xb5\x0f\xe0_\xee\xe6\xfb\x8c\xc8\xea\x89*\x11)\u0413g \x00\x00\u07d4\xa3?p\xdaru\xef\x05q\x04\u07e7\xdbd\xf4r\xe9\xf5\xd5S\x89\x04YF\xb0\xf9\xe9\xd6\x00\x00\u07d4\xa3@v\xf8K\xd9\x17\xf2\x0f\x83B\u024b\xa7\x9eo\xb0\x8e\xcd1\x89\u3bb5sr@\xa0\x00\x00\u07d4\xa3C\x0e\x1fd\u007f2\x1e\xd3G9V##\xc7\xd6#A\vV\x8964\xfb\x9f\x14\x89\xa7\x00\x00\u07d4\xa3O\x9dV\x8b\xf7\xaf\xd9L*[\x8a_\xf5\\f\xc4\by\x99\x89\x84}P;\"\x0e\xb0\x00\x00\u07d4\xa3V\x06\xd5\x12 \xee\u007f!F\xd4\x11X.\xe4\xeeJEYn\x89\u062a\xbe\b\v\xc9@\x00\x00\u07d4\xa3VU\x1b\xb7}OE\xa6\xd7\xe0\x9f\n\b\x9ey\u0322I\u02c9\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\xa3\\\x19\x13,\xac\x195Wj\xbf\xedl\x04\x95\xfb\a\x88\x1b\xa0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa3e\x91\x8b\xfe?&'\xb9\xf3\xa8gu\xd8un\x0f\u0629K\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xa3n\r\x94\xb9Sd\xa8&q\xb6\b\xcb-72Ea)\t\x89\b!\xd2!\xb5)\x1f\x80\x00\u07d4\xa3u\xb4\xbc$\xa2N\x1fyu\x93\xcc0+/3\x10c\xfa\\\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa3v\"\xac\x9b\xbd\xc4\xd8+u\x01]t[\x9f\x8d\xe6Z(\uc25d\xc0\\\xce(\u00b8\x00\x00\xe0\x94\xa3y\xa5\a\fP=/\xac\x89\xb8\xb3\xaf\xa0\x80\xfdE\xedK\xec\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4\xa3\x80-\x8ae\x9e\x89\xa2\xc4~\x90T0\xb2\xa8'\x97\x89P\xa7\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa3\x83\x06\xcbp\xba\xa8\u4446\xbdh\xaap\xa8=$/)\a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa3\x84vi\x1d4\x94.\xeak/v\x88\x92#\x04}\xb4az\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa3\x87\xceN\x96\x1axG\xf5`\a\\d\xe1YkVA\xd2\x1c\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\xa3\x87\xec\xde\x0e\xe4\xc8\a\x94\x99\xfd\x8e\x03G;\u060a\xd7R*\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xa3\x88:$\xf7\xf1f _\x1aj\x99I\al&\xa7nqx\x89b\xa9\x92\xe5:\n\xf0\x00\x00\xe0\x94\xa3\x8b[\xd8\x1a\x9d\xb9\u04b2\x1d^\xc7\xc6\x05R\xcd\x02\xedV\x1b\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xa3\x90\xca\x12+\x85\x01\xee>^\a\xa8\xcaKA\x9f~M\xae\x15\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xa3\x93*1\xd6\xffu\xfb;\x12q\xac\xe7\u02a7\xd5\xe1\xff\x10Q\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xa3\x94\xadO\xd9\xe6S\x0eo\\S\xfa\xec\xbe\u0781\xcb\x17-\xa1\x8a\x01/\x93\x9c\x99\xed\xab\x80\x00\x00\u07d4\xa3\x97\x9a\x92v\n\x13Z\xdfi\xd7/u\xe1gu_\x1c\xb8\u00c9\x05k\xc7^-c\x10\x00\x00\xe0\x94\xa3\x9b\xfe\xe4\xae\u027du\xbd\"\u01b6r\x89\x8c\xa9\xa1\xe9]2\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa3\xa2b\xaf\u0493h\x19#\b\x92\xfd\xe8O-ZYJ\xb2\x83\x89e\xea=\xb7UF`\x00\x00\u07d4\xa3\xa2\xe3\x19\xe7\u04e1D\x8bZ\xa2F\x89S\x16\f-\xbc\xbaq\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa3\xa5{\a\x16\x13(\x04\xd6\n\xac(\x11\x97\xff+=#{\x01\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xa3\xa9>\xf9\xdb\xea&6&=\x06\xd8I/jA\u0790|\"\x89\x03@\xaa\xd2\x1b;p\x00\x00\xe0\x94\xa3\xae\x18y\x00}\x80\x1c\xb5\xf3RqjM\u063a'!\xde=\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\xa3\xba\r:6\x17\xb1\xe3\x1bNB,\xe2i\xe8s\x82\x8d]i\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\u07d4\xa3\xbc\x97\x9bp\x80\t/\xa1\xf9/n\x0f\xb3G\u2359PE\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\xa3\xbf\xf1\u07e9\x97\x16h6\f\r\x82\x82\x842\xe2{\xf5Ng\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xa3\xc1J\xce(\xb1\x92\u02f0b\x14_\u02fdXi\xc6rq\xf6\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xa3\xc3:\xfc\x8c\xb4pN#\x15=\xe2\x04\x9d5\xaeq3$r\x89+X\xad\u06c9\xa2X\x00\x00\u07d4\xa3\u0430<\xff\xbb&\x9fyj\u009d\x80\xbf\xb0}\xc7\u01ad\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa3\u0543\xa7\xb6[#\xf6\vy\x05\xf3\xe4\xaab\xaa\xc8\u007fB'\x898\xbe\xfa\x12mZ\x9f\x80\x00\u07d4\xa3\xdb6J3-\x88K\xa9;&\x17\xaeM\x85\xa1H\x9b\xeaG\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xa3\xe0Q\xfbtJ\xa3A\f;\x88\xf8\x99\xf5\xd5\u007f\x16\x8d\xf1-\x89\xa00\xdc\xeb\xbd/L\x00\x00\u07d4\xa3\xe3\xa6\xeaP\x95s\xe2\x1b\xd0#\x9e\xce\x05#\xa7\xb7\u061b/\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xa3\xf4\xad\x14\xe0\xbbD\xe2\xce,\x145\x9cu\xb8\xe72\xd3pT\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa3\xfa\xccP\x19\\\vI3\xc8X\x97\xfe\xcc[\xbd\x99\\4\xb8\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa4\x03Z\xb1\xe5\x18\b!\xf0\xf3\x80\xf1\x13\x1bs\x87\xc8\u0641\u0349\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa4\n\xa2\xbb\xce\fr\xb4\xd0\xdf\xff\xccBq[+T\xb0\x1b\xfa\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa4\x19\xa9\x84\x14#c&uuV`\x894\x0e\xea\x0e\xa2\b\x19\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\xa4!\u06f8\x9b:\aA\x90\x84\xad\x10\xc3\xc1]\xfe\x9b2\xd0\u008a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xa4\"\xe4\xbf\v\xf7AG\u0309[\xed\x8f\x16\xd3\xce\xf3BaT\x89\x12\xef?b\xee\x116\x80\x00\u07d4\xa4%\x9f\x83E\xf7\u3a37+\x0f\xec,\xf7^2\x1f\xdaM\u0089g\x8a\x93 b\xe4\x18\x00\x00\u07d4\xa4)\b\xe7\xfeS\x98\n\x9a\xbf@D\xe9W\xa5Kp\u973e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa4)\xfa\x88s\x1f\xdd5\x0e\x8e\xcdn\xa5B\x96\xb6HO\u6549j\xc5\xc6-\x94\x86\a\x00\x00\xe0\x94\xa40\x99]\xdb\x18[\x98e\xdb\xe6%9\xad\x90\xd2.Ks\u008a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa46\xc7TS\xcc\xcaJ\x1f\x1bb\xe5\u0123\r\x86\xdd\xe4\xbeh\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xa47\xfen\xc1\x03\u028d\x15\x8fc\xb34\"N\u032c[>\xa3\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xa4;m\xa6\xcbz\xacW\x1d\xff'\xf0\x9d9\xf8F\xf57i\xb1\x89\x14\x99\x8f2\xacxp\x00\x00\u07d4\xa4;\x81\xf9\x93V\xc0\xaf\x14\x1a\x03\x01\rw\xbd\x04,q\xc1\xee\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa4>\x19G\xa9$+5Ua\xc3\n\x82\x9d\xfe\uc881Z\xf8\x89\xd2=\x99\x96\x9f\u0591\x80\x00\u07d4\xa4H\x9aP\xea\xd5\xd5DZ{\xeeM-U6\u00a7lA\xf8\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa4O\xe8\x00\xd9o\xca\xd7;qp\xd0\xf6\x10\u02cc\x06\x82\xd6\u0389\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xa4T2\xa6\xf2\xac\x9dVW{\x93\x8a7\xfa\xba\xc8\xcc|F\x1c\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa4f\xd7p\u0618\xd8\xc9\xd4\x05\xe4\xa0\xe5Q\xef\xaf\xcd\xe5<\xf9\x89\x1a\xb2\xcf|\x9f\x87\xe2\x00\x00\xe0\x94\xa4g\a1\x17X\x93\xbb\xcf\xf4\xfa\x85\u0397\xd9O\xc5\x1cK\xa8\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xa4kC\x87\xfbM\xcc\xe0\x11\xe7nMsT}D\x81\xe0\x9b\xe5\x89Hz\x9a0E9D\x00\x00\u07d4\xa4l\xd27\xb6>\xeaC\x8c\x8e;e\x85\xf6y\xe4\x86\b2\xac\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa4wy\u063c\x1c{\xce\x0f\x01\x1c\xcb9\xefh\xb8T\xf8\u078f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa4\x82kl8\x82\xfa\xd0\xed\\\x8f\xbb%\xcc@\xccO3u\x9f\x89p\x1bC\xe3D3\xd0\x00\x00\u07d4\xa4\x87Y(E\x8e\xc2\x00]\xbbW\x8c\\\xd35\x80\xf0\xcf\x14R\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa4\x9fR:\xa5\x13d\xcb\xc7\u0655\x16=4\xebY\r\xed/\b\x89\x90'B\x1b*\x9f\xbc\x00\x00\u07d4\xa4\xa4\x9f\v\xc8h\x8c\xc9\xe6\xdc\x04\xe1\xe0\x8dR\x10&\xe6Ut\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa4\xa7\xd3\x06\xf5\x10\xcdX5\x94(\xc0\xd2\xf7\xc3`\x9dVt\u05c9\xb5\x8c\xb6\x1c<\xcf4\x00\x00\u07d4\xa4\xa8:\a8y\x9b\x97\x1b\xf2\xdep\x8c.\xbf\x91\x1c\xa7\x9e\xb2\x89 \x86\xac5\x10R`\x00\x00\u07d4\xa4\xb0\x9d\xe6\xe7\x13\xdciTnv\xef\n\xcf@\xb9O\x02A\xe6\x89\x11}\xc0b~\xc8p\x00\x00\xe0\x94\xa4\u04b4)\xf1\xadSI\xe3\x17\x04\x96\x9e\xdc_%\xee\x8a\xca\x10\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\xa4\xd6\xc8.\u076eYG\xfb\xe9\xcd\xfb\xd5H\xae3\xd9\x1aq\x91\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xa4\xda4E\r\"\xec\x0f\xfc\xed\xe0\x00K\x02\xf7\x87.\xe0\xb7:\x89\x05\x0fafs\xf0\x83\x00\x00\xe0\x94\xa4\xddY\xab^Q}9\x8eI\xfaS\u007f\x89\x9f\xedL\x15\xe9]\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xa4\xe6#E\x1e~\x94\xe7\u86e5\xed\x95\u0228:b\xff\xc4\xea\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa4\xed\x11\xb0r\u061f\xb16u\x9f\u019bB\x8cH\xaa]L\xed\x89\x0e?\x15'\xa0<\xa8\x00\x00\u07d4\xa4\xfb\x14@\x9ag\xb4V\x88\xa8Y>\\\xc2\xcfYl\xedo\x11\x89a\t=|,m8\x00\x00\xe0\x94\xa5\x14\xd0\x0e\xddq\b\xa6\xbe\x83\x9ac\x8d\xb2AT\x18\x17A\x96\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\xe0\x94\xa5\"\xde~\xb6\xae\x12PR*Q13\xa9;\xd4(IG\\\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xa5$\xa8\xcc\xccIQ\x8d\x17\n2\x82p\xa2\xf8\x813\xfb\xaf]\x89\x0f\xf7\x02-\xac\x10\x8a\x00\x00\u07d4\xa59\xb4\xa4\x01\xb5\x84\xdf\xe0\xf3D\xb1\xb4\"\xc6UC\x16~.\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xa5>\xadT\xf7\x85\n\xf2\x148\xcb\xe0z\xf6\x86'\x9a1[\x86\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa5C\xa0f\xfb2\xa8f\x8a\xa0sj\f\x9c\xd4\rx\t\x87'\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa5gw\vj\xe3 \xbd\xdeP\xf9\x04\xd6c\xe7F\xa6\x1d\xac\xe6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa5h\xdbMW\xe4\xd6tb\xd73\u019a\x9e\x0f\xe2n!\x83'\x89;k\xff\x92f\xc0\xae\x00\x00\u07d4\xa5i\x8059\x1eg\xa4\x90\x13\xc0\x00 yY1\x14\xfe\xb3S\x89\r\x02\xabHl\xed\xc0\x00\x00\u07d4\xa5p\":\xe3\u02a8QA\x8a\x98C\xa1\xacU\xdbH$\xf4\xfd\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa5s`\xf0\x02\xe0\xd6M-tE}\x8c\xa4\x85~\xe0\v\xcd\u07c9\x123\xe22\xf6\x18\xaa\x00\x00\u07d4\xa5u\xf2\x89\x1d\xcf\u0368<\\\xf0\x14t\xaf\x11\xee\x01\xb7-\u0089\x05l\xd5_\xc6M\xfe\x00\x00\u07d4\xa5x;\xf342\xff\x82\xacI\x89\x85\xd7\xd4`\xaeg\xec6s\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xa5\x87MuF5\xa7b\xb3\x81\xa5\xc4\u01d2H:\xf8\xf2=\x1d\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94\xa5\xa4\"\u007fl\xf9\x88%\xc0\u057a\xffS\x15u,\xcc\x1a\x13\x91\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa5\xabK\xd3X\x8fF\xcb'.V\xe9=\xee\u04c6\xba\x8bu=\x89HB\xf0A\x05\x87,\x80\x00\xe0\x94\xa5\xba\xd8e\t\xfb\xe0\xe0\xe3\xc0\xe9?m8\x1f\x1a\xf6\xe9\u0501\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xa5\xc36\b;\x04\xf9G\x1b\x8cn\xd76y\xb7Mf\xc3c\uc263e\nL\x9d \xe2\x00\x00\u07d4\xa5\xcd\x129\x92\x19K4\xc4x\x13\x140;\x03\xc5IH\xf4\xb9\x89l\xfc\xc3\xd9\x1d\xa5c\x00\x00\u07d4\xa5\u0578\xb6-\x00-\xef\x92A7\x10\xd1;o\xf8\xd4\xfc}\u04c9\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\xa5\xd9ni}F5\x8d\x11\x9a\xf7\x81\x9d\xc7\b\u007fj\xe4\u007f\xef\x8a\x03\x17\xbe\xe8\xaf3\x15\xa7\x80\x00\u07d4\xa5\xde^CO\xdc\xddh\x8f\x1c1\xb6\xfbQ,\xb1\x96rG\x01\x89+^:\xf1k\x18\x80\x00\x00\u07d4\xa5\xe0\xfc<:\xff\xed=\xb6q\tG\xd1\xd6\xfb\x01\u007f>'m\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa5\xe9;I\xea|P\x9d\xe7\xc4Ml\xfe\xdd\xefY\x10\u07aa\xf2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa5\xe9\xcdKt%]\"\xb7\u0672z\xe8\xddC\xedn\xd0%+\x89)\x8d\xb2\xf5D\x11\u0640\x00\xe0\x94\xa5\xf0\a{5\x1flP\\\xd5\x15\u07e6\xd2\xfa\u007f\\L\u0487\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xa5\xf0u\xfd@\x135W{f\x83\u0081\xe6\xd1\x01C-\xc6\xe0\x89\x91Hx\xa8\xc0^\xe0\x00\x00\u07d4\xa5\xfe,\xe9\u007f\x0e\x8c8V\xbe\r\xe5\xf4\u0732\xce]8\x9a\x16\x89\x01=\xb0\xb8\xb6\x86>\x00\x00\u07d4\xa5\xffb\"-\x80\xc0\x13\xce\xc1\xa0\xe8\x85\x0e\xd4\xd3T\xda\xc1m\x89\vA\a\\\x16\x8b\x18\x00\x00\u07d4\xa6\t\xc2m\xd3P\xc25\xe4K+\x9c\x1d\xdd\xcc\u0429\xd9\xf87\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa6\f\x12\tuO]\x87\xb1\x81\xdaO\b\x17\xa8\x18Y\xef\x9f\u0609\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94\xa6\x10\x1c\x96\x1e\x8e\x1c\x15y\x8f\xfc\xd0\xe3 \x1dw\x86\xec7:\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xa6\x13Ei\x96@\x8a\xf1\xc2\xe9>\x17w\x88\xabU\x89^+2\x8a\x01Y\x19\xffG|\x88\xb8\x00\x00\u07d4\xa6\x18\x87\x81\x8f\x91J \xe3\x10w)\v\x83qZk-n\xf9\x89e\xea=\xb7UF`\x00\x00\u07d4\xa6\x1aT\xdfxJD\xd7\x1bw\x1b\x871u\t!\x13\x81\xf2\x00\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa6\x1c\u06ed\xf0K\x1eT\u0203\xde`\x05\xfc\xdf\x16\xbe\xb8\xeb/\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xa69\xac\xd9k1\xbaS\xb0\u0407c\"\x9e\x1f\x06\xfd\x10^\x9d\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xa6BP\x10\x04\xc9\x0e\xa9\xc9\xed\x19\x98\xba\x14\nL\xd6,o_\x89\r\x94\xfb\x8b\x10\xf8\xb1\x80\x00\u07d4\xa6D\xed\x92,\xc27\xa3\xe5\u0117\x9a\x99Tw\xf3nP\xbcb\x89\x1f\xa7=\x84]~\x96\x00\x00\u07d4\xa6F\xa9\\moY\xf1\x04\xc6T\x1dw`uz\xb3\x92\xb0\x8c\x89\u3bb5sr@\xa0\x00\x00\xe0\x94\xa6HL\u0184\xc4\xc9\x1d\xb5>\xb6\x8aM\xa4Zjk\xda0g\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xa6N_\xfbpL,\x919\xd7~\xf6\x1d\x8c\u07e3\x1dz\x88\xe9\x89\a\xc0\x86\x0eZ\x80\xdc\x00\x00\xe0\x94\xa6T&\xcf\xf3x\xed#%5\x13\xb1\x9fIm\xe4_\xa7\u13ca\x01\x86P\x12|\xc3\u0700\x00\x00\u07d4\xa6jIc\xb2\u007f\x1e\xe1\x93+\x17+\xe5\x96N\r:\xe5KQ\x89\t`\xdbwh\x1e\x94\x00\x00\u07d4\xa6\u007f8\x81\x95eB:\xa8_>:\xb6\x1b\xc7c\u02eb\x89\u0749sw\xb0\"\u01be\b\x00\x00\u07d4\xa6\x8c14E\xc2-\x91\x9e\xe4l\xc2\xd0\xcd\xff\x04:uX%\x89\x04\x13t\xfd!\xb0\u0600\x00\u07d4\xa6\x8e\f0\u02e3\xbcZ\x88>T\x03 \xf9\x99\xc7\xcdU\x8e\\\x89a\x9237b\xa5\x8c\x80\x00\u07d4\xa6\x90\xf1\xa4\xb2\n\xb7\xba4b\x86 \u079c\xa0@\xc4<\x19c\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xa6\x9d|\xd1}HB\xfe\x03\xf6*\x90\xb2\xfb\xf8\xf6\xaf{\xb3\x80\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xa6\xa0\x82R\xc8YQw\xcc.`\xfc'Y>#y\xc8\x1f\xb1\x89\x01\x16Q\xac>zu\x80\x00\u07d4\xa6\xa0\xdeB\x1a\xe5Om\x17(\x13\b\xf5dm/9\xf7w]\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa6\xb2\xd5s)s`\x10,\a\xa1\x8f\xc2\x1d\xf2\xe7I\x9f\xf4\xeb\x89\xd9o\u0390\u03eb\xcc\x00\x00\xe0\x94\xa6\xc9\x10\xceMIJ\x91\x9c\u036a\xa1\xfc;\x82\xaat\xba\x06\u03ca\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xa6\u3ea3\x8e\x10J\x1e'\xa4\xd8(i\xaf\xb1\xc0\xaen\xff\x8d\x89\x01\x11@\ueb4bq\x00\x00\u07d4\xa6\xee\xbb\xe4d\u04d1\x87\xbf\x80\u029c\x13\xd7 '\xec[\xa8\xbe\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xa6\xf6+\x8a=\u007f\x11\"\a\x01\xab\x9f\xff\xfc\xb3'\x95\x9a'\x85\x89\x1bn)\x1f\x18\u06e8\x00\x00\u07d4\xa6\xf93\a\xf8\xbc\xe01\x95\xfe\u0387 C\xe8\xa0?{\xd1\x1a\x89\x9csK\xadQ\x11X\x00\x00\u07d4\xa7\x01\xdfy\xf5\x94\x90\x1a\xfe\x14DH^k \u00fd\xa2\xb9\xb3\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xa7\x02L\xfdt,\x1e\xc1<\x01\xfe\xa1\x8d0B\xe6_\x1d]\xee\x8a\x02c\x11\x9a(\xab\u0430\x80\x00\u07d4\xa7\x18\xaa\xadY\xbf9\\\xba+#\xe0\x9b\x02\xfe\f\x89\x81bG\x8960<\x97\xe4hx\x00\x00\u07d4\xa7$|S\xd0Y\xeb|\x93\x10\xf6(\xd7\xfclj\nw?\b\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\xa7%7c\xcfJu\u07d2\xca\x1evm\xc4\xee\x8a'E\x14{\x8a\x02F7p\xe9\n\x8fP\x00\x00\u07d4\xa7.\xe6f\u0133^\x82\xa5\x06\x80\x8bD<\xeb\xd5\xc62\xc7\u0749+^:\xf1k\x18\x80\x00\x00\u07d4\xa7DD\xf9\x0f\xbbT\xe5o:\u0276\xcf\u032aH\x19\xe4aJ\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa7GC\x9a\xd0\u04d3\xb5\xa08a\xd7r\x962m\u8edd\xb9\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa7`{BW;\xb6\xf6\xb4\xd4\xf2<~*&\xb3\xa0\xf6\xb6\xf0\x89WG=\x05\u06ba\xe8\x00\x00\xe0\x94\xa7i)\x89\n{G\xfb\x85\x91\x96\x01lo\u0742\x89\u03b7U\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u0794\xa7kt?\x98\x1bi0r\xa11\xb2+\xa5\x10\x96\\/\xef\u05c8\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xa7m?\x15bQ\xb7,\f\xcfKG\xa39<\xbdoI\xa9\u0149Hz\x9a0E9D\x00\x00\u07d4\xa7t(\xbc\xb2\xa0\xdbv\xfc\x8e\xf1\xe2\x0eF\x1a\n2\u016c\x15\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\xa7u\x8c\xec\xb6\x0e\x8faL\u0396\x13~\xf7+O\xbd\awJ\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xa7w^J\xf6\xa2:\xfa \x1f\xb7\x8b\x91^Q\xa5\x15\xb7\xa7(\x89\x06\x81U\xa46v\xe0\x00\x00\u07d4\xa7\u007f>\u1793\x88\xbb\xbb\"\x15\xc6#\x97\xb9e`\x13#`\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xa7\x85\x9f\xc0\u007fun\xa7\xdc\xeb\xbc\xcdB\xf0X\x17X-\x97?\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa7\x96lH\x9fLt\x8az\u902a'\xa5t%\x17g\xca\xf9\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xa7\xa3\xbba9\xb0\xad\xa0\f\x1f\u007f\x1f\x9fV\u0654\xbaM\x1f\xa8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa7\xa3\xf1S\xcd\u00c8!\xc2\f]\x8c\x82A\xb2\x94\xa3\xf8+$\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xa7\xa5\x17\u05ed5\x82\v\t\u0517\xfa~U@\xcd\xe9IXS\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xa7\xc9\u04c8\xeb\xd8s\xe6k\x17\x13D\x83\x97\xd0\xf3\u007f\x8b\u04e8\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xa7\u073b\xa9\xb9\xbfgb\xc1EAlPjq\u3d17 \x9c\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xa7\xe7O\v\xdb'\x8f\xf0\xa8\x05\xa6Ha\x8e\xc5+\x16o\xf1\xbe\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xa7\xe87r\xbc \x0f\x90\x06\xaa*&\r\xba\xa8H=\xc5+0\x89\vB\xd56f7\xe5\x00\x00\xe0\x94\xa7\xef5\u0387\xed\xa6\u008d\xf2HxX\x15\x05>\xc9zPE\x8a\x01\x0f\f\xe9I\xe0\x0f\x93\x00\x00\u07d4\xa7\xf9\"\f\x80G\x82k\xd5\xd5\x18?Ngjmw\xbf\xed6\x89\bPh\x97k\xe8\x1c\x00\x00\u07d4\xa8\a\x10O'\x03\xd6y\xf8\u07af\xc4B\xbe\xfe\x84\x9eB\x95\v\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa8\f\xb1s\x8b\xac\b\xd4\xf9\xc0\x8bM\xef\xf5\x15T_\xa8XO\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xa8\x19\xd2\xec\xe1\"\xe0(\xc8\xe8\xa0J\x06M\x02\xb9\x02\x9b\b\xb9\x8965\u026d\xc5\u07a0\x00\x00\u0794\xa8%\xfdZ\xbby&\xa6|\xf3k\xa2F\xa2K\xd2{\xe6\xf6\xed\x88\xf4?\xc2\xc0N\xe0\x00\x00\u07d4\xa8(U9\x86\x9d\x88\xf8\xa9aS7Uq}~\xb6Uv\xae\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa83\x82\xb6\xe1Rg\x97J\x85P\xb9\x8fqv\xc1\xa3S\xf9\xbe\x89\xbf\xfd\xaf/\xc1\xb1\xa4\x00\x00\xe0\x94\xa8DlG\x81\xa77\xacC(\xb1\xe1[\x8a\v?\xbb\x0f\xd6h\x8a\x04\x87\x94\xd1\xf2F\x19*\x00\x00\u07d4\xa8E[A\x17e\u0590\x1e1\x1erd\x03\t\x1eB\xc5f\x83\x89\xb7:\xec;\xfe\x14P\x00\x00\xe0\x94\xa8f\x13\xe6\u0124\xc9\xc5_\\\x10\xbc\xda2\x17]\u02f4\xaf`\x8a\x02C\xd6\xc2\xe3k\xe6\xae\x00\x00\u07d4\xa8m\xb0}\x9f\x81/G\x96b-@\xe0=\x13Xt\xa8\x8at\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa8\u007fz\xbdo\xa3\x11\x94(\x96x\xef\xb6<\xf5\x84\xee^*a\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xa8\x80\u2a3f\x88\xa1\xa8&H\xb4\x01W\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa8\x9d\xf3HY\xed\xd7\xc8 \u06c8w@\xd8\xff\x9e\x15\x15|{\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa8\xa4<\x00\x91\x00al\xb4\xaeN\x03?\x1f\xc5\xd7\xe0\xb6\xf1R\x89\u0548\xd0x\xb4?M\x80\x00\u07d4\xa8\xa7\b\xe8O\x82\u06c6\xa3U\x02\x19;Ln\xe9\xa7n\xbe\x8f\x897\b\xba\xed=h\x90\x00\x00\xe0\x94\xa8\xa7\xb6\x8a\u06b4\xe3\xea\xdf\xf1\x9f\xfaX\xe3J?\xce\xc0\xd9j\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xa8\xa8\xdb\xdd\x1a\x85\u047e\xee%i\xe9\x1c\xccM\t\xae\u007fn\xa1\x8a\x01:k+VHq\xa0\x00\x00\u07d4\xa8\xac\xa7H\xf9\xd3\x12\xect\u007f\x8bex\x14&\x94\xc7\xe9\xf3\x99\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa8\xb6[\xa3\x17\x1a?w\xa65\v\x9d\xaf\x1f\x8dU\xb4\xd2\x01\xeb\x89(b\xf3\xb0\xd2\"\x04\x00\x00\u07d4\xa8\xbe\xb9\x1c+\x99\u0216J\xa9[kJ\x18K\x12i\xfc4\x83\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u0794\xa8\xc0\xb0/\xaf\x02\xcbU\x19\u0768\x84\xde{\xbc\x8c\x88\xa2\u0681\x88\xe7\xc2Q\x85\x05\x06\x00\x00\u07d4\xa8\xc1\u05aaA\xfe=e\xf6{\xd0\x1d\xe2\xa8f\xed\x1e\u066eR\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4\xa8\xca\xfa\xc3\"\x80\xd0!\x02\v\xf6\xf2\xa9x(\x83\u05ea\xbe\x12\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xa8\xdb\v\x9b \x14S3A<;\fb\xf5\xf5.\u0544\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa8\xf3\u007f\n\xb3\xa1\xd4H\xa9\xe3\xce@\x96_\x97\xa6F\b:4\x89\x11\xe0\xe4\xf8\xa5\v\xd4\x00\x00\u07d4\xa8\xf8\x9d\xd5\xccnd\u05f1\xee\xac\xe0\a\x02\x02,\xd7\xd2\xf0=\x89%\xf2s\x93=\xb5p\x00\x00\xe0\x94\xa9\x04v\xe2\xef\xdf\xeeO8{\x0f2\xa5\x06x\xb0\xef\xb5s\xb5\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa9\x14PF\xfa6(\xcf_\xd4\xc6\x13\x92{\xe51\xe6\xdb\x1f\u0749\x06\x12O\xee\x99;\xc0\x00\x00\u07d4\xa9\x14\u0375q\xbf\xd9=d\xdaf\xa4\xe1\b\xea\x13NP\xd0\x00\x89M\x878\x99G\x13y\x80\x00\xe0\x94\xa9\x1aZ{4\x1f\x99\xc55\x14N \xbe\x9ck;\xb4\u008eM\x8a\x01&u:\xa2$\xa7\v\x00\x00\u07d4\xa9%%Q\xa6$\xaeQ7\x19\u06beR\a\xfb\xef\xb2\xfdwI\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xa9'\u050b\xb6\u02c1K\xc6\t\xcb\u02a9\x15\x1f]E\x9a'\xe1\x89\x0e\xb95\t\x00d\x18\x00\x00\u07d4\xa9)\u023dq\xdb\f0\x8d\xac\x06\b\n\x17G\xf2\x1b\x14e\xaa\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xa9K\xbb\x82\x14\u03cd\xa0\xc2\xf6h\xa2\xacs\xe8bHR\x8dK\x894\n\xad!\xb3\xb7\x00\x00\x00\u07d4\xa9Q\xb2D\xffP\u03eeY\x1d^\x1a\x14\x8d\xf6\xa98\xef*\x1a\x89^\x00\x15\x84\xdf\xcfX\x00\x00\u07d4\xa9`\xb1\xca\xdd;\\\x1a\x8el\xb3\xab\xca\xf5.\xe7\xc3\xd9\xfa\x88\x89R\x8b\xc3T^Rh\x00\x00\u07d4\xa9a\x17\x1fSB\xb1s\xddp\xe7\xbf\xe5\xb5\xca#\x8b\x13\xbc\u0749\xb8'\x94\xa9$O\f\x80\x00\u07d4\xa9u\xb0w\xfc\xb4\u030e\xfc\xbf\x83\x84Y\xb6\xfa$:AY\u0589\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94\xa9{\xeb:H\xc4_\x15((L\xb6\xa9_}\xe4S5\x8e\u018a\x06\x90\x83l\n\xf5\xf5`\x00\x00\u07d4\xa9~\a!DI\x9f\xe5\xeb\xbd5J\xcc~~\xfbX\x98]\b\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4\xa9\x86v/zO)O.\v\x172y\xad,\x81\xa2\"4X\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa9\x8f\x10\x985\xf5\xea\xcd\x05Cd|4\xa6\xb2i\xe3\x80/\xac\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xa9\x97\xdf\u01d8j'\x05\bH\xfa\x1cd\u05e7\xd6\xe0z\u0322\x89\a\xc0\x86\x0eZ\x80\xdc\x00\x00\u07d4\xa9\x99\x91\u03bd\x98\xd9\xc88\xc2_zt\x16\xd9\xe2D\xca%\r\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xa9\xa1\xcd\xc3;\xfd7o\x1c\rv\xfbl\x84\xb6\xb4\xac'Mh\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xa9\xa8\xec\xa1\x1a#\xd6F\x89\xa2\xaa>A}\xbb=3k\xb5\x9a\x89\x0e4S\xcd;g\xba\x80\x00\u07d4\xa9\xac\xf6\x00\b\x1b\xb5[\xb6\xbf\xba\xb1\x81_\xfcN\x17\xe8Z\x95\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xa9\xad\x19&\xbcf\xbd\xb31X\x8e\xa8\x197\x88SM\x98,\x98\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4\xa9\xaf!\xac\xbeH/\x811\x89j\"\x806\xbaQ\xb1\x94S\u00c9\x02\xb5\xe0!\x98\f\xc1\x80\x00\xe0\x94\xa9\xb2\xd2\xe0IN\xab\x18\xe0}7\xbb\xb8V\xd8\x0e\x80\xf8L\u04ca\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa9\xbaoA;\x82\xfc\xdd\xf3\xaf\xfb\xbd\u0412\x87\xdc\xf5\x04\x15\u0289\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xa9\xbe\x88\xad\x1eQ\x8b\v\xbb\x02J\xb1\xd8\xf0\xe7?y\x0e\fv\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\xa9\xbf\xc4\x10\xdd\xdb q\x1eE\xc0s\x87\xea\xb3\n\x05N\x19\xac\x89>\x99`\x1e\xdfNS\x00\x00\u07d4\xa9\u0522\xbc\xbe[\x9e\bi\xd7\x0f\x0f\xe2\xe1\u05aa\xcdE\xed\u0149\n\xc6\xe7z\xb6c\xa8\x00\x00\xe0\x94\xa9\xd6KO;\xb7\x85\a\"\xb5\x8bG\x8b\xa6\x917^\"NB\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xa9\xd6\xf8q\xcax\x1au\x9a \xac:\u06d7,\xf1()\xa2\b\x892$\xf4'#\xd4T\x00\x00\xe0\x94\xa9\xdc\x04$\u0196\x9dy\x83X\xb3\x93\xb1\x93:\x1fQ\xbe\xe0\n\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xa9\xe1\x94f\x1a\xacpN\xe9\u07a0C\x97N\x96\x92\xde\xd8J]\x89\x1a&\xa5\x14\"\xa0p\x00\x00\u07d4\xa9\xe2\x837\xe65q\x93\xd9\xe2\xcb#k\x01\xbeD\xb8\x14'\u07c9wC\"\x17\xe6\x83`\x00\x00\u07d4\xa9\xe6\xe2^ekv%Xa\x9f\x14z!\x98[\x88t\xed\xfe\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa9\xe9\xdb\xcez,\xb06\x94y\x98\x97\xbe\xd7\xc5M\x15_\u06a8\x89\n\xb5\xae\x8f\u025de\x80\x00\u07d4\xa9\xed7{}n\xc2Yq\xc1\xa5\x97\xa3\xb0\xf3\xbe\xadW\u024f\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xaa\x02\x00\xf1\xd1~\x9cT\xda\x06G\xbb\x969]W\xa7\x858\u06099>\xf1\xa5\x12|\x80\x00\x00\u07d4\xaa\f\xa3ss7\x17\x8a\f\xaa\xc3\t\x9cXK\x05lV0\x1c\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4\xaa\x13kG\x96+\xb8\xb4\xfbT\r\xb4\xcc\xf5\xfd\xd0B\xff\xb8\u03c9\x1b\x1bk\u05efd\xc7\x00\x00\xe0\x94\xaa\x14B-o\n\xe5\xa7X\x19N\xd1W\x80\xc88\xd6\u007f\x1e\xe1\x8a\x06\t2\x05lD\x9d\xe8\x00\x00\u07d4\xaa\x16&\x9a\xac\x9c\r\x800h\xd8/\u01d1Q\xda\xdd3Kf\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xaa\x16p&\u04da\xb7\xa8V5\x94N\xd9\xed\xb2\xbf\xeb\xa1\x18P\x8a\x01\xc1\xd5\xe2\x1bO\xcfh\x00\x00\u07d4\xaa\x1b7h\xc1m\x82\x1fX\x0ev\xc8\xe4\xc8\xe8m}\u01c8S\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xaa\x1d\xf9.Q\xdf\xf7\v\x19s\xe0\xe9$\xc6b\x87\xb4\x94\xa1x\x89\x1c\xf8J0\xa0\xa0\xc0\x00\x00\u07d4\xaa,g\x00\x96\xd3\xf990S%B~\xb9U\xa8\xa6\r\xb3\u0149l\x95Y\x06\x99#-\x00\x00\u07d4\xaa15\xcbT\xf1\x02\xcb\xef\xe0\x9e\x96\x10:\x1ayg\x18\xffT\x89\x03\"\"\xd9\xc31\x94\x00\x00\u07d4\xaa2\x1f\xdb\xd4I\x18\r\xb8\xdd\xd3O\x0f\xe9\x06\xec\x18\xee\t\x14\x89%\"H\u07b6\xe6\x94\x00\x00\xe0\x94\xaa9%\xdc\"\v\xb4\xae!w\xb2\x880x\xb6\xdc4l\xa1\xb2\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xaa?)`\x1a\x131t^\x05\xc4(0\xa1^q\x93\x8ab7\x89\\(=A\x03\x94\x10\x00\x00\xe0\x94\xaaG\xa4\xff\xc9y622\u025b\x99\xfa\xda\x0f'4\xb0\xae\xee\x8a\x01\xb8H\x9d\xf4\xdb\xff\x94\x00\x00\u07d4\xaaI=?O\xb8fI\x1c\xf8\xf8\x00\xef\xb7\xe22N\xd7\xcf\xe5\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xaaV\xa6]\u012b\xb7/\x11\xba\xe3+o\xbb\aDG\x91\xd5\u0249(\x94\xe9u\xbfIl\x00\x00\xe0\x94\xaaZ\xfc\xfd\x83\t\xc2\u07dd\x15\xbe^jPN}pf$\u014a\x01<\xf4\"\xe3\x05\xa17\x80\x00\u07d4\xaa\x8e\xb0\x82;\a\xb0\xe6\xd2\n\xad\xda\x0e\x95\xcf85\xbe\x19.\x89\x01\xbc\x16\xd6t\xec\x80\x00\x00\u07d4\xaa\x91#~t\r%\xa9/\u007f\xa1F\xfa\xa1\x8c\xe5m\xc6\xe1\xf3\x892$\xf4'#\xd4T\x00\x00\u07d4\xaa\x96\x0e\x10\xc5#\x91\xc5N\x158|\xc6z\xf8'\xb51m\u0309lk\x93[\x8b\xbd@\x00\x00\u07d4\xaa\x9b\xd4X\x955\xdb'\xfa+\xc9\x03\xca\x17\xd6y\xddeH\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xaa\xa8\xde\xfe\x11\xe3a?\x11\x06\u007f\xb9\x83bZ\b\x99Z\x8d\xfc\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xaa\xaa\xe6\x8b2\x14\x02\xc8\xeb\xc14h\xf3A\xc6<\f\xf0?\u0389Rf<\u02b1\xe1\xc0\x00\x00\u07d4\xaa\xad\x1b\xaa\xdeZ\xf0N+\x17C\x9e\x93Y\x87\xbf\x8c+\xb4\xb9\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xaa\xb0\n\xbfX(\xd7\xeb\xf2kG\u03ac\u0378\xba\x032Qf\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xaa\xbd\xb3\\\x15\x14\x98J\x03\x92\x13y?3E\xa1h\xe8\x1f\xf1\x89\x10\xca\u0216\xd29\x00\x00\x00\u07d4\xaa\xca`\xd9\xd7\x00\u7156\xbb\xbb\xb1\xf1\xe2\xf7\x0fF'\xf9\u060965\xbbw\xcbK\x86\x00\x00\u07d4\xaa\xce\u0629V;\x1b\xc3\x11\xdb\xdf\xfc\x1a\xe7\xf5u\x19\xc4D\f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xaa\u04b7\xf8\x10f\x95\a\x8el\x13\x8e\xc8\x1at\x86\xaa\xca\x1e\xb2\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xaa\xe6\x1eC\xcb\r\f\x96\xb3\x06\x99\xf7~\x00\xd7\x11\u0423\x97\x9b\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xaa\xe72\xed\xa6Y\x88\u00e0\f\u007fG/5\x1cF;\x1c\x96\x8e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xaa\xf0#\xfe\U0009091b\xb7\x8b\xb7\xab\xc9]f\x9cP\xd5(\xb0\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xaa\xf5\xb2\a\xb8\x8b\r\xe4\xac@\xd7G\xce\xe0n\x17-\xf6\xe7E\x8a\x06\xa7\xb7\x1d\u007fQ\u0410\x00\x00\u07d4\xaa\xf9\xeeK\x88lm\x1e\x95Io\xd2t#[\xf4\xec\xfc\xb0}\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xaa\xfb{\x01:\xa1\xf8T\x1c~2{\xf6P\xad\xbd\x19L \x8f\x89I\x9e\t-\x01\xf4x\x00\x00\xe0\x94\xab\t\x863\xee\xee\f\xce\xfd\xf62\xf9WTV\xf6\u0740\xfc\x86\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\xab\f\xedv.\x16a\xfa\xe1\xa9*\xfb\x14\b\x88\x94\x13yH%\x89g\x8a\x93 b\xe4\x18\x00\x00\xe0\x94\xab\x14\xd2!\xe3=TF)\x19\x8c\u0416\xedc\u07e2\x8d\x9fG\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xab \x9f\u0729y\u0426G\x01\n\xf9\xa8\xb5/\xc7\xd2\r\x8c\u044a\x01\xee\xe2S,|-\x04\x00\x00\u07d4\xab'\xbax\xc8\xe5\xe3\xda\xef1\xad\x05\xae\xf0\xff\x03%r\x1e\b\x89\x19^\xce\x00n\x02\xd0\x00\x00\u07d4\xab(q\xe5\a\u01fe9eI\x8e\x8f\xb4b\x02Z\x1a\x1cBd\x89*\x03I\x19\u07ff\xbc\x00\x00\u07d4\xab8a\"o\xfe\xc1(\x91\x87\xfb\x84\xa0\x8e\xc3\xed\x042d\xe8\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xab=\x86\xbc\x82\x92~\f\xd4!\xd1F\xe0\u007f\x91\x93'\xcd\xf6\xf9\x89g\x8a\x93 b\xe4\x18\x00\x00\xe0\x94\xab>b\xe7z\x8b\"^A\x15\x92\xb1\xaf0\aR\xfeA$c\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\xab>x)K\xa8\x86\xa0\xcf\xd5\xd3H\u007f\xb3\xa3\a\x8d3\x8dn\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xab@\x04\xc0@?~\xab\xb0\xeaXo!!V\xc4 =g\xf1\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xabAo\xe3\rX\xaf\xe5\xd9EL\u007f\xce\u007f\x83\v\xccu\x03V\x89\x0657\x01\xc6\x05\u06c0\x00\u07d4\xabEr\xfb\xb1\xd7+W]i\xecj\xd1s3\x87>\x85R\xfc\x89lj\xc5L\xdahG\x00\x00\u07d4\xabZy\x01av2\ts\xe8\xcd8\xf67U0\x02%1\xc0\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xab]\xfc\x1e\xa2\x1a\xdcB\u03cc?n6\x1e$?\xd0\xdaa\xe5\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xabke\xea\xb8\xdf\xc9\x17\xec\x02Q\xb9\xdb\x0e\u03e0\xfa\x03(I\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xabp\x91\x93.K\u00dd\xbbU#\x80\u0293O\xd7\x16m\x1en\x89\xb5\x0f\u03ef\xeb\xec\xb0\x00\x00\u07d4\xabt\x16\xff2%IQ\u02fcbN\xc7\xfbE\xfc~\u02a8r\x89\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\xab|B\xc5\xe5-d\x1a\a\xadu\t\x9cb\x92\x8b\u007f\x86b/\x89\x126\x1a\xa2\x1d\x14\xba\x00\x00\u07d4\xab}T\xc7\xc6W\x0e\xfc\xa5\xb4\xb8\xcep\xf5*Ws\xe5\xd5;\x89\x0f(:\xbe\x9d\x9f8\x00\x00\u07d4\xab~\v\x83\xed\x9aBLm\x1ejo\x87\xa4\xdb\xf0d\t\xc7\u0589\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4\xab\x84\xa0\xf1G\xad&T\x00\x00+\x85\x02\x9aA\xfc\x9c\xe5\u007f\x85\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xab\x93\xb2n\xce\n\n\xa2\x13e\xaf\xed\x1f\xa9\xae\xa3\x1c\xd5Dh\x89W+{\x98sl \x00\x00\u07d4\xab\x94\x8aJ\xe3y\\\xbc\xa11&\xe1\x92S\xbd\xc2\x1d:\x85\x14\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xab\x9a\xd3n\\t\xce.\x969\x9fW\x83\x941\xd0\u77d6\xab\x89\b\xe3\xf5\v\x17<\x10\x00\x00\u07d4\xab\xb2\xe6\xa7*@\xban\xd9\b\u037c\xec\x91\xac\xfdwx0\xd1dcG\x8a\xe0\xfcw \x89\a?u\u0460\x85\xba\x00\x00\xe0\x94\xab\u071f\x1b\xcfM\x19\xee\x96Y\x100\xe7r\xc340/}\x83\x8a\b~^\x11\xa8\x1c\xb5\xf8\x00\x00\u07d4\xab\xde\x14{*\xf7\x89\ua946T~f\xc4\xfa&d\xd3(\xa4\x89\rk`\x81\xf3L\x12\x80\x00\xe0\x94\xab\xe0|\xedj\xc5\xdd\xf9\x91\xef\xf6\xc3\xda\"jt\x1b\xd2C\xfe\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xab\xf1/\xa1\x9e\x82\xf7lq\x8f\x01\xbd\xca\x00\x03gE#\xef0\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xab\xf7(\u03d3\x12\xf2!(\x02NpF\xc2Q\xf5\xdcY\x01\xed\x8a\x06A\xe8\xa15c\xd8\xf8\x00\x00\u07d4\xab\xf8\xff\xe0p\x8a\x99\xb5(\xcc\x1e\xd4\xe9\xceK\r\x060\xbe\x8c\x89z\xb5\u00ae\xee\xe68\x00\x00\u07d4\xab\xfc\xf5\xf2P\x91\xceW\x87_\xc6t\xdc\xf1\x04\xe2\xa7=\xd2\xf2\x89\x01\x11du\x9f\xfb2\x00\x00\u07d4\xab\xfe\x93d%\xdc\u01f7K\x95P\x82\xbb\xaa\xf2\xa1\x1dx\xbc\x05\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xac\x02OYO\x95X\xf0ICa\x8e\xb0\xe6\xb2\xeeP\x1d\xc2r\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xac\x12*\x03\xcd\x05\x8c\x12._\xe1{\x87/Hw\xf9\u07d5r\x89j\xc5\xc6-\x94\x86\a\x00\x00\u07d4\xac\x14.\xda\x11W\xb9\xa9\xa6C\x90\xdf~j\xe6\x94\xfa\u0249\x05\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xac\x1d\xfc\x98Kq\xa1\x99)\xa8\x1d\x81\xf0J|\xbb\x14\a7\x03\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\xac!\xc1\xe5\xa3\xd7\xe0\xb5\x06\x81g\x9d\xd6\u01d2\xdb\u0287\xde\u02ca\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\xac(\x89\xb5\x96o\f\u007f\x9e\xdbB\x89\\\xb6\x9d\x1c\x04\xf9#\xa2\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xac(\xb5\xed\xea\x05\xb7o\x8c_\x97\bEA'|\x96ijL\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xac,\x8e\t\xd0d\x93\xa68XC{\xd2\v\xe0\x19bE\x03e\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\xac.vm\xac?d\x8fcz\xc6q?\u0770h\xe4\xa4\xf0M\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94\xac9\x00)\x8d\xd1M|\xc9mJ\xbbB\x8d\xa1\xba\xe2\x13\xff\xed\x8a\x05<\xa1)t\x85\x1c\x01\x00\x00\u07d4\xac=\xa5&\xcf\u0388)s\x02\xf3LI\xcaR\r\xc2q\xf9\xb2\x89+^:\xf1k\x18\x80\x00\x00\u07d4\xacD`\xa7nm\xb2\xb9\xfc\xd1R\xd9\xc7q\x8d\x9a\xc6\xed\x8co\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xacJ\xcf\xc3n\xd6\tJ'\xe1\x18\xec\xc9\x11\xcdG>\x8f\xb9\x1f\x89a\x91>\x14@<\f\x00\x00\u07d4\xacL\xc2V\xaet\xd6$\xac\xe8\r\xb0x\xb2 \u007fW\x19\x8fk\x89lyt\x12?d\xa4\x00\x00\u07d4\xacN\xe9\xd5\x02\xe7\xd2\xd2\xe9\x9eY\xd8\xca}_\x00\xc9KM\u058965\u026d\xc5\u07a0\x00\x00\u07d4\xacR\xb7~\x15fH\x14\xf3\x9eO'\x1b\xe6A0\x8d\x91\xd6\u0309\v\xed\x1d\x02c\xd9\xf0\x00\x00\u07d4\xacY\x99\xa8\x9d-\u0486\u0568\fm\xee~\x86\xaa\xd4\x0f\x9e\x12\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4\xac_br1H\r\r\x950.m\x89\xfc2\xcb\x1dO\xe7\xe3\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xac`\x8e+\xac\x9d\xd2\a(\u0494~\xff\xbb\xbf\x90\n\x9c\xe9K\x8a\x01EK\r\xb3uh\xfc\x00\x00\u07d4\xacm\x02\xe9\xa4k7\x9f\xacJ\u0271\u05f5\xd4{\xc8P\xce\x16\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xacoh\xe87\xcf\x19a\xcb\x14\xabGDm\xa1h\xa1m\u0789\x89Hz\x9a0E9D\x00\x00\u07d4\xacw\xbd\xf0\x0f\u0558[]\xb1+\xbe\xf8\x008\n\xbc*\x06w\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xac~\x03p'#\xcb\x16\xee'\xe2-\u0438\x15\xdc-\\\xae\x9f\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\xac\x8bP\x9a\xef\xea\x1d\xbf\xaf+\xb35\x00\xd6W\vo\xd9mQ\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xac\x8e\x87\xdd\xda^x\xfc\xbc\xb9\xfa\u007f\xc3\xce\x03\x8f\x9f}.4\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xac\x9f\xffh\xc6\x1b\x01\x1e\xfb\xec\xf08\xedr\u06d7\xbb\x9er\x81\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\xac\xa1\xe6\xbcd\xcc1\x80\xf6 \xe9M\u0171\xbc\xfd\x81X\xe4]\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xac\xa2\xa883\v\x170-\xa71\xd3\r\xb4\x8a\x04\xf0\xf2\a\xc1\x89Hz\x9a0E9D\x00\x00\u07d4\xac\xaa\xdd\xcb\xf2\x86\xcb\x0e!]\xdaUY\x8f\u007f\xf0\xf4\xad\xa5\u018965\u026d\xc5\u07a0\x00\x00\u07d4\xac\xb9C8UK\u0108\u0308\xae-\x9d\x94\b\rk\u07c4\x10\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xac\xbc-\x19\xe0l;\xab\xbb[o\x05+k\xf7\xfc7\xe0r)\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xac\xbd\x18U\x89\xf7\xa6\x8ag\xaaK\x1b\xd6Pw\xf8\xc6NN!\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xac\xc0bp,Ya]4D\xefb\x14\xb8\x86+\x00\x9a\x02\xed\x89QO\xcb$\xff\x9cP\x00\x00\u07d4\xac\xc0\x90\x9f\xda.\xa6\xb7\xb7\xa8\x8d\xb7\xa0\xaa\xc8h\t\x1d\xdb\xf6\x89\x013v_\x1e&\u01c0\x00\u07d4\xac\xc1\u01c7\x86\xabM+;'q5\xb5\xba\x12>\x04\x00Hk\x89\x04E\x91\xd6\u007f\xec\xc8\x00\x00\u07d4\xac\xc4j*U\\t\xde\u0522\xbd\tN\x82\x1b\x97\x84;@\xc0\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xac\u015f;0\xce\xff\xc5da\xcc[\x8d\xf4\x89\x02$\x0e\x0e{\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xac\xce\x01\xe0\xa7\x06\x10\xdcp\xbb\x91\xe9\x92o\xa9\x95\u007f7/\xba\x89\x1d\x1c_>\xda \xc4\x00\x00\u07d4\xac\xd8\u0751\xf7\x14vLEg|c\xd8R\xe5n\xb9\xee\xce.\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xac\u2af6;\x06\x04@\x9f\xbd\xe3\xe7\x16\u0487mD\xe8\xe5\u0749\b=lz\xabc`\x00\x00\xe0\x94\xac\xec\x91\xefiA\xcfc\v\xa9\xa3\u71e0\x12\xf4\xa2\xd9\x1d\u050a\x10\xf0\xcf\x06M\u0552\x00\x00\x00\u07d4\xad\nJ\xe4x\xe9cn\x88\xc6\x04\xf2B\xcfT9\xc6\xd4V9\x89\xbe\xd1\xd0&=\x9f\x00\x00\x00\u07d4\xad\x17\x99\xaa\xd7`+E@\u0343/\x9d\xb5\xf1\x11P\xf1hz\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xad\x1dh\xa08\xfd%\x86\x06~\xf6\xd15\xd9b\x8ey\xc2\xc9$\x89\xfe\t\xa5'\x9e*\xbc\x00\x00\u07d4\xad*\\\x00\xf9#\xaa\xf2\x1a\xb9\xf3\xfb\x06n\xfa\n\x03\xde/\xb2\x8965\xbbw\xcbK\x86\x00\x00\u07d4\xad5e\xd5+h\x8a\xdd\xed\b\x16\x8b-8r\xd1}\n&\xae\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xad7|\xd2^\xb5>\x83\xae\t\x1a\n\x1d+E\x16\xf4\x84\xaf\u0789i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94\xadAM)\xcb~\xe9s\xfe\xc5N\"\xa3\x88I\x17\x86\xcfT\x02\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\xadD5~\x01~$OGi1\u01f8\x18\x9e\xfe\xe8\n]\n\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xadW\xaa\x9d\x00\xd1\fC\x9b5\xef\xcc\v\xec\xac.9U\xc3\x13\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xadY\xa7\x8e\xb9\xa7J\u007f\xbd\xae\xfa\xfa\x82\xea\u0684u\xf0\u007f\x95\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xadZ\x8dV\x01L\xfc\xb3`\xf4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xadr\x81!\x87?\x04V\xd0Q\x8b\x80\xabe\x80\xa2\x03pe\x95\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xads,\x97e\x93\xee\xc4x;N.\xcdy9yx\v\xfe\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xad}\xd0S\x85\x9e\xdf\xf1\xcbo\x9d*\xcb\xedm\xd5\xe32Bo\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xad\x80\xd8e\xb8\\4\xd2\xe6IK.z\xef\xeak\x9a\xf1\x84\u06c9\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xad\x8b\xfe\xf8\u018aH\x16\xb3\x91o5\xcb{\xfc\xd7\xd3\x04\tv\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xad\x8eH\xa3wi]\xe0\x146:R:(\xb1\xa4\fx\xf2\b\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xad\x91\n#\u0585\x06\x13eJ\xf7\x863z\u04a7\bh\xacm\x89lh\xcc\u041b\x02,\x00\x00\u07d4\xad\x92~\x03\xd1Y\x9ax\xca+\xf0\xca\u04a1\x83\xdc\xebq\xea\xc0\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94\xad\x92\xca\x06n\xdb|q\x1d\xfc[\x16a\x92\xd1\xed\xf8\xe7q\x85\x8a\a\x9f\x90\\o\xd3N\x80\x00\x00\u07d4\xad\x94#_\u00f3\xf4z$\x13\xaf1\u8111I\b\xef\fE\x89\x1b\x1b\x01B\xd8\x15\x84\x00\x00\u07d4\xad\x9e\x97\xa0H/5:\x05\xc0\xf7\x92\xb9w\xb6\xc7\xe8\x11\xfa_\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xad\x9fL\x89\n;Q\x1c\xeeQ\xdf\xe6\xcf\xd7\xf1\t;vA,\x89\x1bv|\xbf\xeb\f\xe4\x00\x00\u07d4\xad\xaa\x0eT\x8c\x03Z\xff\xedd\xcag\x8a\x96?\xab\xe9\xa2k\xfd\x89\x03\xcbq\xf5\x1f\xc5X\x00\x00\u07d4\xad\xb9H\xb1\xb6\xfe\xfe }\xe6^\x9b\xbc-\xe9\x8e`]\vW\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xad\xc1\x9e\xc85\xaf\xe3\u5347\u0713\xa8\xa9!<\x90E\x13&\x89j\xdb\xe54\"\x82\x00\x00\x00\u07d4\xad\xc8\"\x8e\xf9(\xe1\x8b*\x80}\x00\xfb1\xfcgX\x15\xaa\x00\x00\u07d4\xad\xff\r\x1d\v\x97G\x1ev\u05c9\xd2\u470at\xf9\xbdT\xff\x89e\xea=\xb7UF`\x00\x00\u07d4\xae\x06,D\x86\x18d0u\xdez\x0004-\xce\xd6=\xba\u05c9,\xc6\u034c\u0082\xb3\x00\x00\xe0\x94\xae\x10\xe2z\x01O\r0k\xaf&mH\x97\u021a\xee\xe2\xe9t\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xae\x12k8,\xf2W\xfa\xd7\xf0\xbc}\x16)~T\xccrg\u0689\x10CV\x1a\x88)0\x00\x00\u07d4\xae\x13\xa0\x85\x11\x11\x0f2\xe5;\xe4\x12xE\xc8C\xa1\xa5|{\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\xae\x17\x9aF\r\xb6c&t=$\xe6u#\xa5{$m\xaf\u007f\x8a\x01\x00\a\xae|\xe5\xbb\xe4\x00\x00\u07d4\xae\"(ey\x90y\xaa\xf4\xf0gJ\f\u06ab\x02\xa6\xd5p\xff\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xae#\x9a\xcf\xfdN\xbe.\x1b\xa5\xb4\x17\x05r\xdcy\xcce3\xec\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xae/\x9c\x19\xacv\x13e\x94C#\x93\xb0G\x1d\b\x90!d\u04c9%\xdf\x05\u01a8\x97\xe4\x00\x00\u07d4\xae4\x86\x1d4\"S\x19O\xfcfR\xdf\xdeQ\xabD\xca\xd3\xfe\x89\x19F\bhc\x16\xbd\x80\x00\u07d4\xae6\xf7E!!\x91>\x80\x0e\x0f\xcd\x1ae\xa5G\x1c#\x84o\x89\b\xe3\xf5\v\x17<\x10\x00\x00\u07d4\xae?\x98\xa4C\xef\xe0\x0f>q\x1dR]\x98\x94\u071aa\x15{\x89\x10\x04\xe2\xe4_\xb7\xee\x00\x00\xe0\x94\xaeG\xe2`\x9c\xfa\xfe6\x9df\xd4\x15\xd99\xde\x05\b\x1a\x98r\x8a\x05\xba\xec\xf0%\xf9\xb6P\x00\x00\u07d4\xaeO\x12.5\xc0\xb1\xd1\xe4\x06\x92\x91E|\x83\xc0\u007f\x96_\xa3\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xaePU\x81L\xb8\xbe\f\x11{\xb8\xb1\xc8\u04b6;F\x98\xb7(\x89\x01\xbc\x93.\xc5s\xa3\x80\x00\u07d4\xaeS\x8cs\u0173\x8d\x8dXM~\xbd\xad\xef\xb1\\\xab\xe4\x83W\x896'\xe8\xf7\x127<\x00\x00\u07d4\xaeW\xcc\x12\x9a\x96\xa8\x99\x81\xda\xc6\r/\xfb\x87}]\xc5\xe42\x89<:#\x94\xb3\x96U\x00\x00\u07d4\xaeZ\xa1\xe6\u00b6\x0fo\xd3\xef\xe7!\xbbJq\x9c\xbe=o]\x89+$\u01b5Z^b\x00\x00\u07d4\xae\\\x9b\xda\xd3\xc5\u0221\"\x04D\xae\xa5\xc2)\xc1\x83\x9f\x1dd\x89\x19\xe2\xa4\xc8\x18\xb9\x06\x00\x00\u07d4\xae\\\xe35Z{\xa9\xb32v\f\tP\u00bcE\xa8_\xa9\xa0\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\xae]\"\x1a\xfc\xd3\u0493U\xf5\b\xea\xdf\xca@\x8c\xe3<\xa9\x03\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xaec[\xf781\x11\x9d-)\xc0\xd0O\xf8\xf8\xd8\u0425zF\x89Hz\x9a0E9D\x00\x00\xe0\x94\xaed\x81U\xa6X7\x0f\x92\x9b\xe3\x84\xf7\xe0\x01\x04~I\xddF\x8a\x02\xdf$\xae2\xbe D\x00\x00\xe0\x94\xaeo\fs\xfd\xd7|H\x97'Q!t\u0675\x02\x96a\x1cL\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xaep\xe6\x9d,J\n\xf8\x18\x80{\x1a'\x05\xf7\x9f\u0435\xdb\u01095e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\xaew9\x12N\xd1S\x05%\x03\xfc\x10\x14\x10\xd1\xff\xd8\xcd\x13\xb7\x8964\xfb\x9f\x14\x89\xa7\x00\x00\u07d4\xaex\xbb\x84\x919\xa6\xba8\xae\x92\xa0\x9ai`\x1c\xc4\xcbb\u0449\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xae\x84\"\x10\xf4M\x14\u0124\u06d1\xfc\x9d;;P\x01O{\xf7\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xae\x84.\x81\x85\x8e\xcf\xed\xf6Plhm\xc2\x04\xac\x15\xbf\x8b$\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xae\x89T\xf8\xd6\x16m\xe5\a\xcfa)}\x0f\xc7\xcak\x9eq(\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xae\x9e\xcdk\u0755.\xf4\x97\xc0\x05\n\u0aca\x82\xa9\x18\x98\u0389\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4\xae\x9f\\?\xbb\xe0\u027c\xbf\x1a\xf8\xfft\xea(\v:]\x8b\b\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\xae\xad\x88\u0589Ak\x1c\x91\xf26D!7[}\x82\xd0RR\n\xfb\\Wm\x9f~\xb9>\u048a\r\xd0A \xba\t\xcf\xe6\x00\x00\u07d4\xae\xc2\u007f\xf5\xd7\xf9\xdd\u0691\x18?F\xf9\xd5%C\xb6\xcd+/\x89\x18e\x01'\xcc=\xc8\x00\x00\u07d4\xae\xe4\x9dh\xad\xed\xb0\x81\xfdCpZ_x\xc7x\xfb\x90\xdeH\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xae\xf5\xb1\"X\xa1\x8d\xec\a\xd5\xec.1et\x91\x9dy\xd6\u0589lk\x93[\x8b\xbd@\x00\x00\u07d4\xae\xfc\xfe\x88\xc8&\xcc\xf11\xd5N\xb4\ua7b8\x0ea\xe1\xee%\x89\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\xaf\x06\xf5\xfam\x12\x14\xecC\x96}\x1b\xd4\xdd\xe7J\xb8\x14\xa98\x89\x04\xc5>\xcd\xc1\x8a`\x00\x00\u07d4\xaf\x11H\xefl\x8e\x10=u0\xef\xc9\x16y\u026c'\x00\t\x93\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xaf >\"\x9d~mA\x9d\xf47\x8e\xa9\x87\x15Q_c\x14\x85\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94\xaf X\xc7(,\xf6|\x8c<\xf90\x13<\x89a|\xe7])\x8a\x01w\"J\xa8D\xc7 \x00\x00\u07d4\xaf&\xf7\u01bfE> x\xf0\x89S\u4c80\x04\xa2\xc1\xe2\t\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xaf0\x87\xe6.\x04\xbf\x90\rZT\xdc>\x94bt\u0692B;\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xaf6\x14\u0736\x8a6\xe4ZN\x91\x1ebybG\"-Y[\x89z\x81\x06_\x11\x03\xbc\x00\x00\u07d4\xaf6\x15\u01c9\u0431\x15*\xd4\xdb%\xfe]\xcf\"(\x04\xcfb\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xaf<\xb5\x96Y3\xe7\xda\u0603i;\x9c>\x15\xbe\xb6\x8aHs\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xafD\x93\xe8R\x1c\xa8\x9d\x95\xf5&|\x1a\xb6?\x9fEA\x1e\x1b\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xafL\xf4\x17\x85\x16\x1fW\x1d\f\xa6\x9c\x94\xf8\x02\x1fA)N\u028a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\xafR\x9b\xdbE\x9c\xc1\x85\xbe\xe5\xa1\u014b\xf7\xe8\xcc\xe2\\\x15\r\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xafg\xfd>\x12\u007f\xd9\xdc6\xeb?\xcdj\x80\u01feOu2\xb2\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4\xafw\x1094Z40\x01\xbc\x0f\x8aY#\xb1&\xb6\rP\x9c\x895e\x9e\xf9?\x0f\xc4\x00\x00\xe0\x94\xaf\u007fy\xcbAZ\x1f\xb8\u06fd\tF\a\xee\x8dA\xfb|Z;\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xaf\x87\xd27\x1e\xf3x\x95\u007f\xbd\x05\xba/\x1df\x93\x1b\x01\u2e09%\xf2s\x93=\xb5p\x00\x00\u07d4\xaf\x88\x0f\xc7V}U\x95\xca\xcc\xe1\\?\xc1L\x87B\xc2l\x9e\x89\a?u\u0460\x85\xba\x00\x00\u07d4\xaf\x8e\x1d\xcb1L\x95\r6\x87CM0\x98X\xe1\xa8s\x9c\u0509\x0e~\xeb\xa3A\vt\x00\x00\u07d4\xaf\x99-\xd6i\xc0\x88>U\x15\xd3\xf3\x11*\x13\xf6\x17\xa4\xc3g\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xaf\xa1\u056d8\xfe\xd4GY\xc0[\x89\x93\xc1\xaa\r\xac\xe1\x9f@\x89\x04V9\x18$O@\x00\x00\xe0\x94\xaf\xa59XnG\x19\x17J;F\xb9\xb3\xe6c\xa7\u0475\xb9\x87\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xaf\xa6\x94n\xff\xd5\xffS\x15O\x82\x01\x02S\xdfG\xae(\f\u0309j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xaf\xc8\xeb\u860b\xd4\x10Z\xccL\x01\x8eTj\x1e\x8f\x9cx\x88\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\xaf\xcc}\xbb\x83V\xd8B\xd4:\xe7\xe2<\x84\"\xb0\"\xa3\b\x03\x8a\x06o\xfc\xbf\xd5\xe5\xa3\x00\x00\x00\u07d4\xaf\xd0\x19\xff6\xa0\x91U4ki\x97H\x15\xa1\xc9\x12\xc9\n\xa4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xaf\xda\xc5\xc1\xcbV\xe2E\xbfp3\x00f\xa8\x17\uabecL\u0449\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xaf\xdd\x1bxab\xb81~ \xf0\xe9y\xf4\xb2\xceHmv]\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xaf\xf1\x04Z\xdf'\xa1\xaa2\x94a\xb2M\xe1\xba\u950ai\x8b\x89\x01\u03c4\xa3\n\n\f\x00\x00\u07d4\xaf\xf1\a\x96\v~\xc3N\u0590\xb6e\x02M`\x83\x8c\x19\x0fp\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xaf\xf1\x1c\xcfi\x93\x04\xd5\xf5\x86*\xf8`\x83E\x1c&\xe7\x9a\xe5\x89l]\xb2\xa4\xd8\x15\xdc\x00\x00\u07d4\xaf\xf1at\nm\x90\x9f\xe9\x9cY\xa9\xb7yE\xc9\x1c\xc9\x14H\x89\x03@\xaa\xd2\x1b;p\x00\x00\xe0\x94\xaf\xfc\x99\xd5\ubd28O\xe7x\x8d\x97\xdc\xe2t\xb08$\x048\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xaf\xfe\xa0G7\"\xcb\u007f\x0e\x0e\x86\xb9\xe1\x18\x83\xbfB\x8d\x8dT\x89i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94\xb0\t\x96\xb0Vn\xcb>rC\xb8\"y\x88\u0733R\xc2\x18\x99\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xb0\x1e8\x9b(\xa3\x1d\x8eI\x95\xbd\xd7\xd7\xc8\x1b\xee\xab\x1eA\x19\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb0-\x06(s3EE\u03a2\x92\x18\xe4\x05w`Y\x0ft#\x89\xac\xb6\xa1\xc7\xd9:\x88\x00\x00\u07d4\xb0/\xa2\x93\x87\xec\x12\xe3\u007fi\"\xacL\xe9\x8c[\t\xe0\xb0\x0f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb06\x91k\xda\u03d4\xb6\x9eZ\x8ae`)u\xeb\x02a\x04\u0749\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xb0A1\x0f\xe9\xee\u0586L\xed\u053e\xe5\x8d\xf8\x8e\xb4\xed<\xac\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xb0U\xafL\xad\xfc\xfd\xb4%\xcfe\xbad1\a\x8f\a\xec\u056b\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xb0W\x11S\xdb\x1cN\u05ec\xae\xfe\x13\xec\xdf\xdbr\xe7\xe4\xf0j\x8a\x11\f\xffyj\xc1\x95 \x00\x00\u07d4\xb0n\xab\t\xa6\x10\u01a5=V\xa9F\xb2\xc44\x87\xac\x1d[-\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb0rI\xe0U\x04J\x91U5\x9a@)7\xbb\xd9T\xfeH\xb6\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xb0v\x182\x8a\x90\x13\a\xa1\xb7\xa0\xd0X\xfc\xd5xn\x9er\xfe\x8a\x06gI]JC0\xce\x00\x00\u07d4\xb0y\xbbM\x98f\x14:m\xa7*\xe7\xac\x00\"\x06)\x811\\\x89)3\x1eeX\xf0\xe0\x00\x00\u07d4\xb0{\xcc\bZ\xb3\xf7)\xf2D\x00Ah7\xb6\x996\xba\x88s\x89lm\x84\xbc\xcd\xd9\xce\x00\x00\u07d4\xb0{\xcf\x1c\xc5\xd4F.Q$\xc9e\xec\xf0\xd7\r\xc2z\xcau\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\xb0|\xb9\xc1$\x05\xb7\x11\x80uC\u0113De\xf8\u007f\x98\xbd-\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xb0\u007f\u07af\xf9\x1dD`\xfel\xd0\u8870\xbd\x8d\"\xa6.\x87\x8a\x01\x1d%)\xf3SZ\xb0\x00\x00\xe0\x94\xb0\x9f\xe6\xd44\x9b\x99\xbc7\x93\x80T\x02-T\xfc\xa3f\xf7\xaf\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\xe0\x94\xb0\xaa\x00\x95\f\x0e\x81\xfa2\x10\x17>r\x9a\xaf\x16:'\xcdq\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xb0\xacN\xfff\x80\xee\x14\x16\x9c\xda\xdb\xff\xdb0\x80Om%\xf5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb0\xb3j\xf9\xae\xee\u07d7\xb6\xb0\"\x80\xf1\x14\xf19\x84\xea2`\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\xb0\xb7y\xb9K\xfa<.\x1fX{\u031c~!x\x92\"7\x8f\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\xb0\xba\xeb0\xe3\x13wlLm$t\x02\xbaAg\xaf\u0361\u0309j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xb0\xbb)\xa8a\xea\x1dBME\xac\u053f\u0112\xfb\x8e\xd8\t\xb7\x89\x04V9\x18$O@\x00\x00\xe0\x94\xb0\xc1\xb1w\xa2 \xe4\x1f|t\xd0|\u0785i\xc2\x1cu\xc2\xf9\x8a\x01/\x93\x9c\x99\xed\xab\x80\x00\x00\u07d4\xb0\xc7\xceL\r\xc3\u00bb\xb9\x9c\xc1\x85{\x8aE_a\x17\x11\u0389\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb0\xce\xf8\xe8\xfb\x89\x84\xa6\x01\x9f\x01\xc6y\xf2r\xbb\xe6\x8f\\w\x89\b=lz\xabc`\x00\x00\xe0\x94\xb0\xd3+\xd7\xe4\u6577\xb0\x1a\xa3\xd0Ao\x80U}\xba\x99\x03\x8a\x03s\x9f\xf0\xf6\xe6\x130\x00\x00\xe0\x94\xb0\xd3\u0247+\x85\x05n\xa0\xc0\xe6\xd1\xec\xf7\xa7~<\u6ac5\x8a\x01\x0f\b\xed\xa8\xe5U\t\x80\x00\u07d4\xb0\xe4i\u0206Y8\x15\xb3IV8Y]\xae\xf0f_\xaeb\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xb0\xe7`\xbb\a\xc0\x81wsE\xe0W\x8e\x8b\u0218\"mN;\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb1\x040\x04\xec\x19A\xa8\xcfO+\x00\xb1W\x00\u076co\xf1~\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb1\x05\xdd=\x98|\xff\xd8\x13\xe9\xc8P\n\x80\xa1\xad%}V\u0189lj\xccg\u05f1\xd4\x00\x00\u07d4\xb1\x0f\u04a6G\x10/\x88\x1ft\xc9\xfb\xc3}\xa62\x94\x9f#u\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94\xb1\x15\xee:\xb7d\x1e\x1a\xa6\xd0\x00\xe4\x1b\xfc\x1e\xc7!\f/2\x8a\x02\xc0\xbb=\xd3\fN \x00\x00\u07d4\xb1\x17\x8a\xd4s\x83\xc3\x1c\x814\xa1\x94\x1c\xbc\xd4t\xd0bD\xe2\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xb1\x17\x95\x89\u1779\xd4\x15W\xbb\xec\x1c\xb2L\xcc-\xec\x1c\u007f\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xb1\x19\u76a9\xb9\x16Re\x81\xcb\xf5!\xefGJ\xe8M\xcf\xf4\x89O\xba\x10\x01\xe5\xbe\xfe\x00\x00\u07d4\xb1\x1f\xa7\xfb'\n\xbc\xdfZ.\xab\x95\xaa0\u013566\uffc9+^:\xf1k\x18\x80\x00\x00\u07d4\xb1$\xbc\xb6\xff\xa40\xfc\xae.\x86\xb4_'\xe3\xf2\x1e\x81\xee\b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb1)\xa5\xcbq\x05\xfe\x81\v\u0615\xdcr\x06\xa9\x91\xa4TT\x88\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\xe0\x94\xb1.\xd0{\x8a8\xadU\x066?\xc0z\vmy\x996\xbd\xaf\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xb14\xc0\x049\x1a\xb4\x99(x3zQ\xec$/B(WB\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb1?\x93\xaf0\xe8\xd7fs\x81\xb2\xb9[\xc1\xa6\x99\xd5\xe3\xe1)\x89\x16\u012b\xbe\xbe\xa0\x10\x00\x00\u07d4\xb1E\x92\x85\x86>\xa2\xdb7Y\xe5F\u03b3\xfb7a\xf5\x90\x9c\x89<\xd7*\x89@\x87\xe0\x80\x00\u07d4\xb1F\xa0\xb9%U<\xf0o\xca\xf5J\x1bM\xfe\xa6!)\aW\x89lnY\xe6|xT\x00\x00\xe0\x94\xb1Jz\xaa\x8fI\xf2\xfb\x9a\x81\x02\u05bb\xe4\u010a\xe7\xc0o\xb2\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xb1K\xbe\xffpr\tu\xdca\x91\xb2\xa4O\xf4\x9f&r\x87<\x89\a\xc0\x86\x0eZ\x80\xdc\x00\x00\xe0\x94\xb1L\xc8\xde3\xd63\x826S\x9aH\x90 \xceFU\xa3+\u018a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xb1M\xdb\x03\x86\xfb`c\x98\xb8\xccGVZ\xfa\xe0\x0f\xf1\xd6j\x89\xa1*\xff\b>f\xf0\x00\x00\u07d4\xb1S\xf8(\xdd\amJ|\x1c%t\xbb-\xee\x1aD\xa3\x18\xa8\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb1T\x0e\x94\xcf\xf3F\\\xc3\u0447\xe7\xc8\u3f6f\x98FY\u2262\x15\xe4C\x90\xe33\x00\x00\u07d4\xb1X\xdbC\xfab\xd3\x0ee\xf3\u041b\xf7\x81\u01f6sr\uba89l]\xb2\xa4\xd8\x15\xdc\x00\x00\u07d4\xb1ar_\xdc\xed\xd1yR\xd5{#\xef([~K\x11i\xe8\x89\x02\xb6\xdf\xed6d\x95\x80\x00\u07d4\xb1dy\xba\x8e}\xf8\xf6>\x1b\x95\xd1I\u0345)\xd75\xc2\u0689-\xe3:j\xac2T\x80\x00\u07d4\xb1f\xe3}.P\x1a\xe7<\x84\x14+_\xfbZ\xa6U\xddZ\x99\x89l]\xb2\xa4\xd8\x15\xdc\x00\x00\u07d4\xb1\x83\xeb\xeeO\xcbB\xc2 \xe4wt\xf5\x9dlT\xd5\xe3*\xb1\x89V\xf7\xa9\xc3<\x04\xd1\x00\x00\u07d4\xb1\x88\a\x84D\x02~8g\x98\xa8\xaehi\x89\x19\xd5\xcc#\r\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4\xb1\x89j7\xe5\u0602Z-\x01vZ\xe5\xdeb\x99w\u0783R\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb1\x8eg\xa5\x05\n\x1d\xc9\xfb\x19\t\x19\xa3=\xa88\xefDP\x14\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb1\xa2\xb4:t3\xdd\x15\v\xb8\"'\xedQ\x9c\u05b1B\u04c2\x89\x94mb\rtK\x88\x00\x00\u07d4\xb1\xc0\u040b6\xe1\x84\xf9\x95*@7\xe3\xe5:f}\a\nN\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb1\xc3(\xfb\x98\xf2\xf1\x9a\xb6do\n|\x8cVo\xdaZ\x85@\x89\x87\x86x2n\xac\x90\x00\x00\xe0\x94\xb1\xc7Qxi9\xbb\xa0\xd6q\xa6w\xa1X\u01ab\xe7&^F\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\xb1\xcdK\xdf\xd1\x04H\x9a\x02n\u025dYs\a\xa0By\xf1s\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xb1\u03d4\xf8\t\x15\x05\x05_\x01\n\xb4\xba\u0196\xe0\xca\x0fg\xa1\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xb1\u05b0\x1b\x94\xd8T\xfe\x8b7J\xa6^\x89\\\xf2*\xa2V\x0e\x892\xf5\x1e\u06ea\xa30\x00\x00\xe0\x94\xb1\u06e5%\v\xa9bWU$n\x06yg\xf2\xad/\a\x91\u078a\x10\xf0\xcf\x06M\u0552\x00\x00\x00\u07d4\xb1\xe2\u0755\xe3\x9a\xe9w\\U\xae\xb1?\x12\xc2\xfa#0S\xba\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb1\xe6\xe8\x10\xc2J\xb0H\x8d\xe9\xe0\x1eWH7\x82\x9f|w\u0409\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb1\xe9\xc5\xf1\xd2\x1eauzk.\xe7Y\x13\xfcZ\x1aA\x01\u00c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xb2\x03\u049elV\xb9&\x99\u0139-\x1fo\x84d\x8d\xc4\u03fc\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb2\x16\xdcY\xe2|=ry\xf5\xcd[\xb2\xbe\u03f2`n\x14\u0649\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb2\x1byy\xbf|\\\xa0\x1f\xa8-\xd6@\xb4\x1c9\xe6\u01bcu\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xb2#\xbf\x1f\xbf\x80H\\\xa2\xb5V}\x98\xdb{\xc3SM\xd6i\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb2-PU\xd9b15\x96\x1ej\xbd'<\x90\xde\xea\x16\xa3\xe7\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xb2-\xad\xd7\xe1\xe0R2\xa927\xba\xed\x98\xe0\u07d2\xb1\x86\x9e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb24\x03_uDF<\xe1\xe2+\xc5S\x06F\x84\xc5\x13\xcdQ\x89\r\x89\xfa=\u010d\xcf\x00\x00\u07d4\xb2G\u03dcr\xecH*\xf3\xea\xa7Ye\x8fy=g\nW\f\x891p\x8a\xe0\x04T@\x00\x00\u07d4\xb2ghA\xee\x9f-1\xc1r\xe8#\x03\xb0\xfe\x9b\xbf\x9f\x1e\t\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb2y\xc7\xd3U\u0088\x03\x92\xaa\u046a!\xee\x86|;5\a\u07c9D[\xe3\xf2\uf1d4\x00\x00\u07d4\xb2|\x1a$ L\x1e\x11\x8du\x14\x9d\xd1\t1\x1e\a\xc0s\xab\x89\xa8\r$g~\xfe\xf0\x00\x00\u07d4\xb2\x81\x81\xa4X\xa4@\xf1\u01bb\x1d\xe8@\x02\x81\xa3\x14\x8fL5\x89\x14b\fW\xdd\xda\xe0\x00\x00\xe0\x94\xb2\x82E\x03|\xb1\x92\xf7W\x85\u02c6\xcb\xfe|\x93\r\xa2X\xb0\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\xb2\x87\xf7\xf8\xd8\u00c7,\x1bXk\xcd}\n\xed\xbf~s'2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb2\x8b\xb3\x9f4fQ|\xd4o\x97\x9c\xf5\x96S\xee}\x8f\x15.\x89\x18e\x01'\xcc=\xc8\x00\x00\u07d4\xb2\x8d\xbf\xc6I\x98\x94\xf7:q\xfa\xa0\n\xbe\x0fK\xc9\u045f*\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xb2\x96\x8f}5\xf2\b\x87\x161\xc6h{?=\xae\xab\xc6al\x89\bu\xc4\u007f(\x9fv\x00\x00\u07d4\xb2\x9f[|\x190\xd9\xf9z\x11^\x06pf\xf0\xb5M\xb4K;\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb2\xa1D\xb1\xeag\xb9Q\x0f\"g\xf9\xda9\xd3\xf9=\xe2fB\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb2\xa2\xc2\x11\x16\x12\xfb\x8b\xbb\x8e}\xd97\x8dg\xf1\xa3\x84\xf0P\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xb2\xa4\x98\xf0;\xd7\x17\x8b\u0627\x89\xa0\x0fR7\xafy\xa3\xe3\xf8\x8a\x04\x1b\xad\x15^e\x12 \x00\x00\u07d4\xb2\xaa/\x1f\x8e\x93\xe7\x97\x13\xd9,\xea\x9f\xfc\xe9\xa4\n\xf9\xc8-\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb2\xb5\x16\xfd\u045e\u007f8d\xb6\xd2\xcf\x1b%*AV\xf1\xb0;\x89\x02\xe9\x83\xc7a\x15\xfc\x00\x00\u07d4\xb2\xb7\u0374\xffKa\u0577\xce\v\"p\xbb\xb5&\x97C\xec\x04\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb2\xbd\xbe\u07d5\x90\x84v\xd7\x14\x8a7\f\u0193t6(\x05\u007f\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb2\xbf\xaaX\xb5\x19l\\\xb7\xf8\x9d\xe1_G\x9d\x188\xdeq=\x89\x01#n\xfc\xbc\xbb4\x00\x00\u07d4\xb2\xc5>\xfa3\xfeJ:\x1a\x80 \\s\xec;\x1d\xbc\xad\x06\x02\x89h\x01\u06b3Y\x18\x93\x80\x00\xe0\x94\xb2\xd06\x05\x15\xf1}\xab\xa9\x0f\u02ec\x82\x05\xd5i\xb9\x15\u05ac\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xb2\xd1\xe9\x9a\xf9\x121\x85\x8epe\xdd\x19\x183\r\xc4\xc7G\u054a\x03\x89O\x0eo\x9b\x9fp\x00\x00\u07d4\xb2\u066b\x96d\xbc\xf6\xdf <4o\u0192\xfd\x9c\xba\xb9 ^\x89\x17\xbex\x97`e\x18\x00\x00\u07d4\xb2\u0777\x86\xd3yN'\x01\x87\xd0E\x1a\xd6\u0237\x9e\x0e\x87E\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb2\xe0\x85\xfd\xdd\x14h\xba\aA['NsN\x11#\u007f\xb2\xa9\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xb2\xe9\xd7k\xf5\x0f\xc3k\xf7\u04d4Kc\xe9\u0288\x9bi\x99h\x89\x902\xeab\xb7K\x10\x00\x00\xe0\x94\xb2\xf9\xc9r\xc1\xe9swU\xb3\xff\x1b0\x88s\x83\x969[&\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xb2\xfc\x84\xa3\xe5\nP\xaf\x02\xf9M\xa08>\u055fq\xff\x01\u05ca\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4\xb3\x05\v\xef\xf9\xde3\xc8\x0e\x1f\xa1R%\xe2\x8f,A:\xe3\x13\x89%\xf2s\x93=\xb5p\x00\x00\u07d4\xb3\x11\x96qJH\xdf\xf7&\xea\x943\xcd)\x12\xf1\xa4\x14\xb3\xb3\x89\x91Hx\xa8\xc0^\xe0\x00\x00\xe0\x94\xb3\x14[tPm\x1a\x8d\x04|\xdc\xdcU9*{SPy\x9a\x8a\x1bb)t\x1c\r=]\x80\x00\u07d4\xb3 \x83H6\xd1\xdb\xfd\xa9\xe7\xa3\x18M\x1a\xd1\xfdC \xcc\xc0\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb3#\u073f.\xdd\xc58.\u4efb \x1c\xa3\x93\x1b\xe8\xb48\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xb3$\x00\xfd\x13\xc5P\t\x17\xcb\x03{)\xfe\"\xe7\xd5\"\x8f-\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xb3%gL\x01\xe3\xf7)\rR&3\x9f\xbe\xacg\xd2!'\x9f\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\xb3(%\xd5\xf3\xdb$\x9e\xf4\xe8\\\xc4\xf31S\x95\x89v\u8f09\x1b-\xf9\xd2\x19\xf5y\x80\x00\u07d4\xb3*\xf3\xd3\xe8\xd0u4I&To.2\x88{\xf9;\x16\xbd\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb3/\x1c&\x89\xa5\xcey\xf1\xbc\x97\v1XO\x1b\xcf\"\x83\xe7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb3<\x03#\xfb\xf9\xc2l\x1d\x8a\xc4N\xf7C\x91\u0400F\x96\u0689\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xb3O\x04\xb8\xdbe\xbb\xa9\xc2n\xfcL\xe6\xef\xc5\x04\x81\xf3\xd6]\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xb3U}9\xb5A\x1b\x84D__T\xf3\x8fb\xd2qM\x00\x87\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\xb3X\xe9|p\xb6\x05\xb1\xd7\xd7)\u07f6@\xb4<^\xaf\xd1\xe7\x8a\x04<3\xc1\x93ud\x80\x00\x00\u0794\xb3^\x8a\x1c\r\xac~\x0ef\u06ecsjY*\xbdD\x01%a\x88\xcf\xceU\xaa\x12\xb3\x00\x00\xe0\x94\xb3fx\x94\xb7\x86<\x06\x8a\xd3D\x87?\xcf\xf4\xb5g\x1e\x06\x89\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xb3qw1\xda\xd6Q2\xday-\x87`0\xe4j\xc2'\xbb\x8a\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb3s\x1b\x04l\x8a\u0195\xa1'\xfdy\u0425\xd5\xfaj\xe6\xd1.\x89lO\xd1\xee$nx\x00\x00\u07d4\xb3|+\x9fPc{\xec\xe0\u0295\x92\b\xae\xfe\xe6F;\xa7 \x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb3\x88\xb5\xdf\xec\xd2\xc5\u4d56W|d%V\xdb\xfe'xU\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb3\x8cNS{]\xf90\xd6Zt\xd0C\x83\x1dkH[\xbd\xe4\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\xb3\x919Wa\x94\xa0\x86a\x95\x15\x1f3\xf2\x14\n\xd1\u0306\u03ca\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xb3\x9fL\x00\xb2c\f\xab}\xb7)^\xf4=G\xd5\x01\xe1\u007f\u05c9\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb3\xa6K\x11vrOT\t\xe1AJ5#f\x1b\xae\xe7KJ\x89\x01ch\xffO\xf9\xc1\x00\x00\u07d4\xb3\xa6\xbdA\xf9\xd9\xc3 \x1e\x05\v\x87\x19\x8f\xbd\xa3\x994\"\x10\x89\xc4a\xe1\xdd\x10)\xb5\x80\x00\u07d4\xb3\xa8\xc2\xcb}5\x8eW9\x94\x1d\x94[\xa9\x04Z\x02:\x8b\xbb\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb3\xaeT\xfb\xa0\x9d>\xe1\u05bd\xd1\xe9W\x929\x19\x02L5\xfa\x89\x03\x8d,\xeee\xb2*\x80\x00\u07d4\xb3\xb7\xf4\x93\xb4J,\x8d\x80\xecx\xb1\xcd\xc7Ze+s\xb0l\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb3\xc2(s\x1d\x18m-\xed[_\xbe\x00Lfl\x8eF\x9b\x86\x89\x01\x92t\xb2Y\xf6T\x00\x00\u07d4\xb3\xc2``\x9b\x9d\xf4\t^l]\xff9\x8e\xeb^-\xf4\x99\x85\x89\r\xc5_\xdb\x17d{\x00\x00\u07d4\xb3\xc6[\x84Z\xbal\xd8\x16\xfb\xaa\xe9\x83\xe0\xe4l\x82\xaa\x86\"\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb3\xc9H\x11\xe7\x17[\x14\x8b(\x1c\x1a\x84[\xfc\x9b\xb6\xfb\xc1\x15\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb3\xe2\x0e\xb4\xde\x18\xbd\x06\x02!h\x98\x94\xbe\u5bb2SQ\xee\x89\x03\xfc\x80\xcc\xe5\x16Y\x80\x00\u07d4\xb3\xe3\xc49\x06\x98\x80\x15f\x00\u0089.D\x8dA6\xc9-\x9b\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\xe0\x94\xb3\xf8*\x87\xe5\x9a9\xd0\u0480\x8f\aQ\xebr\xc22\x9c\xdc\u014a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xb3\xfc\x1dh\x81\xab\xfc\xb8\xbe\xcc\v\xb0!\xb8\xb7;r3\u0751\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\xb4\x05\x94\xc4\xf3fN\xf8I\u0326\"{\x8a%\xaai\t%\xee\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb4\x1e\xaf]Q\xa5\xba\x1b\xa3\x9b\xb4\x18\u06f5O\xabu\x0e\xfb\x1f\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb4$\u058d\x9d\r\x00\xce\xc1\x93\x8c\x85N\x15\xff\xb8\x80\xba\x01p\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb4%bs\x96+\xf61\xd0\x14U\\\xc1\xda\r\xcc1akI\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb40g\xfep\u0675Ys\xbaX\xdcd\xdd\u007f1\x1eUBY\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb46W\xa5\x0e\xec\xbc0w\xe0\x05\xd8\xf8\xd9O7xv\xba\u0509\x01\xec\x1b:\x1f\xf7Z\x00\x00\u07d4\xb4<'\xf7\xa0\xa1\"\bK\x98\xf4\x83\x92%A\u0203l\xee,\x89&\u009eG\u0104L\x00\x00\xe0\x94\xb4A5v\x86\x9c\b\xf9Q*\xd3\x11\xfe\x92Y\x88\xa5-4\x14\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xb4F\x05U$q\xa6\xee\xe4\u06abq\xff;\xb4\x13&\xd4s\xe0\x89-~=Q\xbaS\xd0\x00\x00\u07d4\xb4GW\x1d\xac\xbb>\u02f6\xd1\xcf\v\f\x8f88\xe5#$\xe2\x89\x01\xa3\x18f\u007f\xb4\x05\x80\x00\u07d4\xb4G\x83\xc8\xe5{H\a\x93\xcb\u059aE\xd9\f{O\fH\xac\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb4H\x15\xa0\xf2\x8eV\x9d\x0e\x92\x1aJ\u078f\xb2d%&Iz\x89\x03\x027\x9b\xf2\xca.\x00\x00\u07d4\xb4Im\xdb'y\x9a\"$W\xd79y\x11g(\u8844[\x89\x8d\x81\x9e\xa6_\xa6/\x80\x00\xe0\x94\xb4RL\x95\xa7\x86\x0e!\x84\x02\x96\xa6\x16$@\x19B\x1cJ\xba\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xb4\\\xca\r6\x82fbh<\xf7\u0432\xfd\xach\u007f\x02\xd0\u010965\u026d\xc5\u07a0\x00\x00\u0794\xb4d@\u01d7\xa5V\xe0L}\x91\x04f\x04\x91\xf9k\xb0v\xbf\x88\xce\xc7o\x0eqR\x00\x00\u07d4\xb4j\u0386^,P\xeaF\x98\xd2\x16\xabE]\xffZ\x11\xcdr\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb4m\x11\x82\xe5\xaa\xca\xff\r&\xb2\xfc\xf7/<\x9f\xfb\xcd\xd9}\x89\xaa*`<\xdd\u007f,\x00\x00\u07d4\xb4\x89!\xc9h}U\x10tE\x84\x93n\x88\x86\xbd\xbf-\xf6\x9b\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb4\x98\xbb\x0fR\x00\x05\xb6!jD%\xb7Z\xa9\xad\xc5-b+\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xb4\xb1\x1d\x10\x9f`\x8f\xa8\xed\xd3\xfe\xa9\xf8\xc3\x15d\x9a\xeb=\x11\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xb4\xb1K\xf4TU\u042b\b\x035\x8bu$\xa7+\xe1\xa2\x04[\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xb4\xb1\x85\xd9C\xee+Xc\x1e3\xdf\xf5\xafhT\xc1y\x93\xac\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb4\xbf$\u02c3hk\xc4i\x86\x9f\xef\xb0D\xb9\tqi\x93\xe2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb4\xc2\x00@\xcc\u0661\xa3(=\xa4\u0522\xf3e\x82\bC\xd7\xe2\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb4\xc8\x17\x0f{*\xb56\xd1\u0662[\xdd :\xe1(\x8d\xc3\u0549\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb4\xd8/.i\x94?}\xe0\xf5\xf7t8y@o\xac.\x9c\xec\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94\xb4\xddF\f\xd0\x16rZd\xb2.\xa4\xf8\xe0n\x06gN\x03>\x8a\x01#\x1b\xb8t\x85G\xa8\x00\x00\u07d4\xb4\xddT\x99\xda\xeb%\a\xfb-\xe1\"\x97s\x1dLr\xb1k\xb0\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xb5\x04l\xb3\xdc\x1d\xed\xbd6E\x14\xa2\x84\x8eD\xc1\xdeN\xd1G\x8a\x03{}\x9b\xb8 @^\x00\x00\xe0\x94\xb5\b\xf9\x87\xb2\xde4\xaeL\xf1\x93\u0785\xbf\xf6\x13\x89b\x1f\x88\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xb5\tU\xaan4\x15q\x98f\b\xbd\u0211\xc2\x13\x9fT\f\u07c9j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xb5\f\x14\x9a\x19\x06\xfa\xd2xo\xfb\x13Z\xabP\x177\xe9\xe5o\x89\x15\b\x94\xe8I\xb3\x90\x00\x00\u07d4\xb5\f\x9fW\x89\xaeD\xe2\xdc\xe0\x17\xc7\x14\xca\xf0\f\x83\x00\x84\u0089\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\xb5\x14\x88,\x97\x9b\xb6B\xa8\r\u04c7T\u0578\xc8)m\x9a\a\x893\xc5I\x901r\f\x00\x00\u07d4\xb5\x1d\u0734\xddN\x8a\xe6\xbe3m\xd9eIq\xd9\xfe\xc8kA\x89\x16\xd4d\xf8=\u2500\x00\u07d4\xb5\x1eU\x8e\xb5Q/\xbc\xfa\x81\xf8\u043d\x93\x8cy\xeb\xb5$+\x89&\u009eG\u0104L\x00\x00\u07d4\xb5#\xff\xf9t\x98q\xb3S\x88C\x887\xf7\xe6\xe0\u07a9\xcbk\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xb5-\xfbE\xde]t\xe3\xdf \x832\xbcW\x1c\x80\x9b\x8d\xcf2\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xb55\xf8\u06c7\x9f\xc6\u007f\xecX\x82J\\\xbenT\x98\xab\xa6\x92\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\xb57\xd3jp\xee\xb8\xd3\xe5\xc8\r\xe8\x15\"\\\x11X\u02d2\u0109QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\xb5;\xcb\x17L%\x184\x8b\x81\x8a\xec\xe0 6E\x96Fk\xa3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb5I>\xf1srDE\xcf4\\\x03]'\x9b\xa7Y\xf2\x8dQ\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb5S\xd2]kT!\xe8\x1c*\xd0^\v\x8b\xa7Q\xf8\xf0\x10\xe3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb5Tt\xbaX\xf0\xf2\xf4\x0el\xba\xbe\xd4\xea\x17n\x01\x1f\xca\u0589j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xb5U\xd0\x0f\x91\x90\xcc6w\xae\xf3\x14\xac\xd7?\xdc99\x92Y\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb5W\xab\x949\xefP\xd27\xb5S\xf0%\b6JFj\\\x03\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb5jx\x00(\x03\x9c\x81\xca\xf3{gu\xc6 \u7195Gd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb5j\u04ae\xc6\xc8\xc3\xf1\x9e\x15\x15\xbb\xb7\u0751(RV\xb69\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb5t\x13\x06\n\xf3\xf1N\xb4y\x06_\x1e\x9d\x19\xb3uz\xe8\u0309\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xb5uI\xbf\xbc\x9b\xdd\x18\xf76\xb2&P\xe4\x8as`\x1f\xa6\\\x89\x18-~L\xfd\xa08\x00\x00\xe0\x94\xb5w\xb6\xbe\xfa\x05N\x9c\x04\x04a\x85P\x94\xb0\x02\xd7\xf5{\u05ca\x18#\xf3\xcfb\x1d#@\x00\x00\u07d4\xb5{\x04\xfa#\xd1 ?\xae\x06\x1e\xacEB\xcb`\xf3\xa5v7\x89\nZ\xa8P\t\xe3\x9c\x00\x00\u07d4\xb5\x87\f\xe3B\xd43C36s\x03\x8bGd\xa4n\x92_>\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb5\x87\xb4J,\xa7\x9eK\xc1\u074b\xfd\xd4: qP\xf2\xe7\xe0\x89\",\x8e\xb3\xfff@\x00\x00\u07d4\xb5\x89gm\x15\xa0DH4B0\xd4\xff'\xc9^\xdf\x12,I\x8965\u026d\xc5\u07a0\x00\x00\u0794\xb5\x8bR\x86^\xa5]\x806\xf2\xfa\xb2`\x98\xb3R\u0283~\x18\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xb5\x90k\n\u9881X\xe8\xacU\x0e9\xda\bn\xe3\x15v#\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb5\xa4g\x96\x85\xfa\x14\x19l.\x920\xc8\xc4\xe3;\xff\xbc\x10\xe2\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xb5\xa5\x89\u075f@q\u06f6\xfb\xa8\x9b?]]\xae}\x96\xc1c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb5\xa6\x06\xf4\xdd\u02f9G\x1e\xc6\u007fe\x8c\xaf+\x00\xees\x02^\x89\xeaun\xa9*\xfct\x00\x00\u07d4\xb5\xadQW\u0769!\xe6\xba\xfa\u0350\x86\xaes\xae\x1fa\x1d?\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb5\xad\xd1\u701f}\x03\x06\x9b\xfe\x88;\n\x93\"\x10\xbe\x87\x12\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb5\xba)\x91|x\xa1\xd9\xe5\xc5\xc7\x13fl\x1eA\x1d\u007fi:\x89\xa8\r$g~\xfe\xf0\x00\x00\u07d4\xb5\xc8\x16\xa8(<\xa4\xdfh\xa1\xa7=c\xbd\x80&\x04\x88\xdf\b\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb5\xca\xc5\xed\x03G}9\v\xb2g\xd4\xeb\xd4a\x01\xfb\xc2\xc3\u0689\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xb5\u037cA\x15@oR\u5a85\xd0\xfe\xa1p\u0497\x9c\u01fa\x89Hz\x9a0E9D\x00\x00\u0794\xb5\u0653M{)+\xcf`;(\x80t\x1e\xb7`(\x83\x83\xa0\x88\xe7\xc2Q\x85\x05\x06\x00\x00\u07d4\xb5\xddP\xa1]\xa3Ih\x89\nS\xb4\xf1?\xe1\xaf\b\x1b\xaa\xaa\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb5\xfa\x81\x84\xe4>\xd3\u0e2b\x91!da\xb3R\x8d\x84\xfd\t\x89\x91Hx\xa8\xc0^\xe0\x00\x00\u07d4\xb5\xfb~\xa2\xdd\xc1Y\x8bfz\x9dW\xdd9\xe8Z8\xf3]V\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xb6\x00B\x97R\xf3\x99\xc8\r\a4tK\xae\n\x02.\xcag\u0189\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb6\x00\xfe\xabJ\xa9lSu\x04\xd9`W\"1Ai,\x19:\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb6\x04|\u07d3-\xb3\xe4\x04_Iv\x12#AS~\u0556\x1e\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb6\x15\xe9@\x14>\xb5\u007f\x87X\x93\xbc\x98\xa6\x1b=a\x8c\x1e\x8c\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xb6\x1c4\xfc\xac\xdap\x1aZ\xa8p$Y\u07b0\u4b83\x8d\xf8\x8a\aiZ\x92\xc2\ro\xe0\x00\x00\xe0\x94\xb60d\xbd3U\xe6\xe0~-7p$\x12Z3wlJ\xfa\x8a\b7Z*\xbc\xca$@\x00\x00\u07d4\xb65\xa4\xbcq\xfb(\xfd\xd5\xd2\xc3\"\x98:V\u0084Bni\x89\t79SM(h\x00\x00\u07d4\xb6F\u07d8\xb4\x94BtkaR\\\x81\xa3\xb0K\xa3\x10bP\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xb6YA\xd4LP\xd2Ffg\r6Gf\xe9\x91\xc0.\x11\u0089 \x86\xac5\x10R`\x00\x00\xe0\x94\xb6[\u05c0\xc7CA\x15\x16 'VR#\xf4NT\x98\xff\x8c\x8a\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4\xb6d\x11\xe3\xa0-\xed\xb7&\xfay\x10}\xc9\v\xc1\xca\xe6MH\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb6fu\x14.1\x11\xa1\xc2\xea\x1e\xb2A\x9c\xfaB\xaa\xf7\xa24\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xb6o\x92\x12K^c\x03XY\xe3\x90b\x88i\xdb\u07a9H^\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\xb6rsJ\xfc\xc2$\xe2\xe6\t\xfcQ\xd4\xf0Ys'D\xc9H\x89\x10\x04\xe2\xe4_\xb7\xee\x00\x00\xe0\x94\xb6w\x1b\v\xf3B\u007f\x9a\xe7\xa9>|.a\xeec\x94\x1f\xdb\b\x8a\x03\xfb&i)T\xbf\xc0\x00\x00\u07d4\xb6z\x80\xf1p\x19}\x96\xcd\xccJ\xb6\u02e6'\xb4\xaf\xa6\xe1,\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4\xb6\x88\x99\xe7a\rL\x93\xa255\xbc\xc4H\x94[\xa1fo\x1c\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb6\xa8)3\xc9\xea\u06bd\x98\x1e]m`\xa6\x81\x8f\xf8\x06\xe3k\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\xb6\xaa\u02cc\xb3\v\xab*\xe4\xa2BF&\xe6\xe1+\x02\xd0F\x05\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xb6\xb3J&?\x10\xc3\xd2\xec\xeb\n\xccU\x9a{*\xb8\\\xe5e\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb6\xbf\xe1\xc3\xef\x94\xe1\x84o\xb9\xe3\xac\xfe\x9bP\xc3\xe9\x06\x923\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xb6\xcdt2\xd5\x16\x1b\xe7\x97h\xadE\xde>Dz\a\x98 c\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb6\xceM\xc5`\xfcs\xdci\xfbzb\xe3\x88\xdb~r\xeavO\x894]\xf1i\xe9\xa3X\x00\x00\u07d4\xb6\xde\u03c2\x96\x98\x19\xba\x02\xde)\xb9\xb5\x93\xf2\x1bd\xee\xda\x0f\x89(\x1d\x90\x1fO\xdd\x10\x00\x00\xe0\x94\xb6\xe6\xc3\"+ko\x9b\xe2\x87]*\x89\xf1'\xfbd\x10\x0f\xe2\x8a\x01\xb2\x1dS#\xcc0 \x00\x00\u07d4\xb6\xe8\xaf\xd9=\xfa\x9a\xf2\u007f9\xb4\xdf\x06\ag\x10\xbe\xe3\u07eb\x89\x01Z\xf1\u05cbX\xc4\x00\x00\xe0\x94\xb6\xf7\x8d\xa4\xf4\xd0A\xb3\xbc\x14\xbc[\xa5\x19\xa5\xba\f2\xf1(\x8a$}\xd3,?\xe1\x95\x04\x80\x00\xe0\x94\xb6\xfb9xbP\b\x14&\xa3B\xc7\rG\xeeR\x1e[\xc5c\x8a\x03-&\xd1.\x98\v`\x00\x00\u07d4\xb7\r\xba\x93\x91h+J6Nw\xfe\x99%c\x01\xa6\xc0\xbf\x1f\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb7\x16#\xf3Q\a\xcft1\xa8?\xb3\xd2\x04\xb2\x9e\u0c67\xf4\x89\x01\x11du\x9f\xfb2\x00\x00\u07d4\xb7\x1a\x13\xba\x8e\x95\x16{\x803\x1bR\u059e7\x05O\xe7\xa8&\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb7\x1bb\xf4\xb4H\xc0+\x12\x01\xcb^9J\xe6'\xb0\xa5`\xee\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xb7\" \xad\xe3d\xd06\x9f--\xa7\x83\xcaGM{\x9b4\u0389\x1b\x1a\xb3\x19\xf5\xecu\x00\x00\xe0\x94\xb7#\r\x1d\x1f\xf2\xac\xa3f\x969\x14\xa7\x9d\xf9\xf7\xc5\xea,\x98\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94\xb7$\n\U000af433<\b\xae\x97d\x10>5\xdc\xe3c\x84(\x8a\x01\xca\xdd/\xe9hnc\x80\x00\u07d4\xb7'\xa9\xfc\x82\xe1\xcf\xfc\\\x17_\xa1HZ\x9b\xef\xa2\u037d\u04496'\xe8\xf7\x127<\x00\x00\u07d4\xb7,*\x01\x1c\r\xf5\x0f\xbbn(\xb2\n\xe1\xaa\xd2\x17\x88g\x90\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb78-7\xdb\x03\x98\xacrA\f\xf9\x81=\xe9\xf8\xe1\uc36d\x8966\xc2^f\xec\xe7\x00\x00\u07d4\xb7;O\xf9\x9e\xb8\x8f\u061b\vmW\xa9\xbc3\x8e\x88o\xa0j\x89\x01\xbc\x16\xd6t\xec\x80\x00\x00\u07d4\xb7=jwU\x9c\x86\xcfet$)\x039K\xac\xf9n5p\x89\x04\xf1\xa7|\xcd;\xa0\x00\x00\u07d4\xb7Cr\xdb\xfa\x18\x1d\xc9$/9\xbf\x1d71\xdf\xfe+\xda\u03c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xb7G\x9d\xabP\"\xc4\xd5\u06ea\xf8\xde\x17\x1bN\x95\x1d\u0464W\x89\x04V9\x18$O@\x00\x00\u07d4\xb7I\xb5N\x04\u0571\x9b\xdc\xed\xfb\x84\xdaw\x01\xabG\x8c'\xae\x89\x91Hx\xa8\xc0^\xe0\x00\x00\u07d4\xb7N\xd2f`\x01\xc1c3\xcfz\xf5\x9eJ=H`6;\x9c\x89\n~\xbd^Cc\xa0\x00\x00\u07d4\xb7QI\xe1\x85\xf6\xe3\x92pWs\x90s\xa1\x82*\xe1\xcf\r\xf2\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\xb7S\xa7_\x9e\xd1\v!d:\n=\xc0Qz\xc9k\x1a@h\x89\x15\xc8\x18[,\x1f\xf4\x00\x00\xe0\x94\xb7V\xadR\xf3\xbft\xa7\xd2LgG\x1e\b\x87Ci6PL\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xb7Wn\x9d1M\xf4\x1e\xc5Pd\x94):\xfb\x1b\xd5\xd3\xf6]\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb7X\x89o\x1b\xaa\x86O\x17\xeb\xed\x16\xd9S\x88o\xeeh\xaa\xe6\x8965\u026d\xc5\u07a0\x00\x00\u0794\xb7h\xb5#N\xba:\x99h\xb3Mm\xdbH\x1c\x84\x19\xb3e]\x88\xcf\xceU\xaa\x12\xb3\x00\x00\u07d4\xb7\x82\xbf\xd1\xe2\xdep\xf4gdo\x9b\xc0\x9e\xa5\xb1\xfc\xf4P\xaf\x89\x0e~\xeb\xa3A\vt\x00\x00\xe0\x94\xb7\xa2\xc1\x03r\x8bs\x05\xb5\xaen\x96\x1c\x94\xee\x99\xc9\xfe\x8e+\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\xb7\xa3\x1a|8\xf3\xdb\t2.\xae\x11\xd2'!A\xea\"\x99\x02\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb7\xa6y\x1c\x16\xebN!b\xf1Ke7\xa0+=c\xbf\xc6\x02\x89*Rc\x91\xac\x93v\x00\x00\u07d4\xb7\xa7\xf7|4\x8f\x92\xa9\xf1\x10\fk\xd8)\xa8\xacm\u007f\u03d1\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xb7\xc0w\x94ft\xba\x93A\xfbLtz]P\xf5\xd2\xdad\x15\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb7\xc0\xd0\xcc\vM4-@b\xba\xc6$\xcc\xc3\xc7\f\xc6\xda?\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb7\xc9\xf1+\x03\x8esCm\x17\xe1\xc1/\xfe\x1a\xec\u0373\xf5\x8c\x89\x1dF\x01b\xf5\x16\xf0\x00\x00\u07d4\xb7\xcck\x1a\xcc2\u0632\x95\xdfh\xed\x9d^`\xb8\xf6L\xb6{\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xb7\xcehK\t\xab\xdaS8\x9a\x87Si\xf7\x19X\xae\xac;\u0749lk\x93[\x8b\xbd@\x00\x00\u07d4\xb7\xd1.\x84\xa2\xe4\u01264Z\xf1\xdd\x1d\xa9\xf2PJ*\x99n\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb7\xd2R\xee\x94\x02\xb0\xee\xf1D)_\x0ei\xf0\xdbXl\bq\x89#\xc7W\a+\x8d\xd0\x00\x00\xe0\x94\xb7\u0541\xfe\n\xf1\xec8?;\xce\x00\xaf\x91\x99\xf3\xcf_\xe0\xcc\xe2\x8c\xd1J\x89\xcf\x15&@\xc5\xc80\x00\x00\u07d4\xb8R\x18\xf3B\xf8\x01.\u069f'Nc\xce!R\xb2\xdc\xfd\xab\x89\xa8\r$g~\xfe\xf0\x00\x00\u07d4\xb8UP\x10wn<\\\xb3\x11\xa5\xad\xee\xfe\x9e\x92\xbb\x9ad\xb9\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xb8_&\xdd\x0er\xd9\u009e\xba\xf6\x97\xa8\xafwG,+X\xb5\x8a\x02\x85\x19\xac\xc7\x19\fp\x00\x00\u07d4\xb8_\xf0>{_\xc4\"\x98\x1f\xae^\x99A\xda\xcb\u06bau\x84\x89Hz\x9a0E9D\x00\x00\xe0\x94\xb8f\a\x02\x1bb\xd3@\xcf&R\xf3\xf9_\xd2\xdcgi\x8b\u07ca\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xb8}\xe1\xbc\u0492i\xd5!\xb8v\x1c\u00dc\xfbC\x19\xd2\xea\u054965\u026d\xc5\u07a0\x00\x00\u07d4\xb8\u007fSv\xc2\xde\vl\xc3\xc1y\xc0`\x87\xaaG=kFt\x89Hz\x9a0E9D\x00\x00\u07d4\xb8\x84\xad\u060d\x83\xdcVJ\xb8\xe0\xe0,\xbd\xb69\x19\xae\xa8D\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb8\x8a7\xc2\u007fx\xa6\x17\xd5\xc0\x91\xb7\u0577:7a\xe6_*\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb8\x94x\"\u056c\u79ad\x83&\xe9T\x96\"\x1e\v\xe6\xb7=\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb8\x9c\x03n\xd7\u0112\x87\x99!\xbeA\xe1\f\xa1i\x81\x98\xa7L\x89b\xa9\x92\xe5:\n\xf0\x00\x00\xe0\x94\xb8\x9fF2\xdfY\t\xe5\x8b*\x99d\xf7O\xeb\x9a;\x01\xe0\u014a\x04\x88u\xbc\xc6\xe7\xcb\xeb\x80\x00\u07d4\xb8\xa7\x9c\x84\x94^G\xa9\xc3C\x86\x83\u05b5\x84,\xffv\x84\xb1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb8\xa9y5'Y\xba\t\xe3Z\xa5\x93]\xf1u\xbf\xf6x\xa1\b\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb8\xab9\x80[\xd8!\x18Ol\xbd=$s4{\x12\xbf\x17\\\x89\x06hZ\xc1\xbf\xe3,\x00\x00\xe0\x94\xb8\xac\x11}\x9f\r\xba\x80\x90\x14E\x82:\x92\x11\x03\xa51o\x85Zew\x9d\x1b\x8a\x05\x15\n\xe8J\x8c\xdf\x00\x00\x00\u07d4\xb9\xe9\f\x11\x92\xb3\xd5\xd3\xe3\xab\a\x00\xf1\xbfe_]\xd44z\x89\x1b\x19\xe5\vD\x97|\x00\x00\u07d4\xb9\xfd83\xe8\x8e|\xf1\xfa\x98y\xbd\xf5Z\xf4\xb9\x9c\xd5\xce?\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xba\x02I\xe0\x1d\x94[\xef\x93\xee^\xc6\x19%\xe0<\\\xa5\t\xfd\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xba\x0f9\x02;\xdb)\xeb\x18b\xa9\xf9\x05\x9c\xab]0nf/\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xba\x10\xf2vB\x90\xf8uCCr\xf7\x9d\xbfq8\x01\u02ac\x01\x893\xc5I\x901r\f\x00\x00\u07d4\xba\x151\xfb\x9ey\x18\x96\xbc\xf3\xa8\x05X\xa3Y\xf6\xe7\xc1D\xbd\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\xba\x17m\xbe2I\xe3E\xcdO\xa9g\xc0\xed\x13\xb2LG\u5189\x15\xae\xf9\xf1\xc3\x1c\u007f\x00\x00\xe0\x94\xba\x1f\x0e\x03\u02da\xa0!\xf4\xdc\xeb\xfa\x94\xe5\u0209\xc9\u01fc\x9e\x8a\x06\u0450\xc4u\x16\x9a \x00\x00\u07d4\xba\x1f\xca\xf2#\x93~\xf8\x9e\x85gU\x03\xbd\xb7\xcaj\x92\x8bx\x89\"\xb1\xc8\xc1\"z\x00\x00\x00\xe0\x94\xba$\xfcCgS\xa79\xdb,\x8d@\xe6\xd4\xd0LR\x8e\x86\xfa\x8a\x02\xc0\xbb=\xd3\fN \x00\x00\u07d4\xbaB\xf9\xaa\xceL\x18E\x04\xab\xf5BWb\xac\xa2oq\xfb\u0709\x02\a\a}\u0627\x9c\x00\x00\u07d4\xbaF\x9a\xa5\u00c6\xb1\x92\x95\u0521\xb5G;T\x03S9\f\x85\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbad@\xae\xb3s{\x8e\xf0\xf1\xaf\x9b\f\x15\xf4\xc2\x14\xff\xc7\u03c965\u026d\xc5\u07a0\x00\x00\xe0\x94\xbam1\xb9\xa2a\xd6@\xb5\u07a5\x1e\xf2\x16,1\t\xf1\uba0a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xbap\xe8\xb4u\x9c\f<\x82\xcc\x00\xacN\x9a\x94\xdd[\xaf\xb2\xb8\x890C\xfa3\xc4\x12\xd7\x00\x00\u07d4\xba\x8ac\xf3\xf4\r\u4a03\x88\xbcP!/\xea\x8e\x06O\xbb\x86\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xba\x8eF\u059d.#C\xd8l`\xd8,\xf4, A\xa0\xc1\u0089\x05k\xc7^-c\x10\x00\x00\u07d4\xba\xa4\xb6L+\x15\xb7\x9f_ BF\xfdp\xbc\xbd\x86\xe4\xa9*\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xba\u0212,J\xcc},\xb6\xfdY\xa1N\xb4\\\xf3\xe7\x02!K\x89+^:\xf1k\x18\x80\x00\x00\u07d4\xba\xd25\xd5\b]\u01f0h\xa6|A&w\xb0>\x186\x88L\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xba\xd4B^\x17\x1c>r\x97^\xb4j\xc0\xa0\x15\xdb1Z]\x8f\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xba\xdc*\xef\x9fYQ\xa8\u05cak5\xc3\u0433\xa4\xe6\xe2\xe79\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xba\xdeCY\x9e\x02\xf8OL0\x14W\x1c\x97k\x13\xa3le\xab\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xba\xe9\xb8/r\x99c\x14\be\x9d\xd7N\x89\x1c\xb8\xf3\x86\x0f\xe5\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94\xbb\x03f\xa7\u03fd4E\xa7\r\xb7\xfeZ\xe3H\x85uO\xd4h\x8a\x01M\xef,B\xeb\xd6@\x00\x00\u07d4\xbb\aj\xac\x92 \x80i\xea1\x8a1\xff\x8e\xeb\x14\xb7\xe9\x96\xe3\x89\b\x13\xcaV\x90m4\x00\x00\u07d4\xbb\bW\xf1\xc9\x11\xb2K\x86\u0227\x06\x81G?\u6aa1\xcc\xe2\x89\x05k\xc7^-c\x10\x00\x00\u0794\xbb\x19\xbf\x91\u02edt\xcc\xeb_\x81\x1d\xb2~A\x1b\xc2\xea\x06V\x88\xf4?\xc2\xc0N\xe0\x00\x00\xe0\x94\xbb'\u01a7\xf9\x10uGZ\xb2)a\x90@\xf8\x04\xc8\xeczj\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xbb7\x1cr\xc9\xf01l\xea+\xd9\xc6\xfb\xb4\a\x9ewT)\xef\x89_h\xe8\x13\x1e\u03c0\x00\x00\xe0\x94\xbb;\x01\v\x18\xe6\xe2\xbe\x115\x87\x10&\xb7\xba\x15\xea\x0f\xde$\x8a\x02 |\x800\x9bwp\x00\x00\xe0\x94\xbb;\x90\x05\xf4o\xd2\xca;0\x16%\x99\x92\x8cw\xd9\xf6\xb6\x01\x8a\x01\xb1\xae\u007f+\x1b\xf7\xdb\x00\x00\u07d4\xbb?\xc0\xa2\x9c\x03Mq\b\x12\xdc\xc7u\xc8\u02b9\u048diu\x899\xd4\xe8D\xd1\xcf_\x00\x00\u07d4\xbbH\xea\xf5\x16\xce-\xec>A\xfe\xb4\xc6y\xe4\x95vA\x16O\x89\xcf\x15&@\xc5\xc80\x00\x00\u07d4\xbbKJKT\x80p\xffAC,\x9e\b\xa0\xcao\xa7\xbc\x9fv\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\u07d4\xbbV\xa4\x04r<\xff \xd0hT\x88\xb0Z\x02\xcd\xc3Z\xac\xaa\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xbba\x8e%\"\x1a\u0667@\xb2\x99\xed\x14\x06\xbc94\xb0\xb1m\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbba\xa0K\xff\xd5|\x10G\rE\u00d1\x03\xf6FP4v\x16\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbbh#\xa1\xbd\x81\x9f\x13QU8&J-\xe0R\xb4D\"\b\x89\x01ch\xffO\xf9\xc1\x00\x00\u07d4\xbbl(J\xac\x8ai\xb7\\\u0770\x0f(\xe1EX;V\xbe\u0389lk\x93[\x8b\xbd@\x00\x00\u07d4\xbbu\xcbPQ\xa0\xb0\x94KFs\xcau*\x97\x03\u007f|\x8c\x15\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbb\x99;\x96\xee\x92Z\xda}\x99\u05c6W=?\x89\x18\f\u3a89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbb\xa3\u0180\x04$\x8eH\x95s\xab\xb2t6w\x06k$\u0227\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbb\xa4\xfa\xc3\xc4 9\xd8(\xe7B\xcd\xe0\xef\xff\xe7t\x94\x1b9\x89lj\u04c2\xd4\xfba\x00\x00\u07d4\xbb\xa8\xab\"\xd2\xfe\xdb\xcf\xc6?hL\b\xaf\xdf\x1c\x17P\x90\xb5\x89\x05_)\xf3~N;\x80\x00\u07d4\xbb\xa9v\xf1\xa1!_u\x12\x87\x18\x92\xd4_pH\xac\xd3V\u0209lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xbb\xab\x00\v\x04\b\xed\x01Z7\xc0GG\xbcF\x1a\xb1N\x15\x1b\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xbb\xab\xf6d;\xebK\xd0\x1c\x12\v\xd0Y\x8a\t\x87\xd8)g\u0449\xb52\x81x\xad\x0f*\x00\x00\u07d4\xbb\xb4\xee\x1d\x82\xf2\xe1VD,\xc938\xa2\xfc(o\xa2\x88d\x89JD\x91\xbdm\xcd(\x00\x00\u07d4\xbb\xb5\xa0\xf4\x80,\x86H\x00\x9e\x8ai\x98\xaf5,\u0787TO\x89\x05-T(\x04\xf1\xce\x00\x00\u07d4\xbb\xb6C\xd2\x18{6J\xfc\x10\xa6\xfd6\x8d}U\xf5\r\x1a<\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbb\xb8\xff\xe4?\x98\u078e\xae\x18F#\xaeRd\xe4$\u0438\u05c9\x05\xd5?\xfd\xe9(\b\x00\x00\u07d4\xbb\xbdn\u02f5u(\x91\xb4\u03b3\xcc\xe7:\x8fGpY7o\x89\x01\xf3\x99\xb1C\x8a\x10\x00\x00\u07d4\xbb\xbf9\xb1\xb6y\x95\xa4\"APO\x97\x03\u04a1JQV\x96\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xbb\xc8\xea\xffc~\x94\xfc\u014d\x91\xdb\\\x89\x12\x1d\x06\xe1/\xff\x98\x80\x00\u07d4\xbc\u065e\xdc!`\xf2\x10\xa0^:\x1f\xa0\xb0CL\xed\x00C\x9b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbc\u07ec\xb9\xd9\x02<4\x17\x18.\x91\x00\xe8\xea\x1d73\x93\xa3\x89\x034-`\xdf\xf1\x96\x00\x00\u07d4\xbc\xe1>\"2*\u03f3U\xcd!\xfd\r\xf6\f\xf9:\xdd&\u0189\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xbc\xe4\x04u\xd3E\xb0q-\xeep=\x87\xcdvW\xfc\u007f;b\x8a\x01\xa4 \xdb\x02\xbd}X\x00\x00\u07d4\xbc\xed\xc4&|\u02c9\xb3\x1b\xb7d\xd7!\x11q\x00\x8d\x94\xd4M\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbc\xfc\x98\xe5\xc8+j\xdb\x18\n?\xcb\x12\v\x9av\x90\xc8j?\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xbd\x04;g\xc6>`\xf8A\xcc\xca\x15\xb1)\xcd\xfee\x90\xc8\xe3\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbd\x04\u007f\xf1\xe6\x9c\u01b2\x9a\xd2d\x97\xa9\xa6\xf2z\x90?\xc4\u0749.\xe4IU\b\x98\xe4\x00\x00\u07d4\xbd\b\xe0\xcd\xde\xc0\x97\xdby\x01\ua05a=\x1f\xd9\u0789Q\xa2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xbd\t\x12l\x89\x1cJ\x83\x06\x80Y\xfe\x0e\x15ylFa\xa9\xf4\x89+^:\xf1k\x18\x80\x00\x00\u07d4\xbd\f\\\u05d9\xeb\u0106B\xef\x97\xd7N\x8eB\x90d\xfe\u4489\x11\xac(\xa8\xc7)X\x00\x00\u07d4\xbd\x17\xee\xd8+\x9a%\x92\x01\x9a\x1b\x1b<\x0f\xba\xd4\\@\x8d\"\x89\r\x8drkqw\xa8\x00\x00\u07d4\xbd\x18\x037\v\u0771)\xd29\xfd\x16\xea\x85&\xa6\x18\x8a\u5389\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xbd+p\xfe\xcc7d\x0fiQO\xc7\xf3@IF\xaa\xd8k\x11\x89A\rXj \xa4\xc0\x00\x00\u07d4\xbd0\x97\xa7\x9b<\r.\xbf\xf0\xe6\xe8j\xb0\xed\xad\xbe\xd4p\x96\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4\xbd2]@)\xe0\xd8r\x9fm9\x9cG\x82$\xae\x9ez\xe4\x1e\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4\xbdC*9\x16$\x9bG$):\xf9\x14nI\xb8(\n\u007f*\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xbdG\xf5\xf7n;\x93\x0f\xd9HR\t\xef\xa0\xd4v=\xa0uh\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbdK`\xfa\xect\n!\xe3\a\x13\x91\xf9j\xa54\xf7\xc1\xf4N\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\xbdK\u0571\"\xd8\xef{|\x8f\x06gE\x03 \xdb!\x16\x14.\x89 \x86\xac5\x10R`\x00\x00\u07d4\xbdQ\xee.\xa1C\u05f1\u05b7~~D\xbd\xd7\xda\x12\U00105b09G~\x06\u0332\xb9(\x00\x00\u07d4\xbdY\tN\aO\x8dy\x14*\xb1H\x9f\x14\x8e2\x15\x1f \x89\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xbdZ\x8c\x94\xbd\x8b\xe6G\x06D\xf7\f\x8f\x8a3\xa8\xa5\\cA\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbd^G:\xbc\xe8\xf9zi2\xf7|/\xac\xaf\x9c\xc0\xa0\x05\x14\x89<\x92X\xa1\x06\xa6\xb7\x00\x00\u07d4\xbd_F\u02ab,=K(\x93\x96\xbb\xb0\u007f *\x06\x11>\xd4\xc3\xfb\xa1\xa8\x91;\x19@~\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbd\x9eV\xe9\x02\xf4\xbe\x1f\xc8v\x8d\x808\xba\xc6>*\u02ff\x8e\x8965f3\xeb\xd8\xea\x00\x00\u07d4\xbd\xa4\xbe1~~K\xed\x84\xc0I^\xee2\xd6\a\xec8\xcaR\x89}2'yx\xefN\x80\x00\u07d4\xbd\xb6\v\x82:\x11s\xd4Z\a\x92$_\xb4\x96\xf1\xfd3\x01\u03c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xbd\xba\xf6CM@\xd65[\x1e\x80\xe4\f\u012b\x9ch\xd9a\x16\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xbd\xc0,\xd43\f\x93\xd6\xfb\xdaOm\xb2\xa8]\xf2/C\xc23\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbd\xc4aF+c\"\xb4b\xbd\xb3?\"y\x9e\x81\b\xe2A}\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\xbd\xc79\xa6\x99p\v.\x8e,JL{\x05\x8a\x0eQ=\u07be\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbd\xc7Hs\xaf\x92+\x9d\xf4t\x85;\x0f\xa7\xff\v\xf8\xc8&\x95\x89\xd8\xc9F\x00c\xd3\x1c\x00\x00\u07d4\xbd\xca*\x0f\xf3E\x88\xafb_\xa8\xe2\x8f\xc3\x01Z\xb5\xa3\xaa\x00\x89~\xd7?w5R\xfc\x00\x00\u07d4\xbd\xd3%N\x1b:m\xc6\xcc,i}Eq\x1a\xca!\xd5\x16\xb2\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xbd\u07e3M\x0e\xbf\x1b\x04\xafS\xb9\x9b\x82IJ\x9e=\x8a\xa1\x00\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xbd\xe4\xc7?\x96\x9b\x89\xe9\u03aef\xa2\xb5\x18DH\x0e\x03\x8e\x9a\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xbd\xe9xj\x84\xe7[H\xf1\x8erm\u05cdp\xe4\xaf>\xd8\x02\x8a\x016\x9f\xb9a(\xacH\x00\x00\u07d4\xbd\xed\x11a/\xb5\xc6\u0699\xd1\xe3\x0e2\v\xc0\x99Tf\x14\x1e\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xbd\xed~\a\xd0q\x1ehM\xe6Z\u0232\xabW\xc5\\\x1a\x86E\x89 \t\xc5\u023fo\xdc\x00\x00\u07d4\xbd\xf6\x93\xf83\xc3\xfeG\x17S\x18G\x88\xebK\xfeJ\xdc?\x96\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xbd\xf6\xe6\x8c\f\xd7X@\x80\xe8G\xd7,\xbb#\xaa\xd4j\xeb\x1d\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xbe\n/8_\t\xdb\xfc\xe9g2\xe1+\xb4\n\xc3I\x87\x1b\xa8\x89WL\x11^\x02\xb8\xbe\x00\x00\u07d4\xbe\f*\x80\xb9\xde\bK\x17(\x94\xa7l\xf4szOR\x9e\x1a\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xbe\x1c\xd7\xf4\xc4r\a\th\xf3\xbd\xe2h6k!\xee\xea\x83!\x89\xe9\x1a|\u045f\xa3\xb0\x00\x00\u07d4\xbe#F\xa2\u007f\xf9\xb7\x02\x04OP\r\xef\xf2\xe7\xff\xe6\x82EA\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xbe$q\xa6\u007f`G\x91\x87r\xd0\xe3h9%^\xd9\u0591\xae\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xbe+\"\x80R7h\xea\x8a\xc3\\\xd9\xe8\x88\xd6\nq\x93\x00\u0509lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xbe+2nx\xed\x10\xe5P\xfe\xe8\xef\xa8\xf8\a\x03\x96R/Z\x8a\bW\xe0\xd6\xf1\xdav\xa0\x00\x00\xe0\x94\xbe0Zyn3\xbb\xf7\xf9\xae\xaee\x12\x95\x90f\xef\xda\x10\x10\x8a\x02M\xceT\xd3J\x1a\x00\x00\x00\u07d4\xbeG\x8e\x8e=\xdek\xd4\x03\xbb-\x1ce|C\x10\xee\x19'#\x89\x1a\xb2\xcf|\x9f\x87\xe2\x00\x00\u07d4\xbeN}\x98?.*ck\x11\x02\xecp9\xef\xeb\xc8B\u9349\x03\x93\xef\x1aQ'\xc8\x00\x00\u07d4\xbeO\xd0sap\"\xb6\u007f\\\x13I\x9b\x82\u007fv69\xe4\xe3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbeRZ3\xea\x91aw\xf1r\x83\xfc\xa2\x9e\x8b5\v\u007fS\v\x89\x8f\x01\x9a\xafF\xe8x\x00\x00\u07d4\xbeS2/C\xfb\xb5\x84\x94\xd7\xcc\xe1\x9d\xda'+$P\xe8'\x89\n\xd7\u03afB\\\x15\x00\x00\u07d4\xbeS\x82F\xddNo\f \xbfZ\xd17<;F:\x13\x1e\x86\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbeZ`h\x99\x98c\x9a\xd7[\xc1\x05\xa3qt>\xef\x0fy@\x89\x1b2|s\xe1%z\x00\x00\u07d4\xbe\\\xba\x8d7By\x86\xe8\xca&\x00\xe8X\xbb\x03\xc3YR\x0f\x89\xa00\xdc\xeb\xbd/L\x00\x00\u07d4\xbe`\x03~\x90qJK\x91~a\xf1\x93\xd84\x90g\x03\xb1:\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xbec:77\xf6\x849\xba\xc7\xc9\nR\x14 X\ue38ao\x894\n\xad!\xb3\xb7\x00\x00\x00\xe0\x94\xbee\x9d\x85\xe7\xc3O\x883\xea\u007fH\x8d\xe1\xfb\xb5\xd4\x14\x9b\xef\x8a\x01\xeb\xd2:\xd9\u057br\x00\x00\u07d4\xbes'M\x8cZ\xa4J<\xbe\xfc\x82c\xc3{\xa1!\xb2\n\u04c9\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xbe\x86\u0430C\x84\x19\u03b1\xa081\x927\xbaR\x06\xd7.F\x8964\xfb\x9f\x14\x89\xa7\x00\x00\u07d4\xbe\x8d\u007f\x18\xad\xfe]l\xc7u9I\x89\xe1\x93\f\x97\x9d\x00}\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbe\x91\x86\xc3JRQJ\xbb\x91\a\x86\x0fgO\x97\xb8!\xbd[\x89\x1b\xa0\x1e\xe4\x06\x03\x10\x00\x00\u07d4\xbe\x93W\x93\xf4[p\xd8\x04]&T\xd8\xdd:\xd2K[a7\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4\xbe\x98\xa7\u007f\xd4\x10\x97\xb3OY\xd7X\x9b\xaa\xd0!e\x9f\xf7\x12\x890\xca\x02O\x98{\x90\x00\x00\u07d4\xbe\x9b\x8c4\xb7\x8e\xe9G\xff\x81G.\xdaz\xf9\xd2\x04\xbc\x84f\x89\b!\xab\rD\x14\x98\x00\x00\u07d4\xbe\xa0\r\xf1pg\xa4:\x82\xbc\x1d\xae\xca\xfbl\x140\x0e\x89\xe6\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xbe\xa0\xaf\xc9:\xae!\b\xa3\xfa\xc0Yb;\xf8o\xa5\x82\xa7^\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xbe\xb35\x8cP\u03dfu\xff\xc7mD<,\u007fU\aZ\x05\x89\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4\xbe\xb4\xfd1UYC`E\u0739\x9dI\xdc\xec\x03\xf4\fB\u0709lk\x93[\x8b\xbd@\x00\x00\u07d4\xbe\xc2\xe6\xde9\xc0|+\xaeUj\u03fe\xe2\xc4r\x8b\x99\x82\xe3\x89\x1f\x0f\xf8\xf0\x1d\xaa\xd4\x00\x00\u07d4\xbe\xc6d\x0fI\t\xb5\x8c\xbf\x1e\x80cB\x96\x1d`u\x95\tl\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xbe\xc8\xca\xf7\xeeIF\x8f\xeeU.\xff:\xc5#N\xb9\xb1}B\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbe\xce\xf6\x1c\x1cD+\xef|\xe0Ks\xad\xb2I\xa8\xba\x04~\x00\x896;V\u00e7T\xc8\x00\x00\u0794\xbe\xd4d\x9d\xf6F\u2052)\x03-\x88hUo\xe1\xe0S\u04c8\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94\xbe\xd4\xc8\xf0\x06\xa2|\x1e_|\xe2\x05\xdeu\xf5\x16\xbf\xb9\xf7d\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\xbe\xe8\u0430\bB\x19T\xf9-\x00\r9\x0f\xb8\xf8\xe6X\xea\xee\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbe\xec\u05af\x90\f\x8b\x06J\xfc\xc6\a?-\x85\u055a\xf1\x19V\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xbe\xef\x94!8y\xe0&\"\x14+\xeaa)\tx\x93\x9a`\u05ca\x016\x85{2\xad\x86\x04\x80\x00\xe0\x94\xbe\xf0}\x97\xc3H\x1f\x9dj\xee\x1c\x98\xf9\xd9\x1a\x18\n2D+\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xbe\xfbD\x8c\f_h?\xb6~\xe5p\xba\xf0\xdbV\x86Y\x97Q\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xbf\x05\a\f,4!\x93\x11\xc4T\x8b&\x14\xa48\x81\r\xedm\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbf\x05\xff^\xcf\r\xf2\u07c8wY\xfb\x82t\xd928\xac&}\x89+^:\xf1k\x18\x80\x00\x00\xe0\x94\xbf\t\xd7pH\xe2p\xb6b3\x0e\x94\x86\xb3\x8bC\xcdx\x14\x95\x8a\\S\x9b{\xf4\xff(\x80\x00\x00\u07d4\xbf\x17\xf3\x97\xf8\xf4o\x1b\xaeE\u0447\x14\x8c\x06\xee\xb9Y\xfaM\x896I\u0156$\xbb0\x00\x00\u07d4\xbf\x186A\xed\xb8\x86\xce`\xb8\x19\x02a\xe1OB\xd9<\xce\x01\x89\x01[5W\xf1\x93\u007f\x80\x00\u07d4\xbf*\xeaZ\x1d\xcfn\u04f5\xe829D\xe9\x83\xfe\xdf\u046c\xfb\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xbf@\x96\xbcT}\xbf\xc4\xe7H\t\xa3\x1c\x03\x9e{8\x9d^\x17\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\xbfI\xc1H\x981eg\u0637\t\xc2\xe5\x05\x94\xb3f\xc6\u04cc\x89'\xbf8\xc6TM\xf5\x00\x00\u07d4\xbfLs\xa7\xed\xe7\xb1d\xfe\a!\x14\x846T\xe4\xd8x\x1d\u0789lk\x93[\x8b\xbd@\x00\x00\u07d4\xbfP\xce.&K\x9f\xe2\xb0h0az\xed\xf5\x02\xb25\x1bE\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbfY\xae\xe2\x81\xfaC\xfe\x97\x19CQ\xa9\x85~\x01\xa3\xb8\x97\xb2\x89 \x86\xac5\x10R`\x00\x00\u07d4\xbfh\u048a\xaf\x1e\xee\xfe\xf6F\xb6^\x8c\xc8\u0450\xf6\xc6\u069c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbfi%\xc0\aQ\x00\x84@\xa6s\x9a\x02\xbf+l\u06ab^:\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbfw\x01\xfcb%\u0561x\x15C\x8a\x89A\xd2\x1e\xbc]\x05\x9d\x89e\xea=\xb7UF`\x00\x00\u07d4\xbf\x8b\x80\x05\xd66\xa4\x96d\xf7Bu\xefBC\x8a\xcde\xac\x91\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbf\x92A\x8a\fl1$M\"\x02`\xcb>\x86}\u05f4\xefI\x89\x05i\x00\xd3<\xa7\xfc\x00\x00\u07d4\xbf\x9a\xcdDE\xd9\xc9UF\x89\u02bb\xba\xb1\x88\x00\xff\x17A\u008965\u026d\xc5\u07a0\x00\x00\u07d4\xbf\x9f'\x1fz~\x12\xe3m\xd2\xfe\x9f\xac\xeb\xf3\x85\xfeaB\xbd\x89\x03f\xf8O{\xb7\x84\x00\x00\u07d4\xbf\xa8\xc8X\xdf\x10,\xb1$!\x00\x8b\n1\xc4\xc7\x19\n\xd5`\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbf\xae\xb9\x10ga}\u03cbD\x17+\x02\xafaVt\x83]\xba\x89\b\xb5\x9e\x88H\x13\b\x80\x00\xe0\x94\xbf\xb0\xea\x02\xfe\xb6\x1d\xec\x9e\"\xa5\a\tY3\x02\x99\xc40r\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xbf\xbc\xa4\x18\xd3R\x9c\xb3\x93\b\x10b\x03*n\x11\x83\u01b2\u070a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xbf\xbe\x05\u831c\xbb\xcc\x0e\x92\xa4\x05\xfa\xc1\xd8]\xe2H\xee$\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xbf\xbf\xbc\xb6V\u0099+\xe8\xfc\u0782\x19\xfb\xc5J\xad\u055f)\x8a\x02\x1e\x18\xd2\xc8!\xc7R\x00\x00\u07d4\xbf\xc5z\xa6f\xfa\u239f\x10zI\xcbP\x89\xa4\xe2!Q\u074965\u026d\xc5\u07a0\x00\x00\u07d4\xbf\u02d70$c\x04p\r\xa9\vAS\xe7\x11Ab.\x1cA\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xbf\xd9<\x90\u009c\a\xbc_\xb5\xfcI\xae\xeaU\xa4\x0e\x13O5\x8a\x05\xed\xe2\x0f\x01\xa4Y\x80\x00\x00\xe0\x94\xbf\xe3\xa1\xfcn$\xc8\xf7\xb3%\x05`\x99\x1f\x93\u02e2\u03c0G\x8a\x10\xf0\xcf\x06M\u0552\x00\x00\x00\u07d4\xbf\u6f30\xf0\xc0xRd3$\xaa]\xf5\xfdb%\xab\xc3\u0289\x04\t\xe5+H6\x9a\x00\x00\u07d4\xbf\xf5\xdfv\x994\xb8\x94<\xa9\x13}\x0e\xfe\xf2\xfen\xbb\xb3N\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xbf\xfbi)$\x1fx\x86\x93'>p\"\xe6\x0e>\xab\x1f\xe8O\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc0\x06O\x1d\x94t\xab\x91]V\x90l\x9f\xb3 \xa2\xc7\t\x8c\x9b\x89\x13h?\u007f<\x15\xd8\x00\x00\u07d4\xc0\a\xf0\xbd\xb6\xe7\x00\x92\x02\xb7\xaf>\xa9\t\x02i|r\x14\x13\x89\xa2\xa0\xe4>\u007f\xb9\x83\x00\x00\u07d4\xc0\n\xb0\x80\xb6C\xe1\u00ba\xe3c\xe0\u0455\xde.\xff\xfc\x1cD\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u0794\xc0 wD\x9a\x13Jz\xd1\xef~M\x92z\xff\xec\ueb75\xae\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xc0$q\xe3\xfc.\xa0S&\x15\xa7W\x1dI2\x89\xc1<6\xef\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc0-n\xad\xea\xcf\x1bx\xb3\u0285\x03\\c{\xb1\xce\x01\xf4\x90\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc03\xb12Z\n\xf4Tr\xc2U'\x85;\x1f\x1c!\xfa5\u0789lk\x93[\x8b\xbd@\x00\x00\u07d4\xc03\xbe\x10\xcbHa;\xd5\xeb\xcb3\xedI\x02\xf3\x8bX0\x03\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xc04[3\xf4\x9c\xe2\u007f\xe8,\xf7\xc8M\x14\x1ch\xf5\x90\xcev\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc0=\xe4*\x10\x9bezd\xe9\"$\xc0\x8d\xc1'^\x80\u0672\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc0@i\u07f1\x8b\tlxg\xf8\xbe\xe7zm\xc7Gz\xd0b\x89\x90\xf54`\x8ar\x88\x00\x00\xe0\x94\xc0A?Z|-\x9aK\x81\b(\x9e\xf6\xec\xd2qx\x15$\xf4\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\xc0C\xf2E-\u02d6\x02\xefb\xbd6\x0e\x03=\xd29q\xfe\x84\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xc0OK\xd4\x04\x9f\x04F\x85\xb8\x83\xb6)Y\xaec\x1df~5\x8a\x01;\x80\xb9\x9cQ\x85p\x00\x00\u07d4\xc0V\u053dk\xf3\u02ec\xace\xf8\xf5\xa0\xe3\x98\v\x85'@\xae\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xc0[t\x06 \xf1s\xf1nRG\x1d\u00cb\x9cQJ\v\x15&\x89\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4\xc0i\xef\x0e\xb3B\x99\xab\xd2\xe3-\xab\xc4yD\xb2r3H$\x89\x06\x81U\xa46v\xe0\x00\x00\u07d4\xc0l\xeb\xbb\xf7\xf5\x14\x9af\xf7\xeb\x97k>G\xd5e\x16\xda/\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xc0r^\u00bd\xc3:\x1d\x82`q\u07a2\x9db\xd48Z\x8c%\x8a\b\xa0\x85\x13F:\xa6\x10\x00\x00\u07d4\xc0~8g\xad\xa0\x96\x80z\x05\x1al\x9c4\xcc;?J\xd3J\x89`\xf0f \xa8IE\x00\x00\u07d4\xc0\x89^\xfd\x05m\x9a:\x81\xc3\xdaW\x8a\xda1\x1b\xfb\x93V\u03c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xc0\x90\xfe#\xdc\xd8k5\x8c2\xe4\x8d*\xf9\x10$%\x9fef\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xc0\x9af\x17*\xea7\r\x9ac\xda\x04\xffq\xff\xbb\xfc\xff\u007f\x94\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc0\x9e<\xfc\x19\xf6\x05\xff>\xc9\xc9\xc7\x0e%@\xd7\xee\x97Cf\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xc0\xa0*\xb9N\xbeV\xd0E\xb4\x1bb\x9b\x98F.:\x02J\x93\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xc0\xa3\x93\b\xa8\x0e\x9e\x84\xaa\xaf\x16\xac\x01\xe3\xb0\x1dt\xbdk-\x89\afM\xddL\x1c\v\x80\x00\u07d4\xc0\xa6\u02edwi*=\x88\xd1A\xefv\x9a\x99\xbb\x9e<\x99Q\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xc0\xa7\xe8C]\xff\x14\xc2Uws\x9d\xb5\\$\u057fW\xa3\u064a\nm\xd9\f\xaeQ\x14H\x00\x00\u07d4\xc0\xae\x14\xd7$\x83./\xce'x\xde\u007f{\x8d\xaf{\x12\xa9>\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc0\xaf\xb7\u0637\x93p\xcf\xd6c\u018c\u01b9p*7\u035e\xff\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc0\xb0\xb7\xa8\xa6\xe1\xac\xdd\x05\xe4\u007f\x94\xc0\x96\x88\xaa\x16\u01ed\x8d\x89\x03{m\x02\xacvq\x00\x00\xe0\x94\xc0\xb3\xf2D\xbc\xa7\xb7\xde[H\xa5>\u06dc\xbe\xab\vm\x88\xc0\x8a\x01;\x80\xb9\x9cQ\x85p\x00\x00\u07d4\xc0\xc0M\x01\x06\x81\x0e>\xc0\xe5J\x19\U000ab157\xe6\x9aW=\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\xc0\xca2w\x94.tE\x87K\xe3\x1c\xeb\x90)rqO\x18#\x89\r\x8drkqw\xa8\x00\x00\u07d4\xc0\u02ed<\xcd\xf6T\xda\"\xcb\xcf\\xe\x97\xca\x19U\xc1\x15\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc0\xcb\xf6\x03/\xa3\x9e|F\xffw\x8a\x94\xf7\xd4E\xfe\"\xcf0\x89\x10\xce\x1d=\x8c\xb3\x18\x00\x00\u07d4\xc0\xe0\xb9\x03\b\x8e\fc\xf5=\xd0iWTR\xaf\xf5$\x10\u00c9\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xc0\xe4W\xbdV\xec6\xa1$k\xfa20\xff\xf3\x8eY&\xef\"\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xc0\xed\rJ\xd1\r\xe045\xb1S\xa0\xfc%\xde;\x93\xf4R\x04\x89\xabM\xcf9\x9a:`\x00\x00\u07d4\xc0\xf2\x9e\xd0\af\x11\xb5\xe5^\x13\x05G\xe6\x8aH\xe2m\xf5\u4262\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xc1\x13(x#\\]\u06e5\xd9\xf3\"\x8bR6\xe4p \xdco\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc1\x17\r\xba\xad\xb3\xde\xe6\x19\x8e\xa5D\xba\xec\x93%\x18`\xfd\xa5\x89A\rXj \xa4\xc0\x00\x00\xe0\x94\xc1&W=\x87\xb0\x17ZR\x95\xf1\xdd\a\xc5u\u03cc\xfa\x15\xf2\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xc1'\xaa\xb5\x90e\xa2\x86D\xa5k\xa3\xf1^.\xac\x13\xda)\x95\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\xc1+\u007f@\u07da/{\xf9\x83f\x14\"\xab\x84\xc9\xc1\xf5\bX\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xc1,\xfb{=\xf7\x0f\xce\xca\x0e\xde&5\x00\xe2xs\xf8\xed\x16\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc1/\x88\x1f\xa1\x12\xb8\x19\x9e\xcb\xc7>\xc4\x18W\x90\xe6\x14\xa2\x0f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc18Lnq~\xbeK#\x01NQ\xf3\x1c\x9d\xf7\xe4\xe2[1\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xc1C\x8c\x99\xddQ\xef\x1c\xa88j\xf0\xa3\x17\xe9\xb0AEx\x88\x89\f\x1d\xaf\x81\u0623\xce\x00\x00\u07d4\xc1c\x12(\xef\xbf*.:@\x92\xee\x89\x00\xc69\xed4\xfb\u02093\xc5I\x901r\f\x00\x00\u07d4\xc1u\xbe1\x94\xe6iB-\x15\xfe\xe8\x1e\xb9\xf2\xc5lg\xd9\u0249\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xc1\x82v\x86\xc0\x16\x94\x85\xec\x15\xb3\xa7\xc8\xc0\x15\x17\xa2\x87M\xe1\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xc1\x8a\xb4g\xfe\xb5\xa0\xaa\xdf\xff\x91#\x0f\xf0VFMx\xd8\x00\x89lk\x93[\x8b\xbd@\x00\x00\u0794\xc1\x95\x05CUM\x8aq0\x03\xf6b\xbba,\x10\xadL\xdf!\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xc1\xa4\x1aZ'\x19\x92&\xe4\xc7\xeb\x19\x8b\x03\x1bY\x19o\x98B\x89\nZ\xa8P\t\xe3\x9c\x00\x00\u07d4\xc1\xb2\xa0\xfb\x9c\xadE\xcdi\x91\x92\xcd'T\v\x88\xd38By\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xc1\xb2\xaa\x8c\xb2\xbfb\xcd\xc1:G\xec\xc4e\u007f\xac\xaa\x99_\x98\x8967\x93\xfa\x96\u6980\x00\u07d4\xc1\xb5\x00\x01\x1c\xfb\xa9]|\xd66\xe9^l\xbfagFK%\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xc1\xb9\xa5pM5\x1c\xfe\x98?y\xab\xee\xc3\u06fb\xae;\xb6)\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc1\xcb\xd2\xe23*RL\xf2\x19\xb1\r\x87\x1c\xcc \xaf\x1f\xb0\xfa\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xc1\xcd\xc6\x01\xf8\x9c\x04(\xb3\x13\x02\u0447\xe0\xdc\b\xad}\x1cW\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xc1\u052f8\xe9\xbay\x90@\x89HI\xb8\xa8!\x93u\xf1\xacx\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xc1\xe1@\x9c\xa5,%CQ4\xd0\x06\u00a6\xa8T-\xfbrs\x89\x01\xdd\x1eK\xd8\xd1\xee\x00\x00\u07d4\xc1\xeb\xa5hJ\xa1\xb2L\xbac\x15\x02c\xb7\xa9\x13\x1a\xee\u008d\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc1\xec\x81\xdd\x12=K|-\u0674\xd48\xa7\a,\x11\u0707L\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc1\xf3\x9b\xd3]\xd9\xce\xc37\xb9oG\xc6w\x81\x81`\xdf7\xb7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u0794\xc1\xff\xad\a\u06d6\x13\x8cK*S\x0e\xc1\xc7\xde)\xb8\xa0Y,\x88\xf4?\xc2\xc0N\xe0\x00\x00\xe0\x94\xc2\x1f\xa6d:\x1f\x14\xc0)\x96\xadqD\xb7Y&\xe8~\xcbK\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xc24\nL\xa9L\x96x\xb7IL<\x85%(\xed\xe5\xeeR\x9f\x89\x02\xa3k\x05\xa3\xfd|\x80\x00\u07d4\xc29\xab\u07ee>\x9a\xf5E\u007fR\xed+\x91\xfd\n\xb4\xd9\xc7\x00\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc2;/\x92\x1c\xe4\xa3z%\x9e\u4b4b!X\xd1]fOY\x89\x01`\x89\x95\xe8\xbd?\x80\x00\u07d4\xc2C\x99\xb4\xbf\x86\xf73\x8f\xbfd^;\"\xb0\u0dd79\x12\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc2L\u03bc#D\xcc\xe5d\x17\xfbhL\xf8\x16\x13\xf0\xf4\xb9\xbd\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\xc2Rf\xc7gf2\xf1>\xf2\x9b\xe4U\ud50a\xddVw\x92\x89Hz\x9a0E9D\x00\x00\u07d4\xc2\\\xf8&U\f\x8e\xaf\x10\xaf\"4\xfe\xf9\x04\u0779R\x13\xbe\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc2f?\x81E\xdb\xfe\xc6\xc6F\xfc\\I\x96\x13E\xde\x1c\x9f\x11\x89%g\xacp9+\x88\x00\x00\u07d4\xc2pEh\x854+d\vL\xfc\x1bR\x0e\x1aTN\xe0\xd5q\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xc2sv\xf4]!\xe1^\xde;&\xf2e_\xce\xe0,\xcc\x0f*\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xc2w\x97q\xf0Smy\xa8p\x8fi1\xab\xc4K05\u964a\x047\u04ca\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xc2\xc1>r\xd2h\xe7\x15\r\u01d9\xe7\xc6\xcf\x03\u0209T\xce\u05c9%\xf2s\x93=\xb5p\x00\x00\u07d4\xc2\xcb\x1a\xda]\xa9\xa0B8s\x81G\x93\xf1aD\xef6\xb2\xf3\x89HU~;p\x17\xdf\x00\x00\u07d4\xc2\xd1w\x8e\xf6\xee_\xe4\x88\xc1E\xf3Xkn\xbb\xe3\xfb\xb4E\x89>\x1f\xf1\xe0;U\xa8\x00\x00\xe0\x94\xc2\xd9\xee\xdb\xc9\x01\x92c\xd9\xd1l\u016e\a-\x1d=\xd9\xdb\x03\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xc2\xe0XJq4\x8c\xc3\x14\xb7; )\xb6#\v\x92\u06f1\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc2\xe2\u0518\xf7\r\xcd\bY\xe5\v\x02:q\nmK!3\xbd\x8989\x11\xf0\f\xbc\xe1\x00\x00\u07d4\xc2\xed_\xfd\u046d\xd8U\xa2i/\xe0b\xb5\xd6\x18t#`\u0509A\rXj \xa4\xc0\x00\x00\u07d4\xc2\xee\x91\xd3\xefX\xc9\u0465\x89\x84N\xa1\xae1%\xd6\u017ai\x894\x95tD\xb8@\xe8\x00\x00\u07d4\xc2\xfa\xfd\xd3\n\xcbmg\x06\xe9)<\xb0&A\xf9\xed\xbe\a\xb5\x89Q\x00\x86\vC\x0fH\x00\x00\u07d4\xc2\xfd\v\xf7\xc7%\xef>\x04~Z\xe1\u009f\xe1\x8f\x12\xa7)\x9c\x89Hz\x9a0E9D\x00\x00\u07d4\xc2\xfe}us\x1fcm\xcd\t\xdb\xda\x06q9;\xa0\xc8*}\x89wC\"\x17\xe6\x83`\x00\x00\u07d4\xc3\x10z\x9a\xf32-R8\xdf\x012A\x911b\x959W}\x89\x1a\xb4\xe4d\xd4\x141\x00\x00\xe0\x94\xc3\x11\v\xe0\x1d\xc9sL\xfcn\x1c\xe0\u007f\x87\xd7}\x13E\xb7\xe1\x8a\x01\x0f\f\xe9I\xe0\x0f\x93\x00\x00\u07d4\xc3 8\xcaR\xae\xe1\x97E\xbe\\1\xfc\xdcT\x14\x8b\xb2\xc4\u0409\x02\xb5\xaa\xd7,e \x00\x00\u07d4\xc3%\xc3R\x80\x1b\xa8\x83\xb3\"l_\xeb\r\xf9\xea\xe2\xd6\xe6S\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\xc3.\xc7\xe4*\xd1l\xe3\xe2UZ\xd4\xc5C\x06\xed\xa0\xb2gX\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc32\xdfP\xb1<\x014\x90\xa5\xd7\xc7]\xbf\xa3f\u0687\xb6\u0589\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc3:\u0373\xba\x1a\xab'P{\x86\xb1]g\xfa\xf9\x1e\xcfb\x93\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xc3>\u0393Z\x8fN\xf98\xea~\x1b\xac\x87\u02d2]\x84\x90\u028a\a\x03\x8c\x16x\x1fxH\x00\x00\u07d4\xc3@\xf9\xb9\x1c&r\x8c1\xd1!\xd5\xd6\xfc;\xb5m=\x86$\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc3F\xcb\x1f\xbc\xe2\xab(]\x8eT\x01\xf4-\xd7#M7\xe8m\x89\x04\x86\u02d7\x99\x19\x1e\x00\x00\xe0\x94\xc3H=n\x88\xac\x1fJ\xe7<\xc4@\x8dl\x03\xab\xe0\xe4\x9d\u028a\x03\x99\x92d\x8a#\u0220\x00\x00\xe0\x94\xc3H\xfcZF\x13#\xb5{\xe3\x03\u02c96\x1b\x99\x19\x13\xdf(\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xc3N;\xa12.\xd0W\x11\x83\xa2O\x94 N\xe4\x9c\x18fA\x89\x03'\xaf\uf927\xbc\x00\x00\xe0\x94\xc3[\x95\xa2\xa3s|\xb8\xf0\xf5\x96\xb3E$\x87+\xd3\r\xa24\x8a\x01\x98\xbe\x85#^-P\x00\x00\xe0\x94\xc3c\x1cv\x98\xb6\xc5\x11\x19\x89\xbfE''\xb3\xf99Zm\xea\x8a\x02C'X\x96d\x1d\xbe\x00\x00\u07d4\xc3l\vc\xbf\xd7\\/\x8e\xfb\x06\b\x83\xd8h\xcc\xcdl\xbd\xb4\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94\xc3uk\xcd\xcc~\xect\xed\x89j\xdf\xc35'Y0&n\b\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\u00c4\xacn\xe2|9\xe2\xf2x\xc2 \xbd\xfa[\xae\xd6&\xd9\u04c9 \x86\xac5\x10R`\x00\x00\u07d4\u00e0F\xe3\u04b2\xbfh\x14\x88\x82n2\xd9\xc0aQ\x8c\xfe\x8c\x89\x8c\xf2?\x90\x9c\x0f\xa0\x00\x00\u07d4\u00e9\"j\xe2u\xdf,\xab1+\x91\x10@cJ\x9c\x9c\x9e\xf6\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u00f9(\xa7o\xadex\xf0O\x05U\xe69R\xcd!\xd1R\n\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xc3\xc2)s)\xa6\xfd\x99\x11~T\xfcj\xf3y\xb4\xd5VT~\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xc3\xc3\xc2Q\rg\x80 HZcs]\x13\a\xecL\xa60+\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc3\xcbk6\xafD?,n%\x8bJ9U:\x81\x87G\x81\x1f\x89WG=\x05\u06ba\xe8\x00\x00\xe0\x94\xc3\xdbVW\xbbr\xf1\rX\xf21\xfd\xdf\x11\x98\n\xffg\x86\x93\x8a\x01@a\xb9\xd7z^\x98\x00\x00\xe0\x94\xc3\u06df\xb6\xf4lH\n\xf3De\u05d7S\xb4\xe2\xb7Jg\u038a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xc3\xddX\x908\x860;\x92\x86%%z\xe1\xa0\x13\xd7\x1a\xe2\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc3\xe0G\x1cd\xff5\xfaR2\xcc1!\xd1\u04cd\x1a\x0f\xb7\u0789lk\x93[\x8b\xbd@\x00\x00\u07d4\xc3\xe2\f\x96\u07cdN8\xf5\v&Z\x98\xa9\x06\xd6\x1b\xc5\x1aq\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc3\u31f0<\xe9\\\xcf\xd7\xfaQ\u0744\x01\x83\xbcCS(\t\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xc3\xf8\xf6r\x95\xa5\xcd\x04\x93d\xd0]#P&#\xa3\xe5.\x84\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xc4\x01\xc4'\xcc\xcf\xf1\r\xec\xb8d /6\xf5\x80\x83\"\xa0\xa8\x89\xb4{Q\xa6\x9c\xd4\x02\x00\x00\u07d4\xc4\b\x8c\x02_>\x85\x01?T9\xfb4@\xa1s\x01\xe5D\xfe\x89~\t\xdbM\x9f?4\x00\x00\u07d4\xc4\x14a\xa3\u03fd2\u0246UU\xa4\x8117\xc0v1#`\x8965\xc6 G9\u0640\x00\u07d4\xc4 8\x8f\xbe\xe8J\xd6V\xddh\xcd\xc1\xfb\xaa\x93\x92x\v4\x89\n-\xcac\xaa\xf4\u0140\x00\u07d4\xc4\"P\xb0\xfeB\xe6\xb7\xdc\xd5\u0210\xa6\xf0\u020f__\xb5t\x89\b\x1e\xe4\x82SY\x84\x00\x00\u07d4\xc4-j\xebq\x0e:P\xbf\xb4Ml1\t)i\xa1\x1a\xa7\xf3\x89\b\"c\xca\xfd\x8c\xea\x00\x00\xe0\x94\xc4@\xc7\xca/\x96Kir\xeffJ\"a\xdd\xe8\x92a\x9d\x9c\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xc4K\xde\xc8\xc3l\\h\xba\xa2\xdd\xf1\xd41i2)rlC\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xc4OJ\xb5\xbc`9|s~\xb0h3\x91\xb63\xf8\xa2G\x1b\x12\x1c\xa4\x89 .h\xf2\u00ae\xe4\x00\x00\u07d4\xc4h\x1es\xbb\x0e2\xf6\xb7& H1\xffi\xba\xa4\x87~2\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xc4k\xbd\xefv\xd4\xca`\xd3\x16\xc0\u007f]\x1ax\x0e;\x16_~\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc4}a\v9\x92P\xf7\x0e\xcf\x13\x89\xba\xb6),\x91&O#\x89\x0f\xa7\xe7\xb5\xdf<\xd0\x00\x00\u07d4\u0100;\xb4\a\xc7b\xf9\vu\x96\xe6\xfd\u1513\x1ev\x95\x90\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u0106Q\xc1\xd9\xc1k\xffL\x95T\x88l??&C\x1foh\x89#\xab\x95\x99\xc4?\b\x00\x00\u07d4\u0109\xc8?\xfb\xb0%*\xc0\xdb\xe3R\x12\x17c\x0e\x0fI\x1f\x14\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u010bi<\xac\xef\xdb\xd6\xcb]x\x95\xa4.1\x962~&\x1c\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u0113H\x9eV\u00fd\xd8)\x00}\xc2\xf9VA)\x06\xf7k\xfa\x89\x02\xa7\x91H\x8eqT\x00\x00\u07d4\u0116\u02f0E\x9aj\x01`\x0f\u0149\xa5Z2\xb4T!\u007f\x9d\x89\x0e\u0683\x8cI)\b\x00\x00\u07d4\u011c\xfa\xa9g\xf3\xaf\xbfU\x03\x10a\xfcL\xef\x88\xf8]\xa5\x84\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\u0136\xe5\xf0\x9c\xc1\xb9\r\xf0x\x03\xce=M\x13vj\x9cF\xf4\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\u013e\xc9c\b\xa2\x0f\x90\u02b1\x83\x99\u0113\xfd=\x06Z\xbfE\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\xe0\x94\xc4\xc0\x1a\xfc>\x0f\x04R!\xda\x12\x84\u05c7\x85tD/\xb9\xac\x8a\x01\x92\xb5\u0249\x02J\x19\xc1\xbdo\x12\x80\x00\xe0\x94\xc5\x00\xb7 sN\xd2)8\u05cc^H\xb2\xba\x93g\xa5u\xba\x8a\a\x12\x9e\x1c\xdf7>\xe0\x00\x00\u07d4\xc5\x0f\xe4\x15\xa6A\xb0\x85lNu\xbf\x96\x05\x15D\x1a\xfa5\x8d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc5\x13L\xfb\xb1\xdfz \xb0\xedpWb.\xee\u0480\x94}\xad\x89\xcd\xff\x97\xfa\xbc\xb4`\x00\x00\xe0\x94\xc5\x17\xd01\\\x87\x88\x13\xc7\x17\u132f\xa1\xea\xb2eN\x01\u068a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xc5\x18y\x9aY%Wb\x13\xe2\x18\x96\xe0S\x9a\xbb\x85\xb0Z\xe3\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc5\"\xe2\x0f\xbf\x04\xed\u007fk\x05\xa3{G\x18\xd6\xfc\xe0\x14.\x1a\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc5$\bmF\xc8\x11+\x12\x8b/\xafo|}\x81`\xa88l\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xc5-\x1a\fs\u00a1\xbe\x84\x91Q\x85\xf8\xb3O\xaa\n\xdf\x1d\xe3\x89K\xe4\xea\xb3\xfa\x0f\xa6\x80\x00\xe0\x94\xc55\x94\xc7\u03f2\xa0\x8f(L\xc9\u05e6;\xbd\xfc\v1\x972\x8a\nk#(\xff:b\xc0\x00\x00\u07d4\xc57I(\xcd\xf1\x93pTC\xb1L\xc2\r\xa4#G<\xd9\u03c9\a}\x10P\x9b\xb3\xaf\x80\x00\u07d4\xc58\xa0\xff(*\xaa_Ku\u03f6,p\x03~\xe6}O\xb5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc5;P\xfd;+r\xbclC\v\xaf\x19JQU\x85\u04d8m\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xc5=y\xf7\u02dbp\x95/\xd3\x0f\xceX\xd5K\x9f\vY\xf6G\x8a\x01\x13\xe2\xd6tCE\xf8\x00\x00\u07d4\xc5I\u07c3\xc6\xf6^\xec\x0f\x1d\u0260\x93J\\_:P\xfd\x88\x89\x9d\xc0\\\xce(\u00b8\x00\x00\u07d4\xc5P\x05\xa6\xc3~\x8c\xa7\xe5C\xce%\x99s\xa3\xca\u0396\x1aJ\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc5U\xb91V\xf0\x91\x01#\x80\x00\xe0\x94\u0166)\xa3\x96%R\u02ce\xde\u0609cj\xaf\xbd\f\x18\xcee\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u016e\x86\xb0\xc6\xc7\xe3\x90\x0f\x13h\x10\\VS\u007f\xaf\x8dt>\x89\n1\x06+\xee\xedp\x00\x00\u07d4\u0170\t\xba\xea\xf7\x88\xa2v\xbd5\x81:\xd6[@\v\x84\x9f;\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u0175l\xd24&|(\xe8\x9cok\"f\xb0\x86\xa1/\x97\f\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xc5\u01a4\x99\x8a3\xfe\xb7dCz\x8b\xe9)\xa7;\xa3J\ad\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\xe0\x94\xc5\xc7=a\xcc\xe7\xc8\xfeL\x8f\xce)\xf3\x90\x92\xcd\x19>\x0f\xff\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xc5\xc7Y\vV!\xec\xf85\x85\x88\u079bh\x90\xf2baC\U000498a1]\tQ\x9b\xe0\x00\x00\u07d4\xc5\xcd\xce\xe0\xe8]\x11}\xab\xbfSj?@i\xbfD?T\xe7\x89j\xc5\xc6-\x94\x86\a\x00\x00\u07d4\xc5\u050c\xa2\xdb/\x85\xd8\xc5U\xcb\x0e\x9c\xfe\x82i6x?\x9e\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xc5\xde\x12\x03\xd3\xcc,\xea1\xc8.\xe2\xdeY\x16\x88\a\x99\xea\xfd\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xc5\xe4\x88\xcf+Vw\x939q\xf6L\xb8 -\xd0WR\xa2\xc0\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc5\xe8\x12\xf7o\x15\xf2\xe1\xf2\xf9\xbcH#H<\x88\x04cog\x89\x03\xf5\x14\x19:\xbb\x84\x00\x00\u07d4\xc5\u94d34\xf1%.\u04ba&\x81D\x87\xdf\u0498+1(\x89\x03\xcbq\xf5\x1f\xc5X\x00\x00\u07d4\xc5\xebB)^\x9c\xad\xea\xf2\xaf\x12\xde\u078a\x8dS\xc5y\xc4i\x89\xcf\x15&@\xc5\xc80\x00\x00\xe0\x94\xc5\xed\xbb\xd2\xca\x03WeJ\xd0\xeaG\x93\xf8\xc5\xce\xcd0\xe2T\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xc5\xf6K\xab\xb7\x031B\xf2\x0eF\u05eab\x01\xed\x86\xf6q\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc5\xf6\x87qrF\u068a \r \xe5\u9f2c`\xb6\u007f8a\x89\x01\x8d\x99?4\xae\xf1\x00\x00\u07d4\xc6\x04[<5\vL\xe9\xca\fkuO\xb4\x1ai\xb9~\x99\x00\x892$\xf4'#\xd4T\x00\x00\u07d4\xc6\v\x04eN\x00;F\x83\x04\x1f\x1c\xbdk\u00cf\xda|\xdb\u0589lk\x93[\x8b\xbd@\x00\x00\u07d4\xc6\x14F\xb7T\xc2N;\x16B\xd9\xe5\x17e\xb4\xd3\xe4k4\xb6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc6\x18R\x13!\xab\xaf[&Q:J\x95(\bo\"\n\xdco\x89\x01v\xb3D\xf2\xa7\x8c\x00\x00\u07d4\xc6#FW\xa8\a8A&\xf8\x96\x8c\xa1p\x8b\xb0{\xaaI<\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc6%\xf8\u024d'\xa0\x9a\x1b\u02bdQ(\xb1\u00a9HV\xaf0\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xc65^\xc4v\x8cp\xa4\x9a\xf6\x95\x13\u0343\xa5\xbc\xa7\xe3\xb9\u034a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xc6:\xc4\x17\x99.\x9f\x9b`8n\xd9S\xe6\xd7\xdf\xf2\xb0\x90\xe8\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\xc6<\u05c8!\x18\xb8\xa9\x1e\aML\x8fK\xa9\x18Q0;\x9a\x89\x0e\x189\x8ev\x01\x90\x00\x00\u07d4\xc6R\x87\x1d\x19$\"\u01bc#_\xa0c\xb4J~\x1dC\u3149\bg\x0e\x9e\xc6Y\x8c\x00\x00\xe0\x94\xc6gD\x1e\u007f)y\x9a\xbaadQ\xd5;?H\x9f\x9e\x0fH\x8a\x02\xf2\x9a\xceh\xad\u0740\x00\x00\u07d4\xc6j\xe4\xce\xe8\u007f\xb352\x19\xf7\u007f\x1dd\x86\u0140(\x032\x89\x01\x9a\x16\xb0o\xf8\xcb\x00\x00\u07d4\xc6t\xf2\x8c\x8a\xfd\a?\x8by\x96\x91\xb2\xf0XM\xf9B\xe8D\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\u0197\xb7\x04w\u02b4.+\x8b&f\x81\xf4\xaesu\xbb%A\x8a\x01.W2\xba\xba\\\x98\x00\x00\u07d4\u019b\x85U9\xce\x1b\x04qG(\xee\xc2Z7\xf3g\x95\x1d\xe7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u019b\xe4@\x13Mb\x80\x98\x01D\xa9\xf6M\x84t\x8a7\xf3I\x89&\u009eG\u0104L\x00\x00\u07d4\u019df<\x8d`\x90\x83\x91\xc8\xd26\x19\x153\xfd\xf7wV\x13\x89\x1aJ\xba\"\\ t\x00\x00\u0794\u01a2\x86\xe0e\xc8_:\xf7H\x12\xed\x8b\u04e8\xce]%\xe2\x1d\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\u01a3\x0e\xf5\xbb3 \xf4\r\xc5\xe9\x81#\rR\xae:\xc1\x93\"\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\u01ae(}\xdb\xe1\x14\x9b\xa1m\xdc\xcaO\xe0j\xa2\uaa48\xa9\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xc6\xc7\xc1\x917\x98\x97\u075c\x9d\x9a3\x83\x9cJ_b\xc0\x89\r\x89\xd8\xd8T\xb2$0h\x80\x00\xe0\x94\xc6\xcdh\xec56,Z\xd8L\x82\xadN\xdc#!%\x91-\x99\x8a\x05\xe0T\x9c\x962\xe1\xd8\x00\x00\u07d4\xc6\u0615N\x8f?\xc53\xd2\xd20\xff\x02\\\xb4\xdc\xe1O4&\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xc6\xdb\u06de\xfd^\xc1\xb3xn\x06q\xeb\"y\xb2S\xf2\x15\xed\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc6\xdf u\xeb\xd2@\xd4Hi\u00bek\u07c2\xe6=N\xf1\xf5\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc6\xe2\xf5\xaf\x97\x9a\x03\xfdr:\x1bn\xfar\x83\x18\u03dc\x18\x00\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\xc6\xe3$\xbe\xeb[6v^\xcdFB`\xf7\xf2`\x06\xc5\xc6.\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc6\xe4\xcc\fr\x83\xfc\x1c\x85\xbcH\x13\xef\xfa\xafr\xb4\x98#\xc0\x89\x0f\x03\x1e\xc9\xc8}\xd3\x00\x00\xe0\x94\xc6\xee5\x93B)i5)\xdcA\u067bq\xa2IfX\xb8\x8e\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4\xc6\xfb\x1e\xe3t\x17\u0400\xa0\xd0H\x92;\u06ba\xb0\x95\xd0w\u0189\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xc7\x05'\xd4D\u0110\xe9\xfc?\\\xc4Nf\xebO0k8\x0f\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc7\r\x85mb\x1e\xc1E0<\nd\x00\xcd\x17\xbb\xd6\xf5\xea\xf7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc7\x0f\xa4Uv\xbf\x9c\x86_\x988\x93\x00,AI&\xf6\x10)\x89\x15\xb4\xaa\x8e\x97\x02h\x00\x00\u07d4\xc7\x11E\xe5)\u01e7\x14\xe6y\x03\xeeb\x06\xe4\xc3\x04+g'\x89M\x85<\x8f\x89\b\x98\x00\x00\u07d4\xc7\x1b*=q5\u04a8_\xb5\xa5q\u073ei^\x13\xfcC\u034965\u026d\xc5\u07a0\x00\x00\u07d4\xc7\x1f\x1du\x87?3\u0732\xddK9\x87\xa1-\a\x91\xa5\xce'\x897\b\xba\xed=h\x90\x00\x00\u07d4\xc7\x1f\x92\xa3\xa5J{\x8c/^\xa4C\x05\xfc\u02c4\xee\xe21H\x89\x02\xb5\x9c\xa11\xd2\x06\x00\x00\u07d4\xc7!\xb2\xa7\xaaD\xc2\x12\x98\xe8P9\xd0\x0e.F\x0eg\v\x9c\x89\a\xa1\xfe\x16\x02w\x00\x00\x00\u07d4\xc7,\xb3\x01%\x8e\x91\xbc\b\x99\x8a\x80]\u0452\xf2\\/\x9a5\x89 \t\xc5\u023fo\xdc\x00\x00\xe0\x94\xc76\x8b\x97\t\xa5\xc1\xb5\x1c\n\xdf\x18ze\xdf\x14\xe1+}\xba\x8a\x02\x02o\xc7\u007f\x03\u5b80\x00\u07d4\xc79%\x9e\u007f\x85\xf2e\x9b\xef_`\x9e\xd8k=Yl \x1e\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xc7>!\x12(\"\x15\xdc\ab\xf3+~\x80}\xcd\x1az\xae>\x8a\x01v\f\xbcb;\xb3P\x00\x00\xe0\x94\xc7If\x80B\xe7\x11#\xa6H\x97^\b\xedc\x82\xf8>\x05\xe2\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\xc7J9\x95\xf8\a\xde\x1d\xb0\x1a.\xb9\xc6.\x97\xd0T\x8fio\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc7Pl\x10\x19\x12\x1f\xf0\x8a,\x8c\x15\x91\xa6^\xb4\xbd\xfbJ?\x89 \x86\xac5\x10R`\x00\x00\u07d4\xc7\\7\xce-\xa0k\xbc@\b\x11Y\u01ba\x0f\x97n9\x93\xb1\x89:y#\x15\x1e\xcfX\x00\x00\u07d4\xc7]\"Y0j\xec}\xf0\"v\x8ci\x89\x9ae!\x85\xdb\u0109\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc7`\x97\x1b\xbc\x18\x1cj|\xf7tA\xf2BG\u045c\xe9\xb4\u03c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xc7a0\xc7<\xb9!\x028\x02\\\x9d\xf9]\v\xe5J\xc6\u007f\xbe\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\xc7e\xe0\x04v\x81\tG\x81j\xf1B\xd4m.\u7f28\xccO\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xc7g^VG\xb9\xd8\xda\xf4\xd3\xdf\xf1\xe5R\xf6\xb0qT\xac8\x89\t\xc2\x00vQ\xb2P\x00\x00\u07d4\xc7{\x01\xa6\xe9\x11\xfa\x98\x8d\x01\xa3\xab3dk\xee\xf9\xc18\xf3\x89'\x1bo\xa5\xdb\xe6\xcc\x00\x00\u07d4\u01c3z\u0420\xbf\x14\x18i7\xac\xe0lUF\xa3j\xa5OF\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u01d8\x06\x03+\xc7\xd8(\xf1\x9a\u01a6@\u018e=\x82\x0f\xa4B\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u01d9\xe3N\x88\xff\x88\xbe}\xe2\x8e\x15\xe4\xf2\xa6=\v3\xc4\u02c9\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\u01ddPb\u01d6\xddwa\xf1\xf1>U\x8ds\xa5\x9f\x82\xf3\x8b\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\u01e0\x18\xf0\x96\x8aQ\xd1\xf6`<\\I\xdcT[\xcb\x0f\xf2\x93\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u01ef\xf9\x19)yt\x89UZ/\xf1\xd1M\\iZ\x10\x83U\x8965\u026d\xc5\u07a0\x00\x00\u0794\u01f1\xc8>c ?\x95G&>\xf6(.}\xa3;n\xd6Y\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94\u01f3\x9b\x06\x04Q\x00\f\xa1\x04\x9b\xa1T\xbc\xfa\x00\xff\x8a\xf2b\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\u01ff\x17\xc4\xc1\x1f\x98\x94\x1fP~w\bO\xff\xbd-\xbd=\xb5\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\u01ff.\xd1\xed1)@\xeej\xde\xd1Qn&\x8eJ`HV\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xc7\xd4O\xe3,\u007f\x8c\xd5\xf1\xa9t'\xb6\xcd:\xfc\x9eE\x02>\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xc7\xd5\xc7\x05@\x81\xe9\x18\xech{Z\xb3n\x97=\x18\x13)5\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\xc7\xde^\x8e\xaf\xb5\xf6+\x1a\n\xf2\x19\\\xf7\x93\u01c9L\x92h\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc7\xe30\xcd\f\x89\n\u025f\xe7q\xfc\xc7\xe7\xb0\t\xb7A=\x8a\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc7\xea\xc3\x1a\xbc\xe6\xd5\xf1\u07a4\"\x02\xb6\xa6t\x15=\xb4z)\x89 \t\xc5\u023fo\xdc\x00\x00\xe0\x94\xc7\xecb\xb8\x04\xb1\xf6\x9b\x1e0p\xb5\xd3b\xc6/\xb3\t\xb0p\x8a\x02\xc4k\xf5A`f\x11\x00\x00\u07d4\xc7\xf7+\xb7X\x01k7G\x14\u0509\x9b\xce\"\xb4\xae\xc7\n1\x89:&\xc9G\x8f^-\x00\x00\u0794\xc8\v6\u047e\xaf\xba_\xccdM`\xacnF\xed)'\xe7\u0708\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\xc8\x11\xc2\xe9\xaa\x1a\xc3F.\xba^\x88\xfc\xb5\x12\x0e\x9fn,\xa2\x89K\xe6\u0607\xbd\x87n\x00\x00\u07d4\xc8\x17\xdf\x1b\x91\xfa\xf3\x0f\xe3%\x15qr|\x97\x11\xb4]\x8f\x06\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xc8\x1f\xb7\xd2\x0f\u0480\x01\x92\xf0\xaa\xc1\x98\xd6\u05a3}?\xcb}\x89\x0e\x11I3\x1c-\xde\x00\x00\u07d4\xc8 \xc7\x11\xf0w\x05'8\a\xaa\xaam\xe4M\x0eKH\xbe.\x89\bg\x0e\x9e\xc6Y\x8c\x00\x00\u07d4\xc8#\x1b\xa5\xa4\x11\xa1>\"+)\xbf\xc1\b?v1X\xf2&\x8967\tlK\xcci\x00\x00\u07d4\xc86\xe2Jo\xcf)\x94;6\b\xe6b)\n!_e)\xea\x89\x0f\xd4Pd\xea\xee\x10\x00\x00\xe0\x94\xc8;\xa6\u0755I\xbe\x1d2\x87\xa5\xa6T\xd1\x06\xc3Lk]\xa2\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\xc8>\x9djX%;\uefb7\x93\xe6\xf2\x8b\x05JXI\x1bt\x89\x0fF\u00b6\xf5\xa9\x14\x00\x00\u07d4\xc8A\x88O\xa4x_\xb7s\xb2\x8e\x97\x15\xfa\xe9\x9aQ40]\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc8M\x9b\xea\n{\x9f\x14\x02 \xfd\x8b\x90\x97\u03ff\xd5\xed\xf5d\x89\x06\xab\x9e\u0091\xad}\x80\x00\u07d4\xc8RB\x8d+Xd\x97\xac\xd3\fV\xaa\x13\xfbU\x82\xf8D\x02\x893B\xd6\r\xff\x19`\x00\x00\u07d4\xc8S![\x9b\x9f-,\xd0t\x1eX^\x98{_\xb8\f!.\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\xc8S%\uaca5\x9b>\xd8c\xc8j_)\x06\xa0B)\xff\xa9\x89\x19=\u007f}%=\xe0\x00\x00\u07d4\xc8^\xf2}\x82\x04\x03\x80_\xc9\xed%\x9f\xffd\xac\xb8\xd64j\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc8akN\xc0\x91(\xcd\xff9\xd6\u4e6c\x86\xee\xc4q\xd5\xf2\x89\x01\r:\xa56\xe2\x94\x00\x00\xe0\x94\xc8a\x90\x90K\x8d\a\x9e\xc0\x10\xe4b\xcb\xff\xc9\b4\xff\xaa\\\x8a\x02#\x85\xa8'\xe8\x15P\x00\x00\u07d4\xc8q\r~\x8bZ;\u059aB\xfe\x0f\xa8\xb8|5\u007f\xdd\xcd\u0209\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc8sR\u06e5\x82\xee f\xb9\xc0\x02\xa9b\xe0\x03\x13Ox\xb1\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\xc8|w\xe3\xc2J\xde\xcd\xcd\x108\xa3\x8bV\xe1\x8d\xea\u04f7\x02\x8a\x01\xdd\f\x88_\x9a\r\x80\x00\x00\u07d4\xc8}:\xe3\u0607\x04\u066b\x00\t\xdc\xc1\xa0\x06q1\xf8\xba<\x89j\xc5\xc6-\x94\x86\a\x00\x00\xe0\x94\u0201N4R>8\xe1\xf9'\xa7\xdc\xe8FjDz\t6\x03\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u0202U\xed\xdc\xf5!\xc6\xf8\x1d\x97\xf5\xa4!\x81\xc9\a=N\xf1\x89\x0f\u00d0D\xd0\n*\x80\x00\u07d4\u0205\xa1\x8a\xab\xf4T\x1b{{~\xcd0\xf6\xfa\u619d\x95i\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u020c\xa1\xe6\xe5\xf4\xd5X\xd17\x80\xf4\x88\xf1\rJ\xd3\x13\r4\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\u020e\xecT\xd3\x05\xc9(\xcc(H\xc2\xfe\xe251\xac\xb9mI\x89lj\u04c2\xd4\xfba\x00\x00\xe0\x94\u021c\xf5\x04\xb9\xf3\xf85\x18\x1f\xd8BO\\\xcb\xc8\xe1\xbd\xdf}\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u0222\xc4\xe5\x9e\x1c\u007f\xc5H\x05X\x048\xae\xd3\xe4J\xfd\xf0\x0e\x89\x02b\x9ff\xe0\xc50\x00\x00\u07d4\u022aI\u301f\b\x99\xf2\x8a\xb5~gCp\x9dXA\x903\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4\u022b\x1a<\xf4l\xb8\xb0d\xdf.\"-9`s\x94 2w\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0231\x85\x05%\xd9F\xf2\xae\x84\xf3\x17\xb1Q\x88\xc56\xa5\u0706\x89\x91\x8d\xdc:B\xa3\xd4\x00\x00\u07d4\xc8\xd4\xe1Y\x9d\x03\xb7\x98\t\xe0\x13\n\x8d\u00c4\b\xf0^\x8c\u04c9\x9f\xad\x06$\x12y\x16\x00\x00\u07d4\xc8\xdd'\xf1k\xf2$P\xf5w\x1b\x9f\xe4\xedO\xfc\xb3\t6\xf4\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xc8\xdezVL\u007f@\x12\xa6\xf6\xd1\x0f\u040fG\x89\x0f\xbf\a\u0509\x10CV\x1a\x88)0\x00\x00\u07d4\xc8\xe2\xad\xebT^I\x9d\x98,\f\x11sc\u03b4\x89\u0171\x1f\x895e\x9e\xf9?\x0f\xc4\x00\x00\xe0\x94\xc8\xe5X\xa3\xc5i~o\xb2:%\x94\u0200\xb7\xa1\xb6\x8f\x98`\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xc8\xf2\xb3 \xe6\xdf\xd7\t\x06\u0157\xba\xd2\xf9P\x13\x12\u01c2Y\x89Q\x93K\x8b:W\xd0\x00\x00\u07d4\xc9\x03\x00\xcb\x1d@w\xe6\xa6\xd7\xe1i\xa4`F\x8c\xf4\xa4\x92\u05c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xc9\f7e\x15k\u028eH\x97\xab\x80$\x19\x15<\xbeR%\xa9\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xc9\x10\xa9pUl\x97\x16\xeaS\xaff\xdd\xef\x93\x141$\x91=\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\xe0\x94\xc9\x12{\u007ff)\xee\x13\xfc?`\xbc/Dg\xa2\aE\xa7b\x8a\x03|\x9a\xa4\xe7\xceB\x1d\x80\x00\u07d4\xc9\x1b\xb5b\xe4+\xd4a0\xe2\u04eeFR\xb6\xa4\ub1bc\x0f\x89\x1dF\x01b\xf5\x16\xf0\x00\x00\xe0\x94\xc90\x88y\x05m\xfe\x13\x8e\xf8 \x8fy\xa9\x15\u01bc~p\xa8\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\xc94\xbe\xca\xf7\x1f\"_\x8bJK\xf7\xb1\x97\xf4\xac\x9604\\\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xc9?\xbd\xe8\xd4m+\xcc\x0f\xa9\xb3;\u063a\u007f\x80B\x12Ue\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xc9@\x89U:\xe4\xc2,\xa0\x9f\xbc\x98\xf5pu\xcf.\u0155\x04\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc9A\x10\xe7\x1a\xfeW\x8a\xa2\x18\xe4\xfc(d\x03\xb03\n\u038d\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xc9F\u056c\xc14n\xba\nry\xa0\xac\x1dF\\\x99m\x82~\x8a\x03x=T_\xdf\n\xa4\x00\x00\u07d4\xc9J(\xfb20\xa9\xdd\xfa\x96Nw\x0f,\xe3\xc2S\xa7\xbeO\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xc9JXR\x03\xda{\xba\xfd\x93\xe1X\x84\xe6`\u0531\xea\xd8T\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\xc9O|5\xc0'\xd4}\xf8\xefO\x9d\xf8Z\x92H\xa1}\xd2;\x89\x01\x9f\x8euY\x92L\x00\x00\u07d4\xc9Q\x90\f4\x1a\xbb\xb3\xba\xfb\xf7\xee )7pq\xdb\xc3j\x89\x11\xc2]\x00M\x01\xf8\x00\x00\u07d4\xc9S\xf94\xc0\xeb-\x0f\x14K\u06b0\x04\x83\xfd\x81\x94\x86\\\xe7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc9f&r\x8a\xaaLO\xb3\xd3\x1c&\xdf:\xf3\x10\b\x17\x10\u0449\xb5\x0f\u03ef\xeb\xec\xb0\x00\x00\u07d4\xc9gQel\n\x8e\xf45{sD2!4\xb9\x83PJ\u0289lk\x93[\x8b\xbd@\x00\x00\u07d4\u0240Hh\u007f+\xfc\u027d\x90\xed\x18slW\xed\xd3R\xb6]\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u0241\xd3\x12\u0487\xd5X\x87\x1e\u0757:\xbbv\xb9y\xe5\xc3^\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\u0242Xmc\xb0\xd7L \x1b\x1a\xf8A\x83r\xe3\fv\x16\xbe\x89\x05k\xc7^-c\x10\x00\x00\u07d4\u0249CO\x82Z\xaf\x9cU/h^\xba|\x11\xdbJ_\xc7:\x89\x1b(\u014d\x96\x96\xb4\x00\x00\u07d4\u0249\xee\xc3\a\u80db\x9dr7\xcf\xda\b\x82)b\xab\u41c9\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\u0252\xbeY\xc6r\x1c\xafN\x02\x8f\x9e\x8f\x05\xc2\\UQ[\u0509\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u0255{\xa9L\x1b)\xe5'~\xc3f\"pI\x04\xc6=\xc0#\x89h>\xfcg\x82d,\x00\x00\xe0\x94\u025a\x9c\xd6\xc9\xc1\xbe54\xee\u0352\xec\xc2/\\8\xe9Q[\x8a\x01\x05Y;:\x16\x9dw\x00\x00\xe0\x94\u026c\x01\xc3\xfb\t)\x03?\f\xcc~\x1a\xcf\uaae7\x94]G\x8a\x02\xa3j\x9e\x9c\xa4\xd2\x03\x80\x00\u07d4\u0276\x98\xe8\x98\xd2\rMO@\x8eNM\x06\x19\"\xaa\x85c\a\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\u0276\xb6\x86\x11\x16\x91\xeej\xa1\x97\xc7#\x1a\x88\xdc`\xbd)]\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xc9\u01ec\v\u0753B\xb5\xea\xd46\t#\xf6\x8cr\xa6\xbac:\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xc9\xc8\r\xc1.{\xab\x86\xe9I\xd0\x1eL>\xd3_+\x9b\xba_\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc9\xd7dF\u056a\xdf\xf8\vh\xb9\x1b\b\u035b\xc8\xf5U\x1a\xc1\x89&\xb4\xbd\x91\x10\xdc\xe8\x00\x00\xe0\x94\xc9\u073b\x05oM\xb7\xd9\xda9\x93b\x02\u017d\x820\xb3\xb4w\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xc9\xe0&\b\x06h(\x84\x8a\xeb(\xc76r\xa1)%\x18\x1fM\x89\x1b\x1bk\u05efd\xc7\x00\x00\u07d4\xca\x042\xcb\x15{Qy\xf0.\xbb\xa5\xc9\u0475O\xecM\x88\u028965\u026d\xc5\u07a0\x00\x00\u07d4\xca\x12,\xf0\U00094216\xb7HC\xf4\x9a\xfe\u043a\x16\x18\xee\u05c9\x1e[\x8f\xa8\xfe*\xc0\x00\x00\xe0\x94\xca\"\u0363`m\xa5\xca\xd0\x13\xb8\aG\x06\xd7\xe9\xe7!\xa5\f\x8a\x01q\x81\xc6\xfa9\x81\x94\x00\x00\u07d4\xca#\xf6-\xff\rd`\x03lb\xe8@\xae\xc5W~\v\xef\u0489\a\xa1\xfe\x16\x02w\x00\x00\x00\u07d4\xca%\xff4\x93L\x19B\xe2*N{\xd5o\x14\x02\x1a\x1a\xf0\x88\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xca7?\xe3\xc9\x06\xb8\xc6U\x9e\xe4\x9c\xcd\a\xf3|\xd4\xfbRf\x89a\t=|,m8\x00\x00\u07d4\xcaA\u032c0\x17 R\xd5\"\xcd//\x95}$\x81S@\x9f\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xcaB\x88\x01N\xdd\xc5c/_\xac\xb5\xe3\x85\x17\xa8\xf8\xbc]\x98\x89\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\xcaB\x88c\xa5\xca06\x98\x92\xd6\x12\x18>\xf9\xfb\x1a\x04\xbc\xea\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d4\xcaI\xa5\xf5\x8a\xdb\xef\xae#\xeeY\xee\xa2A\xcf\x04\x82b.\xaa\x89M\x85<\x8f\x89\b\x98\x00\x00\u07d4\xcaL\xa9\xe4w\x9dS\x0e\u02ec\xd4~j\x80X\xcf\xdee\u064f\x89+^:\xf1k\x18\x80\x00\x00\u07d4\xcae~\xc0o\xe5\xbc\t\xcf#\xe5*\xf7\xf8\f\xc3h\x9en\u07890\xca\x02O\x98{\x90\x00\x00\u07d4\xcaf\xb2(\x0f\xa2\x82\u0176v1\xceU+b\xeeU\xad\x84t\x89j\xc4\"\xf54\x92\x88\x00\x00\xe0\x94\xcal\x81\x8b\xef\xd2Q6\x1e\x02t@h\xbe\x99\u062a`\xb8J\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xcap\xf4\u077f\x06\x9d!C\xbdk\xbc\u007fikRx\x9b2\u7262\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94\xcatuvDjL\x8f0\xb0\x83@\xfe\xe1\x98\xdec\xec\x92\u03ca\x01|\x8e\x12\x06r*0\x00\x00\u07d4\xca{\xa3\xffSl~_\x0e\x158\x00\xbd8=\xb81)\x98\xe0\x89\t1\xac=k\xb2@\x00\x00\xe0\x94\u0282v\xc4w\xb4\xa0{\x80\x10{\x845\x94\x18\x96\a\xb5;\xec\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\u0284\t\b>\x01\xb3\x97\xcf\x12\x92\x8a\x05\xb6\x84U\xceb\x01\u07c9V\xbcu\xe2\xd61\x00\x00\x00\u07d4\u0298\u01d8\x8e\xfa\b\xe9%\uf719ER\x03&\xe9\xf4;\x99\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u029a\x04*j\x80o\xfc\x92\x17\x95\x00\xd2D)\xe8\xabR\x81\x17\x89;\xa1\x91\v\xf3A\xb0\x00\x00\u07d4\u029d\xec\x02\x84\x1a\xdf\\\xc9 WjQ\x87\xed\u04bdCJ\x18\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\u029f\xaa\x17T/\xaf\xbb8\x8e\xab!\xbcL\x94\u89f3G\x88\x89lk\x8f\xce\r\x18y\x80\x00\xe0\x94\u02aah\xeel\xdf\r4EJv\x9b\r\xa1H\xa1\xfa\xaa\x18e\x8a\x01\x87.\x1d\xe7\xfeR\xc0\x00\x00\u07d4\u02ad\x9d\xc2\rX\x9c\xe4(\xd8\xfd\xa3\xa9\xd5:`{y\x88\xb5\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\u02b0\xd3,\xf3v\u007f\xa6\xb3S|\x842\x8b\xaa\x9fPE\x816\x8a\x01\xe5\xb8\xfa\x8f\xe2\xac\x00\x00\x00\u07d4\u02b9\xa3\x01\xe6\xbdF\xe9@5P(\xec\xcd@\xceMZ\x1a\u00c9\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\u02b9\xa9z\xda\x06\\\x87\x81nh`\xa8\xf1Bo\xe6\xb3\xd7u\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\u02ba\xb6'N\xd1P\x89s~({\xe8x\xb7W\x93Hd\xe2\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\u02bd\xaf5OG \xa4f\xa7d\xa5(\xd6\x0e:H*9<\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xca\xcbg^\t\x96#T\x04\ufbfb.\u02c1R'\x1bU\xe0\x89%\xf2s\x93=\xb5p\x00\x00\u07d4\xca\xd1O\x9e\xbb\xa7f\x80\xeb\x83k\a\x9c\u007f{\xaa\xf4\x81\xedm\x89\f\xef={\xd7\xd04\x00\x00\xe0\x94\xca\xe3\xa2S\xbc\xb2\xcfN\x13\xba\x80\u0098\xab\x04\x02\xda|*\xa0\x8a\x01$\xbc\r\u0752\xe5`\x00\x00\u07d4\xca\xef\x02{\x1a\xb5\x04\xc7?A\xf2\xa1\ty\xb4t\xf9~0\x9f\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xca\xf4H\x1d\x9d\xb7\x8d\xc4\xf2_{J\u023d;\x1c\xa0\x10k1\x8a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x94\xca\xfd\xe8U\x86L%\x98\xda<\xaf\xc0Z\u064d\U00089380H\x8a\x03\x00\xa8\xed\x96\xffJ\x94\x00\x00\xe0\x94\xcb\r\xd7\xcfN]\x86a\xf6\x02\x89C\xa4\xb9\xb7\\\x91D6\xa7\x8a\x19i6\x89t\xc0[\x00\x00\x00\u07d4\xcb\x1b\xb6\xf1\xda^\xb1\rH\x99\xf7\xe6\x1d\x06\xc1\xb0\x0f\u07f5-\x898E$\xccp\xb7x\x00\x00\u07d4\xcb=vl\x98?\x19+\xce\xca\xc7\x0fN\xe0=\xd9\xffqMQ\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xcbB\xb4N\xb5\xfd`\xb5\x83~O\x9e\xb4rgR=\x1a\"\x9c\x89.\xe4IU\b\x98\xe4\x00\x00\u07d4\xcbG\xbd0\u03e8\xecTh\xaa\xa6\xa9FB\xce\xd9\xc8\x19\xc8\u0509\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xcbH\xfe\x82e\u066fU\xebp\x06\xbc3VE\xb0\xa3\xa1\x83\xbe\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xcbJ\x91M+\xb0)\xf3._\xef\\#LO\xec--\xd5w\x89a\x94\x04\x9f0\xf7 \x00\x00\xe0\x94\xcbJ\xbf\u0082\xae\xd7n]W\xaf\xfd\xa5B\xc1\xf3\x82\xfc\xac\xf4\x8a\x01\xb9\x0f\x11\xc3\x18?\xaa\x00\x00\u07d4\xcbJ\xd0\xc7#\xdaF\xabV\xd5&\xda\f\x1d%\xc7=\xaf\xf1\n\x89\x1b\xa5\xab\xf9\xe7y8\x00\x00\u07d4\xcbK\xb1\xc6#\xba(\xdcB\xbd\xaa\xa6\xe7N\x1d*\xa1%l*\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xcbPXt\x12\x82#\x04\xeb\u02e0}\xab:\x0f\t\xff\xfe\u4189JD\x91\xbdm\xcd(\x00\x00\u07d4\xcbX\x99\v\u0350\u03ffm\x8f\t\x86\xf6\xfa`\x02v\xb9N-\x8964\xbf9\xab\x98x\x80\x00\u07d4\xcbh\xaeZ\xbe\x02\xdc\xf8\xcb\u016aq\x9c%\x81FQ\xaf\x8b\x85\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xcbty\x10\x9bC\xb2fW\xf4F_M\x18\xc6\xf9t\xbe_B\x89b\xa9\x92\xe5:\n\xf0\x00\x00\xe0\x94\xcb}+\x80\x89\xe91,\u026e\xaa's\xf3S\b\xecl*{\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u02c6\xed\xbc\x8b\xbb\x1f\x911\x02+\xe6IV^\xbd\xb0\x9e2\xa1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u02d3\x19\x9b\x9c\x90\xbcI\x15\xbd\x85\x9e=B\x86m\xc8\xc1\x87I\x89\f\x90\xdf\a\xde\xf7\x8c\x00\x00\u07d4\u02d4\xe7o\xeb\xe2\b\x11g3\xe7n\x80]H\xd1\x12\xec\x9f\u028965\u026d\xc5\u07a0\x00\x00\u07d4\u02dbQ\x03\xe4\u0389\xafOd\x91aP\xbf\xf9\xee\u02df\xaa\\\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\u02e2\\zP<\xc8\xe0\xd0Iq\xca\x05\xc7b\xf9\xb7b\xb4\x8b\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\u02e2\x88\xcd<\x1e\xb4\u055d\xdb\x06\xa6B\x1c\x14\xc3E\xa4{$\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u02f3\x18\x9eK\xd7\xf4_\x17\x8b\x1c0\xc7n&1MJK\n\x89\x0f\xfe\vg|e\xa9\x80\x00\xe0\x94\u02f7\xbe\x17\x95?,\u0313\u1f19\x80[\xf4U\x11CNL\x8a\n\xae[\x9d\xf5m/ \x00\x00\xe0\x94\xcb\xc0KM\x8b\x82\xca\xf6p\x99o\x16\f6)@\xd6o\xcf\x1a\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xcb\u07974\xb8\xe6\xaaS\x8c)\x1dm\u007f\xac\xed\xb0\xf38\xf8W\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xcb\xe1\xb9H\x86M\x84t\xe7e\x14XX\xfc\xa4U\x0fxK\x92\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xcb\xe5/\xc53\xd7\xdd`\x8c\x92\xa2`\xb3|?E\u07b4\xeb3\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xcb\xe8\x10\xfe\x0f\xec\xc9dGJ\x1d\xb9w(\xbc\x87\xe9s\xfc\xbd\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xcb\xf1j\x0f\xe2tRX\xcdR\xdb+\xf2\x19T\xc9u\xfcj\x15\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94\xcb\xf3\u007f\xf8T\xa2\xf1\xceS\x93D\x94wx\x92\xd3\xeceW\x82\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xcb\xfaj\xf6\u0083\xb0F\xe2w,`c\xb0\xb2\x15S\xc4\x01\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcb\xfav\xdb\x04\xce8\xfb ]7\xb8\xd3w\xcf\x13\x80\xda\x03\x17\x89M\x85<\x8f\x89\b\x98\x00\x00\u07d4\xcc\x03I\x85\xd3\xf2\x8c-9\xb1\xa3K\xce\xd4\u04f2\xb6\xca#N\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\xcc\x04\x8d\u01f9]\xca%\xdf&\xee\xfac\x9d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcc+_D\x8f5(\xd3\xfeA\xcc}\x1f\xa9\xc0\xdcv\xf1\xb7v\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\xcc-\x04\xf0\xa4\x01q\x89\xb3@\xcaw\x19\x86A\xdc\xf6Ek\x91\x89\u0556{\xe4\xfc?\x10\x00\x00\xe0\x94\xccA\x9f\u0651+\x85\x13VY\xe7z\x93\xbc=\xf1\x82\xd4Q\x15\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xccE\xfb:U[\xad\x80{8\x8a\x03W\xc8U _|u\xe8\x89.\xe4IU\b\x98\xe4\x00\x00\u07d4\xccHAM*\xc4\xd4*Yb\xf2\x9e\xeeD\x97\t/C\x13R\x89\b\xbaR\xe6\xfcE\xe4\x00\x00\u07d4\xccJ/,\xf8l\xf3\xe43u\xf3`\xa4sF\x91\x19_\x14\x90\x89I\x15\x05;\xd1)\t\x80\x00\u07d4\xccO\x0f\xf2\xae\xb6}T\xce;\xc8\xc6Q\v\x9a\xe8>\x9d2\x8b\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xccO\xaa\xc0\v\xe6b\x8f\x92\xefk\x8c\xb1\xb1\xe7j\xac\x81\xfa\x18\x89\v\"\xa2\xea\xb0\xf0\xfd\x00\x00\xe0\x94\xccO\xebr\u07d8\xff5\xa18\xe0\x17a\xd1 ?\x9b~\xdf\n\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\xcc`oQ\x13\x97\xa3\x8f\u01c7+\u04f0\xbd\x03\xc7\x1b\xbdv\x8b\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xcc`\xf86\xac\xde\xf3T\x8a\x1f\xef\u0321>\u01a97\xdbD\xa0\x89\x04\xb0m\xbb\xb4\x0fJ\x00\x00\u07d4\xccl\x03\xbd`>\t\xdeT\xe9\xc4\u056cmA\xcb\xceqW$\x89\x05V\xf6L\x1f\xe7\xfa\x00\x00\u07d4\xccl-\xf0\x0e\x86\xec\xa4\x0f!\xff\xda\x1ag\xa1i\x0fG|e\x89\xabM\xcf9\x9a:`\x00\x00\xe0\x94\xccm{\x12\x06\x1b\xc9m\x10M`me\xff\xa3+\x006\xeb\a\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xccs\xdd5kIy\xb5y\xb4\x01\xd4\xccz1\xa2h\xdd\xceZ\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\xccu\x8d\a\x1d%\xa62\n\xf6\x8c]\xc9\xc4\xf6\x95[\xa9E \x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xcc{\x04\x81\xcc2\xe6\xfa\xef#\x86\xa0p\"\xbc\xb6\xd2\u00f4\xfc\x89\xabM\xcf9\x9a:`\x00\x00\xe0\x94\u0314;\xe1\",\xd1@\n#\x99\xdd\x1bE\x94E\xcfmT\xa9\x8a\x02\xa7@\xaee6\xfc\x88\x00\x00\u07d4\u0315\x19\xd1\xf3\x98_k%^\xad\xed\x12\xd5bJ\x97'!\xe1\x8965\u026d\xc5\u07a0\x00\x00\u0794\u031a\xc7\x15\xcdo&\x10\xc5+XgdV\x88B\x97\x01\x8b)\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\u0320{\xb7\x94W\x1dJ\xcf\x04\x1d\xad\x87\xf0\xd1\xef1\x85\xb3\x19\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u032b\xc6\x04\x8aSFD$\xfc\xf7n\xeb\x9en\x18\x01\xfa#\u0509\x02\xab{&\x0f\xf3\xfd\x00\x00\u07d4\u032e\r=\x85*}\xa3\x86\x0f\x066\x15L\nl\xa3\x16(\u0509\x05\xc6\xd1+k\xc1\xa0\x00\x00\u07d4\xcc\xca$\xd8\xc5mn,\a\xdb\bn\xc0~X[\xe2g\xac\x8d\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xcc\xd5!\x13-\x98l\xb9hi\x84&\"\xa7\u0762l>\xd0W\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcc\xf49u\xb7k\xfes_\xec<\xb7\xd4\xdd$\xf8\x05\xba\tb\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\xcc\xf6*f?\x13S\xba.\xf8\xe6R\x1d\xc1\xec\xb6s\xec\x8e\xf7\x89\b=lz\xabc`\x00\x00\u07d4\xcc\xf7\x11\r\x1b\u0667K\xfd\x1d}}-\x9dU`~{\x83}\x890\xca\x02O\x98{\x90\x00\x00\u07d4\xcc\xfdrW`\xa6\x88#\xff\x1e\x06/L\xc9~\x13`\xe8\u0657\x89\x15\xacV\xed\xc4\xd1,\x00\x00\u07d4\xcd\x02\x0f\x8e\xdf\xcfRG\x98\xa9\xb7:d\x034\xbb\xf7/\x80\xa5\x89\a?u\u0460\x85\xba\x00\x00\u07d4\xcd\x06\xf8\xc1\xb5\u037d(\xe2\xd9kcF\xc3\xe8Z\x04\x83\xba$\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xcd\a.n\x183\x13y\x95\x19m{\xb1r_\xef\x87a\xf6U\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xcd\n\x16\x1b\xc3g\xae\t'\xa9*\xac\x9c\xf6\xe5\bg\x14\xef\u0289lk\x93[\x8b\xbd@\x00\x00\u07d4\xcd\n\xf3GN\"\xf0i\xec4\a\x87\r\xd7pD=[\x12\xb0\x89\x8e^\xb4\xeew\xb2\xef\x00\x00\u07d4\xcd\v\x02W\u70e3\xd2\xc2\u3e9dny\xb7^\xf9\x80$\u0509\x9f\xad\x06$\x12y\x16\x00\x00\u07d4\xcd\x10,\xd6\xdb=\xf1J\u05af\x0f\x87\xc7$y\x86\x1b\xfc=$\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcd\x1ef\xedS\x9d\xd9/\xc4\v\xba\xa1\xfa\x16\u078c\x02\xc1ME\x89\fw\xe4%hc\xd8\x00\x00\u07d4\xcd\x1e\xd2c\xfb\xf6\xf6\xf7\xb4\x8a\xef\x8fs=2\x9dC\x82\xc7\u01c9\x01\x00\xbd3\xfb\x98\xba\x00\x00\u07d4\xcd*6\xd7S\xe9\xe0\xed\x01*XMqh\aX{A\xd5j\x89\x0e+\xa7[\v\x1f\x1c\x00\x00\u07d4\xcd2\xa4\xa8\xa2\u007f\x1c\xc69T\xaacOxW\x05s4\u01e3\x89:\xd1fWlr\xd4\x00\x00\u07d4\xcd5\xff\x01\x0e\xc5\x01\xa7!\xa1\xb2\xf0z\x9c\xa5\x87}\xfc\xf9Z\x89\xd9o\u0390\u03eb\xcc\x00\x00\u07d4\xcdC\x06\xd7\xf6\x94z\xc1tMN\x13\xb8\xef2\xcbe~\x1c\x00\x89\x1b\x1a\xb3\x19\xf5\xecu\x00\x00\u07d4\xcdC%\x8bs\x92\xa90\x83\x9aQ\xb2\xef\x8a\xd24\x12\xf7Z\x9f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcdI\xbf\x18^p\xd0E\a\x99\x9f\x92\xa4\xdeDU1('\u040965\u026d\xc5\u07a0\x00\x00\u07d4\xcdU\x10\xa2B\u07f0\x18=\xe9%\xfb\xa8f\xe3\x12\xfa\xbc\x16W\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4\xcdVj\u05f8\x83\xf0\x1f\u04d9\x8a\x9aX\xa9\xde\xe4rM\u0725\x89\x030\xae\x185\xbe0\x00\x00\xe0\x94\xcdY\xf3\xdd\xe7~\t\x94\v\xef\xb6\xeeX\x03\x19e\xca\xe7\xa36\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xcdr]p\xbe\x97\xe6w\xe3\xc8\xe8\\\v&\xef1\xe9\x95PE\x89Hz\x9a0E9D\x00\x00\xe0\x94\xcd~G\x90\x94d\xd8q\xb9\xa6\xdcv\xa8\xe9\x19]\xb3H^z\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\xcd~\xce\bkKa\x9b;6\x93R\xee8\xb7\x1d\xdb\x06C\x9a\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xcd\u007f\t\xd7\xedf\xd0\u00cb\u016dN2\xb7\xf2\xb0\x8d\xc1\xb3\r\x89>;\xb3M\xa2\xa4p\x00\x00\u07d4\u0355)I+\\)\xe4u\xac\xb9A@+=;\xa5\x06\x86\xb0\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\u0355\xfaB=o\xc1 'J\xac\xde\x19\xf4\xee\xb7f\xf1\x04 \x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\u035bL\xefs9\f\x83\xa8\xfdq\u05f5@\xa7\xf9\u03cb\x8c\x92\x89\x04\xe1\x00;(\xd9(\x00\x00\u07d4\u0361t\x11\t\xc0&[?\xb2\xbf\x8d^\xc9\u00b8\xa34kc\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u0361\xb8\x86\u39d5\u027aw\x91N\n/\xe5go\x0f\\\u03c9\x05\xbf`\xeaB\xc2\x04\x00\x00\u07d4\u0364S\x0fK\x9b\xc5\t\x05\xb7\x9d\x17\u008f\xc4o\x954\x9b\u07c93\x10\xe0I\x11\xf1\xf8\x00\x00\u07d4\u036bF\xa5\x90 \x80do\xbf\x95B\x04 J\xe8\x84\x04\x82+\x89\x1d\x8a\x96\xe5\xc6\x06\xeb\x00\x00\u07d4\u0375\x97)\x900\x18?n-#\x853\xf4d*\xa5\x87T\xb6\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xcd\xd5\u0601\xa76,\x90p\a;\u07fcu\xe7$S\xacQ\x0e\x89-\xa5\x18\xea\xe4\x8e\xe8\x00\x00\u07d4\xcd\xd6\rs\xef\xaa\xd8s\u027b\xfb\x17\x8c\xa1\xb7\x10Z\x81\xa6\x81\x89\x01\xbc\x16\xd6t\xec\x80\x00\x00\u07d4\xcd\xd9\xef\xacMm`\xbdq\xd9U\x85\xdc\xe5\u0557\x05\xc15d\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xcd\xe3m\x81\xd1(\u015d\xa1Ee!\x93\xee\u00bf\xd9e\x86\xef\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xcd\xea8o\x9d\x0f\xd8\x04\xd0(\x18\xf27\xb7\xd9\xfavF\xd3^\x89\xa3I\xd3m\x80\xecW\x80\x00\u07d4\xcd\xec\xf5gT3\u0370\xc2\xe5Zh\xdb]\x8b\xbexA\x9d\u0489\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xcd\xfd\x82\x173\x97%\xd7\xeb\xac\x11\xa66U\xf2e\xef\xf1\xcc=\x8a\x01\x0f\fid\x10\xe3\xa9\x00\x00\u07d4\xce\a\x9fQ\x88wt\xd8\x02\x1c\xb3\xb5u\xf5\x8f\x18\xe9\xac\xf9\x84\x89\t\xc2\x00vQ\xb2P\x00\x00\u07d4\xce\x18\x84\u077b\xb8\xe1\x0eM\xbanD\xfe\xee\u00a7\xe5\xf9/\x05\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xce\x1b\f\xb4j\xae\xcf\u05db\x88\f\xad\x0f-\u068a\x8d\xed\u0431\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xce&\xf9\xa50_\x83\x81\tCT\xdb\xfc\x92fN\x84\xf9\x02\xb5\x89\fz\xaa\xb0Y\x1e\xec\x00\x00\u07d4\xce-\xea\xb5\x1c\n\x9a\xe0\x9c\xd2\x12\xc4\xfaL\xc5+S\xcc\r\xec\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xce.\r\xa8\x93F\x99\xbb\x1aU>U\xa0\xb8\\\x16\x945\xbe\xa3\x8a\x01\x0f\fid\x10\xe3\xa9\x00\x00\u07d4\xce:a\xf0F\x1b\x00\x93^\x85\xfa\x1e\xad\x82\xc4^Zd\u0508\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xceK\x06]\xbc\xb20G 2b\xfbH\xc1\x18\x83d\x97tp\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xceS\xc8\xcd\xd7B\x96\xac\xa9\x87\xb2\xbc\x19\u00b8u\xa4\x87I\u0409\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xce^\x04\xf0\x18Ci\xbc\xfa\x06\xac\xa6o\xfa\x91\xbfY\xfa\x0f\xb9\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xce^\xb6:{\xf4\xfb\xc2\xf6\u4ea0\u018a\xb1\xcbL\xf9\x8f\xb4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xceb\x12Z\xde\xc37\n\xc5!\x10\x95:Nv\v\xe9E\x1e;\x89\b=lz\xabc`\x00\x00\xe0\x94\xceq\bmL`%T\xb8-\xcb\xfc\xe8\x8d cMS\xccM\x8a\t(\x96R\x9b\xad\u0708\x00\x00\u07d4\u038akmP3\xb1I\x8b\x1f\xfe\xb4\x1aAU\x04\x05\xfa\x03\xa2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u0397\x86\xd3q/\xa2\x00\xe9\xf6\x857\xee\xaa\x1a\x06\xa6\xf4ZK\x89a\t=|,m8\x00\x00\u07d4\u039d!\u0192\xcd<\x01\xf2\x01\x1fP_\x87\x006\xfa\x8fl\u0489\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\u03a2\x89f#\xf4\x91\x02\x87\xa2\xbd\u017e\x83\xae\xa3\xf2\xe6\xde\b\x8a\x01\xfbZ7Q\xe4\x90\xdc\x00\x00\u07d4\u03a3JM\xd9=\u066e\xfd9\x90\x02\xa9}\x99z\x1bK\x89\u0349QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\u03a4?pu\x81k`\xbb\xfc\u62d9:\xf0\x88\x12p\xf6\u0109lk\x93[\x8b\xbd@\x00\x00\u07d4\u03a8t3AS<\xb2\xf0\xb9\xc6\xef\xb8\xfd\xa8\rw\x16(%\x89\x05k\xc7^-c\x10\x00\x00\u07d4\u03b0\x89\xec\x8ax3~\x8e\xf8\x8d\xe1\x1bI\xe3\u0751\x0ft\x8f\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u03b3=x\xe7Tz\x9d\xa2\xe8}Q\xae\xc5\xf3D\x1c\x87\x92:\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u03b3\x898\x1dH\xa8\xaeO\xfcH:\u043b^ L\xfd\xb1\xec\x89('\xe6\xe4\xddb\xba\x80\x00\u07d4\xce\xc6\xfce\x85?\x9c\xce_\x8e\x84Fv6.\x15y\x01_\x02\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xce\xd3\u01fe\x8d\xe7XQ@\x95*\xebP\x1d\xc1\xf8v\ucbf0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xce\xd8\x1e\xc3S?\xf1\xbf\xeb\xf3\xe3\x84>\xe7@\xad\x11u\x8d>\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xce\u0733\xa1\u0584?\xb6\xbe\xf6Ca}\xea\U000cf398\xdd_\x89\x19\xe2\xa4\xc8\x18\xb9\x06\x00\x00\u07d4\xce\xe6\x99\xc0pzx6%+)/\x04|\xe8\xad(\x9b/U\x89\x11\x9a\x1e!\xaaiV\x00\x00\u07d4\xce\xedG\xca[\x89\x9f\xd1b?!\xe9\xbdM\xb6Z\x10\u5c1d\x89\a8w@L\x1e\xee\x00\x00\u07d4\xce\xf7tQ\u07e2\xc6C\xe0\v\x15mlo\xf8N#s\xebf\x89\n1\x06+\xee\xedp\x00\x00\u07d4\xcf\x11i\x04\x1c\x17E\xe4[\x17$5\xa2\xfc\x99\xb4\x9a\xce+\x00\x89\x01\xbb\x88\xba\xab-|\x00\x00\xe0\x94\xcf\x15v\x12vN\x0f\u0596\xc8\xcb_\xba\x85\xdfL\r\xdc<\xb0\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u0794\xcf\x1b\xdby\x9b.\xa6<\xe14f\x8b\xdc\x19\x8bT\x84\x0f\x18\v\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xcf\"\x88\xefN\xbf\x88\xe8m\xb1=\x8a\x0e\v\xf5*\x05e\x82\u00c9\x89Po\xbf\x97@t\x00\x00\u07d4\xcf&Ni%\x13\t\x06\xc4\xd7\xc1\x85\x91\xaaA\xb2\xa6\u007foX\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcf&\xb4{\xd04\xbcP\x8elK\xcf\xd6\xc7\xd3\x004\x92Wa\x89a\x94\x04\x9f0\xf7 \x00\x00\xe0\x94\xcf.*\xd65\xe9\x86\x1a\xe9\\\xb9\xba\xfc\xca\x03kR\x81\xf5\u038a\at2!~h6\x00\x00\x00\u07d4\xcf.s@B\xa3U\xd0_\xfb.9\x15\xb1h\x11\xf4Zi^\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcf4\x8f/\xe4{~A<\az{\xaf:u\xfb\xf8B\x86\x92\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xcf?\x91(\xb0r\x03\xa3\xe1\r}WU\xc0\u012b\xc6\xe2\xca\u008a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xcf?\xbf\xa1\xfd2\u05e6\xe0\xe6\xf8\xefN\xabW\xbe4\x02\\L\x899\xa1\xc0\xf7YMH\x00\x00\u07d4\xcfAftn\x1d;\xc1\xf8\xd0qK\x01\xf1~\x8ab\xdf\x14d\x896w\x03n\xdf\n\xf6\x00\x00\u07d4\xcfO\x118\xf1\xbdk\xf5\xb6\u0505\xcc\xe4\xc1\x01\u007f\u02c5\xf0}\x89/\u043cw\xc3+\xff\x00\x00\u07d4\xcfZo\x9d\xf7Uy\xc6D\xf7\x94q\x12\x15\xb3\rw\xa0\xce@\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcf^\x0e\xac\u0473\x9d\x06U\xf2\xf7u5\xeff\b\xeb\x95\v\xa0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcfhM\xfb\x83\x04r\x93U\xb5\x83\x15\xe8\x01\x9b\x1a\xa2\xad\x1b\xac\x89\x17r$\xaa\x84Lr\x00\x00\u07d4\xcfi@\x81\xc7m\x18\xc6L\xa7\x13\x82\xbe\\\xd6;<\xb4v\xf8\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xcfnR\xe6\xb7t\x80\xb1\x86~\xfe\xc6Dm\x9f\xc3\xcc5w\xe8\x89\f\t\x01\xf6\xbd\x98y\x00\x00\u07d4\u03c8: 2\x96g\xea\"j\x1e\x9a\x92*\x12\xf2\x1f\xaa\x03\x81V\x91\x8cO\u02dc\x89\x04E\x91\xd6\u007f\xec\xc8\x00\x00\u07d4\xcf\xf7\xf8\x9aMB\x19\xa3\x82\x95%\x131V\x82\x10\xff\xc1\xc14\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xcf\xf8\xd0k\x00\xe3\xf5\f\x19\x10\x99\xadV\xbaj\xe2eq\u0348\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xcf\xfcI\xc1x~\ubcb5l\xab\xe9$\x04\xb66\x14}EX\x8a\x013\xe00\x8f@\xa3\u0680\x00\u07d4\xd0\bQ;'`J\x89\xba\x17c\xb6\xf8L\u6233F\x94[\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd0\x0f\x06r\x86\xc0\xfb\u0402\xf9\xf4\xa6\x10\x83\xecv\u07b3\xce\xe6\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd0\x15\xf6\xfc\xb8M\xf7\xbbA\x0e\x8c\x8f\x04\x89J\x88\x1d\xca\xc27\x898E$\xccp\xb7x\x00\x00\u07d4\xd0\x1a\xf9\x13O\xafRW\x17N\x8by\x18oB\xee5Nd-\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd0!\b\u04ae<\xab\x10\xcb\xcf\x16W\xaf\">\x02|\x82\x10\xf6\x89lm\x84\xbc\xcd\xd9\xce\x00\x00\u07d4\xd0*\xfe\u03ce.\u00b6*\u022d Aa\xfd\x1f\xaew\x1d\x0e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd01\x919\xfb\xab.\x8e*\xcc\xc1\xd9$\u0531\x1d\xf6ilZ\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd07\xd2\x15\xd1\x1d\x1d\xf3\xd5O\xbd2\x1c\u0495\xc5F^';\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xd0:-\xa4\x1e\x86\x8e\xd3\xfe\xf5t[\x96\xf5\xec\xa4b\xffo\u0689\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94\xd0?\xc1eWj\xae\xd5%\xe5P,\x8e\x14\x0f\x8b.\x86\x969\x8a\x01sV\u0633%\x01\xc8\x00\x00\u07d4\xd0C\xa0\x11\xecBp\xee~\u0239hsu\x15\xe5\x03\xf80(\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd0K\x86\x1b=\x9a\xccV:\x90\x16\x89\x94\x1a\xb1\xe1\x86\x11a\xa2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd0ZD|\x91\x1d\xbb'[\xfb.Z7\xe5\xa7\x03\xa5o\x99\x97\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd0_\xfb+t\xf8g O\xe51e;\x02H\xe2\x1c\x13TN\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd0bX\x81q\u03d9\xbb\xebX\xf1&\xb8p\xf9\xa3r\x8da\xec\x89\xf3\xf2\v\x8d\xfai\xd0\x00\x00\u07d4\xd0c\x8e\xa5q\x89\xa6\xa6\x99\x02J\u05ccq\xd99\xc1\xc2\xff\x8c\x89\x8e\xaeVg\x10\xfc \x00\x00\xe0\x94\xd0d\x8aX\x1b5\b\xe15\xa2\x93]\x12\xc9epE\xd8q\u028a\x01\xb2\u07dd!\x9fW\x98\x00\x00\u07d4\xd0q\x19)f\xebi\xc3R\x0f\xca:\xa4\xdd\x04)~\xa0KN\x89\x05\xf6\x8e\x811\xec\xf8\x00\x00\u07d4\xd0q\x85 \xea\xe0\xa4\xd6-p\xde\x1b\xe0\xcaC\x1c^\xea$\x82\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd0w]\xba*\xf4\xc3\n:x6Y9\xcdq\xc2\xf9\u0795\u0489i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xd0{\xe0\xf9\t\x97\xca\xf9\x03\u022c\x1dS\xcd\xe9\x04\xfb\x19\aA\x8968\x908\xb6\x99\xb4\x00\x00\u07d4\xd0~Q\x18d\xb1\u03d9i\xe3V\x06\x02\x82\x9e2\xfcNq\xf5\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\u0400\x94\x98\xc5H\x04z\x1e**\xa6\xa2\x9c\xd6\x1a\x0e\xe2h\xbd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0402'_tZ,\xac\x02v\xfb\xdb\x02\u0532\xa3\xab\x17\x11\xfe\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4\u040f\xc0\x9a\x000\xfd\t(\xcd2\x11\x98X\x01\x82\xa7j\xae\x9f\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u0413\xe8)\x81\x9f\xd2\xe2[\x978\x00\xbb=XA\xdd\x15-\x05\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u0414J\xa1\x85\xa13pa\xae \u071d\xd9l\x83\xb2\xbaF\x02\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\u0416V[|t\a\xd0e6X\x03U\xfd\xd6\xd29\x14J\xa1\x89\r\x8drkqw\xa8\x00\x00\u07d4\u041c\xb2\xe6\b-i:\x13\xe8\xd2\xf6\x8d\xd1\u0744a\xf5X@\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u0426\xc6\xf9\xe9\u0133\x83\xd7\x16\xb3\x1d\xe7\x8dVAM\xe8\xfa\x91\x89\x10CV\x1a\x88)0\x00\x00\u07d4\u0427 \x9b\x80\xcf`\xdbb\xf5}\n]}R\x1ai`fU\x89\b\xacr0H\x9e\x80\x00\x00\xe0\x94\u0428\xab\xd8\n\x19\x9bT\xb0\x8be\xf0\x1d \x9c'\xfe\xf0\x11[\x8a\x01a\xc6&\xdca\xa2\xef\x80\x00\xe0\x94\u042b\xccp\xc0B\x0e\x0e\x17/\x97\xd4;\x87\xd5\xe8\f3n\xa9\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u042es]\x91^\x94hf\xe1\xfe\xa7~^\xa4f\xb5\xca\xdd\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0431\x1do+\u0394^\fjP \u00f5'S\xf8\x03\xf9\u0449\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xd0\xc1\x01\xfd\x1f\x01\xc6?k\x1d\x19\xbc\x92\r\x9f\x93#\x14\xb16\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xd0\xc5Z\xbf\x97o\xdc=\xb2\xaf\u9f99\u0519HMWl\x02\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd0\u0422\xadE\xf5\x9a\x9d\xcc\u0195\xd8_%\xcaF\xed1\xa5\xa3\x89-\x89W}}@ \x00\x00\u07d4\xd0\xd6,G\xea`\xfb\x90\xa3c\x92\t\xbb\xfd\xd4\xd93\x99\x1c\u0189\n\x84Jt$\xd9\xc8\x00\x00\u07d4\xd0\xdbEax o\\D0\xfe\x00Pc\x90<=zI\xa7\x89&I\x1eE\xa7S\xc0\x80\x00\u07d4\xd0\xe1\x94\xf3K\x1d\xb6\t(\x85\t\xcc\xd2\xe7;a1\xa2S\x8b\x8965f3\xeb\xd8\xea\x00\x00\u07d4\xd0\xe3^\x04vF\xe7Y\xf4Qp\x93\xd6@\x86BQ\u007f\bM\x89\u054f\xa4h\x18\xec\u02c0\x00\u07d4\xd0\xeeM\x02\xcf$8,0\x90\xd3\xe9\x95`\xde6xs\\\u07c9\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4\xd0\xf0OR\x10\x9a\xeb\xec\x9a{\x1e\x932v\x1e\x9f\xe2\xb9{\xb5\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd0\xf9Yx\x11\xb0\xb9\x92\xbb}7W\xaa%\xb4\xc2V\x1d2\xe2\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd1\x03\x02\xfa\xa1\x92\x9a2i\x04\xd3v\xbf\v\x8d\xc9:\xd0LL\x89a\t=|,m8\x00\x00\xe0\x94\xd1\x10\r\xd0\x0f\xe2\xdd\xf1\x81c\xad\x96M\vi\xf1\xf2\xe9e\x8a\x8a\x01C\x12\tU\xb2Pk\x00\x00\u07d4\xd1\x16\xf3\xdc\xd5\xdbtK\xd0\b\x88v\x87\xaa\x0e\xc9\xfdr\x92\xaa\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd1\x19A|Fs,\xf3M\x1a\x1a\xfby\xc3\xe7\xe2\u034e\xec\xe4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd1-w\xae\x01\xa9-5\x11{\xacpZ\xac\u0642\xd0.t\xc1\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xd15yK\x14\x9a\x18\xe1G\xd1nb\x1ai1\xf0\xa4\n\x96\x9a\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xd1C%8\xe3[vd\x95j\u4563*\xbd\xf0A\xa7\xa2\x1c\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4\xd1C\x82g#\x17\x04\xfcr\x80\xd5c\xad\xf4v8D\xa8\a\"\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd1S\x8e\x9a\x87\u5729\xec\x8eX&\xa5\xb7\x93\xf9\x9f\x96\xc4\u00c965\u026d\xc5\u07a0\x00\x00\xe0\x94\xd1d\x85\x03\xb1\xcc\u0178\xbe\x03\xfa\x1e\xc4\xf3\xee&~j\xdf{\x8a\x01;\xef\xbfQ\xee\xc0\x90\x00\x00\xe0\x94\xd1h,!Y\x01\x8d\xc3\xd0\u007f\b$\n\x8c`m\xafe\xf8\xe1\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\xd1q\xc3\xf2%\x8a\xef5\xe5\x99\xc7\xda\x1a\xa0s\x00#M\xa9\xa6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd1w\x8c\x13\xfb\xd9h\xbc\b<\xb7\xd1\x02O\xfe\x1fI\xd0,\xaa\x89\xd9\xec\xb4\xfd \x8eP\x00\x00\u07d4\xd1\u007f\xbe\"\xd9\x04b\xed7(\x06p\xa2\xea\v0\x86\xa0\xd6\u0589\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4\u0441\x1cU\x97i\x80\xf0\x83\x90\x1d\x8a\r\xb2i\"-\xfb\\\xfe\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\u044e\xb9\xe1\u0485\u06be\x93\xe5\u053a\xe7k\xee\xfeC\xb5!\xe8\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\u0453\xe5\x83\xd6\a\x05c\xe7\xb8b\xb9aJG\u9509\xf3\xe5\x8965f3\xeb\xd8\xea\x00\x00\u07d4\u0457\x8f.4@\u007f\xab\x1d\xc2\x18=\x95\xcf\xdab`\xb3Y\x82\x89*\xb7\xb2`\xff?\xd0\x00\x00\u07d4\u045c\xaf9\xbb7\u007f\xdf,\xf1\x9b\xd4\xfbRY\x1c&1\xa6<\x8965\u026d\xc5\u07a0\x00\x00\u0794\u0463\x96\xdc\u06b2\xc7IA0\xb3\xfd0x 4\r\xfd\x8c\x1f\x88\xf9\"P\xe2\xdf\xd0\x00\x00\xe0\x94\u0467\x1b-\bX\xe82p\b]\x95\xa3\xb1T\x96P\x03^#\x8a\x03'\xbb\t\xd0j\xa8P\x00\x00\u07d4\u046c\xb5\xad\xc1\x189s%\x8dk\x85$\xff\xa2\x8f\xfe\xb2=\xe3\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u0473\u007f\x03\xcb\x10t$\xe9\xc4\xddW\\\xcdOL\xeeW\xe6\u0349lk\x93[\x8b\xbd@\x00\x00\u07d4\u0475\xa4T\xac4\x05\xbbAy \x8cl\x84\xde\x00k\u02db\xe9\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd1\xc4YT\xa6+\x91\x1a\xd7\x01\xff.\x90\x13\x1e\x8c\xeb\x89\xc9\\\x89K\x91\xa2\xdeE~\x88\x00\x00\u07d4\xd1\xc9np\xf0Z\xe0\xe6\xcd`!\xb2\b7P\xa7q|\xdeV\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd1\u0571\u007f\xfe-{\xbby\xcc}y0\xbc\xb2\xe5\x18\xfb\x1b\xbf\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xd1\xda\f\x8f\xb7\xc2\x10\xe0\xf2\xeca\x8f\x85\xbd\xae}>sK\x1c\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xd1\xddy\xfb\x15\x81`\xe5\xb4\xe8\xe2?1.j\x90\u007f\xbcMN\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd1\xdeZ\xad:_\xd8\x03\U00071bb6\x10<\xb8\xe1O\xe7#\xb7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd1\xe1\xf2\xb9\xc1l0\x98t\xde\xe7\xfa\xc3&u\xaf\xf1)\u00d8\x89\x03\xf2M\x8eJ\x00p\x00\x00\xe0\x94\xd1\xe5\xe24\xa9\xf4Bf\xa4\xa6$\x1a\x84\u05e1\xa5Z\u0567\xfe\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xd1\xeaMr\xa6{[>\x0f1UY\xf5+\xd0aMq0i\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd1\xee\x90YW\xfe|\xc7\x0e\xc8\xf2\x86\x8bC\xfeG\xb1?\xeb\xff\x89\x02b\x9ff\xe0\xc50\x00\x00\u07d4\xd1\xf1iM\"g\x1bZ\xadj\x94\x99\\6\x9f\xbea3go\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd1\xf4\xdc\x1d\u06ca\xbb\x88H\xa8\xb1N%\xf3\xb5Z\x85\x91\xc2f\x89\r\x8drkqw\xa8\x00\x00\u07d4\xd1\xfe\u042e\xe6\xf5\xdf\xd7\xe2Wi%L<\xfa\xd1Z\xde\u032a\x89'\x92\xc8\xfcKS(\x00\x00\u07d4\xd2\x05\x1c\xb3\xcbg\x04\xf0T\x8c\u0210\xab\n\x19\xdb4\x15\xb4*\x89\x12\x1b.^ddx\x00\x00\u07d4\xd2\x06\xaa\u07736\xd4^yr\xe9<\xb0uG\x1d\x15\x89{]\x89 \x86\xac5\x10R`\x00\x00\u07d4\xd2\tH+\xb5I\xab\xc4w{\xeam\u007fe\x00b\xc9\xc5z\x1c\x89\x11e\x1a\xc3\xe7\xa7X\x00\x00\u07d4\xd2\r\xcb\vxh+\x94\xbc0\x00(\x14H\xd5W\xa2\v\xfc\x83\x890\x84\x9e\xbe\x166\x9c\x00\x00\u07d4\xd2\x10{57&\u00e2\xb4ef\xea\xa7\xd9\xf8\v]!\xdb\xe3\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd2\x11\xb2\x1f\x1b\x12\xb5\ta\x81Y\r\xe0~\xf8\x1a\x89S~\xad\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd2\x18\xef\xb4\u06d8\x1c\xddjy\u007fK\u050c|&)<\xeb@\x89\xa1Fk1\xc6C\x1c\x00\x00\xe0\x94\xd2\x1asA\xeb\x84\xfd\x15\x10T\xe5\u31fb%\xd3nI\x9c\t\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\xe0\x94\xd2$\xf8\x80\xf9G\x9a\x89\xd3/\t\xe5+\u9432\x88\x13\\\xef\x8a\x03\xa9\u057a\xa4\xab\xf1\xd0\x00\x00\u07d4\xd2/\f\xa4\xcdG\x9ef\x17u\x05;\xccI\xe3\x90\xf6p\u074a\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd21\x92\x975\x13!\x02G\x1b\xa5\x90\a\xb6dL\xc0\xc1\xde>\x8967\tlK\xcci\x00\x00\u07d4\xd25\xd1\\\xb5\xec\xee\xbba)\x9e\x0e\x82\u007f\xa8'H\x91\x1d\x89\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd2:$\xd7\xf9F\x83C\xc1C\xa4\x1ds\xb8\x8f|\xbec\xbe^\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd2=z\xff\xac\xdc>\x9f=\xaez\xfc\xb4\x00oX\xf8\xa4F\x00\x89\xc3(\t>a\xee@\x00\x00\u07d4\xd2C\x18L\x80\x1e]y\xd2\x06?5x\u06ee\x81\u7ce9\u02c9k\u0722h\x1e\x1a\xba\x00\x00\u07d4\xd2KfD\xf49\xc8\x05\x1d\xfcd\u04c1\xb8\xc8lu\xc1u8\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xd2K\xf1--\xdfE}\xec\xb1xt\xef\xde R\xb6\\\xbbI\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\xe0\x94\xd2Q\xf9\x03\xae\x18rrY\xee\xe8A\xa1\x89\xa1\xf5i\xa5\xfdv\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xd2R\x96\v\v\xf6\xb2\x84\x8f\u07ad\x80\x13m\xb5\xf5\a\xf8\xbe\x02\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xd2X\x1aU\xce#\xab\x10\u062d\x8cD7\x8fY\a\x9b\xd6\xf6X\x8a\x01\xdd\f\x88_\x9a\r\x80\x00\x00\u07d4\xd2Z\xec\xd7\xeb\x8b\xd64[\x06;]\xbd'\x1cw\xd3QD\x94\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xd2|#O\xf7\xac\xca\xce=\x99g\b\xf8\xf9\xb0Ip\xf9}6\x89Hz\x9a0E9D\x00\x00\u07d4\u0482\x98RM\xf5\xecK$\xb0\xff\xb9\u07c5\x17\n\x14Z\x9e\xb5\x89\x0f\x98\xa3\xb9\xb37\xe2\x00\x00\xe0\x94\u0483\xb8\xed\xb1\n%R\x8aD\x04\xde\x1ce\xe7A\r\xbc\xaag\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\u0484\xa5\x03\x82\xf8:am9\xb8\xa9\xc0\xf3\x96\xe0\ubfe9]\x8966\xc2^f\xec\xe7\x00\x00\u07d4\u0488\xe7\xcb{\xa9\xf6 \xab\x0ftR\xe5\bc=\x1cZ\xa2v\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\u049d\xc0\x8e\xfb\xb3\xd7.&?x\xabv\x10\xd0\"m\xe7k\x00\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\u04a00\xac\x89R2_\x9e\x1d\xb3x\xa7\x14\x85\xa2N\x1b\a\xb2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u04a4y@CG\xc5T:\xab)*\xe1\xbbJo\x15\x83W\xfa\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\u04a5\xa0$#\nW\xcc\xc6fv\v\x89\xb0\xe2l\xaf\u0449\u01ca\n\x96YZ\\n\x8a?\x80\x00\u07d4\u04a8\x03'\xcb\xe5\\L{\xd5\x1f\xf9\xdd\xe4\xcad\x8f\x9e\xb3\xf8\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\u04a8Oug\\b\xd8\f\x88ulB\x8e\xee+\xcb\x18T!\x89A\rXj \xa4\xc0\x00\x00\u07d4\u04ab\xd8J\x18\x10\x93\xe5\xe2)\x13oB\xd85\xe8#]\xe1\t\x89\x05k\xe0<\xa3\xe4}\x80\x00\u07d4\u04ac\r:X`^\x1d\x0f\x0e\xb3\xde%\xb2\xca\xd1)\xed`X\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u04bfg\xa7\xf3\xc6\xceV\xb7\xbeAg]\xbb\xad\xfe~\xa9:3\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xd2\xdb\xeb\xe8\x9b\x03W\xae\xa9\x8b\xbe\x8eIc8\u07bb(\xe8\x05\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd2\xe2\x1e\xd5hh\xfa\xb2\x8e\tG\x92z\xda\xf2\x9f#\xeb\xadl\x89l\x18O\x13U\xd0\xe8\x00\x00\u07d4\xd2\xe8\x17s\x8a\xbf\x1f\xb4\x86X?\x80\xc3P1\x8b\xed\x86\f\x80\x89\r\x02\xce\xcf_]\x81\x00\x00\u07d4\xd2\xed\xd1\xdd\xd6\xd8m\xc0\x05\xba\xebT\x1d\"\xb6@\xd5\xc7\xca\xe5\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd2\xf1\x99\x8e\x1c\xb1X\f\xecOl\x04}\xcd=\xce\xc5L\xf7<\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd2\xf2A%]\xd7\xc3\xf7<\a\x040q\xec\b\xdd\xd9\xc5\xcd\xe5\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd2\xffg \x16\xf6;/\x859\x8fJo\xed\xbb`\xa5\r<\u0389\x12\x91$o[sJ\x00\x00\u07d4\xd3\rLC\xad\xcfU\xb2\xcbS\u0583#&A4I\x8d\x89\u038965\u026d\xc5\u07a0\x00\x00\u07d4\xd3\x0e\xe9\xa1+Mh\xab\xac\xe6\xba\u029a\u05ff\\\xd1\xfa\xf9\x1c\x89QO\xcb$\xff\x9cP\x00\x00\u07d4\xd3\x11\x8e\xa3\xc85\x05\xa9\u0613\xbbg\xe2\xde\x14-Sz>\xe7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd3\x11\xbc\u05eaN\x9bO8?\xf3\xd0\u05b6\xe0~!\xe3p]\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd3\x15\xde\xea\x1d\x8c\x12q\xf9\xd11\x12c\xabG\xc0\a\xaf\xb6\xf5\x89\x03\xc8\x1dNeK@\x00\x00\u07d4\xd3+,y\xc3dx\xc5C\x19\x01\xf6\xd7\x00\xb0M\xbe\x9b\x88\x10\x89\x15w\x9a\x9d\xe6\xee\xb0\x00\x00\u07d4\xd3+EVF\x14Ql\x91\xb0\u007f\xa9\xf7-\xcfx|\xceN\x1c\x89\x0f\xc6o\xae7F\xac\x00\x00\u07d4\xd30r\x811\xfe\x8e:\x15Hz4W<\x93E~*\xfe\x95\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd31\xc8#\x82Z\x9eRc\xd0R\u0611]M\xcd\xe0z\\7\x89\x1e\x93\x12\x83\xcc\xc8P\x00\x00\u07d4\xd33btE\xf2\u05c7\x90\x1e\xf3;\xb2\xa8\xa3g^'\xff\xec\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xd3<\xf8+\xf1LY&@\xa0\x86\b\x91L#py\u057e4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd3Mp\x8ds\x98\x02E3\xa5\xa2\xb20\x9b\x19\xd3\xc5Qq\xbb\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xd3N\x03\xd3j+\xd4\u045a_\xa1b\x18\xd1\xd6\x1e?\xfa\v\x15\x89\x11X\xe4`\x91=\x00\x00\x00\u07d4\xd3Pu\xcaa\xfeY\xd1#\x96\x9c6\xa8-\x1a\xb2\xd9\x18\xaa8\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4\xd3g\x00\x9a\xb6X&;b\xc23:\x1c\x9eA@I\x8e\x13\x89\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd3g\x9aG\xdf-\x99\xa4\x9b\x01\u024d\x1c>\f\x98|\xe1\xe1X\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\xe0\x94\u04cf\xa2\xc4\xcc\x14z\xd0j\u0562\xf7Uy(\x1f\"\xa7\xcc\x1f\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\u04da]\xa4`9+\x94\v\u01ee8\xf1e\u007f\x8a\x01f\xc5H\b\x89\xdbw\x00\x00\xe0\x94\xd3\xd6\xe9\xfb\x82T/\u049e\xd9\xea6\t\x89\x1e\x15\x13\x96\xb6\xf7\x8a\voX\x8a\xa7\xbc\xf5\xc0\x00\x00\xe0\x94\xd3\xda\u0476\u040dE\x81\u032ee\xa8s-\xb6\xaci\xf0\u019e\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xd3\xdf;S\xcb;GU\xdeT\xe1\x80E\x1c\xc4L\x9e\x8a\u0a89#\u0114\t\xb9w\x82\x80\x00\u07d4\xd3\xf8s\xbd\x99V\x13W\x89\xab\x00\xeb\xc1\x95\xb9\"\xe9K%\x9d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd4\x02\xb4\xf6\xa0\x99\xeb\xe7\x16\xcb\x14\xdfOy\xc0\xcd\x01\xc6\a\x1b\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xd4\r\x00U\xfd\x9a8H\x8a\xff\x92?\xd0=5\xecF\xd7\x11\xb3\x8a\x01\x0f\b\xed\xa8\xe5U\t\x80\x00\u07d4\xd4\x0e\xd6j\xb3\xce\xff$\xca\x05\xec\xd4q\ufd12\xc1__\xfa\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd4\x18\x87\v\xc2\xe4\xfa{\x8aa!\xae\br\xd5RG\xb6%\x01\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xd4\x1d\u007f\xb4\x9f\xe7\x01\xba\xac%qpBl\u0273\x8c\xa3\xa9\xb2\x89\t\x8a}\x9b\x83\x14\xc0\x00\x00\u07d4\xd4 U\x92\x84@U\xb3\u01e1\xf8\f\xef\xe3\xb8\xebP\x9b\xcd\xe7\x89\t\xb3\xbf\xd3B\xa9\xfc\x80\x00\u07d4\xd4+ \xbd\x03\x11`\x8bf\xf8\xa6\xd1[*\x95\xe6\xde'\u017f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd44O}\\\xade\xd1~\\-\x0es#\x94=ob\xfe\x92\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4\xd4>\xe48\xd8=\xe9\xa3ub\xbbN(l\xb1\xbd\x19\xf4\x96M\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd4C4\xb4\xe2:\x16\x9a\f\x16\xbd!\xe8f\xbb\xa5-\x97\x05\x87\x89\x8c\xf2?\x90\x9c\x0f\xa0\x00\x00\xe0\x94\xd4M\x81\xe1\x8fF\xe2\u03f5\xc1\xfc\xf5\x04\x1b\xc8V\x97g\xd1\x00\x8a\a\xb4B\xe6\x84\xf6Z\xa4\x00\x00\u07d4\xd4OJ\xc5\xfa\xd7k\xdc\x157\xa3\xb3\xafdr1\x9bA\r\x9d\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\xd4O^\xdf+\xcf$3\xf2\x11\xda\xdd\f\xc4P\xdb\x1b\x00\x8e\x14\x89\x0e~\xeb\xa3A\vt\x00\x00\xe0\x94\xd4Oj\u00d2;_\xd71\xa4\xc4YD\xecO~\xc5*j\xe4\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xd4[3A\xe8\xf1\\\x802\x93 \u00d7~;\x90\xe7\x82j~\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd4]]\xaa\x13\x8d\xd1\xd3t\xc7\x1b\x90\x19\x91h\x11\xf4\xb2\nN\x89\x1f9\x9b\x148\xa1\x00\x00\x00\u07d4\xd4`\xa4\xb9\b\xdd+\x05gY\xb4\x88\x85\vf\xa88\xfcw\xa8\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xd4g\xcf\x06L\bq\x98\x9b\x90\u0632\xeb\x14\xcc\xc6;6\b#\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd4k\xaea\xb0'\xe5\xbbB.\x83\xa3\xf9\xc9?<\x8f\xc7}'\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd4o\x82#E)\x82\xa1\xee\xa0\x19\xa8\x81n\xfc-o\xc0\ah\x89\amA\xc6$\x94\x84\x00\x00\u07d4\xd4uG\u007f\xa5c\x90\xd30\x17Q\x8dg\x11\x02\u007f\x05\U0008dfc9k\x11\x133\xd4\xfdL\x00\x00\u07d4\xd4|$.\xdf\xfe\xa0\x91\xbcT\xd5}\xf5\xd1\xfd\xb91\x01Gl\x89\x9d\xf7\u07e8\xf7`H\x00\x00\u07d4\xd4}\x86\x85\xfa\xee\x14|R\x0f\u0646p\x91u\xbf/\x88k\xef\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd4\u007fP\u07c9\xa1\xcf\xf9e\x13\xbe\xf1\xb2\xae:)q\xac\xcf,\x89-\x89W}}@ \x00\x00\u07d4\u0502\xe7\xf6\x8eA\xf28\xfeQx)\xde\x15G\u007f\xe0\xf6\xdd\x1d\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\u0507\x9f\xd1+\x1f:'\xf7\xe1\tv\x1b#\xca4\xfa#\x06K\x1c\xaf\x00Qn(pJ\x82\xa4\xf8\x89Hz\x9a0E9D\x00\x00\u07d4\xd5\x00\xe4\xd1\u0242K\xa9\xf5\xb65\u03e3\xa8\xc2\u00cb\xbdL\xed\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xd5\b\u04dcp\x91oj\xbcL\xc7\xf9\x99\xf0\x11\xf0w\x10X\x02\x89\x05rM$\xaf\xe7\u007f\x00\x00\u07d4\xd5\x0f\u007f\xa0>8\x98v\u04d0\x8b`\xa57\xa6pc\x04\xfbV\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xd5\x13\xa4P\x80\xff/\xeb\xe6,\u0545J\xbe)\xeeDg\xf9\x96\x89\bN\x13\xbcO\xc5\xd8\x00\x00\u07d4\xd5'o\f\xd5\xff\xd5\xff\xb6?\x98\xb5p=U\x94\xed\xe0\x83\x8b\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xd5)KfbB0;m\xf0\xb1\u020d7B\x9b\xc8\xc9e\xaa\x89\x10M\r\x00\u04b7\xf6\x00\x00\u07d4\xd5*\xec\xc6I98\xa2\x8c\xa1\xc3g\xb7\x01\xc2\x15\x98\xb6\xa0.\x89;\xa1\x91\v\xf3A\xb0\x00\x00\u07d4\xd5\x99x\xee \xa3\x8c?I\x8dc\xd5\u007f1\xa3\x9fj\x06\x8a\x022\xb3o\xfcg*\xb0\x00\x00\u07d4\u05568\xd3\xc5\xfa\xa7q\x1b\xf0\x85t_\x9d[\xdc#\u0518\u0609lk\x93[\x8b\xbd@\x00\x00\xe0\x94\u055d\x92\xd2\xc8p\x19\x80\xcc\a<7]r\n\xf0dt<\f\x8a\x04\x05\xfd\xf7\u5bc5\xe0\x00\x00\u07d4\u0567\xbe\xc32\xad\xde\x18\xb3\x10KW\x92Tj\xa5\x9b\x87\x9bR\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0571\x17\xec\x11n\xb8FA\x89a\xeb~\xdbb\x9c\xd0\xddi\u007f\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\u0572\x84\x04\x010\xab\xf7\xc1\xd1cq#q\xcc~(\xadf\u0689j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\u0579\xd2w\u062a\xd2\x06\x97\xa5\x1fv\xe2\tx\x99k\xff\xe0U\x89\a\xc3\xfe<\aj\xb5\x00\x00\u07d4\u057d^\x84U\xc10\x16\x93W\xc4q\xe3\u6077\x99jrv\x89-\x9e(\x8f\x8a\xbb6\x00\x00\u07d4\xd5\u02e5\xb2k\xea]s\xfa\xbb\x1a\xba\xfa\xcd\xef\x85\xde\xf3h\u0309\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd5\xceU\u0476/YC\xc0?\x89\b\xe3\x1f\xe1h\x9d\x8a\x00\x00\u07d4\xd6\x06Q\xe3\x93x4#\xe5\xcc\x1b\xc5\xf8\x89\xe4N\xf7\xea$>\x89\x15\x9ev7\x11)\xc8\x00\x00\u07d4\xd6\t\xbfO\x14n\xeak\r\xc8\xe0m\xdc\xf4D\x8a\x1f\xcc\xc9\xfa\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd6\t\xec\v\xe7\r\n\xd2ong\xc9\xd4v+R\xeeQ\x12,\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd6\nRX\a(R\r\xf7Tk\xc1\xe2\x83)\x17\x88\u06ee\f\x8964\x89\xef?\xf0\xd7\x00\x00\u07d4\xd6\v$s!\xa3*Z\xff\xb9k\x1e'\x99'\xccXM\xe9C\x89z\xd0 \xd6\xdd\xd7v\x00\x00\u07d4\xd6\x11\x02v\xcf\xe3\x1eB\x82ZW\u007fkC]\xbc\xc1\f\xf7d\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xd6\x12Y{\xc3\x17C\u01c63\xf63\xf29\xb1\xe9Bk\xd9%\x8a\x10\x17\xf7\u07d6\xbe\x17\x80\x00\x00\u07d4\xd6#J\xafE\xc6\xf2.f\xa2%\xff\xb9:\xddb\x9bN\xf8\x0f\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd6.\u06d6\xfc\u259a\xaflT^\x96|\xf1\xc0\xbc\x80R\x05\x89\x04\xa5eSjZ\u0680\x00\u07d4\xd60\v2\x15\xb1\x1d\xe7b\xec\xdeKp\xb7\x92}\x01)\x15\x82\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd69]\xb5\xa4\xbbf\xe6\x0fL\xfb\xcd\xf0\x05{\xb4\xd9xb\xe2\x891T\xc9r\x9d\x05x\x00\x00\xe0\x94\xd6J-P\xf8\x85\x857\x18\x8a$\xe0\xf5\r\xf1h\x1a\xb0~\u05ca\b7Z*\xbc\xca$@\x00\x00\u07d4\xd6X\n\xb5\xedL}\xfaPo\xa6\xfed\xad\\\xe1)pw2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd6Y\x8b\x13\x86\xe9<\\\u02d6\x02\xffK\xbb\xec\xdb\xd3p\x1d\u0109\f%\xf4\xec\xb0A\xf0\x00\x00\u07d4\xd6dM@\xe9\v\xc9\u007f\xe7\xdf\xe7\u02bd2i\xfdW\x9b\xa4\xb3\x89\b\x9e\x91y\x94\xf7\x1c\x00\x00\xe0\x94\xd6g\f\x03m\xf7T\xbeC\xda\u074fP\xfe\xea(\x9d\x06\x1f\u058a\x01D\xa2\x904H\xce\xf7\x80\x00\u07d4\xd6hR:\x90\xf0)=e\xc58\xd2\xddlWg7\x10\x19n\x89\x02$,0\xb8S\xee\x00\x00\u07d4\xd6j\xb7\x92\x94\aL\x8bb}\x84-\xabA\xe1}\xd7\f]\xe5\x8965\u026d\xc5\u07a0\x00\x00\u0794\xd6j\xcc\r\x11\xb6\x89\u03a6\xd9\xea_\xf4\x01L\"J]\xc7\u0108\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xd6m\xdf\x11Y\xcf\"\xfd\x8czK\xc8\u0540wV\xd43\xc4>\x89wC\"\x17\xe6\x83`\x00\x00\u07d4\u0587\xce\xc0\x05\x90\x87\xfd\xc7\x13\xd4\xd2\xd6^w\xda\xef\xed\xc1_\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\u0588\xe7\x85\u024f\x00\xf8K:\xa1S3U\u01e2X\xe8yH\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\u05a2.Y\x8d\xab\u04ce\xa6\xe9X\xbdy\u050d\u0756\x04\xf4\u07c965\u026d\xc5\u07a0\x00\x00\u07d4\u05a7\xacM\xe7\xb5\x10\xf0\xe8\xdeQ\x9d\x97?\xa4\xc0\x1b\xa84\x00\x89e\xea=\xb7UF`\x00\x00\u07d4\u05ac\xc2 \xba.Q\xdf\xcf!\xd4C6\x1e\xeav\\\xbd5\u0609\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u05ac\xff\u043f\u065c8.{\xd5o\xf0\xe6\x14J\x9eR\xb0\x8e\x89\b\xacr0H\x9e\x80\x00\x00\u07d4\xd6\xc0\u043c\x93\xa6.%qtp\x0e\x10\xf0$\u0232?\x1f\x87\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xd6\xcf\\\x1b\u03dd\xa6b\xbc\xea\"U\x90P\x99\xf9\xd6\xe8M\u030a\x01\u011eB\x01W\xd9\xc2\x00\x00\u07d4\xd6\xd05r\xa4RE\xdb\xd46\x8cO\x82\xc9W\x14\xbd!g\xe2\x89?\x00\xc3\xd6f\x86\xfc\x00\x00\u07d4\xd6\xd6wiX\xee#\x14:\x81\xad\xad\xeb\b8 \t\xe9\x96\u0089\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xd6\xd9\xe3\x0f\bB\x01*qv\xa9\x17\xd9\xd2\x04\x8c\xa0s\x87Y\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd6\xe0\x9e\x98\xfe\x13\x003!\x04\xc1\xca4\xfb\xfa\xc5T6N\u0649lk\x93[\x8b\xbd@\x00\x00\u07d4\xd6\xe8\xe9z\u90db\x9e\xe5\a\xee\xdb(\xed\xfbtw\x03\x149\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd6\uea18\u052e+q\x80'\xa1\x9c\xe9\xa5\xebs\x00\xab\xe3\u0289\x01}J\xce\xeec\u06c0\x00\xe0\x94\xd6\xf1\xe5[\x16\x94\b\x9e\xbc\xb4\xfe}x\x82\xaaf\u0217av\x8a\x04<#\xbd\xbe\x92\x9d\xb3\x00\x00\u07d4\xd6\xf4\xa7\xd0N\x8f\xaf \xe8\xc6\ub15c\xf7\xf7\x8d\xd2=z\x15\x89\a$\xde\xd1\xc7H\x14\x00\x00\u07d4\xd6\xfc\x04F\u01a8\xd4\n\xe3U\x1d\xb7\xe7\x01\xd1\xfa\x87nJI\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd7\x03\u01a4\xf1\x1d`\x19Ey\u054c'f\xa7\xef\x16\xc3\n)\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd7\x05%\x19uj\xf4%\x90\xf1S\x91\xb7#\xa0?\xa5d\xa9Q\x89\xfa61H\r\x01\xfd\x80\x00\u07d4\xd7\na+\xd6\u0769\xea\xb0\xdd\xdc\xffJ\xafA\"\u04cf\xea\xe4\x89\x1dF\x01b\xf5\x16\xf0\x00\x00\u07d4\xd7\n\xd2\xc4\xe9\uefe67\xefV\xbdHj\u04a1\xe5\xbc\xe0\x93\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd7\x14\f\x8eZC\a\xfa\xb0\xcc'\xba\u0752\x95\x01\x8b\xf8yp\x89\x05\xf1\x01kPv\xd0\x00\x00\u07d4\xd7\x16J\xa2a\xc0\x9a\u0672\xb5\x06\x8dE>\xd8\xebj\xa10\x83\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xd7\x1eC\xa4Qw\xadQ\xcb\xe0\xf7!\x84\xa5\xcbP9\x17(Z\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd7\x1f\xb10\xf0\x15\fVRi\xe0\x0e\xfbC\x90+R\xa4U\xa6\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd7\"W8\xdc\xf3W\x848\xf8\xe7\u0233\x83~B\xe0J&/\x89\x18+\x8c\ubec3\xaa\x00\x00\u07d4\xd7'MP\x80M\x9cw\u0693\xfaH\x01V\xef\xe5{\xa5\x01\u0789i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xd71\xbbk_<79^\t\u03ac\xcd\x14\xa9\x18\xa6\x06\a\x89\x89\u0556{\xe4\xfc?\x10\x00\x00\xe0\x94\xd7>\xd2\u0645\xb5\xf2\x1bU\xb2td;\xc6\xda\x03\x1d\x8e\u074d\x8a\nm\xd9\f\xaeQ\x14H\x00\x00\u07d4\xd7D\xac~S\x10\xbeijc\xb0\x03\xc4\v\xd097\x05a\u0189Z\x87\xe7\xd7\xf5\xf6X\x00\x00\xe0\x94\xd7Jn\x8dj\xab4\u0385\x97h\x14\xc12{\xd6\xea\a\x84\u048a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xd7ZP*[gr\x87G\x0fe\u016aQ\xb8|\x10\x15\x05r\x8910\xb4dc\x85t\x00\x00\u07d4\xd7m\xba\xeb\xc3\rN\xf6{\x03\xe6\xe6\xec\xc6\xd8N\x00MP-\x89mv\xb9\x18\x8e\x13\x85\x00\x00\u07d4\xd7q\xd9\xe0\u028a\b\xa1\x13wW1CN\xb3'\x05\x99\xc4\r\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xd7x\x8e\xf2\x86X\xaa\x06\xccS\xe1\xf3\xf0\xdeX\xe5\xc3q\xbex\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\xd7x\x92\xe2';#]v\x89\xe40\xe7\xae\ud73c\xe8\xa1\xf3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u05c1\xf7\xfc\t\x18F\x11V\x85p\xb4\x98n,r\x87+~\u0409\x01\x15\x95a\x06]]\x00\x00\u07d4\u05c5\xa8\xf1\x8c8\xb9\xbcO\xfb\x9b\x8f\xa8\xc7r{\xd6B\xee\x1c\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u05ce\xcd%\xad\xc8k\xc2\x05\x1d\x96\xf6Sd\x86kB\xa4&\xb7\x89\xd20X\xbf/&\x12\x00\x00\xe0\x94\u05cf\x84\xe3\x89D\xa0\xe0%_\xae\xceH\xbaIP\u053d9\u048a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\u05d4\x83\xf6\xa8DO%I\xd6\x11\xaf\xe0,C-\x15\xe1\x10Q\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u05d85\xe4\x04\xfb\x86\xbf\x84_\xba\t\rk\xa2^\f\x88f\xa6\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4\u05da\xff\x13\xba-\xa7]F$\f\xac\n$g\xc6V\x94\x98#\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\u05dd\xb5\xabCb\x1az=\xa7\x95\xe5\x89)\xf3\xdd%\xafg\u0649lj\xccg\u05f1\xd4\x00\x00\u07d4\u05e1C\x1e\xe4S\xd1\xe4\x9a\x05P\xd1%hy\xb4\xf5\xd1\x02\x01\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4\u05ed\t\xc6\xd3&WhSU\xb5\xc6\uc39fW\xb4\ube42\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\u05f7@\xdf\xf8\xc4Wf\x8f\xdft\xf6\xa2f\xbf\xc1\u0737#\xf9\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xd7\u0080>\u05f0\xe0\x83sQA\x1a\x8ef7\xd1h\xbc[\x05\x8a\x06A\xda\xf5\xc9\x1b\xd95\x80\x00\u07d4\xd7\xc6&]\xea\x11\x87l\x90;q\x8eL\u062b$\xfe&[\u0789lk\x93[\x8b\xbd@\x00\x00\u07d4\xd7\xca\u007f\xdc\xfe\xbeE\x88\xef\xf5B\x1d\x15\"\xb6\x13(\xdf{\xf3\x89\xd8\xe6\x00\x1el0+\x00\x00\u07d4\xd7\u037dA\xff\xf2\r\xf7'\xc7\vbU\xc1\xbav\x06\x05Th\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd7\xd1W\xe4\xc0\xa9d7\xa6\u0485t\x1d\xd2>\xc46\x1f\xa3k\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd7\xd2\xc6\xfc\xa8\xad\x1fu9R\x10\xb5}\xe5\xdf\xd6s\x939\t\x89\x12nr\xa6\x9aP\xd0\x00\x00\xe0\x94\xd7\xd3\xc7Y Y\x048\xb8,>\x95\x15\xbe.\xb6\xedz\x8b\x1a\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4\xd7\xd7\xf2\u02a4b\xa4\x1b;0\xa3J\xeb;\xa6\x10\x10\xe2bo\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd7\xe7J\xfd\xba\xd5^\x96\u03bcZ7O,\x8bv\x86\x80\xf2\xb0\x89\x05]\xe6\xa7y\xbb\xac\x00\x00\xe0\x94\xd7\xeb\x901b'\x1c\x1a\xfa5\xfei\xe3s\"\u0224\u049b\x11\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xd7\xeb\u0779\xf99\x87w\x9bh\x01U7T8\xdbe\xaf\xcbj\x89\x05t\x1a\xfe\xff\x94L\x00\x00\u07d4\xd7\xef4\x0ef\xb0\u05ef\xcc\xe2\n\x19\xcb{\xfc\x81\xda3\xd9N\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xd7\xf3p\u053e\xd9\xd5|oI\u0259\xder\x9e\xe5i\xd3\xf4\xe4\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd7\xfa_\xfb`H\xf9o\xb1\xab\xa0\x9e\xf8{\x1c\x11\xddp\x05\xe4\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd8\x06\x9f\x84\xb5!I?G\x15\x03\u007f2&\xb2_3\xb6\x05\x86\x89g\x8a\x93 b\xe4\x18\x00\x00\u0794\xd8\x15\xe1\xd9\xf4\xe2\xb5\xe5~4\x82k|\xfd\x88\x81\xb8Th\x90\x88\xf0\x15\xf2W6B\x00\x00\u07d4\xd8\x1b\xd5K\xa2\xc4Jok\xeb\x15a\u058b\x80\xb5DNm\u0189?\x17\r~\xe4\"\xf8\x9c\x80-1({\x96q\xe8\x1c\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\xd8K\x92/xA\xfcWt\xf0\x0e\x14`J\xe0\xdfB\xc8U\x1e\x89\xd9o\u0390\u03eb\xcc\x00\x00\u07d4\xd8U\xb0<\xcb\x02\x9awG\xb1\xf0s\x03\xe0\xa6dy59\u0209lk\x93[\x8b\xbd@\x00\x00\u07d4\xd8_\u07af*a\xf9]\xb9\x02\xf9\xb5\xa5<\x9b\x8f\x92f\u00ec\x89l\xf6Z~\x90G(\x00\x00\u07d4\xd8q^\xf9\x17o\x85\v.0\xeb\x8e8'\a\xf7w\xa6\xfb\xe9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd8t\xb9\u07eeEj\x92\x9b\xa3\xb1\xa2~W,\x9b,\xec\u07f3\x89\t79SM(h\x00\x00\u07d4\u0613\n9\xc7sW\xc3\n\u04e0`\xf0\v\x06\x04c1\xfdb\x89,s\xc97t,P\x00\x00\u07d4\u061b\xc2q\xb2{\xa3\xabib\xc9JU\x90\x06\xae8\xd5\xf5j\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0637}\xb9\xb8\x1b\xbe\x90B{b\xf7\x02\xb2\x01\xff\u009f\xf6\x18\x892m\x1eC\x96\xd4\\\x00\x00\u07d4\xd8\xcdd\xe0(N\xecS\xaaF9\xaf\xc4u\b\x10\xb9\u007f\xabV\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd8\xd6C\x84$\x9bwg\x94\x06;V\x98x\xd5\xe3\xb50\xa4\xb2\x89\t\xa0C\u0432\xf9V\x80\x00\u07d4\xd8\xd6T \xc1\x8c#'\xccZ\xf9t%\xf8W\xe4\xa9\xfdQ\xb3\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xd8\xe5\xc9g^\xf4\xde\xed&k\x86\x95o\xc4Y\x0e\xa7\u0522}\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xd8\xe8GB\x92\xe7\xa0Q`L\xa1d\xc0pw\x83\xbb(\x85\xe8\x8a\x02\xd4\xca\x05\xe2\xb4<\xa8\x00\x00\u07d4\xd8\xebxP>\xc3\x1aT\xa9\x016x\x1a\xe1\t\x00Lt2W\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd8\xee\xf4\xcfK\xeb\x01\xee \xd1\x11t\x8ba\xcbM?d\x1a\x01\x89\x94\x89#z\u06daP\x00\x00\u07d4\xd8\xf4\xba\xe6\xf8M\x91\rm}Z\xc9\x14\xb1\xe6\x83r\xf9A5\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xd8\xf6 6\xf0;v5\xb8X\xf1\x10?\x8a\x1d\x90\x19\xa8\x92\xb6\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\xd8\xf6e\xfd\x8c\xd5\u00bc\xc6\xdd\xc0\xa8\xaeR\x1eM\u01aa``\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xd8\xf9$\fU\xcf\xf05RB\x80\xc0\x9e\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd8\xfe\b\x8f\xff\u0394\x8fQ7\xee#\xb0\x1d\x95\x9e\x84\xacB#\x89\f[T\xa9O\xc0\x17\x00\x00\u07d4\xd9\x0f0\t\xdbC~N\x11\u01c0\xbe\u0209os\x8de\xef\r\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd9\x10;\xb6\xb6zU\xa7\xfe\xce-\x1a\xf6-E|!x\x94m\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd9\x13\xf0w\x19Iu\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xd9D\u0226\x9f\xf2\xca\x12Ii\f\x12)\xc7\x19/6%\x10b\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xd9JW\x88*Rs\x9b\xbe*\x06G\xc8\f$\xf5\x8a+O\x1c\x89H\xb5N*\xdb\xe1+\x00\x00\xe0\x94\xd9SB\x95<\x8a!\xe8\xb65\xee\xfa\u01c1\x9b\xea0\xf1pG\x8a\x13\xf0l\u007f\xfe\xf0]@\x00\x00\u07d4\xd9\\\x90\xff\xbeT\x84\x86G\x80\xb8gIJ\x83\u0212V\xd6\xe4\x89X\xe7\x92n\xe8X\xa0\x00\x00\u07d4\xd9g\x11T\x0e.\x99\x83C\xd4\xf5\x90\xb6\xfc\x8f\xac;\xb8\xb3\x1d\x89_Z@h\xb7\x1c\xb0\x00\x00\u07d4\xd9j\xc2Pt\t\u01e3\x83\xab.\xee\x18\"\xa5\xd78\xb3kV\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd9m\xb3;{Z\x95\f>\xfa-\xc3\x1b\x10\xba\x10\xa52\uf1c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xd9wYe\xb7\x16Gfu\xa8\xd5\x13\xeb\x14\xbb\xf7\xb0|\xd1J\x8a\x01\x13.m-#\xc5\xe4\x00\x00\u07d4\xd9{\xc8J\xbdG\xc0[\xbfE{.\xf6Y\xd6\x1c\xa5\xe5\u43c9\x06\x9d\x17\x11\x9d\u0168\x00\x00\u07d4\xd9\u007fE&\u07a9\xb1c\xf8\xe8\xe3:k\u03d2\xfb\x90}\xe6\xec\x89\x0feJ\xafM\xb2\xf0\x00\x00\u07d4\xd9\u007f\xe6\xf5?*X\xf6\xd7mu*\xdft\xa8\xa2\xc1\x8e\x90t\x89\x10\xcd\xf9\xb6\x9aCW\x00\x00\u07d4\u0659\x99\xa2I\r\x94\x94\xa50\xca\xe4\xda\xf3\x85T\xf4\xddc>\x89\x06\x81U\xa46v\xe0\x00\x00\u07d4\u065d\xf7B\x1b\x93\x82\xe4,\x89\xb0\x06\xc7\xf0\x87p*\aW\xc0\x89\x1a\x05V\x90\xd9\u06c0\x00\x00\xe0\x94\u0677\x83\xd3\x1d2\xad\xc5\x0f\xa3\xea\u02a1]\x92\xb5h\xea\xebG\x8a\a3\xaf\x907L\x1b(\x00\x00\u07d4\xd9\xd3p\xfe\xc65v\xab\x15\xb3\x18\xbf\x9eX6M\u00a3U*\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xd9\xd4/\xd1>\xbdK\xf6\x9c\xac^\x9c~\x82H:\xb4m\xd7\xe9\x8a\x01!\xeah\xc1\x14\xe5\x10\x00\x00\u07d4\xd9\xe2~\xb0}\xfcq\xa7\x06\x06\f\u007f\a\x928\u0293\xe8\x859\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd9\xe3\x85~\xfd\x1e *D\x17p\xa7w\xa4\x9d\xccE\xe2\xe0\u04c9\f\x1d\xaf\x81\u0623\xce\x00\x00\u07d4\xd9\xec.\xfe\x99\xff\\\xf0\r\x03\xa81{\x92\xa2J\xefD\x1f~\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd9\xec\x8f\xe6\x9bw\x16\xc0\x86Z\xf8\x88\xa1\x1b+\x12\xf7 \xed3\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd9\xf1\xb2d\b\xf0\xecg\xad\x1d\ro\xe2.\x85\x15\xe1t\x06$\x89\x01M\x11 \u05f1`\x00\x00\u07d4\xd9\xf5G\xf2\xc1\xde\x0e\u064aS\xd1a\xdfWc]\xd2\x1a\x00\xbd\x89\x05V\xf6L\x1f\xe7\xfa\x00\x00\u07d4\xd9\xff\x11]\x01&l\x9fs\xb0c\xc1\xc28\xef5e\xe6;6\x89$\xdc\xe5M4\xa1\xa0\x00\x00\u07d4\xda\x06\x04N)#&\xffil\x0091h\xceF\xff\xac9\xec\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xda*\x14\xf9r@\x15\u05d0\x14\xed\x8eY\th\x1dYaH\xf1\x89\x02\xa1\x0f\x0f\x8a\x91\xab\x80\x00\u07d4\xda*\u054ew\xde\xdd\xed\xe2\x18vF\xc4e\x94Z\x8d\xc3\xf6A\x89#\xc7W\a+\x8d\xd0\x00\x00\u07d4\xda0\x17\xc1P\xdd\r\xce\u007f\u03c8\x1b\nH\xd0\xd1\xc7V\xc4\u01c9\x05k\xf9\x1b\x1ae\xeb\x00\x00\u07d4\xda4\xb2\xea\xe3\v\xaf\xe8\xda\xec\xcd\xe8\x19\xa7\x94\u0349\xe0\x95I\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xdaJ_U\u007f;\xab9\n\x92\xf4\x9b\x9b\x90\n\xf3\fF\xae\x80\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xdaPU7S\u007f\xfb3\xc4\x15\xfe\xc6Ni\xba\xe0\x90\xc5\xf6\x0f\x89\b\xacr0H\x9e\x80\x00\x00\u07d4\xdai\x8dd\xc6\\\u007f+,rS\x05\x9c\xd3\u0441\u0619\xb6\xb7\x89\x10\x04\xe2\xe4_\xb7\xee\x00\x00\u07d4\xdaw2\xf0/.'.\xaf(\u07d7.\xcc\r\xde\xed\x9c\xf4\x98\x89\v \xbf\xbfig\x89\x00\x00\u07d4\xdaz\xd0%\xeb\xde%\xd2\"C\u02c3\x0e\xa1\xd3\xf6JVc#\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\u0685]SG\u007fP^\xc4\xc8\xd5\u8ed1\x80\u04c6\x81\x11\x9c\x8a\x01/\x93\x9c\x99\xed\xab\x80\x00\x00\u07d4\u0687^N/<\xab\xe4\xf3~\x0e\xae\xd7\xd1\xf6\xdc\xc6\xff\xefC\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u068b\xbe\xe1\x82\xe4U\xd2\t\x8a\xcb3\x8amE\xb4\xb1~\u0636\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0698.\x96C\xff\xec\xe7#\aZ@\xfewnZ\xce\x04\xb2\x9b\x89\b\xb8\xb6\u0259\x9b\xf2\x00\x00\u07d4\u069fUF\tF\u05ff\xb5p\xdd\xecu|\xa5w;XB\x9a\x89\x1b\x84]v\x9e\xb4H\x00\x00\u07d4\u06a1\xbdz\x91H\xfb\x86\\\xd6\x12\xdd5\xf1b\x86\x1d\x0f;\u0709\xa68\xabr\xd9,\x13\x80\x00\xe0\x94\u06a6<\xbd\xa4]\u0507\xa3\xf1\xcdJtj\x01\xbb^\x06\v\x90\x8a\x01\x04\x16\u0670*\x89$\x00\x00\u07d4\u06a7v\xa6uDi\u05f9&z\x89\xb8g%\xe7@\xda\x0f\xa0\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\u06ac\x91\xc1\xe8Y\xd5\xe5~\xd3\bKP \x0f\x97f\xe2\xc5+\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\u06ac\xda\xf4\"&\xd1\\\xb1\u03d8\xfa\x15\x04\x8c\u007fL\xee\xfei\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94\u06b6\xbc\u06c3\xcf$\xa0\xae\x1c\xb2\x1b;[\x83\xc2\xf3\x82I'\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\u06bb\b\x89\xfc\x04)&\xb0^\xf5{% \x91\n\xbcKAI\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u06bc\"PB\xa6Y,\xfa\x13\xeb\xe5N\xfaA\x04\bx\xa5\xa2\x89\x0e\x11\xfa\xd5\xd8\\\xa3\x00\x00\u07d4\xda\xc0\xc1w\xf1\x1c\\>>x\xf2\xef\xd6c\xd12!H\x85t\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xda\xd16\xb8\x81x\xb4\x83zlx\x0f\xeb\xa2&\xb9\x85i\xa9L\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xda\xdb\xfa\xfd\x8bb\xb9*$\xef\xd7RV\u0743\xab\xdb\u05fb\u06c9\x01\x11du\x9f\xfb2\x00\x00\u07d4\xda\xdc\x00\xaby'`\xaa4\x15i\xfa\x9f\xf5\x98&\x84\x85JJ2\x8an\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xda\xe7 \x1e\xab\x8c\x063\x02\x93\ri9)\xd0\u007f\x95\xe7\x19b\x89\x91\xae\xc0(\xb4\x19\x81\x00\x00\u07d4\xda\xed\u052d\x10{'\x1e\x89Hl\xbf\x80\xeb\xd6!\u0757Ex\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdb\x04\xfa\xd9\u011f\x9e\x88\v\xeb\x8f\xcf\x1d:8\x90\u4cc4o\x89CZ\xe6\xcc\fX\xe5\x00\x00\u07d4\xdb\f\u01cft\u0642{\u070ads'n\xb8O\u0717b\x12\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdb\x12\x93\xa5\x06\xe9\f\xad*Y\xe1\xb8V\x1f^f\x96\x1ag\x88\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdb\x19\xa3\x98\"06\x8f\x01w!\x9c\xb1\f\xb2Y\u0372%|\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdb#\xa6\xfe\xf1\xaf{X\x1ew,\xf9\x18\x82\u07b2Qo\xc0\xa7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xdb$O\x97\xd9\xc4K\x15\x8a@\xed\x96\x06\xd9\xf7\xbd8\x9131\x89\x05\x87\x88\u02d4\xb1\xd8\x00\x00\u07d4\xdb(\x8f\x80\xff\xe22\u00baG\u0314\xc7c\xcfo\u0278+\r\x89\x04\x9b\x9c\xa9\xa6\x944\x00\x00\u07d4\xdb*\f\x9a\xb6M\xf5\x8d\u07f1\u06ec\xf8\xba\r\x89\xc8[1\xb4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xdb4t^\u0785v\xb4\x99\xdb\x01\xbe\xb7\xc1\xec\u0685\xcfJ\xbe\x89\x04V9\x18$O@\x00\x00\u07d4\xdb?%\x8a\xb2\xa3\xc2\xcf3\x9cD\x99\xf7ZK\xd1\xd3G.\x9e\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\xdbK\xc8;\x0ek\xaa\xdb\x11V\xc5\xcf\x06\xe0\xf7!\x80\x8cR\u01c9/\xb4t\t\x8fg\xc0\x00\x00\u07d4\xdbc\x12-\xe7\x03}\xa4\x97\x151\xfa\u9bc5\x86x\x86\u0192\x89\x0f\x04%\xb0d\x1f4\x00\x00\u07d4\xdbl*s\xda\xc7BJ\xb0\xd01\xb6ga\x12%f\xc0\x10C\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xdbnV\f\x9b\xc6 \u053e\xa3\xa9MG\xf7\x88\v\xf4\u007f-_\x89\x04\xda\x0f\xdf\xcf\x05v\x00\x00\u07d4\xdbo\xf7\x1b=\xb0\x92\x8f\x83\x9e\x05\xa72;\xfbW\u049c\x87\xaa\x891T\xc9r\x9d\x05x\x00\x00\u07d4\xdbsF\vY\xd8\xe8PE\xd5\xe7R\xe6%Y\x87^BP.\x8963\x03\"\xd5#\x8c\x00\x00\u07d4\xdbw\xb8\x8d\xcbq/\xd1~\xe9\x1a[\x94t\x8dr\f\x90\xa9\x94\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdb}@7\b\x1fle\xf9Gk\x06\x87\xd9\u007f\x1e\x04M\n\x1d\x89#\xc7W\a+\x8d\xd0\x00\x00\xe0\x94\u06c8.\xac\xed\xd0\xef\xf2cQ\x1b1*\u06fcY\u01b8\xb2[\x8a\x01\xedO\xdez\"6\xb0\x00\x00\u07d4\u06d3q\xb3\fL\x84NY\xe0>\x92K\xe6\x06\xa98\xd1\xd3\x10\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u06e4ym\f\xebM:\x83k\x84\xc9o\x91\n\xfc\x10?[\xa0\x89\t\b\xf4\x93\xf77A\x00\x00\u07d4\u06ed\xc6\x1e\xd5\xf0F\n\u007f\x18\xe5\x1b/\xb2aM\x92d\xa0\xe0\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\u06f6\xacH@'\x04\x16B\xbb\xfd\x8d\x80\xf9\xd0\xc1\xcf3\xc1\xeb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u06fc\xbby\xbfG\x9aB\xadq\xdb\u02b7{Z\u07ea\x87,X\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\xdb\xc1\xce\x0eI\xb1\xa7\x05\xd2. 7\xae\xc8x\xee\ru\xc7\x03\x89\r\x8drkqw\xa8\x00\x00\u07d4\xdb\xc1\xd0\xee+\xabS\x11@\xde\x13w\"\xcd6\xbd\xb4\xe4q\x94\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xdb\u015e\u0609s\u07ad1\b\x84\":\xf4\x97c\xc0P0\xf1\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u0794\xdb\xc6ie\xe4&\xff\x1a\xc8z\xd6\xebx\xc1\xd9Rq\x15\x8f\x9f\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xdb\xcb\xcdzW\ua7724\x9b\x87\x8a\xf3K\x1a\xd6B\xa7\xf1\u0449\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xdb\xd5\x1c\xdf,;\xfa\xcd\xff\x10b!\xde.\x19\xadmB\x04\x14\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xdb\xd7\x1e\xfaK\x93\u0209\xe7e\x93\xde`\x9c;\x04\u02ef\xbe\b\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xdb\xf5\xf0a\xa0\xf4\x8e^ia\x879\xa7}.\xc1\x97h\xd2\x01\x89\b=lz\xabc`\x00\x00\u07d4\xdb\xf8\xb19g\xf5Q%'-\xe0V%6\xc4P\xbaVU\xa0\x89n\xf5x\xf0n\f\xcb\x00\x00\u07d4\xdb\xfb\x1b\xb4d\xb8\xa5\x8eP\r.\xd8\u0797,E\xf5\xf1\xc0\xfb\x89V\xbcu\xe2\xd61\x00\x00\x00\xe0\x94\xdc\x06~\xd3\xe1-q\x1e\xd4u\xf5\x15n\xf7\xe7\x1a\x80\xd94\xb9\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\xdc\b\u007f\x93\x90\xfb\x9e\x97j\xc2:\xb6\x89TJ\tB\xec !\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xdc\x1e\xb9\xb6\xe6CQ\xf5d$P\x96E\xf8>y\xee\xe7l\xf4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xdc\x1f\x19ya_\b!@\xb8\xbbx\xc6{'\xa1\x94'\x13\xb1\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\xdc#\xb2`\xfc\xc2n}\x10\xf4\xbd\x04J\xf7\x94W\x94`\xd9\u0689\x1b\x1bk\u05efd\xc7\x00\x00\u07d4\xdc)\x11\x97E\xd23s \xdaQ\xe1\x91\x00\xc9H\u0640\xb9\x15\x89\b\xacr0H\x9e\x80\x00\x00\u07d4\xdc-\x15\xa6\x9fk\xb3;$j\xef@E\aQ\xc2\xf6uj\u0489l4\x10\x80\xbd\x1f\xb0\x00\x00\u07d4\xdc=\xaeY\xed\x0f\xe1\x8bXQ\x1eo\xe2\xfbi\xb2\x19h\x94#\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xdc?\x0evr\xf7\x1f\xe7R[\xa3\v\x97U\x18: \xb9\x16j\x8a\x02\b\x9c\xf5{[>\x96\x80\x00\xe0\x94\xdcCE\u0581.\x87\n\xe9\fV\x8cg\xd2\xc5g\u03f4\xf0<\x8a\x01k5-\xa5\xe0\xed0\x00\x00\u07d4\xdcD'[\x17\x15\xba\xea\x1b\x03EsZ)\xacB\xc9\xf5\x1bO\x89?\x19\xbe\xb8\xdd\x1a\xb0\x00\x00\u07d4\xdcF\xc13%\u034e\xdf\x020\xd0h\x89d\x86\xf0\a\xbfN\xf1\x89Hz\x9a0E9D\x00\x00\u07d4\xdcQ\xb2\u071d$z\x1d\x0e[\xc3l\xa3\x15oz\xf2\x1f\xf9\xf6\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xdcS\x05\xb4\x02\n\x06\xb4\x9de||\xa3L5\xc9\x1c_,V\x8a\x01}\xf6\xc1\r\xbe\xba\x97\x00\x00\u07d4\xdcW4[8\xe0\xf0g\u0263\x1d\x9d\xea\xc5'Z\x10\x94\x93!\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xdcWG}\xaf\xa4/p\\\u007f\xe4\x0e\xae\x9c\x81un\x02%\xf1\x89\x1b\x1b\x81(\xa7An\x00\x00\u07d4\xdc_Z\xd6c\xa6\xf2c2}d\xca\xc9\xcb\x13=,\x96\x05\x97\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdcp:_7\x94\xc8Ml\xb3TI\x18\xca\xe1J5\u00fdO\x89dI\xe8NG\xa8\xa8\x00\x00\xe0\x94\xdcs\x8f\xb2\x17\u03ad/iYL\b\x17\r\xe1\xaf\x10\xc4\x19\xe3\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xdcv\xe8[\xa5\v\x9b1\xec\x1e& \xbc\xe6\xe7\xc8\x05\x8c\x0e\xaf\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u0703\xb6\xfd\rQ!1 G\a\xea\xf7.\xa0\xc8\u027e\xf9v\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u070c)\x12\xf0\x84\xa6\u0444\xaasc\x85\x13\u033c2n\x01\x02\x89F3\xbc6\xcb\xc2\xdc\x00\x00\u07d4\u0711\x1c\xf7\xdc]\u04016Vg\x05(\xe93\x8eg\x03G\x86\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0730;\xfal\x111#NV\xb7\xea|Or\x14\x87Tkz\x89Hz\x9a0E9D\x00\x00\xe0\x94\u0736M\xf47X\xc7\u03d7O\xa6`HO\xbbq\x8f\x8cg\xc1\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xdc\xc5-\x8f\x8d\x9f\xc7B\xa8\xb8'g\xf0US\x87\xc5c\xef\xff\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xdc\xcb7\x0e\u058a\xa9\"(0C\xef|\xad\x1b\x9d@?\xc3J\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xdc\u0324 E\xec>\x16P\x8b`?\xd96\xe7\xfd}\xe5\xf3j\x89\x01\x11du\x9f\xfb2\x00\x00\u07d4\xdc\xd1\fU\xbb\x85OuD4\xf1!\x9c,\x9a\x98\xac\xe7\x9f\x03\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\xdc\u057c\xa2\x00S\x95\xb6u\xfd\xe5\x03VY\xb2k\xfe\xfcI\xee\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xdc\u06fdN&\x04\xe4\x0e\x17\x10\xccg0(\x9d\xcc\xfa\u04c9-\x89\xf9]\xd2\xec'\xcc\xe0\x00\x00\u07d4\xdc\xe3\f1\xf3\xcafr\x1e\xcb!<\x80\x9a\xabV\x1d\x9bR\xe4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdc\xf39eS\x13\x80\x161h\xfc\x11\xf6~\x89\xc6\xf1\xbc\x17\x8a\x89\x12'v\x854\x06\xb0\x80\x00\u07d4\xdc\xf6\xb6W&n\x91\xa4\xda\xe6\x03=\xda\xc1S2\u074d+4\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xdc\xf9q\x9b\xe8|oFum\xb4\x89\x1d\xb9\xb6\x11\xd2F\x9cP\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xdc\xff\xf3\xe8\xd2<*4\xb5k\u0473\xbdE\u01d3tC\"9\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xdd\x04\xee\xe7N\v\xf3\f?\x8dl,\u007fR\xe0Q\x92\x10\u07d3\x89\x04V9\x18$O@\x00\x00\xe0\x94\xdd&\xb4)\xfdC\xd8N\xc1y\x82S$\xba\u057f\xb9\x16\xb3`\x8a\x01\x16\xbf\x95\xbc\x842\x98\x00\x00\u07d4\xdd*#:\xde\xdef\xfe\x11&\xd6\xc1h#\xb6*\x02\x1f\xed\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xdd+\u07e9\x17\xc1\xf3\x10\xe6\xfa5\xaa\x8a\xf1i9\xc23\xcd}\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xdd5\xcf\xdb\u02d93\x95Sz\xec\xc9\xf5\x90\x85\xa8\xd5\u0776\xf5\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xddG\x18\x9a>d9qg\xf0b\x0eHEe\xb7b\xbf\xbb\xf4\x89dI\xe8NG\xa8\xa8\x00\x00\u07d4\xddM\xd6\xd3`3\xb0co\u030d\t8`\x9fM\xd6OJ\x86\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\xddO_\xa2\x11\x1d\xb6\x8fk\xde5\x89\xb60)9[i\xa9-\x89\b\x96=\xd8\xc2\xc5\xe0\x00\x00\xe0\x94\xddc\x04/%\xed2\x88J\xd2n:\xd9Y\xeb\x94\xea6\xbfg\x8a\x04\x84\xd7\xfd\xe7\u0553\xf0\x00\x00\u07d4\xdde\xf6\xe1qc\xb5\xd2\x03d\x1fQ\xcc{$\xb0\x0f\x02\xc8\xfb\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xddl\x06!\x93\xea\xc2=/\xdb\xf9\x97\xd5\x06:4k\xb3\xb4p\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xdd{\u0366Y$\xaa\xa4\x9b\x80\x98J\xe1su\x02X\xb9(G\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xdd\u007f\xf4A\xbao\xfe6q\xf3\xc0\u06bb\xff\x18#\xa5\x043p\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0742T\x12\x1an\x94/\xc9\b(\xf2C\x1fQ\x1d\xad\u007f2\u6263\x9b)\xe1\xf3`\xe8\x00\x00\xe0\x94\u074a\xf9\xe7vR#\xf4DoD\xd3\xd5\t\x81\x9a==\xb4\x11\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\u0755\xdb\xe3\x0f\x1f\x18w\xc5\xddv\x84\xae\xef0*\xb6\x88Q\x92\x8a\x01\xc5\xd8\xd6\xeb>2P\x00\x00\xe0\x94\u0756|L_\x8a\xe4~&o\xb4\x16\xaa\u0456N\xe3\xe7\xe8\u00ca\x01\xa4 \xdb\x02\xbd}X\x00\x00\u07d4\u075bHZ;\x1c\xd3:j\x9cb\xf1\xe5\xbe\xe9'\x01\x85m%\x89\f3\x83\xed\x03\x1b~\x80\x00\xe0\x94\u0763q\xe6\x00\xd3\x06\x88\xd4q\x0e\b\x8e\x02\xfd\xf2\xb9RM_\x8a\x01w\"J\xa8D\xc7 \x00\x00\u07d4\u0764\xed*X\xa8\xdd \xa72u4{X\rq\xb9[\xf9\x9a\x89\x15\xa1<\xc2\x01\xe4\xdc\x00\x00\xe0\x94\u0764\xff}\xe4\x91\u0187\xdfEt\xdd\x1b\x17\xff\x8f$k\xa3\u044a\x04&\x84\xa4\x1a\xbf\xd8@\x00\x00\u07d4\u076bkQ\xa9\x03\v@\xfb\x95\xcf\vt\x8a\x05\x9c$\x17\xbe\u01c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94\u076bu\xfb/\xf9\xfe\u02c8\xf8\x94vh\x8e+\x00\xe3g\xeb\xf9\x8a\x04\x1b\xad\x15^e\x12 \x00\x00\xe0\x94\u076b\xf1<<\x8e\xa4\xe3\xd7=x\xecqz\xfa\xfaC\x0eTy\x8a\b\xcf#\xf9\t\xc0\xfa\x00\x00\x00\u07d4\u076c1*\x96UBj\x9c\f\x9e\xfa?\xd8%Y\xefE\x05\xbf\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\u076ck\xf4\xbb\xdd}Y}\x9chm\x06\x95Y;\xed\xcc\xc7\xfa\x89.\xe4IU\b\x98\xe4\x00\x00\xe0\x94\u077d+\x93,v;\xa5\xb1\xb7\xae;6.\xac>\x8d@\x12\x1a\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u077d\xdd\x1b\xbd8\xff\xad\xe00]0\xf0 (\xd9.\x9f:\xa8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u077e\xe6\xf0\x94\xea\xe64 \xb0\x03\xfbGW\x14*\xeal\xd0\xfd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdd\u059c[\x9b\xf5\xebZ9\xce\xe7\xc34\x1a\x12\r\x97?\xdb4\x89k\xc1K\x8f\x8e\x1b5\x00\x00\xe0\x94\xdd\xdd{\x9en\xab@\x9b\x92&:\xc2r\u0680\x1bfO\x8aW\x8ai\xe1\r\xe7fv\u0400\x00\x00\u07d4\xdd\xe6p\xd0\x169fuv\xa2-\xd0]2F\xd6\x1f\x06\xe0\x83\x89\x01s\x17\x90SM\xf2\x00\x00\xe0\x94\xdd\xe7zG@\xba\b\xe7\xf7?\xbe:\x16t\x91)1t.\xeb\x8a\x044\xfeMC\x82\xf1\u0500\x00\u07d4\xdd\xe8\xf0\xc3\x1bt\x15Q\x1d\xce\xd1\xcd}F2>K\xd1\"2\x89WG=\x05\u06ba\xe8\x00\x00\u07d4\xdd\xe9i\xae\xf3N\xa8z\u0099\xb7Y~)+J\x01U\u030a\x89\x102\xf2YJ\x01s\x80\x00\u07d4\xdd\xf0\xcc\xe1\xfe\x99m\x91v5\xf0\a\x12\xf4\x05 \x91\xdf\xf9\xea\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xdd\xf3\xadv58\x10\xbej\x89\xd71\xb7\x87\xf6\xf1q\x88a+\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xdd\xf5\x81\n\x0e\xb2\xfb.22;\xb2\u0255\t\xab2\x0f$\xac\x8a\x03\xca\\f\u067cD0\x00\x00\xe0\x94\xdd\xf9\\\x1e\x99\xce/\x9fV\x98\x05|\x19\xd5\xc9@'\xeeJn\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u0794\xdd\xfa\xfd\xbc|\x90\xf12\x0eT\xb9\x8f7F\x17\xfb\xd0\x1d\x10\x9f\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\xdd\xfc\xca\x13\xf94\xf0\u03fe#\x1d\xa109\xd7\x04u\xe6\xa1\u040968\"\x16`\xa5\xaa\x80\x00\u07d4\xde\x02~\xfb\xb3\x85\x03\"n\xd8q\t\x9c\xb3\v\xdb\x02\xaf\x135\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xde\x06\xd5\xeawzN\xb1G^`]\xbc\xbfCDN\x807\xea\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\xde\a\xfb[zFN;\xa7\xfb\xe0\x9e\x9a\xcb'\x1a\xf53\x8cX\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\xde\x11!\x82\x9c\x9a\b(@\x87\xa4?\xbd/\xc1\x14*23\xb4\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xde\x17kR\x84\xbc\xee:\x83\x8b\xa2Og\xfc|\xbfg\u05ce\xf6\x89\x02\t\xce\b\xc9b\xb0\x00\x00\u07d4\xde!\"\x93\xf8\xf1\xd21\xfa\x10\xe6\tG\rQ,\xb8\xff\xc5\x12\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xde0\xe4\x9eZ\xb3\x13!M/\x01\u072b\u0389@\xb8\x1b\x1cv\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xde3\xd7\b\xa3\xb8\x9e\x90\x9e\xafe;0\xfd\u00e5\xd5\u0334\xb3\x89\t\x9c\x88\"\x9f\xd4\xc2\x00\x00\u07d4\xde7B\x99\xc1\xd0}ySs\x85\x19\x0fD.\xf9\xca$\x06\x1f\x89\a?u\u0460\x85\xba\x00\x00\u07d4\xdeB\xfc\xd2L\xe4#\x93\x830CgY_\x06\x8f\fa\a@\x89\x02r*p\xf1\xa9\xa0\x00\x00\u07d4\xdeP\x86\x8e\xb7\xe3\xc7\x197\xecs\xfa\x89\u074b\x9e\xe1\rE\xaa\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xdeU\xde\x04X\xf8P\xb3~Mx\xa6A\xdd.\xb2\u074f8\u0389\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xde[\x00_\xe8\u06ae\x8d\x1f\x05\xde>\xda\x04 f\xc6\xc4i\x1c\x89;\xa1\x91\v\xf3A\xb0\x00\x00\u07d4\xdea-\a$\xe8N\xa4\xa7\xfe\xaa=!B\xbd^\xe8-2\x01\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xdem61\x06\xccb8\xd2\xf0\x92\xf0\xf07!6\xd1\xcdP\u018a\x01!\xeah\xc1\x14\xe5\x10\x00\x00\u07d4\xde}\xee\"\x0f\x04W\xa7\x18}V\xc1\xc4\x1f.\xb0\n\xc5`!\x89\"%\xf3\x9c\x85\x05*\x00\x00\u07d4\u0782\u030dJ\x1b\xb1\xd9CC\x92\x96[>\x80\xba\xd3\xc0=O\x89P\x18nu\u0797\xa6\x00\x00\u07d4\u0797\xf43\a\x00\xb4\x8cImC|\x91\xca\x1d\xe9\u0130\x1b\xa4\x89\x9d\xcc\x05\x15\xb5n\f\x00\x00\u07d4\u079e\xffLy\x88\x11\xd9h\xdc\xcbF\r\x9b\x06\x9c\xf3\x02x\xe0\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\u07b1\xbc4\xd8mJM\xde%\x80\u063e\xaf\aN\xb0\xe1\xa2D\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\u07b2I]j\xca{*j-\x13\x8bn\x1aB\xe2\xdc1\x1f\u0749lk\x93[\x8b\xbd@\x00\x00\u07d4\u07b9rTGL\r/Zyp\xdc\xdb/R\xfb\x10\x98\xb8\x96\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u07b9\xa4\x9aC\x870 \xf0u\x91\x85\xe2\v\xbbL\U000c1ecf\x89\vx\xed\xb0\xbf.^\x00\x00\u07d4\u07bb\u0743\x1e\x0f \xaen7\x82R\xde\xcd\xf9/|\xf0\xc6X\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xde\xc3\xee\xc2d\nu,Fn+~~\u616f\xe9\xacA\xf4\x89G\u0257SYk(\x80\x00\u07d4\xde\xc8#s\xad\xe8\xeb\xcf*\xcbo\x8b\xc2AM\u05eb\xb7\rw\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xde\u0221\xa8\x98\xf1\xb8\x95\xd80\x1f\xe6J\xb3\xad]\xe9A\xf6\x89\x89*\xb4\xf6~\x8as\x0f\x80\x00\u07d4\xde\u025e\x97/\xcaqwP\x8c\x8e\x1aG\xac\"\xd7h\xac\xab|\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xde\xd8w7\x84\a\xb9Nx\x1cN\xf4\xaf|\xfc[\xc2 \xb5\x16\x89\x141y\xd8i\x11\x02\x00\x00\u07d4\xde\xe9B\xd5\xca\xf5\xfa\xc1\x14!\xd8k\x01\vE\x8e\\9)\x90\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xde\xee&\x89\xfa\x90\x06\xb5\x9c\xf2\x85#}\xe5;:\u007f\xd0\x148\x89\x18ey\xf2\x9e %\x00\x00\u07d4\xde\xfd\xdf\u055b\x8d,\x15N\xec\xf5\xc7\xc1g\xbf\v\xa2\x90]>\x89\x05\x12\xcb^&GB\x00\x00\u07d4\xde\xfe\x91A\xf4pE\x99\x15\x9d{\"=\xe4+\xff\xd8\x04\x96\xb3\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xdf\t\x8f^N=\xff\xa5\x1a\xf27\xbd\xa8e,Os\ud726\x89\x1b6\xa6DJ>\x18\x00\x00\xe0\x94\xdf\r\ba{\xd2R\xa9\x11\u07cb\xd4\x1a9\xb8=\u07c0\x96s\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xdf\x0f\xf1\xf3\xd2z\x8e\xc9\xfb\x8fk\f\xb2T\xa6;\xba\x82$\xa5\x89\xec\xc5 )E\xd0\x02\x00\x00\u07d4\xdf\x1f\xa2\xe2\x0e1\x98^\xbe,\x0f\f\x93\xb5L\x0f\xb6z&K\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xdf!\x1c\xd2\x12\x88\xd6\xc5o\xaef\xc3\xffTb]\u0531T'\x89\x87\x86\xcdvN\x1f,\x00\x00\u07d4\xdf#k\xf6\xab\xf4\xf3)7\x95\xbf\f(q\x8f\x93\u3c73k\x89Hz\x9a0E9D\x00\x00\u07d4\xdf1\x02_VI\xd2\xc6\xee\xa4\x1e\u04fd\xd3G\x1ay\x0fu\x9a\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xdf7\xc2.`:\xed\xb6\nbrS\xc4}\x8b\xa8f\xf6\xd9r\x8a\x05\x15\n\xe8J\x8c\xdf\x00\x00\x00\u07d4\xdf;r\u017dq\u0501N\x88\xa6#!\xa9=@\x11\xe3W\x8b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xdf?W\xb8\xeed4\xd0G\"=\xeft\xb2\x0fc\xf9\xe4\xf9U\x89\r\x94b\xc6\xcbKZ\x00\x00\u07d4\xdfD\xc4\u007f\xc3\x03\xacv\xe7O\x97\x19L\xcag\xb5\xbb<\x02?\x89 \t\xc5\u023fo\xdc\x00\x00\u07d4\xdfG\xa6\x1brSQ\x93\xc5a\xcc\xccu\xc3\xf3\xce\b\x04\xa2\x0e\x89\x15\x93\\\vN=x\x00\x00\u07d4\xdfG\xa8\xef\x95\xf2\xf4\x9f\x8eoX\x18AT\x14]\x11\xf7'\x97\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\xdfS\x003F\xd6\\^zdk\xc04\xf2\xb7\xd3/\xcb\xe5j\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdfW5:\xaf\xf2\xaa\xdb\n\x04\xf9\x01N\x8d\xa7\x88N\x86X\x9c\x89\bH\x86\xa6nO\xb0\x00\x00\u07d4\xdf`\xf1\x8c\x81*\x11\xedN'v\xe7\xa8\x0e\xcf^S\x05\xb3\u05890\xca\x02O\x98{\x90\x00\x00\u07d4\xdfd\x85\xc4)z\xc1R\xb2\x89\xb1\x9d\xde2\xc7~\xc4\x17\xf4}\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xdff\n\x91\u06b9\xf70\xf6\x19\rP\xc89\x05aP\aV\u0289lk\x93[\x8b\xbd@\x00\x00\u07d4\xdfn\xd6\x00jj\xbe\x88n\xd3=\x95\xa4\xde(\xfc\x12\x189'\x891T\xc9r\x9d\x05x\x00\x00\u07d4\u07c5\x10y>\xee\x81\x1c-\xab\x1c\x93\xc6\xf4G?0\xfb\xef[\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u07cdH\xb1\xeb\a\xb3\xc2\x17y\x0el-\xf0M\xc3\x19\xe7\xe8H\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\u07e6\xb8\xb8\xad1\x84\xe3W\xda()Q\u05d1a\u03f0\x89\xbc\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\u07ef1\xe6\"\xc0=\x9e\x18\xa0\u0778\xbe`\xfb\xe3\xe6a\xbe\n\x8a\x02\x1e\x17\x1a>\xc9\xf7,\x00\x00\u07d4\u07f1bn\xf4\x8a\x1d}uR\xa5\xe0)\x8f\x1f\xc2:;H-\x89\\\xe8\x95\u0754\x9e\xfa\x00\x00\xe0\x94\u07f4\u052d\xe5/\u0301\x8a\xccz,k\xb2\xb0\x02$e\x8fx\x8a\x01\xa4 \xdb\x02\xbd}X\x00\x00\u07d4\u07fdB2\xc1|@z\x98\r\xb8\u007f\xfb\u036060\xe5\xc4Y\x89\x1d\xfc\u007f\x92I#S\x00\x00\u07d4\xdf\xcb\xdf\tEN\x1a^J@\xd3\xee\xf7\xc5\xcf\x1c\xd3\u0794\x86\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xdf\xdb\xce\xc1\x01K\x96\xda!X\xcaQ>\x9c\x8d;\x9a\xf1\xc3\u0409lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xdf\xde\xd2WK'\xd1a:}\x98\xb7\x15\x15\x9b\r\x00\xba\xab(\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xdf\xdfC9P\x8b\x0fnZ\xb1\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe0\x06\x04b\xc4\u007f\xf9g\x9b\xae\xf0qY\xca\xe0\x8c)\xf2t\xa9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\r\x15;\x106\x91C\xf9\u007fT\xb8\xd4\xca\"\x9e\xb3\xe8\xf3$\x89\b=lz\xabc`\x00\x00\u07d4\xe0\x12\xdbE8'\xa5\x8e\x16\xc16V\b\xd3n\xd6Xr\x05\a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\x15G\xbaB\xfc\xaf\xaf\x93\x93\x8b\xec\xf7i\x9ft)\n\xf7O\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\x16\xdc\x13\x8e%\x81[\x90\xbe?\xe9\xee\xe8\xff\xb2\xe1\x05bO\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe0\x18Y\xf2B\xf1\xa0\xec`/\xa8\xa3\xb0\xb5v@\xec\x89\a^\x89\x1e\x16,\x17{\xe5\xcc\x00\x00\xe0\x94\xe0 \xe8cb\xb4\x87u(6\xa6\xde\v\xc0,\xd8\u061a\x8bj\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xe0#\xf0\x9b(\x87a,|\x9c\xf1\x98\x8e::`+3\x94\u0249lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0'\"\x13\xe8\xd2\xfd>\x96\xbdb\x17\xb2KK\xa0\x1bapy\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xe0+t\xa4v(\xbe1[\x1fv\xb3\x15\x05J\xd4J\xe9qo\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xe02 \u0197\xbc\u048f&\xef\vt@J\x8b\xeb\x06\xb2\xba{\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xe05/\u07c1\x9b\xa2e\xf1L\x06\xa61\\J\xc1\xfe\x13\x1b.\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe08\x8a\xed\xdd?\xe2\xadV\xf8WH\xe8\x0eq\n4\xb7\xc9.\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe0<\x00\xd0\x03\x88\xec\xbfO&=\n\xc7x\xbbA\xa5z@\u064966\xc9yd6t\x00\x00\u07d4\xe0I \xdcn\xcc\x1dn\xcc\bO\x88\xaa\n\xf5\u06d7\xbf\x89:\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\xe0Ir\xa8<\xa4\x11+\xc8q\xc7-J\xe1al/\a(\u06c9\x0e\x81\xc7\u007f)\xa3/\x00\x00\u07d4\xe0O\xf5\xe5\xa7\u2bd9]\x88W\xce\x02\x90\xb5:+\x0e\xda]\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xe0P)\xac\xeb\axg[\xef\x17A\xab,\u0493\x1e\xf7\xc8K\x8a\x01\x0f\r\xba\xe6\x10\tR\x80\x00\u07d4\xe0V\xbf?\xf4\x1c&%o\xefQqf\x12\xb9\u04da\u0799\x9c\x89\x05k\xe7W\xa1.\n\x80\x00\u07d4\xe0a\xa4\xf2\xfcw\xb2\x96\u045a\xda#\x8eI\xa5\u02ce\xcb\xfap\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xe0f>\x8c\xd6g\x92\xa6A\xf5nP\x03f\x01G\x88\x0f\x01\x8e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0f\x8f\xa8,\x14\xd6\xe8\xd9:S\x11>\xf2\x86/\xa8\x15\x81\xbc\x89//9\xfclT\x00\x00\x00\u07d4\xe0i\xc0\x173R\xb1\v\xf6\x83G\x19\xdb[\xed\x01\xad\xf9{\xbc\x89\x01\x064\xf8\xe52;\x00\x00\u07d4\xe0l)\xa8\x15\x17\xe0\u0507\xb6\u007f\xb0\xb6\xaa\xbcOW6\x83\x88\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\xe0l\xb6)G\x04\xee\xa7C|/\xc3\xd3\as\xb7\xbf8\x88\x9a\x89\x01\x16\xdc:\x89\x94\xb3\x00\x00\u07d4\xe0q7\xae\r\x11m\x0353\xc4\uad16\xf8\xa9\xfb\tV\x9c\x89K\xe4\xe7&{j\xe0\x00\x00\xe0\x94\xe0v\xdb0\xabHoy\x19N\xbb\xc4]\x8f\xab\x9a\x92B\xf6T\x8a\x01\x06`~4\x94\xba\xa0\x00\x00\u07d4\xe0~\xbb\xc7\xf4\xdaAnB\xc8\xd4\xf8B\xab\xa1b3\xc1%\x80\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\x81\xca\x1fH\x82\xdb`C\u0569\x19\a\x03\xfd\xe0\xab;\xf5m\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe0\x83\xd3Hc\xe0\xe1\u007f\x92ky(\xed\xff1~\x99\x8e\x9cK\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe0\x8b\x9a\xbak\xd9\u048b\xc2\x05gy\xd2\xfb\xf0\xf2\x85Z=\x9d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\x8b\u009c+H\xb1i\xff+\xdc\x16qLXnl\xb8\\\u03c9\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xe0\x8c`11\x06\xe3\xf93O\xe6\xf7\xe7bM!\x110\xc0w\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xe0\x9ch\xe6\x19\x98\xd9\xc8\x1b\x14\xe4\xee\x80+\xa7\xad\xf6\xd7L\u06c9\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xe0\x9f\xeauZ\xee\x1aD\xc0\xa8\x9f\x03\xb5\u07b7b\xba3\x00o\x89;\xa2\x89\xbc\x94O\xf7\x00\x00\xe0\x94\xe0\xa2T\xac\t\xb9r[\xeb\xc8\xe4`C\x1d\xd0s.\xbc\xab\xbf\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xe0\xaai6UU\xb7?(#3\xd1\xe3\f\x1b\xbd\a(T\xe8\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\xe0\xba\u064e\ue598\xdb\xf6\xd7`\x85\xb7\x92=\xe5uN\x90m\x89\t\r\x97/22<\x00\x00\u07d4\xe0\u012b\x90r\xb4\xe6\xe3eJI\xf8\xa8\xdb\x02jK3\x86\xa9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\u0380\xa4a\xb6H\xa5\x01\xfd\v\x82F\x90\u0206\x8b\x0eM\xe8\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe0\xcfi\x8a\x053'\xeb\xd1k}w\x00\t/\xe2\xe8T$F\x89\x05*4\u02f6\x1fW\x80\x00\xe0\x94\xe0\xd21\xe1D\xec\x91\a8l|\x9b\x02\xf1p,\xea\xa4\xf7\x00\x8a\x01\x0f\r\xba\xe6\x10\tR\x80\x00\u07d4\xe0\xd7kqf\xb1\xf3\xa1+@\x91\xee+)\u078c\xaa}\a\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\xe0\xb2\xe2\x9d\xdes\xafu\x98~\xe4Dl\x82\x9a\x18\x9c\x95\xbc\x89\b\x13\xcaV\x90m4\x00\x00\xe0\x94\xe0\xe9xu=\x98/\u007f\x9d\x1d#\x8a\x18\xbdH\x89\xae\xfeE\x1b\x8a\x02\r\u058a\xaf2\x89\x10\x00\x00\u07d4\xe0\xf3r4|\x96\xb5_}C\x06\x03K\xeb\x83&o\xd9\tf\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe0\xf9\x03\xc1\xe4\x8a\xc4!\xabHR\x8f=J&H\b\x0f\xe0C\x897\b\xba\xed=h\x90\x00\x00\u07d4\xe0\xff\v\xd9\x15D9\u0125\xb7#>)\x1d}\x86\x8a\xf5?3\x89\x15y!jQ\xbb\xfb\x00\x00\xe0\x94\xe1\n\xc1\x9cTo\xc2T|a\xc19\xf5\xd1\xf4Zff\u0570\x8a\x01\x02\xdao\xd0\xf7:<\x00\x00\xe0\x94\xe1\fT\x00\x88\x11?\xa6\xec\x00\xb4\xb2\u0202O\x87\x96\xe9n\u010a2\x0fE\t\xab\x1e\xc7\xc0\x00\x00\xe0\x94\xe1\x17:$})\xd8#\x8d\xf0\x92/M\xf2Z\x05\xf2\xafw\u00ca\bx\xc9]V\x0f0G\x80\x00\xe0\x94\xe1 >\xb3\xa7#\xe9\x9c\" \x11|\xa6\xaf\xebf\xfaBOa\x8a\x02\x00\uf49e2V\xfe\x00\x00\xe0\x94\xe11\xf8~\xfc^\xf0~C\xf0\xf2\xf4\xa7G\xb5Q\xd7P\xd9\xe6\x8a\x04<%\xe0\xdc\xc1\xbd\x1c\x00\x00\u07d4\xe13N\x99\x83y\xdf\xe9\x83\x17pby\x1b\x90\xf8\x0e\xe2-\x8d\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe15@\xec\xee\x11\xb2\x12\xe8\xb7u\u070eq\xf3t\xaa\xe9\xb3\xf8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe1;=+\xbf\u073c\x87r\xa23\x15rL\x14%\x16|V\x88\x897\xf3y\x14\x1e\xd0K\x80\x00\u07d4\xe1D=\xbd\x95\xccA#\u007fa:HEi\x88\xa0Oh2\x82\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\xe1F\x17\xf6\x02%\x01\xe9~{>-\x886\xaaa\xf0\xff-\xba\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe1I\xb5rl\xafm^\xb5\xbf*\xccA\xd4\xe2\xdc2\x8d\u1089i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94\xe1T\xda\xea\xdbTX8\xcb\u01aa\fUu\x19\x02\xf5(h*\x8a\x01\n\xfc\x1a\xde;N\xd4\x00\x00\u07d4\xe1l\xe3Ya\xcdt\xbdY\r\x04\u012dJ\x19\x89\xe0V\x91\u0189\a\xea(2uw\b\x00\x00\u07d4\xe1r\xdf\xc8\xf8\f\xd1\xf8\u03459\xdc&\b \x14\xf5\xa8\xe3\u8262\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xe1w\xe0\xc2\x01\xd35\xba9V\x92\x9cW\x15\x88\xb5\x1cR#\xae\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe1x\x12\xf6l^e\x94\x1e\x18lF\x92+n{/\x0e\xebF\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xe1\x80\u079e\x86\xf5{\xaf\xac\u05d0O\x98&\xb6\xb4\xb2c7\xa3\x89-\x04\x1dpZ,`\x00\x00\xe0\x94\xe1\x92H\x9b\x85\xa9\x82\xc1\x882F\xd9\x15\xb2)\xcb\x13 \u007f8\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xe1\x95\xbb\xc6,{tD\x04\x0e\xb9\x96#\x96Ovg\xb3v\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xe2\x06\xfbs$\xe9\u07b7\x9e\x19\x904\x96\u0596\x1b\x9b\xe5f\x03\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xe2\aW\x8e\x1fM\u06cf\xf6\u0546{9X-q\xb9\x81*\u0149\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4\xe2\b\x81*h@\x98\xf3\xdaN\xfej\xba%bV\xad\xfe?\xe6\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe2\tT\xd0\xf4\x10\x8c\x82\xd4\u0732\x14\x8d&\xbb\xd9$\xf6\xdd$\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xe2\v\xb9\xf3\x96d\x19\xe1K\xbb\xaa\xaag\x89\xe9$\x96\u03e4y\x89\xbb\xd8%\x03\aRv\x00\x00\u07d4\xe2\r\x1b\xcbq(m\xc7\x12\x8a\x9f\xc7\xc6\xed\u007fs8\x92\xee\xf5\x896d\xf8\xe7\xc2J\xf4\x00\x00\u0794\xe2\x19\x12\x15\x98?3\xfd3\xe2,\u0522I\x00T\xdaS\xfd\u0708\xdbD\xe0I\xbb,\x00\x00\u07d4\xe2\x19\x8c\x8c\xa1\xb3\x99\xf7R\x15a\xfdS\x84\xa7\x13/\xbaHk\x897\b\xba\xed=h\x90\x00\x00\xe0\x94\xe2\x1cw\x8e\xf2\xa0\xd7\xf7Q\xea\x8c\aM\x1f\x81\"C\x86>N\x8a\x01\x1f\xc7\x0e,\x8c\x8a\xe1\x80\x00\xe0\x94\xe2)\xe7F\xa8?,\xe2S\xb0\xb0>\xb1G$\x11\xb5~W\x00\x8a\x016\x9f\xb9a(\xacH\x00\x00\u07d4\xe2+ \xc7x\x94F;\xafwL\xc2V\u057d\u06ff}\xdd\t\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe20\xfe\x1b\xff\x03\x18m\x02\x19\xf1]LH\x1b}Y\xbe(j\x89\x01\xfdt\x1e\x80\x88\x97\x00\x00\u07d4\xe27\xba\xa4\xdb\u0252n2\xa3\xd8]\x12d@-T\xdb\x01/\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe2A\t\xbe/Q=\x87I\x8e\x92j(d\x99uO\x9e\u051e\x890\x0e\xa8\xad\x1f'\xca\x00\x00\u07d4\xe2Fh<\u025d\xb7\u0125+\u02ec\xaa\xb0\xb3/k\xfc\x93\u05c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe2Z\x16{\x03\x1e\x84am\x0f\x01?1\xbd\xa9]\xcccP\xb9\x8a\x02\x8c*\xaa\u0243\xd0]\u0187st\xa8\xf4F\xee\xe9\x89\n\xb6@9\x12\x010\x00\x00\u07d4\xe2\x8b\x06\"Y\xe9n\xeb<\x8dA\x04\x94?\x9e\xb3%\x89<\xf5\x89Hz\x9a0E9D\x00\x00\xe0\x94\u237c\x8e\xfd^Ajv.\xc0\xe0\x18\x86K\xb9\xaa\x83({\x8a\x051\xf2\x00\xab>\x03\n\x80\x00\u07d4\xe2\x90K\x1a\xef\xa0V9\x8bb4\xcb5\x81\x12\x88\xd76\xdbg\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\u274a\xe4R\xdc\xf3\xb6\xacd^c\x04\t8UQ\xfa\xae\n\x89\x04Z\r\xa4\xad\xf5B\x00\x00\u07d4\xe2\xbb\xf8FA\xe3T\x1fl3\xe6\xedh:cZp\xbd\xe2\xec\x89\x1bA<\xfc\xbfY\xb7\x80\x00\u07d4\xe2\xcf6\n\xa22\x9e\xb7\x9d+\xf7\xca\x04\xa2z\x17\xc52\xe4\u0609\x05\x87\x88\u02d4\xb1\xd8\x00\x00\u07d4\xe2\xdf#\xf6\xea\x04\xbe\xcfJ\xb7\x01t\x8d\xc0\x961\x84U\\\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xe2\xe1\\`\xdd8\x1e:K\xe2Pq\xab$\x9aL\\Rd\u0689\u007fk\u011b\x81\xb57\x00\x00\u07d4\xe2\xe2nN\x1d\xcf0\xd0H\xccn\u03ddQ\xec\x12\x05\xa4\xe9&\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xe2\xeei\x1f#~\xe6R\x9beW\xf2\xfc\xdd=\xcf\fY\xecc\x8a\x01'r\x9c\x14h| \x00\x00\u07d4\xe2\xef\xa5\xfc\xa7\x958\xce`h\xbf1\xd2\xc5\x16\xd4\xd5<\b\xe5\x89\a\x1c\xc4\b\xdfc@\x00\x00\xe0\x94\xe2\xef\u0429\xbc@~\xce\x03\xd6~\x8e\xc8\xe9\u0483\xf4\x8d*I\x8a\x02\x99\xb3;\xf9\u0144\xe0\x00\x00\u07d4\xe2\xf4\r5\x8f^?\xe7F>\xc7\x04\x80\xbd.\u04d8\xa7\x06;\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xe2\xf98=X\x10\xea{C\x18+\x87\x04\xb6+'\xf5\x92]9\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe2\xff\x9e\xe4\xb6\xec\xc1AA\xcct\xcaR\xa9\xe7\xa2\xee\x14\xd9\b\x89K\xe4\xe7&{j\xe0\x00\x00\xe0\x94\xe3\x02\x12\xb2\x01\x1b\xb5k\xdb\xf1\xbc5i\x0f:N\x0f\xd9\x05\xea\x8a\x01\xb2\u07dd!\x9fW\x98\x00\x00\u07d4\xe3\x03\x16\u007f=I`\xfe\x88\x1b2\x80\n+J\xef\xf1\xb0\x88\u0509lk\x93[\x8b\xbd@\x00\x00\u07d4\xe3\x04\xa3/\x05\xa87btJ\x95B\x97o\xf9\xb7#\xfa1\xea\x89Ur\xf2@\xa3F \x00\x00\u07d4\xe3\bCR\x04y7d\xf5\xfc\xbee\xebQ\x0fZtJeZ\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe3\t\x97L\xe3\x9d`\xaa\xdf.ig2Q\xbf\x0e\x04v\n\x10\x89\r\xc5_\xdb\x17d{\x00\x00\u07d4\xe3\x1bN\xef\x18L$\xab\t\x8e6\xc8\x02qK\xd4t=\xd0\u0509\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe3!\xbbJ\x94j\xda\xfd\xad\xe4W\x1f\xb1\\\x00C\u04de\xe3_\x89Udu8+L\x9e\x00\x00\u07d4\xe3&<\xe8\xafm\xb3\xe4gXE\x02\xedq\t\x12^\xae\"\xa5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe3+\x1cG%\xa1\x87TI\u93d7\x0e\xb3\xe5@b\xd1X\x00\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe3/\x95vmW\xb5\xcdK\x172\x89\u0587o\x9edU\x81\x94\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xe38@\u063c\xa7\u0698\xa6\xf3\u0416\xd8=\xe7\x8bp\xb7\x1e\xf8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe38\xe8Y\xfe.\x8c\x15UHH\xb7\\\xae\u0368w\xa0\xe82\x89a\xac\xff\x81\xa7\x8a\xd4\x00\x00\u07d4\xe3=\x98\x02 \xfa\xb2Y\xafj\x1fK8\xcf\x0e\xf3\xc6\xe2\xea\x1a\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe3=\xf4\u0380\u0336*v\xb1+\xcd\xfc\xec\xc4b\x89\x97:\xa9\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xe3?\xf9\x87T\x1d\xde\\\xde\u0a29m\xcc?3\xc3\xf2L\u008a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\xe3A\v\xb7U|\xf9\x1dy\xfai\xd0\xdf\xea\n\xa0u@&Q\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe3Ad-@\u04af\xce.\x91\a\xc6py\xacz&`\bl\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe3TS\xee\xf2\xcc2\x89\x10CR\x8d\t\x84i\x80\x00\xe0\x94\xe5\x10\xd6y\u007f\xba=f\x93\x83Z\x84N\xa2\xadT\x06\x91\x97\x1b\x8a\x03\xae9\xd4s\x83\xe8t\x00\x00\u07d4\xe5\x14!\xf8\xee\"\x10\xc7\x1e\xd8p\xfea\x82v\u0215J\xfb\xe9\x89Hz\x9a0E9D\x00\x00\u07d4\xe5\x1e\xb8~\u007f\xb71\x1fR(\xc4y\xb4\x8e\u0247\x881\xacL\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5!V1\xb1BH\xd4Z%R\x96\xbe\xd1\xfb\xfa\x030\xff5\x89G\x03\xe6\xebR\x91\xb8\x00\x00\xe0\x94\xe5(\xa0\xe5\xa2g\xd6g\xe99:e\x84\xe1\x9b4\u071b\xe9s\x8a\x01/\x93\x9c\x99\xed\xab\x80\x00\x00\u07d4\xe54%\xd8\xdf\x1f\x11\xc3A\xffX\xae_\x148\xab\xf1\xcaS\u03c9\x11t\xa5\xcd\xf8\x8b\xc8\x00\x00\u07d4\xe5No\x9c\xffV\xe1\x9cF\x1e\xb4T\xf9\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe5A\x02SM\xe8\xf2>\xff\xb0\x93\xb3\x12B\xad;#?\xac\xfd\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xe5E\xee\x84\xeaH\xe5d\x16\x1e\x94\x82\u055b\xcf@j`,\xa2\x89dI\xe8NG\xa8\xa8\x00\x00\u07d4\xe5H\x1a\u007f\xedB\xb9\x01\xbb\xed x\x9b\u052d\xe5\r_\x83\xb9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5Y\xb5\xfd3{\x9cUr\xa9\xbf\x9e\x0f%!\xf7\xd4F\xdb\xe4\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe5\\\x80R\n\x1b\x0fu[\x9a,\xd3\xce!Ov%e>\x8a\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe5mC\x13$\xc9)\x11\xa1t\x9d\xf2\x92p\x9c\x14\xb7ze\u034a\x01\xbc\x85\xdc*\x89\xbb \x00\x00\u07d4\xe5})\x95\xb0\xeb\xdf?<\xa6\xc0\x15\xeb\x04&\r\xbb\x98\xb7\u0189lk\x93[\x8b\xbd@\x00\x00\u07d4\u51f1j\xbc\x8at\b\x1e6\x13\xe1CB\xc03u\xbf\bG\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5\x89\xfav\x98M\xb5\xec@\x04\xb4n\u8954\x92\xc3\aD\u0389\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\xe5\x8d\xd228\xeen\xa7\xc2\x13\x8d8]\xf5\x00\xc3%\xf3v\xbe\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xe5\x95?\xeaIq\x04\xef\x9a\xd2\xd4\xe5\x84\x1c'\x1f\a5\x19\u0089&)\xf6n\fS\x00\x00\x00\xe0\x94\u5587\x97F\x8e\xf7g\x10\x1bv\x1dC\x1f\xce\x14\xab\xff\u06f4\x8a\x01\xb3\xd9i\xfaA\x1c\xa0\x00\x00\u07d4\xe5\x97\xf0\x83\xa4i\xc4Y\x1c=+\x1d,w'\x87\xbe\xfe'\xb2\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\u07d4\xe5\x9b;\xd3\x00\x89?\x97#>\xf9G\xc4or\x17\xe3\x92\xf7\xe9\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe5\xa3e4<\xc4\xeb\x1ew\x03h\xe1\xf1\x14Jw\xb82\xd7\xe0\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xe5\xa3\xd7\xeb\x13\xb1\\\x10\x01w#m\x1b\xeb0\xd1~\xe1T \x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5\xaa\v\x83;\xb9\x16\xdc\x19\xa8\xddh?\x0e\xde$\x1d\x98\x8e\xba\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\u5def\x14i\x86\xc0\xff\x8f\x85\xd2.l\xc34\a}\x84\xe8$\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5\xb8&\x19l\x0e\x1b\xc1\x11\x9b\x02\x1c\xf6\xd2Y\xa6\x10\u0256p\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe5\xb9o\u026c\x03\xd4H\xc1a:\xc9\x1d\x15\x97\x81E\xdb\xdf\u0449\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\u5e40\u048e\xec\xe2\xc0o\xcal\x94s\x06\x8b7\u0526\xd6\xe9\x89%\xaf\u058c\xac+\x90\x00\x00\u07d4\u5eb4\xf0\xaf\u0629\u0463\x81\xb4Wa\xaa\x18\xf3\xd3\xcc\xe1\x05\x89Q\xbf\xd7\xc18x\xd1\x00\x00\u07d4\xe5\xbc\u020c;%on\xd5\xfeU\x0eJ\x18\x19\x8b\x943V\xad\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5\xbd\xf3OL\xccH>L\xa50\xcc|\xf2\xbb\x18\xfe\xbe\x92\xb3\x89\x06\xd85\xa1\v\xbc\xd2\x00\x00\u07d4\xe5\u0713I\xcbR\xe1a\x19a\"\u03c7\xa3\x896\xe2\xc5\u007f4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5\xe38\x00\xa1\xb2\xe9k\xde\x101c\n\x95\x9a\xa0\a\xf2nQ\x89Hz\x9a0E9D\x00\x00\u07d4\xe5\xe3~\x19@\x8f,\xfb\xec\x834\x9d\u0501S\xa4\xa7\x95\xa0\x8f\x89\u3bb5sr@\xa0\x00\x00\u07d4\xe5\xed\xc7>bo]4A\xa4U9\xb5\xf7\xa3\x98\u0153\xed\xf6\x89.\xe4IU\b\x98\xe4\x00\x00\u07d4\xe5\xed\xf8\x12?$\x03\xce\x1a\x02\x99\xbe\xcfz\xactM\a_#\x89\n\xdaUGK\x814\x00\x00\u07d4\xe5\xf8\xefm\x97\x066\xb0\u072aO \x0f\xfd\xc9\xe7Z\xf1t\x1c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5\xfb1\xa5\xca\xeej\x96\xde9;\xdb\xf8\x9f\xbee\xfe\x12[\xb3\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe5\xfb\xe3I\x84\xb67\x19o3\x1cg\x9d\f\fG\xd84\x10\xe1\x89llD\xfeG\xec\x05\x00\x00\u07d4\xe6\tU\xdc\v\xc1V\xf6\xc4\x18I\xf6\xbdwk\xa4K\x0e\xf0\xa1\x89\x10C\x16'\xa0\x93;\x00\x00\u07d4\xe6\nU\xf2\u07d9m\u00ee\xdbil\b\xdd\xe09\xb2d\x1d\xe8\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe6\x11[\x13\xf9y_~\x95e\x02\xd5\aEg\u06b9E\xcek\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xe6\x1f(\t\x15\xc7t\xa3\x1d\"<\xf8\f\x06\x92f\xe5\xad\xf1\x9b\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4\xe6/\x98e\a\x12\xeb\x15\x87S\xd8)r\xb8\u9723\xf6\x18w\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe6/\x9d|d\xe8\xe2cZ\xeb\x88=\xd7;\xa6\x84\xee|\x10y\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xe6>xt\x14\xb9\x04\x84x\xa5\a35\x9e\xcd\xd7\xe3dz\xa6\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xe6FfXr\xe4\v\rz\xa2\xff\x82r\x9c\xaa\xba[\xc3\u8789\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe6N\xf0\x12e\x8dT\xf8\xe8`\x9cN\x90#\xc0\x9f\xe8e\xc8;\x89\x01\x84\x93\xfb\xa6N\xf0\x00\x00\u07d4\xe6On\x1dd\x01\xb5l\akd\xa1\xb0\x86}\v/1\rN\x89\x02\u02edq\xc5:\xe5\x00\x00\u07d4\xe6g\xf6R\xf9W\u008c\x0ef\u04364\x17\xc8\f\x8c\x9d\xb8x\x89 \x9d\x92/RY\xc5\x00\x00\xe0\x94\xe6w\xc3\x1f\xd9\xcbr\x00u\u0724\x9f\x1a\xbc\xcdY\xec3\xf74\x8a\x01\xa6\u05be\xb1\xd4.\xe0\x00\x00\u07d4\xe6|,\x16e\u02038h\x81\x87b\x9fI\xe9\x9b`\xb2\u04fa\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xe6\x9al\xdb:\x8a}\xb8\xe1\xf3\f\x8b\x84\xcds\xba\xe0+\xc0\xf8\x8a\x03\x94\xfd\xc2\xe4R\xf6q\x80\x00\u07d4\xe6\x9d\x1c7\x8bw\x1e\x0f\xef\xf0Q\xdbi\xd9f\xacgy\xf4\xed\x89\x1d\xfaj\xaa\x14\x97\x04\x00\x00\u07d4\xe6\x9f\xcc&\xed\"_{.7\x984\xc5$\xd7\f\x175\u5f09lk\x93[\x8b\xbd@\x00\x00\u07d4\xe6\xa3\x01\x0f\x02\x01\xbc\x94\xffg\xa2\xf6\x99\xdf\xc2\x06\xf9\xe7gB\x89/\xa7\xcb\xf6dd\x98\x00\x00\u07d4\xe6\xa6\xf6\xddop\xa4V\xf4\xec\x15\xefz\xd5\xe5\u06f6\x8b\xd7\u0709\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe6\xb2\x0f\x98\n\xd8S\xad\x04\xcb\xfc\x88|\xe6`\x1ck\xe0\xb2L\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u6cec?]M\xa5\xa8\x85}\v?0\xfcK+i+w\u05c9O%\x91\xf8\x96\xa6P\x00\x00\u07d4\xe6\xb9T_~\u0406\xe5R\x92F9\xf9\xa9\xed\xbb\xd5T\v>\x89\xcb\xd4{n\xaa\x8c\xc0\x00\x00\xe0\x94\xe6\xbc\xd3\n\x8f\xa18\xc5\xd9\xe5\xf6\xc7\xd2\u0680i\x92\x81-\u034a7\x0e\xa0\xd4|\xf6\x1a\x80\x00\x00\u07d4\xe6\xc8\x1f\xfc\xec\xb4~\xcd\xc5\\\vq\xe4\x85_>^\x97\xfc\x1e\x89\x12\x1e\xa6\x8c\x11NQ\x00\x00\u07d4\xe6\xcb&\vqmL\n\xb7&\xee\xeb\a\xc8pr\x04\xe2v\xae\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe6\xcb?1$\xc9\xc9\xcc84\xb1'K\xc33dV\xa3\x8b\xac\x89\x17+\x1d\xe0\xa2\x13\xff\x00\x00\xe0\x94\xe6\xd2\"\t\xff\u0438u\t\xad\xe3\xa8\xe2\xefB\x98y\u02c9\xb5\x8a\x03\xa7\xaa\x9e\x18\x99\xca0\x00\x00\u07d4\xe6\u051f\x86\xc2(\xf4sg\xa3^\x88l\xaa\xcb'\x1eS\x94)\x89\x16^\xc0\x9d\xa7\xa1\x98\x00\x00\u07d4\xe6\xe6!\xea\xab\x01\xf2\x0e\xf0\x83k|\xadGFL\xb5\xfd<\x96\x89\x11!\x93B\xaf\xa2K\x00\x00\u07d4\xe6\xe8\x861{jf\xa5\xb4\xf8\x1b\xf1d\xc58\xc2d5\x17e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe6\u98ddu\x0f\xe9\x949N\xb6\x82\x86\xe5\xeab\xa6\x99x\x82\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\xe6\xec\\\xf0\u011b\x9c1~\x1epc\x15\uf7b7\xc0\xbf\x11\xa7\x8a\x03\xa4i\xf3F~\x8e\xc0\x00\x00\u07d4\xe6\xf5\xebd\x9a\xfb\x99Y\x9cAK'\xa9\xc9\xc8U5\u007f\xa8x\x89\x90\xf54`\x8ar\x88\x00\x00\xe0\x94\xe6\xfe\n\xfb\x9d\xce\xdd7\xb2\xe2,E\x1b\xa6\xfe\xabg4\x803\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xe7\x10\xdc\u041b\x81\x01\xf9C{\xd9}\xb9\ns\xef\x99=\v\xf4\x89\x14\xee6\xc0Z\xc2R\x00\x00\u07d4\xe7'\xe6~\xf9\x11\xb8\x1fl\xf9\xc7?\xcb\xfe\xbc+\x02\xb5\xbf\u0189lk\x93[\x8b\xbd@\x00\x00\u07d4\xe7.\x1d3\\\u009a\x96\xb9\xb1\xc0/\x00:\x16\xd9q\xe9\v\x9d\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xe71\x1c\x953\xf0\t,rH\xc9s\x9b[,\x86J4\xb1\u0389\x97\xf9}l\xc2m\xfe\x00\x00\u07d4\xe7;\xfe\xad\xa6\xf0\xfd\x01o\xbc\x84>\xbc\xf6\xe3p\xa6[\xe7\f\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xe7<\xcfCg%\xc1Q\xe2U\xcc\xf5!\f\xfc\xe5\xa4?\x13\xe3\x89\x01\x15NS!}\xdb\x00\x00\u07d4\xe7B\xb1\xe6\x06\x9a\x8f\xfc'\f\xc6\x1f\xa1d\xac\x15SE\\\x10]\x04\x88~\x14\x89\x06\x96\xd8Y\x00 \xbb\x00\x00\u07d4\xe7\\\x1f\xb1w\b\x9f>X\xb1\x06y5\xa6Yn\xf1s\u007f\xb5\x89\x05j\x87\x9f\xa7uG\x00\x00\u07d4\xe7\\;8\xa5\x8a?3\xd5V\x90\xa5\xa5\x97f\xbe\x18^\x02\x84\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe7a\xd2\u007f\xa3P,\xc7k\xb1\xa6\bt\x0e\x14\x03\u03dd\xfci\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\u07d4\xe7f\xf3O\xf1o<\xfc\xc9s!r\x1fC\xdd\xf5\xa3\x8b\f\xf4\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\xe7m\x94Z\xa8\x9d\xf1\xe4W\xaa4+1\x02\x8a^\x910\xb2\u03897\b\xba\xed=h\x90\x00\x00\u07d4\xe7s^\xc7e\x18\xfcj\xa9-\xa8qZ\x9e\xe3\xf6%x\x8f\x13\x89lM\x16\v\xaf\xa1\xb7\x80\x00\xe0\x94\xe7z\x89\xbdE\xdc\x04\xee\xb4\xe4\x1d{Ykp~nQ\xe7L\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xe7}}\uac96\u0234\xfa\a\xca;\xe1\x84\x16=Zm`l\x89\x05\x049\x04\xb6q\x19\x00\x00\u07d4\xe7\u007f\xeb\xab\xdf\b\x0f\x0f]\xca\x1d?Wf\xf2\xa7\x9c\x0f\xfa|\x89K\"\x9d(\xa8Ch\x00\x00\xe0\x94\u7025c\x06\xba\x1ek\xb31\x95,\"S\x9b\x85\x8a\xf9\xf7}\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\xe7\x81\xecs-@\x12\x02\xbb\x9b\xd18`\x91\r\xd6\u009a\xc0\xb6\x89C8t\xf62\xcc`\x00\x00\u07d4\xe7\x84\xdc\xc8s\xaa\x8c\x15\x13\xec&\xff6\xbc\x92\xea\xc6\xd4\xc9h\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe7\x91-L\xf4V,W=\xdc[q\xe3s\x10\xe3x\xef\x86\u0249\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\xe7\x91\u0545\xb8\x996\xb2])\x8f\x9d5\xf9\xf9\xed\xc2Z)2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe7\x924\x9c\xe9\xf6\xf1O\x81\xd0g@\x96\xbe\xfa\x1f\x92!\xcd\xea\x89[]#J\r\xb48\x80\x00\u07d4\xe7\x96\xfdN\x83\x9bL\x95\xd7Q\x0f\xb7\xc5\xc7+\x83\xc6\xc3\xe3\u01c9\x1b\xc43\xf2?\x83\x14\x00\x00\xe0\x94\xe7\xa4/Y\xfe\xe0t\xe4\xfb\x13\xea\x9eW\xec\xf1\xccH(\"I\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xe7\xa4V\f\x84\xb2\x0e\x0f\xb5LIg\f)\x03\xb0\xa9lB\xa4\x89 j\xea\u01e9\x03\x98\x00\x00\u07d4\xe7\xa8\xe4q\xea\xfby\x8fET\xccnRg0\xfdV\xe6,}\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u7f82\xc6Y<\x1e\xed\xdd*\xe0\xb1P\x01\xff \x1a\xb5{/\x89\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\u07d4\xe7\u01b5\xfc\x05\xfct\x8e[C\x81rdI\xa1\xc0\xad\x0f\xb0\xf1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe7\xd1u$\xd0\v\xad\x82I|\x0f'\x15jd\u007f\xf5\x1d'\x92\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xe7\xd2\x13\x94\u007f\u02d0J\xd78H\v\x1e\xed/\\2\x9f'\xe8\x89\x01\x03\u00f1\xd3\xe9\xc3\x00\x00\u07d4\xe7\xd6$\x06 \xf4,^\u06f2\xed\xe6\xae\xc4=\xa4\xed\x9bWW\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe7\xda`\x9d@\xcd\xe8\x0f\x00\xce[O\xfbj\xa9\u04304\x94\xfc\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe7\xf0oi\x9b\xe3\x1cD\vC\xb4\xdb\x05\x01\xec\x0e%&\x16D\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe7\xf4\xd7\xfeoV\x1f\u007f\xa1\xda0\x05\xfd6TQ\xad\x89\u07c9\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe7\xfd\x8f\xd9Y\xae\xd2v~\xa7\xfa\x96\f\xe1\xdbS\xaf\x80%s\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe8\x0e\u007f\xef\x18\xa5\xdb\x15\xb0\x14s\xf3\xadkx\xb2\xa2\xf8\xac\u0649\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe8\x13\u007f\xc1\xb2\xec|\xc7\x10:\xf9!\x89\x9bJ9\xe1\xd9Y\xa1\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4\xe8\x1c-4l\n\xdfL\xc5g\b\xf69K\xa6\xc8\u0226J\x1e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe8,X\xc5yC\x1bg5F\xb5:\x86E\x9a\xca\xf1\u079b\x93\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe84\xc6C\x18 \\\xa7\xddJ!\xab\xcb\b&l\xb2\x1f\xf0,\x8965\xc6 G9\u0640\x00\u07d4\xe86\x04\xe4\xffk\xe7\xf9o`\x18\xd3\xec0r\xecR]\xffk\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\xe0\x94\xe8E\xe3\x87\xc4\xcb\u07d8\"\x80\xf6\xaa\x01\xc4\x0eK\xe9X\u0772\x8a\x05K@\xb1\xf8R\xbd\xa0\x00\x00\u07d4\xe8H\xca~\xbf\xf5\xc2O\x9b\x9c1g\x97\xa4;\xf7\xc3V)-\x89\x06.\x11\\\x00\x8a\x88\x00\x00\u07d4\xe8KU\xb5%\xf1\x03\x9etK\x91\x8c\xb33$\x92\xe4^\xcaz\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe8O\x80v\xa0\xf2\x96\x9e\xcd3>\xef\x8d\xe4\x10B\x98b\x91\xf2\x89\x17k4O*x\xc0\x00\x00\u07d4\xe8d\xfe\xc0~\xd1!Je1\x1e\x11\xe3)\xde\x04\r\x04\xf0\xfd\x89Y\u0283\xf5\xc4\x04\x96\x80\x00\u07d4\xe8}\xba\xc66\xa3w!\xdfT\xb0\x8a2\xefIY\xb5\xe4\xff\x82\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe8~\x9b\xbf\xbb\xb7\x1c\x1at\ft\xc7#Bm\xf5]\x06=\u064a\x01\xb1\x92\x8c\x00\u01e68\x00\x00\u07d4\xe8~\xacm`+A\t\xc9g\x1b\xf5{\x95\f,\xfd\xb9\x9dU\x89\x02\xb4\xf2\x19r\xec\xce\x00\x00\xe0\x94\u807b\xbeir-\x81\xef\xec\xaaH\u0455*\x10\xa2\xbf\xac\x8f\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\xe8\x92Is\x8b~\xce\xd7\xcbfjf\xe4s\xbcv\x82/U\t\x8d\x89\xb9\x1c\u0149lk\x93[\x8b\xbd@\x00\x00\u07d4\xe8\xc3\u04f0\xe1\u007f\x97\xd1\xe7V\xe6\x84\xf9N\x14p\xf9\x9c\x95\xa1\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\xe8\xc3\xf0E\xbb}8\xc9\xd2\U000d5c3a\x84\x92\xb2S#\t\x01\x8a\x01\xe7\xe4\x17\x1b\xf4\u04e0\x00\x00\u07d4\xe8\xccC\xbcO\x8a\xcf9\xbf\xf0N\xbf\xbfB\xaa\xc0j2\x84p\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe8\xd9B\xd8/\x17^\xcb\x1c\x16\xa4\x05\xb1\x01C\xb3\xf4k\x96:\x89\x1e\xd2\xe8\xffm\x97\x1c\x00\x00\u07d4\xe8\u077e\xd72\xeb\xfeu@\x96\xfd\xe9\bk\x8e\xa4\xa4\xcd\xc6\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe8\xder^\xca]\xef\x80_\xf7\x94\x1d1\xac\x1c.4-\xfe\x95\x89\x85~\ro\x1d\xa7j\x00\x00\u07d4\xe8\xe9\x85\x05\x86\xe9OR\x99\xabIK\xb8!\xa5\xf4\f\x00\xbd\x04\x89\xcf\x15&@\xc5\xc80\x00\x00\xe0\x94\xe8\xea\u047b\x90\xcc\u00ee\xa2\xb0\xdc\u0175\x80VUFU\xd1\u054a\x01\xa4\xab\xa2%\xc2\a@\x00\x00\u07d4\xe8\xea\xf1)D\t-\xc3Y\x9b9S\xfa|\xb1\xc9v\x1c\xc2F\x89a\x94\x04\x9f0\xf7 \x00\x00\xe0\x94\xe8\xedQ\xbb\xb3\xac\xe6\x9e\x06\x02K3\xf8hD\xc4sH\u06de\x8a\"\xf9\xea\x89\xf4\xa7\xd6\xc4\x00\x00\u07d4\xe8\xef\x10\r|\xe0\x89X2\xf2g\x8d\xf7-J\u03cc(\xb8\xe3\x89\x1b\x1bk\u05efd\xc7\x00\x00\u07d4\xe8\xf2\x99i\xe7\\e\xe0\x1c\xe3\xd8aT }\n\x9e|v\xf2\x89\xa2/\xa9\xa7:'\x19\x80\x00\u07d4\xe8\xfc6\xb0\x13\x1e\xc1 \xac\x9e\x85\xaf\xc1\f\xe7\vV\u0636\xba\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe9\n5L\xec\x04\u059e]\x96\xdd\xc0\xc5\x13\x8d=3\x15\n\xa0\x89\x1b\x1a}\u03caD\u04c0\x00\xe0\x94\xe9\x13>}1\x84]_+f\xa2a\x87\x92\xe8i1\x1a\xcff\x8a\x05\x17\xc0\xcb\xf9\xa3\x90\x88\x00\x00\u07d4\xe9\x1d\xac\x01\x95\xb1\x9e7\xb5\x9bS\xf7\xc0\x17\xc0\xb29[\xa4L\x89e\xea=\xb7UF`\x00\x00\u07d4\xe9\x1f\xa0\xba\xda\u0779\xa9~\x88\xd3\xf4\xdb|U\u05bbt0\xfe\x89\x14b\fW\xdd\xda\xe0\x00\x00\u07d4\xe9#\xc0aw\xb3B~\xa4H\xc0\xa6\xff\x01\x9bT\xccT\x8d\x95\x89\x01\xf7\x80\x01Fg\xf2\x80\x00\xe0\x94\xe9=G\xa8\u0288]T\fNRo%\xd5\xc6\xf2\xc1\b\u0138\x8a\x17\xda:\x04\u01f3\xe0\x00\x00\x00\u07d4\xe9E\x8fh\xbb',\xb5g:\x04\xf7\x81\xb4\x03Uo\u04e3\x87\x89\x03N\x8b\x88\xce\xe2\xd4\x00\x00\u07d4\xe9IA\xb6\x03`\x19\xb4\x01j0\xc1\x03}Zi\x03\xba\xba\xad\x89*H\xac\xabb\x04\xb0\x00\x00\u07d4\xe9I[\xa5\x84'(\xc0\ud5fe7\xd0\xe4\"\xb9\x8di ,\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe9M\xed\x99\u0735r\xb9\xbb\x1d\u02e3/m\xee\x91\xe0W\x98N\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\xe0\x94\xe9QyR}\uc951l\xa9\xa3\x8f!\\\x1e\x9c\xe77\xb4\u024a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\xe9U\x91\x85\xf1f\xfc\x95\x13\xccq\x11aD\xce-\xeb\x0f\x1dK\x8a\x04<3\xc1\x93ud\x80\x00\x00\u0794\xe9^\x92\xbb\xc6\xde\a\xbf:f\x0e\xbf_\xeb\x1c\x8a5'\xe1\u0148\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94\xe9e\u06a3@9\xf7\xf0\xdfb7Z7\u5acar\xb3\x01\xe7\x8a\x01\x03\xfd\xde\u0373\xf5p\x00\x00\u07d4\xe9i\xea\x15\x95\xed\xc5\u0127\a\xcf\xde8\t)c2Q\xa2\xb0\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe9k\x18N\x1f\x0fT\x92J\xc8t\xf6\v\xbfDptF\xb7+\x89\x9d\xcc\x05\x15\xb5n\f\x00\x00\xe0\x94\xe9m}L\xdd\x15U:NM1mmd\x80\xca<\xea\x1e8\x8a\x02\x95]\x02\xe1\xa15\xa0\x00\x00\u07d4\xe9n-8\x13\xef\xd1\x16_\x12\xf6\x02\xf9\u007fJb\x90\x9d\x1b;\xc0\xe9\xaa\"\u007f\x90\x89'\xcaK\xd7\x19\xf0\xb8\x00\x00\u07d4\xea,\x19}&\xe9\x8b\r\xa8>\x1br\u01c7a\x8c\x97\x9d=\xb0\x89\x01\x11du\x9f\xfb2\x00\x00\xe0\x94\xea7y\xd1J\x13\xf6\u01c5f\xbc\xde@5\x91A:b9\u06ca)\xb7d2\xb9DQ \x00\x00\u07d4\xeaN\x80\x9e&j\xe5\xf1<\xdb\u33dd\x04V\xe68m\x12t\x89\xf3\xf2\v\x8d\xfai\xd0\x00\x00\xe0\x94\xeaS\xc9T\xf4\xed\x97\xfdH\x10\x11\x1b\u06b6\x9e\xf9\x81\xef%\xb9\x8a\x03\xa9\u057a\xa4\xab\xf1\xd0\x00\x00\u07d4\xeaS\xd2ed\x85\x9d\x9e\x90\xbb\x0eS\xb7\xab\xf5`\xe0\x16,8\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xea`Ci\x12\xdek\xf1\x87\u04e4r\xff\x8fS3\xa0\xf7\xed\x06\x89\x01\x11du\x9f\xfb2\x00\x00\u07d4\xea`T\x9e\xc7U?Q\x1d!I\xf2\xd4fl\xbd\x92C\xd9<\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xeaf\xe7\xb8M\u037f6\xee\xa3\xe7[\x858*u\xf1\xa1]\x96\x89]\xbc\x91\x91&o\x11\x80\x00\u07d4\xeahlPW\t<\x17\x1cf\u06d9\xe0\x1b\x0e\xce\xcb0\x86\x83\x89\x14\u0768],\xe1G\x80\x00\u07d4\xeaj\xfe,\xc9(\xac\x83\x91\xeb\x1e\x16_\xc4\x00@\xe3t!\u7262\u007f\xa0c\xb2\xe2\xe6\x80\x00\u07d4\xeay\x05}\xab\xef^d\xe7\xb4O\u007f\x18d\x8e~S7\x18\u0489\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xea|Mm\xc7)\xcdk\x15|\x03\xad#|\xa1\x9a \x93F\u00c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xea\x81h\xfb\xf2%\xe7\x86E\x9c\xa6\xbb\x18\xd9c\xd2kPS\t\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xea\x81\u02868T\f\xd9\xd4\xd7=\x06\x0f,\xeb\xf2$\x1f\xfc>\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xea\x83\x17\x19yYB@A\xd9\xd7\xc6z>\xce\x1d\xbbx\xbbU\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\xea\x85'\xfe\xbf\xa1\xad\xe2\x9e&A\x93)\u04d3\xb9@\xbb\xb7\u0709lj\xccg\u05f1\xd4\x00\x00\u07d4\xea\x8f0\xb6\xe4\xc5\xe6R\x90\xfb\x98d%\x9b\u0159\x0f\xa8\ue289\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xea\x94\xf3(\b\xa2\uf29b\xf0\x86\x1d\x1d$\x04\xf7\xb7\xbe%\x8a\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xea\xa4\\\xea\x02\xd8},\xc8\xfd\xa9CN-\x98[\xd4\x03\x15\x84\x89h\x1f\xc2\xccn+\x8b\x00\x00\xe0\x94\uac3d\x14\x83\t\x18l\xf8\xcb\xd1;r2\xd8\tZ\u02c3:\x8a\x02C\x9a\x88\x1cjq|\x00\x00\u07d4\uaed0\xd3y\x89\xaa\xb3\x1f\xea\xe5G\xe0\xe6\xf3\x99\x9c\xe6\xa3]\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xea\xc0\x82~\xff\fn?\xf2\x8a}JT\xf6\\\xb7h\x9d{\x99\x89\x9a\xd9\u67ddGR\x00\x00\u07d4\xea\xc1H(&\xac\xb6\x11\x1e\x19\xd3@\xa4_\xb8QWk\xed`\x89\x01\xbe\x8b\xab\x04\u067e\x80\x00\xe0\x94\xea\xc1{\x81\xedQ\x91\xfb\b\x02\xaaT3s\x13\x83A\a\xaa\xa4\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xea\u00efW\x84\x92\u007f\u9958\xfcN\xec8\xb8\x10/7\xbcX\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xea\u01b9\x88BT.\xa1\v\xb7O&\xd7\xc7H\x8fi\x8bdR\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xea\xc7h\xbf\x14\xb8\xf9C.i\xea\xa8*\x99\xfb\xeb\x94\xcd\f\x9c\x8a\x14\u06f2\x19\\\xa2(\x90\x00\x00\u07d4\xea\xd2\x1c\x1d\xec\u03ff\x1c\\\xd9f\x88\xa2Gki\xba\a\xceJ\x89\x03\xf2M\x8eJ\x00p\x00\x00\u07d4\xea\xd4\xd2\xee\xfbv\xab\xaeU3\x96\x1e\xdd\x11@\x04\x06\xb2\x98\xfc\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4\xea\xd6Rb\xed]\x12-\xf2\xb2u\x14\x10\xf9\x8c2\xd1#\x8fQ\x89\x05\x83\x17\xedF\xb9\xb8\x00\x00\u07d4\xea\xd7P\x16\u3801Pr\xb6\xb1\b\xbc\xc1\xb7\x99\xac\xf08>\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xea\xea#\xaa\x05r\x00\xe7\xc9\xc1^\x8f\xf1\x90\xd0\xe6l\f\x0e\x83\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xea\xed\x16\xea\xf5\u06ab[\xf0)^^\a\u007fY\xfb\x82U\x90\v\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xea\xed\xcck\x8bib\xd5\xd9(\x8c\x15lW\x9dG\xc0\xa9\xfc\xff\x89\x04\x9b\x9c\xa9\xa6\x944\x00\x00\u07d4\xea\xf5#\x88Tn\xc3Z\xcaolc\x93\xd8\xd6\t\xde:K\xf3\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xeb\x10E\x8d\xac\xa7\x9eJk$\xb2\x9a\x8a\x8a\xdaq\x1b\u007f.\xb6\x89\u063beI\xb0+\xb8\x00\x00\u07d4\xeb\x1c\xea{E\u047dM\x0e*\x00{\u04ff\xb3Tu\x9e,\x16\x89\n\xbb\xcdN\xf3wX\x00\x00\u07d4\xeb%H\x1f\u035c\"\x1f\x1a\xc7\xe5\xfd\x1e\u0353\a\xa1b\x15\xb8\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94\xeb.\xf3\u04cf\xe6R@<\xd4\xc9\xd8^\xd7\xf0h,\xd7\xc2\u078a\t\x0fSF\b\xa7(\x80\x00\x00\xe0\x94\xeb;\xddY\xdc\u0765\xa9\xbb*\xc1d\x1f\xd0!\x80\xf5\xf3e`\x8a\x01e\xc9fG\xb3\x8a \x00\x00\u07d4\xeb<\xe7\xfc8\x1cQ\xdb}_\xbdi/\x8f\x9e\x05\x8aLp=\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xebE?Z:\xdd\u074a\xb5gP\xfa\xdb\x0f\xe7\xf9M\x9c\x89\xe7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xebO\x00\xe2\x836\xea\t\x94%\x88\ueb12\x18\x11\xc5\"\x14<\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xebR\xab\x10U4\x922\x9c\x1cT\x83:\xe6\x10\xf3\x98\xa6[\x9d\x89\b=lz\xabc`\x00\x00\u07d4\xebW\r\xba\x97R'\xb1\xc4-n\x8d\xea,V\u026d\x96\x06p\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xebc\x94\xa7\xbf\xa4\u0489\x11\u0565\xb2>\x93\xf3^4\f\"\x94\x89\x04:w\xaa\xbd\x00x\x00\x00\u07d4\xebh\x10i\x1d\x1a\xe0\u045eG\xbd\"\u03be\u0cfa'\xf8\x8a\x89\x87\x85c\x15\xd8x\x15\x00\x00\u07d4\xebvBL\x0f\u0557\xd3\xe3A\xa9d*\xd1\xee\x11\x8b+W\x9d\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xeb| +F+|\u0145]t\x84u_n&\xefC\xa1\x15\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xeb\x83\\\x1a\x91\x18\x17\x87\x8a3\xd1gV\x9e\xa3\xcd\u04c7\xf3(\x8965\u026d\xc5\u07a0\x00\x00\u07d4\ub268\x82g\t\t\xcf7~\x9ex(n\xe9{\xa7\x8dF\u0089+|\xc2\xe9\xc3\"\\\x00\x00\xe0\x94\xeb\x90\u01d3\xb3S\x97a\xe1\xc8\x14\xa2\x96q\x14\x86\x92\x19>\xb4\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xeb\x9c\xc9\xfe\bi\xd2\u06b5,\u01ea\xe8\xfdW\xad\xb3_\x9f\xeb\x89j\x93\xbb\x17\xaf\x81\xf8\x00\x00\xe0\x94\ub8c8\xb0\xda'\xc8{\x1c\xc0\xea\xc6\xc5{,Z\vE\x9c\x1a\x8a\x01p\xa0\xf5\x04\x0eP@\x00\x00\u07d4\xeb\xaa!m\xe9\xccZC\x03\x17\a\xd3o\xe6\u057e\xdc\x05\xbd\xf0\x89j\xc5\xc6-\x94\x86\a\x00\x00\u07d4\xeb\xac+D\b\xefT1\xa1;\x85\b\xe8bP\x98!\x14\xe1E\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xeb\xb6,\xf8\xe2,\x88K\x1b(\xc6\xfa\x88\xfb\xbc\x17\x93\x8a\xa7\x87\x89+By\x84\x03\u0278\x00\x00\u07d4\xeb\xb7\xd2\xe1\x1b\u01b5\x8f\n\x8dE\xc2\xf6\xde0\x10W\n\u0211\x89\x01s\x17\x90SM\xf2\x00\x00\u07d4\xeb\xbbO,=\xa8\xbe>\xb6-\x1f\xfb\x1f\x95\x02a\u03d8\xec\u0689lk\x93[\x8b\xbd@\x00\x00\u07d4\xeb\xbdM\xb9\x01\x99R\u058b\x1b\x0fm\x8c\xf0h<\x008{\xb5\x89\x12\x04\x01V=}\x91\x00\x00\u07d4\xeb\xbe\xeb%\x91\x84\xa6\xe0\x1c\xcc\xfc\"\a\xbb\u0603xZ\xc9\n\x89!\x9b\xc1\xb0G\x83\xd3\x00\x00\u07d4\xeb\xd3V\x15j81#4=H\x84;\xff\xeda\x03\xe8f\xb3\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xeb\xd3{%ec\xe3\fo\x92\x89\xa8\xe2p/\bR\x88\b3\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xeb\xe4l\xc3\xc3L2\xf5\xad\xd6\xc3\x19[\xb4\x86\xc4q>\xb9\x18\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xeb\xff\x84\xbb\xefB0q\xe6\x04\xc3a\xbb\xa6w\xf5Y=\xefN\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xec\t'\xba\xc7\xdc6f\x9c(5J\xb1\xbe\x83\xd7\xee\xc3\t4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xec\x0e\x18\xa0\x1d\xc4\xdc]\xaa\xe5g\xc3\xfaL\u007f\x8f\x9bY\x02\x05\x89\x11\x1f\xfe@JA\xe6\x00\x00\xe0\x94\xec\x116,\xec\x81\t\x85\xd0\xeb\xbd{sE\x14D\x98[6\x9f\x8a\x06ZNIWpW1\x80\x00\u07d4\xec,\xb8\xb97\x8d\xff1\xae\xc3\xc2.\x0em\xad\xff1J\xb5\u0749lk\x93[\x8b\xbd@\x00\x00\u07d4\xec0\xad\u0749[\x82\xee1\x9eT\xfb\x04\xcb+\xb09q\xf3k\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xec;\x8bX\xa1'\x03\xe5\x81\xce_\xfd~!\xc5}\x1e\\f?\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xecHg\xd2\x17Z\xb5\xb9F\x93aYUFUF\x84\u0364`\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xecM\b\xaa.GIm\u0287\"]\xe3?+@\xa8\xa5\xb3o\x89\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4\xecX\xbc\r\f \xd8\xf4\x94efAS\xc5\xc1\x96\xfeY\u6f89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xec[\x19\x8a\x00\u03f5Z\x97\xb5\xd56D\xcf\xfa\x8a\x04\u04abE\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xec]\xf2'\xbf\xa8]z\xd7kBn\x1c\xee\x96;\xc7\xf5\x19\u074965\u026d\xc5\u07a0\x00\x00\xe0\x94\xec_\xea\xfe!\f\x12\xbf\u0265\xd0Y%\xa1#\xf1\xe7?\xbe\xf8\x8a`\x8f\xcf=\x88t\x8d\x00\x00\x00\u07d4\xeci\x04\xba\xe1\xf6\x97\x90Y\x17\t\xb0`\x97\x83s?%s\xe3\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\xecs\x11L^@o\u06fe\t\xb4\xfab\x1b\xd7\x0e\xd5N\xa1\xef\x8a\x050%\xcd!o\xceP\x00\x00\u07d4\xecs\x83=\xe4\xb8\x10\xbb\x02x\x10\xfc\x8fi\xf5D\xe8<\x12\u044965\u026d\xc5\u07a0\x00\x00\u07d4\xecu\xb4\xa4u\x13\x12\v\xa5\xf8`9\x81O\x19\x98\xe3\x81z\u00c9\t\xb0\xbc\xe2\xe8\xfd\xba\x00\x00\u07d4\xecv\xf1.W\xa6U\x04\x03?,\v\xceo\xc0;\xd7\xfa\n\u0109\xc2\x12z\xf8X\xdap\x00\x00\u0794\xec\x80\x14\xef\xc7\xcb\xe5\xb0\xceP\xf3V,\xf4\xe6\u007f\x85\x93\xcd2\x88\xf0\x15\xf2W6B\x00\x00\u07d4\xec\x82\xf5\r\x06G_hM\xf1\xb3\x92\xe0\r\xa3A\xaa\x14TD\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xec\x83\xe7\x98\u00d6\xb7\xa5^*\"$\xab\u0343K'\xeaE\x9c\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xec\x89\xf2\xb6x\xa1\xa1[\x914\xec^\xb7\fjb\a\x1f\xba\xf9\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xec\x8c\x1d{j\xac\xcdB\x9d\xb3\xa9\x1e\xe4\xc9\xeb\x1c\xa4\xf6\xf7<\x89\xe6d\x99\"\x88\xf2(\x00\x00\xe0\x94\xec\x98Q\xbd\x91rpa\x02g\xd6\x05\x18\xb5M<\xa2\xb3[\x17\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xec\x99\xe9]\xec\xe4o\xff\xfb\x17^\xb6@\x0f\xbe\xbb\b\ue6d5\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xec\xa5\xf5\x87\x92\xb8\xc6-*\xf5Vq~\xe3\xee0(\xbeM\u0389lk\x93[\x8b\xbd@\x00\x00\u07d4\xec\xabZ\xba[\x82\x8d\xe1pS\x81\xf3\x8b\xc7D\xb3+\xa1\xb47\x892\xf5\x1e\u06ea\xa30\x00\x00\u07d4\xec\xaf3P\xb7\xce\x14M\x06\x8b\x18`\x10\x85,\x84\xdd\f\xe0\xf0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xec\xb9LV\x8b\xfeY\xad\xe6Pd_O&0lsl\xac\xe4\x89\x0e~\xeb\xa3A\vt\x00\x00\xe0\x94\xec\xbeB^g\r9\tN \xfbVC\xa9\xd8\x18\xee\xd26\u078a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x94\xec\xbe^\x1c\x9a\u04b1\xdc\xcf\n0_\xc9R/Fi\xdd:\xe7\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xec\xcfz\x04W\xb5f\xb3F\xcag:\x18\x0fDA0!j\u00c9\x05k\xc7^-c\x10\x00\x00\u07d4\xec\u0466(\x025\x1aAV\x8d#\x030\x04\xac\xc6\xc0\x05\xa5\u04c9\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\xec\xd2v\xafd\u01dd\x1b\u0669+\x86\xb5\u835a\x95\xeb\x88\xf8\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xec\u0506\xfc\x19g\x91\xb9,\xf6\x12\xd3HaO\x91VH\x8b~\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xec\xda\xf92)\xb4^\xe6r\xf6]\xb5\x06\xfb^\xca\x00\xf7\xfc\xe6\x89W\x01\xf9m\xcc@\xee\x80\x00\u07d4\xec\xe1\x11g\vV<\u037e\xbc\xa5#\x84)\x0e\xcdh\xfe\\\x92\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xec\xe1\x15&\x82\xb7Y\x8f\xe2\xd1\xe2\x1e\xc1U3\x88T5\xac\x85\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xec\xe1)\bw\xb5\x83\xe3a\xa2\xd4\x1b\x00\x93F\xe6'N%8\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xec\xf0]\a\xea\x02n~\xbfIA\x00#5\xba\xf2\xfe\xd0\xf0\x02\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xec\xf2L\xdd|\"\x92\x8cD\x1eiM\xe4\xaa1\xb0\xfa\xb5\x97x\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\xec\xfd\x00M\x02\xf3l\xd4\u0634\xa8\xc1\xa9S;j\xf8\\\xd7\x16\x8a\x01\x0fA\xac\xb4\xbb;\x9c\x00\x00\xe0\x94\xed\x02\x06\xcb#1Q(\xf8\xca\xff&\xf6\xa3\v\x98Tg\xd0\"\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xed\x10e\xdb\u03dds\xc0O\xfcy\b\x87\r\x88\x14h\xc1\xe12\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xed\x12vQ;o\u0186(\xa7A\x85\xc2\xe2\f\xbb\xcax\x17\xbf\x89\nZ\xa8P\t\xe3\x9c\x00\x00\xe0\x94\xed\x12\xa1\xba\x1f\xb8\xad\xfc\xb2\r\xfa\x19X.RZ\xa3\xb7E$\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\xed\x16\xce9\xfe\xef;\xd7\xf5\xd1b\x04^\x0fg\xc0\xf0\x00F\xbb\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xed\x1a\\C\xc5t\xd4\xe94)\x9b$\xf1G,\u071f\xd6\xf0\x10\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xed\x1b$\xb6\x91-Q\xb34\xac\r\xe6\xe7q\xc7\xc0EF\x95\xea\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xed\x1f\x1e\x11Z\r`\xce\x02\xfb%\xdf\x01M(\x9e:\f\xbe}\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xed10\\1\x9f\x92s\u04d3m\x8f[/q\u9c72)c\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xed2z\x14\xd5\u03ed\u0641\x03\xfc\t\x99q\x8d~\xd7\x05(\xea\x89N\x10\x03\xb2\x8d\x92\x80\x00\x00\u07d4\xed<\xbc7\x82\u03bdg\x98\x9b0\\A3\xb2\xcd\xe3\"\x11\xeb\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xed@\x14S\x8c\xeefJ/\xbc\xb6\xdcf\x9fz\xb1m\v\xa5|\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xedA\u188f\\\xaa\x848\x80\xefN\x8b\b\xbdl3\x14\x1e\u07c9*\xd5\xdd\xfaz\x8d\x83\x00\x00\xe0\x94\xedK\xe0J\x05-z\u0333\xdc\u03901\x9d\xba@ \xab,h\x8a\a\xf3zp\xea\xf3b\x17\x80\x00\xe0\x94\xedR\xa2\xcc\bi\u071e\x9f\x84+\u0415|G\xa8\xe9\xb0\xc9\xff\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\xed[LA\xe7b\xd9B@Cs\xca\xf2\x1e\xd4a]%\xe6\xc1\x89m-O=\x95%\xb4\x00\x00\u07d4\xed`\u012bnT\x02\x061~5\x94zc\xa9\xcak\x03\xe2\u02c9\x03\x1a\u066d\vF\u007f\x80\x00\u07d4\xedd\x1e\x066\x8f\xb0\xef\xaa\x17\x03\xe0\x1f\xe4\x8fJhS\t\xeb\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xedfC\xc0\xe8\x88K-2\x11\x857\x85\xa0\x8b\xf8\xf3>\u049f\x89Hz\x9a0E9D\x00\x00\xe0\x94\xedp\xa3|\xdd\x1c\xbd\xa9tm\x93\x96X\xae*a\x81(\x85x\x8a\x02\bj\xc3Q\x05&\x00\x00\x00\u07d4\xedsFvn\x1agm\r\x06\xec\x82\x18g\xa2v\xa0\x83\xbf1\x89\u064a\t1\xcc-I\x00\x00\u07d4\xed\x86&\x16\xfc\xbf\xb3\xbe\xcbt\x06\xf7<\\\xbf\xf0\f\x94\aU\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xed\x9e\x03\f\xa7\\\xb1\u049e\xa0\x1d\rL\xdf\xdc\xcd8D\xb6\xe4\x89\x01\xac\xc1\x16\u03ef\xb1\x80\x00\xe0\x94\ud7bc\u02e4/\x98\x15\xe7\x823&m\xd6\xe85\xb6\xaf\xc3\x1b\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\ud7f1\xf5\xaf/\xbf\u007f\xfcP)\xce\xe4+p\xff\\'[\xf5\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\u07d4\xed\xa4\xb2\xfaY\u0584\xb2z\x81\r\xf8\x97\x8as\xdf0\x8ac\u0089\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xed\xb4s59y\xa2\x06\x87\x9d\xe1D\xc1\n:\xcf\x12\xa7'OV9a\xf57R\x9d\x89\xc7\u0789lk\x93[\x8b\xbd@\x00\x00\u07d4\xeer\x88\xd9\x10\x86\xd9\xe2\xeb\x91\x00\x14\u066b\x90\xa0-x\u00a0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xee|=\xed|(\xf4Y\xc9/\xe1;M\x95\xba\xfb\xab\x026}\x89%\xf2s\x93=\xb5p\x00\x00\xe0\x94\xee\x86} \x91k\xd2\xe9\xc9\xec\xe0\x8a\xa0C\x85\xdbf|\x91.\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\ue25b\x02\xcb\xcb99\xcda\xde\x13B\xd5\x04\x82\xab\xb6\x852\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xee\x90m}_\x17H%\x81t\xbeL\xbc8\x93\x03\x02\xab{B\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\ue5ea\x8a\u019e\xdfz\x98}mp\x97\x9f\x8e\xc1\xfb\xcaz\x94\x89\x14b\fW\xdd\xda\xe0\x00\x00\u07d4\xee\xa1\xe9y\x88\xdeu\xd8!\xcd(\xadh\"\xb2,\u0398\x8b1\x89\x1c0s\x1c\xec\x03 \x00\x00\xe0\x94\xee\u048c?\x06\x8e\tJ0K\x85<\x95\nh\t\xeb\xcb\x03\xe0\x8a\x03\xa9\u057a\xa4\xab\xf1\xd0\x00\x00\u07d4\xee\u04c4\xef-A\xd9\xd2\x03\x97NW\xc1#(\xeav\x0e\b\xea\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xee\xdflB\x80\xe6\xeb\x05\xb94\xac\xe4(\xe1\x1dB1\xb5\x90[\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xee\xe7a\x84~3\xfda\u0653\x87\xee\x14b\x86\x94\u047f\xd5%\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xee\xe9\xd0Rn\xda\x01\xe41\x16\xa3\x952-\u0689pW\x8f9\x8a\x02\x1e\x19\x99\xbb\xd5\u04be\x00\x00\u07d4\xee\xf1\xbb\xb1\xe5\xa8?\u0782H\xf8\x8e\xe3\x01\x8a\xfa-\x132\xeb\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xee\xfb\xa1-\xfc\x99gB\xdby\x04d\xca}';\xe6\xe8\x1b>\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xee\xfd\x05\xb0\xe3\xc4\x17\xd5[3C\x06\x04\x86\xcd\xd5\xe9*\xa7\xa6\x89M\x85<\x8f\x89\b\x98\x00\x00\u07d4\xef\r\xc7\xddzS\xd6\x12r\x8b\xcb\u04b2|\x19\xddM}fo\x89&A\x1c[5\xf0Z\x00\x00\u07d4\xef\x11RR\xb1\xb8E\u0345\u007f\x00-c\x0f\x1bo\xa3zNP\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xef\x1c\x04w\xf1\x18M`\xac\u02b3t\xd3tUz\n>\x10\xf3\x89\b=lz\xabc`\x00\x00\u07d4\xef,4\xbbH}7b\xc3\u0327\x82\xcc\xddz\x8f\xbb\n\x991\x89\t\xc2\x00vQ\xb2P\x00\x00\u07d4\xef5\xf6\u0531\a^j\xa19\x15\x1c\x97K/FX\xf7\x058\x89<;\xc3?\x94\xe5\r\x80\x00\u07d4\xef9\u0291s\xdf\x15S\x1ds\xe6\xb7*hKQ\xba\x0f+\xb4\x89V\xa0\xb4un\xe28\x00\x00\u07d4\xefF<&y\xfb'\x91d\xe2\f=&\x915\x87s\xa0\xad\x95\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xefG\xcf\a>6\xf2q\xd5\"\xd7\xfaNq \xadP\a\xa0\xbc\x89\x87\x86x2n\xac\x90\x00\x00\u07d4\xefa\x15[\xa0\t\xdc\u07be\xf1\v(\xd9\xda=\x1b\xc6\xc9\xce\u0509\x034-`\xdf\xf1\x96\x00\x00\u0794\xefix\x1f2\xff\xce34o,\x9a\xe3\xf0\x84\x93\xf3\xe8/\x89\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xefv\xa4\u034f\xeb\xcb\u0278\x18\xf1x(\xf8\xd94s\xf3\xf3\u02c9\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\uf4c1\x8fhM\xb0\xc3g^\xc8\x132\xb3\x18>\xcc(\xa4\x95\x89T\x06\x923\xbf\u007fx\x00\x00\xe0\x94\xef\x9fY\xae\xdaA\x8c\x14\x94h-\x94\x1a\xabI$\xb5\xf4\x92\x9a\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\uf9b1\xf0\xdb`57\x82h\x91\xb8\xb4\xbc\x169\x84\xbb@\u03495e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\xef\xbdR\xf9}\xa5\xfd:g:F\xcb\xf30D{~\x8a\xad\\\x89\x05l<\x9b\x80\xa0\xa6\x80\x00\xe0\x94\xef\xc8\xcf\x19c\u0269Rg\xb2(\xc0\x86#\x98\x89\xf4\xdf\xd4g\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xef\u02ae\x9f\xf6M,\xd9[RI\xdc\xff\xe7\xfa\xa0\xa0\xc0\xe4M\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\xef\xcc\xe0k\xd6\b\x9d\x0eE\x8e\xf5a\xf5\xa6\x89H\n\xfep\x00\x89 \x86\xac5\x10R`\x00\x00\u07d4\xef\xe0g]\xa9\x8a]\xdap\u0356\x19k\x87\xf4\xe7&\xb43H\x89?\x19\xbe\xb8\xdd\x1a\xb0\x00\x00\u07d4\xef\xe8\xff\x87\xfc&\x0e\agc\x8d\xd5\xd0/\xc4g.\x0e\xc0m\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xef\xeb\x19\x97\xaa\xd2w\xcc3C\x0ea\x11\xed\tCY@H\xb8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xef\xee\xa0\x10uo\x81\xdaK\xa2[r\x17\x87\xf0X\x17\v\uff49\x01\u009c\x9c\xf7p\xef\x00\x00\u07d4\xef\xf5\x1dr\xad\xfa\xe1C\xed\xf3\xa4+\x1a\xecU\xa2\xcc\xdd\v\x90\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xef\xf8kQ#\xbc\xdc\x17\xedL\xe8\xe0[~\x12\xe5\x13\x93\xa1\xf7\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xef\xfc\x15\u41f1\xbe\xda\n\x8d\x13%\xbd\xb4\x17\"@\xdcT\n\x89\x03\x8599\xee\xe1\xde\x00\x00\xe0\x94\xf0\x11\x95\xd6W\xef<\x94.l\xb89I\xe5\xa2\v\\\xfa\x8b\x1e\x8a\x05ts\xd0]\xab\xae\x80\x00\x00\u07d4\xf0'\x96)Q\x01gB\x88\xc1\xd94g\x05=\x04\"\x19\xb7\x94\x89(\x1d\x90\x1fO\xdd\x10\x00\x00\u07d4\xf09h={=\"[\xc7\xd8\u07ed\xefc\x164A\xbeA\xe2\x89\x01\xdd\x1eK\xd8\xd1\xee\x00\x00\u07d4\xf0Jj7\x97\b\xb9B\x8dr*\xa2\xb0kw\xe8\x895\u03c9\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xf0M,\x91\xef\xb6\xe9\xc4_\xfb\xe7KCL\x8c_+\x02\x8f\x1f\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf0W\xaaf\xcav~\xde\x12J\x1c[\x9c\xc5\xfc\x94\xef\v\x017\x89p\xa2K\u02b6\xf4]\x00\x00\u07d4\xf0[\xa8\u05f6\x859\xd930\v\xc9(\x9c=\x94t\xd0A\x9e\x89\x06\xda'\x02M\xd9`\x00\x00\u07d4\xf0\\\xee\xabeA\x05dp\x99Qw<\x84E\xad\x9fN\u01d7\x89\x10C\x16'\xa0\x93;\x00\x00\xe0\x94\xf0_\xcdL\rs\xaa\x16~US\xc8\xc0\xd6\xd4\xf2\xfa\xa3\x97W\x8a\x02\xd2\xd6l1p\xb2\x98\x00\x00\u07d4\xf0g\xe1\xf1\u0583UjL\xc4\xfd\f\x03\x13#\x9f2\xc4\xcf\u060965\u026d\xc5\u07a0\x00\x00\u07d4\xf0g\xfb\x10\u07f2\x93\u962b\xe5d\xc0U\xe34\x8f\x9f\xbf\x1e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf0h\xdf\xe9]\x15\xcd:\u007f\x98\xff\xa6\x88\xb44hB\xbe&\x90\x89D\n\xd8\x19\xe0\x97L\x00\x00\xe0\x94\xf0j\x85J<]\xc3m\x1cI\xf4\xc8}m\xb33\xb5~J\u074a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xf0y\xe1\xb1&_P\xe8\u0229\x8e\xc0\u01c1^\xb3\xae\xac\x9e\xb4\x89\x01\x16\xdc:\x89\x94\xb3\x00\x00\xe0\x94\xf0{\xd0\xe5\xc2\xcei\xc7\u0127$\xbd&\xbb\xfa\x9d*\x17\xca\x03\x8a\x01@a\xb9\xd7z^\x98\x00\x00\xe0\x94\xf0\x83*k\xb2U\x03\xee\xcaC[\xe3\x1b\v\xf9\x05\xca\x1f\xcfW\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\xf0\x9b>\x87\xf9\x13\xdd\xfdW\xae\x80I\xc71\u06e9\xb66\xdf\u00c9 \xf5\xb1\uab4d\x80\x00\x00\u07d4\xf0\xb14\v\x99oo\v\xf0\xd9V\x1c\x84\x9c\xaf\u007fD0\xbe\xfa\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xf0\xb1\xf9\xe2x2\xc6\xdei\x14\xd7\n\xfc#\x8ct\x99\x95\xac\xe4\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xf0\xb4i\xea\xe8\x9d@\f\xe7\xd5\xd6j\x96\x95\x03p6\xb8\x89\x03\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xf0\xb9\u0583\u03a1+\xa6\x00\xba\xac\xe2\x19\xb0\xb3\xc9~\x8c\x00\xe4\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xf0\xbe\x0f\xafMy#\xfcDF\"\u0458\f\xf2\u0650\xaa\xb3\a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf0\xc0\x81\xdaR\xa9\xae6d*\xdf^\b _\x05\xc5Ah\xa6\x89\x06\x04o7\xe5\x94\\\x00\x00\u07d4\xf0\xc7\r\rm\xabvc\xaa\x9e\xd9\xce\xeaV~\xe2\u01b0'e\x89qC\x8a\u0167\x91\xa0\x80\x00\u07d4\xf0\xcb\xef\x84\xe1ic\x00\x98\xd4\xe3\x01\xb2\x02\b\xef\x05\x84j\u0249\x0e\v\x83EPkN\x00\x00\u07d4\xf0\xd2\x16c\u0630\x17n\x05\xfd\xe1\xb9\x0e\xf3\x1f\x850\xfd\xa9_\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\xf0\xd5\xc3\x1c\xcbl\xbe0\xc7\xc9\xea\x19\xf2h\xd1Y\x85\x1f\x8c\x9c\x8a\x03\x89O\x0eo\x9b\x9fp\x00\x00\u07d4\xf0\xd6L\xf9\xdf\tt\x113\xd1pH_\xd2K\x00P\x11\xd5 \x89\x1b\b\x93A\xe1O\xcc\x00\x00\u07d4\xf0\xd8X\x10^\x1bd\x81\x01\xac?\x85\xa0\xf8\"+\xf4\xf8\x1dj\x89 \x86\xac5\x10R`\x00\x00\u07d4\xf0\xdcC\xf2\x05a\x91'P{+\x1c\x1c\xfd\xf3-(1\t \x89\x10^\xb7\x9b\x94\x17\b\x80\x00\u07d4\xf0\xe1\u07e4*\u07ac/\x17\xf6\xfd\xf5\x84\xc9Hb\xfdV3\x93\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xf0\xe2d\x9c~j?,]\xfe3\xbb\xfb\xd9'\xca<5\nX\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf0\xe7\xfb\x9eB\nS@\xd56\xf4\x04\b4O\xea\xef\xc0j\xef\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf1\x04b\xe5\x8f\xcc\a\U000d5121\x87c\x94Q\x16~\x85\x92\x01\x89\t4\xdd]3\xbc\x97\x00\x00\xe0\x94\xf1\x06a\xff\x94\x14\x0f >zH%rCy8\xbe\xc9\xc3\xf7\x8a\x04<3\xc1\x93ud\x80\x00\x00\u0794\xf1\x14\xff\r\x0f$\xef\xf8\x96\xed\xdeTq\u07a4\x84\x82J\x99\xb3\x88\xbe -j\x0e\xda\x00\x00\u07d4\xf1\x16\xb0\xb4h\x0fS\xabr\xc9h\xba\x80.\x10\xaa\x1b\xe1\x1d\u0209\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xf1\x1c\xf5\xd3cto\xeehd\xd3\xca3m\xd8\x06y\xbb\x87\xae\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xf1\x1e\x01\u01e9\xd1$\x99\x00_M\xaew\x16\tZ4\x17bw\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xf1;\b0\x93\xbaVN-\xc61V\x8c\xf7T\r\x9a\x0e\xc7\x19\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xf1O\x0e\xb8m\xb0\xebhu?\x16\x91\x8e]K\x80t7\xbd>\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf1Qx\xff\xc4:\xa8\a\x0e\xce2~\x93\x0f\x80\x9a\xb1\xa5O\x9d\x89\n\xb6@9\x12\x010\x00\x00\u07d4\xf1V\xdc\v*\x98\x1e[U\xd3\xf2\xf0;\x814\xe31\u06ed\xb7\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xf1]\x9dZ!\xb1\x92\x9ey\x03q\xa1\u007f\x16\xd9_\fie\\\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf1^\x18,O\xbb\xady\xbd\x934\"B\xd4\xdc\xcf+\xe5\x89%\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xf1bM\x98\ve3o\xea\u0166\xd5A%\x00\\\xfc\xf2\xaa\u02c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xf1g\xf5\x86\x8d\xcfB3\xa7\x83\x06\th,\xaf-\xf4\xb1\xb8\a\x89\x81\xe5B\xe1\xa78?\x00\x00\u07d4\xf1m\xe1\x89\x1d\x81\x96F\x13\x95\xf9\xb16&[;\x95F\xf6\xef\x89\x01\xb2\x8e\x1f\x98\xbb\u0380\x00\u07d4\xf1z\x92\xe06\x1d\xba\xce\xcd\xc5\xde\r\x18\x94\x95Z\xf6\xa9\xb6\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf1z\xdbt\x0fE\u02fd\xe3\tN~\x13qo\x81\x03\xf5c\xbd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf1\x8b\x14\xcb\xf6iC6\xd0\xfe\x12\xac\x1f%\xdf-\xa0\xc0]\xbb\x89\xd8\xd4`,&\xbfl\x00\x00\u07d4\xf1\x9b98\x9dG\xb1\x1b\x8a,?\x1d\xa9\x12M\xec\xff\xbe\xfa\xf7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf1\x9f\x195\b9>M*\x12{ \xb2\x03\x1f9\xc8%\x81\u0189\xbd\xbdz\x83\xbd/l\x00\x00\u07d4\xf1\xa1\xf3 @yd\xfd<\x8f.,\u0224X\r\xa9O\x01\xea\x89ll!wU|D\x00\x00\u07d4\xf1\xb4\xec\xc65%\xf7C,=\x83O\xfe+\x97\x0f\xbe\xb8r\x12\x89\xa2\xa2@h\xfa\u0340\x00\x00\u07d4\U000753ef\xfa\x87\x94\xf5\n\xf8\xe8\x83\t\u01e6&TU\xd5\x1a\x8963\x03\"\xd5#\x8c\x00\x00\u07d4\xf1\xc8\u0129A\xb4b\x8c\rl0\xfd\xa5dR\u065c~\x1bd\x89N\x8c\xea\x1e\xdeu\x04\x00\x00\u07d4\xf1\xda@so\x99\xd5\xdf;\x06\x8a]t_\xaf\xc6F?\u0271\x89\x06\x96\xca#\x05\x8d\xa1\x00\x00\u07d4\xf1\u070a\xc8\x10B\xc6z\x9c\\c2!\xa8\xf76>e\x87\f\x9f(t$\u04a9`\x89J\xcfX\xe0rW\x10\x00\x00\u07d4\xf2B\u0684]B\u053fw\x9a\x00\xf2\x95\xb4\aP\xfeI\xea\x13\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf2RY\xa5\xc99\xcd%\x96l\x9bc\x03\xd3s\x1cS\u077cL\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf2^Lp\xbcFV2\u021eV%\xa82\xa7r/k\xff\xab\x89\xf3K\x82\xfd\x8e\x91 \x00\x00\u07d4\xf2k\xce\xdc\xe3\xfe\xad\u03a3\xbc>\x96\xeb\x10@\xdf\xd8\xff\u1809*\x03I\x19\u07ff\xbc\x00\x00\u07d4\xf2py%v\xf0]QD\x93\xff\xd1\xf5\xe8K\xecK-\xf8\x10\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf2s,\xf2\xc1;\x8b\xb8\xe7I*\x98\x8f_\x89\xe3\x82s\xdd\u0209 \x86\xac5\x10R`\x00\x00\xe0\x94\xf2t.hY\xc5i\xd5\xf2\x10\x83Q\xe0\xbfM\xca5*H\xa8\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xf2\x81:d\xc5&]\x02\x025\u02dc1\x9bl\x96\xf9\x06\xc4\x1e\x89\x12\xf99\u025e\u06b8\x00\x00\u07d4\xf2\x87\xffR\xf4a\x11z\xdb>\x1d\xaaq\x93-\x14\x93\xc6_.\x89\xc5S%\xcat\x15\xe0\x00\x00\u07d4\xf2\xab\x11au\x02D\xd0\xec\xd0H\xee\r>Q\xab\xb1C\xa2\xfd\x89B\xfe+\x90ss\xbc\x00\x00\u07d4\xf2\xb4\xab,\x94'\xa9\x01^\xf6\xee\xff\xf5\xed\xb6\x019\xb7\x19\u0449&\u06d9*;\x18\x00\x00\x00\u07d4\xf2\xc0>*8\x99\x8c!d\x87`\xf1\xe5\xae~\xa3\a}\x85\"\x89\x8f?q\x93\xab\a\x9c\x00\x00\u0794\xf2\u0090N\x9f\xa6d\xa1\x1e\xe2VV\xd8\xfd,\xc0\u0665\"\xa0\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\xf2\xc3b\xb0\xef\x99\x1b\xc8/\xb3nf\xffu\x93*\xe8\u0742%\x89\x04\x02\xf4\xcf\xeeb\xe8\x00\x00\u07d4\xf2\xd0\xe9\x86\xd8\x14\xea\x13\xc8\xf4f\xa0S\x8cS\u0712&Q\xf0\x89J\xcfX\xe0rW\x10\x00\x00\xe0\x94\xf2\u04775w$\xecL\x03\x18[\x87\x9bc\xf5~&X\x91S\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xf2\xd5v<\xe0s\x12~,\xed\xdeo\xab\xa7\x86\xc7<\xa9AA\x8a\x01\xacB\x86\x10\x01\x91\xf0\x00\x00\xe0\x94\xf2\u055c\x89#u\x90s\xd6\xf4\x15\xaa\xf8\xeb\x06_\xf2\U000f614a\x01\xab,\xf7\xc9\xf8~ \x00\x00\u07d4\xf2\xe9\x9f\\\xbb\x83kz\xd3bGW\x1a0,\xbeKH\x1ci\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf2\xed>w%J\u02c3#\x1d\xc0\x86\x0e\x1a\x11$+\xa6'\u06c9kV\x05\x15\x82\xa9p\x00\x00\xe0\x94\xf2\xed\xde7\xf9\xa8\u00dd\u07a2My\xf4\x01WW\xd0k\xf7\x86\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xf2\xef\xe9e`\xc9\xd9{r\xbd6DxC\x88\\\x1d\x90\xc21\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf2\xfb\xb6\u0607\xf8\xb8\xcc:\x86\x9a\xba\x84\u007f=\x1fd\xfcc\x97\xaae\xfbS\xa8\xf0z\x0f\x89:\xae0\xe8\xbc\xee\x89|\xf28\x1fa\x9f\x15\x00\x00\u07d4\xf3@\x83\xec\xea8P\x17\xaa@\xbd\xd3^\xf7\xef\xfbL\xe7v-\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xf3F\xd7\u0792t\x1c\b\xfcX\xa6M\xb5[\x06-\xde\x01-\x14\x89\x0f\xffk\x1fv\x1em\x00\x00\xe0\x94\xf3U\xd3\xec\f\xfb\x90}\x8d\xbb\x1b\xf3FNE\x81(\x19\v\xac\x8a\x01\v\x04n\u007f\r\x80\x10\x00\x00\u07d4\xf3m\xf0/\xbd\x89`sG\xaf\xce)i\xb9\xc4#jX\xa5\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf3s\xe9\u06ac\f\x86u\xf5;yz\x16\x0fo\xc04\xaek#\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xf3{BeG\xa1d-\x8032H\x14\xf0\xed\xe3\x11O\xc2\x12\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\xf3{\xf7\x8cXu\x15G\x11\xcbd\r7\xeam(\xcf\xcb\x12Y\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xf3\x82\xdfX1U\xd8T\x8f?\x93D\f\xd5\xf6\x8c\xb7\x9d`&\x8a8u}\x02\u007f\xc1\xfd\\\x00\x00\xe0\x94\xf3\x82\xe4\xc2\x04\x10\xb9Q\b\x9e\x19\xba\x96\xa2\xfe\xe3\xd9\x1c\xce~\x8a\x01\x11\xfaV\xee\u00a88\x00\x00\xe0\x94\xf3\x8al\xa8\x01hS~\x97M\x14\xe1\xc3\xd19\x90\xa4L,\x1b\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xf3\x9a\x9dz\xa3X\x1d\xf0~\xe4'\x9a\xe6\xc3\x12\xef!\x036X\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf3\xb6h\xb3\xf1M\x92\x0e\xbc7\x90\x92\u06d8\x03\x1bg\xb2\x19\xb3\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4\U000fe679\x10<\xe7U\n\xa7O\xf1\xdb\x18\xe0\x9d\xfe2\xe0\x05\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf3\xc1\xab\u049d\xc5{A\xdc\x19-\x0e8M\x02\x1d\xf0\xb4\xf6\u0509\x97\xae\f\u07cf\x86\xf8\x00\x00\u07d4\xf3\xc4qm\x1e\xe5'\x9a\x86\xd0\x16:\x14a\x81\x81\xe1a6\u01c965\u026d\xc5\u07a0\x00\x00\xe0\x94\xf3\u030b\xcbU\x94e\xf8\x1b\xfeX;\u05eb\n#\x06E;\x9e\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xf3\u0588\xf0k\xbd\xbfP\xf9\x93,AE\xcb\xe4\x8e\xcd\xf6\x89\x04\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf3\xdb\xcf\x13Z\u02dd\xee\x1aH\x9cY<\x02O\x03\u00bb\xae\u0389lk\x93[\x8b\xbd@\x00\x00\u07d4\xf3\xde_&\xefj\xde\xd6\xf0m;\x91\x13F\xeep@\x1d\xa4\xa0\x89\x13:\xb3}\x9f\x9d\x03\x00\x00\u07d4\xf3\xdfc\xa9q\x99\x93308;>\xd7W\v\x96\u0101#4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf3\xe7OG\f}:?\x003x\x0fv\xa8\x9f>\xf6\x91\xe6\u02c9\xa3\xcf\xe61\xd1Cd\x00\x00\u07d4\xf3\xeb\x19H\xb9Q\xe2-\xf1ax)\xbf;\x8d\x86\x80\xeckh\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf3\xf1\xfa9\x18\xca4\xe2\xcf~\x84g\v\x1fM\x8e\xca\x16\r\xb3\x89$\xdc\xe5M4\xa1\xa0\x00\x00\u07d4\xf3\xf2O\u009e @?\xc0\xe8\xf5\xeb\xbbU4&\xf7\x82p\xa2\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xf3\xfar5R\xa5\xd0Q.+b\xf4\x8d\xca{+\x81\x050[\x89\amA\xc6$\x94\x84\x00\x00\u07d4\xf3\xfeQ\xfd\xe3D\x13\xc73\x18\xb9\xc8T7\xfe~\x82\x0fV\x1a\x896b2\\\u044f\xe0\x00\x00\u07d4\xf4\x00\xf9=_\\~?\xc3\x03\x12\x9a\xc8\xfb\f/xd\a\xfa\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf4\v\x13O\xea\"\u01b2\x9c\x84W\xf4\x9f\x00\x0f\x9c\xdax\x9a\u06c9 \x86\xac5\x10R`\x00\x00\u07d4\xf4\x15W\xdf\u07f1\xa1\xbd\xce\xfe\xfe.\xba\x1e!\xfe\nJ\x99B\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf4\x17z\r\x85\u050b\x0e&B\x11\xce*\xa2\xef\xd3\xf1\xb4\u007f\b\x89\xc2\xcc\xca&\xb7\xe8\x0e\x80\x00\u07d4\xf4/\x90R1\xc7p\xf0\xa4\x06\xf2\xb7h\x87\u007f\xb4\x9e\xee\x0f!\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xf42\xb9\u06ef\x11\xbd\xbds\xb6Q\x9f\xc0\xa9\x04\x19\x87q\xaa\u0189\b=lz\xabc`\x00\x00\u07d4\xf4=\xa3\xa4\xe3\xf5\xfa\xb1\x04\u029b\xc1\xa0\xf7\xf3\xbbJV\xf3Q\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\xf4G\x10\x8b\x98\xdfd\xb5~\x87\x103\x88\\\x1a\xd7\x1d\xb1\xa3\xf9\x8a\x01v\xf4\x9e\xad4\x83P\x80\x00\u07d4\xf4O\x85Q\xac\xe93r\a\x12\xc5\u0111\u0376\xf2\xf9Qsl\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u0794\xf4V\x05Z\x11\xab\x91\xfff\x8e.\xc9\"\x96\x1f*#\xe3\xdb%\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xf4V\xa7[\xb9\x96U\xa7A,\xe9}\xa0\x81\x81m\xfd\xb2\xb1\xf2\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf4[\x1d\xcb.A\xdc'\xff\xa0$\u06ad\xf6\x19\xc1\x11u\xc0\x87\x89\x01\x11du\x9f\xfb2\x00\x00\u07d4\xf4c\xa9\f\xb3\xf1>\x1f\x06CB66\xbe\xab\x84\xc1#\xb0m\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xf4h\x90n~\xdffJ\xb0\u063e=\x83\xebz\xb3\xf7\xff\xdcx\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xf4i\x80\u3929\u049ajn\x90`E7\xa3\x11K\xcb(\x97\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xf4kk\x9c|\xb5R\x82\x9c\x1d=\xfd\x8f\xfb\x11\xaa\xba\xe7\x82\xf6\x89\x01#n\xfc\xbc\xbb4\x00\x00\u07d4\xf4v\xe1&\u007f\x86$|\xc9\b\x81o.z\xd58\x8c\x95-\xb0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf4v\xf2\xcbr\b\xa3.\x05\x1f\xd9N\xa8f)\x92c\x82\x87\xa2\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xf4{\xb14\xda0\xa8\x12\xd0\x03\xaf\x8d\u0338\x88\xf4K\xbfW$\x8a\x01\x19Y\xb7\xfe3\x95X\x00\x00\u07d4\xf4\x83\xf6\a\xa2\x1f\xcc(\x10\n\x01\x8cV\x8f\xfb\xe1@8\x04\x10\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf4\x8e\x1f\x13\xf6\xafM\x84\xb3q\xd7\xdeK'=\x03\xa2c'\x8e\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\xf4\x9cG\xb3\xef\xd8knj[\xc9A\x8d\x1f\x9f\xec\x81Ki\xef\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xf4\x9fo\x9b\xaa\xbc\x01\x8c\x8f\x8e\x11\x9e\x01\x15\xf4\x91\xfc\x92\xa8\xa4\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xf4\xa3g\xb1f\u0499\x1a+\xfd\xa9\xf5dc\xa0\x9f%,\x1b\x1d\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf4\xa5\x1f\xceJ\x1d[\x94\xb0q\x83\x89\xbaNx\x14\x13\x9c\xa78\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xf4\xa9\xd0\f\xef\xa9{zX\xef\x94\x17\xfcbg\xa5\x06\x909\xee\x89\x01.\x89(\u007f\xa7\x84\x00\x00\u07d4\xf4\xaa\xa3\xa6\x16>7\x06W{I\xc0v~\x94\x8ah\x1e\x16\xee\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf4\xb1bn$\xf3\v\xca\xd9'!\xb2\x93r\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xf5U\xa2{\xb1\xe2\xfdN,\u01c4\xca\ue493\x9f\xc0n/\u0249lk\x93[\x8b\xbd@\x00\x00\u07d4\xf5X\xa2\xb2\xdd&\u0755\x93\xaa\xe0E1\xfd<<\u00c5Kg\x89\n\xbb\xcdN\xf3wX\x00\x00\u07d4\xf5`H\xdd!\x81\u0523od\xfc\xec\xc6!T\x81\xe4*\xbc\x15\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf5dB\xf6\x0e!i\x13\x95\u043f\xfa\xa9\x19M\xca\xff\x12\u2dc9\x0e\x189\x8ev\x01\x90\x00\x00\u07d4\xf5yqJE\xeb\x8fR\xc3\xd5{\xbd\xef\xd2\xc1[./\x11\u07c9T\x91YV\xc4\t`\x00\x00\u07d4\xf5\x93\xc6R\x85\xeek\xbdf7\U000fe3c9\xad@\u0509\xf6U\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xf5\x98\xdb.\t\xa8\xa5\xee}r\r+\\C\xbb\x12m\x11\xec\u0089\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xf5\x9d\xab\x1b\xf8\xdf\x112~a\xf9\xb7\xa1KV:\x96\xec5T\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xf5\x9f\x9f\x02\xbb\u024e\xfe\t~\xab\xb7\x82\x10\x97\x90!\x89\x8b\xfd\x8a\x02\x1e\x17\x1a>\xc9\xf7,\x00\x00\u07d4\xf5\xa5E\x9f\xcd\xd5\xe5\xb2s\x83\r\xf8\x8e\xeaL\xb7}\xda\u07f9\x89\x04\t\xe5+H6\x9a\x00\x00\u07d4\xf5\xa7gj\xd1H\xae\x9c\x1e\xf8\xb6\xf5\xe5\xa0\xc2\xc4s\xbe\x85\v\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf5\xb0h\x98\x9d\xf2\x9c%5w\xd0@Z\xden\x0eu(\xf8\x9e\x89WG=\x05\u06ba\xe8\x00\x00\u07d4\xf5\xb6\xe9\x06\x1aN\xb0\x96\x16\aw\xe2gb\xcfH\xbd\u0635]\x89\r\xc5_\xdb\x17d{\x00\x00\u07d4\xf5\xcf\xfb\xbabN~\xb3!\xbc\x83\xc6\f\xa6\x81\x99\xb4\xe3fq\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf5\xd1ER\xb1\xdc\xe0\xd6\xdc\x1f2\r\xa6\xff\u02231\xcdo\f\x89Hz\x9a0E9D\x00\x00\xe0\x94\xf5\xd6\x1a\xc4\u0295G^[{\xff\xd5\xf2\xf6\x90\xb3\x16u\x96\x15\x8a\x06\x92\xae\x88\x97\b\x1d\x00\x00\x00\u07d4\xf5\xd9\xcf\x00\xd6X\xddEQzH\xa9\xd3\xf5\xf63T\x1aS=\x89\x06O_\xdfIOx\x00\x00\u07d4\xf5\xea\xdc\xd2\u0478ez\x12\x1f3\xc4X\xa8\xb1>v\xb6U&\x89\r\x8b\x0fZZ\xc2J\x00\x00\u07d4\xf6\a\xc2\x15\r>\x1b\x99\xf2O\xa1\xc7\xd5@\xad\xd3\\N\xbe\x1e\x89\xa7\xf1\xaa\a\xfc\x8f\xaa\x00\x00\u07d4\xf6\v\xd75T>k\xfd.\xa6\xf1\x1b\xffbs@\xbc\x03Z#\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf6\f\x1bE\xf1d\xb9X\x0e 'Z\\9\xe1\xd7\x1e5\xf8\x91\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xf6\x0fb\xd797\x95?\xef5\x16\x9e\x11\xd8r\xd2\xea1~\xec\x8a\x01!\xeah\xc1\x14\xe5\x10\x00\x00\u07d4\xf6\x12\x83\xb4\xbd\x85\x04\x05\x8c\xa3`\u94d9\x9bb\xcb\xc8\xcdg\x89\r\xd2\xd5\xfc\xf3\xbc\x9c\x00\x00\u07d4\xf6\x17\xb9g\xb9\xbdH_v\x95\xd2\xefQ\xfbw\x92\u0618\xf5\x00\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xf6\x18\u0671\x04A\x14\x80\xa8c\xe6#\xfcU#-\x1aOH\xaa\x89\x0eh\x9emD\xb1f\x80\x00\u07d4\xf6\"\u5126b>\xaa\xf9\x9f+\xe4\x9eS\x80\xc5\xcb\xcf\\\u0609\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf62\xad\xffI\r\xa4\xb7-\x126\xd0KQ\x0ft\xd2\xfa\xa3\u0349K\xe4\xe7&{j\xe0\x00\x00\u07d4\xf69\xac1\u069fg'\x1b\xd1\x04\x02\xb7eN\\\xe7c\xbdG\x89\x15\xaf\x0fB\xba\xf9&\x00\x00\u07d4\xf6:W\x9b\xc3\xea\u00a9I\x04\x10\x12\x8d\xbc\xeb\xe6\xd9\u0782C\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4\xf6E\xdd|\x89\x00\x93\xe8\xe4\u022a\x92\xa6\xbb55\"\xd3\u0718\x89\aC\x9f\xa2\t\x9eX\x00\x00\xe0\x94\xf6H\xea\x89\xc2u%q\x01r\x94Ny\xed\xff\x84x\x03\xb7u\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xf6JJ\xc8\xd5@\xa9(\x9ch\xd9`\xd5\xfb|\xc4Zw\x83\x1c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf6N\xcf!\x17\x93\x1cmSZ1\x1eO\xfe\xae\xf9\u0514\x05\xb8\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4\xf6O\xe0\x93\x9a\x8d\x1e\xea*\x0e\u035a\x970\xfdyX\xe31\t\x89\x01\x1d\xe1\xe6\xdbE\f\x00\x00\u07d4\xf6V\x16\xbe\x9c\x8by~t\x15\"|\x918\xfa\xa0\x89\x17B\u05c9*\xd3s\xcef\x8e\x98\x00\x00\u07d4\xf6W\xfc\xbeh.\xb4\xe8\xdb\x15.\u03c9$V\x00\vQ=\x15\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xf6X\x19\xacL\xc1L\x13\u007f\x05\xddyw\xc7\xda\xe0\x8d\x1aJ\xb5\x89\x05\x87\x88\u02d4\xb1\xd8\x00\x00\u07d4\xf6{\xb8\xe2\x11\x8b\xbc\u0550'fn\xed\xf6\x94>\xc9\xf8\x80\xa5\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf6\x84d\xbfd\xf2A\x13V\xe4\xd3%\x0e\xfe\xfe\\P\xa5\xf6[\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf6\x86x[\x89r\va\x14_\ua017\x8dj\u030e\v\xc1\x96\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf6\x8c^3\xfa\x97\x13\x9d\xf5\xb2\xe68\x86\xce4\xeb\xf3\u45dc\x89\xb3\xfaAi\xe2\xd8\xe0\x00\x00\u07d4\xf6\xa8cWW\xc5\xe8\xc14\xd2\r\x02\x8c\xf7x\u03c6\t\xe4j\x89O\x1dw/\xae\xc1|\x00\x00\u07d4\xf6\xb7\x82\xf4\xdc\xd7E\xa6\xc0\xe2\xe00`\x0e\x04\xa2K%\xe5B\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\xf6\xbc7\xb1\u04a3x\x8dX\x9bm\xe2\x12\xdc\x17\x13\xb2\xf6\u738a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xf6\xc3\u010a\x1a\xc0\xa3G\x99\xf0M\xb8n\u01e9u\xfewh\xf3\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf6\xd2]?=\x84m#\x9fR_\xa8\xca\xc9{\xc45x\u06ec\x890\x92\u007ft\xc9\xde\x00\x00\x00\u07d4\xf6\xea\xacp2\u0512\xef\x17\xfd`\x95\xaf\xc1\x1dcOV\xb3\x82\x89\x1b\x1bk\u05efd\xc7\x00\x00\xe0\x94\xf6\xea\xd6}\xbf[~\xb13X\xe1\x0f6\x18\x9dS\xe6C\xcf\u03ca\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xf6\xf1\xa4C\t\x05\x1ck%\xe4}\xff\x90\x9b\x17\x9b\xb9\xabY\x1c\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xf7\x03(\xef\x97b_\xe7E\xfa\xa4\x9e\xe0\xf9\u052a;\r\xfbi\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf7\n\x99\x8aq{3\x8d\x1d\u0658T@\x9b\x1a3\x8d\ue930\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf7\rcz\x84\\\x06\xdbl\u0711\xe67\x1c\xe7\xc48\x8ab\x8e\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf7\x15R\x13D\x98\x92tK\xc6\x0f.\x04@\a\x88\xbd\x04\x1f\u0749\x03\x9f\xba\xe8\xd0B\xdd\x00\x00\xe0\x94\xf7\x1bE4\xf2\x86\xe40\x93\xb1\xe1^\xfe\xa7I\xe7Y{\x8bW\x8a\x16\x1c\x13\xd34\x1c\x87(\x00\x00\u07d4\xf74\xec\x03rM\xde\xe5\xbbRy\xaa\x1a\xfc\xf6\x1b\f\xb4H\xa1\x89\xe5\xbf,\u0270\x97\x80\x00\x00\u07d4\xf76\u0716v\x00\x128\x8f\xe8\x8bf\xc0n\xfeW\xe0\xd7\xcf\n\x89q\xd7Z\xb9\xb9 P\x00\x00\u07d4\xf7:\xc4l ;\xe1S\x81\x11\xb1Q\xec\x82 \u01c6\xd8AD\x89\x0f\xf77x\x17\xb8+\x80\x00\u07d4\xf7=\xd9\xc1B\xb7\x1b\xce\x11\xd0n0\xe7\xe7\xd02\xf2\uc71e\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf7A\x8a\xa0\xe7\x13\xd2H\"\x87v\xb2\xe7CB\"\xaeu\u3949lk\x93[\x8b\xbd@\x00\x00\u07d4\xf7Nn\x14S\x82\xb4\u06c2\x1f\xe0\xf2\u0643\x88\xf4V\t\u019f\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xf7P\f\x16o\x8b\xea/\x824v\x06\xe5\x02K\xe9\xe4\xf4\u0399\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf7W\xfc\x87 \xd3\xc4\xfaRw\a^`\xbd\\A\x1a\xeb\xd9w\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf7[\xb3\x9cy\x97y\xeb\xc0J3m&\r\xa61F\xed\x98\u0409\x01Z\xf1\u05cbX\xc4\x00\x00\xe0\x94\xf7h\xf3!\xfdd3\xd9kO5M<\xc1e,\x172\xf5\u007f\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xf7oi\xce\xe4\xfa\xa0\xa6;0\xae\x1ex\x81\xf4\xf7\x15ep\x10\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf7w6\x1a=\u062bb\xe5\xf1\xb9\xb0GV\x8c\xc0\xb5UpL\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf7|{\x84QI\xef\xba\x19\xe2a\xbc|u\x15y\b\xaf\xa9\x90\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf7\u007f\x95\x87\xffz-r\x95\xf1\xf5q\u0206\xbd3\x92jR|\x89lh\xcc\u041b\x02,\x00\x00\u07d4\xf7\x82X\xc1$\x81\xbc\xdd\u06f7*\x8c\xa0\xc0C\tra\xc6\u0149\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf7\x98\xd1m\xa4\xe4`\xc4`\xcdH_\xae\x0f\xa0Y\x97\b\ub08965\u026d\xc5\u07a0\x00\x00\u07d4\xf7\xa1\xad\xe2\xd0\xf5)\x12=\x10U\xf1\x9b\x17\x91\x9fV!Ng\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xf7\xac\xff\x93K\x84\xda\ti\xdc7\xa8\xfc\xf6C\xb7\xd7\xfb\xedA\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xf7\xb1Q\xcc^W\x1c\x17\xc7e9\xdb\xe9\x96L\xbbo\xe5\xdey\x89tq|\xfbh\x83\x10\x00\x00\u07d4\xf7\xb2\x9b\x82\x19\\\x88-\xabx\x97\u00ae\x95\xe7w\x10\xf5xu\x89w5Aa2\xdb\xfc\x00\x00\u07d4\xf7\xbcLD\x91\rZ\xed\xd6n\xd25U8\xa6\xb1\x93\xc3a\xec\x89\x05A\xde,-\x8db\x00\x00\u07d4\xf7\xc0\f\xdb\x1f\x02\x03\x10\u056c\xab{Ij\xaaD\xb7y\b^\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4\xf7\xc1\xb4C\x96\x8b\x11{]\u0677UW/\xcd9\xca^\xc0K\x89\x18\xb9h\u0092\xf1\xb5\x00\x00\xe0\x94\xf7\xc5\x0f\x92*\xd1ka\xc6\u047a\xa0E\xed\x81h\x15\xba\u010f\x8a\x02\xa99j\x97\x84\xad}\x00\x00\u07d4\xf7\xc7\b\x01Pq\xd4\xfb\n:*\t\xa4]\x15c\x96\xe34\x9e\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xf7\xcb\u06e6\xbel\xfeh\xdb\xc2<+\x0f\xf50\xee\x05\"o\x84\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf7\xd0\xd3\x10\xac\xea\x18@a8\xba\xaa\xbb\xfe\x05q\xe8\r\xe8_\x89Hz\x9a0E9D\x00\x00\u07d4\xf7\u05ef LV\xf3\x1f\xd9C\x98\xe4\r\xf1\x96K\u063f\x12<\x89\b!\xd2!\xb5)\x1f\x80\x00\u07d4\xf7\xdc%\x11\x96\xfb\u02f7|\x94}|\x19F\xb0\xffe\x02\x1c\xea\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xf7\xe4Z\x12\xaaq\x1cp\x9a\xce\xfe\x95\xf3;xa-*\xd2*\x8a\x0e\x06U\xe2\xf2k\xc9\x18\x00\x00\u07d4\xf7\xf4\x89\x8cLRm\x95_!\xf0U\xcbnG\xb9\x15\xe5\x19d\x89|\b`\xe5\xa8\r\xc0\x00\x00\u07d4\xf7\xf9\x1ez\xcb[\x81)\xa3\x06\x87|\xe3\x16\x8eoC\x8bf\xa1\x89\t\x8a}\x9b\x83\x14\xc0\x00\x00\u07d4\xf7\xfcE\xab\xf7oP\x88\xe2\u5d68\xd12\xf2\x8aMN\xc1\xc0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8\x06:\xf4\xcc\x1d\xd9a\x9a\xb5\u063f\xf3\xfc\xd1\xfa\xa8H\x82!\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8\bnBf\x1e\xa9)\xd2\u0761\xablt\x8c\xe3\x05]\x11\x1e\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xf8\bw\x86\xb4-\xa0N\xd6\xd1\xe0\xfe&\xf6\xc0\xee\xfe\x1e\x9fZ\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xf8\r6\x19p/\xa5\x83\x8cH9\x18Y\xa89\xfb\x9c\xe7\x16\x0f\x89l\a\xa7\u0471np\x00\x00\u07d4\xf8\x14y\x9fm\xdfM\xcb)\xc7\xee\x87\x0eu\xf9\xcc-52m\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf8\x15\xc1\n\x03-\x13\xc3K\x89v\xfan;\xd2\xc9\x13\x1a\x8b\xa9\x89Hz\x9a0E9D\x00\x00\u07d4\xf8\x16\"\xe5WW\xda\xeafu\x97]\xd958\xda}\x16\x99\x1e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8$\xee3\x1eJ\xc3\xccXv\x939[W\xec\xf6%\xa6\xc0\u0089V\xc9]\xe8\xe8\xca\x1d\x00\x00\u07d4\xf8'\xd5n\xd2\xd3' \u052b\xf1\x03\xd6\xd0\xefM;\xcdU\x9b\x89\x01l\x80\x06W\x91\xa2\x80\x00\u07d4\xf8)\x85\x91R>P\xb1\x03\xf0\xb7\x01\xd6#\xcb\xf0\xf7EV\xf6\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf8H\xfc\xe9\xaba\x1c}\x99 n#\xfa\u019a\u0508\xb9O\xe1\x89\x02\xa1\x12\x9d\t6r\x00\x00\u07d4\xf8O\t\n\xdf?\x8d\xb7\u1533P\xfb\xb7u\x00i\x9ff\xfd\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf8Q\xb0\x10\xf63\xc4\n\xf1\xa8\xf0js\ubeabe\az\xb5\x89\xee\x86D/\xcd\x06\xc0\x00\x00\u07d4\xf8X\x17\x1a\x04\xd3W\xa1;IA\xc1n~U\xdd\u0514\x13)\x89\x02F\xa5!\x8f*\x00\x00\x00\u07d4\xf8[\xab\x1c\xb3q\x0f\xc0_\xa1\x9f\xfa\xc2.gR\x1a\v\xa2\x1d\x89l\x955\u007f\xa6\xb3l\x00\x00\u07d4\xf8j>\xa8\a\x1fp\x95\xc7\u06ca\x05\xaePz\x89)\u06f8v\x89\x126\xef\xcb\u02f3@\x00\x00\u07d4\xf8pL\x16\xd2\xfd[\xa3\xa2\xc0\x1d\x0e\xb2\x04\x84\xe6\xec\xfa1\t\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf8p\x99_\xe1\xe5\"2\x1duC7\xa4\\\f\x9d{8\x95\x1c\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf8s\xe5ze\xc9;n\x18\xcbu\xf0\xdc\a}[\x893\xdc\\\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xf8ua\x9d\x8a#\xe4]\x89\x98\u0444\u0500\xc0t\x89p\x82*\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf8{\xb0{(\x9d\xf70\x1eT\xc0\xef\xdaj,\xf2\x91\xe8\x92\x00\x89K\xe4\xe7&{j\xe0\x00\x00\u0794\xf8\x89\x00\xdbsyU\xb1Q\x9b\x1a}\x17\n\x18\x86L\xe5\x90\xeb\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xf8\x8bX\xdb7B\vFL\v\xe8\x8bE\xee+\x95)\x0f\x8c\xfa\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xf8\x96+u\xdb]$\xc7\xe8\xb7\xce\xf1\x06\x8c>g\u03bb0\xa5\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\u07d4\xf8\xa0e\xf2\x87\xd9\x1dw\xcdbj\xf3\x8f\xfa\"\r\x9bU*+\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\xf8\xa4\x9c\xa29\f\x1fm\\\x0ebQ;\a\x95qt?|\u0189\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xf8\xa5\f\xee.h\x8c\xee\u3b24\u0522\x97%\xd4\a,\u0103\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8\xacJ9\xb5<\x110x \x97;D\x13e\xcf\xfeYof\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8\xae\x85{g\xa4\xa2\x89:?\xbe|z\x87\xff\x1c\x01\u01a6\xe7\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf8\xbf\x9c\x04\x87NZw\xf3\x8fL8R~\x80\xc6v\xf7\xb8\x87\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8\xc7\xf3J8\xb3\x18\x01\xdaC\x064w\xb1+'\xd0\xf2\x03\xff\x89\x1a\u04ba\xbao\xefH\x00\x00\u07d4\xf8\xca3l\x8e\x91\xbd \xe3\x14\xc2\v-\xd4`\x8b\x9c\x8b\x94Y\x89-\u071b\u0173,x\x00\x00\u07d4\xf8\xd1t$\xc7g\xbe\xa3\x12\x05s\x9a+W\xa7'r\x14\uef89\x02F\xdd\xf9yvh\x00\x00\u07d4\xf8\xd5-\xcc_\x96\xcc(\x00{>\u02f4\t\xf7\xe2*dl\xaa\x89\b\x16\x90\xe1\x81(H\x00\x00\u07d4\xf8\xdc\xe8g\xf0\xa3\x9c[\xef\x9e\xeb\xa6\t\"\x9e\xfa\x02g\x8bl\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8\xf2&\x14*B\x844\xab\x17\xa1\x86J%\x97\xf6J\xab/\x06\x89\tY\x8b/\xb2\xe9\xf2\x80\x00\u07d4\xf8\xf6d^\r\xeedK=\xad\x81\xd5q\uf6ef\x84\x00!\xad\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf9\x01\xc0\x0f\xc1\u06c8\xb6\x9cK\xc3%+\\\xa7\x0e\xa6\xee\\\xf6\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xf9=[\xcb\x06D\xb0\xcc\xe5\xfc\u0763C\xf5\x16\x8f\xfa\xb2\x87}\x89\vb\a\xb6}&\xf9\x00\x00\u07d4\xf9W\x0e\x92L\x95\u07bbpa6\x97\x92\xcf.\xfe\u00a8-^\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf9d \x86\xb1\xfb\xaea\xa6\x80M\xbe_\xb1^\xc2\u04b57\xf4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf9d\x88i\x85\x90\xdc;,UVB\xb8q4\x8d\xfa\x06z\u0549\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xf9d\u064d(\x170\xba5\xb2\xe3\xa3\x14yn{B\xfe\xdfg\x89S\xb0\x87`\x98\xd8\f\x00\x00\u07d4\xf9e\ri\x89\xf1\x99\xab\x1c\xc4ycm\xed0\xf2A\x02\x1fe\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\xe0\x94\xf9h\x83X$Y\x90\x8c\x82v'\xe8o(\xe6F\xf9\xc7\xfcz\x8a\x01\u0127\x877\xcd\u03f8\x00\x00\u07d4\xf9kL\x00voSsj\x85t\xf8\"\xe6GL/!\xda-\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xf9r\x9dH(,\x9e\x87\x16m^\xef-\x01\xed\xa9\xdb\xf7\x88!\x89\x05k\x83\xdd\xc7(T\x80\x00\u07d4\xf9v~N\xcbJY\x80Ru\b\u05fe\xc3\xd4^Ld\x9c\x13\x89g\x8a\x93 b\xe4\x18\x00\x00\xe0\x94\xf9x\xb0%\xb6B3U\\\xc3\xc1\x9a\xda\u007fA\x99\xc94\x8b\xf7\x8aT\xb4\v\x1f\x85+\xda\x00\x00\x00\u07d4\xf9{V\xeb\u0577z\xbc\x9f\xba\u02eb\u0514\xb9\xd2\xc2!\xcd\x03\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf9\x81\x1f\xa1\x9d\xad\xbf\x02\x9f\x8b\xfeV\x9a\xdb\x18\"\x8c\x80H\x1a\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf9\x82Ps\fLa\xc5\u007f\x12\x985\xf2h\b\x94yEB\xf3\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xf9\x894gr\x99^\xc1\x90o\xaf\xfe\xba*\u007f\xe7\u079ck\xab\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\xf9\x98\xca4\x11s\nl\xd1\x0etU\xb0A\x0f\xb0\xf6\xd3\xff\x80\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf9\x9a\xeeDKW\x83\xc0\x93\xcf\xff\xd1\xc4c,\xf9\x90\x9f\xbb\x91\x1d/\x81\x92\xf8B\t\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4\xf9\xbf\xb5\x9dS\x8a\xfcHt\xd4\xf5\x94\x1b\b\xc9s\x0e8\xe2K\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xf9\xdd#\x90\b\x18/\xb5\x19\xfb0\xee\xdd \x93\xfe\xd1c\x9b\xe8\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xf9\u07ba\xec\xb5\xf39\xbe\xeaH\x94\xe5 K\xfa4\r\x06\u007f%\x89ZB\x84Fs\xb1d\x00\x00\xe0\x94\xf9\xe3tG@lA!\x97\xb2\u2bbc\x00\x1dn0\u024c`\x8a\x01\xc4y\xbbCI\xc0\xee\x00\x00\u07d4\xf9\xe7\"/\xaa\xf0\xf4\xda@\xc1\u0124\x0607:\t\xbe\u05f6\x89\x9bO\u0730\x94V$\x00\x00\u07d4\xf9\xec\xe0\"\xbc\xcd,\x924i\x11\xe7\x9d\xd5\x03\x03\xc0\x1e\x01\x88\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfa\x00\xc3v\xe8\x9c\x05\u81c1z\x9d\xd0t\x8d\x96\xf3A\xaa\x89\x89\x10M\r\x00\u04b7\xf6\x00\x00\u07d4\xfa\f\x1a\x98\x8c\x8a\x17\xad5(\xeb(\xb3@\x9d\xaaX\"_&\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xfa\x10_\x1a\x11\xb6\xe4\xb1\xf5`\x12\xa2y\"\xe2\xac-\xa4\x81/\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\xfa\x14/\xe4~\u0697\xe6P;8k\x18\xa2\xbe\xdds\u0335\xb1\x89.\x15:\xd8\x15H\x10\x00\x00\u07d4\xfa\x14\xb5f#J\xbe\xe70B\xc3\x1d!qq\x82\u02e1J\xa1\x89\x11\xc7\xea\x16.x \x00\x00\u07d4\xfa\x19\xd6\xf7\xa5\x0fO\a\x98\x93\xd1g\xbf\x14\xe2\x1d\x00s\u0456\x89\x1c\xbb:?\xf0\x8d\b\x00\x00\u07d4\xfa\x1f\x19q\xa7u\xc3PO\xefPy\xf6@\xc2\u013c\xe7\xac\x05\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfa'\x9b\xfd\x87g\xf9V\xbf\u007f\xa0\xbdV`\x16\x8d\xa7V\x86\xbd\x89\x90\xf54`\x8ar\x88\x00\x00\xe0\x94\xfa'\xccI\xd0\vl\x98s6\xa8u\xae9\xdaX\xfb\x04\x1b.\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\xfa(2\x99`=\x87X\xe8\u02b0\x82\x12],\x8f}DT)\x8a\x01[\xca\xcb\x1e\x05\x01\xae\x80\x00\u07d4\xfa+\xbc\xa1]?\u37ca2\x8e\x91\xf9\r\xa1Oz\xc6%=\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xfa/\u049d\x03\xfe\xe9\xa0x\x93\xdf:&\x9fV\xb7/.\x1ed\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xfa3U2\x85\xa9sq\x9a\r_\x95o\xf8a\xb2\u061e\xd3\x04\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfa:\fK\x90?n\xa5.\xa7\xab{\x88c\xb6\xa6\x16\xadfP\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfa:\x1a\xa4H\x8b5\x1a\xa7V\f\xf5\xeec\n/\xd4\\2\"\x89/\xa4~j\xa74\r\x00\x00\u07d4\xfaA\tq\xad\"\x9c06\xf4\x1a\u03c5/*\u0259(\x19P\x89\u0633\x11\xa8\xdd\xfa|\x00\x00\u07d4\xfaD\xa8U\xe4\x04\xc8m\f\xa8\xef3$%\x1d\xfb4\x9cS\x9e\x89T\"S\xa1&\xce@\x00\x00\xe0\x94\xfaR\x01\xfe\x13B\xaf\x110{\x91B\xa0A$<\xa9./\t\x8a 8\x11j:\xc0C\x98\x00\x00\xe0\x94\xfa`\x86\x8a\xaf\xd4\xffL\\W\x91K\x8e\u054bBWs\u07e9\x8a\x01\xcf\xe5\xc8\b\xf3\x9f\xbc\x00\x00\u07d4\xfag\xb6{O7\xa0\x15\t\x15\x11\x0e\xde\a;\x05\xb8S\xbd\xa2\x89#\x19\xba\x94sq\xad\x00\x00\u07d4\xfah\xe0\xcb>\xdfQ\xf0\xa6\xf2\x11\u0272\xcb^\a<\x9b\xff\xe6\x89\x0f\xc969(\x01\xc0\x00\x00\xe0\x94\xfaj7\xf0\x18\xe9yg\x93\u007f\xc5\xe8a{\xa1\u05c6\xdd_w\x8a\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4\xfav\x06C[5l\xee%{\xd2\xfc\xd3\xd9\xea\xcb<\xd1\xc4\xe1\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xfaz\xdff\v\x8d\x99\xce\x15\x93=|_\a/<\xbe\xb9\x9d3\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4\xfa\x86\xca'\xbf(T\u0648p\x83\u007f\xb6\xf6\xdf\xe4\xbfdS\xfc\x89\x11u~\x85%\xcf\x14\x80\x00\u07d4\xfa\x8c\xf4\xe6'i\x8c]W\x88\xab\xb7\x88\x04\x17\xe7P#\x13\x99\x89\xe6\x1a6\x96\xee\xf6\x10\x00\x00\u07d4\xfa\x8e;\x1f\x13C9\x00s}\xaa\xf1\xf6)\x9cH\x87\xf8[_\x89&\u009eG\u0104L\x00\x00\u07d4\xfa\x9e\xc8\xef\xe0\x86\x86\xfaX\xc1\x813Xr\xbai\x85`\ucac9lj\xccg\u05f1\xd4\x00\x00\u07d4\xfa\xad\x90]\x84|{#A\x8a\xee\xcb\xe3\xad\u06cd\xd3\xf8\x92J\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xfa\xae\xba\x8f\xc0\xbb\xdaU<\xa7.0\xef=s.&\xe8 A\x89H\x8d(*\xaf\xc9\xf6\x80\x00\u07d4\xfa\xb4\x87P\r\xf2\x0f\xb8>\xbe\xd9\x16y\x1dV\x17r\xad\xbe\xbf\x89lkLM\xa6\u077e\x00\x00\u07d4\xfa\xc5\u0294u\x80x\xfb\xfc\xcd\x19\xdb5X\xda~\u8827h\x897(\xa6+\r\xcf\xf6\x00\x00\u07d4\xfa\xd9j\xb6\xacv\x8a\xd5\t\x94R\xacGw\xbd\x1aG\xed\u010f\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xfa\xe7g\x19\xd9~\xacA\x87\x04(\xe9@'\x9d\x97\xddW\xb2\xf6\x8a\x14\u06f2\x19\\\xa2(\x90\x00\x00\u07d4\xfa\u8053pG\x89Zf\f\xf2)v\x0f'\xe6h(\xd6C\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\xfa\xe9,\x13p\xe9\u115a]\xf8;V\xd0\xf5\x86\xaa;@L\x89\x05\u0174\xf3\xd8C\x98\x00\x00\xe0\x94\xfa\xf5\xf0\xb7\xb6\xd5X\xf5\t\r\x9e\xa1\xfb-B%\x9cX`x\x8a\x01Z\xff\xb8B\fkd\x00\x00\xe0\x94\xfb\x12o\x0e\xc7i\xf4\x9d\xce\xfc\xa2\xf2\x00(dQX0\x84\xb8\x8a\x01\x0f\xcb\xc25\x03\x96\xbf\x00\x00\xe0\x94\xfb\x13^\xb1Z\x8b\xacr\xb6\x99\x154*`\xbb\xc0k~\a|\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xfb\"<\x1e\"\xea\xc1&\x9b2\xee\x15jS\x85\x92.\xd3o\xb8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfb7\xcfkO\x81\xa9\xe2\"\xfb\xa2.\x9b\xd2KP\x98\xb73\u03c9\x02\x1auJm\xc5(\x00\x00\u07d4\xfb8`\xf4\x12\x1cC.\xbd\xc8\xecj\x031\xb1\xb7\ty.\x90\x89 \x8c9J\xf1\u0208\x00\x00\u07d4\xfb9\x18\x9a\xf8v\xe7b\xc7\x1dl>t\x18\x93\xdf\"l\xed\u0589\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xfb:\v\rkjq\x8fo\xc0)*\x82]\xc9$z\x90\xa5\u0409\n\xd6\xdd\x19\x9e\x97[\x00\x00\xe0\x94\xfb?\xa1\xac\b\xab\xa9\xcc;\xf0\xfe\x9dH8 h\x8fe\xb4\x10\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4\xfb?\xe0\x9b\xb86\x86\x15)\xd7Q\x8d\xa2v5\xf58PV\x15\x89K\xe3\x92\x16\xfd\xa0p\x00\x00\xe0\x94\xfbQ%\xbf\x0f^\xb0\xb6\xf0 \xe5k\xfc/\xdf=@,\t~\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4\xfbU\x18qL\xef\xc3m\x04\x86]\xe5\x91^\xf0\xffG\xdf\xe7C\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfb_\xfa\xa0\xf7aW&5x\x91GX\x18\x93\x9d 7\u03d6\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfbh\\\x15\xe49\x96^\xf6&\xbf\r\x83L\u0468\x9f+V\x95\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\xfbtK\x95\x1d\tK1\x02b\xc8\xf9\x86\xc8`\u07da\xb1\xdee\x89\x02\xd1\xc5\x15\xf1\xcbJ\x80\x00\u07d4\xfby\xab\u06d2\\U\xb9\xf9\x8e\xfe\xefd\xcf\xc9\xeba\xf5\x1b\xb1\x89a@\xc0V\xfb\n\xc8\x00\x00\u07d4\xfb\x81\x13\xf9M\x91s\xee\xfdZ0s\xf5\x16\x80:\x10\xb2\x86\xae\x89\x04V9\x18$O@\x00\x00\u07d4\xfb\x84,\xa2\xc5\xef\x139\x17\xa26\xa0\u052c@i\x01\x10\xb08\x89\x10\x96\x9ab\xbe\x15\x88\x00\x00\u07d4\xfb\x91\xfb\x1aiUS\xf0\u018e!'m\xec\xf0\xb89\t\xb8m\x89\x05l\x006\x17\xafx\x00\x00\u07d4\xfb\x94s\xcfw\x125\n\x1f\xa09Rs\xfc\x80V\aR\xe4\xfb\x89\x06\xaf!\x98\xba\x85\xaa\x00\x00\xe0\x94\xfb\x94\x9cd\u007f\xdc\xfd%\x14\xc7\u054e1\xf2\x8aS-\x8cX3\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xfb\xa5HmS\xc6\xe2@IBA\xab\xf8~C\xc7`\rA:\x89k\xbfaIIH4\x00\x00\u07d4\xfb\xb1a\xfe\x87_\t)\nK&+\xc6\x01\x10\x84\x8f\r\"&\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfb\xbb\xeb\u03fe#^W\xdd#\x06\xad\x1a\x9e\u0141\xc7\xf9\xf4\x8f\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94\xfb\xc0\x1d\xb5NG\xcd\xc3\xc48iJ\xb7\x17\xa8V\xc2?\xe6\xe9\x8a\x01\xcaqP\xab\x17OG\x00\x00\xe0\x94\xfb\xcf\xccJ{\x0f&\xcf&\xe9\xf33!2\xe2\xfcj#\af\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xfb\xe7\x16\"\xbc\xbd1\xc1\xa3iv\xe7\xe5\xf6p\xc0\u007f\xfe\x16\u0789\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xfb\xed\xe3,4\x9f3\x00\xefL\xd3;M\xe7\xdc\x18\xe4C\xd3&\x89\xabM\xcf9\x9a:`\x00\x00\u07d4\xfb\xf2\x04\xc8\x13\xf86\xd89b\u01c7\fx\b\xca4\u007f\xd3>\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xfb\xf7Y3\xe0\x1bu\xb1T\xef\x06i\ak\xe8\u007fb\xdf\xfa\xe1\x8a\x10\x84cr\xf2I\xd4\xc0\x00\x00\u07d4\xfc\x00\x96\xb2\x1e\x95\xac\xb8\xd6\x19\xd1v\xa4\xa1\xd8\xd5)\xba\xdb\xef\x89\x14\xd9i;\xcb\xec\x02\x80\x00\xe0\x94\xfc\x00\xa4 \xa3a\a\xdf\xd5\xf4\x95\x12\x8a_\u5af2\xdb\x0f4\x8a\x01C\x17\x9d\x86\x91\x10 \x00\x00\xe0\x94\xfc\x01\x8ai\n\xd6tm\xbe:\u03d7\x12\xdd\xcaR\xb6%\x009\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xfc\x02s@3\xe5\u007fpQ~\n\xfc~\xe6$a\xf0o\xad\x8e\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\xfc\x0e\xe6\xf7\u00b3qJ\xe9\x91lEVf\x05\xb6V\xf3$A\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xfc\x10\xb7\xa6{2h\xd53\x1b\xfbj\x14\xde\xf5\xeaJ\x16,\xa3\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xfc\x15\u02d9\xa8\xd1\x03\v\x12w\n\xdd\x03:y\xee\r\f\x90\x8c\x89\x12\xfa\x00\xbdR\xe6$\x00\x00\u07d4\xfc)R\xb4\u011f\xed\u043c\x05(\xa3\bI^mj\x1cq\u0589lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xfc,\x1f\x88\x96\x1d\x01\x9c>\x9e\xa30\t\x15.\x06\x93\xfb\xf8\x8a\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94\xfc6\x11\x05\u0750\xf9\xed\xe5fI\x9di\xe9\x13\x03\x95\xf1*\u020aS\xa4\xfe/ N\x80\xe0\x00\x00\u07d4\xfc7/\xf6\x92|\xb3\x96\xd9\xcf)\x805\x00\x11\r\xa62\xbcR\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfc9\xbeA\tK\x19\x97\xd2\x16\x9e\x82d\xc2\u00fa\xa6\u025b\u0109lk\x93[\x8b\xbd@\x00\x00\u07d4\xfc=\"k\xb3jX\xf5&V\x88W\xb0\xbb\x12\xd1\t\xec\x93\x01\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfcC\x82\x9a\u01c7\xff\x88\xaa\xf1\x83\xba5*\xad\xbfZ\x15\xb1\x93\x89\u05ac\n+\x05R\xe0\x00\x00\u07d4\xfcI\xc1C\x9aA\u05b3\xcf&\xbbg\xe06R$\xe5\xe3\x8f_\x8966\u05ef^\u024e\x00\x00\u07d4\xfcU\x00\x82Q\x05\xcfq*1\x8a^\x9c;\xfci\u021d\f\x12\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xfcf\xfa\xba'\u007fK]\xe6J\xd4^\xb1\x9c1\xe0\f\xed>\u054a\x011\xbe\xb9%\xff\xd3 \x00\x00\xe0\x94\xfc~\"\xa5\x03\xecZ\xbe\x9b\b\xc5\v\xd1I\x99\xf5 \xfaH\x84\x8a\x01ZG}\xfb\xe1\xea\x14\x80\x00\u07d4\xfc\x82\x15\xa0\xa6\x99\x13\xf6*C\xbf\x1c\x85\x90\xb9\xdd\xcd\r\x8d\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xfc\x98\x9c\xb4\x87\xbf\x1a}\x17\xe4\xc1\xb7\u0137\xaa\xfd\xdak\n\x8d\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xfc\x9b4td\xb2\xf9\x92\x9d\x80~\x03\x9d\xaeH\xd3\u064d\xe3y\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\xfc\xa4;\xbc#\xa0\xd3!\xba\x9eF\xb9)s\\\xe7\xd8\xef\f\x18\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfc\xa7>\xff\x87q\xc0\x10;\xa3\xcc\x1a\x9c%\x94H\xc7*\xbf\v\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfc\xad\xa3\x00(?k\xcc\x13J\x91Eg`\xb0\xd7}\xe4\x10\xe0\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xfc\xbc\\q\xac\xe7\x97AE\v\x01,\xf6\xb8\xd3\xf1}\xb6\x8ap\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\xfc\xbd\x85\xfe\xeajuO\xcf4ID\x9e7\xff\x97\x84\xf7w<\x89\xa7J\xdai\xab\xd7x\x00\x00\xe0\x94\xfc\xc9\u0524&.z\x02z\xb7Q\x91\x10\xd8\x02\u0115\xce\xea9\x8a\x01YQ\x82\"K&H\x00\x00\xe0\x94\xfc\xcd\r\x1e\xce\xe2z\xdd\xea\x95\xf6\x85z\xee\xc8\u01e0K(\xee\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\xfc\u0434\x82|\xd2\b\xff\xbf^u\x9d\xba\x8c<\xc6\x1d\x8c,<\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xfc\xe0\x89c\\\xe9z\xba\xc0kD\x81\x9b\xe5\xbb\n>.\v7\x89\x05\x03\x92\nv0\xa7\x80\x00\u07d4\xfc\xf1\x99\xf8\xb8T\"/\x18.N\x1d\t\x9dN2>*\xae\x01\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfc\xfc:P\x04\xd6xa?\v6\xa6B&\x9a\u007f7\x1c?j\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfd\x19\x1a5\x15}x\x13s\xfbA\x1b\xf9\xf2R\x90\x04|^\xef\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfd\x1f\xaa4{\x0f\u0300L-\xa8l6\xd5\xf1\u044bp\x87\xbb\x89\x02\xd6\xeb$z\x96\xf6\x00\x00\u07d4\xfd\x1f\xb5\xa8\x9a\x89\xa7!\xb8yph\xfb\xc4\u007f>\x9dR\xe1I\x89\f\u0435\x83\u007f\xc6X\x00\x00\u07d4\xfd OOJ\xba%%\xbar\x8a\xfd\xf7\x87\x92\xcb\u07b75\xae\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfd'W\xcc5Q\xa0\x95\x87\x8d\x97\x87V\x15\xfe\fj2\xaa\x8a\x89 m\xb1R\x99\xbe\xac\x00\x00\u07d4\xfd(r\u045eW\x85<\xfa\x16\xef\xfe\x93\u0431\xd4{O\x93\xfb\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xfd))'\x1e\x9d \x95\xa2dv~{\r\xf5.\xa0\xd1\xd4\x00\x89\xa2\xa1\xeb%\x1bZ\xe4\x00\x00\u07d4\xfd7z8Rr\x90\f\xb46\xa3\xbbyb\xcd\xff\xe9?]\xad\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfd@$+\xb3Jp\x85^\xf0\xfd\x90\xf3\x80-\xec!6\xb3'\x89h\xa8u\a>)$\x00\x00\xe0\x94\xfdE,9i\xec\xe3\x80\x1cT \xf1\xcd\u02a1\xc7\x1e\xd2=\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xfdKU\x1fo\xdb\u0366\xc5\x11\xb5\xbb7\"P\xa6\xb7\x83\xe54\x89\x01\x1d\xe1\xe6\xdbE\f\x00\x00\u07d4\xfdK\x98\x95X\xae\x11\xbe\f;6\xe2\xd6\xf2\xa5J\x93C\xca.\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfdM\xe8\xe3t\x8a(\x9c\xf7\xd0`Q}\x9d88\x8d\xb0\x1f\xb8\x89\r\x8drkqw\xa8\x00\x00\u07d4\xfdZc\x15\u007f\x91O\u04d8\uac5c\x13}\xd9U\v\xb7q\\\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xfd`\u04b5\xaf=5\xf7\xaa\xf0\u00d3\x05.y\xc4\xd8#\u0645\x89\x03\x0e\xb5\r.\x14\b\x00\x00\u07d4\xfdhm\xe5?\xa9\u007f\x99c\x9e%hT\x97 \xbcX\x8c\x9e\xfc\x89j\xc5\xc6-\x94\x86\a\x00\x00\u07d4\xfd~\u078fR@\xa0eA\xebi\x9dx,/\x9a\xfb!p\xf6\x89Hz\x9a0E9D\x00\x00\u07d4\xfd\x81+\u019f\xb1p\xefW\xe22~\x80\xaf\xfd\x14\xf8\xe4\xb6\u0489lk\x93[\x8b\xbd@\x00\x00\u07d4\xfd\x88\xd1\x14\"\x0f\b\x1c\xb3\xd5\xe1[\xe8\x15*\xb0sfWj\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xfd\x91\x856\xa8\xef\xa6\xf6\xce\xfe\x1f\xa1\x159\x95\xfe\xf5\xe3=;\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xfd\x92\x0fr&\x82\xaf\xb5\xafE\x1b\x05D\xd4\xf4\x1b;\x9dWB\x89~R\x05j\x12?<\x00\x00\u07d4\xfd\x95y\xf1\x19\xbb\xc8\x19\xa0+a\u3348\x03\xc9B\xf2M2\x89\x05\xb9~\x90\x81\xd9@\x00\x00\u07d4\xfd\xa0\xce\x153\a\a\xf1\v\xce2\x01\x17- \x18\xb9\xdd\xeat\x89\x02\xd0A\xd7\x05\xa2\xc6\x00\x00\xe0\x94\xfd\xa3\x04(\x19\xaf>f)\x00\xe1\xb9+CX\xed\xa6\xe9%\x90\x8a\x19\a\xa2\x84\u054fc\xe0\x00\x00\u07d4\xfd\xa6\x81\x0e\xa5\xac\x98]o\xfb\xf1\xc5\x11\xf1\xc1B\xed\xcf\xdd\xf7\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xfd\xb39D\xf26\x06\x15\xe5\xbe#\x95w\u0221\x9b\xa5-\x98\x87\x89 \x9d\x92/RY\xc5\x00\x00\u07d4\xfd\xbaSY\xf7\xec;\xc7p\xacI\x97]\x84N\xc9qbV\xf1\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xfd\xc4\xd4vZ\x94/[\xf9i1\xa9\xe8\xccz\xb8\xb7W\xffL\x8a\x12lG\x8a\x0e>\xa8`\x00\x00\xe0\x94\xfd\xcd]\x80\xb1\x05\x89zW\xab\xc4xev\x8b)\x00RB\x95\x8a\x01Z\xf1\u05cbX\xc4\x00\x00\x00\u0794\xfd\xd1\x19_y}O5q}\x15\xe6\xf9\x81\n\x9a?\xf5T`\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xfd\xd5\x02\xa7N\x81;\u03e3U\xce\xda<\x17ojhq\xaf\u007f\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xfd\u357c\vm\\\xbbL\x1d\x8f\xea>\vK\xffc^\x9d\xb7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfd\xea\xac*\xcf\x1d\x13\x8e\x19\xf2\xfc?\x9f\xb7E\x92\xe3\ud04a\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\xfd\xec\xc8-\xdf\xc5a\x92\xe2oV<=h\xcbTJ\x96\xbf\xed\x89\x17\xda:\x04\u01f3\xe0\x00\x00\u07d4\xfd\xf4#C\x01\x9b\v\fk\xf2`\xb1s\xaf\xab~E\xb9\xd6!\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xfd\xf4I\xf1\b\xc6\xfbOZ+\b\x1e\xed~E\u645eM%\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfd\xfda4\xc0J\x8a\xb7\xeb\x16\xf0\x06C\xf8\xfe\xd7\u06aa\ucc89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xfe\x00\xbfC\x99\x11\xa5S\x98-\xb68\x03\x92E\xbc\xf02\xdb\u0709\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\xfe\x01n\xc1~\xc5\xf1\x0e;\xb9\x8f\xf4\xa1\xed\xa0E\x15v\x82\xab\x89\x14_T\x02\xe7\xb2\xe6\x00\x00\u07d4\xfe\x0e0\xe2\x14)\rt=\xd3\x0e\xb0\x82\xf1\xf0\xa5\"Z\xdea\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xfe!\v\x8f\x04\xdcmOv!j\xcf\xcb\u055b\xa8;\xe9\xb60\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfe\"\xa0\xb3\x88f\x8d\x1a\xe2d>w\x1d\xac\xf3\x8aCB#\u0309\xd8\xdb^\xbd{&8\x00\x00\u07d4\xfe6&\x88\x84_\xa2D\u0300~K\x110\xeb7A\xa8\x05\x1e\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfe8'\xd5v0\u03c7a\xd5\x12y{\v\x85\x8eG\x8b\xbd\x12\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfeA\x8bB\x1a\x9cm76\x02y\x04u\xd20>\x11\xa7Y0\x897\b\xba\xed=h\x90\x00\x00\u07d4\xfeBI\x12yP\xe2\xf8\x96\xec\x0e~.=\x05Z\xab\x10U\x0f\x89$=M\x18\"\x9c\xa2\x00\x00\xe0\x94\xfeM\x84\x03!o\xd5qW+\xf1\xbd\xb0\x1d\x00W\x89x\u0588\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\xfeS\xb9I\x89\u0619d\xda aS\x95&\xbb\xe9y\xdd.\xa9\x89h\xa8u\a>)$\x00\x00\u07d4\xfeT\x9b\xbf\xe6G@\x18\x98\x92\x93%8\u06afF\u04b6\x1dO\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94\xfea]\x97\\\b\x87\xe0\xc9\x11>\xc7)\x84 \xa7\x93\xaf\x8b\x96\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xfee\xc4\x18\x8dy\"Wi\td D\xfd\xc5#\x95V\x01e\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xfei\u007f\xf2,\xa5G\xbf\xc9^3\xd9`\xda`\\gc\xf3[\x89G\xd4\x11\x9f\xd9`\x94\x00\x00\u07d4\xfej\x89[y\\\xb4\xbf\x85\x90=<\xe0\x9cZ\xa49S\u04ff\x89\xb8Pz\x82\a( \x00\x00\u07d4\xfeo_B\xb6\x19;\x1a\xd1b\x06\u4bf5#\x9dM}\xb4^\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\xfep\x11\xb6\x98\xbf3q\x13-tE\xb1\x9e\xb5\xb0\x945j\xee\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfe\x80\xe9#-\xea\xff\x19\xba\xf9\x98i\x88:K\xdf\x00\x04\xe5<\x89.b\xf2\ni\xbe@\x00\x00\u07d4\xfe\x8en6eW\r\xffz\x1b\xdaiz\xa5\x89\xc0\xb4\xe9\x02J\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfe\x8f\x1f\u072b\u007f\xbe\u0266\xa3\xfc\xc5\aa\x96\x00P\\6\xa3\x89\x01\x11du\x9f\xfb2\x00\x00\u07d4\xfe\x91\xec\xcf+\xd5f\xaf\xa1\x16\x96\xc5\x04\x9f\xa8Lic\nR\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xfe\x96\xc4\xcd8\x15b@\x1a\xa3*\x86\xe6[\x9dR\xfa\x8a\xee'\x89\x8f\x1d\\\x1c\xae7@\x00\x00\u07d4\xfe\x98\xc6d\xc3\xe4G\xa9^i\xbdX!q\xb7\x17n\xa2\xa6\x85\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xfe\x9a\xd1.\xf0]m\x90&\x1f\x96\xc84\n\x03\x81\x97M\xf4w\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfe\x9c\x0f\xff\xef\xb8\x03\b\x12V\xc0\xcfMfY\xe6\xd3>\xb4\xfb\x89R\xd5B\x80O\x1c\xe0\x00\x00\u07d4\xfe\x9c\xfc;\xb2\x93\u0772\x85\xe6%\xf3X/t\xa6\xb0\xa5\xa6\u0349j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94\xfe\x9e\x11\x97\u05d7JvH\xdc\u01e01\x12\xa8\x8e\xdb\xc9\x04]\x8a\x01\n\xfc\x1a\xde;N\xd4\x00\x00\xe0\x94\xfe\xac\xa2\xactbK\xf3H\xda\u0258QC\xcf\xd6R\xa4\xbeU\x8a\x05\x89\u007f\u02f0)\x14\b\x80\x00\u07d4\xfe\xad\x18\x03\xe5\xe77\xa6\x8e\x18G-\x9a\xc7\x15\xf0\x99L\u00be\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xfe\xb8\xb8\xe2\xafqj\xe4\x1f\xc7\xc0K\xcf)T\x01VF\x1ek\x89TQt\xa5(\xa7z\x00\x00\u07d4\xfe\xb9-0\xbf\x01\xff\x9a\x19\x01flUsS+\xfa\a\xee\xec\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfe\xbc1s\xbc\x90r\x13cT\x00+{O\xb3\xbf\xc5?\"\xf1\x89\x14\x0e\xc8\x0f\xa7\xee\x88\x00\x00\u07d4\xfe\xbdH\xd0\xff\xdb\xd5el\xd5\xe6\x866:a\x14R(\xf2y\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\xfe\xbd\x9f\x81\xcfx\xbd_\xb6\u0139\xa2K\xd4\x14\xbb\x9b\xfaLN\x89k\xe1\x0f\xb8\xedn\x13\x80\x00\u07d4\xfe\xc0o\xe2{D\u01c4\xb29n\xc9/{\x92:\xd1~\x90w\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfe\xc1NT\x85\xde+>\xef^t\xc4aF\u06ceEN\x035\x89\t\xb4\x1f\xbf\x9e\n\xec\x00\x00\u07d4\xfe\xd8Gm\x10\u0544\xb3\x8b\xfag7`\x0e\xf1\x9d5\xc4\x1e\u0609b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xfe\xef;n\xab\xc9J\xff\xd31\f\x1cM\x0ee7^\x13\x11\x19\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfe\xf0\x9dp$?9\xed\x8c\xd8\x00\xbf\x96QG\x9e\x8fJ\xca<\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xfe\xf3\xb3\u07ad\x1ai&\u051a\xa3+\x12\xc2*\xf5M\x9f\xf9\x85\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xff\v|\xb7\x1d\xa9\xd4\xc1\xean\xcc(\xeb\xdaPLc\xf8/\u04498\x8a\x88]\xf2\xfcl\x00\x00\u07d4\xff\f\xc6\u73c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xff'&)AH\xb8lx\xa97$\x97\xe4Y\x89\x8e\xd3\xfe\xe3\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xff=\xedz@\u04ef\xf0\u05e8\xc4_\xa6\x13j\xa0C=\xb4W\x89lh\xcc\u041b\x02,\x00\x00\u07d4\xff>\xeeW\xc3Mm\xae\x97\r\x8b1\x11\x17\xc55\x86\xcd5\x02\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xff>\xf6\xba\x15\x1c!\xb5\x99\x86\xaed\xf6\xe8\"\x8b\u0262\xc73\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xffA\xd9\xe1\xb4\xef\xfe\x18\u0630\xd1\xf6?\xc4%_\xb4\xe0l=\x89Hz\x9a0E9D\x00\x00\u07d4\xffE\xcb4\xc9(6M\x9c\xc9\u063b\x0074ta\x8f\x06\xf3\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xffI\xa7u\x81N\xc0\x00Q\xa7\x95\xa8u\xde$Y.\xa4\x00\u050a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\xffJ@\x8fP\xe9\xe7!F\xa2\x8c\xe4\xfc\x8d\x90'\x1f\x11n\x84\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xffM\x9c\x84\x84\xc4\x10T\x89H\xa4\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x941\xb9\x8d\x14\x00{\xde\xe67)\x80\x86\x98\x8a\v\xbd1\x18E#\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" const goerliAllocData = "\xf9\x04\x06\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xe0\x94L*\xe4\x82Y5\x05\xf0\x16<\xde\xfc\a>\x81\xc6<\xdaA\a\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\xa8\xe8\xf1G2e\x8eKQ\xe8q\x191\x05:\x8ai\xba\xf2\xb1\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe1\x94\u0665\x17\x9f\t\x1d\x85\x05\x1d<\x98'\x85\xef\xd1E\\\uc199\x8b\bE\x95\x16\x14\x01HJ\x00\x00\x00\xe1\x94\u08bdBX\xd2v\x887\xba\xa2j(\xfeq\xdc\a\x9f\x84\u01cbJG\xe3\xc1$H\xf4\xad\x00\x00\x00" const sepoliaAllocData = "\xf9\x01\xee\u0791i\x16\xa8{\x823?BE\x04f#\xb27\x94\xc6\\\x8b\bE\x95\x16\x14\x01HJ\x00\x00\x00\xe1\x94\x10\xf5\xd4XT\xe08\a\x14\x85\xac\x9e@#\b\u03c0\xd2\xd2\xfe\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\u0794y\x9d2\x9e_X4\x19\x16|\xd7\"\x96$\x85\x92n3\x8fJ\x88\r\u0db3\xa7d\x00\x00\xe0\x94|\xf5\xb7\x9b\xfe)\x1ag\xab\x02\xb3\x93\xe4V\xcc\xc4\xc2f\xf7S\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\x8b\u007f\tw\xbbO\x0f\xbepv\xfa\"\xbc$\xac\xa0CX?^\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xa2\xa6\xd949\x14O\xfeM'\xc9\xe0\x88\xdc\u0637\x83\x94bc\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xaa\xec\x869DA\xf9\x15\xbc\xe3\xe6\xab9\x99w\xe9\x90o;i\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\u1532\x1c3\xde\x1f\xab?\xa1T\x99\xc6+Y\xfe\f\xc3%\x00 \u044bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe0\x94\xbc\x11)Y6\xaay\u0554\x13\x9d\xe1\xb2\xe1&)AO;\u06ca\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xbe\xef2\xca[\x9a\x19\x8d'\xb4\xe0/LpC\x9f\xe6\x03V\u03ca\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe1\x94\xd7\xd7lX\xb3\xa5\x19\xe9\xfal\xc4\xd2-\xc0\x17%\x9b\u011f\x1e\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe0\x94\xd7\xed\xdbx\xed)[<\x96)$\x0e\x89$\xfb\x8d\x88t\xdd\u060a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\u0665\x17\x9f\t\x1d\x85\x05\x1d<\x98'\x85\xef\xd1E\\\uc199\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xe2\xe2e\x90(\x147\x84\xd5W\xbc\xeco\xf3\xa0r\x10H\x88\n\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xf4|\xae\x1c\xf7\x9c\xa6u\x8b\xfcx}\xbd!\u6f7eq\x12\xb8\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00" -const KilnAllocData = `{ - "config": { - "chainId": 1337802, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "berlinBlock": 0, - "londonBlock": 0, - "mergeForkBlock": 1000, - "terminalTotalDifficulty": 20000000000000 - }, - "alloc": { - "0x0000000000000000000000000000000000000000": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000001": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000002": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000003": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000004": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000005": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000006": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000007": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000008": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000009": { - "balance": "1" - }, - "0x000000000000000000000000000000000000000a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000000c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000000d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000000e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000000f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000010": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000011": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000012": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000013": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000014": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000015": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000016": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000017": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000018": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000019": { - "balance": "1" - }, - "0x000000000000000000000000000000000000001a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000001b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000001c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000001d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000001e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000001f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000020": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000021": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000022": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000023": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000024": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000025": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000026": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000027": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000028": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000029": { - "balance": "1" - }, - "0x000000000000000000000000000000000000002a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000002b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000002c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000002d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000002e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000002f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000030": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000031": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000032": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000033": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000034": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000035": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000036": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000037": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000038": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000039": { - "balance": "1" - }, - "0x000000000000000000000000000000000000003a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000003b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000003c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000003d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000003e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000003f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000040": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000041": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000042": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000043": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000044": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000045": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000046": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000047": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000048": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000049": { - "balance": "1" - }, - "0x000000000000000000000000000000000000004a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000004b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000004c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000004d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000004e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000004f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000050": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000051": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000052": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000053": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000054": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000055": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000056": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000057": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000058": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000059": { - "balance": "1" - }, - "0x000000000000000000000000000000000000005a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000005b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000005c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000005d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000005e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000005f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000060": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000061": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000062": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000063": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000064": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000065": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000066": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000067": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000068": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000069": { - "balance": "1" - }, - "0x000000000000000000000000000000000000006a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000006b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000006c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000006d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000006e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000006f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000070": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000071": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000072": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000073": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000074": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000075": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000076": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000077": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000078": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000079": { - "balance": "1" - }, - "0x000000000000000000000000000000000000007a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000007b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000007c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000007d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000007e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000007f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000080": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000081": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000082": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000083": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000084": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000085": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000086": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000087": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000088": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000089": { - "balance": "1" - }, - "0x000000000000000000000000000000000000008a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000008b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000008c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000008d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000008e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000008f": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000090": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000091": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000092": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000093": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000094": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000095": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000096": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000097": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000098": { - "balance": "1" - }, - "0x0000000000000000000000000000000000000099": { - "balance": "1" - }, - "0x000000000000000000000000000000000000009a": { - "balance": "1" - }, - "0x000000000000000000000000000000000000009b": { - "balance": "1" - }, - "0x000000000000000000000000000000000000009c": { - "balance": "1" - }, - "0x000000000000000000000000000000000000009d": { - "balance": "1" - }, - "0x000000000000000000000000000000000000009e": { - "balance": "1" - }, - "0x000000000000000000000000000000000000009f": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a0": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a1": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a2": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a3": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a4": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a5": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a6": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a7": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a8": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000a9": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000aa": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ab": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ac": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ad": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ae": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000af": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b0": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b1": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b2": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b3": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b4": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b5": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b6": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b7": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b8": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000b9": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ba": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000bb": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000bc": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000bd": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000be": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000bf": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c0": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c1": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c2": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c3": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c4": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c5": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c6": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c7": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c8": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000c9": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ca": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000cb": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000cc": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000cd": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ce": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000cf": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d0": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d1": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d2": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d3": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d4": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d5": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d6": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d7": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d8": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000d9": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000da": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000db": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000dc": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000dd": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000de": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000df": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e0": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e1": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e2": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e3": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e4": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e5": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e6": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e7": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e8": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000e9": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ea": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000eb": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ec": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ed": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ee": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ef": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f0": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f1": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f2": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f3": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f4": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f5": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f6": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f7": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f8": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000f9": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000fa": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000fb": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000fc": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000fd": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000fe": { - "balance": "1" - }, - "0x00000000000000000000000000000000000000ff": { - "balance": "1" - }, - "0x4242424242424242424242424242424242424242": { - "balance": "0", - "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a26469706673582212201dd26f37a621703009abf16e77e69c93dc50c79db7f6cc37543e3e0e3decdc9764736f6c634300060b0033", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000022": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", - "0x0000000000000000000000000000000000000000000000000000000000000023": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", - "0x0000000000000000000000000000000000000000000000000000000000000024": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", - "0x0000000000000000000000000000000000000000000000000000000000000025": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", - "0x0000000000000000000000000000000000000000000000000000000000000026": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", - "0x0000000000000000000000000000000000000000000000000000000000000027": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", - "0x0000000000000000000000000000000000000000000000000000000000000028": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", - "0x0000000000000000000000000000000000000000000000000000000000000029": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", - "0x000000000000000000000000000000000000000000000000000000000000002a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", - "0x000000000000000000000000000000000000000000000000000000000000002b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", - "0x000000000000000000000000000000000000000000000000000000000000002c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", - "0x000000000000000000000000000000000000000000000000000000000000002d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", - "0x000000000000000000000000000000000000000000000000000000000000002e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", - "0x000000000000000000000000000000000000000000000000000000000000002f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", - "0x0000000000000000000000000000000000000000000000000000000000000030": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", - "0x0000000000000000000000000000000000000000000000000000000000000031": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", - "0x0000000000000000000000000000000000000000000000000000000000000032": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", - "0x0000000000000000000000000000000000000000000000000000000000000033": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", - "0x0000000000000000000000000000000000000000000000000000000000000034": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", - "0x0000000000000000000000000000000000000000000000000000000000000035": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", - "0x0000000000000000000000000000000000000000000000000000000000000036": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", - "0x0000000000000000000000000000000000000000000000000000000000000037": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", - "0x0000000000000000000000000000000000000000000000000000000000000038": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", - "0x0000000000000000000000000000000000000000000000000000000000000039": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", - "0x000000000000000000000000000000000000000000000000000000000000003a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", - "0x000000000000000000000000000000000000000000000000000000000000003b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", - "0x000000000000000000000000000000000000000000000000000000000000003c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", - "0x000000000000000000000000000000000000000000000000000000000000003d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", - "0x000000000000000000000000000000000000000000000000000000000000003e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", - "0x000000000000000000000000000000000000000000000000000000000000003f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", - "0x0000000000000000000000000000000000000000000000000000000000000040": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" - } - }, - "0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134": { - "balance": "10000000000000000000000000" - }, - "0x2cA5F489CC1Fd1CEC24747B64E8dE0F4A6A850E1": { - "balance": "10000000000000000000000000" - }, - "0x7203bd333a874D9d329050ecE393820fCD501eaA": { - "balance": "10000000000000000000000000" - }, - "0xA51918aA40D78Ff8be939bf0E8404252875c6aDF": { - "balance": "10000000000000000000000000" - }, - "0xAA81078e6b2121dd7A846690DFdD6b10d7658d8B": { - "balance": "10000000000000000000000000" - }, - "0xFA2d31D8f21c1D1633E9BEB641dF77D21D63ccDd": { - "balance": "10000000000000000000000000" - }, - "0xf751C9c6d60614226fE57D2cAD6e10C856a2ddA3": { - "balance": "10000000000000000000000000" - }, - "0x9cD16887f6A808AEaa65D3c840f059EeA4ca1319": { - "balance": "10000000000000000000000000" - }, - "0x2E07043584F11BFF0AC39c927665DF6c6ebaffFB": { - "balance": "10000000000000000000000000" - }, - "0x60e771E5eCA8E26690920de669520Da210D64A9B": { - "balance": "10000000000000000000000000" - }, - "0xFC4db92C2Cf77CE02fBfd7Da0346d2CbFA66aD59": { - "balance": "10000000000000000000000000" - } - }, - "coinbase": "0x0000000000000000000000000000000000000000", - "difficulty": "0x01", - "extraData": "", - "gasLimit": "0x400000", - "nonce": "0x1234", - "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "timestamp": "0" - }` +const holeskyAllocData = "\xf9,\x85\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\x7f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\u0791i\x16\xa8{\x823?BE\x04f#\xb27\x94\xc6\\\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe1\x94\v\xe9I\x92\x8f\xf1\x99\xc9\xeb\xa9\xe1\x10\xdb!\n\xa5\xc9N\xfa\u040b|\x13\xbcK,\x13\x8e\u0344h\xa0\x03\x7f\x05\x8a\x9d\xaf\xady\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xf9!2\x94BBBBBBBBBBBBBBBBBBBB\x80\xf9!\x19\x80\xb9\x18\xd6`\x80`@R`\x046\x10a\x00?W`\x005`\xe0\x1c\x80c\x01\xff\u0267\x14a\x00DW\x80c\"\x89Q\x18\x14a\x00\xa4W\x80cb\x1f\xd10\x14a\x01\xbaW\x80c\xc5\xf2\x89/\x14a\x02DW[`\x00\x80\xfd[4\x80\x15a\x00PW`\x00\x80\xfd[Pa\x00\x90`\x04\x806\x03` \x81\x10\x15a\x00gW`\x00\x80\xfd[P5\x7f\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16a\x02kV[`@\x80Q\x91\x15\x15\x82RQ\x90\x81\x90\x03` \x01\x90\xf3[a\x01\xb8`\x04\x806\x03`\x80\x81\x10\x15a\x00\xbaW`\x00\x80\xfd[\x81\x01\x90` \x81\x01\x815d\x01\x00\x00\x00\x00\x81\x11\x15a\x00\xd5W`\x00\x80\xfd[\x82\x01\x83` \x82\x01\x11\x15a\x00\xe7W`\x00\x80\xfd[\x805\x90` \x01\x91\x84`\x01\x83\x02\x84\x01\x11d\x01\x00\x00\x00\x00\x83\x11\x17\x15a\x01\tW`\x00\x80\xfd[\x91\x93\x90\x92\x90\x91` \x81\x01\x905d\x01\x00\x00\x00\x00\x81\x11\x15a\x01'W`\x00\x80\xfd[\x82\x01\x83` \x82\x01\x11\x15a\x019W`\x00\x80\xfd[\x805\x90` \x01\x91\x84`\x01\x83\x02\x84\x01\x11d\x01\x00\x00\x00\x00\x83\x11\x17\x15a\x01[W`\x00\x80\xfd[\x91\x93\x90\x92\x90\x91` \x81\x01\x905d\x01\x00\x00\x00\x00\x81\x11\x15a\x01yW`\x00\x80\xfd[\x82\x01\x83` \x82\x01\x11\x15a\x01\x8bW`\x00\x80\xfd[\x805\x90` \x01\x91\x84`\x01\x83\x02\x84\x01\x11d\x01\x00\x00\x00\x00\x83\x11\x17\x15a\x01\xadW`\x00\x80\xfd[\x91\x93P\x91P5a\x03\x04V[\x00[4\x80\x15a\x01\xc6W`\x00\x80\xfd[Pa\x01\xcfa\x10\xb5V[`@\x80Q` \x80\x82R\x83Q\x81\x83\x01R\x83Q\x91\x92\x83\x92\x90\x83\x01\x91\x85\x01\x90\x80\x83\x83`\x00[\x83\x81\x10\x15a\x02\tW\x81\x81\x01Q\x83\x82\x01R` \x01a\x01\xf1V[PPPP\x90P\x90\x81\x01\x90`\x1f\x16\x80\x15a\x026W\x80\x82\x03\x80Q`\x01\x83` \x03a\x01\x00\n\x03\x19\x16\x81R` \x01\x91P[P\x92PPP`@Q\x80\x91\x03\x90\xf3[4\x80\x15a\x02PW`\x00\x80\xfd[Pa\x02Ya\x10\xc7V[`@\x80Q\x91\x82RQ\x90\x81\x90\x03` \x01\x90\xf3[`\x00\x7f\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x82\x16\x7f\x01\xff\u0267\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x80a\x02\xfeWP\x7f\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x82\x16\x7f\x85d\t\a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14[\x92\x91PPV[`0\x86\x14a\x03]W`@Q\x7f\b\xc3y\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R`\x04\x01\x80\x80` \x01\x82\x81\x03\x82R`&\x81R` \x01\x80a\x18\x05`&\x919`@\x01\x91PP`@Q\x80\x91\x03\x90\xfd[` \x84\x14a\x03\xb6W`@Q\x7f\b\xc3y\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R`\x04\x01\x80\x80` \x01\x82\x81\x03\x82R`6\x81R` \x01\x80a\x17\x9c`6\x919`@\x01\x91PP`@Q\x80\x91\x03\x90\xfd[``\x82\x14a\x04\x0fW`@Q\x7f\b\xc3y\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R`\x04\x01\x80\x80` \x01\x82\x81\x03\x82R`)\x81R` \x01\x80a\x18x`)\x919`@\x01\x91PP`@Q\x80\x91\x03\x90\xfd[g\r\u0db3\xa7d\x00\x004\x10\x15a\x04pW`@Q\x7f\b\xc3y\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R`\x04\x01\x80\x80` \x01\x82\x81\x03\x82R`&\x81R` \x01\x80a\x18R`&\x919`@\x01\x91PP`@Q\x80\x91\x03\x90\xfd[c;\x9a\xca\x004\x06\x15a\x04\xcdW`@Q\x7f\b\xc3y\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R`\x04\x01\x80\x80` \x01\x82\x81\x03\x82R`3\x81R` \x01\x80a\x17\xd2`3\x919`@\x01\x91PP`@Q\x80\x91\x03\x90\xfd[c;\x9a\xca\x004\x04g\xff\xff\xff\xff\xff\xff\xff\xff\x81\x11\x15a\x055W`@Q\x7f\b\xc3y\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R`\x04\x01\x80\x80` \x01\x82\x81\x03\x82R`'\x81R` \x01\x80a\x18+`'\x919`@\x01\x91PP`@Q\x80\x91\x03\x90\xfd[``a\x05@\x82a\x14\xbaV[\x90P\x7fd\x9b\xbcb\xd0\xe3\x13B\xaf\xeaN\\\xd8-@I\xe7\xe1\xee\x91/\xc0\x88\x9a\xa7\x90\x80;\xe3\x908\u0149\x89\x89\x89\x85\x8a\x8aa\x05u` Ta\x14\xbaV[`@\x80Q`\xa0\x80\x82R\x81\x01\x89\x90R\x90\x81\x90` \x82\x01\x90\x82\x01``\x83\x01`\x80\x84\x01`\xc0\x85\x01\x8e\x8e\x80\x82\x847`\x00\x83\x82\x01R`\x1f\x01\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x16\x90\x91\x01\x87\x81\x03\x86R\x8c\x81R` \x01\x90P\x8c\x8c\x80\x82\x847`\x00\x83\x82\x01\x81\x90R`\x1f\x90\x91\x01\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x16\x90\x92\x01\x88\x81\x03\x86R\x8cQ\x81R\x8cQ` \x91\x82\x01\x93\x91\x8e\x01\x92P\x90\x81\x90\x84\x90\x84\x90[\x83\x81\x10\x15a\x06HW\x81\x81\x01Q\x83\x82\x01R` \x01a\x060V[PPPP\x90P\x90\x81\x01\x90`\x1f\x16\x80\x15a\x06uW\x80\x82\x03\x80Q`\x01\x83` \x03a\x01\x00\n\x03\x19\x16\x81R` \x01\x91P[P\x86\x81\x03\x83R\x88\x81R` \x01\x89\x89\x80\x82\x847`\x00\x83\x82\x01\x81\x90R`\x1f\x90\x91\x01\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x16\x90\x92\x01\x88\x81\x03\x84R\x89Q\x81R\x89Q` \x91\x82\x01\x93\x91\x8b\x01\x92P\x90\x81\x90\x84\x90\x84\x90[\x83\x81\x10\x15a\x06\xefW\x81\x81\x01Q\x83\x82\x01R` \x01a\x06\xd7V[PPPP\x90P\x90\x81\x01\x90`\x1f\x16\x80\x15a\a\x1cW\x80\x82\x03\x80Q`\x01\x83` \x03a\x01\x00\n\x03\x19\x16\x81R` \x01\x91P[P\x9dPPPPPPPPPPPPPP`@Q\x80\x91\x03\x90\xa1`\x00`\x02\x8a\x8a`\x00`\x80\x1b`@Q` \x01\x80\x84\x84\x80\x82\x847\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x94\x16\x91\x90\x93\x01\x90\x81R`@\x80Q\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x81\x84\x03\x01\x81R`\x10\x90\x92\x01\x90\x81\x90R\x81Q\x91\x95P\x93P\x83\x92P` \x85\x01\x91P\x80\x83\x83[` \x83\x10a\a\xfcW\x80Q\x82R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x90\x92\x01\x91` \x91\x82\x01\x91\x01a\a\xbfV[Q\x81Q` \x93\x84\x03a\x01\x00\n\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x80\x19\x90\x92\x16\x91\x16\x17\x90R`@Q\x91\x90\x93\x01\x94P\x91\x92PP\x80\x83\x03\x81\x85Z\xfa\x15\x80\x15a\bYW=`\x00\x80>=`\x00\xfd[PPP`@Q=` \x81\x10\x15a\bnW`\x00\x80\xfd[PQ\x90P`\x00`\x02\x80a\b\x84`@\x84\x8a\x8ca\x16\xfeV[`@Q` \x01\x80\x83\x83\x80\x82\x847\x80\x83\x01\x92PPP\x92PPP`@Q` \x81\x83\x03\x03\x81R\x90`@R`@Q\x80\x82\x80Q\x90` \x01\x90\x80\x83\x83[` \x83\x10a\b\xf8W\x80Q\x82R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x90\x92\x01\x91` \x91\x82\x01\x91\x01a\b\xbbV[Q\x81Q` \x93\x84\x03a\x01\x00\n\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x80\x19\x90\x92\x16\x91\x16\x17\x90R`@Q\x91\x90\x93\x01\x94P\x91\x92PP\x80\x83\x03\x81\x85Z\xfa\x15\x80\x15a\tUW=`\x00\x80>=`\x00\xfd[PPP`@Q=` \x81\x10\x15a\tjW`\x00\x80\xfd[PQ`\x02a\t{\x89`@\x81\x8da\x16\xfeV[`@Q`\x00\x90` \x01\x80\x84\x84\x80\x82\x847\x91\x90\x91\x01\x92\x83RPP`@\x80Q\x80\x83\x03\x81R` \x92\x83\x01\x91\x82\x90R\x80Q\x90\x94P\x90\x92P\x82\x91\x84\x01\x90\x80\x83\x83[` \x83\x10a\t\xf4W\x80Q\x82R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x90\x92\x01\x91` \x91\x82\x01\x91\x01a\t\xb7V[Q\x81Q` \x93\x84\x03a\x01\x00\n\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x80\x19\x90\x92\x16\x91\x16\x17\x90R`@Q\x91\x90\x93\x01\x94P\x91\x92PP\x80\x83\x03\x81\x85Z\xfa\x15\x80\x15a\nQW=`\x00\x80>=`\x00\xfd[PPP`@Q=` \x81\x10\x15a\nfW`\x00\x80\xfd[PQ`@\x80Q` \x81\x81\x01\x94\x90\x94R\x80\x82\x01\x92\x90\x92R\x80Q\x80\x83\x03\x82\x01\x81R``\x90\x92\x01\x90\x81\x90R\x81Q\x91\x92\x90\x91\x82\x91\x84\x01\x90\x80\x83\x83[` \x83\x10a\n\xdaW\x80Q\x82R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x90\x92\x01\x91` \x91\x82\x01\x91\x01a\n\x9dV[Q\x81Q` \x93\x84\x03a\x01\x00\n\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x80\x19\x90\x92\x16\x91\x16\x17\x90R`@Q\x91\x90\x93\x01\x94P\x91\x92PP\x80\x83\x03\x81\x85Z\xfa\x15\x80\x15a\v7W=`\x00\x80>=`\x00\xfd[PPP`@Q=` \x81\x10\x15a\vLW`\x00\x80\xfd[PQ`@\x80Q` \x81\x01\x85\x81R\x92\x93P`\x00\x92`\x02\x92\x83\x92\x87\x92\x8f\x92\x8f\x92\x01\x83\x83\x80\x82\x847\x80\x83\x01\x92PPP\x93PPPP`@Q` \x81\x83\x03\x03\x81R\x90`@R`@Q\x80\x82\x80Q\x90` \x01\x90\x80\x83\x83[` \x83\x10a\v\xd9W\x80Q\x82R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x90\x92\x01\x91` \x91\x82\x01\x91\x01a\v\x9cV[Q\x81Q` \x93\x84\x03a\x01\x00\n\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x80\x19\x90\x92\x16\x91\x16\x17\x90R`@Q\x91\x90\x93\x01\x94P\x91\x92PP\x80\x83\x03\x81\x85Z\xfa\x15\x80\x15a\f6W=`\x00\x80>=`\x00\xfd[PPP`@Q=` \x81\x10\x15a\fKW`\x00\x80\xfd[PQ`@Q\x86Q`\x02\x91\x88\x91`\x00\x91\x88\x91` \x91\x82\x01\x91\x82\x91\x90\x86\x01\x90\x80\x83\x83[` \x83\x10a\f\xa9W\x80Q\x82R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x90\x92\x01\x91` \x91\x82\x01\x91\x01a\flV[`\x01\x83` \x03a\x01\x00\n\x03\x80\x19\x82Q\x16\x81\x84Q\x16\x80\x82\x17\x85RPPPPPP\x90P\x01\x83g\xff\xff\xff\xff\xff\xff\xff\xff\x19\x16g\xff\xff\xff\xff\xff\xff\xff\xff\x19\x16\x81R`\x18\x01\x82\x81R` \x01\x93PPPP`@Q` \x81\x83\x03\x03\x81R\x90`@R`@Q\x80\x82\x80Q\x90` \x01\x90\x80\x83\x83[` \x83\x10a\rNW\x80Q\x82R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x90\x92\x01\x91` \x91\x82\x01\x91\x01a\r\x11V[Q\x81Q` \x93\x84\x03a\x01\x00\n\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x80\x19\x90\x92\x16\x91\x16\x17\x90R`@Q\x91\x90\x93\x01\x94P\x91\x92PP\x80\x83\x03\x81\x85Z\xfa\x15\x80\x15a\r\xabW=`\x00\x80>=`\x00\xfd[PPP`@Q=` \x81\x10\x15a\r\xc0W`\x00\x80\xfd[PQ`@\x80Q` \x81\x81\x01\x94\x90\x94R\x80\x82\x01\x92\x90\x92R\x80Q\x80\x83\x03\x82\x01\x81R``\x90\x92\x01\x90\x81\x90R\x81Q\x91\x92\x90\x91\x82\x91\x84\x01\x90\x80\x83\x83[` \x83\x10a\x0e4W\x80Q\x82R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x90\x92\x01\x91` \x91\x82\x01\x91\x01a\r\xf7V[Q\x81Q` \x93\x84\x03a\x01\x00\n\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x80\x19\x90\x92\x16\x91\x16\x17\x90R`@Q\x91\x90\x93\x01\x94P\x91\x92PP\x80\x83\x03\x81\x85Z\xfa\x15\x80\x15a\x0e\x91W=`\x00\x80>=`\x00\xfd[PPP`@Q=` \x81\x10\x15a\x0e\xa6W`\x00\x80\xfd[PQ\x90P\x85\x81\x14a\x0f\x02W`@Q\x7f\b\xc3y\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R`\x04\x01\x80\x80` \x01\x82\x81\x03\x82R`T\x81R` \x01\x80a\x17H`T\x919``\x01\x91PP`@Q\x80\x91\x03\x90\xfd[` Tc\xff\xff\xff\xff\x11a\x0f`W`@Q\x7f\b\xc3y\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x81R`\x04\x01\x80\x80` \x01\x82\x81\x03\x82R`!\x81R` \x01\x80a\x17'`!\x919`@\x01\x91PP`@Q\x80\x91\x03\x90\xfd[` \x80T`\x01\x01\x90\x81\x90U`\x00[` \x81\x10\x15a\x10\xa9W\x81`\x01\x16`\x01\x14\x15a\x0f\xa0W\x82`\x00\x82` \x81\x10a\x0f\x91W\xfe[\x01UPa\x10\xac\x95PPPPPPV[`\x02`\x00\x82` \x81\x10a\x0f\xafW\xfe[\x01T\x84`@Q` \x01\x80\x83\x81R` \x01\x82\x81R` \x01\x92PPP`@Q` \x81\x83\x03\x03\x81R\x90`@R`@Q\x80\x82\x80Q\x90` \x01\x90\x80\x83\x83[` \x83\x10a\x10%W\x80Q\x82R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x90\x92\x01\x91` \x91\x82\x01\x91\x01a\x0f\xe8V[Q\x81Q` \x93\x84\x03a\x01\x00\n\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x80\x19\x90\x92\x16\x91\x16\x17\x90R`@Q\x91\x90\x93\x01\x94P\x91\x92PP\x80\x83\x03\x81\x85Z\xfa\x15\x80\x15a\x10\x82W=`\x00\x80>=`\x00\xfd[PPP`@Q=` \x81\x10\x15a\x10\x97W`\x00\x80\xfd[PQ\x92P`\x02\x82\x04\x91P`\x01\x01a\x0fnV[P\xfe[PPPPPPPV[``a\x10\xc2` Ta\x14\xbaV[\x90P\x90V[` T`\x00\x90\x81\x90\x81[` \x81\x10\x15a\x12\xf0W\x81`\x01\x16`\x01\x14\x15a\x11\xe6W`\x02`\x00\x82` \x81\x10a\x10\xf5W\xfe[\x01T\x84`@Q` \x01\x80\x83\x81R` \x01\x82\x81R` \x01\x92PPP`@Q` \x81\x83\x03\x03\x81R\x90`@R`@Q\x80\x82\x80Q\x90` \x01\x90\x80\x83\x83[` \x83\x10a\x11kW\x80Q\x82R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x90\x92\x01\x91` \x91\x82\x01\x91\x01a\x11.V[Q\x81Q` \x93\x84\x03a\x01\x00\n\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x80\x19\x90\x92\x16\x91\x16\x17\x90R`@Q\x91\x90\x93\x01\x94P\x91\x92PP\x80\x83\x03\x81\x85Z\xfa\x15\x80\x15a\x11\xc8W=`\x00\x80>=`\x00\xfd[PPP`@Q=` \x81\x10\x15a\x11\xddW`\x00\x80\xfd[PQ\x92Pa\x12\xe2V[`\x02\x83`!\x83` \x81\x10a\x11\xf6W\xfe[\x01T`@Q` \x01\x80\x83\x81R` \x01\x82\x81R` \x01\x92PPP`@Q` \x81\x83\x03\x03\x81R\x90`@R`@Q\x80\x82\x80Q\x90` \x01\x90\x80\x83\x83[` \x83\x10a\x12kW\x80Q\x82R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x90\x92\x01\x91` \x91\x82\x01\x91\x01a\x12.V[Q\x81Q` \x93\x84\x03a\x01\x00\n\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x80\x19\x90\x92\x16\x91\x16\x17\x90R`@Q\x91\x90\x93\x01\x94P\x91\x92PP\x80\x83\x03\x81\x85Z\xfa\x15\x80\x15a\x12\xc8W=`\x00\x80>=`\x00\xfd[PPP`@Q=` \x81\x10\x15a\x12\xddW`\x00\x80\xfd[PQ\x92P[`\x02\x82\x04\x91P`\x01\x01a\x10\xd1V[P`\x02\x82a\x12\xff` Ta\x14\xbaV[`\x00`@\x1b`@Q` \x01\x80\x84\x81R` \x01\x83\x80Q\x90` \x01\x90\x80\x83\x83[` \x83\x10a\x13ZW\x80Q\x82R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x90\x92\x01\x91` \x91\x82\x01\x91\x01a\x13\x1dV[Q\x81Q` \x93\x84\x03a\x01\x00\n\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x80\x19\x90\x92\x16\x91\x16\x17\x90R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x95\x90\x95\x16\x92\x01\x91\x82RP`@\x80Q\x80\x83\x03\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf8\x01\x81R`\x18\x90\x92\x01\x90\x81\x90R\x81Q\x91\x95P\x93P\x83\x92\x85\x01\x91P\x80\x83\x83[` \x83\x10a\x14?W\x80Q\x82R\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\x90\x92\x01\x91` \x91\x82\x01\x91\x01a\x14\x02V[Q\x81Q` \x93\x84\x03a\x01\x00\n\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x80\x19\x90\x92\x16\x91\x16\x17\x90R`@Q\x91\x90\x93\x01\x94P\x91\x92PP\x80\x83\x03\x81\x85Z\xfa\x15\x80\x15a\x14\x9cW=`\x00\x80>=`\x00\xfd[PPP`@Q=` \x81\x10\x15a\x14\xb1W`\x00\x80\xfd[PQ\x92PPP\x90V[`@\x80Q`\b\x80\x82R\x81\x83\x01\x90\x92R``\x91` \x82\x01\x81\x806\x837\x01\x90PP\x90P`\xc0\x82\x90\x1b\x80`\a\x1a`\xf8\x1b\x82`\x00\x81Q\x81\x10a\x14\xf4W\xfe[` \x01\x01\x90~\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x19\x16\x90\x81`\x00\x1a\x90SP\x80`\x06\x1a`\xf8\x1b\x82`\x01\x81Q\x81\x10a\x157W\xfe[` \x01\x01\x90~\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x19\x16\x90\x81`\x00\x1a\x90SP\x80`\x05\x1a`\xf8\x1b\x82`\x02\x81Q\x81\x10a\x15zW\xfe[` \x01\x01\x90~\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x19\x16\x90\x81`\x00\x1a\x90SP\x80`\x04\x1a`\xf8\x1b\x82`\x03\x81Q\x81\x10a\x15\xbdW\xfe[` \x01\x01\x90~\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x19\x16\x90\x81`\x00\x1a\x90SP\x80`\x03\x1a`\xf8\x1b\x82`\x04\x81Q\x81\x10a\x16\x00W\xfe[` \x01\x01\x90~\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x19\x16\x90\x81`\x00\x1a\x90SP\x80`\x02\x1a`\xf8\x1b\x82`\x05\x81Q\x81\x10a\x16CW\xfe[` \x01\x01\x90~\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x19\x16\x90\x81`\x00\x1a\x90SP\x80`\x01\x1a`\xf8\x1b\x82`\x06\x81Q\x81\x10a\x16\x86W\xfe[` \x01\x01\x90~\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x19\x16\x90\x81`\x00\x1a\x90SP\x80`\x00\x1a`\xf8\x1b\x82`\a\x81Q\x81\x10a\x16\xc9W\xfe[` \x01\x01\x90~\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x19\x16\x90\x81`\x00\x1a\x90SPP\x91\x90PV[`\x00\x80\x85\x85\x11\x15a\x17\rW\x81\x82\xfd[\x83\x86\x11\x15a\x17\x19W\x81\x82\xfd[PP\x82\x01\x93\x91\x90\x92\x03\x91PV\xfeDepositContract: merkle tree fullDepositContract: reconstructed DepositData does not match supplied deposit_data_rootDepositContract: invalid withdrawal_credentials lengthDepositContract: deposit value not multiple of gweiDepositContract: invalid pubkey lengthDepositContract: deposit value too highDepositContract: deposit value too lowDepositContract: invalid signature length\xa2dipfsX\"\x12 \x1d\xd2o7\xa6!p0\t\xab\xf1nw\u6713\xdcP\u01dd\xb7\xf6\xcc7T>>\x0e=\xec\u0717dsolcC\x00\x06\v\x003\xf9\b<\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\"\xa0\xf5\xa5\xfdB\xd1j 0'\x98\xefn\xd3\t\x97\x9bC\x00=# \xd9\xf0\xe8\xea\x981\xa9'Y\xfbK\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00#\xa0\xdbV\x11N\x00\xfd\xd4\xc1\xf8\\\x89+\xf3Z\u0268\x92\x89\xaa\xec\xb1\xeb\u0429l\xde`jt\x8b]q\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\xa0\u01c0\t\xfd\xf0\x7f\xc5j\x11\xf1\"7\x06X\xa3S\xaa\xa5B\xedc\xe4LK\xc1_\xf4\xcd\x10Z\xb3<\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00%\xa0Sm\x98\x83\x7f-\xd1e\xa5]^\xea\xe9\x14\x85\x95Dr\xd5o$m\xf2V\xbf<\xae\x195*\x12<\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&\xa0\x9e\xfd\xe0R\xaa\x15B\x9f\xae\x05\xba\xd4\u0431\xd7\xc6M\xa6M\x03\u05e1\x85JX\x8c,\xb8C\f\r0\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\xa0\u060d\xdf\xee\xd4\x00\xa8uU\x96\xb2\x19B\xc1I~\x11L0.a\x18)\x0f\x91\xe6w)v\x04\x1f\xa1\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00(\xa0\x87\xeb\r\u06e5~5\xf6\u0486g8\x02\xa4\xafYu\xe2%\x06\xc7\xcfLd\xbbk\xe5\xee\x11R\x7f,\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00)\xa0&\x84dv\xfd_\xc5J]C8Qg\xc9QD\xf2d?S<\xc8[\xb9\xd1kx/\x8d}\xb1\x93\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00*\xa0Pm\x86X-%$\x05\xb8@\x01\x87\x92\xca\u04bf\x12Y\xf1\xefZ\xa5\xf8\x87\xe1<\xb2\xf0\tOQ\xe1\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00+\xa0\xff\xff\n\xd7\xe6Yw/\x954\xc1\x95\xc8\x15\xef\xc4\x01N\xf1\xe1\xda\xedD\x04\xc0c\x85\xd1\x11\x92\xe9+\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00,\xa0l\xf0A'\xdb\x05D\x1c\xd83\x10zR\xbe\x85(h\x89\x0eC\x17\xe6\xa0*\xb4v\x83\xaau\x96B \xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-\xa0\xb7\xd0_\x87_\x14\x00'\xefQ\x18\xa2${\xbb\x84\u038f/\x0f\x11#b0\x85\xda\xf7\x96\f2\x9f_\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00.\xa0\xdfj\xf5\xf5\xbb\xdbk\xe9\uf2a6\x18\u4fc0s\x96\bg\x17\x1e)go\x8b(M\xeaj\b\xa8^\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00/\xa0\xb5\x8d\x90\x0f^\x18.\x01t\u0285\x18.\xec\x9f:\t\xf6\xa6\xc0\xdfcw\xa5\x10\xd7\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x009\xa01 o\xa8\nP\xbbj\xbe)\bPX\xf1b\x12!*`\xee\xc8\xf0I\xfe\u02d2\xd8\xc8\xe0\xa8K\xc0\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00:\xa0!5+\xfe\xcb\xed\xdd\u94c3\x9faL=\xac\n>\xe3uC\xf9\xb4\x12\xb1a\x99\xdc\x15\x8e#\xb5D\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00;\xa0a\x9e1'$\xbbm|1S\xed\x9d\xe7\x91\xd7d\xa3f\xb3\x89\xaf\x13\u014b\xf8\xa8\xd9\x04\x81\xa4ge\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00<\xa0|\xdd)\x86&\x82Pb\x8d\f\x10\xe3\x85\u014ca\x91\xe6\xfb\xe0Q\x91\xbc\xc0O\x13?,\xear\xc1\xc4\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00=\xa0\x84\x890\xbd{\xa8\xca\xc5Fa\a!\x13\xfb'\x88i\xe0{\xb8X\x7f\x919)37M\x01{\xcb\xe1\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>\xa0\x88i\xff,\"\xb2\x8c\xc1\x05\x10\u06452\x92\x803(\xbeO\xb0\xe8\x04\x95\u8ecd'\x1f[\x88\x966\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00?\xa0\xb5\xfe(\xe7\x9f\x1b\x85\x0f\x86X$l\u9da1\u7d1f\xc0m\xb7\x14>\x8f\xe0\xb4\xf2\xb0\xc5R:\\\xf8B\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@\xa0\x98^\x92\x9fp\xaf(\u043d\u0469\n\x80\x8f\x97\x7fY||w\x8cH\x9e\x98\u04fd\x89\x10\xd3\x1a\xc0\xf7\xe1\x94F#\x96\u677f\xa4U\xf4\x05\xf4\u0742\xf3\x01J\xf8\x00;r\x8b\xa5o\xa5\xb9\x90\x19\xa5\xc8\x00\x00\x00\xe0\x94I\xdf<\xca&p\xeb\rY\x11F\xb1cY\xfe3nGo)\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe1\x94K\xc6V\xb3M\xe28\x96\xfa`i\u0246/5[t\x04\x01\xaf\x8b\bE\x95\x16\x14\x01HJ\x00\x00\x00\xe0\x94M\v\x04\xb4\x05\u01b6,|\xfc:\xe5GYt~,\vFb\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe1\x94MIl\xcc(\x05\x8b\x1dt\xb7\xa1\x95Af>!\x15O\x9c\x84\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe1\x94P\x9avg\xac\x8d\x03 \xe3ar\xc1\x92Pja\x88\xaa\x84\xf6\x8b|\x13\xbcK,\x13\xfa<]\xc1\xaa\x19;\xc6\x03=\xfd\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe1\x94jz\xa9\xb8\x82\xd5\v\xb7\xbc]\xa1\xa2Dq\x9c\x99\xf1/\x06\xa3\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe1\x94l\xc99|;8s\x9d\xac\xbf\xaah\xea\xd5\xf5\xd7{\xa5\xf4U\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe1\x94s\xb2\xe0\xe5E\x10#\x9e\"\u0313o\vJm\xe1\xac\xf0\xab\u078bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe0\x94v,\xa6,\xa2T\x9a\xd8\x06v;:\xa1\xea1|B\x9b\xdb\u068a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe1\x94w\x8f_\x13\u013ex\xa3\xa4\xd7\x14\x1b\xcb&\x99\x97\x02\xf4\a\u03cbR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe0\x94\x83M\xbfZ\x03\xe2\x9c%\xbcUE\x9c\u039c\x02\x1e\xeb\xe6v\xad\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\x87]%\xeeK\xc6\x04\xc7\x1b\xafb6\xa8H\x8f\"9\x9b\xedK\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\u150d\xf7\x87\x8d5q\xbe\xf5\xe5\xa7D\xf9b\x87\xc8\xd2\x03\x86\xd7Z\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe0\x94\x9eAZ\to\xf7vP\u0712]\xeaTe\x85\xb4\xad\xb3\"\xb6\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xa0vke\xa4\xf7\xb1\xday\xa1\xafy\xaciTV\uf886D\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xa2\x9b\x14JD\x9eAJG,`\u01ea\xf1\xaa\xff\xe3)\x02\x1d\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xa5S\x95Vk\vT9[2F\xf9j\v\xdcK\x8aH=\xf9\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xac\x9b\xa7/\xb6\x1a\xa7\xc3\x1a\x95\xdf\n\x8bn\xbaoA\xef\x87^\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xb0I\x8c\x15\x87\x9d\xb2\xeeTq\u0512l_\xaa%\u0260\x96\x83\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xb0J\xef*=-\x86\xb0\x10\x06\xcc\xd43\x9a.\x94=\x9cd\x80\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\u1531\x9f\xb4\xc1\xf2\x802~`\xed7\xb1\xdcn\xe7u3S\x93\x14\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe0\x94\xbb\x97{.\xe8\xa1\x11\u05c8\xb3G}$ x\u04387\xe7+\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xc2\x1c\xb9\u025c1m\x18c\x14/}\xd8m\xd5Im\x81\xa8\u058a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xc4s\xd4\x12\xdcR\xe3I\x86\"\t\x92L\x89\x81\xb2\xeeB\ah\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe1\x94\u010e#\xc5\xf6\xe1\xea\v\xae\xf6S\a4\xed\u00d6\x8fy\xaf.\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe1\x94\xc6\xe2E\x99\x91\xbf\xe2|\xcam\x86r/5\xda#\xa1\xe4\u02d7\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe0\x94\xc9\xca+\xa9\xa2}\xe1\xdbX\x9d\x8c3\xab\x8e\u07e2\x11\x1b1\xfb\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xd1\xf7~L\x1cE\x18n\x86S\u0109\xf9\x0e\x00\x8asYr\x96\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe2\x94\u04d9NM2\x02\xdd#\xc8I}\x7fu\xbf\x16G\xd1\xda\x1b\xb1\x8c\x01\x9d\x97\x1eO\xe8@\x1et\x00\x00\x00\xe0\x94\u0726\u9d0e\xa8j\xeb\xfd\xf9\x92\x99I\x12@B)kn4\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xe0\x99\x1e\x84@A\xbeo\x11\xb9\x9d\xa5\xb1\x14\xb6\xbc\xf8N\xbdW\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe1\x94\u08bdBX\xd2v\x887\xba\xa2j(\xfeq\xdc\a\x9f\x84\u01cbR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00\xe0\x94\xea(\xd0\x02\x04/\u0649\x8d\r\xb0\x16\xbe\x97X\xee\xaf\xe3\\\x1e\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe0\x94\xef\xa7EO\x11\x16\x80yu\xa4u\vFi^\x96xP\xde]\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\xe1\x94\xfb\xfdo\xa9\xf7:\u01a0X\xe0\x12Y\x03L(\x00\x1b\xef\x82G\x8bR\xb7\xd2\xdc\xc8\f\xd2\xe4\x00\x00\x00" diff --git a/core/genesis_test.go b/core/genesis_test.go index e069eb448c90..f9539934ab89 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -30,18 +30,24 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" ) func TestInvalidCliqueConfig(t *testing.T) { block := DefaultGoerliGenesisBlock() block.ExtraData = []byte{} db := rawdb.NewMemoryDatabase() - if _, err := block.Commit(db, trie.NewDatabase(db)); err == nil { + if _, err := block.Commit(db, trie.NewDatabase(db, nil)); err == nil { t.Fatal("Expected error on invalid clique config") } } func TestSetupGenesis(t *testing.T) { + testSetupGenesis(t, rawdb.HashScheme) + testSetupGenesis(t, rawdb.PathScheme) +} + +func testSetupGenesis(t *testing.T, scheme string) { var ( customghash = common.HexToHash("0x89c99d90b79719238d2645c7642f2c9295246e80775b38cfd162b696817fbd50") customg = Genesis{ @@ -53,6 +59,7 @@ func TestSetupGenesis(t *testing.T) { oldcustomg = customg ) oldcustomg.Config = ¶ms.ChainConfig{HomesteadBlock: big.NewInt(2)} + tests := []struct { name string fn func(ethdb.Database) (*params.ChainConfig, common.Hash, error) @@ -63,7 +70,7 @@ func TestSetupGenesis(t *testing.T) { { name: "genesis without ChainConfig", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - return SetupGenesisBlock(db, trie.NewDatabase(db), new(Genesis)) + return SetupGenesisBlock(db, trie.NewDatabase(db, newDbConfig(scheme)), new(Genesis)) }, wantErr: errGenesisNoConfig, wantConfig: params.AllEthashProtocolChanges, @@ -71,7 +78,7 @@ func TestSetupGenesis(t *testing.T) { { name: "no block in DB, genesis == nil", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - return SetupGenesisBlock(db, trie.NewDatabase(db), nil) + return SetupGenesisBlock(db, trie.NewDatabase(db, newDbConfig(scheme)), nil) }, wantHash: params.MainnetGenesisHash, wantConfig: params.MainnetChainConfig, @@ -79,8 +86,8 @@ func TestSetupGenesis(t *testing.T) { { name: "mainnet block in DB, genesis == nil", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - DefaultGenesisBlock().MustCommit(db) - return SetupGenesisBlock(db, trie.NewDatabase(db), nil) + DefaultGenesisBlock().MustCommit(db, trie.NewDatabase(db, newDbConfig(scheme))) + return SetupGenesisBlock(db, trie.NewDatabase(db, newDbConfig(scheme)), nil) }, wantHash: params.MainnetGenesisHash, wantConfig: params.MainnetChainConfig, @@ -88,8 +95,9 @@ func TestSetupGenesis(t *testing.T) { { name: "custom block in DB, genesis == nil", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - customg.MustCommit(db) - return SetupGenesisBlock(db, trie.NewDatabase(db), nil) + tdb := trie.NewDatabase(db, newDbConfig(scheme)) + customg.Commit(db, tdb) + return SetupGenesisBlock(db, tdb, nil) }, wantHash: customghash, wantConfig: customg.Config, @@ -97,8 +105,9 @@ func TestSetupGenesis(t *testing.T) { { name: "custom block in DB, genesis == goerli", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - customg.MustCommit(db) - return SetupGenesisBlock(db, trie.NewDatabase(db), DefaultGoerliGenesisBlock()) + tdb := trie.NewDatabase(db, newDbConfig(scheme)) + customg.Commit(db, tdb) + return SetupGenesisBlock(db, tdb, DefaultGoerliGenesisBlock()) }, wantErr: &GenesisMismatchError{Stored: customghash, New: params.GoerliGenesisHash}, wantHash: params.GoerliGenesisHash, @@ -107,8 +116,9 @@ func TestSetupGenesis(t *testing.T) { { name: "compatible config in DB", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - oldcustomg.MustCommit(db) - return SetupGenesisBlock(db, trie.NewDatabase(db), &customg) + tdb := trie.NewDatabase(db, newDbConfig(scheme)) + oldcustomg.Commit(db, tdb) + return SetupGenesisBlock(db, tdb, &customg) }, wantHash: customghash, wantConfig: customg.Config, @@ -118,16 +128,17 @@ func TestSetupGenesis(t *testing.T) { fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { // Commit the 'old' genesis block with Homestead transition at #2. // Advance to block #4, past the homestead transition block of customg. - genesis := oldcustomg.MustCommit(db) + tdb := trie.NewDatabase(db, newDbConfig(scheme)) + oldcustomg.Commit(db, tdb) - bc, _ := NewBlockChain(db, nil, nil, &oldcustomg, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil) + bc, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), nil, &oldcustomg, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil) defer bc.Stop() - blocks, _ := GenerateChain(oldcustomg.Config, genesis, ethash.NewFaker(), db, 4, nil) + _, blocks, _ := GenerateChainWithGenesis(&oldcustomg, ethash.NewFaker(), 4, nil) bc.InsertChain(blocks) // This should return a compatibility error. - return SetupGenesisBlock(db, trie.NewDatabase(db), &customg) + return SetupGenesisBlock(db, tdb, &customg) }, wantHash: customghash, wantConfig: customg.Config, @@ -172,11 +183,11 @@ func TestGenesisHashes(t *testing.T) { }{ {DefaultGenesisBlock(), params.MainnetGenesisHash}, {DefaultGoerliGenesisBlock(), params.GoerliGenesisHash}, - {DefaultRinkebyGenesisBlock(), params.RinkebyGenesisHash}, {DefaultSepoliaGenesisBlock(), params.SepoliaGenesisHash}, } { // Test via MustCommit - if have := c.genesis.MustCommit(rawdb.NewMemoryDatabase()).Hash(); have != c.want { + db := rawdb.NewMemoryDatabase() + if have := c.genesis.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults)).Hash(); have != c.want { t.Errorf("case: %d a), want: %s, got: %s", i, c.want.Hex(), have.Hex()) } // Test via ToBlock @@ -194,7 +205,7 @@ func TestGenesis_Commit(t *testing.T) { } db := rawdb.NewMemoryDatabase() - genesisBlock := genesis.MustCommit(db) + genesisBlock := genesis.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults)) if genesis.Difficulty != nil { t.Fatalf("assumption wrong") @@ -220,7 +231,7 @@ func TestReadWriteGenesisAlloc(t *testing.T) { {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, {2}: {Balance: big.NewInt(2), Storage: map[common.Hash]common.Hash{{2}: {2}}}, } - hash, _ = alloc.deriveHash() + hash, _ = alloc.hash() ) blob, _ := json.Marshal(alloc) rawdb.WriteGenesisStateSpec(db, hash, blob) @@ -243,3 +254,10 @@ func TestReadWriteGenesisAlloc(t *testing.T) { } } } + +func newDbConfig(scheme string) *trie.Config { + if scheme == rawdb.HashScheme { + return trie.HashDefaults + } + return &trie.Config{PathDB: pathdb.Defaults} +} diff --git a/core/headerchain_test.go b/core/headerchain_test.go index 08d19f695072..2c0323e6f74d 100644 --- a/core/headerchain_test.go +++ b/core/headerchain_test.go @@ -73,7 +73,7 @@ func TestHeaderInsertion(t *testing.T) { db = rawdb.NewMemoryDatabase() gspec = &Genesis{BaseFee: big.NewInt(params.InitialBaseFee), Config: params.AllEthashProtocolChanges} ) - gspec.Commit(db, trie.NewDatabase(db)) + gspec.Commit(db, trie.NewDatabase(db, nil)) hc, err := NewHeaderChain(db, gspec.Config, ethash.NewFaker(), func() bool { return false }) if err != nil { t.Fatal(err) diff --git a/core/mkalloc.go b/core/mkalloc.go index e4c2ec0d83e9..12c40c14fbea 100644 --- a/core/mkalloc.go +++ b/core/mkalloc.go @@ -30,32 +30,55 @@ import ( "fmt" "math/big" "os" - "sort" "strconv" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/exp/slices" ) -type allocItem struct{ Addr, Balance *big.Int } +type allocItem struct { + Addr *big.Int + Balance *big.Int + Misc *allocItemMisc `rlp:"optional"` +} -type allocList []allocItem +type allocItemMisc struct { + Nonce uint64 + Code []byte + Slots []allocItemStorageItem +} -func (a allocList) Len() int { return len(a) } -func (a allocList) Less(i, j int) bool { return a[i].Addr.Cmp(a[j].Addr) < 0 } -func (a allocList) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +type allocItemStorageItem struct { + Key common.Hash + Val common.Hash +} -func makelist(g *core.Genesis) allocList { - a := make(allocList, 0, len(g.Alloc)) +func makelist(g *core.Genesis) []allocItem { + items := make([]allocItem, 0, len(g.Alloc)) for addr, account := range g.Alloc { + var misc *allocItemMisc if len(account.Storage) > 0 || len(account.Code) > 0 || account.Nonce != 0 { - panic(fmt.Sprintf("can't encode account %x", addr)) + misc = &allocItemMisc{ + Nonce: account.Nonce, + Code: account.Code, + Slots: make([]allocItemStorageItem, 0, len(account.Storage)), + } + for key, val := range account.Storage { + misc.Slots = append(misc.Slots, allocItemStorageItem{key, val}) + } + slices.SortFunc(misc.Slots, func(a, b allocItemStorageItem) int { + return a.Key.Cmp(b.Key) + }) } bigAddr := new(big.Int).SetBytes(addr.Bytes()) - a = append(a, allocItem{bigAddr, account.Balance}) + items = append(items, allocItem{bigAddr, account.Balance, misc}) } - sort.Sort(a) - return a + slices.SortFunc(items, func(a, b allocItem) int { + return a.Addr.Cmp(b.Addr) + }) + return items } func makealloc(g *core.Genesis) string { diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 545f3859782a..b702fe31575d 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -22,15 +22,16 @@ import ( "errors" "fmt" "math/big" - "sort" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/exp/slices" ) // ReadCanonicalHash retrieves the hash assigned to a canonical block number. @@ -380,7 +381,7 @@ func ReadHeader(db ethdb.Reader, hash common.Hash, number uint64) *types.Header return nil } header := new(types.Header) - if err := rlp.Decode(bytes.NewReader(data), header); err != nil { + if err := rlp.DecodeBytes(data, header); err != nil { log.Error("Invalid block header RLP", "hash", hash, "err", err) return nil } @@ -497,7 +498,7 @@ func ReadBody(db ethdb.Reader, hash common.Hash, number uint64) *types.Body { return nil } body := new(types.Body) - if err := rlp.Decode(bytes.NewReader(data), body); err != nil { + if err := rlp.DecodeBytes(data, body); err != nil { log.Error("Invalid block body RLP", "hash", hash, "err", err) return nil } @@ -543,7 +544,7 @@ func ReadTd(db ethdb.Reader, hash common.Hash, number uint64) *big.Int { return nil } td := new(big.Int) - if err := rlp.Decode(bytes.NewReader(data), td); err != nil { + if err := rlp.DecodeBytes(data, td); err != nil { log.Error("Invalid block total difficulty RLP", "hash", hash, "err", err) return nil } @@ -637,13 +638,19 @@ func ReadReceipts(db ethdb.Reader, hash common.Hash, number uint64, time uint64, return nil } header := ReadHeader(db, hash, number) + var baseFee *big.Int if header == nil { baseFee = big.NewInt(0) } else { baseFee = header.BaseFee } - if err := receipts.DeriveFields(config, hash, number, time, baseFee, body.Transactions); err != nil { + // Compute effective blob gas price. + var blobGasPrice *big.Int + if header != nil && header.ExcessBlobGas != nil { + blobGasPrice = eip4844.CalcBlobFee(*header.ExcessBlobGas) + } + if err := receipts.DeriveFields(config, hash, number, time, baseFee, blobGasPrice, body.Transactions); err != nil { log.Error("Failed to derive block receipts fields", "hash", hash, "number", number, "err", err) return nil } @@ -726,7 +733,7 @@ func deriveLogFields(receipts []*receiptLogs, hash common.Hash, number uint64, t // ReadLogs retrieves the logs for all transactions in a block. In case // receipts is not found, a nil is returned. // Note: ReadLogs does not derive unstored log fields. -func ReadLogs(db ethdb.Reader, hash common.Hash, number uint64, config *params.ChainConfig) [][]*types.Log { +func ReadLogs(db ethdb.Reader, hash common.Hash, number uint64) [][]*types.Log { // Retrieve the flattened receipt slice data := ReadReceiptsRLP(db, hash, number) if len(data) == 0 { @@ -734,7 +741,7 @@ func ReadLogs(db ethdb.Reader, hash common.Hash, number uint64, config *params.C } receipts := []*receiptLogs{} if err := rlp.DecodeBytes(data, &receipts); err != nil { - if logs := readLegacyLogs(db, hash, number, config); logs != nil { + if logs := readLegacyLogs(db, hash, number); logs != nil { return logs } @@ -753,16 +760,25 @@ func ReadLogs(db ethdb.Reader, hash common.Hash, number uint64, config *params.C // from a block which has its receipt stored in the legacy format. It'll // be removed after users have migrated their freezer databases. // Arbitrum: we are keeping this to handle classic (legacy) receipts -func readLegacyLogs(db ethdb.Reader, hash common.Hash, number uint64, config *params.ChainConfig) [][]*types.Log { +func readLegacyLogs(db ethdb.Reader, hash common.Hash, number uint64) [][]*types.Log { // The time can be zero since Arbitrum legacy receipts on Arbitrum One are pre-Cancun, // and the time only affects which fork's signer is picked. - receipts := ReadReceipts(db, hash, number, 0, config) + receipts := ReadRawReceipts(db, hash, number) if receipts == nil { return nil } logs := make([][]*types.Log, len(receipts)) + logIndex := uint(0) for i, receipt := range receipts { logs[i] = receipt.Logs + for j := range logs[i] { + logs[i][j].BlockNumber = number + logs[i][j].BlockHash = hash + logs[i][j].TxHash = receipt.TxHash + logs[i][j].TxIndex = uint(i) + logs[i][j].Index = logIndex + logIndex++ + } } return logs } @@ -860,23 +876,13 @@ type badBlock struct { Body *types.Body } -// badBlockList implements the sort interface to allow sorting a list of -// bad blocks by their number in the reverse order. -type badBlockList []*badBlock - -func (s badBlockList) Len() int { return len(s) } -func (s badBlockList) Less(i, j int) bool { - return s[i].Header.Number.Uint64() < s[j].Header.Number.Uint64() -} -func (s badBlockList) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - // ReadBadBlock retrieves the bad block with the corresponding block hash. func ReadBadBlock(db ethdb.Reader, hash common.Hash) *types.Block { blob, err := db.Get(badBlockKey) if err != nil { return nil } - var badBlocks badBlockList + var badBlocks []*badBlock if err := rlp.DecodeBytes(blob, &badBlocks); err != nil { return nil } @@ -895,7 +901,7 @@ func ReadAllBadBlocks(db ethdb.Reader) []*types.Block { if err != nil { return nil } - var badBlocks badBlockList + var badBlocks []*badBlock if err := rlp.DecodeBytes(blob, &badBlocks); err != nil { return nil } @@ -913,7 +919,7 @@ func WriteBadBlock(db ethdb.KeyValueStore, block *types.Block) { if err != nil { log.Warn("Failed to load old bad blocks", "error", err) } - var badBlocks badBlockList + var badBlocks []*badBlock if len(blob) > 0 { if err := rlp.DecodeBytes(blob, &badBlocks); err != nil { log.Crit("Failed to decode old bad blocks", "error", err) @@ -929,7 +935,10 @@ func WriteBadBlock(db ethdb.KeyValueStore, block *types.Block) { Header: block.Header(), Body: block.Body(), }) - sort.Sort(sort.Reverse(badBlocks)) + slices.SortFunc(badBlocks, func(a, b *badBlock) int { + // Note: sorting in descending number order. + return -a.Header.Number.Cmp(b.Header.Number) + }) if len(badBlocks) > badBlockToKeep { badBlocks = badBlocks[:badBlockToKeep] } diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 32e38a81ce4d..a7ceb72998a1 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -85,7 +85,7 @@ func TestBodyStorage(t *testing.T) { WriteBody(db, hash, 0, body) if entry := ReadBody(db, hash, 0); entry == nil { t.Fatalf("Stored body not found") - } else if types.DeriveSha(types.Transactions(entry.Transactions), newHasher()) != types.DeriveSha(types.Transactions(body.Transactions), newHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) { + } else if types.DeriveSha(types.Transactions(entry.Transactions), newTestHasher()) != types.DeriveSha(types.Transactions(body.Transactions), newTestHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(body.Uncles) { t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, body) } if entry := ReadBodyRLP(db, hash, 0); entry == nil { @@ -139,7 +139,7 @@ func TestBlockStorage(t *testing.T) { } if entry := ReadBody(db, block.Hash(), block.NumberU64()); entry == nil { t.Fatalf("Stored body not found") - } else if types.DeriveSha(types.Transactions(entry.Transactions), newHasher()) != types.DeriveSha(block.Transactions(), newHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) { + } else if types.DeriveSha(types.Transactions(entry.Transactions), newTestHasher()) != types.DeriveSha(block.Transactions(), newTestHasher()) || types.CalcUncleHash(entry.Uncles) != types.CalcUncleHash(block.Uncles()) { t.Fatalf("Retrieved body mismatch: have %v, want %v", entry, block.Body()) } // Delete the block and verify the execution @@ -435,12 +435,12 @@ func checkReceiptsRLP(have, want types.Receipts) error { func TestAncientStorage(t *testing.T) { // Freezer style fast import the chain. frdir := t.TempDir() - db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false) if err != nil { t.Fatalf("failed to create database with ancient backend") } defer db.Close() + // Create a test block block := types.NewBlockWithHeader(&types.Header{ Number: big.NewInt(0), @@ -736,7 +736,7 @@ func TestReadLogs(t *testing.T) { // Insert the receipt slice into the database and check presence WriteReceipts(db, hash, 0, receipts) - logs := ReadLogs(db, hash, 0, params.TestChainConfig) + logs := ReadLogs(db, hash, 0) if len(logs) == 0 { t.Fatalf("no logs returned") } diff --git a/core/rawdb/accessors_indexes_test.go b/core/rawdb/accessors_indexes_test.go index c5447b16d2bd..124389ba7a13 100644 --- a/core/rawdb/accessors_indexes_test.go +++ b/core/rawdb/accessors_indexes_test.go @@ -18,42 +18,18 @@ package rawdb import ( "bytes" - "hash" "math/big" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/internal/blocktest" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" ) -// testHasher is the helper tool for transaction/receipt list hashing. -// The original hasher is trie, in order to get rid of import cycle, -// use the testing hasher instead. -type testHasher struct { - hasher hash.Hash -} - -func newHasher() *testHasher { - return &testHasher{hasher: sha3.NewLegacyKeccak256()} -} - -func (h *testHasher) Reset() { - h.hasher.Reset() -} - -func (h *testHasher) Update(key, val []byte) error { - h.hasher.Write(key) - h.hasher.Write(val) - return nil -} - -func (h *testHasher) Hash() common.Hash { - return common.BytesToHash(h.hasher.Sum(nil)) -} +var newTestHasher = blocktest.NewHasher // Tests that positional lookup metadata can be stored and retrieved. func TestLookupStorage(t *testing.T) { @@ -100,7 +76,7 @@ func TestLookupStorage(t *testing.T) { tx3 := types.NewTransaction(3, common.BytesToAddress([]byte{0x33}), big.NewInt(333), 3333, big.NewInt(33333), []byte{0x33, 0x33, 0x33}) txs := []*types.Transaction{tx1, tx2, tx3} - block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil, newHasher()) + block := types.NewBlock(&types.Header{Number: big.NewInt(314)}, txs, nil, nil, newTestHasher()) // Check that no transactions entries are in a pristine database for i, tx := range txs { @@ -142,7 +118,7 @@ func TestDeleteBloomBits(t *testing.T) { for i := uint(0); i < 2; i++ { for s := uint64(0); s < 2; s++ { WriteBloomBits(db, i, s, params.MainnetGenesisHash, []byte{0x01, 0x02}) - WriteBloomBits(db, i, s, params.RinkebyGenesisHash, []byte{0x01, 0x02}) + WriteBloomBits(db, i, s, params.SepoliaGenesisHash, []byte{0x01, 0x02}) } } check := func(bit uint, section uint64, head common.Hash, exist bool) { @@ -156,25 +132,25 @@ func TestDeleteBloomBits(t *testing.T) { } // Check the existence of written data. check(0, 0, params.MainnetGenesisHash, true) - check(0, 0, params.RinkebyGenesisHash, true) + check(0, 0, params.SepoliaGenesisHash, true) // Check the existence of deleted data. DeleteBloombits(db, 0, 0, 1) check(0, 0, params.MainnetGenesisHash, false) - check(0, 0, params.RinkebyGenesisHash, false) + check(0, 0, params.SepoliaGenesisHash, false) check(0, 1, params.MainnetGenesisHash, true) - check(0, 1, params.RinkebyGenesisHash, true) + check(0, 1, params.SepoliaGenesisHash, true) // Check the existence of deleted data. DeleteBloombits(db, 0, 0, 2) check(0, 0, params.MainnetGenesisHash, false) - check(0, 0, params.RinkebyGenesisHash, false) + check(0, 0, params.SepoliaGenesisHash, false) check(0, 1, params.MainnetGenesisHash, false) - check(0, 1, params.RinkebyGenesisHash, false) + check(0, 1, params.SepoliaGenesisHash, false) // Bit1 shouldn't be affect. check(1, 0, params.MainnetGenesisHash, true) - check(1, 0, params.RinkebyGenesisHash, true) + check(1, 0, params.SepoliaGenesisHash, true) check(1, 1, params.MainnetGenesisHash, true) - check(1, 1, params.RinkebyGenesisHash, true) + check(1, 1, params.SepoliaGenesisHash, true) } diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index 2ff29d1add93..859566f722f5 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -111,10 +111,10 @@ const crashesToKeep = 10 func PushUncleanShutdownMarker(db ethdb.KeyValueStore) ([]uint64, uint64, error) { var uncleanShutdowns crashList // Read old data - if data, err := db.Get(uncleanShutdownKey); err != nil { - log.Warn("Error reading unclean shutdown markers", "error", err) - } else if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil { - return nil, 0, err + if data, err := db.Get(uncleanShutdownKey); err == nil { + if err := rlp.DecodeBytes(data, &uncleanShutdowns); err != nil { + return nil, 0, err + } } var discarded = uncleanShutdowns.Discarded var previous = make([]uint64, len(uncleanShutdowns.Recent)) diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 39900df23e94..9ce58e7d27b9 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -17,6 +17,8 @@ package rawdb import ( + "encoding/binary" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -92,3 +94,173 @@ func DeleteCode(db ethdb.KeyValueWriter, hash common.Hash) { log.Crit("Failed to delete contract code", "err", err) } } + +// ReadStateID retrieves the state id with the provided state root. +func ReadStateID(db ethdb.KeyValueReader, root common.Hash) *uint64 { + data, err := db.Get(stateIDKey(root)) + if err != nil || len(data) == 0 { + return nil + } + number := binary.BigEndian.Uint64(data) + return &number +} + +// WriteStateID writes the provided state lookup to database. +func WriteStateID(db ethdb.KeyValueWriter, root common.Hash, id uint64) { + var buff [8]byte + binary.BigEndian.PutUint64(buff[:], id) + if err := db.Put(stateIDKey(root), buff[:]); err != nil { + log.Crit("Failed to store state ID", "err", err) + } +} + +// DeleteStateID deletes the specified state lookup from the database. +func DeleteStateID(db ethdb.KeyValueWriter, root common.Hash) { + if err := db.Delete(stateIDKey(root)); err != nil { + log.Crit("Failed to delete state ID", "err", err) + } +} + +// ReadPersistentStateID retrieves the id of the persistent state from the database. +func ReadPersistentStateID(db ethdb.KeyValueReader) uint64 { + data, _ := db.Get(persistentStateIDKey) + if len(data) != 8 { + return 0 + } + return binary.BigEndian.Uint64(data) +} + +// WritePersistentStateID stores the id of the persistent state into database. +func WritePersistentStateID(db ethdb.KeyValueWriter, number uint64) { + if err := db.Put(persistentStateIDKey, encodeBlockNumber(number)); err != nil { + log.Crit("Failed to store the persistent state ID", "err", err) + } +} + +// ReadTrieJournal retrieves the serialized in-memory trie nodes of layers saved at +// the last shutdown. +func ReadTrieJournal(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(trieJournalKey) + return data +} + +// WriteTrieJournal stores the serialized in-memory trie nodes of layers to save at +// shutdown. +func WriteTrieJournal(db ethdb.KeyValueWriter, journal []byte) { + if err := db.Put(trieJournalKey, journal); err != nil { + log.Crit("Failed to store tries journal", "err", err) + } +} + +// DeleteTrieJournal deletes the serialized in-memory trie nodes of layers saved at +// the last shutdown. +func DeleteTrieJournal(db ethdb.KeyValueWriter) { + if err := db.Delete(trieJournalKey); err != nil { + log.Crit("Failed to remove tries journal", "err", err) + } +} + +// ReadStateHistoryMeta retrieves the metadata corresponding to the specified +// state history. Compute the position of state history in freezer by minus +// one since the id of first state history starts from one(zero for initial +// state). +func ReadStateHistoryMeta(db ethdb.AncientReaderOp, id uint64) []byte { + blob, err := db.Ancient(stateHistoryMeta, id-1) + if err != nil { + return nil + } + return blob +} + +// ReadStateHistoryMetaList retrieves a batch of meta objects with the specified +// start position and count. Compute the position of state history in freezer by +// minus one since the id of first state history starts from one(zero for initial +// state). +func ReadStateHistoryMetaList(db ethdb.AncientReaderOp, start uint64, count uint64) ([][]byte, error) { + return db.AncientRange(stateHistoryMeta, start-1, count, 0) +} + +// ReadStateAccountIndex retrieves the state root corresponding to the specified +// state history. Compute the position of state history in freezer by minus one +// since the id of first state history starts from one(zero for initial state). +func ReadStateAccountIndex(db ethdb.AncientReaderOp, id uint64) []byte { + blob, err := db.Ancient(stateHistoryAccountIndex, id-1) + if err != nil { + return nil + } + return blob +} + +// ReadStateStorageIndex retrieves the state root corresponding to the specified +// state history. Compute the position of state history in freezer by minus one +// since the id of first state history starts from one(zero for initial state). +func ReadStateStorageIndex(db ethdb.AncientReaderOp, id uint64) []byte { + blob, err := db.Ancient(stateHistoryStorageIndex, id-1) + if err != nil { + return nil + } + return blob +} + +// ReadStateAccountHistory retrieves the state root corresponding to the specified +// state history. Compute the position of state history in freezer by minus one +// since the id of first state history starts from one(zero for initial state). +func ReadStateAccountHistory(db ethdb.AncientReaderOp, id uint64) []byte { + blob, err := db.Ancient(stateHistoryAccountData, id-1) + if err != nil { + return nil + } + return blob +} + +// ReadStateStorageHistory retrieves the state root corresponding to the specified +// state history. Compute the position of state history in freezer by minus one +// since the id of first state history starts from one(zero for initial state). +func ReadStateStorageHistory(db ethdb.AncientReaderOp, id uint64) []byte { + blob, err := db.Ancient(stateHistoryStorageData, id-1) + if err != nil { + return nil + } + return blob +} + +// ReadStateHistory retrieves the state history from database with provided id. +// Compute the position of state history in freezer by minus one since the id +// of first state history starts from one(zero for initial state). +func ReadStateHistory(db ethdb.AncientReaderOp, id uint64) ([]byte, []byte, []byte, []byte, []byte, error) { + meta, err := db.Ancient(stateHistoryMeta, id-1) + if err != nil { + return nil, nil, nil, nil, nil, err + } + accountIndex, err := db.Ancient(stateHistoryAccountIndex, id-1) + if err != nil { + return nil, nil, nil, nil, nil, err + } + storageIndex, err := db.Ancient(stateHistoryStorageIndex, id-1) + if err != nil { + return nil, nil, nil, nil, nil, err + } + accountData, err := db.Ancient(stateHistoryAccountData, id-1) + if err != nil { + return nil, nil, nil, nil, nil, err + } + storageData, err := db.Ancient(stateHistoryStorageData, id-1) + if err != nil { + return nil, nil, nil, nil, nil, err + } + return meta, accountIndex, storageIndex, accountData, storageData, nil +} + +// WriteStateHistory writes the provided state history to database. Compute the +// position of state history in freezer by minus one since the id of first state +// history starts from one(zero for initial state). +func WriteStateHistory(db ethdb.AncientWriter, id uint64, meta []byte, accountIndex []byte, storageIndex []byte, accounts []byte, storages []byte) { + db.ModifyAncients(func(op ethdb.AncientWriteOp) error { + op.AppendRaw(stateHistoryMeta, id-1, meta) + op.AppendRaw(stateHistoryAccountIndex, id-1, accountIndex) + op.AppendRaw(stateHistoryStorageIndex, id-1, storageIndex) + op.AppendRaw(stateHistoryAccountData, id-1, accounts) + op.AppendRaw(stateHistoryStorageData, id-1, storages) + return nil + }) +} diff --git a/core/rawdb/accessors_sync.go b/core/rawdb/accessors_sync.go index e87ad43c3600..2dc08b3b7285 100644 --- a/core/rawdb/accessors_sync.go +++ b/core/rawdb/accessors_sync.go @@ -17,8 +17,6 @@ package rawdb import ( - "bytes" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -53,7 +51,7 @@ func ReadSkeletonHeader(db ethdb.KeyValueReader, number uint64) *types.Header { return nil } header := new(types.Header) - if err := rlp.Decode(bytes.NewReader(data), header); err != nil { + if err := rlp.DecodeBytes(data, header); err != nil { log.Error("Invalid skeleton header RLP", "number", number, "err", err) return nil } @@ -78,3 +76,25 @@ func DeleteSkeletonHeader(db ethdb.KeyValueWriter, number uint64) { log.Crit("Failed to delete skeleton header", "err", err) } } + +const ( + StateSyncUnknown = uint8(0) // flags the state snap sync is unknown + StateSyncRunning = uint8(1) // flags the state snap sync is not completed yet + StateSyncFinished = uint8(2) // flags the state snap sync is completed +) + +// ReadSnapSyncStatusFlag retrieves the state snap sync status flag. +func ReadSnapSyncStatusFlag(db ethdb.KeyValueReader) uint8 { + blob, err := db.Get(snapSyncStatusFlagKey) + if err != nil || len(blob) != 1 { + return StateSyncUnknown + } + return blob[0] +} + +// WriteSnapSyncStatusFlag stores the state snap sync status flag into database. +func WriteSnapSyncStatusFlag(db ethdb.KeyValueWriter, flag uint8) { + if err := db.Put(snapSyncStatusFlagKey, []byte{flag}); err != nil { + log.Crit("Failed to store sync status flag", "err", err) + } +} diff --git a/core/rawdb/accessors_trie.go b/core/rawdb/accessors_trie.go index e24021302584..78f1a70b1c04 100644 --- a/core/rawdb/accessors_trie.go +++ b/core/rawdb/accessors_trie.go @@ -36,7 +36,7 @@ import ( // // Now this scheme is still kept for backward compatibility, and it will be used // for archive node and some other tries(e.g. light trie). -const HashScheme = "hashScheme" +const HashScheme = "hash" // PathScheme is the new path-based state scheme with which trie nodes are stored // in the disk with node path as the database key. This scheme will only store one @@ -44,23 +44,25 @@ const HashScheme = "hashScheme" // is native. At the same time, this scheme will put adjacent trie nodes in the same // area of the disk with good data locality property. But this scheme needs to rely // on extra state diffs to survive deep reorg. -const PathScheme = "pathScheme" +const PathScheme = "path" -// nodeHasher used to derive the hash of trie node. -type nodeHasher struct{ sha crypto.KeccakState } +// hasher is used to compute the sha256 hash of the provided data. +type hasher struct{ sha crypto.KeccakState } var hasherPool = sync.Pool{ - New: func() interface{} { return &nodeHasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} }, + New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} }, } -func newNodeHasher() *nodeHasher { return hasherPool.Get().(*nodeHasher) } -func returnHasherToPool(h *nodeHasher) { hasherPool.Put(h) } +func newHasher() *hasher { + return hasherPool.Get().(*hasher) +} + +func (h *hasher) hash(data []byte) common.Hash { + return crypto.HashData(h.sha, data) +} -func (h *nodeHasher) hashData(data []byte) (n common.Hash) { - h.sha.Reset() - h.sha.Write(data) - h.sha.Read(n[:]) - return n +func (h *hasher) release() { + hasherPool.Put(h) } // ReadAccountTrieNode retrieves the account trie node and the associated node @@ -70,9 +72,9 @@ func ReadAccountTrieNode(db ethdb.KeyValueReader, path []byte) ([]byte, common.H if err != nil { return nil, common.Hash{} } - hasher := newNodeHasher() - defer returnHasherToPool(hasher) - return data, hasher.hashData(data) + h := newHasher() + defer h.release() + return data, h.hash(data) } // HasAccountTrieNode checks the account trie node presence with the specified @@ -82,9 +84,19 @@ func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte, hash common.Hash) if err != nil { return false } - hasher := newNodeHasher() - defer returnHasherToPool(hasher) - return hasher.hashData(data) == hash + h := newHasher() + defer h.release() + return h.hash(data) == hash +} + +// ExistsAccountTrieNode checks the presence of the account trie node with the +// specified node path, regardless of the node hash. +func ExistsAccountTrieNode(db ethdb.KeyValueReader, path []byte) bool { + has, err := db.Has(accountTrieNodeKey(path)) + if err != nil { + return false + } + return has } // WriteAccountTrieNode writes the provided account trie node into database. @@ -108,9 +120,9 @@ func ReadStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path if err != nil { return nil, common.Hash{} } - hasher := newNodeHasher() - defer returnHasherToPool(hasher) - return data, hasher.hashData(data) + h := newHasher() + defer h.release() + return data, h.hash(data) } // HasStorageTrieNode checks the storage trie node presence with the provided @@ -120,9 +132,19 @@ func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path [ if err != nil { return false } - hasher := newNodeHasher() - defer returnHasherToPool(hasher) - return hasher.hashData(data) == hash + h := newHasher() + defer h.release() + return h.hash(data) == hash +} + +// ExistsStorageTrieNode checks the presence of the storage trie node with the +// specified account hash and node path, regardless of the node hash. +func ExistsStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path []byte) bool { + has, err := db.Has(storageTrieNodeKey(accountHash, path)) + if err != nil { + return false + } + return has } // WriteStorageTrieNode writes the provided storage trie node into database. @@ -261,3 +283,60 @@ func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, has panic(fmt.Sprintf("Unknown scheme %v", scheme)) } } + +// ReadStateScheme reads the state scheme of persistent state, or none +// if the state is not present in database. +func ReadStateScheme(db ethdb.Reader) string { + // Check if state in path-based scheme is present + blob, _ := ReadAccountTrieNode(db, nil) + if len(blob) != 0 { + return PathScheme + } + // In a hash-based scheme, the genesis state is consistently stored + // on the disk. To assess the scheme of the persistent state, it + // suffices to inspect the scheme of the genesis state. + header := ReadHeader(db, ReadCanonicalHash(db, 0), 0) + if header == nil { + return "" // empty datadir + } + blob = ReadLegacyTrieNode(db, header.Root) + if len(blob) == 0 { + return "" // no state in disk + } + return HashScheme +} + +// ParseStateScheme checks if the specified state scheme is compatible with +// the stored state. +// +// - If the provided scheme is none, use the scheme consistent with persistent +// state, or fallback to hash-based scheme if state is empty. +// +// - If the provided scheme is hash, use hash-based scheme or error out if not +// compatible with persistent state scheme. +// +// - If the provided scheme is path: use path-based scheme or error out if not +// compatible with persistent state scheme. +func ParseStateScheme(provided string, disk ethdb.Database) (string, error) { + // If state scheme is not specified, use the scheme consistent + // with persistent state, or fallback to hash mode if database + // is empty. + stored := ReadStateScheme(disk) + if provided == "" { + if stored == "" { + // use default scheme for empty database, flip it when + // path mode is chosen as default + log.Info("State schema set to default", "scheme", "hash") + return HashScheme, nil + } + log.Info("State scheme set to already existing", "scheme", stored) + return stored, nil // reuse scheme of persistent scheme + } + // If state scheme is specified, ensure it's compatible with + // persistent state. + if stored == "" || provided == stored { + log.Info("State scheme set by user", "scheme", provided) + return provided, nil + } + return "", fmt.Errorf("incompatible state scheme, stored: %s, provided: %s", stored, provided) +} diff --git a/core/rawdb/ancient_scheme.go b/core/rawdb/ancient_scheme.go index b0428c5f5bd9..6f409fff1d0a 100644 --- a/core/rawdb/ancient_scheme.go +++ b/core/rawdb/ancient_scheme.go @@ -16,6 +16,8 @@ package rawdb +import "path/filepath" + // The list of table names of chain freezer. const ( // ChainFreezerHeaderTable indicates the name of the freezer header table. @@ -44,10 +46,36 @@ var chainFreezerNoSnappy = map[string]bool{ ChainFreezerDifficultyTable: true, } +const ( + // stateHistoryTableSize defines the maximum size of freezer data files. + stateHistoryTableSize = 2 * 1000 * 1000 * 1000 + + // stateHistoryAccountIndex indicates the name of the freezer state history table. + stateHistoryMeta = "history.meta" + stateHistoryAccountIndex = "account.index" + stateHistoryStorageIndex = "storage.index" + stateHistoryAccountData = "account.data" + stateHistoryStorageData = "storage.data" +) + +var stateFreezerNoSnappy = map[string]bool{ + stateHistoryMeta: true, + stateHistoryAccountIndex: false, + stateHistoryStorageIndex: false, + stateHistoryAccountData: false, + stateHistoryStorageData: false, +} + // The list of identifiers of ancient stores. var ( chainFreezerName = "chain" // the folder name of chain segment ancient store. + stateFreezerName = "state" // the folder name of reverse diff ancient store. ) // freezers the collections of all builtin freezers. -var freezers = []string{chainFreezerName} +var freezers = []string{chainFreezerName, stateFreezerName} + +// NewStateFreezer initializes the freezer for state history. +func NewStateFreezer(ancientDir string, readOnly bool) (*ResettableFreezer, error) { + return NewResettableFreezer(filepath.Join(ancientDir, stateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy) +} diff --git a/core/rawdb/ancient_utils.go b/core/rawdb/ancient_utils.go index 363a911aeea7..dfb2fdfb67df 100644 --- a/core/rawdb/ancient_utils.go +++ b/core/rawdb/ancient_utils.go @@ -50,36 +50,61 @@ func (info *freezerInfo) size() common.StorageSize { return total } +func inspect(name string, order map[string]bool, reader ethdb.AncientReader) (freezerInfo, error) { + info := freezerInfo{name: name} + for t := range order { + size, err := reader.AncientSize(t) + if err != nil { + return freezerInfo{}, err + } + info.sizes = append(info.sizes, tableSize{name: t, size: common.StorageSize(size)}) + } + // Retrieve the number of last stored item + ancients, err := reader.Ancients() + if err != nil { + return freezerInfo{}, err + } + info.head = ancients - 1 + + // Retrieve the number of first stored item + tail, err := reader.Tail() + if err != nil { + return freezerInfo{}, err + } + info.tail = tail + return info, nil +} + // inspectFreezers inspects all freezers registered in the system. func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) { var infos []freezerInfo for _, freezer := range freezers { switch freezer { case chainFreezerName: - // Chain ancient store is a bit special. It's always opened along - // with the key-value store, inspect the chain store directly. - info := freezerInfo{name: freezer} - // Retrieve storage size of every contained table. - for table := range chainFreezerNoSnappy { - size, err := db.AncientSize(table) - if err != nil { - return nil, err - } - info.sizes = append(info.sizes, tableSize{name: table, size: common.StorageSize(size)}) + info, err := inspect(chainFreezerName, chainFreezerNoSnappy, db) + if err != nil { + return nil, err + } + infos = append(infos, info) + + case stateFreezerName: + if ReadStateScheme(db) != PathScheme { + continue + } + datadir, err := db.AncientDatadir() + if err != nil { + return nil, err } - // Retrieve the number of last stored item - ancients, err := db.Ancients() + f, err := NewStateFreezer(datadir, true) if err != nil { return nil, err } - info.head = ancients - 1 + defer f.Close() - // Retrieve the number of first stored item - tail, err := db.Tail() + info, err := inspect(stateFreezerName, stateFreezerNoSnappy, f) if err != nil { return nil, err } - info.tail = tail infos = append(infos, info) default: diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go index 22dbda4a2107..cbfaf5b9e4e6 100644 --- a/core/rawdb/chain_freezer.go +++ b/core/rawdb/chain_freezer.go @@ -200,7 +200,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { } batch.Reset() - // Step into the future and delete and dangling side chains + // Step into the future and delete any dangling side chains if frozen > 0 { tip := frozen for len(dangling) > 0 { diff --git a/core/rawdb/chain_iterator_test.go b/core/rawdb/chain_iterator_test.go index e1f5159753f0..9580cd92a873 100644 --- a/core/rawdb/chain_iterator_test.go +++ b/core/rawdb/chain_iterator_test.go @@ -34,7 +34,7 @@ func TestChainIterator(t *testing.T) { var block *types.Block var txs []*types.Transaction to := common.BytesToAddress([]byte{0x11}) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newHasher()) // Empty genesis block + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newTestHasher()) // Empty genesis block WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) for i := uint64(1); i <= 10; i++ { @@ -60,7 +60,7 @@ func TestChainIterator(t *testing.T) { }) } txs = append(txs, tx) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher()) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newTestHasher()) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) } @@ -111,7 +111,7 @@ func TestIndexTransactions(t *testing.T) { to := common.BytesToAddress([]byte{0x11}) // Write empty genesis block - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newHasher()) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(0))}, nil, nil, nil, newTestHasher()) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) @@ -138,7 +138,7 @@ func TestIndexTransactions(t *testing.T) { }) } txs = append(txs, tx) - block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newHasher()) + block = types.NewBlock(&types.Header{Number: big.NewInt(int64(i))}, []*types.Transaction{tx}, nil, nil, newTestHasher()) WriteBlock(chainDb, block) WriteCanonicalHash(chainDb, block.Hash(), block.NumberU64()) } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index e864bcb2e88e..0c7cf9f11bd2 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -34,7 +34,7 @@ import ( "github.com/olekukonko/tablewriter" ) -// freezerdb is a database wrapper that enabled freezer data retrievals. +// freezerdb is a database wrapper that enables freezer data retrievals. type freezerdb struct { ancientRoot string ethdb.KeyValueStore @@ -123,13 +123,13 @@ func (db *nofreezedb) ModifyAncients(func(ethdb.AncientWriteOp) error) (int64, e } // TruncateHead returns an error as we don't have a backing chain freezer. -func (db *nofreezedb) TruncateHead(items uint64) error { - return errNotSupported +func (db *nofreezedb) TruncateHead(items uint64) (uint64, error) { + return 0, errNotSupported } // TruncateTail returns an error as we don't have a backing chain freezer. -func (db *nofreezedb) TruncateTail(items uint64) error { - return errNotSupported +func (db *nofreezedb) TruncateTail(items uint64) (uint64, error) { + return 0, errNotSupported } // Sync returns an error as we don't have a backing chain freezer. @@ -141,7 +141,7 @@ func (db *nofreezedb) ReadAncients(fn func(reader ethdb.AncientReaderOp) error) // Unlike other ancient-related methods, this method does not return // errNotSupported when invoked. // The reason for this is that the caller might want to do several things: - // 1. Check if something is in freezer, + // 1. Check if something is in the freezer, // 2. If not, check leveldb. // // This will work, since the ancient-checks inside 'fn' will return errors, @@ -209,7 +209,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st // of the freezer and database. Ensure that we don't shoot ourselves in the foot // by serving up conflicting data, leading to both datastores getting corrupted. // - // - If both the freezer and key-value store is empty (no genesis), we just + // - If both the freezer and key-value store are empty (no genesis), we just // initialized a new empty freezer, so everything's fine. // - If the key-value store is empty, but the freezer is not, we need to make // sure the user's genesis matches the freezer. That will be checked in the @@ -218,7 +218,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st // - If neither the key-value store nor the freezer is empty, cross validate // the genesis hashes to make sure they are compatible. If they are, also // ensure that there's no gap between the freezer and subsequently leveldb. - // - If the key-value store is not empty, but the freezer is we might just be + // - If the key-value store is not empty, but the freezer is, we might just be // upgrading to the freezer release, or we might have had a small chain and // not frozen anything yet. Ensure that no blocks are missing yet from the // key-value store, since that would mean we already had an old freezer. @@ -253,7 +253,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st break } } - // We are about to exit on error. Print database metdata beore exiting + // We are about to exit on error. Print database metadata before exiting printChainMetadata(db) return nil, fmt.Errorf("gap in the chain between ancients [0 - #%d] and leveldb [#%d - #%d] ", frozen-1, number, head) @@ -326,10 +326,10 @@ const ( dbLeveldb = "leveldb" ) -// hasPreexistingDb checks the given data directory whether a database is already +// PreexistingDatabase checks the given data directory whether a database is already // instantiated at that location, and if so, returns the type of database (or the // empty string). -func hasPreexistingDb(path string) string { +func PreexistingDatabase(path string) string { if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil { return "" // No pre-existing db } @@ -352,6 +352,9 @@ type OpenOptions struct { Cache int // the capacity(in megabytes) of the data caching Handles int // number of files to be open simultaneously ReadOnly bool + // Ephemeral means that filesystem sync operations should be avoided: data integrity in the face of + // a crash is not important. This option should typically be used in tests. + Ephemeral bool } // openKeyValueDatabase opens a disk-based key-value database, e.g. leveldb or pebble. @@ -367,14 +370,14 @@ func openKeyValueDatabase(o OpenOptions) (ethdb.Database, error) { } // Retrieve any pre-existing database's type and use that or the requested one // as long as there's no conflict between the two types - existingDb := hasPreexistingDb(o.Directory) + existingDb := PreexistingDatabase(o.Directory) if len(existingDb) != 0 && len(o.Type) != 0 && o.Type != existingDb { return nil, fmt.Errorf("db.engine choice was %v but found pre-existing %v database in specified data directory", o.Type, existingDb) } if o.Type == dbPebble || existingDb == dbPebble { if PebbleEnabled { log.Info("Using pebble as the backing database") - return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly) + return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly, o.Ephemeral) } else { return nil, errors.New("db.engine 'pebble' not supported on this platform") } @@ -387,7 +390,7 @@ func openKeyValueDatabase(o OpenOptions) (ethdb.Database, error) { // on supported platforms and LevelDB on anything else. if PebbleEnabled { log.Info("Defaulting to pebble as the backing database") - return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly) + return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly, o.Ephemeral) } else { log.Info("Defaulting to leveldb as the backing database") return NewLevelDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly) @@ -463,7 +466,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { tds stat numHashPairings stat hashNumPairings stat - tries stat + legacyTries stat + stateLookups stat + accountTries stat + storageTries stat codes stat txLookups stat accountSnaps stat @@ -504,8 +510,14 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { numHashPairings.Add(size) case bytes.HasPrefix(key, headerNumberPrefix) && len(key) == (len(headerNumberPrefix)+common.HashLength): hashNumPairings.Add(size) - case len(key) == common.HashLength: - tries.Add(size) + case IsLegacyTrieNode(key, it.Value()): + legacyTries.Add(size) + case bytes.HasPrefix(key, stateIDPrefix) && len(key) == len(stateIDPrefix)+common.HashLength: + stateLookups.Add(size) + case IsAccountTrieNode(key): + accountTries.Add(size) + case IsStorageTrieNode(key): + storageTries.Add(size) case bytes.HasPrefix(key, CodePrefix) && len(key) == len(CodePrefix)+common.HashLength: codes.Add(size) case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength): @@ -543,6 +555,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { lastPivotKey, fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey, snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey, + persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey, } { if bytes.Equal(key, meta) { metadata.Add(size) @@ -571,7 +584,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { {"Key-Value store", "Transaction index", txLookups.Size(), txLookups.Count()}, {"Key-Value store", "Bloombit index", bloomBits.Size(), bloomBits.Count()}, {"Key-Value store", "Contract codes", codes.Size(), codes.Count()}, - {"Key-Value store", "Trie nodes", tries.Size(), tries.Count()}, + {"Key-Value store", "Hash trie nodes", legacyTries.Size(), legacyTries.Count()}, + {"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()}, + {"Key-Value store", "Path trie account nodes", accountTries.Size(), accountTries.Count()}, + {"Key-Value store", "Path trie storage nodes", storageTries.Size(), storageTries.Count()}, {"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()}, {"Key-Value store", "Account snapshot", accountSnaps.Size(), accountSnaps.Count()}, {"Key-Value store", "Storage snapshot", storageSnaps.Size(), storageSnaps.Count()}, @@ -618,7 +634,7 @@ func printChainMetadata(db ethdb.KeyValueStore) { fmt.Fprintf(os.Stderr, "\n\n") } -// ReadChainMetadata returns a set of key/value pairs that contains informatin +// ReadChainMetadata returns a set of key/value pairs that contains information // about the database chain status. This can be used for diagnostic purposes // when investigating the state of the node. func ReadChainMetadata(db ethdb.KeyValueStore) [][]string { diff --git a/core/rawdb/databases_64bit.go b/core/rawdb/databases_64bit.go index 73bfeb20838f..e9f9332ad01c 100644 --- a/core/rawdb/databases_64bit.go +++ b/core/rawdb/databases_64bit.go @@ -23,13 +23,13 @@ import ( "github.com/ethereum/go-ethereum/ethdb/pebble" ) -// Pebble is unsuported on 32bit architecture +// Pebble is unsupported on 32bit architecture const PebbleEnabled = true // NewPebbleDBDatabase creates a persistent key-value database without a freezer // moving immutable chain segments into cold storage. -func NewPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) { - db, err := pebble.New(file, cache, handles, namespace, readonly) +func NewPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly, ephemeral bool) (ethdb.Database, error) { + db, err := pebble.New(file, cache, handles, namespace, readonly, ephemeral) if err != nil { return nil, err } diff --git a/core/rawdb/databases_non64bit.go b/core/rawdb/databases_non64bit.go index 1f10c2f52bd2..fb0777a5e65f 100644 --- a/core/rawdb/databases_non64bit.go +++ b/core/rawdb/databases_non64bit.go @@ -29,6 +29,6 @@ const PebbleEnabled = false // NewPebbleDBDatabase creates a persistent key-value database without a freezer // moving immutable chain segments into cold storage. -func NewPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) { +func NewPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly, ephemeral bool) (ethdb.Database, error) { return nil, errors.New("pebble is not supported on this platform") } diff --git a/core/rawdb/fileutil.go b/core/rawdb/fileutil.go index a5a7298ec3bc..50b9bfb2f004 100644 --- a/core/rawdb/fileutil.go +++ b/core/rawdb/fileutil.go @@ -24,6 +24,7 @@ import "github.com/gofrs/flock" type FileLock interface { Unlock() error TryLock() (bool, error) + TryRLock() (bool, error) } func NewFileLock(fileName string) FileLock { diff --git a/core/rawdb/fileutil_mock.go b/core/rawdb/fileutil_mock.go index de61a276e66f..b8028fadbfad 100644 --- a/core/rawdb/fileutil_mock.go +++ b/core/rawdb/fileutil_mock.go @@ -22,6 +22,7 @@ package rawdb type FileLock interface { Unlock() error TryLock() (bool, error) + TryRLock() (bool, error) } func NewFileLock(_ string) FileLock { @@ -36,3 +37,6 @@ func (r mockFileLock) Unlock() error { func (r mockFileLock) TryLock() (bool, error) { return true, nil } +func (r mockFileLock) TryRLock() (bool, error) { + return true, nil +} diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index be63663adf77..dbee468e8a6d 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -107,7 +107,11 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui // Leveldb uses LOCK as the filelock filename. To prevent the // name collision, we use FLOCK as the lock name. lock := NewFileLock(flockFile) - if locked, err := lock.TryLock(); err != nil { + tryLock := lock.TryLock + if readonly { + tryLock = lock.TryRLock + } + if locked, err := tryLock(); err != nil { return nil, err } else if !locked { return nil, errors.New("locking failed") @@ -196,9 +200,10 @@ func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) { // AncientRange retrieves multiple items in sequence, starting from the index 'start'. // It will return -// - at most 'max' items, -// - at least 1 item (even if exceeding the maxByteSize), but will otherwise -// return as many items as fit into maxByteSize. +// - at most 'count' items, +// - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize), +// but will otherwise return as many items as fit into maxByteSize. +// - if maxBytes is not specified, 'count' items will be returned if they are present. func (f *Freezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { if table := f.tables[kind]; table != nil { return table.RetrieveItems(start, count, maxBytes) @@ -273,43 +278,46 @@ func (f *Freezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (writeSize } // TruncateHead discards any recent data above the provided threshold number. -func (f *Freezer) TruncateHead(items uint64) error { +// It returns the previous head number. +func (f *Freezer) TruncateHead(items uint64) (uint64, error) { if f.readonly { - return errReadOnly + return 0, errReadOnly } f.writeLock.Lock() defer f.writeLock.Unlock() - if f.frozen.Load() <= items { - return nil + oitems := f.frozen.Load() + if oitems <= items { + return oitems, nil } for _, table := range f.tables { if err := table.truncateHead(items); err != nil { - return err + return 0, err } } f.frozen.Store(items) - return nil + return oitems, nil } // TruncateTail discards any recent data below the provided threshold number. -func (f *Freezer) TruncateTail(tail uint64) error { +func (f *Freezer) TruncateTail(tail uint64) (uint64, error) { if f.readonly { - return errReadOnly + return 0, errReadOnly } f.writeLock.Lock() defer f.writeLock.Unlock() - if f.tail.Load() >= tail { - return nil + old := f.tail.Load() + if old >= tail { + return old, nil } for _, table := range f.tables { if err := table.truncateTail(tail); err != nil { - return err + return 0, err } } f.tail.Store(tail) - return nil + return old, nil } // Sync flushes all data tables to disk. @@ -433,7 +441,7 @@ func (f *Freezer) MigrateTable(kind string, convert convertLegacyFn) error { // TODO(s1na): This is a sanity-check since as of now no process does tail-deletion. But the migration // process assumes no deletion at tail and needs to be modified to account for that. if table.itemOffset.Load() > 0 || table.itemHidden.Load() > 0 { - return fmt.Errorf("migration not supported for tail-deleted freezers") + return errors.New("migration not supported for tail-deleted freezers") } ancientsPath := filepath.Dir(table.index.Name()) // Set up new dir for the migrated table, the content of which diff --git a/core/rawdb/freezer_resettable.go b/core/rawdb/freezer_resettable.go index f9a56c6de552..0a3892bcdfa4 100644 --- a/core/rawdb/freezer_resettable.go +++ b/core/rawdb/freezer_resettable.go @@ -170,7 +170,8 @@ func (f *ResettableFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) } // TruncateHead discards any recent data above the provided threshold number. -func (f *ResettableFreezer) TruncateHead(items uint64) error { +// It returns the previous head number. +func (f *ResettableFreezer) TruncateHead(items uint64) (uint64, error) { f.lock.RLock() defer f.lock.RUnlock() @@ -178,7 +179,8 @@ func (f *ResettableFreezer) TruncateHead(items uint64) error { } // TruncateTail discards any recent data below the provided threshold number. -func (f *ResettableFreezer) TruncateTail(tail uint64) error { +// It returns the previous value +func (f *ResettableFreezer) TruncateTail(tail uint64) (uint64, error) { f.lock.RLock() defer f.lock.RUnlock() diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index 928b37d70b2d..cb32d61ae8a9 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -212,6 +212,9 @@ func (t *freezerTable) repair() error { } // Ensure the index is a multiple of indexEntrySize bytes if overflow := stat.Size() % indexEntrySize; overflow != 0 { + if t.readonly { + return fmt.Errorf("index file(path: %s, name: %s) size is not a multiple of %d", t.path, t.name, indexEntrySize) + } truncateFreezerFile(t.index, stat.Size()-overflow) // New file can't trigger this path } // Retrieve the file sizes and prepare for truncation @@ -270,6 +273,9 @@ func (t *freezerTable) repair() error { // Keep truncating both files until they come in sync contentExp = int64(lastIndex.offset) for contentExp != contentSize { + if t.readonly { + return fmt.Errorf("freezer table(path: %s, name: %s, num: %d) is corrupted", t.path, t.name, lastIndex.filenum) + } verbose = true // Truncate the head file to the last offset pointer if contentExp < contentSize { @@ -712,7 +718,7 @@ func (t *freezerTable) RetrieveItems(start, count, maxBytes uint64) ([][]byte, e if !t.noCompression { decompressedSize, _ = snappy.DecodedLen(item) } - if i > 0 && uint64(outputSize+decompressedSize) > maxBytes { + if i > 0 && maxBytes != 0 && uint64(outputSize+decompressedSize) > maxBytes { break } if !t.noCompression { @@ -730,8 +736,10 @@ func (t *freezerTable) RetrieveItems(start, count, maxBytes uint64) ([][]byte, e } // retrieveItems reads up to 'count' items from the table. It reads at least -// one item, but otherwise avoids reading more than maxBytes bytes. -// It returns the (potentially compressed) data, and the sizes. +// one item, but otherwise avoids reading more than maxBytes bytes. Freezer +// will ignore the size limitation and continuously allocate memory to store +// data if maxBytes is 0. It returns the (potentially compressed) data, and +// the sizes. func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []int, error) { t.lock.RLock() defer t.lock.RUnlock() @@ -752,25 +760,22 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i if start+count > items { count = items - start } - var ( - output = make([]byte, maxBytes) // Buffer to read data into - outputSize int // Used size of that buffer - ) + var output []byte // Buffer to read data into + if maxBytes != 0 { + output = make([]byte, 0, maxBytes) + } else { + output = make([]byte, 0, 1024) // initial buffer cap + } // readData is a helper method to read a single data item from disk. readData := func(fileId, start uint32, length int) error { - // In case a small limit is used, and the elements are large, may need to - // realloc the read-buffer when reading the first (and only) item. - if len(output) < length { - output = make([]byte, length) - } + output = grow(output, length) dataFile, exist := t.files[fileId] if !exist { return fmt.Errorf("missing data file %d", fileId) } - if _, err := dataFile.ReadAt(output[outputSize:outputSize+length], int64(start)); err != nil { + if _, err := dataFile.ReadAt(output[len(output)-length:], int64(start)); err != nil { return err } - outputSize += length return nil } // Read all the indexes in one go @@ -801,7 +806,7 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i } readStart = 0 } - if i > 0 && uint64(totalSize+size) > maxBytes { + if i > 0 && uint64(totalSize+size) > maxBytes && maxBytes != 0 { // About to break out due to byte limit being exceeded. We don't // read this last item, but we need to do the deferred reads now. if unreadSize > 0 { @@ -815,7 +820,7 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i unreadSize += size totalSize += size sizes = append(sizes, size) - if i == len(indices)-2 || uint64(totalSize) > maxBytes { + if i == len(indices)-2 || (uint64(totalSize) > maxBytes && maxBytes != 0) { // Last item, need to do the read now if err := readData(secondIndex.filenum, readStart, unreadSize); err != nil { return nil, nil, err @@ -826,7 +831,7 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i // Update metrics. t.readMeter.Mark(int64(totalSize)) - return output[:outputSize], sizes, nil + return output, sizes, nil } // has returns an indicator whether the specified number data is still accessible diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go index 5c4cc40eddc7..939d0939464d 100644 --- a/core/rawdb/freezer_table_test.go +++ b/core/rawdb/freezer_table_test.go @@ -994,6 +994,52 @@ func TestSequentialReadByteLimit(t *testing.T) { } } +// TestSequentialReadNoByteLimit tests the batch-read if maxBytes is not specified. +// Freezer should return the requested items regardless the size limitation. +func TestSequentialReadNoByteLimit(t *testing.T) { + rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() + fname := fmt.Sprintf("batchread-3-%d", rand.Uint64()) + { // Fill table + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 100, true, false) + if err != nil { + t.Fatal(err) + } + // Write 10 bytes 30 times, + // Splitting it at every 100 bytes (10 items) + writeChunks(t, f, 30, 10) + f.Close() + } + for i, tc := range []struct { + items uint64 + want int + }{ + {1, 1}, + {30, 30}, + {31, 30}, + } { + { + f, err := newTable(os.TempDir(), fname, rm, wm, sg, 100, true, false) + if err != nil { + t.Fatal(err) + } + items, err := f.RetrieveItems(0, tc.items, 0) + if err != nil { + t.Fatal(err) + } + if have, want := len(items), tc.want; have != want { + t.Fatalf("test %d: want %d items, have %d ", i, want, have) + } + for ii, have := range items { + want := getChunk(10, ii) + if !bytes.Equal(want, have) { + t.Fatalf("test %d: data corruption item %d: have\n%x\n, want \n%x\n", i, ii, have, want) + } + } + f.Close() + } + } +} + func TestFreezerReadonly(t *testing.T) { tmpdir := os.TempDir() // Case 1: Check it fails on non-existent file. diff --git a/core/rawdb/freezer_test.go b/core/rawdb/freezer_test.go index 630c9029b0f5..b4bd6a382a86 100644 --- a/core/rawdb/freezer_test.go +++ b/core/rawdb/freezer_test.go @@ -192,7 +192,7 @@ func TestFreezerConcurrentModifyTruncate(t *testing.T) { for i := 0; i < 10; i++ { // First reset and write 100 items. - if err := f.TruncateHead(0); err != nil { + if _, err := f.TruncateHead(0); err != nil { t.Fatal("truncate failed:", err) } _, err := f.ModifyAncients(func(op ethdb.AncientWriteOp) error { @@ -227,7 +227,7 @@ func TestFreezerConcurrentModifyTruncate(t *testing.T) { wg.Done() }() go func() { - truncateErr = f.TruncateHead(10) + _, truncateErr = f.TruncateHead(10) wg.Done() }() go func() { @@ -283,6 +283,57 @@ func TestFreezerReadonlyValidate(t *testing.T) { } } +func TestFreezerConcurrentReadonly(t *testing.T) { + t.Parallel() + + tables := map[string]bool{"a": true} + dir := t.TempDir() + + f, err := NewFreezer(dir, "", false, 2049, tables) + if err != nil { + t.Fatal("can't open freezer", err) + } + var item = make([]byte, 1024) + batch := f.tables["a"].newBatch() + items := uint64(10) + for i := uint64(0); i < items; i++ { + require.NoError(t, batch.AppendRaw(i, item)) + } + require.NoError(t, batch.commit()) + if loaded := f.tables["a"].items.Load(); loaded != items { + t.Fatalf("unexpected number of items in table, want: %d, have: %d", items, loaded) + } + require.NoError(t, f.Close()) + + var ( + wg sync.WaitGroup + fs = make([]*Freezer, 5) + errs = make([]error, 5) + ) + for i := 0; i < 5; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + + f, err := NewFreezer(dir, "", true, 2049, tables) + if err == nil { + fs[i] = f + } else { + errs[i] = err + } + }(i) + } + + wg.Wait() + + for i := range fs { + if err := errs[i]; err != nil { + t.Fatal("failed to open freezer", err) + } + require.NoError(t, fs[i].Close()) + } +} + func newFreezerForTesting(t *testing.T, tables map[string]bool) (*Freezer, string) { t.Helper() diff --git a/core/rawdb/freezer_utils.go b/core/rawdb/freezer_utils.go index e7cce2920db7..1bbb50c4984f 100644 --- a/core/rawdb/freezer_utils.go +++ b/core/rawdb/freezer_utils.go @@ -117,3 +117,19 @@ func truncateFreezerFile(file *os.File, size int64) error { } return nil } + +// grow prepares the slice space for new item, and doubles the slice capacity +// if space is not enough. +func grow(buf []byte, n int) []byte { + if cap(buf)-len(buf) < n { + newcap := 2 * cap(buf) + if newcap-len(buf) < n { + newcap = len(buf) + n + } + nbuf := make([]byte, len(buf), newcap) + copy(nbuf, buf) + buf = nbuf + } + buf = buf[:len(buf)+n] + return buf +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 18722ed5d4cb..8e82459e8225 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -43,6 +43,9 @@ var ( // headFinalizedBlockKey tracks the latest known finalized block hash. headFinalizedBlockKey = []byte("LastFinalized") + // persistentStateIDKey tracks the id of latest stored state(for path-based only). + persistentStateIDKey = []byte("LastStateID") + // lastPivotKey tracks the last pivot block used by fast sync (to reenable on sethead). lastPivotKey = []byte("LastPivot") @@ -70,6 +73,9 @@ var ( // skeletonSyncStatusKey tracks the skeleton sync status across restarts. skeletonSyncStatusKey = []byte("SkeletonSyncStatus") + // trieJournalKey tracks the in-memory trie node layers across restarts. + trieJournalKey = []byte("TrieJournal") + // txIndexTailKey tracks the oldest block whose transactions have been indexed. txIndexTailKey = []byte("TransactionIndexTail") @@ -85,6 +91,9 @@ var ( // transitionStatusKey tracks the eth2 transition status. transitionStatusKey = []byte("eth2-transition") + // snapSyncStatusFlagKey flags that status of snap sync. + snapSyncStatusFlagKey = []byte("SnapSyncStatus") + // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td @@ -104,6 +113,7 @@ var ( // Path-based storage scheme of merkle patricia trie. trieNodeAccountPrefix = []byte("A") // trieNodeAccountPrefix + hexPath -> trie node trieNodeStoragePrefix = []byte("O") // trieNodeStoragePrefix + accountHash + hexPath -> trie node + stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db @@ -188,7 +198,11 @@ func accountSnapshotKey(hash common.Hash) []byte { // storageSnapshotKey = SnapshotStoragePrefix + account hash + storage hash func storageSnapshotKey(accountHash, storageHash common.Hash) []byte { - return append(append(SnapshotStoragePrefix, accountHash.Bytes()...), storageHash.Bytes()...) + buf := make([]byte, len(SnapshotStoragePrefix)+common.HashLength+common.HashLength) + n := copy(buf, SnapshotStoragePrefix) + n += copy(buf[n:], accountHash.Bytes()) + copy(buf[n:], storageHash.Bytes()) + return buf } // storageSnapshotsKey = SnapshotStoragePrefix + account hash + storage hash @@ -240,6 +254,11 @@ func genesisStateSpecKey(hash common.Hash) []byte { return append(genesisPrefix, hash.Bytes()...) } +// stateIDKey = stateIDPrefix + root (32 bytes) +func stateIDKey(root common.Hash) []byte { + return append(stateIDPrefix, root.Bytes()...) +} + // accountTrieNodeKey = trieNodeAccountPrefix + nodePath. func accountTrieNodeKey(path []byte) []byte { return append(trieNodeAccountPrefix, path...) @@ -247,7 +266,11 @@ func accountTrieNodeKey(path []byte) []byte { // storageTrieNodeKey = trieNodeStoragePrefix + accountHash + nodePath. func storageTrieNodeKey(accountHash common.Hash, path []byte) []byte { - return append(append(trieNodeStoragePrefix, accountHash.Bytes()...), path...) + buf := make([]byte, len(trieNodeStoragePrefix)+common.HashLength+len(path)) + n := copy(buf, trieNodeStoragePrefix) + n += copy(buf[n:], accountHash.Bytes()) + copy(buf[n:], path) + return buf } // IsLegacyTrieNode reports whether a provided database entry is a legacy trie @@ -261,9 +284,10 @@ func IsLegacyTrieNode(key []byte, val []byte) bool { return bytes.Equal(key, crypto.Keccak256(val)) } -// IsAccountTrieNode reports whether a provided database entry is an account -// trie node in path-based state scheme. -func IsAccountTrieNode(key []byte) (bool, []byte) { +// ResolveAccountTrieNodeKey reports whether a provided database entry is an +// account trie node in path-based state scheme, and returns the resolved +// node path if so. +func ResolveAccountTrieNodeKey(key []byte) (bool, []byte) { if !bytes.HasPrefix(key, trieNodeAccountPrefix) { return false, nil } @@ -276,9 +300,17 @@ func IsAccountTrieNode(key []byte) (bool, []byte) { return true, key[len(trieNodeAccountPrefix):] } -// IsStorageTrieNode reports whether a provided database entry is a storage +// IsAccountTrieNode reports whether a provided database entry is an account // trie node in path-based state scheme. -func IsStorageTrieNode(key []byte) (bool, common.Hash, []byte) { +func IsAccountTrieNode(key []byte) bool { + ok, _ := ResolveAccountTrieNodeKey(key) + return ok +} + +// ResolveStorageTrieNode reports whether a provided database entry is a storage +// trie node in path-based state scheme, and returns the resolved account hash +// and node path if so. +func ResolveStorageTrieNode(key []byte) (bool, common.Hash, []byte) { if !bytes.HasPrefix(key, trieNodeStoragePrefix) { return false, common.Hash{}, nil } @@ -294,3 +326,10 @@ func IsStorageTrieNode(key []byte) (bool, common.Hash, []byte) { accountHash := common.BytesToHash(key[len(trieNodeStoragePrefix) : len(trieNodeStoragePrefix)+common.HashLength]) return true, accountHash, key[len(trieNodeStoragePrefix)+common.HashLength:] } + +// IsStorageTrieNode reports whether a provided database entry is a storage +// trie node in path-based state scheme. +func IsStorageTrieNode(key []byte) bool { + ok, _, _ := ResolveStorageTrieNode(key) + return ok +} diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 6d6fa0555da9..19e4ed5b5c4b 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -97,13 +97,13 @@ func (t *table) ReadAncients(fn func(reader ethdb.AncientReaderOp) error) (err e // TruncateHead is a noop passthrough that just forwards the request to the underlying // database. -func (t *table) TruncateHead(items uint64) error { +func (t *table) TruncateHead(items uint64) (uint64, error) { return t.db.TruncateHead(items) } // TruncateTail is a noop passthrough that just forwards the request to the underlying // database. -func (t *table) TruncateTail(items uint64) error { +func (t *table) TruncateTail(items uint64) (uint64, error) { return t.db.TruncateTail(items) } @@ -219,7 +219,7 @@ func (b *tableBatch) Put(key, value []byte) error { return b.batch.Put(append([]byte(b.prefix), key...), value) } -// Delete inserts the a key removal into the batch for later committing. +// Delete inserts a key removal into the batch for later committing. func (b *tableBatch) Delete(key []byte) error { return b.batch.Delete(append([]byte(b.prefix), key...)) } diff --git a/core/state/database.go b/core/state/database.go index 1e97945763e7..3daaa08e714a 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" @@ -50,21 +51,21 @@ type Database interface { OpenTrie(root common.Hash) (Trie, error) // OpenStorageTrie opens the storage trie of an account. - OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error) + OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash) (Trie, error) // CopyTrie returns an independent copy of the given trie. CopyTrie(Trie) Trie // ContractCode retrieves a particular contract's code. - ContractCode(addrHash, codeHash common.Hash) ([]byte, error) + ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) // ContractCodeSize retrieves a particular contracts code's size. - ContractCodeSize(addrHash, codeHash common.Hash) (int, error) + ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) // DiskDB returns the underlying key-value disk database. DiskDB() ethdb.KeyValueStore - // TrieDB retrieves the low level trie database used for data storage. + // TrieDB returns the underlying trie database for managing trie nodes. TrieDB() *trie.Database } @@ -100,6 +101,10 @@ type Trie interface { // in the trie with provided address. UpdateAccount(address common.Address, account *types.StateAccount) error + // UpdateContractCode abstracts code write to the trie. It is expected + // to be moved to the stateWriter interface when the latter is ready. + UpdateContractCode(address common.Address, codeHash common.Hash, code []byte) error + // DeleteStorage removes any existing value for key from the trie. If a node // was not found in the database, a trie.MissingNodeError is returned. DeleteStorage(addr common.Address, key []byte) error @@ -117,11 +122,12 @@ type Trie interface { // The returned nodeset can be nil if the trie is clean(nothing to commit). // Once the trie is committed, it's not usable anymore. A new trie must // be created with new root and updated trie database for following usage - Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) + Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) // NodeIterator returns an iterator that returns nodes of the trie. Iteration - // starts at the key after the given start key. - NodeIterator(startKey []byte) trie.NodeIterator + // starts at the key after the given start key. And error will be returned + // if fails to create node iterator. + NodeIterator(startKey []byte) (trie.NodeIterator, error) // Prove constructs a Merkle proof for key. The result contains all encoded nodes // on the path to the value at key. The value itself is also included in the last @@ -130,7 +136,7 @@ type Trie interface { // If the trie does not contain a value for key, the returned proof contains all // nodes of the longest existing prefix of the key (at least the root), ending // with the node that proves the absence of the key. - Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error + Prove(key []byte, proofDb ethdb.KeyValueWriter) error } // NewDatabase creates a backing store for state. The returned database is safe for @@ -152,7 +158,7 @@ func NewDatabaseWithConfig(db ethdb.Database, config *trie.Config) Database { disk: db, codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), - triedb: trie.NewDatabaseWithConfig(db, config), + triedb: trie.NewDatabase(db, config), } return cdb } @@ -193,8 +199,8 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { } // OpenStorageTrie opens the storage trie of an account. -func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error) { - tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, addrHash, root), db.triedb) +func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash) (Trie, error) { + tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root), db.triedb) if err != nil { return nil, err } @@ -212,7 +218,7 @@ func (db *cachingDB) CopyTrie(t Trie) Trie { } // ContractCode retrieves a particular contract's code. -func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) { +func (db *cachingDB) ContractCode(address common.Address, codeHash common.Hash) ([]byte, error) { code, _ := db.codeCache.Get(codeHash) if len(code) > 0 { return code, nil @@ -229,7 +235,7 @@ func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error // ContractCodeWithPrefix retrieves a particular contract's code. If the // code can't be found in the cache, then check the existence with **new** // db scheme. -func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]byte, error) { +func (db *cachingDB) ContractCodeWithPrefix(address common.Address, codeHash common.Hash) ([]byte, error) { code, _ := db.codeCache.Get(codeHash) if len(code) > 0 { return code, nil @@ -244,11 +250,11 @@ func (db *cachingDB) ContractCodeWithPrefix(addrHash, codeHash common.Hash) ([]b } // ContractCodeSize retrieves a particular contracts code's size. -func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) { +func (db *cachingDB) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) { if cached, ok := db.codeSizeCache.Get(codeHash); ok { return cached, nil } - code, err := db.ContractCode(addrHash, codeHash) + code, err := db.ContractCode(addr, codeHash) return len(code), err } diff --git a/core/state/dump.go b/core/state/dump.go index 96a2ac3d3e36..9ce6cd394b88 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -140,7 +140,11 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] log.Info("Trie dumping started", "root", s.trie.Hash()) c.OnRoot(s.trie.Hash()) - it := trie.NewIterator(s.trie.NodeIterator(conf.Start)) + trieIt, err := s.trie.NodeIterator(conf.Start) + if err != nil { + return nil + } + it := trie.NewIterator(trieIt) for it.Next() { var data types.StateAccount if err := rlp.DecodeBytes(it.Value, &data); err != nil { @@ -167,18 +171,23 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] } else { address = &addr } - obj := newObject(s, addr, data) + obj := newObject(s, addr, &data) if !conf.SkipCode { - account.Code = obj.Code(s.db) + account.Code = obj.Code() } if !conf.SkipStorage { account.Storage = make(map[common.Hash]string) - tr, err := obj.getTrie(s.db) + tr, err := obj.getTrie() if err != nil { log.Error("Failed to load storage trie", "err", err) continue } - storageIt := trie.NewIterator(tr.NodeIterator(nil)) + trieIt, err := tr.NodeIterator(nil) + if err != nil { + log.Error("Failed to create trie iterator", "err", err) + continue + } + storageIt := trie.NewIterator(trieIt) for storageIt.Next() { _, content, _, err := rlp.Split(storageIt.Value) if err != nil { diff --git a/core/state/iterator.go b/core/state/iterator.go index 79bb650abc4d..683efd73de70 100644 --- a/core/state/iterator.go +++ b/core/state/iterator.go @@ -18,6 +18,7 @@ package state import ( "bytes" + "errors" "fmt" "github.com/ethereum/go-ethereum/common" @@ -27,7 +28,8 @@ import ( ) // nodeIterator is an iterator to traverse the entire state trie post-order, -// including all of the contract code and contract state tries. +// including all of the contract code and contract state tries. Preimage is +// required in order to resolve the contract address. type nodeIterator struct { state *StateDB // State being iterated @@ -74,8 +76,12 @@ func (it *nodeIterator) step() error { return nil } // Initialize the iterator if we've just started + var err error if it.stateIt == nil { - it.stateIt = it.state.trie.NodeIterator(nil) + it.stateIt, err = it.state.trie.NodeIterator(nil) + if err != nil { + return err + } } // If we had data nodes previously, we surely have at least state nodes if it.dataIt != nil { @@ -106,21 +112,31 @@ func (it *nodeIterator) step() error { } // Otherwise we've reached an account node, initiate data iteration var account types.StateAccount - if err := rlp.Decode(bytes.NewReader(it.stateIt.LeafBlob()), &account); err != nil { + if err := rlp.DecodeBytes(it.stateIt.LeafBlob(), &account); err != nil { + return err + } + // Lookup the preimage of account hash + preimage := it.state.trie.GetKey(it.stateIt.LeafKey()) + if preimage == nil { + return errors.New("account address is not available") + } + address := common.BytesToAddress(preimage) + + // Traverse the storage slots belong to the account + dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, address, account.Root) + if err != nil { return err } - dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, common.BytesToHash(it.stateIt.LeafKey()), account.Root) + it.dataIt, err = dataTrie.NodeIterator(nil) if err != nil { return err } - it.dataIt = dataTrie.NodeIterator(nil) if !it.dataIt.Next(true) { it.dataIt = nil } if !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) { it.codeHash = common.BytesToHash(account.CodeHash) - addrHash := common.BytesToHash(it.stateIt.LeafKey()) - it.code, err = it.state.db.ContractCode(addrHash, common.BytesToHash(account.CodeHash)) + it.code, err = it.state.db.ContractCode(address, common.BytesToHash(account.CodeHash)) if err != nil { return fmt.Errorf("code %x: %v", account.CodeHash, err) } diff --git a/core/state/iterator_test.go b/core/state/iterator_test.go index c8e1a181eb6f..73cc22490b6e 100644 --- a/core/state/iterator_test.go +++ b/core/state/iterator_test.go @@ -26,9 +26,14 @@ import ( // Tests that the node iterator indeed walks over the entire database contents. func TestNodeIteratorCoverage(t *testing.T) { + testNodeIteratorCoverage(t, rawdb.HashScheme) + testNodeIteratorCoverage(t, rawdb.PathScheme) +} + +func testNodeIteratorCoverage(t *testing.T, scheme string) { // Create some arbitrary test state to iterate - db, sdb, root, _ := makeTestState() - sdb.TrieDB().Commit(root, false) + db, sdb, ndb, root, _ := makeTestState(scheme) + ndb.Commit(root, false) state, err := New(root, sdb, nil) if err != nil { @@ -48,7 +53,7 @@ func TestNodeIteratorCoverage(t *testing.T) { ) it := db.NewIterator(nil, nil) for it.Next() { - ok, hash := isTrieNode(sdb.TrieDB().Scheme(), it.Key(), it.Value()) + ok, hash := isTrieNode(scheme, it.Key(), it.Value()) if !ok { continue } @@ -90,11 +95,11 @@ func isTrieNode(scheme string, key, val []byte) (bool, common.Hash) { return true, common.BytesToHash(key) } } else { - ok, _ := rawdb.IsAccountTrieNode(key) + ok := rawdb.IsAccountTrieNode(key) if ok { return true, crypto.Keccak256Hash(val) } - ok, _, _ = rawdb.IsStorageTrieNode(key) + ok = rawdb.IsStorageTrieNode(key) if ok { return true, crypto.Keccak256Hash(val) } diff --git a/core/state/journal.go b/core/state/journal.go index 1722fb4c02e1..beaf6f3e176b 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -92,10 +92,16 @@ type ( resetObjectChange struct { prev *stateObject prevdestruct bool + prevAccount []byte + prevStorage map[common.Hash][]byte + + prevAccountOriginExist bool + prevAccountOrigin []byte + prevStorageOrigin map[common.Hash][]byte } - suicideChange struct { + selfDestructChange struct { account *common.Address - prev bool // whether account had already suicided + prev bool // whether account had already self-destructed prevbalance *big.Int } @@ -159,21 +165,39 @@ func (ch resetObjectChange) revert(s *StateDB) { if !ch.prevdestruct { delete(s.stateObjectsDestruct, ch.prev.address) } + if ch.prevAccount != nil { + s.accounts[ch.prev.addrHash] = ch.prevAccount + } + if ch.prevStorage != nil { + s.storages[ch.prev.addrHash] = ch.prevStorage + } + if ch.prevAccountOriginExist { + s.accountsOrigin[ch.prev.address] = ch.prevAccountOrigin + } + if ch.prevStorageOrigin != nil { + s.storagesOrigin[ch.prev.address] = ch.prevStorageOrigin + } } func (ch resetObjectChange) dirtied() *common.Address { + // Arbitrum: We keep the behavior that existed before go-ethereum v1.12.1 and return nil, + // instead of returning the reset address as upstream go-ethereum v1.12.1 does. + // That's because, unlike for go-ethereum, whether this account is dirty or not is relevant for Arbitrum. + // Arbitrum hooks manipulate the state in some ways that go-ethereum doesn't which cause that relevance, + // e.g. subtracting balance from an account that hasn't been otherwise touched. + // See https://github.com/OffchainLabs/nitro/pull/1976 for details return nil } -func (ch suicideChange) revert(s *StateDB) { +func (ch selfDestructChange) revert(s *StateDB) { obj := s.getStateObject(*ch.account) if obj != nil { - obj.suicided = ch.prev + obj.selfDestructed = ch.prev obj.setBalance(ch.prevbalance) } } -func (ch suicideChange) dirtied() *common.Address { +func (ch selfDestructChange) dirtied() *common.Address { return ch.account } diff --git a/core/state/metrics.go b/core/state/metrics.go index e702ef3a81a6..64c651461e73 100644 --- a/core/state/metrics.go +++ b/core/state/metrics.go @@ -27,4 +27,11 @@ var ( storageTriesUpdatedMeter = metrics.NewRegisteredMeter("state/update/storagenodes", nil) accountTrieDeletedMeter = metrics.NewRegisteredMeter("state/delete/accountnodes", nil) storageTriesDeletedMeter = metrics.NewRegisteredMeter("state/delete/storagenodes", nil) + + slotDeletionMaxCount = metrics.NewRegisteredGauge("state/delete/storage/max/slot", nil) + slotDeletionMaxSize = metrics.NewRegisteredGauge("state/delete/storage/max/size", nil) + slotDeletionTimer = metrics.NewRegisteredResettingTimer("state/delete/storage/timer", nil) + slotDeletionCount = metrics.NewRegisteredMeter("state/delete/storage/slot", nil) + slotDeletionSize = metrics.NewRegisteredMeter("state/delete/storage/size", nil) + slotDeletionSkip = metrics.NewRegisteredGauge("state/delete/storage/skip", nil) ) diff --git a/core/state/pruner/pruner.go b/core/state/pruner/pruner.go index 3b73d4adf46c..69b347114aff 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -57,7 +57,6 @@ const ( // Config includes all the configurations for pruning. type Config struct { Datadir string // The directory of the state database - Cachedir string // The directory of state clean cache BloomSize uint64 // The Megabytes of memory allocated to bloom-filter } @@ -86,13 +85,16 @@ func NewPruner(db ethdb.Database, config Config) (*Pruner, error) { if headBlock == nil { return nil, errors.New("failed to load head block") } + // Offline pruning is only supported in legacy hash based scheme. + triedb := trie.NewDatabase(db, trie.HashDefaults) + snapconfig := snapshot.Config{ CacheSize: 256, Recovery: false, NoBuild: true, AsyncBuild: false, } - snaptree, err := snapshot.New(snapconfig, db, trie.NewDatabase(db), headBlock.Root()) + snaptree, err := snapshot.New(snapconfig, db, triedb, headBlock.Root()) if err != nil { return nil, err // The relevant snapshot(s) might not exist } @@ -336,7 +338,10 @@ func dumpRawTrieDescendants(db ethdb.Database, root common.Hash, output *stateBl if err != nil { return err } - accountIt := tr.NodeIterator(nil) + accountIt, err := tr.NodeIterator(nil) + if err != nil { + return err + } startedAt := time.Now() lastLog := time.Now() @@ -379,7 +384,7 @@ func dumpRawTrieDescendants(db ethdb.Database, root common.Hash, output *stateBl output.Put(data.CodeHash, nil) } if data.Root != (common.Hash{}) { - storageTr, err := sdb.OpenStorageTrie(key, common.BytesToHash(accountIt.LeafKey()), data.Root) + storageTr, err := trie.NewStateTrie(trie.StorageTrieID(root, key, data.Root), sdb.TrieDB()) if err != nil { return err } @@ -392,7 +397,10 @@ func dumpRawTrieDescendants(db ethdb.Database, root common.Hash, output *stateBl defer func() { results <- err }() - storageIt := storageTr.NodeIterator(nil) + storageIt, err := storageTr.NodeIterator(nil) + if err != nil { + return + } for storageIt.Next(true) { storageTrieHash := storageIt.Hash() if storageTrieHash != (common.Hash{}) { @@ -436,7 +444,7 @@ func (p *Pruner) Prune(inputRoots []common.Hash) error { return err } if bloomExists { - return RecoverPruning(p.config.Datadir, p.db, p.config.Cachedir) + return RecoverPruning(p.config.Datadir, p.db) } // Retrieve all snapshot layers from the current HEAD. // In theory there are 128 difflayers + 1 disk layer present, @@ -491,11 +499,6 @@ func (p *Pruner) Prune(inputRoots []common.Hash) error { if len(roots) == 0 { return errors.New("no pruning target roots found") } - // Before start the pruning, delete the clean trie cache first. - // It's necessary otherwise in the next restart we will hit the - // deleted state root in the "clean cache" so that the incomplete - // state is picked for usage. - deleteCleanTrieCache(p.config.Cachedir) // Traverse the target state, re-construct the whole state trie and // commit to the given bloom filter. @@ -535,7 +538,7 @@ func (p *Pruner) Prune(inputRoots []common.Hash) error { // pruning can be resumed. What's more if the bloom filter is constructed, the // pruning **has to be resumed**. Otherwise a lot of dangling nodes may be left // in the disk. -func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) error { +func RecoverPruning(datadir string, db ethdb.Database) error { exists, err := bloomFilterExists(datadir) if err != nil { return err @@ -561,7 +564,9 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err NoBuild: true, AsyncBuild: false, } - snaptree, err := snapshot.New(snapconfig, db, trie.NewDatabase(db), headBlock.Root()) + // Offline pruning is only supported in legacy hash based scheme. + triedb := trie.NewDatabase(db, trie.HashDefaults) + snaptree, err := snapshot.New(snapconfig, db, triedb, headBlock.Root()) if err != nil { return err // The relevant snapshot(s) might not exist } @@ -572,12 +577,6 @@ func RecoverPruning(datadir string, db ethdb.Database, trieCachePath string) err } log.Info("Loaded state bloom filter", "path", stateBloomPath, "roots", stateBloomRoots) - // Before start the pruning, delete the clean trie cache first. - // It's necessary otherwise in the next restart we will hit the - // deleted state root in the "clean cache" so that the incomplete - // state is picked for usage. - deleteCleanTrieCache(trieCachePath) - return prune(snaptree, stateBloomRoots, db, stateBloom, stateBloomPath, time.Now()) } @@ -610,23 +609,3 @@ func bloomFilterExists(datadir string) (bool, error) { return true, nil } } - -const warningLog = ` - -WARNING! - -The clean trie cache is not found. Please delete it by yourself after the -pruning. Remember don't start the Geth without deleting the clean trie cache -otherwise the entire database may be damaged! - -Check the command description "geth snapshot prune-state --help" for more details. -` - -func deleteCleanTrieCache(path string) { - if !common.FileExist(path) { - log.Warn(warningLog) - return - } - os.RemoveAll(path) - log.Info("Deleted trie clean cache", "path", path) -} diff --git a/core/state/snapshot/account.go b/core/state/snapshot/account.go deleted file mode 100644 index b5634972ad62..000000000000 --- a/core/state/snapshot/account.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package snapshot - -import ( - "bytes" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" -) - -// Account is a modified version of a state.Account, where the root is replaced -// with a byte slice. This format can be used to represent full-consensus format -// or slim-snapshot format which replaces the empty root and code hash as nil -// byte slice. -type Account struct { - Nonce uint64 - Balance *big.Int - Root []byte - CodeHash []byte -} - -// SlimAccount converts a state.Account content into a slim snapshot account -func SlimAccount(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) Account { - slim := Account{ - Nonce: nonce, - Balance: balance, - } - if root != types.EmptyRootHash { - slim.Root = root[:] - } - if !bytes.Equal(codehash, types.EmptyCodeHash[:]) { - slim.CodeHash = codehash - } - return slim -} - -// SlimAccountRLP converts a state.Account content into a slim snapshot -// version RLP encoded. -func SlimAccountRLP(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) []byte { - data, err := rlp.EncodeToBytes(SlimAccount(nonce, balance, root, codehash)) - if err != nil { - panic(err) - } - return data -} - -// FullAccount decodes the data on the 'slim RLP' format and return -// the consensus format account. -func FullAccount(data []byte) (Account, error) { - var account Account - if err := rlp.DecodeBytes(data, &account); err != nil { - return Account{}, err - } - if len(account.Root) == 0 { - account.Root = types.EmptyRootHash[:] - } - if len(account.CodeHash) == 0 { - account.CodeHash = types.EmptyCodeHash[:] - } - return account, nil -} - -// FullAccountRLP converts data on the 'slim RLP' format into the full RLP-format. -func FullAccountRLP(data []byte) ([]byte, error) { - account, err := FullAccount(data) - if err != nil { - return nil, err - } - return rlp.EncodeToBytes(account) -} diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index 5c303644948f..5645e3b9a5f1 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -17,7 +17,6 @@ package snapshot import ( - "bytes" "encoding/binary" "errors" "fmt" @@ -303,7 +302,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou fullData []byte ) if leafCallback == nil { - fullData, err = FullAccountRLP(it.(AccountIterator).Account()) + fullData, err = types.FullAccountRLP(it.(AccountIterator).Account()) if err != nil { return stop(err) } @@ -315,7 +314,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou return stop(err) } // Fetch the next account and process it concurrently - account, err := FullAccount(it.(AccountIterator).Account()) + account, err := types.FullAccount(it.(AccountIterator).Account()) if err != nil { return stop(err) } @@ -325,7 +324,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou results <- err return } - if !bytes.Equal(account.Root, subroot.Bytes()) { + if account.Root != subroot { results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x", hash, account.Root, subroot) return } @@ -367,11 +366,11 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou func stackTrieGenerate(db ethdb.KeyValueWriter, scheme string, owner common.Hash, in chan trieKV, out chan common.Hash) { var nodeWriter trie.NodeWriteFunc if db != nil { - nodeWriter = func(owner common.Hash, path []byte, hash common.Hash, blob []byte) { + nodeWriter = func(path []byte, hash common.Hash, blob []byte) { rawdb.WriteTrieNode(db, owner, path, hash, blob, scheme) } } - t := trie.NewStackTrieWithOwner(nodeWriter, owner) + t := trie.NewStackTrie(nodeWriter) for leaf := range in { t.Update(leaf.key[:], leaf.value) } diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 203d66f93d02..b6aca599c509 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -21,14 +21,15 @@ import ( "fmt" "math" "math/rand" - "sort" "sync" "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" bloomfilter "github.com/holiman/bloomfilter/v2" + "golang.org/x/exp/slices" ) var ( @@ -272,7 +273,7 @@ func (dl *diffLayer) Stale() bool { // Account directly retrieves the account associated with a particular hash in // the snapshot slim data format. -func (dl *diffLayer) Account(hash common.Hash) (*Account, error) { +func (dl *diffLayer) Account(hash common.Hash) (*types.SlimAccount, error) { data, err := dl.AccountRLP(hash) if err != nil { return nil, err @@ -280,7 +281,7 @@ func (dl *diffLayer) Account(hash common.Hash) (*Account, error) { if len(data) == 0 { // can be both nil and []byte{} return nil, nil } - account := new(Account) + account := new(types.SlimAccount) if err := rlp.DecodeBytes(data, account); err != nil { panic(err) } @@ -292,8 +293,8 @@ func (dl *diffLayer) Account(hash common.Hash) (*Account, error) { // // Note the returned account is not a copy, please don't modify it. func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) { - dl.lock.RLock() // Check staleness before reaching further. + dl.lock.RLock() if dl.Stale() { dl.lock.RUnlock() return nil, ErrSnapshotStale @@ -524,7 +525,7 @@ func (dl *diffLayer) AccountList() []common.Hash { dl.accountList = append(dl.accountList, hash) } } - sort.Sort(hashes(dl.accountList)) + slices.SortFunc(dl.accountList, common.Hash.Cmp) dl.memory += uint64(len(dl.accountList) * common.HashLength) return dl.accountList } @@ -562,7 +563,7 @@ func (dl *diffLayer) StorageList(accountHash common.Hash) ([]common.Hash, bool) for k := range storageMap { storageList = append(storageList, k) } - sort.Sort(hashes(storageList)) + slices.SortFunc(storageList, common.Hash.Cmp) dl.storageList[accountHash] = storageList dl.memory += uint64(len(dl.storageList)*common.HashLength + common.HashLength) return storageList, destructed diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index 7cbf6e293db9..513f0f5abaed 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -23,6 +23,7 @@ import ( "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" @@ -65,7 +66,7 @@ func (dl *diskLayer) Stale() bool { // Account directly retrieves the account associated with a particular hash in // the snapshot slim data format. -func (dl *diskLayer) Account(hash common.Hash) (*Account, error) { +func (dl *diskLayer) Account(hash common.Hash) (*types.SlimAccount, error) { data, err := dl.AccountRLP(hash) if err != nil { return nil, err @@ -73,7 +74,7 @@ func (dl *diskLayer) Account(hash common.Hash) (*Account, error) { if len(data) == 0 { // can be both nil and []byte{} return nil, nil } - account := new(Account) + account := new(types.SlimAccount) if err := rlp.DecodeBytes(data, account); err != nil { panic(err) } diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 4703b45df31d..f54debebed83 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -20,7 +20,6 @@ import ( "bytes" "errors" "fmt" - "math/big" "time" "github.com/VictoriaMetrics/fastcache" @@ -257,7 +256,7 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ if origin == nil { origin = common.Hash{}.Bytes() } - if err := tr.Prove(origin, 0, proof); err != nil { + if err := tr.Prove(origin, proof); err != nil { log.Debug("Failed to prove range", "kind", kind, "origin", origin, "err", err) return &proofResult{ keys: keys, @@ -268,7 +267,7 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ }, nil } if last != nil { - if err := tr.Prove(last, 0, proof); err != nil { + if err := tr.Prove(last, proof); err != nil { log.Debug("Failed to prove range", "kind", kind, "last", last, "err", err) return &proofResult{ keys: keys, @@ -357,14 +356,18 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefi var resolver trie.NodeResolver if len(result.keys) > 0 { mdb := rawdb.NewMemoryDatabase() - tdb := trie.NewDatabase(mdb) + tdb := trie.NewDatabase(mdb, trie.HashDefaults) + defer tdb.Close() snapTrie := trie.NewEmpty(tdb) for i, key := range result.keys { snapTrie.Update(key, result.vals[i]) } - root, nodes := snapTrie.Commit(false) + root, nodes, err := snapTrie.Commit(false) + if err != nil { + return false, nil, err + } if nodes != nil { - tdb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + tdb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) tdb.Commit(root, false) } resolver = func(owner common.Hash, path []byte, hash common.Hash) []byte { @@ -383,8 +386,6 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefi } var ( trieMore bool - nodeIt = tr.NodeIterator(origin) - iter = trie.NewIterator(nodeIt) kvkeys, kvvals = result.keys, result.vals // counters @@ -398,7 +399,12 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefi start = time.Now() internal time.Duration ) + nodeIt, err := tr.NodeIterator(origin) + if err != nil { + return false, nil, err + } nodeIt.AddResolver(resolver) + iter := trie.NewIterator(nodeIt) for iter.Next() { if last != nil && bytes.Compare(iter.Key, last) > 0 { @@ -440,6 +446,10 @@ func (dl *diskLayer) generateRange(ctx *generatorContext, trieId *trie.ID, prefi internal += time.Since(istart) } if iter.Err != nil { + // Trie errors should never happen. Still, in case of a bug, expose the + // error here, as the outer code will presume errors are interrupts, not + // some deeper issues. + log.Error("State snapshotter failed to iterate trie", "err", err) return false, nil, iter.Err } // Delete all stale snapshot states remaining @@ -573,12 +583,7 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er return nil } // Retrieve the current account and flatten it into the internal format - var acc struct { - Nonce uint64 - Balance *big.Int - Root common.Hash - CodeHash []byte - } + var acc types.StateAccount if err := rlp.DecodeBytes(val, &acc); err != nil { log.Crit("Invalid account encountered during snapshot creation", "err", err) } @@ -594,7 +599,7 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er } snapRecoveredAccountMeter.Mark(1) } else { - data := SlimAccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash) + data := types.SlimAccountRLP(acc) dataLen = len(data) rawdb.WriteAccountSnapshot(ctx.batch, account, data) snapGeneratedAccountMeter.Mark(1) @@ -640,7 +645,7 @@ func generateAccounts(ctx *generatorContext, dl *diskLayer, accMarker []byte) er origin := common.CopyBytes(accMarker) for { id := trie.StateTrieID(dl.root) - exhausted, last, err := dl.generateRange(ctx, id, rawdb.SnapshotAccountPrefix, snapAccount, origin, accountRange, onAccount, FullAccountRLP) + exhausted, last, err := dl.generateRange(ctx, id, rawdb.SnapshotAccountPrefix, snapAccount, origin, accountRange, onAccount, types.FullAccountRLP) if err != nil { return err // The procedure it aborted, either by external signal or internal error. } diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go index a17ae05e5cf9..07016b675ce8 100644 --- a/core/state/snapshot/generate_test.go +++ b/core/state/snapshot/generate_test.go @@ -30,6 +30,8 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/hashdb" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" "github.com/ethereum/go-ethereum/trie/trienode" "golang.org/x/crypto/sha3" ) @@ -45,15 +47,20 @@ func hashData(input []byte) common.Hash { // Tests that snapshot generation from an empty database. func TestGeneration(t *testing.T) { + testGeneration(t, rawdb.HashScheme) + testGeneration(t, rawdb.PathScheme) +} + +func testGeneration(t *testing.T, scheme string) { // We can't use statedb to make a test trie (circular dependency), so make // a fake one manually. We're going with a small account trie of 3 accounts, // two of which also has the same 3-slot storage trie attached. - var helper = newHelper() + var helper = newHelper(scheme) stRoot := helper.makeStorageTrie(common.Hash{}, []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, false) - helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) - helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) - helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) @@ -79,22 +86,27 @@ func TestGeneration(t *testing.T) { // Tests that snapshot generation with existent flat state. func TestGenerateExistentState(t *testing.T) { + testGenerateExistentState(t, rawdb.HashScheme) + testGenerateExistentState(t, rawdb.PathScheme) +} + +func testGenerateExistentState(t *testing.T, scheme string) { // We can't use statedb to make a test trie (circular dependency), so make // a fake one manually. We're going with a small account trie of 3 accounts, // two of which also has the same 3-slot storage trie attached. - var helper = newHelper() + var helper = newHelper(scheme) stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) - helper.addSnapAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) - helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) - helper.addSnapAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) - helper.addSnapAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) root, snap := helper.CommitAndGenerate() @@ -148,9 +160,15 @@ type testHelper struct { nodes *trienode.MergedNodeSet } -func newHelper() *testHelper { +func newHelper(scheme string) *testHelper { diskdb := rawdb.NewMemoryDatabase() - triedb := trie.NewDatabase(diskdb) + config := &trie.Config{} + if scheme == rawdb.PathScheme { + config.PathDB = &pathdb.Config{} // disable caching + } else { + config.HashDB = &hashdb.Config{} // disable caching + } + triedb := trie.NewDatabase(diskdb, config) accTrie, _ := trie.NewStateTrie(trie.StateTrieID(types.EmptyRootHash), triedb) return &testHelper{ diskdb: diskdb, @@ -160,18 +178,17 @@ func newHelper() *testHelper { } } -func (t *testHelper) addTrieAccount(acckey string, acc *Account) { +func (t *testHelper) addTrieAccount(acckey string, acc *types.StateAccount) { val, _ := rlp.EncodeToBytes(acc) t.accTrie.MustUpdate([]byte(acckey), val) } -func (t *testHelper) addSnapAccount(acckey string, acc *Account) { - val, _ := rlp.EncodeToBytes(acc) +func (t *testHelper) addSnapAccount(acckey string, acc *types.StateAccount) { key := hashData([]byte(acckey)) - rawdb.WriteAccountSnapshot(t.diskdb, key, val) + rawdb.WriteAccountSnapshot(t.diskdb, key, types.SlimAccountRLP(*acc)) } -func (t *testHelper) addAccount(acckey string, acc *Account) { +func (t *testHelper) addAccount(acckey string, acc *types.StateAccount) { t.addTrieAccount(acckey, acc) t.addSnapAccount(acckey, acc) } @@ -183,28 +200,28 @@ func (t *testHelper) addSnapStorage(accKey string, keys []string, vals []string) } } -func (t *testHelper) makeStorageTrie(owner common.Hash, keys []string, vals []string, commit bool) []byte { +func (t *testHelper) makeStorageTrie(owner common.Hash, keys []string, vals []string, commit bool) common.Hash { id := trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash) stTrie, _ := trie.NewStateTrie(id, t.triedb) for i, k := range keys { stTrie.MustUpdate([]byte(k), []byte(vals[i])) } if !commit { - return stTrie.Hash().Bytes() + return stTrie.Hash() } - root, nodes := stTrie.Commit(false) + root, nodes, _ := stTrie.Commit(false) if nodes != nil { t.nodes.Merge(nodes) } - return root.Bytes() + return root } func (t *testHelper) Commit() common.Hash { - root, nodes := t.accTrie.Commit(true) + root, nodes, _ := t.accTrie.Commit(true) if nodes != nil { t.nodes.Merge(nodes) } - t.triedb.Update(root, types.EmptyRootHash, t.nodes) + t.triedb.Update(root, types.EmptyRootHash, 0, t.nodes, nil) t.triedb.Commit(root, false) return root } @@ -234,31 +251,36 @@ func (t *testHelper) CommitAndGenerate() (common.Hash, *diskLayer) { // - extra slots in the middle // - extra slots in the end func TestGenerateExistentStateWithWrongStorage(t *testing.T) { - helper := newHelper() + testGenerateExistentStateWithWrongStorage(t, rawdb.HashScheme) + testGenerateExistentStateWithWrongStorage(t, rawdb.PathScheme) +} + +func testGenerateExistentStateWithWrongStorage(t *testing.T, scheme string) { + helper := newHelper(scheme) // Account one, empty root but non-empty database - helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) // Account two, non empty root but empty database stRoot := helper.makeStorageTrie(hashData([]byte("acc-2")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-2", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // Miss slots { // Account three, non empty root but misses slots in the beginning helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-3", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-3", []string{"key-2", "key-3"}, []string{"val-2", "val-3"}) // Account four, non empty root but misses slots in the middle helper.makeStorageTrie(hashData([]byte("acc-4")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addAccount("acc-4", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-4", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-4", []string{"key-1", "key-3"}, []string{"val-1", "val-3"}) // Account five, non empty root but misses slots in the end helper.makeStorageTrie(hashData([]byte("acc-5")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addAccount("acc-5", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-5", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-5", []string{"key-1", "key-2"}, []string{"val-1", "val-2"}) } @@ -266,22 +288,22 @@ func TestGenerateExistentStateWithWrongStorage(t *testing.T) { { // Account six, non empty root but wrong slots in the beginning helper.makeStorageTrie(hashData([]byte("acc-6")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addAccount("acc-6", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-6", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-6", []string{"key-1", "key-2", "key-3"}, []string{"badval-1", "val-2", "val-3"}) // Account seven, non empty root but wrong slots in the middle helper.makeStorageTrie(hashData([]byte("acc-7")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addAccount("acc-7", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-7", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-7", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "badval-2", "val-3"}) // Account eight, non empty root but wrong slots in the end helper.makeStorageTrie(hashData([]byte("acc-8")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addAccount("acc-8", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-8", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-8", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "badval-3"}) // Account 9, non empty root but rotated slots helper.makeStorageTrie(hashData([]byte("acc-9")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addAccount("acc-9", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-9", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-9", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-3", "val-2"}) } @@ -289,17 +311,17 @@ func TestGenerateExistentStateWithWrongStorage(t *testing.T) { { // Account 10, non empty root but extra slots in the beginning helper.makeStorageTrie(hashData([]byte("acc-10")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addAccount("acc-10", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-10", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-10", []string{"key-0", "key-1", "key-2", "key-3"}, []string{"val-0", "val-1", "val-2", "val-3"}) // Account 11, non empty root but extra slots in the middle helper.makeStorageTrie(hashData([]byte("acc-11")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addAccount("acc-11", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-11", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-11", []string{"key-1", "key-2", "key-2-1", "key-3"}, []string{"val-1", "val-2", "val-2-1", "val-3"}) // Account 12, non empty root but extra slots in the end helper.makeStorageTrie(hashData([]byte("acc-12")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addAccount("acc-12", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-12", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-12", []string{"key-1", "key-2", "key-3", "key-4"}, []string{"val-1", "val-2", "val-3", "val-4"}) } @@ -326,7 +348,12 @@ func TestGenerateExistentStateWithWrongStorage(t *testing.T) { // - wrong accounts // - extra accounts func TestGenerateExistentStateWithWrongAccounts(t *testing.T) { - helper := newHelper() + testGenerateExistentStateWithWrongAccounts(t, rawdb.HashScheme) + testGenerateExistentStateWithWrongAccounts(t, rawdb.PathScheme) +} + +func testGenerateExistentStateWithWrongAccounts(t *testing.T, scheme string) { + helper := newHelper(scheme) helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.makeStorageTrie(hashData([]byte("acc-2")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) @@ -339,25 +366,25 @@ func TestGenerateExistentStateWithWrongAccounts(t *testing.T) { // Missing accounts, only in the trie { - helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // Beginning - helper.addTrieAccount("acc-4", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // Middle - helper.addTrieAccount("acc-6", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // End + helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // Beginning + helper.addTrieAccount("acc-4", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // Middle + helper.addTrieAccount("acc-6", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // End } // Wrong accounts { - helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) - helper.addSnapAccount("acc-2", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: common.Hex2Bytes("0x1234")}) + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapAccount("acc-2", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: common.Hex2Bytes("0x1234")}) - helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) - helper.addSnapAccount("acc-3", &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addSnapAccount("acc-3", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) } // Extra accounts, only in the snap { - helper.addSnapAccount("acc-0", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // before the beginning - helper.addSnapAccount("acc-5", &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: common.Hex2Bytes("0x1234")}) // Middle - helper.addSnapAccount("acc-7", &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) // after the end + helper.addSnapAccount("acc-0", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // before the beginning + helper.addSnapAccount("acc-5", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: common.Hex2Bytes("0x1234")}) // Middle + helper.addSnapAccount("acc-7", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // after the end } root, snap := helper.CommitAndGenerate() @@ -381,20 +408,27 @@ func TestGenerateExistentStateWithWrongAccounts(t *testing.T) { // Tests that snapshot generation errors out correctly in case of a missing trie // node in the account trie. func TestGenerateCorruptAccountTrie(t *testing.T) { + testGenerateCorruptAccountTrie(t, rawdb.HashScheme) + testGenerateCorruptAccountTrie(t, rawdb.PathScheme) +} + +func testGenerateCorruptAccountTrie(t *testing.T, scheme string) { // We can't use statedb to make a test trie (circular dependency), so make // a fake one manually. We're going with a small account trie of 3 accounts, // without any storage slots to keep the test smaller. - helper := newHelper() + helper := newHelper(scheme) - helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) // 0xc7a30f39aff471c95d8a837497ad0e49b65be475cc0953540f80cfcdbdcd9074 - helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 - helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4 + helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0xc7a30f39aff471c95d8a837497ad0e49b65be475cc0953540f80cfcdbdcd9074 + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4 root := helper.Commit() // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978 - // Delete an account trie leaf and ensure the generator chokes - helper.triedb.Commit(root, false) - helper.diskdb.Delete(common.HexToHash("0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7").Bytes()) + // Delete an account trie node and ensure the generator chokes + targetPath := []byte{0xc} + targetHash := common.HexToHash("0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7") + + rawdb.DeleteTrieNode(helper.diskdb, common.Hash{}, targetPath, targetHash, scheme) snap := generateSnapshot(helper.diskdb, helper.triedb, 16, root) select { @@ -415,21 +449,30 @@ func TestGenerateCorruptAccountTrie(t *testing.T) { // trie node for a storage trie. It's similar to internal corruption but it is // handled differently inside the generator. func TestGenerateMissingStorageTrie(t *testing.T) { + testGenerateMissingStorageTrie(t, rawdb.HashScheme) + testGenerateMissingStorageTrie(t, rawdb.PathScheme) +} + +func testGenerateMissingStorageTrie(t *testing.T, scheme string) { // We can't use statedb to make a test trie (circular dependency), so make // a fake one manually. We're going with a small account trie of 3 accounts, // two of which also has the same 3-slot storage trie attached. - helper := newHelper() - - stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 - helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e - helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + var ( + acc1 = hashData([]byte("acc-1")) + acc3 = hashData([]byte("acc-3")) + helper = newHelper(scheme) + ) + stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 + helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 root := helper.Commit() - // Delete a storage trie root and ensure the generator chokes - helper.diskdb.Delete(stRoot) + // Delete storage trie root of account one and three. + rawdb.DeleteTrieNode(helper.diskdb, acc1, nil, stRoot, scheme) + rawdb.DeleteTrieNode(helper.diskdb, acc3, nil, stRoot, scheme) snap := generateSnapshot(helper.diskdb, helper.triedb, 16, root) select { @@ -449,21 +492,29 @@ func TestGenerateMissingStorageTrie(t *testing.T) { // Tests that snapshot generation errors out correctly in case of a missing trie // node in a storage trie. func TestGenerateCorruptStorageTrie(t *testing.T) { + testGenerateCorruptStorageTrie(t, rawdb.HashScheme) + testGenerateCorruptStorageTrie(t, rawdb.PathScheme) +} + +func testGenerateCorruptStorageTrie(t *testing.T, scheme string) { // We can't use statedb to make a test trie (circular dependency), so make // a fake one manually. We're going with a small account trie of 3 accounts, // two of which also has the same 3-slot storage trie attached. - helper := newHelper() + helper := newHelper(scheme) - stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 - helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e - helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 + stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) // 0xddefcd9376dd029653ef384bd2f0a126bb755fe84fdcc9e7cf421ba454f2bc67 + helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 root := helper.Commit() - // Delete a storage trie leaf and ensure the generator chokes - helper.diskdb.Delete(common.HexToHash("0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371").Bytes()) + // Delete a node in the storage trie. + targetPath := []byte{0x4} + targetHash := common.HexToHash("0x18a0f4d79cff4459642dd7604f303886ad9d77c30cf3d7d7cedb3a693ab6d371") + rawdb.DeleteTrieNode(helper.diskdb, hashData([]byte("acc-1")), targetPath, targetHash, scheme) + rawdb.DeleteTrieNode(helper.diskdb, hashData([]byte("acc-3")), targetPath, targetHash, scheme) snap := generateSnapshot(helper.diskdb, helper.triedb, 16, root) select { @@ -482,7 +533,12 @@ func TestGenerateCorruptStorageTrie(t *testing.T) { // Tests that snapshot generation when an extra account with storage exists in the snap state. func TestGenerateWithExtraAccounts(t *testing.T) { - helper := newHelper() + testGenerateWithExtraAccounts(t, rawdb.HashScheme) + testGenerateWithExtraAccounts(t, rawdb.PathScheme) +} + +func testGenerateWithExtraAccounts(t *testing.T, scheme string) { + helper := newHelper(scheme) { // Account one in the trie stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), @@ -490,7 +546,7 @@ func TestGenerateWithExtraAccounts(t *testing.T) { []string{"val-1", "val-2", "val-3", "val-4", "val-5"}, true, ) - acc := &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()} + acc := &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()} val, _ := rlp.EncodeToBytes(acc) helper.accTrie.MustUpdate([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e @@ -510,7 +566,7 @@ func TestGenerateWithExtraAccounts(t *testing.T) { []string{"val-1", "val-2", "val-3", "val-4", "val-5"}, true, ) - acc := &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()} + acc := &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()} val, _ := rlp.EncodeToBytes(acc) key := hashData([]byte("acc-2")) rawdb.WriteAccountSnapshot(helper.diskdb, key, val) @@ -550,10 +606,15 @@ func enableLogging() { // Tests that snapshot generation when an extra account with storage exists in the snap state. func TestGenerateWithManyExtraAccounts(t *testing.T) { + testGenerateWithManyExtraAccounts(t, rawdb.HashScheme) + testGenerateWithManyExtraAccounts(t, rawdb.PathScheme) +} + +func testGenerateWithManyExtraAccounts(t *testing.T, scheme string) { if false { enableLogging() } - helper := newHelper() + helper := newHelper(scheme) { // Account one in the trie stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), @@ -561,7 +622,7 @@ func TestGenerateWithManyExtraAccounts(t *testing.T) { []string{"val-1", "val-2", "val-3"}, true, ) - acc := &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()} + acc := &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()} val, _ := rlp.EncodeToBytes(acc) helper.accTrie.MustUpdate([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e @@ -575,7 +636,7 @@ func TestGenerateWithManyExtraAccounts(t *testing.T) { { // 100 accounts exist only in snapshot for i := 0; i < 1000; i++ { - acc := &Account{Balance: big.NewInt(int64(i)), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()} + acc := &types.StateAccount{Balance: big.NewInt(int64(i)), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()} val, _ := rlp.EncodeToBytes(acc) key := hashData([]byte(fmt.Sprintf("acc-%d", i))) rawdb.WriteAccountSnapshot(helper.diskdb, key, val) @@ -606,13 +667,18 @@ func TestGenerateWithManyExtraAccounts(t *testing.T) { // So in trie, we iterate 2 entries 0x03, 0x07. We create the 0x07 in the database and abort the procedure, because the trie is exhausted. // But in the database, we still have the stale storage slots 0x04, 0x05. They are not iterated yet, but the procedure is finished. func TestGenerateWithExtraBeforeAndAfter(t *testing.T) { + testGenerateWithExtraBeforeAndAfter(t, rawdb.HashScheme) + testGenerateWithExtraBeforeAndAfter(t, rawdb.PathScheme) +} + +func testGenerateWithExtraBeforeAndAfter(t *testing.T, scheme string) { accountCheckRange = 3 if false { enableLogging() } - helper := newHelper() + helper := newHelper(scheme) { - acc := &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()} + acc := &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()} val, _ := rlp.EncodeToBytes(acc) helper.accTrie.MustUpdate(common.HexToHash("0x03").Bytes(), val) helper.accTrie.MustUpdate(common.HexToHash("0x07").Bytes(), val) @@ -643,13 +709,18 @@ func TestGenerateWithExtraBeforeAndAfter(t *testing.T) { // TestGenerateWithMalformedSnapdata tests what happes if we have some junk // in the snapshot database, which cannot be parsed back to an account func TestGenerateWithMalformedSnapdata(t *testing.T) { + testGenerateWithMalformedSnapdata(t, rawdb.HashScheme) + testGenerateWithMalformedSnapdata(t, rawdb.PathScheme) +} + +func testGenerateWithMalformedSnapdata(t *testing.T, scheme string) { accountCheckRange = 3 if false { enableLogging() } - helper := newHelper() + helper := newHelper(scheme) { - acc := &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()} + acc := &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()} val, _ := rlp.EncodeToBytes(acc) helper.accTrie.MustUpdate(common.HexToHash("0x03").Bytes(), val) @@ -680,15 +751,20 @@ func TestGenerateWithMalformedSnapdata(t *testing.T) { } func TestGenerateFromEmptySnap(t *testing.T) { + testGenerateFromEmptySnap(t, rawdb.HashScheme) + testGenerateFromEmptySnap(t, rawdb.PathScheme) +} + +func testGenerateFromEmptySnap(t *testing.T, scheme string) { //enableLogging() accountCheckRange = 10 storageCheckRange = 20 - helper := newHelper() + helper := newHelper(scheme) // Add 1K accounts to the trie for i := 0; i < 400; i++ { stRoot := helper.makeStorageTrie(hashData([]byte(fmt.Sprintf("acc-%d", i))), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) helper.addTrieAccount(fmt.Sprintf("acc-%d", i), - &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) } root, snap := helper.CommitAndGenerate() t.Logf("Root: %#x\n", root) // Root: 0x6f7af6d2e1a1bf2b84a3beb3f8b64388465fbc1e274ca5d5d3fc787ca78f59e4 @@ -715,8 +791,13 @@ func TestGenerateFromEmptySnap(t *testing.T) { // This hits a case where the snap verification passes, but there are more elements in the trie // which we must also add. func TestGenerateWithIncompleteStorage(t *testing.T) { + testGenerateWithIncompleteStorage(t, rawdb.HashScheme) + testGenerateWithIncompleteStorage(t, rawdb.PathScheme) +} + +func testGenerateWithIncompleteStorage(t *testing.T, scheme string) { storageCheckRange = 4 - helper := newHelper() + helper := newHelper(scheme) stKeys := []string{"1", "2", "3", "4", "5", "6", "7", "8"} stVals := []string{"v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8"} // We add 8 accounts, each one is missing exactly one of the storage slots. This means @@ -725,7 +806,7 @@ func TestGenerateWithIncompleteStorage(t *testing.T) { for i := 0; i < 8; i++ { accKey := fmt.Sprintf("acc-%d", i) stRoot := helper.makeStorageTrie(hashData([]byte(accKey)), stKeys, stVals, true) - helper.addAccount(accKey, &Account{Balance: big.NewInt(int64(i)), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount(accKey, &types.StateAccount{Balance: big.NewInt(int64(i)), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) var moddedKeys []string var moddedVals []string for ii := 0; ii < 8; ii++ { @@ -814,14 +895,19 @@ func populateDangling(disk ethdb.KeyValueStore) { // // This test will populate some dangling storages to see if they can be cleaned up. func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) { - var helper = newHelper() + testGenerateCompleteSnapshotWithDanglingStorage(t, rawdb.HashScheme) + testGenerateCompleteSnapshotWithDanglingStorage(t, rawdb.PathScheme) +} + +func testGenerateCompleteSnapshotWithDanglingStorage(t *testing.T, scheme string) { + var helper = newHelper(scheme) stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) - helper.addAccount("acc-2", &Account{Balance: big.NewInt(1), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-2", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addAccount("acc-3", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addAccount("acc-3", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) helper.addSnapStorage("acc-1", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) helper.addSnapStorage("acc-3", []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}) @@ -849,14 +935,19 @@ func TestGenerateCompleteSnapshotWithDanglingStorage(t *testing.T) { // // This test will populate some dangling storages to see if they can be cleaned up. func TestGenerateBrokenSnapshotWithDanglingStorage(t *testing.T) { - var helper = newHelper() + testGenerateBrokenSnapshotWithDanglingStorage(t, rawdb.HashScheme) + testGenerateBrokenSnapshotWithDanglingStorage(t, rawdb.PathScheme) +} + +func testGenerateBrokenSnapshotWithDanglingStorage(t *testing.T, scheme string) { + var helper = newHelper(scheme) stRoot := helper.makeStorageTrie(hashData([]byte("acc-1")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addTrieAccount("acc-1", &Account{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) - helper.addTrieAccount("acc-2", &Account{Balance: big.NewInt(2), Root: types.EmptyRootHash.Bytes(), CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addTrieAccount("acc-3", &Account{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) populateDangling(helper.diskdb) diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go index 1a042c7cd3c0..0502d9cf8596 100644 --- a/core/state/snapshot/iterator_fast.go +++ b/core/state/snapshot/iterator_fast.go @@ -22,6 +22,7 @@ import ( "sort" "github.com/ethereum/go-ethereum/common" + "golang.org/x/exp/slices" ) // weightedIterator is a iterator with an assigned weight. It is used to prioritise @@ -32,32 +33,25 @@ type weightedIterator struct { priority int } -// weightedIterators is a set of iterators implementing the sort.Interface. -type weightedIterators []*weightedIterator - -// Len implements sort.Interface, returning the number of active iterators. -func (its weightedIterators) Len() int { return len(its) } - -// Less implements sort.Interface, returning which of two iterators in the stack -// is before the other. -func (its weightedIterators) Less(i, j int) bool { +func (it *weightedIterator) Cmp(other *weightedIterator) int { // Order the iterators primarily by the account hashes - hashI := its[i].it.Hash() - hashJ := its[j].it.Hash() + hashI := it.it.Hash() + hashJ := other.it.Hash() switch bytes.Compare(hashI[:], hashJ[:]) { case -1: - return true + return -1 case 1: - return false + return 1 } // Same account/storage-slot in multiple layers, split by priority - return its[i].priority < its[j].priority -} - -// Swap implements sort.Interface, swapping two entries in the iterator stack. -func (its weightedIterators) Swap(i, j int) { - its[i], its[j] = its[j], its[i] + if it.priority < other.priority { + return -1 + } + if it.priority > other.priority { + return 1 + } + return 0 } // fastIterator is a more optimized multi-layer iterator which maintains a @@ -69,7 +63,7 @@ type fastIterator struct { curAccount []byte curSlot []byte - iterators weightedIterators + iterators []*weightedIterator initiated bool account bool fail error @@ -167,7 +161,7 @@ func (fi *fastIterator) init() { } } // Re-sort the entire list - sort.Sort(fi.iterators) + slices.SortFunc(fi.iterators, func(a, b *weightedIterator) int { return a.Cmp(b) }) fi.initiated = false } diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index c1a4cc3d47e5..4d070208f5c7 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -305,7 +305,7 @@ func iterateJournal(db ethdb.KeyValueReader, callback journalCallback) error { } if baseRoot := rawdb.ReadSnapshotRoot(db); baseRoot != parent { log.Warn("Loaded snapshot journal", "diskroot", baseRoot, "diffs", "unmatched") - return fmt.Errorf("mismatched disk and diff layers") + return errors.New("mismatched disk and diff layers") } for { var ( diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 7d8e9547587e..2ba034105f78 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -102,7 +103,7 @@ type Snapshot interface { // Account directly retrieves the account associated with a particular hash in // the snapshot slim data format. - Account(hash common.Hash) (*Account, error) + Account(hash common.Hash) (*types.SlimAccount, error) // AccountRLP directly retrieves the account RLP associated with a particular // hash in the snapshot slim data format. @@ -569,7 +570,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer { // Ensure we don't delete too much data blindly (contract can be // huge). It's ok to flush, the root will go missing in case of a // crash and we'll detect and regenerate the snapshot. - if batch.ValueSize() > ethdb.IdealBatchSize { + if batch.ValueSize() > 64*1024*1024 { if err := batch.Write(); err != nil { log.Crit("Failed to write storage deletions", "err", err) } @@ -595,7 +596,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer { // Ensure we don't write too much data blindly. It's ok to flush, the // root will go missing in case of a crash and we'll detect and regen // the snapshot. - if batch.ValueSize() > ethdb.IdealBatchSize { + if batch.ValueSize() > 64*1024*1024 { if err := batch.Write(); err != nil { log.Crit("Failed to write storage deletions", "err", err) } @@ -857,3 +858,21 @@ func (t *Tree) DiskRoot() common.Hash { return t.diskRoot() } + +// Size returns the memory usage of the diff layers above the disk layer and the +// dirty nodes buffered in the disk layer. Currently, the implementation uses a +// special diff layer (the first) as an aggregator simulating a dirty buffer, so +// the second return will always be 0. However, this will be made consistent with +// the pathdb, which will require a second return. +func (t *Tree) Size() (diffs common.StorageSize, buf common.StorageSize) { + t.lock.RLock() + defer t.lock.RUnlock() + + var size common.StorageSize + for _, layer := range t.layers { + if layer, ok := layer.(*diffLayer); ok { + size += common.StorageSize(layer.memory) + } + } + return size, 0 +} diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index 3f25e3b932ba..f490a6698125 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -43,11 +43,10 @@ func randomHash() common.Hash { // randomAccount generates a random account and returns it RLP encoded. func randomAccount() []byte { - root := randomHash() - a := Account{ + a := &types.StateAccount{ Balance: big.NewInt(rand.Int63()), Nonce: rand.Uint64(), - Root: root[:], + Root: randomHash(), CodeHash: types.EmptyCodeHash[:], } data, _ := rlp.EncodeToBytes(a) @@ -466,7 +465,7 @@ func TestReadStateDuringFlattening(t *testing.T) { snap := snaps.Snapshot(common.HexToHash("0xa3")) // Register the testing hook to access the state after flattening - var result = make(chan *Account) + var result = make(chan *types.SlimAccount) snaps.onFlatten = func() { // Spin up a thread to read the account from the pre-created // snapshot handler. It's expected to be blocked. diff --git a/core/state/snapshot/sort.go b/core/state/snapshot/sort.go deleted file mode 100644 index 88841231d917..000000000000 --- a/core/state/snapshot/sort.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package snapshot - -import ( - "bytes" - - "github.com/ethereum/go-ethereum/common" -) - -// hashes is a helper to implement sort.Interface. -type hashes []common.Hash - -// Len is the number of elements in the collection. -func (hs hashes) Len() int { return len(hs) } - -// Less reports whether the element with index i should sort before the element -// with index j. -func (hs hashes) Less(i, j int) bool { return bytes.Compare(hs[i][:], hs[j][:]) < 0 } - -// Swap swaps the elements with indexes i and j. -func (hs hashes) Swap(i, j int) { hs[i], hs[j] = hs[j], hs[i] } diff --git a/core/state/snapshot/utils.go b/core/state/snapshot/utils.go index fa1f216e6826..62f073d2e159 100644 --- a/core/state/snapshot/utils.go +++ b/core/state/snapshot/utils.go @@ -23,9 +23,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) // CheckDanglingStorage iterates the snap storage data, and verifies that all @@ -98,8 +98,8 @@ func CheckJournalAccount(db ethdb.KeyValueStore, hash common.Hash) error { baseRoot := rawdb.ReadSnapshotRoot(db) fmt.Printf("Disklayer: Root: %x\n", baseRoot) if data := rawdb.ReadAccountSnapshot(db, hash); data != nil { - account := new(Account) - if err := rlp.DecodeBytes(data, account); err != nil { + account, err := types.FullAccount(data) + if err != nil { panic(err) } fmt.Printf("\taccount.nonce: %d\n", account.Nonce) @@ -129,8 +129,8 @@ func CheckJournalAccount(db ethdb.KeyValueStore, hash common.Hash) error { } fmt.Printf("Disklayer+%d: Root: %x, parent %x\n", depth, root, pRoot) if data, ok := accounts[hash]; ok { - account := new(Account) - if err := rlp.DecodeBytes(data, account); err != nil { + account, err := types.FullAccount(data) + if err != nil { panic(err) } fmt.Printf("\taccount.nonce: %d\n", account.Nonce) diff --git a/core/state/state_object.go b/core/state/state_object.go index 47ba46e0787c..60c7def0e19b 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -58,29 +58,38 @@ func (s Storage) Copy() Storage { // stateObject represents an Ethereum account which is being modified. // // The usage pattern is as follows: -// First you need to obtain a state object. -// Account values can be accessed and modified through the object. -// Finally, call commitTrie to write the modified storage trie into a database. +// - First you need to obtain a state object. +// - Account values as well as storages can be accessed and modified through the object. +// - Finally, call commit to return the changes of storage trie and update account data. type stateObject struct { - address common.Address - addrHash common.Hash // hash of ethereum address of the account - data types.StateAccount db *StateDB + address common.Address // address of ethereum account + addrHash common.Hash // hash of ethereum address of the account + origin *types.StateAccount // Account original data without any change applied, nil means it was not existent + data types.StateAccount // Account data with all mutations applied in the scope of block // Write caches. trie Trie // storage trie, which becomes non-nil on first access code Code // contract bytecode, which gets set when code is loaded - originStorage Storage // Storage cache of original entries to dedup rewrites, reset for every transaction + originStorage Storage // Storage cache of original entries to dedup rewrites pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block - dirtyStorage Storage // Storage entries that have been modified in the current transaction execution + dirtyStorage Storage // Storage entries that have been modified in the current transaction execution, reset for every transaction // Cache flags. - // When an object is marked suicided it will be deleted from the trie - // during the "update" phase of the state transition. dirtyCode bool // true if the code was updated - suicided bool - deleted bool + + // Flag whether the account was marked as self-destructed. The self-destructed account + // is still accessible in the scope of same transaction. + selfDestructed bool + + // Flag whether the account was marked as deleted. A self-destructed account + // or an account that is considered as empty will be marked as deleted at + // the end of transaction and no longer accessible anymore. + deleted bool + + // Flag whether the object was created in the current transaction + created bool } // empty returns whether the account is considered empty. @@ -89,21 +98,17 @@ func (s *stateObject) empty() bool { } // newObject creates a state object. -func newObject(db *StateDB, address common.Address, data types.StateAccount) *stateObject { - if data.Balance == nil { - data.Balance = new(big.Int) - } - if data.CodeHash == nil { - data.CodeHash = types.EmptyCodeHash.Bytes() - } - if data.Root == (common.Hash{}) { - data.Root = types.EmptyRootHash +func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject { + origin := acct + if acct == nil { + acct = types.NewEmptyStateAccount() } return &stateObject{ db: db, address: address, addrHash: crypto.Keccak256Hash(address[:]), - data: data, + origin: origin, + data: *acct, originStorage: make(Storage), pendingStorage: make(Storage), dirtyStorage: make(Storage), @@ -115,8 +120,8 @@ func (s *stateObject) EncodeRLP(w io.Writer) error { return rlp.Encode(w, &s.data) } -func (s *stateObject) markSuicided() { - s.suicided = true +func (s *stateObject) markSelfdestructed() { + s.selfDestructed = true } func (s *stateObject) touch() { @@ -133,17 +138,15 @@ func (s *stateObject) touch() { // getTrie returns the associated storage trie. The trie will be opened // if it's not loaded previously. An error will be returned if trie can't // be loaded. -func (s *stateObject) getTrie(db Database) (Trie, error) { +func (s *stateObject) getTrie() (Trie, error) { if s.trie == nil { // Try fetching from prefetcher first - // We don't prefetch empty tries if s.data.Root != types.EmptyRootHash && s.db.prefetcher != nil { - // When the miner is creating the pending state, there is no - // prefetcher + // When the miner is creating the pending state, there is no prefetcher s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root) } if s.trie == nil { - tr, err := db.OpenStorageTrie(s.db.originalRoot, s.addrHash, s.data.Root) + tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root) if err != nil { return nil, err } @@ -154,18 +157,18 @@ func (s *stateObject) getTrie(db Database) (Trie, error) { } // GetState retrieves a value from the account storage trie. -func (s *stateObject) GetState(db Database, key common.Hash) common.Hash { +func (s *stateObject) GetState(key common.Hash) common.Hash { // If we have a dirty value for this state entry, return it value, dirty := s.dirtyStorage[key] if dirty { return value } // Otherwise return the entry's original value - return s.GetCommittedState(db, key) + return s.GetCommittedState(key) } // GetCommittedState retrieves a value from the committed account storage trie. -func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Hash { +func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { // If we have a pending write or clean cached, return that if value, pending := s.pendingStorage[key]; pending { return value @@ -184,8 +187,9 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has } // If no live objects are available, attempt to use snapshots var ( - enc []byte - err error + enc []byte + err error + value common.Hash ) if s.db.snap != nil { start := time.Now() @@ -193,16 +197,23 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has if metrics.EnabledExpensive { s.db.SnapshotStorageReads += time.Since(start) } + if len(enc) > 0 { + _, content, _, err := rlp.Split(enc) + if err != nil { + s.db.setError(err) + } + value.SetBytes(content) + } } // If the snapshot is unavailable or reading from it fails, load from the database. if s.db.snap == nil || err != nil { start := time.Now() - tr, err := s.getTrie(db) + tr, err := s.getTrie() if err != nil { s.db.setError(err) return common.Hash{} } - enc, err = tr.GetStorage(s.address, key.Bytes()) + val, err := tr.GetStorage(s.address, key.Bytes()) if metrics.EnabledExpensive { s.db.StorageReads += time.Since(start) } @@ -210,23 +221,16 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has s.db.setError(err) return common.Hash{} } - } - var value common.Hash - if len(enc) > 0 { - _, content, _, err := rlp.Split(enc) - if err != nil { - s.db.setError(err) - } - value.SetBytes(content) + value.SetBytes(val) } s.originStorage[key] = value return value } // SetState updates a value in account storage. -func (s *stateObject) SetState(db Database, key, value common.Hash) { +func (s *stateObject) SetState(key, value common.Hash) { // If the new value is the same as old, don't set - prev := s.GetState(db, key) + prev := s.GetState(key) if prev == value { return } @@ -261,12 +265,17 @@ func (s *stateObject) finalise(prefetch bool) { } } -// updateTrie writes cached storage modifications into the object's storage trie. -// It will return nil if the trie has not been loaded and no changes have been -// made. An error will be returned if the trie can't be loaded/updated correctly. -func (s *stateObject) updateTrie(db Database) (Trie, error) { +// updateTrie is responsible for persisting cached storage changes into the +// object's storage trie. In case the storage trie is not yet loaded, this +// function will load the trie automatically. If any issues arise during the +// loading or updating of the trie, an error will be returned. Furthermore, +// this function will return the mutated storage trie, or nil if there is no +// storage change at all. +func (s *stateObject) updateTrie() (Trie, error) { // Make sure all dirty slots are finalized into the pending storage area - s.finalise(false) // Don't prefetch anymore, pull directly if need be + s.finalise(false) + + // Short circuit if nothing changed, don't bother with hashing anything if len(s.pendingStorage) == 0 { return s.trie, nil } @@ -277,14 +286,14 @@ func (s *stateObject) updateTrie(db Database) (Trie, error) { // The snapshot storage map for the object var ( storage map[common.Hash][]byte - hasher = s.db.hasher + origin map[common.Hash][]byte ) - tr, err := s.getTrie(db) + tr, err := s.getTrie() if err != nil { s.db.setError(err) return nil, err } - // Insert all the pending updates into the trie + // Insert all the pending storage updates into the trie usedStorage := make([][]byte, 0, len(s.pendingStorage)) keysToDelete := make([]common.Hash, 0, len(s.pendingStorage)) for key, value := range s.pendingStorage { @@ -292,9 +301,10 @@ func (s *stateObject) updateTrie(db Database) (Trie, error) { if value == s.originStorage[key] { continue } + prev := s.originStorage[key] s.originStorage[key] = value - var v []byte + var encoded []byte // rlp-encoded value to be used by the snapshot if (value == common.Hash{}) { if s.db.deterministic { keysToDelete = append(keysToDelete, key) @@ -307,51 +317,68 @@ func (s *stateObject) updateTrie(db Database) (Trie, error) { } } else { // Encoding []byte cannot fail, ok to ignore the error. - v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:])) - if err := tr.UpdateStorage(s.address, key[:], v); err != nil { + trimmed := common.TrimLeftZeroes(value[:]) + encoded, _ = rlp.EncodeToBytes(trimmed) + if err := tr.UpdateStorage(s.address, key[:], trimmed); err != nil { s.db.setError(err) return nil, err } s.db.StorageUpdated += 1 } - // If state snapshotting is active, cache the data til commit - if s.db.snap != nil { - if storage == nil { - // Retrieve the old storage map, if available, create a new one otherwise - if storage = s.db.snapStorage[s.addrHash]; storage == nil { - storage = make(map[common.Hash][]byte) - s.db.snapStorage[s.addrHash] = storage - } + // Cache the mutated storage slots until commit + if storage == nil { + if storage = s.db.storages[s.addrHash]; storage == nil { + storage = make(map[common.Hash][]byte) + s.db.storages[s.addrHash] = storage + } + } + khash := crypto.HashData(s.db.hasher, key[:]) + storage[khash] = encoded // encoded will be nil if it's deleted + + // Cache the original value of mutated storage slots + if origin == nil { + if origin = s.db.storagesOrigin[s.address]; origin == nil { + origin = make(map[common.Hash][]byte) + s.db.storagesOrigin[s.address] = origin + } + } + // Track the original value of slot only if it's mutated first time + if _, ok := origin[khash]; !ok { + if prev == (common.Hash{}) { + origin[khash] = nil // nil if it was not present previously + } else { + // Encoding []byte cannot fail, ok to ignore the error. + b, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(prev[:])) + origin[khash] = b } - storage[crypto.HashData(hasher, key[:])] = v // v will be nil if it's deleted } + // Cache the items for preloading usedStorage = append(usedStorage, common.CopyBytes(key[:])) // Copy needed for closure } if len(keysToDelete) > 0 { sort.Slice(keysToDelete, func(i, j int) bool { return bytes.Compare(keysToDelete[i][:], keysToDelete[j][:]) < 0 }) for _, key := range keysToDelete { - s.db.setError(tr.DeleteStorage(s.address, key[:])) + if err := tr.DeleteStorage(s.address, key[:]); err != nil { + s.db.setError(err) + return nil, err + } s.db.StorageDeleted += 1 } } if s.db.prefetcher != nil { s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage) } - if len(s.pendingStorage) > 0 { - s.pendingStorage = make(Storage) - } + s.pendingStorage = make(Storage) // reset pending map return tr, nil } -// UpdateRoot sets the trie root to the current root hash of. An error -// will be returned if trie root hash is not computed correctly. -func (s *stateObject) updateRoot(db Database) { - tr, err := s.updateTrie(db) - if err != nil { - return - } - // If nothing changed, don't bother with hashing anything - if tr == nil { +// updateRoot flushes all cached storage mutations to trie, recalculating the +// new storage trie root. +func (s *stateObject) updateRoot() { + // Flush cached storage mutations into trie, short circuit if any error + // is occurred or there is not change in the trie. + tr, err := s.updateTrie() + if err != nil || tr == nil { return } // Track the amount of time wasted on hashing the storage trie @@ -361,23 +388,30 @@ func (s *stateObject) updateRoot(db Database) { s.data.Root = tr.Hash() } -// commitTrie submits the storage changes into the storage trie and re-computes -// the root. Besides, all trie changes will be collected in a nodeset and returned. -func (s *stateObject) commitTrie(db Database) (*trienode.NodeSet, error) { - tr, err := s.updateTrie(db) - if err != nil { - return nil, err - } - // If nothing changed, don't bother with committing anything - if tr == nil { +// commit obtains a set of dirty storage trie nodes and updates the account data. +// The returned set can be nil if nothing to commit. This function assumes all +// storage mutations have already been flushed into trie by updateRoot. +func (s *stateObject) commit() (*trienode.NodeSet, error) { + // Short circuit if trie is not even loaded, don't bother with committing anything + if s.trie == nil { + s.origin = s.data.Copy() return nil, nil } // Track the amount of time wasted on committing the storage trie if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now()) } - root, nodes := tr.Commit(false) + // The trie is currently in an open state and could potentially contain + // cached mutations. Call commit to acquire a set of nodes that have been + // modified, the set can be nil if nothing to commit. + root, nodes, err := s.trie.Commit(false) + if err != nil { + return nil, err + } s.data.Root = root + + // Update original account data after commit + s.origin = s.data.Copy() return nodes, nil } @@ -417,19 +451,24 @@ func (s *stateObject) setBalance(amount *big.Int) { } func (s *stateObject) deepCopy(db *StateDB) *stateObject { - stateObject := newObject(db, s.address, s.data) + obj := &stateObject{ + db: db, + address: s.address, + addrHash: s.addrHash, + origin: s.origin, + data: s.data, + } if s.trie != nil { - stateObject.trie = db.db.CopyTrie(s.trie) + obj.trie = db.db.CopyTrie(s.trie) } - stateObject.code = s.code - stateObject.dirtyStorage = s.dirtyStorage.Copy() - stateObject.originStorage = s.originStorage.Copy() - stateObject.pendingStorage = s.pendingStorage.Copy() - stateObject.suicided = s.suicided - stateObject.dirtyCode = s.dirtyCode - stateObject.deleted = s.deleted - - return stateObject + obj.code = s.code + obj.dirtyStorage = s.dirtyStorage.Copy() + obj.originStorage = s.originStorage.Copy() + obj.pendingStorage = s.pendingStorage.Copy() + obj.selfDestructed = s.selfDestructed + obj.dirtyCode = s.dirtyCode + obj.deleted = s.deleted + return obj } // @@ -442,14 +481,14 @@ func (s *stateObject) Address() common.Address { } // Code returns the contract code associated with this object, if any. -func (s *stateObject) Code(db Database) []byte { +func (s *stateObject) Code() []byte { if s.code != nil { return s.code } if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { return nil } - code, err := db.ContractCode(s.addrHash, common.BytesToHash(s.CodeHash())) + code, err := s.db.db.ContractCode(s.address, common.BytesToHash(s.CodeHash())) if err != nil { s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err)) } @@ -460,14 +499,14 @@ func (s *stateObject) Code(db Database) []byte { // CodeSize returns the size of the contract code associated with this object, // or zero if none. This method is an almost mirror of Code, but uses a cache // inside the database to avoid loading codes seen recently. -func (s *stateObject) CodeSize(db Database) int { +func (s *stateObject) CodeSize() int { if s.code != nil { return len(s.code) } if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) { return 0 } - size, err := db.ContractCodeSize(s.addrHash, common.BytesToHash(s.CodeHash())) + size, err := s.db.db.ContractCodeSize(s.address, common.BytesToHash(s.CodeHash())) if err != nil { s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err)) } @@ -475,7 +514,7 @@ func (s *stateObject) CodeSize(db Database) int { } func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { - prevcode := s.Code(s.db.db) + prevcode := s.Code() s.db.journal.append(codeChange{ account: &s.address, prevhash: s.CodeHash(), @@ -513,3 +552,7 @@ func (s *stateObject) Balance() *big.Int { func (s *stateObject) Nonce() uint64 { return s.data.Nonce } + +func (s *stateObject) Root() common.Hash { + return s.data.Root +} diff --git a/core/state/state_test.go b/core/state/state_test.go index 15e603726578..2553133dea55 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -30,21 +30,22 @@ import ( "github.com/ethereum/go-ethereum/trie" ) -type stateTest struct { +type stateEnv struct { db ethdb.Database state *StateDB } -func newStateTest() *stateTest { +func newStateEnv() *stateEnv { db := rawdb.NewMemoryDatabase() sdb, _ := New(types.EmptyRootHash, NewDatabase(db), nil) - return &stateTest{db: db, state: sdb} + return &stateEnv{db: db, state: sdb} } func TestDump(t *testing.T) { db := rawdb.NewMemoryDatabase() - sdb, _ := New(types.EmptyRootHash, NewDatabaseWithConfig(db, &trie.Config{Preimages: true}), nil) - s := &stateTest{db: db, state: sdb} + tdb := NewDatabaseWithConfig(db, &trie.Config{Preimages: true}) + sdb, _ := New(types.EmptyRootHash, tdb, nil) + s := &stateEnv{db: db, state: sdb} // generate a few entries obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01})) @@ -57,9 +58,10 @@ func TestDump(t *testing.T) { // write some of them to the trie s.state.updateStateObject(obj1) s.state.updateStateObject(obj2) - s.state.Commit(false) + root, _ := s.state.Commit(0, false) // check that DumpToCollector contains the state objects that are in trie + s.state, _ = New(root, tdb, nil) got := string(s.state.Dump(nil)) want := `{ "root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2", @@ -95,8 +97,9 @@ func TestDump(t *testing.T) { func TestIterativeDump(t *testing.T) { db := rawdb.NewMemoryDatabase() - sdb, _ := New(types.EmptyRootHash, NewDatabaseWithConfig(db, &trie.Config{Preimages: true}), nil) - s := &stateTest{db: db, state: sdb} + tdb := NewDatabaseWithConfig(db, &trie.Config{Preimages: true}) + sdb, _ := New(types.EmptyRootHash, tdb, nil) + s := &stateEnv{db: db, state: sdb} // generate a few entries obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01})) @@ -111,7 +114,8 @@ func TestIterativeDump(t *testing.T) { // write some of them to the trie s.state.updateStateObject(obj1) s.state.updateStateObject(obj2) - s.state.Commit(false) + root, _ := s.state.Commit(0, false) + s.state, _ = New(root, tdb, nil) b := &bytes.Buffer{} s.state.IterativeDump(nil, json.NewEncoder(b)) @@ -129,14 +133,14 @@ func TestIterativeDump(t *testing.T) { } func TestNull(t *testing.T) { - s := newStateTest() + s := newStateEnv() address := common.HexToAddress("0x823140710bf13990e4500136726d8b55") s.state.CreateAccount(address) //value := common.FromHex("0x823140710bf13990e4500136726d8b55") var value common.Hash s.state.SetState(address, common.Hash{}, value) - s.state.Commit(false) + s.state.Commit(0, false) if value := s.state.GetState(address, common.Hash{}); value != (common.Hash{}) { t.Errorf("expected empty current value, got %x", value) @@ -151,7 +155,7 @@ func TestSnapshot(t *testing.T) { var storageaddr common.Hash data1 := common.BytesToHash([]byte{42}) data2 := common.BytesToHash([]byte{43}) - s := newStateTest() + s := newStateEnv() // snapshot the genesis state genesis := s.state.Snapshot() @@ -182,7 +186,7 @@ func TestSnapshot(t *testing.T) { } func TestSnapshotEmpty(t *testing.T) { - s := newStateTest() + s := newStateEnv() s.state.RevertToSnapshot(s.state.Snapshot()) } @@ -204,11 +208,11 @@ func TestSnapshot2(t *testing.T) { so0.SetBalance(big.NewInt(42)) so0.SetNonce(43) so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'}) - so0.suicided = false + so0.selfDestructed = false so0.deleted = false state.setStateObject(so0) - root, _ := state.Commit(false) + root, _ := state.Commit(0, false) state, _ = New(root, state.db, state.snaps) // and one with deleted == true @@ -216,7 +220,7 @@ func TestSnapshot2(t *testing.T) { so1.SetBalance(big.NewInt(52)) so1.SetNonce(53) so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'}) - so1.suicided = true + so1.selfDestructed = true so1.deleted = true state.setStateObject(so1) @@ -230,8 +234,8 @@ func TestSnapshot2(t *testing.T) { so0Restored := state.getStateObject(stateobjaddr0) // Update lazily-loaded values before comparing. - so0Restored.GetState(state.db, storageaddr) - so0Restored.Code(state.db) + so0Restored.GetState(storageaddr) + so0Restored.Code() // non-deleted is equal (restored) compareStateObjects(so0Restored, so0, t) diff --git a/core/state/statedb.go b/core/state/statedb.go index cc4f158aefb9..2e7484fa8054 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -19,7 +19,6 @@ package state import ( "bytes" - "errors" "fmt" "math/big" "sort" @@ -33,9 +32,15 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +const ( + // storageDeleteLimit denotes the highest permissible memory allocation + // employed for contract storage deletion. + storageDeleteLimit = 512 * 1024 * 1024 ) type revision struct { @@ -46,22 +51,17 @@ type revision struct { unexpectedBalanceDelta *big.Int } -type proofList [][]byte - -func (n *proofList) Put(key []byte, value []byte) error { - *n = append(*n, value) - return nil -} - -func (n *proofList) Delete(key []byte) error { - panic("not supported") -} - // 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: +// // * Contracts // * Accounts +// +// Once the state is committed, tries cached in stateDB (including account +// trie, storage tries) will no longer be functional. A new state instance +// must be created with new root and updated database for accessing post- +// commit states. type StateDB struct { // Arbitrum unexpectedBalanceDelta *big.Int // total balance change across all accounts @@ -75,21 +75,26 @@ type StateDB struct { prefetcher *triePrefetcher trie Trie hasher crypto.KeccakState + snaps *snapshot.Tree // Nil if snapshot is not available + snap snapshot.Snapshot // Nil if snapshot is not available // originalRoot is the pre-state root, before any changes were made. // It will be updated when the Commit is called. originalRoot common.Hash - snaps *snapshot.Tree - snap snapshot.Snapshot - snapAccounts map[common.Hash][]byte - snapStorage map[common.Hash]map[common.Hash][]byte + // These maps hold the state changes (including the corresponding + // original value) that occurred in this **block**. + accounts map[common.Hash][]byte // The mutated accounts in 'slim RLP' encoding + storages map[common.Hash]map[common.Hash][]byte // The mutated slots in prefix-zero trimmed rlp format + accountsOrigin map[common.Address][]byte // The original value of mutated accounts in 'slim RLP' encoding + storagesOrigin map[common.Address]map[common.Hash][]byte // The original value of mutated slots in prefix-zero trimmed rlp format - // This map holds 'live' objects, which will get modified while processing a state transition. + // This map holds 'live' objects, which will get modified while processing + // a state transition. stateObjects map[common.Address]*stateObject - stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie - stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution - stateObjectsDestruct map[common.Address]struct{} // State objects destructed in the block + stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie + stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution + stateObjectsDestruct map[common.Address]*types.StateAccount // State objects destructed in the block along with its previous value // DB error. // State objects are used by the consensus core and VM which are @@ -103,11 +108,13 @@ type StateDB struct { // The refund counter, also used by state transitioning. refund uint64 + // The tx context and all occurred logs in the scope of transaction. thash common.Hash txIndex int logs map[common.Hash][]*types.Log logSize uint + // Preimages occurred seen by VM in the scope of block. preimages map[common.Hash][]byte // Per-transaction access list @@ -140,6 +147,9 @@ type StateDB struct { StorageUpdated int AccountDeleted int StorageDeleted int + + // Testing hooks + onCommit func(states *triestate.Set) // Hook invoked when commit is performed } // New creates a new state from a given trie. @@ -158,10 +168,14 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) trie: tr, originalRoot: root, snaps: snaps, + accounts: make(map[common.Hash][]byte), + storages: make(map[common.Hash]map[common.Hash][]byte), + accountsOrigin: make(map[common.Address][]byte), + storagesOrigin: make(map[common.Address]map[common.Hash][]byte), stateObjects: make(map[common.Address]*stateObject), stateObjectsPending: make(map[common.Address]struct{}), stateObjectsDirty: make(map[common.Address]struct{}), - stateObjectsDestruct: make(map[common.Address]struct{}), + stateObjectsDestruct: make(map[common.Address]*types.StateAccount), logs: make(map[common.Hash][]*types.Log), preimages: make(map[common.Hash][]byte), journal: newJournal(), @@ -170,10 +184,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) hasher: crypto.NewKeccakState(), } if sdb.snaps != nil { - if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil { - sdb.snapAccounts = make(map[common.Hash][]byte) - sdb.snapStorage = make(map[common.Hash]map[common.Hash][]byte) - } + sdb.snap = sdb.snaps.Snapshot(root) } return sdb, nil } @@ -273,7 +284,7 @@ func (s *StateDB) SubRefund(gas uint64) { } // Exist reports whether the given account address exists in the state. -// Notably this also returns true for suicided accounts. +// Notably this also returns true for self-destructed accounts. func (s *StateDB) Exist(addr common.Address) bool { return s.getStateObject(addr) != nil } @@ -294,6 +305,7 @@ func (s *StateDB) GetBalance(addr common.Address) *big.Int { return common.Big0 } +// GetNonce retrieves the nonce from the given address or 0 if object not found func (s *StateDB) GetNonce(addr common.Address) uint64 { stateObject := s.getStateObject(addr) if stateObject != nil { @@ -303,6 +315,16 @@ func (s *StateDB) GetNonce(addr common.Address) uint64 { return 0 } +// GetStorageRoot retrieves the storage root from the given address or empty +// if object not found. +func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash { + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.Root() + } + return common.Hash{} +} + // TxIndex returns the current transaction index set by Prepare. func (s *StateDB) TxIndex() int { return s.txIndex @@ -311,7 +333,7 @@ func (s *StateDB) TxIndex() int { func (s *StateDB) GetCode(addr common.Address) []byte { stateObject := s.getStateObject(addr) if stateObject != nil { - return stateObject.Code(s.db) + return stateObject.Code() } return nil } @@ -319,7 +341,7 @@ func (s *StateDB) GetCode(addr common.Address) []byte { func (s *StateDB) GetCodeSize(addr common.Address) int { stateObject := s.getStateObject(addr) if stateObject != nil { - return stateObject.CodeSize(s.db) + return stateObject.CodeSize() } return 0 } @@ -336,45 +358,16 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { stateObject := s.getStateObject(addr) if stateObject != nil { - return stateObject.GetState(s.db, hash) + return stateObject.GetState(hash) } return common.Hash{} } -// GetProof returns the Merkle proof for a given account. -func (s *StateDB) GetProof(addr common.Address) ([][]byte, error) { - return s.GetProofByHash(crypto.Keccak256Hash(addr.Bytes())) -} - -// GetProofByHash returns the Merkle proof for a given account. -func (s *StateDB) GetProofByHash(addrHash common.Hash) ([][]byte, error) { - var proof proofList - err := s.trie.Prove(addrHash[:], 0, &proof) - return proof, err -} - -// GetStorageProof returns the Merkle proof for given storage slot. -func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) { - trie, err := s.StorageTrie(a) - if err != nil { - return nil, err - } - if trie == nil { - return nil, errors.New("storage trie for requested address does not exist") - } - var proof proofList - err = trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof) - if err != nil { - return nil, err - } - return proof, nil -} - // GetCommittedState retrieves a value from the given account's committed storage trie. func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { stateObject := s.getStateObject(addr) if stateObject != nil { - return stateObject.GetCommittedState(s.db, hash) + return stateObject.GetCommittedState(hash) } return common.Hash{} } @@ -384,25 +377,10 @@ func (s *StateDB) Database() Database { return s.db } -// StorageTrie returns the storage trie of an account. The return value is a copy -// and is nil for non-existent accounts. An error will be returned if storage trie -// is existent but can't be loaded correctly. -func (s *StateDB) StorageTrie(addr common.Address) (Trie, error) { - stateObject := s.getStateObject(addr) - if stateObject == nil { - return nil, nil - } - cpy := stateObject.deepCopy(s) - if _, err := cpy.updateTrie(s.db); err != nil { - return nil, err - } - return cpy.getTrie(s.db) -} - -func (s *StateDB) HasSuicided(addr common.Address) bool { +func (s *StateDB) HasSelfDestructed(addr common.Address) bool { stateObject := s.getStateObject(addr) if stateObject != nil { - return stateObject.suicided + return stateObject.selfDestructed } return false } @@ -466,45 +444,62 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) { func (s *StateDB) SetState(addr common.Address, key, value common.Hash) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - stateObject.SetState(s.db, key, value) + stateObject.SetState(key, value) } } // SetStorage replaces the entire storage for the specified account with given -// storage. This function should only be used for debugging. +// storage. This function should only be used for debugging and the mutations +// must be discarded afterwards. func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) { // SetStorage needs to wipe existing storage. We achieve this by pretending // that the account self-destructed earlier in this block, by flagging // it in stateObjectsDestruct. The effect of doing so is that storage lookups // will not hit disk, since it is assumed that the disk-data is belonging // to a previous incarnation of the object. - s.stateObjectsDestruct[addr] = struct{}{} + // + // TODO(rjl493456442) this function should only be supported by 'unwritable' + // state and all mutations made should all be discarded afterwards. + if _, ok := s.stateObjectsDestruct[addr]; !ok { + s.stateObjectsDestruct[addr] = nil + } stateObject := s.GetOrNewStateObject(addr) for k, v := range storage { - stateObject.SetState(s.db, k, v) + stateObject.SetState(k, v) } } -// Suicide marks the given account as suicided. +// SelfDestruct marks the given account as selfdestructed. // This clears the account balance. // // The account's state object is still available until the state is committed, -// getStateObject will return a non-nil account after Suicide. -func (s *StateDB) Suicide(addr common.Address) bool { +// getStateObject will return a non-nil account after SelfDestruct. +func (s *StateDB) SelfDestruct(addr common.Address) { stateObject := s.getStateObject(addr) if stateObject == nil { - return false + return } - s.journal.append(suicideChange{ + s.journal.append(selfDestructChange{ account: &addr, - prev: stateObject.suicided, + prev: stateObject.selfDestructed, prevbalance: new(big.Int).Set(stateObject.Balance()), }) - stateObject.markSuicided() + + stateObject.markSelfdestructed() s.unexpectedBalanceDelta.Sub(s.unexpectedBalanceDelta, stateObject.data.Balance) + stateObject.data.Balance = new(big.Int) +} + +func (s *StateDB) Selfdestruct6780(addr common.Address) { + stateObject := s.getStateObject(addr) + if stateObject == nil { + return + } - return true + if stateObject.created { + s.SelfDestruct(addr) + } } // SetTransientState sets transient storage for a given account. It @@ -549,13 +544,24 @@ func (s *StateDB) updateStateObject(obj *stateObject) { if err := s.trie.UpdateAccount(addr, &obj.data); err != nil { s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err)) } - - // If state snapshotting is active, cache the data til commit. Note, this - // update mechanism is not symmetric to the deletion, because whereas it is - // enough to track account updates at commit time, deletions need tracking - // at transaction boundary level to ensure we capture state clearing. - if s.snap != nil { - s.snapAccounts[obj.addrHash] = snapshot.SlimAccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash) + if obj.dirtyCode { + s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code) + } + // Cache the data until commit. Note, this update mechanism is not symmetric + // to the deletion, because whereas it is enough to track account updates + // at commit time, deletions need tracking at transaction boundary level to + // ensure we capture state clearing. + s.accounts[obj.addrHash] = types.SlimAccountRLP(obj.data) + + // Track the original value of mutated account, nil means it was not present. + // Skip if it has been tracked (because updateStateObject may be called + // multiple times in a block). + if _, ok := s.accountsOrigin[obj.address]; !ok { + if obj.origin == nil { + s.accountsOrigin[obj.address] = nil + } else { + s.accountsOrigin[obj.address] = types.SlimAccountRLP(*obj.origin) + } } } @@ -634,7 +640,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { } } // Insert into the live set - obj := newObject(s, addr, *data) + obj := newObject(s, addr, data) s.setStateObject(obj) return obj } @@ -656,20 +662,39 @@ func (s *StateDB) GetOrNewStateObject(addr common.Address) *stateObject { // the given address, it is overwritten and returned as the second return value. func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) { prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that! - - var prevdestruct bool - if prev != nil { - _, prevdestruct = s.stateObjectsDestruct[prev.address] - if !prevdestruct { - s.stateObjectsDestruct[prev.address] = struct{}{} - } - } - newobj = newObject(s, addr, types.StateAccount{}) + newobj = newObject(s, addr, nil) if prev == nil { s.journal.append(createObjectChange{account: &addr}) } else { - s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct}) + // The original account should be marked as destructed and all cached + // account and storage data should be cleared as well. Note, it must + // be done here, otherwise the destruction event of "original account" + // will be lost. + _, prevdestruct := s.stateObjectsDestruct[prev.address] + if !prevdestruct { + s.stateObjectsDestruct[prev.address] = prev.origin + } + // There may be some cached account/storage data already since IntermediateRoot + // will be called for each transaction before byzantium fork which will always + // cache the latest account/storage data. + prevAccount, ok := s.accountsOrigin[prev.address] + s.journal.append(resetObjectChange{ + prev: prev, + prevdestruct: prevdestruct, + prevAccount: s.accounts[prev.addrHash], + prevStorage: s.storages[prev.addrHash], + prevAccountOriginExist: ok, + prevAccountOrigin: prevAccount, + prevStorageOrigin: s.storagesOrigin[prev.address], + }) + delete(s.accounts, prev.addrHash) + delete(s.storages, prev.addrHash) + delete(s.accountsOrigin, prev.address) + delete(s.storagesOrigin, prev.address) } + + newobj.created = true + s.setStateObject(newobj) if prev != nil && !prev.deleted { return newobj, prev @@ -694,39 +719,6 @@ func (s *StateDB) CreateAccount(addr common.Address) { } } -func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error { - so := db.getStateObject(addr) - if so == nil { - return nil - } - tr, err := so.getTrie(db.db) - if err != nil { - return err - } - it := trie.NewIterator(tr.NodeIterator(nil)) - - for it.Next() { - key := common.BytesToHash(db.trie.GetKey(it.Key)) - if value, dirty := so.dirtyStorage[key]; dirty { - if !cb(key, value) { - return nil - } - continue - } - - if len(it.Value) > 0 { - _, content, _, err := rlp.Split(it.Value) - if err != nil { - return err - } - if !cb(key, common.BytesToHash(content)) { - return nil - } - } - } - return nil -} - // Copy creates a deep, independent copy of the state. // Snapshots of the copied state cannot be applied to the copy. func (s *StateDB) Copy() *StateDB { @@ -740,16 +732,27 @@ func (s *StateDB) Copy() *StateDB { db: s.db, trie: s.db.CopyTrie(s.trie), originalRoot: s.originalRoot, + accounts: make(map[common.Hash][]byte), + storages: make(map[common.Hash]map[common.Hash][]byte), + accountsOrigin: make(map[common.Address][]byte), + storagesOrigin: make(map[common.Address]map[common.Hash][]byte), stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), - stateObjectsDestruct: make(map[common.Address]struct{}, len(s.stateObjectsDestruct)), + stateObjectsDestruct: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestruct)), refund: s.refund, logs: make(map[common.Hash][]*types.Log, len(s.logs)), logSize: s.logSize, preimages: make(map[common.Hash][]byte, len(s.preimages)), journal: newJournal(), hasher: crypto.NewKeccakState(), + + // In order for the block producer to be able to use and make additions + // to the snapshot tree, we need to copy that as well. Otherwise, any + // block mined by ourselves will cause gaps in the tree, and force the + // miner to operate trie-backed only. + snaps: s.snaps, + snap: s.snap, } // Copy the dirty states, logs, and preimages for addr := range s.journal.dirties { @@ -783,10 +786,18 @@ func (s *StateDB) Copy() *StateDB { } state.stateObjectsDirty[addr] = struct{}{} } - // Deep copy the destruction flag. - for addr := range s.stateObjectsDestruct { - state.stateObjectsDestruct[addr] = struct{}{} + // Deep copy the destruction markers. + for addr, value := range s.stateObjectsDestruct { + state.stateObjectsDestruct[addr] = value } + // Deep copy the state changes made in the scope of block + // along with their original values. + state.accounts = copySet(s.accounts) + state.storages = copy2DSet(s.storages) + state.accountsOrigin = copySet(state.accountsOrigin) + state.storagesOrigin = copy2DSet(state.storagesOrigin) + + // Deep copy the logs occurred in the scope of block for hash, logs := range s.logs { cpy := make([]*types.Log, len(logs)) for i, l := range logs { @@ -795,6 +806,7 @@ func (s *StateDB) Copy() *StateDB { } state.logs[hash] = cpy } + // Deep copy the preimages occurred in the scope of block for hash, preimage := range s.preimages { state.preimages[hash] = preimage } @@ -825,28 +837,6 @@ func (s *StateDB) Copy() *StateDB { if s.prefetcher != nil { state.prefetcher = s.prefetcher.copy() } - if s.snaps != nil { - // In order for the miner to be able to use and make additions - // to the snapshot tree, we need to copy that as well. - // Otherwise, any block mined by ourselves will cause gaps in the tree, - // and force the miner to operate trie-backed only - state.snaps = s.snaps - state.snap = s.snap - - // deep copy needed - state.snapAccounts = make(map[common.Hash][]byte, len(s.snapAccounts)) - for k, v := range s.snapAccounts { - state.snapAccounts[k] = v - } - state.snapStorage = make(map[common.Hash]map[common.Hash][]byte, len(s.snapStorage)) - for k, v := range s.snapStorage { - temp := make(map[common.Hash][]byte, len(v)) - for kk, vv := range v { - temp[kk] = vv - } - state.snapStorage[k] = temp - } - } return state } @@ -897,24 +887,26 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // Thus, we can safely ignore it here continue } - if obj.suicided || (deleteEmptyObjects && obj.empty()) { + if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { obj.deleted = true // We need to maintain account deletions explicitly (will remain - // set indefinitely). - s.stateObjectsDestruct[obj.address] = struct{}{} - - // If state snapshotting is active, also mark the destruction there. + // set indefinitely). Note only the first occurred self-destruct + // event is tracked. + if _, ok := s.stateObjectsDestruct[obj.address]; !ok { + s.stateObjectsDestruct[obj.address] = obj.origin + } // Note, we can't do this only at the end of a block because multiple // transactions within the same block might self destruct and then // resurrect an account; but the snapshotter needs both events. - if s.snap != nil { - delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) - delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) - } + delete(s.accounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(s.storages, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a resurrect) + delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) + delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) } else { obj.finalise(true) // Prefetch slots in the background } + obj.created = false s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} @@ -964,13 +956,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { sort.Slice(addressesToUpdate, func(i, j int) bool { return bytes.Compare(addressesToUpdate[i][:], addressesToUpdate[j][:]) < 0 }) for _, addr := range addressesToUpdate { if obj := s.stateObjects[addr]; !obj.deleted { - obj.updateRoot(s.db) + obj.updateRoot() } } } else { for addr := range s.stateObjectsPending { if obj := s.stateObjects[addr]; !obj.deleted { - obj.updateRoot(s.db) + obj.updateRoot() } } } @@ -1026,8 +1018,221 @@ func (s *StateDB) clearJournalAndRefund() { s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entries } +// fastDeleteStorage is the function that efficiently deletes the storage trie +// 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(addrHash common.Hash, root common.Hash) (bool, common.StorageSize, map[common.Hash][]byte, *trienode.NodeSet, error) { + iter, err := s.snaps.StorageIterator(s.originalRoot, addrHash, common.Hash{}) + if err != nil { + return false, 0, nil, nil, err + } + defer iter.Release() + + var ( + size common.StorageSize + nodes = trienode.NewNodeSet(addrHash) + slots = make(map[common.Hash][]byte) + ) + stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + nodes.AddNode(path, trienode.NewDeleted()) + size += common.StorageSize(len(path)) + }) + for iter.Next() { + if size > storageDeleteLimit { + return true, size, nil, nil, nil + } + slot := common.CopyBytes(iter.Slot()) + if err := iter.Error(); err != nil { // error might occur after Slot function + return false, 0, nil, nil, err + } + size += common.StorageSize(common.HashLength + len(slot)) + slots[iter.Hash()] = slot + + if err := stack.Update(iter.Hash().Bytes(), slot); err != nil { + return false, 0, nil, nil, err + } + } + if err := iter.Error(); err != nil { // error might occur during iteration + return false, 0, nil, nil, err + } + if stack.Hash() != root { + return false, 0, nil, nil, fmt.Errorf("snapshot is not matched, exp %x, got %x", root, stack.Hash()) + } + return false, size, slots, nodes, nil +} + +// 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) (bool, common.StorageSize, map[common.Hash][]byte, *trienode.NodeSet, error) { + tr, err := s.db.OpenStorageTrie(s.originalRoot, addr, root) + if err != nil { + return false, 0, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err) + } + it, err := tr.NodeIterator(nil) + if err != nil { + return false, 0, nil, nil, fmt.Errorf("failed to open storage iterator, err: %w", err) + } + var ( + size common.StorageSize + nodes = trienode.NewNodeSet(addrHash) + slots = make(map[common.Hash][]byte) + ) + for it.Next(true) { + if size > storageDeleteLimit { + return true, size, nil, nil, nil + } + if it.Leaf() { + slots[common.BytesToHash(it.LeafKey())] = common.CopyBytes(it.LeafBlob()) + size += common.StorageSize(common.HashLength + len(it.LeafBlob())) + continue + } + if it.Hash() == (common.Hash{}) { + continue + } + size += common.StorageSize(len(it.Path())) + nodes.AddNode(it.Path(), trienode.NewDeleted()) + } + if err := it.Error(); err != nil { + return false, 0, nil, nil, err + } + return false, size, slots, nodes, nil +} + +// deleteStorage is designed to delete the storage trie of a designated account. +// It could potentially be terminated if the storage size is excessively large, +// potentially leading to an out-of-memory panic. 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) (bool, map[common.Hash][]byte, *trienode.NodeSet, error) { + var ( + start = time.Now() + err error + aborted bool + size common.StorageSize + slots map[common.Hash][]byte + nodes *trienode.NodeSet + ) + // 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. + if s.snap != nil { + aborted, size, slots, nodes, err = s.fastDeleteStorage(addrHash, root) + } + if s.snap == nil || err != nil { + aborted, size, slots, nodes, err = s.slowDeleteStorage(addr, addrHash, root) + } + if err != nil { + return false, nil, nil, err + } + if metrics.EnabledExpensive { + if aborted { + slotDeletionSkip.Inc(1) + } + n := int64(len(slots)) + + slotDeletionMaxCount.UpdateIfGt(int64(len(slots))) + slotDeletionMaxSize.UpdateIfGt(int64(size)) + + slotDeletionTimer.UpdateSince(start) + slotDeletionCount.Mark(n) + slotDeletionSize.Mark(int64(size)) + } + return aborted, slots, nodes, nil +} + +// handleDestruction processes all destruction markers and deletes the account +// and associated storage slots if necessary. There are four possible situations +// here: +// +// - the account was not existent and be marked as destructed +// +// - the account was not existent and be marked as destructed, +// however, it's resurrected later in the same block. +// +// - the account was existent and be marked as destructed +// +// - the account was existent and be marked as destructed, +// however it's resurrected later in the same block. +// +// In case (a), nothing needs be deleted, nil to nil transition can be ignored. +// +// In case (b), nothing needs be deleted, nil is used as the original value for +// newly created account and storages +// +// In case (c), **original** account along with its storages should be deleted, +// 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(nodes *trienode.MergedNodeSet) (map[common.Address]struct{}, error) { + // Short circuit if geth is running with hash mode. This procedure can consume + // considerable time and storage deletion isn't supported in hash mode, thus + // preemptively avoiding unnecessary expenses. + incomplete := make(map[common.Address]struct{}) + if s.db.TrieDB().Scheme() == rawdb.HashScheme { + return incomplete, nil + } + for addr, prev := range s.stateObjectsDestruct { + // The original account was non-existing, and it's marked as destructed + // in the scope of block. It can be case (a) or (b). + // - for (a), skip it without doing anything. + // - for (b), track account's original value as nil. It may overwrite + // the data cached in s.accountsOrigin set by 'updateStateObject'. + addrHash := crypto.Keccak256Hash(addr[:]) + if prev == nil { + if _, ok := s.accounts[addrHash]; ok { + s.accountsOrigin[addr] = nil // case (b) + } + continue + } + // It can overwrite the data in s.accountsOrigin set by 'updateStateObject'. + s.accountsOrigin[addr] = types.SlimAccountRLP(*prev) // case (c) or (d) + + // Short circuit if the storage was empty. + if prev.Root == types.EmptyRootHash { + continue + } + // Remove storage slots belong to the account. + aborted, slots, set, err := s.deleteStorage(addr, addrHash, prev.Root) + if err != nil { + return nil, fmt.Errorf("failed to delete storage, err: %w", err) + } + // The storage is too huge to handle, skip it but mark as incomplete. + // For case (d), the account is resurrected might with a few slots + // created. In this case, wipe the entire storage state diff because + // of aborted deletion. + if aborted { + incomplete[addr] = struct{}{} + delete(s.storagesOrigin, addr) + continue + } + if s.storagesOrigin[addr] == nil { + s.storagesOrigin[addr] = slots + } else { + // It can overwrite the data in s.storagesOrigin[addrHash] set by + // 'object.updateTrie'. + for key, val := range slots { + s.storagesOrigin[addr][key] = val + } + } + if err := nodes.Merge(set); err != nil { + return nil, err + } + } + return incomplete, nil +} + // Commit writes the state to the underlying in-memory trie database. -func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { +// Once the state is committed, tries cached in stateDB (including account +// trie, storage tries) will no longer be functional. A new state instance +// must be created with new root and updated database for accessing post- +// commit states. +// +// The associated block number of the state transition is also provided +// for more chain context. +func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error) { // Short circuit in case any database failure occurred earlier. if s.dbErr != nil { return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr) @@ -1044,38 +1249,38 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { nodes = trienode.NewMergedNodeSet() codeWriter = s.db.DiskDB().NewBatch() ) + // Handle all state deletions first + incomplete, err := s.handleDestruction(nodes) + if err != nil { + return common.Hash{}, err + } + // Handle all state updates afterwards for addr := range s.stateObjectsDirty { - if obj := s.stateObjects[addr]; !obj.deleted { - // Write any contract code associated with the state object - if obj.code != nil && obj.dirtyCode { - rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code) - obj.dirtyCode = false - } - - // Write any storage changes in the state object to its storage trie - set, err := obj.commitTrie(s.db) - if err != nil { + obj := s.stateObjects[addr] + if obj.deleted { + continue + } + // Write any contract code associated with the state object + if obj.code != nil && obj.dirtyCode { + rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code) + obj.dirtyCode = false + } + // Write any storage changes in the state object to its storage trie + set, err := obj.commit() + if err != nil { + return common.Hash{}, err + } + // Merge the dirty nodes of storage trie into global set. It is possible + // that the account was destructed and then resurrected in the same block. + // In this case, the node set is shared by both accounts. + if set != nil { + if err := nodes.Merge(set); err != nil { return common.Hash{}, err } - // Merge the dirty nodes of storage trie into global set. - if set != nil { - if err := nodes.Merge(set); err != nil { - return common.Hash{}, err - } - updates, deleted := set.Size() - storageTrieNodesUpdated += updates - storageTrieNodesDeleted += deleted - } + updates, deleted := set.Size() + storageTrieNodesUpdated += updates + storageTrieNodesDeleted += deleted } - // If the contract is destructed, the storage is still left in the - // database as dangling data. Theoretically it's should be wiped from - // database as well, but in hash-based-scheme it's extremely hard to - // determine that if the trie nodes are also referenced by other storage, - // and in path-based-scheme some technical challenges are still unsolved. - // Although it won't affect the correctness but please fix it TODO(rjl493456442). - } - if len(s.stateObjectsDirty) > 0 { - s.stateObjectsDirty = make(map[common.Address]struct{}) } // Arbitrum: write Stylus programs to disk @@ -1096,7 +1301,10 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if metrics.EnabledExpensive { start = time.Now() } - root, set := s.trie.Commit(true) + root, set, err := s.trie.Commit(true) + if err != nil { + return common.Hash{}, err + } // Merge the dirty nodes of account trie into global set if set != nil { if err := nodes.Merge(set); err != nil { @@ -1123,7 +1331,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { start := time.Now() // Only update if there's a state transition (skip empty Clique blocks) if parent := s.snap.Root(); parent != root { - if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.snapAccounts, s.snapStorage); err != nil { + if err := s.snaps.Update(root, parent, s.convertAccountSet(s.stateObjectsDestruct), s.accounts, s.storages); err != nil { log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err) } // Keep 128 diff layers in the memory, persistent layer is 129th. @@ -1137,10 +1345,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if metrics.EnabledExpensive { s.SnapshotCommits += time.Since(start) } - s.snap, s.snapAccounts, s.snapStorage = nil, nil, nil - } - if len(s.stateObjectsDestruct) > 0 { - s.stateObjectsDestruct = make(map[common.Address]struct{}) + s.snap = nil } s.unexpectedBalanceDelta.Set(new(big.Int)) @@ -1154,14 +1359,25 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { } if root != origin { start := time.Now() - if err := s.db.TrieDB().Update(root, origin, nodes); err != nil { + set := triestate.New(s.accountsOrigin, s.storagesOrigin, incomplete) + if err := s.db.TrieDB().Update(root, origin, block, nodes, set); err != nil { return common.Hash{}, err } s.originalRoot = root if metrics.EnabledExpensive { s.TrieDBCommits += time.Since(start) } + if s.onCommit != nil { + s.onCommit(set) + } } + // Clear all internal flags at the end of commit operation. + s.accounts = make(map[common.Hash][]byte) + s.storages = make(map[common.Hash]map[common.Hash][]byte) + s.accountsOrigin = make(map[common.Address][]byte) + s.storagesOrigin = make(map[common.Address]map[common.Hash][]byte) + s.stateObjectsDirty = make(map[common.Address]struct{}) + s.stateObjectsDestruct = make(map[common.Address]*types.StateAccount) return root, nil } @@ -1242,7 +1458,7 @@ func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addre } // convertAccountSet converts a provided account set from address keyed to hash keyed. -func (s *StateDB) convertAccountSet(set map[common.Address]struct{}) map[common.Hash]struct{} { +func (s *StateDB) convertAccountSet(set map[common.Address]*types.StateAccount) map[common.Hash]struct{} { ret := make(map[common.Hash]struct{}, len(set)) for addr := range set { obj, exist := s.stateObjects[addr] @@ -1254,3 +1470,24 @@ func (s *StateDB) convertAccountSet(set map[common.Address]struct{}) map[common. } return ret } + +// copySet returns a deep-copied set. +func copySet[k comparable](set map[k][]byte) map[k][]byte { + copied := make(map[k][]byte, len(set)) + for key, val := range set { + copied[key] = common.CopyBytes(val) + } + return copied +} + +// copy2DSet returns a two-dimensional deep-copied set. +func copy2DSet[k comparable](set map[k]map[common.Hash][]byte) map[k]map[common.Hash][]byte { + copied := make(map[k]map[common.Hash][]byte, len(set)) + for addr, subset := range set { + copied[addr] = make(map[common.Hash][]byte, len(subset)) + for key, val := range subset { + copied[addr][key] = common.CopyBytes(val) + } + } + return copied +} diff --git a/core/state/statedb_arbitrum.go b/core/state/statedb_arbitrum.go index 98361b6a623e..7626e8ddb4ed 100644 --- a/core/state/statedb_arbitrum.go +++ b/core/state/statedb_arbitrum.go @@ -26,6 +26,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" ) var ( @@ -155,18 +157,61 @@ func (s *StateDB) GetUnexpectedBalanceDelta() *big.Int { return new(big.Int).Set(s.unexpectedBalanceDelta) } -func (s *StateDB) GetSuicides() []common.Address { - suicides := []common.Address{} +func (s *StateDB) GetSelfDestructs() []common.Address { + selfDestructs := []common.Address{} for addr := range s.journal.dirties { obj, exist := s.stateObjects[addr] if !exist { continue } - if obj.suicided { - suicides = append(suicides, addr) + if obj.selfDestructed { + selfDestructs = append(selfDestructs, addr) } } - return suicides + return selfDestructs +} + +// making the function public to be used by external tests +func ForEachStorage(s *StateDB, addr common.Address, cb func(key, value common.Hash) bool) error { + return forEachStorage(s, addr, cb) +} + +// moved here from statedb_test.go +func forEachStorage(s *StateDB, addr common.Address, cb func(key, value common.Hash) bool) error { + so := s.getStateObject(addr) + if so == nil { + return nil + } + tr, err := so.getTrie() + if err != nil { + return err + } + trieIt, err := tr.NodeIterator(nil) + if err != nil { + return err + } + it := trie.NewIterator(trieIt) + + for it.Next() { + key := common.BytesToHash(s.trie.GetKey(it.Key)) + if value, dirty := so.dirtyStorage[key]; dirty { + if !cb(key, value) { + return nil + } + continue + } + + if len(it.Value) > 0 { + _, content, _, err := rlp.Split(it.Value) + if err != nil { + return err + } + if !cb(key, common.BytesToHash(content)) { + return nil + } + } + } + return nil } // maps moduleHash to activation info diff --git a/core/state/statedb_fuzz_test.go b/core/state/statedb_fuzz_test.go new file mode 100644 index 000000000000..6408f3bdecee --- /dev/null +++ b/core/state/statedb_fuzz_test.go @@ -0,0 +1,394 @@ +// Copyright 2023 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 ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "math" + "math/big" + "math/rand" + "reflect" + "strings" + "testing" + "testing/quick" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +// A stateTest checks that the state changes are correctly captured. Instances +// of this test with pseudorandom content are created by Generate. +// +// The test works as follows: +// +// A list of states are created by applying actions. The state changes between +// each state instance are tracked and be verified. +type stateTest struct { + addrs []common.Address // all account addresses + actions [][]testAction // modifications to the state, grouped by block + chunk int // The number of actions per chunk + err error // failure details are reported through this field +} + +// newStateTestAction creates a random action that changes state. +func newStateTestAction(addr common.Address, r *rand.Rand, index int) testAction { + actions := []testAction{ + { + name: "SetBalance", + fn: func(a testAction, s *StateDB) { + s.SetBalance(addr, big.NewInt(a.args[0])) + }, + args: make([]int64, 1), + }, + { + name: "SetNonce", + fn: func(a testAction, s *StateDB) { + s.SetNonce(addr, uint64(a.args[0])) + }, + args: make([]int64, 1), + }, + { + name: "SetState", + fn: func(a testAction, s *StateDB) { + var key, val common.Hash + binary.BigEndian.PutUint16(key[:], uint16(a.args[0])) + binary.BigEndian.PutUint16(val[:], uint16(a.args[1])) + s.SetState(addr, key, val) + }, + args: make([]int64, 2), + }, + { + name: "SetCode", + fn: func(a testAction, s *StateDB) { + code := make([]byte, 16) + binary.BigEndian.PutUint64(code, uint64(a.args[0])) + binary.BigEndian.PutUint64(code[8:], uint64(a.args[1])) + s.SetCode(addr, code) + }, + args: make([]int64, 2), + }, + { + name: "CreateAccount", + fn: func(a testAction, s *StateDB) { + s.CreateAccount(addr) + }, + }, + { + name: "Selfdestruct", + fn: func(a testAction, s *StateDB) { + s.SelfDestruct(addr) + }, + }, + } + var nonRandom = index != -1 + if index == -1 { + index = r.Intn(len(actions)) + } + action := actions[index] + var names []string + if !action.noAddr { + names = append(names, addr.Hex()) + } + for i := range action.args { + if nonRandom { + action.args[i] = rand.Int63n(10000) + 1 // set balance to non-zero + } else { + action.args[i] = rand.Int63n(10000) + } + names = append(names, fmt.Sprint(action.args[i])) + } + action.name += " " + strings.Join(names, ", ") + return action +} + +// Generate returns a new snapshot test of the given size. All randomness is +// derived from r. +func (*stateTest) Generate(r *rand.Rand, size int) reflect.Value { + addrs := make([]common.Address, 5) + for i := range addrs { + addrs[i][0] = byte(i) + } + actions := make([][]testAction, rand.Intn(5)+1) + + for i := 0; i < len(actions); i++ { + actions[i] = make([]testAction, size) + for j := range actions[i] { + if j == 0 { + // Always include a set balance action to make sure + // the state changes are not empty. + actions[i][j] = newStateTestAction(common.HexToAddress("0xdeadbeef"), r, 0) + continue + } + actions[i][j] = newStateTestAction(addrs[r.Intn(len(addrs))], r, -1) + } + } + chunk := int(math.Sqrt(float64(size))) + if size > 0 && chunk == 0 { + chunk = 1 + } + return reflect.ValueOf(&stateTest{ + addrs: addrs, + actions: actions, + chunk: chunk, + }) +} + +func (test *stateTest) String() string { + out := new(bytes.Buffer) + for i, actions := range test.actions { + fmt.Fprintf(out, "---- block %d ----\n", i) + for j, action := range actions { + if j%test.chunk == 0 { + fmt.Fprintf(out, "---- transaction %d ----\n", j/test.chunk) + } + fmt.Fprintf(out, "%4d: %s\n", j%test.chunk, action.name) + } + } + return out.String() +} + +func (test *stateTest) run() bool { + var ( + roots []common.Hash + accountList []map[common.Address][]byte + storageList []map[common.Address]map[common.Hash][]byte + onCommit = func(states *triestate.Set) { + accountList = append(accountList, copySet(states.Accounts)) + storageList = append(storageList, copy2DSet(states.Storages)) + } + disk = rawdb.NewMemoryDatabase() + tdb = trie.NewDatabase(disk, &trie.Config{PathDB: pathdb.Defaults}) + sdb = NewDatabaseWithNodeDB(disk, tdb) + byzantium = rand.Intn(2) == 0 + ) + defer disk.Close() + defer tdb.Close() + + var snaps *snapshot.Tree + if rand.Intn(3) == 0 { + snaps, _ = snapshot.New(snapshot.Config{ + CacheSize: 1, + Recovery: false, + NoBuild: false, + AsyncBuild: false, + }, disk, tdb, types.EmptyRootHash) + } + for i, actions := range test.actions { + root := types.EmptyRootHash + if i != 0 { + root = roots[len(roots)-1] + } + state, err := New(root, sdb, snaps) + if err != nil { + panic(err) + } + state.onCommit = onCommit + + for i, action := range actions { + if i%test.chunk == 0 && i != 0 { + if byzantium { + state.Finalise(true) // call finalise at the transaction boundary + } else { + state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary + } + } + action.fn(action, state) + } + if byzantium { + state.Finalise(true) // call finalise at the transaction boundary + } else { + state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary + } + nroot, err := state.Commit(0, true) // call commit at the block boundary + if err != nil { + panic(err) + } + if nroot == root { + return true // filter out non-change state transition + } + roots = append(roots, nroot) + } + for i := 0; i < len(test.actions); i++ { + root := types.EmptyRootHash + if i != 0 { + root = roots[i-1] + } + test.err = test.verify(root, roots[i], tdb, accountList[i], storageList[i]) + if test.err != nil { + return false + } + } + return true +} + +// verifyAccountCreation this function is called once the state diff says that +// specific account was not present. A serial of checks will be performed to +// ensure the state diff is correct, includes: +// +// - the account was indeed not present in trie +// - the account is present in new trie, nil->nil is regarded as invalid +// - the slots transition is correct +func (test *stateTest) verifyAccountCreation(next common.Hash, db *trie.Database, otr, ntr *trie.Trie, addr common.Address, slots map[common.Hash][]byte) error { + // Verify account change + addrHash := crypto.Keccak256Hash(addr.Bytes()) + oBlob, err := otr.Get(addrHash.Bytes()) + if err != nil { + return err + } + nBlob, err := ntr.Get(addrHash.Bytes()) + if err != nil { + return err + } + if len(oBlob) != 0 { + return fmt.Errorf("unexpected account in old trie, %x", addrHash) + } + if len(nBlob) == 0 { + return fmt.Errorf("missing account in new trie, %x", addrHash) + } + + // Verify storage changes + var nAcct types.StateAccount + if err := rlp.DecodeBytes(nBlob, &nAcct); err != nil { + return err + } + // Account has no slot, empty slot set is expected + if nAcct.Root == types.EmptyRootHash { + if len(slots) != 0 { + return fmt.Errorf("unexpected slot changes %x", addrHash) + } + return nil + } + // Account has slots, ensure all new slots are contained + st, err := trie.New(trie.StorageTrieID(next, addrHash, nAcct.Root), db) + if err != nil { + return err + } + for key, val := range slots { + st.Update(key.Bytes(), val) + } + if st.Hash() != types.EmptyRootHash { + return errors.New("invalid slot changes") + } + return nil +} + +// verifyAccountUpdate this function is called once the state diff says that +// specific account was present. A serial of checks will be performed to +// ensure the state diff is correct, includes: +// +// - the account was indeed present in trie +// - the account in old trie matches the provided value +// - the slots transition is correct +func (test *stateTest) verifyAccountUpdate(next common.Hash, db *trie.Database, otr, ntr *trie.Trie, addr common.Address, origin []byte, slots map[common.Hash][]byte) error { + // Verify account change + addrHash := crypto.Keccak256Hash(addr.Bytes()) + oBlob, err := otr.Get(addrHash.Bytes()) + if err != nil { + return err + } + nBlob, err := ntr.Get(addrHash.Bytes()) + if err != nil { + return err + } + if len(oBlob) == 0 { + return fmt.Errorf("missing account in old trie, %x", addrHash) + } + full, err := types.FullAccountRLP(origin) + if err != nil { + return err + } + if !bytes.Equal(full, oBlob) { + return fmt.Errorf("account value is not matched, %x", addrHash) + } + + // Decode accounts + var ( + oAcct types.StateAccount + nAcct types.StateAccount + nRoot common.Hash + ) + if err := rlp.DecodeBytes(oBlob, &oAcct); err != nil { + return err + } + if len(nBlob) == 0 { + nRoot = types.EmptyRootHash + } else { + if err := rlp.DecodeBytes(nBlob, &nAcct); err != nil { + return err + } + nRoot = nAcct.Root + } + + // Verify storage + st, err := trie.New(trie.StorageTrieID(next, addrHash, nRoot), db) + if err != nil { + return err + } + for key, val := range slots { + st.Update(key.Bytes(), val) + } + if st.Hash() != oAcct.Root { + return errors.New("invalid slot changes") + } + return nil +} + +func (test *stateTest) verify(root common.Hash, next common.Hash, db *trie.Database, accountsOrigin map[common.Address][]byte, storagesOrigin map[common.Address]map[common.Hash][]byte) error { + otr, err := trie.New(trie.StateTrieID(root), db) + if err != nil { + return err + } + ntr, err := trie.New(trie.StateTrieID(next), db) + if err != nil { + return err + } + for addr, account := range accountsOrigin { + var err error + if len(account) == 0 { + err = test.verifyAccountCreation(next, db, otr, ntr, addr, storagesOrigin[addr]) + } else { + err = test.verifyAccountUpdate(next, db, otr, ntr, addr, accountsOrigin[addr], storagesOrigin[addr]) + } + if err != nil { + return err + } + } + return nil +} + +func TestStateChanges(t *testing.T) { + t.Skip("Arbitrum doesn't support some of these operations due to https://github.com/OffchainLabs/go-ethereum/pull/271") + + config := &quick.Config{MaxCount: 1000} + err := quick.Check((*stateTest).run, config) + if cerr, ok := err.(*quick.CheckError); ok { + test := cerr.In[0].(*stateTest) + t.Errorf("%v:\n%s", test.err, test) + } else if err != nil { + t.Error(err) + } +} diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index e73b1b8c6e53..84e1d2590f3d 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -19,6 +19,7 @@ package state import ( "bytes" "encoding/binary" + "errors" "fmt" "math" "math/big" @@ -31,15 +32,25 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/hashdb" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/holiman/uint256" ) // Tests that updating a state trie does not leak any database writes prior to // actually committing the state. func TestUpdateLeaks(t *testing.T) { // Create an empty state database - db := rawdb.NewMemoryDatabase() - state, _ := New(types.EmptyRootHash, NewDatabase(db), nil) + var ( + db = rawdb.NewMemoryDatabase() + tdb = trie.NewDatabase(db, nil) + ) + state, _ := New(types.EmptyRootHash, NewDatabaseWithNodeDB(db, tdb), nil) // Update it with some accounts for i := byte(0); i < 255; i++ { @@ -55,7 +66,7 @@ func TestUpdateLeaks(t *testing.T) { } root := state.IntermediateRoot(false) - if err := state.Database().TrieDB().Commit(root, false); err != nil { + if err := tdb.Commit(root, false); err != nil { t.Errorf("can not commit trie %v to persistent database", root.Hex()) } @@ -73,8 +84,10 @@ func TestIntermediateLeaks(t *testing.T) { // Create two state databases, one transitioning to the final state, the other final from the beginning transDb := rawdb.NewMemoryDatabase() finalDb := rawdb.NewMemoryDatabase() - transState, _ := New(types.EmptyRootHash, NewDatabase(transDb), nil) - finalState, _ := New(types.EmptyRootHash, NewDatabase(finalDb), nil) + transNdb := trie.NewDatabase(transDb, nil) + finalNdb := trie.NewDatabase(finalDb, nil) + transState, _ := New(types.EmptyRootHash, NewDatabaseWithNodeDB(transDb, transNdb), nil) + finalState, _ := New(types.EmptyRootHash, NewDatabaseWithNodeDB(finalDb, finalNdb), nil) modify := func(state *StateDB, addr common.Address, i, tweak byte) { state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak))) @@ -102,19 +115,19 @@ func TestIntermediateLeaks(t *testing.T) { } // Commit and cross check the databases. - transRoot, err := transState.Commit(false) + transRoot, err := transState.Commit(0, false) if err != nil { t.Fatalf("failed to commit transition state: %v", err) } - if err = transState.Database().TrieDB().Commit(transRoot, false); err != nil { + if err = transNdb.Commit(transRoot, false); err != nil { t.Errorf("can not commit trie %v to persistent database", transRoot.Hex()) } - finalRoot, err := finalState.Commit(false) + finalRoot, err := finalState.Commit(0, false) if err != nil { t.Fatalf("failed to commit final state: %v", err) } - if err = finalState.Database().TrieDB().Commit(finalRoot, false); err != nil { + if err = finalNdb.Commit(finalRoot, false); err != nil { t.Errorf("can not commit trie %v to persistent database", finalRoot.Hex()) } @@ -297,9 +310,9 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction { }, }, { - name: "Suicide", + name: "SelfDestruct", fn: func(a testAction, s *StateDB) { - s.Suicide(addr) + s.SelfDestruct(addr) }, }, { @@ -449,7 +462,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error { } // Check basic accessor methods. checkeq("Exist", state.Exist(addr), checkstate.Exist(addr)) - checkeq("HasSuicided", state.HasSuicided(addr), checkstate.HasSuicided(addr)) + checkeq("HasSelfdestructed", state.HasSelfDestructed(addr), checkstate.HasSelfDestructed(addr)) checkeq("GetBalance", state.GetBalance(addr), checkstate.GetBalance(addr)) checkeq("GetNonce", state.GetNonce(addr), checkstate.GetNonce(addr)) checkeq("GetCode", state.GetCode(addr), checkstate.GetCode(addr)) @@ -457,10 +470,10 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error { checkeq("GetCodeSize", state.GetCodeSize(addr), checkstate.GetCodeSize(addr)) // Check storage. if obj := state.getStateObject(addr); obj != nil { - state.ForEachStorage(addr, func(key, value common.Hash) bool { + forEachStorage(state, addr, func(key, value common.Hash) bool { return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value) }) - checkstate.ForEachStorage(addr, func(key, value common.Hash) bool { + forEachStorage(checkstate, addr, func(key, value common.Hash) bool { return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value) }) } @@ -481,9 +494,9 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error { } func TestTouchDelete(t *testing.T) { - s := newStateTest() + s := newStateEnv() s.state.GetOrNewStateObject(common.Address{}) - root, _ := s.state.Commit(false) + root, _ := s.state.Commit(0, false) s.state, _ = New(root, s.state.db, s.state.snaps) snapshot := s.state.Snapshot() @@ -518,7 +531,8 @@ func TestCopyOfCopy(t *testing.T) { // // See https://github.com/ethereum/go-ethereum/issues/20106. func TestCopyCommitCopy(t *testing.T) { - state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) + tdb := NewDatabase(rawdb.NewMemoryDatabase()) + state, _ := New(types.EmptyRootHash, tdb, nil) // Create an account and check if the retrieved balance is correct addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") @@ -555,20 +569,6 @@ func TestCopyCommitCopy(t *testing.T) { if val := copyOne.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("first copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } - - copyOne.Commit(false) - if balance := copyOne.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { - t.Fatalf("first copy post-commit balance mismatch: have %v, want %v", balance, 42) - } - if code := copyOne.GetCode(addr); !bytes.Equal(code, []byte("hello")) { - t.Fatalf("first copy post-commit code mismatch: have %x, want %x", code, []byte("hello")) - } - if val := copyOne.GetState(addr, skey); val != sval { - t.Fatalf("first copy post-commit non-committed storage slot mismatch: have %x, want %x", val, sval) - } - if val := copyOne.GetCommittedState(addr, skey); val != sval { - t.Fatalf("first copy post-commit committed storage slot mismatch: have %x, want %x", val, sval) - } // Copy the copy and check the balance once more copyTwo := copyOne.Copy() if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { @@ -580,8 +580,23 @@ func TestCopyCommitCopy(t *testing.T) { if val := copyTwo.GetState(addr, skey); val != sval { t.Fatalf("second copy non-committed storage slot mismatch: have %x, want %x", val, sval) } - if val := copyTwo.GetCommittedState(addr, skey); val != sval { - t.Fatalf("second copy post-commit committed storage slot mismatch: have %x, want %x", val, sval) + if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) { + t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, sval) + } + // Commit state, ensure states can be loaded from disk + root, _ := state.Commit(0, false) + state, _ = New(root, tdb, nil) + if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { + t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42) + } + if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) { + t.Fatalf("state post-commit code mismatch: have %x, want %x", code, []byte("hello")) + } + if val := state.GetState(addr, skey); val != sval { + t.Fatalf("state post-commit non-committed storage slot mismatch: have %x, want %x", val, sval) + } + if val := state.GetCommittedState(addr, skey); val != sval { + t.Fatalf("state post-commit committed storage slot mismatch: have %x, want %x", val, sval) } } @@ -641,19 +656,6 @@ func TestCopyCopyCommitCopy(t *testing.T) { if val := copyTwo.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("second copy pre-commit committed storage slot mismatch: have %x, want %x", val, common.Hash{}) } - copyTwo.Commit(false) - if balance := copyTwo.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { - t.Fatalf("second copy post-commit balance mismatch: have %v, want %v", balance, 42) - } - if code := copyTwo.GetCode(addr); !bytes.Equal(code, []byte("hello")) { - t.Fatalf("second copy post-commit code mismatch: have %x, want %x", code, []byte("hello")) - } - if val := copyTwo.GetState(addr, skey); val != sval { - t.Fatalf("second copy post-commit non-committed storage slot mismatch: have %x, want %x", val, sval) - } - if val := copyTwo.GetCommittedState(addr, skey); val != sval { - t.Fatalf("second copy post-commit committed storage slot mismatch: have %x, want %x", val, sval) - } // Copy the copy-copy and check the balance once more copyThree := copyTwo.Copy() if balance := copyThree.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { @@ -665,11 +667,56 @@ func TestCopyCopyCommitCopy(t *testing.T) { if val := copyThree.GetState(addr, skey); val != sval { t.Fatalf("third copy non-committed storage slot mismatch: have %x, want %x", val, sval) } - if val := copyThree.GetCommittedState(addr, skey); val != sval { + if val := copyThree.GetCommittedState(addr, skey); val != (common.Hash{}) { t.Fatalf("third copy committed storage slot mismatch: have %x, want %x", val, sval) } } +// TestCommitCopy tests the copy from a committed state is not functional. +func TestCommitCopy(t *testing.T) { + state, _ := New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) + + // Create an account and check if the retrieved balance is correct + addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") + skey := common.HexToHash("aaa") + sval := common.HexToHash("bbb") + + state.SetBalance(addr, big.NewInt(42)) // Change the account trie + state.SetCode(addr, []byte("hello")) // Change an external metadata + state.SetState(addr, skey, sval) // Change the storage trie + + if balance := state.GetBalance(addr); balance.Cmp(big.NewInt(42)) != 0 { + t.Fatalf("initial balance mismatch: have %v, want %v", balance, 42) + } + if code := state.GetCode(addr); !bytes.Equal(code, []byte("hello")) { + t.Fatalf("initial code mismatch: have %x, want %x", code, []byte("hello")) + } + if val := state.GetState(addr, skey); val != sval { + t.Fatalf("initial non-committed storage slot mismatch: have %x, want %x", val, sval) + } + if val := state.GetCommittedState(addr, skey); val != (common.Hash{}) { + t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{}) + } + // Copy the committed state database, the copied one is not functional. + state.Commit(0, true) + copied := state.Copy() + if balance := copied.GetBalance(addr); balance.Cmp(big.NewInt(0)) != 0 { + t.Fatalf("unexpected balance: have %v", balance) + } + if code := copied.GetCode(addr); code != nil { + t.Fatalf("unexpected code: have %x", code) + } + if val := copied.GetState(addr, skey); val != (common.Hash{}) { + t.Fatalf("unexpected storage slot: have %x", val) + } + if val := copied.GetCommittedState(addr, skey); val != (common.Hash{}) { + t.Fatalf("unexpected storage slot: have %x", val) + } + if !errors.Is(copied.Error(), trie.ErrCommitted) { + t.Fatalf("unexpected state error, %v", copied.Error()) + } +} + // TestDeleteCreateRevert tests a weird state transition corner case that we hit // while changing the internals of StateDB. The workflow is that a contract is // self-destructed, then in a follow-up transaction (but same block) it's created @@ -685,11 +732,11 @@ func TestDeleteCreateRevert(t *testing.T) { addr := common.BytesToAddress([]byte("so")) state.SetBalance(addr, big.NewInt(1)) - root, _ := state.Commit(false) + root, _ := state.Commit(0, false) state, _ = New(root, state.db, state.snaps) // Simulate self-destructing in one transaction, then create-reverting in another - state.Suicide(addr) + state.SelfDestruct(addr) state.Finalise(true) id := state.Snapshot() @@ -697,7 +744,7 @@ func TestDeleteCreateRevert(t *testing.T) { state.RevertToSnapshot(id) // Commit the entire state and make sure we don't crash and have the correct state - root, _ = state.Commit(true) + root, _ = state.Commit(0, true) state, _ = New(root, state.db, state.snaps) if state.getStateObject(addr) != nil { @@ -709,9 +756,28 @@ func TestDeleteCreateRevert(t *testing.T) { // the Commit operation fails with an error // If we are missing trie nodes, we should not continue writing to the trie func TestMissingTrieNodes(t *testing.T) { + testMissingTrieNodes(t, rawdb.HashScheme) + testMissingTrieNodes(t, rawdb.PathScheme) +} + +func testMissingTrieNodes(t *testing.T, scheme string) { // Create an initial state with a few accounts - memDb := rawdb.NewMemoryDatabase() - db := NewDatabase(memDb) + var ( + triedb *trie.Database + memDb = rawdb.NewMemoryDatabase() + ) + if scheme == rawdb.PathScheme { + triedb = trie.NewDatabase(memDb, &trie.Config{PathDB: &pathdb.Config{ + CleanCacheSize: 0, + DirtyCacheSize: 0, + }}) // disable caching + } else { + triedb = trie.NewDatabase(memDb, &trie.Config{HashDB: &hashdb.Config{ + CleanCacheSize: 0, + }}) // disable caching + } + db := NewDatabaseWithNodeDB(memDb, triedb) + var root common.Hash state, _ := New(types.EmptyRootHash, db, nil) addr := common.BytesToAddress([]byte("so")) @@ -721,10 +787,10 @@ func TestMissingTrieNodes(t *testing.T) { a2 := common.BytesToAddress([]byte("another")) state.SetBalance(a2, big.NewInt(100)) state.SetCode(a2, []byte{1, 2, 4}) - root, _ = state.Commit(false) + root, _ = state.Commit(0, false) t.Logf("root: %x", root) // force-flush - state.Database().TrieDB().Cap(0) + triedb.Commit(root, false) } // Create a new state on the old root state, _ = New(root, db, nil) @@ -745,7 +811,7 @@ func TestMissingTrieNodes(t *testing.T) { } // Modify the state state.SetBalance(addr, big.NewInt(2)) - root, err := state.Commit(false) + root, err := state.Commit(0, false) if err == nil { t.Fatalf("expected error, got root :%x", root) } @@ -931,7 +997,8 @@ func TestFlushOrderDataLoss(t *testing.T) { // Create a state trie with many accounts and slots var ( memdb = rawdb.NewMemoryDatabase() - statedb = NewDatabase(memdb) + triedb = trie.NewDatabase(memdb, nil) + statedb = NewDatabaseWithNodeDB(memdb, triedb) state, _ = New(types.EmptyRootHash, statedb, nil) ) for a := byte(0); a < 10; a++ { @@ -940,15 +1007,15 @@ func TestFlushOrderDataLoss(t *testing.T) { state.SetState(common.Address{a}, common.Hash{a, s}, common.Hash{a, s}) } } - root, err := state.Commit(false) + root, err := state.Commit(0, false) if err != nil { t.Fatalf("failed to commit state trie: %v", err) } - statedb.TrieDB().Reference(root, common.Hash{}) - if err := statedb.TrieDB().Cap(1024); err != nil { + triedb.Reference(root, common.Hash{}) + if err := triedb.Cap(1024); err != nil { t.Fatalf("failed to cap trie dirty cache: %v", err) } - if err := statedb.TrieDB().Commit(root, false); err != nil { + if err := triedb.Commit(root, false); err != nil { t.Fatalf("failed to commit state trie: %v", err) } // Reopen the state trie from flushed disk and verify it @@ -998,3 +1065,91 @@ func TestStateDBTransientStorage(t *testing.T) { t.Fatalf("transient storage mismatch: have %x, want %x", got, value) } } + +func TestResetObject(t *testing.T) { + var ( + disk = rawdb.NewMemoryDatabase() + tdb = trie.NewDatabase(disk, nil) + db = NewDatabaseWithNodeDB(disk, tdb) + snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash) + state, _ = New(types.EmptyRootHash, db, snaps) + addr = common.HexToAddress("0x1") + slotA = common.HexToHash("0x1") + slotB = common.HexToHash("0x2") + ) + // Initialize account with balance and storage in first transaction. + state.SetBalance(addr, big.NewInt(1)) + state.SetState(addr, slotA, common.BytesToHash([]byte{0x1})) + state.IntermediateRoot(true) + + // Reset account and mutate balance and storages + state.CreateAccount(addr) + state.SetBalance(addr, big.NewInt(2)) + state.SetState(addr, slotB, common.BytesToHash([]byte{0x2})) + root, _ := state.Commit(0, true) + + // Ensure the original account is wiped properly + snap := snaps.Snapshot(root) + slot, _ := snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotA.Bytes())) + if len(slot) != 0 { + t.Fatalf("Unexpected storage slot") + } + slot, _ = snap.Storage(crypto.Keccak256Hash(addr.Bytes()), crypto.Keccak256Hash(slotB.Bytes())) + if !bytes.Equal(slot, []byte{0x2}) { + t.Fatalf("Unexpected storage slot value %v", slot) + } +} + +func TestDeleteStorage(t *testing.T) { + var ( + disk = rawdb.NewMemoryDatabase() + tdb = trie.NewDatabase(disk, nil) + db = NewDatabaseWithNodeDB(disk, tdb) + snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash) + state, _ = New(types.EmptyRootHash, db, snaps) + addr = common.HexToAddress("0x1") + ) + // Initialize account and populate storage + state.SetBalance(addr, big.NewInt(1)) + state.CreateAccount(addr) + for i := 0; i < 1000; i++ { + slot := common.Hash(uint256.NewInt(uint64(i)).Bytes32()) + value := common.Hash(uint256.NewInt(uint64(10 * i)).Bytes32()) + state.SetState(addr, slot, value) + } + root, _ := state.Commit(0, true) + // Init phase done, create two states, one with snap and one without + fastState, _ := New(root, db, snaps) + slowState, _ := New(root, db, nil) + + obj := fastState.GetOrNewStateObject(addr) + storageRoot := obj.data.Root + + _, _, fastNodes, err := fastState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot) + if err != nil { + t.Fatal(err) + } + + _, _, slowNodes, err := slowState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot) + if err != nil { + t.Fatal(err) + } + check := func(set *trienode.NodeSet) string { + var a []string + set.ForEachWithOrder(func(path string, n *trienode.Node) { + if n.Hash != (common.Hash{}) { + t.Fatal("delete should have empty hashes") + } + if len(n.Blob) != 0 { + t.Fatal("delete should have have empty blobs") + } + a = append(a, fmt.Sprintf("%x", path)) + }) + return strings.Join(a, ",") + } + slowRes := check(slowNodes) + fastRes := check(fastNodes) + if slowRes != fastRes { + t.Fatalf("difference found:\nfast: %v\nslow: %v\n", fastRes, slowRes) + } +} diff --git a/core/state/sync.go b/core/state/sync.go index 61097c6462d4..d6775e889610 100644 --- a/core/state/sync.go +++ b/core/state/sync.go @@ -17,8 +17,6 @@ package state import ( - "bytes" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" @@ -45,7 +43,7 @@ func NewStateSync(root common.Hash, database ethdb.KeyValueReader, onLeaf func(k } } var obj types.StateAccount - if err := rlp.Decode(bytes.NewReader(leaf), &obj); err != nil { + if err := rlp.DecodeBytes(leaf, &obj); err != nil { return err } syncer.AddSubTrie(obj.Root, path, parent, parentPath, onSlot) diff --git a/core/state/sync_test.go b/core/state/sync_test.go index 6e9d9342ee80..6196e77817e2 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -28,6 +28,8 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/hashdb" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" ) // testAccount is the data associated with an account used by the state tests. @@ -39,10 +41,17 @@ type testAccount struct { } // makeTestState create a sample test state to test node-wise reconstruction. -func makeTestState() (ethdb.Database, Database, common.Hash, []*testAccount) { +func makeTestState(scheme string) (ethdb.Database, Database, *trie.Database, common.Hash, []*testAccount) { // Create an empty state + config := &trie.Config{Preimages: true} + if scheme == rawdb.PathScheme { + config.PathDB = pathdb.Defaults + } else { + config.HashDB = hashdb.Defaults + } db := rawdb.NewMemoryDatabase() - sdb := NewDatabase(db) + nodeDb := trie.NewDatabase(db, config) + sdb := NewDatabaseWithNodeDB(db, nodeDb) state, _ := New(types.EmptyRootHash, sdb, nil) // Fill it with some arbitrary data @@ -64,27 +73,30 @@ func makeTestState() (ethdb.Database, Database, common.Hash, []*testAccount) { if i%5 == 0 { for j := byte(0); j < 5; j++ { hash := crypto.Keccak256Hash([]byte{i, i, i, i, i, j, j}) - obj.SetState(sdb, hash, hash) + obj.SetState(hash, hash) } } - state.updateStateObject(obj) accounts = append(accounts, acc) } - root, _ := state.Commit(false) + root, _ := state.Commit(0, false) // Return the generated state - return db, sdb, root, accounts + return db, sdb, nodeDb, root, accounts } // checkStateAccounts cross references a reconstructed state with an expected // account array. -func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accounts []*testAccount) { +func checkStateAccounts(t *testing.T, db ethdb.Database, scheme string, root common.Hash, accounts []*testAccount) { + var config trie.Config + if scheme == rawdb.PathScheme { + config.PathDB = pathdb.Defaults + } // Check root availability and state contents - state, err := New(root, NewDatabase(db), nil) + state, err := New(root, NewDatabaseWithConfig(db, &config), nil) if err != nil { t.Fatalf("failed to create state trie at %x: %v", root, err) } - if err := checkStateConsistency(db, root); err != nil { + if err := checkStateConsistency(db, scheme, root); err != nil { t.Fatalf("inconsistent state trie at %x: %v", root, err) } for i, acc := range accounts { @@ -100,28 +112,13 @@ func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accou } } -// checkTrieConsistency checks that all nodes in a (sub-)trie are indeed present. -func checkTrieConsistency(db ethdb.Database, root common.Hash) error { - if v, _ := db.Get(root[:]); v == nil { - return nil // Consider a non existent state consistent. - } - trie, err := trie.New(trie.StateTrieID(root), trie.NewDatabase(db)) - if err != nil { - return err - } - it := trie.NodeIterator(nil) - for it.Next(true) { - } - return it.Error() -} - // checkStateConsistency checks that all data of a state root is present. -func checkStateConsistency(db ethdb.Database, root common.Hash) error { - // Create and iterate a state trie rooted in a sub-node - if _, err := db.Get(root.Bytes()); err != nil { - return nil // Consider a non existent state consistent. +func checkStateConsistency(db ethdb.Database, scheme string, root common.Hash) error { + config := &trie.Config{Preimages: true} + if scheme == rawdb.PathScheme { + config.PathDB = pathdb.Defaults } - state, err := New(root, NewDatabase(db), nil) + state, err := New(root, NewDatabaseWithConfig(db, config), nil) if err != nil { return err } @@ -133,8 +130,14 @@ func checkStateConsistency(db ethdb.Database, root common.Hash) error { // Tests that an empty state is not scheduled for syncing. func TestEmptyStateSync(t *testing.T) { - db := trie.NewDatabase(rawdb.NewMemoryDatabase()) - sync := NewStateSync(types.EmptyRootHash, rawdb.NewMemoryDatabase(), nil, db.Scheme()) + dbA := trie.NewDatabase(rawdb.NewMemoryDatabase(), nil) + dbB := trie.NewDatabase(rawdb.NewMemoryDatabase(), &trie.Config{PathDB: pathdb.Defaults}) + + sync := NewStateSync(types.EmptyRootHash, rawdb.NewMemoryDatabase(), nil, dbA.Scheme()) + if paths, nodes, codes := sync.Missing(1); len(paths) != 0 || len(nodes) != 0 || len(codes) != 0 { + t.Errorf("content requested for empty state: %v, %v, %v", nodes, paths, codes) + } + sync = NewStateSync(types.EmptyRootHash, rawdb.NewMemoryDatabase(), nil, dbB.Scheme()) if paths, nodes, codes := sync.Missing(1); len(paths) != 0 || len(nodes) != 0 || len(codes) != 0 { t.Errorf("content requested for empty state: %v, %v, %v", nodes, paths, codes) } @@ -143,22 +146,28 @@ func TestEmptyStateSync(t *testing.T) { // Tests that given a root hash, a state can sync iteratively on a single thread, // requesting retrieval tasks and returning all of them in one go. func TestIterativeStateSyncIndividual(t *testing.T) { - testIterativeStateSync(t, 1, false, false) + testIterativeStateSync(t, 1, false, false, rawdb.HashScheme) + testIterativeStateSync(t, 1, false, false, rawdb.PathScheme) } func TestIterativeStateSyncBatched(t *testing.T) { - testIterativeStateSync(t, 100, false, false) + testIterativeStateSync(t, 100, false, false, rawdb.HashScheme) + testIterativeStateSync(t, 100, false, false, rawdb.PathScheme) } func TestIterativeStateSyncIndividualFromDisk(t *testing.T) { - testIterativeStateSync(t, 1, true, false) + testIterativeStateSync(t, 1, true, false, rawdb.HashScheme) + testIterativeStateSync(t, 1, true, false, rawdb.PathScheme) } func TestIterativeStateSyncBatchedFromDisk(t *testing.T) { - testIterativeStateSync(t, 100, true, false) + testIterativeStateSync(t, 100, true, false, rawdb.HashScheme) + testIterativeStateSync(t, 100, true, false, rawdb.PathScheme) } func TestIterativeStateSyncIndividualByPath(t *testing.T) { - testIterativeStateSync(t, 1, false, true) + testIterativeStateSync(t, 1, false, true, rawdb.HashScheme) + testIterativeStateSync(t, 1, false, true, rawdb.PathScheme) } func TestIterativeStateSyncBatchedByPath(t *testing.T) { - testIterativeStateSync(t, 100, false, true) + testIterativeStateSync(t, 100, false, true, rawdb.HashScheme) + testIterativeStateSync(t, 100, false, true, rawdb.PathScheme) } // stateElement represents the element in the state trie(bytecode or trie node). @@ -169,17 +178,17 @@ type stateElement struct { syncPath trie.SyncPath } -func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { +func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool, scheme string) { // Create a random state to copy - _, srcDb, srcRoot, srcAccounts := makeTestState() + srcDisk, srcDb, ndb, srcRoot, srcAccounts := makeTestState(scheme) if commit { - srcDb.TrieDB().Commit(srcRoot, false) + ndb.Commit(srcRoot, false) } - srcTrie, _ := trie.New(trie.StateTrieID(srcRoot), srcDb.TrieDB()) + srcTrie, _ := trie.New(trie.StateTrieID(srcRoot), ndb) // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme()) + sched := NewStateSync(srcRoot, dstDb, nil, ndb.Scheme()) var ( nodeElements []stateElement @@ -194,9 +203,11 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { }) } for i := 0; i < len(codes); i++ { - codeElements = append(codeElements, stateElement{ - code: codes[i], - }) + codeElements = append(codeElements, stateElement{code: codes[i]}) + } + reader, err := ndb.Reader(srcRoot) + if err != nil { + t.Fatalf("state is not existent, %#x", srcRoot) } for len(nodeElements)+len(codeElements) > 0 { var ( @@ -204,7 +215,7 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { codeResults = make([]trie.CodeSyncResult, len(codeElements)) ) for i, element := range codeElements { - data, err := srcDb.ContractCode(common.Hash{}, element.code) + data, err := srcDb.ContractCode(common.Address{}, element.code) if err != nil { t.Fatalf("failed to retrieve contract bytecode for hash %x", element.code) } @@ -224,7 +235,7 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { t.Fatalf("failed to decode account on path %x: %v", node.syncPath[0], err) } id := trie.StorageTrieID(srcRoot, common.BytesToHash(node.syncPath[0]), acc.Root) - stTrie, err := trie.New(id, srcDb.TrieDB()) + stTrie, err := trie.New(id, ndb) if err != nil { t.Fatalf("failed to retriev storage trie for path %x: %v", node.syncPath[1], err) } @@ -235,7 +246,8 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { nodeResults[i] = trie.NodeSyncResult{Path: node.path, Data: data} } } else { - data, err := srcDb.TrieDB().Node(node.hash) + owner, inner := trie.ResolvePath([]byte(node.path)) + data, err := reader.Node(owner, inner, node.hash) if err != nil { t.Fatalf("failed to retrieve node data for key %v", []byte(node.path)) } @@ -274,19 +286,28 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { }) } } + // Copy the preimages from source db in order to traverse the state. + srcDb.TrieDB().WritePreimages() + copyPreimages(srcDisk, dstDb) + // Cross check that the two states are in sync - checkStateAccounts(t, dstDb, srcRoot, srcAccounts) + checkStateAccounts(t, dstDb, ndb.Scheme(), srcRoot, srcAccounts) } // Tests that the trie scheduler can correctly reconstruct the state even if only // partial results are returned, and the others sent only later. func TestIterativeDelayedStateSync(t *testing.T) { + testIterativeDelayedStateSync(t, rawdb.HashScheme) + testIterativeDelayedStateSync(t, rawdb.PathScheme) +} + +func testIterativeDelayedStateSync(t *testing.T, scheme string) { // Create a random state to copy - _, srcDb, srcRoot, srcAccounts := makeTestState() + srcDisk, srcDb, ndb, srcRoot, srcAccounts := makeTestState(scheme) // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme()) + sched := NewStateSync(srcRoot, dstDb, nil, ndb.Scheme()) var ( nodeElements []stateElement @@ -301,9 +322,11 @@ func TestIterativeDelayedStateSync(t *testing.T) { }) } for i := 0; i < len(codes); i++ { - codeElements = append(codeElements, stateElement{ - code: codes[i], - }) + codeElements = append(codeElements, stateElement{code: codes[i]}) + } + reader, err := ndb.Reader(srcRoot) + if err != nil { + t.Fatalf("state is not existent, %#x", srcRoot) } for len(nodeElements)+len(codeElements) > 0 { // Sync only half of the scheduled nodes @@ -312,7 +335,7 @@ func TestIterativeDelayedStateSync(t *testing.T) { if len(codeElements) > 0 { codeResults := make([]trie.CodeSyncResult, len(codeElements)/2+1) for i, element := range codeElements[:len(codeResults)] { - data, err := srcDb.ContractCode(common.Hash{}, element.code) + data, err := srcDb.ContractCode(common.Address{}, element.code) if err != nil { t.Fatalf("failed to retrieve contract bytecode for %x", element.code) } @@ -328,7 +351,8 @@ func TestIterativeDelayedStateSync(t *testing.T) { if len(nodeElements) > 0 { nodeResults := make([]trie.NodeSyncResult, len(nodeElements)/2+1) for i, element := range nodeElements[:len(nodeResults)] { - data, err := srcDb.TrieDB().Node(element.hash) + owner, inner := trie.ResolvePath([]byte(element.path)) + data, err := reader.Node(owner, inner, element.hash) if err != nil { t.Fatalf("failed to retrieve contract bytecode for %x", element.code) } @@ -363,23 +387,33 @@ func TestIterativeDelayedStateSync(t *testing.T) { }) } } + // Copy the preimages from source db in order to traverse the state. + srcDb.TrieDB().WritePreimages() + copyPreimages(srcDisk, dstDb) + // Cross check that the two states are in sync - checkStateAccounts(t, dstDb, srcRoot, srcAccounts) + checkStateAccounts(t, dstDb, ndb.Scheme(), srcRoot, srcAccounts) } // Tests that given a root hash, a trie can sync iteratively on a single thread, // requesting retrieval tasks and returning all of them in one go, however in a // random order. -func TestIterativeRandomStateSyncIndividual(t *testing.T) { testIterativeRandomStateSync(t, 1) } -func TestIterativeRandomStateSyncBatched(t *testing.T) { testIterativeRandomStateSync(t, 100) } +func TestIterativeRandomStateSyncIndividual(t *testing.T) { + testIterativeRandomStateSync(t, 1, rawdb.HashScheme) + testIterativeRandomStateSync(t, 1, rawdb.PathScheme) +} +func TestIterativeRandomStateSyncBatched(t *testing.T) { + testIterativeRandomStateSync(t, 100, rawdb.HashScheme) + testIterativeRandomStateSync(t, 100, rawdb.PathScheme) +} -func testIterativeRandomStateSync(t *testing.T, count int) { +func testIterativeRandomStateSync(t *testing.T, count int, scheme string) { // Create a random state to copy - _, srcDb, srcRoot, srcAccounts := makeTestState() + srcDisk, srcDb, ndb, srcRoot, srcAccounts := makeTestState(scheme) // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme()) + sched := NewStateSync(srcRoot, dstDb, nil, ndb.Scheme()) nodeQueue := make(map[string]stateElement) codeQueue := make(map[common.Hash]struct{}) @@ -394,12 +428,16 @@ func testIterativeRandomStateSync(t *testing.T, count int) { for _, hash := range codes { codeQueue[hash] = struct{}{} } + reader, err := ndb.Reader(srcRoot) + if err != nil { + t.Fatalf("state is not existent, %#x", srcRoot) + } for len(nodeQueue)+len(codeQueue) > 0 { // Fetch all the queued nodes in a random order if len(codeQueue) > 0 { results := make([]trie.CodeSyncResult, 0, len(codeQueue)) for hash := range codeQueue { - data, err := srcDb.ContractCode(common.Hash{}, hash) + data, err := srcDb.ContractCode(common.Address{}, hash) if err != nil { t.Fatalf("failed to retrieve node data for %x", hash) } @@ -414,7 +452,8 @@ func testIterativeRandomStateSync(t *testing.T, count int) { if len(nodeQueue) > 0 { results := make([]trie.NodeSyncResult, 0, len(nodeQueue)) for path, element := range nodeQueue { - data, err := srcDb.TrieDB().Node(element.hash) + owner, inner := trie.ResolvePath([]byte(element.path)) + data, err := reader.Node(owner, inner, element.hash) if err != nil { t.Fatalf("failed to retrieve node data for %x %v %v", element.hash, []byte(element.path), element.path) } @@ -426,7 +465,6 @@ func testIterativeRandomStateSync(t *testing.T, count int) { } } } - // Feed the retrieved results back and queue new tasks batch := dstDb.NewBatch() if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) @@ -447,19 +485,28 @@ func testIterativeRandomStateSync(t *testing.T, count int) { codeQueue[hash] = struct{}{} } } + // Copy the preimages from source db in order to traverse the state. + srcDb.TrieDB().WritePreimages() + copyPreimages(srcDisk, dstDb) + // Cross check that the two states are in sync - checkStateAccounts(t, dstDb, srcRoot, srcAccounts) + checkStateAccounts(t, dstDb, ndb.Scheme(), srcRoot, srcAccounts) } // Tests that the trie scheduler can correctly reconstruct the state even if only // partial results are returned (Even those randomly), others sent only later. func TestIterativeRandomDelayedStateSync(t *testing.T) { + testIterativeRandomDelayedStateSync(t, rawdb.HashScheme) + testIterativeRandomDelayedStateSync(t, rawdb.PathScheme) +} + +func testIterativeRandomDelayedStateSync(t *testing.T, scheme string) { // Create a random state to copy - _, srcDb, srcRoot, srcAccounts := makeTestState() + srcDisk, srcDb, ndb, srcRoot, srcAccounts := makeTestState(scheme) // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme()) + sched := NewStateSync(srcRoot, dstDb, nil, ndb.Scheme()) nodeQueue := make(map[string]stateElement) codeQueue := make(map[common.Hash]struct{}) @@ -474,6 +521,10 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) { for _, hash := range codes { codeQueue[hash] = struct{}{} } + reader, err := ndb.Reader(srcRoot) + if err != nil { + t.Fatalf("state is not existent, %#x", srcRoot) + } for len(nodeQueue)+len(codeQueue) > 0 { // Sync only half of the scheduled nodes, even those in random order if len(codeQueue) > 0 { @@ -481,7 +532,7 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) { for hash := range codeQueue { delete(codeQueue, hash) - data, err := srcDb.ContractCode(common.Hash{}, hash) + data, err := srcDb.ContractCode(common.Address{}, hash) if err != nil { t.Fatalf("failed to retrieve node data for %x", hash) } @@ -502,7 +553,8 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) { for path, element := range nodeQueue { delete(nodeQueue, path) - data, err := srcDb.TrieDB().Node(element.hash) + owner, inner := trie.ResolvePath([]byte(element.path)) + data, err := reader.Node(owner, inner, element.hash) if err != nil { t.Fatalf("failed to retrieve node data for %x", element.hash) } @@ -537,15 +589,24 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) { codeQueue[hash] = struct{}{} } } + // Copy the preimages from source db in order to traverse the state. + srcDb.TrieDB().WritePreimages() + copyPreimages(srcDisk, dstDb) + // Cross check that the two states are in sync - checkStateAccounts(t, dstDb, srcRoot, srcAccounts) + checkStateAccounts(t, dstDb, ndb.Scheme(), srcRoot, srcAccounts) } // Tests that at any point in time during a sync, only complete sub-tries are in // the database. func TestIncompleteStateSync(t *testing.T) { + testIncompleteStateSync(t, rawdb.HashScheme) + testIncompleteStateSync(t, rawdb.PathScheme) +} + +func testIncompleteStateSync(t *testing.T, scheme string) { // Create a random state to copy - db, srcDb, srcRoot, srcAccounts := makeTestState() + db, srcDb, ndb, srcRoot, srcAccounts := makeTestState(scheme) // isCodeLookup to save some hashing var isCode = make(map[common.Hash]struct{}) @@ -555,17 +616,20 @@ func TestIncompleteStateSync(t *testing.T) { } } isCode[types.EmptyCodeHash] = struct{}{} - checkTrieConsistency(db, srcRoot) // Create a destination state and sync with the scheduler dstDb := rawdb.NewMemoryDatabase() - sched := NewStateSync(srcRoot, dstDb, nil, srcDb.TrieDB().Scheme()) + sched := NewStateSync(srcRoot, dstDb, nil, ndb.Scheme()) var ( addedCodes []common.Hash addedPaths []string addedHashes []common.Hash ) + reader, err := ndb.Reader(srcRoot) + if err != nil { + t.Fatalf("state is not available %x", srcRoot) + } nodeQueue := make(map[string]stateElement) codeQueue := make(map[common.Hash]struct{}) paths, nodes, codes := sched.Missing(1) @@ -584,7 +648,7 @@ func TestIncompleteStateSync(t *testing.T) { if len(codeQueue) > 0 { results := make([]trie.CodeSyncResult, 0, len(codeQueue)) for hash := range codeQueue { - data, err := srcDb.ContractCode(common.Hash{}, hash) + data, err := srcDb.ContractCode(common.Address{}, hash) if err != nil { t.Fatalf("failed to retrieve node data for %x", hash) } @@ -598,12 +662,11 @@ func TestIncompleteStateSync(t *testing.T) { } } } - var nodehashes []common.Hash if len(nodeQueue) > 0 { results := make([]trie.NodeSyncResult, 0, len(nodeQueue)) for path, element := range nodeQueue { owner, inner := trie.ResolvePath([]byte(element.path)) - data, err := srcDb.TrieDB().Reader(srcRoot).Node(owner, inner, element.hash) + data, err := reader.Node(owner, inner, element.hash) if err != nil { t.Fatalf("failed to retrieve node data for %x", element.hash) } @@ -613,7 +676,6 @@ func TestIncompleteStateSync(t *testing.T) { addedPaths = append(addedPaths, element.path) addedHashes = append(addedHashes, element.hash) } - nodehashes = append(nodehashes, element.hash) } // Process each of the state nodes for _, result := range results { @@ -628,13 +690,6 @@ func TestIncompleteStateSync(t *testing.T) { } batch.Write() - for _, root := range nodehashes { - // Can't use checkStateConsistency here because subtrie keys may have odd - // length and crash in LeafKey. - if err := checkTrieConsistency(dstDb, root); err != nil { - t.Fatalf("state inconsistent: %v", err) - } - } // Fetch the next batch to retrieve nodeQueue = make(map[string]stateElement) codeQueue = make(map[common.Hash]struct{}) @@ -650,16 +705,19 @@ func TestIncompleteStateSync(t *testing.T) { codeQueue[hash] = struct{}{} } } + // Copy the preimages from source db in order to traverse the state. + srcDb.TrieDB().WritePreimages() + copyPreimages(db, dstDb) + // Sanity check that removing any node from the database is detected for _, node := range addedCodes { val := rawdb.ReadCode(dstDb, node) rawdb.DeleteCode(dstDb, node) - if err := checkStateConsistency(dstDb, srcRoot); err == nil { + if err := checkStateConsistency(dstDb, ndb.Scheme(), srcRoot); err == nil { t.Errorf("trie inconsistency not caught, missing: %x", node) } rawdb.WriteCode(dstDb, node, val) } - scheme := srcDb.TrieDB().Scheme() for i, path := range addedPaths { owner, inner := trie.ResolvePath([]byte(path)) hash := addedHashes[i] @@ -668,9 +726,21 @@ func TestIncompleteStateSync(t *testing.T) { t.Error("missing trie node") } rawdb.DeleteTrieNode(dstDb, owner, inner, hash, scheme) - if err := checkStateConsistency(dstDb, srcRoot); err == nil { + if err := checkStateConsistency(dstDb, scheme, srcRoot); err == nil { t.Errorf("trie inconsistency not caught, missing: %v", path) } rawdb.WriteTrieNode(dstDb, owner, inner, hash, val, scheme) } } + +func copyPreimages(srcDb, dstDb ethdb.Database) { + it := srcDb.NewIterator(rawdb.PreimagePrefix, nil) + defer it.Release() + + preimages := make(map[common.Hash][]byte) + for it.Next() { + hash := it.Key()[len(rawdb.PreimagePrefix):] + preimages[common.BytesToHash(hash)] = common.CopyBytes(it.Value()) + } + rawdb.WritePreimages(dstDb, preimages) +} diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index 844f72fc106b..772c698dd0e1 100644 --- a/core/state/trie_prefetcher.go +++ b/core/state/trie_prefetcher.go @@ -37,7 +37,7 @@ var ( type triePrefetcher struct { db Database // Database to fetch trie nodes through root common.Hash // Root hash of the account trie for metrics - fetches map[string]Trie // Partially or fully fetcher tries + fetches map[string]Trie // Partially or fully fetched tries. Only populated for inactive copies. fetchers map[string]*subfetcher // Subfetchers for each trie deliveryMissMeter metrics.Meter @@ -197,7 +197,10 @@ func (p *triePrefetcher) used(owner common.Hash, root common.Hash, used [][]byte // trieID returns an unique trie identifier consists the trie owner and root hash. func (p *triePrefetcher) trieID(owner common.Hash, root common.Hash) string { - return string(append(owner.Bytes(), root.Bytes()...)) + trieID := make([]byte, common.HashLength*2) + copy(trieID, owner.Bytes()) + copy(trieID[common.HashLength:], root.Bytes()) + return string(trieID) } // subfetcher is a trie fetcher goroutine responsible for pulling entries for a @@ -302,7 +305,7 @@ func (sf *subfetcher) loop() { } sf.trie = trie } else { - trie, err := sf.db.OpenStorageTrie(sf.state, sf.owner, sf.root) + trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root) if err != nil { log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) return diff --git a/core/state_processor.go b/core/state_processor.go index ed712d8e806a..0990142a33fe 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -17,6 +17,7 @@ package core import ( + "errors" "fmt" "math/big" @@ -74,6 +75,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vmenv = vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg) signer = types.MakeSigner(p.config, header.Number, header.Time) ) + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { + ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { msg, err := TransactionToMessage(tx, signer, header.BaseFee) @@ -91,7 +95,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg // Fail if Shanghai not enabled and len(withdrawals) is non-zero. withdrawals := block.Withdrawals() if len(withdrawals) > 0 && !p.config.IsShanghai(block.Number(), block.Time(), types.DeserializeHeaderExtraInformation(header).ArbOSFormatVersion) { - return nil, nil, 0, fmt.Errorf("withdrawals before shanghai") + return nil, nil, 0, errors.New("withdrawals before shanghai") } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals) @@ -137,6 +141,11 @@ func applyTransaction(msg *Message, config *params.ChainConfig, gp *GasPool, sta receipt.TxHash = tx.Hash() receipt.GasUsed = result.UsedGas + if tx.Type() == types.BlobTxType { + receipt.BlobGasUsed = uint64(len(tx.BlobHashes()) * params.BlobTxBlobGasPerBlob) + receipt.BlobGasPrice = evm.Context.BlobBaseFee + } + // If the transaction created a contract, store the creation address in the receipt. if result.TopLevelDeployed != nil { receipt.ContractAddress = *result.TopLevelDeployed @@ -167,6 +176,26 @@ func ApplyTransactionWithResultFilter(config *params.ChainConfig, bc ChainContex } // Create a new context to be used in the EVM environment blockContext := NewEVMBlockContext(header, bc, author) - vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) + vmenv := vm.NewEVM(blockContext, vm.TxContext{BlobHashes: tx.BlobHashes()}, statedb, config, cfg) return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv, resultFilter) } + +// 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, vmenv *vm.EVM, statedb *state.StateDB) { + // If EIP-4788 is enabled, we need to invoke the beaconroot storage contract with + // the new root + msg := &Message{ + From: params.SystemAddress, + GasLimit: 30_000_000, + GasPrice: common.Big0, + GasFeeCap: common.Big0, + GasTipCap: common.Big0, + To: ¶ms.BeaconRootsStorageAddress, + Data: beaconRoot[:], + } + vmenv.Reset(NewEVMTxContext(msg), statedb) + statedb.AddAddressToAccessList(params.BeaconRootsStorageAddress) + _, _, _ = vmenv.Call(vm.AccountRef(msg.From), *msg.To, msg.Data, 30_000_000, common.Big0) + statedb.Finalise(true) +} diff --git a/core/state_processor_test.go b/core/state_processor_test.go index 53675c93e7f0..12961fc9fade 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -26,13 +26,15 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" + "github.com/holiman/uint256" "golang.org/x/crypto/sha3" ) @@ -45,19 +47,23 @@ func u64(val uint64) *uint64 { return &val } func TestStateProcessorErrors(t *testing.T) { var ( config = ¶ms.ChainConfig{ - 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), - Ethash: new(params.EthashConfig), + 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), + Ethash: new(params.EthashConfig), + TerminalTotalDifficulty: big.NewInt(0), + TerminalTotalDifficultyPassed: true, + ShanghaiTime: new(uint64), + CancunTime: new(uint64), } signer = types.LatestSigner(config) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -89,6 +95,22 @@ func TestStateProcessorErrors(t *testing.T) { }), signer, key1) return tx } + var mkBlobTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int, hashes []common.Hash) *types.Transaction { + tx, err := types.SignTx(types.NewTx(&types.BlobTx{ + Nonce: nonce, + GasTipCap: uint256.MustFromBig(gasTipCap), + GasFeeCap: uint256.MustFromBig(gasFeeCap), + Gas: gasLimit, + To: to, + BlobHashes: hashes, + Value: new(uint256.Int), + }), signer, key1) + if err != nil { + t.Fatal(err) + } + return tx + } + { // Tests against a 'recent' chain definition var ( db = rawdb.NewMemoryDatabase() @@ -105,8 +127,10 @@ func TestStateProcessorErrors(t *testing.T) { }, }, } - blockchain, _ = NewBlockChain(db, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) + tooBigInitCode = [params.DefaultMaxInitCodeSize + 1]byte{} ) + defer blockchain.Stop() bigNumber := new(big.Int).SetBytes(common.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) tooBigNumber := new(big.Int).Set(bigNumber) @@ -209,8 +233,26 @@ func TestStateProcessorErrors(t *testing.T) { }, want: "could not apply tx 0 [0xd82a0c2519acfeac9a948258c47e784acd20651d9d80f9a1c67b4137651c3a24]: insufficient funds for gas * price + value: address 0x71562b71999873DB5b286dF957af199Ec94617F7 have 1000000000000000000 want 2431633873983640103894990685182446064918669677978451844828609264166175722438635000", }, + { // ErrMaxInitCodeSizeExceeded + txs: []*types.Transaction{ + mkDynamicCreationTx(0, 500000, common.Big0, big.NewInt(params.InitialBaseFee), tooBigInitCode[:]), + }, + want: "could not apply tx 0 [0xd491405f06c92d118dd3208376fcee18a57c54bc52063ee4a26b1cf296857c25]: max initcode size exceeded: code size 49153 limit 49152", + }, + { // ErrIntrinsicGas: Not enough gas to cover init code + txs: []*types.Transaction{ + mkDynamicCreationTx(0, 54299, common.Big0, big.NewInt(params.InitialBaseFee), make([]byte, 320)), + }, + want: "could not apply tx 0 [0xfd49536a9b323769d8472fcb3ebb3689b707a349379baee3e2ee3fe7baae06a1]: intrinsic gas too low: have 54299, want 54300", + }, + { // ErrBlobFeeCapTooLow + txs: []*types.Transaction{ + mkBlobTx(0, common.Address{}, params.TxGas, big.NewInt(1), big.NewInt(1), []common.Hash{(common.Hash{1})}), + }, + want: "could not apply tx 0 [0x6c11015985ce82db691d7b2d017acda296db88b811c3c60dc71449c76256c716]: max fee per gas less than block base fee: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxFeePerGas: 1 baseFee: 875000000", + }, } { - block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config) + block := GenerateBadBlock(gspec.ToBlock(), beacon.New(ethash.NewFaker()), tt.txs, gspec.Config) _, err := blockchain.InsertChain(types.Blocks{block}) if err == nil { t.Fatal("block imported without errors") @@ -284,7 +326,7 @@ func TestStateProcessorErrors(t *testing.T) { }, }, } - blockchain, _ = NewBlockChain(db, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(db, nil, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) ) defer blockchain.Stop() for i, tt := range []struct { @@ -298,73 +340,7 @@ func TestStateProcessorErrors(t *testing.T) { want: "could not apply tx 0 [0x88626ac0d53cb65308f2416103c62bb1f18b805573d4f96a3640bbbfff13c14f]: sender not an eoa: address 0x71562b71999873DB5b286dF957af199Ec94617F7, codehash: 0x9280914443471259d4570a8661015ae4a5b80186dbc619658fb494bebc3da3d1", }, } { - block := GenerateBadBlock(gspec.ToBlock(), ethash.NewFaker(), tt.txs, gspec.Config) - _, err := blockchain.InsertChain(types.Blocks{block}) - if err == nil { - t.Fatal("block imported without errors") - } - if have, want := err.Error(), tt.want; have != want { - t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want) - } - } - } - - // ErrMaxInitCodeSizeExceeded, for this we need extra Shanghai (EIP-3860) enabled. - { - var ( - db = rawdb.NewMemoryDatabase() - gspec = &Genesis{ - Config: ¶ms.ChainConfig{ - 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), - GrayGlacierBlock: big.NewInt(0), - MergeNetsplitBlock: big.NewInt(0), - TerminalTotalDifficulty: big.NewInt(0), - TerminalTotalDifficultyPassed: true, - ShanghaiTime: u64(0), - }, - Alloc: GenesisAlloc{ - common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{ - Balance: big.NewInt(1000000000000000000), // 1 ether - Nonce: 0, - }, - }, - } - genesis = gspec.MustCommit(db) - blockchain, _ = NewBlockChain(db, nil, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) - tooBigInitCode = [params.MaxInitCodeSize + 1]byte{} - smallInitCode = [320]byte{} - ) - defer blockchain.Stop() - for i, tt := range []struct { - txs []*types.Transaction - want string - }{ - { // ErrMaxInitCodeSizeExceeded - txs: []*types.Transaction{ - mkDynamicCreationTx(0, 500000, common.Big0, misc.CalcBaseFee(config, genesis.Header()), tooBigInitCode[:]), - }, - want: "could not apply tx 0 [0x832b54a6c3359474a9f504b1003b2cc1b6fcaa18e4ef369eb45b5d40dad6378f]: max initcode size exceeded: code size 49153 limit 49152", - }, - { // ErrIntrinsicGas: Not enough gas to cover init code - txs: []*types.Transaction{ - mkDynamicCreationTx(0, 54299, common.Big0, misc.CalcBaseFee(config, genesis.Header()), smallInitCode[:]), - }, - want: "could not apply tx 0 [0x39b7436cb432d3662a25626474282c5c4c1a213326fd87e4e18a91477bae98b2]: intrinsic gas too low: have 54299, want 54300", - }, - } { - block := GenerateBadBlock(genesis, beacon.New(ethash.NewFaker()), tt.txs, gspec.Config) + block := GenerateBadBlock(gspec.ToBlock(), beacon.New(ethash.NewFaker()), tt.txs, gspec.Config) _, err := blockchain.InsertChain(types.Blocks{block}) if err == nil { t.Fatal("block imported without errors") @@ -401,9 +377,10 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr UncleHash: types.EmptyUncleHash, } if config.IsLondon(header.Number) { - header.BaseFee = misc.CalcBaseFee(config, parent.Header()) + header.BaseFee = eip1559.CalcBaseFee(config, parent.Header()) } - if config.IsShanghai(header.Number, header.Time, types.DeserializeHeaderExtraInformation(header).ArbOSFormatVersion) { + arbosVersion := types.DeserializeHeaderExtraInformation(header).ArbOSFormatVersion + if config.IsShanghai(header.Number, header.Time, arbosVersion) { header.WithdrawalsHash = &types.EmptyWithdrawalsHash } var receipts []*types.Receipt @@ -412,6 +389,7 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr hasher := sha3.NewLegacyKeccak256() hasher.Write(header.Number.Bytes()) var cumulativeGas uint64 + var nBlobs int for _, tx := range txs { txh := tx.Hash() hasher.Write(txh[:]) @@ -420,8 +398,23 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr receipt.GasUsed = tx.Gas() receipts = append(receipts, receipt) cumulativeGas += tx.Gas() + nBlobs += len(tx.BlobHashes()) } header.Root = common.BytesToHash(hasher.Sum(nil)) + if config.IsCancun(header.Number, header.Time, arbosVersion) { + var pExcess, pUsed = uint64(0), uint64(0) + if parent.ExcessBlobGas() != nil { + pExcess = *parent.ExcessBlobGas() + pUsed = *parent.BlobGasUsed() + } + excess := eip4844.CalcExcessBlobGas(pExcess, pUsed) + used := uint64(nBlobs * params.BlobTxBlobGasPerBlob) + header.ExcessBlobGas = &excess + header.BlobGasUsed = &used + + beaconRoot := common.HexToHash("0xbeac00") + header.ParentBeaconRoot = &beaconRoot + } // Assemble and return the final block for sealing if config.IsShanghai(header.Number, header.Time, types.DeserializeHeaderExtraInformation(header).ArbOSFormatVersion) { return types.NewBlockWithWithdrawals(header, txs, nil, receipts, []*types.Withdrawal{}, trie.NewStackTrie(nil)) diff --git a/core/state_transition.go b/core/state_transition.go index a70ce4eb064f..1d785979f014 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,6 +17,7 @@ package core import ( + "errors" "fmt" "math" "math/big" @@ -134,16 +135,18 @@ type Message struct { TxRunMode MessageRunMode Tx *types.Transaction - To *common.Address - From common.Address - Nonce uint64 - Value *big.Int - GasLimit uint64 - GasPrice *big.Int - GasFeeCap *big.Int - GasTipCap *big.Int - Data []byte - AccessList types.AccessList + To *common.Address + From common.Address + Nonce uint64 + Value *big.Int + GasLimit uint64 + GasPrice *big.Int + GasFeeCap *big.Int + GasTipCap *big.Int + Data []byte + AccessList types.AccessList + BlobGasFeeCap *big.Int + BlobHashes []common.Hash // When SkipAccountChecks is true, the message nonce is not checked against the // account nonce in state. It also disables checking that the sender is an EOA. @@ -176,7 +179,9 @@ func TransactionToMessage(tx *types.Transaction, s types.Signer, baseFee *big.In Value: tx.Value(), Data: tx.Data(), AccessList: tx.AccessList(), - SkipAccountChecks: tx.SkipAccountChecks(), + SkipAccountChecks: tx.SkipAccountChecks(), // TODO Arbitrum upstream this was init'd to false + BlobHashes: tx.BlobHashes(), + BlobGasFeeCap: tx.BlobGasFeeCap(), } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { @@ -254,12 +259,24 @@ func (st *StateTransition) to() common.Address { func (st *StateTransition) buyGas() error { mgval := new(big.Int).SetUint64(st.msg.GasLimit) mgval = mgval.Mul(mgval, st.msg.GasPrice) - balanceCheck := mgval + balanceCheck := new(big.Int).Set(mgval) if st.msg.GasFeeCap != nil { - balanceCheck = new(big.Int).SetUint64(st.msg.GasLimit) + balanceCheck.SetUint64(st.msg.GasLimit) balanceCheck = balanceCheck.Mul(balanceCheck, st.msg.GasFeeCap) balanceCheck.Add(balanceCheck, st.msg.Value) } + if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time, st.evm.Context.ArbOSVersion) { + if blobGas := st.blobGasUsed(); blobGas > 0 { + // Check that the user has enough funds to cover blobGasUsed * tx.BlobGasFeeCap + blobBalanceCheck := new(big.Int).SetUint64(blobGas) + blobBalanceCheck.Mul(blobBalanceCheck, st.msg.BlobGasFeeCap) + balanceCheck.Add(balanceCheck, blobBalanceCheck) + // Pay for blobGasUsed * actual blob fee + blobFee := new(big.Int).SetUint64(blobGas) + blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee) + mgval.Add(mgval, blobFee) + } + } if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 { return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) } @@ -327,6 +344,29 @@ func (st *StateTransition) preCheck() error { } } } + // Check the blob version validity + if msg.BlobHashes != nil { + if len(msg.BlobHashes) == 0 { + return errors.New("blob transaction missing blob hashes") + } + for i, hash := range msg.BlobHashes { + if hash[0] != params.BlobTxHashVersion { + return fmt.Errorf("blob %d hash version mismatch (have %d, supported %d)", + i, hash[0], params.BlobTxHashVersion) + } + } + } + + if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time, st.evm.Context.ArbOSVersion) { + if st.blobGasUsed() > 0 { + // Check that the user is paying at least the current blob fee + blobFee := st.evm.Context.BlobBaseFee + if st.msg.BlobGasFeeCap.Cmp(blobFee) < 0 { + return fmt.Errorf("%w: address %v have %v want %v", ErrBlobFeeCapTooLow, st.msg.From.Hex(), st.msg.BlobGasFeeCap, blobFee) + } + } + } + return st.buyGas() } @@ -408,8 +448,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } // Check whether the init code size has been exceeded. - if rules.IsShanghai && contractCreation && len(msg.Data) > params.MaxInitCodeSize { - return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), params.MaxInitCodeSize) + if rules.IsShanghai && contractCreation && len(msg.Data) > int(st.evm.ChainConfig().MaxInitCodeSize()) { + return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(msg.Data), int(st.evm.ChainConfig().MaxInitCodeSize())) } // Execute the preparatory steps for state transition which includes: @@ -464,7 +504,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // Arbitrum: record self destructs if tracer := st.evm.Config.Tracer; tracer != nil { - suicides := st.evm.StateDB.GetSuicides() + suicides := st.evm.StateDB.GetSelfDestructs() for i, address := range suicides { balance := st.evm.StateDB.GetBalance(address) tracer.CaptureArbitrumTransfer(st.evm, &suicides[i], nil, balance, false, "selfDestruct") @@ -511,3 +551,8 @@ func (st *StateTransition) refundGas(refundQuotient uint64) { func (st *StateTransition) gasUsed() uint64 { return st.initialGas - st.gasRemaining } + +// blobGasUsed returns the amount of blob gas used by the message. +func (st *StateTransition) blobGasUsed() uint64 { + return uint64(len(st.msg.BlobHashes) * params.BlobTxBlobGasPerBlob) +} diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go new file mode 100644 index 000000000000..ab769043aa80 --- /dev/null +++ b/core/txpool/blobpool/blobpool.go @@ -0,0 +1,1561 @@ +// Copyright 2022 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 blobpool implements the EIP-4844 blob transaction pool. +package blobpool + +import ( + "container/heap" + "errors" + "fmt" + "math" + "math/big" + "os" + "path/filepath" + "sort" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/billy" + "github.com/holiman/uint256" +) + +const ( + // blobSize is the protocol constrained byte size of a single blob in a + // transaction. There can be multiple of these embedded into a single tx. + blobSize = params.BlobTxFieldElementsPerBlob * params.BlobTxBytesPerFieldElement + + // maxBlobsPerTransaction is the maximum number of blobs a single transaction + // is allowed to contain. Whilst the spec states it's unlimited, the block + // data slots are protocol bound, which implicitly also limit this. + maxBlobsPerTransaction = params.MaxBlobGasPerBlock / params.BlobTxBlobGasPerBlob + + // txAvgSize is an approximate byte size of a transaction metadata to avoid + // tiny overflows causing all txs to move a shelf higher, wasting disk space. + txAvgSize = 4 * 1024 + + // txMaxSize is the maximum size a single transaction can have, outside + // the included blobs. Since blob transactions are pulled instead of pushed, + // and only a small metadata is kept in ram, the rest is on disk, there is + // no critical limit that should be enforced. Still, capping it to some sane + // limit can never hurt. + txMaxSize = 1024 * 1024 + + // maxTxsPerAccount is the maximum number of blob transactions admitted from + // a single account. The limit is enforced to minimize the DoS potential of + // a private tx cancelling publicly propagated blobs. + // + // Note, transactions resurrected by a reorg are also subject to this limit, + // so pushing it down too aggressively might make resurrections non-functional. + maxTxsPerAccount = 16 + + // pendingTransactionStore is the subfolder containing the currently queued + // blob transactions. + pendingTransactionStore = "queue" + + // limboedTransactionStore is the subfolder containing the currently included + // but not yet finalized transaction blobs. + limboedTransactionStore = "limbo" +) + +// blobTxMeta is the minimal subset of types.BlobTx necessary to validate and +// schedule the blob transactions into the following blocks. Only ever add the +// bare minimum needed fields to keep the size down (and thus number of entries +// larger with the same memory consumption). +type blobTxMeta struct { + hash common.Hash // Transaction hash to maintain the lookup table + id uint64 // Storage ID in the pool's persistent store + size uint32 // Byte size in the pool's persistent store + + nonce uint64 // Needed to prioritize inclusion order within an account + costCap *uint256.Int // Needed to validate cumulative balance sufficiency + execTipCap *uint256.Int // Needed to prioritize inclusion order across accounts and validate replacement price bump + execFeeCap *uint256.Int // Needed to validate replacement price bump + blobFeeCap *uint256.Int // Needed to validate replacement price bump + execGas uint64 // Needed to check inclusion validity before reading the blob + blobGas uint64 // Needed to check inclusion validity before reading the blob + + basefeeJumps float64 // Absolute number of 1559 fee adjustments needed to reach the tx's fee cap + blobfeeJumps float64 // Absolute number of 4844 fee adjustments needed to reach the tx's blob fee cap + + evictionExecTip *uint256.Int // Worst gas tip across all previous nonces + evictionExecFeeJumps float64 // Worst base fee (converted to fee jumps) across all previous nonces + evictionBlobFeeJumps float64 // Worse blob fee (converted to fee jumps) across all previous nonces +} + +// newBlobTxMeta retrieves the indexed metadata fields from a blob transaction +// and assembles a helper struct to track in memory. +func newBlobTxMeta(id uint64, size uint32, tx *types.Transaction) *blobTxMeta { + meta := &blobTxMeta{ + hash: tx.Hash(), + id: id, + size: size, + nonce: tx.Nonce(), + costCap: uint256.MustFromBig(tx.Cost()), + execTipCap: uint256.MustFromBig(tx.GasTipCap()), + execFeeCap: uint256.MustFromBig(tx.GasFeeCap()), + blobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()), + execGas: tx.Gas(), + blobGas: tx.BlobGas(), + } + meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap) + meta.blobfeeJumps = dynamicFeeJumps(meta.blobFeeCap) + + return meta +} + +// BlobPool is the transaction pool dedicated to EIP-4844 blob transactions. +// +// Blob transactions are special snowflakes that are designed for a very specific +// purpose (rollups) and are expected to adhere to that specific use case. These +// behavioural expectations allow us to design a transaction pool that is more robust +// (i.e. resending issues) and more resilient to DoS attacks (e.g. replace-flush +// attacks) than the generic tx pool. These improvements will also mean, however, +// that we enforce a significantly more aggressive strategy on entering and exiting +// the pool: +// +// - Blob transactions are large. With the initial design aiming for 128KB blobs, +// we must ensure that these only traverse the network the absolute minimum +// number of times. Broadcasting to sqrt(peers) is out of the question, rather +// these should only ever be announced and the remote side should request it if +// it wants to. +// +// - Block blob-space is limited. With blocks being capped to a few blob txs, we +// can make use of the very low expected churn rate within the pool. Notably, +// we should be able to use a persistent disk backend for the pool, solving +// the tx resend issue that plagues the generic tx pool, as long as there's no +// artificial churn (i.e. pool wars). +// +// - Purpose of blobs are layer-2s. Layer-2s are meant to use blob transactions to +// commit to their own current state, which is independent of Ethereum mainnet +// (state, txs). This means that there's no reason for blob tx cancellation or +// replacement, apart from a potential basefee / miner tip adjustment. +// +// - Replacements are expensive. Given their size, propagating a replacement +// blob transaction to an existing one should be aggressively discouraged. +// Whilst generic transactions can start at 1 Wei gas cost and require a 10% +// fee bump to replace, we suggest requiring a higher min cost (e.g. 1 gwei) +// and a more aggressive bump (100%). +// +// - Cancellation is prohibitive. Evicting an already propagated blob tx is a huge +// DoS vector. As such, a) replacement (higher-fee) blob txs mustn't invalidate +// already propagated (future) blob txs (cumulative fee); b) nonce-gapped blob +// txs are disallowed; c) the presence of blob transactions exclude non-blob +// transactions. +// +// - Malicious cancellations are possible. Although the pool might prevent txs +// that cancel blobs, blocks might contain such transaction (malicious miner +// or flashbotter). The pool should cap the total number of blob transactions +// per account as to prevent propagating too much data before cancelling it +// via a normal transaction. It should nonetheless be high enough to support +// resurrecting reorged transactions. Perhaps 4-16. +// +// - Local txs are meaningless. Mining pools historically used local transactions +// for payouts or for backdoor deals. With 1559 in place, the basefee usually +// dominates the final price, so 0 or non-0 tip doesn't change much. Blob txs +// retain the 1559 2D gas pricing (and introduce on top a dynamic blob gas fee), +// so locality is moot. With a disk backed blob pool avoiding the resend issue, +// there's also no need to save own transactions for later. +// +// - No-blob blob-txs are bad. Theoretically there's no strong reason to disallow +// blob txs containing 0 blobs. In practice, admitting such txs into the pool +// breaks the low-churn invariant as blob constraints don't apply anymore. Even +// though we could accept blocks containing such txs, a reorg would require moving +// them back into the blob pool, which can break invariants. +// +// - Dropping blobs needs delay. When normal transactions are included, they +// are immediately evicted from the pool since they are contained in the +// including block. Blobs however are not included in the execution chain, +// so a mini reorg cannot re-pool "lost" blob transactions. To support reorgs, +// blobs are retained on disk until they are finalised. +// +// - Blobs can arrive via flashbots. Blocks might contain blob transactions we +// have never seen on the network. Since we cannot recover them from blocks +// either, the engine_newPayload needs to give them to us, and we cache them +// until finality to support reorgs without tx losses. +// +// Whilst some constraints above might sound overly aggressive, the general idea is +// that the blob pool should work robustly for its intended use case and whilst +// anyone is free to use blob transactions for arbitrary non-rollup use cases, +// they should not be allowed to run amok the network. +// +// Implementation wise there are a few interesting design choices: +// +// - Adding a transaction to the pool blocks until persisted to disk. This is +// viable because TPS is low (2-4 blobs per block initially, maybe 8-16 at +// peak), so natural churn is a couple MB per block. Replacements doing O(n) +// updates are forbidden and transaction propagation is pull based (i.e. no +// pileup of pending data). +// +// - When transactions are chosen for inclusion, the primary criteria is the +// signer tip (and having a basefee/data fee high enough of course). However, +// same-tip transactions will be split by their basefee/datafee, preferring +// those that are closer to the current network limits. The idea being that +// very relaxed ones can be included even if the fees go up, when the closer +// ones could already be invalid. +// +// When the pool eventually reaches saturation, some old transactions - that may +// never execute - will need to be evicted in favor of newer ones. The eviction +// strategy is quite complex: +// +// - Exceeding capacity evicts the highest-nonce of the account with the lowest +// paying blob transaction anywhere in the pooled nonce-sequence, as that tx +// would be executed the furthest in the future and is thus blocking anything +// after it. The smallest is deliberately not evicted to avoid a nonce-gap. +// +// - Analogously, if the pool is full, the consideration price of a new tx for +// evicting an old one is the smallest price in the entire nonce-sequence of +// the account. This avoids malicious users DoSing the pool with seemingly +// high paying transactions hidden behind a low-paying blocked one. +// +// - Since blob transactions have 3 price parameters: execution tip, execution +// fee cap and data fee cap, there's no singular parameter to create a total +// price ordering on. What's more, since the base fee and blob fee can move +// independently of one another, there's no pre-defined way to combine them +// into a stable order either. This leads to a multi-dimensional problem to +// solve after every block. +// +// - The first observation is that comparing 1559 base fees or 4844 blob fees +// needs to happen in the context of their dynamism. Since these fees jump +// up or down in ~1.125 multipliers (at max) across blocks, comparing fees +// in two transactions should be based on log1.125(fee) to eliminate noise. +// +// - The second observation is that the basefee and blobfee move independently, +// so there's no way to split mixed txs on their own (A has higher base fee, +// B has higher blob fee). Rather than look at the absolute fees, the useful +// metric is the max time it can take to exceed the transaction's fee caps. +// Specifically, we're interested in the number of jumps needed to go from +// the current fee to the transaction's cap: +// +// jumps = log1.125(txfee) - log1.125(basefee) +// +// - The third observation is that the base fee tends to hover around rather +// than swing wildly. The number of jumps needed from the current fee starts +// to get less relevant the higher it is. To remove the noise here too, the +// pool will use log(jumps) as the delta for comparing transactions. +// +// delta = sign(jumps) * log(abs(jumps)) +// +// - To establish a total order, we need to reduce the dimensionality of the +// two base fees (log jumps) to a single value. The interesting aspect from +// the pool's perspective is how fast will a tx get executable (fees going +// down, crossing the smaller negative jump counter) or non-executable (fees +// going up, crossing the smaller positive jump counter). As such, the pool +// cares only about the min of the two delta values for eviction priority. +// +// priority = min(delta-basefee, delta-blobfee) +// +// - The above very aggressive dimensionality and noise reduction should result +// in transaction being grouped into a small number of buckets, the further +// the fees the larger the buckets. This is good because it allows us to use +// the miner tip meaningfully as a splitter. +// +// - For the scenario where the pool does not contain non-executable blob txs +// anymore, it does not make sense to grant a later eviction priority to txs +// with high fee caps since it could enable pool wars. As such, any positive +// priority will be grouped together. +// +// priority = min(delta-basefee, delta-blobfee, 0) +// +// Optimisation tradeoffs: +// +// - Eviction relies on 3 fee minimums per account (exec tip, exec cap and blob +// cap). Maintaining these values across all transactions from the account is +// problematic as each transaction replacement or inclusion would require a +// rescan of all other transactions to recalculate the minimum. Instead, the +// pool maintains a rolling minimum across the nonce range. Updating all the +// minimums will need to be done only starting at the swapped in/out nonce +// and leading up to the first no-change. +type BlobPool struct { + config Config // Pool configuration + reserve txpool.AddressReserver // Address reserver to ensure exclusivity across subpools + + store billy.Database // Persistent data store for the tx metadata and blobs + stored uint64 // Useful data size of all transactions on disk + limbo *limbo // Persistent data store for the non-finalized blobs + + signer types.Signer // Transaction signer to use for sender recovery + chain BlockChain // Chain object to access the state through + + head *types.Header // Current head of the chain + state *state.StateDB // Current state at the head of the chain + gasTip *uint256.Int // Currently accepted minimum gas tip + + lookup map[common.Hash]uint64 // Lookup table mapping hashes to tx billy entries + index map[common.Address][]*blobTxMeta // Blob transactions grouped by accounts, sorted by nonce + spent map[common.Address]*uint256.Int // Expenditure tracking for individual accounts + evict *evictHeap // Heap of cheapest accounts for eviction when full + + discoverFeed event.Feed // Event feed to send out new tx events on pool discovery (reorg excluded) + insertFeed event.Feed // Event feed to send out new tx events on pool inclusion (reorg included) + + lock sync.RWMutex // Mutex protecting the pool during reorg handling +} + +// New creates a new blob transaction pool to gather, sort and filter inbound +// blob transactions from the network. +func New(config Config, chain BlockChain) *BlobPool { + // Sanitize the input to ensure no vulnerable gas prices are set + config = (&config).sanitize() + + // Create the transaction pool with its initial settings + return &BlobPool{ + config: config, + signer: types.LatestSigner(chain.Config()), + chain: chain, + lookup: make(map[common.Hash]uint64), + index: make(map[common.Address][]*blobTxMeta), + spent: make(map[common.Address]*uint256.Int), + } +} + +// Filter returns whether the given transaction can be consumed by the blob pool. +func (p *BlobPool) Filter(tx *types.Transaction) bool { + return tx.Type() == types.BlobTxType +} + +// Init sets the gas price needed to keep a transaction in the pool and the chain +// head to allow balance / nonce checks. The transaction journal will be loaded +// from disk and filtered based on the provided starting settings. +func (p *BlobPool) Init(gasTip *big.Int, head *types.Header, reserve txpool.AddressReserver) error { + p.reserve = reserve + + var ( + queuedir string + limbodir string + ) + if p.config.Datadir != "" { + queuedir = filepath.Join(p.config.Datadir, pendingTransactionStore) + if err := os.MkdirAll(queuedir, 0700); err != nil { + return err + } + limbodir = filepath.Join(p.config.Datadir, limboedTransactionStore) + if err := os.MkdirAll(limbodir, 0700); err != nil { + return err + } + } + // Initialize the state with head block, or fallback to empty one in + // case the head state is not available(might occur when node is not + // fully synced). + state, err := p.chain.StateAt(head.Root) + if err != nil { + state, err = p.chain.StateAt(types.EmptyRootHash) + } + if err != nil { + return err + } + p.head, p.state = head, state + + // Index all transactions on disk and delete anything inprocessable + var fails []uint64 + index := func(id uint64, size uint32, blob []byte) { + if p.parseTransaction(id, size, blob) != nil { + fails = append(fails, id) + } + } + store, err := billy.Open(billy.Options{Path: queuedir}, newSlotter(), index) + if err != nil { + return err + } + p.store = store + + if len(fails) > 0 { + log.Warn("Dropping invalidated blob transactions", "ids", fails) + for _, id := range fails { + if err := p.store.Delete(id); err != nil { + p.Close() + return err + } + } + } + // Sort the indexed transactions by nonce and delete anything gapped, create + // the eviction heap of anyone still standing + for addr := range p.index { + p.recheck(addr, nil) + } + var ( + basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), p.head)) + blobfee = uint256.MustFromBig(big.NewInt(params.BlobTxMinBlobGasprice)) + ) + if p.head.ExcessBlobGas != nil { + blobfee = uint256.MustFromBig(eip4844.CalcBlobFee(*p.head.ExcessBlobGas)) + } + p.evict = newPriceHeap(basefee, blobfee, &p.index) + + // Pool initialized, attach the blob limbo to it to track blobs included + // recently but not yet finalized + p.limbo, err = newLimbo(limbodir) + if err != nil { + p.Close() + return err + } + // Set the configured gas tip, triggering a filtering of anything just loaded + basefeeGauge.Update(int64(basefee.Uint64())) + blobfeeGauge.Update(int64(blobfee.Uint64())) + + p.SetGasTip(gasTip) + + // Since the user might have modified their pool's capacity, evict anything + // above the current allowance + for p.stored > p.config.Datacap { + p.drop() + } + // Update the metrics and return the constructed pool + datacapGauge.Update(int64(p.config.Datacap)) + p.updateStorageMetrics() + return nil +} + +// Close closes down the underlying persistent store. +func (p *BlobPool) Close() error { + var errs []error + if err := p.limbo.Close(); err != nil { + errs = append(errs, err) + } + if err := p.store.Close(); err != nil { + errs = append(errs, err) + } + switch { + case errs == nil: + return nil + case len(errs) == 1: + return errs[0] + default: + return fmt.Errorf("%v", errs) + } +} + +// parseTransaction is a callback method on pool creation that gets called for +// each transaction on disk to create the in-memory metadata index. +func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error { + tx := new(types.Transaction) + if err := rlp.DecodeBytes(blob, tx); err != nil { + // This path is impossible unless the disk data representation changes + // across restarts. For that ever unprobable case, recover gracefully + // by ignoring this data entry. + log.Error("Failed to decode blob pool entry", "id", id, "err", err) + return err + } + if tx.BlobTxSidecar() == nil { + log.Error("Missing sidecar in blob pool entry", "id", id, "hash", tx.Hash()) + return errors.New("missing blob sidecar") + } + + meta := newBlobTxMeta(id, size, tx) + + sender, err := p.signer.Sender(tx) + if err != nil { + // This path is impossible unless the signature validity changes across + // restarts. For that ever unprobable case, recover gracefully by ignoring + // this data entry. + log.Error("Failed to recover blob tx sender", "id", id, "hash", tx.Hash(), "err", err) + return err + } + if _, ok := p.index[sender]; !ok { + if err := p.reserve(sender, true); err != nil { + return err + } + p.index[sender] = []*blobTxMeta{} + p.spent[sender] = new(uint256.Int) + } + p.index[sender] = append(p.index[sender], meta) + p.spent[sender] = new(uint256.Int).Add(p.spent[sender], meta.costCap) + + p.lookup[meta.hash] = meta.id + p.stored += uint64(meta.size) + + return nil +} + +// recheck verifies the pool's content for a specific account and drops anything +// that does not fit anymore (dangling or filled nonce, overdraft). +func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint64) { + // Sort the transactions belonging to the account so reinjects can be simpler + txs := p.index[addr] + if inclusions != nil && txs == nil { // during reorgs, we might find new accounts + return + } + sort.Slice(txs, func(i, j int) bool { + return txs[i].nonce < txs[j].nonce + }) + // If there is a gap between the chain state and the blob pool, drop + // all the transactions as they are non-executable. Similarly, if the + // entire tx range was included, drop all. + var ( + next = p.state.GetNonce(addr) + gapped = txs[0].nonce > next + filled = txs[len(txs)-1].nonce < next + ) + if gapped || filled { + var ( + ids []uint64 + nonces []uint64 + ) + for i := 0; i < len(txs); i++ { + ids = append(ids, txs[i].id) + nonces = append(nonces, txs[i].nonce) + + p.stored -= uint64(txs[i].size) + delete(p.lookup, txs[i].hash) + + // Included transactions blobs need to be moved to the limbo + if filled && inclusions != nil { + p.offload(addr, txs[i].nonce, txs[i].id, inclusions) + } + } + delete(p.index, addr) + delete(p.spent, addr) + if inclusions != nil { // only during reorgs will the heap will be initialized + heap.Remove(p.evict, p.evict.index[addr]) + } + p.reserve(addr, false) + + if gapped { + log.Warn("Dropping dangling blob transactions", "from", addr, "missing", next, "drop", nonces, "ids", ids) + } else { + log.Trace("Dropping filled blob transactions", "from", addr, "filled", nonces, "ids", ids) + } + for _, id := range ids { + if err := p.store.Delete(id); err != nil { + log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err) + } + } + return + } + // If there is overlap between the chain state and the blob pool, drop + // anything below the current state + if txs[0].nonce < next { + var ( + ids []uint64 + nonces []uint64 + ) + for txs[0].nonce < next { + ids = append(ids, txs[0].id) + nonces = append(nonces, txs[0].nonce) + + p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[0].costCap) + p.stored -= uint64(txs[0].size) + delete(p.lookup, txs[0].hash) + + // Included transactions blobs need to be moved to the limbo + if inclusions != nil { + p.offload(addr, txs[0].nonce, txs[0].id, inclusions) + } + txs = txs[1:] + } + log.Trace("Dropping overlapped blob transactions", "from", addr, "overlapped", nonces, "ids", ids, "left", len(txs)) + for _, id := range ids { + if err := p.store.Delete(id); err != nil { + log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err) + } + } + p.index[addr] = txs + } + // Iterate over the transactions to initialize their eviction thresholds + // and to detect any nonce gaps + txs[0].evictionExecTip = txs[0].execTipCap + txs[0].evictionExecFeeJumps = txs[0].basefeeJumps + txs[0].evictionBlobFeeJumps = txs[0].blobfeeJumps + + for i := 1; i < len(txs); i++ { + // If there's no nonce gap, initialize the evicion thresholds as the + // minimum between the cumulative thresholds and the current tx fees + if txs[i].nonce == txs[i-1].nonce+1 { + txs[i].evictionExecTip = txs[i-1].evictionExecTip + if txs[i].evictionExecTip.Cmp(txs[i].execTipCap) > 0 { + txs[i].evictionExecTip = txs[i].execTipCap + } + txs[i].evictionExecFeeJumps = txs[i-1].evictionExecFeeJumps + if txs[i].evictionExecFeeJumps > txs[i].basefeeJumps { + txs[i].evictionExecFeeJumps = txs[i].basefeeJumps + } + txs[i].evictionBlobFeeJumps = txs[i-1].evictionBlobFeeJumps + if txs[i].evictionBlobFeeJumps > txs[i].blobfeeJumps { + txs[i].evictionBlobFeeJumps = txs[i].blobfeeJumps + } + continue + } + // Sanity check that there's no double nonce. This case would be a coding + // error, but better know about it + if txs[i].nonce == txs[i-1].nonce { + log.Error("Duplicate nonce blob transaction", "from", addr, "nonce", txs[i].nonce) + } + // Otherwise if there's a nonce gap evict all later transactions + var ( + ids []uint64 + nonces []uint64 + ) + for j := i; j < len(txs); j++ { + ids = append(ids, txs[j].id) + nonces = append(nonces, txs[j].nonce) + + p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[j].costCap) + p.stored -= uint64(txs[j].size) + delete(p.lookup, txs[j].hash) + } + txs = txs[:i] + + log.Error("Dropping gapped blob transactions", "from", addr, "missing", txs[i-1].nonce+1, "drop", nonces, "ids", ids) + for _, id := range ids { + if err := p.store.Delete(id); err != nil { + log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err) + } + } + p.index[addr] = txs + break + } + // Ensure that there's no over-draft, this is expected to happen when some + // transactions get included without publishing on the network + var ( + balance = uint256.MustFromBig(p.state.GetBalance(addr)) + spent = p.spent[addr] + ) + if spent.Cmp(balance) > 0 { + // Evict the highest nonce transactions until the pending set falls under + // the account's available balance + var ( + ids []uint64 + nonces []uint64 + ) + for p.spent[addr].Cmp(balance) > 0 { + last := txs[len(txs)-1] + txs[len(txs)-1] = nil + txs = txs[:len(txs)-1] + + ids = append(ids, last.id) + nonces = append(nonces, last.nonce) + + p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap) + p.stored -= uint64(last.size) + delete(p.lookup, last.hash) + } + if len(txs) == 0 { + delete(p.index, addr) + delete(p.spent, addr) + if inclusions != nil { // only during reorgs will the heap will be initialized + heap.Remove(p.evict, p.evict.index[addr]) + } + p.reserve(addr, false) + } else { + p.index[addr] = txs + } + log.Warn("Dropping overdrafted blob transactions", "from", addr, "balance", balance, "spent", spent, "drop", nonces, "ids", ids) + for _, id := range ids { + if err := p.store.Delete(id); err != nil { + log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err) + } + } + } + // Sanity check that no account can have more queued transactions than the + // DoS protection threshold. + if len(txs) > maxTxsPerAccount { + // Evict the highest nonce transactions until the pending set falls under + // the account's transaction cap + var ( + ids []uint64 + nonces []uint64 + ) + for len(txs) > maxTxsPerAccount { + last := txs[len(txs)-1] + txs[len(txs)-1] = nil + txs = txs[:len(txs)-1] + + ids = append(ids, last.id) + nonces = append(nonces, last.nonce) + + p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap) + p.stored -= uint64(last.size) + delete(p.lookup, last.hash) + } + p.index[addr] = txs + + log.Warn("Dropping overcapped blob transactions", "from", addr, "kept", len(txs), "drop", nonces, "ids", ids) + for _, id := range ids { + if err := p.store.Delete(id); err != nil { + log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err) + } + } + } + // Included cheap transactions might have left the remaining ones better from + // an eviction point, fix any potential issues in the heap. + if _, ok := p.index[addr]; ok && inclusions != nil { + heap.Fix(p.evict, p.evict.index[addr]) + } +} + +// offload removes a tracked blob transaction from the pool and moves it into the +// limbo for tracking until finality. +// +// The method may log errors for various unexpcted scenarios but will not return +// any of it since there's no clear error case. Some errors may be due to coding +// issues, others caused by signers mining MEV stuff or swapping transactions. In +// all cases, the pool needs to continue operating. +func (p *BlobPool) offload(addr common.Address, nonce uint64, id uint64, inclusions map[common.Hash]uint64) { + data, err := p.store.Get(id) + if err != nil { + log.Error("Blobs missing for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err) + return + } + var tx types.Transaction + if err = rlp.DecodeBytes(data, &tx); err != nil { + log.Error("Blobs corrupted for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err) + return + } + block, ok := inclusions[tx.Hash()] + if !ok { + log.Warn("Blob transaction swapped out by signer", "from", addr, "nonce", nonce, "id", id) + return + } + if err := p.limbo.push(&tx, block); err != nil { + log.Warn("Failed to offload blob tx into limbo", "err", err) + return + } +} + +// Reset implements txpool.SubPool, allowing the blob pool's internal state to be +// kept in sync with the main transacion pool's internal state. +func (p *BlobPool) Reset(oldHead, newHead *types.Header) { + waitStart := time.Now() + p.lock.Lock() + resetwaitHist.Update(time.Since(waitStart).Nanoseconds()) + defer p.lock.Unlock() + + defer func(start time.Time) { + resettimeHist.Update(time.Since(start).Nanoseconds()) + }(time.Now()) + + statedb, err := p.chain.StateAt(newHead.Root) + if err != nil { + log.Error("Failed to reset blobpool state", "err", err) + return + } + p.head = newHead + p.state = statedb + + // Run the reorg between the old and new head and figure out which accounts + // need to be rechecked and which transactions need to be readded + if reinject, inclusions := p.reorg(oldHead, newHead); reinject != nil { + var adds []*types.Transaction + for addr, txs := range reinject { + // Blindly push all the lost transactions back into the pool + for _, tx := range txs { + if err := p.reinject(addr, tx.Hash()); err == nil { + adds = append(adds, tx.WithoutBlobTxSidecar()) + } + } + // Recheck the account's pooled transactions to drop included and + // invalidated one + p.recheck(addr, inclusions) + } + if len(adds) > 0 { + p.insertFeed.Send(core.NewTxsEvent{Txs: adds}) + } + } + + arbosVersion := types.DeserializeHeaderExtraInformation(p.head).ArbOSFormatVersion + // Flush out any blobs from limbo that are older than the latest finality + if p.chain.Config().IsCancun(p.head.Number, p.head.Time, arbosVersion) { + p.limbo.finalize(p.chain.CurrentFinalBlock()) + } + // Reset the price heap for the new set of basefee/blobfee pairs + var ( + basefee = uint256.MustFromBig(eip1559.CalcBaseFee(p.chain.Config(), newHead)) + blobfee = uint256.MustFromBig(big.NewInt(params.BlobTxMinBlobGasprice)) + ) + if newHead.ExcessBlobGas != nil { + blobfee = uint256.MustFromBig(eip4844.CalcBlobFee(*newHead.ExcessBlobGas)) + } + p.evict.reinit(basefee, blobfee, false) + + basefeeGauge.Update(int64(basefee.Uint64())) + blobfeeGauge.Update(int64(blobfee.Uint64())) + p.updateStorageMetrics() +} + +// reorg assembles all the transactors and missing transactions between an old +// and new head to figure out which account's tx set needs to be rechecked and +// which transactions need to be requeued. +// +// The transactionblock inclusion infos are also returned to allow tracking any +// just-included blocks by block number in the limbo. +func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*types.Transaction, map[common.Hash]uint64) { + // If the pool was not yet initialized, don't do anything + if oldHead == nil { + return nil, nil + } + // If the reorg is too deep, avoid doing it (will happen during snap sync) + oldNum := oldHead.Number.Uint64() + newNum := newHead.Number.Uint64() + + if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 { + return nil, nil + } + // Reorg seems shallow enough to pull in all transactions into memory + var ( + transactors = make(map[common.Address]struct{}) + discarded = make(map[common.Address][]*types.Transaction) + included = make(map[common.Address][]*types.Transaction) + inclusions = make(map[common.Hash]uint64) + + rem = p.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64()) + add = p.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64()) + ) + if add == nil { + // if the new head is nil, it means that something happened between + // the firing of newhead-event and _now_: most likely a + // reorg caused by sync-reversion or explicit sethead back to an + // earlier block. + log.Warn("Blobpool reset with missing new head", "number", newHead.Number, "hash", newHead.Hash()) + return nil, nil + } + if rem == nil { + // This can happen if a setHead is performed, where we simply discard + // the old head from the chain. If that is the case, we don't have the + // lost transactions anymore, and there's nothing to add. + if newNum >= oldNum { + // If we reorged to a same or higher number, then it's not a case + // of setHead + log.Warn("Blobpool reset with missing old head", + "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) + return nil, nil + } + // If the reorg ended up on a lower number, it's indicative of setHead + // being the cause + log.Debug("Skipping blobpool reset caused by setHead", + "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) + return nil, nil + } + // Both old and new blocks exist, traverse through the progression chain + // and accumulate the transactors and transactions + for rem.NumberU64() > add.NumberU64() { + for _, tx := range rem.Transactions() { + from, _ := p.signer.Sender(tx) + + discarded[from] = append(discarded[from], tx) + transactors[from] = struct{}{} + } + if rem = p.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { + log.Error("Unrooted old chain seen by blobpool", "block", oldHead.Number, "hash", oldHead.Hash()) + return nil, nil + } + } + for add.NumberU64() > rem.NumberU64() { + for _, tx := range add.Transactions() { + from, _ := p.signer.Sender(tx) + + included[from] = append(included[from], tx) + inclusions[tx.Hash()] = add.NumberU64() + transactors[from] = struct{}{} + } + if add = p.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { + log.Error("Unrooted new chain seen by blobpool", "block", newHead.Number, "hash", newHead.Hash()) + return nil, nil + } + } + for rem.Hash() != add.Hash() { + for _, tx := range rem.Transactions() { + from, _ := p.signer.Sender(tx) + + discarded[from] = append(discarded[from], tx) + transactors[from] = struct{}{} + } + if rem = p.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { + log.Error("Unrooted old chain seen by blobpool", "block", oldHead.Number, "hash", oldHead.Hash()) + return nil, nil + } + for _, tx := range add.Transactions() { + from, _ := p.signer.Sender(tx) + + included[from] = append(included[from], tx) + inclusions[tx.Hash()] = add.NumberU64() + transactors[from] = struct{}{} + } + if add = p.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { + log.Error("Unrooted new chain seen by blobpool", "block", newHead.Number, "hash", newHead.Hash()) + return nil, nil + } + } + // Generate the set of transactions per address to pull back into the pool, + // also updating the rest along the way + reinject := make(map[common.Address][]*types.Transaction) + for addr := range transactors { + // Generate the set that was lost to reinject into the pool + lost := make([]*types.Transaction, 0, len(discarded[addr])) + for _, tx := range types.TxDifference(discarded[addr], included[addr]) { + if p.Filter(tx) { + lost = append(lost, tx) + } + } + reinject[addr] = lost + + // Update the set that was already reincluded to track the blocks in limbo + for _, tx := range types.TxDifference(included[addr], discarded[addr]) { + if p.Filter(tx) { + p.limbo.update(tx.Hash(), inclusions[tx.Hash()]) + } + } + } + return reinject, inclusions +} + +// reinject blindly pushes a transaction previously included in the chain - and +// just reorged out - into the pool. The transaction is assumed valid (having +// been in the chain), thus the only validation needed is nonce sorting and over- +// draft checks after injection. +// +// Note, the method will not initialize the eviction cache values as those will +// be done once for all transactions belonging to an account after all individual +// transactions are injected back into the pool. +func (p *BlobPool) reinject(addr common.Address, txhash common.Hash) error { + // Retrieve the associated blob from the limbo. Without the blobs, we cannot + // add the transaction back into the pool as it is not mineable. + tx, err := p.limbo.pull(txhash) + if err != nil { + log.Error("Blobs unavailable, dropping reorged tx", "err", err) + return err + } + // TODO: seems like an easy optimization here would be getting the serialized tx + // from limbo instead of re-serializing it here. + + // Serialize the transaction back into the primary datastore. + blob, err := rlp.EncodeToBytes(tx) + if err != nil { + log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err) + return err + } + id, err := p.store.Put(blob) + if err != nil { + log.Error("Failed to write transaction into storage", "hash", tx.Hash(), "err", err) + return err + } + + // Update the indixes and metrics + meta := newBlobTxMeta(id, p.store.Size(id), tx) + if _, ok := p.index[addr]; !ok { + if err := p.reserve(addr, true); err != nil { + log.Warn("Failed to reserve account for blob pool", "tx", tx.Hash(), "from", addr, "err", err) + return err + } + p.index[addr] = []*blobTxMeta{meta} + p.spent[addr] = meta.costCap + p.evict.Push(addr) + } else { + p.index[addr] = append(p.index[addr], meta) + p.spent[addr] = new(uint256.Int).Add(p.spent[addr], meta.costCap) + } + p.lookup[meta.hash] = meta.id + p.stored += uint64(meta.size) + return nil +} + +// SetGasTip implements txpool.SubPool, allowing the blob pool's gas requirements +// to be kept in sync with the main transacion pool's gas requirements. +func (p *BlobPool) SetGasTip(tip *big.Int) { + p.lock.Lock() + defer p.lock.Unlock() + + // Store the new minimum gas tip + old := p.gasTip + p.gasTip = uint256.MustFromBig(tip) + + // If the min miner fee increased, remove transactions below the new threshold + if old == nil || p.gasTip.Cmp(old) > 0 { + for addr, txs := range p.index { + for i, tx := range txs { + if tx.execTipCap.Cmp(p.gasTip) < 0 { + // Drop the offending transaction + var ( + ids = []uint64{tx.id} + nonces = []uint64{tx.nonce} + ) + p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap) + p.stored -= uint64(tx.size) + delete(p.lookup, tx.hash) + txs[i] = nil + + // Drop everything afterwards, no gaps allowed + for j, tx := range txs[i+1:] { + ids = append(ids, tx.id) + nonces = append(nonces, tx.nonce) + + p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], tx.costCap) + p.stored -= uint64(tx.size) + delete(p.lookup, tx.hash) + txs[i+1+j] = nil + } + // Clear out the dropped transactions from the index + if i > 0 { + p.index[addr] = txs[:i] + heap.Fix(p.evict, p.evict.index[addr]) + } else { + delete(p.index, addr) + delete(p.spent, addr) + + heap.Remove(p.evict, p.evict.index[addr]) + p.reserve(addr, false) + } + // Clear out the transactions from the data store + log.Warn("Dropping underpriced blob transaction", "from", addr, "rejected", tx.nonce, "tip", tx.execTipCap, "want", tip, "drop", nonces, "ids", ids) + for _, id := range ids { + if err := p.store.Delete(id); err != nil { + log.Error("Failed to delete dropped transaction", "id", id, "err", err) + } + } + break + } + } + } + } + log.Debug("Blobpool tip threshold updated", "tip", tip) + pooltipGauge.Update(tip.Int64()) + p.updateStorageMetrics() +} + +// validateTx checks whether a transaction is valid according to the consensus +// rules and adheres to some heuristic limits of the local node (price and size). +func (p *BlobPool) validateTx(tx *types.Transaction) error { + // Ensure the transaction adheres to basic pool filters (type, size, tip) and + // consensus rules + baseOpts := &txpool.ValidationOptions{ + Config: p.chain.Config(), + Accept: 1 << types.BlobTxType, + MaxSize: txMaxSize, + MinTip: p.gasTip.ToBig(), + } + if err := txpool.ValidateTransaction(tx, p.head, p.signer, baseOpts); err != nil { + return err + } + // Ensure the transaction adheres to the stateful pool filters (nonce, balance) + stateOpts := &txpool.ValidationOptionsWithState{ + State: p.state, + + FirstNonceGap: func(addr common.Address) uint64 { + // Nonce gaps are not permitted in the blob pool, the first gap will + // be the next nonce shifted by however many transactions we already + // have pooled. + return p.state.GetNonce(addr) + uint64(len(p.index[addr])) + }, + UsedAndLeftSlots: func(addr common.Address) (int, int) { + have := len(p.index[addr]) + if have >= maxTxsPerAccount { + return have, 0 + } + return have, maxTxsPerAccount - have + }, + ExistingExpenditure: func(addr common.Address) *big.Int { + if spent := p.spent[addr]; spent != nil { + return spent.ToBig() + } + return new(big.Int) + }, + ExistingCost: func(addr common.Address, nonce uint64) *big.Int { + next := p.state.GetNonce(addr) + if uint64(len(p.index[addr])) > nonce-next { + return p.index[addr][int(tx.Nonce()-next)].costCap.ToBig() + } + return nil + }, + } + if err := txpool.ValidateTransactionWithState(tx, p.signer, stateOpts); err != nil { + return err + } + // If the transaction replaces an existing one, ensure that price bumps are + // adhered to. + var ( + from, _ = p.signer.Sender(tx) // already validated above + next = p.state.GetNonce(from) + ) + if uint64(len(p.index[from])) > tx.Nonce()-next { + // Account can support the replacement, but the price bump must also be met + prev := p.index[from][int(tx.Nonce()-next)] + switch { + case tx.GasFeeCapIntCmp(prev.execFeeCap.ToBig()) <= 0: + return fmt.Errorf("%w: new tx gas fee cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.GasFeeCap(), prev.execFeeCap) + case tx.GasTipCapIntCmp(prev.execTipCap.ToBig()) <= 0: + return fmt.Errorf("%w: new tx gas tip cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.GasTipCap(), prev.execTipCap) + case tx.BlobGasFeeCapIntCmp(prev.blobFeeCap.ToBig()) <= 0: + return fmt.Errorf("%w: new tx blob gas fee cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.BlobGasFeeCap(), prev.blobFeeCap) + } + var ( + multiplier = uint256.NewInt(100 + p.config.PriceBump) + onehundred = uint256.NewInt(100) + + minGasFeeCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.execFeeCap), onehundred) + minGasTipCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.execTipCap), onehundred) + minBlobGasFeeCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.blobFeeCap), onehundred) + ) + switch { + case tx.GasFeeCapIntCmp(minGasFeeCap.ToBig()) < 0: + return fmt.Errorf("%w: new tx gas fee cap %v <= %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.GasFeeCap(), prev.execFeeCap, p.config.PriceBump) + case tx.GasTipCapIntCmp(minGasTipCap.ToBig()) < 0: + return fmt.Errorf("%w: new tx gas tip cap %v <= %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.GasTipCap(), prev.execTipCap, p.config.PriceBump) + case tx.BlobGasFeeCapIntCmp(minBlobGasFeeCap.ToBig()) < 0: + return fmt.Errorf("%w: new tx blob gas fee cap %v <= %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.BlobGasFeeCap(), prev.blobFeeCap, p.config.PriceBump) + } + } + return nil +} + +// Has returns an indicator whether subpool has a transaction cached with the +// given hash. +func (p *BlobPool) Has(hash common.Hash) bool { + p.lock.RLock() + defer p.lock.RUnlock() + + _, ok := p.lookup[hash] + return ok +} + +// Get returns a transaction if it is contained in the pool, or nil otherwise. +func (p *BlobPool) Get(hash common.Hash) *types.Transaction { + // Track the amount of time waiting to retrieve a fully resolved blob tx from + // the pool and the amount of time actually spent on pulling the data from disk. + getStart := time.Now() + p.lock.RLock() + getwaitHist.Update(time.Since(getStart).Nanoseconds()) + defer p.lock.RUnlock() + + defer func(start time.Time) { + gettimeHist.Update(time.Since(start).Nanoseconds()) + }(time.Now()) + + // Pull the blob from disk and return an assembled response + id, ok := p.lookup[hash] + if !ok { + return nil + } + data, err := p.store.Get(id) + if err != nil { + log.Error("Tracked blob transaction missing from store", "hash", hash, "id", id, "err", err) + return nil + } + item := new(types.Transaction) + if err = rlp.DecodeBytes(data, item); err != nil { + log.Error("Blobs corrupted for traced transaction", "hash", hash, "id", id, "err", err) + return nil + } + return item +} + +// Add inserts a set of blob transactions into the pool if they pass validation (both +// consensus validity and pool restictions). +func (p *BlobPool) Add(txs []*types.Transaction, local bool, sync bool) []error { + var ( + adds = make([]*types.Transaction, 0, len(txs)) + errs = make([]error, len(txs)) + ) + for i, tx := range txs { + errs[i] = p.add(tx) + if errs[i] == nil { + adds = append(adds, tx.WithoutBlobTxSidecar()) + } + } + if len(adds) > 0 { + p.discoverFeed.Send(core.NewTxsEvent{Txs: adds}) + p.insertFeed.Send(core.NewTxsEvent{Txs: adds}) + } + return errs +} + +// Add inserts a new blob transaction into the pool if it passes validation (both +// consensus validity and pool restictions). +func (p *BlobPool) add(tx *types.Transaction) (err error) { + // The blob pool blocks on adding a transaction. This is because blob txs are + // only even pulled form the network, so this method will act as the overload + // protection for fetches. + waitStart := time.Now() + p.lock.Lock() + addwaitHist.Update(time.Since(waitStart).Nanoseconds()) + defer p.lock.Unlock() + + defer func(start time.Time) { + addtimeHist.Update(time.Since(start).Nanoseconds()) + }(time.Now()) + + // Ensure the transaction is valid from all perspectives + if err := p.validateTx(tx); err != nil { + log.Trace("Transaction validation failed", "hash", tx.Hash(), "err", err) + return err + } + // If the address is not yet known, request exclusivity to track the account + // only by this subpool until all transactions are evicted + from, _ := types.Sender(p.signer, tx) // already validated above + if _, ok := p.index[from]; !ok { + if err := p.reserve(from, true); err != nil { + return err + } + defer func() { + // If the transaction is rejected by some post-validation check, remove + // the lock on the reservation set. + // + // Note, `err` here is the named error return, which will be initialized + // by a return statement before running deferred methods. Take care with + // removing or subscoping err as it will break this clause. + if err != nil { + p.reserve(from, false) + } + }() + } + // Transaction permitted into the pool from a nonce and cost perspective, + // insert it into the database and update the indices + blob, err := rlp.EncodeToBytes(tx) + if err != nil { + log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err) + return err + } + id, err := p.store.Put(blob) + if err != nil { + return err + } + meta := newBlobTxMeta(id, p.store.Size(id), tx) + + var ( + next = p.state.GetNonce(from) + offset = int(tx.Nonce() - next) + newacc = false + ) + var oldEvictionExecFeeJumps, oldEvictionBlobFeeJumps float64 + if txs, ok := p.index[from]; ok { + oldEvictionExecFeeJumps = txs[len(txs)-1].evictionExecFeeJumps + oldEvictionBlobFeeJumps = txs[len(txs)-1].evictionBlobFeeJumps + } + if len(p.index[from]) > offset { + // Transaction replaces a previously queued one + prev := p.index[from][offset] + if err := p.store.Delete(prev.id); err != nil { + // Shitty situation, but try to recover gracefully instead of going boom + log.Error("Failed to delete replaced transaction", "id", prev.id, "err", err) + } + // Update the transaction index + p.index[from][offset] = meta + p.spent[from] = new(uint256.Int).Sub(p.spent[from], prev.costCap) + p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.costCap) + + delete(p.lookup, prev.hash) + p.lookup[meta.hash] = meta.id + p.stored += uint64(meta.size) - uint64(prev.size) + } else { + // Transaction extends previously scheduled ones + p.index[from] = append(p.index[from], meta) + if _, ok := p.spent[from]; !ok { + p.spent[from] = new(uint256.Int) + newacc = true + } + p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.costCap) + p.lookup[meta.hash] = meta.id + p.stored += uint64(meta.size) + } + // Recompute the rolling eviction fields. In case of a replacement, this will + // recompute all subsequent fields. In case of an append, this will only do + // the fresh calculation. + txs := p.index[from] + + for i := offset; i < len(txs); i++ { + // The first transaction will always use itself + if i == 0 { + txs[0].evictionExecTip = txs[0].execTipCap + txs[0].evictionExecFeeJumps = txs[0].basefeeJumps + txs[0].evictionBlobFeeJumps = txs[0].blobfeeJumps + + continue + } + // Subsequent transactions will use a rolling calculation + txs[i].evictionExecTip = txs[i-1].evictionExecTip + if txs[i].evictionExecTip.Cmp(txs[i].execTipCap) > 0 { + txs[i].evictionExecTip = txs[i].execTipCap + } + txs[i].evictionExecFeeJumps = txs[i-1].evictionExecFeeJumps + if txs[i].evictionExecFeeJumps > txs[i].basefeeJumps { + txs[i].evictionExecFeeJumps = txs[i].basefeeJumps + } + txs[i].evictionBlobFeeJumps = txs[i-1].evictionBlobFeeJumps + if txs[i].evictionBlobFeeJumps > txs[i].blobfeeJumps { + txs[i].evictionBlobFeeJumps = txs[i].blobfeeJumps + } + } + // Update the eviction heap with the new information: + // - If the transaction is from a new account, add it to the heap + // - If the account had a singleton tx replaced, update the heap (new price caps) + // - If the account has a transaction replaced or appended, update the heap if significantly changed + switch { + case newacc: + heap.Push(p.evict, from) + + case len(txs) == 1: // 1 tx and not a new acc, must be replacement + heap.Fix(p.evict, p.evict.index[from]) + + default: // replacement or new append + evictionExecFeeDiff := oldEvictionExecFeeJumps - txs[len(txs)-1].evictionExecFeeJumps + evictionBlobFeeDiff := oldEvictionBlobFeeJumps - txs[len(txs)-1].evictionBlobFeeJumps + + if math.Abs(evictionExecFeeDiff) > 0.001 || math.Abs(evictionBlobFeeDiff) > 0.001 { // need math.Abs, can go up and down + heap.Fix(p.evict, p.evict.index[from]) + } + } + // If the pool went over the allowed data limit, evict transactions until + // we're again below the threshold + for p.stored > p.config.Datacap { + p.drop() + } + p.updateStorageMetrics() + + return nil +} + +// drop removes the worst transaction from the pool. It is primarily used when a +// freshly added transaction overflows the pool and needs to evict something. The +// method is also called on startup if the user resizes their storage, might be an +// expensive run but it should be fine-ish. +func (p *BlobPool) drop() { + // Peek at the account with the worse transaction set to evict from (Go's heap + // stores the minimum at index zero of the heap slice) and retrieve it's last + // transaction. + var ( + from = p.evict.addrs[0] // cannot call drop on empty pool + + txs = p.index[from] + drop = txs[len(txs)-1] + last = len(txs) == 1 + ) + // Remove the transaction from the pool's index + if last { + delete(p.index, from) + delete(p.spent, from) + p.reserve(from, false) + } else { + txs[len(txs)-1] = nil + txs = txs[:len(txs)-1] + + p.index[from] = txs + p.spent[from] = new(uint256.Int).Sub(p.spent[from], drop.costCap) + } + p.stored -= uint64(drop.size) + delete(p.lookup, drop.hash) + + // Remove the transaction from the pool's evicion heap: + // - If the entire account was dropped, pop off the address + // - Otherwise, if the new tail has better eviction caps, fix the heap + if last { + heap.Pop(p.evict) + } else { + tail := txs[len(txs)-1] // new tail, surely exists + + evictionExecFeeDiff := tail.evictionExecFeeJumps - drop.evictionExecFeeJumps + evictionBlobFeeDiff := tail.evictionBlobFeeJumps - drop.evictionBlobFeeJumps + + if evictionExecFeeDiff > 0.001 || evictionBlobFeeDiff > 0.001 { // no need for math.Abs, monotonic decreasing + heap.Fix(p.evict, 0) + } + } + // Remove the transaction from the data store + log.Warn("Evicting overflown blob transaction", "from", from, "evicted", drop.nonce, "id", drop.id) + if err := p.store.Delete(drop.id); err != nil { + log.Error("Failed to drop evicted transaction", "id", drop.id, "err", err) + } +} + +// Pending retrieves all currently processable transactions, grouped by origin +// account and sorted by nonce. +func (p *BlobPool) Pending(enforceTips bool) map[common.Address][]*txpool.LazyTransaction { + // Track the amount of time waiting to retrieve the list of pending blob txs + // from the pool and the amount of time actually spent on assembling the data. + // The latter will be pretty much moot, but we've kept it to have symmetric + // across all user operations. + pendStart := time.Now() + p.lock.RLock() + pendwaitHist.Update(time.Since(pendStart).Nanoseconds()) + defer p.lock.RUnlock() + + defer func(start time.Time) { + pendtimeHist.Update(time.Since(start).Nanoseconds()) + }(time.Now()) + + pending := make(map[common.Address][]*txpool.LazyTransaction) + for addr, txs := range p.index { + var lazies []*txpool.LazyTransaction + for _, tx := range txs { + lazies = append(lazies, &txpool.LazyTransaction{ + Pool: p, + Hash: tx.hash, + Time: time.Now(), // TODO(karalabe): Maybe save these and use that? + GasFeeCap: tx.execFeeCap.ToBig(), + GasTipCap: tx.execTipCap.ToBig(), + Gas: tx.execGas, + BlobGas: tx.blobGas, + }) + } + if len(lazies) > 0 { + pending[addr] = lazies + } + } + return pending +} + +// updateStorageMetrics retrieves a bunch of stats from the data store and pushes +// them out as metrics. +func (p *BlobPool) updateStorageMetrics() { + stats := p.store.Infos() + + var ( + dataused uint64 + datareal uint64 + slotused uint64 + + oversizedDataused uint64 + oversizedDatagaps uint64 + oversizedSlotused uint64 + oversizedSlotgaps uint64 + ) + for _, shelf := range stats.Shelves { + slotDataused := shelf.FilledSlots * uint64(shelf.SlotSize) + slotDatagaps := shelf.GappedSlots * uint64(shelf.SlotSize) + + dataused += slotDataused + datareal += slotDataused + slotDatagaps + slotused += shelf.FilledSlots + + metrics.GetOrRegisterGauge(fmt.Sprintf(shelfDatausedGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(slotDataused)) + metrics.GetOrRegisterGauge(fmt.Sprintf(shelfDatagapsGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(slotDatagaps)) + metrics.GetOrRegisterGauge(fmt.Sprintf(shelfSlotusedGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.FilledSlots)) + metrics.GetOrRegisterGauge(fmt.Sprintf(shelfSlotgapsGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.GappedSlots)) + + if shelf.SlotSize/blobSize > maxBlobsPerTransaction { + oversizedDataused += slotDataused + oversizedDatagaps += slotDatagaps + oversizedSlotused += shelf.FilledSlots + oversizedSlotgaps += shelf.GappedSlots + } + } + datausedGauge.Update(int64(dataused)) + datarealGauge.Update(int64(datareal)) + slotusedGauge.Update(int64(slotused)) + + oversizedDatausedGauge.Update(int64(oversizedDataused)) + oversizedDatagapsGauge.Update(int64(oversizedDatagaps)) + oversizedSlotusedGauge.Update(int64(oversizedSlotused)) + oversizedSlotgapsGauge.Update(int64(oversizedSlotgaps)) + + p.updateLimboMetrics() +} + +// updateLimboMetrics retrieves a bunch of stats from the limbo store and pushes +// // them out as metrics. +func (p *BlobPool) updateLimboMetrics() { + stats := p.limbo.store.Infos() + + var ( + dataused uint64 + datareal uint64 + slotused uint64 + ) + for _, shelf := range stats.Shelves { + slotDataused := shelf.FilledSlots * uint64(shelf.SlotSize) + slotDatagaps := shelf.GappedSlots * uint64(shelf.SlotSize) + + dataused += slotDataused + datareal += slotDataused + slotDatagaps + slotused += shelf.FilledSlots + + metrics.GetOrRegisterGauge(fmt.Sprintf(limboShelfDatausedGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(slotDataused)) + metrics.GetOrRegisterGauge(fmt.Sprintf(limboShelfDatagapsGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(slotDatagaps)) + metrics.GetOrRegisterGauge(fmt.Sprintf(limboShelfSlotusedGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.FilledSlots)) + metrics.GetOrRegisterGauge(fmt.Sprintf(limboShelfSlotgapsGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.GappedSlots)) + } + limboDatausedGauge.Update(int64(dataused)) + limboDatarealGauge.Update(int64(datareal)) + limboSlotusedGauge.Update(int64(slotused)) +} + +// SubscribeTransactions registers a subscription for new transaction events, +// supporting feeding only newly seen or also resurrected transactions. +func (p *BlobPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription { + if reorgs { + return p.insertFeed.Subscribe(ch) + } else { + return p.discoverFeed.Subscribe(ch) + } +} + +// Nonce returns the next nonce of an account, with all transactions executable +// by the pool already applied on top. +func (p *BlobPool) Nonce(addr common.Address) uint64 { + p.lock.Lock() + defer p.lock.Unlock() + + if txs, ok := p.index[addr]; ok { + return txs[len(txs)-1].nonce + 1 + } + return p.state.GetNonce(addr) +} + +// Stats retrieves the current pool stats, namely the number of pending and the +// number of queued (non-executable) transactions. +func (p *BlobPool) Stats() (int, int) { + p.lock.Lock() + defer p.lock.Unlock() + + var pending int + for _, txs := range p.index { + pending += len(txs) + } + return pending, 0 // No non-executable txs in the blob pool +} + +// Content retrieves the data content of the transaction pool, returning all the +// pending as well as queued transactions, grouped by account and sorted by nonce. +// +// For the blob pool, this method will return nothing for now. +// TODO(karalabe): Abstract out the returned metadata. +func (p *BlobPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { + return make(map[common.Address][]*types.Transaction), make(map[common.Address][]*types.Transaction) +} + +// ContentFrom retrieves the data content of the transaction pool, returning the +// pending as well as queued transactions of this address, grouped by nonce. +// +// For the blob pool, this method will return nothing for now. +// TODO(karalabe): Abstract out the returned metadata. +func (p *BlobPool) ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { + return []*types.Transaction{}, []*types.Transaction{} +} + +// Locals retrieves the accounts currently considered local by the pool. +// +// There is no notion of local accounts in the blob pool. +func (p *BlobPool) Locals() []common.Address { + return []common.Address{} +} + +// Status returns the known status (unknown/pending/queued) of a transaction +// identified by their hashes. +func (p *BlobPool) Status(hash common.Hash) txpool.TxStatus { + if p.Has(hash) { + return txpool.TxStatusPending + } + return txpool.TxStatusUnknown +} diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go new file mode 100644 index 000000000000..8914301e14c3 --- /dev/null +++ b/core/txpool/blobpool/blobpool_test.go @@ -0,0 +1,1251 @@ +// Copyright 2023 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 blobpool + +import ( + "bytes" + "crypto/ecdsa" + "crypto/sha256" + "errors" + "math" + "math/big" + "os" + "path/filepath" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/billy" + "github.com/holiman/uint256" +) + +var ( + emptyBlob = kzg4844.Blob{} + emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob) + emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit) + emptyBlobVHash = blobHash(emptyBlobCommit) +) + +func blobHash(commit kzg4844.Commitment) common.Hash { + hasher := sha256.New() + hasher.Write(commit[:]) + hash := hasher.Sum(nil) + + var vhash common.Hash + vhash[0] = params.BlobTxHashVersion + copy(vhash[1:], hash[1:]) + + return vhash +} + +// Chain configuration with Cancun enabled. +// +// TODO(karalabe): replace with params.MainnetChainConfig after Cancun. +var testChainConfig *params.ChainConfig + +func init() { + testChainConfig = new(params.ChainConfig) + *testChainConfig = *params.MainnetChainConfig + + testChainConfig.CancunTime = new(uint64) + *testChainConfig.CancunTime = uint64(time.Now().Unix()) +} + +// testBlockChain is a mock of the live chain for testing the pool. +type testBlockChain struct { + config *params.ChainConfig + basefee *uint256.Int + blobfee *uint256.Int + statedb *state.StateDB +} + +func (bc *testBlockChain) Config() *params.ChainConfig { + return bc.config +} + +func (bc *testBlockChain) CurrentBlock() *types.Header { + // Yolo, life is too short to invert mist.CalcBaseFee and misc.CalcBlobFee, + // just binary search it them. + + // The base fee at 5714 ETH translates into the 21000 base gas higher than + // mainnet ether existence, use that as a cap for the tests. + var ( + blockNumber = new(big.Int).Add(bc.config.LondonBlock, big.NewInt(1)) + blockTime = *bc.config.CancunTime + 1 + gasLimit = uint64(30_000_000) + ) + lo := new(big.Int) + hi := new(big.Int).Mul(big.NewInt(5714), new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)) + + for new(big.Int).Add(lo, big.NewInt(1)).Cmp(hi) != 0 { + mid := new(big.Int).Add(lo, hi) + mid.Div(mid, big.NewInt(2)) + + if eip1559.CalcBaseFee(bc.config, &types.Header{ + Number: blockNumber, + GasLimit: gasLimit, + GasUsed: 0, + BaseFee: mid, + }).Cmp(bc.basefee.ToBig()) > 0 { + hi = mid + } else { + lo = mid + } + } + baseFee := lo + + // The excess blob gas at 2^27 translates into a blob fee higher than mainnet + // ether existence, use that as a cap for the tests. + lo = new(big.Int) + hi = new(big.Int).Exp(big.NewInt(2), big.NewInt(27), nil) + + for new(big.Int).Add(lo, big.NewInt(1)).Cmp(hi) != 0 { + mid := new(big.Int).Add(lo, hi) + mid.Div(mid, big.NewInt(2)) + + if eip4844.CalcBlobFee(mid.Uint64()).Cmp(bc.blobfee.ToBig()) > 0 { + hi = mid + } else { + lo = mid + } + } + excessBlobGas := lo.Uint64() + + return &types.Header{ + Number: blockNumber, + Time: blockTime, + GasLimit: gasLimit, + BaseFee: baseFee, + ExcessBlobGas: &excessBlobGas, + } +} + +func (bc *testBlockChain) CurrentFinalBlock() *types.Header { + return &types.Header{ + Number: big.NewInt(0), + } +} + +func (bt *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { + return nil +} + +func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { + return bc.statedb, nil +} + +// makeAddressReserver is a utility method to sanity check that accounts are +// properly reserved by the blobpool (no duplicate reserves or unreserves). +func makeAddressReserver() txpool.AddressReserver { + var ( + reserved = make(map[common.Address]struct{}) + lock sync.Mutex + ) + return func(addr common.Address, reserve bool) error { + lock.Lock() + defer lock.Unlock() + + _, exists := reserved[addr] + if reserve { + if exists { + panic("already reserved") + } + reserved[addr] = struct{}{} + return nil + } + if !exists { + panic("not reserved") + } + delete(reserved, addr) + return nil + } +} + +// makeTx is a utility method to construct a random blob transaction and sign it +// with a valid key, only setting the interesting fields from the perspective of +// the blob pool. +func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, key *ecdsa.PrivateKey) *types.Transaction { + blobtx := makeUnsignedTx(nonce, gasTipCap, gasFeeCap, blobFeeCap) + return types.MustSignNewTx(key, types.LatestSigner(testChainConfig), blobtx) +} + +// makeUnsignedTx is a utility method to construct a random blob tranasaction +// without signing it. +func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64) *types.BlobTx { + return &types.BlobTx{ + ChainID: uint256.MustFromBig(testChainConfig.ChainID), + Nonce: nonce, + GasTipCap: uint256.NewInt(gasTipCap), + GasFeeCap: uint256.NewInt(gasFeeCap), + Gas: 21000, + BlobFeeCap: uint256.NewInt(blobFeeCap), + BlobHashes: []common.Hash{emptyBlobVHash}, + Value: uint256.NewInt(100), + Sidecar: &types.BlobTxSidecar{ + Blobs: []kzg4844.Blob{emptyBlob}, + Commitments: []kzg4844.Commitment{emptyBlobCommit}, + Proofs: []kzg4844.Proof{emptyBlobProof}, + }, + } +} + +// verifyPoolInternals iterates over all the transactions in the pool and checks +// that sort orders, calculated fields, cumulated fields are correct. +func verifyPoolInternals(t *testing.T, pool *BlobPool) { + // Mark this method as a helper to remove from stack traces + t.Helper() + + // Verify that all items in the index are present in the lookup and nothing more + seen := make(map[common.Hash]struct{}) + for addr, txs := range pool.index { + for _, tx := range txs { + if _, ok := seen[tx.hash]; ok { + t.Errorf("duplicate hash #%x in transaction index: address %s, nonce %d", tx.hash, addr, tx.nonce) + } + seen[tx.hash] = struct{}{} + } + } + for hash, id := range pool.lookup { + if _, ok := seen[hash]; !ok { + t.Errorf("lookup entry missing from transaction index: hash #%x, id %d", hash, id) + } + delete(seen, hash) + } + for hash := range seen { + t.Errorf("indexed transaction hash #%x missing from lookup table", hash) + } + // Verify that transactions are sorted per account and contain no nonce gaps + for addr, txs := range pool.index { + for i := 1; i < len(txs); i++ { + if txs[i].nonce != txs[i-1].nonce+1 { + t.Errorf("addr %v, tx %d nonce mismatch: have %d, want %d", addr, i, txs[i].nonce, txs[i-1].nonce+1) + } + } + } + // Verify that calculated evacuation thresholds are correct + for addr, txs := range pool.index { + if !txs[0].evictionExecTip.Eq(txs[0].execTipCap) { + t.Errorf("addr %v, tx %d eviction execution tip mismatch: have %d, want %d", addr, 0, txs[0].evictionExecTip, txs[0].execTipCap) + } + if math.Abs(txs[0].evictionExecFeeJumps-txs[0].basefeeJumps) > 0.001 { + t.Errorf("addr %v, tx %d eviction execution fee jumps mismatch: have %f, want %f", addr, 0, txs[0].evictionExecFeeJumps, txs[0].basefeeJumps) + } + if math.Abs(txs[0].evictionBlobFeeJumps-txs[0].blobfeeJumps) > 0.001 { + t.Errorf("addr %v, tx %d eviction blob fee jumps mismatch: have %f, want %f", addr, 0, txs[0].evictionBlobFeeJumps, txs[0].blobfeeJumps) + } + for i := 1; i < len(txs); i++ { + wantExecTip := txs[i-1].evictionExecTip + if wantExecTip.Gt(txs[i].execTipCap) { + wantExecTip = txs[i].execTipCap + } + if !txs[i].evictionExecTip.Eq(wantExecTip) { + t.Errorf("addr %v, tx %d eviction execution tip mismatch: have %d, want %d", addr, i, txs[i].evictionExecTip, wantExecTip) + } + + wantExecFeeJumps := txs[i-1].evictionExecFeeJumps + if wantExecFeeJumps > txs[i].basefeeJumps { + wantExecFeeJumps = txs[i].basefeeJumps + } + if math.Abs(txs[i].evictionExecFeeJumps-wantExecFeeJumps) > 0.001 { + t.Errorf("addr %v, tx %d eviction execution fee jumps mismatch: have %f, want %f", addr, i, txs[i].evictionExecFeeJumps, wantExecFeeJumps) + } + + wantBlobFeeJumps := txs[i-1].evictionBlobFeeJumps + if wantBlobFeeJumps > txs[i].blobfeeJumps { + wantBlobFeeJumps = txs[i].blobfeeJumps + } + if math.Abs(txs[i].evictionBlobFeeJumps-wantBlobFeeJumps) > 0.001 { + t.Errorf("addr %v, tx %d eviction blob fee jumps mismatch: have %f, want %f", addr, i, txs[i].evictionBlobFeeJumps, wantBlobFeeJumps) + } + } + } + // Verify that account balance accumulations are correct + for addr, txs := range pool.index { + spent := new(uint256.Int) + for _, tx := range txs { + spent.Add(spent, tx.costCap) + } + if !pool.spent[addr].Eq(spent) { + t.Errorf("addr %v expenditure mismatch: have %d, want %d", addr, pool.spent[addr], spent) + } + } + // Verify that pool storage size is correct + var stored uint64 + for _, txs := range pool.index { + for _, tx := range txs { + stored += uint64(tx.size) + } + } + if pool.stored != stored { + t.Errorf("pool storage mismatch: have %d, want %d", pool.stored, stored) + } + // Verify the price heap internals + verifyHeapInternals(t, pool.evict) +} + +// Tests that transactions can be loaded from disk on startup and that they are +// correctly discarded if invalid. +// +// - 1. A transaction that cannot be decoded must be dropped +// - 2. A transaction that cannot be recovered (bad signature) must be dropped +// - 3. All transactions after a nonce gap must be dropped +// - 4. All transactions after an underpriced one (including it) must be dropped +func TestOpenDrops(t *testing.T) { + log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + + // Create a temporary folder for the persistent backend + storage, _ := os.MkdirTemp("", "blobpool-") + defer os.RemoveAll(storage) + + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + + // Insert a malformed transaction to verify that decoding errors (or format + // changes) are handled gracefully (case 1) + malformed, _ := store.Put([]byte("this is a badly encoded transaction")) + + // Insert a transaction with a bad signature to verify that stale junk after + // potential hard-forks can get evicted (case 2) + tx := types.NewTx(&types.BlobTx{ + ChainID: uint256.MustFromBig(testChainConfig.ChainID), + GasTipCap: new(uint256.Int), + GasFeeCap: new(uint256.Int), + Gas: 0, + Value: new(uint256.Int), + Data: nil, + BlobFeeCap: new(uint256.Int), + V: new(uint256.Int), + R: new(uint256.Int), + S: new(uint256.Int), + }) + blob, _ := rlp.EncodeToBytes(tx) + badsig, _ := store.Put(blob) + + // Insert a sequence of transactions with a nonce gap in between to verify + // that anything gapped will get evicted (case 3) + var ( + gapper, _ = crypto.GenerateKey() + + valids = make(map[uint64]struct{}) + gapped = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{0, 1, 3, 4, 6, 7} { // first gap at #2, another at #5 + tx := makeTx(nonce, 1, 1, 1, gapper) + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + if nonce < 2 { + valids[id] = struct{}{} + } else { + gapped[id] = struct{}{} + } + } + // Insert a sequence of transactions with a gapped starting nonce to verify + // that the entire set will get dropped. + var ( + dangler, _ = crypto.GenerateKey() + dangling = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{1, 2, 3} { // first gap at #0, all set dangling + tx := makeTx(nonce, 1, 1, 1, dangler) + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + dangling[id] = struct{}{} + } + // Insert a sequence of transactions with already passed nonces to veirfy + // that the entire set will get dropped. + var ( + filler, _ = crypto.GenerateKey() + filled = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{0, 1, 2} { // account nonce at 3, all set filled + tx := makeTx(nonce, 1, 1, 1, filler) + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + filled[id] = struct{}{} + } + // Insert a sequence of transactions with partially passed nonces to veirfy + // that the included part of the set will get dropped + var ( + overlapper, _ = crypto.GenerateKey() + overlapped = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{0, 1, 2, 3} { // account nonce at 2, half filled + tx := makeTx(nonce, 1, 1, 1, overlapper) + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + if nonce >= 2 { + valids[id] = struct{}{} + } else { + overlapped[id] = struct{}{} + } + } + // Insert a sequence of transactions with an underpriced first to verify that + // the entire set will get dropped (case 4). + var ( + underpayer, _ = crypto.GenerateKey() + underpaid = make(map[uint64]struct{}) + ) + for i := 0; i < 5; i++ { // make #0 underpriced + var tx *types.Transaction + if i == 0 { + tx = makeTx(uint64(i), 0, 0, 0, underpayer) + } else { + tx = makeTx(uint64(i), 1, 1, 1, underpayer) + } + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + underpaid[id] = struct{}{} + } + + // Insert a sequence of transactions with an underpriced in between to verify + // that it and anything newly gapped will get evicted (case 4). + var ( + outpricer, _ = crypto.GenerateKey() + outpriced = make(map[uint64]struct{}) + ) + for i := 0; i < 5; i++ { // make #2 underpriced + var tx *types.Transaction + if i == 2 { + tx = makeTx(uint64(i), 0, 0, 0, outpricer) + } else { + tx = makeTx(uint64(i), 1, 1, 1, outpricer) + } + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + if i < 2 { + valids[id] = struct{}{} + } else { + outpriced[id] = struct{}{} + } + } + // Insert a sequence of transactions fully overdrafted to verify that the + // entire set will get invalidated. + var ( + exceeder, _ = crypto.GenerateKey() + exceeded = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{0, 1, 2} { // nonce 0 overdrafts the account + var tx *types.Transaction + if nonce == 0 { + tx = makeTx(nonce, 1, 100, 1, exceeder) + } else { + tx = makeTx(nonce, 1, 1, 1, exceeder) + } + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + exceeded[id] = struct{}{} + } + // Insert a sequence of transactions partially overdrafted to verify that part + // of the set will get invalidated. + var ( + overdrafter, _ = crypto.GenerateKey() + overdrafted = make(map[uint64]struct{}) + ) + for _, nonce := range []uint64{0, 1, 2} { // nonce 1 overdrafts the account + var tx *types.Transaction + if nonce == 1 { + tx = makeTx(nonce, 1, 100, 1, overdrafter) + } else { + tx = makeTx(nonce, 1, 1, 1, overdrafter) + } + blob, _ := rlp.EncodeToBytes(tx) + + id, _ := store.Put(blob) + if nonce < 1 { + valids[id] = struct{}{} + } else { + overdrafted[id] = struct{}{} + } + } + // Insert a sequence of transactions overflowing the account cap to verify + // that part of the set will get invalidated. + var ( + overcapper, _ = crypto.GenerateKey() + overcapped = make(map[uint64]struct{}) + ) + for nonce := uint64(0); nonce < maxTxsPerAccount+3; nonce++ { + blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, overcapper)) + + id, _ := store.Put(blob) + if nonce < maxTxsPerAccount { + valids[id] = struct{}{} + } else { + overcapped[id] = struct{}{} + } + } + store.Close() + + // Create a blob pool out of the pre-seeded data + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + statedb.AddBalance(crypto.PubkeyToAddress(gapper.PublicKey), big.NewInt(1000000)) + statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), big.NewInt(1000000)) + statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), big.NewInt(1000000)) + statedb.SetNonce(crypto.PubkeyToAddress(filler.PublicKey), 3) + statedb.AddBalance(crypto.PubkeyToAddress(overlapper.PublicKey), big.NewInt(1000000)) + statedb.SetNonce(crypto.PubkeyToAddress(overlapper.PublicKey), 2) + statedb.AddBalance(crypto.PubkeyToAddress(underpayer.PublicKey), big.NewInt(1000000)) + statedb.AddBalance(crypto.PubkeyToAddress(outpricer.PublicKey), big.NewInt(1000000)) + statedb.AddBalance(crypto.PubkeyToAddress(exceeder.PublicKey), big.NewInt(1000000)) + statedb.AddBalance(crypto.PubkeyToAddress(overdrafter.PublicKey), big.NewInt(1000000)) + statedb.AddBalance(crypto.PubkeyToAddress(overcapper.PublicKey), big.NewInt(10000000)) + statedb.Commit(0, true) + + chain := &testBlockChain{ + config: testChainConfig, + basefee: uint256.NewInt(params.InitialBaseFee), + blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), + statedb: statedb, + } + pool := New(Config{Datadir: storage}, chain) + if err := pool.Init(big.NewInt(1), chain.CurrentBlock(), makeAddressReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + defer pool.Close() + + // Verify that the malformed (case 1), badly signed (case 2) and gapped (case + // 3) txs have been deleted from the pool + alive := make(map[uint64]struct{}) + for _, txs := range pool.index { + for _, tx := range txs { + switch tx.id { + case malformed: + t.Errorf("malformed RLP transaction remained in storage") + case badsig: + t.Errorf("invalidly signed transaction remained in storage") + default: + if _, ok := dangling[tx.id]; ok { + t.Errorf("dangling transaction remained in storage: %d", tx.id) + } else if _, ok := filled[tx.id]; ok { + t.Errorf("filled transaction remained in storage: %d", tx.id) + } else if _, ok := overlapped[tx.id]; ok { + t.Errorf("overlapped transaction remained in storage: %d", tx.id) + } else if _, ok := gapped[tx.id]; ok { + t.Errorf("gapped transaction remained in storage: %d", tx.id) + } else if _, ok := underpaid[tx.id]; ok { + t.Errorf("underpaid transaction remained in storage: %d", tx.id) + } else if _, ok := outpriced[tx.id]; ok { + t.Errorf("outpriced transaction remained in storage: %d", tx.id) + } else if _, ok := exceeded[tx.id]; ok { + t.Errorf("fully overdrafted transaction remained in storage: %d", tx.id) + } else if _, ok := overdrafted[tx.id]; ok { + t.Errorf("partially overdrafted transaction remained in storage: %d", tx.id) + } else if _, ok := overcapped[tx.id]; ok { + t.Errorf("overcapped transaction remained in storage: %d", tx.id) + } else { + alive[tx.id] = struct{}{} + } + } + } + } + // Verify that the rest of the transactions remained alive + if len(alive) != len(valids) { + t.Errorf("valid transaction count mismatch: have %d, want %d", len(alive), len(valids)) + } + for id := range alive { + if _, ok := valids[id]; !ok { + t.Errorf("extra transaction %d", id) + } + } + for id := range valids { + if _, ok := alive[id]; !ok { + t.Errorf("missing transaction %d", id) + } + } + // Verify all the calculated pool internals. Interestingly, this is **not** + // a duplication of the above checks, this actually validates the verifier + // using the above already hard coded checks. + // + // Do not remove this, nor alter the above to be generic. + verifyPoolInternals(t, pool) +} + +// Tests that transactions loaded from disk are indexed corrently. +// +// - 1. Transactions must be groupped by sender, sorted by nonce +// - 2. Eviction thresholds are calculated correctly for the sequences +// - 3. Balance usage of an account is totals across all transactions +func TestOpenIndex(t *testing.T) { + log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + + // Create a temporary folder for the persistent backend + storage, _ := os.MkdirTemp("", "blobpool-") + defer os.RemoveAll(storage) + + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + + // Insert a sequence of transactions with varying price points to check that + // the cumulative minimumw will be maintained. + var ( + key, _ = crypto.GenerateKey() + addr = crypto.PubkeyToAddress(key.PublicKey) + + txExecTipCaps = []uint64{10, 25, 5, 7, 1, 100} + txExecFeeCaps = []uint64{100, 90, 200, 10, 80, 300} + txBlobFeeCaps = []uint64{55, 66, 77, 33, 22, 11} + + //basefeeJumps = []float64{39.098, 38.204, 44.983, 19.549, 37.204, 48.426} // log 1.125 (exec fee cap) + //blobfeeJumps = []float64{34.023, 35.570, 36.879, 29.686, 26.243, 20.358} // log 1.125 (blob fee cap) + + evictExecTipCaps = []uint64{10, 10, 5, 5, 1, 1} + evictExecFeeJumps = []float64{39.098, 38.204, 38.204, 19.549, 19.549, 19.549} // min(log 1.125 (exec fee cap)) + evictBlobFeeJumps = []float64{34.023, 34.023, 34.023, 29.686, 26.243, 20.358} // min(log 1.125 (blob fee cap)) + + totalSpent = uint256.NewInt(21000*(100+90+200+10+80+300) + blobSize*(55+66+77+33+22+11) + 100*6) // 21000 gas x price + 128KB x blobprice + value + ) + for _, i := range []int{5, 3, 4, 2, 0, 1} { // Randomize the tx insertion order to force sorting on load + tx := makeTx(uint64(i), txExecTipCaps[i], txExecFeeCaps[i], txBlobFeeCaps[i], key) + blob, _ := rlp.EncodeToBytes(tx) + store.Put(blob) + } + store.Close() + + // Create a blob pool out of the pre-seeded data + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + statedb.AddBalance(addr, big.NewInt(1_000_000_000)) + statedb.Commit(0, true) + + chain := &testBlockChain{ + config: testChainConfig, + basefee: uint256.NewInt(params.InitialBaseFee), + blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), + statedb: statedb, + } + pool := New(Config{Datadir: storage}, chain) + if err := pool.Init(big.NewInt(1), chain.CurrentBlock(), makeAddressReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + defer pool.Close() + + // Verify that the transactions have been sorted by nonce (case 1) + for i := 0; i < len(pool.index[addr]); i++ { + if pool.index[addr][i].nonce != uint64(i) { + t.Errorf("tx %d nonce mismatch: have %d, want %d", i, pool.index[addr][i].nonce, uint64(i)) + } + } + // Verify that the cumulative fee minimums have been correctly calculated (case 2) + for i, cap := range evictExecTipCaps { + if !pool.index[addr][i].evictionExecTip.Eq(uint256.NewInt(cap)) { + t.Errorf("eviction tip cap %d mismatch: have %d, want %d", i, pool.index[addr][i].evictionExecTip, cap) + } + } + for i, jumps := range evictExecFeeJumps { + if math.Abs(pool.index[addr][i].evictionExecFeeJumps-jumps) > 0.001 { + t.Errorf("eviction fee cap jumps %d mismatch: have %f, want %f", i, pool.index[addr][i].evictionExecFeeJumps, jumps) + } + } + for i, jumps := range evictBlobFeeJumps { + if math.Abs(pool.index[addr][i].evictionBlobFeeJumps-jumps) > 0.001 { + t.Errorf("eviction blob fee cap jumps %d mismatch: have %f, want %f", i, pool.index[addr][i].evictionBlobFeeJumps, jumps) + } + } + // Verify that the balance usage has been correctly calculated (case 3) + if !pool.spent[addr].Eq(totalSpent) { + t.Errorf("expenditure mismatch: have %d, want %d", pool.spent[addr], totalSpent) + } + // Verify all the calculated pool internals. Interestingly, this is **not** + // a duplication of the above checks, this actually validates the verifier + // using the above already hard coded checks. + // + // Do not remove this, nor alter the above to be generic. + verifyPoolInternals(t, pool) +} + +// Tests that after indexing all the loaded transactions from disk, a price heap +// is correctly constructed based on the head basefee and blobfee. +func TestOpenHeap(t *testing.T) { + log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + + // Create a temporary folder for the persistent backend + storage, _ := os.MkdirTemp("", "blobpool-") + defer os.RemoveAll(storage) + + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + + // Insert a few transactions from a few accounts. To remove randomness from + // the heap initialization, use a deterministic account/tx/priority ordering. + var ( + key1, _ = crypto.GenerateKey() + key2, _ = crypto.GenerateKey() + key3, _ = crypto.GenerateKey() + + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + ) + if bytes.Compare(addr1[:], addr2[:]) > 0 { + key1, addr1, key2, addr2 = key2, addr2, key1, addr1 + } + if bytes.Compare(addr1[:], addr3[:]) > 0 { + key1, addr1, key3, addr3 = key3, addr3, key1, addr1 + } + if bytes.Compare(addr2[:], addr3[:]) > 0 { + key2, addr2, key3, addr3 = key3, addr3, key2, addr2 + } + var ( + tx1 = makeTx(0, 1, 1000, 90, key1) + tx2 = makeTx(0, 1, 800, 70, key2) + tx3 = makeTx(0, 1, 1500, 110, key3) + + blob1, _ = rlp.EncodeToBytes(tx1) + blob2, _ = rlp.EncodeToBytes(tx2) + blob3, _ = rlp.EncodeToBytes(tx3) + + heapOrder = []common.Address{addr2, addr1, addr3} + heapIndex = map[common.Address]int{addr2: 0, addr1: 1, addr3: 2} + ) + store.Put(blob1) + store.Put(blob2) + store.Put(blob3) + store.Close() + + // Create a blob pool out of the pre-seeded data + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + statedb.AddBalance(addr1, big.NewInt(1_000_000_000)) + statedb.AddBalance(addr2, big.NewInt(1_000_000_000)) + statedb.AddBalance(addr3, big.NewInt(1_000_000_000)) + statedb.Commit(0, true) + + chain := &testBlockChain{ + config: testChainConfig, + basefee: uint256.NewInt(1050), + blobfee: uint256.NewInt(105), + statedb: statedb, + } + pool := New(Config{Datadir: storage}, chain) + if err := pool.Init(big.NewInt(1), chain.CurrentBlock(), makeAddressReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + defer pool.Close() + + // Verify that the heap's internal state matches the expectations + for i, addr := range pool.evict.addrs { + if addr != heapOrder[i] { + t.Errorf("slot %d mismatch: have %v, want %v", i, addr, heapOrder[i]) + } + } + for addr, i := range pool.evict.index { + if i != heapIndex[addr] { + t.Errorf("index for %v mismatch: have %d, want %d", addr, i, heapIndex[addr]) + } + } + // Verify all the calculated pool internals. Interestingly, this is **not** + // a duplication of the above checks, this actually validates the verifier + // using the above already hard coded checks. + // + // Do not remove this, nor alter the above to be generic. + verifyPoolInternals(t, pool) +} + +// Tests that after the pool's previous state is loaded back, any transactions +// over the new storage cap will get dropped. +func TestOpenCap(t *testing.T) { + log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + + // Create a temporary folder for the persistent backend + storage, _ := os.MkdirTemp("", "blobpool-") + defer os.RemoveAll(storage) + + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + + // Insert a few transactions from a few accounts + var ( + key1, _ = crypto.GenerateKey() + key2, _ = crypto.GenerateKey() + key3, _ = crypto.GenerateKey() + + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + + tx1 = makeTx(0, 1, 1000, 100, key1) + tx2 = makeTx(0, 1, 800, 70, key2) + tx3 = makeTx(0, 1, 1500, 110, key3) + + blob1, _ = rlp.EncodeToBytes(tx1) + blob2, _ = rlp.EncodeToBytes(tx2) + blob3, _ = rlp.EncodeToBytes(tx3) + + keep = []common.Address{addr1, addr3} + drop = []common.Address{addr2} + size = uint64(2 * (txAvgSize + blobSize)) + ) + store.Put(blob1) + store.Put(blob2) + store.Put(blob3) + store.Close() + + // Verify pool capping twice: first by reducing the data cap, then restarting + // with a high cap to ensure everything was persisted previously + for _, datacap := range []uint64{2 * (txAvgSize + blobSize), 100 * (txAvgSize + blobSize)} { + // Create a blob pool out of the pre-seeded data, but cap it to 2 blob transaction + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + statedb.AddBalance(addr1, big.NewInt(1_000_000_000)) + statedb.AddBalance(addr2, big.NewInt(1_000_000_000)) + statedb.AddBalance(addr3, big.NewInt(1_000_000_000)) + statedb.Commit(0, true) + + chain := &testBlockChain{ + config: testChainConfig, + basefee: uint256.NewInt(1050), + blobfee: uint256.NewInt(105), + statedb: statedb, + } + pool := New(Config{Datadir: storage, Datacap: datacap}, chain) + if err := pool.Init(big.NewInt(1), chain.CurrentBlock(), makeAddressReserver()); err != nil { + t.Fatalf("failed to create blob pool: %v", err) + } + // Verify that enough transactions have been dropped to get the pool's size + // under the requested limit + if len(pool.index) != len(keep) { + t.Errorf("tracked account count mismatch: have %d, want %d", len(pool.index), len(keep)) + } + for _, addr := range keep { + if _, ok := pool.index[addr]; !ok { + t.Errorf("expected account %v missing from pool", addr) + } + } + for _, addr := range drop { + if _, ok := pool.index[addr]; ok { + t.Errorf("unexpected account %v present in pool", addr) + } + } + if pool.stored != size { + t.Errorf("pool stored size mismatch: have %v, want %v", pool.stored, size) + } + // Verify all the calculated pool internals. Interestingly, this is **not** + // a duplication of the above checks, this actually validates the verifier + // using the above already hard coded checks. + // + // Do not remove this, nor alter the above to be generic. + verifyPoolInternals(t, pool) + + pool.Close() + } +} + +// Tests that adding transaction will correctly store it in the persistent store +// and update all the indices. +// +// Note, this tests mostly checks the pool transaction shuffling logic or things +// specific to the blob pool. It does not do an exhaustive transaction validity +// check. +func TestAdd(t *testing.T) { + log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + + // seed is a helper tumpe to seed an initial state db and pool + type seed struct { + balance uint64 + nonce uint64 + txs []*types.BlobTx + } + + // addtx is a helper sender/tx tuple to represent a new tx addition + type addtx struct { + from string + tx *types.BlobTx + err error + } + + tests := []struct { + seeds map[string]seed + adds []addtx + }{ + // Transactions from new accounts should be accepted if their initial + // nonce matches the expected one from the statedb. Higher or lower must + // be rejected. + { + seeds: map[string]seed{ + "alice": {balance: 21100 + blobSize}, + "bob": {balance: 21100 + blobSize, nonce: 1}, + "claire": {balance: 21100 + blobSize}, + "dave": {balance: 21100 + blobSize, nonce: 1}, + }, + adds: []addtx{ + { // New account, no previous txs: accept nonce 0 + from: "alice", + tx: makeUnsignedTx(0, 1, 1, 1), + err: nil, + }, + { // Old account, 1 tx in chain, 0 pending: accept nonce 1 + from: "bob", + tx: makeUnsignedTx(1, 1, 1, 1), + err: nil, + }, + { // New account, no previous txs: reject nonce 1 + from: "claire", + tx: makeUnsignedTx(1, 1, 1, 1), + err: core.ErrNonceTooHigh, + }, + { // Old account, 1 tx in chain, 0 pending: reject nonce 0 + from: "dave", + tx: makeUnsignedTx(0, 1, 1, 1), + err: core.ErrNonceTooLow, + }, + { // Old account, 1 tx in chain, 0 pending: reject nonce 2 + from: "dave", + tx: makeUnsignedTx(2, 1, 1, 1), + err: core.ErrNonceTooHigh, + }, + }, + }, + // Transactions from already pooled accounts should only be accepted if + // the nonces are contiguous (ignore prices for now, will check later) + { + seeds: map[string]seed{ + "alice": { + balance: 1000000, + txs: []*types.BlobTx{ + makeUnsignedTx(0, 1, 1, 1), + }, + }, + "bob": { + balance: 1000000, + nonce: 1, + txs: []*types.BlobTx{ + makeUnsignedTx(1, 1, 1, 1), + }, + }, + }, + adds: []addtx{ + { // New account, 1 tx pending: reject replacement nonce 0 (ignore price for now) + from: "alice", + tx: makeUnsignedTx(0, 1, 1, 1), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 1 tx pending: accept nonce 1 + from: "alice", + tx: makeUnsignedTx(1, 1, 1, 1), + err: nil, + }, + { // New account, 2 txs pending: reject nonce 3 + from: "alice", + tx: makeUnsignedTx(3, 1, 1, 1), + err: core.ErrNonceTooHigh, + }, + { // New account, 2 txs pending: accept nonce 2 + from: "alice", + tx: makeUnsignedTx(2, 1, 1, 1), + err: nil, + }, + { // New account, 3 txs pending: accept nonce 3 now + from: "alice", + tx: makeUnsignedTx(3, 1, 1, 1), + err: nil, + }, + { // Old account, 1 tx in chain, 1 tx pending: reject replacement nonce 1 (ignore price for now) + from: "bob", + tx: makeUnsignedTx(1, 1, 1, 1), + err: txpool.ErrReplaceUnderpriced, + }, + { // Old account, 1 tx in chain, 1 tx pending: accept nonce 2 (ignore price for now) + from: "bob", + tx: makeUnsignedTx(2, 1, 1, 1), + err: nil, + }, + }, + }, + // Transactions should only be accepted into the pool if the cumulative + // expenditure doesn't overflow the account balance + { + seeds: map[string]seed{ + "alice": {balance: 63299 + 3*blobSize}, // 3 tx - 1 wei + }, + adds: []addtx{ + { // New account, no previous txs: accept nonce 0 with 21100 wei spend + from: "alice", + tx: makeUnsignedTx(0, 1, 1, 1), + err: nil, + }, + { // New account, 1 pooled tx with 21100 wei spent: accept nonce 1 with 21100 wei spend + from: "alice", + tx: makeUnsignedTx(1, 1, 1, 1), + err: nil, + }, + { // New account, 2 pooled tx with 42200 wei spent: reject nonce 2 with 21100 wei spend (1 wei overflow) + from: "alice", + tx: makeUnsignedTx(2, 1, 1, 1), + err: core.ErrInsufficientFunds, + }, + }, + }, + // Transactions should only be accepted into the pool if the total count + // from the same account doesn't overflow the pool limits + { + seeds: map[string]seed{ + "alice": {balance: 10000000}, + }, + adds: []addtx{ + { // New account, no previous txs, 16 slots left: accept nonce 0 + from: "alice", + tx: makeUnsignedTx(0, 1, 1, 1), + err: nil, + }, + { // New account, 1 pooled tx, 15 slots left: accept nonce 1 + from: "alice", + tx: makeUnsignedTx(1, 1, 1, 1), + err: nil, + }, + { // New account, 2 pooled tx, 14 slots left: accept nonce 2 + from: "alice", + tx: makeUnsignedTx(2, 1, 1, 1), + err: nil, + }, + { // New account, 3 pooled tx, 13 slots left: accept nonce 3 + from: "alice", + tx: makeUnsignedTx(3, 1, 1, 1), + err: nil, + }, + { // New account, 4 pooled tx, 12 slots left: accept nonce 4 + from: "alice", + tx: makeUnsignedTx(4, 1, 1, 1), + err: nil, + }, + { // New account, 5 pooled tx, 11 slots left: accept nonce 5 + from: "alice", + tx: makeUnsignedTx(5, 1, 1, 1), + err: nil, + }, + { // New account, 6 pooled tx, 10 slots left: accept nonce 6 + from: "alice", + tx: makeUnsignedTx(6, 1, 1, 1), + err: nil, + }, + { // New account, 7 pooled tx, 9 slots left: accept nonce 7 + from: "alice", + tx: makeUnsignedTx(7, 1, 1, 1), + err: nil, + }, + { // New account, 8 pooled tx, 8 slots left: accept nonce 8 + from: "alice", + tx: makeUnsignedTx(8, 1, 1, 1), + err: nil, + }, + { // New account, 9 pooled tx, 7 slots left: accept nonce 9 + from: "alice", + tx: makeUnsignedTx(9, 1, 1, 1), + err: nil, + }, + { // New account, 10 pooled tx, 6 slots left: accept nonce 10 + from: "alice", + tx: makeUnsignedTx(10, 1, 1, 1), + err: nil, + }, + { // New account, 11 pooled tx, 5 slots left: accept nonce 11 + from: "alice", + tx: makeUnsignedTx(11, 1, 1, 1), + err: nil, + }, + { // New account, 12 pooled tx, 4 slots left: accept nonce 12 + from: "alice", + tx: makeUnsignedTx(12, 1, 1, 1), + err: nil, + }, + { // New account, 13 pooled tx, 3 slots left: accept nonce 13 + from: "alice", + tx: makeUnsignedTx(13, 1, 1, 1), + err: nil, + }, + { // New account, 14 pooled tx, 2 slots left: accept nonce 14 + from: "alice", + tx: makeUnsignedTx(14, 1, 1, 1), + err: nil, + }, + { // New account, 15 pooled tx, 1 slots left: accept nonce 15 + from: "alice", + tx: makeUnsignedTx(15, 1, 1, 1), + err: nil, + }, + { // New account, 16 pooled tx, 0 slots left: accept nonce 15 replacement + from: "alice", + tx: makeUnsignedTx(15, 10, 10, 10), + err: nil, + }, + { // New account, 16 pooled tx, 0 slots left: reject nonce 16 with overcap + from: "alice", + tx: makeUnsignedTx(16, 1, 1, 1), + err: txpool.ErrAccountLimitExceeded, + }, + }, + }, + // Previously existing transactions should be allowed to be replaced iff + // the new cumulative expenditure can be covered by the account and the + // prices are bumped all around (no percentage check here). + { + seeds: map[string]seed{ + "alice": {balance: 2*100 + 5*21000 + 3*blobSize}, + }, + adds: []addtx{ + { // New account, no previous txs: reject nonce 0 with 341172 wei spend + from: "alice", + tx: makeUnsignedTx(0, 1, 20, 1), + err: core.ErrInsufficientFunds, + }, + { // New account, no previous txs: accept nonce 0 with 173172 wei spend + from: "alice", + tx: makeUnsignedTx(0, 1, 2, 1), + err: nil, + }, + { // New account, 1 pooled tx with 173172 wei spent: accept nonce 1 with 152172 wei spend + from: "alice", + tx: makeUnsignedTx(1, 1, 1, 1), + err: nil, + }, + { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with 599684 wei spend (173072 extra) (would overflow balance at nonce 1) + from: "alice", + tx: makeUnsignedTx(0, 2, 5, 2), + err: core.ErrInsufficientFunds, + }, + { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-gastip-bump + from: "alice", + tx: makeUnsignedTx(0, 1, 3, 2), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-gascap-bump + from: "alice", + tx: makeUnsignedTx(0, 2, 2, 2), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-blobcap-bump + from: "alice", + tx: makeUnsignedTx(0, 2, 4, 1), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 2 pooled tx with 325344 wei spent: accept nonce 0 with 84100 wei spend (42000 extra) + from: "alice", + tx: makeUnsignedTx(0, 2, 4, 2), + err: nil, + }, + }, + }, + // Previously existing transactions should be allowed to be replaced iff + // the new prices are bumped by a sufficient amount. + { + seeds: map[string]seed{ + "alice": {balance: 100 + 8*21000 + 4*blobSize}, + }, + adds: []addtx{ + { // New account, no previous txs: accept nonce 0 + from: "alice", + tx: makeUnsignedTx(0, 2, 4, 2), + err: nil, + }, + { // New account, 1 pooled tx: reject nonce 0 with low-gastip-bump + from: "alice", + tx: makeUnsignedTx(0, 3, 8, 4), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 1 pooled tx: reject nonce 0 with low-gascap-bump + from: "alice", + tx: makeUnsignedTx(0, 4, 6, 4), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 1 pooled tx: reject nonce 0 with low-blobcap-bump + from: "alice", + tx: makeUnsignedTx(0, 4, 8, 3), + err: txpool.ErrReplaceUnderpriced, + }, + { // New account, 1 pooled tx: accept nonce 0 with all-bumps + from: "alice", + tx: makeUnsignedTx(0, 4, 8, 4), + err: nil, + }, + }, + }, + } + for i, tt := range tests { + // Create a temporary folder for the persistent backend + storage, _ := os.MkdirTemp("", "blobpool-") + defer os.RemoveAll(storage) // late defer, still ok + + os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) + store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) + + // Insert the seed transactions for the pool startup + var ( + keys = make(map[string]*ecdsa.PrivateKey) + addrs = make(map[string]common.Address) + ) + statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) + for acc, seed := range tt.seeds { + // Generate a new random key/address for the seed account + keys[acc], _ = crypto.GenerateKey() + addrs[acc] = crypto.PubkeyToAddress(keys[acc].PublicKey) + + // Seed the state database with this acocunt + statedb.AddBalance(addrs[acc], new(big.Int).SetUint64(seed.balance)) + statedb.SetNonce(addrs[acc], seed.nonce) + + // Sign the seed transactions and store them in the data store + for _, tx := range seed.txs { + signed := types.MustSignNewTx(keys[acc], types.LatestSigner(testChainConfig), tx) + blob, _ := rlp.EncodeToBytes(signed) + store.Put(blob) + } + } + statedb.Commit(0, true) + store.Close() + + // Create a blob pool out of the pre-seeded dats + chain := &testBlockChain{ + config: testChainConfig, + basefee: uint256.NewInt(1050), + blobfee: uint256.NewInt(105), + statedb: statedb, + } + pool := New(Config{Datadir: storage}, chain) + if err := pool.Init(big.NewInt(1), chain.CurrentBlock(), makeAddressReserver()); err != nil { + t.Fatalf("test %d: failed to create blob pool: %v", i, err) + } + verifyPoolInternals(t, pool) + + // Add each transaction one by one, verifying the pool internals in between + for j, add := range tt.adds { + signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(testChainConfig), add.tx) + if err := pool.add(signed); !errors.Is(err, add.err) { + t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err) + } + verifyPoolInternals(t, pool) + } + // Verify the pool internals and close down the test + verifyPoolInternals(t, pool) + pool.Close() + } +} diff --git a/core/txpool/blobpool/config.go b/core/txpool/blobpool/config.go new file mode 100644 index 000000000000..99a2002a303f --- /dev/null +++ b/core/txpool/blobpool/config.go @@ -0,0 +1,50 @@ +// Copyright 2022 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 blobpool + +import ( + "github.com/ethereum/go-ethereum/log" +) + +// Config are the configuration parameters of the blob transaction pool. +type Config struct { + Datadir string // Data directory containing the currently executable blobs + Datacap uint64 // Soft-cap of database storage (hard cap is larger due to overhead) + PriceBump uint64 // Minimum price bump percentage to replace an already existing nonce +} + +// DefaultConfig contains the default configurations for the transaction pool. +var DefaultConfig = Config{ + Datadir: "blobpool", + Datacap: 10 * 1024 * 1024 * 1024, + PriceBump: 100, // either have patience or be aggressive, no mushy ground +} + +// sanitize checks the provided user configurations and changes anything that's +// unreasonable or unworkable. +func (config *Config) sanitize() Config { + conf := *config + if conf.Datacap < 1 { + log.Warn("Sanitizing invalid blobpool storage cap", "provided", conf.Datacap, "updated", DefaultConfig.Datacap) + conf.Datacap = DefaultConfig.Datacap + } + if conf.PriceBump < 1 { + log.Warn("Sanitizing invalid blobpool price bump", "provided", conf.PriceBump, "updated", DefaultConfig.PriceBump) + conf.PriceBump = DefaultConfig.PriceBump + } + return conf +} diff --git a/core/txpool/blobpool/evictheap.go b/core/txpool/blobpool/evictheap.go new file mode 100644 index 000000000000..df594099f79b --- /dev/null +++ b/core/txpool/blobpool/evictheap.go @@ -0,0 +1,146 @@ +// Copyright 2023 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 blobpool + +import ( + "bytes" + "container/heap" + "math" + "sort" + + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" +) + +// evictHeap is a helper data structure to keep track of the cheapest bottleneck +// transaction from each account to determine which account to evict from. +// +// The heap internally tracks a slice of cheapest transactions from each account +// and a mapping from addresses to indices for direct removals/udates. +// +// The goal of the heap is to decide which account has the worst bottleneck to +// evict transactions from. +type evictHeap struct { + metas *map[common.Address][]*blobTxMeta // Pointer to the blob pool's index for price retrievals + + basefeeJumps float64 // Pre-calculated absolute dynamic fee jumps for the base fee + blobfeeJumps float64 // Pre-calculated absolute dynamic fee jumps for the blob fee + + addrs []common.Address // Heap of addresses to retrieve the cheapest out of + index map[common.Address]int // Indices into the heap for replacements +} + +// newPriceHeap creates a new heap of cheapest accounts in the blob pool to evict +// from in case of over saturation. +func newPriceHeap(basefee *uint256.Int, blobfee *uint256.Int, index *map[common.Address][]*blobTxMeta) *evictHeap { + heap := &evictHeap{ + metas: index, + index: make(map[common.Address]int), + } + // Populate the heap in account sort order. Not really needed in practice, + // but it makes the heap initialization deterministic and less annoying to + // test in unit tests. + addrs := make([]common.Address, 0, len(*index)) + for addr := range *index { + addrs = append(addrs, addr) + } + sort.Slice(addrs, func(i, j int) bool { return bytes.Compare(addrs[i][:], addrs[j][:]) < 0 }) + + for _, addr := range addrs { + heap.index[addr] = len(heap.addrs) + heap.addrs = append(heap.addrs, addr) + } + heap.reinit(basefee, blobfee, true) + return heap +} + +// reinit updates the pre-calculated dynamic fee jumps in the price heap and runs +// the sorting algorithm from scratch on the entire heap. +func (h *evictHeap) reinit(basefee *uint256.Int, blobfee *uint256.Int, force bool) { + // If the update is mostly the same as the old, don't sort pointlessly + basefeeJumps := dynamicFeeJumps(basefee) + blobfeeJumps := dynamicFeeJumps(blobfee) + + if !force && math.Abs(h.basefeeJumps-basefeeJumps) < 0.01 && math.Abs(h.blobfeeJumps-blobfeeJumps) < 0.01 { // TODO(karalabe): 0.01 enough, maybe should be smaller? Maybe this optimization is moot? + return + } + // One or both of the dynamic fees jumped, resort the pool + h.basefeeJumps = basefeeJumps + h.blobfeeJumps = blobfeeJumps + + heap.Init(h) +} + +// Len implements sort.Interface as part of heap.Interface, returning the number +// of accounts in the pool which can be considered for eviction. +func (h *evictHeap) Len() int { + return len(h.addrs) +} + +// Less implements sort.Interface as part of heap.Interface, returning which of +// the two requested accounts has a cheaper bottleneck. +func (h *evictHeap) Less(i, j int) bool { + txsI := (*(h.metas))[h.addrs[i]] + txsJ := (*(h.metas))[h.addrs[j]] + + lastI := txsI[len(txsI)-1] + lastJ := txsJ[len(txsJ)-1] + + prioI := evictionPriority(h.basefeeJumps, lastI.evictionExecFeeJumps, h.blobfeeJumps, lastI.evictionBlobFeeJumps) + if prioI > 0 { + prioI = 0 + } + prioJ := evictionPriority(h.basefeeJumps, lastJ.evictionExecFeeJumps, h.blobfeeJumps, lastJ.evictionBlobFeeJumps) + if prioJ > 0 { + prioJ = 0 + } + if prioI == prioJ { + return lastI.evictionExecTip.Lt(lastJ.evictionExecTip) + } + return prioI < prioJ +} + +// Swap implements sort.Interface as part of heap.Interface, maintaining both the +// order of the accounts according to the heap, and the account->item slot mapping +// for replacements. +func (h *evictHeap) Swap(i, j int) { + h.index[h.addrs[i]], h.index[h.addrs[j]] = h.index[h.addrs[j]], h.index[h.addrs[i]] + h.addrs[i], h.addrs[j] = h.addrs[j], h.addrs[i] +} + +// Push implements heap.Interface, appending an item to the end of the account +// ordering as well as the address to item slot mapping. +func (h *evictHeap) Push(x any) { + h.index[x.(common.Address)] = len(h.addrs) + h.addrs = append(h.addrs, x.(common.Address)) +} + +// Pop implements heap.Interface, removing and returning the last element of the +// heap. +// +// Note, use `heap.Pop`, not `evictHeap.Pop`. This method is used by Go's heap, +// to provide the functionality, it does not embed it. +func (h *evictHeap) Pop() any { + // Remove the last element from the heap + size := len(h.addrs) + addr := h.addrs[size-1] + h.addrs = h.addrs[:size-1] + + // Unindex the removed element and return + delete(h.index, addr) + return addr +} diff --git a/core/txpool/blobpool/evictheap_test.go b/core/txpool/blobpool/evictheap_test.go new file mode 100644 index 000000000000..01b136551cf2 --- /dev/null +++ b/core/txpool/blobpool/evictheap_test.go @@ -0,0 +1,320 @@ +// Copyright 2023 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 blobpool + +import ( + "container/heap" + mrand "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +var rand = mrand.New(mrand.NewSource(1)) + +// verifyHeapInternals verifies that all accounts present in the index are also +// present in the heap and internals are consistent across various indices. +func verifyHeapInternals(t *testing.T, evict *evictHeap) { + t.Helper() + + // Ensure that all accounts are present in the heap and no extras + seen := make(map[common.Address]struct{}) + for i, addr := range evict.addrs { + seen[addr] = struct{}{} + if _, ok := (*evict.metas)[addr]; !ok { + t.Errorf("heap contains unexpected address at slot %d: %v", i, addr) + } + } + for addr := range *evict.metas { + if _, ok := seen[addr]; !ok { + t.Errorf("heap is missing required address %v", addr) + } + } + if len(evict.addrs) != len(*evict.metas) { + t.Errorf("heap size %d mismatches metadata size %d", len(evict.addrs), len(*evict.metas)) + } + // Ensure that all accounts are present in the heap order index and no extras + have := make([]common.Address, len(evict.index)) + for addr, i := range evict.index { + have[i] = addr + } + if len(have) != len(evict.addrs) { + t.Errorf("heap index size %d mismatches heap size %d", len(have), len(evict.addrs)) + } + for i := 0; i < len(have) && i < len(evict.addrs); i++ { + if have[i] != evict.addrs[i] { + t.Errorf("heap index for slot %d mismatches: have %v, want %v", i, have[i], evict.addrs[i]) + } + } +} + +// Tests that the price heap can correctly sort its set of transactions based on +// an input base- and blob fee. +func TestPriceHeapSorting(t *testing.T) { + tests := []struct { + execTips []uint64 + execFees []uint64 + blobFees []uint64 + + basefee uint64 + blobfee uint64 + + order []int + }{ + // If everything is above the basefee and blobfee, order by miner tip + { + execTips: []uint64{1, 0, 2}, + execFees: []uint64{1, 2, 3}, + blobFees: []uint64{3, 2, 1}, + basefee: 0, + blobfee: 0, + order: []int{1, 0, 2}, + }, + // If only basefees are used (blob fee matches with network), return the + // ones the furthest below the current basefee, splitting same ones with + // the tip. Anything above the basefee should be split by tip. + { + execTips: []uint64{100, 50, 100, 50, 1, 2, 3}, + execFees: []uint64{1000, 1000, 500, 500, 2000, 2000, 2000}, + blobFees: []uint64{0, 0, 0, 0, 0, 0, 0}, + basefee: 1999, + blobfee: 0, + order: []int{3, 2, 1, 0, 4, 5, 6}, + }, + // If only blobfees are used (base fee matches with network), return the + // ones the furthest below the current blobfee, splitting same ones with + // the tip. Anything above the blobfee should be split by tip. + { + execTips: []uint64{100, 50, 100, 50, 1, 2, 3}, + execFees: []uint64{0, 0, 0, 0, 0, 0, 0}, + blobFees: []uint64{1000, 1000, 500, 500, 2000, 2000, 2000}, + basefee: 0, + blobfee: 1999, + order: []int{3, 2, 1, 0, 4, 5, 6}, + }, + // If both basefee and blobfee is specified, sort by the larger distance + // of the two from the current network conditions, splitting same (loglog) + // ones via the tip. + // + // Basefee: 1000 + // Blobfee: 100 + // + // Tx #0: (800, 80) - 2 jumps below both => priority -1 + // Tx #1: (630, 63) - 4 jumps below both => priority -2 + // Tx #2: (800, 63) - 2 jumps below basefee, 4 jumps below blobfee => priority -2 (blob penalty dominates) + // Tx #3: (630, 80) - 4 jumps below basefee, 2 jumps below blobfee => priority -2 (base penalty dominates) + // + // Txs 1, 2, 3 share the same priority, split via tip, prefer 0 as the best + { + execTips: []uint64{1, 2, 3, 4}, + execFees: []uint64{800, 630, 800, 630}, + blobFees: []uint64{80, 63, 63, 80}, + basefee: 1000, + blobfee: 100, + order: []int{1, 2, 3, 0}, + }, + } + for i, tt := range tests { + // Create an index of the transactions + index := make(map[common.Address][]*blobTxMeta) + for j := byte(0); j < byte(len(tt.execTips)); j++ { + addr := common.Address{j} + + var ( + execTip = uint256.NewInt(tt.execTips[j]) + execFee = uint256.NewInt(tt.execFees[j]) + blobFee = uint256.NewInt(tt.blobFees[j]) + + basefeeJumps = dynamicFeeJumps(execFee) + blobfeeJumps = dynamicFeeJumps(blobFee) + ) + index[addr] = []*blobTxMeta{{ + id: uint64(j), + size: 128 * 1024, + nonce: 0, + execTipCap: execTip, + execFeeCap: execFee, + blobFeeCap: blobFee, + basefeeJumps: basefeeJumps, + blobfeeJumps: blobfeeJumps, + evictionExecTip: execTip, + evictionExecFeeJumps: basefeeJumps, + evictionBlobFeeJumps: blobfeeJumps, + }} + } + // Create a price heap and check the pop order + priceheap := newPriceHeap(uint256.NewInt(tt.basefee), uint256.NewInt(tt.blobfee), &index) + verifyHeapInternals(t, priceheap) + + for j := 0; j < len(tt.order); j++ { + if next := heap.Pop(priceheap); int(next.(common.Address)[0]) != tt.order[j] { + t.Errorf("test %d, item %d: order mismatch: have %d, want %d", i, j, next.(common.Address)[0], tt.order[j]) + } else { + delete(index, next.(common.Address)) // remove to simulate a correct pool for the test + } + verifyHeapInternals(t, priceheap) + } + } +} + +// Benchmarks reheaping the entire set of accounts in the blob pool. +func BenchmarkPriceHeapReinit1MB(b *testing.B) { benchmarkPriceHeapReinit(b, 1024*1024) } +func BenchmarkPriceHeapReinit10MB(b *testing.B) { benchmarkPriceHeapReinit(b, 10*1024*1024) } +func BenchmarkPriceHeapReinit100MB(b *testing.B) { benchmarkPriceHeapReinit(b, 100*1024*1024) } +func BenchmarkPriceHeapReinit1GB(b *testing.B) { benchmarkPriceHeapReinit(b, 1024*1024*1024) } +func BenchmarkPriceHeapReinit10GB(b *testing.B) { benchmarkPriceHeapReinit(b, 10*1024*1024*1024) } +func BenchmarkPriceHeapReinit25GB(b *testing.B) { benchmarkPriceHeapReinit(b, 25*1024*1024*1024) } +func BenchmarkPriceHeapReinit50GB(b *testing.B) { benchmarkPriceHeapReinit(b, 50*1024*1024*1024) } +func BenchmarkPriceHeapReinit100GB(b *testing.B) { benchmarkPriceHeapReinit(b, 100*1024*1024*1024) } + +func benchmarkPriceHeapReinit(b *testing.B, datacap uint64) { + // Calculate how many unique transactions we can fit into the provided disk + // data cap + blobs := datacap / (params.BlobTxBytesPerFieldElement * params.BlobTxFieldElementsPerBlob) + + // Create a random set of transactions with random fees. Use a separate account + // for each transaction to make it worse case. + index := make(map[common.Address][]*blobTxMeta) + for i := 0; i < int(blobs); i++ { + var addr common.Address + rand.Read(addr[:]) + + var ( + execTip = uint256.NewInt(rand.Uint64()) + execFee = uint256.NewInt(rand.Uint64()) + blobFee = uint256.NewInt(rand.Uint64()) + + basefeeJumps = dynamicFeeJumps(execFee) + blobfeeJumps = dynamicFeeJumps(blobFee) + ) + index[addr] = []*blobTxMeta{{ + id: uint64(i), + size: 128 * 1024, + nonce: 0, + execTipCap: execTip, + execFeeCap: execFee, + blobFeeCap: blobFee, + basefeeJumps: basefeeJumps, + blobfeeJumps: blobfeeJumps, + evictionExecTip: execTip, + evictionExecFeeJumps: basefeeJumps, + evictionBlobFeeJumps: blobfeeJumps, + }} + } + // Create a price heap and reinit it over and over + heap := newPriceHeap(uint256.NewInt(rand.Uint64()), uint256.NewInt(rand.Uint64()), &index) + + basefees := make([]*uint256.Int, b.N) + blobfees := make([]*uint256.Int, b.N) + for i := 0; i < b.N; i++ { + basefees[i] = uint256.NewInt(rand.Uint64()) + blobfees[i] = uint256.NewInt(rand.Uint64()) + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + heap.reinit(basefees[i], blobfees[i], true) + } +} + +// Benchmarks overflowing the heap over and over (add and then drop). +func BenchmarkPriceHeapOverflow1MB(b *testing.B) { benchmarkPriceHeapOverflow(b, 1024*1024) } +func BenchmarkPriceHeapOverflow10MB(b *testing.B) { benchmarkPriceHeapOverflow(b, 10*1024*1024) } +func BenchmarkPriceHeapOverflow100MB(b *testing.B) { benchmarkPriceHeapOverflow(b, 100*1024*1024) } +func BenchmarkPriceHeapOverflow1GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 1024*1024*1024) } +func BenchmarkPriceHeapOverflow10GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 10*1024*1024*1024) } +func BenchmarkPriceHeapOverflow25GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 25*1024*1024*1024) } +func BenchmarkPriceHeapOverflow50GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 50*1024*1024*1024) } +func BenchmarkPriceHeapOverflow100GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 100*1024*1024*1024) } + +func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) { + // Calculate how many unique transactions we can fit into the provided disk + // data cap + blobs := datacap / (params.BlobTxBytesPerFieldElement * params.BlobTxFieldElementsPerBlob) + + // Create a random set of transactions with random fees. Use a separate account + // for each transaction to make it worse case. + index := make(map[common.Address][]*blobTxMeta) + for i := 0; i < int(blobs); i++ { + var addr common.Address + rand.Read(addr[:]) + + var ( + execTip = uint256.NewInt(rand.Uint64()) + execFee = uint256.NewInt(rand.Uint64()) + blobFee = uint256.NewInt(rand.Uint64()) + + basefeeJumps = dynamicFeeJumps(execFee) + blobfeeJumps = dynamicFeeJumps(blobFee) + ) + index[addr] = []*blobTxMeta{{ + id: uint64(i), + size: 128 * 1024, + nonce: 0, + execTipCap: execTip, + execFeeCap: execFee, + blobFeeCap: blobFee, + basefeeJumps: basefeeJumps, + blobfeeJumps: blobfeeJumps, + evictionExecTip: execTip, + evictionExecFeeJumps: basefeeJumps, + evictionBlobFeeJumps: blobfeeJumps, + }} + } + // Create a price heap and overflow it over and over + evict := newPriceHeap(uint256.NewInt(rand.Uint64()), uint256.NewInt(rand.Uint64()), &index) + var ( + addrs = make([]common.Address, b.N) + metas = make([]*blobTxMeta, b.N) + ) + for i := 0; i < b.N; i++ { + rand.Read(addrs[i][:]) + + var ( + execTip = uint256.NewInt(rand.Uint64()) + execFee = uint256.NewInt(rand.Uint64()) + blobFee = uint256.NewInt(rand.Uint64()) + + basefeeJumps = dynamicFeeJumps(execFee) + blobfeeJumps = dynamicFeeJumps(blobFee) + ) + metas[i] = &blobTxMeta{ + id: uint64(int(blobs) + i), + size: 128 * 1024, + nonce: 0, + execTipCap: execTip, + execFeeCap: execFee, + blobFeeCap: blobFee, + basefeeJumps: basefeeJumps, + blobfeeJumps: blobfeeJumps, + evictionExecTip: execTip, + evictionExecFeeJumps: basefeeJumps, + evictionBlobFeeJumps: blobfeeJumps, + } + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + index[addrs[i]] = []*blobTxMeta{metas[i]} + heap.Push(evict, addrs[i]) + + drop := heap.Pop(evict) + delete(index, drop.(common.Address)) + } +} diff --git a/core/txpool/blobpool/interface.go b/core/txpool/blobpool/interface.go new file mode 100644 index 000000000000..6f296a54bd63 --- /dev/null +++ b/core/txpool/blobpool/interface.go @@ -0,0 +1,44 @@ +// Copyright 2023 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 blobpool + +import ( + "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/params" +) + +// BlockChain defines the minimal set of methods needed to back a blob pool with +// a chain. Exists to allow mocking the live chain out of tests. +type BlockChain interface { + // Config retrieves the chain's fork configuration. + Config() *params.ChainConfig + + // CurrentBlock returns the current head of the chain. + CurrentBlock() *types.Header + + // CurrentFinalBlock returns the current block below which blobs should not + // be maintained anymore for reorg purposes. + CurrentFinalBlock() *types.Header + + // GetBlock retrieves a specific block, used during pool resets. + GetBlock(hash common.Hash, number uint64) *types.Block + + // StateAt returns a state database for a given root hash (generally the head). + StateAt(root common.Hash) (*state.StateDB, error) +} diff --git a/core/txpool/blobpool/limbo.go b/core/txpool/blobpool/limbo.go new file mode 100644 index 000000000000..d1fe9c739477 --- /dev/null +++ b/core/txpool/blobpool/limbo.go @@ -0,0 +1,253 @@ +// Copyright 2023 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 blobpool + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/billy" +) + +// limboBlob is a wrapper around an opaque blobset that also contains the tx hash +// to which it belongs as well as the block number in which it was included for +// finality eviction. +type limboBlob struct { + TxHash common.Hash // Owner transaction's hash to support resurrecting reorged txs + Block uint64 // Block in which the blob transaction was included + Tx *types.Transaction +} + +// limbo is a light, indexed database to temporarily store recently included +// blobs until they are finalized. The purpose is to support small reorgs, which +// would require pulling back up old blobs (which aren't part of the chain). +// +// TODO(karalabe): Currently updating the inclusion block of a blob needs a full db rewrite. Can we do without? +type limbo struct { + store billy.Database // Persistent data store for limboed blobs + + index map[common.Hash]uint64 // Mappings from tx hashes to datastore ids + groups map[uint64]map[uint64]common.Hash // Set of txs included in past blocks +} + +// newLimbo opens and indexes a set of limboed blob transactions. +func newLimbo(datadir string) (*limbo, error) { + l := &limbo{ + index: make(map[common.Hash]uint64), + groups: make(map[uint64]map[uint64]common.Hash), + } + // Index all limboed blobs on disk and delete anything inprocessable + var fails []uint64 + index := func(id uint64, size uint32, data []byte) { + if l.parseBlob(id, data) != nil { + fails = append(fails, id) + } + } + store, err := billy.Open(billy.Options{Path: datadir}, newSlotter(), index) + if err != nil { + return nil, err + } + l.store = store + + if len(fails) > 0 { + log.Warn("Dropping invalidated limboed blobs", "ids", fails) + for _, id := range fails { + if err := l.store.Delete(id); err != nil { + l.Close() + return nil, err + } + } + } + return l, nil +} + +// Close closes down the underlying persistent store. +func (l *limbo) Close() error { + return l.store.Close() +} + +// parseBlob is a callback method on limbo creation that gets called for each +// limboed blob on disk to create the in-memory metadata index. +func (l *limbo) parseBlob(id uint64, data []byte) error { + item := new(limboBlob) + if err := rlp.DecodeBytes(data, item); err != nil { + // This path is impossible unless the disk data representation changes + // across restarts. For that ever unprobable case, recover gracefully + // by ignoring this data entry. + log.Error("Failed to decode blob limbo entry", "id", id, "err", err) + return err + } + if _, ok := l.index[item.TxHash]; ok { + // This path is impossible, unless due to a programming error a blob gets + // inserted into the limbo which was already part of if. Recover gracefully + // by ignoring this data entry. + log.Error("Dropping duplicate blob limbo entry", "owner", item.TxHash, "id", id) + return errors.New("duplicate blob") + } + l.index[item.TxHash] = id + + if _, ok := l.groups[item.Block]; !ok { + l.groups[item.Block] = make(map[uint64]common.Hash) + } + l.groups[item.Block][id] = item.TxHash + + return nil +} + +// finalize evicts all blobs belonging to a recently finalized block or older. +func (l *limbo) finalize(final *types.Header) { + // Just in case there's no final block yet (network not yet merged, weird + // restart, sethead, etc), fail gracefully. + if final == nil { + log.Error("Nil finalized block cannot evict old blobs") + return + } + for block, ids := range l.groups { + if block > final.Number.Uint64() { + continue + } + for id, owner := range ids { + if err := l.store.Delete(id); err != nil { + log.Error("Failed to drop finalized blob", "block", block, "id", id, "err", err) + } + delete(l.index, owner) + } + delete(l.groups, block) + } +} + +// push stores a new blob transaction into the limbo, waiting until finality for +// it to be automatically evicted. +func (l *limbo) push(tx *types.Transaction, block uint64) error { + // If the blobs are already tracked by the limbo, consider it a programming + // error. There's not much to do against it, but be loud. + if _, ok := l.index[tx.Hash()]; ok { + log.Error("Limbo cannot push already tracked blobs", "tx", tx) + return errors.New("already tracked blob transaction") + } + if err := l.setAndIndex(tx, block); err != nil { + log.Error("Failed to set and index limboed blobs", "tx", tx, "err", err) + return err + } + return nil +} + +// pull retrieves a previously pushed set of blobs back from the limbo, removing +// it at the same time. This method should be used when a previously included blob +// transaction gets reorged out. +func (l *limbo) pull(tx common.Hash) (*types.Transaction, error) { + // If the blobs are not tracked by the limbo, there's not much to do. This + // can happen for example if a blob transaction is mined without pushing it + // into the network first. + id, ok := l.index[tx] + if !ok { + log.Trace("Limbo cannot pull non-tracked blobs", "tx", tx) + return nil, errors.New("unseen blob transaction") + } + item, err := l.getAndDrop(id) + if err != nil { + log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err) + return nil, err + } + return item.Tx, nil +} + +// update changes the block number under which a blob transaction is tracked. This +// method should be used when a reorg changes a transaction's inclusion block. +// +// The method may log errors for various unexpcted scenarios but will not return +// any of it since there's no clear error case. Some errors may be due to coding +// issues, others caused by signers mining MEV stuff or swapping transactions. In +// all cases, the pool needs to continue operating. +func (l *limbo) update(txhash common.Hash, block uint64) { + // If the blobs are not tracked by the limbo, there's not much to do. This + // can happen for example if a blob transaction is mined without pushing it + // into the network first. + id, ok := l.index[txhash] + if !ok { + log.Trace("Limbo cannot update non-tracked blobs", "tx", txhash) + return + } + // If there was no change in the blob's inclusion block, don't mess around + // with heavy database operations. + if _, ok := l.groups[block][id]; ok { + log.Trace("Blob transaction unchanged in limbo", "tx", txhash, "block", block) + return + } + // Retrieve the old blobs from the data store and write them back with a new + // block number. IF anything fails, there's not much to do, go on. + item, err := l.getAndDrop(id) + if err != nil { + log.Error("Failed to get and drop limboed blobs", "tx", txhash, "id", id, "err", err) + return + } + if err := l.setAndIndex(item.Tx, block); err != nil { + log.Error("Failed to set and index limboed blobs", "tx", txhash, "err", err) + return + } + log.Trace("Blob transaction updated in limbo", "tx", txhash, "old-block", item.Block, "new-block", block) +} + +// getAndDrop retrieves a blob item from the limbo store and deletes it both from +// the store and indices. +func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) { + data, err := l.store.Get(id) + if err != nil { + return nil, err + } + item := new(limboBlob) + if err = rlp.DecodeBytes(data, item); err != nil { + return nil, err + } + delete(l.index, item.TxHash) + delete(l.groups[item.Block], id) + if len(l.groups[item.Block]) == 0 { + delete(l.groups, item.Block) + } + if err := l.store.Delete(id); err != nil { + return nil, err + } + return item, nil +} + +// setAndIndex assembles a limbo blob database entry and stores it, also updating +// the in-memory indices. +func (l *limbo) setAndIndex(tx *types.Transaction, block uint64) error { + txhash := tx.Hash() + item := &limboBlob{ + TxHash: txhash, + Block: block, + Tx: tx, + } + data, err := rlp.EncodeToBytes(item) + if err != nil { + panic(err) // cannot happen runtime, dev error + } + id, err := l.store.Put(data) + if err != nil { + return err + } + l.index[txhash] = id + if _, ok := l.groups[block]; !ok { + l.groups[block] = make(map[uint64]common.Hash) + } + l.groups[block][id] = txhash + return nil +} diff --git a/core/txpool/blobpool/metrics.go b/core/txpool/blobpool/metrics.go new file mode 100644 index 000000000000..070cc5ca4712 --- /dev/null +++ b/core/txpool/blobpool/metrics.go @@ -0,0 +1,78 @@ +// Copyright 2023 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 blobpool + +import "github.com/ethereum/go-ethereum/metrics" + +var ( + // datacapGauge tracks the user's configured capacity for the blob pool. It + // is mostly a way to expose/debug issues. + datacapGauge = metrics.NewRegisteredGauge("blobpool/datacap", nil) + + // The below metrics track the per-datastore metrics for the primary blob + // store and the temporary limbo store. + datausedGauge = metrics.NewRegisteredGauge("blobpool/dataused", nil) + datarealGauge = metrics.NewRegisteredGauge("blobpool/datareal", nil) + slotusedGauge = metrics.NewRegisteredGauge("blobpool/slotused", nil) + + limboDatausedGauge = metrics.NewRegisteredGauge("blobpool/limbo/dataused", nil) + limboDatarealGauge = metrics.NewRegisteredGauge("blobpool/limbo/datareal", nil) + limboSlotusedGauge = metrics.NewRegisteredGauge("blobpool/limbo/slotused", nil) + + // The below metrics track the per-shelf metrics for the primary blob store + // and the temporary limbo store. + shelfDatausedGaugeName = "blobpool/shelf_%d/dataused" + shelfDatagapsGaugeName = "blobpool/shelf_%d/datagaps" + shelfSlotusedGaugeName = "blobpool/shelf_%d/slotused" + shelfSlotgapsGaugeName = "blobpool/shelf_%d/slotgaps" + + limboShelfDatausedGaugeName = "blobpool/limbo/shelf_%d/dataused" + limboShelfDatagapsGaugeName = "blobpool/limbo/shelf_%d/datagaps" + limboShelfSlotusedGaugeName = "blobpool/limbo/shelf_%d/slotused" + limboShelfSlotgapsGaugeName = "blobpool/limbo/shelf_%d/slotgaps" + + // The oversized metrics aggregate the shelf stats above the max blob count + // limits to track transactions that are just huge, but don't contain blobs. + // + // There are no oversized data in the limbo, it only contains blobs and some + // constant metadata. + oversizedDatausedGauge = metrics.NewRegisteredGauge("blobpool/oversized/dataused", nil) + oversizedDatagapsGauge = metrics.NewRegisteredGauge("blobpool/oversized/datagaps", nil) + oversizedSlotusedGauge = metrics.NewRegisteredGauge("blobpool/oversized/slotused", nil) + oversizedSlotgapsGauge = metrics.NewRegisteredGauge("blobpool/oversized/slotgaps", nil) + + // basefeeGauge and blobfeeGauge track the current network 1559 base fee and + // 4844 blob fee respectively. + basefeeGauge = metrics.NewRegisteredGauge("blobpool/basefee", nil) + blobfeeGauge = metrics.NewRegisteredGauge("blobpool/blobfee", nil) + + // pooltipGauge is the configurable miner tip to permit a transaction into + // the pool. + pooltipGauge = metrics.NewRegisteredGauge("blobpool/pooltip", nil) + + // addwait/time, resetwait/time and getwait/time track the rough health of + // the pool and wether or not it's capable of keeping up with the load from + // the network. + addwaitHist = metrics.NewRegisteredHistogram("blobpool/addwait", nil, metrics.NewExpDecaySample(1028, 0.015)) + addtimeHist = metrics.NewRegisteredHistogram("blobpool/addtime", nil, metrics.NewExpDecaySample(1028, 0.015)) + getwaitHist = metrics.NewRegisteredHistogram("blobpool/getwait", nil, metrics.NewExpDecaySample(1028, 0.015)) + gettimeHist = metrics.NewRegisteredHistogram("blobpool/gettime", nil, metrics.NewExpDecaySample(1028, 0.015)) + pendwaitHist = metrics.NewRegisteredHistogram("blobpool/pendwait", nil, metrics.NewExpDecaySample(1028, 0.015)) + pendtimeHist = metrics.NewRegisteredHistogram("blobpool/pendtime", nil, metrics.NewExpDecaySample(1028, 0.015)) + resetwaitHist = metrics.NewRegisteredHistogram("blobpool/resetwait", nil, metrics.NewExpDecaySample(1028, 0.015)) + resettimeHist = metrics.NewRegisteredHistogram("blobpool/resettime", nil, metrics.NewExpDecaySample(1028, 0.015)) +) diff --git a/core/txpool/blobpool/priority.go b/core/txpool/blobpool/priority.go new file mode 100644 index 000000000000..a8332bd9b0e6 --- /dev/null +++ b/core/txpool/blobpool/priority.go @@ -0,0 +1,90 @@ +// Copyright 2023 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 blobpool + +import ( + "math" + "math/bits" + + "github.com/holiman/uint256" +) + +// log2_1_125 is used in the eviction priority calculation. +var log2_1_125 = math.Log2(1.125) + +// evictionPriority calculates the eviction priority based on the algorithm +// described in the BlobPool docs for both fee components. +// +// This method takes about 8ns on a very recent laptop CPU, recalculating about +// 125 million transaction priority values per second. +func evictionPriority(basefeeJumps float64, txBasefeeJumps, blobfeeJumps, txBlobfeeJumps float64) int { + var ( + basefeePriority = evictionPriority1D(basefeeJumps, txBasefeeJumps) + blobfeePriority = evictionPriority1D(blobfeeJumps, txBlobfeeJumps) + ) + if basefeePriority < blobfeePriority { + return basefeePriority + } + return blobfeePriority +} + +// evictionPriority1D calculates the eviction priority based on the algorithm +// described in the BlobPool docs for a single fee component. +func evictionPriority1D(basefeeJumps float64, txfeeJumps float64) int { + jumps := txfeeJumps - basefeeJumps + if int(jumps) == 0 { + return 0 // can't log2 0 + } + if jumps < 0 { + return -intLog2(uint(-math.Floor(jumps))) + } + return intLog2(uint(math.Ceil(jumps))) +} + +// dynamicFeeJumps calculates the log1.125(fee), namely the number of fee jumps +// needed to reach the requested one. We only use it when calculating the jumps +// between 2 fees, so it doesn't matter from what exact number with returns. +// it returns the result from (0, 1, 1.125). +// +// This method is very expensive, taking about 75ns on a very recent laptop CPU, +// but the result does not change with the lifetime of a transaction, so it can +// be cached. +func dynamicFeeJumps(fee *uint256.Int) float64 { + if fee.IsZero() { + return 0 // can't log2 zero, should never happen outside tests, but don't choke + } + return math.Log2(fee.Float64()) / log2_1_125 +} + +// intLog2 is a helper to calculate the integral part of a log2 of an unsigned +// integer. It is a very specific calculation that's not particularly useful in +// general, but it's what we need here (it's fast). +func intLog2(n uint) int { + switch { + case n == 0: + panic("log2(0) is undefined") + + case n < 2048: + return bits.UintSize - bits.LeadingZeros(n) - 1 + + default: + // The input is log1.125(uint256) = log2(uint256) / log2(1.125). At the + // most extreme, log2(uint256) will be a bit below 257, and the constant + // log2(1.125) ~= 0.17. The larges input thus is ~257 / ~0.17 ~= ~1511. + panic("dynamic fee jump diffs cannot reach this") + } +} diff --git a/core/txpool/blobpool/priority_test.go b/core/txpool/blobpool/priority_test.go new file mode 100644 index 000000000000..4aad919925f5 --- /dev/null +++ b/core/txpool/blobpool/priority_test.go @@ -0,0 +1,87 @@ +// Copyright 2023 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 blobpool + +import ( + "testing" + + "github.com/holiman/uint256" +) + +// Tests that the priority fees are calculated correctly as the log2 of the fee +// jumps needed to go from the base fee to the tx's fee cap. +func TestPriorityCalculation(t *testing.T) { + tests := []struct { + basefee uint64 + txfee uint64 + result int + }{ + {basefee: 7, txfee: 10, result: 2}, // 3.02 jumps, 4 ceil, 2 log2 + {basefee: 17_200_000_000, txfee: 17_200_000_000, result: 0}, // 0 jumps, special case 0 log2 + {basefee: 9_853_941_692, txfee: 11_085_092_510, result: 0}, // 0.99 jumps, 1 ceil, 0 log2 + {basefee: 11_544_106_391, txfee: 10_356_781_100, result: 0}, // -0.92 jumps, -1 floor, 0 log2 + {basefee: 17_200_000_000, txfee: 7, result: -7}, // -183.57 jumps, -184 floor, -7 log2 + {basefee: 7, txfee: 17_200_000_000, result: 7}, // 183.57 jumps, 184 ceil, 7 log2 + } + for i, tt := range tests { + var ( + baseJumps = dynamicFeeJumps(uint256.NewInt(tt.basefee)) + feeJumps = dynamicFeeJumps(uint256.NewInt(tt.txfee)) + ) + if prio := evictionPriority1D(baseJumps, feeJumps); prio != tt.result { + t.Errorf("test %d priority mismatch: have %d, want %d", i, prio, tt.result) + } + } +} + +// Benchmarks how many dynamic fee jump values can be done. +func BenchmarkDynamicFeeJumpCalculation(b *testing.B) { + fees := make([]*uint256.Int, b.N) + for i := 0; i < b.N; i++ { + fees[i] = uint256.NewInt(rand.Uint64()) + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + dynamicFeeJumps(fees[i]) + } +} + +// Benchmarks how many priority recalculations can be done. +func BenchmarkPriorityCalculation(b *testing.B) { + // The basefee and blob fee is constant for all transactions across a block, + // so we can assume theit absolute jump counts can be pre-computed. + basefee := uint256.NewInt(17_200_000_000) // 17.2 Gwei is the 22.03.2023 zero-emission basefee, random number + blobfee := uint256.NewInt(123_456_789_000) // Completely random, no idea what this will be + + basefeeJumps := dynamicFeeJumps(basefee) + blobfeeJumps := dynamicFeeJumps(blobfee) + + // The transaction's fee cap and blob fee cap are constant across the life + // of the transaction, so we can pre-calculate and cache them. + txBasefeeJumps := make([]float64, b.N) + txBlobfeeJumps := make([]float64, b.N) + for i := 0; i < b.N; i++ { + txBasefeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rand.Uint64())) + txBlobfeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rand.Uint64())) + } + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + evictionPriority(basefeeJumps, txBasefeeJumps[i], blobfeeJumps, txBlobfeeJumps[i]) + } +} diff --git a/core/txpool/blobpool/slotter.go b/core/txpool/blobpool/slotter.go new file mode 100644 index 000000000000..35349c3445cf --- /dev/null +++ b/core/txpool/blobpool/slotter.go @@ -0,0 +1,38 @@ +// Copyright 2023 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 blobpool + +// newSlotter creates a helper method for the Billy datastore that returns the +// individual shelf sizes used to store transactions in. +// +// The slotter will create shelves for each possible blob count + some tx metadata +// wiggle room, up to the max permitted limits. +// +// The slotter also creates a shelf for 0-blob transactions. Whilst those are not +// allowed in the current protocol, having an empty shelf is not a relevant use +// of resources, but it makes stress testing with junk transactions simpler. +func newSlotter() func() (uint32, bool) { + slotsize := uint32(txAvgSize) + slotsize -= uint32(blobSize) // underflows, it's ok, will overflow back in the first return + + return func() (size uint32, done bool) { + slotsize += blobSize + finished := slotsize > maxBlobsPerTransaction*blobSize+txMaxSize + + return slotsize, finished + } +} diff --git a/core/txpool/blobpool/slotter_test.go b/core/txpool/blobpool/slotter_test.go new file mode 100644 index 000000000000..a7b43b4d2224 --- /dev/null +++ b/core/txpool/blobpool/slotter_test.go @@ -0,0 +1,60 @@ +// Copyright 2023 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 blobpool + +import "testing" + +// Tests that the slotter creates the expected database shelves. +func TestNewSlotter(t *testing.T) { + // Generate the database shelve sizes + slotter := newSlotter() + + var shelves []uint32 + for { + shelf, done := slotter() + shelves = append(shelves, shelf) + if done { + break + } + } + // Compare the database shelves to the expected ones + want := []uint32{ + 0*blobSize + txAvgSize, // 0 blob + some expected tx infos + 1*blobSize + txAvgSize, // 1 blob + some expected tx infos + 2*blobSize + txAvgSize, // 2 blob + some expected tx infos (could be fewer blobs and more tx data) + 3*blobSize + txAvgSize, // 3 blob + some expected tx infos (could be fewer blobs and more tx data) + 4*blobSize + txAvgSize, // 4 blob + some expected tx infos (could be fewer blobs and more tx data) + 5*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 6*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 7*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 8*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 9*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 10*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 11*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 12*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 13*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size + 14*blobSize + txAvgSize, // 1-6 blobs + unexpectedly large tx infos >= 4 blobs + max tx metadata size + } + if len(shelves) != len(want) { + t.Errorf("shelves count mismatch: have %d, want %d", len(shelves), len(want)) + } + for i := 0; i < len(shelves) && i < len(want); i++ { + if shelves[i] != want[i] { + t.Errorf("shelf %d mismatch: have %d, want %d", i, shelves[i], want[i]) + } + } +} diff --git a/core/txpool/errors.go b/core/txpool/errors.go new file mode 100644 index 000000000000..61daa999ffdc --- /dev/null +++ b/core/txpool/errors.go @@ -0,0 +1,57 @@ +// Copyright 2014 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 txpool + +import "errors" + +var ( + // ErrAlreadyKnown is returned if the transactions is already contained + // within the pool. + ErrAlreadyKnown = errors.New("already known") + + // ErrInvalidSender is returned if the transaction contains an invalid signature. + ErrInvalidSender = errors.New("invalid sender") + + // ErrUnderpriced is returned if a transaction's gas price is below the minimum + // configured for the transaction pool. + ErrUnderpriced = errors.New("transaction underpriced") + + // ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced + // with a different one without the required price bump. + ErrReplaceUnderpriced = errors.New("replacement transaction underpriced") + + // ErrAccountLimitExceeded is returned if a transaction would exceed the number + // allowed by a pool for a single account. + ErrAccountLimitExceeded = errors.New("account limit exceeded") + + // ErrGasLimit is returned if a transaction's requested gas limit exceeds the + // maximum allowance of the current block. + ErrGasLimit = errors.New("exceeds block gas limit") + + // ErrNegativeValue is a sanity error to ensure no one is able to specify a + // transaction with a negative value. + ErrNegativeValue = errors.New("negative value") + + // ErrOversizedData is returned if the input data of a transaction is greater + // than some meaningful limit a user might use. This is not a consensus error + // making the transaction invalid, rather a DOS protection. + ErrOversizedData = errors.New("oversized data") + + // ErrFutureReplacePending is returned if a future transaction replaces a pending + // one. Future transactions should only be able to replace other future transactions. + ErrFutureReplacePending = errors.New("future transaction tries to replace pending") +) diff --git a/core/txpool/journal.go b/core/txpool/legacypool/journal.go similarity index 99% rename from core/txpool/journal.go rename to core/txpool/legacypool/journal.go index 1b330b0c3cab..f04ab8fc14e5 100644 --- a/core/txpool/journal.go +++ b/core/txpool/legacypool/journal.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package txpool +package legacypool import ( "errors" diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go new file mode 100644 index 000000000000..e71204185f03 --- /dev/null +++ b/core/txpool/legacypool/legacypool.go @@ -0,0 +1,1946 @@ +// Copyright 2014 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 legacypool implements the normal EVM execution transaction pool. +package legacypool + +import ( + "errors" + "math" + "math/big" + "sort" + "sync" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/prque" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/params" +) + +const ( + // txSlotSize is used to calculate how many data slots a single transaction + // takes up based on its size. The slots are used as DoS protection, ensuring + // that validating a new transaction remains a constant operation (in reality + // O(maxslots), where max slots are 4 currently). + txSlotSize = 32 * 1024 + + // txMaxSize is the maximum size a single transaction can have. This field has + // non-trivial consequences: larger transactions are significantly harder and + // more expensive to propagate; larger transactions also take more resources + // to validate whether they fit into the pool or not. + txMaxSize = 4 * txSlotSize // 128KB +) + +var ( + // ErrAlreadyKnown is returned if the transactions is already contained + // within the pool. + ErrAlreadyKnown = errors.New("already known") + + // ErrTxPoolOverflow is returned if the transaction pool is full and can't accept + // another remote transaction. + ErrTxPoolOverflow = errors.New("txpool is full") +) + +var ( + evictionInterval = time.Minute // Time interval to check for evictable transactions + statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats +) + +var ( + // Metrics for the pending pool + pendingDiscardMeter = metrics.NewRegisteredMeter("txpool/pending/discard", nil) + pendingReplaceMeter = metrics.NewRegisteredMeter("txpool/pending/replace", nil) + pendingRateLimitMeter = metrics.NewRegisteredMeter("txpool/pending/ratelimit", nil) // Dropped due to rate limiting + pendingNofundsMeter = metrics.NewRegisteredMeter("txpool/pending/nofunds", nil) // Dropped due to out-of-funds + + // Metrics for the queued pool + queuedDiscardMeter = metrics.NewRegisteredMeter("txpool/queued/discard", nil) + queuedReplaceMeter = metrics.NewRegisteredMeter("txpool/queued/replace", nil) + queuedRateLimitMeter = metrics.NewRegisteredMeter("txpool/queued/ratelimit", nil) // Dropped due to rate limiting + queuedNofundsMeter = metrics.NewRegisteredMeter("txpool/queued/nofunds", nil) // Dropped due to out-of-funds + queuedEvictionMeter = metrics.NewRegisteredMeter("txpool/queued/eviction", nil) // Dropped due to lifetime + + // General tx metrics + knownTxMeter = metrics.NewRegisteredMeter("txpool/known", nil) + validTxMeter = metrics.NewRegisteredMeter("txpool/valid", nil) + invalidTxMeter = metrics.NewRegisteredMeter("txpool/invalid", nil) + underpricedTxMeter = metrics.NewRegisteredMeter("txpool/underpriced", nil) + overflowedTxMeter = metrics.NewRegisteredMeter("txpool/overflowed", nil) + + // throttleTxMeter counts how many transactions are rejected due to too-many-changes between + // txpool reorgs. + throttleTxMeter = metrics.NewRegisteredMeter("txpool/throttle", nil) + // reorgDurationTimer measures how long time a txpool reorg takes. + reorgDurationTimer = metrics.NewRegisteredTimer("txpool/reorgtime", nil) + // dropBetweenReorgHistogram counts how many drops we experience between two reorg runs. It is expected + // that this number is pretty low, since txpool reorgs happen very frequently. + dropBetweenReorgHistogram = metrics.NewRegisteredHistogram("txpool/dropbetweenreorg", nil, metrics.NewExpDecaySample(1028, 0.015)) + + pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil) + queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) + localGauge = metrics.NewRegisteredGauge("txpool/local", nil) + slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil) + + reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil) +) + +// BlockChain defines the minimal set of methods needed to back a tx pool with +// a chain. Exists to allow mocking the live chain out of tests. +type BlockChain interface { + // Config retrieves the chain's fork configuration. + Config() *params.ChainConfig + + // CurrentBlock returns the current head of the chain. + CurrentBlock() *types.Header + + // GetBlock retrieves a specific block, used during pool resets. + GetBlock(hash common.Hash, number uint64) *types.Block + + // StateAt returns a state database for a given root hash (generally the head). + StateAt(root common.Hash) (*state.StateDB, error) +} + +// Config are the configuration parameters of the transaction pool. +type Config struct { + Locals []common.Address // Addresses that should be treated by default as local + NoLocals bool // Whether local transaction handling should be disabled + Journal string // Journal of local transactions to survive node restarts + Rejournal time.Duration // Time interval to regenerate the local transaction journal + + PriceLimit uint64 // Minimum gas price to enforce for acceptance into the pool + PriceBump uint64 // Minimum price bump percentage to replace an already existing transaction (nonce) + + AccountSlots uint64 // Number of executable transaction slots guaranteed per account + GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts + AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account + GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts + + Lifetime time.Duration // Maximum amount of time non-executable transaction are queued +} + +// DefaultConfig contains the default configurations for the transaction pool. +var DefaultConfig = Config{ + Journal: "transactions.rlp", + Rejournal: time.Hour, + + PriceLimit: 1, + PriceBump: 10, + + AccountSlots: 16, + GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio + AccountQueue: 64, + GlobalQueue: 1024, + + Lifetime: 3 * time.Hour, +} + +// sanitize checks the provided user configurations and changes anything that's +// unreasonable or unworkable. +func (config *Config) sanitize() Config { + conf := *config + if conf.Rejournal < time.Second { + log.Warn("Sanitizing invalid txpool journal time", "provided", conf.Rejournal, "updated", time.Second) + conf.Rejournal = time.Second + } + if conf.PriceLimit < 1 { + log.Warn("Sanitizing invalid txpool price limit", "provided", conf.PriceLimit, "updated", DefaultConfig.PriceLimit) + conf.PriceLimit = DefaultConfig.PriceLimit + } + if conf.PriceBump < 1 { + log.Warn("Sanitizing invalid txpool price bump", "provided", conf.PriceBump, "updated", DefaultConfig.PriceBump) + conf.PriceBump = DefaultConfig.PriceBump + } + if conf.AccountSlots < 1 { + log.Warn("Sanitizing invalid txpool account slots", "provided", conf.AccountSlots, "updated", DefaultConfig.AccountSlots) + conf.AccountSlots = DefaultConfig.AccountSlots + } + if conf.GlobalSlots < 1 { + log.Warn("Sanitizing invalid txpool global slots", "provided", conf.GlobalSlots, "updated", DefaultConfig.GlobalSlots) + conf.GlobalSlots = DefaultConfig.GlobalSlots + } + if conf.AccountQueue < 1 { + log.Warn("Sanitizing invalid txpool account queue", "provided", conf.AccountQueue, "updated", DefaultConfig.AccountQueue) + conf.AccountQueue = DefaultConfig.AccountQueue + } + if conf.GlobalQueue < 1 { + log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultConfig.GlobalQueue) + conf.GlobalQueue = DefaultConfig.GlobalQueue + } + if conf.Lifetime < 1 { + log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultConfig.Lifetime) + conf.Lifetime = DefaultConfig.Lifetime + } + return conf +} + +// LegacyPool contains all currently known transactions. Transactions +// enter the pool when they are received from the network or submitted +// locally. They exit the pool when they are included in the blockchain. +// +// The pool separates processable transactions (which can be applied to the +// current state) and future transactions. Transactions move between those +// two states over time as they are received and processed. +type LegacyPool struct { + config Config + chainconfig *params.ChainConfig + chain BlockChain + gasTip atomic.Pointer[big.Int] + txFeed event.Feed + signer types.Signer + mu sync.RWMutex + + currentHead atomic.Pointer[types.Header] // Current head of the blockchain + currentState *state.StateDB // Current state in the blockchain head + pendingNonces *noncer // Pending state tracking virtual nonces + + locals *accountSet // Set of local transaction to exempt from eviction rules + journal *journal // Journal of local transaction to back up to disk + + reserve txpool.AddressReserver // Address reserver to ensure exclusivity across subpools + pending map[common.Address]*list // All currently processable transactions + queue map[common.Address]*list // Queued but non-processable transactions + beats map[common.Address]time.Time // Last heartbeat from each known account + all *lookup // All transactions to allow lookups + priced *pricedList // All transactions sorted by price + + reqResetCh chan *txpoolResetRequest + reqPromoteCh chan *accountSet + queueTxEventCh chan *types.Transaction + reorgDoneCh chan chan struct{} + reorgShutdownCh chan struct{} // requests shutdown of scheduleReorgLoop + wg sync.WaitGroup // tracks loop, scheduleReorgLoop + initDoneCh chan struct{} // is closed once the pool is initialized (for tests) + + changesSinceReorg int // A counter for how many drops we've performed in-between reorg. +} + +type txpoolResetRequest struct { + oldHead, newHead *types.Header +} + +// New creates a new transaction pool to gather, sort and filter inbound +// transactions from the network. +func New(config Config, chain BlockChain) *LegacyPool { + // Sanitize the input to ensure no vulnerable gas prices are set + config = (&config).sanitize() + + // Create the transaction pool with its initial settings + pool := &LegacyPool{ + config: config, + chain: chain, + chainconfig: chain.Config(), + signer: types.LatestSigner(chain.Config()), + pending: make(map[common.Address]*list), + queue: make(map[common.Address]*list), + beats: make(map[common.Address]time.Time), + all: newLookup(), + reqResetCh: make(chan *txpoolResetRequest), + reqPromoteCh: make(chan *accountSet), + queueTxEventCh: make(chan *types.Transaction), + reorgDoneCh: make(chan chan struct{}), + reorgShutdownCh: make(chan struct{}), + initDoneCh: make(chan struct{}), + } + pool.locals = newAccountSet(pool.signer) + for _, addr := range config.Locals { + log.Info("Setting new local account", "address", addr) + pool.locals.add(addr) + } + pool.priced = newPricedList(pool.all) + + if !config.NoLocals && config.Journal != "" { + pool.journal = newTxJournal(config.Journal) + } + return pool +} + +// Filter returns whether the given transaction can be consumed by the legacy +// pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction. +func (pool *LegacyPool) Filter(tx *types.Transaction) bool { + switch tx.Type() { + case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType: + return true + default: + return false + } +} + +// Init sets the gas price needed to keep a transaction in the pool and the chain +// head to allow balance / nonce checks. The transaction journal will be loaded +// from disk and filtered based on the provided starting settings. The internal +// goroutines will be spun up and the pool deemed operational afterwards. +func (pool *LegacyPool) Init(gasTip *big.Int, head *types.Header, reserve txpool.AddressReserver) error { + // Set the address reserver to request exclusive access to pooled accounts + pool.reserve = reserve + + // Set the basic pool parameters + pool.gasTip.Store(gasTip) + + // Initialize the state with head block, or fallback to empty one in + // case the head state is not available(might occur when node is not + // fully synced). + statedb, err := pool.chain.StateAt(head.Root) + if err != nil { + statedb, err = pool.chain.StateAt(types.EmptyRootHash) + } + if err != nil { + return err + } + pool.currentHead.Store(head) + pool.currentState = statedb + pool.pendingNonces = newNoncer(statedb) + + // Start the reorg loop early, so it can handle requests generated during + // journal loading. + pool.wg.Add(1) + go pool.scheduleReorgLoop() + + // If local transactions and journaling is enabled, load from disk + if pool.journal != nil { + if err := pool.journal.load(pool.addLocals); err != nil { + log.Warn("Failed to load transaction journal", "err", err) + } + if err := pool.journal.rotate(pool.local()); err != nil { + log.Warn("Failed to rotate transaction journal", "err", err) + } + } + pool.wg.Add(1) + go pool.loop() + return nil +} + +// loop is the transaction pool's main event loop, waiting for and reacting to +// outside blockchain events as well as for various reporting and transaction +// eviction events. +func (pool *LegacyPool) loop() { + defer pool.wg.Done() + + var ( + prevPending, prevQueued, prevStales int + + // Start the stats reporting and transaction eviction tickers + report = time.NewTicker(statsReportInterval) + evict = time.NewTicker(evictionInterval) + journal = time.NewTicker(pool.config.Rejournal) + ) + defer report.Stop() + defer evict.Stop() + defer journal.Stop() + + // Notify tests that the init phase is done + close(pool.initDoneCh) + for { + select { + // Handle pool shutdown + case <-pool.reorgShutdownCh: + return + + // Handle stats reporting ticks + case <-report.C: + pool.mu.RLock() + pending, queued := pool.stats() + pool.mu.RUnlock() + stales := int(pool.priced.stales.Load()) + + if pending != prevPending || queued != prevQueued || stales != prevStales { + log.Debug("Transaction pool status report", "executable", pending, "queued", queued, "stales", stales) + prevPending, prevQueued, prevStales = pending, queued, stales + } + + // Handle inactive account transaction eviction + case <-evict.C: + pool.mu.Lock() + for addr := range pool.queue { + // Skip local transactions from the eviction mechanism + if pool.locals.contains(addr) { + continue + } + // Any non-locals old enough should be removed + if time.Since(pool.beats[addr]) > pool.config.Lifetime { + list := pool.queue[addr].Flatten() + for _, tx := range list { + pool.removeTx(tx.Hash(), true, true) + } + queuedEvictionMeter.Mark(int64(len(list))) + } + } + pool.mu.Unlock() + + // Handle local transaction journal rotation + case <-journal.C: + if pool.journal != nil { + pool.mu.Lock() + if err := pool.journal.rotate(pool.local()); err != nil { + log.Warn("Failed to rotate local tx journal", "err", err) + } + pool.mu.Unlock() + } + } + } +} + +// Close terminates the transaction pool. +func (pool *LegacyPool) Close() error { + // Terminate the pool reorger and return + close(pool.reorgShutdownCh) + pool.wg.Wait() + + if pool.journal != nil { + pool.journal.close() + } + log.Info("Transaction pool stopped") + return nil +} + +// Reset implements txpool.SubPool, allowing the legacy pool's internal state to be +// kept in sync with the main transaction pool's internal state. +func (pool *LegacyPool) Reset(oldHead, newHead *types.Header) { + wait := pool.requestReset(oldHead, newHead) + <-wait +} + +// SubscribeTransactions registers a subscription for new transaction events, +// supporting feeding only newly seen or also resurrected transactions. +func (pool *LegacyPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription { + // The legacy pool has a very messed up internal shuffling, so it's kind of + // hard to separate newly discovered transaction from resurrected ones. This + // is because the new txs are added to the queue, resurrected ones too and + // reorgs run lazily, so separating the two would need a marker. + return pool.txFeed.Subscribe(ch) +} + +// SetGasTip updates the minimum gas tip required by the transaction pool for a +// new transaction, and drops all transactions below this threshold. +func (pool *LegacyPool) SetGasTip(tip *big.Int) { + pool.mu.Lock() + defer pool.mu.Unlock() + + old := pool.gasTip.Load() + pool.gasTip.Store(new(big.Int).Set(tip)) + + // If the min miner fee increased, remove transactions below the new threshold + if tip.Cmp(old) > 0 { + // pool.priced is sorted by GasFeeCap, so we have to iterate through pool.all instead + drop := pool.all.RemotesBelowTip(tip) + for _, tx := range drop { + pool.removeTx(tx.Hash(), false, true) + } + pool.priced.Removed(len(drop)) + } + log.Info("Legacy pool tip threshold updated", "tip", tip) +} + +// Nonce returns the next nonce of an account, with all transactions executable +// by the pool already applied on top. +func (pool *LegacyPool) Nonce(addr common.Address) uint64 { + pool.mu.RLock() + defer pool.mu.RUnlock() + + return pool.pendingNonces.get(addr) +} + +// Stats retrieves the current pool stats, namely the number of pending and the +// number of queued (non-executable) transactions. +func (pool *LegacyPool) Stats() (int, int) { + pool.mu.RLock() + defer pool.mu.RUnlock() + + return pool.stats() +} + +// stats retrieves the current pool stats, namely the number of pending and the +// number of queued (non-executable) transactions. +func (pool *LegacyPool) stats() (int, int) { + pending := 0 + for _, list := range pool.pending { + pending += list.Len() + } + queued := 0 + for _, list := range pool.queue { + queued += list.Len() + } + return pending, queued +} + +// Content retrieves the data content of the transaction pool, returning all the +// pending as well as queued transactions, grouped by account and sorted by nonce. +func (pool *LegacyPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { + pool.mu.Lock() + defer pool.mu.Unlock() + + pending := make(map[common.Address][]*types.Transaction, len(pool.pending)) + for addr, list := range pool.pending { + pending[addr] = list.Flatten() + } + queued := make(map[common.Address][]*types.Transaction, len(pool.queue)) + for addr, list := range pool.queue { + queued[addr] = list.Flatten() + } + return pending, queued +} + +// ContentFrom retrieves the data content of the transaction pool, returning the +// pending as well as queued transactions of this address, grouped by nonce. +func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { + pool.mu.RLock() + defer pool.mu.RUnlock() + + var pending []*types.Transaction + if list, ok := pool.pending[addr]; ok { + pending = list.Flatten() + } + var queued []*types.Transaction + if list, ok := pool.queue[addr]; ok { + queued = list.Flatten() + } + return pending, queued +} + +// Pending retrieves all currently processable transactions, grouped by origin +// account and sorted by nonce. The returned transaction set is a copy and can be +// freely modified by calling code. +// +// The enforceTips parameter can be used to do an extra filtering on the pending +// transactions and only return those whose **effective** tip is large enough in +// the next pending execution environment. +func (pool *LegacyPool) Pending(enforceTips bool) map[common.Address][]*txpool.LazyTransaction { + pool.mu.Lock() + defer pool.mu.Unlock() + + pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending)) + for addr, list := range pool.pending { + txs := list.Flatten() + + // If the miner requests tip enforcement, cap the lists now + if enforceTips && !pool.locals.contains(addr) { + for i, tx := range txs { + if tx.EffectiveGasTipIntCmp(pool.gasTip.Load(), pool.priced.urgent.baseFee) < 0 { + txs = txs[:i] + break + } + } + } + if len(txs) > 0 { + lazies := make([]*txpool.LazyTransaction, len(txs)) + for i := 0; i < len(txs); i++ { + lazies[i] = &txpool.LazyTransaction{ + Pool: pool, + Hash: txs[i].Hash(), + Tx: txs[i], + Time: txs[i].Time(), + GasFeeCap: txs[i].GasFeeCap(), + GasTipCap: txs[i].GasTipCap(), + Gas: txs[i].Gas(), + BlobGas: txs[i].BlobGas(), + } + } + pending[addr] = lazies + } + } + return pending +} + +// Locals retrieves the accounts currently considered local by the pool. +func (pool *LegacyPool) Locals() []common.Address { + pool.mu.Lock() + defer pool.mu.Unlock() + + return pool.locals.flatten() +} + +// local retrieves all currently known local transactions, grouped by origin +// account and sorted by nonce. The returned transaction set is a copy and can be +// freely modified by calling code. +func (pool *LegacyPool) local() map[common.Address]types.Transactions { + txs := make(map[common.Address]types.Transactions) + for addr := range pool.locals.accounts { + if pending := pool.pending[addr]; pending != nil { + txs[addr] = append(txs[addr], pending.Flatten()...) + } + if queued := pool.queue[addr]; queued != nil { + txs[addr] = append(txs[addr], queued.Flatten()...) + } + } + return txs +} + +// validateTxBasics checks whether a transaction is valid according to the consensus +// rules, but does not check state-dependent validation such as sufficient balance. +// This check is meant as an early check which only needs to be performed once, +// and does not require the pool mutex to be held. +func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) error { + opts := &txpool.ValidationOptions{ + Config: pool.chainconfig, + Accept: 0 | + 1< pool.config.GlobalSlots+pool.config.GlobalQueue { + // If the new transaction is underpriced, don't accept it + if !isLocal && pool.priced.Underpriced(tx) { + log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) + underpricedTxMeter.Mark(1) + return false, txpool.ErrUnderpriced + } + + // We're about to replace a transaction. The reorg does a more thorough + // analysis of what to remove and how, but it runs async. We don't want to + // do too many replacements between reorg-runs, so we cap the number of + // replacements to 25% of the slots + if pool.changesSinceReorg > int(pool.config.GlobalSlots/4) { + throttleTxMeter.Mark(1) + return false, ErrTxPoolOverflow + } + + // New transaction is better than our worse ones, make room for it. + // If it's a local transaction, forcibly discard all available transactions. + // Otherwise if we can't make enough room for new one, abort the operation. + drop, success := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), isLocal) + + // Special case, we still can't make the room for the new remote one. + if !isLocal && !success { + log.Trace("Discarding overflown transaction", "hash", hash) + overflowedTxMeter.Mark(1) + return false, ErrTxPoolOverflow + } + + // If the new transaction is a future transaction it should never churn pending transactions + if !isLocal && pool.isGapped(from, tx) { + var replacesPending bool + for _, dropTx := range drop { + dropSender, _ := types.Sender(pool.signer, dropTx) + if list := pool.pending[dropSender]; list != nil && list.Contains(dropTx.Nonce()) { + replacesPending = true + break + } + } + // Add all transactions back to the priced queue + if replacesPending { + for _, dropTx := range drop { + pool.priced.Put(dropTx, false) + } + log.Trace("Discarding future transaction replacing pending tx", "hash", hash) + return false, txpool.ErrFutureReplacePending + } + } + + // Kick out the underpriced remote transactions. + for _, tx := range drop { + log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) + underpricedTxMeter.Mark(1) + + sender, _ := types.Sender(pool.signer, tx) + dropped := pool.removeTx(tx.Hash(), false, sender != from) // Don't unreserve the sender of the tx being added if last from the acc + + pool.changesSinceReorg += dropped + } + } + + // Try to replace an existing transaction in the pending pool + if list := pool.pending[from]; list != nil && list.Contains(tx.Nonce()) { + // Nonce already pending, check if required price bump is met + inserted, old := list.Add(tx, pool.config.PriceBump) + if !inserted { + pendingDiscardMeter.Mark(1) + return false, txpool.ErrReplaceUnderpriced + } + // New transaction is better, replace old one + if old != nil { + pool.all.Remove(old.Hash()) + pool.priced.Removed(1) + pendingReplaceMeter.Mark(1) + } + pool.all.Add(tx, isLocal) + pool.priced.Put(tx, isLocal) + pool.journalTx(from, tx) + pool.queueTxEvent(tx) + log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) + + // Successful promotion, bump the heartbeat + pool.beats[from] = time.Now() + return old != nil, nil + } + // New transaction isn't replacing a pending one, push into queue + replaced, err = pool.enqueueTx(hash, tx, isLocal, true) + if err != nil { + return false, err + } + // Mark local addresses and journal local transactions + if local && !pool.locals.contains(from) { + log.Info("Setting new local account", "address", from) + pool.locals.add(from) + pool.priced.Removed(pool.all.RemoteToLocals(pool.locals)) // Migrate the remotes if it's marked as local first time. + } + if isLocal { + localGauge.Inc(1) + } + pool.journalTx(from, tx) + + log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To()) + return replaced, nil +} + +// isGapped reports whether the given transaction is immediately executable. +func (pool *LegacyPool) isGapped(from common.Address, tx *types.Transaction) bool { + // Short circuit if transaction falls within the scope of the pending list + // or matches the next pending nonce which can be promoted as an executable + // transaction afterwards. Note, the tx staleness is already checked in + // 'validateTx' function previously. + next := pool.pendingNonces.get(from) + if tx.Nonce() <= next { + return false + } + // The transaction has a nonce gap with pending list, it's only considered + // as executable if transactions in queue can fill up the nonce gap. + queue, ok := pool.queue[from] + if !ok { + return true + } + for nonce := next; nonce < tx.Nonce(); nonce++ { + if !queue.Contains(nonce) { + return true // txs in queue can't fill up the nonce gap + } + } + return false +} + +// enqueueTx inserts a new transaction into the non-executable transaction queue. +// +// Note, this method assumes the pool lock is held! +func (pool *LegacyPool) enqueueTx(hash common.Hash, tx *types.Transaction, local bool, addAll bool) (bool, error) { + // Try to insert the transaction into the future queue + from, _ := types.Sender(pool.signer, tx) // already validated + if pool.queue[from] == nil { + pool.queue[from] = newList(false) + } + inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump) + if !inserted { + // An older transaction was better, discard this + queuedDiscardMeter.Mark(1) + return false, txpool.ErrReplaceUnderpriced + } + // Discard any previous transaction and mark this + if old != nil { + pool.all.Remove(old.Hash()) + pool.priced.Removed(1) + queuedReplaceMeter.Mark(1) + } else { + // Nothing was replaced, bump the queued counter + queuedGauge.Inc(1) + } + // If the transaction isn't in lookup set but it's expected to be there, + // show the error log. + if pool.all.Get(hash) == nil && !addAll { + log.Error("Missing transaction in lookup set, please report the issue", "hash", hash) + } + if addAll { + pool.all.Add(tx, local) + pool.priced.Put(tx, local) + } + // If we never record the heartbeat, do it right now. + if _, exist := pool.beats[from]; !exist { + pool.beats[from] = time.Now() + } + return old != nil, nil +} + +// journalTx adds the specified transaction to the local disk journal if it is +// deemed to have been sent from a local account. +func (pool *LegacyPool) journalTx(from common.Address, tx *types.Transaction) { + // Only journal if it's enabled and the transaction is local + if pool.journal == nil || !pool.locals.contains(from) { + return + } + if err := pool.journal.insert(tx); err != nil { + log.Warn("Failed to journal local transaction", "err", err) + } +} + +// promoteTx adds a transaction to the pending (processable) list of transactions +// and returns whether it was inserted or an older was better. +// +// Note, this method assumes the pool lock is held! +func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool { + // Try to insert the transaction into the pending queue + if pool.pending[addr] == nil { + pool.pending[addr] = newList(true) + } + list := pool.pending[addr] + + inserted, old := list.Add(tx, pool.config.PriceBump) + if !inserted { + // An older transaction was better, discard this + pool.all.Remove(hash) + pool.priced.Removed(1) + pendingDiscardMeter.Mark(1) + return false + } + // Otherwise discard any previous transaction and mark this + if old != nil { + pool.all.Remove(old.Hash()) + pool.priced.Removed(1) + pendingReplaceMeter.Mark(1) + } else { + // Nothing was replaced, bump the pending counter + pendingGauge.Inc(1) + } + // Set the potentially new pending nonce and notify any subsystems of the new tx + pool.pendingNonces.set(addr, tx.Nonce()+1) + + // Successful promotion, bump the heartbeat + pool.beats[addr] = time.Now() + return true +} + +// addLocals enqueues a batch of transactions into the pool if they are valid, marking the +// senders as local ones, ensuring they go around the local pricing constraints. +// +// This method is used to add transactions from the RPC API and performs synchronous pool +// reorganization and event propagation. +func (pool *LegacyPool) addLocals(txs []*types.Transaction) []error { + return pool.Add(txs, !pool.config.NoLocals, true) +} + +// addLocal enqueues a single local transaction into the pool if it is valid. This is +// a convenience wrapper around addLocals. +func (pool *LegacyPool) addLocal(tx *types.Transaction) error { + errs := pool.addLocals([]*types.Transaction{tx}) + return errs[0] +} + +// addRemotes enqueues a batch of transactions into the pool if they are valid. If the +// senders are not among the locally tracked ones, full pricing constraints will apply. +// +// This method is used to add transactions from the p2p network and does not wait for pool +// reorganization and internal event propagation. +func (pool *LegacyPool) addRemotes(txs []*types.Transaction) []error { + return pool.Add(txs, false, false) +} + +// addRemote enqueues a single transaction into the pool if it is valid. This is a convenience +// wrapper around addRemotes. +func (pool *LegacyPool) addRemote(tx *types.Transaction) error { + errs := pool.addRemotes([]*types.Transaction{tx}) + return errs[0] +} + +// addRemotesSync is like addRemotes, but waits for pool reorganization. Tests use this method. +func (pool *LegacyPool) addRemotesSync(txs []*types.Transaction) []error { + return pool.Add(txs, false, true) +} + +// This is like addRemotes with a single transaction, but waits for pool reorganization. Tests use this method. +func (pool *LegacyPool) addRemoteSync(tx *types.Transaction) error { + return pool.Add([]*types.Transaction{tx}, false, true)[0] +} + +// Add enqueues a batch of transactions into the pool if they are valid. Depending +// on the local flag, full pricing constraints will or will not be applied. +// +// If sync is set, the method will block until all internal maintenance related +// to the add is finished. Only use this during tests for determinism! +func (pool *LegacyPool) Add(txs []*types.Transaction, local, sync bool) []error { + // Filter out known ones without obtaining the pool lock or recovering signatures + var ( + errs = make([]error, len(txs)) + news = make([]*types.Transaction, 0, len(txs)) + ) + for i, tx := range txs { + // If the transaction is known, pre-set the error slot + if pool.all.Get(tx.Hash()) != nil { + errs[i] = ErrAlreadyKnown + knownTxMeter.Mark(1) + continue + } + // Exclude transactions with basic errors, e.g invalid signatures and + // insufficient intrinsic gas as soon as possible and cache senders + // in transactions before obtaining lock + if err := pool.validateTxBasics(tx, local); err != nil { + errs[i] = err + invalidTxMeter.Mark(1) + continue + } + // Accumulate all unknown transactions for deeper processing + news = append(news, tx) + } + if len(news) == 0 { + return errs + } + + // Process all the new transaction and merge any errors into the original slice + pool.mu.Lock() + newErrs, dirtyAddrs := pool.addTxsLocked(news, local) + pool.mu.Unlock() + + var nilSlot = 0 + for _, err := range newErrs { + for errs[nilSlot] != nil { + nilSlot++ + } + errs[nilSlot] = err + nilSlot++ + } + // Reorg the pool internals if needed and return + done := pool.requestPromoteExecutables(dirtyAddrs) + if sync { + <-done + } + return errs +} + +// addTxsLocked attempts to queue a batch of transactions if they are valid. +// The transaction pool lock must be held. +func (pool *LegacyPool) addTxsLocked(txs []*types.Transaction, local bool) ([]error, *accountSet) { + dirty := newAccountSet(pool.signer) + errs := make([]error, len(txs)) + for i, tx := range txs { + replaced, err := pool.add(tx, local) + errs[i] = err + if err == nil && !replaced { + dirty.addTx(tx) + } + } + validTxMeter.Mark(int64(len(dirty.accounts))) + return errs, dirty +} + +// Status returns the status (unknown/pending/queued) of a batch of transactions +// identified by their hashes. +func (pool *LegacyPool) Status(hash common.Hash) txpool.TxStatus { + tx := pool.get(hash) + if tx == nil { + return txpool.TxStatusUnknown + } + from, _ := types.Sender(pool.signer, tx) // already validated + + pool.mu.RLock() + defer pool.mu.RUnlock() + + if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { + return txpool.TxStatusPending + } else if txList := pool.queue[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { + return txpool.TxStatusQueued + } + return txpool.TxStatusUnknown +} + +// Get returns a transaction if it is contained in the pool and nil otherwise. +func (pool *LegacyPool) Get(hash common.Hash) *types.Transaction { + tx := pool.get(hash) + if tx == nil { + return nil + } + return tx +} + +// get returns a transaction if it is contained in the pool and nil otherwise. +func (pool *LegacyPool) get(hash common.Hash) *types.Transaction { + return pool.all.Get(hash) +} + +// Has returns an indicator whether txpool has a transaction cached with the +// given hash. +func (pool *LegacyPool) Has(hash common.Hash) bool { + return pool.all.Get(hash) != nil +} + +// removeTx removes a single transaction from the queue, moving all subsequent +// transactions back to the future queue. +// +// In unreserve is false, the account will not be relinquished to the main txpool +// even if there are no more references to it. This is used to handle a race when +// a tx being added, and it evicts a previously scheduled tx from the same account, +// which could lead to a premature release of the lock. +// +// Returns the number of transactions removed from the pending queue. +func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bool) int { + // Fetch the transaction we wish to delete + tx := pool.all.Get(hash) + if tx == nil { + return 0 + } + addr, _ := types.Sender(pool.signer, tx) // already validated during insertion + + // If after deletion there are no more transactions belonging to this account, + // relinquish the address reservation. It's a bit convoluted do this, via a + // defer, but it's safer vs. the many return pathways. + if unreserve { + defer func() { + var ( + _, hasPending = pool.pending[addr] + _, hasQueued = pool.queue[addr] + ) + if !hasPending && !hasQueued { + pool.reserve(addr, false) + } + }() + } + // Remove it from the list of known transactions + pool.all.Remove(hash) + if outofbound { + pool.priced.Removed(1) + } + if pool.locals.contains(addr) { + localGauge.Dec(1) + } + // Remove the transaction from the pending lists and reset the account nonce + if pending := pool.pending[addr]; pending != nil { + if removed, invalids := pending.Remove(tx); removed { + // If no more pending transactions are left, remove the list + if pending.Empty() { + delete(pool.pending, addr) + } + // Postpone any invalidated transactions + for _, tx := range invalids { + // Internal shuffle shouldn't touch the lookup set. + pool.enqueueTx(tx.Hash(), tx, false, false) + } + // Update the account nonce if needed + pool.pendingNonces.setIfLower(addr, tx.Nonce()) + // Reduce the pending counter + pendingGauge.Dec(int64(1 + len(invalids))) + return 1 + len(invalids) + } + } + // Transaction is in the future queue + if future := pool.queue[addr]; future != nil { + if removed, _ := future.Remove(tx); removed { + // Reduce the queued counter + queuedGauge.Dec(1) + } + if future.Empty() { + delete(pool.queue, addr) + delete(pool.beats, addr) + } + } + return 0 +} + +// requestReset requests a pool reset to the new head block. +// The returned channel is closed when the reset has occurred. +func (pool *LegacyPool) requestReset(oldHead *types.Header, newHead *types.Header) chan struct{} { + select { + case pool.reqResetCh <- &txpoolResetRequest{oldHead, newHead}: + return <-pool.reorgDoneCh + case <-pool.reorgShutdownCh: + return pool.reorgShutdownCh + } +} + +// requestPromoteExecutables requests transaction promotion checks for the given addresses. +// The returned channel is closed when the promotion checks have occurred. +func (pool *LegacyPool) requestPromoteExecutables(set *accountSet) chan struct{} { + select { + case pool.reqPromoteCh <- set: + return <-pool.reorgDoneCh + case <-pool.reorgShutdownCh: + return pool.reorgShutdownCh + } +} + +// queueTxEvent enqueues a transaction event to be sent in the next reorg run. +func (pool *LegacyPool) queueTxEvent(tx *types.Transaction) { + select { + case pool.queueTxEventCh <- tx: + case <-pool.reorgShutdownCh: + } +} + +// scheduleReorgLoop schedules runs of reset and promoteExecutables. Code above should not +// call those methods directly, but request them being run using requestReset and +// requestPromoteExecutables instead. +func (pool *LegacyPool) scheduleReorgLoop() { + defer pool.wg.Done() + + var ( + curDone chan struct{} // non-nil while runReorg is active + nextDone = make(chan struct{}) + launchNextRun bool + reset *txpoolResetRequest + dirtyAccounts *accountSet + queuedEvents = make(map[common.Address]*sortedMap) + ) + for { + // Launch next background reorg if needed + if curDone == nil && launchNextRun { + // Run the background reorg and announcements + go pool.runReorg(nextDone, reset, dirtyAccounts, queuedEvents) + + // Prepare everything for the next round of reorg + curDone, nextDone = nextDone, make(chan struct{}) + launchNextRun = false + + reset, dirtyAccounts = nil, nil + queuedEvents = make(map[common.Address]*sortedMap) + } + + select { + case req := <-pool.reqResetCh: + // Reset request: update head if request is already pending. + if reset == nil { + reset = req + } else { + reset.newHead = req.newHead + } + launchNextRun = true + pool.reorgDoneCh <- nextDone + + case req := <-pool.reqPromoteCh: + // Promote request: update address set if request is already pending. + if dirtyAccounts == nil { + dirtyAccounts = req + } else { + dirtyAccounts.merge(req) + } + launchNextRun = true + pool.reorgDoneCh <- nextDone + + case tx := <-pool.queueTxEventCh: + // Queue up the event, but don't schedule a reorg. It's up to the caller to + // request one later if they want the events sent. + addr, _ := types.Sender(pool.signer, tx) + if _, ok := queuedEvents[addr]; !ok { + queuedEvents[addr] = newSortedMap() + } + queuedEvents[addr].Put(tx) + + case <-curDone: + curDone = nil + + case <-pool.reorgShutdownCh: + // Wait for current run to finish. + if curDone != nil { + <-curDone + } + close(nextDone) + return + } + } +} + +// runReorg runs reset and promoteExecutables on behalf of scheduleReorgLoop. +func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirtyAccounts *accountSet, events map[common.Address]*sortedMap) { + defer func(t0 time.Time) { + reorgDurationTimer.Update(time.Since(t0)) + }(time.Now()) + defer close(done) + + var promoteAddrs []common.Address + if dirtyAccounts != nil && reset == nil { + // Only dirty accounts need to be promoted, unless we're resetting. + // For resets, all addresses in the tx queue will be promoted and + // the flatten operation can be avoided. + promoteAddrs = dirtyAccounts.flatten() + } + pool.mu.Lock() + if reset != nil { + // Reset from the old head to the new, rescheduling any reorged transactions + pool.reset(reset.oldHead, reset.newHead) + + // Nonces were reset, discard any events that became stale + for addr := range events { + events[addr].Forward(pool.pendingNonces.get(addr)) + if events[addr].Len() == 0 { + delete(events, addr) + } + } + // Reset needs promote for all addresses + promoteAddrs = make([]common.Address, 0, len(pool.queue)) + for addr := range pool.queue { + promoteAddrs = append(promoteAddrs, addr) + } + } + // Check for pending transactions for every account that sent new ones + promoted := pool.promoteExecutables(promoteAddrs) + + // If a new block appeared, validate the pool of pending transactions. This will + // remove any transaction that has been included in the block or was invalidated + // because of another transaction (e.g. higher gas price). + if reset != nil { + pool.demoteUnexecutables() + if reset.newHead != nil { + if pool.chainconfig.IsLondon(new(big.Int).Add(reset.newHead.Number, big.NewInt(1))) { + pendingBaseFee := eip1559.CalcBaseFee(pool.chainconfig, reset.newHead) + pool.priced.SetBaseFee(pendingBaseFee) + } else { + pool.priced.Reheap() + } + } + // Update all accounts to the latest known pending nonce + nonces := make(map[common.Address]uint64, len(pool.pending)) + for addr, list := range pool.pending { + highestPending := list.LastElement() + nonces[addr] = highestPending.Nonce() + 1 + } + pool.pendingNonces.setAll(nonces) + } + // Ensure pool.queue and pool.pending sizes stay within the configured limits. + pool.truncatePending() + pool.truncateQueue() + + dropBetweenReorgHistogram.Update(int64(pool.changesSinceReorg)) + pool.changesSinceReorg = 0 // Reset change counter + pool.mu.Unlock() + + // Notify subsystems for newly added transactions + for _, tx := range promoted { + addr, _ := types.Sender(pool.signer, tx) + if _, ok := events[addr]; !ok { + events[addr] = newSortedMap() + } + events[addr].Put(tx) + } + if len(events) > 0 { + var txs []*types.Transaction + for _, set := range events { + txs = append(txs, set.Flatten()...) + } + pool.txFeed.Send(core.NewTxsEvent{Txs: txs}) + } +} + +// reset retrieves the current state of the blockchain and ensures the content +// of the transaction pool is valid with regard to the chain state. +func (pool *LegacyPool) reset(oldHead, newHead *types.Header) { + // If we're reorging an old state, reinject all dropped transactions + var reinject types.Transactions + + if oldHead != nil && oldHead.Hash() != newHead.ParentHash { + // If the reorg is too deep, avoid doing it (will happen during fast sync) + oldNum := oldHead.Number.Uint64() + newNum := newHead.Number.Uint64() + + if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 { + log.Debug("Skipping deep transaction reorg", "depth", depth) + } else { + // Reorg seems shallow enough to pull in all transactions into memory + var ( + rem = pool.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64()) + add = pool.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64()) + ) + if rem == nil { + // This can happen if a setHead is performed, where we simply discard the old + // head from the chain. + // If that is the case, we don't have the lost transactions anymore, and + // there's nothing to add + if newNum >= oldNum { + // If we reorged to a same or higher number, then it's not a case of setHead + log.Warn("Transaction pool reset with missing old head", + "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) + return + } + // If the reorg ended up on a lower number, it's indicative of setHead being the cause + log.Debug("Skipping transaction reset caused by setHead", + "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) + // We still need to update the current state s.th. the lost transactions can be readded by the user + } else { + if add == nil { + // if the new head is nil, it means that something happened between + // the firing of newhead-event and _now_: most likely a + // reorg caused by sync-reversion or explicit sethead back to an + // earlier block. + log.Warn("Transaction pool reset with missing new head", "number", newHead.Number, "hash", newHead.Hash()) + return + } + var discarded, included types.Transactions + for rem.NumberU64() > add.NumberU64() { + discarded = append(discarded, rem.Transactions()...) + if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { + log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) + return + } + } + for add.NumberU64() > rem.NumberU64() { + included = append(included, add.Transactions()...) + if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { + log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) + return + } + } + for rem.Hash() != add.Hash() { + discarded = append(discarded, rem.Transactions()...) + if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { + log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) + return + } + included = append(included, add.Transactions()...) + if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { + log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) + return + } + } + lost := make([]*types.Transaction, 0, len(discarded)) + for _, tx := range types.TxDifference(discarded, included) { + if pool.Filter(tx) { + lost = append(lost, tx) + } + } + reinject = lost + } + } + } + // Initialize the internal state to the current head + if newHead == nil { + newHead = pool.chain.CurrentBlock() // Special case during testing + } + statedb, err := pool.chain.StateAt(newHead.Root) + if err != nil { + log.Error("Failed to reset txpool state", "err", err) + return + } + pool.currentHead.Store(newHead) + pool.currentState = statedb + pool.pendingNonces = newNoncer(statedb) + + // Inject any transactions discarded due to reorgs + log.Debug("Reinjecting stale transactions", "count", len(reinject)) + core.SenderCacher.Recover(pool.signer, reinject) + pool.addTxsLocked(reinject, false) +} + +// promoteExecutables moves transactions that have become processable from the +// future queue to the set of pending transactions. During this process, all +// invalidated transactions (low nonce, low balance) are deleted. +func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.Transaction { + // Track the promoted transactions to broadcast them at once + var promoted []*types.Transaction + + // Iterate over all accounts and promote any executable transactions + gasLimit := pool.currentHead.Load().GasLimit + for _, addr := range accounts { + list := pool.queue[addr] + if list == nil { + continue // Just in case someone calls with a non existing account + } + // Drop all transactions that are deemed too old (low nonce) + forwards := list.Forward(pool.currentState.GetNonce(addr)) + for _, tx := range forwards { + hash := tx.Hash() + pool.all.Remove(hash) + } + log.Trace("Removed old queued transactions", "count", len(forwards)) + // Drop all transactions that are too costly (low balance or out of gas) + drops, _ := list.Filter(pool.currentState.GetBalance(addr), gasLimit) + for _, tx := range drops { + hash := tx.Hash() + pool.all.Remove(hash) + } + log.Trace("Removed unpayable queued transactions", "count", len(drops)) + queuedNofundsMeter.Mark(int64(len(drops))) + + // Gather all executable transactions and promote them + readies := list.Ready(pool.pendingNonces.get(addr)) + for _, tx := range readies { + hash := tx.Hash() + if pool.promoteTx(addr, hash, tx) { + promoted = append(promoted, tx) + } + } + log.Trace("Promoted queued transactions", "count", len(promoted)) + queuedGauge.Dec(int64(len(readies))) + + // Drop all transactions over the allowed limit + var caps types.Transactions + if !pool.locals.contains(addr) { + caps = list.Cap(int(pool.config.AccountQueue)) + for _, tx := range caps { + hash := tx.Hash() + pool.all.Remove(hash) + log.Trace("Removed cap-exceeding queued transaction", "hash", hash) + } + queuedRateLimitMeter.Mark(int64(len(caps))) + } + // Mark all the items dropped as removed + pool.priced.Removed(len(forwards) + len(drops) + len(caps)) + queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) + if pool.locals.contains(addr) { + localGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) + } + // Delete the entire queue entry if it became empty. + if list.Empty() { + delete(pool.queue, addr) + delete(pool.beats, addr) + if _, ok := pool.pending[addr]; !ok { + pool.reserve(addr, false) + } + } + } + return promoted +} + +// truncatePending removes transactions from the pending queue if the pool is above the +// pending limit. The algorithm tries to reduce transaction counts by an approximately +// equal number for all for accounts with many pending transactions. +func (pool *LegacyPool) truncatePending() { + pending := uint64(0) + for _, list := range pool.pending { + pending += uint64(list.Len()) + } + if pending <= pool.config.GlobalSlots { + return + } + + pendingBeforeCap := pending + // Assemble a spam order to penalize large transactors first + spammers := prque.New[int64, common.Address](nil) + for addr, list := range pool.pending { + // Only evict transactions from high rollers + if !pool.locals.contains(addr) && uint64(list.Len()) > pool.config.AccountSlots { + spammers.Push(addr, int64(list.Len())) + } + } + // Gradually drop transactions from offenders + offenders := []common.Address{} + for pending > pool.config.GlobalSlots && !spammers.Empty() { + // Retrieve the next offender if not local address + offender, _ := spammers.Pop() + offenders = append(offenders, offender) + + // Equalize balances until all the same or below threshold + if len(offenders) > 1 { + // Calculate the equalization threshold for all current offenders + threshold := pool.pending[offender].Len() + + // Iteratively reduce all offenders until below limit or threshold reached + for pending > pool.config.GlobalSlots && pool.pending[offenders[len(offenders)-2]].Len() > threshold { + for i := 0; i < len(offenders)-1; i++ { + list := pool.pending[offenders[i]] + + caps := list.Cap(list.Len() - 1) + for _, tx := range caps { + // Drop the transaction from the global pools too + hash := tx.Hash() + pool.all.Remove(hash) + + // Update the account nonce to the dropped transaction + pool.pendingNonces.setIfLower(offenders[i], tx.Nonce()) + log.Trace("Removed fairness-exceeding pending transaction", "hash", hash) + } + pool.priced.Removed(len(caps)) + pendingGauge.Dec(int64(len(caps))) + if pool.locals.contains(offenders[i]) { + localGauge.Dec(int64(len(caps))) + } + pending-- + } + } + } + } + + // If still above threshold, reduce to limit or min allowance + if pending > pool.config.GlobalSlots && len(offenders) > 0 { + for pending > pool.config.GlobalSlots && uint64(pool.pending[offenders[len(offenders)-1]].Len()) > pool.config.AccountSlots { + for _, addr := range offenders { + list := pool.pending[addr] + + caps := list.Cap(list.Len() - 1) + for _, tx := range caps { + // Drop the transaction from the global pools too + hash := tx.Hash() + pool.all.Remove(hash) + + // Update the account nonce to the dropped transaction + pool.pendingNonces.setIfLower(addr, tx.Nonce()) + log.Trace("Removed fairness-exceeding pending transaction", "hash", hash) + } + pool.priced.Removed(len(caps)) + pendingGauge.Dec(int64(len(caps))) + if pool.locals.contains(addr) { + localGauge.Dec(int64(len(caps))) + } + pending-- + } + } + } + pendingRateLimitMeter.Mark(int64(pendingBeforeCap - pending)) +} + +// truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit. +func (pool *LegacyPool) truncateQueue() { + queued := uint64(0) + for _, list := range pool.queue { + queued += uint64(list.Len()) + } + if queued <= pool.config.GlobalQueue { + return + } + + // Sort all accounts with queued transactions by heartbeat + addresses := make(addressesByHeartbeat, 0, len(pool.queue)) + for addr := range pool.queue { + if !pool.locals.contains(addr) { // don't drop locals + addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]}) + } + } + sort.Sort(sort.Reverse(addresses)) + + // Drop transactions until the total is below the limit or only locals remain + for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; { + addr := addresses[len(addresses)-1] + list := pool.queue[addr.address] + + addresses = addresses[:len(addresses)-1] + + // Drop all transactions if they are less than the overflow + if size := uint64(list.Len()); size <= drop { + for _, tx := range list.Flatten() { + pool.removeTx(tx.Hash(), true, true) + } + drop -= size + queuedRateLimitMeter.Mark(int64(size)) + continue + } + // Otherwise drop only last few transactions + txs := list.Flatten() + for i := len(txs) - 1; i >= 0 && drop > 0; i-- { + pool.removeTx(txs[i].Hash(), true, true) + drop-- + queuedRateLimitMeter.Mark(1) + } + } +} + +// demoteUnexecutables removes invalid and processed transactions from the pools +// executable/pending queue and any subsequent transactions that become unexecutable +// are moved back into the future queue. +// +// Note: transactions are not marked as removed in the priced list because re-heaping +// is always explicitly triggered by SetBaseFee and it would be unnecessary and wasteful +// to trigger a re-heap is this function +func (pool *LegacyPool) demoteUnexecutables() { + // Iterate over all accounts and demote any non-executable transactions + gasLimit := pool.currentHead.Load().GasLimit + for addr, list := range pool.pending { + nonce := pool.currentState.GetNonce(addr) + + // Drop all transactions that are deemed too old (low nonce) + olds := list.Forward(nonce) + for _, tx := range olds { + hash := tx.Hash() + pool.all.Remove(hash) + log.Trace("Removed old pending transaction", "hash", hash) + } + // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later + drops, invalids := list.Filter(pool.currentState.GetBalance(addr), gasLimit) + for _, tx := range drops { + hash := tx.Hash() + log.Trace("Removed unpayable pending transaction", "hash", hash) + pool.all.Remove(hash) + } + pendingNofundsMeter.Mark(int64(len(drops))) + + for _, tx := range invalids { + hash := tx.Hash() + log.Trace("Demoting pending transaction", "hash", hash) + + // Internal shuffle shouldn't touch the lookup set. + pool.enqueueTx(hash, tx, false, false) + } + pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) + if pool.locals.contains(addr) { + localGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) + } + // If there's a gap in front, alert (should never happen) and postpone all transactions + if list.Len() > 0 && list.txs.Get(nonce) == nil { + gapped := list.Cap(0) + for _, tx := range gapped { + hash := tx.Hash() + log.Error("Demoting invalidated transaction", "hash", hash) + + // Internal shuffle shouldn't touch the lookup set. + pool.enqueueTx(hash, tx, false, false) + } + pendingGauge.Dec(int64(len(gapped))) + } + // Delete the entire pending entry if it became empty. + if list.Empty() { + delete(pool.pending, addr) + if _, ok := pool.queue[addr]; !ok { + pool.reserve(addr, false) + } + } + } +} + +// addressByHeartbeat is an account address tagged with its last activity timestamp. +type addressByHeartbeat struct { + address common.Address + heartbeat time.Time +} + +type addressesByHeartbeat []addressByHeartbeat + +func (a addressesByHeartbeat) Len() int { return len(a) } +func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) } +func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// accountSet is simply a set of addresses to check for existence, and a signer +// capable of deriving addresses from transactions. +type accountSet struct { + accounts map[common.Address]struct{} + signer types.Signer + cache *[]common.Address +} + +// newAccountSet creates a new address set with an associated signer for sender +// derivations. +func newAccountSet(signer types.Signer, addrs ...common.Address) *accountSet { + as := &accountSet{ + accounts: make(map[common.Address]struct{}, len(addrs)), + signer: signer, + } + for _, addr := range addrs { + as.add(addr) + } + return as +} + +// contains checks if a given address is contained within the set. +func (as *accountSet) contains(addr common.Address) bool { + _, exist := as.accounts[addr] + return exist +} + +// containsTx checks if the sender of a given tx is within the set. If the sender +// cannot be derived, this method returns false. +func (as *accountSet) containsTx(tx *types.Transaction) bool { + if addr, err := types.Sender(as.signer, tx); err == nil { + return as.contains(addr) + } + return false +} + +// add inserts a new address into the set to track. +func (as *accountSet) add(addr common.Address) { + as.accounts[addr] = struct{}{} + as.cache = nil +} + +// addTx adds the sender of tx into the set. +func (as *accountSet) addTx(tx *types.Transaction) { + if addr, err := types.Sender(as.signer, tx); err == nil { + as.add(addr) + } +} + +// flatten returns the list of addresses within this set, also caching it for later +// reuse. The returned slice should not be changed! +func (as *accountSet) flatten() []common.Address { + if as.cache == nil { + accounts := make([]common.Address, 0, len(as.accounts)) + for account := range as.accounts { + accounts = append(accounts, account) + } + as.cache = &accounts + } + return *as.cache +} + +// merge adds all addresses from the 'other' set into 'as'. +func (as *accountSet) merge(other *accountSet) { + for addr := range other.accounts { + as.accounts[addr] = struct{}{} + } + as.cache = nil +} + +// lookup is used internally by LegacyPool to track transactions while allowing +// lookup without mutex contention. +// +// Note, although this type is properly protected against concurrent access, it +// is **not** a type that should ever be mutated or even exposed outside of the +// transaction pool, since its internal state is tightly coupled with the pools +// internal mechanisms. The sole purpose of the type is to permit out-of-bound +// peeking into the pool in LegacyPool.Get without having to acquire the widely scoped +// LegacyPool.mu mutex. +// +// This lookup set combines the notion of "local transactions", which is useful +// to build upper-level structure. +type lookup struct { + slots int + lock sync.RWMutex + locals map[common.Hash]*types.Transaction + remotes map[common.Hash]*types.Transaction +} + +// newLookup returns a new lookup structure. +func newLookup() *lookup { + return &lookup{ + locals: make(map[common.Hash]*types.Transaction), + remotes: make(map[common.Hash]*types.Transaction), + } +} + +// Range calls f on each key and value present in the map. The callback passed +// should return the indicator whether the iteration needs to be continued. +// Callers need to specify which set (or both) to be iterated. +func (t *lookup) Range(f func(hash common.Hash, tx *types.Transaction, local bool) bool, local bool, remote bool) { + t.lock.RLock() + defer t.lock.RUnlock() + + if local { + for key, value := range t.locals { + if !f(key, value, true) { + return + } + } + } + if remote { + for key, value := range t.remotes { + if !f(key, value, false) { + return + } + } + } +} + +// Get returns a transaction if it exists in the lookup, or nil if not found. +func (t *lookup) Get(hash common.Hash) *types.Transaction { + t.lock.RLock() + defer t.lock.RUnlock() + + if tx := t.locals[hash]; tx != nil { + return tx + } + return t.remotes[hash] +} + +// GetLocal returns a transaction if it exists in the lookup, or nil if not found. +func (t *lookup) GetLocal(hash common.Hash) *types.Transaction { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.locals[hash] +} + +// GetRemote returns a transaction if it exists in the lookup, or nil if not found. +func (t *lookup) GetRemote(hash common.Hash) *types.Transaction { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.remotes[hash] +} + +// Count returns the current number of transactions in the lookup. +func (t *lookup) Count() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return len(t.locals) + len(t.remotes) +} + +// LocalCount returns the current number of local transactions in the lookup. +func (t *lookup) LocalCount() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return len(t.locals) +} + +// RemoteCount returns the current number of remote transactions in the lookup. +func (t *lookup) RemoteCount() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return len(t.remotes) +} + +// Slots returns the current number of slots used in the lookup. +func (t *lookup) Slots() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.slots +} + +// Add adds a transaction to the lookup. +func (t *lookup) Add(tx *types.Transaction, local bool) { + t.lock.Lock() + defer t.lock.Unlock() + + t.slots += numSlots(tx) + slotsGauge.Update(int64(t.slots)) + + if local { + t.locals[tx.Hash()] = tx + } else { + t.remotes[tx.Hash()] = tx + } +} + +// Remove removes a transaction from the lookup. +func (t *lookup) Remove(hash common.Hash) { + t.lock.Lock() + defer t.lock.Unlock() + + tx, ok := t.locals[hash] + if !ok { + tx, ok = t.remotes[hash] + } + if !ok { + log.Error("No transaction found to be deleted", "hash", hash) + return + } + t.slots -= numSlots(tx) + slotsGauge.Update(int64(t.slots)) + + delete(t.locals, hash) + delete(t.remotes, hash) +} + +// RemoteToLocals migrates the transactions belongs to the given locals to locals +// set. The assumption is held the locals set is thread-safe to be used. +func (t *lookup) RemoteToLocals(locals *accountSet) int { + t.lock.Lock() + defer t.lock.Unlock() + + var migrated int + for hash, tx := range t.remotes { + if locals.containsTx(tx) { + t.locals[hash] = tx + delete(t.remotes, hash) + migrated += 1 + } + } + return migrated +} + +// RemotesBelowTip finds all remote transactions below the given tip threshold. +func (t *lookup) RemotesBelowTip(threshold *big.Int) types.Transactions { + found := make(types.Transactions, 0, 128) + t.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool { + if tx.GasTipCapIntCmp(threshold) < 0 { + found = append(found, tx) + } + return true + }, false, true) // Only iterate remotes + return found +} + +// numSlots calculates the number of slots needed for a single transaction. +func numSlots(tx *types.Transaction) int { + return int((tx.Size() + txSlotSize - 1) / txSlotSize) +} diff --git a/core/txpool/txpool2_test.go b/core/txpool/legacypool/legacypool2_test.go similarity index 84% rename from core/txpool/txpool2_test.go rename to core/txpool/legacypool/legacypool2_test.go index d7ae3bdb1b00..a73c1bb8a772 100644 --- a/core/txpool/txpool2_test.go +++ b/core/txpool/legacypool/legacypool2_test.go @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package txpool +package legacypool import ( "crypto/ecdsa" @@ -33,7 +33,7 @@ func pricedValuedTransaction(nonce uint64, value int64, gaslimit uint64, gaspric return tx } -func count(t *testing.T, pool *TxPool) (pending int, queued int) { +func count(t *testing.T, pool *LegacyPool) (pending int, queued int) { t.Helper() pending, queued = pool.stats() if err := validatePoolInternals(pool); err != nil { @@ -42,7 +42,7 @@ func count(t *testing.T, pool *TxPool) (pending int, queued int) { return pending, queued } -func fillPool(t testing.TB, pool *TxPool) { +func fillPool(t testing.TB, pool *LegacyPool) { t.Helper() // Create a number of test accounts, fund them and make transactions executableTxs := types.Transactions{} @@ -56,8 +56,8 @@ func fillPool(t testing.TB, pool *TxPool) { } } // Import the batch and verify that limits have been enforced - pool.AddRemotesSync(executableTxs) - pool.AddRemotesSync(nonExecutableTxs) + pool.addRemotesSync(executableTxs) + pool.addRemotesSync(nonExecutableTxs) pending, queued := pool.Stats() slots := pool.all.Slots() // sanity-check that the test prerequisites are ok (pending full) @@ -79,12 +79,13 @@ func TestTransactionFutureAttack(t *testing.T) { // Create the pool to test the limit enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) config := testTxPoolConfig config.GlobalQueue = 100 config.GlobalSlots = 100 - pool := NewTxPool(config, eip1559Config, blockchain) - defer pool.Stop() + pool := New(config, blockchain) + pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() fillPool(t, pool) pending, _ := pool.Stats() // Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops @@ -96,7 +97,7 @@ func TestTransactionFutureAttack(t *testing.T) { futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 100000, big.NewInt(500), key)) } for i := 0; i < 5; i++ { - pool.AddRemotesSync(futureTxs) + pool.addRemotesSync(futureTxs) newPending, newQueued := count(t, pool) t.Logf("pending: %d queued: %d, all: %d\n", newPending, newQueued, pool.all.Slots()) } @@ -115,9 +116,10 @@ func TestTransactionFuture1559(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, eip1559Config, blockchain) - defer pool.Stop() + blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) + pool := New(testTxPoolConfig, blockchain) + pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Create a number of test accounts, fund them and make transactions fillPool(t, pool) @@ -131,7 +133,7 @@ func TestTransactionFuture1559(t *testing.T) { for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ { futureTxs = append(futureTxs, dynamicFeeTx(1000+uint64(j), 100000, big.NewInt(200), big.NewInt(101), key)) } - pool.AddRemotesSync(futureTxs) + pool.addRemotesSync(futureTxs) } newPending, _ := pool.Stats() // Pending should not have been touched @@ -147,9 +149,10 @@ func TestTransactionZAttack(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, eip1559Config, blockchain) - defer pool.Stop() + blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) + pool := New(testTxPoolConfig, blockchain) + pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Create a number of test accounts, fund them and make transactions fillPool(t, pool) @@ -181,7 +184,7 @@ func TestTransactionZAttack(t *testing.T) { key, _ := crypto.GenerateKey() pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), big.NewInt(100000000000)) futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 21000, big.NewInt(500), key)) - pool.AddRemotesSync(futureTxs) + pool.addRemotesSync(futureTxs) } overDraftTxs := types.Transactions{} @@ -192,11 +195,11 @@ func TestTransactionZAttack(t *testing.T) { overDraftTxs = append(overDraftTxs, pricedValuedTransaction(uint64(j), 600000000000, 21000, big.NewInt(500), key)) } } - pool.AddRemotesSync(overDraftTxs) - pool.AddRemotesSync(overDraftTxs) - pool.AddRemotesSync(overDraftTxs) - pool.AddRemotesSync(overDraftTxs) - pool.AddRemotesSync(overDraftTxs) + pool.addRemotesSync(overDraftTxs) + pool.addRemotesSync(overDraftTxs) + pool.addRemotesSync(overDraftTxs) + pool.addRemotesSync(overDraftTxs) + pool.addRemotesSync(overDraftTxs) newPending, newQueued := count(t, pool) newIvPending := countInvalidPending() @@ -214,12 +217,13 @@ func TestTransactionZAttack(t *testing.T) { func BenchmarkFutureAttack(b *testing.B) { // Create the pool to test the limit enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) config := testTxPoolConfig config.GlobalQueue = 100 config.GlobalSlots = 100 - pool := NewTxPool(config, eip1559Config, blockchain) - defer pool.Stop() + pool := New(config, blockchain) + pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() fillPool(b, pool) key, _ := crypto.GenerateKey() @@ -231,6 +235,6 @@ func BenchmarkFutureAttack(b *testing.B) { } b.ResetTimer() for i := 0; i < 5; i++ { - pool.AddRemotesSync(futureTxs) + pool.addRemotesSync(futureTxs) } } diff --git a/core/txpool/txpool_test.go b/core/txpool/legacypool/legacypool_test.go similarity index 85% rename from core/txpool/txpool_test.go rename to core/txpool/legacypool/legacypool_test.go index 22e106aaf8c5..a8f3dd7d8624 100644 --- a/core/txpool/txpool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package txpool +package legacypool import ( "crypto/ecdsa" @@ -24,6 +24,7 @@ import ( "math/big" "math/rand" "os" + "sync" "sync/atomic" "testing" "time" @@ -32,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" @@ -59,17 +61,22 @@ func init() { } type testBlockChain struct { + config *params.ChainConfig gasLimit atomic.Uint64 statedb *state.StateDB chainHeadFeed *event.Feed } -func newTestBlockChain(gasLimit uint64, statedb *state.StateDB, chainHeadFeed *event.Feed) *testBlockChain { - bc := testBlockChain{statedb: statedb, chainHeadFeed: new(event.Feed)} +func newTestBlockChain(config *params.ChainConfig, gasLimit uint64, statedb *state.StateDB, chainHeadFeed *event.Feed) *testBlockChain { + bc := testBlockChain{config: config, statedb: statedb, chainHeadFeed: new(event.Feed)} bc.gasLimit.Store(gasLimit) return &bc } +func (bc *testBlockChain) Config() *params.ChainConfig { + return bc.config +} + func (bc *testBlockChain) CurrentBlock() *types.Header { return &types.Header{ Number: new(big.Int), @@ -121,24 +128,51 @@ func dynamicFeeTx(nonce uint64, gaslimit uint64, gasFee *big.Int, tip *big.Int, return tx } -func setupPool() (*TxPool, *ecdsa.PrivateKey) { +func makeAddressReserver() txpool.AddressReserver { + var ( + reserved = make(map[common.Address]struct{}) + lock sync.Mutex + ) + return func(addr common.Address, reserve bool) error { + lock.Lock() + defer lock.Unlock() + + _, exists := reserved[addr] + if reserve { + if exists { + panic("already reserved") + } + reserved[addr] = struct{}{} + return nil + } + if !exists { + panic("not reserved") + } + delete(reserved, addr) + return nil + } +} + +func setupPool() (*LegacyPool, *ecdsa.PrivateKey) { return setupPoolWithConfig(params.TestChainConfig) } -func setupPoolWithConfig(config *params.ChainConfig) (*TxPool, *ecdsa.PrivateKey) { +func setupPoolWithConfig(config *params.ChainConfig) (*LegacyPool, *ecdsa.PrivateKey) { statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(10000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(config, 10000000, statedb, new(event.Feed)) key, _ := crypto.GenerateKey() - pool := NewTxPool(testTxPoolConfig, config, blockchain) - + pool := New(testTxPoolConfig, blockchain) + if err := pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()); err != nil { + panic(err) + } // wait for the pool to initialize <-pool.initDoneCh return pool, key } // validatePoolInternals checks various consistency invariants within the pool. -func validatePoolInternals(pool *TxPool) error { +func validatePoolInternals(pool *LegacyPool) error { pool.mu.RLock() defer pool.mu.RUnlock() @@ -242,20 +276,21 @@ func TestStateChangeDuringReset(t *testing.T) { // setup pool with 2 transaction in it statedb.SetBalance(address, new(big.Int).SetUint64(params.Ether)) - blockchain := &testChain{newTestBlockChain(1000000000, statedb, new(event.Feed)), address, &trigger} + blockchain := &testChain{newTestBlockChain(params.TestChainConfig, 1000000000, statedb, new(event.Feed)), address, &trigger} tx0 := transaction(0, 100000, key) tx1 := transaction(1, 100000, key) - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) - defer pool.Stop() + pool := New(testTxPoolConfig, blockchain) + pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() nonce := pool.Nonce(address) if nonce != 0 { t.Fatalf("Invalid nonce, want 0, got %d", nonce) } - pool.AddRemotesSync([]*types.Transaction{tx0, tx1}) + pool.addRemotesSync([]*types.Transaction{tx0, tx1}) nonce = pool.Nonce(address) if nonce != 2 { @@ -272,13 +307,13 @@ func TestStateChangeDuringReset(t *testing.T) { } } -func testAddBalance(pool *TxPool, addr common.Address, amount *big.Int) { +func testAddBalance(pool *LegacyPool, addr common.Address, amount *big.Int) { pool.mu.Lock() pool.currentState.AddBalance(addr, amount) pool.mu.Unlock() } -func testSetNonce(pool *TxPool, addr common.Address, nonce uint64) { +func testSetNonce(pool *LegacyPool, addr common.Address, nonce uint64) { pool.mu.Lock() pool.currentState.SetNonce(addr, nonce) pool.mu.Unlock() @@ -288,36 +323,36 @@ func TestInvalidTransactions(t *testing.T) { t.Parallel() pool, key := setupPool() - defer pool.Stop() + defer pool.Close() tx := transaction(0, 100, key) from, _ := deriveSender(tx) // Intrinsic gas too low testAddBalance(pool, from, big.NewInt(1)) - if err, want := pool.AddRemote(tx), core.ErrIntrinsicGas; !errors.Is(err, want) { + if err, want := pool.addRemote(tx), core.ErrIntrinsicGas; !errors.Is(err, want) { t.Errorf("want %v have %v", want, err) } // Insufficient funds tx = transaction(0, 100000, key) - if err, want := pool.AddRemote(tx), core.ErrInsufficientFunds; !errors.Is(err, want) { + if err, want := pool.addRemote(tx), core.ErrInsufficientFunds; !errors.Is(err, want) { t.Errorf("want %v have %v", want, err) } testSetNonce(pool, from, 1) testAddBalance(pool, from, big.NewInt(0xffffffffffffff)) tx = transaction(0, 100000, key) - if err, want := pool.AddRemote(tx), core.ErrNonceTooLow; !errors.Is(err, want) { + if err, want := pool.addRemote(tx), core.ErrNonceTooLow; !errors.Is(err, want) { t.Errorf("want %v have %v", want, err) } tx = transaction(1, 100000, key) - pool.gasPrice = big.NewInt(1000) - if err, want := pool.AddRemote(tx), ErrUnderpriced; !errors.Is(err, want) { + pool.gasTip.Store(big.NewInt(1000)) + if err, want := pool.addRemote(tx), txpool.ErrUnderpriced; !errors.Is(err, want) { t.Errorf("want %v have %v", want, err) } - if err := pool.AddLocal(tx); err != nil { + if err := pool.addLocal(tx); err != nil { t.Error("expected", nil, "got", err) } } @@ -326,7 +361,7 @@ func TestQueue(t *testing.T) { t.Parallel() pool, key := setupPool() - defer pool.Stop() + defer pool.Close() tx := transaction(0, 100, key) from, _ := deriveSender(tx) @@ -357,7 +392,7 @@ func TestQueue2(t *testing.T) { t.Parallel() pool, key := setupPool() - defer pool.Stop() + defer pool.Close() tx1 := transaction(0, 100, key) tx2 := transaction(10, 100, key) @@ -383,13 +418,13 @@ func TestNegativeValue(t *testing.T) { t.Parallel() pool, key := setupPool() - defer pool.Stop() + defer pool.Close() tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key) from, _ := deriveSender(tx) testAddBalance(pool, from, big.NewInt(1)) - if err := pool.AddRemote(tx); err != ErrNegativeValue { - t.Error("expected", ErrNegativeValue, "got", err) + if err := pool.addRemote(tx); err != txpool.ErrNegativeValue { + t.Error("expected", txpool.ErrNegativeValue, "got", err) } } @@ -397,11 +432,11 @@ func TestTipAboveFeeCap(t *testing.T) { t.Parallel() pool, key := setupPoolWithConfig(eip1559Config) - defer pool.Stop() + defer pool.Close() tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key) - if err := pool.AddRemote(tx); err != core.ErrTipAboveFeeCap { + if err := pool.addRemote(tx); err != core.ErrTipAboveFeeCap { t.Error("expected", core.ErrTipAboveFeeCap, "got", err) } } @@ -410,18 +445,18 @@ func TestVeryHighValues(t *testing.T) { t.Parallel() pool, key := setupPoolWithConfig(eip1559Config) - defer pool.Stop() + defer pool.Close() veryBigNumber := big.NewInt(1) veryBigNumber.Lsh(veryBigNumber, 300) tx := dynamicFeeTx(0, 100, big.NewInt(1), veryBigNumber, key) - if err := pool.AddRemote(tx); err != core.ErrTipVeryHigh { + if err := pool.addRemote(tx); err != core.ErrTipVeryHigh { t.Error("expected", core.ErrTipVeryHigh, "got", err) } tx2 := dynamicFeeTx(0, 100, veryBigNumber, big.NewInt(1), key) - if err := pool.AddRemote(tx2); err != core.ErrFeeCapVeryHigh { + if err := pool.addRemote(tx2); err != core.ErrFeeCapVeryHigh { t.Error("expected", core.ErrFeeCapVeryHigh, "got", err) } } @@ -430,14 +465,14 @@ func TestChainFork(t *testing.T) { t.Parallel() pool, key := setupPool() - defer pool.Stop() + defer pool.Close() addr := crypto.PubkeyToAddress(key.PublicKey) resetState := func() { statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb.AddBalance(addr, big.NewInt(100000000000000)) - pool.chain = newTestBlockChain(1000000, statedb, new(event.Feed)) + pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed)) <-pool.requestReset(nil, nil) } resetState() @@ -446,7 +481,7 @@ func TestChainFork(t *testing.T) { if _, err := pool.add(tx, false); err != nil { t.Error("didn't expect error", err) } - pool.removeTx(tx.Hash(), true) + pool.removeTx(tx.Hash(), true, true) // reset the pool's internal state resetState() @@ -459,14 +494,14 @@ func TestDoubleNonce(t *testing.T) { t.Parallel() pool, key := setupPool() - defer pool.Stop() + defer pool.Close() addr := crypto.PubkeyToAddress(key.PublicKey) resetState := func() { statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb.AddBalance(addr, big.NewInt(100000000000000)) - pool.chain = newTestBlockChain(1000000, statedb, new(event.Feed)) + pool.chain = newTestBlockChain(pool.chainconfig, 1000000, statedb, new(event.Feed)) <-pool.requestReset(nil, nil) } resetState() @@ -510,7 +545,7 @@ func TestMissingNonce(t *testing.T) { t.Parallel() pool, key := setupPool() - defer pool.Stop() + defer pool.Close() addr := crypto.PubkeyToAddress(key.PublicKey) testAddBalance(pool, addr, big.NewInt(100000000000000)) @@ -534,7 +569,7 @@ func TestNonceRecovery(t *testing.T) { const n = 10 pool, key := setupPool() - defer pool.Stop() + defer pool.Close() addr := crypto.PubkeyToAddress(key.PublicKey) testSetNonce(pool, addr, n) @@ -542,7 +577,7 @@ func TestNonceRecovery(t *testing.T) { <-pool.requestReset(nil, nil) tx := transaction(n, 100000, key) - if err := pool.AddRemote(tx); err != nil { + if err := pool.addRemote(tx); err != nil { t.Error(err) } // simulate some weird re-order of transactions and missing nonce(s) @@ -560,7 +595,7 @@ func TestDropping(t *testing.T) { // Create a test account and fund it pool, key := setupPool() - defer pool.Stop() + defer pool.Close() account := crypto.PubkeyToAddress(key.PublicKey) testAddBalance(pool, account, big.NewInt(1000)) @@ -664,10 +699,11 @@ func TestPostponing(t *testing.T) { // Create the pool to test the postponing with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) - defer pool.Stop() + pool := New(testTxPoolConfig, blockchain) + pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Create two test accounts to produce different gap profiles with keys := make([]*ecdsa.PrivateKey, 2) @@ -692,7 +728,7 @@ func TestPostponing(t *testing.T) { txs = append(txs, tx) } } - for i, err := range pool.AddRemotesSync(txs) { + for i, err := range pool.addRemotesSync(txs) { if err != nil { t.Fatalf("tx %d: failed to add transactions: %v", i, err) } @@ -777,7 +813,7 @@ func TestGapFilling(t *testing.T) { // Create a test account and fund it pool, key := setupPool() - defer pool.Stop() + defer pool.Close() account := crypto.PubkeyToAddress(key.PublicKey) testAddBalance(pool, account, big.NewInt(1000000)) @@ -788,7 +824,7 @@ func TestGapFilling(t *testing.T) { defer sub.Unsubscribe() // Create a pending and a queued transaction with a nonce-gap in between - pool.AddRemotesSync([]*types.Transaction{ + pool.addRemotesSync([]*types.Transaction{ transaction(0, 100000, key), transaction(2, 100000, key), }) @@ -831,7 +867,7 @@ func TestQueueAccountLimiting(t *testing.T) { // Create a test account and fund it pool, key := setupPool() - defer pool.Stop() + defer pool.Close() account := crypto.PubkeyToAddress(key.PublicKey) testAddBalance(pool, account, big.NewInt(1000000)) @@ -876,14 +912,15 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) { // Create the pool to test the limit enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) config := testTxPoolConfig config.NoLocals = nolocals config.GlobalQueue = config.AccountQueue*3 - 1 // reduce the queue limits to shorten test time (-1 to make it non divisible) - pool := NewTxPool(config, params.TestChainConfig, blockchain) - defer pool.Stop() + pool := New(config, blockchain) + pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Create a number of test accounts and fund them (last one will be the local) keys := make([]*ecdsa.PrivateKey, 5) @@ -905,7 +942,7 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) { nonces[addr]++ } // Import the batch and verify that limits have been enforced - pool.AddRemotesSync(txs) + pool.addRemotesSync(txs) queued := 0 for addr, list := range pool.queue { @@ -922,7 +959,7 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) { for i := uint64(0); i < 3*config.GlobalQueue; i++ { txs = append(txs, transaction(i+1, 100000, local)) } - pool.AddLocals(txs) + pool.addLocals(txs) // If locals are disabled, the previous eviction algorithm should apply here too if nolocals { @@ -968,14 +1005,15 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { // Create the pool to test the non-expiration enforcement statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) config := testTxPoolConfig config.Lifetime = time.Second config.NoLocals = nolocals - pool := NewTxPool(config, params.TestChainConfig, blockchain) - defer pool.Stop() + pool := New(config, blockchain) + pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Create two test accounts to ensure remotes expire but locals do not local, _ := crypto.GenerateKey() @@ -985,10 +1023,10 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { testAddBalance(pool, crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(1000000000)) // Add the two transactions and ensure they both are queued up - if err := pool.AddLocal(pricedTransaction(1, 100000, big.NewInt(1), local)); err != nil { + if err := pool.addLocal(pricedTransaction(1, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add local transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(1), remote)); err != nil { + if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(1), remote)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } pending, queued := pool.Stats() @@ -1055,7 +1093,7 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { } // Queue gapped transactions - if err := pool.AddLocal(pricedTransaction(4, 100000, big.NewInt(1), local)); err != nil { + if err := pool.addLocal(pricedTransaction(4, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } if err := pool.addRemoteSync(pricedTransaction(4, 100000, big.NewInt(1), remote)); err != nil { @@ -1064,7 +1102,7 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { time.Sleep(5 * evictionInterval) // A half lifetime pass // Queue executable transactions, the life cycle should be restarted. - if err := pool.AddLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil { + if err := pool.addLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add remote transaction: %v", err) } if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(1), remote)); err != nil { @@ -1078,7 +1116,7 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) { t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 2) } if queued != 2 { - t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 3) + t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2) } if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) @@ -1112,7 +1150,7 @@ func TestPendingLimiting(t *testing.T) { // Create a test account and fund it pool, key := setupPool() - defer pool.Stop() + defer pool.Close() account := crypto.PubkeyToAddress(key.PublicKey) testAddBalance(pool, account, big.NewInt(1000000000000)) @@ -1153,13 +1191,14 @@ func TestPendingGlobalLimiting(t *testing.T) { // Create the pool to test the limit enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) config := testTxPoolConfig config.GlobalSlots = config.AccountSlots * 10 - pool := NewTxPool(config, params.TestChainConfig, blockchain) - defer pool.Stop() + pool := New(config, blockchain) + pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Create a number of test accounts and fund them keys := make([]*ecdsa.PrivateKey, 5) @@ -1179,7 +1218,7 @@ func TestPendingGlobalLimiting(t *testing.T) { } } // Import the batch and verify that limits have been enforced - pool.AddRemotesSync(txs) + pool.addRemotesSync(txs) pending := 0 for _, list := range pool.pending { @@ -1201,7 +1240,7 @@ func TestAllowedTxSize(t *testing.T) { // Create a test account and fund it pool, key := setupPool() - defer pool.Stop() + defer pool.Close() account := crypto.PubkeyToAddress(key.PublicKey) testAddBalance(pool, account, big.NewInt(1000000000)) @@ -1210,7 +1249,7 @@ func TestAllowedTxSize(t *testing.T) { // // It is assumed the fields in the transaction (except of the data) are: // - nonce <= 32 bytes - // - gasPrice <= 32 bytes + // - gasTip <= 32 bytes // - gasLimit <= 32 bytes // - recipient == 20 bytes // - value <= 32 bytes @@ -1218,22 +1257,21 @@ func TestAllowedTxSize(t *testing.T) { // All those fields are summed up to at most 213 bytes. baseSize := uint64(213) dataSize := txMaxSize - baseSize - maxGas := pool.currentMaxGas.Load() // Try adding a transaction with maximal allowed size - tx := pricedDataTransaction(0, maxGas, big.NewInt(1), key, dataSize) + tx := pricedDataTransaction(0, pool.currentHead.Load().GasLimit, big.NewInt(1), key, dataSize) if err := pool.addRemoteSync(tx); err != nil { t.Fatalf("failed to add transaction of size %d, close to maximal: %v", int(tx.Size()), err) } // Try adding a transaction with random allowed size - if err := pool.addRemoteSync(pricedDataTransaction(1, maxGas, big.NewInt(1), key, uint64(rand.Intn(int(dataSize))))); err != nil { + if err := pool.addRemoteSync(pricedDataTransaction(1, pool.currentHead.Load().GasLimit, big.NewInt(1), key, uint64(rand.Intn(int(dataSize))))); err != nil { t.Fatalf("failed to add transaction of random allowed size: %v", err) } // Try adding a transaction of minimal not allowed size - if err := pool.addRemoteSync(pricedDataTransaction(2, maxGas, big.NewInt(1), key, txMaxSize)); err == nil { + if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentHead.Load().GasLimit, big.NewInt(1), key, txMaxSize)); err == nil { t.Fatalf("expected rejection on slightly oversize transaction") } // Try adding a transaction of random not allowed size - if err := pool.addRemoteSync(pricedDataTransaction(2, maxGas, big.NewInt(1), key, dataSize+1+uint64(rand.Intn(10*txMaxSize)))); err == nil { + if err := pool.addRemoteSync(pricedDataTransaction(2, pool.currentHead.Load().GasLimit, big.NewInt(1), key, dataSize+1+uint64(rand.Intn(10*txMaxSize)))); err == nil { t.Fatalf("expected rejection on oversize transaction") } // Run some sanity checks on the pool internals @@ -1255,15 +1293,16 @@ func TestCapClearsFromAll(t *testing.T) { // Create the pool to test the limit enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) config := testTxPoolConfig config.AccountSlots = 2 config.AccountQueue = 2 config.GlobalSlots = 8 - pool := NewTxPool(config, params.TestChainConfig, blockchain) - defer pool.Stop() + pool := New(config, blockchain) + pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Create a number of test accounts and fund them key, _ := crypto.GenerateKey() @@ -1275,7 +1314,7 @@ func TestCapClearsFromAll(t *testing.T) { txs = append(txs, transaction(uint64(j), 100000, key)) } // Import the batch and verify that limits have been enforced - pool.AddRemotes(txs) + pool.addRemotes(txs) if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } @@ -1289,13 +1328,14 @@ func TestPendingMinimumAllowance(t *testing.T) { // Create the pool to test the limit enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) config := testTxPoolConfig config.GlobalSlots = 1 - pool := NewTxPool(config, params.TestChainConfig, blockchain) - defer pool.Stop() + pool := New(config, blockchain) + pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Create a number of test accounts and fund them keys := make([]*ecdsa.PrivateKey, 5) @@ -1315,7 +1355,7 @@ func TestPendingMinimumAllowance(t *testing.T) { } } // Import the batch and verify that limits have been enforced - pool.AddRemotesSync(txs) + pool.addRemotesSync(txs) for addr, list := range pool.pending { if list.Len() != int(config.AccountSlots) { @@ -1337,10 +1377,11 @@ func TestRepricing(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) - defer pool.Stop() + pool := New(testTxPoolConfig, blockchain) + pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) @@ -1371,8 +1412,8 @@ func TestRepricing(t *testing.T) { ltx := pricedTransaction(0, 100000, big.NewInt(1), keys[3]) // Import the batch and that both pending and queued transactions match up - pool.AddRemotesSync(txs) - pool.AddLocal(ltx) + pool.addRemotesSync(txs) + pool.addLocal(ltx) pending, queued := pool.Stats() if pending != 7 { @@ -1388,7 +1429,7 @@ func TestRepricing(t *testing.T) { t.Fatalf("pool internal state corrupted: %v", err) } // Reprice the pool and check that underpriced transactions get dropped - pool.SetGasPrice(big.NewInt(2)) + pool.SetGasTip(big.NewInt(2)) pending, queued = pool.Stats() if pending != 2 { @@ -1404,14 +1445,14 @@ func TestRepricing(t *testing.T) { t.Fatalf("pool internal state corrupted: %v", err) } // Check that we can't add the old transactions back - if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(1), keys[0])); err != ErrUnderpriced { - t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(1), keys[0])); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) } - if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); err != ErrUnderpriced { - t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) } - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); err != ErrUnderpriced { - t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), keys[2])); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) } if err := validateEvents(events, 0); err != nil { t.Fatalf("post-reprice event firing failed: %v", err) @@ -1421,7 +1462,7 @@ func TestRepricing(t *testing.T) { } // However we can add local underpriced transactions tx := pricedTransaction(1, 100000, big.NewInt(1), keys[3]) - if err := pool.AddLocal(tx); err != nil { + if err := pool.addLocal(tx); err != nil { t.Fatalf("failed to add underpriced local transaction: %v", err) } if pending, _ = pool.Stats(); pending != 3 { @@ -1434,13 +1475,13 @@ func TestRepricing(t *testing.T) { t.Fatalf("pool internal state corrupted: %v", err) } // And we can fill gaps with properly priced transactions - if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(2), keys[0])); err != nil { + if err := pool.addRemote(pricedTransaction(1, 100000, big.NewInt(2), keys[0])); err != nil { t.Fatalf("failed to add pending transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(2), keys[1])); err != nil { + if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(2), keys[1])); err != nil { t.Fatalf("failed to add pending transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(2), keys[2])); err != nil { + if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(2), keys[2])); err != nil { t.Fatalf("failed to add queued transaction: %v", err) } if err := validateEvents(events, 5); err != nil { @@ -1461,7 +1502,7 @@ func TestRepricingDynamicFee(t *testing.T) { // Create the pool to test the pricing enforcement with pool, _ := setupPoolWithConfig(eip1559Config) - defer pool.Stop() + defer pool.Close() // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) @@ -1492,8 +1533,8 @@ func TestRepricingDynamicFee(t *testing.T) { ltx := dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[3]) // Import the batch and that both pending and queued transactions match up - pool.AddRemotesSync(txs) - pool.AddLocal(ltx) + pool.addRemotesSync(txs) + pool.addLocal(ltx) pending, queued := pool.Stats() if pending != 7 { @@ -1509,7 +1550,7 @@ func TestRepricingDynamicFee(t *testing.T) { t.Fatalf("pool internal state corrupted: %v", err) } // Reprice the pool and check that underpriced transactions get dropped - pool.SetGasPrice(big.NewInt(2)) + pool.SetGasTip(big.NewInt(2)) pending, queued = pool.Stats() if pending != 2 { @@ -1526,16 +1567,16 @@ func TestRepricingDynamicFee(t *testing.T) { } // Check that we can't add the old transactions back tx := pricedTransaction(1, 100000, big.NewInt(1), keys[0]) - if err := pool.AddRemote(tx); err != ErrUnderpriced { - t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) } tx = dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1]) - if err := pool.AddRemote(tx); err != ErrUnderpriced { - t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) } tx = dynamicFeeTx(2, 100000, big.NewInt(1), big.NewInt(1), keys[2]) - if err := pool.AddRemote(tx); err != ErrUnderpriced { - t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced queued transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) } if err := validateEvents(events, 0); err != nil { t.Fatalf("post-reprice event firing failed: %v", err) @@ -1545,7 +1586,7 @@ func TestRepricingDynamicFee(t *testing.T) { } // However we can add local underpriced transactions tx = dynamicFeeTx(1, 100000, big.NewInt(1), big.NewInt(1), keys[3]) - if err := pool.AddLocal(tx); err != nil { + if err := pool.addLocal(tx); err != nil { t.Fatalf("failed to add underpriced local transaction: %v", err) } if pending, _ = pool.Stats(); pending != 3 { @@ -1559,15 +1600,15 @@ func TestRepricingDynamicFee(t *testing.T) { } // And we can fill gaps with properly priced transactions tx = pricedTransaction(1, 100000, big.NewInt(2), keys[0]) - if err := pool.AddRemote(tx); err != nil { + if err := pool.addRemote(tx); err != nil { t.Fatalf("failed to add pending transaction: %v", err) } tx = dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), keys[1]) - if err := pool.AddRemote(tx); err != nil { + if err := pool.addRemote(tx); err != nil { t.Fatalf("failed to add pending transaction: %v", err) } tx = dynamicFeeTx(2, 100000, big.NewInt(2), big.NewInt(2), keys[2]) - if err := pool.AddRemote(tx); err != nil { + if err := pool.addRemoteSync(tx); err != nil { t.Fatalf("failed to add queued transaction: %v", err) } if err := validateEvents(events, 5); err != nil { @@ -1585,10 +1626,11 @@ func TestRepricingKeepsLocals(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, eip1559Config, blockchain) - defer pool.Stop() + pool := New(testTxPoolConfig, blockchain) + pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Create a number of test accounts and fund them keys := make([]*ecdsa.PrivateKey, 3) @@ -1600,23 +1642,23 @@ func TestRepricingKeepsLocals(t *testing.T) { for i := uint64(0); i < 500; i++ { // Add pending transaction. pendingTx := pricedTransaction(i, 100000, big.NewInt(int64(i)), keys[2]) - if err := pool.AddLocal(pendingTx); err != nil { + if err := pool.addLocal(pendingTx); err != nil { t.Fatal(err) } // Add queued transaction. queuedTx := pricedTransaction(i+501, 100000, big.NewInt(int64(i)), keys[2]) - if err := pool.AddLocal(queuedTx); err != nil { + if err := pool.addLocal(queuedTx); err != nil { t.Fatal(err) } // Add pending dynamic fee transaction. pendingTx = dynamicFeeTx(i, 100000, big.NewInt(int64(i)+1), big.NewInt(int64(i)), keys[1]) - if err := pool.AddLocal(pendingTx); err != nil { + if err := pool.addLocal(pendingTx); err != nil { t.Fatal(err) } // Add queued dynamic fee transaction. queuedTx = dynamicFeeTx(i+501, 100000, big.NewInt(int64(i)+1), big.NewInt(int64(i)), keys[1]) - if err := pool.AddLocal(queuedTx); err != nil { + if err := pool.addLocal(queuedTx); err != nil { t.Fatal(err) } } @@ -1638,13 +1680,13 @@ func TestRepricingKeepsLocals(t *testing.T) { validate() // Reprice the pool and check that nothing is dropped - pool.SetGasPrice(big.NewInt(2)) + pool.SetGasTip(big.NewInt(2)) validate() - pool.SetGasPrice(big.NewInt(2)) - pool.SetGasPrice(big.NewInt(4)) - pool.SetGasPrice(big.NewInt(8)) - pool.SetGasPrice(big.NewInt(100)) + pool.SetGasTip(big.NewInt(2)) + pool.SetGasTip(big.NewInt(4)) + pool.SetGasTip(big.NewInt(8)) + pool.SetGasTip(big.NewInt(100)) validate() } @@ -1658,14 +1700,15 @@ func TestUnderpricing(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) config := testTxPoolConfig config.GlobalSlots = 2 config.GlobalQueue = 2 - pool := NewTxPool(config, params.TestChainConfig, blockchain) - defer pool.Stop() + pool := New(config, blockchain) + pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) @@ -1689,8 +1732,8 @@ func TestUnderpricing(t *testing.T) { ltx := pricedTransaction(0, 100000, big.NewInt(1), keys[2]) // Import the batch and that both pending and queued transactions match up - pool.AddRemotes(txs) - pool.AddLocal(ltx) + pool.addRemotes(txs) + pool.addLocal(ltx) pending, queued := pool.Stats() if pending != 3 { @@ -1706,26 +1749,26 @@ func TestUnderpricing(t *testing.T) { t.Fatalf("pool internal state corrupted: %v", err) } // Ensure that adding an underpriced transaction on block limit fails - if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); err != ErrUnderpriced { - t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), keys[1])); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) } // Replace a future transaction with a future transaction - if err := pool.AddRemote(pricedTransaction(1, 100000, big.NewInt(2), keys[1])); err != nil { // +K1:1 => -K1:1 => Pend K0:0, K0:1, K2:0; Que K1:1 + if err := pool.addRemoteSync(pricedTransaction(1, 100000, big.NewInt(2), keys[1])); err != nil { // +K1:1 => -K1:1 => Pend K0:0, K0:1, K2:0; Que K1:1 t.Fatalf("failed to add well priced transaction: %v", err) } // Ensure that adding high priced transactions drops cheap ones, but not own - if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { // +K1:0 => -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que - + if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(3), keys[1])); err != nil { // +K1:0 => -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que - t.Fatalf("failed to add well priced transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(4), keys[1])); err != nil { // +K1:2 => -K0:0 => Pend K1:0, K2:0; Que K0:1 K1:2 + if err := pool.addRemoteSync(pricedTransaction(2, 100000, big.NewInt(4), keys[1])); err != nil { // +K1:2 => -K0:0 => Pend K1:0, K2:0; Que K0:1 K1:2 t.Fatalf("failed to add well priced transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(3, 100000, big.NewInt(5), keys[1])); err != nil { // +K1:3 => -K0:1 => Pend K1:0, K2:0; Que K1:2 K1:3 + if err := pool.addRemote(pricedTransaction(3, 100000, big.NewInt(5), keys[1])); err != nil { // +K1:3 => -K0:1 => Pend K1:0, K2:0; Que K1:2 K1:3 t.Fatalf("failed to add well priced transaction: %v", err) } // Ensure that replacing a pending transaction with a future transaction fails - if err := pool.AddRemote(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); err != ErrFutureReplacePending { - t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, ErrFutureReplacePending) + if err := pool.addRemote(pricedTransaction(5, 100000, big.NewInt(6), keys[1])); err != txpool.ErrFutureReplacePending { + t.Fatalf("adding future replace transaction error mismatch: have %v, want %v", err, txpool.ErrFutureReplacePending) } pending, queued = pool.Stats() if pending != 2 { @@ -1742,11 +1785,11 @@ func TestUnderpricing(t *testing.T) { } // Ensure that adding local transactions can push out even higher priced ones ltx = pricedTransaction(1, 100000, big.NewInt(0), keys[2]) - if err := pool.AddLocal(ltx); err != nil { + if err := pool.addLocal(ltx); err != nil { t.Fatalf("failed to append underpriced local transaction: %v", err) } ltx = pricedTransaction(0, 100000, big.NewInt(0), keys[3]) - if err := pool.AddLocal(ltx); err != nil { + if err := pool.addLocal(ltx); err != nil { t.Fatalf("failed to add new underpriced local transaction: %v", err) } pending, queued = pool.Stats() @@ -1772,14 +1815,15 @@ func TestStableUnderpricing(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) config := testTxPoolConfig config.GlobalSlots = 128 config.GlobalQueue = 0 - pool := NewTxPool(config, params.TestChainConfig, blockchain) - defer pool.Stop() + pool := New(config, blockchain) + pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) @@ -1797,7 +1841,7 @@ func TestStableUnderpricing(t *testing.T) { for i := uint64(0); i < config.GlobalSlots; i++ { txs = append(txs, pricedTransaction(i, 100000, big.NewInt(1), keys[0])) } - pool.AddRemotesSync(txs) + pool.addRemotesSync(txs) pending, queued := pool.Stats() if pending != int(config.GlobalSlots) { @@ -1840,7 +1884,7 @@ func TestUnderpricingDynamicFee(t *testing.T) { t.Parallel() pool, _ := setupPoolWithConfig(eip1559Config) - defer pool.Stop() + defer pool.Close() pool.config.GlobalSlots = 2 pool.config.GlobalQueue = 2 @@ -1867,8 +1911,8 @@ func TestUnderpricingDynamicFee(t *testing.T) { ltx := dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[2]) // Import the batch and that both pending and queued transactions match up - pool.AddRemotes(txs) // Pend K0:0, K0:1; Que K1:1 - pool.AddLocal(ltx) // +K2:0 => Pend K0:0, K0:1, K2:0; Que K1:1 + pool.addRemotes(txs) // Pend K0:0, K0:1; Que K1:1 + pool.addLocal(ltx) // +K2:0 => Pend K0:0, K0:1, K2:0; Que K1:1 pending, queued := pool.Stats() if pending != 3 { @@ -1886,22 +1930,22 @@ func TestUnderpricingDynamicFee(t *testing.T) { // Ensure that adding an underpriced transaction fails tx := dynamicFeeTx(0, 100000, big.NewInt(2), big.NewInt(1), keys[1]) - if err := pool.AddRemote(tx); err != ErrUnderpriced { // Pend K0:0, K0:1, K2:0; Que K1:1 - t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, ErrUnderpriced) + if err := pool.addRemote(tx); !errors.Is(err, txpool.ErrUnderpriced) { // Pend K0:0, K0:1, K2:0; Que K1:1 + t.Fatalf("adding underpriced pending transaction error mismatch: have %v, want %v", err, txpool.ErrUnderpriced) } // Ensure that adding high priced transactions drops cheap ones, but not own tx = pricedTransaction(0, 100000, big.NewInt(2), keys[1]) - if err := pool.AddRemote(tx); err != nil { // +K1:0, -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que - + if err := pool.addRemote(tx); err != nil { // +K1:0, -K1:1 => Pend K0:0, K0:1, K1:0, K2:0; Que - t.Fatalf("failed to add well priced transaction: %v", err) } tx = pricedTransaction(1, 100000, big.NewInt(3), keys[1]) - if err := pool.AddRemote(tx); err != nil { // +K1:2, -K0:1 => Pend K0:0 K1:0, K2:0; Que K1:2 + if err := pool.addRemoteSync(tx); err != nil { // +K1:2, -K0:1 => Pend K0:0 K1:0, K2:0; Que K1:2 t.Fatalf("failed to add well priced transaction: %v", err) } tx = dynamicFeeTx(2, 100000, big.NewInt(4), big.NewInt(1), keys[1]) - if err := pool.AddRemote(tx); err != nil { // +K1:3, -K1:0 => Pend K0:0 K2:0; Que K1:2 K1:3 + if err := pool.addRemoteSync(tx); err != nil { // +K1:3, -K1:0 => Pend K0:0 K2:0; Que K1:2 K1:3 t.Fatalf("failed to add well priced transaction: %v", err) } pending, queued = pool.Stats() @@ -1919,11 +1963,11 @@ func TestUnderpricingDynamicFee(t *testing.T) { } // Ensure that adding local transactions can push out even higher priced ones ltx = dynamicFeeTx(1, 100000, big.NewInt(0), big.NewInt(0), keys[2]) - if err := pool.AddLocal(ltx); err != nil { + if err := pool.addLocal(ltx); err != nil { t.Fatalf("failed to append underpriced local transaction: %v", err) } ltx = dynamicFeeTx(0, 100000, big.NewInt(0), big.NewInt(0), keys[3]) - if err := pool.AddLocal(ltx); err != nil { + if err := pool.addLocal(ltx); err != nil { t.Fatalf("failed to add new underpriced local transaction: %v", err) } pending, queued = pool.Stats() @@ -1947,7 +1991,7 @@ func TestDualHeapEviction(t *testing.T) { t.Parallel() pool, _ := setupPoolWithConfig(eip1559Config) - defer pool.Stop() + defer pool.Close() pool.config.GlobalSlots = 10 pool.config.GlobalQueue = 10 @@ -1976,7 +2020,7 @@ func TestDualHeapEviction(t *testing.T) { tx = dynamicFeeTx(0, 100000, big.NewInt(int64(baseFee+200+i)), big.NewInt(1), key) highCap = tx } - pool.AddRemotesSync([]*types.Transaction{tx}) + pool.addRemotesSync([]*types.Transaction{tx}) } pending, queued := pool.Stats() if pending+queued != 20 { @@ -2004,10 +2048,11 @@ func TestDeduplication(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) - defer pool.Stop() + pool := New(testTxPoolConfig, blockchain) + pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Create a test account to add transactions with key, _ := crypto.GenerateKey() @@ -2022,7 +2067,7 @@ func TestDeduplication(t *testing.T) { for i := 0; i < len(txs); i += 2 { firsts = append(firsts, txs[i]) } - errs := pool.AddRemotesSync(firsts) + errs := pool.addRemotesSync(firsts) if len(errs) != len(firsts) { t.Fatalf("first add mismatching result count: have %d, want %d", len(errs), len(firsts)) } @@ -2039,7 +2084,7 @@ func TestDeduplication(t *testing.T) { t.Fatalf("queued transactions mismatched: have %d, want %d", queued, len(txs)/2-1) } // Try to add all of them now and ensure previous ones error out as knowns - errs = pool.AddRemotesSync(txs) + errs = pool.addRemotesSync(txs) if len(errs) != len(txs) { t.Fatalf("all add mismatching result count: have %d, want %d", len(errs), len(txs)) } @@ -2070,10 +2115,11 @@ func TestReplacement(t *testing.T) { // Create the pool to test the pricing enforcement with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) - defer pool.Stop() + pool := New(testTxPoolConfig, blockchain) + pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Keep track of transaction events to ensure all executables get announced events := make(chan core.NewTxsEvent, 32) @@ -2091,10 +2137,10 @@ func TestReplacement(t *testing.T) { if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), key)); err != nil { t.Fatalf("failed to add original cheap pending transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); err != ErrReplaceUnderpriced { - t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(1), key)); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original cheap pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced) } - if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(2), key)); err != nil { + if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(2), key)); err != nil { t.Fatalf("failed to replace original cheap pending transaction: %v", err) } if err := validateEvents(events, 2); err != nil { @@ -2104,10 +2150,10 @@ func TestReplacement(t *testing.T) { if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(price), key)); err != nil { t.Fatalf("failed to add original proper pending transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); err != ErrReplaceUnderpriced { - t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + if err := pool.addRemote(pricedTransaction(0, 100001, big.NewInt(threshold-1), key)); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original proper pending transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced) } - if err := pool.AddRemote(pricedTransaction(0, 100000, big.NewInt(threshold), key)); err != nil { + if err := pool.addRemote(pricedTransaction(0, 100000, big.NewInt(threshold), key)); err != nil { t.Fatalf("failed to replace original proper pending transaction: %v", err) } if err := validateEvents(events, 2); err != nil { @@ -2115,23 +2161,23 @@ func TestReplacement(t *testing.T) { } // Add queued transactions, ensuring the minimum price bump is enforced for replacement (for ultra low prices too) - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(1), key)); err != nil { + if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(1), key)); err != nil { t.Fatalf("failed to add original cheap queued transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(2, 100001, big.NewInt(1), key)); err != ErrReplaceUnderpriced { - t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(1), key)); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original cheap queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced) } - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(2), key)); err != nil { + if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(2), key)); err != nil { t.Fatalf("failed to replace original cheap queued transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(price), key)); err != nil { + if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(price), key)); err != nil { t.Fatalf("failed to add original proper queued transaction: %v", err) } - if err := pool.AddRemote(pricedTransaction(2, 100001, big.NewInt(threshold-1), key)); err != ErrReplaceUnderpriced { - t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, ErrReplaceUnderpriced) + if err := pool.addRemote(pricedTransaction(2, 100001, big.NewInt(threshold-1), key)); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original proper queued transaction replacement error mismatch: have %v, want %v", err, txpool.ErrReplaceUnderpriced) } - if err := pool.AddRemote(pricedTransaction(2, 100000, big.NewInt(threshold), key)); err != nil { + if err := pool.addRemote(pricedTransaction(2, 100000, big.NewInt(threshold), key)); err != nil { t.Fatalf("failed to replace original proper queued transaction: %v", err) } @@ -2150,7 +2196,7 @@ func TestReplacementDynamicFee(t *testing.T) { // Create the pool to test the pricing enforcement with pool, key := setupPoolWithConfig(eip1559Config) - defer pool.Stop() + defer pool.Close() testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000)) // Keep track of transaction events to ensure all executables get announced @@ -2192,12 +2238,12 @@ func TestReplacementDynamicFee(t *testing.T) { } // 2. Don't bump tip or feecap => discard tx = dynamicFeeTx(nonce, 100001, big.NewInt(2), big.NewInt(1), key) - if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced { - t.Fatalf("original cheap %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced) + if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original cheap %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced) } // 3. Bump both more than min => accept tx = dynamicFeeTx(nonce, 100000, big.NewInt(3), big.NewInt(2), key) - if err := pool.AddRemote(tx); err != nil { + if err := pool.addRemote(tx); err != nil { t.Fatalf("failed to replace original cheap %s transaction: %v", stage, err) } // 4. Check events match expected (2 new executable txs during pending, 0 during queue) @@ -2215,27 +2261,27 @@ func TestReplacementDynamicFee(t *testing.T) { } // 6. Bump tip max allowed so it's still underpriced => discard tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold-1), key) - if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced { - t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced) + if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced) } // 7. Bump fee cap max allowed so it's still underpriced => discard tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold-1), big.NewInt(gasTipCap), key) - if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced { - t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced) + if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced) } // 8. Bump tip min for acceptance => accept tx = dynamicFeeTx(nonce, 100000, big.NewInt(gasFeeCap), big.NewInt(tipThreshold), key) - if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced { - t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced) + if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced) } // 9. Bump fee cap min for acceptance => accept tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold), big.NewInt(gasTipCap), key) - if err := pool.AddRemote(tx); err != ErrReplaceUnderpriced { - t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, ErrReplaceUnderpriced) + if err := pool.addRemote(tx); err != txpool.ErrReplaceUnderpriced { + t.Fatalf("original proper %s transaction replacement error mismatch: have %v, want %v", stage, err, txpool.ErrReplaceUnderpriced) } // 10. Check events match expected (3 new executable txs during pending, 0 during queue) tx = dynamicFeeTx(nonce, 100000, big.NewInt(feeCapThreshold), big.NewInt(tipThreshold), key) - if err := pool.AddRemote(tx); err != nil { + if err := pool.addRemote(tx); err != nil { t.Fatalf("failed to replace original cheap %s transaction: %v", stage, err) } // 11. Check events match expected (3 new executable txs during pending, 0 during queue) @@ -2275,14 +2321,15 @@ func testJournaling(t *testing.T, nolocals bool) { // Create the original pool to inject transaction into the journal statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) config := testTxPoolConfig config.NoLocals = nolocals config.Journal = journal config.Rejournal = time.Second - pool := NewTxPool(config, params.TestChainConfig, blockchain) + pool := New(config, blockchain) + pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) // Create two test accounts to ensure remotes expire but locals do not local, _ := crypto.GenerateKey() @@ -2292,13 +2339,13 @@ func testJournaling(t *testing.T, nolocals bool) { testAddBalance(pool, crypto.PubkeyToAddress(remote.PublicKey), big.NewInt(1000000000)) // Add three local and a remote transactions and ensure they are queued up - if err := pool.AddLocal(pricedTransaction(0, 100000, big.NewInt(1), local)); err != nil { + if err := pool.addLocal(pricedTransaction(0, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add local transaction: %v", err) } - if err := pool.AddLocal(pricedTransaction(1, 100000, big.NewInt(1), local)); err != nil { + if err := pool.addLocal(pricedTransaction(1, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add local transaction: %v", err) } - if err := pool.AddLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil { + if err := pool.addLocal(pricedTransaction(2, 100000, big.NewInt(1), local)); err != nil { t.Fatalf("failed to add local transaction: %v", err) } if err := pool.addRemoteSync(pricedTransaction(0, 100000, big.NewInt(1), remote)); err != nil { @@ -2315,11 +2362,12 @@ func testJournaling(t *testing.T, nolocals bool) { t.Fatalf("pool internal state corrupted: %v", err) } // Terminate the old pool, bump the local nonce, create a new pool and ensure relevant transaction survive - pool.Stop() + pool.Close() statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) - blockchain = newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain = newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) - pool = NewTxPool(config, params.TestChainConfig, blockchain) + pool = New(config, blockchain) + pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) pending, queued = pool.Stats() if queued != 0 { @@ -2341,11 +2389,12 @@ func testJournaling(t *testing.T, nolocals bool) { statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 2) <-pool.requestReset(nil, nil) time.Sleep(2 * config.Rejournal) - pool.Stop() + pool.Close() statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1) - blockchain = newTestBlockChain(1000000, statedb, new(event.Feed)) - pool = NewTxPool(config, params.TestChainConfig, blockchain) + blockchain = newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) + pool = New(config, blockchain) + pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) pending, queued = pool.Stats() if pending != 0 { @@ -2363,7 +2412,7 @@ func testJournaling(t *testing.T, nolocals bool) { if err := validatePoolInternals(pool); err != nil { t.Fatalf("pool internal state corrupted: %v", err) } - pool.Stop() + pool.Close() } // TestStatusCheck tests that the pool can correctly retrieve the @@ -2373,10 +2422,11 @@ func TestStatusCheck(t *testing.T) { // Create the pool to test the status retrievals with statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - blockchain := newTestBlockChain(1000000, statedb, new(event.Feed)) + blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed)) - pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) - defer pool.Stop() + pool := New(testTxPoolConfig, blockchain) + pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() // Create the test accounts to check various transaction statuses with keys := make([]*ecdsa.PrivateKey, 3) @@ -2393,7 +2443,7 @@ func TestStatusCheck(t *testing.T) { txs = append(txs, pricedTransaction(2, 100000, big.NewInt(1), keys[2])) // Queued only // Import the transaction and ensure they are correctly added - pool.AddRemotesSync(txs) + pool.addRemotesSync(txs) pending, queued := pool.Stats() if pending != 2 { @@ -2411,13 +2461,11 @@ func TestStatusCheck(t *testing.T) { hashes[i] = tx.Hash() } hashes = append(hashes, common.Hash{}) + expect := []txpool.TxStatus{txpool.TxStatusPending, txpool.TxStatusPending, txpool.TxStatusQueued, txpool.TxStatusQueued, txpool.TxStatusUnknown} - statuses := pool.Status(hashes) - expect := []TxStatus{TxStatusPending, TxStatusPending, TxStatusQueued, TxStatusQueued, TxStatusUnknown} - - for i := 0; i < len(statuses); i++ { - if statuses[i] != expect[i] { - t.Errorf("transaction %d: status mismatch: have %v, want %v", i, statuses[i], expect[i]) + for i := 0; i < len(hashes); i++ { + if status := pool.Status(hashes[i]); status != expect[i] { + t.Errorf("transaction %d: status mismatch: have %v, want %v", i, status, expect[i]) } } } @@ -2449,7 +2497,7 @@ func BenchmarkPendingDemotion10000(b *testing.B) { benchmarkPendingDemotion(b, 1 func benchmarkPendingDemotion(b *testing.B, size int) { // Add a batch of transactions to a pool one by one pool, key := setupPool() - defer pool.Stop() + defer pool.Close() account := crypto.PubkeyToAddress(key.PublicKey) testAddBalance(pool, account, big.NewInt(1000000)) @@ -2474,7 +2522,7 @@ func BenchmarkFuturePromotion10000(b *testing.B) { benchmarkFuturePromotion(b, 1 func benchmarkFuturePromotion(b *testing.B, size int) { // Add a batch of transactions to a pool one by one pool, key := setupPool() - defer pool.Stop() + defer pool.Close() account := crypto.PubkeyToAddress(key.PublicKey) testAddBalance(pool, account, big.NewInt(1000000)) @@ -2502,7 +2550,7 @@ func BenchmarkBatchLocalInsert10000(b *testing.B) { benchmarkBatchInsert(b, 1000 func benchmarkBatchInsert(b *testing.B, size int, local bool) { // Generate a batch of transactions to enqueue into the pool pool, key := setupPool() - defer pool.Stop() + defer pool.Close() account := crypto.PubkeyToAddress(key.PublicKey) testAddBalance(pool, account, big.NewInt(1000000000000000000)) @@ -2518,9 +2566,9 @@ func benchmarkBatchInsert(b *testing.B, size int, local bool) { b.ResetTimer() for _, batch := range batches { if local { - pool.AddLocals(batch) + pool.addLocals(batch) } else { - pool.AddRemotes(batch) + pool.addRemotes(batch) } } } @@ -2548,15 +2596,15 @@ func BenchmarkInsertRemoteWithAllLocals(b *testing.B) { pool, _ := setupPool() testAddBalance(pool, account, big.NewInt(100000000)) for _, local := range locals { - pool.AddLocal(local) + pool.addLocal(local) } b.StartTimer() // Assign a high enough balance for testing testAddBalance(pool, remoteAddr, big.NewInt(100000000)) for i := 0; i < len(remotes); i++ { - pool.AddRemotes([]*types.Transaction{remotes[i]}) + pool.addRemotes([]*types.Transaction{remotes[i]}) } - pool.Stop() + pool.Close() } } @@ -2564,7 +2612,7 @@ func BenchmarkInsertRemoteWithAllLocals(b *testing.B) { func BenchmarkMultiAccountBatchInsert(b *testing.B) { // Generate a batch of transactions to enqueue into the pool pool, _ := setupPool() - defer pool.Stop() + defer pool.Close() b.ReportAllocs() batches := make(types.Transactions, b.N) for i := 0; i < b.N; i++ { @@ -2577,6 +2625,6 @@ func BenchmarkMultiAccountBatchInsert(b *testing.B) { // Benchmark importing the transactions into the queue b.ResetTimer() for _, tx := range batches { - pool.AddRemotesSync([]*types.Transaction{tx}) + pool.addRemotesSync([]*types.Transaction{tx}) } } diff --git a/core/txpool/list.go b/core/txpool/legacypool/list.go similarity index 96% rename from core/txpool/list.go rename to core/txpool/legacypool/list.go index fae7c2fcac2d..05ae0b58cd59 100644 --- a/core/txpool/list.go +++ b/core/txpool/legacypool/list.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package txpool +package legacypool import ( "container/heap" @@ -53,9 +53,10 @@ func (h *nonceHeap) Pop() interface{} { // sortedMap is a nonce->transaction hash map with a heap based index to allow // iterating over the contents in a nonce-incrementing way. type sortedMap struct { - items map[uint64]*types.Transaction // Hash map storing the transaction data - index *nonceHeap // Heap of nonces of all the stored transactions (non-strict mode) - cache types.Transactions // Cache of the transactions already sorted + items map[uint64]*types.Transaction // Hash map storing the transaction data + index *nonceHeap // Heap of nonces of all the stored transactions (non-strict mode) + cache types.Transactions // Cache of the transactions already sorted + cacheMu sync.Mutex // Mutex covering the cache } // newSortedMap creates a new nonce-sorted transaction map. @@ -78,7 +79,9 @@ func (m *sortedMap) Put(tx *types.Transaction) { if m.items[nonce] == nil { heap.Push(m.index, nonce) } + m.cacheMu.Lock() m.items[nonce], m.cache = tx, nil + m.cacheMu.Unlock() } // Forward removes all transactions from the map with a nonce lower than the @@ -94,9 +97,11 @@ func (m *sortedMap) Forward(threshold uint64) types.Transactions { delete(m.items, nonce) } // If we had a cached order, shift the front + m.cacheMu.Lock() if m.cache != nil { m.cache = m.cache[len(removed):] } + m.cacheMu.Unlock() return removed } @@ -120,7 +125,9 @@ func (m *sortedMap) reheap() { *m.index = append(*m.index, nonce) } heap.Init(m.index) + m.cacheMu.Lock() m.cache = nil + m.cacheMu.Unlock() } // filter is identical to Filter, but **does not** regenerate the heap. This method @@ -136,7 +143,9 @@ func (m *sortedMap) filter(filter func(*types.Transaction) bool) types.Transacti } } if len(removed) > 0 { + m.cacheMu.Lock() m.cache = nil + m.cacheMu.Unlock() } return removed } @@ -160,9 +169,11 @@ func (m *sortedMap) Cap(threshold int) types.Transactions { heap.Init(m.index) // If we had a cache, shift the back + m.cacheMu.Lock() if m.cache != nil { m.cache = m.cache[:len(m.cache)-len(drops)] } + m.cacheMu.Unlock() return drops } @@ -182,7 +193,9 @@ func (m *sortedMap) Remove(nonce uint64) bool { } } delete(m.items, nonce) + m.cacheMu.Lock() m.cache = nil + m.cacheMu.Unlock() return true } @@ -192,7 +205,7 @@ func (m *sortedMap) Remove(nonce uint64) bool { // removed from the list. // // Note, all transactions with nonces lower than start will also be returned to -// prevent getting into and invalid state. This is not something that should ever +// prevent getting into an invalid state. This is not something that should ever // happen but better to be self correcting than failing! func (m *sortedMap) Ready(start uint64) types.Transactions { // Short circuit if no transactions are available @@ -206,7 +219,9 @@ func (m *sortedMap) Ready(start uint64) types.Transactions { delete(m.items, next) heap.Pop(m.index) } + m.cacheMu.Lock() m.cache = nil + m.cacheMu.Unlock() return ready } @@ -217,6 +232,8 @@ func (m *sortedMap) Len() int { } func (m *sortedMap) flatten() types.Transactions { + m.cacheMu.Lock() + defer m.cacheMu.Unlock() // If the sorting was not cached yet, create and cache it if m.cache == nil { m.cache = make(types.Transactions, 0, len(m.items)) @@ -232,8 +249,8 @@ func (m *sortedMap) flatten() types.Transactions { // sorted internal representation. The result of the sorting is cached in case // it's requested again before any modifications are made to the contents. func (m *sortedMap) Flatten() types.Transactions { - // Copy the cache to prevent accidental modifications cache := m.flatten() + // Copy the cache to prevent accidental modification txs := make(types.Transactions, len(cache)) copy(txs, cache) return txs @@ -404,7 +421,7 @@ func (l *list) Remove(tx *types.Transaction) (bool, types.Transactions) { // removed from the list. // // Note, all transactions with nonces lower than start will also be returned to -// prevent getting into and invalid state. This is not something that should ever +// prevent getting into an invalid state. This is not something that should ever // happen but better to be self correcting than failing! func (l *list) Ready(start uint64) types.Transactions { txs := l.txs.Ready(start) @@ -590,7 +607,7 @@ func (l *pricedList) underpricedFor(h *priceHeap, tx *types.Transaction) bool { func (l *pricedList) Discard(slots int, force bool) (types.Transactions, bool) { drop := make(types.Transactions, 0, slots) // Remote underpriced transactions to drop for slots > 0 { - if len(l.urgent.list)*floatingRatio > len(l.floating.list)*urgentRatio || floatingRatio == 0 { + if len(l.urgent.list)*floatingRatio > len(l.floating.list)*urgentRatio { // Discard stale transactions if found during cleanup tx := heap.Pop(&l.urgent).(*types.Transaction) if l.all.GetRemote(tx.Hash()) == nil { // Removed or migrated diff --git a/core/txpool/list_test.go b/core/txpool/legacypool/list_test.go similarity index 99% rename from core/txpool/list_test.go rename to core/txpool/legacypool/list_test.go index 4e1a5d5e832a..b5cd34b23b62 100644 --- a/core/txpool/list_test.go +++ b/core/txpool/legacypool/list_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package txpool +package legacypool import ( "math/big" diff --git a/core/txpool/noncer.go b/core/txpool/legacypool/noncer.go similarity index 99% rename from core/txpool/noncer.go rename to core/txpool/legacypool/noncer.go index ba7fbedad568..2c65dd2caea7 100644 --- a/core/txpool/noncer.go +++ b/core/txpool/legacypool/noncer.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package txpool +package legacypool import ( "sync" diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go new file mode 100644 index 000000000000..de05b38d433d --- /dev/null +++ b/core/txpool/subpool.go @@ -0,0 +1,140 @@ +// Copyright 2023 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 txpool + +import ( + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// LazyTransaction contains a small subset of the transaction properties that is +// enough for the miner and other APIs to handle large batches of transactions; +// and supports pulling up the entire transaction when really needed. +type LazyTransaction struct { + Pool LazyResolver // Transaction resolver to pull the real transaction up + Hash common.Hash // Transaction hash to pull up if needed + Tx *types.Transaction // Transaction if already resolved + + Time time.Time // Time when the transaction was first seen + GasFeeCap *big.Int // Maximum fee per gas the transaction may consume + GasTipCap *big.Int // Maximum miner tip per gas the transaction can pay + + Gas uint64 // Amount of gas required by the transaction + BlobGas uint64 // Amount of blob gas required by the transaction +} + +// Resolve retrieves the full transaction belonging to a lazy handle if it is still +// maintained by the transaction pool. +func (ltx *LazyTransaction) Resolve() *types.Transaction { + if ltx.Tx == nil { + ltx.Tx = ltx.Pool.Get(ltx.Hash) + } + return ltx.Tx +} + +// LazyResolver is a minimal interface needed for a transaction pool to satisfy +// resolving lazy transactions. It's mostly a helper to avoid the entire sub- +// pool being injected into the lazy transaction. +type LazyResolver interface { + // Get returns a transaction if it is contained in the pool, or nil otherwise. + Get(hash common.Hash) *types.Transaction +} + +// AddressReserver is passed by the main transaction pool to subpools, so they +// may request (and relinquish) exclusive access to certain addresses. +type AddressReserver func(addr common.Address, reserve bool) error + +// SubPool represents a specialized transaction pool that lives on its own (e.g. +// blob pool). Since independent of how many specialized pools we have, they do +// need to be updated in lockstep and assemble into one coherent view for block +// production, this interface defines the common methods that allow the primary +// transaction pool to manage the subpools. +type SubPool interface { + // Filter is a selector used to decide whether a transaction whould be added + // to this particular subpool. + Filter(tx *types.Transaction) bool + + // Init sets the base parameters of the subpool, allowing it to load any saved + // transactions from disk and also permitting internal maintenance routines to + // start up. + // + // These should not be passed as a constructor argument - nor should the pools + // start by themselves - in order to keep multiple subpools in lockstep with + // one another. + Init(gasTip *big.Int, head *types.Header, reserve AddressReserver) error + + // Close terminates any background processing threads and releases any held + // resources. + Close() error + + // Reset retrieves the current state of the blockchain and ensures the content + // of the transaction pool is valid with regard to the chain state. + Reset(oldHead, newHead *types.Header) + + // SetGasTip updates the minimum price required by the subpool for a new + // transaction, and drops all transactions below this threshold. + SetGasTip(tip *big.Int) + + // Has returns an indicator whether subpool has a transaction cached with the + // given hash. + Has(hash common.Hash) bool + + // Get returns a transaction if it is contained in the pool, or nil otherwise. + Get(hash common.Hash) *types.Transaction + + // Add enqueues a batch of transactions into the pool if they are valid. Due + // to the large transaction churn, add may postpone fully integrating the tx + // to a later point to batch multiple ones together. + Add(txs []*types.Transaction, local bool, sync bool) []error + + // Pending retrieves all currently processable transactions, grouped by origin + // account and sorted by nonce. + Pending(enforceTips bool) map[common.Address][]*LazyTransaction + + // SubscribeTransactions subscribes to new transaction events. The subscriber + // can decide whether to receive notifications only for newly seen transactions + // or also for reorged out ones. + SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription + + // Nonce returns the next nonce of an account, with all transactions executable + // by the pool already applied on top. + Nonce(addr common.Address) uint64 + + // Stats retrieves the current pool stats, namely the number of pending and the + // number of queued (non-executable) transactions. + Stats() (int, int) + + // Content retrieves the data content of the transaction pool, returning all the + // pending as well as queued transactions, grouped by account and sorted by nonce. + Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) + + // ContentFrom retrieves the data content of the transaction pool, returning the + // pending as well as queued transactions of this address, grouped by nonce. + ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) + + // Locals retrieves the accounts currently considered local by the pool. + Locals() []common.Address + + // Status returns the known status (unknown/pending/queued) of a transaction + // identified by their hashes. + Status(hash common.Hash) TxStatus +} diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index 745568d437fe..0d4e05da4c18 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -1,4 +1,4 @@ -// Copyright 2014 The go-ethereum Authors +// Copyright 2023 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 @@ -19,125 +19,15 @@ package txpool import ( "errors" "fmt" - "math" "math/big" - "sort" "sync" - "sync/atomic" - "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/prque" - "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/params" -) - -const ( - // chainHeadChanSize is the size of channel listening to ChainHeadEvent. - chainHeadChanSize = 10 - - // txSlotSize is used to calculate how many data slots a single transaction - // takes up based on its size. The slots are used as DoS protection, ensuring - // that validating a new transaction remains a constant operation (in reality - // O(maxslots), where max slots are 4 currently). - txSlotSize = 32 * 1024 - - // txMaxSize is the maximum size a single transaction can have. This field has - // non-trivial consequences: larger transactions are significantly harder and - // more expensive to propagate; larger transactions also take more resources - // to validate whether they fit into the pool or not. - txMaxSize = 4 * txSlotSize // 128KB -) - -var ( - // ErrAlreadyKnown is returned if the transactions is already contained - // within the pool. - ErrAlreadyKnown = errors.New("already known") - - // ErrInvalidSender is returned if the transaction contains an invalid signature. - ErrInvalidSender = errors.New("invalid sender") - - // ErrUnderpriced is returned if a transaction's gas price is below the minimum - // configured for the transaction pool. - ErrUnderpriced = errors.New("transaction underpriced") - - // ErrTxPoolOverflow is returned if the transaction pool is full and can't accept - // another remote transaction. - ErrTxPoolOverflow = errors.New("txpool is full") - - // ErrReplaceUnderpriced is returned if a transaction is attempted to be replaced - // with a different one without the required price bump. - ErrReplaceUnderpriced = errors.New("replacement transaction underpriced") - - // ErrGasLimit is returned if a transaction's requested gas limit exceeds the - // maximum allowance of the current block. - ErrGasLimit = errors.New("exceeds block gas limit") - - // ErrNegativeValue is a sanity error to ensure no one is able to specify a - // transaction with a negative value. - ErrNegativeValue = errors.New("negative value") - - // ErrOversizedData is returned if the input data of a transaction is greater - // than some meaningful limit a user might use. This is not a consensus error - // making the transaction invalid, rather a DOS protection. - ErrOversizedData = errors.New("oversized data") - - // ErrFutureReplacePending is returned if a future transaction replaces a pending - // transaction. Future transactions should only be able to replace other future transactions. - ErrFutureReplacePending = errors.New("future transaction tries to replace pending") - - // ErrOverdraft is returned if a transaction would cause the senders balance to go negative - // thus invalidating a potential large number of transactions. - ErrOverdraft = errors.New("transaction would cause overdraft") -) - -var ( - evictionInterval = time.Minute // Time interval to check for evictable transactions - statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats -) - -var ( - // Metrics for the pending pool - pendingDiscardMeter = metrics.NewRegisteredMeter("txpool/pending/discard", nil) - pendingReplaceMeter = metrics.NewRegisteredMeter("txpool/pending/replace", nil) - pendingRateLimitMeter = metrics.NewRegisteredMeter("txpool/pending/ratelimit", nil) // Dropped due to rate limiting - pendingNofundsMeter = metrics.NewRegisteredMeter("txpool/pending/nofunds", nil) // Dropped due to out-of-funds - - // Metrics for the queued pool - queuedDiscardMeter = metrics.NewRegisteredMeter("txpool/queued/discard", nil) - queuedReplaceMeter = metrics.NewRegisteredMeter("txpool/queued/replace", nil) - queuedRateLimitMeter = metrics.NewRegisteredMeter("txpool/queued/ratelimit", nil) // Dropped due to rate limiting - queuedNofundsMeter = metrics.NewRegisteredMeter("txpool/queued/nofunds", nil) // Dropped due to out-of-funds - queuedEvictionMeter = metrics.NewRegisteredMeter("txpool/queued/eviction", nil) // Dropped due to lifetime - - // General tx metrics - knownTxMeter = metrics.NewRegisteredMeter("txpool/known", nil) - validTxMeter = metrics.NewRegisteredMeter("txpool/valid", nil) - invalidTxMeter = metrics.NewRegisteredMeter("txpool/invalid", nil) - underpricedTxMeter = metrics.NewRegisteredMeter("txpool/underpriced", nil) - overflowedTxMeter = metrics.NewRegisteredMeter("txpool/overflowed", nil) - - // throttleTxMeter counts how many transactions are rejected due to too-many-changes between - // txpool reorgs. - throttleTxMeter = metrics.NewRegisteredMeter("txpool/throttle", nil) - // reorgDurationTimer measures how long time a txpool reorg takes. - reorgDurationTimer = metrics.NewRegisteredTimer("txpool/reorgtime", nil) - // dropBetweenReorgHistogram counts how many drops we experience between two reorg runs. It is expected - // that this number is pretty low, since txpool reorgs happen very frequently. - dropBetweenReorgHistogram = metrics.NewRegisteredHistogram("txpool/dropbetweenreorg", nil, metrics.NewExpDecaySample(1028, 0.015)) - - pendingGauge = metrics.NewRegisteredGauge("txpool/pending", nil) - queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) - localGauge = metrics.NewRegisteredGauge("txpool/local", nil) - slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil) - - reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil) ) // TxStatus is the current status of a transaction as seen by the pool. @@ -150,1768 +40,378 @@ const ( TxStatusIncluded ) -// blockChain provides the state of blockchain and current gas limit to do -// some pre checks in tx pool and event subscribers. -type blockChain interface { +var ( + // reservationsGaugeName is the prefix of a per-subpool address reservation + // metric. + // + // This is mostly a sanity metric to ensure there's no bug that would make + // some subpool hog all the reservations due to mis-accounting. + reservationsGaugeName = "txpool/reservations" +) + +// BlockChain defines the minimal set of methods needed to back a tx pool with +// a chain. Exists to allow mocking the live chain out of tests. +type BlockChain interface { + // CurrentBlock returns the current head of the chain. CurrentBlock() *types.Header - GetBlock(hash common.Hash, number uint64) *types.Block - StateAt(root common.Hash) (*state.StateDB, error) + // SubscribeChainHeadEvent subscribes to new blocks being added to the chain. SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription } -// Config are the configuration parameters of the transaction pool. -type Config struct { - Locals []common.Address // Addresses that should be treated by default as local - NoLocals bool // Whether local transaction handling should be disabled - Journal string // Journal of local transactions to survive node restarts - Rejournal time.Duration // Time interval to regenerate the local transaction journal - - PriceLimit uint64 // Minimum gas price to enforce for acceptance into the pool - PriceBump uint64 // Minimum price bump percentage to replace an already existing transaction (nonce) - - AccountSlots uint64 // Number of executable transaction slots guaranteed per account - GlobalSlots uint64 // Maximum number of executable transaction slots for all accounts - AccountQueue uint64 // Maximum number of non-executable transaction slots permitted per account - GlobalQueue uint64 // Maximum number of non-executable transaction slots for all accounts - - Lifetime time.Duration // Maximum amount of time non-executable transaction are queued -} - -// DefaultConfig contains the default configurations for the transaction -// pool. -var DefaultConfig = Config{ - Journal: "transactions.rlp", - Rejournal: time.Hour, - - PriceLimit: 1, - PriceBump: 10, - - AccountSlots: 16, - GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio - AccountQueue: 64, - GlobalQueue: 1024, - - Lifetime: 3 * time.Hour, -} - -// sanitize checks the provided user configurations and changes anything that's -// unreasonable or unworkable. -func (config *Config) sanitize() Config { - conf := *config - if conf.Rejournal < time.Second { - log.Warn("Sanitizing invalid txpool journal time", "provided", conf.Rejournal, "updated", time.Second) - conf.Rejournal = time.Second - } - if conf.PriceLimit < 1 { - log.Warn("Sanitizing invalid txpool price limit", "provided", conf.PriceLimit, "updated", DefaultConfig.PriceLimit) - conf.PriceLimit = DefaultConfig.PriceLimit - } - if conf.PriceBump < 1 { - log.Warn("Sanitizing invalid txpool price bump", "provided", conf.PriceBump, "updated", DefaultConfig.PriceBump) - conf.PriceBump = DefaultConfig.PriceBump - } - if conf.AccountSlots < 1 { - log.Warn("Sanitizing invalid txpool account slots", "provided", conf.AccountSlots, "updated", DefaultConfig.AccountSlots) - conf.AccountSlots = DefaultConfig.AccountSlots - } - if conf.GlobalSlots < 1 { - log.Warn("Sanitizing invalid txpool global slots", "provided", conf.GlobalSlots, "updated", DefaultConfig.GlobalSlots) - conf.GlobalSlots = DefaultConfig.GlobalSlots - } - if conf.AccountQueue < 1 { - log.Warn("Sanitizing invalid txpool account queue", "provided", conf.AccountQueue, "updated", DefaultConfig.AccountQueue) - conf.AccountQueue = DefaultConfig.AccountQueue - } - if conf.GlobalQueue < 1 { - log.Warn("Sanitizing invalid txpool global queue", "provided", conf.GlobalQueue, "updated", DefaultConfig.GlobalQueue) - conf.GlobalQueue = DefaultConfig.GlobalQueue - } - if conf.Lifetime < 1 { - log.Warn("Sanitizing invalid txpool lifetime", "provided", conf.Lifetime, "updated", DefaultConfig.Lifetime) - conf.Lifetime = DefaultConfig.Lifetime - } - return conf -} - -// TxPool contains all currently known transactions. Transactions -// enter the pool when they are received from the network or submitted -// locally. They exit the pool when they are included in the blockchain. -// -// The pool separates processable transactions (which can be applied to the -// current state) and future transactions. Transactions move between those -// two states over time as they are received and processed. +// TxPool is an aggregator for various transaction specific pools, collectively +// tracking all the transactions deemed interesting by the node. Transactions +// enter the pool when they are received from the network or submitted locally. +// They exit the pool when they are included in the blockchain or evicted due to +// resource constraints. type TxPool struct { - config Config - chainconfig *params.ChainConfig - chain blockChain - gasPrice *big.Int - txFeed event.Feed - scope event.SubscriptionScope - signer types.Signer - mu sync.RWMutex - - istanbul atomic.Bool // Fork indicator whether we are in the istanbul stage. - eip2718 atomic.Bool // Fork indicator whether we are using EIP-2718 type transactions. - eip1559 atomic.Bool // Fork indicator whether we are using EIP-1559 type transactions. - shanghai atomic.Bool // Fork indicator whether we are in the Shanghai stage. + subpools []SubPool // List of subpools for specialized transaction handling - currentState *state.StateDB // Current state in the blockchain head - pendingNonces *noncer // Pending state tracking virtual nonces - currentMaxGas atomic.Uint64 // Current gas limit for transaction caps + reservations map[common.Address]SubPool // Map with the account to pool reservations + reserveLock sync.Mutex // Lock protecting the account reservations - locals *accountSet // Set of local transaction to exempt from eviction rules - journal *journal // Journal of local transaction to back up to disk - - pending map[common.Address]*list // All currently processable transactions - queue map[common.Address]*list // Queued but non-processable transactions - beats map[common.Address]time.Time // Last heartbeat from each known account - all *lookup // All transactions to allow lookups - priced *pricedList // All transactions sorted by price - - chainHeadCh chan core.ChainHeadEvent - chainHeadSub event.Subscription - reqResetCh chan *txpoolResetRequest - reqPromoteCh chan *accountSet - queueTxEventCh chan *types.Transaction - reorgDoneCh chan chan struct{} - reorgShutdownCh chan struct{} // requests shutdown of scheduleReorgLoop - wg sync.WaitGroup // tracks loop, scheduleReorgLoop - initDoneCh chan struct{} // is closed once the pool is initialized (for tests) - - changesSinceReorg int // A counter for how many drops we've performed in-between reorg. + subs event.SubscriptionScope // Subscription scope to unsubscribe all on shutdown + quit chan chan error // Quit channel to tear down the head updater } -type txpoolResetRequest struct { - oldHead, newHead *types.Header -} - -// NewTxPool creates a new transaction pool to gather, sort and filter inbound +// New creates a new transaction pool to gather, sort and filter inbound // transactions from the network. -func NewTxPool(config Config, chainconfig *params.ChainConfig, chain blockChain) *TxPool { - // Sanitize the input to ensure no vulnerable gas prices are set - config = (&config).sanitize() +func New(gasTip *big.Int, chain BlockChain, subpools []SubPool) (*TxPool, error) { + // Retrieve the current head so that all subpools and this main coordinator + // pool will have the same starting state, even if the chain moves forward + // during initialization. + head := chain.CurrentBlock() - // Create the transaction pool with its initial settings pool := &TxPool{ - config: config, - chainconfig: chainconfig, - chain: chain, - signer: types.LatestSigner(chainconfig), - pending: make(map[common.Address]*list), - queue: make(map[common.Address]*list), - beats: make(map[common.Address]time.Time), - all: newLookup(), - chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), - reqResetCh: make(chan *txpoolResetRequest), - reqPromoteCh: make(chan *accountSet), - queueTxEventCh: make(chan *types.Transaction), - reorgDoneCh: make(chan chan struct{}), - reorgShutdownCh: make(chan struct{}), - initDoneCh: make(chan struct{}), - gasPrice: new(big.Int).SetUint64(config.PriceLimit), - } - pool.locals = newAccountSet(pool.signer) - for _, addr := range config.Locals { - log.Info("Setting new local account", "address", addr) - pool.locals.add(addr) - } - pool.priced = newPricedList(pool.all) - pool.reset(nil, chain.CurrentBlock()) - - // Start the reorg loop early so it can handle requests generated during journal loading. - pool.wg.Add(1) - go pool.scheduleReorgLoop() - - // If local transactions and journaling is enabled, load from disk - if !config.NoLocals && config.Journal != "" { - pool.journal = newTxJournal(config.Journal) - - if err := pool.journal.load(pool.AddLocals); err != nil { - log.Warn("Failed to load transaction journal", "err", err) - } - if err := pool.journal.rotate(pool.local()); err != nil { - log.Warn("Failed to rotate transaction journal", "err", err) - } - } - - // Subscribe events from blockchain and start the main event loop. - pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh) - pool.wg.Add(1) - go pool.loop() - - return pool -} - -// loop is the transaction pool's main event loop, waiting for and reacting to -// outside blockchain events as well as for various reporting and transaction -// eviction events. -func (pool *TxPool) loop() { - defer pool.wg.Done() - - var ( - prevPending, prevQueued, prevStales int - // Start the stats reporting and transaction eviction tickers - report = time.NewTicker(statsReportInterval) - evict = time.NewTicker(evictionInterval) - journal = time.NewTicker(pool.config.Rejournal) - // Track the previous head headers for transaction reorgs - head = pool.chain.CurrentBlock() - ) - defer report.Stop() - defer evict.Stop() - defer journal.Stop() - - // Notify tests that the init phase is done - close(pool.initDoneCh) - for { - select { - // Handle ChainHeadEvent - case ev := <-pool.chainHeadCh: - if ev.Block != nil { - pool.requestReset(head, ev.Block.Header()) - head = ev.Block.Header() - } - - // System shutdown. - case <-pool.chainHeadSub.Err(): - close(pool.reorgShutdownCh) - return - - // Handle stats reporting ticks - case <-report.C: - pool.mu.RLock() - pending, queued := pool.stats() - pool.mu.RUnlock() - stales := int(pool.priced.stales.Load()) - - if pending != prevPending || queued != prevQueued || stales != prevStales { - log.Debug("Transaction pool status report", "executable", pending, "queued", queued, "stales", stales) - prevPending, prevQueued, prevStales = pending, queued, stales + subpools: subpools, + reservations: make(map[common.Address]SubPool), + quit: make(chan chan error), + } + for i, subpool := range subpools { + if err := subpool.Init(gasTip, head, pool.reserver(i, subpool)); err != nil { + for j := i - 1; j >= 0; j-- { + subpools[j].Close() } - - // Handle inactive account transaction eviction - case <-evict.C: - pool.mu.Lock() - for addr := range pool.queue { - // Skip local transactions from the eviction mechanism - if pool.locals.contains(addr) { - continue - } - // Any non-locals old enough should be removed - if time.Since(pool.beats[addr]) > pool.config.Lifetime { - list := pool.queue[addr].Flatten() - for _, tx := range list { - pool.removeTx(tx.Hash(), true) - } - queuedEvictionMeter.Mark(int64(len(list))) + return nil, err + } + } + go pool.loop(head, chain) + return pool, nil +} + +// reserver is a method to create an address reservation callback to exclusively +// assign/deassign addresses to/from subpools. This can ensure that at any point +// in time, only a single subpool is able to manage an account, avoiding cross +// subpool eviction issues and nonce conflicts. +func (p *TxPool) reserver(id int, subpool SubPool) AddressReserver { + return func(addr common.Address, reserve bool) error { + p.reserveLock.Lock() + defer p.reserveLock.Unlock() + + owner, exists := p.reservations[addr] + if reserve { + // Double reservations are forbidden even from the same pool to + // avoid subtle bugs in the long term. + if exists { + if owner == subpool { + log.Error("pool attempted to reserve already-owned address", "address", addr) + return nil // Ignore fault to give the pool a chance to recover while the bug gets fixed } + return errors.New("address already reserved") } - pool.mu.Unlock() - - // Handle local transaction journal rotation - case <-journal.C: - if pool.journal != nil { - pool.mu.Lock() - if err := pool.journal.rotate(pool.local()); err != nil { - log.Warn("Failed to rotate local tx journal", "err", err) - } - pool.mu.Unlock() - } - } - } -} - -// Stop terminates the transaction pool. -func (pool *TxPool) Stop() { - // Unsubscribe all subscriptions registered from txpool - pool.scope.Close() - - // Unsubscribe subscriptions registered from blockchain - pool.chainHeadSub.Unsubscribe() - pool.wg.Wait() - - if pool.journal != nil { - pool.journal.close() - } - log.Info("Transaction pool stopped") -} - -// SubscribeNewTxsEvent registers a subscription of NewTxsEvent and -// starts sending event to the given channel. -func (pool *TxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { - return pool.scope.Track(pool.txFeed.Subscribe(ch)) -} - -// GasPrice returns the current gas price enforced by the transaction pool. -func (pool *TxPool) GasPrice() *big.Int { - pool.mu.RLock() - defer pool.mu.RUnlock() - - return new(big.Int).Set(pool.gasPrice) -} - -// SetGasPrice updates the minimum price required by the transaction pool for a -// new transaction, and drops all transactions below this threshold. -func (pool *TxPool) SetGasPrice(price *big.Int) { - pool.mu.Lock() - defer pool.mu.Unlock() - - old := pool.gasPrice - pool.gasPrice = price - // if the min miner fee increased, remove transactions below the new threshold - if price.Cmp(old) > 0 { - // pool.priced is sorted by GasFeeCap, so we have to iterate through pool.all instead - drop := pool.all.RemotesBelowTip(price) - for _, tx := range drop { - pool.removeTx(tx.Hash(), false) - } - pool.priced.Removed(len(drop)) - } - - log.Info("Transaction pool price threshold updated", "price", price) -} - -// Nonce returns the next nonce of an account, with all transactions executable -// by the pool already applied on top. -func (pool *TxPool) Nonce(addr common.Address) uint64 { - pool.mu.RLock() - defer pool.mu.RUnlock() - - return pool.pendingNonces.get(addr) -} - -// Stats retrieves the current pool stats, namely the number of pending and the -// number of queued (non-executable) transactions. -func (pool *TxPool) Stats() (int, int) { - pool.mu.RLock() - defer pool.mu.RUnlock() - - return pool.stats() -} - -// stats retrieves the current pool stats, namely the number of pending and the -// number of queued (non-executable) transactions. -func (pool *TxPool) stats() (int, int) { - pending := 0 - for _, list := range pool.pending { - pending += list.Len() - } - queued := 0 - for _, list := range pool.queue { - queued += list.Len() - } - return pending, queued -} - -// Content retrieves the data content of the transaction pool, returning all the -// pending as well as queued transactions, grouped by account and sorted by nonce. -func (pool *TxPool) Content() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { - pool.mu.Lock() - defer pool.mu.Unlock() - - pending := make(map[common.Address]types.Transactions, len(pool.pending)) - for addr, list := range pool.pending { - pending[addr] = list.Flatten() - } - queued := make(map[common.Address]types.Transactions, len(pool.queue)) - for addr, list := range pool.queue { - queued[addr] = list.Flatten() - } - return pending, queued -} - -// ContentFrom retrieves the data content of the transaction pool, returning the -// pending as well as queued transactions of this address, grouped by nonce. -func (pool *TxPool) ContentFrom(addr common.Address) (types.Transactions, types.Transactions) { - pool.mu.RLock() - defer pool.mu.RUnlock() - - var pending types.Transactions - if list, ok := pool.pending[addr]; ok { - pending = list.Flatten() - } - var queued types.Transactions - if list, ok := pool.queue[addr]; ok { - queued = list.Flatten() - } - return pending, queued -} - -// Pending retrieves all currently processable transactions, grouped by origin -// account and sorted by nonce. The returned transaction set is a copy and can be -// freely modified by calling code. -// -// The enforceTips parameter can be used to do an extra filtering on the pending -// transactions and only return those whose **effective** tip is large enough in -// the next pending execution environment. -func (pool *TxPool) Pending(enforceTips bool) map[common.Address]types.Transactions { - pool.mu.Lock() - defer pool.mu.Unlock() - - pending := make(map[common.Address]types.Transactions, len(pool.pending)) - for addr, list := range pool.pending { - txs := list.Flatten() - - // If the miner requests tip enforcement, cap the lists now - if enforceTips && !pool.locals.contains(addr) { - for i, tx := range txs { - if tx.EffectiveGasTipIntCmp(pool.gasPrice, pool.priced.urgent.baseFee) < 0 { - txs = txs[:i] - break - } + p.reservations[addr] = subpool + if metrics.Enabled { + m := fmt.Sprintf("%s/%d", reservationsGaugeName, id) + metrics.GetOrRegisterGauge(m, nil).Inc(1) } + return nil } - if len(txs) > 0 { - pending[addr] = txs - } - } - return pending -} - -// Locals retrieves the accounts currently considered local by the pool. -func (pool *TxPool) Locals() []common.Address { - pool.mu.Lock() - defer pool.mu.Unlock() - - return pool.locals.flatten() -} - -// local retrieves all currently known local transactions, grouped by origin -// account and sorted by nonce. The returned transaction set is a copy and can be -// freely modified by calling code. -func (pool *TxPool) local() map[common.Address]types.Transactions { - txs := make(map[common.Address]types.Transactions) - for addr := range pool.locals.accounts { - if pending := pool.pending[addr]; pending != nil { - txs[addr] = append(txs[addr], pending.Flatten()...) - } - if queued := pool.queue[addr]; queued != nil { - txs[addr] = append(txs[addr], queued.Flatten()...) + // Ensure subpools only attempt to unreserve their own owned addresses, + // otherwise flag as a programming error. + if !exists { + log.Error("pool attempted to unreserve non-reserved address", "address", addr) + return errors.New("address not reserved") } - } - return txs -} - -// validateTxBasics checks whether a transaction is valid according to the consensus -// rules, but does not check state-dependent validation such as sufficient balance. -// This check is meant as an early check which only needs to be performed once, -// and does not require the pool mutex to be held. -func (pool *TxPool) validateTxBasics(tx *types.Transaction, local bool) error { - // Accept only legacy transactions until EIP-2718/2930 activates. - if !pool.eip2718.Load() && tx.Type() != types.LegacyTxType { - return core.ErrTxTypeNotSupported - } - // Reject dynamic fee transactions until EIP-1559 activates. - if !pool.eip1559.Load() && tx.Type() == types.DynamicFeeTxType { - return core.ErrTxTypeNotSupported - } - // Reject blob transactions forever, those will have their own pool. - if tx.Type() == types.BlobTxType { - return core.ErrTxTypeNotSupported - } - // Reject transactions over defined size to prevent DOS attacks - if tx.Size() > txMaxSize { - return ErrOversizedData - } - // Check whether the init code size has been exceeded. - if pool.shanghai.Load() && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { - return fmt.Errorf("%w: code size %v limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize) - } - // Transactions can't be negative. This may never happen using RLP decoded - // transactions but may occur if you create a transaction using the RPC. - if tx.Value().Sign() < 0 { - return ErrNegativeValue - } - // Ensure the transaction doesn't exceed the current block limit gas. - if pool.currentMaxGas.Load() < tx.Gas() { - return ErrGasLimit - } - // Sanity check for extremely large numbers - if tx.GasFeeCap().BitLen() > 256 { - return core.ErrFeeCapVeryHigh - } - if tx.GasTipCap().BitLen() > 256 { - return core.ErrTipVeryHigh - } - // Ensure gasFeeCap is greater than or equal to gasTipCap. - if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 { - return core.ErrTipAboveFeeCap - } - // Make sure the transaction is signed properly. - if _, err := types.Sender(pool.signer, tx); err != nil { - return ErrInvalidSender - } - // Drop non-local transactions under our own minimal accepted gas price or tip - if !local && tx.GasTipCapIntCmp(pool.gasPrice) < 0 { - return ErrUnderpriced - } - // Ensure the transaction has more gas than the basic tx fee. - intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul.Load(), pool.shanghai.Load()) - if err != nil { - return err - } - if tx.Gas() < intrGas { - return core.ErrIntrinsicGas - } - return nil -} - -// validateTx checks whether a transaction is valid according to the consensus -// rules and adheres to some heuristic limits of the local node (price and size). -func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { - // Signature has been checked already, this cannot error. - from, _ := types.Sender(pool.signer, tx) - // Ensure the transaction adheres to nonce ordering - if pool.currentState.GetNonce(from) > tx.Nonce() { - return core.ErrNonceTooLow - } - // Transactor should have enough funds to cover the costs - // cost == V + GP * GL - balance := pool.currentState.GetBalance(from) - if balance.Cmp(tx.Cost()) < 0 { - return core.ErrInsufficientFunds - } - - // Verify that replacing transactions will not result in overdraft - list := pool.pending[from] - if list != nil { // Sender already has pending txs - sum := new(big.Int).Add(tx.Cost(), list.totalcost) - if repl := list.txs.Get(tx.Nonce()); repl != nil { - // Deduct the cost of a transaction replaced by this - sum.Sub(sum, repl.Cost()) + if subpool != owner { + log.Error("pool attempted to unreserve non-owned address", "address", addr) + return errors.New("address not owned") } - if balance.Cmp(sum) < 0 { - log.Trace("Replacing transactions would overdraft", "sender", from, "balance", pool.currentState.GetBalance(from), "required", sum) - return ErrOverdraft + delete(p.reservations, addr) + if metrics.Enabled { + m := fmt.Sprintf("%s/%d", reservationsGaugeName, id) + metrics.GetOrRegisterGauge(m, nil).Dec(1) } + return nil } - return nil } -// add validates a transaction and inserts it into the non-executable queue for later -// pending promotion and execution. If the transaction is a replacement for an already -// pending or queued one, it overwrites the previous transaction if its price is higher. -// -// If a newly added transaction is marked as local, its sending account will be -// be added to the allowlist, preventing any associated transaction from being dropped -// out of the pool due to pricing constraints. -func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err error) { - // If the transaction is already known, discard it - hash := tx.Hash() - if pool.all.Get(hash) != nil { - log.Trace("Discarding already known transaction", "hash", hash) - knownTxMeter.Mark(1) - return false, ErrAlreadyKnown - } - // Make the local flag. If it's from local source or it's from the network but - // the sender is marked as local previously, treat it as the local transaction. - isLocal := local || pool.locals.containsTx(tx) - - // If the transaction fails basic validation, discard it - if err := pool.validateTx(tx, isLocal); err != nil { - log.Trace("Discarding invalid transaction", "hash", hash, "err", err) - invalidTxMeter.Mark(1) - return false, err - } - - // already validated by this point - from, _ := types.Sender(pool.signer, tx) - - // If the transaction pool is full, discard underpriced transactions - if uint64(pool.all.Slots()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue { - // If the new transaction is underpriced, don't accept it - if !isLocal && pool.priced.Underpriced(tx) { - log.Trace("Discarding underpriced transaction", "hash", hash, "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) - underpricedTxMeter.Mark(1) - return false, ErrUnderpriced - } - - // We're about to replace a transaction. The reorg does a more thorough - // analysis of what to remove and how, but it runs async. We don't want to - // do too many replacements between reorg-runs, so we cap the number of - // replacements to 25% of the slots - if pool.changesSinceReorg > int(pool.config.GlobalSlots/4) { - throttleTxMeter.Mark(1) - return false, ErrTxPoolOverflow - } - - // New transaction is better than our worse ones, make room for it. - // If it's a local transaction, forcibly discard all available transactions. - // Otherwise if we can't make enough room for new one, abort the operation. - drop, success := pool.priced.Discard(pool.all.Slots()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+numSlots(tx), isLocal) - - // Special case, we still can't make the room for the new remote one. - if !isLocal && !success { - log.Trace("Discarding overflown transaction", "hash", hash) - overflowedTxMeter.Mark(1) - return false, ErrTxPoolOverflow - } - - // If the new transaction is a future transaction it should never churn pending transactions - if !isLocal && pool.isGapped(from, tx) { - var replacesPending bool - for _, dropTx := range drop { - dropSender, _ := types.Sender(pool.signer, dropTx) - if list := pool.pending[dropSender]; list != nil && list.Contains(dropTx.Nonce()) { - replacesPending = true - break - } - } - // Add all transactions back to the priced queue - if replacesPending { - for _, dropTx := range drop { - pool.priced.Put(dropTx, false) - } - log.Trace("Discarding future transaction replacing pending tx", "hash", hash) - return false, ErrFutureReplacePending - } - } +// Close terminates the transaction pool and all its subpools. +func (p *TxPool) Close() error { + var errs []error - // Kick out the underpriced remote transactions. - for _, tx := range drop { - log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) - underpricedTxMeter.Mark(1) - dropped := pool.removeTx(tx.Hash(), false) - pool.changesSinceReorg += dropped - } + // Terminate the reset loop and wait for it to finish + errc := make(chan error) + p.quit <- errc + if err := <-errc; err != nil { + errs = append(errs, err) } - - // Try to replace an existing transaction in the pending pool - if list := pool.pending[from]; list != nil && list.Contains(tx.Nonce()) { - // Nonce already pending, check if required price bump is met - inserted, old := list.Add(tx, pool.config.PriceBump) - if !inserted { - pendingDiscardMeter.Mark(1) - return false, ErrReplaceUnderpriced + // Terminate each subpool + for _, subpool := range p.subpools { + if err := subpool.Close(); err != nil { + errs = append(errs, err) } - // New transaction is better, replace old one - if old != nil { - pool.all.Remove(old.Hash()) - pool.priced.Removed(1) - pendingReplaceMeter.Mark(1) - } - pool.all.Add(tx, isLocal) - pool.priced.Put(tx, isLocal) - pool.journalTx(from, tx) - pool.queueTxEvent(tx) - log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To()) - - // Successful promotion, bump the heartbeat - pool.beats[from] = time.Now() - return old != nil, nil - } - // New transaction isn't replacing a pending one, push into queue - replaced, err = pool.enqueueTx(hash, tx, isLocal, true) - if err != nil { - return false, err - } - // Mark local addresses and journal local transactions - if local && !pool.locals.contains(from) { - log.Info("Setting new local account", "address", from) - pool.locals.add(from) - pool.priced.Removed(pool.all.RemoteToLocals(pool.locals)) // Migrate the remotes if it's marked as local first time. - } - if isLocal { - localGauge.Inc(1) - } - pool.journalTx(from, tx) - - log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To()) - return replaced, nil -} - -// isGapped reports whether the given transaction is immediately executable. -func (pool *TxPool) isGapped(from common.Address, tx *types.Transaction) bool { - // Short circuit if transaction matches pending nonce and can be promoted - // to pending list as an executable transaction. - next := pool.pendingNonces.get(from) - if tx.Nonce() == next { - return false - } - // The transaction has a nonce gap with pending list, it's only considered - // as executable if transactions in queue can fill up the nonce gap. - queue, ok := pool.queue[from] - if !ok { - return true - } - for nonce := next; nonce < tx.Nonce(); nonce++ { - if !queue.Contains(nonce) { - return true // txs in queue can't fill up the nonce gap - } - } - return false -} - -// enqueueTx inserts a new transaction into the non-executable transaction queue. -// -// Note, this method assumes the pool lock is held! -func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local bool, addAll bool) (bool, error) { - // Try to insert the transaction into the future queue - from, _ := types.Sender(pool.signer, tx) // already validated - if pool.queue[from] == nil { - pool.queue[from] = newList(false) - } - inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump) - if !inserted { - // An older transaction was better, discard this - queuedDiscardMeter.Mark(1) - return false, ErrReplaceUnderpriced - } - // Discard any previous transaction and mark this - if old != nil { - pool.all.Remove(old.Hash()) - pool.priced.Removed(1) - queuedReplaceMeter.Mark(1) - } else { - // Nothing was replaced, bump the queued counter - queuedGauge.Inc(1) - } - // If the transaction isn't in lookup set but it's expected to be there, - // show the error log. - if pool.all.Get(hash) == nil && !addAll { - log.Error("Missing transaction in lookup set, please report the issue", "hash", hash) - } - if addAll { - pool.all.Add(tx, local) - pool.priced.Put(tx, local) - } - // If we never record the heartbeat, do it right now. - if _, exist := pool.beats[from]; !exist { - pool.beats[from] = time.Now() - } - return old != nil, nil -} - -// journalTx adds the specified transaction to the local disk journal if it is -// deemed to have been sent from a local account. -func (pool *TxPool) journalTx(from common.Address, tx *types.Transaction) { - // Only journal if it's enabled and the transaction is local - if pool.journal == nil || !pool.locals.contains(from) { - return - } - if err := pool.journal.insert(tx); err != nil { - log.Warn("Failed to journal local transaction", "err", err) - } -} - -// promoteTx adds a transaction to the pending (processable) list of transactions -// and returns whether it was inserted or an older was better. -// -// Note, this method assumes the pool lock is held! -func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool { - // Try to insert the transaction into the pending queue - if pool.pending[addr] == nil { - pool.pending[addr] = newList(true) } - list := pool.pending[addr] + // Unsubscribe anyone still listening for tx events + p.subs.Close() - inserted, old := list.Add(tx, pool.config.PriceBump) - if !inserted { - // An older transaction was better, discard this - pool.all.Remove(hash) - pool.priced.Removed(1) - pendingDiscardMeter.Mark(1) - return false + if len(errs) > 0 { + return fmt.Errorf("subpool close errors: %v", errs) } - // Otherwise discard any previous transaction and mark this - if old != nil { - pool.all.Remove(old.Hash()) - pool.priced.Removed(1) - pendingReplaceMeter.Mark(1) - } else { - // Nothing was replaced, bump the pending counter - pendingGauge.Inc(1) - } - // Set the potentially new pending nonce and notify any subsystems of the new tx - pool.pendingNonces.set(addr, tx.Nonce()+1) - - // Successful promotion, bump the heartbeat - pool.beats[addr] = time.Now() - return true -} - -// AddLocals enqueues a batch of transactions into the pool if they are valid, marking the -// senders as a local ones, ensuring they go around the local pricing constraints. -// -// This method is used to add transactions from the RPC API and performs synchronous pool -// reorganization and event propagation. -func (pool *TxPool) AddLocals(txs []*types.Transaction) []error { - return pool.addTxs(txs, !pool.config.NoLocals, true) -} - -// AddLocal enqueues a single local transaction into the pool if it is valid. This is -// a convenience wrapper around AddLocals. -func (pool *TxPool) AddLocal(tx *types.Transaction) error { - errs := pool.AddLocals([]*types.Transaction{tx}) - return errs[0] -} - -// AddRemotes enqueues a batch of transactions into the pool if they are valid. If the -// senders are not among the locally tracked ones, full pricing constraints will apply. -// -// This method is used to add transactions from the p2p network and does not wait for pool -// reorganization and internal event propagation. -func (pool *TxPool) AddRemotes(txs []*types.Transaction) []error { - return pool.addTxs(txs, false, false) -} - -// AddRemotesSync is like AddRemotes, but waits for pool reorganization. Tests use this method. -func (pool *TxPool) AddRemotesSync(txs []*types.Transaction) []error { - return pool.addTxs(txs, false, true) -} - -// This is like AddRemotes with a single transaction, but waits for pool reorganization. Tests use this method. -func (pool *TxPool) addRemoteSync(tx *types.Transaction) error { - errs := pool.AddRemotesSync([]*types.Transaction{tx}) - return errs[0] -} - -// AddRemote enqueues a single transaction into the pool if it is valid. This is a convenience -// wrapper around AddRemotes. -// -// Deprecated: use AddRemotes -func (pool *TxPool) AddRemote(tx *types.Transaction) error { - errs := pool.AddRemotes([]*types.Transaction{tx}) - return errs[0] + return nil } -// addTxs attempts to queue a batch of transactions if they are valid. -func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error { - // Filter out known ones without obtaining the pool lock or recovering signatures +// loop is the transaction pool's main event loop, waiting for and reacting to +// outside blockchain events as well as for various reporting and transaction +// eviction events. +func (p *TxPool) loop(head *types.Header, chain BlockChain) { + // Subscribe to chain head events to trigger subpool resets var ( - errs = make([]error, len(txs)) - news = make([]*types.Transaction, 0, len(txs)) + newHeadCh = make(chan core.ChainHeadEvent) + newHeadSub = chain.SubscribeChainHeadEvent(newHeadCh) ) - for i, tx := range txs { - // If the transaction is known, pre-set the error slot - if pool.all.Get(tx.Hash()) != nil { - errs[i] = ErrAlreadyKnown - knownTxMeter.Mark(1) - continue - } - // Exclude transactions with basic errors, e.g invalid signatures and - // insufficient intrinsic gas as soon as possible and cache senders - // in transactions before obtaining lock - - if err := pool.validateTxBasics(tx, local); err != nil { - errs[i] = err - invalidTxMeter.Mark(1) - continue - } - // Accumulate all unknown transactions for deeper processing - news = append(news, tx) - } - if len(news) == 0 { - return errs - } - - // Process all the new transaction and merge any errors into the original slice - pool.mu.Lock() - newErrs, dirtyAddrs := pool.addTxsLocked(news, local) - pool.mu.Unlock() - - var nilSlot = 0 - for _, err := range newErrs { - for errs[nilSlot] != nil { - nilSlot++ - } - errs[nilSlot] = err - nilSlot++ - } - // Reorg the pool internals if needed and return - done := pool.requestPromoteExecutables(dirtyAddrs) - if sync { - <-done - } - return errs -} - -// addTxsLocked attempts to queue a batch of transactions if they are valid. -// The transaction pool lock must be held. -func (pool *TxPool) addTxsLocked(txs []*types.Transaction, local bool) ([]error, *accountSet) { - dirty := newAccountSet(pool.signer) - errs := make([]error, len(txs)) - for i, tx := range txs { - replaced, err := pool.add(tx, local) - errs[i] = err - if err == nil && !replaced { - dirty.addTx(tx) - } - } - validTxMeter.Mark(int64(len(dirty.accounts))) - return errs, dirty -} - -// Status returns the status (unknown/pending/queued) of a batch of transactions -// identified by their hashes. -func (pool *TxPool) Status(hashes []common.Hash) []TxStatus { - status := make([]TxStatus, len(hashes)) - for i, hash := range hashes { - tx := pool.Get(hash) - if tx == nil { - continue - } - from, _ := types.Sender(pool.signer, tx) // already validated - pool.mu.RLock() - if txList := pool.pending[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { - status[i] = TxStatusPending - } else if txList := pool.queue[from]; txList != nil && txList.txs.items[tx.Nonce()] != nil { - status[i] = TxStatusQueued - } - // implicit else: the tx may have been included into a block between - // checking pool.Get and obtaining the lock. In that case, TxStatusUnknown is correct - pool.mu.RUnlock() - } - return status -} - -// Get returns a transaction if it is contained in the pool and nil otherwise. -func (pool *TxPool) Get(hash common.Hash) *types.Transaction { - return pool.all.Get(hash) -} - -// Has returns an indicator whether txpool has a transaction cached with the -// given hash. -func (pool *TxPool) Has(hash common.Hash) bool { - return pool.all.Get(hash) != nil -} - -// removeTx removes a single transaction from the queue, moving all subsequent -// transactions back to the future queue. -// Returns the number of transactions removed from the pending queue. -func (pool *TxPool) removeTx(hash common.Hash, outofbound bool) int { - // Fetch the transaction we wish to delete - tx := pool.all.Get(hash) - if tx == nil { - return 0 - } - addr, _ := types.Sender(pool.signer, tx) // already validated during insertion - - // Remove it from the list of known transactions - pool.all.Remove(hash) - if outofbound { - pool.priced.Removed(1) - } - if pool.locals.contains(addr) { - localGauge.Dec(1) - } - // Remove the transaction from the pending lists and reset the account nonce - if pending := pool.pending[addr]; pending != nil { - if removed, invalids := pending.Remove(tx); removed { - // If no more pending transactions are left, remove the list - if pending.Empty() { - delete(pool.pending, addr) - } - // Postpone any invalidated transactions - for _, tx := range invalids { - // Internal shuffle shouldn't touch the lookup set. - pool.enqueueTx(tx.Hash(), tx, false, false) - } - // Update the account nonce if needed - pool.pendingNonces.setIfLower(addr, tx.Nonce()) - // Reduce the pending counter - pendingGauge.Dec(int64(1 + len(invalids))) - return 1 + len(invalids) - } - } - // Transaction is in the future queue - if future := pool.queue[addr]; future != nil { - if removed, _ := future.Remove(tx); removed { - // Reduce the queued counter - queuedGauge.Dec(1) - } - if future.Empty() { - delete(pool.queue, addr) - delete(pool.beats, addr) - } - } - return 0 -} - -// requestReset requests a pool reset to the new head block. -// The returned channel is closed when the reset has occurred. -func (pool *TxPool) requestReset(oldHead *types.Header, newHead *types.Header) chan struct{} { - select { - case pool.reqResetCh <- &txpoolResetRequest{oldHead, newHead}: - return <-pool.reorgDoneCh - case <-pool.reorgShutdownCh: - return pool.reorgShutdownCh - } -} - -// requestPromoteExecutables requests transaction promotion checks for the given addresses. -// The returned channel is closed when the promotion checks have occurred. -func (pool *TxPool) requestPromoteExecutables(set *accountSet) chan struct{} { - select { - case pool.reqPromoteCh <- set: - return <-pool.reorgDoneCh - case <-pool.reorgShutdownCh: - return pool.reorgShutdownCh - } -} - -// queueTxEvent enqueues a transaction event to be sent in the next reorg run. -func (pool *TxPool) queueTxEvent(tx *types.Transaction) { - select { - case pool.queueTxEventCh <- tx: - case <-pool.reorgShutdownCh: - } -} - -// scheduleReorgLoop schedules runs of reset and promoteExecutables. Code above should not -// call those methods directly, but request them being run using requestReset and -// requestPromoteExecutables instead. -func (pool *TxPool) scheduleReorgLoop() { - defer pool.wg.Done() + defer newHeadSub.Unsubscribe() + // Track the previous and current head to feed to an idle reset var ( - curDone chan struct{} // non-nil while runReorg is active - nextDone = make(chan struct{}) - launchNextRun bool - reset *txpoolResetRequest - dirtyAccounts *accountSet - queuedEvents = make(map[common.Address]*sortedMap) + oldHead = head + newHead = oldHead ) - for { - // Launch next background reorg if needed - if curDone == nil && launchNextRun { - // Run the background reorg and announcements - go pool.runReorg(nextDone, reset, dirtyAccounts, queuedEvents) - - // Prepare everything for the next round of reorg - curDone, nextDone = nextDone, make(chan struct{}) - launchNextRun = false + // Consume chain head events and start resets when none is running + var ( + resetBusy = make(chan struct{}, 1) // Allow 1 reset to run concurrently + resetDone = make(chan *types.Header) + ) + var errc chan error + for errc == nil { + // Something interesting might have happened, run a reset if there is + // one needed but none is running. The resetter will run on its own + // goroutine to allow chain head events to be consumed contiguously. + if newHead != oldHead { + // Try to inject a busy marker and start a reset if successful + select { + case resetBusy <- struct{}{}: + // Busy marker injected, start a new subpool reset + go func(oldHead, newHead *types.Header) { + for _, subpool := range p.subpools { + subpool.Reset(oldHead, newHead) + } + resetDone <- newHead + }(oldHead, newHead) - reset, dirtyAccounts = nil, nil - queuedEvents = make(map[common.Address]*sortedMap) + default: + // Reset already running, wait until it finishes + } } - + // Wait for the next chain head event or a previous reset finish select { - case req := <-pool.reqResetCh: - // Reset request: update head if request is already pending. - if reset == nil { - reset = req - } else { - reset.newHead = req.newHead - } - launchNextRun = true - pool.reorgDoneCh <- nextDone + case event := <-newHeadCh: + // Chain moved forward, store the head for later consumption + newHead = event.Block.Header() - case req := <-pool.reqPromoteCh: - // Promote request: update address set if request is already pending. - if dirtyAccounts == nil { - dirtyAccounts = req - } else { - dirtyAccounts.merge(req) - } - launchNextRun = true - pool.reorgDoneCh <- nextDone + case head := <-resetDone: + // Previous reset finished, update the old head and allow a new reset + oldHead = head + <-resetBusy - case tx := <-pool.queueTxEventCh: - // Queue up the event, but don't schedule a reorg. It's up to the caller to - // request one later if they want the events sent. - addr, _ := types.Sender(pool.signer, tx) - if _, ok := queuedEvents[addr]; !ok { - queuedEvents[addr] = newSortedMap() - } - queuedEvents[addr].Put(tx) - - case <-curDone: - curDone = nil - - case <-pool.reorgShutdownCh: - // Wait for current run to finish. - if curDone != nil { - <-curDone - } - close(nextDone) - return + case errc = <-p.quit: + // Termination requested, break out on the next loop round } } + // Notify the closer of termination (no error possible for now) + errc <- nil } -// runReorg runs reset and promoteExecutables on behalf of scheduleReorgLoop. -func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirtyAccounts *accountSet, events map[common.Address]*sortedMap) { - defer func(t0 time.Time) { - reorgDurationTimer.Update(time.Since(t0)) - }(time.Now()) - defer close(done) - - var promoteAddrs []common.Address - if dirtyAccounts != nil && reset == nil { - // Only dirty accounts need to be promoted, unless we're resetting. - // For resets, all addresses in the tx queue will be promoted and - // the flatten operation can be avoided. - promoteAddrs = dirtyAccounts.flatten() - } - pool.mu.Lock() - if reset != nil { - // Reset from the old head to the new, rescheduling any reorged transactions - pool.reset(reset.oldHead, reset.newHead) - - // Nonces were reset, discard any events that became stale - for addr := range events { - events[addr].Forward(pool.pendingNonces.get(addr)) - if events[addr].Len() == 0 { - delete(events, addr) - } - } - // Reset needs promote for all addresses - promoteAddrs = make([]common.Address, 0, len(pool.queue)) - for addr := range pool.queue { - promoteAddrs = append(promoteAddrs, addr) - } - } - // Check for pending transactions for every account that sent new ones - promoted := pool.promoteExecutables(promoteAddrs) - - // If a new block appeared, validate the pool of pending transactions. This will - // remove any transaction that has been included in the block or was invalidated - // because of another transaction (e.g. higher gas price). - if reset != nil { - pool.demoteUnexecutables() - if reset.newHead != nil && pool.chainconfig.IsLondon(new(big.Int).Add(reset.newHead.Number, big.NewInt(1))) { - pendingBaseFee := misc.CalcBaseFee(pool.chainconfig, reset.newHead) - pool.priced.SetBaseFee(pendingBaseFee) - } - // Update all accounts to the latest known pending nonce - nonces := make(map[common.Address]uint64, len(pool.pending)) - for addr, list := range pool.pending { - highestPending := list.LastElement() - nonces[addr] = highestPending.Nonce() + 1 - } - pool.pendingNonces.setAll(nonces) - } - // Ensure pool.queue and pool.pending sizes stay within the configured limits. - pool.truncatePending() - pool.truncateQueue() - - dropBetweenReorgHistogram.Update(int64(pool.changesSinceReorg)) - pool.changesSinceReorg = 0 // Reset change counter - pool.mu.Unlock() - - // Notify subsystems for newly added transactions - for _, tx := range promoted { - addr, _ := types.Sender(pool.signer, tx) - if _, ok := events[addr]; !ok { - events[addr] = newSortedMap() - } - events[addr].Put(tx) - } - if len(events) > 0 { - var txs []*types.Transaction - for _, set := range events { - txs = append(txs, set.Flatten()...) - } - pool.txFeed.Send(core.NewTxsEvent{Txs: txs}) +// SetGasTip updates the minimum gas tip required by the transaction pool for a +// new transaction, and drops all transactions below this threshold. +func (p *TxPool) SetGasTip(tip *big.Int) { + for _, subpool := range p.subpools { + subpool.SetGasTip(tip) } } -// reset retrieves the current state of the blockchain and ensures the content -// of the transaction pool is valid with regard to the chain state. -func (pool *TxPool) reset(oldHead, newHead *types.Header) { - // If we're reorging an old state, reinject all dropped transactions - var reinject types.Transactions - - if oldHead != nil && oldHead.Hash() != newHead.ParentHash { - // If the reorg is too deep, avoid doing it (will happen during fast sync) - oldNum := oldHead.Number.Uint64() - newNum := newHead.Number.Uint64() - - if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 { - log.Debug("Skipping deep transaction reorg", "depth", depth) - } else { - // Reorg seems shallow enough to pull in all transactions into memory - var discarded, included types.Transactions - var ( - rem = pool.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64()) - add = pool.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64()) - ) - if rem == nil { - // This can happen if a setHead is performed, where we simply discard the old - // head from the chain. - // If that is the case, we don't have the lost transactions anymore, and - // there's nothing to add - if newNum >= oldNum { - // If we reorged to a same or higher number, then it's not a case of setHead - log.Warn("Transaction pool reset with missing oldhead", - "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) - return - } - // If the reorg ended up on a lower number, it's indicative of setHead being the cause - log.Debug("Skipping transaction reset caused by setHead", - "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum) - // We still need to update the current state s.th. the lost transactions can be readded by the user - } else { - for rem.NumberU64() > add.NumberU64() { - discarded = append(discarded, rem.Transactions()...) - if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { - log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) - return - } - } - for add.NumberU64() > rem.NumberU64() { - included = append(included, add.Transactions()...) - if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { - log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) - return - } - } - for rem.Hash() != add.Hash() { - discarded = append(discarded, rem.Transactions()...) - if rem = pool.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil { - log.Error("Unrooted old chain seen by tx pool", "block", oldHead.Number, "hash", oldHead.Hash()) - return - } - included = append(included, add.Transactions()...) - if add = pool.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil { - log.Error("Unrooted new chain seen by tx pool", "block", newHead.Number, "hash", newHead.Hash()) - return - } - } - reinject = types.TxDifference(discarded, included) - } +// Has returns an indicator whether the pool has a transaction cached with the +// given hash. +func (p *TxPool) Has(hash common.Hash) bool { + for _, subpool := range p.subpools { + if subpool.Has(hash) { + return true } } - // Initialize the internal state to the current head - if newHead == nil { - newHead = pool.chain.CurrentBlock() // Special case during testing - } - statedb, err := pool.chain.StateAt(newHead.Root) - if err != nil { - log.Error("Failed to reset txpool state", "err", err) - return - } - pool.currentState = statedb - pool.pendingNonces = newNoncer(statedb) - pool.currentMaxGas.Store(newHead.GasLimit) - - // Inject any transactions discarded due to reorgs - log.Debug("Reinjecting stale transactions", "count", len(reinject)) - core.SenderCacher.Recover(pool.signer, reinject) - pool.addTxsLocked(reinject, false) - - // Update all fork indicator by next pending block number. - next := new(big.Int).Add(newHead.Number, big.NewInt(1)) - pool.istanbul.Store(pool.chainconfig.IsIstanbul(next)) - pool.eip2718.Store(pool.chainconfig.IsBerlin(next)) - pool.eip1559.Store(pool.chainconfig.IsLondon(next)) - pool.shanghai.Store(pool.chainconfig.IsShanghai(next, uint64(time.Now().Unix()), types.DeserializeHeaderExtraInformation(newHead).ArbOSFormatVersion)) + return false } -// promoteExecutables moves transactions that have become processable from the -// future queue to the set of pending transactions. During this process, all -// invalidated transactions (low nonce, low balance) are deleted. -func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Transaction { - // Track the promoted transactions to broadcast them at once - var promoted []*types.Transaction - - // Iterate over all accounts and promote any executable transactions - for _, addr := range accounts { - list := pool.queue[addr] - if list == nil { - continue // Just in case someone calls with a non existing account - } - // Drop all transactions that are deemed too old (low nonce) - forwards := list.Forward(pool.currentState.GetNonce(addr)) - for _, tx := range forwards { - hash := tx.Hash() - pool.all.Remove(hash) - } - log.Trace("Removed old queued transactions", "count", len(forwards)) - // Drop all transactions that are too costly (low balance or out of gas) - drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas.Load()) - for _, tx := range drops { - hash := tx.Hash() - pool.all.Remove(hash) - } - log.Trace("Removed unpayable queued transactions", "count", len(drops)) - queuedNofundsMeter.Mark(int64(len(drops))) - - // Gather all executable transactions and promote them - readies := list.Ready(pool.pendingNonces.get(addr)) - for _, tx := range readies { - hash := tx.Hash() - if pool.promoteTx(addr, hash, tx) { - promoted = append(promoted, tx) - } - } - log.Trace("Promoted queued transactions", "count", len(promoted)) - queuedGauge.Dec(int64(len(readies))) - - // Drop all transactions over the allowed limit - var caps types.Transactions - if !pool.locals.contains(addr) { - caps = list.Cap(int(pool.config.AccountQueue)) - for _, tx := range caps { - hash := tx.Hash() - pool.all.Remove(hash) - log.Trace("Removed cap-exceeding queued transaction", "hash", hash) - } - queuedRateLimitMeter.Mark(int64(len(caps))) - } - // Mark all the items dropped as removed - pool.priced.Removed(len(forwards) + len(drops) + len(caps)) - queuedGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) - if pool.locals.contains(addr) { - localGauge.Dec(int64(len(forwards) + len(drops) + len(caps))) - } - // Delete the entire queue entry if it became empty. - if list.Empty() { - delete(pool.queue, addr) - delete(pool.beats, addr) +// Get returns a transaction if it is contained in the pool, or nil otherwise. +func (p *TxPool) Get(hash common.Hash) *types.Transaction { + for _, subpool := range p.subpools { + if tx := subpool.Get(hash); tx != nil { + return tx } } - return promoted + return nil } -// truncatePending removes transactions from the pending queue if the pool is above the -// pending limit. The algorithm tries to reduce transaction counts by an approximately -// equal number for all for accounts with many pending transactions. -func (pool *TxPool) truncatePending() { - pending := uint64(0) - for _, list := range pool.pending { - pending += uint64(list.Len()) - } - if pending <= pool.config.GlobalSlots { - return - } - - pendingBeforeCap := pending - // Assemble a spam order to penalize large transactors first - spammers := prque.New[int64, common.Address](nil) - for addr, list := range pool.pending { - // Only evict transactions from high rollers - if !pool.locals.contains(addr) && uint64(list.Len()) > pool.config.AccountSlots { - spammers.Push(addr, int64(list.Len())) - } - } - // Gradually drop transactions from offenders - offenders := []common.Address{} - for pending > pool.config.GlobalSlots && !spammers.Empty() { - // Retrieve the next offender if not local address - offender, _ := spammers.Pop() - offenders = append(offenders, offender) - - // Equalize balances until all the same or below threshold - if len(offenders) > 1 { - // Calculate the equalization threshold for all current offenders - threshold := pool.pending[offender].Len() - - // Iteratively reduce all offenders until below limit or threshold reached - for pending > pool.config.GlobalSlots && pool.pending[offenders[len(offenders)-2]].Len() > threshold { - for i := 0; i < len(offenders)-1; i++ { - list := pool.pending[offenders[i]] - - caps := list.Cap(list.Len() - 1) - for _, tx := range caps { - // Drop the transaction from the global pools too - hash := tx.Hash() - pool.all.Remove(hash) - - // Update the account nonce to the dropped transaction - pool.pendingNonces.setIfLower(offenders[i], tx.Nonce()) - log.Trace("Removed fairness-exceeding pending transaction", "hash", hash) - } - pool.priced.Removed(len(caps)) - pendingGauge.Dec(int64(len(caps))) - if pool.locals.contains(offenders[i]) { - localGauge.Dec(int64(len(caps))) - } - pending-- - } - } - } - } - - // If still above threshold, reduce to limit or min allowance - if pending > pool.config.GlobalSlots && len(offenders) > 0 { - for pending > pool.config.GlobalSlots && uint64(pool.pending[offenders[len(offenders)-1]].Len()) > pool.config.AccountSlots { - for _, addr := range offenders { - list := pool.pending[addr] +// Add enqueues a batch of transactions into the pool if they are valid. Due +// to the large transaction churn, add may postpone fully integrating the tx +// to a later point to batch multiple ones together. +func (p *TxPool) Add(txs []*types.Transaction, local bool, sync bool) []error { + // Split the input transactions between the subpools. It shouldn't really + // happen that we receive merged batches, but better graceful than strange + // errors. + // + // We also need to track how the transactions were split across the subpools, + // so we can piece back the returned errors into the original order. + txsets := make([][]*types.Transaction, len(p.subpools)) + splits := make([]int, len(txs)) - caps := list.Cap(list.Len() - 1) - for _, tx := range caps { - // Drop the transaction from the global pools too - hash := tx.Hash() - pool.all.Remove(hash) - - // Update the account nonce to the dropped transaction - pool.pendingNonces.setIfLower(addr, tx.Nonce()) - log.Trace("Removed fairness-exceeding pending transaction", "hash", hash) - } - pool.priced.Removed(len(caps)) - pendingGauge.Dec(int64(len(caps))) - if pool.locals.contains(addr) { - localGauge.Dec(int64(len(caps))) - } - pending-- + for i, tx := range txs { + // Mark this transaction belonging to no-subpool + splits[i] = -1 + + // Try to find a subpool that accepts the transaction + for j, subpool := range p.subpools { + if subpool.Filter(tx) { + txsets[j] = append(txsets[j], tx) + splits[i] = j + break } } } - pendingRateLimitMeter.Mark(int64(pendingBeforeCap - pending)) -} - -// truncateQueue drops the oldest transactions in the queue if the pool is above the global queue limit. -func (pool *TxPool) truncateQueue() { - queued := uint64(0) - for _, list := range pool.queue { - queued += uint64(list.Len()) + // Add the transactions split apart to the individual subpools and piece + // back the errors into the original sort order. + errsets := make([][]error, len(p.subpools)) + for i := 0; i < len(p.subpools); i++ { + errsets[i] = p.subpools[i].Add(txsets[i], local, sync) } - if queued <= pool.config.GlobalQueue { - return - } - - // Sort all accounts with queued transactions by heartbeat - addresses := make(addressesByHeartbeat, 0, len(pool.queue)) - for addr := range pool.queue { - if !pool.locals.contains(addr) { // don't drop locals - addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]}) - } - } - sort.Sort(sort.Reverse(addresses)) - - // Drop transactions until the total is below the limit or only locals remain - for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; { - addr := addresses[len(addresses)-1] - list := pool.queue[addr.address] - - addresses = addresses[:len(addresses)-1] - - // Drop all transactions if they are less than the overflow - if size := uint64(list.Len()); size <= drop { - for _, tx := range list.Flatten() { - pool.removeTx(tx.Hash(), true) - } - drop -= size - queuedRateLimitMeter.Mark(int64(size)) + errs := make([]error, len(txs)) + for i, split := range splits { + // If the transaction was rejected by all subpools, mark it unsupported + if split == -1 { + errs[i] = core.ErrTxTypeNotSupported continue } - // Otherwise drop only last few transactions - txs := list.Flatten() - for i := len(txs) - 1; i >= 0 && drop > 0; i-- { - pool.removeTx(txs[i].Hash(), true) - drop-- - queuedRateLimitMeter.Mark(1) - } + // Find which subpool handled it and pull in the corresponding error + errs[i] = errsets[split][0] + errsets[split] = errsets[split][1:] } + return errs } -// demoteUnexecutables removes invalid and processed transactions from the pools -// executable/pending queue and any subsequent transactions that become unexecutable -// are moved back into the future queue. -// -// Note: transactions are not marked as removed in the priced list because re-heaping -// is always explicitly triggered by SetBaseFee and it would be unnecessary and wasteful -// to trigger a re-heap is this function -func (pool *TxPool) demoteUnexecutables() { - // Iterate over all accounts and demote any non-executable transactions - for addr, list := range pool.pending { - nonce := pool.currentState.GetNonce(addr) - - // Drop all transactions that are deemed too old (low nonce) - olds := list.Forward(nonce) - for _, tx := range olds { - hash := tx.Hash() - pool.all.Remove(hash) - log.Trace("Removed old pending transaction", "hash", hash) - } - // Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later - drops, invalids := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas.Load()) - for _, tx := range drops { - hash := tx.Hash() - log.Trace("Removed unpayable pending transaction", "hash", hash) - pool.all.Remove(hash) - } - pendingNofundsMeter.Mark(int64(len(drops))) - - for _, tx := range invalids { - hash := tx.Hash() - log.Trace("Demoting pending transaction", "hash", hash) - - // Internal shuffle shouldn't touch the lookup set. - pool.enqueueTx(hash, tx, false, false) - } - pendingGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) - if pool.locals.contains(addr) { - localGauge.Dec(int64(len(olds) + len(drops) + len(invalids))) - } - // If there's a gap in front, alert (should never happen) and postpone all transactions - if list.Len() > 0 && list.txs.Get(nonce) == nil { - gapped := list.Cap(0) - for _, tx := range gapped { - hash := tx.Hash() - log.Error("Demoting invalidated transaction", "hash", hash) - - // Internal shuffle shouldn't touch the lookup set. - pool.enqueueTx(hash, tx, false, false) - } - pendingGauge.Dec(int64(len(gapped))) - } - // Delete the entire pending entry if it became empty. - if list.Empty() { - delete(pool.pending, addr) +// Pending retrieves all currently processable transactions, grouped by origin +// account and sorted by nonce. +func (p *TxPool) Pending(enforceTips bool) map[common.Address][]*LazyTransaction { + txs := make(map[common.Address][]*LazyTransaction) + for _, subpool := range p.subpools { + for addr, set := range subpool.Pending(enforceTips) { + txs[addr] = set } } + return txs } -// addressByHeartbeat is an account address tagged with its last activity timestamp. -type addressByHeartbeat struct { - address common.Address - heartbeat time.Time -} - -type addressesByHeartbeat []addressByHeartbeat - -func (a addressesByHeartbeat) Len() int { return len(a) } -func (a addressesByHeartbeat) Less(i, j int) bool { return a[i].heartbeat.Before(a[j].heartbeat) } -func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -// accountSet is simply a set of addresses to check for existence, and a signer -// capable of deriving addresses from transactions. -type accountSet struct { - accounts map[common.Address]struct{} - signer types.Signer - cache *[]common.Address -} - -// newAccountSet creates a new address set with an associated signer for sender -// derivations. -func newAccountSet(signer types.Signer, addrs ...common.Address) *accountSet { - as := &accountSet{ - accounts: make(map[common.Address]struct{}, len(addrs)), - signer: signer, - } - for _, addr := range addrs { - as.add(addr) - } - return as -} - -// contains checks if a given address is contained within the set. -func (as *accountSet) contains(addr common.Address) bool { - _, exist := as.accounts[addr] - return exist -} - -// containsTx checks if the sender of a given tx is within the set. If the sender -// cannot be derived, this method returns false. -func (as *accountSet) containsTx(tx *types.Transaction) bool { - if addr, err := types.Sender(as.signer, tx); err == nil { - return as.contains(addr) - } - return false -} - -// add inserts a new address into the set to track. -func (as *accountSet) add(addr common.Address) { - as.accounts[addr] = struct{}{} - as.cache = nil -} - -// addTx adds the sender of tx into the set. -func (as *accountSet) addTx(tx *types.Transaction) { - if addr, err := types.Sender(as.signer, tx); err == nil { - as.add(addr) +// SubscribeTransactions registers a subscription for new transaction events, +// supporting feeding only newly seen or also resurrected transactions. +func (p *TxPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription { + subs := make([]event.Subscription, len(p.subpools)) + for i, subpool := range p.subpools { + subs[i] = subpool.SubscribeTransactions(ch, reorgs) } + return p.subs.Track(event.JoinSubscriptions(subs...)) } -// flatten returns the list of addresses within this set, also caching it for later -// reuse. The returned slice should not be changed! -func (as *accountSet) flatten() []common.Address { - if as.cache == nil { - accounts := make([]common.Address, 0, len(as.accounts)) - for account := range as.accounts { - accounts = append(accounts, account) +// Nonce returns the next nonce of an account, with all transactions executable +// by the pool already applied on top. +func (p *TxPool) Nonce(addr common.Address) uint64 { + // Since (for now) accounts are unique to subpools, only one pool will have + // (at max) a non-state nonce. To avoid stateful lookups, just return the + // highest nonce for now. + var nonce uint64 + for _, subpool := range p.subpools { + if next := subpool.Nonce(addr); nonce < next { + nonce = next } - as.cache = &accounts } - return *as.cache + return nonce } -// merge adds all addresses from the 'other' set into 'as'. -func (as *accountSet) merge(other *accountSet) { - for addr := range other.accounts { - as.accounts[addr] = struct{}{} - } - as.cache = nil -} - -// lookup is used internally by TxPool to track transactions while allowing -// lookup without mutex contention. -// -// Note, although this type is properly protected against concurrent access, it -// is **not** a type that should ever be mutated or even exposed outside of the -// transaction pool, since its internal state is tightly coupled with the pools -// internal mechanisms. The sole purpose of the type is to permit out-of-bound -// peeking into the pool in TxPool.Get without having to acquire the widely scoped -// TxPool.mu mutex. -// -// This lookup set combines the notion of "local transactions", which is useful -// to build upper-level structure. -type lookup struct { - slots int - lock sync.RWMutex - locals map[common.Hash]*types.Transaction - remotes map[common.Hash]*types.Transaction -} +// Stats retrieves the current pool stats, namely the number of pending and the +// number of queued (non-executable) transactions. +func (p *TxPool) Stats() (int, int) { + var runnable, blocked int + for _, subpool := range p.subpools { + run, block := subpool.Stats() -// newLookup returns a new lookup structure. -func newLookup() *lookup { - return &lookup{ - locals: make(map[common.Hash]*types.Transaction), - remotes: make(map[common.Hash]*types.Transaction), + runnable += run + blocked += block } + return runnable, blocked } -// Range calls f on each key and value present in the map. The callback passed -// should return the indicator whether the iteration needs to be continued. -// Callers need to specify which set (or both) to be iterated. -func (t *lookup) Range(f func(hash common.Hash, tx *types.Transaction, local bool) bool, local bool, remote bool) { - t.lock.RLock() - defer t.lock.RUnlock() +// Content retrieves the data content of the transaction pool, returning all the +// pending as well as queued transactions, grouped by account and sorted by nonce. +func (p *TxPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { + var ( + runnable = make(map[common.Address][]*types.Transaction) + blocked = make(map[common.Address][]*types.Transaction) + ) + for _, subpool := range p.subpools { + run, block := subpool.Content() - if local { - for key, value := range t.locals { - if !f(key, value, true) { - return - } + for addr, txs := range run { + runnable[addr] = txs } - } - if remote { - for key, value := range t.remotes { - if !f(key, value, false) { - return - } + for addr, txs := range block { + blocked[addr] = txs } } + return runnable, blocked } -// Get returns a transaction if it exists in the lookup, or nil if not found. -func (t *lookup) Get(hash common.Hash) *types.Transaction { - t.lock.RLock() - defer t.lock.RUnlock() - - if tx := t.locals[hash]; tx != nil { - return tx - } - return t.remotes[hash] -} - -// GetLocal returns a transaction if it exists in the lookup, or nil if not found. -func (t *lookup) GetLocal(hash common.Hash) *types.Transaction { - t.lock.RLock() - defer t.lock.RUnlock() - - return t.locals[hash] -} - -// GetRemote returns a transaction if it exists in the lookup, or nil if not found. -func (t *lookup) GetRemote(hash common.Hash) *types.Transaction { - t.lock.RLock() - defer t.lock.RUnlock() - - return t.remotes[hash] -} - -// Count returns the current number of transactions in the lookup. -func (t *lookup) Count() int { - t.lock.RLock() - defer t.lock.RUnlock() - - return len(t.locals) + len(t.remotes) -} - -// LocalCount returns the current number of local transactions in the lookup. -func (t *lookup) LocalCount() int { - t.lock.RLock() - defer t.lock.RUnlock() - - return len(t.locals) -} - -// RemoteCount returns the current number of remote transactions in the lookup. -func (t *lookup) RemoteCount() int { - t.lock.RLock() - defer t.lock.RUnlock() - - return len(t.remotes) -} - -// Slots returns the current number of slots used in the lookup. -func (t *lookup) Slots() int { - t.lock.RLock() - defer t.lock.RUnlock() - - return t.slots -} - -// Add adds a transaction to the lookup. -func (t *lookup) Add(tx *types.Transaction, local bool) { - t.lock.Lock() - defer t.lock.Unlock() - - t.slots += numSlots(tx) - slotsGauge.Update(int64(t.slots)) - - if local { - t.locals[tx.Hash()] = tx - } else { - t.remotes[tx.Hash()] = tx +// ContentFrom retrieves the data content of the transaction pool, returning the +// pending as well as queued transactions of this address, grouped by nonce. +func (p *TxPool) ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { + for _, subpool := range p.subpools { + run, block := subpool.ContentFrom(addr) + if len(run) != 0 || len(block) != 0 { + return run, block + } } + return []*types.Transaction{}, []*types.Transaction{} } -// Remove removes a transaction from the lookup. -func (t *lookup) Remove(hash common.Hash) { - t.lock.Lock() - defer t.lock.Unlock() - - tx, ok := t.locals[hash] - if !ok { - tx, ok = t.remotes[hash] +// Locals retrieves the accounts currently considered local by the pool. +func (p *TxPool) Locals() []common.Address { + // Retrieve the locals from each subpool and deduplicate them + locals := make(map[common.Address]struct{}) + for _, subpool := range p.subpools { + for _, local := range subpool.Locals() { + locals[local] = struct{}{} + } } - if !ok { - log.Error("No transaction found to be deleted", "hash", hash) - return + // Flatten and return the deduplicated local set + flat := make([]common.Address, 0, len(locals)) + for local := range locals { + flat = append(flat, local) } - t.slots -= numSlots(tx) - slotsGauge.Update(int64(t.slots)) - - delete(t.locals, hash) - delete(t.remotes, hash) + return flat } -// RemoteToLocals migrates the transactions belongs to the given locals to locals -// set. The assumption is held the locals set is thread-safe to be used. -func (t *lookup) RemoteToLocals(locals *accountSet) int { - t.lock.Lock() - defer t.lock.Unlock() - - var migrated int - for hash, tx := range t.remotes { - if locals.containsTx(tx) { - t.locals[hash] = tx - delete(t.remotes, hash) - migrated += 1 +// Status returns the known status (unknown/pending/queued) of a transaction +// identified by its hash. +func (p *TxPool) Status(hash common.Hash) TxStatus { + for _, subpool := range p.subpools { + if status := subpool.Status(hash); status != TxStatusUnknown { + return status } } - return migrated -} - -// RemotesBelowTip finds all remote transactions below the given tip threshold. -func (t *lookup) RemotesBelowTip(threshold *big.Int) types.Transactions { - found := make(types.Transactions, 0, 128) - t.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool { - if tx.GasTipCapIntCmp(threshold) < 0 { - found = append(found, tx) - } - return true - }, false, true) // Only iterate remotes - return found -} - -// numSlots calculates the number of slots needed for a single transaction. -func numSlots(tx *types.Transaction) int { - return int((tx.Size() + txSlotSize - 1) / txSlotSize) + return TxStatusUnknown } diff --git a/core/txpool/validation.go b/core/txpool/validation.go new file mode 100644 index 000000000000..54592b080cff --- /dev/null +++ b/core/txpool/validation.go @@ -0,0 +1,248 @@ +// Copyright 2023 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 txpool + +import ( + "crypto/sha256" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +// ValidationOptions define certain differences between transaction validation +// across the different pools without having to duplicate those checks. +type ValidationOptions struct { + Config *params.ChainConfig // Chain configuration to selectively validate based on current fork rules + + Accept uint8 // Bitmap of transaction types that should be accepted for the calling pool + MaxSize uint64 // Maximum size of a transaction that the caller can meaningfully handle + MinTip *big.Int // Minimum gas tip needed to allow a transaction into the caller pool +} + +// ValidateTransaction is a helper method to check whether a transaction is valid +// according to the consensus rules, but does not check state-dependent validation +// (balance, nonce, etc). +// +// This check is public to allow different transaction pools to check the basic +// rules without duplicating code and running the risk of missed updates. +func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types.Signer, opts *ValidationOptions) error { + // Ensure transactions not implemented by the calling pool are rejected + if opts.Accept&(1< opts.MaxSize { + return fmt.Errorf("%w: transaction size %v, limit %v", ErrOversizedData, tx.Size(), opts.MaxSize) + } + // Ensure only transactions that have been enabled are accepted + if !opts.Config.IsBerlin(head.Number) && tx.Type() != types.LegacyTxType { + return fmt.Errorf("%w: type %d rejected, pool not yet in Berlin", core.ErrTxTypeNotSupported, tx.Type()) + } + if !opts.Config.IsLondon(head.Number) && tx.Type() == types.DynamicFeeTxType { + return fmt.Errorf("%w: type %d rejected, pool not yet in London", core.ErrTxTypeNotSupported, tx.Type()) + } + arbosVersion := types.DeserializeHeaderExtraInformation(head).ArbOSFormatVersion + if !opts.Config.IsCancun(head.Number, head.Time, arbosVersion) && tx.Type() == types.BlobTxType { + return fmt.Errorf("%w: type %d rejected, pool not yet in Cancun", core.ErrTxTypeNotSupported, tx.Type()) + } + // Check whether the init code size has been exceeded + if opts.Config.IsShanghai(head.Number, head.Time, arbosVersion) && tx.To() == nil && len(tx.Data()) > int(opts.Config.MaxInitCodeSize()) { + return fmt.Errorf("%w: code size %v, limit %v", core.ErrMaxInitCodeSizeExceeded, len(tx.Data()), int(opts.Config.MaxInitCodeSize())) + } + // Transactions can't be negative. This may never happen using RLP decoded + // transactions but may occur for transactions created using the RPC. + if tx.Value().Sign() < 0 { + return ErrNegativeValue + } + // Ensure the transaction doesn't exceed the current block limit gas + if head.GasLimit < tx.Gas() { + return ErrGasLimit + } + // Sanity check for extremely large numbers (supported by RLP or RPC) + if tx.GasFeeCap().BitLen() > 256 { + return core.ErrFeeCapVeryHigh + } + if tx.GasTipCap().BitLen() > 256 { + return core.ErrTipVeryHigh + } + // Ensure gasFeeCap is greater than or equal to gasTipCap + if tx.GasFeeCapIntCmp(tx.GasTipCap()) < 0 { + return core.ErrTipAboveFeeCap + } + // Make sure the transaction is signed properly + if _, err := types.Sender(signer, tx); err != nil { + return ErrInvalidSender + } + // Ensure the transaction has more gas than the bare minimum needed to cover + // the transaction metadata + intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, opts.Config.IsIstanbul(head.Number), opts.Config.IsShanghai(head.Number, head.Time, types.DeserializeHeaderExtraInformation(head).ArbOSFormatVersion)) + if err != nil { + return err + } + if tx.Gas() < intrGas { + return fmt.Errorf("%w: needed %v, allowed %v", core.ErrIntrinsicGas, intrGas, tx.Gas()) + } + // Ensure the gasprice is high enough to cover the requirement of the calling + // pool and/or block producer + if tx.GasTipCapIntCmp(opts.MinTip) < 0 { + return fmt.Errorf("%w: tip needed %v, tip permitted %v", ErrUnderpriced, opts.MinTip, tx.GasTipCap()) + } + // Ensure blob transactions have valid commitments + if tx.Type() == types.BlobTxType { + sidecar := tx.BlobTxSidecar() + if sidecar == nil { + return fmt.Errorf("missing sidecar in blob transaction") + } + // Ensure the number of items in the blob transaction and various side + // data match up before doing any expensive validations + hashes := tx.BlobHashes() + if len(hashes) == 0 { + return fmt.Errorf("blobless blob transaction") + } + if len(hashes) > params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob { + return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob) + } + if err := validateBlobSidecar(hashes, sidecar); err != nil { + return err + } + } + return nil +} + +func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) error { + if len(sidecar.Blobs) != len(hashes) { + return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes)) + } + if len(sidecar.Commitments) != len(hashes) { + return fmt.Errorf("invalid number of %d blob commitments compared to %d blob hashes", len(sidecar.Commitments), len(hashes)) + } + if len(sidecar.Proofs) != len(hashes) { + return fmt.Errorf("invalid number of %d blob proofs compared to %d blob hashes", len(sidecar.Proofs), len(hashes)) + } + // Blob quantities match up, validate that the provers match with the + // transaction hash before getting to the cryptography + hasher := sha256.New() + for i, want := range hashes { + hasher.Write(sidecar.Commitments[i][:]) + hash := hasher.Sum(nil) + hasher.Reset() + + var vhash common.Hash + vhash[0] = params.BlobTxHashVersion + copy(vhash[1:], hash[1:]) + + if vhash != want { + return fmt.Errorf("blob %d: computed hash %#x mismatches transaction one %#x", i, vhash, want) + } + } + // Blob commitments match with the hashes in the transaction, verify the + // blobs themselves via KZG + for i := range sidecar.Blobs { + if err := kzg4844.VerifyBlobProof(sidecar.Blobs[i], sidecar.Commitments[i], sidecar.Proofs[i]); err != nil { + return fmt.Errorf("invalid blob %d: %v", i, err) + } + } + return nil +} + +// ValidationOptionsWithState define certain differences between stateful transaction +// validation across the different pools without having to duplicate those checks. +type ValidationOptionsWithState struct { + State *state.StateDB // State database to check nonces and balances against + + // FirstNonceGap is an optional callback to retrieve the first nonce gap in + // the list of pooled transactions of a specific account. If this method is + // set, nonce gaps will be checked and forbidden. If this method is not set, + // nonce gaps will be ignored and permitted. + FirstNonceGap func(addr common.Address) uint64 + + // UsedAndLeftSlots is a mandatory callback to retrieve the number of tx slots + // used and the number still permitted for an account. New transactions will + // be rejected once the number of remaining slots reaches zero. + UsedAndLeftSlots func(addr common.Address) (int, int) + + // ExistingExpenditure is a mandatory callback to retrieve the cumulative + // cost of the already pooled transactions to check for overdrafts. + ExistingExpenditure func(addr common.Address) *big.Int + + // ExistingCost is a mandatory callback to retrieve an already pooled + // transaction's cost with the given nonce to check for overdrafts. + ExistingCost func(addr common.Address, nonce uint64) *big.Int +} + +// ValidateTransactionWithState is a helper method to check whether a transaction +// is valid according to the pool's internal state checks (balance, nonce, gaps). +// +// This check is public to allow different transaction pools to check the stateful +// rules without duplicating code and running the risk of missed updates. +func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, opts *ValidationOptionsWithState) error { + // Ensure the transaction adheres to nonce ordering + from, err := signer.Sender(tx) // already validated (and cached), but cleaner to check + if err != nil { + log.Error("Transaction sender recovery failed", "err", err) + return err + } + next := opts.State.GetNonce(from) + if next > tx.Nonce() { + return fmt.Errorf("%w: next nonce %v, tx nonce %v", core.ErrNonceTooLow, next, tx.Nonce()) + } + // Ensure the transaction doesn't produce a nonce gap in pools that do not + // support arbitrary orderings + if opts.FirstNonceGap != nil { + if gap := opts.FirstNonceGap(from); gap < tx.Nonce() { + return fmt.Errorf("%w: tx nonce %v, gapped nonce %v", core.ErrNonceTooHigh, tx.Nonce(), gap) + } + } + // Ensure the transactor has enough funds to cover the transaction costs + var ( + balance = opts.State.GetBalance(from) + cost = tx.Cost() + ) + if balance.Cmp(cost) < 0 { + return fmt.Errorf("%w: balance %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, cost, new(big.Int).Sub(cost, balance)) + } + // Ensure the transactor has enough funds to cover for replacements or nonce + // expansions without overdrafts + spent := opts.ExistingExpenditure(from) + if prev := opts.ExistingCost(from, tx.Nonce()); prev != nil { + bump := new(big.Int).Sub(cost, prev) + need := new(big.Int).Add(spent, bump) + if balance.Cmp(need) < 0 { + return fmt.Errorf("%w: balance %v, queued cost %v, tx bumped %v, overshot %v", core.ErrInsufficientFunds, balance, spent, bump, new(big.Int).Sub(need, balance)) + } + } else { + need := new(big.Int).Add(spent, cost) + if balance.Cmp(need) < 0 { + return fmt.Errorf("%w: balance %v, queued cost %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, spent, cost, new(big.Int).Sub(need, balance)) + } + // Transaction takes a new nonce value out of the pool. Ensure it doesn't + // overflow the number of permitted transactions from a single account + // (i.e. max cancellable via out-of-bound transaction). + if used, left := opts.UsedAndLeftSlots(from); left <= 0 { + return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used) + } + } + return nil +} diff --git a/core/types/arb_types.go b/core/types/arb_types.go index 08a0b1b78339..ae329c24bf3f 100644 --- a/core/types/arb_types.go +++ b/core/types/arb_types.go @@ -1,6 +1,7 @@ package types import ( + "bytes" "context" "encoding/binary" "math/big" @@ -8,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/common" ) @@ -92,19 +94,22 @@ func (tx *ArbitrumUnsignedTx) copy() TxData { return cpy } -func (tx *ArbitrumUnsignedTx) chainID() *big.Int { return tx.ChainId } -func (tx *ArbitrumUnsignedTx) accessList() AccessList { return nil } -func (tx *ArbitrumUnsignedTx) data() []byte { return tx.Data } -func (tx *ArbitrumUnsignedTx) gas() uint64 { return tx.Gas } -func (tx *ArbitrumUnsignedTx) gasPrice() *big.Int { return tx.GasFeeCap } -func (tx *ArbitrumUnsignedTx) gasTipCap() *big.Int { return bigZero } -func (tx *ArbitrumUnsignedTx) gasFeeCap() *big.Int { return tx.GasFeeCap } -func (tx *ArbitrumUnsignedTx) value() *big.Int { return tx.Value } -func (tx *ArbitrumUnsignedTx) nonce() uint64 { return tx.Nonce } -func (tx *ArbitrumUnsignedTx) to() *common.Address { return tx.To } -func (tx *ArbitrumUnsignedTx) blobGas() uint64 { return 0 } -func (tx *ArbitrumUnsignedTx) blobGasFeeCap() *big.Int { return nil } -func (tx *ArbitrumUnsignedTx) blobHashes() []common.Hash { return nil } +func (tx *ArbitrumUnsignedTx) chainID() *big.Int { return tx.ChainId } +func (tx *ArbitrumUnsignedTx) accessList() AccessList { return nil } +func (tx *ArbitrumUnsignedTx) data() []byte { return tx.Data } +func (tx *ArbitrumUnsignedTx) gas() uint64 { return tx.Gas } +func (tx *ArbitrumUnsignedTx) gasPrice() *big.Int { return tx.GasFeeCap } +func (tx *ArbitrumUnsignedTx) gasTipCap() *big.Int { return bigZero } +func (tx *ArbitrumUnsignedTx) gasFeeCap() *big.Int { return tx.GasFeeCap } +func (tx *ArbitrumUnsignedTx) value() *big.Int { return tx.Value } +func (tx *ArbitrumUnsignedTx) nonce() uint64 { return tx.Nonce } +func (tx *ArbitrumUnsignedTx) to() *common.Address { return tx.To } +func (tx *ArbitrumUnsignedTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} +func (tx *ArbitrumUnsignedTx) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} func (tx *ArbitrumUnsignedTx) rawSignatureValues() (v, r, s *big.Int) { return bigZero, bigZero, bigZero @@ -162,19 +167,23 @@ func (tx *ArbitrumContractTx) copy() TxData { return cpy } -func (tx *ArbitrumContractTx) chainID() *big.Int { return tx.ChainId } -func (tx *ArbitrumContractTx) accessList() AccessList { return nil } -func (tx *ArbitrumContractTx) data() []byte { return tx.Data } -func (tx *ArbitrumContractTx) gas() uint64 { return tx.Gas } -func (tx *ArbitrumContractTx) gasPrice() *big.Int { return tx.GasFeeCap } -func (tx *ArbitrumContractTx) gasTipCap() *big.Int { return bigZero } -func (tx *ArbitrumContractTx) gasFeeCap() *big.Int { return tx.GasFeeCap } -func (tx *ArbitrumContractTx) value() *big.Int { return tx.Value } -func (tx *ArbitrumContractTx) nonce() uint64 { return 0 } -func (tx *ArbitrumContractTx) to() *common.Address { return tx.To } -func (tx *ArbitrumContractTx) blobGas() uint64 { return 0 } -func (tx *ArbitrumContractTx) blobGasFeeCap() *big.Int { return nil } -func (tx *ArbitrumContractTx) blobHashes() []common.Hash { return nil } +func (tx *ArbitrumContractTx) chainID() *big.Int { return tx.ChainId } +func (tx *ArbitrumContractTx) accessList() AccessList { return nil } +func (tx *ArbitrumContractTx) data() []byte { return tx.Data } +func (tx *ArbitrumContractTx) gas() uint64 { return tx.Gas } +func (tx *ArbitrumContractTx) gasPrice() *big.Int { return tx.GasFeeCap } +func (tx *ArbitrumContractTx) gasTipCap() *big.Int { return bigZero } +func (tx *ArbitrumContractTx) gasFeeCap() *big.Int { return tx.GasFeeCap } +func (tx *ArbitrumContractTx) value() *big.Int { return tx.Value } +func (tx *ArbitrumContractTx) nonce() uint64 { return 0 } +func (tx *ArbitrumContractTx) to() *common.Address { return tx.To } +func (tx *ArbitrumContractTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} +func (tx *ArbitrumContractTx) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} + func (tx *ArbitrumContractTx) rawSignatureValues() (v, r, s *big.Int) { return bigZero, bigZero, bigZero } @@ -242,19 +251,23 @@ func (tx *ArbitrumRetryTx) copy() TxData { return cpy } -func (tx *ArbitrumRetryTx) chainID() *big.Int { return tx.ChainId } -func (tx *ArbitrumRetryTx) accessList() AccessList { return nil } -func (tx *ArbitrumRetryTx) data() []byte { return tx.Data } -func (tx *ArbitrumRetryTx) gas() uint64 { return tx.Gas } -func (tx *ArbitrumRetryTx) gasPrice() *big.Int { return tx.GasFeeCap } -func (tx *ArbitrumRetryTx) gasTipCap() *big.Int { return bigZero } -func (tx *ArbitrumRetryTx) gasFeeCap() *big.Int { return tx.GasFeeCap } -func (tx *ArbitrumRetryTx) value() *big.Int { return tx.Value } -func (tx *ArbitrumRetryTx) nonce() uint64 { return tx.Nonce } -func (tx *ArbitrumRetryTx) to() *common.Address { return tx.To } -func (tx *ArbitrumRetryTx) blobGas() uint64 { return 0 } -func (tx *ArbitrumRetryTx) blobGasFeeCap() *big.Int { return nil } -func (tx *ArbitrumRetryTx) blobHashes() []common.Hash { return nil } +func (tx *ArbitrumRetryTx) chainID() *big.Int { return tx.ChainId } +func (tx *ArbitrumRetryTx) accessList() AccessList { return nil } +func (tx *ArbitrumRetryTx) data() []byte { return tx.Data } +func (tx *ArbitrumRetryTx) gas() uint64 { return tx.Gas } +func (tx *ArbitrumRetryTx) gasPrice() *big.Int { return tx.GasFeeCap } +func (tx *ArbitrumRetryTx) gasTipCap() *big.Int { return bigZero } +func (tx *ArbitrumRetryTx) gasFeeCap() *big.Int { return tx.GasFeeCap } +func (tx *ArbitrumRetryTx) value() *big.Int { return tx.Value } +func (tx *ArbitrumRetryTx) nonce() uint64 { return tx.Nonce } +func (tx *ArbitrumRetryTx) to() *common.Address { return tx.To } +func (tx *ArbitrumRetryTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} +func (tx *ArbitrumRetryTx) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} + func (tx *ArbitrumRetryTx) rawSignatureValues() (v, r, s *big.Int) { return bigZero, bigZero, bigZero } @@ -327,18 +340,22 @@ func (tx *ArbitrumSubmitRetryableTx) copy() TxData { return cpy } -func (tx *ArbitrumSubmitRetryableTx) chainID() *big.Int { return tx.ChainId } -func (tx *ArbitrumSubmitRetryableTx) accessList() AccessList { return nil } -func (tx *ArbitrumSubmitRetryableTx) gas() uint64 { return tx.Gas } -func (tx *ArbitrumSubmitRetryableTx) gasPrice() *big.Int { return tx.GasFeeCap } -func (tx *ArbitrumSubmitRetryableTx) gasTipCap() *big.Int { return big.NewInt(0) } -func (tx *ArbitrumSubmitRetryableTx) gasFeeCap() *big.Int { return tx.GasFeeCap } -func (tx *ArbitrumSubmitRetryableTx) value() *big.Int { return common.Big0 } -func (tx *ArbitrumSubmitRetryableTx) nonce() uint64 { return 0 } -func (tx *ArbitrumSubmitRetryableTx) to() *common.Address { return &ArbRetryableTxAddress } -func (tx *ArbitrumSubmitRetryableTx) blobGas() uint64 { return 0 } -func (tx *ArbitrumSubmitRetryableTx) blobGasFeeCap() *big.Int { return nil } -func (tx *ArbitrumSubmitRetryableTx) blobHashes() []common.Hash { return nil } +func (tx *ArbitrumSubmitRetryableTx) chainID() *big.Int { return tx.ChainId } +func (tx *ArbitrumSubmitRetryableTx) accessList() AccessList { return nil } +func (tx *ArbitrumSubmitRetryableTx) gas() uint64 { return tx.Gas } +func (tx *ArbitrumSubmitRetryableTx) gasPrice() *big.Int { return tx.GasFeeCap } +func (tx *ArbitrumSubmitRetryableTx) gasTipCap() *big.Int { return big.NewInt(0) } +func (tx *ArbitrumSubmitRetryableTx) gasFeeCap() *big.Int { return tx.GasFeeCap } +func (tx *ArbitrumSubmitRetryableTx) value() *big.Int { return common.Big0 } +func (tx *ArbitrumSubmitRetryableTx) nonce() uint64 { return 0 } +func (tx *ArbitrumSubmitRetryableTx) to() *common.Address { return &ArbRetryableTxAddress } +func (tx *ArbitrumSubmitRetryableTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} +func (tx *ArbitrumSubmitRetryableTx) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} + func (tx *ArbitrumSubmitRetryableTx) rawSignatureValues() (v, r, s *big.Int) { return bigZero, bigZero, bigZero } @@ -411,19 +428,22 @@ func (d *ArbitrumDepositTx) copy() TxData { return tx } -func (d *ArbitrumDepositTx) chainID() *big.Int { return d.ChainId } -func (d *ArbitrumDepositTx) accessList() AccessList { return nil } -func (d *ArbitrumDepositTx) data() []byte { return nil } -func (d *ArbitrumDepositTx) gas() uint64 { return 0 } -func (d *ArbitrumDepositTx) gasPrice() *big.Int { return bigZero } -func (d *ArbitrumDepositTx) gasTipCap() *big.Int { return bigZero } -func (d *ArbitrumDepositTx) gasFeeCap() *big.Int { return bigZero } -func (d *ArbitrumDepositTx) value() *big.Int { return d.Value } -func (d *ArbitrumDepositTx) nonce() uint64 { return 0 } -func (d *ArbitrumDepositTx) to() *common.Address { return &d.To } -func (d *ArbitrumDepositTx) blobGas() uint64 { return 0 } -func (d *ArbitrumDepositTx) blobGasFeeCap() *big.Int { return nil } -func (d *ArbitrumDepositTx) blobHashes() []common.Hash { return nil } +func (d *ArbitrumDepositTx) chainID() *big.Int { return d.ChainId } +func (d *ArbitrumDepositTx) accessList() AccessList { return nil } +func (d *ArbitrumDepositTx) data() []byte { return nil } +func (d *ArbitrumDepositTx) gas() uint64 { return 0 } +func (d *ArbitrumDepositTx) gasPrice() *big.Int { return bigZero } +func (d *ArbitrumDepositTx) gasTipCap() *big.Int { return bigZero } +func (d *ArbitrumDepositTx) gasFeeCap() *big.Int { return bigZero } +func (d *ArbitrumDepositTx) value() *big.Int { return d.Value } +func (d *ArbitrumDepositTx) nonce() uint64 { return 0 } +func (d *ArbitrumDepositTx) to() *common.Address { return &d.To } +func (d *ArbitrumDepositTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, d) +} +func (d *ArbitrumDepositTx) decode(input []byte) error { + return rlp.DecodeBytes(input, d) +} func (d *ArbitrumDepositTx) rawSignatureValues() (v, r, s *big.Int) { return bigZero, bigZero, bigZero @@ -453,19 +473,22 @@ func (t *ArbitrumInternalTx) copy() TxData { } } -func (t *ArbitrumInternalTx) chainID() *big.Int { return t.ChainId } -func (t *ArbitrumInternalTx) accessList() AccessList { return nil } -func (t *ArbitrumInternalTx) data() []byte { return t.Data } -func (t *ArbitrumInternalTx) gas() uint64 { return 0 } -func (t *ArbitrumInternalTx) gasPrice() *big.Int { return bigZero } -func (t *ArbitrumInternalTx) gasTipCap() *big.Int { return bigZero } -func (t *ArbitrumInternalTx) gasFeeCap() *big.Int { return bigZero } -func (t *ArbitrumInternalTx) value() *big.Int { return common.Big0 } -func (t *ArbitrumInternalTx) nonce() uint64 { return 0 } -func (t *ArbitrumInternalTx) to() *common.Address { return &ArbosAddress } -func (t *ArbitrumInternalTx) blobGas() uint64 { return 0 } -func (t *ArbitrumInternalTx) blobGasFeeCap() *big.Int { return nil } -func (t *ArbitrumInternalTx) blobHashes() []common.Hash { return nil } +func (t *ArbitrumInternalTx) chainID() *big.Int { return t.ChainId } +func (t *ArbitrumInternalTx) accessList() AccessList { return nil } +func (t *ArbitrumInternalTx) data() []byte { return t.Data } +func (t *ArbitrumInternalTx) gas() uint64 { return 0 } +func (t *ArbitrumInternalTx) gasPrice() *big.Int { return bigZero } +func (t *ArbitrumInternalTx) gasTipCap() *big.Int { return bigZero } +func (t *ArbitrumInternalTx) gasFeeCap() *big.Int { return bigZero } +func (t *ArbitrumInternalTx) value() *big.Int { return common.Big0 } +func (t *ArbitrumInternalTx) nonce() uint64 { return 0 } +func (t *ArbitrumInternalTx) to() *common.Address { return &ArbosAddress } +func (t *ArbitrumInternalTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, t) +} +func (t *ArbitrumInternalTx) decode(input []byte) error { + return rlp.DecodeBytes(input, t) +} func (d *ArbitrumInternalTx) rawSignatureValues() (v, r, s *big.Int) { return bigZero, bigZero, bigZero @@ -504,7 +527,7 @@ func (info HeaderInfo) UpdateHeaderWithInfo(header *Header) { } func DeserializeHeaderExtraInformation(header *Header) HeaderInfo { - if header.BaseFee == nil || header.BaseFee.Sign() == 0 || len(header.Extra) != 32 || header.Difficulty.Cmp(common.Big1) != 0 { + if header == nil || header.BaseFee == nil || header.BaseFee.Sign() == 0 || len(header.Extra) != 32 || header.Difficulty.Cmp(common.Big1) != 0 { // imported blocks have no base fee // The genesis block doesn't have an ArbOS encoded extra field return HeaderInfo{} diff --git a/core/types/arbitrum_legacy_tx.go b/core/types/arbitrum_legacy_tx.go index 03e6ab76b2f4..12696704766d 100644 --- a/core/types/arbitrum_legacy_tx.go +++ b/core/types/arbitrum_legacy_tx.go @@ -49,6 +49,14 @@ func (tx *ArbitrumLegacyTxData) copy() TxData { func (tx *ArbitrumLegacyTxData) txType() byte { return ArbitrumLegacyTxType } +func (tx *ArbitrumLegacyTxData) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} + +func (tx *ArbitrumLegacyTxData) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} + func (tx *ArbitrumLegacyTxData) EncodeOnlyLegacyInto(w *bytes.Buffer) { rlp.Encode(w, tx.LegacyTx) } diff --git a/core/types/block.go b/core/types/block.go index a1a14f5b1657..1a357baa3a41 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -85,26 +85,28 @@ type Header struct { // WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers. WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` - // ExcessDataGas was added by EIP-4844 and is ignored in legacy headers. - ExcessDataGas *big.Int `json:"excessDataGas" rlp:"optional"` + // BlobGasUsed was added by EIP-4844 and is ignored in legacy headers. + BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"` - /* - TODO (MariusVanDerWijden) Add this field once needed - // Random was added during the merge and contains the BeaconState randomness - Random common.Hash `json:"random" rlp:"optional"` - */ + // ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers. + ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"` + + // ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers. + ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` } // field type overrides for gencodec type headerMarshaling struct { - Difficulty *hexutil.Big - Number *hexutil.Big - GasLimit hexutil.Uint64 - GasUsed hexutil.Uint64 - Time hexutil.Uint64 - Extra hexutil.Bytes - BaseFee *hexutil.Big - Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON + Difficulty *hexutil.Big + Number *hexutil.Big + GasLimit hexutil.Uint64 + GasUsed hexutil.Uint64 + Time hexutil.Uint64 + Extra hexutil.Bytes + BaseFee *hexutil.Big + Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON + BlobGasUsed *hexutil.Uint64 + ExcessBlobGas *hexutil.Uint64 } // Hash returns the block hash of the header, which is simply the keccak256 hash of its @@ -152,10 +154,10 @@ 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 { - if h.WithdrawalsHash == nil { - return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash + if h.WithdrawalsHash != nil { + return h.TxHash == EmptyTxsHash && *h.WithdrawalsHash == EmptyWithdrawalsHash } - return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash && *h.WithdrawalsHash == EmptyWithdrawalsHash + return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash } // EmptyReceipts returns true if there are no receipts for this header/block. @@ -171,7 +173,23 @@ type Body struct { Withdrawals []*Withdrawal `rlp:"optional"` } -// Block represents an entire block in the Ethereum blockchain. +// Block represents an Ethereum block. +// +// Note the Block type tries to be 'immutable', and contains certain caches that rely +// on that. The rules around block immutability are as follows: +// +// - We copy all data when the block is constructed. This makes references held inside +// the block independent of whatever value was passed in. +// +// - We copy all header data on access. This is because any change to the header would mess +// up the cached hash and size values in the block. Calling code is expected to take +// advantage of this to avoid over-allocating! +// +// - When new body data is attached to the block, a shallow copy of the block is returned. +// This ensures block modifications are race-free. +// +// - We do not copy body data on access because it does not affect the caches, and also +// because it would be too expensive. type Block struct { header *Header uncles []*Header @@ -196,9 +214,8 @@ type extblock struct { Withdrawals []*Withdrawal `rlp:"optional"` } -// NewBlock creates a new block. The input data is copied, -// changes to header and to the field values will not affect the -// block. +// NewBlock creates a new block. The input data is copied, changes to header and to the +// field values will not affect the block. // // The values of TxHash, UncleHash, ReceiptHash and Bloom in header // are ignored and set to values derived from the given txs, uncles @@ -235,13 +252,11 @@ func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []* return b } -// NewBlockWithWithdrawals creates a new block with withdrawals. The input data -// is copied, changes to header and to the field values will not -// affect the block. +// NewBlockWithWithdrawals creates a new block with withdrawals. The input data is copied, +// changes to header and to the field values will not affect the block. // -// The values of TxHash, UncleHash, ReceiptHash and Bloom in header -// are ignored and set to values derived from the given txs, uncles -// and receipts. +// The values of TxHash, UncleHash, ReceiptHash and Bloom in header are ignored and set to +// values derived from the given txs, uncles and receipts. func NewBlockWithWithdrawals(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, withdrawals []*Withdrawal, hasher TrieHasher) *Block { b := NewBlock(header, txs, uncles, receipts, hasher) @@ -257,15 +272,7 @@ func NewBlockWithWithdrawals(header *Header, txs []*Transaction, uncles []*Heade return b.WithWithdrawals(withdrawals) } -// NewBlockWithHeader creates a block with the given header data. The -// header data is copied, changes to header and to the field values -// will not affect the block. -func NewBlockWithHeader(header *Header) *Block { - return &Block{header: CopyHeader(header)} -} - -// CopyHeader creates a deep copy of a block header to prevent side effects from -// modifying a header variable. +// CopyHeader creates a deep copy of a block header. func CopyHeader(h *Header) *Header { cpy := *h if cpy.Difficulty = new(big.Int); h.Difficulty != nil { @@ -285,10 +292,22 @@ func CopyHeader(h *Header) *Header { cpy.WithdrawalsHash = new(common.Hash) *cpy.WithdrawalsHash = *h.WithdrawalsHash } + if h.ExcessBlobGas != nil { + cpy.ExcessBlobGas = new(uint64) + *cpy.ExcessBlobGas = *h.ExcessBlobGas + } + if h.BlobGasUsed != nil { + cpy.BlobGasUsed = new(uint64) + *cpy.BlobGasUsed = *h.BlobGasUsed + } + if h.ParentBeaconRoot != nil { + cpy.ParentBeaconRoot = new(common.Hash) + *cpy.ParentBeaconRoot = *h.ParentBeaconRoot + } return &cpy } -// DecodeRLP decodes the Ethereum +// DecodeRLP decodes a block from RLP. func (b *Block) DecodeRLP(s *rlp.Stream) error { var eb extblock _, size, _ := s.Kind() @@ -300,9 +319,9 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error { return nil } -// EncodeRLP serializes b into the Ethereum RLP block format. +// EncodeRLP serializes a block as RLP. func (b *Block) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, extblock{ + return rlp.Encode(w, &extblock{ Header: b.header, Txs: b.transactions, Uncles: b.uncles, @@ -310,10 +329,18 @@ func (b *Block) EncodeRLP(w io.Writer) error { }) } -// TODO: copies +// Body returns the non-header content of the block. +// Note the returned data is not an independent copy. +func (b *Block) Body() *Body { + return &Body{b.transactions, b.uncles, b.withdrawals} +} + +// 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) Transaction(hash common.Hash) *Transaction { for _, transaction := range b.transactions { @@ -324,6 +351,13 @@ func (b *Block) Transaction(hash common.Hash) *Transaction { return nil } +// Header returns the block header (as a copy). +func (b *Block) Header() *Header { + return CopyHeader(b.header) +} + +// Header value accessors. These do copy! + func (b *Block) Number() *big.Int { return new(big.Int).Set(b.header.Number) } func (b *Block) GasLimit() uint64 { return b.header.GasLimit } func (b *Block) GasUsed() uint64 { return b.header.GasUsed } @@ -349,14 +383,25 @@ func (b *Block) BaseFee() *big.Int { return new(big.Int).Set(b.header.BaseFee) } -func (b *Block) Withdrawals() Withdrawals { - return b.withdrawals -} +func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot } -func (b *Block) Header() *Header { return CopyHeader(b.header) } +func (b *Block) ExcessBlobGas() *uint64 { + var excessBlobGas *uint64 + if b.header.ExcessBlobGas != nil { + excessBlobGas = new(uint64) + *excessBlobGas = *b.header.ExcessBlobGas + } + return excessBlobGas +} -// Body returns the non-header content of the block. -func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles, b.withdrawals} } +func (b *Block) BlobGasUsed() *uint64 { + var blobGasUsed *uint64 + if b.header.BlobGasUsed != nil { + blobGasUsed = new(uint64) + *blobGasUsed = *b.header.BlobGasUsed + } + return blobGasUsed +} // Size returns the true RLP encoded storage size of the block, either by encoding // and returning it, or returning a previously cached value. @@ -390,25 +435,31 @@ func CalcUncleHash(uncles []*Header) common.Hash { return rlpHash(uncles) } +// NewBlockWithHeader creates a block with the given header data. The +// header data is copied, changes to header and to the field values +// will not affect the block. +func NewBlockWithHeader(header *Header) *Block { + return &Block{header: CopyHeader(header)} +} + // WithSeal returns a new block with the data from b but the header replaced with // the sealed one. func (b *Block) WithSeal(header *Header) *Block { - cpy := *header - return &Block{ - header: &cpy, + header: CopyHeader(header), transactions: b.transactions, uncles: b.uncles, withdrawals: b.withdrawals, } } -// WithBody returns a new block with the given transaction and uncle contents. +// WithBody returns a copy of the block with the given transaction and uncle contents. func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block { block := &Block{ - header: CopyHeader(b.header), + header: b.header, transactions: make([]*Transaction, len(transactions)), uncles: make([]*Header, len(uncles)), + withdrawals: b.withdrawals, } copy(block.transactions, transactions) for i := range uncles { @@ -417,13 +468,18 @@ func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block { return block } -// WithWithdrawals sets the withdrawal contents of a block, does not return a new block. +// WithWithdrawals returns a copy of the block containing the given withdrawals. func (b *Block) WithWithdrawals(withdrawals []*Withdrawal) *Block { + block := &Block{ + header: b.header, + transactions: b.transactions, + uncles: b.uncles, + } if withdrawals != nil { - b.withdrawals = make([]*Withdrawal, len(withdrawals)) - copy(b.withdrawals, withdrawals) + block.withdrawals = make([]*Withdrawal, len(withdrawals)) + copy(block.withdrawals, withdrawals) } - return b + return block } // Hash returns the keccak256 hash of b's header. diff --git a/core/types/block_test.go b/core/types/block_test.go index 966015eb015d..cf0b1dd85c1e 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -18,7 +18,6 @@ package types import ( "bytes" - "hash" "math/big" "reflect" "testing" @@ -26,9 +25,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/blocktest" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" ) // from bcValidBlockTest.json, "SimpleTx" @@ -217,31 +216,6 @@ func BenchmarkEncodeBlock(b *testing.B) { } } -// testHasher is the helper tool for transaction/receipt list hashing. -// The original hasher is trie, in order to get rid of import cycle, -// use the testing hasher instead. -type testHasher struct { - hasher hash.Hash -} - -func newHasher() *testHasher { - return &testHasher{hasher: sha3.NewLegacyKeccak256()} -} - -func (h *testHasher) Reset() { - h.hasher.Reset() -} - -func (h *testHasher) Update(key, val []byte) error { - h.hasher.Write(key) - h.hasher.Write(val) - return nil -} - -func (h *testHasher) Hash() common.Hash { - return common.BytesToHash(h.hasher.Sum(nil)) -} - func makeBenchBlock() *Block { var ( key, _ = crypto.GenerateKey() @@ -280,7 +254,7 @@ func makeBenchBlock() *Block { Extra: []byte("benchmark uncle"), } } - return NewBlock(header, txs, uncles, receipts, newHasher()) + return NewBlock(header, txs, uncles, receipts, blocktest.NewHasher()) } func TestRlpDecodeParentHash(t *testing.T) { diff --git a/core/types/gen_account_rlp.go b/core/types/gen_account_rlp.go index 5181d884112f..3fb36f403875 100644 --- a/core/types/gen_account_rlp.go +++ b/core/types/gen_account_rlp.go @@ -1,8 +1,5 @@ // Code generated by rlpgen. DO NOT EDIT. -//go:build !norlpgen -// +build !norlpgen - package types import "github.com/ethereum/go-ethereum/rlp" diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index 73287257bd1c..fb1f915d01d9 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -16,25 +16,27 @@ 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"` - ExcessDataGas *big.Int `json:"excessDataGas" 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"` + Hash common.Hash `json:"hash"` } var enc Header enc.ParentHash = h.ParentHash @@ -54,7 +56,9 @@ func (h Header) MarshalJSON() ([]byte, error) { enc.Nonce = h.Nonce enc.BaseFee = (*hexutil.Big)(h.BaseFee) enc.WithdrawalsHash = h.WithdrawalsHash - enc.ExcessDataGas = h.ExcessDataGas + enc.BlobGasUsed = (*hexutil.Uint64)(h.BlobGasUsed) + enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas) + enc.ParentBeaconRoot = h.ParentBeaconRoot enc.Hash = h.Hash() return json.Marshal(&enc) } @@ -62,24 +66,26 @@ 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"` - ExcessDataGas *big.Int `json:"excessDataGas" 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"` } var dec Header if err := json.Unmarshal(input, &dec); err != nil { @@ -148,8 +154,14 @@ func (h *Header) UnmarshalJSON(input []byte) error { if dec.WithdrawalsHash != nil { h.WithdrawalsHash = dec.WithdrawalsHash } - if dec.ExcessDataGas != nil { - h.ExcessDataGas = dec.ExcessDataGas + if dec.BlobGasUsed != nil { + h.BlobGasUsed = (*uint64)(dec.BlobGasUsed) + } + if dec.ExcessBlobGas != nil { + h.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) + } + if dec.ParentBeaconRoot != nil { + h.ParentBeaconRoot = dec.ParentBeaconRoot } return nil } diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go index 005f6c2ad45d..ed6a1a002cdb 100644 --- a/core/types/gen_header_rlp.go +++ b/core/types/gen_header_rlp.go @@ -1,8 +1,5 @@ // Code generated by rlpgen. DO NOT EDIT. -//go:build !norlpgen -// +build !norlpgen - package types import "github.com/ethereum/go-ethereum/rlp" @@ -42,8 +39,10 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBytes(obj.Nonce[:]) _tmp1 := obj.BaseFee != nil _tmp2 := obj.WithdrawalsHash != nil - _tmp3 := obj.ExcessDataGas != nil - if _tmp1 || _tmp2 || _tmp3 { + _tmp3 := obj.BlobGasUsed != nil + _tmp4 := obj.ExcessBlobGas != nil + _tmp5 := obj.ParentBeaconRoot != nil + if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 { if obj.BaseFee == nil { w.Write(rlp.EmptyString) } else { @@ -53,21 +52,32 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBigInt(obj.BaseFee) } } - if _tmp2 || _tmp3 { + if _tmp2 || _tmp3 || _tmp4 || _tmp5 { if obj.WithdrawalsHash == nil { w.Write([]byte{0x80}) } else { w.WriteBytes(obj.WithdrawalsHash[:]) } } - if _tmp3 { - if obj.ExcessDataGas == nil { - w.Write(rlp.EmptyString) + if _tmp3 || _tmp4 || _tmp5 { + if obj.BlobGasUsed == nil { + w.Write([]byte{0x80}) } else { - if obj.ExcessDataGas.Sign() == -1 { - return rlp.ErrNegativeBigInt - } - w.WriteBigInt(obj.ExcessDataGas) + w.WriteUint64((*obj.BlobGasUsed)) + } + } + if _tmp4 || _tmp5 { + if obj.ExcessBlobGas == nil { + w.Write([]byte{0x80}) + } else { + w.WriteUint64((*obj.ExcessBlobGas)) + } + } + if _tmp5 { + if obj.ParentBeaconRoot == nil { + w.Write([]byte{0x80}) + } else { + w.WriteBytes(obj.ParentBeaconRoot[:]) } } w.ListEnd(_tmp0) diff --git a/core/types/gen_log_json.go b/core/types/gen_log_json.go index 90e1c14d9017..3ffa9c2feb1a 100644 --- a/core/types/gen_log_json.go +++ b/core/types/gen_log_json.go @@ -18,12 +18,12 @@ func (l Log) MarshalJSON() ([]byte, error) { Address common.Address `json:"address" gencodec:"required"` Topics []common.Hash `json:"topics" gencodec:"required"` Data hexutil.Bytes `json:"data" gencodec:"required"` - BlockNumber hexutil.Uint64 `json:"blockNumber"` - TxHash common.Hash `json:"transactionHash" gencodec:"required"` - TxIndex hexutil.Uint `json:"transactionIndex"` - BlockHash common.Hash `json:"blockHash"` - Index hexutil.Uint `json:"logIndex"` - Removed bool `json:"removed"` + BlockNumber hexutil.Uint64 `json:"blockNumber" rlp:"-"` + TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"` + TxIndex hexutil.Uint `json:"transactionIndex" rlp:"-"` + BlockHash common.Hash `json:"blockHash" rlp:"-"` + Index hexutil.Uint `json:"logIndex" rlp:"-"` + Removed bool `json:"removed" rlp:"-"` } var enc Log enc.Address = l.Address @@ -44,12 +44,12 @@ func (l *Log) UnmarshalJSON(input []byte) error { Address *common.Address `json:"address" gencodec:"required"` Topics []common.Hash `json:"topics" gencodec:"required"` Data *hexutil.Bytes `json:"data" gencodec:"required"` - BlockNumber *hexutil.Uint64 `json:"blockNumber"` - TxHash *common.Hash `json:"transactionHash" gencodec:"required"` - TxIndex *hexutil.Uint `json:"transactionIndex"` - BlockHash *common.Hash `json:"blockHash"` - Index *hexutil.Uint `json:"logIndex"` - Removed *bool `json:"removed"` + BlockNumber *hexutil.Uint64 `json:"blockNumber" rlp:"-"` + TxHash *common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"` + TxIndex *hexutil.Uint `json:"transactionIndex" rlp:"-"` + BlockHash *common.Hash `json:"blockHash" rlp:"-"` + Index *hexutil.Uint `json:"logIndex" rlp:"-"` + Removed *bool `json:"removed" rlp:"-"` } var dec Log if err := json.Unmarshal(input, &dec); err != nil { diff --git a/core/types/gen_log_rlp.go b/core/types/gen_log_rlp.go index 4a6c6b0094f8..7e8962966842 100644 --- a/core/types/gen_log_rlp.go +++ b/core/types/gen_log_rlp.go @@ -1,14 +1,11 @@ // Code generated by rlpgen. DO NOT EDIT. -//go:build !norlpgen -// +build !norlpgen - package types import "github.com/ethereum/go-ethereum/rlp" import "io" -func (obj *rlpLog) EncodeRLP(_w io.Writer) error { +func (obj *Log) EncodeRLP(_w io.Writer) error { w := rlp.NewEncoderBuffer(_w) _tmp0 := w.List() w.WriteBytes(obj.Address[:]) diff --git a/core/types/gen_receipt_json.go b/core/types/gen_receipt_json.go index 9c507117e710..152ecf33ce8d 100644 --- a/core/types/gen_receipt_json.go +++ b/core/types/gen_receipt_json.go @@ -27,6 +27,8 @@ func (r Receipt) MarshalJSON() ([]byte, error) { ContractAddress common.Address `json:"contractAddress"` GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice"` + BlobGasUsed hexutil.Uint64 `json:"blobGasUsed,omitempty"` + BlobGasPrice *hexutil.Big `json:"blobGasPrice,omitempty"` BlockHash common.Hash `json:"blockHash,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` TransactionIndex hexutil.Uint `json:"transactionIndex"` @@ -43,6 +45,8 @@ func (r Receipt) MarshalJSON() ([]byte, error) { enc.ContractAddress = r.ContractAddress enc.GasUsed = hexutil.Uint64(r.GasUsed) enc.EffectiveGasPrice = (*hexutil.Big)(r.EffectiveGasPrice) + enc.BlobGasUsed = hexutil.Uint64(r.BlobGasUsed) + enc.BlobGasPrice = (*hexutil.Big)(r.BlobGasPrice) enc.BlockHash = r.BlockHash enc.BlockNumber = (*hexutil.Big)(r.BlockNumber) enc.TransactionIndex = hexutil.Uint(r.TransactionIndex) @@ -63,6 +67,8 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { ContractAddress *common.Address `json:"contractAddress"` GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` EffectiveGasPrice *hexutil.Big `json:"effectiveGasPrice"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed,omitempty"` + BlobGasPrice *hexutil.Big `json:"blobGasPrice,omitempty"` BlockHash *common.Hash `json:"blockHash,omitempty"` BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` TransactionIndex *hexutil.Uint `json:"transactionIndex"` @@ -109,6 +115,12 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { if dec.EffectiveGasPrice != nil { r.EffectiveGasPrice = (*big.Int)(dec.EffectiveGasPrice) } + if dec.BlobGasUsed != nil { + r.BlobGasUsed = uint64(*dec.BlobGasUsed) + } + if dec.BlobGasPrice != nil { + r.BlobGasPrice = (*big.Int)(dec.BlobGasPrice) + } if dec.BlockHash != nil { r.BlockHash = *dec.BlockHash } diff --git a/core/types/gen_withdrawal_rlp.go b/core/types/gen_withdrawal_rlp.go index d0b4e0147a0d..6a97c04c8153 100644 --- a/core/types/gen_withdrawal_rlp.go +++ b/core/types/gen_withdrawal_rlp.go @@ -1,8 +1,5 @@ // Code generated by rlpgen. DO NOT EDIT. -//go:build !norlpgen -// +build !norlpgen - package types import "github.com/ethereum/go-ethereum/rlp" diff --git a/core/types/hashes.go b/core/types/hashes.go index 3bad430be571..3a787aa136f8 100644 --- a/core/types/hashes.go +++ b/core/types/hashes.go @@ -19,6 +19,7 @@ package types import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" ) var ( @@ -40,3 +41,13 @@ var ( // EmptyWithdrawalsHash is the known hash of the empty withdrawal set. EmptyWithdrawalsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") ) + +// TrieRootHash returns the hash itself if it's non-empty or the predefined +// emptyHash one instead. +func TrieRootHash(hash common.Hash) common.Hash { + if hash == (common.Hash{}) { + log.Error("Zero trie root hash!") + return EmptyRootHash + } + return hash +} diff --git a/core/types/hashing.go b/core/types/hashing.go index fbdeaf0d0793..224d7a87eafc 100644 --- a/core/types/hashing.go +++ b/core/types/hashing.go @@ -18,6 +18,8 @@ package types import ( "bytes" + "fmt" + "math" "sync" "github.com/ethereum/go-ethereum/common" @@ -36,6 +38,22 @@ var encodeBufferPool = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } +// getPooledBuffer retrieves a buffer from the pool and creates a byte slice of the +// requested size from it. +// +// The caller should return the *bytes.Buffer object back into encodeBufferPool after use! +// The returned byte slice must not be used after returning the buffer. +func getPooledBuffer(size uint64) ([]byte, *bytes.Buffer, error) { + if size > math.MaxInt { + return nil, nil, fmt.Errorf("can't get buffer of size %d", size) + } + buf := encodeBufferPool.Get().(*bytes.Buffer) + buf.Reset() + buf.Grow(int(size)) + b := buf.Bytes()[:int(size)] + return b, buf, nil +} + // rlpHash encodes x and hashes the encoded bytes. func rlpHash(x interface{}) (h common.Hash) { sha := hasherPool.Get().(crypto.KeccakState) @@ -77,7 +95,7 @@ type DerivableList interface { func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte { buf.Reset() list.EncodeIndex(i, buf) - // It's really unfortunate that we need to do perform this copy. + // It's really unfortunate that we need to perform this copy. // StackTrie holds onto the values until Hash is called, so the values // written to it must not alias. return common.CopyBytes(buf.Bytes()) diff --git a/core/types/hashing_test.go b/core/types/hashing_test.go index c5b9f690d801..d2a98ed7bf55 100644 --- a/core/types/hashing_test.go +++ b/core/types/hashing_test.go @@ -39,7 +39,7 @@ func TestDeriveSha(t *testing.T) { t.Fatal(err) } for len(txs) < 1000 { - exp := types.DeriveSha(txs, trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase()))) + exp := types.DeriveSha(txs, trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase(), nil))) got := types.DeriveSha(txs, trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp) @@ -86,7 +86,7 @@ func BenchmarkDeriveSha200(b *testing.B) { b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - exp = types.DeriveSha(txs, trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase()))) + exp = types.DeriveSha(txs, trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase(), nil))) } }) @@ -107,7 +107,7 @@ func TestFuzzDeriveSha(t *testing.T) { rndSeed := mrand.Int() for i := 0; i < 10; i++ { seed := rndSeed + i - exp := types.DeriveSha(newDummy(i), trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase()))) + exp := types.DeriveSha(newDummy(i), trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase(), nil))) got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { printList(newDummy(seed)) @@ -135,7 +135,7 @@ func TestDerivableList(t *testing.T) { }, } for i, tc := range tcs[1:] { - exp := types.DeriveSha(flatList(tc), trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase()))) + exp := types.DeriveSha(flatList(tc), trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase(), nil))) got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil)) if !bytes.Equal(got[:], exp[:]) { t.Fatalf("case %d: got %x exp %x", i, got, exp) diff --git a/core/types/log.go b/core/types/log.go index e48919136889..54c7ff6372c8 100644 --- a/core/types/log.go +++ b/core/types/log.go @@ -17,13 +17,11 @@ package types import ( - "io" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/rlp" ) +//go:generate go run ../../rlp/rlpgen -type Log -out gen_log_rlp.go //go:generate go run github.com/fjl/gencodec -type Log -field-override logMarshaling -out gen_log_json.go // Log represents a contract log event. These events are generated by the LOG opcode and @@ -40,19 +38,19 @@ type Log struct { // Derived fields. These fields are filled in by the node // but not secured by consensus. // block in which the transaction was included - BlockNumber uint64 `json:"blockNumber"` + BlockNumber uint64 `json:"blockNumber" rlp:"-"` // hash of the transaction - TxHash common.Hash `json:"transactionHash" gencodec:"required"` + TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"` // index of the transaction in the block - TxIndex uint `json:"transactionIndex"` + TxIndex uint `json:"transactionIndex" rlp:"-"` // hash of the block in which the transaction was included - BlockHash common.Hash `json:"blockHash"` + BlockHash common.Hash `json:"blockHash" rlp:"-"` // index of the log in the block - Index uint `json:"logIndex"` + Index uint `json:"logIndex" rlp:"-"` // The Removed field is true if this log was reverted due to a chain reorganisation. // You must pay attention to this field if you receive logs through a filter query. - Removed bool `json:"removed"` + Removed bool `json:"removed" rlp:"-"` } type logMarshaling struct { @@ -61,28 +59,3 @@ type logMarshaling struct { TxIndex hexutil.Uint Index hexutil.Uint } - -//go:generate go run ../../rlp/rlpgen -type rlpLog -out gen_log_rlp.go - -// rlpLog is used to RLP-encode both the consensus and storage formats. -type rlpLog struct { - Address common.Address - Topics []common.Hash - Data []byte -} - -// EncodeRLP implements rlp.Encoder. -func (l *Log) EncodeRLP(w io.Writer) error { - rl := rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data} - return rlp.Encode(w, &rl) -} - -// DecodeRLP implements rlp.Decoder. -func (l *Log) DecodeRLP(s *rlp.Stream) error { - var dec rlpLog - err := s.Decode(&dec) - if err == nil { - l.Address, l.Topics, l.Data = dec.Address, dec.Topics, dec.Data - } - return err -} diff --git a/core/types/log_test.go b/core/types/log_test.go index 0e56acfe4aa3..02eef3ecd43f 100644 --- a/core/types/log_test.go +++ b/core/types/log_test.go @@ -18,7 +18,7 @@ package types import ( "encoding/json" - "fmt" + "errors" "reflect" "testing" @@ -97,7 +97,7 @@ var unmarshalLogTests = map[string]struct { }, "missing data": { input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`, - wantError: fmt.Errorf("missing required field 'data' for Log"), + wantError: errors.New("missing required field 'data' for Log"), }, } diff --git a/core/types/receipt.go b/core/types/receipt.go index 34413f018bba..ff5c1b97e546 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -67,6 +67,8 @@ type Receipt struct { ContractAddress common.Address `json:"contractAddress"` GasUsed uint64 `json:"gasUsed" gencodec:"required"` EffectiveGasPrice *big.Int `json:"effectiveGasPrice"` // required, but tag omitted for backwards compatibility + BlobGasUsed uint64 `json:"blobGasUsed,omitempty"` + BlobGasPrice *big.Int `json:"blobGasPrice,omitempty"` // Inclusion information: These fields provide information about the inclusion of the // transaction corresponding to this receipt. @@ -85,6 +87,8 @@ type receiptMarshaling struct { CumulativeGasUsed hexutil.Uint64 GasUsed hexutil.Uint64 EffectiveGasPrice *hexutil.Big + BlobGasUsed hexutil.Uint64 + BlobGasPrice *hexutil.Big BlockNumber *hexutil.Big TransactionIndex hexutil.Uint } @@ -168,7 +172,7 @@ func (r *Receipt) MarshalBinary() ([]byte, error) { // DecodeRLP implements rlp.Decoder, and loads the consensus fields of a receipt // from an RLP stream. func (r *Receipt) DecodeRLP(s *rlp.Stream) error { - kind, _, err := s.Kind() + kind, size, err := s.Kind() switch { case err != nil: return err @@ -180,12 +184,18 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error { } r.Type = LegacyTxType return r.setFromRLP(dec) + case kind == rlp.Byte: + return errShortTypedReceipt default: // It's an EIP-2718 typed tx receipt. - b, err := s.Bytes() + b, buf, err := getPooledBuffer(size) if err != nil { return err } + defer encodeBufferPool.Put(buf) + if err := s.ReadBytes(b); err != nil { + return err + } return r.decodeTyped(b) } } @@ -213,7 +223,7 @@ func (r *Receipt) decodeTyped(b []byte) error { return errShortTypedReceipt } switch b[0] { - case DynamicFeeTxType, AccessListTxType: + case DynamicFeeTxType, AccessListTxType, BlobTxType: var data receiptRLP err := rlp.DecodeBytes(b[1:], &data) if err != nil { @@ -289,7 +299,7 @@ func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error { } logList := w.List() for _, log := range r.Logs { - if err := rlp.Encode(w, log); err != nil { + if err := log.EncodeRLP(w); err != nil { return err } } @@ -368,18 +378,28 @@ func (rs Receipts) Len() int { return len(rs) } func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { r := rs[i] data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} + if r.Type == LegacyTxType || r.Type == ArbitrumLegacyTxType { + rlp.Encode(w, data) + return + } + w.WriteByte(r.Type) switch r.Type { - case LegacyTxType, ArbitrumLegacyTxType: + case AccessListTxType, DynamicFeeTxType, BlobTxType: rlp.Encode(w, data) - default: - w.WriteByte(r.Type) + case ArbitrumDepositTxType, ArbitrumUnsignedTxType, ArbitrumContractTxType, ArbitrumRetryTxType, ArbitrumSubmitRetryableTxType, ArbitrumInternalTxType: + // Arbitrum: same as above, but this should help prevent future merge conflicts, + // by keeping above case statement the same. rlp.Encode(w, data) + default: + // For unsupported types, write nothing. Since this is for + // DeriveSha, the error will be caught matching the derived hash + // to the block. } } // DeriveFields fills the receipts with their computed fields based on consensus // data and contextual infos like containing block and transactions. -func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, time uint64, baseFee *big.Int, txs []*Transaction) error { +func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, number uint64, time uint64, baseFee *big.Int, blobGasPrice *big.Int, txs []*Transaction) error { signer := MakeSigner(config, new(big.Int).SetUint64(number), time) logIndex := uint(0) @@ -390,9 +410,14 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu // The transaction type and hash can be retrieved from the transaction itself rs[i].Type = txs[i].Type() rs[i].TxHash = txs[i].Hash() - rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee) + // EIP-4844 blob transaction fields + if txs[i].Type() == BlobTxType { + rs[i].BlobGasUsed = txs[i].BlobGas() + rs[i].BlobGasPrice = blobGasPrice + } + // block location fields rs[i].BlockHash = hash rs[i].BlockNumber = new(big.Int).SetUint64(number) diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 1b5e7e305d8e..8fd48839b950 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -130,22 +130,24 @@ var ( }), // EIP-4844 transactions. NewTx(&BlobTx{ - To: &to6, + To: to6, Nonce: 6, Value: uint256.NewInt(6), Gas: 6, GasTipCap: uint256.NewInt(66), GasFeeCap: uint256.NewInt(1066), BlobFeeCap: uint256.NewInt(100066), + BlobHashes: []common.Hash{{}}, }), NewTx(&BlobTx{ - To: &to7, + To: to7, Nonce: 7, Value: uint256.NewInt(7), Gas: 7, GasTipCap: uint256.NewInt(77), GasFeeCap: uint256.NewInt(1077), BlobFeeCap: uint256.NewInt(100077), + BlobHashes: []common.Hash{{}, {}, {}}, }), } @@ -270,6 +272,8 @@ var ( TxHash: txs[5].Hash(), GasUsed: 6, EffectiveGasPrice: big.NewInt(1066), + BlobGasUsed: params.BlobTxBlobGasPerBlob, + BlobGasPrice: big.NewInt(920), BlockHash: blockHash, BlockNumber: blockNumber, TransactionIndex: 5, @@ -283,6 +287,8 @@ var ( TxHash: txs[6].Hash(), GasUsed: 7, EffectiveGasPrice: big.NewInt(1077), + BlobGasUsed: 3 * params.BlobTxBlobGasPerBlob, + BlobGasPrice: big.NewInt(920), BlockHash: blockHash, BlockNumber: blockNumber, TransactionIndex: 6, @@ -303,8 +309,9 @@ func TestDecodeEmptyTypedReceipt(t *testing.T) { func TestDeriveFields(t *testing.T) { // Re-derive receipts. basefee := big.NewInt(1000) + blobGasPrice := big.NewInt(920) derivedReceipts := clearComputedFieldsOnReceipts(receipts) - err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), blockTime, basefee, txs) + err := Receipts(derivedReceipts).DeriveFields(params.TestChainConfig, blockHash, blockNumber.Uint64(), blockTime, basefee, blobGasPrice, txs) if err != nil { t.Fatalf("DeriveFields(...) = %v, want ", err) } @@ -501,6 +508,9 @@ func clearComputedFieldsOnReceipt(receipt *Receipt) *Receipt { cpy.ContractAddress = common.Address{} // Arbitrum: we check address is blank before rederiving cpy.GasUsed = 0xffffffff cpy.Logs = clearComputedFieldsOnLogs(receipt.Logs) + cpy.EffectiveGasPrice = big.NewInt(0) + cpy.BlobGasUsed = 0 + cpy.BlobGasPrice = nil return &cpy } diff --git a/core/types/state_account.go b/core/types/state_account.go index 3b01be45198f..ad07ca3f3a3d 100644 --- a/core/types/state_account.go +++ b/core/types/state_account.go @@ -17,9 +17,11 @@ package types import ( + "bytes" "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" ) //go:generate go run ../../rlp/rlpgen -type StateAccount -out gen_account_rlp.go @@ -32,3 +34,88 @@ type StateAccount struct { Root common.Hash // merkle root of the storage trie CodeHash []byte } + +// NewEmptyStateAccount constructs an empty state account. +func NewEmptyStateAccount() *StateAccount { + return &StateAccount{ + Balance: new(big.Int), + Root: EmptyRootHash, + CodeHash: EmptyCodeHash.Bytes(), + } +} + +// Copy returns a deep-copied state account object. +func (acct *StateAccount) Copy() *StateAccount { + var balance *big.Int + if acct.Balance != nil { + balance = new(big.Int).Set(acct.Balance) + } + return &StateAccount{ + Nonce: acct.Nonce, + Balance: balance, + Root: acct.Root, + CodeHash: common.CopyBytes(acct.CodeHash), + } +} + +// SlimAccount is a modified version of an Account, where the root is replaced +// with a byte slice. This format can be used to represent full-consensus format +// or slim format which replaces the empty root and code hash as nil byte slice. +type SlimAccount struct { + Nonce uint64 + Balance *big.Int + Root []byte // Nil if root equals to types.EmptyRootHash + CodeHash []byte // Nil if hash equals to types.EmptyCodeHash +} + +// SlimAccountRLP encodes the state account in 'slim RLP' format. +func SlimAccountRLP(account StateAccount) []byte { + slim := SlimAccount{ + Nonce: account.Nonce, + Balance: account.Balance, + } + if account.Root != EmptyRootHash { + slim.Root = account.Root[:] + } + if !bytes.Equal(account.CodeHash, EmptyCodeHash[:]) { + slim.CodeHash = account.CodeHash + } + data, err := rlp.EncodeToBytes(slim) + if err != nil { + panic(err) + } + return data +} + +// FullAccount decodes the data on the 'slim RLP' format and returns +// the consensus format account. +func FullAccount(data []byte) (*StateAccount, error) { + var slim SlimAccount + if err := rlp.DecodeBytes(data, &slim); err != nil { + return nil, err + } + var account StateAccount + account.Nonce, account.Balance = slim.Nonce, slim.Balance + + // Interpret the storage root and code hash in slim format. + if len(slim.Root) == 0 { + account.Root = EmptyRootHash + } else { + account.Root = common.BytesToHash(slim.Root) + } + if len(slim.CodeHash) == 0 { + account.CodeHash = EmptyCodeHash[:] + } else { + account.CodeHash = slim.CodeHash + } + return &account, nil +} + +// FullAccountRLP converts data on the 'slim RLP' format into the full RLP-format. +func FullAccountRLP(data []byte) ([]byte, error) { + account, err := FullAccount(data) + if err != nil { + return nil, err + } + return rlp.EncodeToBytes(account) +} diff --git a/core/types/transaction.go b/core/types/transaction.go index ae38316bd73b..5d86b69c2b31 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -18,7 +18,6 @@ package types import ( "bytes" - "container/heap" "errors" "io" "math/big" @@ -94,9 +93,6 @@ type TxData interface { value() *big.Int nonce() uint64 to() *common.Address - blobGas() uint64 - blobGasFeeCap() *big.Int - blobHashes() []common.Hash rawSignatureValues() (v, r, s *big.Int) setSignatureValues(chainID, v, r, s *big.Int) @@ -110,6 +106,9 @@ type TxData interface { // copy of the computed value, i.e. callers are allowed to mutate the result. // Method implementations can use 'dst' to store the result. effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int + + encode(*bytes.Buffer) error + decode([]byte) error } // EncodeRLP implements rlp.Encoder @@ -130,7 +129,7 @@ func (tx *Transaction) EncodeRLP(w io.Writer) error { // encodeTyped writes the canonical encoding of a typed transaction to w. func (tx *Transaction) encodeTyped(w *bytes.Buffer) error { w.WriteByte(tx.Type()) - return rlp.Encode(w, tx.inner) + return tx.inner.encode(w) } // MarshalBinary returns the canonical encoding of the transaction. @@ -159,22 +158,30 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error { tx.setDecoded(&inner, rlp.ListSize(size)) } return err + case kind == rlp.Byte: + return errShortTypedTx default: // It's an EIP-2718 typed TX envelope. - var b []byte - if b, err = s.Bytes(); err != nil { + // First read the tx payload bytes into a temporary buffer. + b, buf, err := getPooledBuffer(size) + if err != nil { return err } + defer encodeBufferPool.Put(buf) + if err := s.ReadBytes(b); err != nil { + return err + } + // Now decode the inner transaction. inner, err := tx.decodeTyped(b, true) if err == nil { - tx.setDecoded(inner, uint64(len(b))) + tx.setDecoded(inner, size) } return err } } // UnmarshalBinary decodes the canonical encoding of transactions. -// It supports legacy RLP transactions and EIP2718 typed transactions. +// It supports legacy RLP transactions and EIP-2718 typed transactions. func (tx *Transaction) UnmarshalBinary(b []byte) error { if len(b) > 0 && b[0] > 0x7f { // It's a legacy transaction. @@ -186,7 +193,7 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error { tx.setDecoded(&data, uint64(len(b))) return nil } - // It's an EIP2718 typed transaction envelope. + // It's an EIP-2718 typed transaction envelope. inner, err := tx.decodeTyped(b, false) if err != nil { return err @@ -200,54 +207,41 @@ func (tx *Transaction) decodeTyped(b []byte, arbParsing bool) (TxData, error) { if len(b) <= 1 { return nil, errShortTypedTx } + var inner TxData if arbParsing { switch b[0] { case ArbitrumDepositTxType: - var inner ArbitrumDepositTx - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err + inner = new(ArbitrumDepositTx) case ArbitrumInternalTxType: - var inner ArbitrumInternalTx - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err + inner = new(ArbitrumInternalTx) case ArbitrumUnsignedTxType: - var inner ArbitrumUnsignedTx - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err + inner = new(ArbitrumUnsignedTx) case ArbitrumContractTxType: - var inner ArbitrumContractTx - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err + inner = new(ArbitrumContractTx) case ArbitrumRetryTxType: - var inner ArbitrumRetryTx - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err + inner = new(ArbitrumRetryTx) case ArbitrumSubmitRetryableTxType: - var inner ArbitrumSubmitRetryableTx - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err + inner = new(ArbitrumSubmitRetryableTx) case ArbitrumLegacyTxType: - var inner ArbitrumLegacyTxData - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err + inner = new(ArbitrumLegacyTxData) + default: + arbParsing = false } } - switch b[0] { - case AccessListTxType: - var inner AccessListTx - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err - case DynamicFeeTxType: - var inner DynamicFeeTx - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err - case BlobTxType: - var inner BlobTx - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err - default: - return nil, ErrTxTypeNotSupported + if !arbParsing { + switch b[0] { + case AccessListTxType: + inner = new(AccessListTx) + case DynamicFeeTxType: + inner = new(DynamicFeeTx) + case BlobTxType: + inner = new(BlobTx) + default: + return nil, ErrTxTypeNotSupported + } } + err := inner.decode(b[1:]) + return inner, err } // setDecoded sets the inner transaction and size after decoding. @@ -338,15 +332,6 @@ func (tx *Transaction) GasTipCap() *big.Int { return new(big.Int).Set(tx.inner.g // GasFeeCap returns the fee cap per gas of the transaction. func (tx *Transaction) GasFeeCap() *big.Int { return new(big.Int).Set(tx.inner.gasFeeCap()) } -// BlobGas returns the data gas limit of the transaction for blob transactions, 0 otherwise. -func (tx *Transaction) BlobGas() uint64 { return tx.inner.blobGas() } - -// BlobGasFeeCap returns the data gas fee cap per data gas of the transaction for blob transactions, nil otherwise. -func (tx *Transaction) BlobGasFeeCap() *big.Int { return tx.inner.blobGasFeeCap() } - -// BlobHashes returns the hases of the blob commitments for blob transactions, nil otherwise. -func (tx *Transaction) BlobHashes() []common.Hash { return tx.inner.blobHashes() } - // Value returns the ether amount of the transaction. func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.inner.value()) } @@ -433,14 +418,79 @@ func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) i return tx.EffectiveGasTipValue(baseFee).Cmp(other) } +// BlobGas returns the blob gas limit of the transaction for blob transactions, 0 otherwise. +func (tx *Transaction) BlobGas() uint64 { + if blobtx, ok := tx.inner.(*BlobTx); ok { + return blobtx.blobGas() + } + return 0 +} + +// BlobGasFeeCap returns the blob gas fee cap per blob gas of the transaction for blob transactions, nil otherwise. +func (tx *Transaction) BlobGasFeeCap() *big.Int { + if blobtx, ok := tx.inner.(*BlobTx); ok { + return blobtx.BlobFeeCap.ToBig() + } + return nil +} + +// BlobHashes returns the hashes of the blob commitments for blob transactions, nil otherwise. +func (tx *Transaction) BlobHashes() []common.Hash { + if blobtx, ok := tx.inner.(*BlobTx); ok { + return blobtx.BlobHashes + } + return nil +} + +// BlobTxSidecar returns the sidecar of a blob transaction, nil otherwise. +func (tx *Transaction) BlobTxSidecar() *BlobTxSidecar { + if blobtx, ok := tx.inner.(*BlobTx); ok { + return blobtx.Sidecar + } + return nil +} + // BlobGasFeeCapCmp compares the blob fee cap of two transactions. func (tx *Transaction) BlobGasFeeCapCmp(other *Transaction) int { - return tx.inner.blobGasFeeCap().Cmp(other.inner.blobGasFeeCap()) + return tx.BlobGasFeeCap().Cmp(other.BlobGasFeeCap()) } // BlobGasFeeCapIntCmp compares the blob fee cap of the transaction against the given blob fee cap. func (tx *Transaction) BlobGasFeeCapIntCmp(other *big.Int) int { - return tx.inner.blobGasFeeCap().Cmp(other) + return tx.BlobGasFeeCap().Cmp(other) +} + +// WithoutBlobTxSidecar returns a copy of tx with the blob sidecar removed. +func (tx *Transaction) WithoutBlobTxSidecar() *Transaction { + blobtx, ok := tx.inner.(*BlobTx) + if !ok { + return tx + } + cpy := &Transaction{ + inner: blobtx.withoutSidecar(), + time: tx.time, + } + // Note: tx.size cache not carried over because the sidecar is included in size! + if h := tx.hash.Load(); h != nil { + cpy.hash.Store(h) + } + if f := tx.from.Load(); f != nil { + cpy.from.Store(f) + } + return cpy +} + +// SetTime sets the decoding time of a transaction. This is used by tests to set +// arbitrary times and by persistent transaction pools when loading old txs from +// disk. +func (tx *Transaction) SetTime(t time.Time) { + tx.time = t +} + +// Time returns the time when the transaction was first seen on the network. It +// is a heuristic to prefer mining older txs vs new all other things equal. +func (tx *Transaction) Time() time.Time { + return tx.time } // Hash returns the transaction hash. @@ -467,13 +517,24 @@ func (tx *Transaction) Size() uint64 { if size := tx.size.Load(); size != nil { return size.(uint64) } + + // Cache miss, encode and cache. + // Note we rely on the assumption that all tx.inner values are RLP-encoded! c := writeCounter(0) rlp.Encode(&c, &tx.inner) - size := uint64(c) + + // For blob transactions, add the size of the blob content and the outer list of the + // tx + sidecar encoding. + if sc := tx.BlobTxSidecar(); sc != nil { + size += rlp.ListSize(sc.encodedSize()) + } + + // For typed transactions, the encoding also includes the leading type byte. if tx.Type() != LegacyTxType { - size += 1 // type byte + size += 1 } + tx.size.Store(size) return size } @@ -556,123 +617,6 @@ func (s TxByNonce) Len() int { return len(s) } func (s TxByNonce) Less(i, j int) bool { return s[i].Nonce() < s[j].Nonce() } func (s TxByNonce) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -// TxWithMinerFee wraps a transaction with its gas price or effective miner gasTipCap -type TxWithMinerFee struct { - tx *Transaction - minerFee *big.Int -} - -// NewTxWithMinerFee creates a wrapped transaction, calculating the effective -// miner gasTipCap if a base fee is provided. -// Returns error in case of a negative effective miner gasTipCap. -func NewTxWithMinerFee(tx *Transaction, baseFee *big.Int) (*TxWithMinerFee, error) { - minerFee, err := tx.EffectiveGasTip(baseFee) - if err != nil { - return nil, err - } - return &TxWithMinerFee{ - tx: tx, - minerFee: minerFee, - }, nil -} - -// TxByPriceAndTime implements both the sort and the heap interface, making it useful -// for all at once sorting as well as individually adding and removing elements. -type TxByPriceAndTime []*TxWithMinerFee - -func (s TxByPriceAndTime) Len() int { return len(s) } -func (s TxByPriceAndTime) Less(i, j int) bool { - // If the prices are equal, use the time the transaction was first seen for - // deterministic sorting - cmp := s[i].minerFee.Cmp(s[j].minerFee) - if cmp == 0 { - return s[i].tx.time.Before(s[j].tx.time) - } - return cmp > 0 -} -func (s TxByPriceAndTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -func (s *TxByPriceAndTime) Push(x interface{}) { - *s = append(*s, x.(*TxWithMinerFee)) -} - -func (s *TxByPriceAndTime) Pop() interface{} { - old := *s - n := len(old) - x := old[n-1] - old[n-1] = nil - *s = old[0 : n-1] - return x -} - -// TransactionsByPriceAndNonce represents a set of transactions that can return -// transactions in a profit-maximizing sorted order, while supporting removing -// entire batches of transactions for non-executable accounts. -type TransactionsByPriceAndNonce struct { - txs map[common.Address]Transactions // Per account nonce-sorted list of transactions - heads TxByPriceAndTime // Next transaction for each unique account (price heap) - signer Signer // Signer for the set of transactions - baseFee *big.Int // Current base fee -} - -// NewTransactionsByPriceAndNonce creates a transaction set that can retrieve -// price sorted transactions in a nonce-honouring way. -// -// Note, the input map is reowned so the caller should not interact any more with -// if after providing it to the constructor. -func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address]Transactions, baseFee *big.Int) *TransactionsByPriceAndNonce { - // Initialize a price and received time based heap with the head transactions - heads := make(TxByPriceAndTime, 0, len(txs)) - for from, accTxs := range txs { - acc, _ := Sender(signer, accTxs[0]) - wrapped, err := NewTxWithMinerFee(accTxs[0], baseFee) - // Remove transaction if sender doesn't match from, or if wrapping fails. - if acc != from || err != nil { - delete(txs, from) - continue - } - heads = append(heads, wrapped) - txs[from] = accTxs[1:] - } - heap.Init(&heads) - - // Assemble and return the transaction set - return &TransactionsByPriceAndNonce{ - txs: txs, - heads: heads, - signer: signer, - baseFee: baseFee, - } -} - -// Peek returns the next transaction by price. -func (t *TransactionsByPriceAndNonce) Peek() *Transaction { - if len(t.heads) == 0 { - return nil - } - return t.heads[0].tx -} - -// Shift replaces the current best head with the next one from the same account. -func (t *TransactionsByPriceAndNonce) Shift() { - acc, _ := Sender(t.signer, t.heads[0].tx) - if txs, ok := t.txs[acc]; ok && len(txs) > 0 { - if wrapped, err := NewTxWithMinerFee(txs[0], t.baseFee); err == nil { - t.heads[0], t.txs[acc] = wrapped, txs[1:] - heap.Fix(&t.heads, 0) - return - } - } - heap.Pop(&t.heads) -} - -// Pop removes the best transaction, *not* replacing it with the next one from -// the same account. This should be used when a transaction cannot be executed -// and hence all subsequent ones should be discarded from the same account. -func (t *TransactionsByPriceAndNonce) Pop() { - heap.Pop(&t.heads) -} - // copyAddressPtr copies an address. func copyAddressPtr(a *common.Address) *common.Address { if a == nil { diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index 1689341eabd6..60925c00be4b 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -37,7 +37,7 @@ type txJSON struct { GasPrice *hexutil.Big `json:"gasPrice"` MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"` MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"` - MaxFeePerDataGas *hexutil.Big `json:"maxFeePerDataGas,omitempty"` + MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` Value *hexutil.Big `json:"value"` Input *hexutil.Bytes `json:"input"` AccessList *AccessList `json:"accessList,omitempty"` @@ -45,6 +45,7 @@ type txJSON struct { V *hexutil.Big `json:"v"` R *hexutil.Big `json:"r"` S *hexutil.Big `json:"s"` + YParity *hexutil.Uint64 `json:"yParity,omitempty"` // Arbitrum fields: From *common.Address `json:"from,omitempty"` // Contract SubmitRetryable Unsigned Retry @@ -67,6 +68,26 @@ type txJSON struct { Hash common.Hash `json:"hash"` } +// yParityValue returns the YParity value from JSON. For backwards-compatibility reasons, +// this can be given in the 'v' field or the 'yParity' field. If both exist, they must match. +func (tx *txJSON) yParityValue() (*big.Int, error) { + if tx.YParity != nil { + val := uint64(*tx.YParity) + if val != 0 && val != 1 { + return nil, errors.New("'yParity' field must be 0 or 1") + } + bigval := new(big.Int).SetUint64(val) + if tx.V != nil && tx.V.ToInt().Cmp(bigval) != 0 { + return nil, errors.New("'v' and 'yParity' fields do not match") + } + return bigval, nil + } + if tx.V != nil { + return tx.V.ToInt(), nil + } + return nil, errors.New("missing 'yParity' or 'v' field in transaction") +} + // MarshalJSON marshals as JSON with a hash. func (tx *Transaction) MarshalJSON() ([]byte, error) { var enc txJSON @@ -97,6 +118,9 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { enc.V = (*hexutil.Big)(itx.V) enc.R = (*hexutil.Big)(itx.R) enc.S = (*hexutil.Big)(itx.S) + if tx.Protected() { + enc.ChainID = (*hexutil.Big)(tx.ChainId()) + } case *AccessListTx: enc.ChainID = (*hexutil.Big)(itx.ChainID) @@ -110,6 +134,8 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { enc.V = (*hexutil.Big)(itx.V) enc.R = (*hexutil.Big)(itx.R) enc.S = (*hexutil.Big)(itx.S) + yparity := itx.V.Uint64() + enc.YParity = (*hexutil.Uint64)(&yparity) case *DynamicFeeTx: enc.ChainID = (*hexutil.Big)(itx.ChainID) @@ -124,6 +150,8 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { enc.V = (*hexutil.Big)(itx.V) enc.R = (*hexutil.Big)(itx.R) enc.S = (*hexutil.Big)(itx.S) + yparity := itx.V.Uint64() + enc.YParity = (*hexutil.Uint64)(&yparity) case *ArbitrumLegacyTxData: enc.Nonce = (*hexutil.Uint64)(&itx.Nonce) @@ -202,7 +230,7 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { enc.Gas = (*hexutil.Uint64)(&itx.Gas) enc.MaxFeePerGas = (*hexutil.Big)(itx.GasFeeCap.ToBig()) enc.MaxPriorityFeePerGas = (*hexutil.Big)(itx.GasTipCap.ToBig()) - enc.MaxFeePerDataGas = (*hexutil.Big)(itx.BlobFeeCap.ToBig()) + enc.MaxFeePerBlobGas = (*hexutil.Big)(itx.BlobFeeCap.ToBig()) enc.Value = (*hexutil.Big)(itx.Value.ToBig()) enc.Input = (*hexutil.Bytes)(&itx.Data) enc.AccessList = &itx.AccessList @@ -211,6 +239,8 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { enc.V = (*hexutil.Big)(itx.V.ToBig()) enc.R = (*hexutil.Big)(itx.R.ToBig()) enc.S = (*hexutil.Big)(itx.S.ToBig()) + yparity := itx.V.Uint64() + enc.YParity = (*hexutil.Uint64)(&yparity) } return json.Marshal(&enc) } @@ -218,7 +248,8 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (tx *Transaction) UnmarshalJSON(input []byte) error { var dec txJSON - if err := json.Unmarshal(input, &dec); err != nil { + err := json.Unmarshal(input, &dec) + if err != nil { return err } @@ -251,20 +282,23 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'input' in transaction") } itx.Data = *dec.Input - if dec.V == nil { - return errors.New("missing required field 'v' in transaction") - } - itx.V = (*big.Int)(dec.V) + + // signature R if dec.R == nil { return errors.New("missing required field 'r' in transaction") } itx.R = (*big.Int)(dec.R) + // signature S if dec.S == nil { return errors.New("missing required field 's' in transaction") } itx.S = (*big.Int)(dec.S) - withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 - if withSignature { + // signature V + if dec.V == nil { + return errors.New("missing required field 'v' in transaction") + } + itx.V = (*big.Int)(dec.V) + if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 { if err := sanityCheckSignature(itx.V, itx.R, itx.S, true); err != nil { return err } @@ -300,23 +334,26 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'input' in transaction") } itx.Data = *dec.Input - if dec.V == nil { - return errors.New("missing required field 'v' in transaction") - } if dec.AccessList != nil { itx.AccessList = *dec.AccessList } - itx.V = (*big.Int)(dec.V) + + // signature R if dec.R == nil { return errors.New("missing required field 'r' in transaction") } itx.R = (*big.Int)(dec.R) + // signature S if dec.S == nil { return errors.New("missing required field 's' in transaction") } itx.S = (*big.Int)(dec.S) - withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 - if withSignature { + // signature V + itx.V, err = dec.yParityValue() + if err != nil { + return err + } + if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 { if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil { return err } @@ -362,17 +399,23 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { if dec.AccessList != nil { itx.AccessList = *dec.AccessList } - itx.V = (*big.Int)(dec.V) + + // signature R if dec.R == nil { return errors.New("missing required field 'r' in transaction") } itx.R = (*big.Int)(dec.R) + // signature S if dec.S == nil { return errors.New("missing required field 's' in transaction") } itx.S = (*big.Int)(dec.S) - withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 - if withSignature { + // signature V + itx.V, err = dec.yParityValue() + if err != nil { + return err + } + if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 { if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil { return err } @@ -651,9 +694,10 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'nonce' in transaction") } itx.Nonce = uint64(*dec.Nonce) - if dec.To != nil { - itx.To = dec.To + if dec.To == nil { + return errors.New("missing required field 'to' in transaction") } + itx.To = *dec.To if dec.Gas == nil { return errors.New("missing required field 'gas' for txdata") } @@ -666,10 +710,10 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'maxFeePerGas' for txdata") } itx.GasFeeCap = uint256.MustFromBig((*big.Int)(dec.MaxFeePerGas)) - if dec.MaxFeePerDataGas == nil { - return errors.New("missing required field 'maxFeePerDataGas' for txdata") + if dec.MaxFeePerBlobGas == nil { + return errors.New("missing required field 'maxFeePerBlobGas' for txdata") } - itx.BlobFeeCap = uint256.MustFromBig((*big.Int)(dec.MaxFeePerDataGas)) + itx.BlobFeeCap = uint256.MustFromBig((*big.Int)(dec.MaxFeePerBlobGas)) if dec.Value == nil { return errors.New("missing required field 'value' in transaction") } @@ -688,18 +732,35 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'blobVersionedHashes' in transaction") } itx.BlobHashes = dec.BlobVersionedHashes - itx.V = uint256.MustFromBig((*big.Int)(dec.V)) + + // signature R + var overflow bool if dec.R == nil { return errors.New("missing required field 'r' in transaction") } - itx.R = uint256.MustFromBig((*big.Int)(dec.R)) + itx.R, overflow = uint256.FromBig((*big.Int)(dec.R)) + if overflow { + return errors.New("'r' value overflows uint256") + } + // signature S if dec.S == nil { return errors.New("missing required field 's' in transaction") } - itx.S = uint256.MustFromBig((*big.Int)(dec.S)) - withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 - if withSignature { - if err := sanityCheckSignature(itx.V.ToBig(), itx.R.ToBig(), itx.S.ToBig(), false); err != nil { + itx.S, overflow = uint256.FromBig((*big.Int)(dec.S)) + if overflow { + return errors.New("'s' value overflows uint256") + } + // signature V + vbig, err := dec.yParityValue() + if err != nil { + return err + } + itx.V, overflow = uint256.FromBig(vbig) + if overflow { + return errors.New("'v' value overflows uint256") + } + if itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 { + if err := sanityCheckSignature(vbig, itx.R.ToBig(), itx.S.ToBig(), false); err != nil { return err } } diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index ccac4d0dfdd7..675bdb4698e4 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -40,7 +40,8 @@ type sigCache struct { func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer { var signer Signer switch { - case config.IsCancun(blockNumber, blockTime): + // we can use 0 here because arbitrum doesn't support Blob transactions. + case config.IsCancun(blockNumber, blockTime, 0): signer = NewCancunSigner(config.ChainID) case config.IsLondon(blockNumber): signer = NewLondonSigner(config.ChainID) @@ -60,9 +61,9 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint } // LatestSigner returns the 'most permissive' Signer available for the given chain -// configuration. Specifically, this enables support of EIP-155 replay protection and -// EIP-2930 access list transactions when their respective forks are scheduled to occur at -// any block number in the chain config. +// configuration. Specifically, this enables support of all types of transactions +// when their respective forks are scheduled to occur at any block number (or time) +// in the chain config. // // Use this in transaction-handling code where the current block number is unknown. If you // have the current block number available, use MakeSigner instead. @@ -342,11 +343,7 @@ func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) { V, R, S := tx.RawSignatureValues() switch tx.Type() { case LegacyTxType: - if !tx.Protected() { - return HomesteadSigner{}.Sender(tx) - } - V = new(big.Int).Sub(V, s.chainIdMul) - V.Sub(V, big8) + return s.EIP155Signer.Sender(tx) case AccessListTxType: // AL txs are defined to use 0 and 1 as their recovery // id, add 27 to become equivalent to unprotected Homestead signatures. @@ -383,15 +380,7 @@ func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *bi func (s eip2930Signer) Hash(tx *Transaction) common.Hash { switch tx.Type() { case LegacyTxType: - return rlpHash([]interface{}{ - tx.Nonce(), - tx.GasPrice(), - tx.Gas(), - tx.To(), - tx.Value(), - tx.Data(), - s.chainId, uint(0), uint(0), - }) + return s.EIP155Signer.Hash(tx) case AccessListTxType: return prefixedRlpHash( tx.Type(), diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 4b96c6b91a01..25ced0841ba1 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -23,10 +23,8 @@ import ( "errors" "fmt" "math/big" - "math/rand" "reflect" "testing" - "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -216,7 +214,7 @@ func TestEIP2718TransactionEncode(t *testing.T) { func decodeTx(data []byte) (*Transaction, error) { var tx Transaction - t, err := &tx, rlp.Decode(bytes.NewReader(data), &tx) + t, err := &tx, rlp.DecodeBytes(data, &tx) return t, err } @@ -259,152 +257,6 @@ func TestRecipientNormal(t *testing.T) { } } -func TestTransactionPriceNonceSortLegacy(t *testing.T) { - testTransactionPriceNonceSort(t, nil) -} - -func TestTransactionPriceNonceSort1559(t *testing.T) { - testTransactionPriceNonceSort(t, big.NewInt(0)) - testTransactionPriceNonceSort(t, big.NewInt(5)) - testTransactionPriceNonceSort(t, big.NewInt(50)) -} - -// Tests that transactions can be correctly sorted according to their price in -// decreasing order, but at the same time with increasing nonces when issued by -// the same account. -func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) { - // Generate a batch of accounts to start with - keys := make([]*ecdsa.PrivateKey, 25) - for i := 0; i < len(keys); i++ { - keys[i], _ = crypto.GenerateKey() - } - signer := LatestSignerForChainID(common.Big1) - - // Generate a batch of transactions with overlapping values, but shifted nonces - groups := map[common.Address]Transactions{} - expectedCount := 0 - for start, key := range keys { - addr := crypto.PubkeyToAddress(key.PublicKey) - count := 25 - for i := 0; i < 25; i++ { - var tx *Transaction - gasFeeCap := rand.Intn(50) - if baseFee == nil { - tx = NewTx(&LegacyTx{ - Nonce: uint64(start + i), - To: &common.Address{}, - Value: big.NewInt(100), - Gas: 100, - GasPrice: big.NewInt(int64(gasFeeCap)), - Data: nil, - }) - } else { - tx = NewTx(&DynamicFeeTx{ - Nonce: uint64(start + i), - To: &common.Address{}, - Value: big.NewInt(100), - Gas: 100, - GasFeeCap: big.NewInt(int64(gasFeeCap)), - GasTipCap: big.NewInt(int64(rand.Intn(gasFeeCap + 1))), - Data: nil, - }) - if count == 25 && int64(gasFeeCap) < baseFee.Int64() { - count = i - } - } - tx, err := SignTx(tx, signer, key) - if err != nil { - t.Fatalf("failed to sign tx: %s", err) - } - groups[addr] = append(groups[addr], tx) - } - expectedCount += count - } - // Sort the transactions and cross check the nonce ordering - txset := NewTransactionsByPriceAndNonce(signer, groups, baseFee) - - txs := Transactions{} - for tx := txset.Peek(); tx != nil; tx = txset.Peek() { - txs = append(txs, tx) - txset.Shift() - } - if len(txs) != expectedCount { - t.Errorf("expected %d transactions, found %d", expectedCount, len(txs)) - } - for i, txi := range txs { - fromi, _ := Sender(signer, txi) - - // Make sure the nonce order is valid - for j, txj := range txs[i+1:] { - fromj, _ := Sender(signer, txj) - if fromi == fromj && txi.Nonce() > txj.Nonce() { - t.Errorf("invalid nonce ordering: tx #%d (A=%x N=%v) < tx #%d (A=%x N=%v)", i, fromi[:4], txi.Nonce(), i+j, fromj[:4], txj.Nonce()) - } - } - // If the next tx has different from account, the price must be lower than the current one - if i+1 < len(txs) { - next := txs[i+1] - fromNext, _ := Sender(signer, next) - tip, err := txi.EffectiveGasTip(baseFee) - nextTip, nextErr := next.EffectiveGasTip(baseFee) - if err != nil || nextErr != nil { - t.Errorf("error calculating effective tip") - } - if fromi != fromNext && tip.Cmp(nextTip) < 0 { - t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice()) - } - } - } -} - -// Tests that if multiple transactions have the same price, the ones seen earlier -// are prioritized to avoid network spam attacks aiming for a specific ordering. -func TestTransactionTimeSort(t *testing.T) { - // Generate a batch of accounts to start with - keys := make([]*ecdsa.PrivateKey, 5) - for i := 0; i < len(keys); i++ { - keys[i], _ = crypto.GenerateKey() - } - signer := HomesteadSigner{} - - // Generate a batch of transactions with overlapping prices, but different creation times - groups := map[common.Address]Transactions{} - for start, key := range keys { - addr := crypto.PubkeyToAddress(key.PublicKey) - - tx, _ := SignTx(NewTransaction(0, common.Address{}, big.NewInt(100), 100, big.NewInt(1), nil), signer, key) - tx.time = time.Unix(0, int64(len(keys)-start)) - - groups[addr] = append(groups[addr], tx) - } - // Sort the transactions and cross check the nonce ordering - txset := NewTransactionsByPriceAndNonce(signer, groups, nil) - - txs := Transactions{} - for tx := txset.Peek(); tx != nil; tx = txset.Peek() { - txs = append(txs, tx) - txset.Shift() - } - if len(txs) != len(keys) { - t.Errorf("expected %d transactions, found %d", len(keys), len(txs)) - } - for i, txi := range txs { - fromi, _ := Sender(signer, txi) - if i+1 < len(txs) { - next := txs[i+1] - fromNext, _ := Sender(signer, next) - - if txi.GasPrice().Cmp(next.GasPrice()) < 0 { - t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice()) - } - // Make sure time order is ascending if the txs have the same gas price - if txi.GasPrice().Cmp(next.GasPrice()) == 0 && txi.time.After(next.time) { - t.Errorf("invalid received time ordering: tx #%d (A=%x T=%v) > tx #%d (A=%x T=%v)", i, fromi[:4], txi.time, i+1, fromNext[:4], next.time) - } - } - } -} - // TestTransactionCoding tests serializing/de-serializing to/from rlp and JSON. func TestTransactionCoding(t *testing.T) { key, err := crypto.GenerateKey() @@ -526,7 +378,7 @@ func assertEqual(orig *Transaction, cpy *Transaction) error { } if orig.AccessList() != nil { if !reflect.DeepEqual(orig.AccessList(), cpy.AccessList()) { - return fmt.Errorf("access list wrong!") + return errors.New("access list wrong!") } } return nil diff --git a/core/types/tx_access_list.go b/core/types/tx_access_list.go index 7ce9da73ecbe..730a77b75286 100644 --- a/core/types/tx_access_list.go +++ b/core/types/tx_access_list.go @@ -17,9 +17,11 @@ package types import ( + "bytes" "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" ) //go:generate go run github.com/fjl/gencodec -type AccessTuple -out gen_access_tuple.go @@ -94,20 +96,17 @@ func (tx *AccessListTx) copy() TxData { } // accessors for innerTx. -func (tx *AccessListTx) txType() byte { return AccessListTxType } -func (tx *AccessListTx) chainID() *big.Int { return tx.ChainID } -func (tx *AccessListTx) accessList() AccessList { return tx.AccessList } -func (tx *AccessListTx) data() []byte { return tx.Data } -func (tx *AccessListTx) gas() uint64 { return tx.Gas } -func (tx *AccessListTx) gasPrice() *big.Int { return tx.GasPrice } -func (tx *AccessListTx) gasTipCap() *big.Int { return tx.GasPrice } -func (tx *AccessListTx) gasFeeCap() *big.Int { return tx.GasPrice } -func (tx *AccessListTx) value() *big.Int { return tx.Value } -func (tx *AccessListTx) nonce() uint64 { return tx.Nonce } -func (tx *AccessListTx) to() *common.Address { return tx.To } -func (tx *AccessListTx) blobGas() uint64 { return 0 } -func (tx *AccessListTx) blobGasFeeCap() *big.Int { return nil } -func (tx *AccessListTx) blobHashes() []common.Hash { return nil } +func (tx *AccessListTx) txType() byte { return AccessListTxType } +func (tx *AccessListTx) chainID() *big.Int { return tx.ChainID } +func (tx *AccessListTx) accessList() AccessList { return tx.AccessList } +func (tx *AccessListTx) data() []byte { return tx.Data } +func (tx *AccessListTx) gas() uint64 { return tx.Gas } +func (tx *AccessListTx) gasPrice() *big.Int { return tx.GasPrice } +func (tx *AccessListTx) gasTipCap() *big.Int { return tx.GasPrice } +func (tx *AccessListTx) gasFeeCap() *big.Int { return tx.GasPrice } +func (tx *AccessListTx) value() *big.Int { return tx.Value } +func (tx *AccessListTx) nonce() uint64 { return tx.Nonce } +func (tx *AccessListTx) to() *common.Address { return tx.To } func (tx *AccessListTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { return dst.Set(tx.GasPrice) @@ -120,3 +119,11 @@ func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) { func (tx *AccessListTx) setSignatureValues(chainID, v, r, s *big.Int) { tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s } + +func (tx *AccessListTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} + +func (tx *AccessListTx) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} diff --git a/core/types/tx_blob.go b/core/types/tx_blob.go index 22d3c1f1fbfc..71b267e57cc1 100644 --- a/core/types/tx_blob.go +++ b/core/types/tx_blob.go @@ -17,10 +17,14 @@ package types import ( + "bytes" + "crypto/sha256" "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" "github.com/holiman/uint256" ) @@ -31,24 +35,68 @@ type BlobTx struct { GasTipCap *uint256.Int // a.k.a. maxPriorityFeePerGas GasFeeCap *uint256.Int // a.k.a. maxFeePerGas Gas uint64 - To *common.Address `rlp:"nil"` // nil means contract creation + To common.Address Value *uint256.Int Data []byte AccessList AccessList - BlobFeeCap *uint256.Int // a.k.a. maxFeePerDataGas + BlobFeeCap *uint256.Int // a.k.a. maxFeePerBlobGas BlobHashes []common.Hash + // A blob transaction can optionally contain blobs. This field must be set when BlobTx + // is used to create a transaction for sigining. + Sidecar *BlobTxSidecar `rlp:"-"` + // Signature values V *uint256.Int `json:"v" gencodec:"required"` R *uint256.Int `json:"r" gencodec:"required"` S *uint256.Int `json:"s" gencodec:"required"` } +// BlobTxSidecar contains the blobs of a blob transaction. +type BlobTxSidecar struct { + Blobs []kzg4844.Blob // Blobs needed by the blob pool + Commitments []kzg4844.Commitment // Commitments needed by the blob pool + Proofs []kzg4844.Proof // Proofs needed by the blob pool +} + +// BlobHashes computes the blob hashes of the given blobs. +func (sc *BlobTxSidecar) BlobHashes() []common.Hash { + h := make([]common.Hash, len(sc.Commitments)) + for i := range sc.Blobs { + h[i] = blobHash(&sc.Commitments[i]) + } + return h +} + +// encodedSize computes the RLP size of the sidecar elements. This does NOT return the +// encoded size of the BlobTxSidecar, it's just a helper for tx.Size(). +func (sc *BlobTxSidecar) encodedSize() uint64 { + var blobs, commitments, proofs uint64 + for i := range sc.Blobs { + blobs += rlp.BytesSize(sc.Blobs[i][:]) + } + for i := range sc.Commitments { + commitments += rlp.BytesSize(sc.Commitments[i][:]) + } + for i := range sc.Proofs { + proofs += rlp.BytesSize(sc.Proofs[i][:]) + } + return rlp.ListSize(blobs) + rlp.ListSize(commitments) + rlp.ListSize(proofs) +} + +// blobTxWithBlobs is used for encoding of transactions when blobs are present. +type blobTxWithBlobs struct { + BlobTx *BlobTx + Blobs []kzg4844.Blob + Commitments []kzg4844.Commitment + Proofs []kzg4844.Proof +} + // copy creates a deep copy of the transaction data and initializes all fields. func (tx *BlobTx) copy() TxData { cpy := &BlobTx{ Nonce: tx.Nonce, - To: copyAddressPtr(tx.To), + To: tx.To, Data: common.CopyBytes(tx.Data), Gas: tx.Gas, // These are copied below. @@ -90,25 +138,30 @@ func (tx *BlobTx) copy() TxData { if tx.S != nil { cpy.S.Set(tx.S) } + if tx.Sidecar != nil { + cpy.Sidecar = &BlobTxSidecar{ + Blobs: append([]kzg4844.Blob(nil), tx.Sidecar.Blobs...), + Commitments: append([]kzg4844.Commitment(nil), tx.Sidecar.Commitments...), + Proofs: append([]kzg4844.Proof(nil), tx.Sidecar.Proofs...), + } + } return cpy } // accessors for innerTx. -func (tx *BlobTx) txType() byte { return BlobTxType } -func (tx *BlobTx) chainID() *big.Int { return tx.ChainID.ToBig() } -func (tx *BlobTx) accessList() AccessList { return tx.AccessList } -func (tx *BlobTx) data() []byte { return tx.Data } -func (tx *BlobTx) gas() uint64 { return tx.Gas } -func (tx *BlobTx) gasFeeCap() *big.Int { return tx.GasFeeCap.ToBig() } -func (tx *BlobTx) gasTipCap() *big.Int { return tx.GasTipCap.ToBig() } -func (tx *BlobTx) gasPrice() *big.Int { return tx.GasFeeCap.ToBig() } -func (tx *BlobTx) value() *big.Int { return tx.Value.ToBig() } -func (tx *BlobTx) nonce() uint64 { return tx.Nonce } -func (tx *BlobTx) to() *common.Address { return tx.To } -func (tx *BlobTx) blobGas() uint64 { return params.BlobTxDataGasPerBlob * uint64(len(tx.BlobHashes)) } -func (tx *BlobTx) blobGasFeeCap() *big.Int { return tx.BlobFeeCap.ToBig() } -func (tx *BlobTx) blobHashes() []common.Hash { return tx.BlobHashes } -func (tx *BlobTx) skipAccountChecks() bool { return false } +func (tx *BlobTx) txType() byte { return BlobTxType } +func (tx *BlobTx) chainID() *big.Int { return tx.ChainID.ToBig() } +func (tx *BlobTx) accessList() AccessList { return tx.AccessList } +func (tx *BlobTx) data() []byte { return tx.Data } +func (tx *BlobTx) gas() uint64 { return tx.Gas } +func (tx *BlobTx) gasFeeCap() *big.Int { return tx.GasFeeCap.ToBig() } +func (tx *BlobTx) gasTipCap() *big.Int { return tx.GasTipCap.ToBig() } +func (tx *BlobTx) gasPrice() *big.Int { return tx.GasFeeCap.ToBig() } +func (tx *BlobTx) value() *big.Int { return tx.Value.ToBig() } +func (tx *BlobTx) nonce() uint64 { return tx.Nonce } +func (tx *BlobTx) to() *common.Address { tmp := tx.To; return &tmp } +func (tx *BlobTx) blobGas() uint64 { return params.BlobTxBlobGasPerBlob * uint64(len(tx.BlobHashes)) } +func (tx *BlobTx) skipAccountChecks() bool { return false } func (tx *BlobTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { if baseFee == nil { @@ -131,3 +184,64 @@ func (tx *BlobTx) setSignatureValues(chainID, v, r, s *big.Int) { tx.R.SetFromBig(r) tx.S.SetFromBig(s) } + +func (tx *BlobTx) withoutSidecar() *BlobTx { + cpy := *tx + cpy.Sidecar = nil + return &cpy +} + +func (tx *BlobTx) encode(b *bytes.Buffer) error { + if tx.Sidecar == nil { + return rlp.Encode(b, tx) + } + inner := &blobTxWithBlobs{ + BlobTx: tx, + Blobs: tx.Sidecar.Blobs, + Commitments: tx.Sidecar.Commitments, + Proofs: tx.Sidecar.Proofs, + } + return rlp.Encode(b, inner) +} + +func (tx *BlobTx) decode(input []byte) error { + // Here we need to support two formats: the network protocol encoding of the tx (with + // blobs) or the canonical encoding without blobs. + // + // The two encodings can be distinguished by checking whether the first element of the + // input list is itself a list. + + outerList, _, err := rlp.SplitList(input) + if err != nil { + return err + } + firstElemKind, _, _, err := rlp.Split(outerList) + if err != nil { + return err + } + + if firstElemKind != rlp.List { + return rlp.DecodeBytes(input, tx) + } + // It's a tx with blobs. + var inner blobTxWithBlobs + if err := rlp.DecodeBytes(input, &inner); err != nil { + return err + } + *tx = *inner.BlobTx + tx.Sidecar = &BlobTxSidecar{ + Blobs: inner.Blobs, + Commitments: inner.Commitments, + Proofs: inner.Proofs, + } + return nil +} + +func blobHash(commit *kzg4844.Commitment) common.Hash { + hasher := sha256.New() + hasher.Write(commit[:]) + var vhash common.Hash + hasher.Sum(vhash[:0]) + vhash[0] = params.BlobTxHashVersion + return vhash +} diff --git a/core/types/tx_blob_test.go b/core/types/tx_blob_test.go new file mode 100644 index 000000000000..44ac48cc6fac --- /dev/null +++ b/core/types/tx_blob_test.go @@ -0,0 +1,90 @@ +package types + +import ( + "crypto/ecdsa" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + "github.com/holiman/uint256" +) + +// This test verifies that tx.Hash() is not affected by presence of a BlobTxSidecar. +func TestBlobTxHashing(t *testing.T) { + key, _ := crypto.GenerateKey() + withBlobs := createEmptyBlobTx(key, true) + withBlobsStripped := withBlobs.WithoutBlobTxSidecar() + withoutBlobs := createEmptyBlobTx(key, false) + + hash := withBlobs.Hash() + t.Log("tx hash:", hash) + + if h := withBlobsStripped.Hash(); h != hash { + t.Fatal("wrong tx hash after WithoutBlobTxSidecar:", h) + } + if h := withoutBlobs.Hash(); h != hash { + t.Fatal("wrong tx hash on tx created without sidecar:", h) + } +} + +// This test verifies that tx.Size() takes BlobTxSidecar into account. +func TestBlobTxSize(t *testing.T) { + key, _ := crypto.GenerateKey() + withBlobs := createEmptyBlobTx(key, true) + withBlobsStripped := withBlobs.WithoutBlobTxSidecar() + withoutBlobs := createEmptyBlobTx(key, false) + + withBlobsEnc, _ := withBlobs.MarshalBinary() + withoutBlobsEnc, _ := withoutBlobs.MarshalBinary() + + size := withBlobs.Size() + t.Log("size with blobs:", size) + + sizeNoBlobs := withoutBlobs.Size() + t.Log("size without blobs:", sizeNoBlobs) + + if size != uint64(len(withBlobsEnc)) { + t.Error("wrong size with blobs:", size, "encoded length:", len(withBlobsEnc)) + } + if sizeNoBlobs != uint64(len(withoutBlobsEnc)) { + t.Error("wrong size without blobs:", sizeNoBlobs, "encoded length:", len(withoutBlobsEnc)) + } + if sizeNoBlobs >= size { + t.Error("size without blobs >= size with blobs") + } + if sz := withBlobsStripped.Size(); sz != sizeNoBlobs { + t.Fatal("wrong size on tx after WithoutBlobTxSidecar:", sz) + } +} + +var ( + emptyBlob = kzg4844.Blob{} + emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob) + emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit) +) + +func createEmptyBlobTx(key *ecdsa.PrivateKey, withSidecar bool) *Transaction { + sidecar := &BlobTxSidecar{ + Blobs: []kzg4844.Blob{emptyBlob}, + Commitments: []kzg4844.Commitment{emptyBlobCommit}, + Proofs: []kzg4844.Proof{emptyBlobProof}, + } + blobtx := &BlobTx{ + ChainID: uint256.NewInt(1), + Nonce: 5, + GasTipCap: uint256.NewInt(22), + GasFeeCap: uint256.NewInt(5), + Gas: 25000, + To: common.Address{0x03, 0x04, 0x05}, + Value: uint256.NewInt(99), + Data: make([]byte, 50), + BlobFeeCap: uint256.NewInt(15), + BlobHashes: sidecar.BlobHashes(), + } + if withSidecar { + blobtx.Sidecar = sidecar + } + signer := NewCancunSigner(blobtx.ChainID.ToBig()) + return MustSignNewTx(key, signer, blobtx) +} diff --git a/core/types/tx_dynamic_fee.go b/core/types/tx_dynamic_fee.go index 47b870abbf5c..8b5b514fdec5 100644 --- a/core/types/tx_dynamic_fee.go +++ b/core/types/tx_dynamic_fee.go @@ -17,9 +17,11 @@ package types import ( + "bytes" "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" ) // DynamicFeeTx represents an EIP-1559 transaction. @@ -83,20 +85,17 @@ func (tx *DynamicFeeTx) copy() TxData { } // accessors for innerTx. -func (tx *DynamicFeeTx) txType() byte { return DynamicFeeTxType } -func (tx *DynamicFeeTx) chainID() *big.Int { return tx.ChainID } -func (tx *DynamicFeeTx) accessList() AccessList { return tx.AccessList } -func (tx *DynamicFeeTx) data() []byte { return tx.Data } -func (tx *DynamicFeeTx) gas() uint64 { return tx.Gas } -func (tx *DynamicFeeTx) gasFeeCap() *big.Int { return tx.GasFeeCap } -func (tx *DynamicFeeTx) gasTipCap() *big.Int { return tx.GasTipCap } -func (tx *DynamicFeeTx) gasPrice() *big.Int { return tx.GasFeeCap } -func (tx *DynamicFeeTx) value() *big.Int { return tx.Value } -func (tx *DynamicFeeTx) nonce() uint64 { return tx.Nonce } -func (tx *DynamicFeeTx) to() *common.Address { return tx.To } -func (tx *DynamicFeeTx) blobGas() uint64 { return 0 } -func (tx *DynamicFeeTx) blobGasFeeCap() *big.Int { return nil } -func (tx *DynamicFeeTx) blobHashes() []common.Hash { return nil } +func (tx *DynamicFeeTx) txType() byte { return DynamicFeeTxType } +func (tx *DynamicFeeTx) chainID() *big.Int { return tx.ChainID } +func (tx *DynamicFeeTx) accessList() AccessList { return tx.AccessList } +func (tx *DynamicFeeTx) data() []byte { return tx.Data } +func (tx *DynamicFeeTx) gas() uint64 { return tx.Gas } +func (tx *DynamicFeeTx) gasFeeCap() *big.Int { return tx.GasFeeCap } +func (tx *DynamicFeeTx) gasTipCap() *big.Int { return tx.GasTipCap } +func (tx *DynamicFeeTx) gasPrice() *big.Int { return tx.GasFeeCap } +func (tx *DynamicFeeTx) value() *big.Int { return tx.Value } +func (tx *DynamicFeeTx) nonce() uint64 { return tx.Nonce } +func (tx *DynamicFeeTx) to() *common.Address { return tx.To } func (tx *DynamicFeeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { if baseFee == nil { @@ -116,3 +115,11 @@ func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) { func (tx *DynamicFeeTx) setSignatureValues(chainID, v, r, s *big.Int) { tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s } + +func (tx *DynamicFeeTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} + +func (tx *DynamicFeeTx) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} diff --git a/core/types/tx_legacy.go b/core/types/tx_legacy.go index 902e70cf94b9..71025b78fc06 100644 --- a/core/types/tx_legacy.go +++ b/core/types/tx_legacy.go @@ -17,6 +17,7 @@ package types import ( + "bytes" "math/big" "github.com/ethereum/go-ethereum/common" @@ -91,20 +92,17 @@ func (tx *LegacyTx) copy() TxData { } // accessors for innerTx. -func (tx *LegacyTx) txType() byte { return LegacyTxType } -func (tx *LegacyTx) chainID() *big.Int { return deriveChainId(tx.V) } -func (tx *LegacyTx) accessList() AccessList { return nil } -func (tx *LegacyTx) data() []byte { return tx.Data } -func (tx *LegacyTx) gas() uint64 { return tx.Gas } -func (tx *LegacyTx) gasPrice() *big.Int { return tx.GasPrice } -func (tx *LegacyTx) gasTipCap() *big.Int { return tx.GasPrice } -func (tx *LegacyTx) gasFeeCap() *big.Int { return tx.GasPrice } -func (tx *LegacyTx) value() *big.Int { return tx.Value } -func (tx *LegacyTx) nonce() uint64 { return tx.Nonce } -func (tx *LegacyTx) to() *common.Address { return tx.To } -func (tx *LegacyTx) blobGas() uint64 { return 0 } -func (tx *LegacyTx) blobGasFeeCap() *big.Int { return nil } -func (tx *LegacyTx) blobHashes() []common.Hash { return nil } +func (tx *LegacyTx) txType() byte { return LegacyTxType } +func (tx *LegacyTx) chainID() *big.Int { return deriveChainId(tx.V) } +func (tx *LegacyTx) accessList() AccessList { return nil } +func (tx *LegacyTx) data() []byte { return tx.Data } +func (tx *LegacyTx) gas() uint64 { return tx.Gas } +func (tx *LegacyTx) gasPrice() *big.Int { return tx.GasPrice } +func (tx *LegacyTx) gasTipCap() *big.Int { return tx.GasPrice } +func (tx *LegacyTx) gasFeeCap() *big.Int { return tx.GasPrice } +func (tx *LegacyTx) value() *big.Int { return tx.Value } +func (tx *LegacyTx) nonce() uint64 { return tx.Nonce } +func (tx *LegacyTx) to() *common.Address { return tx.To } func (tx *LegacyTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { return dst.Set(tx.GasPrice) @@ -117,3 +115,11 @@ func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) { func (tx *LegacyTx) setSignatureValues(chainID, v, r, s *big.Int) { tx.V, tx.R, tx.S = v, r, s } + +func (tx *LegacyTx) encode(*bytes.Buffer) error { + panic("encode called on LegacyTx") +} + +func (tx *LegacyTx) decode([]byte) error { + panic("decode called on LegacyTx)") +} diff --git a/core/vm/contract.go b/core/vm/contract.go index 4bec38f824ef..077991905d8e 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -31,13 +31,13 @@ type ContractRef interface { // AccountRef implements ContractRef. // // Account references are used during EVM initialisation and -// it's primary use is to fetch addresses. Removing this object +// its primary use is to fetch addresses. Removing this object // proves difficult because of the cached jump destinations which // are fetched from the parent contract (i.e. the caller), which // is a ContractRef. type AccountRef common.Address -// Address casts AccountRef to a Address +// Address casts AccountRef to an Address func (ar AccountRef) Address() common.Address { return (common.Address)(ar) } // Contract represents an ethereum contract in the state database. It contains diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 7553281ffc88..ff927ad1ed1f 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -20,6 +20,7 @@ import ( "crypto/sha256" "encoding/binary" "errors" + "fmt" "math/big" "github.com/ethereum/go-ethereum/common" @@ -28,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/blake2b" "github.com/ethereum/go-ethereum/crypto/bls12381" "github.com/ethereum/go-ethereum/crypto/bn256" + "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" "golang.org/x/crypto/ripemd160" ) @@ -90,6 +92,21 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{9}): &blake2F{}, } +// PrecompiledContractsCancun contains the default set of pre-compiled Ethereum +// contracts used in the Cancun release. +var PrecompiledContractsCancun = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hash{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2F{}, + common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{}, +} + // PrecompiledContractsBLS contains the set of pre-compiled Ethereum // contracts specified in EIP-2537. These are exported for testing purposes. var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ @@ -105,6 +122,7 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ } var ( + PrecompiledAddressesCancun []common.Address PrecompiledAddressesBerlin []common.Address PrecompiledAddressesIstanbul []common.Address PrecompiledAddressesByzantium []common.Address @@ -124,6 +142,9 @@ func init() { for k := range PrecompiledContractsBerlin { PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k) } + for k := range PrecompiledContractsCancun { + PrecompiledAddressesCancun = append(PrecompiledAddressesCancun, k) + } } // ActivePrecompiles returns the precompiles enabled with the current configuration. @@ -131,6 +152,8 @@ func ActivePrecompiles(rules params.Rules) []common.Address { switch { case rules.IsArbitrum: return PrecompiledAddressesArbitrum + case rules.IsCancun: + return PrecompiledAddressesCancun case rules.IsBerlin: return PrecompiledAddressesBerlin case rules.IsIstanbul: @@ -1069,3 +1092,67 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) { // Encode the G2 point to 256 bytes return g.EncodePoint(r), nil } + +// kzgPointEvaluation implements the EIP-4844 point evaluation precompile. +type kzgPointEvaluation struct{} + +// RequiredGas estimates the gas required for running the point evaluation precompile. +func (b *kzgPointEvaluation) RequiredGas(input []byte) uint64 { + return params.BlobTxPointEvaluationPrecompileGas +} + +const ( + blobVerifyInputLength = 192 // Max input length for the point evaluation precompile. + blobCommitmentVersionKZG uint8 = 0x01 // Version byte for the point evaluation precompile. + blobPrecompileReturnValue = "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" +) + +var ( + errBlobVerifyInvalidInputLength = errors.New("invalid input length") + errBlobVerifyMismatchedVersion = errors.New("mismatched versioned hash") + errBlobVerifyKZGProof = errors.New("error verifying kzg proof") +) + +// Run executes the point evaluation precompile. +func (b *kzgPointEvaluation) Run(input []byte) ([]byte, error) { + if len(input) != blobVerifyInputLength { + return nil, errBlobVerifyInvalidInputLength + } + // versioned hash: first 32 bytes + var versionedHash common.Hash + copy(versionedHash[:], input[:]) + + var ( + point kzg4844.Point + claim kzg4844.Claim + ) + // Evaluation point: next 32 bytes + copy(point[:], input[32:]) + // Expected output: next 32 bytes + copy(claim[:], input[64:]) + + // input kzg point: next 48 bytes + var commitment kzg4844.Commitment + copy(commitment[:], input[96:]) + if kZGToVersionedHash(commitment) != versionedHash { + return nil, errBlobVerifyMismatchedVersion + } + + // Proof: next 48 bytes + var proof kzg4844.Proof + copy(proof[:], input[144:]) + + if err := kzg4844.VerifyProof(commitment, point, claim, proof); err != nil { + return nil, fmt.Errorf("%w: %v", errBlobVerifyKZGProof, err) + } + + return common.Hex2Bytes(blobPrecompileReturnValue), nil +} + +// kZGToVersionedHash implements kzg_to_versioned_hash from EIP-4844 +func kZGToVersionedHash(kzg kzg4844.Commitment) common.Hash { + h := sha256.Sum256(kzg[:]) + h[0] = blobCommitmentVersionKZG + + return h +} diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 4a9d9c78f666..04aa8a116ddd 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -56,15 +56,17 @@ var allPrecompiles = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, common.BytesToAddress([]byte{9}): &blake2F{}, - common.BytesToAddress([]byte{10}): &bls12381G1Add{}, - common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, - common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, - common.BytesToAddress([]byte{13}): &bls12381G2Add{}, - common.BytesToAddress([]byte{14}): &bls12381G2Mul{}, - common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{}, - common.BytesToAddress([]byte{16}): &bls12381Pairing{}, - common.BytesToAddress([]byte{17}): &bls12381MapG1{}, - common.BytesToAddress([]byte{18}): &bls12381MapG2{}, + common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{}, + + common.BytesToAddress([]byte{0x0f, 0x0a}): &bls12381G1Add{}, + common.BytesToAddress([]byte{0x0f, 0x0b}): &bls12381G1Mul{}, + common.BytesToAddress([]byte{0x0f, 0x0c}): &bls12381G1MultiExp{}, + common.BytesToAddress([]byte{0x0f, 0x0d}): &bls12381G2Add{}, + common.BytesToAddress([]byte{0x0f, 0x0e}): &bls12381G2Mul{}, + common.BytesToAddress([]byte{0x0f, 0x0f}): &bls12381G2MultiExp{}, + common.BytesToAddress([]byte{0x0f, 0x10}): &bls12381Pairing{}, + common.BytesToAddress([]byte{0x0f, 0x11}): &bls12381MapG1{}, + common.BytesToAddress([]byte{0x0f, 0x12}): &bls12381MapG2{}, } // EIP-152 test vectors @@ -302,36 +304,38 @@ func benchJson(name, addr string, b *testing.B) { } } -func TestPrecompiledBLS12381G1Add(t *testing.T) { testJson("blsG1Add", "0a", t) } -func TestPrecompiledBLS12381G1Mul(t *testing.T) { testJson("blsG1Mul", "0b", t) } -func TestPrecompiledBLS12381G1MultiExp(t *testing.T) { testJson("blsG1MultiExp", "0c", t) } -func TestPrecompiledBLS12381G2Add(t *testing.T) { testJson("blsG2Add", "0d", t) } -func TestPrecompiledBLS12381G2Mul(t *testing.T) { testJson("blsG2Mul", "0e", t) } -func TestPrecompiledBLS12381G2MultiExp(t *testing.T) { testJson("blsG2MultiExp", "0f", t) } -func TestPrecompiledBLS12381Pairing(t *testing.T) { testJson("blsPairing", "10", t) } -func TestPrecompiledBLS12381MapG1(t *testing.T) { testJson("blsMapG1", "11", t) } -func TestPrecompiledBLS12381MapG2(t *testing.T) { testJson("blsMapG2", "12", t) } - -func BenchmarkPrecompiledBLS12381G1Add(b *testing.B) { benchJson("blsG1Add", "0a", b) } -func BenchmarkPrecompiledBLS12381G1Mul(b *testing.B) { benchJson("blsG1Mul", "0b", b) } -func BenchmarkPrecompiledBLS12381G1MultiExp(b *testing.B) { benchJson("blsG1MultiExp", "0c", b) } -func BenchmarkPrecompiledBLS12381G2Add(b *testing.B) { benchJson("blsG2Add", "0d", b) } -func BenchmarkPrecompiledBLS12381G2Mul(b *testing.B) { benchJson("blsG2Mul", "0e", b) } -func BenchmarkPrecompiledBLS12381G2MultiExp(b *testing.B) { benchJson("blsG2MultiExp", "0f", b) } -func BenchmarkPrecompiledBLS12381Pairing(b *testing.B) { benchJson("blsPairing", "10", b) } -func BenchmarkPrecompiledBLS12381MapG1(b *testing.B) { benchJson("blsMapG1", "11", b) } -func BenchmarkPrecompiledBLS12381MapG2(b *testing.B) { benchJson("blsMapG2", "12", b) } +func TestPrecompiledBLS12381G1Add(t *testing.T) { testJson("blsG1Add", "f0a", t) } +func TestPrecompiledBLS12381G1Mul(t *testing.T) { testJson("blsG1Mul", "f0b", t) } +func TestPrecompiledBLS12381G1MultiExp(t *testing.T) { testJson("blsG1MultiExp", "f0c", t) } +func TestPrecompiledBLS12381G2Add(t *testing.T) { testJson("blsG2Add", "f0d", t) } +func TestPrecompiledBLS12381G2Mul(t *testing.T) { testJson("blsG2Mul", "f0e", t) } +func TestPrecompiledBLS12381G2MultiExp(t *testing.T) { testJson("blsG2MultiExp", "f0f", t) } +func TestPrecompiledBLS12381Pairing(t *testing.T) { testJson("blsPairing", "f10", t) } +func TestPrecompiledBLS12381MapG1(t *testing.T) { testJson("blsMapG1", "f11", t) } +func TestPrecompiledBLS12381MapG2(t *testing.T) { testJson("blsMapG2", "f12", t) } + +func TestPrecompiledPointEvaluation(t *testing.T) { testJson("pointEvaluation", "0a", t) } + +func BenchmarkPrecompiledBLS12381G1Add(b *testing.B) { benchJson("blsG1Add", "f0a", b) } +func BenchmarkPrecompiledBLS12381G1Mul(b *testing.B) { benchJson("blsG1Mul", "f0b", b) } +func BenchmarkPrecompiledBLS12381G1MultiExp(b *testing.B) { benchJson("blsG1MultiExp", "f0c", b) } +func BenchmarkPrecompiledBLS12381G2Add(b *testing.B) { benchJson("blsG2Add", "f0d", b) } +func BenchmarkPrecompiledBLS12381G2Mul(b *testing.B) { benchJson("blsG2Mul", "f0e", b) } +func BenchmarkPrecompiledBLS12381G2MultiExp(b *testing.B) { benchJson("blsG2MultiExp", "f0f", b) } +func BenchmarkPrecompiledBLS12381Pairing(b *testing.B) { benchJson("blsPairing", "f10", b) } +func BenchmarkPrecompiledBLS12381MapG1(b *testing.B) { benchJson("blsMapG1", "f11", b) } +func BenchmarkPrecompiledBLS12381MapG2(b *testing.B) { benchJson("blsMapG2", "f12", b) } // Failure tests -func TestPrecompiledBLS12381G1AddFail(t *testing.T) { testJsonFail("blsG1Add", "0a", t) } -func TestPrecompiledBLS12381G1MulFail(t *testing.T) { testJsonFail("blsG1Mul", "0b", t) } -func TestPrecompiledBLS12381G1MultiExpFail(t *testing.T) { testJsonFail("blsG1MultiExp", "0c", t) } -func TestPrecompiledBLS12381G2AddFail(t *testing.T) { testJsonFail("blsG2Add", "0d", t) } -func TestPrecompiledBLS12381G2MulFail(t *testing.T) { testJsonFail("blsG2Mul", "0e", t) } -func TestPrecompiledBLS12381G2MultiExpFail(t *testing.T) { testJsonFail("blsG2MultiExp", "0f", t) } -func TestPrecompiledBLS12381PairingFail(t *testing.T) { testJsonFail("blsPairing", "10", t) } -func TestPrecompiledBLS12381MapG1Fail(t *testing.T) { testJsonFail("blsMapG1", "11", t) } -func TestPrecompiledBLS12381MapG2Fail(t *testing.T) { testJsonFail("blsMapG2", "12", t) } +func TestPrecompiledBLS12381G1AddFail(t *testing.T) { testJsonFail("blsG1Add", "f0a", t) } +func TestPrecompiledBLS12381G1MulFail(t *testing.T) { testJsonFail("blsG1Mul", "f0b", t) } +func TestPrecompiledBLS12381G1MultiExpFail(t *testing.T) { testJsonFail("blsG1MultiExp", "f0c", t) } +func TestPrecompiledBLS12381G2AddFail(t *testing.T) { testJsonFail("blsG2Add", "f0d", t) } +func TestPrecompiledBLS12381G2MulFail(t *testing.T) { testJsonFail("blsG2Mul", "f0e", t) } +func TestPrecompiledBLS12381G2MultiExpFail(t *testing.T) { testJsonFail("blsG2MultiExp", "f0f", t) } +func TestPrecompiledBLS12381PairingFail(t *testing.T) { testJsonFail("blsPairing", "f10", t) } +func TestPrecompiledBLS12381MapG1Fail(t *testing.T) { testJsonFail("blsMapG1", "f11", t) } +func TestPrecompiledBLS12381MapG2Fail(t *testing.T) { testJsonFail("blsMapG2", "f12", t) } func loadJson(name string) ([]precompiledTest, error) { data, err := os.ReadFile(fmt.Sprintf("testdata/precompiles/%v.json", name)) diff --git a/core/vm/eips.go b/core/vm/eips.go index 29ff27c55268..0a9c2e5fffd5 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -17,6 +17,7 @@ package vm import ( + "errors" "fmt" "sort" @@ -26,6 +27,8 @@ import ( ) var activators = map[int]func(*JumpTable){ + 5656: enable5656, + 6780: enable6780, 3855: enable3855, 3860: enable3860, 3529: enable3529, @@ -235,9 +238,88 @@ func opPush0(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by return nil, nil } -// ebnable3860 enables "EIP-3860: Limit and meter initcode" +// enable3860 enables "EIP-3860: Limit and meter initcode" // https://eips.ethereum.org/EIPS/eip-3860 func enable3860(jt *JumpTable) { jt[CREATE].dynamicGas = gasCreateEip3860 jt[CREATE2].dynamicGas = gasCreate2Eip3860 } + +// enable5656 enables EIP-5656 (MCOPY opcode) +// https://eips.ethereum.org/EIPS/eip-5656 +func enable5656(jt *JumpTable) { + jt[MCOPY] = &operation{ + execute: opMcopy, + constantGas: GasFastestStep, + dynamicGas: gasMcopy, + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + memorySize: memoryMcopy, + } +} + +// opMcopy implements the MCOPY opcode (https://eips.ethereum.org/EIPS/eip-5656) +func opMcopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + var ( + dst = scope.Stack.pop() + src = scope.Stack.pop() + length = scope.Stack.pop() + ) + // These values are checked for overflow during memory expansion calculation + // (the memorySize function on the opcode). + scope.Memory.Copy(dst.Uint64(), src.Uint64(), length.Uint64()) + return nil, nil +} + +// opBlobHash implements the BLOBHASH opcode +func opBlobHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + index := scope.Stack.peek() + if index.LtUint64(uint64(len(interpreter.evm.TxContext.BlobHashes))) { + blobHash := interpreter.evm.TxContext.BlobHashes[index.Uint64()] + index.SetBytes32(blobHash[:]) + } else { + index.Clear() + } + return nil, nil +} + +// opBlobBaseFee implements BLOBBASEFEE opcode +func opBlobBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.evm.chainConfig.IsArbitrum() { + return nil, errors.New("BLOBBASEFEE is not supported on Arbitrum") + } + blobBaseFee, _ := uint256.FromBig(interpreter.evm.Context.BlobBaseFee) + scope.Stack.push(blobBaseFee) + return nil, nil +} + +// enable4844 applies EIP-4844 (BLOBHASH opcode) +func enable4844(jt *JumpTable) { + jt[BLOBHASH] = &operation{ + execute: opBlobHash, + constantGas: GasFastestStep, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } +} + +// enable7516 applies EIP-7516 (BLOBBASEFEE opcode) +func enable7516(jt *JumpTable) { + jt[BLOBBASEFEE] = &operation{ + execute: opBlobBaseFee, + constantGas: GasQuickStep, + minStack: minStack(0, 1), + maxStack: maxStack(0, 1), + } +} + +// enable6780 applies EIP-6780 (deactivate SELFDESTRUCT) +func enable6780(jt *JumpTable) { + jt[SELFDESTRUCT] = &operation{ + execute: opSelfdestruct6780, + dynamicGas: gasSelfdestructEIP3529, + constantGas: params.SelfdestructGasEIP150, + minStack: minStack(1, 0), + maxStack: maxStack(1, 0), + } +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 2815b0476758..30eacd4ca897 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -24,14 +24,11 @@ import ( "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/crypto" "github.com/ethereum/go-ethereum/params" ) -// emptyCodeHash is used by create to ensure deployment is disallowed to already -// deployed contract addresses (relevant after the account abstraction). -var emptyCodeHash = crypto.Keccak256Hash(nil) - type ( // CanTransferFunc is the signature of a transfer guard function CanTransferFunc func(StateDB, common.Address, *big.Int) bool @@ -47,6 +44,8 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { switch { case evm.chainRules.IsArbitrum: precompiles = PrecompiledContractsArbitrum + case evm.chainRules.IsCancun: + precompiles = PrecompiledContractsCancun case evm.chainRules.IsBerlin: precompiles = PrecompiledContractsBerlin case evm.chainRules.IsIstanbul: @@ -78,6 +77,7 @@ type BlockContext struct { Time uint64 // Provides information for TIME Difficulty *big.Int // Provides information for DIFFICULTY BaseFee *big.Int // Provides information for BASEFEE + BlobBaseFee *big.Int // Provides information for BLOBBASEFEE Random *common.Hash // Provides information for PREVRANDAO // Arbitrum information @@ -88,8 +88,9 @@ type BlockContext struct { // All fields can change between transactions. type TxContext struct { // Message information - Origin common.Address // Provides information for ORIGIN - GasPrice *big.Int // Provides information for GASPRICE + Origin common.Address // Provides information for ORIGIN + GasPrice *big.Int // Provides information for GASPRICE + BlobHashes []common.Hash // Provides information for BLOBHASH } // EVM is the Ethereum Virtual Machine base object and provides @@ -486,7 +487,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } // Ensure there's no existing contract already at the designated address contractHash := evm.StateDB.GetCodeHash(address) - if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { + if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { return nil, common.Address{}, 0, ErrContractAddressCollision } // Create a new account on the state @@ -513,7 +514,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, ret, err := evm.interpreter.Run(contract, nil, false) // Check whether the max code size has been exceeded, assign err if the case. - if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { + if err == nil && evm.chainRules.IsEIP158 && len(ret) > int(evm.chainConfig.MaxCodeSize()) { err = ErrMaxCodeSizeExceeded } diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 4f961ef4db3f..4ef945ef8498 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -60,6 +60,7 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { // as argument: // CALLDATACOPY (stack position 2) // CODECOPY (stack position 2) +// MCOPY (stack position 2) // EXTCODECOPY (stack position 3) // RETURNDATACOPY (stack position 2) func memoryCopierGas(stackpos int) gasFunc { @@ -89,6 +90,7 @@ func memoryCopierGas(stackpos int) gasFunc { var ( gasCallDataCopy = memoryCopierGas(2) gasCodeCopy = memoryCopierGas(2) + gasMcopy = memoryCopierGas(2) gasExtCodeCopy = memoryCopierGas(3) gasReturnDataCopy = memoryCopierGas(2) ) @@ -102,7 +104,7 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi // Legacy rules should be applied if we are in Petersburg (removal of EIP-1283) // OR Constantinople is not active if evm.chainRules.IsPetersburg || !evm.chainRules.IsConstantinople { - // This checks for 3 scenario's and calculates gas accordingly: + // This checks for 3 scenarios and calculates gas accordingly: // // 1. From a zero-value address to a non-zero value (NEW VALUE) // 2. From a non-zero value address to a zero-value address (DELETE) @@ -308,10 +310,10 @@ func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m return 0, err } size, overflow := stack.Back(2).Uint64WithOverflow() - if overflow || size > params.MaxInitCodeSize { + if overflow || size > evm.chainConfig.MaxInitCodeSize() { return 0, ErrGasUintOverflow } - // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow + // Since size <= evm.chainConfig.MaxInitCodeSize(), these multiplication cannot overflow moreGas := params.InitCodeWordGas * ((size + 31) / 32) if gas, overflow = math.SafeAdd(gas, moreGas); overflow { return 0, ErrGasUintOverflow @@ -324,10 +326,10 @@ func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, return 0, err } size, overflow := stack.Back(2).Uint64WithOverflow() - if overflow || size > params.MaxInitCodeSize { + if overflow || size > evm.chainConfig.MaxInitCodeSize() { return 0, ErrGasUintOverflow } - // Since size <= params.MaxInitCodeSize, these multiplication cannot overflow + // Since size <= evm.chainConfig.MaxInitCodeSize(), these multiplication cannot overflow moreGas := (params.InitCodeWordGas + params.Keccak256WordGas) * ((size + 31) / 32) if gas, overflow = math.SafeAdd(gas, moreGas); overflow { return 0, ErrGasUintOverflow @@ -470,7 +472,7 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me } } - if !evm.StateDB.HasSuicided(contract.Address()) { + if !evm.StateDB.HasSelfDestructed(contract.Address()) { evm.StateDB.AddRefund(params.SelfdestructRefundGas) } return gas, nil diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 611570d055f7..b745f5752642 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -20,6 +20,7 @@ import ( "math/big" "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/crypto" "github.com/ethereum/go-ethereum/params" @@ -253,6 +254,7 @@ func opKeccak256(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( size.SetBytes(interpreter.hasherBuf[:]) return nil, nil } + func opAddress(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { scope.Stack.push(new(uint256.Int).SetBytes(scope.Contract.Address().Bytes())) return nil, nil @@ -269,6 +271,7 @@ func opOrigin(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b scope.Stack.push(new(uint256.Int).SetBytes(interpreter.evm.Origin.Bytes())) return nil, nil } + func opCaller(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { scope.Stack.push(new(uint256.Int).SetBytes(scope.Contract.Caller().Bytes())) return nil, nil @@ -410,7 +413,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) // emptyCodeHash. If the precompile account is not transferred any amount on a private or // customized chain, the return value will be zero. // -// 5. Caller tries to get the code hash for an account which is marked as suicided +// 5. Caller tries to get the code hash for an account which is marked as self-destructed // in the current transaction, the code hash of this account should be returned. // // 6. Caller tries to get the code hash for an account which is marked as deleted, this @@ -837,7 +840,7 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext beneficiary := scope.Stack.pop() balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) - interpreter.evm.StateDB.Suicide(scope.Contract.Address()) + interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address()) if beneficiary.Bytes20() == scope.Contract.Address() { // Arbitrum: calling selfdestruct(this) burns the balance interpreter.evm.StateDB.ExpectBalanceBurn(balance) @@ -849,6 +852,29 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext return nil, errStopToken } +func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + + // Arbitrum: revert if acting account is a Stylus program + actingAddress := scope.Contract.Address() + if code := interpreter.evm.StateDB.GetCode(actingAddress); state.IsStylusProgram(code) { + return nil, ErrExecutionReverted + } + + beneficiary := scope.Stack.pop() + balance := interpreter.evm.StateDB.GetBalance(scope.Contract.Address()) + interpreter.evm.StateDB.SubBalance(scope.Contract.Address(), balance) + interpreter.evm.StateDB.AddBalance(beneficiary.Bytes20(), balance) + interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address()) + if tracer := interpreter.evm.Config.Tracer; tracer != nil { + tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) + tracer.CaptureExit([]byte{}, 0, nil) + } + return nil, errStopToken +} + // following functions are used by the instruction jump table // make log instruction function diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index 2a66f8163e4f..807073336d6d 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -22,9 +22,11 @@ import ( "fmt" "math/big" "os" + "strings" "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -723,7 +725,7 @@ func TestRandom(t *testing.T) { for _, tt := range []testcase{ {name: "empty hash", random: common.Hash{}}, {name: "1", random: common.Hash{0}}, - {name: "emptyCodeHash", random: emptyCodeHash}, + {name: "emptyCodeHash", random: types.EmptyCodeHash}, {name: "hash(0x010203)", random: crypto.Keccak256Hash([]byte{0x01, 0x02, 0x03})}, } { var ( @@ -746,3 +748,183 @@ func TestRandom(t *testing.T) { } } } + +func TestBlobHash(t *testing.T) { + type testcase struct { + name string + idx uint64 + expect common.Hash + hashes []common.Hash + } + var ( + zero = common.Hash{0} + one = common.Hash{1} + two = common.Hash{2} + three = common.Hash{3} + ) + for _, tt := range []testcase{ + {name: "[{1}]", idx: 0, expect: one, hashes: []common.Hash{one}}, + {name: "[1,{2},3]", idx: 2, expect: three, hashes: []common.Hash{one, two, three}}, + {name: "out-of-bounds (empty)", idx: 10, expect: zero, hashes: []common.Hash{}}, + {name: "out-of-bounds", idx: 25, expect: zero, hashes: []common.Hash{one, two, three}}, + {name: "out-of-bounds (nil)", idx: 25, expect: zero, hashes: nil}, + } { + var ( + env = NewEVM(BlockContext{}, TxContext{BlobHashes: tt.hashes}, nil, params.TestChainConfig, Config{}) + stack = newstack() + pc = uint64(0) + evmInterpreter = env.interpreter + ) + stack.push(uint256.NewInt(tt.idx)) + opBlobHash(&pc, evmInterpreter, &ScopeContext{nil, stack, nil}) + if len(stack.data) != 1 { + t.Errorf("Expected one item on stack after %v, got %d: ", tt.name, len(stack.data)) + } + actual := stack.pop() + expected, overflow := uint256.FromBig(new(big.Int).SetBytes(tt.expect.Bytes())) + if overflow { + t.Errorf("Testcase %v: invalid overflow", tt.name) + } + if actual.Cmp(expected) != 0 { + t.Errorf("Testcase %v: expected %x, got %x", tt.name, expected, actual) + } + } +} + +func TestOpMCopy(t *testing.T) { + // Test cases from https://eips.ethereum.org/EIPS/eip-5656#test-cases + for i, tc := range []struct { + dst, src, len string + pre string + want string + wantGas uint64 + }{ + { // MCOPY 0 32 32 - copy 32 bytes from offset 32 to offset 0. + dst: "0x0", src: "0x20", len: "0x20", + pre: "0000000000000000000000000000000000000000000000000000000000000000 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + want: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + wantGas: 6, + }, + + { // MCOPY 0 0 32 - copy 32 bytes from offset 0 to offset 0. + dst: "0x0", src: "0x0", len: "0x20", + pre: "0101010101010101010101010101010101010101010101010101010101010101", + want: "0101010101010101010101010101010101010101010101010101010101010101", + wantGas: 6, + }, + { // MCOPY 0 1 8 - copy 8 bytes from offset 1 to offset 0 (overlapping). + dst: "0x0", src: "0x1", len: "0x8", + pre: "000102030405060708 000000000000000000000000000000000000000000000000", + want: "010203040506070808 000000000000000000000000000000000000000000000000", + wantGas: 6, + }, + { // MCOPY 1 0 8 - copy 8 bytes from offset 0 to offset 1 (overlapping). + dst: "0x1", src: "0x0", len: "0x8", + pre: "000102030405060708 000000000000000000000000000000000000000000000000", + want: "000001020304050607 000000000000000000000000000000000000000000000000", + wantGas: 6, + }, + // Tests below are not in the EIP, but maybe should be added + { // MCOPY 0xFFFFFFFFFFFF 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds index(overlapping). + dst: "0xFFFFFFFFFFFF", src: "0xFFFFFFFFFFFF", len: "0x0", + pre: "11", + want: "11", + wantGas: 3, + }, + { // MCOPY 0xFFFFFFFFFFFF 0 0 - copy zero bytes from start of mem to out-of-bounds. + dst: "0xFFFFFFFFFFFF", src: "0x0", len: "0x0", + pre: "11", + want: "11", + wantGas: 3, + }, + { // MCOPY 0 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds to start of mem + dst: "0x0", src: "0xFFFFFFFFFFFF", len: "0x0", + pre: "11", + want: "11", + wantGas: 3, + }, + { // MCOPY - copy 1 from space outside of uint64 space + dst: "0x0", src: "0x10000000000000000", len: "0x1", + pre: "0", + }, + { // MCOPY - copy 1 from 0 to space outside of uint64 + dst: "0x10000000000000000", src: "0x0", len: "0x1", + pre: "0", + }, + { // MCOPY - copy nothing from 0 to space outside of uint64 + dst: "0x10000000000000000", src: "0x0", len: "0x0", + pre: "", + want: "", + wantGas: 3, + }, + { // MCOPY - copy 1 from 0x20 to 0x10, with no prior allocated mem + dst: "0x10", src: "0x20", len: "0x1", + pre: "", + // 64 bytes + want: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + wantGas: 12, + }, + { // MCOPY - copy 1 from 0x19 to 0x10, with no prior allocated mem + dst: "0x10", src: "0x19", len: "0x1", + pre: "", + // 32 bytes + want: "0x0000000000000000000000000000000000000000000000000000000000000000", + wantGas: 9, + }, + } { + var ( + env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) + stack = newstack() + pc = uint64(0) + evmInterpreter = env.interpreter + ) + data := common.FromHex(strings.ReplaceAll(tc.pre, " ", "")) + // Set pre + mem := NewMemory() + mem.Resize(uint64(len(data))) + mem.Set(0, uint64(len(data)), data) + // Push stack args + len, _ := uint256.FromHex(tc.len) + src, _ := uint256.FromHex(tc.src) + dst, _ := uint256.FromHex(tc.dst) + + stack.push(len) + stack.push(src) + stack.push(dst) + wantErr := (tc.wantGas == 0) + // Calc mem expansion + var memorySize uint64 + if memSize, overflow := memoryMcopy(stack); overflow { + if wantErr { + continue + } + t.Errorf("overflow") + } else { + var overflow bool + if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow { + t.Error(ErrGasUintOverflow) + } + } + // and the dynamic cost + var haveGas uint64 + if dynamicCost, err := gasMcopy(env, nil, stack, mem, memorySize); err != nil { + t.Error(err) + } else { + haveGas = GasFastestStep + dynamicCost + } + // Expand mem + if memorySize > 0 { + mem.Resize(memorySize) + } + // Do the copy + opMcopy(&pc, evmInterpreter, &ScopeContext{mem, stack, nil}) + want := common.FromHex(strings.ReplaceAll(tc.want, " ", "")) + if have := mem.store; !bytes.Equal(want, have) { + t.Errorf("case %d: \nwant: %#x\nhave: %#x\n", i, want, have) + } + wantGas := tc.wantGas + if haveGas != wantGas { + t.Errorf("case %d: gas wrong, want %d have %d\n", i, wantGas, haveGas) + } + } +} diff --git a/core/vm/interface.go b/core/vm/interface.go index b01fab9c49ce..f2be2acac705 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -68,12 +68,14 @@ type StateDB interface { GetTransientState(addr common.Address, key common.Hash) common.Hash SetTransientState(addr common.Address, key, value common.Hash) - Suicide(common.Address) bool - HasSuicided(common.Address) bool - GetSuicides() []common.Address + SelfDestruct(common.Address) + HasSelfDestructed(common.Address) bool + GetSelfDestructs() []common.Address + + Selfdestruct6780(common.Address) // Exist reports whether the given account exists in state. - // Notably this should also return true for suicided accounts. + // Notably this should also return true for self-destructed accounts. Exist(common.Address) bool // Empty returns whether the given account is empty. Empty // is defined according to EIP161 (balance = nonce = code = 0). diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index cedd61d6d35f..3c4ecf5594d8 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -46,7 +46,7 @@ type EVMInterpreter struct { table *JumpTable hasher crypto.KeccakState // Keccak256 hasher instance shared across opcodes - hasherBuf common.Hash // Keccak256 hasher result array shared aross opcodes + hasherBuf common.Hash // Keccak256 hasher result array shared across opcodes readOnly bool // Whether to throw on stateful modifications returnData []byte // Last CALL's return data for subsequent reuse @@ -57,6 +57,8 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { // If jump table was not initialised we set the default one. var table *JumpTable switch { + case evm.chainRules.IsCancun: + table = &cancunInstructionSet case evm.chainRules.IsShanghai: table = &shanghaiInstructionSet case evm.chainRules.IsMerge: diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index a45287de80df..fb87258326bd 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -56,6 +56,7 @@ var ( londonInstructionSet = newLondonInstructionSet() mergeInstructionSet = newMergeInstructionSet() shanghaiInstructionSet = newShanghaiInstructionSet() + cancunInstructionSet = newCancunInstructionSet() ) // JumpTable contains the EVM opcodes supported at a given fork. @@ -79,10 +80,21 @@ func validate(jt JumpTable) JumpTable { return jt } +func newCancunInstructionSet() JumpTable { + instructionSet := newShanghaiInstructionSet() + enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode) + enable7516(&instructionSet) // EIP-7516 (BLOBBASEFEE opcode) + enable1153(&instructionSet) // EIP-1153 "Transient Storage" + enable5656(&instructionSet) // EIP-5656 (MCOPY opcode) + enable6780(&instructionSet) // EIP-6780 SELFDESTRUCT only in same transaction + return validate(instructionSet) +} + func newShanghaiInstructionSet() JumpTable { instructionSet := newMergeInstructionSet() enable3855(&instructionSet) // PUSH0 instruction enable3860(&instructionSet) // Limit and meter initcode + return validate(instructionSet) } diff --git a/core/vm/jump_table_export.go b/core/vm/jump_table_export.go index 0d61b00ede09..b74109da0ad1 100644 --- a/core/vm/jump_table_export.go +++ b/core/vm/jump_table_export.go @@ -22,14 +22,16 @@ import ( "github.com/ethereum/go-ethereum/params" ) -// LookupInstructionSet returns the instructionset for the fork configured by +// LookupInstructionSet returns the instruction set for the fork configured by // the rules. func LookupInstructionSet(rules params.Rules) (JumpTable, error) { switch { + case rules.IsVerkle: + return newCancunInstructionSet(), errors.New("verkle-fork not defined yet") case rules.IsPrague: - return newShanghaiInstructionSet(), errors.New("prague-fork not defined yet") + return newCancunInstructionSet(), errors.New("prague-fork not defined yet") case rules.IsCancun: - return newShanghaiInstructionSet(), errors.New("cancun-fork not defined yet") + return newCancunInstructionSet(), nil case rules.IsShanghai: return newShanghaiInstructionSet(), nil case rules.IsMerge: @@ -54,7 +56,7 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) { return newFrontierInstructionSet(), nil } -// Stack returns the mininum and maximum stack requirements. +// Stack returns the minimum and maximum stack requirements. func (op *operation) Stack() (int, int) { return op.minStack, op.maxStack } diff --git a/core/vm/memory.go b/core/vm/memory.go index 35b729996075..e0202fd7c020 100644 --- a/core/vm/memory.go +++ b/core/vm/memory.go @@ -103,3 +103,14 @@ func (m *Memory) Len() int { func (m *Memory) Data() []byte { return m.store } + +// Copy copies data from the src position slice into the dst position. +// The source and destination may overlap. +// OBS: This operation assumes that any necessary memory expansion has already been performed, +// and this method may panic otherwise. +func (m *Memory) Copy(dst, src, len uint64) { + if len == 0 { + return + } + copy(m.store[dst:], m.store[src:src+len]) +} diff --git a/core/vm/memory_table.go b/core/vm/memory_table.go index e35ca84e0efa..61a910a03daa 100644 --- a/core/vm/memory_table.go +++ b/core/vm/memory_table.go @@ -48,6 +48,14 @@ func memoryMStore(stack *Stack) (uint64, bool) { return calcMemSize64WithUint(stack.Back(0), 32) } +func memoryMcopy(stack *Stack) (uint64, bool) { + mStart := stack.Back(0) // stack[0]: dest + if stack.Back(1).Gt(mStart) { + mStart = stack.Back(1) // stack[1]: source + } + return calcMemSize64(mStart, stack.Back(2)) // stack[2]: length +} + func memoryCreate(stack *Stack) (uint64, bool) { return calcMemSize64(stack.Back(1), stack.Back(2)) } diff --git a/core/vm/memory_test.go b/core/vm/memory_test.go new file mode 100644 index 000000000000..ba36f8023c6d --- /dev/null +++ b/core/vm/memory_test.go @@ -0,0 +1,69 @@ +package vm + +import ( + "bytes" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +func TestMemoryCopy(t *testing.T) { + // Test cases from https://eips.ethereum.org/EIPS/eip-5656#test-cases + for i, tc := range []struct { + dst, src, len uint64 + pre string + want string + }{ + { // MCOPY 0 32 32 - copy 32 bytes from offset 32 to offset 0. + 0, 32, 32, + "0000000000000000000000000000000000000000000000000000000000000000 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", + }, + + { // MCOPY 0 0 32 - copy 32 bytes from offset 0 to offset 0. + 0, 0, 32, + "0101010101010101010101010101010101010101010101010101010101010101", + "0101010101010101010101010101010101010101010101010101010101010101", + }, + { // MCOPY 0 1 8 - copy 8 bytes from offset 1 to offset 0 (overlapping). + 0, 1, 8, + "000102030405060708 000000000000000000000000000000000000000000000000", + "010203040506070808 000000000000000000000000000000000000000000000000", + }, + { // MCOPY 1 0 8 - copy 8 bytes from offset 0 to offset 1 (overlapping). + 1, 0, 8, + "000102030405060708 000000000000000000000000000000000000000000000000", + "000001020304050607 000000000000000000000000000000000000000000000000", + }, + // Tests below are not in the EIP, but maybe should be added + { // MCOPY 0xFFFFFFFFFFFF 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds index(overlapping). + 0xFFFFFFFFFFFF, 0xFFFFFFFFFFFF, 0, + "11", + "11", + }, + { // MCOPY 0xFFFFFFFFFFFF 0 0 - copy zero bytes from start of mem to out-of-bounds. + 0xFFFFFFFFFFFF, 0, 0, + "11", + "11", + }, + { // MCOPY 0 0xFFFFFFFFFFFF 0 - copy zero bytes from out-of-bounds to start of mem + 0, 0xFFFFFFFFFFFF, 0, + "11", + "11", + }, + } { + m := NewMemory() + // Clean spaces + data := common.FromHex(strings.ReplaceAll(tc.pre, " ", "")) + // Set pre + m.Resize(uint64(len(data))) + m.Set(0, uint64(len(data)), data) + // Do the copy + m.Copy(tc.dst, tc.src, tc.len) + want := common.FromHex(strings.ReplaceAll(tc.want, " ", "")) + if have := m.store; !bytes.Equal(want, have) { + t.Errorf("case %d: want: %#x\nhave: %#x\n", i, want, have) + } + } +} diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 910491c60078..a11cf05a1578 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -100,6 +100,8 @@ const ( CHAINID OpCode = 0x46 SELFBALANCE OpCode = 0x47 BASEFEE OpCode = 0x48 + BLOBHASH OpCode = 0x49 + BLOBBASEFEE OpCode = 0x4a ) // 0x50 range - 'storage' and execution. @@ -116,6 +118,9 @@ const ( MSIZE OpCode = 0x59 GAS OpCode = 0x5a JUMPDEST OpCode = 0x5b + TLOAD OpCode = 0x5c + TSTORE OpCode = 0x5d + MCOPY OpCode = 0x5e PUSH0 OpCode = 0x5f ) @@ -204,12 +209,6 @@ const ( LOG4 ) -// 0xb0 range. -const ( - TLOAD OpCode = 0xb3 - TSTORE OpCode = 0xb4 -) - // 0xf0 range - closures. const ( CREATE OpCode = 0xf0 @@ -288,6 +287,8 @@ var opCodeToString = map[OpCode]string{ CHAINID: "CHAINID", SELFBALANCE: "SELFBALANCE", BASEFEE: "BASEFEE", + BLOBHASH: "BLOBHASH", + BLOBBASEFEE: "BLOBBASEFEE", // 0x50 range - 'storage' and execution. POP: "POP", @@ -302,6 +303,9 @@ var opCodeToString = map[OpCode]string{ MSIZE: "MSIZE", GAS: "GAS", JUMPDEST: "JUMPDEST", + TLOAD: "TLOAD", + TSTORE: "TSTORE", + MCOPY: "MCOPY", PUSH0: "PUSH0", // 0x60 range - pushes. @@ -381,10 +385,6 @@ var opCodeToString = map[OpCode]string{ LOG3: "LOG3", LOG4: "LOG4", - // 0xb0 range. - TLOAD: "TLOAD", - TSTORE: "TSTORE", - // 0xf0 range - closures. CREATE: "CREATE", CALL: "CALL", @@ -445,6 +445,8 @@ var stringToOp = map[string]OpCode{ "CALLDATACOPY": CALLDATACOPY, "CHAINID": CHAINID, "BASEFEE": BASEFEE, + "BLOBHASH": BLOBHASH, + "BLOBBASEFEE": BLOBBASEFEE, "DELEGATECALL": DELEGATECALL, "STATICCALL": STATICCALL, "CODESIZE": CODESIZE, @@ -474,6 +476,9 @@ var stringToOp = map[string]OpCode{ "MSIZE": MSIZE, "GAS": GAS, "JUMPDEST": JUMPDEST, + "TLOAD": TLOAD, + "TSTORE": TSTORE, + "MCOPY": MCOPY, "PUSH0": PUSH0, "PUSH1": PUSH1, "PUSH2": PUSH2, @@ -544,8 +549,6 @@ var stringToOp = map[string]OpCode{ "LOG2": LOG2, "LOG3": LOG3, "LOG4": LOG4, - "TLOAD": TLOAD, - "TSTORE": TSTORE, "CREATE": CREATE, "CREATE2": CREATE2, "CALL": CALL, diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 551e1f5f1188..04c6409ebd86 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -235,7 +235,7 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { gas += params.CreateBySelfdestructGas } - if refundsEnabled && !evm.StateDB.HasSuicided(contract.Address()) { + if refundsEnabled && !evm.StateDB.HasSelfDestructed(contract.Address()) { evm.StateDB.AddRefund(params.SelfdestructRefundGas) } return gas, nil diff --git a/core/vm/operations_acl_arbitrum.go b/core/vm/operations_acl_arbitrum.go index e704ae7532c2..95fb5ace7787 100644 --- a/core/vm/operations_acl_arbitrum.go +++ b/core/vm/operations_acl_arbitrum.go @@ -144,10 +144,10 @@ func WasmCallCost(db StateDB, contract common.Address, value *big.Int, budget ui // Computes the cost of touching an account in wasm // Note: the code here is adapted from gasEip2929AccountCheck with the most recent parameters as of The Merge -func WasmAccountTouchCost(db StateDB, addr common.Address, withCode bool) uint64 { +func WasmAccountTouchCost(cfg *params.ChainConfig, db StateDB, addr common.Address, withCode bool) uint64 { cost := uint64(0) if withCode { - cost = params.MaxCodeSize / 24576 * params.ExtcodeSizeGasEIP150 + cost = cfg.MaxCodeSize() / 24576 * params.ExtcodeSizeGasEIP150 } if !db.AddressInAccessList(addr) { diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 8885b10af9e1..cdbb2cdd1ab9 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -26,8 +26,9 @@ func NewEnv(cfg *Config) *vm.EVM { panic("chain config has arbitrum enabled") } txContext := vm.TxContext{ - Origin: cfg.Origin, - GasPrice: cfg.GasPrice, + Origin: cfg.Origin, + GasPrice: cfg.GasPrice, + BlobHashes: cfg.BlobHashes, } blockContext := vm.BlockContext{ CanTransfer: core.CanTransfer, @@ -39,6 +40,8 @@ func NewEnv(cfg *Config) *vm.EVM { Difficulty: cfg.Difficulty, GasLimit: cfg.GasLimit, BaseFee: cfg.BaseFee, + BlobBaseFee: cfg.BlobBaseFee, + Random: cfg.Random, } return vm.NewEVM(blockContext, txContext, cfg.State, cfg.ChainConfig, cfg.EVMConfig) diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index aa239b157d07..e086cf690279 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -44,6 +44,9 @@ type Config struct { Debug bool EVMConfig vm.Config BaseFee *big.Int + BlobBaseFee *big.Int + BlobHashes []common.Hash + Random *common.Hash State *state.StateDB GetHashFn func(n uint64) common.Hash @@ -93,6 +96,9 @@ func setDefaults(cfg *Config) { if cfg.BaseFee == nil { cfg.BaseFee = big.NewInt(params.InitialBaseFee) } + if cfg.BlobBaseFee == nil { + cfg.BlobBaseFee = new(big.Int) + } } // Execute executes the code using the input as call data during the execution. diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 7b521a2ead59..796d3b443414 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -679,30 +679,30 @@ func TestColdAccountAccessCost(t *testing.T) { func TestRuntimeJSTracer(t *testing.T) { jsTracers := []string{ `{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, steps:0, - step: function() { this.steps++}, - fault: function() {}, - result: function() { - return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps].join(",") - }, - enter: function(frame) { - this.enters++; + step: function() { this.steps++}, + fault: function() {}, + result: function() { + return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps].join(",") + }, + enter: function(frame) { + this.enters++; this.enterGas = frame.getGas(); - }, - exit: function(res) { - this.exits++; + }, + exit: function(res) { + this.exits++; this.gasUsed = res.getGasUsed(); }}`, `{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, steps:0, - fault: function() {}, - result: function() { - return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps].join(",") - }, - enter: function(frame) { - this.enters++; + fault: function() {}, + result: function() { + return [this.enters, this.exits,this.enterGas,this.gasUsed, this.steps].join(",") + }, + enter: function(frame) { + this.enters++; this.enterGas = frame.getGas(); - }, - exit: function(res) { - this.exits++; + }, + exit: function(res) { + this.exits++; this.gasUsed = res.getGasUsed(); }}`} tests := []struct { diff --git a/core/vm/testdata/precompiles/pointEvaluation.json b/core/vm/testdata/precompiles/pointEvaluation.json new file mode 100644 index 000000000000..dfb2cad2e32d --- /dev/null +++ b/core/vm/testdata/precompiles/pointEvaluation.json @@ -0,0 +1,9 @@ +[ + { + "Input": "01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a", + "Expected": "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", + "Name": "pointEvaluation1", + "Gas": 50000, + "NoBenchmark": false + } +] diff --git a/crypto/bls12381/g2.go b/crypto/bls12381/g2.go index 4d6f1ff11de8..e5fe75af20c7 100644 --- a/crypto/bls12381/g2.go +++ b/crypto/bls12381/g2.go @@ -121,7 +121,7 @@ func (g *G2) FromBytes(in []byte) (*PointG2, error) { return p, nil } -// DecodePoint given encoded (x, y) coordinates in 256 bytes returns a valid G1 Point. +// DecodePoint given encoded (x, y) coordinates in 256 bytes returns a valid G2 Point. func (g *G2) DecodePoint(in []byte) (*PointG2, error) { if len(in) != 256 { return nil, errors.New("invalid g2 point length") diff --git a/crypto/bn256/cloudflare/optate.go b/crypto/bn256/cloudflare/optate.go index b71e50e3a21c..e8caa7a08656 100644 --- a/crypto/bn256/cloudflare/optate.go +++ b/crypto/bn256/cloudflare/optate.go @@ -199,9 +199,8 @@ func miller(q *twistPoint, p *curvePoint) *gfP12 { r = newR r2.Square(&minusQ2.y) - a, b, c, newR = lineFunctionAdd(r, minusQ2, bAffine, r2) + a, b, c, _ = lineFunctionAdd(r, minusQ2, bAffine, r2) mulLine(ret, a, b, c) - r = newR return ret } diff --git a/crypto/kzg4844/kzg4844.go b/crypto/kzg4844/kzg4844.go index 3bd814159db3..5969d1c2cee1 100644 --- a/crypto/kzg4844/kzg4844.go +++ b/crypto/kzg4844/kzg4844.go @@ -54,7 +54,7 @@ func UseCKZG(use bool) error { useCKZG.Store(use) // Initializing the library can take 2-4 seconds - and can potentially crash - // on CKZG and non-ADX CPUs - so might as well so it now and don't wait until + // on CKZG and non-ADX CPUs - so might as well do it now and don't wait until // a crypto operation is actually needed live. if use { ckzgIniter.Do(ckzgInit) diff --git a/crypto/kzg4844/kzg4844_ckzg_cgo.go b/crypto/kzg4844/kzg4844_ckzg_cgo.go index d62ca3fad65a..54002856987c 100644 --- a/crypto/kzg4844/kzg4844_ckzg_cgo.go +++ b/crypto/kzg4844/kzg4844_ckzg_cgo.go @@ -74,6 +74,8 @@ func ckzgBlobToCommitment(blob Blob) (Commitment, error) { // ckzgComputeProof computes the KZG proof at the given point for the polynomial // represented by the blob. func ckzgComputeProof(blob Blob, point Point) (Proof, Claim, error) { + ckzgIniter.Do(ckzgInit) + proof, claim, err := ckzg4844.ComputeKZGProof((ckzg4844.Blob)(blob), (ckzg4844.Bytes32)(point)) if err != nil { return Proof{}, Claim{}, err @@ -84,6 +86,8 @@ func ckzgComputeProof(blob Blob, point Point) (Proof, Claim, error) { // ckzgVerifyProof verifies the KZG proof that the polynomial represented by the blob // evaluated at the given point is the claimed value. func ckzgVerifyProof(commitment Commitment, point Point, claim Claim, proof Proof) error { + ckzgIniter.Do(ckzgInit) + valid, err := ckzg4844.VerifyKZGProof((ckzg4844.Bytes48)(commitment), (ckzg4844.Bytes32)(point), (ckzg4844.Bytes32)(claim), (ckzg4844.Bytes48)(proof)) if err != nil { return err @@ -99,6 +103,8 @@ func ckzgVerifyProof(commitment Commitment, point Point, claim Claim, proof Proo // // This method does not verify that the commitment is correct with respect to blob. func ckzgComputeBlobProof(blob Blob, commitment Commitment) (Proof, error) { + ckzgIniter.Do(ckzgInit) + proof, err := ckzg4844.ComputeBlobKZGProof((ckzg4844.Blob)(blob), (ckzg4844.Bytes48)(commitment)) if err != nil { return Proof{}, err @@ -108,6 +114,8 @@ func ckzgComputeBlobProof(blob Blob, commitment Commitment) (Proof, error) { // ckzgVerifyBlobProof verifies that the blob data corresponds to the provided commitment. func ckzgVerifyBlobProof(blob Blob, commitment Commitment, proof Proof) error { + ckzgIniter.Do(ckzgInit) + valid, err := ckzg4844.VerifyBlobKZGProof((ckzg4844.Blob)(blob), (ckzg4844.Bytes48)(commitment), (ckzg4844.Bytes48)(proof)) if err != nil { return err diff --git a/crypto/kzg4844/kzg4844_test.go b/crypto/kzg4844/kzg4844_test.go index 0d35667a9ce6..fae8a7a76eaf 100644 --- a/crypto/kzg4844/kzg4844_test.go +++ b/crypto/kzg4844/kzg4844_test.go @@ -47,7 +47,6 @@ func randBlob() Blob { func TestCKZGWithPoint(t *testing.T) { testKZGWithPoint(t, true) } func TestGoKZGWithPoint(t *testing.T) { testKZGWithPoint(t, false) } - func testKZGWithPoint(t *testing.T, ckzg bool) { if ckzg && !ckzgAvailable { t.Skip("CKZG unavailable in this test build") @@ -73,7 +72,6 @@ func testKZGWithPoint(t *testing.T, ckzg bool) { func TestCKZGWithBlob(t *testing.T) { testKZGWithBlob(t, true) } func TestGoKZGWithBlob(t *testing.T) { testKZGWithBlob(t, false) } - func testKZGWithBlob(t *testing.T, ckzg bool) { if ckzg && !ckzgAvailable { t.Skip("CKZG unavailable in this test build") @@ -106,6 +104,8 @@ func benchmarkBlobToCommitment(b *testing.B, ckzg bool) { useCKZG.Store(ckzg) blob := randBlob() + + b.ResetTimer() for i := 0; i < b.N; i++ { BlobToCommitment(blob) } @@ -124,6 +124,8 @@ func benchmarkComputeProof(b *testing.B, ckzg bool) { blob = randBlob() point = randFieldElement() ) + + b.ResetTimer() for i := 0; i < b.N; i++ { ComputeProof(blob, point) } @@ -144,6 +146,8 @@ func benchmarkVerifyProof(b *testing.B, ckzg bool) { commitment, _ = BlobToCommitment(blob) proof, claim, _ = ComputeProof(blob, point) ) + + b.ResetTimer() for i := 0; i < b.N; i++ { VerifyProof(commitment, point, claim, proof) } @@ -162,6 +166,8 @@ func benchmarkComputeBlobProof(b *testing.B, ckzg bool) { blob = randBlob() commitment, _ = BlobToCommitment(blob) ) + + b.ResetTimer() for i := 0; i < b.N; i++ { ComputeBlobProof(blob, commitment) } @@ -181,6 +187,8 @@ func benchmarkVerifyBlobProof(b *testing.B, ckzg bool) { commitment, _ = BlobToCommitment(blob) proof, _ = ComputeBlobProof(blob, commitment) ) + + b.ResetTimer() for i := 0; i < b.N; i++ { VerifyBlobProof(blob, commitment, proof) } diff --git a/crypto/kzg4844/trusted_setup.json b/crypto/kzg4844/trusted_setup.json index 37108fee3e62..c6d724efafdf 100644 --- a/crypto/kzg4844/trusted_setup.json +++ b/crypto/kzg4844/trusted_setup.json @@ -1,8265 +1,4167 @@ { - "setup_G1": [ - "0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", - "0x854262641262cb9e056a8512808ea6864d903dbcad713fd6da8dddfa5ce40d85612c912063ace060ed8c4bf005bab839", - "0x86f708eee5ae0cf40be36993e760d9cb3b2371f22db3209947c5d21ea68e55186b30871c50bf11ef29e5248bf42d5678", - "0x94f9c0bafb23cbbf34a93a64243e3e0f934b57593651f3464de7dc174468123d9698f1b9dfa22bb5b6eb96eae002f29f", - "0x82b8775b874067bdd4479ac237f8d56036a742c17901354caaf38bf8c70e696650fbec76f0cd941ed8c658f44ea359ff", - "0xa7ce299c79c7d7e4f1adecd754c5aff8a720728ab27f5737b7b399f72724407ac54965088596375b8c876665ed8e4ff1", - "0x81ca4c808a76a6f217f8b0540ff400199295da69b2587b7be6aeb56447fa4fac08d154a27c4aa6082bc40660078d36e9", - "0xa70bad5311c97f1f3fea5d3375da1a11ba948aca41609ea28666dd343e07af766834e1256dc685ac1dcd915073250864", - "0xa91c2911a658ba79f56abe30716a3398387630e785b351b07344022a04b2f5c90de5573bd6e8048fe8878dde19336c5b", - "0xa8c560283fce9813bcbaddfb78cff93efcbc39b33025cfad94ebd40942a9fa605d2a947dc3a1f03c2e454075892e96bf", - "0xaa14f07fbd2c1ce7bd995e335c69b5f675ea573517c1834e787b30ab4fa10aecc62ecc5e617ac8a539af1aff114dc9ec", - "0x87f03429aff126b7c5a918423da278e17b5f48a4cdd6d34dba77a75f4f99e26a417e65d6a8579bcb2eaaf1d4d8c64dce", - "0xb1ac81ba91ede78315f712d524e9d821a152203f04141ba77f4e481ad5881473dff14a71788ce941f0905b429e7ee5b2", - "0x8f5c2af611ddfa3edf7e442d00e56a24d615bac848c05070c908c741ba18b67eb2e82e6651c9b3c70fb8edbf051810c4", - "0xaa4115b19221e4d17cc335d4f9b0aad22df566231f2286d550e97ff2875cbc419edfa189c4ecb24001123b95c6aaa2da", - "0xb363ba913969df0debd4e2712ae6e9177ce82e169ce7e0ff1d7616ef8e352aff3efb40fffbf7bff1b21cb8a33e19b455", - "0xb1013d778727d20466778cea47e1bf56a77168a8ce1b33bb1265f66438ab2bf4a7df4f4142b8681f2993ea9baf798d17", - "0x83b7250ee17d8529207db97b73c1c4a92ac076951a577ce2fe3a2cd633b461c1820c139ab36a895a5962e143c6198386", - "0x86d180bd2f0a4919764e6f4e846ec0d5ebe44060ec1e529ed15101d7e531bf1b8f9412916ea5aeb93b37b2d7c6bfb408", - "0x827451462c79d74b504c01bda199481b3c70416f66a95b2216686ca4d48da48932b0d323d0cd630a1e86e8033e832e5f", - "0xb789d217cb12c334fedff0ae01f85b96e968fb98761755d1ba3ee17934e8fbd83172225df7c0c6cb99432a30a0ef8c24", - "0xb730e5412dfbd646b0d2fe084a1a32eb5596c3fe8a5bc0f430151804f9e67f05f70b522e9aef08082b0afdc652a1d213", - "0x9987653bacd9bc1659b17f6964aec06ea63b787813d4601bee0479706aed5595ac82c87ed4f96f0cd30c19e1d9510a91", - "0x9506a9ba38f1d26c35a17c7e2554e28eb347a19cef523846a2559fb80fb40306b2f85bdc2c9fb98c2267df21c1ee3589", - "0x98dda58de74c0cdaef97b2826b4a9d53c9e9ea592dc0a755ccf5b3fbc1264979578563f5979aaa246e679918053c5f83", - "0xb00aaa16841ab53883af481e2f925050f5f7bf7d8088bc696f55f30593bdbbaf434f5d2b46095ed097b6cdb96c8fbc3b", - "0xb463d656480f89335d3a840a7b9398877003985388871b148ba708c60f9857c7585ef17c8b2ae67fbb191c04ad61e692", - "0x80af54f3d0584126e23635276d650292baf7e3e12bb06468708107bcd80937d36575721ee7472c5f085ffa71dbf438ad", - "0x94ccb8ade84e834685110c96908b42e10d2184208f434d7f98d96cc158e0c0c95135979600e5e9f465d5846b0bb3c787", - "0x8e13674b00c633d7cceb4f6ecd61e4f99420d6cccf9db5e81f8c90f6c661bc76e10939b83b56c225fce8565f525d4fa4", - "0xa46a15b2e671c1a1df2490768dec1093caf537e1a21fbc11ff8ba8b21b9f2be8d50257027d9357df20d9fbb1307d7249", - "0xb8ed532d48b0533a3084d7a5eea7b401255c5825e9a1b80ed81fd530cd69e347d152b1ad8a899acff7d68e0103bbfbde", - "0xad6b7df980ebaa24177d830c4aa522d6179a9a489257f60ee6604cccc2cbe90fb1f72aa9d5bee0d3a88f73b179485f56", - "0xa56855e9fcf62ceef3043991a93ec24f8f6b5667ef5fb7ad1771249ece68a73580ec3cf3e58a009ca4650c01241ad172", - "0xab2f25517d4b0b33d317eb78d091d3c3f98dc352b8a3e4650f7005f9327129e23d95f38eaeda5e9b51c50a31d20a4c20", - "0xa2d4071385b8a421da86f39739eaadcdea5685466feb6ac083cba0ea4c71dbbdf655032097276d703f9a77a4ca6fab03", - "0xa8681d7c258984f01e92e92c95738692b7bbd59c3a802adf4dda8d34add69590b080391c09e98e3b75c085c9f191e5e5", - "0x97685643da6c07b5e5fe91393122813ba11c8ef3dbd43a03b3a22a7a1603201fd516c1929418eafb14039270694c239a", - "0xa7bb3b85d6101e4fb0bcf540f52041cdb3e819d517465e342b832f0e91346a9a18bdb38845ea4d2b71ab87ef3bf59f96", - "0x8afc90b7d35336fdcf8f81cd024e921e244520ecfcb5a3994f2bbd595366b68bfa792a8dceb11e1e889b11432c9dad6b", - "0x94d9db7bd04f962d8d6caa3b7aa0f19acbd58a09d35ae187158d77e537d2fc65215f51f1afd62d4ba378e7d176a680f9", - "0xad62d7c01b14b6f97e6084ec9f9d084f106a7ff3d603032e6e34c027cdce4b0fe3c20ac7931f1209439a59c9fede4815", - "0xa5b44a87bd0ada7498e011e495a2818a8694746c4e7dc9d24c0c1096f54be6439e08c1b11c10d7c4bf68fe392000e971", - "0x828626c6609acc599f1bf216e9a4310fc3cb227e0d2e47bfe3360239427c8b0cc300cddf92456a5c36620596a6d37027", - "0x8380f91baac6447dd39886455ec5d99b874ac114a3c6a6ded18fc4ef69c2208ec19732428d8329d200a69f31792b852e", - "0x85a8389b522b0a686234450165514127006baaa3907f6eb29c976522591a542ffb681b3b88c4886814fd7ba3cc8110f7", - "0xb8ae7949ddafad37c0bc4d48325a7cbcd3096fb92c04a027c650a03cc609c7eac250d6a7ba341616bc36f64f1b4c8be4", - "0x8f9b9d2c2ab5c85abe946ed9986e0f304281b277d4d48c7760ea2331b55a9e9a1c4d53a6bdd83fa6294f421ca7431e29", - "0x9464b906ea8bc994b31e03c5f2af2be0724a43293fd42cbd2263b2de75a2ec04832d1100ce62ac2c0708f05fb6bb3ce6", - "0x93d923f6805e7cf972d8387b352d77215724a3e1f5489c4114fcf0b25fc2231963eda872387a1369a02b2e8b888d6427", - "0xaba4af392884eb7283fc5611ddc1cebfecf9477462e951bdae650e311606e129b4424452a3a14717146f954a7fa1cfc3", - "0xa8d0bab694d116e4f21fa19ff8fa4c6fe4061dbb54cbceda8235a7e05159737f87e008beccb90df0bac9c7d572281903", - "0x85743e3ecbac7ae04a81a09c2961397aa4bd20401533cd24d5fc0693cbfbdd2b37bbee6dec2ae5d0a66250d1fcba6944", - "0x80ae913447d7d3f6c54f9cb584aa1603308146daeb3024c8e06ede66ddc97821df09f9591453e9b617b828a02d54c719", - "0x803c2a64bb1c68890b5f1909be3aa180830ee3ef316d3aac38bfd909e2b19d890525e18e8fc2f405ee70ac14f5569b3f", - "0x964d2968724eb790f2f42263fcaaa1869c373b57b3eeee904f8b36f13169f07d5e29cb2b03c74d3a7adb772e91d5a59a", - "0x98a72ce71a57262aa058643a5cd39af64cc9eee88bef7edb003143983f29d87c7f9658b1ec89712f79f6f79dc24a6a45", - "0x91f3479c5d7c76acd2d51883961179efc975c714720187cc9c0aa7aeff71ca1b3e2db5b0a90fd3ff6abf880ebc49fe36", - "0x84312757edd09f111420bfede10ed3c1fa39d1723ddb9bd4d0004c829f0c1eb060e9648fd75f2e5427a67a5b37945a9f", - "0x95edd726cf4042a082d786262304c51d8d5e6a89b1b58e825a11febe5f861d5ce076bdcb2fc0a5dfa95eb2e5b0ffc32e", - "0x96500da38f942871d78fcc46cda1e72944c7888b538b82e2a979f149e5061a20c7602860f82b76510d02efdf3a911f5a", - "0x8ac62eda98bef8864df243696b53651a02a391b898535d2d76ac5a8e9322e0178a290c83f5afe72ffe80ad56183469e3", - "0x8ab2d4427fb6d3da5cf6c59835bdb39fb0c2de82c213b5de77edae3304458ea505511bd98fda95bdbbb9058bd5e92c34", - "0xab67c4344a5080930029ca3b803883ad05ca004ddefb48d5164e71a1c6dd96b27aaec70f62b39bb126ce1a57bbff1453", - "0x86c6bf91686bff714a873a78b0fe449db5317a5172a0a14eb3a96b2997b888d5d3f440de8baa32a6966fe44c3625b014", - "0x81d4f1e9d9e550125290d993a4886d46aac8cb29dbbba1e608aefc3432569c5faf14d8b49fcb485d9b75b649ad6b2fa5", - "0x8594140f253ced6fa98dd90ab4f38899916bcc6f1052572f182e46c00752f3053c390512338a0bc8f8c27a91916b855f", - "0x911284d4fad4999bb37590206d582b9e62ffbb815f414fd829f5b2843e6f0e1a132cd64464c131d5a0f476469a37daa1", - "0x8631a6a4987410982db9c0ba632023a5b613f553b6b8ffd3cfd501b2417523ba8cf06741c62f24b405554bd93e39e626", - "0x906ac35d22794a10a7273fdbca499fd921799b1ce9414643779dce9e1ec37920a5aa2caceb4b70a0eaf56c6032ef1b43", - "0x87374cdb8b7a1ce3c182b31eec465d435e35df782fe3a11f421462b48cf56c6fef2a9cb8ee4fe89672ba7804156d9e3a", - "0xa1f825e0246eee506c8ce40f849a17f75e8a0d6fc3f68b6a4dd431173b4fe997d30dca53005829e4e2422a4077ce35c7", - "0x875ad0379abd9873f6634692e33e9b36353e1a0d15b13d3215eb591244e1f236eb2f8f75274ca7f096179d1714fa68b7", - "0xb87b4e1acc09c5701fd9d75375ab896f178c1b3648fb9a2e2c6e1478778156decc32cd390766f3e80b36beb1e3a6bdec", - "0x836ca80949269eb52395776ac5ceb35b7df717a981c5cbbbb627f73c274aa8164e973a7b01012fa72a02404e878a9918", - "0xa770b13a8f07f74e5a75842b18f2520f6d0be42c865a29dd81bfe485e69a83c40ad10ce229afce276ccc9cb46c54b912", - "0xb4b919322bba2866baeed38bf0e2389d4fe6ab6166597e87dbfee75acac7c2f5ad3bef55293b56957c103d5825051bb5", - "0xb6171f1bbeedb3ee1af368c9c9f327d1dc3e55aeaffbe15f14db8038cd72540b62fe65f44ad0b5486dcf4023f0e91af8", - "0x8e42d0c1e8e8c2ccaf06edcc3c686aed56b8c987f9d68f20937fc088120a410cb92fb0ab45bba5db70b86876015a6b72", - "0x937bcff1af9685fd0d1f6616acf91d97ac9fcb1eb96d49d1c880c9945c1fcf1414f63d59fb78348d08a8546f6e83e407", - "0xa6eeb4873c0531fbcd407c2e702c68e4980fa77c9c032b9913b89031702cfa56f335fc413576c37ac4d523357a841203", - "0xb3962b5eed69cfa27fb94edba74b6cedd7569352ea71861494dd579da96d9743655b6308e54f8a42ee6d7e805c1bc0f9", - "0x8eea944dce7202b033ce734c9e88e82dd760c916e00b217cf1f00bf6ec5f20e21885d5fe95d6138871d167de4c46359e", - "0x81e6c7b356e2703ee333a9dfeb2b54260636422b9bda118e0523a20ce83b30fefc2f019e8291a8db05d207f0fa7332fb", - "0x83817f6164dc9e8e2506252511cb9871a8c9b595dde45f67e75ce3505f947b3fb3b804c18c054ad13b1518a98f59f008", - "0xa9ab4dbe7699e7982cd750d7effe031f273fab6b2e024a0b4f8beccb5c280903bcd3f2400b9cac7e8c94e157b4658ab6", - "0x84d2e3bc66fc6b59a1ee98b8981ebca0901d846c40d207e5bb5004ec9339d28956d16f054af52453f6a7ff3fc66c346b", - "0xb24bf0f69c3e86f610b6d27885ac5f4556fbb14e8286681538ddbb0b4921aa0d5604fedef0daf4a514ae15268a640174", - "0xa4be967f7f31995562669bf9587f5463bd1d9873fe9169687790e75961efa5ce2252fd21276d022f580de23527282838", - "0xa3f3c4e673b302bdb91fa3cbdec88499752e6ffe37e2215d69b8a426f4da98c3a10e4c659e77c656172e4e8b1b1a41bb", - "0xb704ffbb3434563bbbce69ca7e812a8bd30757b1e90630bf3261735f9ea221524b36f97dec324ffd209bef45bdf6f2b4", - "0x959dde49f15c663a2de000195e182a11d8c396c1380f98322cbe5521b697bc3bec3223ca9e94ee2734c4ffdfb6a19e8c", - "0xa469685143cd82b78d7b1854c350da63819d9d86670e9b35a72381d0362cf5c3f1d24e22ef2ea6a12176c9dad39fd51c", - "0xadb97ef4463e5e13d91b75a3086d72a841a60be278e9651d9ac5f76c9537bac5eac33424a1ea00522b3357fcefea0738", - "0xa4597b2ced7566576e71b4f105b5ee48aa4ffca93901e9b626f7059824f53be3e8f3560e6861285f3d97fe88054fee83", - "0xa18d9b1b81564447f798ce5966bf10c823aedb14b143972eb4dbbba9312fc79f46635aa016cd20c53be90f170f06fb84", - "0xac4724069177d3c6ac1b72ea2a7d6bc5ac3d4b2a4dbad124152fbd170c9c1038cdcf255d162a25c14ae8df11a3849945", - "0x892683f64179ba84f6a447c5c7489e3cdf02474d2837dd7bf3b82a4dd05a6461ce94fff28d65b9539cacaf47dddedbc1", - "0xa68ad797bbc1b2909e216a0b3f39aa6c3e4dfc7a49f81a206b530ec0c6ba30f871e4a0053625aeb99448026ae2e0a6eb", - "0x964ff8badf35b6b93be6d97209d87f4ac8847be1c2ac4bcafa1db5c3f604f61834c85b3dcf58af50d09bd03ff8d78f27", - "0xb76dc9ec64b1fab7be269097a18a77144623d37bc656934fa1562817c922485e69b18ef40413ee309e100fde645fa7b2", - "0xb2a812be6e69f284580ebdec5ae2cdffd587bc7eae10989e9d2f290498b1eaa934b148ec7783edec300be5d7a9b34af0", - "0x85ffcabc623f8ffc58c5f640f857e27b7c105359315a3969f346e1366acb2af88f4acc025b299b9c324a8535c380a2c5", - "0x8d0140f79fb8ef02d13b1d51c4ba1af5b5ffb19322f88912215d4198f9a592f7ec6800c8a3ca853a3b68f9bf0353a13a", - "0xb3174deb53c1ebb6a1e16c915cac287573b70fe4e0036e8e971e8e807a77362ede632f6e3f29cb87a80a318213946ff1", - "0x8c58d603f6420e3f55522ec2853065125af4e7773a909e28296552f7f8ec4691ada9211d834dca38e118f432b6cfe03b", - "0xaa7ac268e155ff074bfc197844e21fc2a9f9aec9b50d9cda63f50d3c4fbbf9221e6fac3a6ba0f7e4cde71fecd493a15d", - "0xa191337721bc9fd2d3ec2ca6f6f97ca2462ef5e207464bf9e746a650a67d69abb5f578a8238521cee3f704b275845e47", - "0x93521abea8f38c103ebed3313a3af8f27f03c9a54681847f4201bf9f72f1f63064b18175986fca64f80b4380905e894c", - "0xa1b9d063d6538885f9826b84944123d7d6027dd030aef29fd6229f4cf5d32404f7dd0e899a0c8f4b6bdf4649e8a8966f", - "0xa15d5497f0fd2fd0b2c2e5df58a25a72a9d99df8215951ea58c15569d312c6f096f78034f6a8502f808e649f6cb9283a", - "0xb3c275306852612362e1073d0f4da3ce598dc5fac3f3eefa22ccee35dd57a4caae347b43342cd1f6a6e068d3ea9fd30c", - "0x94eb678e0700bf39caf428c65bbf2fbf7f601c39e382570a4df9186ff1dd5a958d78e051a5fd084e4f75536a14b7690b", - "0x97b13995bbcb8e824bec28488994a830a9c1f34ae4c1a16d5528d57f09e4c8b5d81677ea9f979f0acb8cac629ee09c85", - "0x817c99ad48bc05bd4fd29f952dbdc5ef56bb02f3442c18e3b91cb6d72ac2d2a5df901c099165ded1bee62c3ed13c41e8", - "0xa884acf980f6470e11cff347692d8a7cb7860d4822112f7bfeb02efb05948ea98c837d5d98dd7a104aa36eb8f016a0f4", - "0x95debd2ed23a23a16a393f59f666cfc864f63751238b73981faec4a85b4c04cfa11520c9e4cbe4e23fe80e86c260093a", - "0x937b4691c59453bc6cf6468ed5b17dbb25496bfa3817798562cd5fd86ab5ee35745991afea2fe271ce0fbe5a990c41c7", - "0xb4da98c879e6b475c540ff2c5501299f0e3e97b7b93beb45faef1253f7de96968898505e672cfc4a3ee7e20c1f72c256", - "0x8ec9d806f344d0c675bb5ecd47c54defb5f059a5233dfb2d459632b9b22edd6c4b8c47fd7899ab13e35f37ede9b124f8", - "0xaab4408410abb4d2cd98694f71b5452e6fab2690daa3066b3f9747e7dc40b57259d52e6fddeaeeca227b733d049b9694", - "0xb85a12f39808961c331038159255140a61dedc56050345a2eb13b1f7d140ae07b353d68d22f2cf60925fe66e598213e9", - "0xb61bc3bd68bffdbe9731f48fcd523491da04dab83add49fde390070513b9ad87a489010f1ccfe6f54e9a48edaf88b5f9", - "0x8f50f6d8235824cf25031f09e4b139bd89c1090269dae95a5aa0dacaf5f9b59c329a5a3cdddf9efe6c77cd61f481dcbc", - "0x91a543b85e18f34361d7df5ece8504d456627fbce65abff437007e9109359538a03996e35393a40f0586f962921eccaf", - "0xb7557bc52931324dd4c58d0e63c89a8dbdd2d00d0baf79d81d7a062aedd2de9dd743ea30fb031b18c806ba03175d7e1d", - "0x8e056b842a9af7aeb6c0d113a3acc8bfb5c6a8980fa81869747f75abef76b7fd20cb67694e70016e3de6e7821cde030b", - "0x966c00fd6472bb13ffa531d8eedc145ffb7733114e0f4a6a9fddb34ab7601f6cfb056460f757636230b692453d8b31d6", - "0xa25d85947c6939547fbee088e0131988053c5bb23aa2bd48ca764f4ef2b29235a817b8918d1de6865695977a95711e9d", - "0x958567f217ce7a6d74861777801663d7175eeeca8ff62e240582fb603ac91dc402331034fb4855632352df2328fe0233", - "0x85e53f3802a7d32dec2db84fad7f8c8fc856037cc0cd4ef9a8988e97ab580d4b929023f1fcde7633828b5e8bcdab08c7", - "0x878d1fbbedee7f7ff72eaa3848d7f6bc3cd13b40149278b3afe5e3621e6d1f0386f8ede32971d3f33be189c927bef6f7", - "0xb041e880e4ecb254f6f8d92635a1ef3be3d5d885c751f247bec2d8a016aada6a7fd2f7c599f458ee466886abe721bba9", - "0x920747dac9f35ba0b2670f82c762a71ee9bfb9e490825fb7ed613bf2548ef4ea00bc01e9d2c952dd9c56f3586a3ffb49", - "0x800005cefda1ddb860fd8974342fe315d227902dcb5f3736f8b9ad1fa2f8fbeff8c8ba0eb3f0c21a6706f288ef4bb13b", - "0x91f2b822b728fc5d1f15b69a303985bab14c08df5e929decbfa5aa5689f3cd93ccfe19ab10499d31df9d38c84039e492", - "0x957a909486abd85b1e627a4739c7d212cd03f2b62952045b704c415acdf2e6c0cc627af93f382836603f33d1a716ac7d", - "0x9733ec7a30ed833cc1e7e0ada4badddb1cd1908bcbd3d4e4694576421c94090a9297aacd7f42d9d305b87d711828304a", - "0xac2785a0dadfd246fe12b63f759e9f021006cff4f06b2b5a9986f0b02a40f29513feb1c9044af6e27d1c5029b1e1db35", - "0x948b22bddf55f4b4bc26892e83f70b882a0458582ed87fbbc81bbd037c946d833c19162327354240c42e05cfef55394b", - "0xa49c5d81544028d56f4caf8699477bcda589c65f6754dd40a487ef88d93925008dc7fefa6d458619d51a54b3edb5e5c4", - "0xac57b8ca2d0623f5c4137cada67afd6935fb75fd82567f2c57cb22e89a0562d3c0716d5e903fc06694a8c2edbc9a6f1c", - "0xad52af6a0cf838bbca5a97aec5d87fee1aec4fcf5e802b8bbad1b110c31ed777de0b0ebf74384bae68289af20e351bb3", - "0xb0c7c48d734e5a1b37674465eb07a629dbdf8f9080c44a578f3dd687261d9d1cc5cbdc084488c745c9114fd998bfefb2", - "0x8a2b2ccd4c52d15bf7aa4a8847b8015bd53f58ee484589339b4510ef08a27db56178c15b4d79a9c6eba1ac0b641eaa61", - "0x98f659a37bffd7a9b7759bb111412ea9e9eec483645511590f683064eaf15e1b101b5eac3b98f79ea38662b1956a06d2", - "0xaf6cda3fb2479b6f2d959f2d03e52b49afd12bdccd7a65a1bf6b91e345387924d5e355363f79bbe32a4624287cf4c1ac", - "0xa24d325d8c2dbf9d2e346e3504154018937efb74246ee0658e68d148d9ad0f4bfe348ea9bdca77d4467ea1b3dc2fae5f", - "0x81a729dad3798121027c29e9310d56e36a48c1c479cffe674cbf9131c562f541d7e6c52c2718025d3470b05b67cdd321", - "0x95bd5cd6d9895c775e58cd4296ebefa51ab9e324418208c3c4d073be59410497a4d0daddba6c1e7373abc08e13d32b89", - "0x809fa97a229b056def6b548902d8d90c873e496db6cb1b2d448709b9ae08d9b9762559666cd96b6bba396eebbab4ea4e", - "0x8bcae63cc680494606e44037a3bf6dc7bae2e723e5ec3ac0451550b8ca7914ee1d4bed0f40adc3dfa45f8f80a36c11a5", - "0xb3474711a0f933cf269e97e4e1e98762ddbbf49dd72e468f1e8a2f89514c1c35cb8db32d08dff50f93e50db43bed54f2", - "0x9788a37c3d95310627deec58ba6d9e0324618469275276632a3fa7841fb127c8fefc1b7392064f2eecb508056bd346c7", - "0x8d031fdb156023e185fe5fcac67b966baf9c098fddead4a6f1a3cef54d8e912d0de2d1e1d3f3f05da538eac4af5b6973", - "0xa5efe72b86a714dbbae40fa69fbccf41042e0647d177cd60275700257aa583708130a64b2f9dcacde4fb636b5cbd5aac", - "0x824092ea32eb7a8c619182d926f292cedce7ac3d3fc64f60d00fcd767649e1d6cffc20dd9c1d1c8ef6f64be315d1e2b3", - "0x900ad22d3b63376b1ac80c7343a58df23c03c4e7d6e5740dc10d8cdee793be07fec09cfbdf29e1d1c6484d3077630d6a", - "0x826815005550844ac5a6e831de0e25fadc49aff808cd601d70743d4873a341e3f0cd40d490422c87df3f3c40921fa723", - "0xb39d189aea740c52b03660c0abc8e796cab72193ed44a4b1f59fd1ec0e283ef7d5e157ed23741eaf289cf968597c0900", - "0x968ed61662d1e656e039900912ab61173b49d2e24aa6b7d3af07c3b04a2c89c4516540934aa543bb48ee14153780d10a", - "0xa433b8b689007ecae7f1df15d442b0570664a2db6318de66c6e5fd68884615778d296bd260ab7d07469bfb5f6d8c94ca", - "0xa69ed4a0f39920d1a62a01214daec143fb7596212e2439583df9ba508784ef4d2fe86334f0f9a6b7a3342ec0a72ef15f", - "0x96f260d9cd88757e7c45201d57bd816f1cfd37587ba17a64997bf7716ca1c2cfe16a7119c36acf1754231f303058a9cf", - "0xa51f2bb09d30028eeb0860e2de38094623e5b5514fd5d591a7d4a9731cd4b9c4c12c5dd6ef409e009dafb10d185d5346", - "0x8abe821036140ccb3ff9063dcb5e8b8724cff1cf0784b8f44486c8380fc51715cf55b443cc20870f426c4874be93caeb", - "0xacd73facb964d9012ad12405dc866beb52d8de3ef81fe966cfdb14d22a19bbd2e7ad3a29cf029617b9d6510ed323c6a2", - "0x8f18f6883c8e4741cd6c52e0d3335dd71b5571446ee28e8c27cb0625f77a9f5bd0720d960e5e8970257907f503d58a9a", - "0xb66457a91e7ddcf56c8ce4936a209c69ee53d71236b72ea386f7719c8b8c9b4ba4ea19039a8de17a0a869da427da42e7", - "0x80b1de58bb3ac5f264e0273061f485e49413de604b5ade73ef81bc249f5e89ce97dbec7d99b088b5a2ff65c0bb93fa76", - "0x8bdf276c88f80371ef0ef7e1224929681629aaebc8cba3c0db0b258be3c26cd17268f56369832f564b1679be33e98c69", - "0x943cf6fc88678816da42e4f337c730eb2dd59f8d738ea638a799e8b77214ad7e74723056bae96b100f9a972155885d26", - "0x91c8c1a8a61f47119005869c11edf0b69d0bcf40534b82e46aa96bb6107f940e582b6733f855144accb8dc21d79acc39", - "0x96ba98bd291faa0904ca0398d6c50eb5bc2ab5a389c359ca42b8909f41f4fc37dcedc370ece777d5035074a597da503e", - "0xb4598e6f889d319713a9896161a6c9bd8575ca30c21d3fdd37cff15dc0141ce28dc536f73957e6fc8f6185fc0adb731d", - "0xaf1ed593a0547c26ff729c159ef14bd0818f25e7c1c6c51ce8ce5425bd2526086eff9fa3341279daf82e480bfe431230", - "0x8c02b9ad3aebf156c80fec9b012241f3794d736adfbe4a272faf505ab818cb121ad2ad7c2eb1716e252d0a2e7ee6b246", - "0x8d2a8a31784c446eff4c2ed7b004009f08b86c87a4786a0b7be3df36ca9130a0ec42a58d09dfede1279a4a6d3d37b501", - "0xa78b61be13005b1718a3aa3deba103ce71e1ff73163c76815f9cbcc101d993f119ca128a25c51a12fa52f46550c4b609", - "0xb990d81d7aec9fc50d798eb8c38b40b162004f78730e9ed4a103faeea0995bb654893e557e5eee9b74046ddcaa70617b", - "0xad56d68777d0ed53d3331b0cfd44503b27435278416ac2268965d8ef711fdd819c16ef5499d8d7fddadd229c3d0d4bd6", - "0xb5110140b9ee542ec03c945cd6180ab1af205537704fd408fc4490d799d87a3f3aa0f1f0ae9c8daa55c1757f7bb53cbd", - "0xb7d8a4080c5eeb00be4540a00e65e744f4c7792b518c9fd2dbdd25abd0dd89e76563618cdb92e4cda0fe3ba4597508dd", - "0xa880b33af98cc0bd1065767a2600145e6e326c3cee25602dd22d531c26c4b8543f846fadf679e26749c929310779d858", - "0x941f124078990e03310cacd79e4a38667f4dac4dda4dfa3173a03c14aafbf538fdaa01b940fd5be770c1cde0a84bfefd", - "0xb234e9d0f04da6efc5aa5c77bf71cb05001cd193178fdd546e4ec81875830113d3d4f1512e7405b16d0b3aead285999d", - "0xb857bf6f62c4b19ca9441f700ea6676ffa3b0c7138299533ede59a9b9cf9b94295046e7eafcf1d4ecaf0341898ed6b15", - "0xa2b0d74f17d3387566bb4f17dfef214cdc6b61dc50698fbbe298a7e2f4a82d20aefd719c5e82bbf4ba4fee0e2e35b4c6", - "0xb5ffae433aafad3fd51ac137976e2c22661d8a286b221e0107baf19f5c8f2f6c7eac1e785f145bf7c16a497310fbf51d", - "0xa69e9dfb14f8c6cda716166cb69f06879718656c9f46730d94f194e2888fec371a11c9701071bf8690e57996fa91d213", - "0xa1f51ecd5c5d73155013dcf02b327cdbae9f9c2fbc62f73959050cd3a0bd66192213d1f4bb56a85cd21343722ff3f57c", - "0xab3e54b8f4829f1115694a4be7d16e8214c94387ae783263cfe145f965705d51943176191c706f6211c8be2699dc79a9", - "0x8cd6a64c5d30149ca4dae4fb7e8974dce1200aba9be9c8cf9af5d43e40098746ecff7bcde7ff84a0072138dcd04c2771", - "0xa52f6fe24305bcff685f2d047c9a8d9a1f70c2b416cfe55fc137c6b5b185029f3644890418873665712dba4886e3fc07", - "0xb2e8e3d2ba2d64815bafb678dfc1180534186eca415bd8cd32b303bbac6cfb637b17200aa7cacb953e656ad19dd5c9b4", - "0xb5412d1073b3b80bf0d18f791a6d02727cd9c40a86ab0f13ccfd847bf4e767b8b79aba3935194398da2c9cf17c6bfc8a", - "0x8bbaee84aca9089585d5ff385dc2ee6e653d0fcb9088f98bc5fb1c6c83db026d9a73d77c33c6cae3268be90805d862fa", - "0x9691343d1a8b7fcebefe2584b28ab5808764ed79d8f28168a65ca90b0094e7920fa43e56b960331209f4b8465cb8a5bd", - "0x8ea475e12088d558f5cf6dea2da71837791093a622d5cbee608a95b4c8a61296255c1545d454562e371ea0e2cb2e0b1f", - "0x951d6b404667ccca099d01328562790d1e8446231d7d22bc2b1c4c6b15785bf59f2099accc58817a06d24d59ff4e6a2f", - "0xa5d012687f341eb9c783c1c2040388eb7ad662cfb2b84cd94d270bcc99116939aea80440d7ab726f9abcad22fcd90274", - "0x818fb57b7a8cc59f06af054ce09dfef812f8f115eb2473d06c8f20fc50cf17423331aae1f843bcae57fe4e2926ad5aaa", - "0xaad27bde8eaa2e7fb1a9a5ab531eb41f475afdc89b7f02085f7289f8f84d172fe516d0104560a40c89e97db7e5e836ee", - "0xb8cd923efac1b09d9c6b1d97a0c1bce9fe4eba1d872eaa3c0df34dcff2e7ea2354f1b31b69c6b266944ec8cae2a16865", - "0xaf628e772d609224aa7cd3eddbbfe965fdae6a05cf6d14959c5c26c4806043afd5fef803091bec710c6854ec095ba18e", - "0xb662e1d32704d96919f5dbefc3cc40e7d41d4124c5865b46408c2ee5c40926eed71fa3df71fa7ad349d476d9a525d7fc", - "0xae4c5512396f9c26381394ff8e22b1d6267e3d3a5d3fe48457450694520c5e173716572b68fc1dc352761991abd262b4", - "0x86b530978a7e549e6ca014168fa4aeda9438bcd3a29f7edb1f4e9c712c16faa58b81b028c25a8e90b421b61a1766d7d7", - "0x97b32f1371f76dac7a449295365780d1bd03f290a041b3d19db3f14bee6365a614ca356e7cbd6f966260420b451f6117", - "0x8be97569ea08d0b6c4d46e9565ae14f79d1990f192a26ec935a865cedd6bb5f69f608b069f7d645877c5081fb4a68c54", - "0x9733488f48de05f57f623752b80b37c91f6c9afc0f9b4df4cf400f3f82b137bdf06fee82190f2a4ad4aad20e964cc214", - "0xa794f6dbf155666056529748a792be13011bee6ca10e0d55c82c3e84c5dfa1f370c8e8abf2971a75c73a4ddef3da3319", - "0x95ff5d16c0d9bd679738257a1f7f309f257c20469f2fa41bcfadc671ad65acb249793defab591f515bb3d8072e2e05f3", - "0x8d849150bf8dc3452839256ec4eb65cc9ef87aa0f90dfea4d1d486f16ee855d6c480a8fa4b6cf8d536e435f9fb7bf507", - "0xb61c29121dca2bbc6024ad2f487bb57b926786ae60a9e7a721440787752432ba9c7e1df86ef0d74c2592d23f0e89326e", - "0x819630a678e4a5e6adbde9b292f5c8f2b6e3f2ecc9bcec60ba0f8502e503f697b0ded4f0f7157b60ddc976ded66713aa", - "0xb3525b071e26babf669ae2b98319b3516c083e797d74bd5b9b0e1f67792a2e8ab2c60921812690b5928de66290ff7b86", - "0xa344c6670718b9824ae62b309813bd31984eefb5efee38052cd06812308edcc39fdee165f8164629267bc0e98fb50ba6", - "0x81d78d54738817dadee7bf70a468a51728de0e9775f8779fea5d0d95e55b2004377b4e2db595d420f017af18a384d9aa", - "0x848c97b9413ba6ede751ece925ba57b8f8ae27168c5d46347d39e0232a5eb42069a85f1ee2d30d8b94fde574642be5d1", - "0xb020048c5a5a2d186df628550c6f61a204f16e6eb991283e975de520c4f916badc999b3b7e9402ccc39db5c0b510e2d4", - "0x9298c1aec9664ab3fe328f532428e9b037efe8412ccfdd15e33c9c82dc3631e49f84e0d2d75dced85e3a4e0fd0f3f2dc", - "0x8c4a78841f51e2f8b91defb0a3844933999f9884e2b324bd89a01e482756758b1b5a278289163409947c73106bf934f7", - "0xb328a9db915c4bea1783218c7668e2bd7a8fa62e52d3a005a43590041d34ff388c0555b044ec5ff85368352a3643b7eb", - "0x8a395d89469d374c1ec472c4d571ae670849549d05124907faae5a086519686683c1773d22d290ebdcfb8dab926d10b5", - "0xaec52b8a883f4ff68fa5f418cc91c8e9d08ef324544356b0ac56a7f0980fab6b376b8f567e357ba96b408652b8e568ed", - "0xaf80f0c5d50ab23d8ad99c7fba504f2f02b7307b5ae5ff529142bead28d69c3d55f4e2226c44549548fdf761ce30cff2", - "0xaf73700803caf7b06f5453a620253731de33a458da01f5790106e9418fb59e7aecf6fc1d1b650e1c7b3456f7a55d4301", - "0x8be3ee3caa86cbe21ce9998fe1c0de73ba6481125ef28e312002377676317b5ac4c27180086fb83794efbf86438ad27e", - "0xa0439d051d06a7fbd5ab83f32f0f95364bc043d9d934ac64df7031223e731d7992206879d567e77f35badcb7623f03fc", - "0xb99de1a16670fbbe3ec26ccd37399e2a23c96813c26428deda4f74dd3afdbd28cbe47e074379f6094b85176f8ab349fc", - "0x8a943a039aa33f38d3887de4e77566d446e87225bb8333e3ea991466c15c6487077c6decb9cc10e5de6af03e6b81a10f", - "0x80b109fb49ab810121fd411e4cb85773a1004af2d257e85ab5b4c99aad8d66e5803a8ca7b95587197e88abaaef0b8d42", - "0x892148bd190b042fe9b7914b8aab073c0d19001158087077a5946690dd60d99a1ef372ac01e372a434d00b0568a75fd7", - "0xa266dcc9ccbda054e396e1605eabde6cf79a028b697898090e9f34a4a4e0b771c121b8d470b14130a79cebc19f8d6e58", - "0xb1ab30b97c76392712b173460c227247cac50597c036f674361c63c3638a4c03420fa5b7efdacd0496a9b83956cf5d06", - "0x8a33c46084f669455ba089b369b9c8493a97c131f09c66f9347873504f35d6b94a09483b2775656ab32a12c7b9766ab1", - "0xb77a7c1402edd9ae448b7a606ba2eed192a9bb8f852b647b6ed689b0a3ccb81a4632edbca4c113750f62643a0626e2a2", - "0x8586e85e3bb07b07a39ecbd822d2adbfbf1fc66cf2377fbe6b1bc38369f86292c6cfdb5b405a0bc4d584c0600178321f", - "0x80cfe5b1b032d5a28662d13772fe112e9b73c997f8ef0fc796576bb39e02189c3ec0228d192c981061dcccb9dd3c4f39", - "0x873c085029b900d1fcbe93f8789d635e3a8fa558766701ba9fee76dcf05abb6cef518f2b56c4ca5e26f3847cf23bfd72", - "0xae8075937a23505f51a1a26f7f54e35caadff44ffc43465368daa9c330b553cb4548adbdb04e24c3977e35a08841c36a", - "0xb1c7076afec527912f7648bedef633ea0e3b02e5fc3fc495779b93e8a9f64eb503f46a1372c8dcd8fc2572c198112da2", - "0xb5233c4545bae360b07c4411776218a1d9040bad1e788e099f90149c58258ecdf01dbf246ddea48ac8fc2dcde6f34f20", - "0xb62655a8376ce1ca225dba04cb29f1a95d09e1a20b58f0330c478c6acf931ae52268779d6cab87d9074a362b9e82b205", - "0x9684e676088b409052773bb740bd3577bf0dc15d0392ea792393a158e643b165f8cbdd91cf355d5425682c77f2a91f34", - "0xa892744cc0c428c97bc929913ada86c36f280f49bd1603e21bf6b6abf8ed195cb05b22e586f0c841ee02f234731985cd", - "0xa62c089a73c6dcf3f7d957719c7d452962ee851d6ed8c4b57ade8a1e57156762c348fe5f20adf6d6ce47b4e98f93d60d", - "0x91b29be6022d43937df9c597d19e23cbb83cb6f5b764e1f7db6cf60dd9b3e9c79f1f617c3101c60fe6c7af9b5719fd5d", - "0x91d13fe99d7dd7b4744fa2fde41bb51f4edbefb2189ef3ca5d337ee84ca3f728e300aec19b96dee18aec090669c85400", - "0xb17a5328808ca929b794dbf0bf3a3fc318f8df144a892ec0ac2163a0f7c3a4614d7ec433b66bc491c05a286fe986d986", - "0x84a9e84bbecfc2aaf8bd623d79bd4240c630b81ecd55a50198de21758255207238179a345700e65d9bc6eec1a1a1985a", - "0x8d428be451efbe630740449ab3677ce6f69d94d75c5a9d91d14b2493a838260d6418be3d4658fd15218eabe3adfe455d", - "0xaf11126224f6ff0e88a09dbc0de6db3c70e3db3f6e154deb448d044100f989ea41c6c0259a8ecefdcf531f892a957d82", - "0xa51716b900a00277aa932bb03fb61eab3bd8e74edfad6153a06f85aece6f832af710f1477d883dd8e48931deca12bae9", - "0x9542a82039c2d3c538f15da884f090622c5af556c16370d21bdd6544208cb17e0a30e511b0af4a618e8ef70d0c43af07", - "0xaf76f93250bd7bda2b5e30e6f88416ef6fc8ce0cb614515a1f8d81dec717077209493cb47b79e8b1a62e09e394038338", - "0x8fa8d657f1d584b06d5bf41a52bc2c73853e4092290789df04eb8952c6eb236df601a1c6cc81de420a424d8e748dfc38", - "0xa6e93e27421b9e32b170d240b4cf2710c97b49dabfc0ea25759c5f61937eb3da8d45a6400f8bcfbb42bc9a7ae9b66ef1", - "0x81848c8d66d34d274b21dfc10bb36fb9497a1b152aad64a8f7c874e58d31d5dd5f39e30e6000b6d7b444e573da1e043f", - "0xb85692a84154f87869d97cb5f16c81fb0559e097fc37473bb11dc9cbd311ab91a46b01aa9debcada590992c2473ef0fe", - "0xb565371692ab0f0d899d8139d3eaacd213e7d23d6f5df6ac3409c961aca019ce861fb4ca8317f462be01e8c1dc7af154", - "0x82ae2bda0228d36343f6153fbc41fc5c79fafbc03c99a7926c624dfa28ed0a1d215e11ab83cfd438fe5d85d7fee50363", - "0x923f38a2f839e165fd197e1711ad52673deed9774e0590ff63ff9a9985f99612aabe003b9a98db2407c2878abc6d9b0a", - "0xaf8d5e1048de3b813308544705eeb0facbd604a0ed03e66c1d221be64cad35d71748d2a55d1ff3049e1e5053c7b1f712", - "0xa90a4b3b9d3b7c87c34f85c7643fd67dc771caa940c9e2ea81294ce6c072eaed698368a0e8056d7b819ce3d73de4424e", - "0x93a106e914d2c6892fee866602edfbf8d03dea1918d82d511e528b99c8423c260c0d103bfaf9992e0e24638b913af737", - "0x864cb44b1adf5a59ce7baeda0ddec3a0ecedd42923205dfabf30dcdb216a7b760d8895dedab52ee09bb09e999486b521", - "0xacb5f2bc1257c49c7df89837502e699bcb9652567c1716513f258f021755092954f2dc65b9766ffd9a10584bba424c7c", - "0x86653b3a479bf6e10e781e316e61437af1abc988f59399bed8fb4ff128f5f6d53f50a293da58774acd42b8d342e52429", - "0x926b7b90eb7d81fdad2a8a59e13b1573970e15c10515954b7c232c37955755b6758178314439ee6c3b0c881d4092c838", - "0xac05f011011a354f0e16fbbfb7e9dff03b3cf403dcc449eb5c71067128e314badf4d4dc5dca4b8616994ecdb15909c93", - "0x8e063c6601e553f33abc64f9553db5a19ea794a1f254d5a5f7b8ff2db4ed9d180f68ec919a0f83142c5710813baef4a7", - "0xb6e891dd4d44fd54120b7b8716292c27d4bc8744d96253a841433cf4b07895606db4a3cc5872c480616b863073bf77e1", - "0x8dc623d7928234bfbb8cd0b4fce5c8d9a9db848ab0af967ba9c49daffdf719cf8b55e1dad0b7e421571b8770cdfe9df0", - "0xb5b53f7d6b5d1af75e5a1720281feefb8c9039ef7f1e1969d83bed5a2f73cfbca91dbf4fb8179d9b0d3bd06d1207089b", - "0xa5dbce9e6db637e053b4b4d3df07b724b50d11eacd3327ddfc5aa8f37b9a5bf628cc9b428328e16cacc552c1dba505c9", - "0xacb82d6c9af9af0dd426a07b1aec81b388b61042bd601546cde248730ef85a09016bdc66dd014447fbb56fdcc23011a7", - "0xa41692e96f1d775b3a9378b3634495a8350dcfa52b4b2b7773b39d36f7d349fd5ee9a2b3e72769ca98f2319319890216", - "0xa0b4bd6a68ac5735539cbbdd78ee4faaef7d6488eb7a11e091d94e315cfcc49a90f204f636dd8033857378ddd67cc153", - "0xac3dab32427b0583159482f73f94236980d69f9f8f781b93f44aeb43dbeaa740c77898c38c57677b42c248b9bbb1d673", - "0xa6cd1090b97826486f59a056ed90cde29f2ed821211391f2f16e66f1e8914398348cf6f0df6d3acaadab31f0382bb5bb", - "0xabd1252b722aa56010e3bd4119f2a28a852e9ac1a8ce68c96b6da9d00fac0c9fa70e67cd4afd45e0a8042a810b8e0a91", - "0x9194b629ca80b3bfefc0144553017343d0915aab59faa3d0e2bb3720dd3c8fe07804be6e582c6d57c764be96cd40f2c9", - "0xb6bece03ae1c5935eb38b14f0f64d9d0b4410c02ac309e085a233c74bc3e67ce63edea56ea37f4532e8b864aecacadd0", - "0xb753eb9184f5b30e77bcb5d3487323e0f1178f1ab3e15130953381272209a97c3e8084e810dcebf1ea7b22f0a90b9c77", - "0x87dd4a76955bc98326823cffd653fb7b7eda5df1a971b72ec2a4d25fb3958b9d6092369539361069e7e4f1dc9343d929", - "0xb0f1e8b25a2687d98cc037272473b4e3f33cc8d49a3c83a335d48b8a0d3ca5f84e8e2bde304ade6f7d50e5f3539d639b", - "0xafce1c0205adad1ce52fcca71a99cd6df9da5b748209c2ed1013b5b7d484b937bfbb26db9e9f8e77c081e0a0384114b4", - "0xb363d31209c075b94441d1a8ddcc6bcf9eaee78f8adbf0992d5c7e7d638a02d58e19203247443c35d700fc8ac8a1b7ef", - "0xa0aac7dbb08a10f6cc2c6a4d37aea6bc3dc034d933f49de3dcc79bc0b7a011b1e27df7cb882d155287436b72092a1da7", - "0x86dde01fb7090c80fb404afdc9ec64ac40909b64c4e16460a4c862c3a3f857ebfc0c3122250306c266cb1e9f9f245934", - "0x8b3ebbbb0ccc466c72afb4c27ad99d2d4e98b5aee9c05bc283ea3332e5f67a3d9263b71d16b20db31ad4d8f48174b0d7", - "0x8610c492ce130e76c06b7e0204617087ebd8f285cc3f007897c253a0e1af50f80a825ea7fa3167f882e146402fd342b7", - "0xb17f04a257d3142005b8517dfb57d28661604eea9050ce39c60ba9a05d23897114c59c55af199ed186034e456e111cb2", - "0xa04cd806847686ffe023db1721fffbc26160582c239d5bdef08f4730e2fbb64c341fbabf1fd831af6eb84a113ad7e2f7", - "0x879018340deed1fc762e1b8f3a8b78a40539d6f917215621b725c0a3aa347eeff60765e5ad6f4a36bbea51ab77f88726", - "0xb421e65891dd0c6641e8ddf479b065163897a418d723fc6dce19046501e01c616bd19c9d5fd6b395e65abe0ef956d53b", - "0x89350af1d432a8c209b69f937d2aa20a24d5eb95c5b4cec097ca3dbbb3ea9efcde2a8c56c58f8d7901b96a627c45df9e", - "0xa32d6b31cc9efbad4bcffd8b0ffa46b8fa97ddf3453ed151d7de1d03a02cf233f07415584893155d2d7e14b9534921d1", - "0x8efad47caa32277eb04137c92def618e0715c1e77b5053b0cdd60fa03256fa1c9fba9aa86fdf1c04cda9c5450863d58f", - "0x8dff9d309f7294ba750158e70474c978d1dd98739df654945f5f29fedc607caa06b2866c93a0c7b830ff9b26955833a6", - "0x84bb00fbaa4358a2563abf96d2434e4a26acda87b189cd8d2aabde1323dc1eb2eefcdaba3b90e9ad1215ee469745b72e", - "0xb75acb924159ecdcf49df587d5ac1b1b04291600a4f530fb7cb6231e7fd1029f8cfc957c891a59182518480c2947f472", - "0x8d2c671ad0d442664a0cf267b39be742b1d3760935137e4c50195695bdb99254c4a4d5830634059d96dfb2b4e214b067", - "0xac27b31843caa8140e729a01e7d0229d7c221feccc89ffc173c11a419af3db0f8a47a41cac358e15ef41f679a3f0b96b", - "0xb0b3e33c590bc00faeb83f4b160748fea4fad3e21dfa324bc14a138ee8c5e18743b6bb27cd0ad7c7c28c2b3e92040b0e", - "0xb0d2882c5a0a21fe05b522f2e8a4f43a402bfc961874deec962a1e3d039e411d43bd3d95a26d930c2509aec8ed69e2e0", - "0xaded1e47b3ea6ea7276736fbd1896297b9ead21dc634d68ee56c20fae3da90170f30ad0642be10929ecfe7de5ad8ce5e", - "0xaefe525c0dd24d6c0a66b43ebc6403ac75bfc322d1a22f76340948cf3536d2ae87290ca80acd3e55d2df9aaf0fe6bfcf", - "0x979d1510d3271ff1f06d9cefe30badaece436fae8de70b01ac843850f678aa5f48821dea48ce1c363fa35eec37283f3e", - "0xb8e8d10692f1bad943052fc366291c134a0fc7ca4696feb216aed46eb32de7333a9ba4f553389e7e58c8fa96ba023f58", - "0x913353bc585c0248a54d4705b5e29cc778f304472446eb4baaf30bafa30f2ad0643aaf21196a6c4d177b11eb4e2ad5b2", - "0xb25a0e3b9f983c47b8faaae8549fa7d00d12d7145e1b232d1813ff94058ed603957a340beff25711075cefacde767661", - "0x8515151729ce9a7984af3b94f74880a2402ff853b99f924d581fd3935d8ecfc80e2a1185918a5b1c4902106bd1561ff8", - "0x88e4282ded5e2163874f6464236d5bdcc3c484a0fef8ed0da8d0177973e8175432b75afcde2a4d7d6aefeaed52fbeaa7", - "0x81c31113f2a5ff37250f395d6722a43cebe2a267a0ee40ac06faccaffd7d6eb522103f0431a325aa46a54e606b14de84", - "0x9302ade30ccd62a803b9610a397661298941a644b1ee9d293c63a6c3368fa3557dcf6bfd0c9b44c5c2a6be06d1baf663", - "0xb4ff9f1f6a2a64c50b0a16980ca7cdcc297c6f93e11c580019de52f58381fd0f60a66d3e010fa7ab56bdd250e7b2df2b", - "0x8e57eb61ed3c919dfa0f0cbca2cf559cbede5bbb1e89ae4849b380339cb1c567c98fc2c671211fff4df1a058d46a42bc", - "0xb3d5b45b4096eb088523d16bda1c6aacda01473533314961e6a8de36ccfb35d4b717eeb1ee1bce47ad3b80e9e5084d4e", - "0xb933ff4d3c5a77cd7cd32926266d4f05198178ce350f7215e512e71b07177ac1ff89ba183e424138e1fbf088ecf86c24", - "0x8cf430a6e4eafd23bcb5ec8ca3d711bb56ae719c8621ecee964ef3bae7c53044f7ab3d5d0b911e09c7543e56c1e82e11", - "0x8b3c34f5321c9ed48024196e1e941fb7a5975a045a5a9de88d7f200fc7ffaa0b3e500ab7b535e02bc5c35fbe420e2c3a", - "0xb3c235b65fbdd5c4c2aa45271b9e51674f9a0383a8ac383b0de53125a67c87261540a95b8f81ffe67ecdbf3955b13814", - "0xaaa93ce79ed6e7084fe906c9a1002435ed6829ee3d1380681b902d35dc9e5a23a214ae12dd4fb76691b0016f28d43651", - "0xb4c9533e50ec58f75ea82e2aa7f735c4257bdc1ecd0da0b6521d1442fa61f19e4f73cc90972b47a762f5cd9af591d957", - "0xae0255dd70befe7eb979d41f9a7407040937e7a879daa64353c66d524d3d3cf1d5e854886a6c32c142f4673c56a4df1d", - "0x805fc5ea840d1c2e6b35ce586309698530f056b41de7a403d9e7d81efc2d7068976e8e23bc0b9ee256f39b15bc4f7ecd", - "0xa8de5055b6d2310b6ccb211a397077b211683b05c7e68e55ff05b546c5c81522e6097a3c3b4b4c21fe06667071beaa4c", - "0xa4014d39b23c13efb4326956c5ee476b1d474663950c9e3e45aa1345037be862cfa14aa1d03bb388085bdb4ba9d70a59", - "0xaebe9a9ba34d6cd3692a8bc0b0aff5648e16b36d6c123e636e9260386642e29d52ba71ef7778481c1b1cfeca7fe6acba", - "0xb59706380c9271918ee16a04e84e91046caf99623a0120aeb37a7a98d4c954d3d880960086de6cb180c8b922ca1d7405", - "0x8dc0713371808850f2137a89c33fd55ec2df6a028e22b2679e09f7084d5c471451187f6488fbd9b5100b84593540e5f3", - "0xb492c55e470c35c7a7efa536f3e7c1e586b623c6669ba6eceeebaa1f81fe3b8b927c2e522fb12e603ae246d9566e4d23", - "0xa5148eadcedab9ae08f5db6265326fa415aef46d0b24155910210338500be6d77bc9fa6f6e284a4c2552dac09167e450", - "0xa0af7b66c8a1319ffbe7a0180795b442cffde153f9a871046d6bdef959378c3068813c516e280371825af06ef2320b15", - "0x95479ffc4903f252fe58632e833d63d963469e89744d5c91315d38eca21b98f1ad6fb3ca77d620a6f97d9ca3aefa1f7e", - "0x84861bdb5880f663a5d9b5e89b59a940611a233d82a9895a330464f7e9b7a6965c2420704f3adc61f876584d06771f03", - "0x933c374f176381a3a63fa98d238d3b7d337aa745528e271168f5b550fb703664c3128097b927b5922b4ae8fad30d5e40", - "0xa3ed2c5080c52ad1235fd93c9bbf128b48ba8abe364247104bbf298582930bf3faaa4f4b6103062a4696e68c44f79555", - "0x94668bae91eccfa8ad459588f927bd1a169af834a76132b2f2d5cda26a91094cb94661e3c59f7547b290f827eb43125f", - "0xb704404a487a7dce87ea8207dd5d813378a345375e8e2c07de349c1448a39af8672bb4436779b3485adc46df2212f409", - "0x9347dacaf6dd678574a4f1a95df79369e3f5543c565b1580f907ecfd17b5d6e1ee3322d83601cbbc6d6ffe0bd2833a83", - "0x92841abd813bd9934bfe945e428193e33ae6d4dd235a16edfecd6e4184abefb8a1f85015ee83caf9532dda380fd678b6", - "0x95c14a1d3a1e1ea18f8a61f34b85ee8a794c95d3b4b0ce6ffc89013c9a80291a9a2487b00bb3de51ca2e4290fead7482", - "0x962fb52a2134123ca31d91027fe9fb62dff4e0542c66b55899a163e50f6ff2c4c4b9c1f5b5b3d6c6dbda40e757c0bd3a", - "0x8aa06ae95b0ff361dea2792e465436d810b86f803ba6121ff93fddd9ba60ce47e846eb2d248b28f2c47bccc9457c1ece", - "0x81adde02ddc49b6cc89561716a839fdee2879c78d1ea0fc0418a6cd4a2a8189a2bc245bf2d1e535dde07e93b8a5e18c0", - "0xa7a5713055455728d6d982a6650d1edf1a3b4612c9072ee8ee0bdaa3992963a6fe91ca242fe36f839595d09f6a47aaa5", - "0x93900cefff6f918dfb12ccbb256adec89fb8da019324b811398eea03f6fd34f28a6eac2ce5580904cdb289879bd4b4d1", - "0x820262cbf7864213e768b5a38f39d27dcfa7baa5abca557ab575b07c33917f7b0f06f0a6abd81222fe8a5a69d95d774f", - "0xa33114d4cc3cc84258fdf252e754c8bb1feb6a130785d35a78b4b05d0f782424a5ce0f34be3c1a14e3bb1bc0246bf0b6", - "0xb966ca0a11f0361e611ab2a8907f78a3d639980cae405d380f3a080125c734059acb08431a42ef3a60ae9331a07e6a5b", - "0x9305d107311654ee727182a1683f322a78fc637bc214eae311f8778773e5bc52063bb0a902a5a962a4a26fa0cba3b06c", - "0xb3dc808231c75e681aa2bc4358c41f01e371bfa5bd504e7bd2282e35e72a2889a51747cc008dd4d8b2a070c8e4c2d7a5", - "0x8f05cc76848367abf2020461b6bcc1ecc412ae9f114d44715875f25f34d8cd26b84b01fd6c0640648676b8d2d5120814", - "0x8798c23f0ca8a7b23152ce17086f31e2a80533067f15ab4a7b43c127a5d8af3297115a3cd7978ace811fcc38805abccb", - "0x99a917f54253059a45103e15e92e1bbdb03e80584a85b658f231aa5947b29388f5b04588de1ed6de998741d360015313", - "0x8b0ce3f6afb5aa83ff229ae1ee0f986ec4d31b113070c7ef3c1ca3613d74e3f23cc1cc81957bddc709a5c5bd85cc64f1", - "0x9035b71e4cbdc7c410fc03a85543aed34a1c0a98e07ddc933e64886f1796416ff3a0f7754b5246ec93d617aad0f53d5d", - "0x87478f69c45394f94c67b7196f60aca42823ad92ea86a427d705576fa6a9bead40b1a4106767b8a20411e757f8762b68", - "0xb36901adf577f159b4263821a48fc5888e7bbd6c9f3857968a9cd02e1a1a788c08a566b7bd5bb6be294fa5ab92b4ff6f", - "0x8a738b1392aecb35a5a1f12920522997c9016a0455354e41d2e1b81d8ec9b30a90f71492c7bc122505b2ecb0654545ec", - "0xa5a422515f17f2bf4b9b6c4b5b94df27ce80826cc3ad2a8579eb6666c67a96355e60bf227b36e1f082d749ade7a38a92", - "0xb6d0e36a98e0518b14728bfd79db76c408f58220111e8c4dbf5bcfbd7a85bc68022456196f07b9f40158037a3c3eb90b", - "0x82ad91b812d08bfa815a93b47bd3656b493853bad52656450eb408fc915e430192ae123fb9daf4aeef4608800e818b74", - "0xb8ae5b30118dda7b972464e14a96853147c4b362e9cde22130250447575c0d8d05053202db4c650467dc16330cb54b36", - "0x835d913a3d15ff205497b98107eca77058beebe1aa35ffc20241bbc2a9b4d2019ba41fa3c9b43fe2265a1110b5c2fbe7", - "0xa283d88acbddb50983356f2aed99c2f153b6a8f489b0597d8db08ff7e3b04392609e01aceb37fe985f59773327258195", - "0xb6927dc3318931eac59c6e21def3ca79154beeaa4c57e11ec1f3362aeb33445366dae770e533aaf33c273eaa4f54275e", - "0xa6033a62119e077b438e0170f27835597e21c1d6e4acbd53fec7df69bd1372148f90966732fc5c004857cdd44b8a03c2", - "0xacc764a116e31d63f534b3e0e42a3f899d817d3ec32fb4504045bce7ba3a952ddc81a33d48c5b0499eacbef4268bd5ae", - "0xaf5d1f6a67dc6361e19f222a24163be388033a3fd0d33ad204f4411302668436f933c4a91c6472fd4262397417e3c588", - "0xa2b1fe93eb481d4fec6fccbd64945a12cfeca85aa8b8bbadc4e4ecab2f3ef65616294dc168d6c955744b7c6acd712012", - "0xacb6d3e123572ec20d0ecceaf4916401874f0298218b36a0ce089acef90329204611968c7397c2a518c0a78d02a9285e", - "0x88e4457b1c9b56957b76a08e98c569fb588b081e0e420a0d859b70296f648a8d64ff35ca61a39d1b8ac3613ea5fdc2eb", - "0xa7d1643b3bbef49b2f9fff326061cc27a7f65228e40929562de73e1c66a9d164d42bfcc3dae9103b2acf27606f18b031", - "0xa66e3b97efb7ce4e81534453d3d41ecd4b5b6e9bb829b07b5afbf11fc6ea30382a0059c33c97afd906656ec19432830d", - "0xae9a17d0044abbf3e6aa2e388a986754d6b0fa35d115e410f69ad4aa114db1af5dd0389222b838cfd859d436aded1b5c", - "0xa4a66a163365528b08333f15c6673ca48d7a9b6d17822f1e5390fecad122bcf7ec5656eed2f22fbc6ccb6dd96ee260f3", - "0xb7dd42c938c2ec50c3b3fde92ff629a571e46f8ce128fde7c2d8f18796ba1b1d7eaf7337212f55cf5cfc540c7d2dbf31", - "0xa36bcad22f3408b3bfd45d272f3387cdfbff57e014226dcd1db54bf3f8d1d896fc4fd16640b5d1484c9567ab9322a37d", - "0x8c9831fd5f74ffac203aa6b6ce03acfde8a2fd939b79236a01931d28b424fd8f6b6e44522d28e086aa12f0b110e5688c", - "0xb48bc95abd331d901610335299580ecec02a263d2b03bb0579cae3aa87ebf5e93dd110e7fa4306d31974099fe6e8f58b", - "0xa15e27a87bcd8ba69ebfb6228c3c48e19d79b22978d3a63af553b3083ad13e48dca496896cec195e63b8a4e2c40cae7e", - "0x96f3de6fa492dd2d653888311bc918ab832d6342dc7af9155bc7070004e89ca940b7672dce0a1b4976a7c3018f13e49b", - "0x81a022bee3593997f556ea53e2ee484188cfba0be4b831ccc048ae4b5a6df9e3b4d57c436caae5cba92486abb71813b0", - "0xb9d8e46df67e481c84d5520a9613aa92750c8e8a1e8f376b8ad7b71a3ebd95d2d191ce538e6f7fde3ac5943f70c670a9", - "0x8f0b52365d944f59d2ed445b6ecc4f88c687fd281c1913912c8e6075c3a59667060b42f2c1589a5955e9f3791e23aa02", - "0xad07429bab813045fd909b928ba4eaf262b6ea40b353aa43157e1e683b2752c5bf19eea7ab6ebb8daa8ee91241fbe84f", - "0xb90a99ec1f31c43060ef649e047bf24f2fa7fa9daf904136c6a5846d9479966b54090ded7093e481c52d872c6377eb65", - "0x8cb05fab3ee23db24c9bac109db52895b200dd115209bfa41fde510b29d9871907d44f689fa0f5474d12314a711f6fa4", - "0xb00d8f280ee21866b01ba3de3bf943a7d0825ed67db03d13a0b69f54a4ab389df1cb10909e831ec0af8f1675fa7dc399", - "0xb383d14fdc47df80be46390420603e7f505052b1a44ebf595354726f2b487f7f18d4243709d347e1e584c28167a0e988", - "0xaa951f60d1e069304222a8eb0338a94c8b3b4515d7cee833864b6c222ad76f6c48e0346c5603c35a3b52edb6f9381911", - "0xb887070ecae2884109eed80ff9341f5fc514d59158f5dc755ea46ba396f6783b8a86ffd2fae4419cec2ed57f4dfd4327", - "0xb1a6f1e4d25f4aade76714e52bc426beaa7592b975f07d0a6b372a3f94e7a3ab0e8829575bccc953195ba0c9bf46e68c", - "0xaa64bc4e0d9502d294f0d3e6a1400dc38f28e87c85d3429ab3575c821e1229f1dc8e2c13f03080006bc897e8fc3555c8", - "0x8f215476d94bc2af7d2e0eb68783292e314c9a4f812f3065cf064f427aae165990dc9665011af502f5713f3664317989", - "0xa578c8991e9e29bf3ad7be44bce3817e1c4af3e4a8ba3d82643378da78538787f581b9caea7602b87619e5f8cfb337fc", - "0xabe5453b650106cf65bf2b7faf8ff973b7b3be0e6f42983daaa5359dd4ca225edb7228bcca3d71bcb8d77241b320fa90", - "0xb7ed1d027dfa91d0ca5d797295e359bdb1b0221b1f5eabd2ef76ea3bf456f9aa9788dd00ea24fe0add9e3d9b09ae2428", - "0x96ba0f0c5ac0eae3f0031f8b7a87543ac369c22122681cade0ea33a6ca370cafd360ea6b80758476ab94cb07ad6820e6", - "0x966f6191951b998202b8a63e3b10ece69616b989e9695cda84a450cb953acaf9c4f902200b7492eb66cb9ae0cdc8ecf0", - "0x8d7bf21f76ca0e3b3758c293e66e977f83533d918dc445a09f4f38975ccf7220855627de6460d318290daa03a5f5c68f", - "0xb10dcd91d6602852783bb76b0a286523a0942e8eaaca4e0ee5bc76cf19d33bc631f6d0fda1c1ca51bb3d5d5c7dd43728", - "0x884d502d934e2b045357e981506900849e6eb051ca3ecf3079b485b348372496db97da384f8d2b5a52216b4d223c90ea", - "0xb074162e5d33171477ed48f2f185b1c83e8fc2e7906681f96ed97da8ee86be7476d65e61648383c2766ad9853ead35b5", - "0x90bd3d8b475da20c6e32324e30bab475f2059cd81fa67840a6c831026cf3d5806496a3a25192128da4b819c1b7cd6bd8", - "0x8da4889258cd6ffdf1608af8325230f74abe6a2a511872c2dd10123b491cb09407fb979d80fb1185ebedf421ba22d0fc", - "0x96fe1d9137c24fba18b1ac431ccffc01ef1792623bc334ec362751b7bac73c4d4f7e9bdc2d595ad4731c71808adea15e", - "0xac816ee0b9103f0bbdb50cc05f9c5c8f7ff2f14bb827541c51ae5d788f979c00fe4796b86eb9e3ba5d810925c1f34a17", - "0xb231e98ecb3a534dfda5b40916fd4fda270e316399c9d514dd510f0602cbc29e51c5ed60107b73e3c9721f7ada779f91", - "0x80115e104f22ff2653ba7c4e1cc417dc054663d488f861a9bbec4b9e907dedbb985e6e78f31dc16defa3aaf4f88dabe8", - "0xa0dbc25dde933e6114f2ec22445f1e209836585997b14100f3f8b7e62f5fdc6aa2a85ba5ec39a5197c9d4dabc9a5c452", - "0x8d2deffdeb1f0abed8ba62187f5e1cc06a1e2bc49b3e15f73c3d8e574dfba7efdfb762ab512cce53d7db790a7354c56b", - "0xb73f4897e221927feedbbf209e3d5b9c08f52bb732dc0d710822576abb7ba5ef0e728d2d95c802a00eba925ce99d734a", - "0x970761c7ee891b3ed08253d2c0d28478145d0776e2429c85b4734e5eb7a6416d197d0b1ad3392b37ce8d86fcaf9de7ec", - "0xb4c9e2acb4c05236357be37609abc437612244bb4421d69486050e390d5ddb52887a1b3e1bfe968a90f1695d892ba8cd", - "0x87caac2c93e192c34b5dabc36abe26a844a33bf63e9b01a668c90b70701360a0417ae3248173450c64034685d913f4f1", - "0xa16ac64cd1a7ad46cde1c93024fdeff724afe228818b72bb66176c7daa090acf58e7fc0aabc582ad22486e46f0b96c5c", - "0x936bdd6d67d666274c29765690f4ad9c4b9203e9bc9dd5af558a8d908dfe8d6d4346f6fbbfa69158cdaccb0058ed0193", - "0xb39af8d43ce9d120497888fba0dc95ceeabdd3d84421c1a30fea226e03b78cadca0eee57db524f6ccf1f6235fadd1470", - "0x847da75509ca07fde2277aac9e7622c5874256903a92f7a56382ad3f79d1b3b0cc0b06b2a6b2bd1749ed567e68816d31", - "0x969407bab3f8106a49be63f17ddd603e185afc1c9fc0ca0e90ac415f53923e3c6a69fe488d33403521231c5008bc11e4", - "0x82e25ef35abbd9b98c55a45e7a71791925639afd92780e64a154ad8a94e9807f2643854250f30bff1c5e8806632778f7", - "0x8e6da5cb8cd80d6b8e2321ba3f034ece1813a7b6ee3afac73371a51434a3e66221188162cd9b9ec035326e7e04e74b25", - "0x9868bc3e60478fd0ce37d35e0e4f7695f1ffb7cf2e05842b3a09e832af33c7ba48448935d425196fdaea9c3e8a5122e7", - "0xac7733adfeba1da388eee6540a127d0eadcbd23770f2deec39edc0bfb1002beacb9a8c7106baedb22e664f37771c1410", - "0x912581c23e3ad0d7eb886cfc22633fc704e530b6b4977086f68f1d9f839bbca3bf0162acede87c853e8ad8137b5cf345", - "0xa0315fee6285a33d4ec60f6c1557ebe4473e8990ade0feff7e008d3be1a709f5f486abe784398734d9ea1193929697e8", - "0xa44a08d6fe0a22849a8f518ed9b30b78367de205c3301fc8159ea273076488299b35c362530436dbb7e21b6b9f36835c", - "0xa591ea6ef83f2ec78a402a86ae5b82e330998e18ce66126a89046f169dee58634dfc531b1286277eed49f571df5202a8", - "0xa60d86619b41f59b48c800a302775656266725b44ff8318014fb668f331bec82b3b543ca848a7d40b2718f29e5ce6cd1", - "0x9420d0219d407583fff43c560964e1da06b105043187ea156771b1e4dfb5d5851d06fcfd819c7d8bb6568fa1bdacd916", - "0x97ba0b6731c78eed331530be7cc374a7f4a7cb2144ac73b7c000ca36036f68754d4edccf73ce373dd6c6be55177d89d0", - "0xb4e07b5c1376900fa2dfef8fd1a5a4b6152df7b805d5efc29057d1df2343f8bc841284ed23d2bab5cd1431fb95f71b60", - "0x8017de31e62a24bed74460dbdde1717f3a9cc17e2e2ca9848d77c3b5c364e7e1d58ac0eabb3daa2b7336edcc8a418b01", - "0xab6e409231b778bbc1ab74c3062a376c5287c0cbd7d19d4ac1d5da1a8d0571864d0723944da72581783cd7b6b0d529a6", - "0xb5f2fd4ef29a2ac847358abf2b3e7a3567b8653a4b9ed8da70809f2affc6ab44c65cd17f255db0cd8315e4801bb1a408", - "0x91b61d5d047e9c672d7312f563b8da90d9c2c1c1268913656f061028748a351e116f524593b1be7117a46f168b3e829a", - "0xb6c10b09ecfb92168906191756cb824694caa32c6f2f9b19c51658d44dc330dcd344e7b04333392a8a93c73346a3845b", - "0x9431d01a121e6ffa15c32e724dadcebff65f806c11717b050c106c0c80e43e622130f41224533d13be4a8d14a66ae1e7", - "0xa1248085c85855b4df6eb5a02df0dbd5de5a8a82656e1a5f61214885fcb75428647c8545a848960701d61c3002840647", - "0x9867caba8f4be9483df9b48e2bfa024e79e6797adc2198f2b5115d7283931fe4cefc382323edfa1e850c3970bd1a2d53", - "0x89e88c50c43d7e966e60d49b3afea792429563c93550b10584c91e4a827a3617971eb286c39205e2af4e7dfbc523fd8e", - "0x8ed261502f95814410fb081e7348eb09f3a3df22cc3ca82a2f071abca0190e9f041e8714b811418caf7e1753cf284e9e", - "0x87ac65370073b6bb85a945e138e4d0a5d71ed88739f72b9ba747d2a03b5d4877e8e526026348d2578c752bc4102055ed", - "0xb07de38d07906dc2838be840c291f467d9b695c62175c5afa46d80f34674d061107d6fec6847ba5f17f2d8729f31f5f5", - "0x899348bd385a7c3d38f9d740001c9a543dd8496b58807a6a73180c94f3aa5c15a56cbb85cd7124458e2ae44a454a8a58", - "0x91b70c3543b8e21cbcc8a40cbe00cf2ee0372ba9ddc7f610b711a070110508159e6a52e8778b20f0194ca09b109881bb", - "0x8ab84d75831ec1e708ec74eb6d6de2b13bf97e2d2262ece39e5ba5a4a3049c8303023e40fce5e87b237bb1dabfff5246", - "0x914ac70dd91ccb6d7b83a5ed0a9250c66e77a815aca17416f1796fc0e1c26bee7acec5de11e65061a44d2d9c35f5d19a", - "0x8867260f8024f533fcb75d9c3f2ab113d349504b40f650a2c92bb46aebae3a55de6a113cb6387bf00eeb2bd4719d87ea", - "0x9976dd4e56b16fe11533dce2734e2903a3ec986dca6540bd9ca8b758a59a1e45b1e69c0b8f094d42cf7e015363ce37ff", - "0xb48c840786653a0f3ed6b07f8f980284c5eb2dd22e9ecd5a0566754a1534300e129b39a8a6d4fc48bd403b351e714f05", - "0xb1633aae7c5e5c51a82aa4e4bf9f92c0cd30cc1067b03364825ecc492fa43391ea075195f2f73b99a11dc49f670c0e89", - "0x8769a592f503bf8ab03d767524d9ec2223c502ebf15b69eb4b3d53325ab366888afbb668bcb380230b5bd74b32d90a44", - "0x87439671fda66bf5989fe1fa2aa32519ef908aa6ab3eb34eb5b7d908e9a7db2d679170cf3fa0e0a388a355b8c51d306c", - "0xae1ca219832c90554a91a7258ca5598f8bcaaa7059c574803b2688d8026df9083985c2f8f4ad3aa9b122efe64e0b2481", - "0x94916e6dca309d9c7afb9aa4c0bc89a3de875a8537cae1fd32258b34782994e5be5c4987577d697ddc86b8d68dbbcbaa", - "0x8c5361b85176adf77ab1949d34edd562d8c16979e33b59d09548ad372b8c913ef385166bae53c8fef814a529fceafaef", - "0xb968172a6a831c6ae53e876dc4ef8686879cdadff0aef4147c4dc3ccbc173f89748b840a30ad393eaab69e422363bb86", - "0x8fabda060f8bb2bfcd675803ff0a3f834e2356152f88bc79c23f58fbfa6b0c82850f281f7b8fd2a5e16230aeb4077320", - "0x8e5c887c318335c5561e63fd3c3f64edc669c0b03b217e3ae40ea29245885442864dde15751d7c6ab177a91fdc1f7235", - "0xb2f67f9d64650c6b51b88e7ee6d6a796b453131c93a7791cdb2d0a4922d3c913a4ac988bac5b4b9bfe61469886e1e7a4", - "0x96b836824dc2a12ffecc6a053f7549b7faad9808e98bf20f3c9146fab05098df56fc2833a6002eb39c935fd8757d4716", - "0xa4aa33fa77b62605f751bcad91333659e9345967845226371e5f38d5a7f72405d0e30777b485b730e6c62d8216790cba", - "0xa041bf3467320df4bb7baee569cd685a65c9d0e431824b7de93ee47ab8b3ab20298d60746fea7fefb5bc82d3f7e25dd6", - "0xa85842f11f490bda22e9f73409de0909a2e61efc6d8be0c3f561d881988b4d2e6924ffaf0a4c40843481892b272943cc", - "0x94de0ecf58ef27228f5afb12496c53b075bb347f900b2df98f47ceda8675bc2941aec04d1c8ca0dec0233430f2759824", - "0xb1795a70651be509c0955b07d58a1b8655a2e6c292b939b6c156f0c0983abd7e416cb0cf14afac6ceec85f2c46b83a28", - "0xb6beb936ea1f1639ae59eaf53015dc1855ca0f798d9ed72607edbc6c119741e10af5354c29571af8befd83b8255a8f58", - "0x9424188ceb15c1b470c4bb17c71a37af56c87625e7b7fa752099802673c3a5a99d16e7d6dd8f8b680e89b75cbe7920f9", - "0xb9e22b5df8318bc0ff81003e8208ff3217ba1a84edf6a0b326b7180208d3a9144c6fa54c57ce6d6071ccb1a40eaf4509", - "0x8e5fb55da49feb7a9152528ad6a6766af75cce249eadaaf4806c6d4162f65f3c7311bcf8da72b96f6636cc019546c05e", - "0xa55f751de82aed5842f94d1ba1e29976c3d0146267b11eacaa4fc765da8d2acf617d3a65a2a74aa983130851f8c57d05", - "0x9647758fc596b78fb52db58f2ec31cea186d9d4f68692f56e474961b277396af4a51781b0a358a6a6aa8138e46443a43", - "0x9461f6dc72988b44c662865cdc01c0f970f078637859cbe6314fb17d2cfb3451b222cfb93a5c6eecafd1ddb36de075ef", - "0x93b30bbf4fa0926cc5483ba9803c8b001aa80322addcc866bc514f2a10aa43bbd86008e4671ea26d8e0d2ffd4bb8f2f1", - "0xb44020d0f062a001bd6dca2bc3ce61b17efc7a121a9035709f01a8c34708ed0c1c85cfe98c534189e0669eea719c88fb", - "0xafabce43f35e0d3201b60226c72c30177c4c5d75bac654fd2b58b3ce9de7d83ef01be60514817f1e7bdb525c910b8bca", - "0xa97bbab394253ebb02ba47ad391db3aec1b4d03e88ab3e7505730640558c11fbfce42d53b7f85787cb564208d3dc826f", - "0x805a34cb0c8c7ade28c69dfdde46b7a283e539977602aab165316e973c62bc65396b6fe2c96750ba028c550de03100ea", - "0xa0be38fdba281e0c248933ed73f1119f90e34d5b4435bb704a5fb7c20805e195518a2a424bb483f16500d74f440d4a53", - "0xabbabc7db0a20030c6e687b89162e704720a010d7ac53b9766a9ccb7e02d4ea1926792f5263d715cb97d67f2010288c2", - "0xb9e471a7a433a678090fe4324739dffe238ed7e9a867159e0b43fa80c9c0798cac6b58bc09a389223f94f22fec43e18b", - "0x9818e9a42ebf415c6d970c87261645f876d709751c8629d1ffbcba4abc8e3a2a1db8c4c6a6324dbf433c43fff62803d1", - "0x8290ed53eecdb61157cc458dd081b9e890bed5e4cfb643d11b549b2c65fe68fb981d4311473510781945b0ee763a84aa", - "0xae730a7c69866f22d8f9b0d8e17d7564c25763cc77a5eb718d5651b9c5198b2b9d3eed1c066f4985b2f6d7edb0a109d2", - "0x88325e421a1be440175293efd498cd167dcd0914c8827ebf64ad86788f1fdeb3c16d3de7a681f958b0f49046c54fd804", - "0xa8f592d6ba7fc3ab8ce8260f13f9c4886191530cb1d7505d0beae54d4c97d09712930b8f34ad74f1ac5ebedcea25dc8b", - "0x81c0853b0310a96674a92a144a14c48fcee0d72a772451ed046c284f16fd6447f67389ff7841d752a025da172d62e73e", - "0xb9f50526ce4bee12fc3fd8f3582f3829b90840f6eba06f37b53febc1d0987bbf58107d73fe4373d79e53827270bcd817", - "0xa2ca28f619d4821f450b9431bdcdb129d4f35dbc2a4976e4d416dbd14e378d4d60a517457aa0343f7a5e60a7e246e22f", - "0xb9576225cf7e13374d3975703b3850251d53ccafc6feeedd07be2b0bdea63b899139a1fb446dcf76f62f3c03beea0486", - "0xa88df9f6e95df995345c6265af546158499fc0d89447d3b387e7708fa037f95ac9c4e20ed35b749b8d8a7471dedeea87", - "0xa853ec333af8f35d51ddd6c4d45972b68fb36219e34278efa6cce02bf8193d72c6014ba6961f8448785b0a43a31a688d", - "0xa1ead9282496e590bb43908dc64341595cd22b844624e02f2daf597f708ab0d336bcacb5862bce3ce23d1a9616fc6179", - "0xb97398d8ebb52535a1ce3a10b2255d358142ff653def823ad9e9ce4ca5f372c6e7c9301588ae5d914b2b921a0fac7442", - "0x8d0d292c7e9122b8d001b3a3323f9d37dca61de5a595f9402ab0e53e941c83f80237a853abe4aaf012a35cf59df48c68", - "0x830535a5a8268d5ce4e7462fca4f809348908ae7ee117312244e0a9c30b09d91b6f798654d8064091078400346614e04", - "0xa44a90d3d307ee3a3c3838ce43a873311789a9b9292c62a01622bb813a02f6defd21b0889cb6dda6d7225009cc6d2641", - "0xa219afe00a9327f2c937afabdf5f10bca0687f48d8f7a2a046a52e6924af613f38cf82454df4f412f5991ba20b7db74e", - "0xb448ed4b15ced4de098781793a6e850ea1547d514646fb8f1c137c86e14231ac4340b308bf07813fb813cd02e47c015e", - "0x905fb68b8f5bc14834a06d61f3da686bee77b3b590a33c640c82f34e68ab993f8c4897df463973d6d9f0d53f9ac5cf5e", - "0x991cb6857dd0b3ee6597aa2fb1f4ccc962cb038228615466964680795587240e6ccf7861ec220a53ede1e2e9752e1cb7", - "0xb823dc0249ae72e2de91108cd4ae6d6af3e464f12a53a46ca583727c7351a67f2d12c911534e234ee187389fcbf1f621", - "0x981ba6bda1816036e75a864f635629a141905a4805c706260e7a5e12b82dfa9de4f4058143065b10a1012adca6b7d083", - "0x8bd8ec0e77a6867057e5393d82132474eba9fcc4bbe025544bab0ada4ebad3d296ceffa3788acfea0a44406e2ab637bc", - "0x93eaca3a7f9a0dc809eb9f604905b0cab18750a9bfa42d98d99728a6de6e0f1e05b6e98bb3b0d9862a69eb57ee2e18f3", - "0x90b077d7b7b1651ac0d723978b3e408191c2b8b08247fe2a7fd69afe0615dec09e43771cd845c2cd064b56295e53f634", - "0x847e8f607332129e95eb1f3e00003b087e92ebf1ac9477304b0d54ea38419fe8581122d21bef8d034f788a9c92f4ec00", - "0xb0301becb003dc7cd56ea7d830bf0fb4b85bdb33606d8d9ab2b70c6415ab5c8f4934bb7079ced16081b8f6d16b77c0c0", - "0x9068fbbfcc95fff7ef79ab64063dd9bff0c40b4855eedb39bfced9250cc351b5b3b1bc6c2d038cb6d59a12a41b3db664", - "0x84857e081fa1c6c08bf7b0bcfe7c6d74b57cbad1b67676e99686bcca0b17715ede19f826517dce3f84cfa014e11909b0", - "0x98fbfd6a94ac3e4b53b811e4d275b865486a52884352ff514889313c7a15b07822f76d428533a0f8d3cb42f1e6f72356", - "0xb4faa1b1245aa6339b5bb987f3423d187f6e7e5d4b4b341de87ebdea53b124932cd0e586470cf4a3b1060a126e4ce7e1", - "0x973e88d073071c2cf5ed643d770a45f6be7b230896caf72a2cef10e56ff0a4e032d6ae1ff4c19bba2cc29f29ba70cc19", - "0x8d40b3285879fb9ac0b6c9d92199afaf4716fe21edcd56b1a1fcb6ed298b5ec5b3b64222eb6f0cd1086d41872911068a", - "0xb5e338a02076ad851778d590ada4af1c217d035c2505b891163689a554e5a957219410bbb435bbb38c8a1515146f8789", - "0xb1d3e990d027a38fc8a38579e39e199d9984dc6d857bf51e2ed5fae061c8723fed3c74662405378c29342bc4f1fff7ca", - "0x8679f10f866804b19dd0b14b24068c1d32908a52149d33ab03394990cc60c0f388eef02bc0db819f92f8197b1fc60c17", - "0xaee5157db1cb7ca8013b0c19201ea1e7af32e4117896b3f8ec0ef0b2a4ded6a5e7c893281865cdae7deff4532a6a3fe0", - "0x950315818b710d3903b679dd0de0619059bea7dac3bf4edc8fd4a6dba81b7aff9bca7cf1972940b789458f287609439b", - "0xade345a6171b8e8afce7a455cb98024d0d91dfa347632e1a5a40721868bfed1c1959300f1e1e39a551d99a4e1abb563a", - "0xadde1719c13b3ec224bdb6b44dc2c5f2daad54e7ee736209653a0198a769180019d87fe6bdc37ec1b48f0212ea5a8927", - "0xa3397eba3ed2ea491e8d0328333689f66b2bbed0e1892d7b14b2aa45460a12e4d592d78a5d0ac20bd6d34c88b8f1f7a3", - "0x8613160aca85f0154e170b1b3f1052ba984f5c422c4c25e0771a53469c274130a31f875a0ba9650f77fabd910cb10467", - "0xa91ae4d048c56d5b2383a9d8f6396837543b609d8b0be139ebd5fd31fe4a90071656442ca7f14136cb8205734d929b5b", - "0x8e42732269c77887f105d1c326488025f9938cbade678bc6b39941311360408ea6baf274bbf5ffff996756cd2390bf1d", - "0xb96e1ca66d51a186237fef402bc4e14f8f96a138db377b7e2c3243954b6f47ca75cf4fb5dd081aaee634b5e2efe2a065", - "0x81d1c20d76ed054923c17b362b50205050f185137ea10559e35ee7e191bd89383b68179c0aa4531eb61abdc239ae6891", - "0xa350b5778e26ee808466619f73900e09bd387849d072c0c014517d16adb4e3394673238c4f4e705d30b4ec2edfe5a695", - "0xa13657433e39c0241d48075ae8ab1efe3680c96d078685c5dc0ac3c49d468db98f2094dd4204f44e8e90bf54059b5807", - "0xa96255abe489be9d42ce6fa76ee90e4bb6a36421fb78068432cc935632ea5b5bb2ab70790ef79422f93d35d1034568b0", - "0xb745d643480edb577b1f92ded38a522236fa1be2944ad8102ca64c3d55f6297b7e0aa1beb082261af1cc334f5a723614", - "0xb235ccbf94e2bbd3c794bcaf84266141c0e04ecdcd7d2def83a7eeb86a2ff4dd3ddbd8245296b27344770f3d5d332f90", - "0x935f3e4e9dceb4f58404ba1a489985001827e96bf6be227a8ac4e2eb8a950d4a446320ce3a245d09d2d74776c7033a3e", - "0x99cb7f3d6256ee8918f40642f5cb788f0047a04c482146e70687c3298629bf082dd98d4a4c222fbfea3afa3d7d806f00", - "0xad6abd2fcc67af691e76792432b83b8cd9b0a9e5e73de21f89ab54081ea002ffd904d77ab8efb6906790987e29c53ff9", - "0xb6de4c3a45ed7898abc037a47507f46f7327c057a911529d3a671286f98e79a421f4586a7ff3235f1892d0cbbd0e7bff", - "0x9120311b071d38214e39f4b48ce6299ae9297c7b76ab364353d3816669cba56592fe4c7f1f93507bec7ddc1df471f0f1", - "0xa6daf71681485d01ae7fd4bb81a326d3d2764bbed5d3be45efcbc04aed190163ce8f9d04a84bacf25ec151790f8fe917", - "0x9534da45c2a497607f7440f61943f4c16878a18f0bbce00dd644de88383470705b489225f5be4428d1f988256b70c926", - "0xb2d1b633b4832dab1a530a1d85415e7fa3e4a1fd383ddb898a79c7ad028f2dd8fbd56b83600cf481eb14a073cd65431a", - "0x8c43dc994dfeb5f22df9560518df32deb1af43f254acb8e6f93eec3fb3ac80081b39610800d0822246e130e8c5f7a067", - "0xa18174ffb85d13b7edde5822f22872ece12383d79fbbdb8c02bcc9f654cea904ed8c03b8709d70736dd4b308ecc1607c", - "0xa54e4bb27d6d561261a3fc48705781399f337448c0afa68c074918d2c14ea7d51263199b01070b7161c3db8b9949717d", - "0xa7457cba2c5b455584980ab6d0bb5253dbf2cafea4efe5bd769020b970dc35fba4109d002f5934610b8b4a158252ebdc", - "0x877d4111f50f77463b60e07843b4521b2c44629a7deff20dbabd412206a4fe03f976de1a3897b9df7eed16217f03e2c2", - "0x84d1ab99732fed1470f69fdb499dd3de795b055354e89d522c6a7df4d6a5375052c8befa4dc7615d29b3d92ce7df2f24", - "0x93bd139c343d8b83403e04547072c3e546c67445220afd06c119f7119730288629439640302d0628e74fa596e305c0e0", - "0x8157b5ab48d026684f6b51b802b4d8e7f85ef82583d1e8dfeca042b47a0e0f58e30cfdf4738e6d51394b260a4ca7e19f", - "0x8f03d5c1720540c29a1dee44ef5c7f8b209094ba8376d8e5eb9b52537d9843912b68562eff742f0a7a07f5faf6abd1ba", - "0xa15e4999a0028b8b083c2afbf4968a1f0397c26cda8dd7f6c134c6a860e740ac4bf1a1936849a4f2080e0cc9f8e44387", - "0x8b71fb85363158c7afc3c41422e9a32ecb2d1f9d3c01fff00b77e0ec6a8661e95b552a7f05f4acebee751448ed750684", - "0xb34125432d0704c0638090fc4566780d2d8038d803f96e19ff748325f8b5579cb8867e12491921feaf3c0df949f36aab", - "0x968196e10bcdc6cba28331a229acd54b59edaa83cad0f8d14f39d787467bd5ea725a3dc3d50accc334e74c81fd762cff", - "0x968abfa40af365986e68c47b4eb3562a72793fbd66a7d1b3804a5bac8137f0a3cbbf5cd306097cbf1a3b95c3414fb061", - "0x85395fa84223dcc16b7e620a7ef6f902f7b29dce7760f57baafb37d985755e65623768b8bd745c8de7d00e2035aba7ab", - "0xb57ad86ab3f5cb00ca0855088921865893b6e539edbbd504238df2f9b2fa7c7bdbf2d6eec6ba8e2a70a4c4fa3f459a97", - "0xa2f203ed1f07cca3f8c0d35ccf7a63216ab98c9e71557e829dea45e2c723583bfbaa7a83d66521b08a0718c63973a6b2", - "0x99a3522974525f4ed10623bae83dddace6f9495687cb9cf4ef52c8530b05672c2b226d3fc5058c56462ab3737a068baf", - "0xa4a50d127ad06067f1eac2d61c0a1e813fceba2e5e895467b5e6045c9b7308d3678bed9212b98e44c19a1783e0f57bef", - "0xa62d103ecc1d5e1d5cb98a0bbf9682ad65774d63f67f95bcbfb0cdb5e2437f2279043e4426d490f534961a2487782cce", - "0xb12fdaa5ca77456e6e96eccf97a303ee2d73f547916ed67378835402136c2aa03e63912edf5a67785f7ac1636f6ddb51", - "0x91315750043c4e08c7e4359b9cba25309eedc9c85672851f05a0651dd9b9329bef00a79cfe73ddc308d97cf548486b47", - "0x947115aa6cb3c635bda7f3c5fc3dd0e4881500d74db4c0579e4b9039b75b131eb5db54174b1bb970064740551e6cd1c7", - "0xaff091a9c7e86c80646cfffbf154ecbcfeb66877c5b773b6e8759649ada1094270e57970cbf2b0a4bcde9bbfa9689b1c", - "0x81e3cb9116f81e583b7579f9af06931a5337fae0d57d9ef777003f32e0eb619b75b74137385f9e30dfe0e10c2120b02e", - "0x81ab49647db2a5a6f47ec757d8427325fe721142377a287c547fbe04ea280acb32d71f3dedf7ec0f67b43ffc5d748342", - "0x84b0e16d8478b798694503ac4a87ff31affe0ef6d2bad47abe0fcb3a2571fc8e4e9c966276a5f4968b2675827a115818", - "0x9567b2edd65974393cf2181d235f877f5827a6d5ca16e77165ef35f6c66370f0c55a2dca5387c02ae73a66b81f01798c", - "0xaf19f841026271e284548b2cfe9fe7d6f9acdb4759ca76fc566de7a8d835408f86627185fe32e705f94e6a719e463cd3", - "0x83883e1c9d215c90948d066d2210528552093a726f0a27b8342b611e4b8639f6d2a5f95bef8cfea4312c1f2203f34986", - "0xa48019b2da37a232b7999f6b668e2758f82132e15ea93608bb2350d3188297c8ff8c791977f2a083ad9773570bb560db", - "0xa1fcc29974eb065a350cdcb4283b2a813f02421b872eb3c15056ef96e2d5ffe2fba0e10ba19a4d271937cf08838e4106", - "0x86f9ec59a1f5a5796e498247c0ef1457ea7ab098247f363329a336a1ee57afb31cc18d35e008a5263e7c401fad5719eb", - "0xa903f95675c14cc618b02f7a0401ab67170b4a143925979791d76aacc90ad1faab828fe904f13d155425b2ffd79c008e", - "0x8f652c4982220b8e9868a621a91eee85279b13b0c2974472fbba11775e6bb1d8d53309f500fbdacdd432170bc76c93a8", - "0xa9b02cfa052b5808c1c9ee65ade446a6ce20174bd2e9d9c7388a1973b0290debbb6fe82697f09afee6ed01c9dd99b905", - "0x8b4c700fdbcb13854c7b1d257a781fb7449a9e3236b962871f11b31b1f2e69ecfa6039e2d168ebdf2f142f93b91f5882", - "0xa9ba2295980603515f80f0130993f1be434281fd4442ce7e68b2fee12b24e440bc0282df67707e460bc67a4706bdf8b8", - "0xa382b85dd64b70296a2d16d1d15d6de80687dec9cc074445fac8de7bad616a95972ec399bda7c2cffa4247bd04413b76", - "0xb6adb37da1c6cba5ddfaafa3718aa66fe2821b43923ec371cd4eb9e974ebf3d0e94dff1ffc1347cee5c9e19af7c76be9", - "0xb5b531ea7f93c4756e5799118654ebc478a3ab57ea51125fd31c012053c759c8a52c8830b53208f74215e437d059eda6", - "0x89c88a5ecee1931dc027d1553b5aa82dbc5fed2a4bed329809467f79f2712fa5529c0f80ce6891817927d0b66d356db6", - "0xb4ad1964f73d3b7bc338909df2ab8889c4faad9b3b8a5959ea81f44c6c4bec95f0fb6e8fea1fb7e09789c690423e2b85", - "0xb573bcbd8f484e350db04eb263187ae4e99ecd03494058e68221aad8d044db82957f4bf23f71a9634b2ef9612a78ecc8", - "0x93c3dd86f7c3105fe482f62b0a56fe43338aef50f0d10f237ca774f834151273aa653e17bf919e54aeb35343ed790c0e", - "0x9069c429e7c6507a755871b301b31c3b4233006c51bb66ea2c9051c6caa52e933ad81a8e879129e0c1b099a124bcb295", - "0xa22203e5bb65593bd22cd5bc6e95a2f5c9a9aac1e14d948a7e0aebce4f009a56623026e0980bd194a633b42f15822ad5", - "0xb1585de69b3014634da2ba76218321ff4ce8476b653ea985a7330290b1bb1445db3e1f3c510f9ae7c22940157e2df36f", - "0x802a70ea7fa057a03d12538c3ad6b348a8e694bc6b483cd62c97af8809627a161223557f1d5196e23f13eddce15c814f", - "0xafe8b0e94d8d9b44652602c5ad15bb0140456d90c95af4ba58cff528e2834e0036572af867488f27cb2d27d81cf02e30", - "0x93bb332d924bcacc41b4b9bf726647d7cbb642847fee5ee7dbf3d2a0489d71802d959a3e905a80ab1f34097328632f00", - "0x8caad1d29fe712bf09d505ccfc724574c8edaf5fc743953b2771cdae006ad9792a889e0c8136409b8f92e2cab5ba09f9", - "0x8678be67412da4d43d74660df98744c54365cf10aa59e522c59afc3836d115380416cb1ae497ba4b50ad31a23ece8b92", - "0xa48e64a5447ebeb5f6b0e0fea29fd5845b378e83f6b06b79b604081e5e723930a0d4c6025627382f6baba8d47425cd27", - "0xb8914eefa2f5613dfe99f11212912dd53d678ed349fe871781074d5b6eed1fc7f2e5bbfad3356a685c52a3c8a26e7963", - "0x836ba66155facd2a1839f603644aa5520cecaad130fcd5cf379139056d3e163bf35f172a4a1f015924b89137f83d366a", - "0x835b70cc340b57a09b1fecac678be381ffa4c4951f6742322c2751cf1c748ffc2b9bee8f155c007d88ca69c12bd9db20", - "0x8e98b4ae7c68941a48a70f703c3d5bc9a4cf6c20c61eb4c1338095920c4f23aa9eeb474a0430dc28d355b15dc6e83b22", - "0xb24be8171a105f203c5bf2ab0797dca8ce61ee07307e1d82fd26fcc064bd8a8a5b6bcae8dd611f8ab650176e694da677", - "0xb057bec8ca008dbfd4982ce4516a4925a61bd68e7a36b182575c6a4044c7a413ecd1dffa66ae3cfe2213763dd0f55a01", - "0x8d270924c541120a18d587cee51711486f09a39444182800355c4193a76789614c6925e6a448f46c1891106f866f08db", - "0xa0ebf85c44453153764bfc817364493166833b0f84b7a7c505a955cf3a7d4c1b4d2dd00145220d8a3207758a82dd8e4c", - "0xa56fbc83a3f1034337ca0d5aa89a0a18f900c3654d171d47ee86b0720c6a965c09c9b06678e3f25b151b115d129ff7bb", - "0x833618f5d13b7919206c8e9666997ef26c04a74844f57150e7268bea540e30b93eb785803535566765bdc899d4f10667", - "0x987daa13c00dcacdfb1f0eb13c38ddf773e7e8e19af125604ede42c6d0907f9ed1e4b8b8c9118b14f9449026802a6200", - "0x99b6e669cd7532b435d01b20dfed29211042beea6de58acd68b6eba26baa1687d80aadff901b5607a2553df047ac51d0", - "0x82c81899cb76ae21838558a1946425c719cf68d07950b0f106b859048107c13e4e83b0f2762ac8590cdd044c3e731f6f", - "0x8f1c5f634e38f47cc6967f2a80a449f5bf69585622c333d784263e3f6f027bccf8910da76435a84155a6fbe9a8adc4cc", - "0x92d3b5515744115dd20742be1a72a455c6d481855f4366a0e960104665db4ecae8925182f32d4e1d9dd7fb9aa246726c", - "0xac86e14775cc4ef22cafa8ac3298bff27fbefa9b7004ccb16d2937128492a2c1319641062f609d27b9314aa225301d14", - "0xa07e1ac19f4c374d68084415fa4a8068c0be540c8b9d81c0837347fe096547d8318bbd804b7642820e43c284af663258", - "0x839266a2fe6dddc446d4b515eb538a27b5a3a5e1a8246f6df77c2de8267e172bb7522aa7985e0503c68db9cf93399b95", - "0x8a381fa29e553fb57e3780f915a86048aa82a8a09059c80154df9490271aa6b99baf6bb217df43c8ea1265e85f07adfc", - "0x8d8806db0093161d7f83aaa2cbf0bfb8cabf823cb54bec094f886da6461397f41d54c39f216d7ff4a8262d12aa8ebfc7", - "0x90aff3f98394674791e194b57c3f4e6e019471df1a74dc47bed725d4c47399e91c88a955612be47e89002f451ebacb55", - "0x8bce2d60f3e82042ba94cddd02543b46cebb8770e9b7833b4e79289d4c491df7f4da0ab69778cef92dd81e5a6f0eb71d", - "0x8246fc9424b5d5ae0a3344acd7d6962fba6b68cde09332c262d7b3f379cac2c650d80cb5ed4baeea16a5557efb6878d9", - "0x92ea8547fedbf440517522c687f1d652ae4637cd072147ef31338a40e11017bfdeac42a32808d33522a71136cc3bf26b", - "0x84f6a64600184c54d3d5c320498282947b8a8166f09ccfdfd6d285cff374312da57087fec3838a49eac5b93315f03b80", - "0x86dfa1485e343c861286c057109119ce8e20abc646a4411696a3bf4718ce03d37fe14b6ea1600d8a7b172fcca6d08ea1", - "0x8dd3404facfe49c2f096d2e74641c474c9c54cd6121771061db3c1757cdb1cd6813d3ffd79e3b839b348d34b7b4f1ba4", - "0x8870cf255b342ffbaa2dcff41910a37afb29ca6a721774953dec182d95b426a481eac7bc107c4c2ef3df9f70e04e0b88", - "0xb0b843ccc630209b9ab35a69f3aad58c76b2cd3cbe94579b5757350460633217246b342fd098e365fb3ae88d5b7d13f0", - "0x804fe307b2d477085f8d9800c8a11c2dbf6f662d684d6a0d2fd415cbe4a09255e47535a08796a805188e1bad779ce121", - "0x93d91029bce430ecc5f41a460c02cefd3fdcb8c3e761ba26a020e108e06520cbe2eb0c04139aad0c0fe58ed34d8b2215", - "0x830867ec984210b314e7f23dc5b10e6d9ca53789cc447e29ebca229f4c79c9120618a540a9d21e4ba2ed8a811d6c456b", - "0x8d7a89ae9d7318d6578c1fa75b3babfa7c9df7099eefc2a9983ffa96627f4e7fc99dfde21b92fef5e0034dfaee35e97b", - "0x8eb68f5875dac63cdbbeb5df2fad7c1426939ecb6e3b6a48f737bbac1179ed4cf5a1e6919529878169d6d8552fa5ad56", - "0x861e26c9a31d21839735cca8a384b981f7346b026cab7d60fa95a7ad7a4a370cfb409812ca285090c3f1c3a95e5194b0", - "0xa02ab98589d48b2240209f54b0be78edb56b614b1aa02095ab5a9cec6a04faf065eb7b81bfe45aead551b1f774c60161", - "0x88124374273a2425bd5932a6b446986756379c7eb93d3ba0c5d7cbc3477e6267d9c67e5e956cf6df841bb263d1a8e224", - "0x91a766128a4c718a45db571606867bfe6e1b1049f0ccf71a01138d5443014c9758000a8be4dae0caca56321e3f992e99", - "0x8dbfc433e2477b9d86f221e9c49fb8db67c85438fd54b670ce44b68b62d4c0a9cd56c37a2127fb2adef22c07643fdd3d", - "0x880cb650f01191db0dbfe63215d208f70f924380fa22baa0e5bcab60f61ece3c6d4cca0e4363291f6a10aca9649da69d", - "0x8532214650619e201bd330865a3228e9ffaf1f64ddd33d206be5616c691b1965814f8bc507fc8a695c8291c2f8713dae", - "0x90e81d5a9d8fc976a3bf6ee6d3022107d3a9441ff212305cbc7c35bc9163321cadb352632181ccdc1450f91f51872b00", - "0x94d656836edd68384df1fe61239d40a36a0fadd59abead673e4a2ae58de5e2a6bcc4b980dd9b517e7212726b8ac94ee7", - "0xafa70edfed2d81326f26f111982aafad55f510de95555a4d05d9916a600f3ca6c1e3f66d6b092c91c1fce6c407e022a8", - "0x95cfbd616c2a59acde8152578737d3ed329aa82a950dcbb9378bebc3ec8beef9be2759a937381ed5aec1a46d486d1afc", - "0xa0a1ae94bcd07ba44c30bf50cbe0ddca2fdb5db82ae73e53c2efe9446c2464fea8e5de31da4afb99c6405798f0f9a59c", - "0x848e10f6c12a6adcf711ae3af8382178c46b90b9ff9970350f14b1a1b565a7efd91eb96871277b86040d26561acee099", - "0x815e749e4a56c3b982b50ef5ed249c4defee558647a5c6062022c3ef42b5ebb219ba770f0de74869bea14a98eec02360", - "0xa4d88794689a0f2e194988114ab96d28f77a29cfff606228ebe030a62eb4fba25cefd59d3d5f2fb66acaeda866f5c24c", - "0xad59a8541eb9641c3045d5cea6e3930b35886da4c96906f701ed3ef90cf74431df3c444174d9071a1657efc8cebdc739", - "0x97ae83289d535707039e9df8ebc73262f881ee8e288f73b9f0d6fd209385d3e2b761fb87ca852e10cc4818384ee155de", - "0xb47983e11702462a23e26c8d6407b01b67ad532bce3f1e0626fe3164886603bbc803c688729a64a69d119b15235389bd", - "0xb447011409a07a2d9074e08502e882098799f3b649e947de44c79ecf86a63045a19985857ec500638a3baa2b228a79c7", - "0x870f506356aa4f8df7d61449a7c7a8689705388b8b81dfe08fd79e8a734c998a7ba71f1f6e9df085b8aa5813a4ec4adc", - "0xa07abf6abcacd7612338b455c1461ff484dccda7430d4e9c5f9b4e5c1cb65055f4be650e6d67179b2c62709cd52a9b07", - "0x988b73c2a71f3b1d6b4734d231c089ad6cb07f7ea6f4b8fcfdd34aa33f09feab6cda91232c06b47e90ae9930ea46beeb", - "0x886443bb8d7d6c7634f55da1c5695f1691750fbf9ad2d63621589f91a0205ed4adbd4b905c62effaab235e740a172040", - "0xb66caf1ac38a8a66c43767e8597ddb66fbefd888989ca1ed56abb96ab9fb41937927a792ce422577c68286e53bb4856b", - "0xa84be3b37007cc932429ba2b4064ab7fabbd0b77400bbeaff09f8c6b818b5cd127ff8497e131dd8bf4323e092c690219", - "0xa99e9898b6f9b7b1b9ef6f28f60fe2ea71e961b64b262cceae41003f6aaa16fa3dc1c2ab63bf63534718ad812e882a35", - "0xa1cea8f3f5605a5c60144fed53943d3f259e3e33545eb0dfeb211a9dad8d99cb3cd3b2cf5031b85778ef6520700eac4f", - "0x8b979026924097a06b3827ad28f3efd7f0e5aaf7920ebe5347fabc61b000631f0ee973b61b7468fcc60ba6e4381ee478", - "0xb5dd7393dcff33d6d337328167ceaa7a04a98e0acf1dcbaf454247e85793fcc9a7d280ab14693cf2cee01afdf44506d4", - "0x8580c90d72c0c83c6c003dcc340553ea547eca5989780493c2551ea9f04225d77ea76acc1bde20fef1a0bb7ec01685c4", - "0x8c77db66f09e76ebf7ac14fe2fadabd41291f7ec5971060580b317f6af0daabe099f9db2c3d09c4c6edfa41211da0c4a", - "0xb6dec051200c25f150d3b9a7802f5b7c361b074528c79dccefa77d26ea2f67562a6d9fb8246369c6a60f832fec6b7636", - "0x8620173e19eac12fdc7796df12bd3648c66f78fb83a8e6f6c9077c34027a3acd0884ef2e3455a3de0fbfd4ca130ed545", - "0xb44e3ae4047f917fe1af378cacae2813f8774307c20d54c565b674de197fdf90e1a6da0733e948c3218353c613d23fbc", - "0xb330af874ac5d749a4ce1a23f4fbfa67f71e8fd16f6da07c714218be431b2a30cc4ad2594994a7a35f5aa06bf87ea3ff", - "0xa5be67aad05a965685aadfe03d66ea1136e6979cef00605e92912fe8f84be7351a6acf6b73c567a20ce6045a703cf557", - "0xa1672ed63df30aabe34e8eb81209ff31f4e5eee620b58074d92d9cf2687e40217169df59be8af7374aa5a9107c5f51c1", - "0xac01de17b74e2dacfe3db539910b6c370de94e646b6f2dd2a828a381b04f2979f8a62bac473659fe7b6e126f15ed7aed", - "0xb978099cd3aec49300ef9ce5561aa30da4d37cb5c697e0b5cbc3c42ccf2f96e53e948fc579cbd24605101176a353a962", - "0x8c8c439d9da3627e9f74da784bab8191552b945bb5bf9abb673659c939a60903e11f37300dddcbc8a495adf5c038234c", - "0x8b4570ac55ea349560a4e7043fa17f264dbaae15a2f3dbc5ef8a6579e1f9b5a440aeda94122982fe564f78b615de3e1f", - "0xa76bbb163db2ba26f5dcae8267d1a890815a76196af10444d3a04c1debeaa3c7cd51102fd0bff8944710c743f5393745", - "0x8d3ba2494b612f93b4ebab77e6f207b636e2d09a3e4a9666d4ddd5859fdbb9747a88eddb7749356b141a071584677ec5", - "0xa8bfd973dee352ae653f7c7bc7df2b32d790653a3f1f2b239d71677992938cabe941fa609e915e607809b5fa954c9073", - "0xaeb4c1ccee15753d4fbba545ec4ebb05c7428427f087fdc0852a18439b19b1669a3c744a0ae2e7f74c46905f520c3231", - "0x8fffac3ff9de863257a836aff3cdb705fe7f4bf604c2cbe10180d81c0956f723b69438bb8a3aa094fc755e386234dbf9", - "0xa583153b241d31223ebec9a95e11ebc4a657b14056b8ca052aebdd9866140dc4669bef4f02b5ffdf667ddc9a87e0bac4", - "0x93177005082ccf2143f24c063d20068fda393948bfac34af57ca58cfbcd0bf9a0de46f8f41312e83a502b7ad69b8f2ce", - "0xa79b0967599894340ef2408b48f42e6ba4f406e5ccaff13b46414ee38e5329ffc145f6c34d8e8acc6aba41c23e57e7f8", - "0x809a356a76d54a05e5006f2cddf0decf73e5392b57ead32ab56bea9fe13c1ad090cd69a8e297fa6e017b39361906360f", - "0xb051226cb44ab1bf94a9cc0e4f246751d68f32ffd12f1d077d3318de642f3997fbfb0f2ae1dd103264542c2bd0293e57", - "0x8cac28256b1a82d0be373d884d00e9ff2e384d5afbeedda706f942b1d222694f126ad44f9453fc8a985cf69fe11ad70d", - "0xa13b073290de7a2f01a65e429e1adb78cd37eb23c24d6fd5a1632cce2275496179e3c22e0b7f59fb51d526402c0f3f7a", - "0x92dab68d1dbf07e5b058120422ae610806809ddecd2aeb9d11d8fcac738c72eca584b88ff52c95817b79b9e0369e3ba6", - "0xb24267fbee28883cc8649c243b13905874e5d97a285b9c6abec749a53e106db0a6fd6fd8671d5b7c9a1851da75a4ac5a", - "0x99cdf977dbfc10084b698c81cffb431a9eabb55b1323e1b15baed5984a1ed212ec5f6c58372f965fe18de0100292e26c", - "0xb021c697c56989bc8c06636cd623c3672e8885598fd2014f5e560fa2f721f9487cfdbcf4adfa34c178ac84771fbb77a1", - "0x8fd7e3ad3330d4eb1a0bd42801d95ce40a82b43c366abc823e25311aa1ed882446d60b6309e1a1e201e725326736257a", - "0xb1b3c641ef4cbd5e9c69955217f53373cbd104916e04d012eb40a24d798e76bf05ed0a218862ce02619ef694c42be170", - "0xa376d0296c0105789e9fe539a5d22bf62ee36a2de4c9aa0f5e57210ae49e2cfc5209fe0f467ed19dc95b9746595255e0", - "0x8a0ec125a145e373929ae33efb978bdaf16041eba684ada612c244bc3e28c7027886e6308373a5ea53c9c3d8e868ce1b", - "0x93fde45cbf04cc386507b03eeb93c885da12bfe519df9fbdac5ada735934ea6e1a6cce066d033be66163b078e96e2100", - "0x80c1839ee1d2ddcae1fed77d5f8091ae3074409461e04153db801e05b44a7658c6ccadd33ad682e51e211dd9e3c80f72", - "0x87112961553b4a243875ac8d46bb6e274325699ccbdc40d7d9b7f7e58d3fd164f86b0b1df5df5f980785cb3918dc9b33", - "0xa011463964a319c1ea11c7c55c607bffe0116fc834b8a1d3684df33f77f6e51dbe16a891307c9f51d5b4d205c4530072", - "0xb316c4be33abd10400a4925f9d20ba02ab1feb50af39b6f6120d6dbcf1bde0a8dff7e08c64bd1f5c43543b013e242483", - "0x9555b696d428c4b74806a7d08b9ff17c8512a86cbb13040360ce248de241facc42c042d3779c28fe98dc3ca96a47b2fa", - "0x819f54bcfc58a7b793d185d8ffe411bde6207b77cf22b0d5e1b3d9843e4638009c907fdec1966b485f95870da57f131a", - "0x82c3f9623bfb8a8ff3573197497c175fcb314addafadd025528f805b7a63c87b0e54b48d46c0322110b0043f7f77153c", - "0xabc023b35318fd97ec81933ce55799d8c36c3d55cf59b9efb302b276a76a37c517d5c690287f216ffc5d1fc082e116c3", - "0xa6579226d602a7ceec06d402d38f217b836c8804e9da202bfaf1f3f4f15c24762ad6a2414ac022d8de68fb76ba8a725f", - "0xb701d6d60387d4e2308a77cebd210e868eaec10d86532ea18c0c6393475b3976a3eddd79e469458bae4f496da7398fcc", - "0xab202a2acd4ff874cfc147ad1b02d4515ace254f8b828f619df2537698f4e1b2687e004c70a152d605a73ab1ae40fb3c", - "0xa7e09ef6c86ec7475eb3ed69e57e0cbe86114ca5c0748069d00b6e3a1e2ed74e4366adfcb4d938023560fd91d0f64612", - "0xa9fc42b05ceaff4312d5dacd78fd2394dfb8dc87d52efb0529595877727747827c1c7e3a3da81255356033fce1f97513", - "0xb0150a1dadde09cd60ec3686256b4378f47dc6a55c092c60a3a3f0bbf586013dc37ed53ba7a91c72791c0d52e4c49c2e", - "0xac88e91b48f031df297c29fbb2cd0d2bcc767be5e0a7db87acc87fcc0f4300cce6deffc0b1cb6fc7e51c6ab13ec2ea24", - "0xa8fb1542a956fdb1dcf90da2672d40c90a4aaa2f1232318b4112816bab660657eb97e3d0fee9f327793f6ba9bf8df2cd", - "0xb78191d1ec4615b03b21d7730d48fd9643c78c31feea19866429073f4cbb0d1a67f7d7ed210ab62b760c679515b20acb", - "0x967c20d53d46011f59ae675a26aaadbb7512d9f7fe87b7a20c3a84c76569d23920121063235e37cee2692bca3af69039", - "0x9766abf0251cefbcfbf85ab1322f62267c22e6556b7fb909413a7819f635e3ac1670da6f5f72d3bb4b739e12eae5ccc6", - "0xb0e9c5c327fba5347474366eed1ff60b986a41aabab00abe18a91dec69aa54197d3f5680603057f05d5efa0a48dbc92b", - "0xae2f5defdbd14e2c7eaf595b017b4a97edf521f561ca649b6bc2e66382478b5323aaf84f0b90f0147e20ad078d185248", - "0xb841bb6e04d2409a419dff4bf97dd3d4f06f6fa4e5e23e4c85f23533b7f25fe3da9285ba033c6eae7e5e447e35329c0c", - "0x85e26db850536cb6d3de259f662a88d23577fd56d1f99b3113ef1bb166456324d3f141a7ff63dbccc639cff68e7ae5a5", - "0x8cc36d89424da80bcc2b9d9969bbd75bab038c0cf836f561080e14bb691e8e0c17306fd6d42522030d4640a01d5c0704", - "0x817e72d50f68dfbdfc9d5611eef7c6b490ef1509559801fe1ff916050429a5f79c8d03c60d2bcb02e72310b3c4c9d068", - "0xa15ed72881c49b545413102975fc69649fd5417f5b7ea9091f8209974024785496fa0682352c879953cd1e9edb3fbee7", - "0xadafd20b962921334f4be2188f9ced4a5914389d0afcdbb485096d3848db85152e2881aed0fdfca11f9c8a9858a745eb", - "0x8d8aaea706815f1ec45d9ee470698ff199c40b1ff2d75bb54afd4a29250b094335538dd41637eb862e822c4cf0e2bebf", - "0xb8480d2a79cb6ada254435dd19d793598adda44f44a386ccb1a90d32cd13fe129a8d66d8babd67044de375ee59d8db51", - "0x97c17d6594ccefd8f17944fb760fd32cc41a9b046f87893bb7ab2260090de291e8260ffc63e774a4b6b1dfe0e5107ef8", - "0xb5b7e1d4d9683de7193120be850395762ac9a5669cded9226f5ca2a3de13eb13b2900af083645ec35345894de349433f", - "0x9405d473872cc9f9b9c57bb9976d3ec6892ea429cbd1b12f22962b74d88448d4ccdfcc6d5c6ffa068d560d7bdc3208a1", - "0xb99cca139a3733b365f4718beb4ff4a5fd6aada0173471156640d8be2cc69f2a70d959b57688f927bca2329c3b30477a", - "0x94872ec872f19279fd26abfb132b4a7fd8c485fbdf04515c7b416fc564e61a7b0fc5da9f1a380d2b3db989f1832ac1b4", - "0x92aba716538bd66e35a7bb877cd364c1b8dc3055a9cba2da23c7d9c0a010209ba8afab455da99747fb4bcc8fd3144cd8", - "0x95ec4c205be3dd5df181220c96bba3f4e3b526fe5369035edfcf706c1eca43f29a4c95cfcf94cecfc974e01547e55125", - "0xb48c719d7cbda1e79b3f7ee9c17c13bbac197bb015b344f79bc6785b28a1e9484e2349178178a2fe94c46132c54983c3", - "0x908c495c355a0555544ec6f0b8e0dd0926ef2c5c640fcb89049e6178697625b85492722d42bb5c966aee2cee9981607e", - "0x98ded9cdfa92bc8e3664ae90af87f88759503715f6eaccfc983e3ab53f7b3af6802999da93caa7eb3530402ec4d8901e", - "0x993266bb366ba46d532973391af7204aab46a3189c83ce7cfd2713bc11e99066b1a5a012bead2fedb09274e7b362e8be", - "0x88d462a3a17f84326b1e4177799d6e9c5f4ef57152cb83ffff4353a8382ac8be7d50381723aeca77d33d8f07fccf69f7", - "0x80438d9eadea15c90008ccf4758d4e3fd5a7bd02809eed5b683f2c96a15d24524ffe75683b7167d42a47161c65d533a2", - "0xb9e7dbbd3d3d0d86e347831cf99657fb269930087920637ac6cdf185d5eded3f09cf3eb27759ce3f4b46f41411e2fdce", - "0x8f0215f23b4945470f74b99607c12c36eca41aaaf99747f522d8531244b668d6ab8c1096b9b5697208c3931e1fefaed4", - "0xb2c8d8515ff16beae04c855b4365e450e0ebfb423acf5da2501fea76259f862bf29738a858a4093b98c2a444396249f6", - "0xb27364a7258c30a59d1f13d358eb49dcef298a92bfa699b3b91817d2f324be8fff91c0b71cabf26747802a92582e7dea", - "0xaee7d6f71fd674cdd8dd1f22195981e7160990c16647c871835d988e881a3d4c52345e74f7a54768fd97a65fdbd84567", - "0x91356cb2024f7703ccd662f50baee33409c28ff13bb5eb92fa93f303913e9bf31bf83b0babff4b5e3649003ae95492e6", - "0xb744e4754043d3ed85c3bf6ccda60e665568dd087548ac70670b90328509d0d5013cbdd07bf603949067e54d8094fc2a", - "0x8146cbea5899401a80676850d0b43b02d376b4b8f02ed63a7d92532d13689e2c02846df79cffa0f33ff81c3bf492339a", - "0x94bba8a1508c6296d3dd5d2e609d6d732ab2541849deea5436a4a9034e1e6f1c8d26f6b781fa34dcdae7cbf8899d006b", - "0x80260b321d932e1179667de4916428c1b77ee1ea537a569dc64a12da5ddc85d09896939718ce08ea7e0fe8f8b115c408", - "0x89d4640cbbca5d105dd67250f3bbfaa96d7ce19a89f8d6e188353f3a9b8737f2db1707c506f8ffe1d3144dd1da371920", - "0x92f5962946ef7190fbb7bd3935427157ffc815a52ef44397ead3aaddddc82e5f85b1edcca1e9082a500960e19b492614", - "0x8b89240c9b7257cbbfcd6e415fd035ce33bb46c773569d217c82ecee5dc2d66eedc9333e0b043616b0cbf21744909b60", - "0xa3d23484916d2c0ad1b81fc7df70c97d711040799cab076223e0ee02a45a0fe9ab564faf7a225982468f3e62e32424d0", - "0xb31751386bcd471b5858d001fee15d566215e34d2d62556c51ddc60a834d3f1acf18c415c23a36b581cdf4791f461ce1", - "0x860a99003b841221dc5ea2bd7e226e5aad72db8a5959d5d4dae8a86114d30b9e8915b2314ef867e9c2a477d9424a2d94", - "0xac925b330cafddc7d95d115a9e62b2c135acd22b5e35a4aa789f4318f03aabef818805845f2532e9504bb19f69171809", - "0x95d8180cae0815d33bf8854f4590be652f95f72fc29f0c519ca9bf3f490ba4a724b23d9054e08e3d31bd61d609a8f0dc", - "0x994f223740ff95764fb88de1ad6dd90c9c58c0dfbf8482e1dd9bafc20c099a6772acf40569c54143f6697fab72772296", - "0x971d93cb1e7aec5defa52815bf202b11de6a2ac9c5d4c0eb236cf2c4941460731e12b718f4a5b980ec6f4c54c3d17deb", - "0xa341095fe5adb96dec2be367f09804ef4fe77d553102ddf7d643b7277992708e84be9a7748a97a56f065002a97dd7cbe", - "0x843709280fba29d446779b1ac6e31bc3ec8ab8082e8b063ef8d2f3733ee77a4191b55772302964bf867fe1044dbfad65", - "0xb7ccc71fd0d0c9642c32d292ae88ca369d1fb5cabb97b1745c621aee41da8f94bb580c1ab23664c1baee65e97b21f0b0", - "0xa9b41f31be84f8ba061570633bd9e5f4d8af6fcc5276c25d9ab67b2b88c1f8c2a87eb19280cd4fe7b4c04da8b2d02d7e", - "0x93eb14ce0632cd325429e1c23340da9655d3d7c2b42a4594bfd5a4e07815afc9eb1ac737228771492020f6528c0b7c61", - "0x959aedea532471b9610150657b895c5f51ca950aaca910df137dbda2d17184173cf2638a2a0efea3f61d82b6ef8a7c3e", - "0x8ebfb50bd48fbf9a6f782454ea900acf0c7143164de9b5b46c1cd072c69b76143ac4c99bd43139b5e55f847841fa6a1c", - "0x851499b3a1eae6da530a47d3e8bc068e6e7144b744d5eca5394f96253df65094e5f3c34abfaf7c7d78c4d5a5d4863da4", - "0xa8d68bf15b900cc47197739856557b43a5eb233b6c095f21a14a90ac8c36caaa1a54690c95840f0a4d2e2ffad0874a2d", - "0x81a6ff8fb1dc4d4042089d4cfc10cf826e39083aa5983e53f4866f8f4c10cf06cd8608c4cb1b785f8d309bdb9b2dda63", - "0x82f658bd1a95fac0b65d337efc95d856aa65541d49aa993b094d70e527e7d478970eeb3daa2904a1309d755e1d677691", - "0xb46ba4f3d8f287eb92390e5d930df4f1a40abe500c9aebf62e2eeeb2e5ecfe5296b09fa22d6c9cfdae28d431fd10a00a", - "0xb5b38508befa4623166f6213cfd160782fae5b7c3c7ec279b42a83d43a7adcfaa6c5b34cedbf98bba357fa663eec896c", - "0x89b8a0fb37a0c45eb1f234ae9c7be65c8a708f08d431728572169b33f8288b1e17b7d4b18de9fb76afc37ae609290623", - "0xa7d1f5779c043900f3ddf29b6b7ae4301699c0ee9e70314fcd3bb2643f912fb1225a0164f45c47419ab762420bf8e5ad", - "0x89d2a69fc014068aa6d0b79784b8953f3519f563b5c9f774f4b148334d822aa645b662d5efe7dc6f9cccc2f67268c3fa", - "0xa698d3f0b1b6b72b72358d5fd5e49e928cfde69bfda10e163b9b43bb9604362b32af1909d28da5e0364abcf5e96cc226", - "0x91c12dc25c48aee56484172de8c6aba0d9f5eae8db848a7b53d76001c292d115ec57d816c2cf10bb9e901b2707dcb71d", - "0xb0740219e084d56db4829daa30b2812115b2e95ae85ee96a140b7c4012860e8017e19b482e981547e50e25bd4ba76716", - "0x8c84d4fa255e2de7cd23b0bbd110687edc47ed7fa87bd42658fbaf3831c6d68cde3ef403ed6c585f8654d0cd32074bad", - "0xa530d3272aa1740a73e15cb9b31c5e2e54c404db72274b0840c19b164642389acdab4514b9b2bf9688ce51392d8b6793", - "0xa601f52bf7b3226fcab93c67dccd95c1d6673270671c4a099b867bd5578d5711fe9acc9b935b867ca780ba4a394279ef", - "0x8a238082dc8ae33314fe1257e7bec69e905c194ded6f894267bce97443166fb443628490755e1d453545f389b5beaa2f", - "0x88a9737f3e9ded874681fb6cc8abe0f6e1ce18a05ab599b2f855f73e6fe5bf804de5c5dddeb11057aeca6613bba72c8c", - "0x8a5cf70293eb99ad3c34992c47299646c8702d1035b75e4784cbec67b28cd4c88eb8c721f4cb8982d3c6a42d1b9f7fae", - "0x8a62228b84fa7463a6a8392a7af767b661382175633c5e00b36979d816a53b388f31afedfc47a5d8cbcb645e8d5928b7", - "0x92836b5a41900a1c1ceec83cf4f15c6177dc20f95eed23a203810116ede2a072a8d6c96532ef32c93ee21acfb14448b9", - "0xb4e538d7bf40c263dd1ede65c81883dd31f9237a0fc8d134a2b480a1a681dd89cd2edb19e63070ee69e96cd12069ce3f", - "0x913eceddd4c9939cf82c7e9ca5ac300cd79dc5a72b8458cd69e9f8929168eb19e5f21eac12a3b09eb8d3998e28e3801f", - "0x81f4a3e7195661b174aa2059796dd88d3206bedeb7d7cfbb7e61aee335a01ac50bb8edeb258a68949492d4ac6215d95f", - "0x913a393eba8eb88d1076effa8d2a30258d83635ccb346f1bfe099fb5fcc69d0457ce5a79363a618f9e8b43f53728433b", - "0xb11d721b08be428254665bd64a8864d78c5112e252feccca113631b2818fb729129fcff1e739178507ece41b807ffafd", - "0x92603fb7d50d11b59fe376720aa57412b866fcd5da90195a5a401e6222201b30c29f8797dcc1b41ee2cbc6349bd5ee1d", - "0xa466c5d41cd4a8d1f47a650ca67b529ad3873ba3fd3a36db27f7a5869b74b42381788bb1a1c100ed184118839b9879e5", - "0x85c50607a86d4f76826220286784fa9b6ccbaadccb661fb3489fd35a3a8917d6999ac891190f2297afac3c37abba2967", - "0x966320c2762b266cf7eac7aae39221599df4fd608036f6101cb8c68192fcbfd5f61c7f93172aa2be0934486fdf4816f6", - "0xab69525f1c77b6706592cdd5b98f840184b49efc6fc2687d6dad3b014f6a12c4d5cbcb5120d8869246da010823534d8b", - "0xaa2c9df15c06b58d7b9bdf617df8bcda83ccaaf6ddeb8074db931f7f03dc06a7914e322777e297226ee51dc8268e80af", - "0x97035b62f8db4df6e787cc2c940f2298c7d26c2127c7a76e4660d132a14f43c8bac8dd4e261605611b2e9c08929f2bac", - "0x8ace33e696953806f594427f137e84ea6b22ca9b48c3bdf3830b3e951e5a463d4a7067c68d2033eff452295a741fa1cb", - "0xb621fe49b12580bc8ec68fa39d5133875da23524a5ebc793c35040fa3436350d0f3e4bb4e53eaa23d312a7847e2eb2d6", - "0xab7d6ccc0de9c7ddea145e83fb423a535cf26d470af3326d1d6a9a579592b32ededb078bae4613028557877a9fe71642", - "0x97528eef76389dd77d33ee7daebbb290814911beb725ef1326c3160b9003c3043be09bf3f84e4818bc2a7960ce1acef5", - "0xa408eaf5c675b50dc1c655f83b9595dabed68e74e6d2eca5a4948252667413cfffb46358400df5d35f92657811ae56e2", - "0xb36537726b26b474e184dce0ad868a66f0944b4105ff6d35a2cae0f3a520fd14a66630388aeba178d400b5fe104e521b", - "0xb2b88518d10bdcb111c82a261076367e34718f1d0a1a06b421891b4eca1e3c1f904b66e65dc914ff1ea5991f6a638a02", - "0xaa3172531879a5c8f594ce96277b2c8c8d4a2d0f4bbe567ae40d5b36fa6108e00f0b1dc94b81f36c9eb6d1e9ee1896ca", - "0xa53975587f10667a9474ae2756faefe43e7f81bf9e051049de175a8ec085530fdee3d5e3db15d4be874ecacf49f31691", - "0xa1abdc58bff4fad0f6562338daeacdac8e37f9f3212aa252b17389bd9c54db58706129a63bd0695d299d043b5ef0e2d3", - "0xb8588fa1090597fe0f6275e5779da11a4d128c52fb8954e475c4940f1a3e10fc23ce1f61e9aabe8a75e82824f718a94c", - "0x8a1981c536747d4cc06315c794f1536db7ab3c9dfa024a0df854b948d93bee72083b6c9c4c4a7ce999c98b904813a659", - "0x95b2b1ed525d629eed454bd6bd059b01869423c3463a56689a7c39cffbd3453c962426a1126ed631b25ae8cd7538302c", - "0x8032c60f083477693f533c2d8ae391d62ea754b8eb41ce9cd59bc469b980dd959a8ac840ccac54b404a9d08a6f9e4b98", - "0xa72ccc14eeed758d3d43c51d68341fd7e98880c3687e122238d77dac8d987c8edb3067bb63baf13a0e57fe02334545c7", - "0xaac3eb536a5061a8ec788ce131582dea691957ce8b9c6af5ab7224bdf0fd15c77bc6bc63ad037bd83e0ae52fda738361", - "0x97dfa193800e57e6b19d1b7fbab40da6dd1463f043eeec34b316ba6bee21b6bb633ec0c4fe107c9dab6e06e07e0acdce", - "0x966ee3cf2f54777968fbc34f08c8de121ae7c1d6b2cdf1f1f9c675828d22ccb909bfdffa2e3f2ce51b0cc85bb29f8504", - "0xa9df6dfd12f8c43c28b929280355cb23ab0ddd2cc2e4fe76603a2e5dc2ef5d1aca2edf89b304a27345cbb1f24a86cad6", - "0xabbceef80c744e5a1194313f7b84b5dee1c9861cd4bd3d0d12c433e5f2e8c6ef6f10b860abf3b788aa04896f708426bf", - "0xb1dffdd81711e9782c992c4b14583ad9d6c39ef88974682a72e717e21923da6892490d7efd121423fdc638467e62e064", - "0x817f30dd799c422da33e13ac2bada8cce3930233ddad495f714a1c789b7aa8f41ff6e688bbffc5f2e8dfc72e5243b645", - "0x96760a79e4414ff1d19fee65b6e65b2dd6665323981ce8b4ee93d0a9c410b018ac086c08fcbc7a71720e1e3a676f2b3f", - "0x95445cabb75909262975a5b06381af2bff5c4c6cf51cc84adbc0b7f2a985117f35c014e33672cd5216a9737d3f37e067", - "0xa279c905fd9d49482d213f5eb98256d020c2b90bebac45004d6f152ee4ddcfc72a7e6b188ce3b6c93ebb9ba9b8be587f", - "0x8591e8379a78af8860e6a0e002be5b25aa4b93c5e556f5ae2e40400f828dfa19d93a4910823e230a51e2c1ea5464d437", - "0xa6fde17d41fd9f03605ab6ddfc992e36535a23b2c39608d30cd6d72588f1ec6afb9db193e88eb609e104e73ddde779a7", - "0x93e2cb6352a5eec063151e5c9a822f6fd475a072dfde2464af4afaf6a730a6af1fd74c424c09727328a7f23505b91407", - "0xa7b1e4f703386fdd16f5fc9b59ef1dd682bfe5a23bd42b3c4b1385bff894e758ab09674dd6d0ded5b32a0a0526aa6d98", - "0xaa7f01c450e619c4bb42d6cb1a90a94dfe132a641728a642997b71e2c3b02d09a7695b544712b2e14416e7de0252fb11", - "0xae840b870a938668d0d4404b76f5d4d252d8ae1e3619661df0890ccbab900e3d8dbd5dc9f96013413e3f1e30dc541db3", - "0xab7552930ab07b0f5d50edea3a2e5ea3ac1a05cc985246ca066fc3350bc58949dfb99d4f6a6408d1bba64d3de47a3c2b", - "0x8053634d4c730b5e90d68c2830a73e93f1c9e522ae0e00a04e2ba15a1b7b4fffb8b25516ceea61719f886c7763d46219", - "0x880c39ca4cafa622bc767d3127d62143434d0a1d7de8dce1a2f94cdcaa023a7096641a46e6b97e1b1ce9c233c873a519", - "0xab9d46e46cb2f382ee7d21b6da01578b786b7998e0fc2b5d5a4e1a0d93aaab997b5c481c2d9a741865d6460ceef57a5b", - "0x857a5957adc3a888cf93f144aa809c70a211932742a258978af5e657f4f57fcb6d9e39dbe8d3128fac6c592dd5bc4ddb", - "0x8c98656861fb8c8a03d491db45077f1d136a759177842ecf6c1ca36923088237e928558433d5b631590f395db56f96c1", - "0xabddacadd7d536e91d36609fd0047f5db113be0f4d84abc7631ffc5c00df919c085c049c013a05131150b0043d51f892", - "0xa8b14af12cfdd0e11c8487334efbfdd22c8b4fe6bf350333d42ac8c704efe54f50a4bb51d9d802e5185ce72e4b21aa58", - "0xa8badc2bb3cad0143db1bb3cc81751f9974ff3f0e2ee35921d04985409def84ac2803a657571699eba34767b773666e5", - "0xa6739a05d270efdab18462e8536f43dad577158e1c1655fa130da97e469adce9bb7cda6f9ac26f4a9ba3f9b22329b066", - "0x842ed6efb4395603e7fef0bf92326c0c63992da4ce7912f850c4960f7a19e0b2ecc720d9510f15ba6f73a2c5ada8ea71", - "0x8502ede859944047898d533e1923ef90e1b5c17d985c9fb4c6aa39d50636de4c5a4df278f2f62cfd3ad08bba4c5ca6cb", - "0x8c738573226dd5617b3ca1dec8780000a77f3fa8de241cac99b0d9b1b6c90cbb8aa2009668005f2c5c7abb09c0ab3f99", - "0xb101335c403d769313bd05c755a9196769465f7068fd6f9e00937f3cc843d48f013f5931f999bb5c0082d4315134f5d5", - "0x925ace190259b321981fcf8bcf52c6852b206099f25c0f278439ef6edc4320d6f926cd6fccf1b4cd224bc52e5c681612", - "0x95f5855ad1bf14224e51f7d5e0d229683c0d38fa324b1abe9d595685d3497955e30289618c4775f6083bbf923ff3a37d", - "0xa3d3c7100962c8b60c40b830af834ddc48858e7eba5ebe2874ebf74e505c25cf52e661b49d7619f2f2a039e1df02f5c8", - "0xaf7e66c1d5dca63e6be117b210c616efd533e77199d67d8f694e4278841963e0a46e4e44f0416e69bce6a7156e1872ca", - "0xab796760166d1e1fceb20f9bf19b1b7cfcd327650cc7cc35c161ddbb3cd4846e9a971b541f303cf62fdc0124688fbd41", - "0xb920211c5b440b3567942dedf62a65ffbcad1e3516f58d14d8f8dbe86f45c4b9745fbce43f5219b7052b27a3a04df12b", - "0xab6d5d25b9fc46b0824df1628993e44535febd7c62185b6795550388185035ae12bab60fa34848f465fb4f4a8add3544", - "0xa6539b67dfd6f3976cb6b304f4d152532b3f08c02bb97730c03c746da0f7b38ba65536faa43953d46e5e7687a86c356e", - "0x95bb225586b1c82d894ababea7e5dfa8468bc0e10a2ef34e5f736fd1611114cddaf1a5c58bc9d664b667adef68b5c25c", - "0xa16eefa4e6161a8e7bac63cffb2dd5cefcae57144e63b3fded581abf7ce32016a654aaa582fc25bfa51c83f352e09372", - "0x8b742428f6af81261a47a0df061e480ef9176694d361ecb57967bea67e11cd44df686e38e35b7d4a6ee02ebd520aa1c0", - "0xa2a4f2307f646384a0238a711c2dcf7000b4747b8df1d46c5da962fdb106c5339790b48682e8ec2532b8d319ccafae5f", - "0x81910c1d72f6731d27d3a4059ccb0316faf51fa58e0fb3d1287b798ea8f9b00bbbde31fac03f93c7e9a1cdbc9502d5df", - "0xb846b933c2acd71e9f9845f1013cea14d35cd4b8f7a371b9be9bec9d4b3c37a2d0da315ba766c3a126f8e2893f10af4b", - "0x8ffad59284b41b75064c277ab01c5b4b3a4f3c4b355bf9128160b1a55ed6b0d91366f7804006b4e6991525d3435d5235", - "0x82ff36a72533fd5d6745d0c3a346fce4f62b6aca0b8eccd11399b482f91cdf6a5a4135c627043008cb137ef4ccd935d0", - "0xa11c27f6eefe54cf32fd86333d9ccb59477a655bb0c35dcd028eea58d4cc40ef9a26cf3432fad4e9d058a27b419b8f04", - "0x96642ce0eea3c2c0fd155a75bec3b5cd573d41e8081632c56528464cd69a1141be3180c457213128bcd37f5fae47f7f2", - "0x8349a9e390e05150bbab2351b77a3674f1af000b6eb6752927ef838b6f0a1200e6fd7201dad8565e3caf3802f204246c", - "0xb8ae7fea6275ea61935d3047d8156e8fbc4a95c9fefd1c36439b2111b9ebeb7ccc306e0f8c875fa772f7b433cff848aa", - "0xb366f056e23905bae10ef7ce1728b317b83f504d128f5bd34701ecb0d25ec08491969625e23d5a2fcf0048af610664df", - "0xa3d88d506ba46b73bf07729aafe9698e788fd688647a6b4145761275257d262cc450c7889b8a40d698455baca55e3da4", - "0x891ebaac7a7a408aee4ba61605f44f9ca5a6d5e046eebfd8f5108b6dc4479482806dd01686045b4c7760051f22bce468", - "0xa6ddb74e3e3725e6f2d9025532ee3f357ee35289e1cb38dcd5b2ea8ebc0bb697416fb3aa73e1eba632d593d40fdb030c", - "0xa7dc097f440ebd31ec1a005648468c702bb77073ac8cfa32b050e90a9e1cf388f138abdd18f07951c752f7e19f706af1", - "0xa200f25299f9a0542c196adc2e00289f453411066b88b125d3f0e6b17e98efe9da8096312a2f1841e01837da90a65440", - "0x97cd3a9d4185d77d4c7bd4ee80928def7b660d8b949b0face798c62a7cadce1000997af29504d28ccf9070fc3016dc56", - "0xb9ebaba1a15eecae6b1998ae6d08233d05610dc0933b16922076b2dc4418cbeb4e5cbe099bbded3139d8a47f2b2eae10", - "0x86f5fe8fb36b419fe6fece1c5c4b9d64468b4aa0154bb5dca466a243b6fb1227c3b8bdaf7ce5c2d4fd05c061979f87df", - "0x8050e011011e7918ebc25825d9863c91046fc3756703bdedf936dec2815cbd10c2403ce6f4a0b4f576cdfa1347efdb85", - "0xac22132a482d2950be9442167be214ed9d24519073bf5ef1c8e3e6f4a77065da198a851950330fe4d62b2a1272835015", - "0x819e2e8e3ac43b6ae4885899346f3b558bd7658ef7d380070588154694957596695a925a001a9fec7cf3655326c50c2c", - "0xb00f40c084d2eafa36811e0d822ffef874a0d4bebd4817690408a737624be05c920a08307cfa0c1195505c5e7a5fd878", - "0x8355768c09515a593c8fc8289baa3b6cf7fc10d302abc93f72090ad99a70a1ef1107eccf839be722132259500a565d68", - "0x8bf0615d2cd11b03546ab7a0c90c0c938776aca8a8b989a709c367f0f5eea7b0a7cdd78f96050cdd5d0a123d01b99c53", - "0x827c2cce458464fdc716a2198fc67b3cf2ed7802a1f53f7a2793b2314789998b13ea61343c723da8863cb63def6a285c", - "0xb609cfe6acfccd632759700bbb0a06fc7903a6c0c5875c2c3bd85c65bfae7b29b03e77092f29d565a6a89b85012396fc", - "0xb73ddbc330e872363bed36578b245b666d42923393a1341816769ce0af24b700c19ea0f579e4f9aff1c3ff369e63da8b", - "0x976d658085e5978807e13b150c7aa539b44ab8855a386bb58a52d9ec9b5e21ddaf89a18b043394d6cf47bd589d04b240", - "0xa213897312aa28cbb2c572e643d3aed003c84bc2ca571dc5fbea8a0b642313be94db0047e293078d975fbc6800751a87", - "0xb54f2914f6a7508b6686280d3cc955730458ff035978be29645fba161ed54ef3d4086f956e68d2a48c49afe904edff5a", - "0xaf99e470055062390904673e18d04427c16afeb7b9f13ad83bc2599e9a92314bd91d6f1f81b55419a4d668bd889ec8c5", - "0x946ff0cff4030b73a1342a9173fe697ab20cc5e43ea6158573f2def601e12a174da431f8170bd31ceed4be48c90b4f6b", - "0xabc51f8bb5f74cee819ee383cbab739026c453bb55336fdf423af2c2ac6712ba90006d62dd72d8cc1b2ff6cac900c8b6", - "0xb43623a56c5fd1bf28bc356fb4a875d72dd4cbb00c9c863646a3376937088f9932a4a0aa26afe2ad69840b06242ec76c", - "0xb0f371952f99eabf7ed368a142ee07d06bf2b7ec1ff852fd948b8c53eaa52300753fb9ff6765201e35873b5167583f3a", - "0xb3906488172c09e148c571ef0712f88bc9f1ecae0db95380f61901660fc1aa090d0740378d5b9b76883507bed100093c", - "0x945373b5e6ffce11d39a722df7b24eb929b14a967d211be3b969f48fe1ad3dd4280317d1ca772a69b033f3bf26c02c4f", - "0xb2ad3490389fe5bfdd5ac7eb5bd61facff8d57a8f9969f4938ea56f4a85eaa2c1179a2e5b4f87d9e6409925c75b61828", - "0xa4d61547e405319cbc20cad16a2bfd9e6d093a064522c332dd22134ab05e893bc84786b21b4c71a6265bbd06da2ef4b1", - "0x86749c26715d22b185e1b25dd34818e96aad319d7add22a98486ef9f9808b5e4b938c6320d391dc4e0fb5d57bd41778c", - "0xacc554d5b866693a453a9ec46d422c8b410458fe8397384b927a62bf5f2b1fb9706c8c21af9845050fea8a91786e3577", - "0x8eb7e763d297cd93a7a54dbe4654c39c0ebfd73fcc34d3f1338de0c347676f445d32f270664fcb7b33347bd377c9f867", - "0xa1b469e3f9dabd36b13149c83aa5b7b5987eb0ecc1ce6b68c72acb39ed503a11ab4451e658576a92df4aa51d1bc709f6", - "0xb1ef105cd0259486be8f265a73ea089d5b7fab7bd7547932134539963467fb917b2206aa72446e2fed5a8185b04d345d", - "0xb3e211c1a14925f6de451271728a3c1e555ebebecd4bae29bf666927868039d4ec99d9f9aa98d835da7845a5c863dfaf", - "0xa416632a50500f29b6bb471bf00b37558975ac91e5c5b5004b67e130be1acc954a8ebaee7efcaf6883187ee9173d1ccb", - "0x8c655a85f66b5f28ab8760c94b6cf01cdc36fedd19a09c261e432fa7eda7928c3c88355384e689f1d2715d419fd8d898", - "0xb1fa9f82c9866d4f296755bef5b7c39fadd09374f38ef9954aa57b1431a1ea4cc17a9750da844fa1f5848f0ab7ca295c", - "0xb45cdf1a9eaaf85c0b07bfe239da618ee649ce90b417d90b08eb518b1fd88c0d25cd29fa7a0d8058d6616627a3dda306", - "0xa2be1552d3c4142755e0371a9543032ee82ad669d7edd24c4e2941bde3b78c5c6df427228fc45812a55943b3663cdbda", - "0xa28feb053e86dd9e2f9ccbb7c38467e2425fd580ba0f63190036fb47d01eb198ba8590b5cf68d1c0f47638e9dbdaec74", - "0xae06b849e080efcdba86fa03a0c9dacb38a15ba911aaec624d15787c3e11ada6909b1e33a2e3de928a23818d833eade4", - "0xb4888445d86bcf4d1f6a9c2d253f277596795084c3d45a4591b307b7ae4ba177d6ce871c2cacdcf9457f9c132f244722", - "0x87a568aa2f5471214f63932b0d48e589898e82a1f4c1055a9e73120763430537c233e9a3cb6cc178df53768e4c58c993", - "0x81e0ec97cdf91ae66d065234492a1119198c396e2db204b7edf192c88eb4238e0a45bf7e245f3714bd864244cba0ebed", - "0xa954a3785588d4bb3cfd7cb27df45c82e6958051f916594d76cdb35bb07e4f88e2831a5cda35fe1f3c99f32a275f0668", - "0xa9c9f4d54339d414342e87b03679baf29c219d28b6548f01891cf94d0313a64d3384658d82373d6e838d886235ac446d", - "0x8ef46cb24432b419b4cc803e60b3ef5872db8ea614dc37643e4592fbb2891cdff61f6b2a10653d9e99e6c7359ca4c590", - "0xb23eeb458c05ffa5d58be21cd0699974694dc61a9a928fb1eb509954a3dfe7d8a71620a2d4046a448de0fb213be7e97d", - "0xad631be8e17285f6310fb72ba913c564fc66d14460c4e8c4b0c68c572a5c2a45b088ef60eaa9d317403bacf534d57a23", - "0xb7130f5607f236374f5e023fd43cc6dee38286ca47d504c9e75c6504957ac2bb9134fd59d8bb1010d545c56ad9c71c4b", - "0xb83cb511757d80781e26b5e9b3e0597c4cf9a976a3fb60c84efeab2b6793e46282612da45b1bb8b45af4b7f39877feb2", - "0xa0c5f8b0027ee11cd5f86515698f689ad514cfa890ac4ead5502b5ede9d7d7ad12285f5806c9c85ab58f89bd9f188938", - "0xaa8e8f9335c6e34bca3472b5f412ce93ab1ed5f9e51c3affdf986a5badd2ba3ca1ee69eae53ba8144927f082371b4cf3", - "0xb2a4f775a10cd9caa776123771f08e928ecdb22dcb91efc440c69e37c6b467acfa6112c2776d4a530bfd6df3b04fd50d", - "0xa0c553d5d2a9b0525f71a5a0a539d579d937275df9220a0c3c322d6c0ac7fbd2fc55335a1a283e687856e2b30398e4b6", - "0x8ab800ab4c810e8f6a9d42d2dae9be89841bc7328bab06b88bbe1256f720ca99c056fbe4e1378d7cf805586ae18dcc55", - "0xb9a8766f4f4bf796e2517a8a7a05bafaa6d3ec601a85c466d33b8a7e0498fa1dd4e2a9e42161fe2362c81d4c8ee1fbf3", - "0x8cb7d054162e9f41245b0914e7dcf6108ec11456b39b473ecf6c40e56b172fe5be4e7b0753a3685667436796a977b977", - "0x9131d0395897f5591ad56b62ef83a3ed9e7b3951080b33ea606a15742f78a283f924373e813b877f32762dd69884658e", - "0x8d784d7f0884cce988305d314896dc6dac2d2934cf5d650904e1397f9b9dca397eb7f3accad60ab5e34cb2e494bb640b", - "0x8819629608ca1535bfc156c1e17f8fce5821d81e6661bca75a1754a5919d0404e31e65bd509387383a4111535e949f5a", - "0x820a6f46e251a1e6d92784aee18fb0d265d7e2f0a5b7e0b15180273eabdefb34f1d575e1d8e93dfc2be1114d10abf31c", - "0x8d10d0e0557beb8db344c2d8bcada724e720823fc37ee7c51b322c3269559ae932bb2ea07e50d7ada88ede788839dc8f", - "0x911a333e2f7578a0ff6533284176cf235036047a11534acb649a0043a326723662bccddaf1970b7c37b5146977277b88", - "0xa4be2104cc5d6fce4a46de5de8d210559a6b743b6347b8d9990315bb56cbf80695ff936afadfdcc415d88b23ce6863ce", - "0x87ec5877ea8f1123371c49263dd9fedfbde41846a23e12073ef80f7afddf5a0ddab298cc02e861a90188ef1282139ecf", - "0xa3f1dae70745b8284b1353aa6902ebe3cf5580e24e02490d42b2f509ffec7e8e777fdce4f1a92d83bbb23cbaeaddac57", - "0x8ed5a0733b42482d88da7c24e85a841ece65f6066dec060bb267a8d1f1ec165ad5f7964c2908d3fbdc2999c580eb8990", - "0xb124a1db23f4875e0caff1c7f4b9a411564b93a9ec3ad8143bc7a70b8305d380b934c194de8201f7e3699e905a1f0115", - "0x8af58886d4ac5578a29c2e309a68f19a62edef5285d0757c42f0ec2550c633c0e991c4cd7a60df4523cdde40c3909345", - "0xa63fbdbde883f54667c6cacb356db1fb976bad147b790064ff25ae72be53bb6f4d74b22ca803996e0d95d216caa3fa81", - "0xb99fc9012ad938b36246a4471d29f0a2b37b2a3be6fbfae7ec9fdccbfd14d48fdbede0d88ef3b6cc273f2488f4cab55f", - "0xacb6cd4e1672eabf530d38f50ae651db8bc4025c2557c59ac4f1a278b4741f1e2cda978e5d1337f9e5aae77c95ccb872", - "0x8f8f6964534e4a9294c61c76206674d836d4d56970e9c14ad6835adc6b0d256402742d8a4879764569d9082ea6a750cb", - "0x969607ac6ca9bbef4fbc2fac22b12714a31f5d6103dfb998c3b6f2776283ebc7346e81a22da168af40752f28ff60d97b", - "0xb633f60cf6eb8ed588c545c04972ff156cee767edf31720c9715be3cda8c8de0290b623b22cb8fadb1690bf3665a7be6", - "0x8235bc2e818e4d259bf2c9fcc9646ccf83b7e238044e26be417d1d0dd5283d7b38c86e8c88a5447645be191516e2993c", - "0xb503052246ea840a4083bb4a8978029af3e242e831518bcca015f2c2df504e98a48c9002b6b9fbb97e861a0a3c5b4b5c", - "0xa145ac57d7c028c3cbd2a2bfea25caa35a9b5d69cb491b13eaadc2b0d927a590decb7c4995541f8f29089a2cbde6429a", - "0x80b4c0938058fa5d03c948777f13c70f46fc025d4d6c2f2051915b476eb0c0bef902374d784df57ac368c01e1fd51c00", - "0x92eb253e3b1770b36c4b2869a944caeed7b5c8a5b8356b25dcd4102df79fab8dd2c9d01e3253070f1206d149c43f64e2", - "0xb7979ad6187f7921e725787b0a99050f4c98762c63fa64a467f7f110932f6d07556453a95e3a2c0162bf1c9c41424c90", - "0x8808ae4c7cb38202c8c8bca0321e827580155197a700fa54b6a15b0f14b001327d4c9a0923168bb5afdd1b45d6a78367", - "0xb16a4ceee9de5f49a99430e18aefc192f3c1ffdc4b41392069f690893bccdca760e6dadf4127539a763e4f60aef37dde", - "0x8ac113da7ca59ca97d6bf7d6e03f1e9570867bed27230515475f965ce9ce0b424c85810e18a584ae5a3d5c2c80c6d4a0", - "0x847ae1b0ef5cb11be37320f3ab5e30f59d7910ba3d7cbf8265c74df25f4b8f56f1ac96cf49fd166c3b6985d1e8091e6f", - "0xaaa9b04f50ed6778e2481842cda30c7dbc7d462b40c7602a438ca9f2c1599e83fe6423f30d7789fd240d2e3166836f5d", - "0x8c18492569faa8cfa1c2a05a0edeea3f63d003e38d9ce23c4a5b31cde993a4ec88c9db83011ae15b578e0d0f6b72ddb8", - "0x838b400217af9241755032c21a3ac4610f77f3ad76abc43f0c59a59f9bd52f2251e46fcf1552b6ee0220f4f2902e54e5", - "0x8675f8de084c6c05644deeed1ff45090096c72c0db6bb2ceaf1c0d070bd10ff1e83b2dcd89b6f99bf132d3e131ef6d0f", - "0x89611bc63c83d56131bc2a8653278b234b4635aa7a05033d71a8377a5d188ffed7506a50a5c37a33d199a42b9e55fea4", - "0x90c290c17f1687a87023fadf74b1e10ad0c0414cf08629b2a313347f0f6913bbe511e5d18d1c3264b47f65dee7887d4a", - "0xa590bcb6391506035466dea82617f11dd9417c9f379d32b4c3bbf723840e1a3124d2327deb28849aacac278470d7ae20", - "0x97c55f459ebdf94ade7bc3bb18b329bbe2bccea345f0b4dc38cfff2839749b8f9365e8a1cf31722649c165e265344c35", - "0x8159d02fd03c1d0b3c928658b3df1a27a57699ed8a573e0c3a179e97f50b6c1a6467b7055e42f9f9c6c858459eed517f", - "0x84d4f009c052f3bf76b2b972b3d8f7a4b2d78605a566478670c33016aab06828a1737a36d3c9173583e7bed0aee84fcc", - "0xb99d7558944ac2d61f5a800c24ee47fca719e69f7284956be94596623cf434a214c042aa46d54019de3556540ea53236", - "0x8d1efbad46f69b80efc5776d8afe95dc0a8182d57318b9f2d6fb5b7d5c48e7181e6bd61a8446a553c58f7899ea7a7c78", - "0x84a9cf6a9d64cee7e7d8f0b678d3606c9080ab3ecf62fe0d6f994a681de68b30534ded61db1445a257b2c5427e97b36c", - "0xb6a5d2c55a23841a4263b10cdf784be6fdfe1b25350a4af510ca294949716711363ca19f9c44ab1c347aa3fcd60f0573", - "0xb1b5b6dbe6945db539fe7e2de07d222c88d7b91753118593ad9890c55c4c3d83b4194f886ea7f66ccbb348f5a23a2a22", - "0xa8a58169edd3e58f87fe8529f5cf7da7679807467ec707ab96faedf75085185a78f2ef912d9180a5e820adfad32ae4ae", - "0x874c1f416f866756ae3e93360342848afdea0048a575f977fb1f8a57325e50da122d3e9f423e308f0acb1b28fd47a6eb", - "0x95cbe8b47ec42a5c72ef7b1f91e3de0b1f648ae8069416c48d5529c9cffb104ba4dcbe87cc06e4e798a1b23bf1595f9a", - "0xa1b6e9c5d63ab1262559727872d1140b74a4f01c12366ed2d401c64007faf7917ec591b631c6bb4dd44b39aa43c7f965", - "0x89e6f4a05679c95d45b54e760056378a5eeacc72624eec8b5f19aecf8ef0d8acfb2d807d3b88c6b1206827203f219905", - "0xb7f7b30cdea6377d5f16d200b987e3b4a6f28387faa701dc579cf7b3c6887d74ca43609c5bc36414a6dfd0317ec75448", - "0x83474b58135f3e2c5e8355e31ae44a77721db71cb2919c3f3403f44903622d4116e812ea9ee9ca073938dee780f4aa22", - "0xa3e4cbbec770630c5e2f3b67059a55b1217435bb70ba5b5010244e241ad6a3e6b8d9261d8a0765c4b42bf795fa4e96d4", - "0x87d3ebf0fc03ad67299f3b9cf9c9ff0890b1d0d2d1a0ca2a62147444922d207663329e49898d79bd8e09ee48a1560fa5", - "0xa1d33282cb17c7a4c5cfeab4dee8875d324aca8d0513567c4e5eae180d1e8ac98b2ef16b31afa7c3f2ec25cf3e8bbd11", - "0xb10b6cfe3ba563b41ae0d66813105948416ce0848ba3b34b8e96547e8842086b632a52904e56eb61d93e0cbdd402d305", - "0x84c4feb35c8d3583ca17245e6f7e73cb488aed515c2ef671b09a04d8eebe6b7579e5b1fc8634fcd4c3bf8100d2cb98de", - "0x918d8fa2f52a9b3957ba412c24cc579dbd1f0b0834b909a6ac0da5dc602ceec17046f61b3d4a2658f724757ca8041fb9", - "0x87296e4775fb887bb00dd3265f202f31a8fdeae5c6ad8ec63508476cc57d330827d0d241c68091bb724a2ba921694a7a", - "0xa8908019d96c506b314c84b22c475157daa36016a9b94feecc4571e869918e4e5a9e39fb7c9ae0f73f9f868bdc50e2af", - "0xabedfabf75a93e7521eb339ce2e22e0e887f94ea28d3adfa42d1e0523686c6cbee4c96b2bbab3b8393feda1099b24d4b", - "0xa464d6bb17386cb431520cdbb3818beb3951b0255d72f58c300fd780aea1fe4dbce5532f5321e80e16db2f9b9bfe8a1b", - "0x8cb8fe0df930e1e19446ff0183c7034e35e33442da346df8a802160120a5f4d8abac236763114a650dcb1a1d38bafb37", - "0x975c47ea6412bfa97db9cf12c2b4c07ebbda436716aaa7253b2343138b36de6c897386833849f539bad7659d9319abce", - "0x8cf94457a5a708cc91bca9615e599b0c0afa92a7f2d9c83704e05a3dba56a90c4eedebb6d2d25b3080786e16c27194c6", - "0x950d02a5e41c8f704184c7c59715fdf3b48d86b53b04dff7c21738c7c38c9f4f75349ac1e70ca18a0744b01fb8b13504", - "0x9458faad893db4458b330ee283d6a90f68346332c99cbe8e121c890bfca908f0c91168072aa221c3c078d7fd5e4b44d9", - "0xb0262948c113fa2a122dc6208250b62ff35b12d3aa1e5735e95198424cf16a4829e9211c9edad83989c537572c5b41ad", - "0xabed7125de7dc52b0b42cd34fb350d4c6c45016319ab776b52289bc8c2b341a15d48165c0eb09511a1a5a5ed7ff39e4e", - "0xb4c352b4a127afb5b0833d210dc2c216bea666e7c5a940a3372988c0b02dfd236e4ac7c124664bcbf353132d6f061f3f", - "0xa334c5919909dadca50f3124de06400df660082b527f1f32b386b9216d021d38685f1839bafbaa7950eea6c1cb14bf53", - "0xa52f4534e9de29f91039af3fce055f2f6726fd9b10595a43ae41f7b466cc4ea6314487081e867ff4b5e35cd622fb428a", - "0xa68c6ba9673896bf49ed145935773fa50d95ec0103f97a6f1ed698d93b4dd78111325f797e47fe153fb3852f4590ee89", - "0xa5c456d516a557aaca80441705cda63d081181199097e83b22e9cf7b9947a8bb78cc476642f04a5ca3b13032319591eb", - "0x8a359a3dacc7b45da2b826dc27700178553f6a52e9705451f24c6d6026a0c597328acaa10b3b5a883b6353eee4eca594", - "0x807217b435d73c1374bca84d2d3e069db756176220a01607b81438a70f69232b82099c676fff361dd909271be8d5d555", - "0x965d0f46eb0804f19dd700d8721349287335c70e992efdfe89058ec424b87acccb3fbb18d84b727ff5ccb6f6783e9065", - "0xaeb5f2a0bff1e6115bc2fa73093019f8c679efec91d03398e24651be187265f7ca80369a1dfa61e8701385dc0ce9a0a8", - "0x85732f872228dd5d691f1507ba00cc94e054baa59a764565401e9e9b3287d2d0cd0f2af290b28b5e3c80da9cf23ded63", - "0x8e9a315c5b40e7cdb866b8a7e6ec01eeb27a52a76a88d5956ac3e66fd9ade3ec954acce816227b57fea6ae9244f1303c", - "0x80436457879607efd008f959cfd7507fbe22e417c701f59b5a36e878a04e51e87eb38c48c0992333656b24a4e671bfb3", - "0xa012f6d166cd1d98098544bcddfbdfa956ce60011694b640b012da3a0a22ac8a054a9e205aa9fae4df764ad60c65a6f2", - "0xb8225afd6e4d45520678e243d97bf48f87c2b8d2cbc24b43f94bf6e7f60b7768d4c3b30d28a490e7c8a1c3a104ac8317", - "0x8437fc2ab6d90716419f544a1d16c607173fae5bdc242d8224d7714c115cc54f2246d1062ecd77d5a9cd3ebed3a8adc9", - "0xb113c6c63125930882c18f548c1baa69a26f9f3dcfbedf5be41aecd61adb896ff9622ce038f0ed27a5ac602b6020740e", - "0xb893aee6291a3962fe17ea41322de7edbea6ebd51d2c564fe23ba8a4cf4b6270b7ac72c87f2cbca209be1ba607ecab75", - "0x92e6a7494114cb4dcf2b86ba61f57f6db7e4d52895ba6c896433139eb2ec9c9604f3e9100c690e1949e32f5b7e29de93", - "0x881a323e772a639553cbb401e2b6a255094412addcece2c99ec9e1346aea2f4e9eb247552435eab74799ee4c7a927b6b", - "0x8d5d3ec378922311374fcb998fe5a42176448b629a6475abe494fa56abd5faa5835af37624c138beeba649f7803a4855", - "0xb1a082ba449e93cc15fb4dc5114351437599fbd4d28eb6b4746d1bd242172518f94b2ca8b1f76c08d9f6ef260d9cfbb2", - "0x8fd2b7728a3c61cd8e0c607cf40e935dc45d52d040ef1259f62e3eeb30bd3a6cd030fcf407fa0b21423b23a795a02b90", - "0x9214aee5787f4666c3e2aff70949dd679d4203a2c3e7b6f88c548b80a3e52d7763f2bc2b7df714eef053f60eda4db331", - "0xb15df25b62c6f4ac9edc414ecacfe8eec055bb07a1220e327bf35c5e452da7620df03416a449197bfc8d948445c5f734", - "0xb41ff69731e7f4308fa18ad286d3ecd7be21afef3d32f5133a0bae877a347f8773c6e9d9b3b850d054236a6f186e6913", - "0x8d9d13d1b7d9df41cf5d30dd62b9d1d2c4933d62b6cf8d1830bd1ae4dd5fa3de36bfa1fc4d57681ae13996f85ad2551e", - "0x8011a7fd7534b248db40050edd9752c960ffd89b0300a91520759ad51da1698454affb4aa8907946605a02ca09a7f340", - "0x9159054fbc10164fa19f68736c2a683d374681e6e9d5e56f7496aeebb0969b8eb1a91e377b3a2928879147a7fb60b3e2", - "0xafd4980aa4661fe05bf9040f6551d980af562da69ec5072104d8ea34a8ebd28baa0b70e0fe3c11f631005693fb99213e", - "0xa92879cac7940c6d363ab3d0ba7f7f24bad0b16142c78969a737c27ebb09a62071540bec1822ae6224d943d02804da50", - "0x89338d27ba29343279dd83827ae17a53e7d634bc77bbd848f3b6a352fe92f6021dc1c81ea6693b3cbcb1f24188edc757", - "0xa2490a856c273b6eb5242672f817e60a157a1dfdf25b1d32e0f4836a9c2371fae72c93b94d78267b3cb142b4f4d7148b", - "0x8efcf5d06107554f896084e32e8dc95c49fc5da3f8c4be4ef6f2ed89914233eaacfea886040bfff14759ce28a1eeaf3b", - "0xa3516280b169a6832e997a4a45daf46aeaec1d8953387f493cacc2835a5791d4dcb24a0c0ad5de79988d76f843d79994", - "0x95eb7531a46bdc51acacf7fd9e7210bf6d5ca59b0efe58f79422394447adcca6f4ea991600e8558da8e19e029701c5d7", - "0xb1fcb4177f16187c76b421c29f715f1551ff365bdce9fe17b74425f76dd90fb4ebe828ffff3d20f75ac620abeb9381a8", - "0x886246027be4062258b232926cc82b6a51591138561ddd0173ec6e4b7ff750e15d9ba175f569c266148c653ac905d498", - "0x952c089dd09dbe531f2fd4137c971622fc1d85a78ff07de634f63853f62110dbae3646564addef8f2a070f5a16396ef4", - "0x812ed85f4559fb28732d17c8fd7c6b09a70da454a2318a0276949df0a5dd2714b14096656b7b5b6398f54c74eb9ca49a", - "0x9340db62e43e43144e1afb1da748e81a1b00f7b0600e8eed117e92ffcf801b9d89b494ffb003b4ebd5bb4e0eb96c9374", - "0x9287c0745b4bbe24b56784ac28bec43ed2abb6bb15bf11ba2b18b01801da7d162aef88e967d2f10fb9f52f6645d7702e", - "0x9615bc232ba6053fe86c6328eead899bd62c4f975273f72595407fe36ea43e30eeac7524bc17dbe78b4692d42ae81c04", - "0xa387899b521b1a89e860756bd0986b302f3c06271ece653425d6c697e0b330a3ed7789efe0e5a1b32e60257a12fa0147", - "0xb4c99909fbb92b1f39e9b2fabe05abf58af834b6c15ab0f62304ccfc5047f187a3ce35388ef293d2857b777f9938bd55", - "0x97dcb90d2dd9291366b557936931550d665cd05bb1b19a7a53a31c2a39d264789477a47ae14f6bdeb171e78941a9d9e2", - "0x81417b4a3e61ab9b48e0ff1afa8b523bf63ef95a6d6980092408b61f4293fb202395b10a5d12dcc54961370c134d5b0d", - "0x9135da893ef0a9d45a719207659cad4a0590218303d0e02016bcc5d14f54de5fb8de642efc7826b3b3212f714114600e", - "0xa00d0f8e2ea06b13f5a75a6dbd1f2ba7ce3f3bb3e62cd3b53f8b6ab39431fd2ce156a1aa4a1988613d4a2b6d91550147", - "0xa3f8f17dfdda07166a7e5503366dbef45ea6b6eaa1dbe02b8051dff58453f1ac24762c82f6db6de4370869f9b25d6d51", - "0x847c2b79076f9284d9a866a72f74f62fd73cccbe2df18c0fe34a35416d4825d364e24f95f728bc0e6a5215b08b6f0d2a", - "0x9816284cd6b8b35e1f5409d3a5899af5f4524a4826470fd164fcfe863994ee3aac77cbc16831f0866b9f0ae561903d61", - "0x8ab1f9feaa8ba2e1691acbfbd5460a4bab531344ce4accbabdbe5ba8cedb5d5fc0967def4365d755ecb62d83b7ffa4bc", - "0xb0cb477aee9bd113959ff7b7675f81ef251b76cccbb67cf68ba571fc08561736e32c18aae93fc8d1912e7eb2fc0ecca2", - "0x8cc41304caf0357d13a25ecf66336bece67d5d319bc5a50328a96199d7ca4fad05dbd7b5edda58be73141bb06e269c8e", - "0xa7b4d91a884abad5337925c34d7fd5f2aea5a09ff3c027cac98c646b5058f7fe2cbf47208930509e2a4eef1468f64c89", - "0x97d942e97efe46594e8fc86828ad3ed1c9133a8067f9b11bc0f4ee3815affbc0c7c46a91c40f989d50f1d8df96982ada", - "0x95a7d369f3ce7f7ad7ddf85bc994667ca25a0c2f11b9312d06654599410d5325ca3ea74f33f21b5aeedfb582a9e40c62", - "0xb0a05b564a754b46fc7aa4f5289f02bd9f19708b5ecb9db5c36bb7505c8b56ec22b53fedefc1df289c0f636c97e8ec47", - "0xab6e2801ea8bc600f9159d05a3b39e8b0973fb9c2696b3f2685424757a6953a9f8ddf5e29c97399c4821b8d7fd9f1bc4", - "0xa6fbbad2ad3ce8e4f9b939080e9e7049eba9f76b8ffb57f7cac2aa46793a064743239ce287e156d49cf4936517632290", - "0xa606632b62194aec737403ce5a9b6316178c1d27baffdac83981baab63e75d51caa414ea92465ef37d6d687b4fd90141", - "0xa5a99b7bf8f4c109af04c31af9b5f3148370319c8483796cbb5ef555ee1d4858b2c1acb82ab5e26180254399fd7a0625", - "0xab2b00f64355ad294436339636e7764403b821d4dd4fd74a6bbdc2aae450f14d7dbe8423336e793a393f4580f1b9e35b", - "0xa6c98a6ad7f36f16633fc216c12ca34e596b292524753ca1067eb75ab52facd28ed3a7c55e0a0cf1d3c9115a2a0d6524", - "0x84acda31e618eaf0424a37cb3c386585a3870b2c24020550a16134ad8802d427c918e2854c98e5def58a2363a8e1a314", - "0x9911ec15af39af1a18003ae120da8d909ad4bd43ff03078091d54de71de70e19786b2aaebaa5d55d9b2877004da2c271", - "0x8cb5a148f065e36b67a219bdb347a625a7a4be8f20dfb1cffbb38fd4d843c2b1b1886c1f015793bbcb02af04ed91b170", - "0x815d9adf22a36533fd4a3efae3c4326213ba2aad48724ef958cdd6f0dd5059b519e12d91ed5d92f1418a07b62b108bfe", - "0xae5c244f309467ada13e2fcd8942886f563bd996a5c65aee73a364c2ecab49be3ba6bc8a387f3baad44776f4f1042eb8", - "0xa47d93b35f57ad890239a6f2f69ef8760268adbe614d5877802db4b6cc75cc093baf101f75be0f7b4d71ad8724dbb9f7", - "0xa0d089701b965df9fea938e337016ab20e0e567e736e6652955f1a93760b4a9f128be5a594e71df8e7db47c3f88c2fa7", - "0xa9d9a7170a860e2860f785edbe18ad909ecfa489cd3a2abc580869c7eb8e9a2db93c1c473a5f1474ec0d51dfdedf95e1", - "0xb665abdd084abd292548c336e3e6fa1c5ed1a53d2e61a10ad6a4c66487d8a9e101632ff468b012506135907f0896156e", - "0xa10ccb363b26beb9622e1d91021d08a3bf02bec96a059ead01961ad51610992ef03558c5f77e074442836c9d2ff44e0a", - "0x96d6476066264eb3090ba3544dbfec7c8a0d90985a1697985db0d04773f6d37d5899a9d4fb5a3207c320ca78c37492e6", - "0xb4290ff9213e2ecd30d303b2b4ecc66c2614b8df246e70ece4e55bea9a1f5a0bae9df6dcbd8efdcf8c4b0f2f4cb44d48", - "0x8ef10b2e53e6770a36b6403678ffb86f5d85e3e87bb1b3ce9f1f0cb0cf32f1fe991c565595389ad83d8c8d54a47dcc82", - "0x91f950ef60014e3dd28f7661e6275ab6f085c803988b7d6dbb2cab25f10b0372e271267245761e1af97da6f48c230205", - "0x97c626e7114396daa337ada4f08da5129464d8e8c68a407c8798949817337578733fbcabf454a22b57926485c28d9d62", - "0xb596984b609a9858b1adefd15a546d4b8a417c8b54504efadffcc805caf8935b9c7f55d9e6b34592241195f513453572", - "0xa3fdd36f3eefffe0cd2a9e6cbfc4eb9c3a499eec25230df8786b23f5eb71efddde062940ac23d5b2885081da48d3c1c1", - "0xaa1822db9ee136d0a51910f0a59bf0d2af6819e4ec0b859b790e01bb08c1def87e9613b355525d4ab7d088b520a6a3dc", - "0xa9089edfa96fdb7204a68c4ffcb7e0a875106886a0c589dbc57a6709e7822747affb07035b99d056baf11d0852720489", - "0x85664ab9d32ab0cc2d2e61901b2682f88a7259c2da4ae6263b917ae8afc232614b4ee56539a868a24940eab74142198f", - "0xb90e06a1a117659b52b364359e2265daaa8981954e9a9c37e3256cbabf133dd4900974a895dde6ec6b394fb36b5bc1c8", - "0xb414aefaa4833283dce85add23d1cfd776567735f2ba9018cd791d652bab55bb0cc0cb38b88fe47e3b4b877e63edbd75", - "0xae579eae9c0b09c906cc2824eeebe5b4ea031547055c8ad635194f3e864c7a184dc21a3eca9c43c01d9a2f272cb2ce81", - "0xa7b1d13997c283c13f770d5203cb09b5d3ca7d45324ec89c069928e1ed1a17c57510e0ebaaf54a21d27b0f9f057bccec", - "0xb15d4555520565b76ec21d87e094ece2e04c7c4bbbf560264da37604f1a484ecc3ce8143b04759fe716411293876d0a6", - "0x810bb0773c06caae8cc06ffc92303d51eadca1e1b0acd57ed23f5feda70378e180619f68b8db98e61d792568f49a8316", - "0x87dee32807e2e5f2c884822b31098e5be2a4d950ae728e3281a39e661937c4b7e9fc025b50f437f01d69e5c33dd751a0", - "0xb46810bd73d077a6b73757d22b5939c02a3632e81287073b00ebee30cdd402e89c318e0b03d01fa331193842f3a1ae53", - "0x95a136a7bdca77f764d2c2d4795a8fc9e5b9097d73bb3956b7a45b42185a99c949db8ac5627ca263206cab9cbecbc31c", - "0x967eee3c3afc138a482bd120050dcb9b45a9fe258e5e4b678b1d67b4691f4c5d89cd260210fb50f9cf2d3e2e2802968b", - "0xb2d59a9ed0448b88f8eb26d8017a129ebaf27f11e0a031130266796e5f777bce93cf2c7e0fba8f8ccc997315db9aeb9a", - "0xaec708d3093b12caf29efbd8afe3ace1de24496cee72270223aeaefe4f0ba3a7acea7f2f5f85c1f274aaf5188616133f", - "0x8563ec52704c1c7ab515451a8f89f87201d30a12c95812ac95fde2af033e5019615a07f28b540a92781ed35786b5614b", - "0xb1c8f819a4ceb17d35ab997c14f81ae2af9d4510caffc61d4a19e9129e0bf7264482a10f329054908f99909999b6f538", - "0x8a65668637ba24358800076d8edc90979d6e614e6a683dff7859ce7d686014e6de85298f523ab060c9a9a4c4b8862cfd", - "0xb4df02dd6f4d3908142654a42af60fef034379b1526c12be66afcfc4f1177991811646495aa85702f3461060732cce80", - "0x8991bef253f0bb9b86e68e81f78116c51097004b0309e199025e45ac7ea55f8f6b2bdc58886899d275424ebd405ffac0", - "0xa74f1048548fb41e57f679d632280fd2e4cc6ab88c81675c59fe143b74dc7ccf050db53dac5611ed6b45b6a0b1b7f3dc", - "0x92011c668bff7ea995a71e4774e3fb5d521ee2552bdc33d9a65afd9677572c2a303a940751ffea470af898b01b9285ad", - "0x881a0e6042771492633b46b6101f96a48a93aa3860533dc207cdc90783fbe52b4a9ade1eea9117cea004bae802cd3fbd", - "0xb3e578bfd77a3a13368ecf8139b69f729cc720aab25853cc9e2f505c2e03e75cb779d685698af8cc4aba8d1c17f5ec29", - "0xa025b6e8dbeb68e7ac4a595b34089fed0d24eb29a7be235048205e35a97634d6015ab24c21a017b5012c3175677fd0bb", - "0xb751acd86ead936ed0f22d770872cdb5aeca3b1ec75a5a1e65748b665f8d1c859b5620d761d5f0a2a86331188e82b2a7", - "0xa05faf0bdb81caada6c662ed2fd145eff5db5c423258d6609bfd4c467edf3ddba6480ab95ac9f4dbc932f4887b070c82", - "0x8fd1faccaa7cf1d59be37bad69b7a99b7641cbfe930d778e0f712ae1fe9e78d53f37d7d5d3aafde48452eaeb65d980b8", - "0x86042bc710953f0042940625d8b69ef57c615f9631fc49aae169ca595446e9d55e149c92994d4bce7b544877d7b6f22a", - "0xb396047f716c5fa8ca9234c7026f1772d83f41be03410b4a32a376e5a038d252b8f36cb813bc3684f1b50326994c31cb", - "0xa2eece2d76db005f5d95f5f480bb3353ec67a9c27896fe54a2cd5cc7f802507d8d518596601bb3d2798842b96fc03df2", - "0xb738c1264d094f7b7edd27b0ddd8e29716c73bcf7b450ad7715fd21e1052998675873ccbec486fe45a8f72d9b006f239", - "0x826c4c5fea1596e353f6c15d91a9bbacd9ea592aba4d22e735263062eac44f073e5defb794f8ae4afb7d4dbcd1ace959", - "0xa8f1d170f63ae3b05ca9996347a1b3987136e7bafd02774698829986d48da3d421d269d31743bfd3e7917c5ace7ce729", - "0xae6871a8278f24d816657889ccdef509df0fb941fe6c5839cbfb704e81b942ea2a324fe0ac9881b385bc97410fd94b0f", - "0x8aa6bb564b6a0354be89c4ac10309f941162fb3a546259c5d789d4608cc628f69ecf814b59bb8bce364162f7552e628e", - "0x8ed85481cdc58fc540384213dd1b86f80af8908683d7d2c63ef5f8c4ac2e90f0e5f4e07b1b841eaecaab1f7e091423bf", - "0x88741d9c9d875e2c1ee5b95bafa4d8a22d72a728260297d048e4f0cd1c5f1eaa94fc233be3fa15a69163f218d62ab17a", - "0x8a99655974ad5c0f27b49d88a9c52a5375e16b9ac4f22b1e1bde53ce0a21589022c0ea926a4c2d7c432a53656ccffa37", - "0x8e2628878858764824471fd613cf40d1bbb3fa84ed081a762da0d6d491d54688723273d87a587ed1d3067976ab74fe1b", - "0x8f1a6162bd6cbd2353265bb348311073bcfca5a86f41cd0c63ab91b14aabbeffade5ae8a94f8e91faa386223fc2bf849", - "0xaabe8cd92f0193d12b032a9bab4bf4f02ebc0b24d1ac09f8ca8906621d6c7d4bb436b2dd879a1a1cca2b44ebb5642995", - "0x91cd27988ae8100d48ace10ac9cac4cf1cc8539bb492521a8a6489f8575a737f2a1d37fcdbe88dd651179145a59af920", - "0x8baefbda554bc0a0b425f2e132c7de061fdd120ebd452ecff0d78cc5bc5b15401997231727a37e9bc4abf1a553a4cbd8", - "0x971b12e25b989511477c04602f48f584485a0a0773b46643190263c0288c2434969bdddb1e55dc1f5b1b028c1c53eb32", - "0xa0e47f42444a16e51323af6f519c0dd2271a85746882818d02373ba33c2e2f7bd6a1c321497377e4781f72427fa34224", - "0xb52bc02de867d7b20cd247cbf496e03d940be2d7ca5755145e9a0168889db345fa9ab17c41635ab275a459fc9d02ff16", - "0xb01db7077e9f01e675c62f5095400cdc68a059e1a5005027033ac535a0505f45f89faae4fb9831f7ff9cbad3b55db02d", - "0x81ae065f1d55f4643a2ee120bc1245b9730455ad9e5402df8d6fcbb1bec71e40f1bfe7b8e67f96fff76d1478cd3973ca", - "0xa1be3723920044be80f398279e2f8432aaed45a36cc4fc71c87f5dbfd52225379e94600793f40aedaac2391caa57d155", - "0xb682f74fe46d4b647196b7c14804dc0b35e36cdff59671d7164ece874107964ff9f76c29b23c190796a9a3aa2df822fb", - "0xb8152e458970ab53f6b5bf6101008c5c31d2f58993474eed6bccda074555f7ad2351810d78676b62612e7eba2d86247d", - "0x9132a8fab2010360ca80adcc08b3a01658dc8ba8f60bbc45e1144c1219f69b985436c36c65cd7910a8aebd91ea1d3d38", - "0x805cd373a0919de801b6bb7a6ebf55530037fa41a1993c159e90213c492165c42b5642dda5fe7283ac4e3ade6e63a155", - "0x91f20d77fb7a8276174989faed41fa6da841d35b074c4a756c2b4730a7efb9b124ea6c7d5eb150a8b1126636cdb2ff0b", - "0x8cda3ffbd0ab6846dbee6cb8c0360842837a65f83b6ba17085161a7371a4466172354e494a8614cf2f1f4726d0a7262b", - "0xadc603e61dc36ee605dd7f2761ed568bf91b9dd3d40903eb7d77b11d10e4f762694fbbbcece72a7ec26976054139c768", - "0xa6accdb3df5029f19273a39bc30cb622f87522ca5a63372dfe61d993dd783ca5e918218b5c519d25e535d8b8238339a2", - "0xa188897269053f2494bd0de8cf098e41010fdd01f5a49d7ddd7b294ea748f1139e0d92fa7841dda9f8dc923ed6f02615", - "0xb26ad5dde632259293d91109fad4f742ab74de91f68ed2416ff53c060d1ea4377a875b2ce960cb7962c37a5fd47e85c8", - "0x82cfa86a17b27f375172d66b389df727734480a224b91585fb4782401d6c49d4dd347b8d1e8df6b9c0c1d2f8ae658de6", - "0x82911748e1471bf5d7fe3ff111ac06dcaf5b8a43c76f6583ca491e0aa845b61cdd443613c5728863c163952d86bfd482", - "0xb7b0d4ff87df02b5481183066f6ac0d1636718fbddc19889e92a71a168fbe338ffe780a792ec5642aaa4024d0964db69", - "0x8ec21f08594ad38e9ac365e5246aa5c2c8e34ae66382ac483b47771c33390ccace4d906695b1ac0f1c9204c46576946b", - "0xb9617d746596b26b84f2709a03b64fe77e9a10d0c85535d92d28dae9de3bbf6455a247f775dd9f67061792cb924e3925", - "0xabb2ff3f16309fcfe0a3b1bc928ca5cf618706cad3645b029bd54e5305682754e6ca47e364ff21b1750f45041eeeb358", - "0x867abcb8029b35a54552c57346024ae7eea38e9ae4bdbd68bb3c1de3935126880f237d9aa95d6644dba8ddce67e343e7", - "0x86eb4283147a9e595d639f29a967310acbed9ff09d9043868fd18f0b735d8619eb4ee0250764f35a51e00b58543bcc66", - "0xaf1779d2115ca7021533bcf55a100b4d3ff4e45f8ce6a6d98df22881526a429d97818fa1867ede09918a438957a03534", - "0xb10b36d0b69b0dbecb6f7efb6c612b0462c346079109970a26541a21aa2b5b81c1e121ed0d5c81af00ea8eb709a83dfd", - "0x911f81ed75fed55f1fabc5f86f9f38490e006820e5380963a739ebc0f87a1dd3b7da8c69dff1e580c5ad2246bc08e2cc", - "0x8379449499da9159cac2c09c61777955e61c63378d051bd28b59c78409ee5d09c43e7a6c246572bf34233a314511bbdf", - "0x84b48ec8895049bd03dc3256bd0d63f6e9abb178221f7d47703b447c709fc5fda47b19a3439f30f10d2670194f390915", - "0xab3bb5afe824d8aa20f97ead4c40aaa93350f33d980b5783cf56c8552a4298c989b7b188d023711a2eb79631f3a8c317", - "0xababba2722186a3b2272feebaf2ff46c93883b7265a6a4fba039d5fc0e7fe81b7d4dc2cef7738406f156f693ba3a55eb", - "0xad50302a51eeebe63085d3c1705eee9142bf8717d07c5d87e0e4ef5a12207dd5432994c72b9493f9ceb558a20929c9f6", - "0x8bcc3d83a6b8998e1a1066347c647ab122eac80c9c505d5cfbc370f466349671d8da4d500201226c15c1f62162efc62f", - "0xaad6946b5d5df34ee6f7422fbefc6de33dcf4461868ed7ee7f47fe9b8eb2f7a89759c73b7a029d422b02afd0f550e722", - "0xb0fe1d9a30759d83084b4c567b586e5a8f5a080bfa93b4a3feba59edaec33b6a2ebc98ccd82aa9d8cf0bd254d5f03baa", - "0xb993c4c2b77fcfbdb213bfd5f8d655d1d41a52583de63b432e2732df2f9d88c4c6779f314848417c06a089fcb970c0f2", - "0x842ea3aa645e5852695405b6ff2184e55bdfcf50be2319761e717b7b52d904ec47ad3abf986850c643003442e302ef30", - "0x8093b0ef1f6c84a8253d086a6fda6be8376f925f416a9d1f44ea72489f60fbd8b53cee616cc5ece43e2a202653c0640d", - "0x8c75f10b6aa848d84baa4120e75d3edb7f8471473851326cbd9ed7b29b22c5403028f49430bfe4320c3f4227827e667c", - "0xb4fde4f20ab98f76f55afd533f1b09ee4ffbac9486399714514fd694fecd0ad1fdafe13b2b80721829c7a59e4c951a76", - "0x843b2ed867cd8edc2eee84497dbd49f3dc481e7ece69310d06225325ef032a4e72907e16e7b6215ca775f88983d55e5c", - "0x9881e5caa9706e4d7ba6ab81525090e29ecdf1808931f3f2b11ff9ff5cc97f83f3e14fcf18abf18159c3fcf4cbc27042", - "0xb6c4acc868c05c955eb36a24652314be37004bfc14283600523729d466c56018c99a45a41ec0389449fcc3f8aa745638", - "0xb6820864d07715dcf4a9ece336464aeef9ce381ca7dba25acd48f60af056a3405c22792cdc57c641e782896c0ea05b25", - "0xa1bb482e35f71772486675cb4ee0fa5709b757083d18a29d4f4344e6ce901b2edb2889b7eac92c498b90c7d3844c450c", - "0x8cd8d8d47de859d0c68bdbe1834a1c9a34e92636600fc592a08f96d66426c5f41f388138f42c9b8ad72c596f4bf85496", - "0x801cc0631310656864b25d980c9e99a98fec2316414819afeaf182d3e7ff93b32a989e2ce63f5ea9301745080854188c", - "0x8fcc6b2b656f7960d9ad48c091c1ea71b6f0f61553f7695049c770afd509ee58ca8e1dcb403aa2c5acfbbba58676bd44", - "0xb997b9a6b994e3eb2de8723ec485d8181fd674de19ac9c2f50704785d9f5a28fe3ad194eb052b5ce122ab5e6e6968a70", - "0xa909e7002b82b371952ca9d0832f531db15882180e97c12c56da649fd65334904fbbc3f097b6a954469221d181e718bf", - "0xacfc712e1a61504814e37b3aad0d7a5cafce5901ffa43c13bc5f70507800ff03ed261367ccd09db7429cc5dbb892a7e6", - "0x8d634a07b69ad87e41d941aca08550ae9cd72fe31f3075511d030c364fd6578a36f3f0f3785d19305a1e772486ca097a", - "0x9746ce2d890248002c1bfb755e06f4f4570cefa7636e10319bf491c654b83608766e95fe9c77f1a6a630f5add77b71f8", - "0xa9dfa56bf82297f709f1b4bdbe4bc194bf22c0424815bafa6c1a536f2d15f35bfdebe0867ff20781a49274075622861e", - "0xa723af2702c6b473caa4a64142464f201bd1e2f765454fb0236082fe3ad77f22b4353e5981e6bc37e974c7ef797f875e", - "0xa42a1a0c50befa6864fa35c25a17f5309684c53257376f8111fe96c84a5e09376fad9c8545e1946f360e16e1e4c941e3", - "0x84231f6bc3038320dc13f3ac014977326dd13e5b2ba112c084d366b5255729b2abe665aca8a41d7aa6645412765887ca", - "0xa64e21d651bed6dce8dcfcb4caa60791b9345cd7b6a100f5bb78f7423fba5ea0d0cb3668f3415c27af29ac35e5dab0ae", - "0xb8eeb2128ea14d81fec5b1103d8511a3dfdab925212363c75c5cc01515fd94be8db2335bb84e221654380e58e9f2be67", - "0xa92e9cb287981b33a5e697eb1e757bd44f45efdda1759122fb27dd4bd4ce3694f1b6b2082ce4e6e3919d9d7a0b7c8a12", - "0x88f22b83fd9dad63e800b0bef709759f380c6dd9af7058100413e7b09c7517eba258d6367e0cb1a41b7762b86b2ef137", - "0x8353d45a2096fb4bde82ca22381bd2ed93fb58b236b16e68bb37df3024672067c4378d7f04a4da4d116e7d57a2211f7d", - "0x9076205bf231de091fcba7f5a4fe1d4a359f07236efa39f5715f206e5cb7eb3d9adb56af8181f63a9d3e965dc909556c", - "0x93ab7f56e8d37b47d3a8cbd222f2dab4bdbf94a1152302752f0a731294f4dc214fdba17977f11aaff2eea9517fdd5789", - "0x96d9883ee108c88342befc358325356dfe5d72c521d71e4b3a58d6773ea3d1a1de1a20572aa96ca0e8483eba62466504", - "0x950e0d61ce4e76fe0cdc3d59c5bf23d8e1cfa9d6ee13b9fe41e6ddc0fd52081bb16bcdd973d319c20709ec517fe15626", - "0x88809c1e272b552d46137165e5396917d107547b65059fa646b742489e8892acebeccbb3eb8f2d676e3836c985cb1756", - "0x945f13ff081b74403a19dbb04173780f04766f7624ac6b77f46464df5f4f3b547c459f41fb1842164d8f1c126ad6be65", - "0xabfbadc599bcab1c2b7cf1fc5aac7798d9f617d6afa0469ee23230c0d004fcd3de0ea645feddc74e676ecab1fcdcd8a2", - "0x83ea1571b064d05e1b7f4527b20ada121024a4b2dd8f7d551945488ccfddd671ed2ed3895578afcb3cf958f9a2c75c29", - "0x8fa75050bda001409f2bc0a275d8dc0fefaa47b3a0ae132758bd711eaed0851d6bf3e4b7f355377a93fb8eb02b3ac6f5", - "0xb2fff49083bb30e2661e2d8978149e0d0588dc972222f46d5d120d01dc5c9978830c442827c8fa295f6b8e6d8c786198", - "0xa352c2dbe4f18b311bf0690d77fbc9439a1b8088c806a9d89071b3ea04ff387325cdc04a091d2bde5fd087bcd0f4f482", - "0x948ea89408826ded81549cce823dfd7605ffc2279ca7d0964b1ab3d5f35f4b174e81575291edeb9eaa4baad3610ba3a4", - "0x998073b618140b04ec394ffe4af02df044d923a5cbc8f06f26c9eb4ece17abedd4f72e10c9738bd16863327c0f6ee20b", - "0xb3bfdda0d6960af897ab508bd9312d9c166157f78b45157b46fd2e38ab2e430e8a19335d8a611366cf74642bda77bc78", - "0xb8dae3e2ec5eb97ce3b5e9be719bb747e6e8f28dfb1a6b7bf5063822b502a5422cd586bacd87ef83c0af081ea4d30a57", - "0x859713ddf0ae843ba690fd8177ce6c08e2fe5fc1c8893d829d39a199e04758719bd3046034926de40973a992ecbfeda2", - "0x866f150d4b6a015b03ce8ad93a70644b55ca1818a0f50d24795698c62f3abe59d3b8abe4c11ffcbef20127d3b7afb970", - "0x9145367ce9e2a5a6140db58cb097767b5a6e19eb36d1c03acadef612af95eba80048f2b02c6fb46eaf38c75288e3e4eb", - "0x8c298aee778f4af13329975754e9b428e127680f26be139307d43268dc63892ac98284d78ced0ecd384301e26d5b63e2", - "0xb4c2cc9256fc33ed09531abd7c3e34f8f24830a8a2cf2d684cdde46155f43ff2715c94e7dfc7377765ec0cdefb21cd2d", - "0xb9193113b81bba4ebfe40e97be436515254bc67a94939220e5e69a197765bba40dac3369e5cde115d1bbb65e1c826038", - "0x8474d72b7cb52768c484ff92d014d7733003b511c0c915649f65dfceced47ecd933ce876eae254cdf2f6357ea865580e", - "0x808e9a59f947b2b39af51deab4c164878e02d95773dddf1123091e27de87cfffc07aecd7c9cf3e08c0b9f525bd87fff8", - "0xa8e0049eec8eb70c12446596ba5c8a29823704be3753312c34cb271000b6c154b1022812dd02d1352cd263b655437d6d", - "0xab7894a75e40d888a4d0539582cfd6b458da009a5017e561c14d312335a75745ce134b57466fd30c250ca07e0529c8a4", - "0xb30c5c0abfd35ded7a3da8f9c95e3e1c320857be1af317f6ff5e35101d3f31de3735ff8741f6460ae1e63cee543081fc", - "0xb15557ec268b4eba9628ccec0a5f3c947e624b61edc876e2ad8c36ada061fda76f69c8afb95270b85f4672171678d078", - "0xb7ec103d6695fa64107f66622148902019ff3acbff7b77ad80993bdf209b73990b0fef92dddc5fb66aed77cdb59af9d3", - "0xb3d002f0a35808e3785d58d0074be620416ee9381bdbdc889805ec2acfd169e1ccb60045d87cae3e90d5da94cd58bf80", - "0xa17c44ade6eca0942742edd237661ed406a129a968fdab28a58d19308d207a1e7853099a4a3c1c181695fcf265107a55", - "0x91fe5c0d672fce368e229e735eef43868e31265502e2876e54aa44470a257d1c126ed73d6df860f42d8e1dd425d8987c", - "0x8434fa331278fcdff2c8c07596a051847425fd7cf09af31bb235d208ef6e282cae173d6ffb73c0475307453d6133ae7e", - "0x940188d6c20924edf1d9343ea85ef9e08d9d87d2a188f8b69514a22cae10aa2d3ea8e662d43d60b8b77183b3c6e8cb1e", - "0xa89f57a730437fc511e1873830b300df7a417493a468afeed2f837f31641cba04924effe11be92d3bfabbad0bbb7d04c", - "0xa561550cb347fc9178c875ebd8dbf5d14c0afbefa79f7b93b893a25ca8fcdeb0293de5a350ef63413aa70745cbce9a5e", - "0x89fe7dcaa6a10cdbeee9d0d3bc8dfeacd47e1490a6c3b591f66d3a64ed668e6034381e0ea9f5f04fd2a5d9ad5044b8b4", - "0xaac54b334514d41665b80b2cf18285391f47be820446e2272d69edce022f6d7689c8e137e2e9579d0846bf5440d768c8", - "0xa231a04b942d471b32cdd12eac3eba00b8910fca0812c9470802246c479050d6c860f64bcdc6b6e39ed0e9609df9239c", - "0xa6bf6eca52b5f3ffd89b79be6edc4f517fe9c9bc67051179157734689fd63649e321d1fabda916a9c4666b64ed60bb4c", - "0xa7c4f791a1d77cfcdf34c3b73ec7a43aa1c8ec81c39ce81d12c51973ddb0bfacc79e1a128ce17afc5838982f66cede6a", - "0xa1644b337c4398f00e9ebfed20d9b2c900ccb667be036abba0c4d372939f881df2bdb5d40b64354f65c8f2ad9ffcd656", - "0x84f6e86481d3322de791ad01d8c1556e5480534e52970fa601b295a40270882476779301d78bc2ebc323323ad0b62253", - "0xb32eb2beaaeab27e190c9d381b9f3446038391da552db5ded0f5b58d070694f07c737315a465175da29e2a236c539e9b", - "0x857029d97cb9fcbb67e194d9aeadf5b25cf8184b3b704ff5da424fb4b39abdf3f7f317b3f79c762605bd9bdd5823e7aa", - "0x883926170997ba84cf45691c117912f6be5c691abab77fd18fe114577e6dcba18f8c0a6641ef59affcba1b2c92e093cf", - "0x945be3febcff77b4238500054a053c983add7a96ef43cd91921dad908c20d4ae08857fb93a5bb588e9b441aa9a536567", - "0xb9efb8be322722302d1c06640f772596fc362586d8f2e49c41810f4bd2b59e8e9abf3d5369b2421e1ce6949c067f07be", - "0x920ad6d5cacbdb46af424141391817da2fe3d463bab8db760026f98e50bb51aa4f3668520c133ccf9622d66eb8a60e86", - "0xa1a9ca07d8d3a44fe372aceda194f15a2dc3d29267aedcfc3fdbadff0bab1c4397da1049bc0feb9097afdcf1cd1ab603", - "0x935eb5fe97d580c10766bfc2fbff71d8584e00e1a321018540c25f6b04791b63a0d6992257fe110b0d17712f334c9b49", - "0x9530bde6dc33e48e05d98b77844766afc0d5581922e382a2fc1c183adf998c8137df29e56b868c7892b2c1af56edeeac", - "0xa8cd3698276c2bb8d39ebf7fb5fec139580755adbf81bf362e1cc19f4a8be750707bdf4e1fde3064873495cce5cf5171", - "0xac5a83c82004728b34677bc6b1fa507687992b5b78745e5820de08f3fd99e35c905608936ccab62ae39f0408334b3c6c", - "0x927b0077386a5055b499cb5a597ec3c9934767343fd91214fbbb5487faa4339837eab52c75a627d7addc5cda5ee35108", - "0xa8acc2ea4a548d9a2fc2738abcf75cc0efa189b92a99296c0635d53f2c0d7ee40ccc8ae410d2779f95ac6f2027c81d06", - "0xa74c24b8c695920b12a86ed6da6ecff72f8e19fb06fdfee9cd1c1e8e5f1c202d26fbf2fbedc9a5deaeb2d986425477ce", - "0x871251e8d69de5c3117f364bb95d876fb89974428bc167666088d5ff1b83328b675ac2efa2d0e215831e69ee254623fa", - "0x946f7a6d3d6700f65088c817636ed3c1349e4f5122fbc22723d131d8ccd055931dec977cd0cb8dd888c6abc51a5f4194", - "0x82f7c1dc3f133725570c7b64e31b0397fc3a82cb4966948803de210182b9716ccd19e59c0e0382c0c970d05c5e13509e", - "0x8bc45b43102e0df4767156b1e8ec635cc07fd629793d289be1f2470297e8a084bc9af0d76566cc485a8ac898c0493fc5", - "0x85000f8c8130abca642ae94b4feb3448390745decb1f443c34fd06575f1d0de35bbe649b46251df0a4bdc7a8bc133b2b", - "0xad1ef07d34c59afa37fd5147646c24c03622ae4884c163b80d45ebfb5fa994699ad9166ce1ef727c22be3c28e0838cbf", - "0x8d1dd5500229f463f94c611bb2674640d20f2d34dd40b28c4d2a21d3e64ba7355fae55228f1c70095d1b288828a1950e", - "0x834cf56a4f2c2eb04b89383213b84bc6ba554a4715c3c1547278e5501102f6ff2af27cce0f876a2aa2da57b5ac6f3b3f", - "0xa468d06083d770bb4e484718d1c147b49770757b5b296fc6d6035ecb3c2f5c4155176f12ccbe6616184789350403f387", - "0x8abe730d80ea895705bf67ac4f6b6a36fef7403702d8458a383d04e4859b4c8c7a75598721cc75793d29276afea27ccc", - "0xa3890145fa43e6b5c7b8aa0a73a62c39d623c9a75d17c5a05bdddec08d114ab5b0a865c9edb2be6ef31c3dc9544119ea", - "0xb2b7c1cd0aed6b776515a12a0f3a86353fa3d3a3b6027422bf7f2c21e6917dab543e189e860c8fd3aab65484b77efbe5", - "0x95215b7d3d504ff83ae2bff789feb6b5919287d354d567141bae68a0f0d27b3e898edd8a9be5a51c04dd28ce9d4ab937", - "0xa93a3da0e101797c690c38a5bf5bc14e10842e48a18c9888807b2233809ea8a34a76d20a8ece0b682d36c086853cee40", - "0x849a7fee901a9279dcc36fe8f276ea6dfc37c30f75b679ddca2cae9c283de19c4df56790e6ae12c4bde33e837fcbc324", - "0xb5c1587d84b0826e64438d8ee7c103119b164bede8d243a0256b5b798240259dd63281b81bfc613a4874a6732d05e143", - "0x97600c536388c942e0a72ba3bc33b3af48045994a3ad0948fe0741391c1eb99693d072d1efdb644abcb08e10474b7885", - "0x94c2120a5b4743496e7ab9bb2e474580ed27d7cf5b6fb132efcdd7bf934434d2be8d6f0af009c637b31727b3ad5d2280", - "0x8a5ff1e7f552fa8b34b22a220eb1cb018c9c9430f0f14a634121923497cdb4a69fbb8b60eb33e5fdf9b0feb3e9f5afe6", - "0x8b4c9032f25181e6fb9f60eb07e3d6cfa2b14ffdd6a0fc1b309b078f8290901e229a5a6ed96dda74e1a9a894224ff588", - "0xa5e04e164ffc46da1dfe026ffdcd99332874a110cd168c44762c461a5560b5c098ec71673d509fc053f6d9064d4ba255", - "0x97d21cf8327a81385fd3915c7e8efac7662f4b39a9785b4a936fe1b581d630678f42a3e9ea7e02bb4413da7ca9a6f35f", - "0x806d8462bbf148eb4cff812cab11b3d819669ef5f0d76b228fa166b83727c92fdac98ff3afe946855685b050d9d4c6aa", - "0x8a9899b0ddbcf4ba3f16bb006218022efca867a5b32e1de9c7efe1d7039c8e200a406bfd09ebb8921bf1997185e9266c", - "0x8fad2d8629c546c5de443b36927b068cfa333c8c4c1328e1221a1f6af7be5363ab8981fee54307532f239eda7656e6f2", - "0x930146a1f6c3decf40198955059f70c98de7c5bb1b25bdc97fc72de3a84db1b121430cf7a7456a692d8bbb6b325b6001", - "0x82987887016fdb90f79f045c16629c5b2b17b1b4702cd89d06b70086e5922cd10c5763cba6f3d30a2c33bc84be36c6f5", - "0xa6fd7e4834f7f29da41170c13d29acbba86c74d5924cd361588cdda26a3ea7f11ec34c31869537ff7ee0b57a24555e9c", - "0x97b2474cbfb632148869a6b911c2ab91e4af9eff6c181566a1eb34a05d2ef3fa9da4fdf14e8fd8746a7c3123e20d572e", - "0x99ea177bb7d98dce25d300b09bf6ce08a7061360c4ed9a54e30c1aa5a467be6225737b62ae921e91547b5b9d39b800d9", - "0xb9dae836e37d51c9611e6522aa6aa8bccf2644f23113584c74c963d79af0a7ae533af823215fdcbbd8df62f00ec1505a", - "0xb1a7165aa1ac480b4eb1f0b3d4284c69907d1b5056a343a2da84b3863c9a2ec4d757493f5daf9ef252a253bb3b2b6745", - "0xa1322eec41b38b8bf3f4566bd12f9c230dd04d085e0526218489e986d59895d471bd8bb08351edf40021efab1d29b2d7", - "0x96d559df46015e62d8876f4d8679f9a9867dff31eb151238cd75b3a10bbb2ab0f51c804a2f5adec1decbfa355042a6c6", - "0xab55e38cd273bffaa94400bf4913ce0ec1c1c848e8c53be1808d4ce5338ec92b4a4160b8faf0d1d8ee8b71ae751d0ae7", - "0xb61c2987e2b402a52670abe305f8a9976efa9720ad0d7c5c1d0d6d9ec6f1569f51621b6edae84d9bb3fef32bae31a088", - "0xb5234aa19fd9e714c7a9f3ea33d39a5c49f42e7a8edabd8f306083669df4898711d4b50b049dfb91815588ca60052673", - "0x8e98a7b90baa4693c6a1e1c2e556d018c3408bbbb5dcf2c32d120f797fd8ed1373f1f112dbca114863801ec6efc1a5d0", - "0xa7e1e77cbd6274f8c74b37a607cc20596bb7fc35ff1ab4358de15b07952aea397e409b30188c8516676cdd05d4919f3b", - "0xa5f2336ed9338772b71e490b1b3916d33df8b013e4d38dd57185b7314ec9aedaa34eda2733c38e06e656a8cec74080ab", - "0xb5de079ec867af3a3910fe47628c7d793c7d70b79e25a9a436e0a75405e2c58b740c1b86e1b073842d475e0b717d0bd9", - "0xabcadb7a09173f1eda179ab7e3a5722f020402eaeafb9d604641645c21f1e009b758f2a6fd262f115d80e23f8baf7328", - "0x8694ad59d4cc328b064884d147f66095605d9bf339d09e45652d68de765f2b09d45558d45daf9b4b36dcf881df8d4fb8", - "0xa2cc7b2e812041f17b450b5fa7429cf62e2da06a7bb3c08a63d6f802ddf13e8b73d2056bcd6407476dd322fa35b9b065", - "0xa97b0e7e22214f329fc57b6d7ba882ca563f863c06f1afcb60c0bbc81ef08ec866d39c81a80a7843889fc957d532cc0e", - "0xa8a809392dbf35911df8566dc20e2373e2fb3272bd9eaf9f474588a9132f06b5a1433ba9f36a738c6cd3fee403188fca", - "0xa3fb0038f83116eef1d6b023e2e17ba2795f7f90ed7c857d9f04337cb4e0c2e7d691bcea54aa72ac5e4383125b74b755", - "0xa80ada835fede8d121162aabfc8c349f685775406693d599e3c288364097b02d96c10ddc20e72fd308fc882e5b70c064", - "0xb6e6c4b24731a2895b7513ad97c0928efeeb0c645dac9fc8cbb0a6419221807073f6996f2b778e1dcdde63acc3a6b2cd", - "0x880a2e8fc2eb57f44b08cf4db5cf1751bf9f4aa688708039007d2a198f4e7f0f808aa566b36b15b971e804835102400c", - "0x8b3baeb4e1c1d7493bd885dde7873afdc235b58e45b515cf51ebcd02a9b81911c5ca182a9e340575585186c99e71d2bd", - "0xa6248e1bef3c6c6ddc155dfe95631a3f00308fa77b1c1779935e76401e750f151b7377f9376c08e8273680e924382af1", - "0x800133df4ea65de3935d98b0249e335a918c44167a34a16c0a4adaa4654f458c376eaa76ef088672d39aec4c7d951833", - "0x8317a6e0667fb524f35672e070f047db29450b06348604319765e4db09f966ad995098cf38acd30346c7fef5dd62528a", - "0x81fc2ef2ee0e6f21f406c51f02b9b7be8d99d30a054df918cf89c708d64c34d8b0dd060dff4383de858c0dbff25d71d3", - "0xa28611f96138fe6974e3e1925b582cba76166259c32b39e95702fa0c4957ef2ca32d575b1c08cc8dbe96ddc0eb56a9f2", - "0x86c6773f4e0261413d6d3944e0f7e498a6dae518120e3940d2f45054a912e706b3b615fd160e6143a7e54942406f9af5", - "0xae91e3db099d165b198d80b6d9af894203949d87cb980f4db97dd43ee55fbe1a45df156b72e3c3e9306975f9e5e62d77", - "0xad00ceaea52dcef616be9f9815548f8e9b800bc9c1a8832a4d8acca6c8779317d1951e5700e54db070a23db41266c934", - "0x94426f78470aea2d82eded320b45bea09b7cbdf02a3d7c2af4ae4567a3493b352b36f43c3669237879910dcefcc82fe0", - "0x8aad924eb1a30d2844654c9829d82c65fefe964d815572b6c9f902c6a826c247257a7d0d4967e2bae331d52fb3b7c0ed", - "0xac9489ec928e4f43f8d194b8f3ab83382b66b045f18efdfcb05c1d4e67af7b3745ffbb7f52cab4b8895550d10132e2a8", - "0xaf8f390c7cc40a08c0143b467634c10e8046ce40466006a4b4297c76a6c16309b50f41a4a022fc838738c4c72edfb34e", - "0x923b0384e87a2ddfb7a2c47f628172e8dee76fe812c44a756c67cb20527d8e9029a561bd4ef446a013d4be7db7259f6b", - "0x856316b53f09a90af770bafb5c9ea7deb921687fdfcf512840e96fb83df08820c42263c9ccf51465da33f1b03db04d09", - "0x92e8823b523f90ab75ac6e30869dcb257d232b55a3e167769ab5b54cbb83be94cf5d84eed4b1653db17f3f1350ab5e53", - "0x8d0d05fac92079a3df86a72fa399e606fec7e56f81d3443cdf0cd373b3330235b76890197ae61f24d17de39dd1aadd06", - "0x8a801fc71b9b6988a829044060679a7cc3d40630fba81f72bcd15c0e5728867f4bfe938066e68cbb54b042a39600fde2", - "0xb40a6a786ca1a21159b72990b4d3ae8729722cdace4e8124f8cbcc3fa96005563535d28e9d92cda02e91d979d27f8f97", - "0x914f30250d79829919c8ed184c2e471c0d9835f2348e628164dbfe39a51dcdc3f8bf99c945b1f413e65fc5424014e5c2", - "0x8ab8b347b7846fbc7ffe69c89ff67dafd522bec708b7ffea312b3a7eac47fb9d6006cb9038962a07dd89d4688ee6a18b", - "0x8e755f8cde0750700252e41f6d16b825e7f02748a13744c004a52b19e52d58c42d1ac32cd5ed1d6ad14cee5174b4ddf4", - "0x88d6192d72e1fefbbc9ab400e5b0018bd300839cf604cfc1034657f62fe8fcfc52acd86c207dad0fa6383361d338b2bc", - "0x971fa2ab593578b341076d98c49c71dc7d9eb4ca706efe252441499037cc86fea49af681d8a4d324d302526b2a3e5c18", - "0xb2deac648501d7e284a85c19f514f8744c48d2b5516c993c2111128a9fa042aed34dc371a0cc3f00e918531dbf16c0fb", - "0xb63fab8600fa531d7f48f8d207298544d2e03d4da23cfb43d99b0612f1a20441526de63b7609f5969429e763147ee5e2", - "0xa8f30d9b4ac3675d61199e8e624f88b9dc52658a2ba26a2bda5f9cd3780f0b1e32b56c825d9dbc3a059d6c61fd37e261", - "0x8a6f8e963dccbf1db9c839c21a4e832c7a218b00fc31400346b5379fdb8394142bf8f8b981fca3f4d3c43d4e34dd3e31", - "0xb4883e6a4213c799abb2a9b6998ebd4c89aeadfbabbe4c363b22beaff46939dfbe4dd20d113688a293a41daf5cd82c8d", - "0xaedb55058fb467ee9556a3b601af86962f99fc06f7eaf837b4deda030b1899f565da07ddc7108e9f5e7024e11c723ed0", - "0xa8185aafdbd22a2df2ea0f0cf67fc88c4c3f8e64040da08cfa9e8075b792406c20d3155d6ea6fdcbe9f5502c44125545", - "0xb2b27ff20d24cff756e8edbd6f8686d202d687016c561e56dcffebc78f404ff544c4d3ae8802b91bed0487792d6dfd05", - "0xb6fba06a70d8b1000555b8c6d791b1db3fb7f57a0f8b1fa8dd00b2ee14242877e1e836cef89be3f9e0565e61a6b4c275", - "0x92b3dd6e18600ab856c276bc787429d42b8c02abf5243f7919625aa1f4e8cc3eca61cbe106b81d0e4909393a5efc021a", - "0xa508e1a1d4375f5130c95a169fd1d4df51cecd84822dc28b18e464c2189d464e6dc6a5855e0cbb94500d041319749ef7", - "0x84b3e9a6b5d1a7bc7df44ce760b5b686fba006945f6e1f3f67ea2c90dfa6ed70bc1f021828a0461fe158ece87deb1e30", - "0xadd83e686118fc5eb56d79199d33cf0c90fb2a5996c6f453fcd9b9eb3a273a466776adba1cccd6be62a4ea154480fe17", - "0xa1fb58d9a323dcd7862ad4bc6359ab2bae35a608276a3053d40bb3abdaf3e8827027284d964e51ae7b61dbf299f2bea3", - "0xac901ece7cf087c782f75f1c61371f77ba061bb752ad680c9b1012768e5ebb6241b492bafd9e016e989cea1ff51aaf5c", - "0x961b9ef616b7faa3befd807772893c7c66ab6990a9405cf4345ec29cf13d75dbb6da41ec87af5b5c4bddc8787b88b480", - "0xb386f7ba0b94ced118691d883549d70ecd28d1c0d1b718cb82a92a246e61de4ba80b6a76d6039c261e342f9ac136941c", - "0xb6415848092dd93da62b5a5307d356d968bd7c935d3626f40e9446573e5794f37a23ca072fe8af2a9355a4b04ad35e58", - "0x843b3e3221bb08122a1e649e81759297d985c7f393c36cc3bc707a7aaf2f53b9cdd449e7a4384981c5976fb3955871d4", - "0x94083ab99a73dc5cd463b5259a0f4e99847bf32ae03739a440f8f48e12f078602c76b3fe4e7ecd31d52a7aa31168c5ee", - "0xb6f994b5482aabe833e388b24b9445c01e47fd6e354c3684094237189001290aa77a327181e7e7e756682a04b8b3c56a", - "0x8366f418a3fb2dbc9ffb5b798adb968aab991fa689ec24a4c4bde6f046989b1815e1bce5e846f3554028e16799e17281", - "0xb8e5680915eb37153daa9a3a977b47c88b4f30fd358901888a1056e07d2a7070d28a47acac7aa7856ede16bd0c93ff2a", - "0x871cc7a122cd7b9ae2199801e6a0974ba8cea64e5866a5130ee0ec926adda24f91b3ff2785932cb55537030bb5ad811e", - "0x9370ff1ba27d33080efb22836147f766c60f0a8ca250ac6b2a82bb464ffa543da056284b712dc3cac53dfd1680a4cf87", - "0x8614d8029df5058f5a072716489f734131b228972ea9b2b952ab1150bc50b6637543aec1c35763f8dc578275f7c9df3d", - "0xb8efd01dd0016a27a0e2df65b571d405be4dc8e0df5dc0d8354fb187b96589e95847ba0c2856613924125d21193753ca", - "0xa86e524431247115ee497c07ca2a73387eb820d293e8bb74e1ef1ae7ffdb21a9dd8ef1a6e3f391e6f02ee0b51fae2a06", - "0x9151e2dcc0b928573421ffbe43b1761b6ccefa4ecd58be7fbc8ea8e975e18d52c264f682104480d590e6f8c0b8b9f63d", - "0x85ac8cb79fb8916f7eb5431b7e81606b38afba15895909873f85d9577c87ed2c1d0fd489fe058362f20ac05626681346", - "0xa076dd75ed807bb7afcae8bb9821ed46758c1a8d00e7f3d3c91a18e6b95dff3958ed70441a1f4691ac3268d95e243614", - "0x89d8dbe170b9804de3fff5b6512d04643ea0041c3f9bedd7432b171ced1577b0c0a7bb911852c6bafe154ba36cd30320", - "0x809a63ba788e618a281804ef97a75df39c7115900078a6bdb203bd79d3df87e863c631e934dcee62e28a16cb8735acfd", - "0x9727e6720f8b73b6ccad519d8ca1d4f90c2db33ab536f399e2c4ce269be15d99e22504ef153aa26c40d4cfbc450f25f6", - "0x83e77918ba6e28ee01ba6b8dbdd84c53faf65446a90bcef46f262f341dace2e237b1ff8f8d566fdfefc6973deafde716", - "0xb5a4d3fff76905bbb229d579b8433e76f2f070108230f20a30e4f974f12f29ed017aa66e9b298a4de0fd535a0e1a44dd", - "0x876d3a0bb439e7da26539b98abd0f7e0b7e8035eafed08df623a77fdac30ac85ab4d58984396319a88e072dd7a5149a9", - "0x98923e83be5b2877ac18415f9391ea792933db718b29b6970001682cc8434ae9fc640427c0a27f6d62af5f78f3901bcc", - "0x805c675a34443a14c0098613d11b4c015264e038a8d1adf083844f2e3e3f2414689788423dd0ff77c02130331d511068", - "0x8d8cd51d4146bfa48492e9d3f3e4b845d4ad1442ce6bbd95979f9778ffeb108c641c9ffc2ebbba532f922237e5849222", - "0x839862454707a99eef931335e5c5ed80805ba06bab0337c5301fe9fb92fd59c9ff6620e66de7369352b079dc52bf2113", - "0xb3cf3bd867f60b345a0b91314b34ce1c02e64dfbaabd70782614208d32fcb5d4448102bd54728fb05d1ed18a750e88e1", - "0x8207a421d010e1c5854b8e41460c6a13035ee77f7add0df83c5c31bb00d7acdbb676478a7dfc738b9aef5c29d345ab63", - "0xad2b14f87281ad6e1d2b713e6e8303f1a45cefe097820d6a1bdf4652364e70d28ca92193e2bc3d0a1e69da5a51c90ff2", - "0x98025be2d7e59ffd3f6c3c2b28b27ec42206968c0f96d09330598fe17a207baa6574aa22cc26555139766cc284224fe7", - "0x8e80fe898b7fee849f7dc8e5eac668c76f1fe18d159c51eaf4ddd8d4d600c852dbf6c2abcb878c64f37db7fba3d56968", - "0x871c0e2dd929ba4e157ed606741a6301aef759e10a3f919166faab23e599d3409b232240e3afe9c0e1622a11cd453c1a", - "0x919f7e465b399e2819ec17aacc199421d267ff2979ea8dc8962542ddbae51e2bbdf6cac92f8a35e05e4d95a4a8315cd4", - "0xa6e6667e6127ee4f0224a9a94be3c22831a1ab3b16f57462562b11473c425e7112b33bbbb6af860c81bd6e84bdbd3b86", - "0x87eaa9e3515f2d94acf113d77dc085609d06cb038f5e8e90ed29bd04bd4814e95ed0d6db5a1d65572dfaf73ab2e50ba9", - "0x90b30c66ebc16f767f3f0bc1d8bb17ca1951a616292297ca8dd06d54cc53e5fb5fd6321ce158c04cb4c91a04c01f7fbb", - "0xb5fda3715566188630f96207c4253315a9cd166ef96651afa0ae1d6f0aa8856e7642e2f8ef3b1fb1eb2c14a7331f6592", - "0xa54143f662a6946da901ddaa9e514a0e96bd6397020cf5d88084a1e1edc092b94facc150b1c029a508fb3995acee50b7", - "0x8dfdb813296bd105d5813657c98337a24c8bea19bf0d119efca052c018ff5c88f31e05e110fa12f306ae4b0a8498f113", - "0x8b7429599915ffec755060d9cfc2c445df9184ba6bf298bfff5b54c2ec8747a9b65bdc6c73746a94a54b0a62d93b6a28", - "0x8a1d1108174d383465a57ab4b1a6811ab86dc007de4f342d37f4cd311650382e0352d3664ef09cf1626c0b74e2f21ace", - "0x98cb860aee0b7251da2d114b2253daf977badf82027a018c956fd59c6c93b716bfe69a132a4778ee4b7168fbfe390ad2", - "0x94d5a0d33a0aa590fe76c71e80b21246dd9bd8c2f5ecc647e47a423c2dddd743010484cf2fa363ea73bb217247429066", - "0xa082b7a109fad08e2c01dd7322625c18f47497b32269ae4e529b1681aeeb3c4a813cc6088ebb4427b486320fbc4b7872", - "0x86c23e2d3b23244c7763c123ad67a41a2dad8e4556cac23696906d1acf5f4cd7f661281b8ab2027d268405b08eee6771", - "0x801522a5c211e49eb96294a9113022d86c84bb8741e44fa7328122836a39ba7e11e27d0d6773550b234531400ba1e7eb", - "0x9683d154b18ed641867fe67b2dc70e8b8afba79f73fdeafdf9015d85aa0c74d270b290952683c3667c0202a83626687e", - "0x994febc16f8d216a20774955523262966e955cf964950b4b2831a3483f818c20ee6f51cd24f499dda0d3191910a9fd35", - "0xaaa8f12184525e89ce980468fd24e1a9af846246297546655763ecabf0b5b5047394543f1791ba1c70e21637cd815877", - "0x9193a37d5692ff1bacb0265bd7825c479624d2adf33a419b0a71c8a744ca1b0c9828127831302ffea4fcceb1a53ccd54", - "0xb9f3213d5d588ad73b86365cbcf0fabcec5c30cddad418281ff2408dc140e3f6a25afcb6bb569605191665706c675e35", - "0x96aa280b2f0ae5c3ac51edaea4435ecff8ecf8f2536a3400d8c4c9b12c64d16418838dd7ffc1b815656109ca63261050", - "0x8486373d67804e9832bddca04a0084d1976d324d85c22a52ce2bcf7518f014ad00e4795e61c71e0dcad1f23316288dcc", - "0xb4f2e7f7e2ed7917e7c5036681e1ceff18b688c1abbd203c2bda0731ab56701a847cef4f753f68119110680913c2dd4c", - "0x87dc2336d88edd81b94ef78e7bcb6d3876257c326d28b3f4484465d6c65faa6c17aa7a2f85c6b94ddece39f6736751aa", - "0xb4b3502ebe175820f53da8e3fa28160579c4150d79d932923739aab545af537b3301d5b21f5138ab4100e737fb61a084", - "0x88063af42d5845267d979df07be0735cbb42d9b57d3625eb5d0aa7e4ee90ca88fa52aed480a4d60eaf0ab8dbc4f444fe", - "0x85cb81247c09e21de6deec42e668b72f513c7b105f60ed478b08b85fdc8a886a97bb7e39eca0cab09b294e4b1490b0c1", - "0x9920fcfcf836faafd211fa1ca78302aa6feffcda98aadb6302300c250fe8621b60d9c214ea92087c44996ae0999eae78", - "0xa1f91af5b378d61ea277e5dac81cb71d71a4ac35322aaf42b3a8aab1641fd51d8da1783bae0e8ccb66d73db8e1003478", - "0x87507b427d381ce3906e372a12f4e61514ad7a102334826266df14542adcbc8bb7c8450a1fe110069d9dc2e9bf0687c7", - "0xb7581b0cb549d71201583e0987e9e9bc6cd36585c96664f836e1b7326e5375ce8d0a450343fe0b106dcc581b77de88f9", - "0xb26504a6a7a64c44d7f97d0402bf752740934ea4c6e101ec131666deaf574d55fd7f96c8807473722b6629dbda2ca3b5", - "0xb90accb5c6b78322ef88d017fee2ae1cf87194f4b3f6f4ba6510c0adf4c11b20870043cdaf45372844f5e801464bb682", - "0xa904dfa6e1f813b4aa0b242f3eaaf893da7ea854efe514487a237a01fe244721482476b81ed75ef1a951fc54802b29a1", - "0xa00373aa8d98f4dedf9cec4d227b5fab00f3af2a7bb4c8b0dcedecb5a04244321d5f25a81d57ed0ddcf293c701d290f5", - "0x91bedcb316698e73f43e9dbe0229772c856f34901fa4c1e018e96eb898e4ae02b19d900e87d01501099163be56db57ae", - "0xb84dd6b9a61cfc0817da422380b0dcc5221deb600b4b6a6f6c5ad934110a3b66c59f7407ad68bf8642b2bcb5427e8050", - "0x8507c172e499856675ba69fc1b0389a08e58f8e5658c9268172b926dabb4a67b7c836a44d865f736e8fcb14aa2809529", - "0x86609a1d82d90a971786da9ad342035ae4865136e513559069b6dc8ba82ec0bd1ac695fe8afa5f61f85c2310194014ed", - "0x94914f127a645594ed372855550ec0817663224208c127a08bff3d5c4f463b7939cf13a45dee68586b678ae453c6d60d", - "0x80b55565972213814afd6ad9b1884a4d8143ae90c148ba730ca77b0937c2faabb23a6f985dd0bbbe05705fada4cb1a00", - "0x930f5fe58dabae91c26c6fcbb61c3e336678dcc35d028e5c958d2ee7d50b80e1693c0693b82d719dfd9fbe2c03b52c10", - "0xa45053c493da932896d95d5fb158869c1051e1bf99658b183c9cf4415fc8d4fa1b6a8752b8bb26e8b706a03a57fc05d2", - "0xaf7434b48d2ebe639c8082be09060422f662317bdc136d534b76ee3e3aba5ea8f234cd4936aa2b928f6eafdbe5165a6b", - "0xa57a073bbbb3020a92497f0ce854666997a182f2b437e7b06c9888db8acb2fd2128e3959f45c391f0548a3de49e37e76", - "0xa0ea8131b2d8cfb799e806d8cb92cb02d32de37869cf2ac3c82f7c5d9a963d562755b16d25c4b60f4ca214e323790a9c", - "0x82f920aed42eb630281919b9c1fa4acc02b05ef34020cad3583a29375bdaee167a47ca3366ef065cd8e658301942dbfd", - "0x8415ef32a93820618abb91329224bc46d478ee8749ef42e372ae4ea29b6c05a65d5ef515ffc7d720b2f41ccbc040f176", - "0xa0fbbb0113daceaa05478163fa835b070be5898dd9bbfa9abc582409a7b671c0e41a5070de4cb6dd2072888b11825acf", - "0xadfc99221d7f044b57ed40f4ef8a9e47e57265ef8eac654043cf5e777950af6fbdc2c2d5a5b916048fab1c19acd69dbb", - "0xb3d8e85fccf623fb3848e4886d580469bd41ec0533975298bfbedc7a1a9b4e554714991ec4238d8ff976a83cab6383b7", - "0x8b09702f3789ae1f7799ce58a0ffc2327b3ebf2b56cd870f2be66c0d6781cc1f34c2d721d0de63e0fe9db85bee842fbe", - "0xa935864851b73676cb49f509a198caab467e5dfe4358e7088d2a76e9b8c13e5d20b01eb7c0cb9e51ee98c90cfc393c71", - "0xb5035d76a5a8251bcb18f33968b077d43403c69492b809eaa3e202eef174a5649aee30f701ef0be050ba5026957093ab", - "0xb1cedb563cfb09713009b2263975a56abb9932b8cdebf10f7836c5c34785149e9875ff590fe1414ad2d21da977b7ba26", - "0x98a718c23d44b24ac295b328d91ab7a40b23ffbccaa90bc5888efbd32b6a95c530bf5e999ccbd4f1c85263104f336ce9", - "0x8d9d2ee952d5b135eac2f06f0478faaac175f23cb3789144f3a490f2ed34c885ae4d8ad7ed48db85cc6c2bd70b38c6c2", - "0x8155763582ff6c68d7071ba842b6543361cd5f65b7c70d5bb838da2dab2c02f3363e2324307e7d2149b12700d96bde38", - "0xb18b277334ef7f24706b7d48fb764a487bc4e21fcbfb01627b7524e9a5d3253be99d84c417084fea769b550b3ecb4574", - "0xb80db9d83cb1ae861a3f61197a1f14b6c5004a2b3d031fb207adda94d725f3e265535ed7b69b9c801f2e95e1d64c1901", - "0x82cb673ac9c0c124fc546c59505fe4fdbc05a1fece0fa579f6a6df96f74bfa877ad82b6fa768cb678ff04ae4cec58d1e", - "0xb2e190b785a4a882939489b86d0a06cb637b7be8b14204645bdd9d6c37626e8623e35e1e4eab5c8fdec0f8349ede8918", - "0xa82237c64f15d306365be19085e1c725cd148702fb66658c7974b02051b685715fb9e35fd4a596ec24d532df4711f82d", - "0xad6f7e3992518ba04b510b705fa6b28e3733e0000a5480e8a3c30fe71394de2bfa43333c69e750bdc3e7092b9e0f7ffe", - "0x8c0ee358f37c28f3b80cb9ad5487c342fab734886e31e30c667e616f3aba737a3a07bac4da552d8405ad8b00c03e09f0", - "0xb7851e0b88486b0a858a218f4307e0c0c8c314fc69e2b90cce8ba86d3fdb796b572e50eb4e82f83f73c7f048484b45ac", - "0xa7c35abc2e15723a9f395d16d2484b798d098be5414ddef083c8283b0c29823226fbc4727d9cccf96e33b27fc40e032a", - "0x8ec5ff2ba7c3ca8a2d18df81d46e93a3bc94ceca88134ea75cc8ec2ec4b1ba3d0de49dcd4d385083c648a63483377fdd", - "0x80ca7ee722c3253e7b534b42a8947e38741c542dee1d671b603a9a743f5ba2fa95f193ace46c01000ed20ea05ad0639b", - "0xac14edc2d803b28a169154364dac5360cf0926d911a615077a94858fb4cbbe31bae2f30a6a68b248cd8bed015e0f3b29", - "0xa4bdb63e91fa72995316d03cd117347cbefd14eb1b19a0adea1c9d39f49d82ca1ceeb2a4184187e1dade109d10b83090", - "0xac8f528e9e8fafde00e66a75d4bb68c99029456ae9b3b7cc76ea4816e89aca2b8b7d094db214bad1e87dd4e84d1c1a5e", - "0x8a8d090a01aff14383419735840fc5286e71a5feefb98c563b2d7ee593b518c3aef6654f10da8a77f40feb52e1d31fac", - "0xac4259562982b355fe5e57e1cef574a6a40a7144598c13a6bf07cdd8000bfda95b0b0b44f215e9dbc71be114a1857441", - "0xb53741dc30b11fdc6c9778555c1f714fde60890c191a0effe419fe2b6100228d07cd0738d0dd73057cfc7e340c75f0c4", - "0x80ff52fdfae53dd2410ea556ea6504696439919687d2dcce1e952d9d17b6e3699816ee623b0153bb0e0588e36b6f56b1", - "0xa92b34d785a71d10e6796ad07df788c6878717cef4f1f0623898370725006d46fa00a0a22a3934fc5cf323be85fc7767", - "0xac1cc08cd1a8fd6c946bbe14662b18e89725933a79965c663b73ae3cf5f5ab87e794559ed579564884e430e108385e18", - "0x88b8b2264d84106d38c321c3a4927b9b41cac172ae27f6292ea44cd9ce11d185d0061a59148e50474d4dad3c9e940476", - "0xb7ac9f257b4f676d69899a181b45f40358dcaa70fa2dad38870d52838aad9001f3a3145f6550fa2826018952431e4cd4", - "0xade67b3d1602ab0af6a256f25a65b621dded7a0adca65c526ab34c5ca3088a549b7ccf76c586993cef0d2d38af541617", - "0x8fcd8bdc44ab42a70c174682a1e8b929004834d4962a902de460eaf8649883c868cde1cd660d14d7d3ce589fe3aa83ab", - "0xb914f6ec60f1767a12fa34a4b400ce102564dac4c1c42f1497c7bb824bfb9000c9e23ed7cadaa16ad79d5ac906070710", - "0xabb1683b313612b583e87228384eddc3e2e7539e0aa26e825f5c27da222941b6a37ec47127cb0f11b6b8e0d02a6f66e9", - "0xb01efb31962345a2fc71b7c370e7d3117bb1d1e1a9b6984ce11bd83c898dc127fec2e821669deca7c74d406e4678a736", - "0x92439394c6c811d908b05c626f1afeda3a0f8c925747bedf66a4a5895ee76e7445a1982e99d8658117128df5866eb64e", - "0x956bfdcb00837be56d44f159bab9bcc2292295ec1ca7424615e3b163b5d14f7143e214609c0b65ab74a0dbddbed4d782", - "0x880b9a8dc9bf6499f1f71828e6c906e2ae59660c9aaa824a6f36116746406351b4e364b6fa26c45e9d90018555bc7dd4", - "0x83f4a0dcf523d414e023075ce0dde10161d65c0abdba522c811f0e446980cbc21eb0bb42737136bce30fcaae3c673b6a", - "0xabfc5593e02dff15161c4da67a806af3170bb2bbc65e3a0457b4bd994ecf5e001d02bdd417655c2b4433dec270a6273c", - "0x99c6d8bab7d937a4cb5c272c4bc3856a3cb8295cd77ec9e2fcc6a50e0545999cac4c413c3ca8e5408afdb60388c82ae9", - "0xb08f5d230713639ec98a7afcb2a25b9b2d1c48820447d28b6a3ef448aedc4b9a90b6c5ffc6613a70ff1766b51992074f", - "0x99d4b54e35dd3f844088155f114ef93507372ed32a6898b9954d5a6d0743e55d8e7de20d67671454d26561ed5e4fb05c", - "0xb7cad70deba1622c79f1ecfdb2612e380e9048fb6146760ba61cb62e98cef129d3944c5f442b15fc11c102fcc6e2adb4", - "0x95feea870c86525ed214e3e0ecca9f66c5e0babf6da8473e5cc5e2f305c26939f6afda0207bf5855b6a6c928815577ea", - "0xad6e77ec226053ab331f3f871d7fb770ae78227a85096d263bb42915299147a7a7b57a4f8f929765cfb323267b94865d", - "0x82339f53ab7344f8dad554fd0270c2aedb34f7b0c630f0a56ca9217c04f0e4a38781eec769354a44fa90f556b388ad01", - "0x837d4672d73588f19b872d81b7993e5e0628139f5685d0520b1b766d40e71b9d83a8d2bd65a03987eef89b3d5c254683", - "0xb3c27e19f579133f1ded8c066dbc3e4edaf449a1edcb1aaf215939d63a7f2b250b9b7afb62d4cd7cf37c28da81898a67", - "0x91f669f9db8fbc6d7a5ee92cb67c2fc1ccef6dde622efa455dd7535b11f506f4e309a8878b859d6605a3917f6d7d67e8", - "0x8332dc636222829a83501a8312904096c2984cc0c5dc077e067d8962bd87666226e3324a9e5057c1cbc3ba700a3b22f3", - "0x97e81e20bf33baa4412d6b81c5fbd406dccbe70973bd73e956d8ce85c99d2199daee5fa6e99fc6d40071b352b5044865", - "0xb716066fb9e470cca4546a401048c0e6c6408c8c9f4cd80aca6778d3f4121378e11cccf8a005845fcc8dea2e1b9f16df", - "0xa7b340eb603da43f2aa542dfad1ef3d3357f583c46040f2dab234c8246d7c55d6885f9f7a14f319e22355ad498c22a04", - "0x8281ea97a28ade9a0cdc73a077c72a92810b70912006611a00df8e7d2ee1036af73c0f062b367f3d4d75be4b9bf78aa4", - "0xa481ffa0813a4f2110c6ac535fb446282dce73c182eb99baf786ad42b804ef12df078b2f534e3cd8210973880bba6a63", - "0xb71a581ae08eda0437f9e9274c1f9431d6b357e4866e40d4c2470252f0888978497af823dbf464785479e5f35eb89aa8", - "0xa07c9010308bcfb0c97a1059d5213980000841ca0565697d45aa46e82fb36494e4940aa435ede417856d24f73d374757", - "0x8fc353fa8733947ba067ca2bf5e14a6c334e4ff30efdfa67829dc86f49424f4548e879b153e79dc75f1ec00afd6693c6", - "0xa663faca50e1fe5d00f62abb0b7828d6b761fde9f5a54f27c0b726d8d53281f83ac165b3d3db87f970913350a7dd07f2", - "0x970535269744905640d6ab238930dff375ea7efb2f391db324724166f0c436e7a3eab7ef6eb2e5d6724c58d588a4c592", - "0x800f33f5936498e16fd0f58210a5a5c104074039db7d9d5d92dc62cc00d796ea0a3a22e5d368fe269cedcf30bf6149fd", - "0xb4b921cc901a7775df7ae73e97cdd108d98c54534015a1469f0ca6b07989827e0d3f9bea2ec015fabe9d309054aef802", - "0x93295c8a7e5c0bd9decd99ee2d704d814cb6bd0061404fe00984a8afc337e78af11965a8560288529c2a722e8b54b488", - "0xaf43d382ff7951bea94f4540a3a2dbb53ed527e966d0dcd117d5212f36112976e1fa00a47bb9870d3841cb01621c5d7e", - "0xb4d106b21e4676556bedc6e7f5a7eb5c2ad0d5fe8004a1d968bc7806ba871e241d38892b1fa73e9648b23158802ab57b", - "0xa96cbe38f86165288a365efa796b0e2076ae9fa94bb6377cb80c7d5db9d376e9c18164a8a3667dddb3f5b847f52fd319", - "0xa0bde83e1f3e925561c481ceb58c7575027f9641e69f14242b886e7fbc532a2bc54aeeb94ca39bd7da3ac984bfe8cced", - "0x8211c4a70d08fe052246d3ccda60c9e9677910a93d9262d572606d99e273c1ade353eeeadf5b1e3c1ac3c4b9019d5f61", - "0x954ba6744e3f991580b6633e5d184550e44400f20f00149d899d97bc4b51b01d09bb4f82ad975cd55189320523fd60f6", - "0xb7e3f17ae79c2faaf5f3cbe0dc528c6aab0035eb3f38954820556bdf7c3546585fb9814717302c5f45fde7170748ff63", - "0x880446589f33ffe7ff5e105fa1c380d401d6c46e80526948fbf4edcb779753a594f3891461f52eeb3f5f2f6051c361b2", - "0xa26c06cf79c412d49f39e0e27e37c82c4cf0c8648638ee66a97d22d822e064a9a7cbb0b1ede46806ea0430639769cb88", - "0xa968341c5e4a3e6d2a2116222e3c58c2e558f5bb0a2877a27c69fdbd38dc3892f9ed7d7c114f557e52a351c73614fedb", - "0xae9b8bf4774ce3b84185be77723ec62b9a415e21cd60e86513c1500916c96d62519ee8cc061d81ac9db9709d6e191649", - "0x83a30c1ebc046c9a1ba911ecf6f147644f58f54e32357dc395388e6bab66d71fb9b691754b11bf414d43816af8058828", - "0xab5b804fcfb68b6439f311d0420005b083a84da15a8415cc4013898806e67c47698a9d594263fd9be42bf48efdfbe2fd", - "0xa41c18185f8111ddd551ecc8f6dcb87036cebb6eabbce7faba40c6c5c8af2ab59ef027c6fb2dc523eb0159335a1ab189", - "0xb24cd94b7c6e161e651107769d863fe5a3d7a847b9c60c7c803846bd782cec0bd54e6278a318ed23b90cd7ad25933fa2", - "0xa5ba23ead78d1678414d4e986b448e7a24b23a5c0f529ba604a51e4ee0f87baee450fd121b43a954be50bff6c0d7908a", - "0xb89c17de4809e722527832b90b810d9691b437f19db9cb88ca5cdb67bbc6946ec1d454dc0990b66093ebeb6eeb6896a6", - "0x914f436fe0ac7540129c3deb04d51bc61192ab5d0d16eda77ef70ecf8cab5f55a13492f54e8052f2f214186a113d8949", - "0x8e0b3d1dd756a9008894028d0443083c21e99de69b8d8f4e7eb3ca7fc52ad540355d4a1081774a6d51a093110f4bc838", - "0xa9c1730eb5c0a42deda9d9b39390661717479e29007f5f8499d0645b8b85bc0ff12cea2ac4328f6588a12126f56284ee", - "0xa2318a42c99f7613ac78cb110656c6e470cac6903a5bfdc1bb182af21e0f0f409bd39324a13e2790f0facba04459d3c0", - "0xa11ba34521434cb718f1b2015bbf451ba1a7c60e59b1620ea843835c7e75bb42b6ad29263cd3705f7f6f1e40a0ebdfe7", - "0x90705112b625973e1cb35e30f9e15e3c752b2e972231b4caf53518f44b4a40b8a6bd15c4af2adbce5dc194169b860cba", - "0x828035b0e70af8db1294379b4b70e56624e1138ef49f7be81d938e8b25aa5dcc03655e045a95a79e0143c23a77407004", - "0xa7abb1836282917d1eb9886c79b6a36d720612e3b823d9420a4a705e8add6c6bfff2f682e6f992a6af10ae2f71ca8828", - "0x81e97c7f980dbbe93df9efdd9c0a8172ba0f00378e9375c926b9e24758e8b827037ba67e06e994fa9d05942320353d71", - "0xafa640b2a7fb997cffc5db74a91dece901be4a36415786190dfd17a77ac837a2fb2d73e973b8e60582e71824c57104cc", - "0xae860a6850068f2b0e1e5a03afbd08b667f44c4f06e431f1f83269e754f37e18a764b00e100dcdbd1c1d18af9d6304a5", - "0x9443fd7e1263d5ab9baa8b1a3c893765da1dbed0bdf62ac9c886425ea9f05876df1920889b707a2cf248e7a029883588", - "0xacb38feff88de8db3477ea9ae3b33e0c5715cfc91cc71926dce26f4f290dc4f437461a186cf1bdcfcd6d121e087bba33", - "0x942882666a9f49ac24d9099facbf1e65484ee76cfdd2eacef25e0f30260654a7b5c0cb7dc37aa1601980877f945c51dc", - "0xab2c9035b2ee9c5e57d8de70b24329cfbd247324309eb30ac78c404ced268dbe2aaea8d417300c90d87924a48702b793", - "0x80aedcea9c5a9911731ebb444500eb95b519e2d4650c1d465afc61f4997879d60750ae3fe049e54654a06eaa2db7d8c2", - "0xa63e1ba5fac918c8bc0f4364b5fc8d26214deee825aa1bff111e03c0ed43baad47e8bae154ad580b851a0f66be85c88e", - "0xaea7f5f8c387c21cf671246803cd5baac61cd6359848ad4fd685b1350ed6298a129ed74dace279fe7846001bd6577dfb", - "0x906ad36bbec72813b368bd2b79c1c9624966dcbe94ca9dbacc297d0d8af86edbd80cd702ed04f0adebb913a6a7bc1a62", - "0xa46201c20560ef2ded1ed3047fc196bfaef445c4a716890d9235f3a06d6993a8ab29e816eba54c6a2e2590dc8dd61216", - "0xb37eb2c0d765b044ed2fa2923160a19e11509e764025e43a62b4ccbe38e534ab59e68c2cc92cc5aff9d97154b8210c50", - "0x91f93b1404a4bfd3fc8ea019d76230637ceee315da0faf366c712c3ba19088cd3efa2dd30172dcdac11e636f8473a26d", - "0xb6b905abc4a795bf95d055ea09c3f9d0a8a9ba0014e288492a3751d2aef60cd3b7846e1ca8366635a94988b2e197191f", - "0x847529bf842d7623150a3bb91fc4ccbdc66010bf008179a32359f98bd007330bbfabfdc487f4b98691ad65680af67a8e", - "0xb3d37a8098d02b5ee69ed060527f3d924c727016fd92b21d6a52fb1c1ca18c7eaf0caf8144e9e6bb5b6a039ca85cb1e8", - "0x98cef893dbcec865cceae01138613de146d563f13853ae34bed5f142da716673c105ecbf4f2aa7d187bdee20702d8582", - "0x97f60078d18928c4d7dee1ab244b2b7540928e20cf7ccbbf6684148611afdd9cce60dbf412c1fc544ab8c356fda8fe11", - "0x872a6758004e6c87c3788c5c11bcc74db78f076efaeb75127f0baec28febd02528c65b227b7619fb8c29cc92d7c8e799", - "0x8d72cf1191629440d7af8daf3b76b6b1bcdaa8d6ddcde52603dc8b092c2ac78d6e24bec32e1223eeda15dd17ba2c26d5", - "0x89dcc8c10be08277a1e394de336bb1b135bcc5131dee5eece80973ef364a305235936a3b6dc40f2eeec2aaf227a86376", - "0x972c4ee3b4b3b028ab683415bdfecb2454d326a19d274f499e48bb2cfd55165b928bdfa7f97c4fb6d27082cb88b73dd5", - "0xab5438a8af3acf2eb75bea0ae71d8aeae363d6644c54e3b020082c80809ef86faf5811808adc8240c7693515ed8bf199", - "0xb594133dc9f71f72e448796316ff3ce2f8a03c21ef9c54e551d23723d5f197f7fb0bf1c33e9cb3f51188db7dca51bf49", - "0xaee981b45d570a666d0d0b2c7aeaca3cc22d4873812b4424d1f91144142393fd64c49401dfb970c7d5ae91233676cacd", - "0x8f978d21de1e264178f88cad7213463a5efd139c30dfce81a7eecb46942870a3c1971f6e6e6a50e0a8b20c379ac084e6", - "0x9153701c8b82ab43fa4635cf677789c9c9911efcf23250bd393301c0be51f14fd0acc4e467ec9682acc89085b94641d7", - "0x8681989a1be217d77cc8e012c95128557de70b362442e7f1e6162bd52ec6e4ebb0ab28f9ad3f67c1d35ff00216ceeb74", - "0x8e85421256fc71a82d35de9645a6da9cbe4dabb9670758c4eafbcf42b26fb99866bb2b4c374601749738ad34e51dba6a", - "0x976774296281bbe1e8dabaee7453613d0a615cc6abaeffd8e15ca4484b5a743e298522b2dfbdcaa697e1eea2b2bff736", - "0xa585501faf955b6acfb328d801cfec5b59be8ff2fe46ef0bd73b86ba4c19c1dbfcc1df844d61a5acc64bb5e8a68f6cc5", - "0xa776217e5073714b36bd2ff0621246a48799eb5ae3ca438d1efff6f9f9beb13779bc18ae5ddb77c838732e8925018118", - "0x992d726bd4889f4e7565bcdc31c7b4a58ba44da5f361e3b46e0a67a6e4f00c25e3503c94e7b2bece737d7efd47ff9beb", - "0xb277f124d5dd8dd669ef1f6840276c0bb0b60379ca3a0aaf00ca337c40f478d511b1a73e73df6c3b600e6bfaf37a8fa9", - "0xb037e78617c235e6528e535bf13bf5e82c70588d1d0bd08de754d089bd47a4fdcfee79b5666b95698cd98c0e32164afb", - "0xaefef9e398e0edb60615713d7c1334005b21844d3f1401903e09af2db20d7b342b8d80796fccab583c8607c533c9b735", - "0xaad20eec7cf4f0b518007ec1df7dbf4935f6f9ecb36a11d148dbf9e5281aab43feebcc8ce9001374be40776c5ffde825", - "0xa4ebd6018e004ac8b5d022cfbb7c5b3833456faff4f198a3d9dbbd077c8752087bda1ea060466fde4a5f31cb8a50a7b0", - "0xa56ebb8ac9901915400234c1c6c8502905765a7224de56b084f9b0a3468a065e78b4daea27d9887b4f44a72fa61a15fa", - "0xb0269890863c63203dd4da3a08a1bf06621cca212acb49799bfc48be7e41c951d807f85dd4171ed57c372914dbd2ffee", - "0xae11fc0f5fd5ba488104bfc07fed50799f51ceab4768afdab300325e9a913b1f257fea067d357e54950c8d08af5ecf59", - "0xaefce65396c61e835ffa38857df426f64508de6e93f966cc46b54dcbc5e2bfd72df927b00489fc4460414569ce99e610", - "0xa5a1fed75677dc956c000b9135c4b6138e0cff53770399ffbc3b12ff0c1677ace264aef2058aea535ee1a7195afb034d", - "0x8071def0890d01f0d10dab3afb13125f0194e79608b9ff129572b5daffb49cde5bf6d9f24da3f84483612aaac3cb8eb1", - "0xb5e5bb8c0be22349ea51e249cf2159189fb9aee615dd62c5f67cc9f43745676e703abfa6561df4f5f1d79b86c459b11c", - "0x978dfc57cf0d3538ef336a25ca7a2cf373f84b71bc06d1c74907464e3e816d834087ee126bbbbd5090a09ed063f87a46", - "0xa2ff4b59b3e7fef169835e67d47218eff5368aed3e6e2f1cacd29a5efe6c1c2e7e1839d87759bad8ad1871b39c481bf3", - "0x96de49b44bcd2f5ac3d07d6f5270af081776d8631fefbaf9fec6771e13d40a4e5158767067297029bd38e8c6847971b6", - "0x8f2f820e8e3645f2ab9a27b3c23b5f656b681264d08e298ec546c5aaf51119893e0dc8e04d6f64fef48d3cece89692f0", - "0x8de2eeac7dd4b53119d02f0ec99f127cbd8f6a57120d94a9a554c04467fa74ecbdfebbb111d9f15cdc1be2be8c2396db", - "0xb6616f68b00ea0fb78a25ecd51d3018b9ef13664a7da42663d1bfd6fe71fab615624af863f3b41e625b36a607bb42dc4", - "0xabab5be2ab033afd6d110a340c658fb512bb53368886d8a5ea29e3c916a6b1bc46decb2cd0f508b5667f9dd88033ef7d", - "0x8872d0cb09df44c2a75895d46588316a4c9c743080f7a03a384bf4d4be80d341f8dcf0e208383bf3587a3509f3324fe5", - "0xa3f57fda2e8c06fa7ce9de223f5ff56d53ce9fbc48486d88d2845e7011dc038b6f2f270dcfd46ef5222ae9a1557070f8", - "0xa82c4e46f0d1962cb48d6c3d8ed3976c4fd4c174d119470479d9770619a45e6e16e30693b2804a82b516ccdd400508c5", - "0xb53188c6b2907abcfe47fab98f23ac602525e05a5ac6b4421c437025819c80529e9d2d63f8a3c10cb9dced196e572506", - "0x951934cad4c2772aa0ffdfc4f12a55f490824e104f669e4dffc70d9c14239570c87eb998dbb2a6d423bdfe1ab50f4377", - "0xa276bddb27d86e1e70ebb96103a239ae4848ad20c4c5b7de85f480c3f293c934ebe35792361d9767de4333ac6de11643", - "0xb9c8eccc03d7270779a87dd7c52a42c7bd632b9bdf94274b1dc864bc7a59e13eb30870ab740066040aff0beeefe14d2a", - "0x8e0908e4d15aaa582dc028e015c4b2bd97c82b8086737cdd1f2820641e65d88166d1fc763bc483f8fb4643339182473a", - "0x810c6c46945ad5b4f699c51130bf204e47c62066fbe54fd099c3567ca79aa8aa8b04dc5321c09e03df4bb7c9b93857ad", - "0x916d4b23adf202ccfaea7dd124d28573c73b39ebd74bf4dfe32a366f9dd48f4160b8cb0e687e7dca887c4b4f19570cb8", - "0xb1b8fff52dbbd5b9bc6915ba20f3185fa8e23fe52c026a41cdedea5301dfcf6c79c4fe1058f3abf280a00c7b2cbb20a0", - "0x95f9623510e12ddc6f4ae59d06448f496cc911c99a4d5f5c6ff7e434b807fcd4b35ec1ec976a40208ee1a505a892e38d", - "0xac7217596d42d40380fddef22e83db9e6d6b2d0d2e912f868d7fc07bacfb83e8e6f01af544e8f450d31db014fb094c9a", - "0xb10855b8ff1a81ac32d81773ce8a6391169902290af0637038b58ab59fc84e3403d515ba7c99e26b7382c2e2d0edcedc", - "0x89eebe9789a333f5db0aa9e8604798b15a934ff45e19699c2e7fdb46b6863ce02defcef9f6dbd0cb799ffe2b669428c8", - "0xb9cc540b405c5ec78a2d8fc17ee4a08690e347cc1d860885205bc19cba09e62f25b94ffc2cab1f638c87caf217f7b6e3", - "0xb16d06b120906f085cb183a96a2b635334afda4272ac650259f23059407fdcc8b83e91f2521223f79769ba45428c04bb", - "0x83e0a2d9d9f6654d916a822ab1725d58a10efd64e889a17f44860db4d2c77ec1bdde7d0ec8deabc12f8ffa5af879d4e5", - "0x98cef31d7ee167d9c4248e29402ea8d5546288d1b7ca54a5370e80a9ce371bc4aa3f5c7a24c2e4805d8c99af059b4156", - "0x8fd55a0dc38b65c2b0b45c9127c14b9396db4898f14e1559e428a2951cb5076bff9e3f202a83236f15c1d2530539e5ad", - "0xb3252594c3060118acb12eb91d002a74c068c0b8f9bd735a9ecb082f787c7e046dd6e40ddf4b3ba56bf89f223bb5d76b", - "0xa88446262600f605fc4f067dca855ebc56990a9ea050c708961e486fe685707d9e9ca734068b92778a144c0f3c23b4bf", - "0x97beed96ba821515996045a40f17ad46f8f4d927cd9a2c7ce134a60d19ec4a5819a19aab1bb0df886d9cafcff872bcea", - "0x98ce98dc7908161ceefa0ac132b63c860ec2e53f7ba28e66c6c5e45c5945e459797c65668e58c0a5b8a26811f17c3f41", - "0xb0419cef96d4d44fff0338132d53d2c03e7e9b4618dc2c6b9f4475368e21700fc08b844a2f140158fff81f56aef83b7e", - "0xae1eba4a4a715f6d077e90e9efb59852b7025adced47fd9f705c2745e6734f2fd2f2f86f07ce24695a06e24e63f00b03", - "0x86db2fd15dd3cef1e504fb057136f0405758f6fcadc391e6f64b3080f92bfbd4537a0d8f59cd1a0e913b2b188093feb6", - "0xb418cff26800f8793b083a879c8b1823285f7a3cac6fa34cf48ac5355f04f6ba74255eaf436739c4d26d0d80d2607129", - "0x8eda3c25b5699569c03b85bc585acf25bc3f9539e9dc3e8707b34520ae5ac53920f45528f0870d93f84647cae36b6aeb", - "0xa2622af11642fb6cd60cddcd4c242cf13045f4ce20539d11727e8942b4f9a7fd1ea2192e83596a35c096fec3658c0c2a", - "0x80735f92d09dc0af19f593ea118bf52146143c1d2a7343f6e2ab95e00debfbd329d4e887f7421e4a361d815dc1a27973", - "0xa7eff30a31db635e239c8632f7f84263c9a9d82511422f49077823aeb124e6ee3c995ceb846902fcd2cff0f5f219db51", - "0x99129aedaac32b3ec18d689a2589e35fc9715fb3f1a72d28a09ad95e39a68ea939ec5721c501a9e35c60cecb3f4379df", - "0xb9995d65636ce1e70967a8ffdf45e50eb264eb64f15ee887781455c5472459cbb309ab58b1645bd6e8f2bd29e69d81b0", - "0xb8049f4c3ddc22405880bf55b5d5d94a6dbb071485f25a49a6457db0446663f8d4fabcf14106b9cabb1b3222d8786773", - "0xb581027c7d9bf7b97f6eb085934b9caa43a46368cc6740139e33e4cb2c94683411710a52d5933a27c9d12a43e75163ae", - "0xb5dfce672e670158c259f36fa549aaacb0699da2f13702c81f5a93afb00361f9ca22d02dcebeaceaee6813a3c9bf7aa5", - "0xb8184f3eb809be1986530dffd7464d84750df02196274955769a0afa02b65e87686d915ecdc7e75a0a76be8b7ad8d064", - "0xb7ab837f300f4aa2ebd2d770f7a36dedaaa68e1d601eb36a28fada4dc73dbd55e7f31c88ab2835aeb57ff113a14c5f32", - "0xa72013c811ca674c3e909064777df1484190fffb0643b6b1435892f5dd0f1d09579189fe00c862bcd18d03309b958b72", - "0x87fb528e03f1b6a000141f4a6ee24a9738d9d2efa795cc262203fec10d76adcd0f89968a46fdebac99af8d048300b8ee", - "0xb2a1ca5d5d16c7addb73341ebed1f8e832250c2f8e03915a417064750d7deec3289e646c06a09c6a3ae40ea2817636a4", - "0xa90cba4d0928da2a5d8c6935790e1a1f026073632a4c1460fe686d06c3f2933661c2b3c49bb0bbeef386f2bcc4d08485", - "0xa5b684d544500be25136b0b5b95d9f363103a6d08cf49f4934d6c96d43720a79cdffe66698de0ffe5b02bb3c2e30286f", - "0xb246952dcdc38a500e64ccf4f312bc7c690d33a3a951fde5f839f6eec77ac78147f1fcf26ff7b990e8868f5cefe1c4eb", - "0x981ed33458e8ead67d4adeb884153bb0fee0ad98ebd9010ee706ea1da7975c290f82c492cf16fb42d1b739632e66e50e", - "0x88bdec223786c894fbd8f964ab2c92c5ad7fa7ed2b97a6bf31423a6ad5bbb5a946ae3cebccce8cc97af9e788d03f547b", - "0xae852b074e5716e3190593e11fb17f1135d7a5d888986d2be53973fa14c1d4a9887381e648a10a4725291ff062c9d88b", - "0xb87050f914c4f09e2dfef845ace5a06504b6fdb815f685921710c7e82a9fac11f864e3e6023ed5807256d6269271d051", - "0x8cbd11617ab819680cfa68e70e205f3ffecf6e469d88dbdb1d9b0c9c7c38746dd6e64bd526306a8ab59cb7e66841a757", - "0xa1c51cbc1a91618b1ede5cdd77fce26b04971081e5cbf83be20c22b9b30cc9197b9bfd5998fd9ade9b665c8218afe94c", - "0xb5cdb2091d114847dc14a4c922bfe944021549df2d75cfc08ccacc2d740726e90e20a0bc2bb73303e9f0bbb5192fb982", - "0x8e60327955c5de97f56838cdebd24c2ed4021d9e3d74ab9eefd4543a286c1be82a1e8455f8cfc0a17f03358c4648683b", - "0x87f9c1c0987493c631279112fbc79c5f5d7dbf46544119492785f444d063fcb0da4f2d1129735ab77663a9000d9e18ee", - "0xa970df3d50c4ef3d76d53dd2b887e9274fdedced7a83560eb1950fed2075879d9fe1d5af811f04ec92d557a0be0380f7", - "0x95a69bf4092567f5b55a401329d5a08220ae65825f05d56043974fb7b7090372e941a85e2d197c46c9165031b3bd36fd", - "0x8e62c98171e54ff549ccac5d6d381291d0861439dd24e584d356a862d22942e0ff17cdc0d1faab07e496374a547ee812", - "0xab62d0eed8422a3172269de0e325eae9294914fa67f1ed8e5d0609afa2991a26b1e1b9a04ccda8436d04ec085957b110", - "0xa3292bc88e2a9dec7b55ae4c27a3a8ea46a7b2dfe3a817675eb3712f95264c08668703771b65afcdf6d305e396d5f005", - "0xafbaf9cc19adf63a0716cb868a970a372d7a1e24a4c78718a114ced412a12fda6fdf42f701ca1492a8f8c1ef0466f7a3", - "0xb41a5f064f9d900d1534a68c74796927e4018e23f949d86eb76dd5b26e5b686115d63d858a49b545924b3941bcec2341", - "0xb4e1ef520119f9a238fc4988ab2f1266606f53079744b92c1039541aee78b67ac570d7839fc9b2331244d734ad4637ed", - "0xb0ce754a33a506174d5feaff4e9a79295c743b2a122c8a1788c1427482585b398a750b7bd93cc53c38bd3e557caed172", - "0x9842cd13ee9490d9ca7ddc83d1f7d79495afb7301d1f51f4b007dd2b2eaf15abbff18666126adc25df5ae26b98a80f41", - "0xa976af142268d20a248c4b71304a878efec29b5022199cfc88bf82c081f55d06a89f178606d50bd3f8576f0c5c01a6ad", - "0x985ac6f315ab1d2db1b4f2b107eb1652810e63e36b8c14e8852f072d2c8b14922f20d1374a57d75cec62db0d050a0c7c", - "0x8c1be9e8317fdf847a8131ac14cedda922bbfbe15cf95537493c4e7eccc7f2f1a56ddd1a8832e6300734d6019d8b128b", - "0xb55d129c88d252556fe688f84982becce253736ef3b1fb88328e41300ed0713465c8bd15918386844c725fe7a94e8364", - "0xa96384d2d81cf6a79614c7fd6bb68fec6e74064435a1a79dd8b1533e9c7e578da5ecf03e979969d983da893f42adcd84", - "0x8c2b3c06b7249ef5ecedeb4f2c65c0925cda8877bb4b672afb7a15bb5a7b5818748d6b022c6ab8fe9c5a1499e2037c69", - "0x91c8b2b8b204897741124a37f85ddc45c3ef94ceb5dff681b13771e712f2ba5ac95cb1bd2d3e94a84625d384b51b099b", - "0x8bf852945910e9a773120c5ad975f080c07c8fa37c2158e1138162a82983211da70f27e22876741d58c20a6c9dd770da", - "0xb9e907d9176a0fcba87a2797651765c814df756bbd1d0a86a9b7b06d9d886d1908d4e74ab27d618129dcde81e7d969d1", - "0xac4d3b156db2570c349e21f07fd17df935872f9687842035b533c6e4773ad5752f4ba8f9ea4501953f6b8c4232a4562d", - "0xad91c4a7ea0a314d7d1ed7a69a74adf6ad810586c1bf907ae9878ee5f6528437c048c6ae785cc255707ea3e58a4b452b", - "0x8013b76604bda0c429e37006b01750999414100d0ff59ff5ab7b233399adaacb34906ee65054abb94db80fc92ac6d2e8", - "0xb26a2a660af34a4b9b8910463d0dd439a3dc563494f5ec280dd5eec0b14b0e9426a0422f3c75370201299d394c4d90ad", - "0x8e1c7ea11dd513fb8527fa99b899444bf89a1188089d3bb65e3eb87025de9a48e8b4a3068a955fe752f2416de282ca20", - "0xb6cbdbf2b143330db09841aa0e7d22d32772ee62006e7cee13d8c4ac911ff4a59a9dba3d84bc46ace1760353d847bbd3", - "0xb8f5aa3ee213a44c41f63c11f685e754997cac37b27e91d07bcb69947344d94f3b86284b3b1655e168befc01c880d550", - "0x89f93b37bda703494263b10768118ce998ac1f395d422c0ae840e47c6d649a3ec59b404c164a1ad5ed14ccc2408fc662", - "0x97255607a1aaae89530a3bdbb7f2b7ba3fb9d5dc93509991021152dde08a638bb3152503cf0c896c9c19d61f8eea36d7", - "0x909c7ecafb798e6aa45867976f59cdc9d219aca6fd0881f82f296a83a2a3cc5ed47f08794e6e3009f8847f16345f5f4b", - "0x9560fbc2c531571eee5b7389855117644f156ddb00b23a7c2189205d4cc613ec83952b96e941cc1e725c2b574c46ee9c", - "0xaaa69f68b6086bd369fd92355f3a0bc632c1b1b4284529c18a7cd4d71d827291bc997ce74bc92dcd6900419be68efb37", - "0xaf9ab7e6a27e61a99f37b89fc816974ff916b6a24ec3aa31d76579204bdd5ff01a2eea26e76188976c033db4af167db5", - "0xb026dc8850af970d2ffd300dce6ae07db0ca2d21978e4f3a6797b6e3e81f1d9680465080a983c31d473a77ffb62acb5c", - "0x8f82f92ca992ac352ed1e8fe31d24f8090ce6a7f02d6086720422b9bab20f3e3c38a5f63c7fdb193e30d63f08e53c900", - "0x8b896a2ae84c66109c8501cf6070c4da65c43ca8ef9b6b06fc85b6cd92bf2e5397d492796c528c7b2cf29ba93341a87b", - "0x961bf4c0b8068c8406a864595e156004d427138e06b390519cef53af8eb00c748bdfdd480521c6aa0d53a78e8f806217", - "0xa6fa456250d20c6842dde55d3884eaecfe8a39f546cc5e4a77f57907192e849a956a33a81369b0f2633c55bd6608eb63", - "0xb1d1d2f3e3e058ee97c9b6246cf073236438ed5e782bb21c68cd0d77b44f29745dc24d01edbce4437d93071b6fa6e0a4", - "0x81a0bec80ecd1b1e72256ed5be7de8deb11046ead7a96e1d150573f4d896e642b4af095735343f6831bb6b7f4037cfca", - "0xb48d8e15fa8e0b46937637de3c727157f8073eb8a9a04bf127e68977758385a791da2e9c69fedb89b334fc638ece78d3", - "0xafdee0774369653bf371b8820e285e1b48b40745a44d22cf2098b630b8ac95796a74f79337cb97fc60b6d6b903a61321", - "0x8fcd9ff2991902149db29cd4674d60387d4f65397891fbf91b7699a42f579f6b0afdaccec70e5e82d1abd81de859183a", - "0x8af5c73367a8439b2e3e5f1b65e00ebef2eda640bfba2eae48582cdfb244e1b1cc540bc0ef72f9e24399affce1c3e222", - "0xb58cad4da101363bb8d6e8cd0ec7c078f7719462856d7ea573e2bf95e00cc23020031901bd1f2112ffb90d847241e5a1", - "0xa671f7fe2ad81e9e0d5e3260a9dd7808125dcebd970877b000bdaa3207ca45ae1e5458d5ab7bd69b2adfca8b6abd88d0", - "0xa8411cde9eefe73fbceec3e5e3628b159ca4e4c19385ab50b8d7a482f4258f405c47051a89f11dbedb2b15e84d8bfcc9", - "0xb5dd09d5ebb26e341b6df80e836c6de2305ce4941238e3e96da549857ec314b1658f8b03ef069633625b6e4bc13b531c", - "0x81bc9bc924039fcca8892b40aa9fe8f5d6f305343f6054e36647d5f14cad3e4d754dd6ce9ded67ae65825adb4e16df31", - "0x935ec74c2dba94b1c5ef2060c31bb5c1426965f68d9f4125cdd891f20495da9d5dca513f65bf3e8c599f1562e81a0c1b", - "0xb9581e11f361097620130e753d134cce6d40ddc7c516388fe4c881fceadf738f314d241dc14d4f87be8ff0481e898c4b", - "0xb7be50ea49e09d10cbcf21b6f717e0cdca582d57935d72d17e62cdd7bf2071e5d5c91ad7bea79476537e515f0d2fa5af", - "0xab467b7fd32a795411e991417be57af8b62ca199983efc1f744799136ae5339173111465e91083dbce60e77f9f2c0fc6", - "0xb99afb338f747ae89e7cebf069612e22f9704f247d66548d305aacdfae395609a57d4d5405ff0f1eb1045dca4c3827ce", - "0x99a5e52374e1c55f65e44951f68cc3d607157e60d52cd088125a81bc60f2009d1b894eff8e1efb175509aa4b57af7276", - "0x87e3323cf6f11b595ed745a9475a6d99d11333043d512bb61d5f9d8c3f0cb6957aa8c3f041688f63ac13a51df29fa061", - "0x96a5f9ed28056138439eedba186b754f5f7693c09422f42ef82a315b7413b418c4971112f4261e1b9793ec9066c3641c", - "0xb9b5fd36d2d861d40b947c3c879a42fff24b9ee346163e544ce6c3301d0003cdb47218644fd5f1f7f0d6f19bf647ceed", - "0xa8899296b58e5d56d7da438ea48bd76310364ffe666d698c86f20683343663d742a0b3f8c1255e33f1d424cbf61bf1e6", - "0xac4be82ca78df2a367f13c8bd1cb73a28015853f2745e025626c325a10b778cf4bd9942439e35015cb38504bc02993c8", - "0xae5d6b99ef56cebd5e25a9c002e9e80c1d3e8e5fb5dcefc8ea7b7798c7e09b02147da2ba14e42e2b6db2b2a6a738f598", - "0x8c94abefc71d245b0bf04f34085da0a9b8d4d798ee7441596c5166ac353425175dfcab0f76bdabab8f0ef5a2b453255d", - "0x960ab6939b1185806e9f985c9381206c7032ea8a7a99eae5a66f276ad5cf450e654a6f1e956a2a63f33d6f715064d051", - "0xa4c7c7d0fce514db07bae5582f5e4f7a05d79f7605b33fe2a1ae980bc388b31c056438616bc8391ddc7dd5f98810c74e", - "0xad5df00f96ee6e9e1ee65b562d6311c38bc2a0a25aa9ee36f39766a2a03141e95285dd2850a598385f45b9935d63b78c", - "0xb051de656e37ccdf3844a6e095d3b42ea9c5a545e0dc2a5234e2016570375bff6b55ee0dff04ece5713ba8e85629a7da", - "0xac01fad1ac299567a22da6949a011f429bd9775de956dcdc247d5c186ec577fbc12a482ebff3a4ab18a8e35f3e2218c2", - "0x9654db9c6b5e58e0b68fc49718773d44129a0e77bfeee3fb56d27c282de6b75fe9c10f4f3b5d3374443a9fad45c400ce", - "0xa556631390e6cecc2ebe390e605e6fd754f1961e4bbc063c31c08812e0993eff5b5b7449b9732bfd3a22c87f9c528743", - "0xb41b7abb971e253dfec3aaec4443e875d73373c70c33e9ea19c1176f8cf1278c7716a76a4eeb641c142b2c6c1ace5db7", - "0x8bf37cbe29245c5e217a48140d7f0374f46596f2e82c1144ceb41c9801211869b96d7f1d0f7345233abcfead0309cc3e", - "0xa380a799b80f1309ba326f26ee46ba3081b12b5a1143f8289b2fa067aa3ba80c3690fcefded8534a80368799b71ee9c1", - "0x93dce0a2aee4d67efec1b284142d890d1e0d7abdbbfac82f90dcbaea94eef829645675cf17050af7b2e504a46d1bd288", - "0xb8e90f54bc57ff52b84fa3fc3c3047f379c5587ca18d9988c613a3bfe614fd5fc381106729bd62eda298faaf17b10210", - "0x8d8e4f508c284c52a6f907ec39950235c9443c5c6046762911f4818b98293d7d60a2c3f94c5cf60ccfeaeb8f283d8ce1", - "0xa513b66299ba5104ba633cd68121b9ec848e0c8c5252d04a0bdbab5e3bfe6ceac93ebb1ee6f0274920d84eae27df1520", - "0x80e2db8b919dd2ca33e833270738b1f437ae312b1c53a73106b6d12672a395fc3b941292fbb019d40e31b8e96bcb85c5", - "0xa4c28fba416985d47c947b0669cc22153ce887ec54535a18cf457622d03120b6aca71a45fd8704166f6f7a9ea2e9d608", - "0x850b05b9c7e168a83b0e0e77d16181a52d78aa96f4026c4420824cbd44dea9f27f3336b1736bd545bfdf548eb3f4276c", - "0x8efabbd63f3b9ae6111dceb1cffe45dd23f1500f87382816d4192161a77dd0776da2a4463d32da85b802ba7299fa726b", - "0x9426e75c6f7fb77072773a2ee03e1e3f1d90878fdb5d8c294265262f5c1cdd74a7aca339b46af8a5c43823dac7e57edd", - "0xa1c4d2ed335a3c92d867c5cb999b2b807dfb1d45e35b3960dfab19da43e2d1ca9a8748738380cefd137088d8b80d3006", - "0x987a7e22092931f39f05f5a6b38f419750370a71157d4443510b61fe07ac5aa31cd7f88ea04121947b1c0d0419d2a25f", - "0xae73cbce7cda7cd90404302388d41b49ed7d7f505a9a406f0317fccb29e32a5be61a6eb0951657f2d93abbb497be62ad", - "0xa1c7cb4056984c22a57ce76272428a50fd33f0f7a68c29c9438af05a87bec23d8de72062fb4829adafe597a278de0c01", - "0xb72c81a9a747a83a650b58ee01015a8882789983b67ac4f2fbedbbf47dbe30f04f686877d8f118b4634289866aecf9da", - "0x91ba1797d6913270ac1cb9c87d9d8440a651e294c45b2301ff8c40416e58126318f0f2d411b7d9c09c8e19f4da8ca0ef", - "0x864107657717124339cb2ec06cdfa75fb9c4a7ad5155cbdd03d155a7f9e9026e237d7cf5f4cbf07239e7bfbd79900957", - "0x87af853a334b8cdd10bf5f78753b27a0c9aac9f55db7570e2d9d42f13d0e2f8bfc4ca64b77b21e478f23385f17eb4f6d", - "0x8658227bb8733d6c7608d66a748caba761f28da4d95e70506dcfdc18300a559b4a84d11a9a048e82b292eb1b5d88bbf9", - "0xb078413570ead3243b9666c109a17678fe60dd1240caf01d1d344de09e346015cba7a40560b0d68b18df82a0a37ca529", - "0xaf6dd12875a891eea9d846aa660a207a527d08f5959976f6cb7585a98b1133f341f4ae29157f6ea8e0500fb6b49fb9c1", - "0xabc0fb42239fa531cf09f7288fb00f1d1587f2a86503593d481bb19b1159a6a9d6f4794565fe923a545d45b058d3a74b", - "0xb95966d42c59bb12029aef1da7fd50e9e8aa9ea287649ec3ba44247b185b485260af077e0d755f322ee4ecf8e2c8137b", - "0x8b1a2350f9bb0d6de377c00f0897081bfbaac5d47cac852a22dd8a427fd2e3029a1708f452e958a07236c7f35ddeb565", - "0xacaff21e9740b831fee42d80a9a80cffa6673e39f85b815b4f546f538dcd803320f90f4f25436796721c8a11f5a1b25e", - "0xa0dd42f019eedba19f4345553965508aa9d2eb1499a363056d95e27f7083c2343e74a0e7dfb101567250148ee1bec1d7", - "0xa08d1b1863e594bfcfa2e21ef4edee8535c8ee69490a4113787899ad8cf2f2ebbdea54de193ded85af82fde074ccd0fc", - "0x960912b621ff08e27781a4f9b80ef1014a4064fa3c96f534b67e5a094a5c11d9cadb2b69cd2011cdddb463f2936c7ff5", - "0xb3437f1e0872f6b9ec071a951f26120f27425789e00c1a8d3183879ed02e3b017406c051f32580b78b4d0f090474b42a", - "0xa90e6d1b11ebd1f1dec54d7b3fb336b9a53c821f295a592e147d5fd453d66e63295a96ce827c4ad64c37d4bc0df2c7e7", - "0xb357a785f3dc1f9bc1034da77033c0c64b29b78c7381ca59ef81e24ab14448d67dbf84756ea233b9e3539b5ed517d9c3", - "0x9360adb42210abb9d7644bb95532e1f461464446e94cb5047bf8ed5513398414130630866b6980b6afec5401e608f6f5", - "0x9145a7f8b2cf1bdd90b9a860051eacdb937189e8d68793e52bed202fa1e23a87db9c51a18f0bc050dfc3c600780099c3", - "0xae086e289e16608f02281bbde5a6fb2479e3151a2464b86ea737f8a43e15af4fe781312d0e5620a42a096cfbec885b0a", - "0x92b57fb14a0c567a16567f83e72b03b8b564ff6d830a5776014167cea06205579dd10715071097710dbf50b660b9143b", - "0x83e6a3f027163e635c2a1a397d2661a2d6c72c25082df129572082db29b1587c78dc3d2e5999112983a040ca46bc983c", - "0xb1667d022c8099dac5af4ce3b4ed6f524819240275725c7580a2386f067fdc9b3a49b74195cc6f661212fb07ff133463", - "0xaa2eb0c44df0a80047eec28a80440ed5f363e3d42908506bf8418bf04e9c17a5e9f550bec9c8ab8dc9979736ce325780", - "0xa2c1d257de1a55e4c10879eadd49af8950b0cf25121e8d7de30049360470aeecfbef263739262bf1f97020c6b025f9cd", - "0xaf29d1afc9f76417e4396c54300773fd283f1bc2cb00308da5e6b1deac7a48cb117c0e8c87b03076c7a1b8414d25dc97", - "0xa44d4f2186f5d728fdb224f10b496c9b57d96204325c452842423cbd29bbb2d07e98013a3880c7dfd63ede725d15953a", - "0xa30c45d1cdc68a5d5ab65b57d60c8b386be836c5bfda7e2f0347229b7807f6a97b632bf54ba3711066bcbd5e0831e5bb", - "0xa8c3c93d6a3526270ae47bc2628da82bbdb8b2c8e4d6a4cb5e9cf70b49999a963f3e856ff9db12cfd2575187bec668c7", - "0xa03566f1a99f5b82e8243678d0bb033441cb8a2f160c0c66dcebd0b6922a56f895a69b94a9c65f4adc9ed73420fd30dd", - "0xa4e3c839a6f4f4317e7bd06f25c5236e42fb0e54bb975f18f0240bdc214780049f0258dae24fba6301aad508ef9abf69", - "0xb7e0349d89616156679d06d1626f45dbc9683ad73ed91f0d92f8f82cb0ea2ae8d3ba3a752e73a39da70569d41e84015e", - "0x8c9ec5ff6be4b0d9337c5336b467c6d4f552af691bf083a23f1f9856e18b5a13852143dabf03869009febc443b2edbef", - "0xa12ff782575aca7b48844f0402a311bcb3e19514dd4d2ba5b39694c66846b22dc9ba25ea39c3c1bc325eda3afa1f00b1", - "0xb55bb586ebf5c9a3c83a04bae254e22547f37b9090151d96f5d8aa81be17bb38d2763a08cf0519a91878633ced6ce0f4", - "0xb3957203932032fe180ba9cb5347c2c1865a3094d03f6611148af4094fa6a8eae522f2651780d9bc49b41f5c36054eab", - "0xa0c865b498e30180c48fcab93342a50ca1cddd8759d6e0bb54e9f92e7b60c51c373f7ab1432aeb5e5c2d3ffcd79e8180", - "0x9503ffb3529c3415c07247211c2a4f35d8ecef98ce9f921e67438ffd538caa54520fc6d248a081f46221a0f1165011bb", - "0x906deaabf6e8dd0c24a4b22757b7681bf88268d9b4ff97f2844f9de825af511155d0bbc48dc4c03b87007be94f835d92", - "0x96c2a7f48990ecffccbefe128a28cd5b26c664b8dc9bbae16d857f7efc1b7711c734ba7d1476945d09ace569297ea96b", - "0xa37ea083b0a61f400b498ac5ba2360c22e40b688428ff4a02e3cc80206b61061bde037cd52d97eeca175394dc675e216", - "0x89b15c3af439769829ca930fa83c47afe070f6e2d7a7df88e5a4f3a2c0630f9d143bb3cc43ebf9bbc1b91be03d35ffda", - "0x8eca6996ba407886d3b9d2e4b1aae1983023dbb1c9ae47b6637458c73ffb7f422b0a893eb0b07fea2c5172ba335595b4", - "0x81df4d7f576930b2865af5ee1525718a09b65d9a013feafd19cad335e4e425485531807078b9564c8db3bad95d23bb0f", - "0xb6635aa3ca31c851a0283c0c6356235a5d8de9d1db9780e62087be32089c1c081bdc642f067224e88c14252efb960e3d", - "0xa0120e81025ba07848ef24ca9a94699db5274a8c85eb9c2f3b41a81f630d09d100127154ddc3270525961613a41ed81e", - "0xaaa8dd063f9f4f73f5a7c440671e1375ca8c224f8f869af736edcc435329487902249c68ef646fbf71c33a8bd1a04d9d", - "0xa36bfb14bbf3956c317e01fe744bd9c6c6f526a3881f6800592501ca1d9caba7f81b3b54f53b2ee1b13aa6de42ba06ec", - "0x819cd123fd793c0c9aba75aa96293268a4731c68c0a26a52561a695fc4acc409752de84ebd19494bae70849ce538138a", - "0xad4e50ce325477621b6eb4d453b087c3d7df6e3d019ab41239f2ad0615c6030aeaf85e0e020f3e6c89e46b8586b4a347", - "0xa4327072fbcf33be1e57ee4bd5db4c079c5ec11694a25fa2fb30932f8a2a35a63183b24d3ded7f6c8a8d0ad111586dbf", - "0x9454f17aa8fbdd2b15dfa6600ad305936a37b205eb554c915adc43aceb4dff6b0d1414e61584d5b15265f2ec0c85abea", - "0x80eed3725282c83dde575620bc0d86e50412df5dac3b3556d1e3bd9e7ef6f56dab202f4dfe4ce542babd49c1fa7dea5a", - "0xb90d1a07ff760daa23b7408b067c322f126023389beb7bf373f0c68b85ba0ea8a2c03e77e6d3339a01ed3ff8ba51f1f6", - "0x92789ad894995ba07f36a0814fc3289810136f9dbc6c70c57ea80db464772d760b57d5b059d4ed458f256af7603fa2c3", - "0x96a4ae1ca46d3b26029767e02fcf2f623d32c952712badf2a2af721226473f4875c40d5b14e66bf961a5a56aaced3aeb", - "0x8c5073f4846df9a0e057f52fdefe01a9b8c9ace91ef5ac253e823e165ae698e733eb936ad9cb04d2c54cd8570f328c4e", - "0xa9f36450b5ca66a20e52bc196620852a41f1f40262a2e12c278818b6071e6972c3cc6fdf83a9ccf586db6cc177173cae", - "0x8f101df23aa7e353ac1034c38adab8f20b8753aacabd10d70acb41d0fd0b1f34277546b30f64d0a861f448f112e38acf", - "0xb45b0779ef1ffbfa86d7e02e89bba0316c5ce60742b350296eff0d04246f1c8b1bf5bff68bc97792c85f1e5d4dcabacf", - "0xb7e89d015f6c7122a2f35f1c48b43eb0076ac4269158d52e38bf2a11de11cf2928175f717ee5c1bf543ea38945658558", - "0xade2a57ebd7600929dcdacc290168443437bc288371ef40580df515012350f3453b09aad8ae9e64bbc3fe6a3456f2c31", - "0x91c2f8de02bd8dfed1eeebc40a422d444e3459f9c33476b55de3e950d2c38d8463c4edf4d4f95347b0599a48cb2d47e5", - "0x8f6e77d9ceec539e0407a8d75d4e855e376838c0f886b36615a9c7715bce56a8669586f6d7cef75812d84b8be91380bd", - "0x87637da91b051ad92081e682e289bb904c51d95ee1a6ae2b8956982093a7bb4f8a66d91874265dc32229f9db5bd51ba0", - "0x94691811eb74f2970a95e9a2d64435952145f1d0caa76040f9811c9ea1ed7327750d57d6e8dd63c6378f336421d11093", - "0x884cff4ebea1bb48c0d651bcf0a710ebccab9062c96364aa64aa1275e9364a4c261e40a4b9f7e1e135572681a5a7a965", - "0x93f21d4b6b53cdc1dd41cb1b80ff73c0f1620db41c35aeccc059128704e9d1d7da5fd3240e7d075a2503273e7525664c", - "0xb9afe0a9b64dc43fa78f607cdcfe337ac952fccfde41c2e88abe3a8dbb36a51b3445d724908e552ba74bf67ea2cab56d", - "0x910280ba145bcb6a99d89d1526f10632206d2ca9e1a8596e5d181dfa37e5f407e1264b9c71c39530caa59894c10b371b", - "0xa5f583c9fbed59f99cf5e21b9a734de6d5685b9c33931325dd4b581bcf5aa4764c2a250924e7b6f7931dc5278bd17152", - "0xa87267f2ad292572a1cfc89308c96aec0d12e5f0fc2b4135ff8df7cf83bb1e71d619906d415db5841bbbeb173868ca82", - "0x899d7ff8d7f8d0daf62ec8d28adbfe4e7856582a23e62dee175e3bb9461f38bf8e4f73dffe10654a046573896f6de690", - "0xa8f3601e6787e788d46a9d7592dd4bdd8ea8b5136e3c897d79ce560e9511f6236e67a85a35c59295428c1f9c019a0841", - "0xb180a16448f085227a6f3e363b0dbcab285bf419d438a13be2cac1ac9f97973ff6b8aee38294f70a8d72bb4ff474577f", - "0x869038341a2f68ba85f5b2de58d2d794584a3c00a76ad0dda5aec31d4e3ee433be20c197b40618f89f7c8f1692ea3cc9", - "0x8366f825dabdf4f7714c5d089443d0de315198e23fb93c3ed063c4b8fca0727b05665c04beca145dc4c02f333e300c18", - "0x93291da32b501cdfa3624b39f6e38ed982c75c1209cd85630cf83288204032c0a90f013f1dfb4dcedee7aaf0fd95566a", - "0x96c95a1e73016fecc3483fc94dfaceea376ac700fd4804b24e9eda7135048e521daf96f8f63d5a1439950a64296d8124", - "0x866429fba47fb691a4c39460031a7e614096abbca3073e9246babd23075e8e5f6051e424e47d860296ac8ac646f8a283", - "0xb817f3d9985cf9f9657fa800ebd36a9622566697ce68f91c509d9ad7df8146532e24ad85c07f399908f87d1206c7642c", - "0x8761c3755cf5440775fe00081f79dbf59829f8d400adf7448188b97f756ad35658295649294ac9626c2569ab21a5df86", - "0xaad65ace72ef89783507c9feb5555275d70a421a95f306b7613c894bc24e978be809410b519e9314ac56fdae0c71d326", - "0x8ed16ed07d0e989061db5087d50cebfcd6983fd54be5062e333bfb8f6f609bf1b7b840c91ffe4b66fd674eeae2dd1558", - "0xaf3919bbc0df42b1e2e8f62e931701f7c35cfefe3ac3f1985ddb70212476112e8a19d51c673da931777ffa28944306f2", - "0x99a364d8819b5ea0f6d900167b60063f40f9afcf291ded7adaa2d0e46f344751cb312df1c2113bad8d84a028f680b41b", - "0x8d970bad8f95ced0b0323f4b7b087efd0624ce21834b3c9ed435dc0a394cc2c7ce58f1741c1a64265c81654eeb6801ee", - "0xa5f96a4d794f6f844b38f9b82ee15c2441cce293b6b2ba26b25643165236db05ffa918ebbe20aa89ed2a8ffc8df393fa", - "0x8ca69e0006f6a72e5abcc32c3961aeeebb8c0a76d877fdd8a093467485c19662b75f2ad8c750acc9cc12c8fcbfbe9b0c", - "0xb5378b855f6ed3eec19546cc21c947dd12e98783164d95a95d3cac36c89a840bcb9f7c99b191fa7730ec28d57e7326dc", - "0x884e50d5e20bebca96dda539daeb0e15edaac7fc88bca254a7239f30aaec47a64f29b69fb2d90041b82f8ad8e3f13d3c", - "0xabcce1f6149037ac8d27497831acb867cd5e05f637b7579736ba5c384b8145f127c56b82b1876881b782b94a84d32d04", - "0x8747985d53fac369c4a23224d50bdc556c00f406e7ab3e38427aec317ae7c0feee5b48b9386c5764de883cf296ed1daa", - "0xa153c77887f271316d5a7185fe0d2bb7359cad86ba80b03434bee8f21b3a5e52263d28cb9d3d2e6d5b7443196e03cf80", - "0xa77b16b2b7b6e999144af6c919e0a74b9a6ff70de41a133f7f820befc1261bf261142717133dd4a99e168a5cca4791e5", - "0xb89beb83489db9fb62fa32d1a8ecb66fe9ed41d318820d13c3e07e1c97802dfd7b05d34652a478a1deb3b17b4243a499", - "0xa80200902da696d0d3974ab29676f0eb67d15166b173fd63b247a17cc49f56b6ffa28d9690841ed4865229248650601f", - "0x8210103eccfd1f4be55e33991a831c50260bbabc1f311564fc1c52c3b2755d3e4a11ad69cd95e398dffdb9a0f5b77df0", - "0x9958745d00d8f29d05d97875746d863007b1c05d3ae920794e6c65adb47ec208734fdaed1b49982c4f4cdd1d3043c369", - "0x94a4f28dc7a9d2dd01ebc2f3ed11a5bb01a2095e7c772d2753c022d991da7b2e4c80c2170209bcc4771d68ef8cf007c0", - "0xa6b5c5543ae3de57e074fac82221590a8d771e93e22fffc2029b44e8a1c2c8c9cb0362416de54d00fd5420e5b1375eb3", - "0x875e801265871509c71dce38005ad6423fd027206e6ab4c58d2978ab4812d5720401c1310b56ce9ecd95241a17ce0e7a", - "0xb6819bc6497ed57feb41bd82f56216b513085b6d1a560a958adcc06a6da304424ee34ab2580604b0e59f6b0091ffe6ad", - "0x93bef0806f21f8bac88a5d6e2e6d1adda06f9daad5cc3c8de61162495d8fcc3889b767a3e2f3380f162166ce40a0ce80", - "0xa1f699cd7446cdb1321a05f970bc70cc98593aaf0145a0d097e60e5897aa311b00d019e09cd533d0c0b7cc5c00a753e5", - "0x89ae140ad75a83db2903a93a3711be90986d08dcfe962aec5ea4ee69656026dce77821993c1defc4464442bfe7d44734", - "0xa4110c80ba92f545a1a7545cbeef997d6c0242fd4d771977192269d626b35c88c361df53bb36dfa8ea7e40da68e45f81", - "0x906786f38eb7e98c431fa2464048ac3f1f1df8f908a25262978327224bc82168f564b2f3e6da77f49457ce49c1a72c2b", - "0xb28d92b3228547f03a3f489e09070ad9a1e20a73e49f7ada96ce41c19cd6416ad809b3a3a01f141b3698e85c641d795d", - "0xa25b9df9b377baafc8c735a772e0ed9ac007c0b6ebac2cc0f8f2e799e5e6038a616968c9896cea862e99b1750224ffe7", - "0x8085eaabc79a2faf1ed0b9fdd017fba1e46c671c6d8ed78fb089494f792765b0617f790016d8f55697dd0f45d17de4b1", - "0xa0e81b557af74efb95cf94054264d30396121312c643052070ab53eac8e75075f1fd0b384cdf1d96bd39cc98681b2d92", - "0xb8e0ffc7548969ae28beaa9d8bd65872840a03150e2140dd799d9924249f92d962a0089171bf4b311520ab527198668f", - "0xa6188827a500b99af6eb91094a0e464e394c8c0a6d80cfcc5d8be89e8810732a03ca75b2befd00d07d1dfbe7dbe89be5", - "0xa4e5a47c656e74107e6007199b940d8381f706d5bb4226a0b2fb13eda725a556530b8d4876dc49c5f9631dc6bfcc4c9f", - "0x90330a50442db9a9c459e06d42cf7a69e009332976c3950ae7d9981d99066fd2af22f22ac429850b998f1ec929c82bfd", - "0x89dcc51fb717212b2dcbd0fa0da189e194b4ad5bf7f43ab2cc2c96f11c186d0872bd930aeaae01661ce2dd9f94eefce9", - "0xadee914ece15575cc34ab485f2dbdf3979406ce7cd8cd82197f156f373beee6d80e5e3623d79a2fef14b0b4ed1678a51", - "0x87e97e8866002364bbe9b49c5f2b5eb729c7018ec61dff7b8bcee1c1ea349e5e04a3f3781617d46d8fe0e62afe55d62b", - "0xb6b7bd0bc652a0bf79aeeea1767f0f17dd543b9de582531bb3e14ba2bfe1b720a6c6b613cfc295372eab9202f5e2d340", - "0xa6f9cd96d8e422d9897d50bf36288bf4c09d28cb0f5c4e13ef7f76cef6c75bb594d0ca954ff7339590cdece16414fdba", - "0xb9bc319dc5e55630d1ee8cb48978a256b69c96aaabb5269bed8c5366add03a2c38da11cb03a44e150a5c7f34bb49bcd5", - "0x868c36924f0056b3464bff8831543a280ced62be748d60f82ac860c32025c4589e9354984e1cedf24678374c959383a8", - "0xa6244602362c09b382926dabae5793ca4fc50600193c69e645fe229a471f7cf9e58c4a59124d6d2dabaecf50f1e1fd1d", - "0xb42df58ee9e20fce589837d5ed8a938eb83a00c6ffe2f6afc973f6ce26559b8d220976ea1fc18ffbafe739c92dda6618", - "0x90c0b2ed8ed7cd6f6ff812c84ed297b3231f6e2106f2df6d5e4b4bbf5378231025582cf39f35dc9344d9fad3adf04685", - "0xa968386bf1221425cee0d0b926689426fd77e8e8bca5ad3bd07298fbbeef4fc676e0cf7a4f29cf981c682a78a54a2d1e", - "0xa3a46bb7db36e0294b509036a40875850ea5ce4e8853cc0a7d85e8455fc2bd7d5b593879408ef2f3b2b2bfa44aca2276", - "0xaf825963207f046b23534896086a3e56247d752982417047f850bf306d0cce285b537508747afc700dff6472fe3b5569", - "0x8022af88981249b5da08ccc19e4ffbc35feb2cb5308b34064de4d5bfc8ff2b933363988c833ec70723e3b5107f8fbd67", - "0x89687fe6e424c7f0d2751e5f7838e9a3fca4c0bca043806fe511442bbf41cb67d01165ecb662b1ece1b2adede5a9537e", - "0x99c925763420fdac4149a02131831449c1df8be4867a6d2d09e6b14abb821d46bc1fc4fc9aacfa4e9de1a93f9b56fbcc", - "0xb819ee6a0724de9c944ce2ca51ffd3f1d93c77ff25e39de8be2a612abe732dddbf2219e839686a4373609a560041291f", - "0xb5eabf12513e91139025f1236c7ec235368eb8586522dce04d370acd3d854c1e6676d92014b60ea3e4e21e9d6f063f2a", - "0xb82e94f1013db6cc682032c7760aca2a1082826d280801aad9c6564704362e61a61cb52c6f35f769bd8ca191e68e0b0a", - "0x95dcb02a676b17f20b75632c7a9060f990e44b0c1fba84ec8e633554f875ebcf6e54caeb9816267e84a11808d68728af", - "0xb0c7c401dcc019d2108eab7e87d6494e06399f6eb4fd95b8ff9ba4a56e549a3d3a4aff13771229f4c456283fc3cbc53c", - "0xb1a8e3e500e3ed74bacf91a82b39f2b870963dec0b98b7d5ccefa3212fc9f3ef923101887572e14d08145aaafa8da5ba", - "0xb2caf72c47870ce9f0524c4b3df6ab3eb3695765c010a27c0f3cda0ee1c1f5bee64e5392ef8b3f0f11e66bd8c9d4630d", - "0xa8fb4864bce5f1c48d681eb37efe7d9ed1a83ed36bdc1f2627539b92c90e100d4dd64ab664e404b0eb7b645a8f95642e", - "0xa1b6164a4f0467444fd56a1f4668c8d1f295f6e6f5191355dcfd004c34153317202823d72162b621f677c970a3f0bfd0", - "0xb2cc59a2f6f3b7e18064720f93b28801fb684d98ee808ec5c04a5235dc40372aa0e0521410d8f736161470443bd97ed7", - "0xb5d9a823649c09151b214406189d75d7f1ca150cc7431d79b7d60348b6d7405014a44bb7840e35f9c0a634b4c6785561", - "0xaf6b8229fe035cbd6a5da3a3aad93e7ca5ed233dea5fe4477dce46ed17bac9243ebf25a8439ac2896c41baa671c0fdfc", - "0xb42d9023551d999d2be3ee51f6ca82c3b2d41fce51e1dab52095af6d4b59edcad70a1f9b1e71eddff894e3fe35a1f11c", - "0xb868543c09fa9b9b990b276ddc5b68a2415965d3de71b9ac538c26a6333543a7c33d0b432f57756ac0077d0021878944", - "0x846577a8c877461a58a94c5829f2ed9d7ed107fa63a48ee77a1ef1f1d1f940b2605fc742cb5ef849e3cbfc86942488fc", - "0x967ca22cc8c21382b15d73b4dd4f6f0a0bdb2056c21e3c75eb3d9c13dd41336672ceca03065d8cd1062389afa4726974", - "0x8e0b872d766c439f3f868f18ef0c173896eac883783dcc58917f76d5a2e8c291967a032d254450fa7f9a12fa7d7a4cf9", - "0xa0236eb36a4ce3b7d649ff02de9279d364ecd5059932328230314ecdce3278c42cb836f547bb9da9de0fc96cda2fbc7c", - "0x92eac5a5a88648e6d821d3bb51b280fc106f751d85a1742a6a1ceed071eaaa215a0a0238492ddbefbdcdf2e38e4149fc", - "0x88e1036f9b20a2c4b3534175c93d59c1ade3fa6652a4c5c490f21f6c3340769c7f8147d53a92fbfd84c23d7c4295cdd2", - "0x8b094165ad429a339f12696bc8967ca89ec47a4778f387e42e273a1863a38199dd795d120d198d3cbd93203604c6914c", - "0x8f8013229eb6bc6a8f93c17d3b4a1b206c258f14091c6dc39cb1ec492d403cdf5f696070ef5a6c0ab9ed4ec141b08d73", - "0x81c7ad27bd7a48b444b2be3d4b5d4845743d6ac4857b061e659d7ed48ebacdeac29cabd0cd163f3fe6c5cc28753148cc", - "0x91c8a92749183e3e6f3499d3b0e9b080109d5e88ce8acb03b35f3d04591e13b4c489ae323a149def1edaaf62f93bbbe4", - "0xa6a2d69f012d877460c33095924771065fdcdddc30670ea84576b72dd3f7769f90d1735f8914b6841c7d938a2046ff4d", - "0xa8ad4b976a5e4477a97d48a3cfcce16b358fd3dc1ed1df301fad6d6f0e188782c518796faf1465e52312b47bd713e2d4", - "0xafa2bab9363187473a85f7020106b176903bc3a3e3df1f4938feed5145b79b66db8aa608cdda554166ec47e60fb34b95", - "0xaf691bf473160cfb84ea517702f3c01daa6155f31393d807c897b39523448c5af09be581ad713c76aba194f90895cd9e", - "0xb74f3cbc198c9e4b2c7316fffd57fc749e367b7d1cf81b3f5311d266c9a3ab9598075ffb9230dceee230d5f1bbe3f796", - "0x8c28d21c49a15299f7ff3eff7568b8450e6404a168554b8965a291c03fdbbd3dae9ea6b9760869cb1f2e8c7206183195", - "0xa496a0df4e79827cf3bec117b92b5b248dfe129d783841935363362aee4822399974e6c03a92797b3ecde80b207fd7c0", - "0xb39fa07fc8f4be41588ff5560ed68a33c3020bceaf172fd11e0c1288ea885c6dcfb56a151e4773e57d864dce06fdbea0", - "0x990cd050ab056ea447c114217219d9c0c7526803f63952e22ae60a3996608bfa3c6119a56befc597592761e3a90ef448", - "0xb6f02dff3dc330daf82d1edbd4e6964d2e9c38481e74cde8d9d85a9e602ed22c4fe6c9b6f41ec76582f0a4e4414bf300", - "0x84440e4a7146ec2f34e8099e85c09b8d7bf505a15638aa34cd2b42a20f1f335cbc9f0e4fdaf2e53fa0ebb2dcb00519e7", - "0xaf389aed116fe58580810fc474eb15518dcd9746f04a7efd2de44c9774824db79f8ce4c4fa108e7396e1fc016132a402", - "0xb202985e01c62d0de1f6807fe600a3b81fd11f30f5aa033b1e7baf7a62f34fa5342d42ad6a6e309560e3e9ebc662920c", - "0x8a07641140db9701c676b2c094c24cd663a5a34d3534fd4f5f1e38ca0c46772d141679730b5d0cd71d056c257d9a125c", - "0x99dc01e76174370a741e8e9ef5654a3a7769a010da85de41dd315b674ba8786e6a697b74a79ea782a1fcf74a48e51775", - "0x93fc897841609670a1eb88d4e4498c54e286e25238309fc95389b16e4edfb82b8ee8447a436893c7180827a996b9a0f7", - "0x8e2dd561acc8954a53635c0108ff964774fe98d12b28a0c6ea8b5ec5ea3523a45b81ec642c1453e3b2a1c0e0749562be", - "0xa95b0b7f9e53720f4b0394bb6ae8222aa5be00a2050f59ccb595d50e0dd9100e397af9ea77b0335be02d1713c361357c", - "0x8e21dcb67da3eaff5b950f989939237e3735a31e346e1bec8e6ca11edff5223e33c1c6f2f79da975de2fd86dea286e1c", - "0xac02cadeba36143767bdb8cd4e1caf8cb287296b53955f33ed07f771a1fea521fd64b7e153c90d5e270c12ab959cfd24", - "0xaf95bca4016b2ddbca61c9c854cf999ed59ab4b5d619dd55460f20cde5ecc86081a2586a7eb37f15c20280dd06b65809", - "0xb7d7c81261e8c6a8983442e1e801f5072bbada1eb2e49b8e90759dcad653c52c0afdff9cbec41bf21cfe832e49ef8db8", - "0x97fe8c6d071dc80355bf2a74c15ecb16c59bc042eff323e999f4fdc39e1209803d32622c642ad25673c84761f0d357bf", - "0xb37da716119c00a0955a7fee59b93185a6e325bc5cb2a7fb35681fca0688d0ad2d25a0e40dfdbec1a11deadb1cc69d47", - "0xafb8091548179fd2a17d95ca47909d97866e4fe54099736e6414682ad083fce300e0a20dfe3a017c1ee4ee7d271bc470", - "0x9306ba1f3f2f74964dfcbcf9b87bafa44b5e013853c46cb501e10409f3c2af7269aa17c8cab261fe82e52a188ce0d18a", - "0x82430e3c25970411f40aa72ef1cda5b2b51bbc7e243a1b4951e92cb56a2f5b200a039f5554d0d1bb44330d89d1ef8840", - "0xaabfccb8f3dfbd4012b9d196448e83f17bd1ddb8c857dbf98e80ffc60c1af3493ac5c70e3a2f1f26352b1ead143dee87", - "0x832cd6dc83380d068c068d815ad0f4677de0ef602890835b8d32b73223490a6f753092d651968cb3d798cbf2a227960d", - "0x80e3e7f0c46fe5d962322f3fb2535de40dc078db80e7ef57923d46b742a8e4d6dd35ef74234f2b1637a317364d57abbf", - "0x9306bcc29d6f8a478ec085b144161850afa29d282cec756d0d3fcce6f4860f4a4b8c8a5952cce54ea893cf84abd6c4fb", - "0x9234c03bebfe6b47aedc7c5452058ca6a8def3c368bdbc9019ef121ad44171d6b31d9bda9c82300b5b396187324684ec", - "0xabc2ec6016ee252f5693558b694eeeddeabf4579b7e03d37504c26ecc29263e455ce8f0158fbfc54135600b72dc54315", - "0xb46fe7b51df64cf46888a810365f891d43db5b34ac4d3505f0692603adef04b1d08eadb3e31d039817e7b89bf0789802", - "0x988e0dd101bba7d7e4094cde99eeeb6d4411341e684fc06ae78d163d30c4b585375a868eda7ba1e5495ee7f0a7d509e1", - "0x94d3033ee1926aef656b31192653d3da96d5c533ac2436d68fcbaebf827475778689ecf14fc53042a523e4652fb9d713", - "0x993b598555bd2a35e9a03f99950d09f55a48ba63f9e0e65802ecb95602d045001f82f25c3bb60221adcb8ab4e2709ba1", - "0xa0acd921ea7db9870716acb595c65a934a5a06a07c6e54cd26efc86c97eadaae1522a4a26c8f93b7b7cbc4746ecfc21d", - "0x8dbd8f492764bee920e0224dbe39d650be6732b56976a5e1b636b2e7371c1509431175b66c6ca879ba8f915f9df8fa36", - "0xa01b24c1e3aa044cd2598032950755763345534f95f6f71d50565d25cbbbdf9c42e35253e35b683f6c3156f5c998ca4d", - "0xb895522dee1ec9c5289e6fec652093519cbbdca7a2936fd1df3ef956eb404f1a24272c9ae6ce58eceeceff36d76d34d5", - "0xb91cea120e200858457a64a60aa876f167b1b88c1dacd9988700b9f0f0d1bd1dfdd8dab56c2e7197a174b7b8bb8422e0", - "0x8406767e4f7cee2e12431b093ce82f633ffc76b451ac8414716fc74fbadff30c52a22869607d5de465d0f4df8a740343", - "0xa2cf431d18b2fa526291c7027d59b18cbd73a9b48d68cfd6e4b745d27774941af809edba06c8534b1864045d6fc1bc20", - "0xab3fe23aa8c45ab2efb2ca0c593c8644d3f47f748c2f753626289b0b9c761add755e3b52521ef37fd609429b2f8770ff", - "0xaf4530dfc5b3f37888900d9fd08554bef4e47c4c09a8c82bb48c4b9c6c9089465f98762d81ba4272b6861121b65f3c5d", - "0x80f61d086511b9b8b2033921336a68adde99cd25fac71d8f8fd0e476dd30cdfba49363784f0d0578c1f648f93ae23f8f", - "0x82ca682cc254952330d1be8c0e53da24aa943ffe0209b00bbf046e1e4f9425886a01d6582e2853137a9c256316e6f737", - "0xad1d508d2ea2806c351d5bd1098c46ae7ef83f4e49e4e87f83fa2c63f715ec56109996284a541c2005693687b4813623", - "0x9061817ee94bd2895064f4af04777b499a1fedd9688ed64bdba848202c3cf9286b699c92400ed456db926ee23a34f90a", - "0xa8bda55cf6f3f9edb78b43a52b7fe76e5cc2cde21e08487ea597cc266e54700ddcea1a287ec6d8f16b738b67caa27152", - "0xb605576e55d1fa4fd9d7fac2ce549dfe23fd6ade41fa859bf809baa3f1497d078cab06a257ccfd6cd59f67f17eb22f5f", - "0xa92d22ff5b5ec6dbb1d57db1b740521e82b4bef84dec3e130cab63d0641c3a8fec1f6f86141fb1918dc0f3fcfcbd8cb6", - "0xa0165df8dfd7b3cb58883768471cf485b886ece529d5bb78b26acf9ef6c44314cf9f34914233c93b10b1918533dcb8c7", - "0x88b79c9c721c1936fdbe22d68459d1033fdc986d3e52f39341ab06cc85a3f230ecf0965ee8d2dd54496981fd08a02657", - "0x939b77fcd53a523240bee730c2d7b8dae0b32bc3dbbd31428c7b5fdb4c3d34afe7f2a377b2918497574606bc06cac750", - "0xabbf82d0156439761b36a913b661e3d452dfa57e443ddb61613f80e110acf52765139fe3d1dd59c9e7773b262140cb90", - "0xaba28324844cd19b2d5d07a87e6f3180a3c02c7326bca846c1e7a7c131c7ddbefeabbd6787b4e1e910449f3cd1249ed6", - "0xab2f71af8596c10351f7ce9c3a9bec08a5c7837cee92a7400826284752c98531a0199e2a7f9ba7ccccc8fa0a2207aa43", - "0xa71d5a4f8af3a16ec9c3c110ca2135c68103109d4384a299cb7ed09d96231c90b04ce34ce12de02a40924d84947f7f31", - "0xb9dd79bf3286ea08c9b779910c84fdd02a33dbff7adc2d6612cd58e81aaff3f64ba021f875ea9e1201243ce353510350", - "0x9838fce2f70e7c47dca7239883229c1573ea97d469f120e4af659b18bca31cb68d12220fbd6e4e9e952b28eb29c1e5ee", - "0x8dd341e67e4c567a4ea95252854cfff8a7631c228ac852b33b2ea9211b2a6c606e4b0db28afec61a1a55e6b5f0a6604f", - "0xae9b02d60441859e3e6f3866a9bab8895f0cd6168f8e84dda7c9b1cd7917f1c454f10aff9a8de39909e36576bc0b4828", - "0x89fba7834469a06cb0da39c39a288245e577fd956c241707c432c2590e18e956e8ea3f67e4bee5a5562377617af53334", - "0xb7ab26d79ee65eb9612e54f41f75e22abd83db45010e1a94ce5026a24675bdf670e806c71f0964a33d6ed277d464732b", - "0x8a25bae10ef86d7e91a7d686965d17fe16ed635d787d4d6ca337b10ea32082938f4354620a72b5aa43ae62c7a0e751b9", - "0xb18fd9213bf3b2d7d191266c7bc1c31f683fc7da7dc5ddb4c600e1ebf5fa80a399af9e31b4ae747581a07ccb736b4b32", - "0x9968346d8a867eb57f628e2ba00f69e9d6aa8e713377a69413323b1b9b26218f527c0e719dcc1027daf10c3392f59733", - "0x831ee266686776eae4e3de1f2bc37761a5e1b918d4bf0bbeeb20b490902ae97722bcb1c98c485407491f248eecb841fd", - "0xb0e949d7c50b852055f38f3542a974bbfe7a33409d67c557d70c1204f87265bd7478e1751251792435fa22097d1762e4", - "0x8b0bee83715e20f2ef832347c926249b5b168e4ad87b2e5a9149ea4e07513e4790f60b1769ddd1816d7126a0f6fdbac3", - "0x84edc35061dbe8f3de90c2f9ace94be5ab4170b66c42583a0643ff776256217bbc6fa31612e68bfb9ab678f8e8e49457", - "0xafb4ca7a4781dd31a7d81ba8a739eb65c43f3374e76b4ffeb2c7048b055f837e6853b14ed2d3224a40dea35799f0e4a4", - "0x9945fd5ecdda5ac952785310e87917126917fd4f504fc5565c236db9b96f9666934766f46a1989c1aa176e543c6e33af", - "0xa6d4466b53c48d7facb9cc33ced1bec98897e545b10586857e896d35c850f2cdda65e19bb934a8c74f6def805b1df4f2", - "0x81e3fe4330948c279d99a8a1a4e4e141a039b3ccb7287aaba6f9041c3a8a41db1a4763fe04a36bdadd3d3295becb9d41", - "0xb6be2ef16b60a78b17991d27463e401eca731129843021e302830c2fd665726547240ec3a3240586b01a05ca8206dba1", - "0xb9d7fe5671b220a3da83bfccdc16c0b6f5e9e5c87810db14f070dfee582fa190a360c62acff13cd877c818d705a8a872", - "0x86867f22bf6b859e7f0ae7724a1174a65c4902cdcf74bdb22415875d72b67f49c62ea8bf9ed0d6883ab76512ebb951f1", - "0xab728a8167b9e82d608d4939a0712f82843f624d08d4013dfd3de41bc526e9d495cbfd40c443f67ac59dc4b5f30ff217", - "0xa5c4d10a04452c1ad12c18ce8ed7eadea1f3cdb34fa5ce0cbd804f5dd92eae2551b771523e711e8037770cb66d1951e4", - "0x8808f69b975f363bc08f8578729a6e68445138dada78d5818d33fb83a7af6cc6e7030f7b76286829861a4534e0b30248", - "0xa280773d32e1ce3544d3ba5025896d21e358592504737de72ae76d164009fdad05c8a1e5e1f8658ca6374b347d47c29b", - "0xace91a3971be87b1ca8e737802918d86375088e74380c444751c65978afba2b017cbd8fdcd3f9a0c19c0782b0034a589", - "0xb5445d816d65ea36c9bc6a3d5ec44ce6b76dcc18343d7084567dcf2603d2af93fa8469a1c493e19f1853c96f89621fce", - "0xa238867fce5b09e8695240f936a3f3cb12a715511b7516de995543b2e15aed8860a12754ac8d1c5ca2364e4471a9c5ac", - "0x9467528341f5b93b89c7f37c5dac8bafd0af620230a9f7de3e809f01cf73b8ddf70c38c5023a631a1978ac05ca35c318", - "0x8e5f1c3c411f0939ce4b6a5ced42172fc5c3774f596a114e7c5c8ba433c4efd94ca84affc0bfa89a1c5ace5090276a43", - "0xa6351818f7553d446cbe8d3a318841b0607d1f1890ebf9c6220a092bad3ece9ef8acad4d17935e437377af8f9309606e", - "0x86630d0fb2bc104d8cf840b0e545c0c149c1a8e4dd6d460dd15a52a5935c8ea5c934ef099653d783894a6d1f68414a84", - "0xb357b5d9cc645b645fbce2020db583cdb68772751d6d11d635f1e3ecf995a55bc374be7750b6e8bd4968a55600ca9806", - "0xa9b659b8cacb73a81093eeec42dd7f4fc5d955f9fc543037f31bbcf456af6476f303aaf0ef960a2df88365c2704bb61a", - "0x8b6ff5201c15cffe64bdeb818422fa10dc503ef2a6a4d686364afd0f35b6473e4463719173550d234639f6077e19542d", - "0x98efe45bca5ac679cadc25ad0bdb1f8deffba13d2d7eb14c6149d5addfac06b82fbba6d24b323d615eeee1465b3cc30d", - "0x8c2329c976d78f1d5e30ac34a3fab1f96436947d85f0dd190301a1868e5dcbe4ce60f48fdeffc3e6a05ee34a461d7dd9", - "0xaec012ad25d99ce014101d7da512fe032673399526435f6e1faca4b63759e8f6694a46ad01672da9eaaa4634f61ce89b", - "0xb8d52e530c942c3c7a67bbd0366f4cfdc6a1a075471878516b7a2258aa073eba50a113cf433879a0e15462e82087d17b", - "0xb40c5ce16f94837c86e81d98e2130a9e1dd229da5aea52e79cb42217d3b5908a53d76782cbe3934fa8769db58b00dee8", - "0x877300304eb69720f7cfb4f907b4a7e238920fda129a38516dffcbdaae2e46633d31080590d6df05756781224d532fe8", - "0x973632dc791a5214516c3e59b2b48169470678b7dab66d513e35a0fd1df86b992e27ffe6050a5233af20b5d4998d283c", - "0xa8ae0e723a8ea6e95d721337465a388b60b92b1d9b1deb0b9f59ea30842de356184fd55d9b8331d8a29ef473c1ac2315", - "0x92ed6cca30f76135c4b7e7893c3460501e92592f7d2d6409c1e1d80074120243a5b9ec14d801991204f5ec4f94ff1daa", - "0xa9f575b8518dacdbc5cae766389ab2ec01c876038414b7796f640f633367a5281cb49b48b5e80f6416a33b401c21309a", - "0xb9793588283cfdd47cc4547cecfd987f9f8f92c2b408725f39c1d879199d695e87675fa7e5a190ab3bbc97683a0b9587", - "0x8329a844dd67dfd48546791c4330af65501baf9524ecf8ed4fec9ea87067d0afbd33099052c1c2df819ca1afcf25dfc6", - "0xb908eba1b40edc300b63ff6e20e87b17e6dfe975c37ca63c92e8866968070a2c07204264646bbc9318145fcb90c23555", - "0x8123871ed78f46e9eff4fc7af9f490594fd7c20fb814e505481ac5c7bc7588c1706a79b14b85d29bd7b97d7c82b2ae79", - "0x833ed8928f154fe0a88ae98e5d8c74f816e3ad679c1c4ac1322604093e85ed4b9b9c4361ac188f0da5443c72ee4bf3d4", - "0xb9fcbb8a422bd8d996e713d176b7e63edcc6d73b3d1fe3f2c4b59da637a168accb5fb4d227b709f979742cc0af8c0ea8", - "0xad3759a6a6bac3047935443347e3c63819905f6c01f58f0ba76aab422d723cee10c769663be9554473e668bffde1d500", - "0xa60c1909703211a93d7b5e8b8ec1cf4ca06ada653c27696a7dc9a2ff75cb712918888c6b61b8f792ce9b413aac09f48d", - "0x91f05985ff17f9ae20498185f6558f9f38b67966876dcc6981af4d179cd055661adc63155f4afa6167ad61b7038ac49f", - "0x95c5add9bab6b9792517772f9f8b21bf7cc325dfd13a43177b0bd982d0f620185d8596c2cba46a5e10aae597129870ce", - "0xac0b4b6e2b3e417166ad9b17de0b3ba775df6ad3a78ad13a1892c0992735ae54c06b1e6123b0c0bc90544441630c6a1b", - "0xb0135c25f74ae776c241faa6c91a3f7ed6138d19a2100928b7ede64b79e177d92c5cf921dcce3c614e32de34975fa6ca", - "0xb2215b560d5a36f045de7257098e9d75a40122919d4726990b4395eb2bf1ec789cd0c64c46b775f6a8be28f23958e17a", - "0x870dc7f7a513728f2b428a3c08b15a6af88a288824e790f41b1190fbe02b59dce2914a1339f7203cdb7f2f9c98d8d721", - "0x8e3895f03952cdab36f602418cd746bc0b6a07629eab0a20bbd8de6c993030c5287fc146fc45fe97a06c992e0a9ddf02", - "0xa4cea15ebc0dfad9feb3d18168fd33768e8ac69e263263ceffcdfa35e8638711c2971697b7d5b2aaa0fd8c5440f3e164", - "0x8cfaf5369781a59f4117283fd3f290b81816abd3124a9486ab1faf7018d36a73c1630efc4ad648ce462e541827d51975", - "0x82b420eb25736126ef18d91e91ca2ecaea8983b8091df88343e8e54ca5ea7a3da6918c97695cc0cd5c2df95afb1e3cb7", - "0xb3c13923a3d46d990aaa6a1eff3ad32f162ccc5186e16a549dc29ad4d63de6287cd05579452785cab32e2485636d568a", - "0xad8a43ad6195e08a36f755dd536842ec88a7d920bc302451c860444a3fdaf294e5b5dc5a122423474d322af5de8cd8a1", - "0xae40d1a90a77965366b5b5ce87d6fe86eb255cc3d127526930d128ef7763455adb82475ebfb7be31f9c512394f2a22fb", - "0x9763bb9459fd4c0de2534767bd99f98b859030b6af5739a7081d889d6875f5c23f0154c30d00b7240baf6450b4459987", - "0x94aace9e9318d79d3c7ab533baca31724bfec839b01187e326b1fdef846968b1b29882f2520a9e237dc41ada01bc3761", - "0xb6084f9e0051be76244ead401e8d2758717e93c4cdac58443261b3603cfee0eaec7d758b2e4357650d2c1f5391edf798", - "0x8c656a798fea470163e70869a13edd30d138bc148460d122a2275df8cb43f2b45a14e0d8a8a49eeb7c1afd02484b6ffe", - "0x8ec317e63df2881f49401eb2f6a82e261b07474006fc293bbb54e0fb7437697b16ec1d6ea101fcd56543bf4d69374cf4", - "0xb27d9b3b8c3cc59d08159c765d24fd4660bd0a54b2b7fa9fa00b47e6770e6e8d3ca353d305fd772c8171e20765c8a66c", - "0x863ca045abc38ceee09c4a21a3dd18f1c0f70c0289437957aaa39ff764760bc422b748bef8ef133ee28d88c46e6be1c3", - "0xb0de194caa68f5288dc365faf9e9ca3c69b0a8376cdb532cd6f1cc3478671a1e755d0e8afbde4e3a88440fd9cff4e8f6", - "0x8a259f48cf5a45773522f3c5f283a6c01a0febdae09f873e009e4635c57fe5060b01243b2e5e1c9d2ff7490f2dd3b334", - "0x8c4398e1e579778c88976ba12feaeac0c96fc97b4e26a133ae74fca1b9c315c1112ce3977d20fbe9ae5866ca6544fdcf", - "0xb54b25aeebf1917bb4981b43f39491918773bacce37e994b74f877d4a636f1b3f4a2f164b858f95259f285ca0c294f24", - "0xa9db33b15331e852da3693f6328bde30b8cdd79c9b9b63107cf78dedcf14da68446c462720b9ffa5a1bfdaa68f5d931e", - "0x9966b6bea54405df1dc4edfde9f8c3ed7c0733d5a73bcd8b349035744d5eabbad0d19801a678d48cec84c0335346af33", - "0xa3d0c32b5e3036c4a4b222c13f7db23924bc2b2f724bd908a38db3b8f9c95cf5034c4cda0c5083c0967d34061a216b57", - "0x92ca6b883b2b20015fbb56cac4c4b5ef22e555a9b75f4f020822fba9167eebff8f9fe5c729c574cfa5ac27bae1a83fdd", - "0xb72b58d6ddf54c2d37bdc1599ac966c54cb4926c8d2f70d1bd4cdc383c6eec5e4b87efc59466682f8db964d80a4b740a", - "0x89ba63ee57a1e6f13d7c66150a8d6721329b385eed92be3ea784eed89c76a1ea88595725612b109a9e4aae41d3f6c972", - "0x8727bb53bb62fb714e4e5de461c6cb298730641e38a0b49b3b3d4a29fa24167c7c6f4ff47f4f3b91e464a581a4181853", - "0x816699bc7c3ed65747d34786b7fca4e35e79907f459f2df0918669adee54a70c03580c4e7d2e410ceb45c71fcadd44e5", - "0x979688c14ce623dd17344e67373e5852bc1d3ea12d37f7b28095e5d578d8c9c646e4b97a3a69a97764ed0a88f62c99c7", - "0xb4539a9eb6578ed3b8dd54cbf57419e99b69c0ae1ca3ae3b4a21f204813b2a78438d6c72f86c13dfa06a0b9244b98688", - "0xa5d957181c30701fe6eabe3e65a53a33dc43df364c45f0c4d882ab88a069024bf04b71015f1c2fbf03f368e63bd82fe9", - "0xb9ce9a54d9b17d4da41ba3135d077c546cf39dc83230506a4ee88cfe39e76f7e35664ff1b571e231054cf1b764b9267f", - "0xae6bf2eec8046137016ba94442a7a0aaed0924ec1558885135fd339d2996aeff31ac29f1de07e84f7b7391fc5355f429", - "0x85c7c247766a4ca44278be81752f4170dcc069f76992b236b40e71e31e08f30de6a5ecaddc44debe4f94151cdd8d735f", - "0xa19d41fcac394b750248e575c300b9a96dfc5b3dca07ad6e1d68dd3f8ab94d10aaf8edf500e3fc7774e7ee52935f73ea", - "0xb3c959a22fddce5a2e199bc8724e825a6d9776455c033299b5cdc9a9d184be169d807829d5df5e747476d172b5701cca", - "0x916aa7bc58f34bb8f32808858cecd3e90ea26c3ec1f80a40e863ba18fe9af6e67c0b2664a2274eca6d36ed72e59a9341", - "0x864d945b7be551926f747406d72057c7a141110f5d269fb6657cf347cfad7178670dd294f6a98c19dc0943a68d7ed45f", - "0xb3480f8a42ba0e8eb020c2e1c1284a8a9102fa68b43f6eaf28e031621b9f68bc399899e35a1a283fb52530c8574484a3", - "0xa8cd1cb93974d1a6072ed51f356449ac19b04539517cde34bb7b2ba55949d213ee07d387ce7b5534175bd8a044556ff3", - "0x8e81fcc5fa5579f2479011caaa393f47a4e12828e2e82072736d85ba1bf70ffef9fe3b2c22fd11ce8eaeccdfa2579758", - "0x897f935b4542b9ccf8c0660c8fb1a570a8ba108fe8440e17e6c50e01affc2a8597b7f7cde5244c7026013b52c7331b5d", - "0xb9a20f612c74821da05f48d8bcfa7a4a550979e35b49d52031be8bc9cf717fff21db0142b633465c5edafc42b7c73c84", - "0xb88caeb2157d636fe26d3b221143443940427e8722596746bc337679e10ae6e5a9b33c456ac271f8b01db2f5d1b00a62", - "0xb23bbd978725aae647ca2778e801235f605dde17897d4d56914b0d2241eb31f930028904a6555581ad5b2b74ec3c9587", - "0x97a331ffcd02eda1d6e0e15deb110ad6106d3159ea641cfbf424d2e3065bf65c9b14f72a27ff3f576dc51eb068bfb22f", - "0xa9317840cd8f437ea97d80a3f445a99eef463a5e2beba3c986da8fa67def4ae9a0e8d1a675a35e5616ee90986366bb70", - "0x8c26dd7451b12c65351d5ede6a00ac7b9316f9e28be8c692d20709c3b4a5dbc76fb914667a2f1e9a654f8d2850b7dc3a", - "0x8bf4aa18a988f82dfc54668bd4ad5161f276e31567c949b7857cec331c74c6b68849afe852892816c802736cf7c547c4", - "0x836fd166bb9689520cefd6f23905e4c1260f97167b17534930923107fe934d4afb1216e4b89679a564433dc952a77b0c", - "0x94d6a5a4a11f41887eb814acf9b5a031d013d614621642384504eb78e65b6a07c50326632af47b408d8ccf43faf8399a", - "0xa213812713128750bbc5311dc317992bfb5124fa067072891f452880183d64d6fdfac8825552cb809178a3f3a641c9b5", - "0x976d1290308868c5e41dd3766447b29ab8c3b72047a0b7de85d3ee5b1e13d522147a02572cc0d1ed8976d411faff5b9a", - "0x82a4494a95738ebe56578e1e4c0e486eea66d5cc44141f478bfc5a6b3ebbae6f32063725284df81b438603aa564a2b6e", - "0x8a6f4dee79baf71a4a40843437c16b2f304785f3e56b32d9ab2474666fce2c7749c776bd898a65f4a4d542a497cb6d6d", - "0xa04a3484be07c2d60f1a90f9dd8d4170270a808cfdb863864377c2515dd71c152920b65fcd5f47004d27d14d7ee7eaf2", - "0xa984f6633ce3d42c75083ef7732e5d0ea15d91e73cf893be3ebac5e56defb8db97088c5cb3acb661e26bbb354ad91ce8", - "0xa5ab5b4b0dab86706d68c9ad921d4917215c4fbcadc8adacef7309c0c853bc3c2ea34b3868d8f03cda6f504793832594", - "0x88f03e55eb028353b70352dbe91f298ade322951ca115972f1207744254fdd01ccf899aa40ca747da8812dda5bd5f985", - "0xa4bab627f7de273f8085169cf05413bc368c5d9e5f58bf10995a8bbd95e511b1ce15d008405728ae8e8a83621efb56f1", - "0x8ed518d0f225b90fe7f01b0fe4c451589390325044f0d18a8c47bf13e24eae8627feb0c9e9514397536f73f33f67a044", - "0x97c73837e77d965f401b4e4f089ef4de7aed1126bef6be4e9002b2b68014b98997213e492f7aabfd2e47cd0917a11d6a", - "0xa99e8a55ed0385bd279e11a80255b375f2d59bf8b0879bf2337ab5e3be450a2ec05d1bd8867a633e359a02cece4dc1e4", - "0x82a74b5efaf3c217ee2bb56c9b8e76b3eedfc553c73177e59d982f503a5b0572b5cc0d1292820823307eec956c42b28d", - "0x9800ad3e10e8a19d65d5963673c183bd536b65e14ec18dca45e881ff3bc74eac32bef2ef845515ac4fd6caf558a6926b", - "0xa2933c78a67cb40489ffb8096c021ca017b99feda1f9c5d702227d7f0a2ff66a539d68a47ad90ffdfb5c31c774946f87", - "0x947b29715258ca20da5b17a8e3d99665b7e599aa5bcdc5d2d7830a2e3cd78364d51a3d7c0d8bce48a1992b27d1ac4980", - "0x86f2e2d3e160d3ff979ca70c456785b4b2437eb64e58adcb78c4aebc96b470f1b8b999a3ce8ce20e3d3f030d163cd138", - "0x958f4435d35932a91eaad0dc476bfc2761a85f336ad2ca6fe0c6830fe54e8f417434616df9e6f07a9454a4403b00b64d", - "0x8b1755af961e0f9f59651d56b538ea59af489e859a1c93726cee62649da0e304093d62db9a2c5854c8da1be61bde990b", - "0xa5e11042f73f979c8649592f6cd01dafb319344e379a65aa9200d3b636abc569edf822c2bc12b3db5c30b9ee74f2c981", - "0x92ac5584de1adcd38a2ebe361225f224e9b498344521be519faff77f87c1f22fe8e112f9df7cf960b16e358efca0db08", - "0x81db84f05f75a218045d7d5fd4620648bd4a95cf468cbd69787011d615778ba7300b729163e7c8abd1a5b0ea66fffbf7", - "0xac2f522e9f030a7c576fbe19041f5db3913af58da75b87e8ad64b93bb34850a79b852804dc68ad5e7de66d90878544cb", - "0xade9763d1c7e9f68b5f817cdfeebf31bb3ec1391dad04576c55fbe4bb13cf0d45abced3d51b5512c73b2d0f403906340", - "0xa0b431bdd9641595fe1eb8d96ba4fe86a447a31ccf36cd2f7d94c5c86a7d96bbc95b204fcfe7c69c1385997b1daea3b1", - "0xb3b093bd8fbd84414609ec9a108507f97d7f77833b93b15439423d2a2928e40b192247c8471cdbc12891d83c765cc6e2", - "0x8531a5ce8e0c44e887ebf4beac65352c8a9673c51b6a1edc439e08bda1354d359e1ab2e27b82636c6dc0daa3aade931a", - "0xb22c2f3a77ae4813a75004dc2c9593cb2a51c430c559bc7d07d83e95592883b99fbd0f9ad24d2d80d86c871cfaad2721", - "0x8b6dc7d5b8cb6bf36352fb19e42aa37647505436e1442eb1f228b0804916d569643102b2282ef66bc9a4442520521dee", - "0xb29a811ab81dba820242a990dc774cd937cd299495cf721cd11971b9f1dd9441ac687dfff0e91656b9764963a56e4625", - "0x805b280e31664008fdd874bc38e870db271027da70fc2246fa82c499742a9a8de1152275e0be61f307dc8f7a918e270c", - "0x929f690538a500d238208930b55caa9c489bfd3476f6be2d385c36df3159dc3d8bdeb24a1ffd7b028ff4d881551e2888", - "0xa92bbf103ad851a41e5230e1e37ec7802e08f4610c0db9706806afc4a247679b9525f9a534c70d970a1acb47fec9bcdb", - "0xb9f2698a39d6d7aa8aca181fc5d95dec796ed6eec002557d4b63369bd90aa4438c27ab90da4f3ce81168cb42f7400070", - "0xb08703bc97292c56833d8e61105f1431c334f98a7946850c6175f37f703ff790d9a1522c0003f08dd111eeb083235073", - "0x9355141cfadf46f37afb73414c8803f9094b06952c9fccb24a1f8c18a13fa7b1197321b19cb832de3f83ebdf8deee53f", - "0xb7c23f7cd8e212108906b7809df90db58d2c2f3a8e1f775274181bd81c74fd7c2f8d68bc7d4aef639ff4e19f86243f98", - "0x92728e009fc3faa08e81c36c268b3ac18627da7618c96c97598b13242286645789c15c99518a07e658d92eb8d2b89c79", - "0x8fbe36d4f2f08cd6245e8999728884c636a264451e4ed894d2116375f3d9eafcaa72ee59cf7923ed8ddacb53cc478761", - "0xa6b2bffd6bf8f54231fabe46ab2c1d014ddaa797d08e5914f13988140bf804019fff3ad07ac2cb31283fc3e74e28d0fb", - "0x886387540b5a7acc8b2bd107124bd17d6515697e09c85c4e932a6421965c872f014d11d1ddf321651e4b3564eed4f252", - "0x8b81f3ebc962e9ecd13a11e919d86ce14dd89d373cffa158b807fc91555a4ec1d7164504fb67edd9599b10fac5e32aa5", - "0x91e3213ded5f82e34389408e95d4f7fcd0f50ecbdef9726a289238e4159c6d3cd2f401479a1f785865e91ca213d2f8b3", - "0x99154b88ca5462f62031300177e571708821348e1027cad4867eebe42a6fe92a58ee1dc21da9031002f1b051351b3785", - "0xb5c2b7cfd87f2f65df07b39f8a26dccb16946fef6b89268b9300c8529d730a1469ba565a480d7c5ae9df8600ac50e90d", - "0x87df32def37370bf8c4c3a22a670bf5605c78f240eccf8dba13bf19c8a3a9d0560f8899259c4e51c6b0fa64d7d1e4c76", - "0x980a20e5cd352786bffeca1b8a31930d8898eff9f4a6b2570829248410bbe1c78105b6a61cce7e3ed1642e5e2af127e9", - "0xb18b8dbb9eda5cf333ea29fad7734235ac9e7234b49fd04f178136b15d97595d5b415a92455a319ab594b81200cb17d5", - "0xb713a71be9bd22ef6a2747d0bc8f4d008cdf6182e287c1e0274689e915a68150d6083268188c1f4a7fc76d21a219ec85", - "0xb86ff129a981359972bb793a81fd422e0b37f89e76fea70da012fad160b9eb7b029ced81c7e34679f6897a45b4e8da4e", - "0xa74a4cb9707156e21caa20b95a2a4b4eae8f773cf679e2073fca2cd3b1e502ef06de8a3c010833d525a7f8bb6bd24601", - "0xb51f06da38a76c2728cd01f6073f402fc49cf4bc5c60113a2700b5bb0ca500e465e541c467013a2804bd7641604bd2d4", - "0x9855dd73307d8671b6f9ebcf676de3ab7e37e7ac1544447c7ff34a213da46123b57ce23bb0f381da8fdefbcbe6c35645", - "0x8fb382c63f4c935462d013a0d3e2321d72fb4781c10afe6e31ac51766832218a05addc6dbb1f644aa61b5da9bccfd5ae", - "0x855dcff23e0ebbaa3562fd27c43957cfb35d492837aa71f27cfd1bf65a59a12d2beded9d09f3ddb4f801aca8cc34d2af", - "0xb7e7b317f10cdd13bc879c2fb0bfcd137af23e0cb70917e48d53b2bcf8c157ed7e5f58cdb966383ece9d3a4c92012746", - "0x80d2f84c39422afcb449aa68b34fa9d72e9de79a473c3ea5897f6f3576d2bb6fa2d49f0b44aebe5e68b11e85e066e028", - "0xa35b083749f8a5551f0dcf529e845aee189cdcc6ba779f4e88765adc49cc4779cdc2290598908ccedd8dccfdce29d53f", - "0xa30c412f4bbc2de80fe5c577b4f94442255cb3061a40649b0ee5357977503c6fe54821ecc8cc92d5056b6977c4695e70", - "0xa2ed0d90ab612fa3526f7450a43d45a2d9e886f2e5888ccb8405adeb8ca3e41c6a94d18a54b3cb1eab5b8f3851841ebf", - "0x8d4dd3f8f8a3d69bb217d338e757c814eb69e6a776f55cf51fa7c1b2f1ce5f8e9bce8353dd335e793d68eef676cf7c36", - "0x880d1ca33d5d3bb47b788a7ec64b9130752610816facec99af53b6e58a7e414616e9c815b1bad870d426380085f6b5cd", - "0xa287578293da4354f2c3c46d637aa77b91526f9618799dec4bc602305ffd8336d373786eb67eef01dbaab88f07f292c6", - "0xa86d3fad257a64c84954a7530822346da0215ebf4ad9c583f35cdbe16a02fd70d58ab34c93681fbf55d6075db6425cbc", - "0xa7bd884d343a6bde5f6c2512d81ba701fae7afa6389389e4776eacc0698a54c3ab1a0e1652c1a7a23d3a1d2a63cde8c6", - "0x8e0653c8b7279d5c958ab1b53dd77b73fd30d9781630a870d0a75681d38cde4fb7c2183b9c5758596ac556578b43fef3", - "0xb76a00c6f5093e7b28703df85bf968dffb70c455c91e75cc81189598df052244f7549d18e45dc70d98d3d86e0094ab2a", - "0xb270f2ad3dbc8b43ee2603c4e641be76820f07a4757cfa96be2be9c310b7e39b574572103253594a51fa9243298cbd94", - "0x977b8b86841ab8be7d1d50da7369e2bf71f24360aab8448d7748d59e010ce81bfe79530ee6f6644b987fc0d83df3ed15", - "0x8e18bc59841b7d56f8d9eff8818eee06288cd6ca86200eee7b5e6b230070debaf254a2198b4cd7dfbda8a1d55a916c8f", - "0x8e7a328ada969ed6289972b7f43eb5958d23688603ee6d118b6ccd8978378dce2d733ff64c30519b19007a78340fafa9", - "0x98a0fea70a219292584c69546d6d242cebb2f1d84f69c5aa275a257a87de652e721078b983ed67410e3a5eb0cfbb2bdb", - "0xa09fbecfd05772a9989008281a9585accba3850831485802f042413da533b1c7ee45a8cc679804340bd9142b2f9e0069", - "0x99890a6b273a2787fcfdd8e8500134efd60df99410e8432664a3e5325e55e78942f4bb11024c90e4c3618a70729a277b", - "0xa5f3eb1617a77f2d5c76bbd1bc3546ad1628be90fafa9a8b62c605d04e599ab2eb74b25afe0e68fd020daf4868dadcfb", - "0x8b53517d93f42b833f7669c131dc67f14c3b0639c46d3b02bfdb24cc9e642133e0c665236a7ba851c100ca733d673341", - "0x849fd288217bdb154213e79abe1a78672903e15429e37f6846019986e1cc8dd2b3ed28e4cb52dee1762a4dddb9ca95de", - "0x954d839198c3dd2ea1ffddf98050e2c52ee81b89f38d967bd30c6863672e43bfc32e1030bb12f5aa424983bfa31dbf5b", - "0xb52fe86414a98d0896d7a427d57739da35cac4ee24be565956d15a5c1cf5b4b95e5425dd2607fb9f6d6024549b59a4ec", - "0x9586070415a6bf1e11304d2819330eda88e81a88b9347aa866692c163e1af772be9fb747d9281d7aabaf5c9934596934", - "0xa5b78e5bea362df26a89df682df61287763ca1b87ab9618609c99e52e6ba047fba7ec828c0552ee26279aa8a48751334", - "0xaabf36b9dd465ae03551dc82bed9cbf1d22a2236ded28964334f7ad474f317f4fb8515b853354bc06181fc9af82714a4", - "0x910f0b2efc608cae8cdd39df7a5ef9e570592b31df2331baa7721708057188ae96e1b43e2f2f2c8cb360b961d687b60f", - "0xa5c5b131205c21ca68d6103f8499279621da337a743e4a08547c3b4507d52d2d6e5014fa5d920b351a6f53a195687766", - "0xa6898dac2d8748b8bae155a7d8c169e7eded73cace1e382c4dae8633f19463151399c5cf877f8ba344a698a98228864e", - "0x92919d8be671b4f490efb49bae145f419c84a1e81d3ef78761fa326f67d749ff3530f5de04f984a018065f42e852e1e3", - "0x81083de978e025f0b5995550fa17915d02489344cabf8a79248352d78dd6e893d28a5c5204a65a8873756a34ee3c0120", - "0xa6de92ecef84d188cefe29a03b564b1e7bef2a6afd785b58897f7f97a958573a35aa0767bef12a49b352de30b4f0dc18", - "0x985cb3475c7a9f582c11784cf61a1988240d74e49084a4c0f55f3f6068c4da0b08b136f8fa62e9001e0a265bf65fa3d4", - "0x97e6d360b504991d51119a78c5b647f25d5fcc1298631209d82c2ca40ead0380835fe3cbf8b82148b0b01b8157e884e8", - "0xb313df44b2c47126b58064599a0dd6ea49e5ace9ffa663de03ad30c1e95301cc68eed67d37ae6238469e45124c59bd39", - "0x8a58f70545db2242cbdbb12492cc11ec4d2b2ab0ed8450d21ceb573558d7bda91ab03c98736e13d041bcab84fd8248b9", - "0x9077880ac352a5ab0e5e15ac89b14d173cda0b41b6f7fa66bb357195f10cfcf491fad6bdb49d71cc20d99cc6c8e28d04", - "0xa09b2930fb3b1a60af8c5214e8c3f6deecb3fd3d0a5662f3885948f48d1836b5ad3dc74affc54dbeb5b522b90a17dc4d", - "0x9163bd2e5f58fb1d81007422b91147685542fb1c7e2c8421af284c7cbfdcd2d2b399a37123b58a2a349f27b31bfa47ab", - "0x8a3d859f141457f9d63818634f81deb5c858ac48bfbf2e1da21f4f0dcd66b3e1d2d8fe99c4cad38206b1e15dad94934d", - "0x86d3fec476b59782d0477ff333fa79922fb9fe3d6d6b6c5be9da9e88b006b46b2a0f8f86ba4159c5085e66e32fba67a3", - "0x8041cd57335bcdddd37651de2c3e92edc600ac23041d0e383baf55651b1b0960b6a601491608307160f0d7d48ce395f9", - "0x805c284059f8c03b2bf006b1af95ef726874c5548e93ea965b402931f42b189f9f674b6b52ff09df35320085172973c5", - "0x8acf781a0b40cc56b1013cc1fc3bc43036545ce35591f3b905543c09cb1ac1a70a074202b6d5ce3680be913200c58879", - "0xae670c448996156c80d063f1dfb03d7770201a35c71cf8e70b38d52dcb5e2bf73d5286d63ba2f561525d62cd67d43125", - "0xb0fcd0150fc0005ca438d6b0fdd6a70b121d35ecd74e62bc119bb0187cdf6bf674ce9fe01eeac5d46a68ff4d4210ad09", - "0xb752c6850985ab13a057028887bc84674697c012e9da0265dd5ce1e48f0aeddce5e07e3e7cb68ae17a648cd1207eef19", - "0xa6a5c71915a980fd0225847b45e2e9f3731c6b2a627cefb1e2c6a0cd7f1d0555dd32b6b601a7ae9cfc4b9d06a56a578a", - "0xb7d96f59a988a7a810c25018f7f85cd6e81b335a84504ec76c97d7257f9cbfe88215ec89553f0dbf39507d990b3a7f84", - "0xa7cea7b3ba43cf6ecc488c34511b17fc7b97150b2d265785c09c676ad3123b322db32e043c5961384ed6d90d20c63061", - "0x809dc467b304e9bda732cd92b15c0f9b363cc707432788971508b8d60844911ed4edfca96d8cc20b9874f1e38a2d1685", - "0xa5b6a089e022fe460d62c4c5228e1381902c9a796ad92c03211c855541a7fe27c5a39d9123b001b0b892ffdf0a1fa065", - "0x95d67a21154a49bcdc79ed5f2773b651c81fba1ad82bd373239f09a67a50371a147310623fcbc1211ac57aa154e8b300", - "0xa4a4f0ca8073407575dfd5d04ebf76f8bb467598824f2ce7fa74756803d9645d63c9eb3ed39aa202dabafa4ff0a0bf34", - "0x8a77374f6e449d94a443f2d4593a0c3e4925527e0653e873dc20756396a9a4e5696fe44fc1b49e456711259deeb3f037", - "0x82585a825011d6eefa85cd530685b103862aa0777510d22942d8f77a0a7f489f5d10e5b36ee38f66cc96dc57d13f5893", - "0x98e24625c31d5d97c789eacb91c3d51cc6edb38cedcc474deee459f55de557c42e4d0754ca4ce472d0123638eeafb55b", - "0xad4351c76d96c35ee37362f2384ffb809bf6a47213863330aeac1ff9be2c6cc7275f0f974e46bfb716a89ce1bdbd0710", - "0xafc8f5af4f9c38ae672d20e7bc3796aba23a41eb033619b4c0a06e07884e1e0c7a7326f069068dd22e69fa5f672efece", - "0x983d5af05af31f9082f381378fca3526f88309bbe51d0cea5860813bb0fcf6b32a3be110336bd728952dcd6ff8a26361", - "0xad3b55b67b64b188447a1fb10d027bf7f86ce0a0fac966d709e8b6ccdbb7333964045f0c4719c45c36b7f3c9ff73944b", - "0xb410fde230d8dd24b9f1bdbce8338b05110b130591913f23a34c5fd092cdd3f747c383f6967cdb529ade1a264a3ece39", - "0xb3e4f0a046f93c332be07058db00c5182a498987759315bcc3a58d9334e09a59333031c3144b59d03596925703491cd6", - "0xb77e58619c8c471531d9b2e5dce8f82bb8794223bc9459599a911440e64e0b5be1d37e289807733ddbc2858bded1c34c", - "0xb450945bc3e290df96a196083a45aa929ee080bf45112e678eac0a939db2ba67334ef782c855b9b354caccd94b3babb4", - "0x9794d81e968770a6e12add60b32ccbbe80cb2680b157d125461cc3db998691e836d98cb3b3cfff4f156b2800d426b955", - "0x98d1284b4c035e93b4ea0431884d91d5a7855ac6c5b1ea2a994e653cf77f0ac1a771dc75899bd1485066da17e40ee341", - "0xb1da89b14efc14d15b2bc967ffab85c41dc447b6a7922b619b5d5b06dcda725bc4530959b70355ee20eee7c1802601b9", - "0xb8e50ae98515dbd9ccaf27192e58a5c34def86b1d0d181e63e64439107c30254267969f6069e0b863c254440c3480de3", - "0x915f0c7dc95f630bf1114b02e7d9936b0911a69c932950ecb7f800cb1aa1a4e1f1b6bef6ff4a23301cfd904c39025863", - "0x85392fe0edd316031c69d90b106b6685bed56a0d8d814db2cd5d77d07b18fadb632694a214a176ef60aa0f82ea14b00e", - "0xae4cdff23859b7570179586549165c728de4ca254a5da29668cfda259d43a387b3caea8537888d43f713d458da6bd4e8", - "0xaa0b6a3e0555d64a5cd1201fdff7ba7ff3019e9ada1d86c90c626a710df3d97d2ed62d2b63e5632963e09cfbedf83732", - "0xadd726d97dcff922dfd748eb897e540a2b4b8bdbb4eac1feb74717bf086b1760a957f83586a57b5345bf4c73d791ab9e", - "0x9721889b6fd55cf9a914e5aeefdfbfb94d379c6312001ba50ec4bb1dcd03f95fdb45041330da8871cf3dc3c6a6b5e330", - "0x8eb9417573ec6af24a610da5260639efcdfc802a95aba8efa829dd70ff179dec061da9facac95b6af02cba6a8646f7bb", - "0xa477ad7d2885e1f081556a98b3904cd75a4ac7a8c27fb0ccf15d117feca59f891a677fb4ff4fbf38203055a9436ebd1d", - "0x95b3b2ff92e8a0bace130d165984966637a74280d0e056cebdefa6f825b1d55c9bc6e13cc8f263e657dba3dc7fa68627", - "0xb096fc33c038b425a7a922a4274d01eb366a488fc969497a575587ada74b9452a607992aa2d8b9de66705fe20b4abb39", - "0xa813ef1053ea6ae8a37f4da722f16b6ad0213b0ec7829998362292aef68c28357ee27a406b567a629592447db8ea6085", - "0x84248425c3201ed389fa1b64b9e1d151b5a6f5fcb8f5e28ebd665db57156ecf9b2fa77bca857200df9f54383b7c5eae5", - "0x86d0a3c7fa1e64111115469ed0373dc3dbd448e1098250e9e8c5c7e775fd1f267d49b4123c347af07a28e686d5f357fa", - "0x8340b2ef4fc2afab3a3d51b6c0361cef4aec3d5e1d0f779f9fcb258711cb79ba4083508644e2bd182fb25b21523557c1", - "0xb840749c259b5af5874750853b4de6f4d7a274e18fb77f774f5f454c82efc5979a431e28bc8e43bb831715c7fda96db4", - "0xb168d333cf20b053c1b2a915c3485200a7590c3c3661507990390800fb95d3772ec6815d53aec5e2964eaec19833e787", - "0x8f1bb538dd5005384f38f88cd2588228aeb0c6313aede14ccc12affa9715cdb938ed4573c391572f0a7ba6e33a1ace89", - "0xae4a8ec2eb938eec00e6608c087471128b14a773d75a99634671f6fed95f7b24b14f04b3271d1c32faff7f0f2d98547c", - "0xa4ad66552924a6831b657f8b318f303225b2cf29b09790a48285b028bb1420c56dfa2ca0df2e823f694e8e3b27952a01", - "0x8af4eed962eeff534234d7c34f1033c68e8cf798c99880a67eabf38b533570a3776399b883f8658265cd14277b060790", - "0xab2c6406132413cba89a951d919bbe123fe4f220364ec2282d8ee0c140ad8d48ded0df7ab56f8f18ec7526ea2f1cbbd4", - "0x9154df8800e26020155b98f630e640be97a3ac41b182fcdbcf31a3e4f233810e34e224c97df8ef0f39ccca29a9921fb5", - "0x8f306dfc5b8376a88a104cdf67eab54f93e478ca09036eb780050ba2e8112b400bcc09d49665ab37d21b5a2d8440b3c8", - "0xb768260e94bbabaa527b2af8be423577cec3bf4aec3c569a4fb69e1fb997a2157c59f1169065d24a8aa3625d89d988fd", - "0xaf06139ca7d240f2495314d941890c078d504b2bc09d98a6156c373de29781e7581f33adfc738650cad0da3f6e07af88", - "0x849a6e458ab2f4101167cbf75bf47ec1f9e481f556b1b9d297a6b4737584011d7881695bbf3ba31e3e4180696fff6407", - "0xb107e7aff27aa19a4a92d1a65679bf40e85ac6f08d4e5f14859d97c170ceb431858fa4c46d00131527c605164b5f7bfd", - "0xa00666055e18f34ce02e8b67b6f181327ec0a11547c0795bee61802aabef9a3a76ea138b905cebcff9c4c86391763e6c", - "0xa65cd8dec5166949696dcccf031c300895c5fdd53709a1897c61d795dc22bae2f7717e7ae52a9950f9d00471ba6257e7", - "0x8b49aeac3550ef28b5de37576a5d4e2e43bcce82de09f491984171251e26c27fd0a884daa6f3d30dda107dde4544b34f", - "0x91666b88be09799c7de9a5d9a9d4c1bc1b6fbc44c664adb15a2eb27229be910226514c2ce22818fd38b850c89291a7fb", - "0x85abf4084c735b20333b1c2145571b793f96188850bae161160b47dea7c48b0f588adcbe9cf80e05d17851cfe3400f1d", - "0xaedaee73c52d71d7ac3854fa41199615ecf49cb0c35d8203f95175d1ddf565499a8e9cb8d31d89e7cd9cb75a9fb56f9d", - "0x9413589f0746d3b81e2f88b280e354fbd63ac164369dec353e6259a2c4acc6bbcc10f2a851901f39f90da7e523d77848", - "0x826121abbcefe3ad431c713a1a2cef336a0f06f69980a14d0a8adae5640e9aeebf4eb82be4621165ba32ce5e16de4880", - "0xadbff68221279985891e9f3fdb7b1dc71db3e20213b7c8e1931e6f75c6f02e7a1f6f05ec0687885de55ac85440f372ae", - "0x99ce8b064f874cf028e85281bbfa43145893f80a8b12813d047bedbf88699266652de6ae9e4ef9ce575e67065854fdb4", - "0xa809a71a663b0a9719c0327d33215b63c6ebb12da3477da8534d7e8f79fb81e06adfdad79686e40efb2c75abde559a34", - "0xb26c4cd057118f9b12c9b86e77d370b3fdbf2654a5d80a7763ae98c68cc2769a7cb293ea89b3a08250c2f699b8d76e22", - "0x867c56da9a2ed672f47924cce82c9d7e801d6a1fd18cdfdbbe07c82091c70ba0ebc6008b0b9d505632a97aa23c45b8c2", - "0x8cf14633888f2ba0b02fc8ca7536f39fa290678c7e0840c58c53a9d2fe10628be343a86acd74b2fc01b0c03af0996f59", - "0x86696802e4f27928dd6b0287d0188f8067283496d154060383c5ee295a468df32a2e8e24648d93ba868120ac429b68cc", - "0xb15439762d0f7b6c98e6946b3c0a7ea0521845fc68b47fe9c673194d81a6cb375c79b0122e81a027f21a7fa4cd6bbf56", - "0xb1bc19c9a3756098c02bfe36429c0f0d8166a5c9274edc7f80ce65ae7d6c67864a457f19cfde6924d204b81f2a195fe6", - "0x997f1cc78d707f29e3eea0952b5514b34c2cf0720f33a3244cc466df62b13031bea13df2296270eed42b3667c53d6c26", - "0x94f599c9995caffc9b47543b822dd8f84f921fe2a31e82d5d0fc79dd93a4da0b87a0906b82fe7c2a8c23c7829c21dc2d", - "0xa7fc8a6ed802660bcc07d3ca454c415da18d798719dc2688eeafeb8971910377ce909de68721fd97c4d9fe439f37a8d7", - "0xab16f93e6df2464018be01fe040fea08c67e0b032fe1950fa37c7593c8ecbca24dcf0fdb9e1209d5b0def622f3f6e92d", - "0xaeaf19b49843e3fac538075dccbb29a63d55d12f8c4150185b1ae62de778c983632542eb495808ba629cd4cbd629e07e", - "0x85614d537efaee823452d0427ea3a2f7d5a3c988b10cf7adef8715becaa519a9b5174b63e401946362176dc0d65667d4", - "0xaa08d8365e78efc1919cbbe562be7b28c57eb05c36e8da89378cfcad9f21e134eed923559530aa3f62bec758b00c70ff", - "0xb4c2760454170276885d66f03e9fc4e6a4254547b04fea3c233c11dfbf71ab05dd755b9697b442ec419aca000152f2a8", - "0xb814059b189c0ed46f9dab604fca25d881a12fdfaf834a75cc2c0e1d8454ce0ed9f2a79b34bc5e27004903a48c6ace90", - "0x847707b0aeb4fe91c12ea8570cf0d16caece8946951360433c8a375a69fa4c01136172ff2acab6d5164ff6d3b5340858", - "0xa7a9304ecc5ff6fdaaba6e774556bcd7c5dfe8ee7580a677301dece55c6a8564e7c64b60fc4efe89ff73954f3c3f5b0f", - "0xa1a86fc5648edd58cc7eb61cc30c62edb5314caca5551ffedf088fc9c1b92ec5b487f670c5bcd2127067e8fd5faff03c", - "0x9086a31715283fd525034d59d4ba3465d6c30059b967b1eeb7d537f3bf8caf6879481ada2849167e997212f3300f8ff3", - "0x99c11903cebf722e1cfd63a46b0ae93312439ff2f014b6653fc61927ba430c432b4955b30b7f078c340f5aad4ae24313", - "0x934b7a8b7bcf0108ed31d35a645d73f661c064a6fc6a5d1ad417ccf1b8864623b0cfb54707f10baa86643afb5c5ec980", - "0x89d5a69ae8cc18ad77995ae92d30236d5a5ef00cc63274e318d18abcf9d936453d18a8e6392b52d2d66b51c18d904d6f", - "0xad2448cea1948f0a4915ab054273bdae33a08c494203d11f46888f852d0abefa310b50367c80cacfb602cbc249b31a71", - "0x807274fbe6f08c332a5d2e2ae12cfabccfb53511b8d83bdc875856cf15ab52c2d01cf706c9be428307ea62fbfd67f87a", - "0xb2f4fee9f32c0ea7fae306605b62d983b130e4d423e2de286bf9f4343b79e5c4545214250cd1348402d8278140c61c00", - "0x8a36f79ab3ee0063098a39382061ec3e1234e67087b9519d0b762aa9cad54a7e0bd5d24e2b0a57a690993e3182f3e83c", - "0x86668e6743a7b6d1ee62e70e6031fc8639ecffed38afdb1afb41d64ec402a308fe0438a22387d9b0c130ed301c39acb4", - "0xb816309d1730cb39b1ab00c5333c6962fd5f5d8b22f3c3ba987b1e0a0065334d206141dcf0e68eba717a4eea533aa6f0", - "0x8754e190b8f751aaf9f8e7076d21bd31db8d9ebbee6b26517b190f624b3a892050312cee9d73cf3d7245446c6a376437", - "0x87826589ac28f442c608faeaf3d63ff057af7724f9d412d1f2cce8c58fad0adde325aa496c6e4e8441775c02d8a74c2c", - "0xaf30e5e32fcb17226edc54030f1eff8af619c207cd9e42a2ded7f15cd29fe52f140901f0925ebe4e997b56f34d3f406a", - "0xa62a4e5b6591d336744481a0797eb23ccd0f580d04cfacbb3e415ae3f273761042b8901b0312f93a6eafc42a50f81cc6", - "0x968a9ccc95e8c124f4475c348a33ad2a52a42e191a93bab3d7f0d211df999aa081efa935391a8289cdc4a5a8f7433822", - "0x93350cd99ab7d3e51756eb01c89172cb406c1debd3f0001d2fa8a01018be5609d73df671e1ff43e612ddbfe7076d9ecb", - "0x8df26dbc565ea7e758ce4c2656b65c1f0396761c9360d7092d12c121d3bc1c293ed28d82f1057f4eb5375b15443e9258", - "0x80a0dc22fb4a12b06cf05ce39f76537eb3db9691ca466ca89b2585237c03d13fe3fcd311ce2b3dbd1b7382044b803782", - "0x818b79cab08e11dff3d55bb0f55333f6340c5b462609d43334c14fd878b0f310b77c542c74d3674a94c692de704e88a9", - "0xad1bda19b1bc3f6d757fe4d189ca82bdcd0a9c1ef509c43e3f49700f84be33bb9b8b8e70f7a09bc6bc00a78cad0cf9e0", - "0xa22ab44c676ba2b3889341fb137dfa14cfc5491ce4c3c1fbe2cb7103fdf720ff2b77806a40109dea9a68d8f072e1c167", - "0x8eba6af1659b6145676d3663b04ebe58c199a1c24837ac4969793f07ed97165d20bb0410421e561cb9283faafd9eb51c", - "0x81b216cf08a29dfc3e16b2865e712e15f494b914cb24526a96799a3078f200a3fd403767119732ca4de07203b479ce8c", - "0xa023ac601c8e0c22553068ce4a7b8361b0b37bef5705fa68a71c3cfa80510041cef3640bec2cdb4f317904521e99443e", - "0xaaaab84c8aea75303fec31694114b3ee10fc1a67357cdd675ac9d0e33c3279e3117d389e9ab017882d517131b14e6088", - "0x8bf9a44b3df3d7e0c776e7ea5eb76f16f1870960f32e7c5b63aee9b432a0adeebbd378c574ed60e15a3abadb409376f4", - "0xa93faee621d930f336f4fd952954ffcbdb261c9dcc4e60cb848362223374010c555a73c0563e7933d1596b0526bf75cb", - "0x88753d0e35e87f7572f2012a40bb757364af5cf6e5dc0dfd16d082e698d3fedfab3c671bd58edbf11cedca247e9fa55a", - "0xb7de5f03681634991d2aa8a0ffdafd223b1a0d1ff70fbd9c00d03f228c6772d93c388c02045461d51326483af97bca37", - "0x81f96d4fbef3cf00da423a1c48ab8acc222016c21f6be3df778342c1d1aa1a420faa8ce906bfcdf955be045efa4b447e", - "0x8dc75ec37122afaf0aafdbea333291ebb735792b4d5934fd16bf28b536fa759dd851e1de448c3efac3d2b0097e0b349c", - "0x9186f66655fc1c551d0233b761c6982a3b8539085ca9a2baebb826091e179026b90f7ba6a825f38c6a09b190a31bace1", - "0xa1cf319c9ed31ffdb2108b684bc21cb495e77c853e6c502e03f2ea08e88a0c2b4e31958004d9879242df420b628acd8f", - "0xb3d3e5a75c34640bb2fbc7b62f8aced8dcb4b9b165992717fdffdf765bfc81fb4e67f3e737e6f70f24d3c24812ec0ed2", - "0x86ee6ce0480f73cc89ce7959b4af52351317cb6406cc368e889472ee5567e8a98560dc1f13b87442c9a8c5d6b31fc446", - "0x9478256948d960e3148acec3487da232fc2ae6818ac2c6eba491adf130c55badfe83f9a519379fc5ed0b63366de86a02", - "0x898a8130718ac6f98ef673fa8b725af6012ef28be3f2320359a5c2c40e479969e5926f1864624ebec10f27594b24f618", - "0x906f45d4ec3f647d0c49deb95884629a04fa65cf91a075bcde67940634cdc98f76fea8717fc1e714ecebb337e9fd6998", - "0x874c5a55bca05fe52a5d1743b8254b642431b720eaa74f73b0faacff2225f448ef94e12585b2d3bcf12c140ee3e81510", - "0x96f76cf34b14263a30df2135131dea00074f2ee853677b94fc32e04cd9872424dd93b32c55026b89c18bdb4e58bfd19d", - "0xb62e2ebd543f3e9a11b72f45275cadf77b1033713625c7374c4d2284d63acaeb64977fd2fdc90145066146c311a68737", - "0xb1759d3b667af9f15da8d4e77440fba4193d0db159a0bf73df32215b2d292bfed7cbaf41c07c7a94ae1f04bab23cefb6", - "0x88423607f005af97b5f8131bdb1fd6d7cdfc4c2da4a4a14bb818b3ecf50c2ae6d3b8cf55e23632354537f5c0dcb0f48a", - "0x8ba63acf22ffc1576935467af19f555a0c27a4b56e5bf752163038f0010fbdbff8a2131124f4cf36a326dfc188740e77", - "0x8b1996a0cdac9c6d896111671ac4dfa84a3a3738c43db6d6788f1a7b8ccd6df16a31606db00cf0107eedab28af05cd7c", - "0x912a604a97457a6b46d48731fb44dbaca26e7cc70a4628dcf553b43a9efddc4e5fb040a1b89e31902888a7cbbf709333", - "0x86eaf5b2fa873bb56b94eb7fc823527ae50364c1bce87e36fc13de149f1fc937af858a25cc477277dc6eddbf9efd5480", - "0xa0169e6e915e7216b83b00b31eeda207a02c9db6825b5ea44134368eae5bd009b7c95005c621e0d258c33c59085cb66c", - "0x8c8ac664946b5e69b4e34ffaa486b745ac8afc8ac702e4a4cc36c59f420a81b31ebf8b875b1f572dad8e4ef1f547a1af", - "0xaa6fd75ca832fe60eda078fc81a1a529364cfa8a4b4fac071d89e33cdbafa7d88ff3df611720b48e6fcdca2e3eeea0da", - "0x8d30857ada34991ce6faa82b4326bc353691ca32aa25511cf3d52cebefb262d6db8d93521020a2d11b3ea085287ad54d", - "0xb78bd8ea8bd6a2fd5741228502b9777177039ac8f033071c82ae11fed7f0a51d8bc64fa9aee44df25eb4b3822d571144", - "0x90904aeb1a99c4818ef21498a583848f4d1ee9253d70c10b03ed7d669b587f8712fd26d4409f00fafc3e26b5d72b4c5e", - "0x87cc8ebf78ff2ad752843792e11aeddbfdc628e03e13e0db598e08b496313f463f481f3a17ec889a3acfd128fb89aa81", - "0xb4fd122c4830f339fc019da6372286d3a0565ac04d4f5ac4f28b2c066ed507316e1b7beb7b552f60060825977a2db9c5", - "0x86e709d48d03738ca97d6140f13effa03137570c43ef00469eb0310909f66061d9fb933fbcf30bf04f13839e36d45a4d", - "0xb4a595cdd219aff5b8d0f80b679e58d9a7ab9cc389b47784484704e7d2c5249981b2b86be4c37ccb11b9afbcc8070214", - "0x97c6bf26c8b28b982b7a56ff867b2f5785b37260b90e0ae680920f368478a3c88f4a47bc394c07bbe88fa1aa1776f255", - "0xaa48418728684c9a10992d1851b69e54529dbc3548fe46721758ac6b33f82254d56738b351d146268fcc56a9b7f05df5", - "0x962a282caf6f08a63aaaf7ed2146dd61d527144f3fdacf1beef36b34356df50302330598b8602f1447f6beb4439a1048", - "0xb55d325499ce03c9b1c35e6aea30622841aff2a2c225276d677338579ce83177c0d64d78e7d11eac657a30648ef702c3", - "0x8a91b9296e5633b3b9144f61e5436654cffaf04623a864ccbcdd21c8f981618a908e890f61c74df19ce5b6995bc358c2", - "0xa7b6b32333377df24c0b0194393a1487a72a8783e06b1cd00ce6bc39337b34ff58ace57c8dee5b7f0ea2c9a54048a61f", - "0x97db4494e4208c9f297b484cb8159e8f600c61a44e1d878b07d29f0406fd32a0c12ebccd42ee7ac4c0bf33ff54a582e8", - "0x8697bc039265f7b6e73c133823dcac9041d18634c68fe16412b4af41286a4164dc86f7e71ab7a493223a84e185cb6f1b", - "0xb18a66cf37f93ca0189201811e7de02ee029445132f0fd4209e5efbcef46ba6a28aaaee42b30cc7e97a25b08f4bbb43d", - "0x8b69f189f3cfc34cc3968a07e13d1cab0f5c7e093027a9fac38504acdf12e2defced4261a686a2fc850336187e017957", - "0x96afba402124d9ff7048200acf329ccb4e35dabcd609e62d04d25140729e110a674849037e4b8aedfc99c889b132cfab", - "0xb75a809fa3b1c17139962bc22ddfce47d38d017d585a4e76ae1eb8f02849551ff7bdae178cb4546067bbab45b7041ddd", - "0x89196f1fe0869f2fd18f5c01118853503d71c4073aed8bd9cfaf694ca4a9e87974a9ad6e37449bafd391a2045ef5cd2b", - "0xae52921b5d8eb5df7d4923aed1afb125cb98aa6606f8cbc2129cfee56ba3cdb7225a30d98ca9271cca67fe39c763d508", - "0x99f1cfd27833fb64905f8678a532aa984329b2369ade3860025ad334131a9550214297bb2f7d3569eed7a9cc558a5922", - "0xa77fabcb76e8c6ac2a5196666e0c75c7f6c73fd8a0a5fca32a454a9457870689c83f5821f90f28dfd91abc3bc62ee761", - "0x92a4b97b7c14ec14c74e06363b0ab2e263d0d7d84125e2cfbf659bbee996a4d8561992e19789e507f4c24e5afbb91b2d", - "0xa2387e7857600a93de57faa0484650289c7553b9ae5fb001d011f43e5bf31c010c9c8b5bb82e7000465b546236e79066", - "0x8641b6f2dbe9f0b83e0a7ad8098b0836af158fa2ee6ff1bcdf3e2ac8b3d25d2e5a24d515e9d549feab4e82b49e468fa3", - "0x937306770a47ab2d5d2eec4bd6d9b3a8ffbb8c8067504571609a7e7a85c665b34ad2662701b67858e01530907172768f", - "0xb6b1b89f261e56b0cee15e2f5284c76789db26a6ca4762500745e260bda40b00b65add4826be6131775202c8c6c4247d", - "0xb1caac20a1b2aeaf287d38d42987e2c381e74495d9e880eda3ff59821d5974d01c7e3c611f4773a13ff41bef0f2ad44c", - "0x81ef049b849d7b0a732579299a86f1cfeb85f27ecee4280066dedf6024159fd47f311f1ebc46b58f63f71735a05480c9", - "0xb3b6b657e64fc154eb33b6056b8279ef736839b56f2c8f8ca438cdaceeb5398b8d3625676cd393c196f664d7baa3a615", - "0xa450678001e8db1ebd8fbd5c808c99945bb3549e834a346cdff316ef8d3b49b818cf9642e5b8097181cf40583ce901b0", - "0xaf3edcbfae3c8f368958cd11c95df4682ed10f894f770783e967fac1eed533ac427c1d4eee51f968ffdef080593ca262", - "0x8348eee6ec1102884929736d6768477029961c3d6d09e9ebf84d2fbe55c0501165f274fc1c0549ab831388d431e051ef", - "0x8d799492659dc44aa38262f8a4ae37b6ba6eb10dd20481f652a1c77ee9a4529efe042ea873c13bb2ba3ec4792b167c14", - "0xb4d3962f574c3298ffb0958ac999367db8207dacf2ca9d563cc1efb42fc889e19b7f00db15ffa91d145ff05eed97c3bf", - "0xa3a7c0e45dc8ae816d8765bbf097502b56651c0c11a03f476e362b64ddaee223128defbcec5629f4d7f1f9c3e4cb9f2f", - "0x951036c2878582d84d90dff79ecaca673df4760fbf9e09e63d35facf3e3257be6e1bd504f3c3daf8ac1e91d306e80d6a", - "0x8ae85094b13d349e60c8f303550cf4b01e96e24fa3a9f12d44c9822c004f1b3e9cbd772a2b4699e54023176074778993", - "0xa7292b61d2667d74cf62a47aeb559499f19dfab2a9f41f16e7b8d6e77909457eb2aeefadd9d3d3f6db18a438ae53ea0d", - "0x804310f5d2ce8bcf9095945f931eecff79f999ffdd24abb9e91d92f6e405decccffe4a8d9e731c4553de79baf7a5dd98", - "0xa77d3af0fb79b6f5b6cb640d04f4e13a28f8aaad1f60e732b88f86de547b33117386636d1afc7bfb7bd1d4e527812365", - "0xa431f239ffc68f6b1ea13bbd45675f0323cacb279e11a14f664acbb15d1673b99cf3603b335a100a0e297c305d743383", - "0xa64f4c28cc36b86dca65359cfdb50ed3dcc06fdb22ad567c7e0f833c880e76a53c330720fc2b96235cb0638394bae41e", - "0xb6fcd2c047de58003e9af3a416a2cdb143899441d82c691fa46d89045a12d3b087ee4603b401287a0f2629154bfc9bdc", - "0xa06e3b863bd183d8f91dea6d0211913663b3924f1e3476cfe0f328ff7c388aeb8e5c97757bcb56992c104ce0ab6ff27c", - "0xaea78204081cf5d24162686a824ff8e72fc0f88388525d646af7739265f60695b7d80b53cd1ddfd046bfcf59aa25f5cb", - "0xa89f556d42541a655864adcc1d5d67459ab488143e1b4eb48c67af30a8e753541fbcb479558ac26e1fa498f74a59025e", - "0xafc385b6b08c355a05fdc75e9360f4ffb384fcd74e8c9db34bbae9e0c67e0d1fa7efbff4160b387428ed58e129fcc027", - "0x9428d05e17e5525fae515e1ba3f04742fad1a43baa2ee166d2f9431dabb46895b7345ad833d495c99939f0c57cbaf1c3", - "0xb7a62d36ae55e681d48c911e1a433b568871c65a97916f939bfd638a054d6f1136a78c96640779ce1e3afcf90e3bb23f", - "0xa45b6d24930d91fc610e57ee78c6dc7557cb2ad976cb92e2157769447cd7c9a6a040f1008be9eb5dda2a7b8c9e524774", - "0x8b24eddad804790df3ed82db7c0ba05082c61a81763c44c98ad436dcc7e1e89a2800ff9c2deaf350f6222cf4278fdf9b", - "0x895409dc0aba4d29ff322d2414b33c1458126c023a3d53b25b9038bb90372b7c20d3e9f6b791fcf8f76449fa0aafa758", - "0xb22767ed218b575f397ad8306ec48fe07e8dc3a9f2f090fbaee411b6ba673a1258785d61adcba007d748cb019c458fd3", - "0xad4b9e4164010c4ba05a23f9a46957c8625fd4281a4e76f76ef7b4d6040d2228dbd2e6faf22b4a966ab42f32467a4655", - "0x92340f1051f88c25a915d0504c1413146f37f709ab060e3859b14aff9be7f8c91352dcc3fc866910a84192d301029cc1", - "0xb4e19bae926db3e1e295ba856984b32b796d86cbc81e81c6978e989f5331f27ce9004f90536a741ca996d19f998541c8", - "0x91502e2a69aeac8e709553501311b4392dea3d5b6f14e7523bf780b8af246e1f2bdc4b29fc4ec3ceb725fafa31bf51e0", - "0xb20607db1bdd6136130ba9683d581f5f45d8623ec4a2d35946723e0d8768654bdd9aeed55ba38303d8d1e312bc4f2442", - "0x8fec23ac3b4cde8c18346dda1afb2b72d4af1a6c013dcea36cd8cbf7223626690ce933b920bd9137f673d0985b64d54f", - "0x996bba551ae3b76c5aafadfadfcf80fcb554ff26e6a9e14e60440b3864239129734115d11a89ba79c19e452525cb5a39", - "0xa632f25ec68f02f7758103caf613511a1fa2e529e0861f286b4e490e8fca6874af2c13e3aa6ca97c63f3c621c197ae24", - "0xb332292c6213c7216bb78612457de615da878619024626383914f9c28f835f1289818514038c30eb2bc3566d2da470b4", - "0xb5bd5ed7e990ed8abf7de268aa1ef7ccf5562cf9c92486c2472051c1b5506bc9e72594380e7bd00c91771ed4e9707851", - "0x8781393278ffd5c522ec450220698328e60294ae1e35f60b25baa290a125cc47fbf7435eaf9b22ea819d431de0656f38", - "0x80a308c1acc4363f9bc54e6831c5aebca2b2af47d699a17ae2fba24495984acd4a25c7c95b96aeae3027f0fef9549284", - "0x94a55b36389e05b848c6d0e6426a400d1596195c2cfb4a972b6bf8abde2cf86a932b769a90b62a65d0aaf388e66d516f", - "0x8d29a5db4ab3a1199946a79ebaee9de225284f0523637f90e4ac16fc609dd3dd5a71072c30e869fdf6f057b7806ec254", - "0x99caa565547b13953b91f0468b78551784d947b5a3fe1b7278e4a45b294f074a93281e9ee084647d1b24c83b39a0cc90", - "0xaeee1c88769e7bae12f163a056d19b0090c7fd866d451963bc855bda2736c41500bb97a8d72a1a077357419ca94bc3a5", - "0xa94bd8b793a57b4fd79a84daf1f7fed5820bfeb44cfec0248f6aef130fb3219e1bbce68a6a55d332b124e1cc55224c51", - "0x8528607774d780b31417bf85fa3e54a94e4ef6e8cc233ad2a1dc795c68c299abae209c46ba77c33ba74c6ae75ee004a1", - "0x930f2c302a87d6bd159bd6b4db43212e7c806e17f572277ab14dd9715a435bd67b3624a9e72d9a2777f9b2080ef5cc36", - "0xb50d97fd2fbe60105dd1dd44cd12d8ad62b8a3127329f969be917fbf10132f1c6c6fda8029deb990fa1ed26e8c220c39", - "0xb685aea07aa1a45941f5eb2a593c0d97ecb5a803fd2977783488fb00fe6580c41ab83ab6cdd678704311c5542129c510", - "0x8cec65b68f4b3b10d032d39ec4c448e6d76e7615560bb754a53c4c6929c2470a884e7d39d9f3e58a2a9f121ad4175a34", - "0x96279388cc3e91dba49763ef50faa7550c3b4c277b2a0b0ae3541a2f990f9352748db75755a7b13efaffc9b8df40c74e", - "0xa7599c33614456b1b02b57921cb76b01109811a82f230f9e7e82675d57757f06021ac3f514d557ed9f2dec025364284c", - "0x869684197084f42dfd95350f8a54b0c7d940ceae2bbe49ec18fcfd178b6b0d21903447509e0ef356aa3d2aee83701bb3", - "0x85e9ab73165878b93e0229e3384f048e9651ae29980f9c5e26492c45e180e09a3af9058fada434d1c398b43d99d13056", - "0xa453a46ae96e6330c1b315d1b5f37d160731309d49d13d6c38c5d7f0b4f23ff1d18c985c471564afb54e4477c5d28d19", - "0xa5999c704320d4468f94d647d83c9e8720c19782d2a03677143c7216dc434b3160d193389b0115dc638f6e2e12f2d441", - "0xabc7a466cd848304616b2eca049c4b7509c5260c9236dc1432044ebe3e912afcc3a6ffe3e27d5d79d3ad4636ecda09a4", - "0x89ca07faeef1118c6b840a2c328fd32a5709b31850057302a7e607891e11f3f9f62e4fafd420564ff10a35b9a44c0f06", - "0xb0002f9d2a8aa850b9f22dd8d3b7881e8656cfc53e6c2ae6a913d88f6934e0062f30da2702dcebfbfafe36785203cefd", - "0xb8527c70bc791c87f5fbc67e2856e45b7254c5a0b673d4a5d3e9b79fe0715b608a2f35d88a61eb1d8d7cb615fea650bc", - "0xb9be558dbe778ba11fac7080789522fc004510f7b740c42023d850946933362a173267106aea046f338533e4cb29aea6", - "0xb021f9e635e64d3c9b4ecc8075fb74cf0e5727ecbacad15f822c8608f0d981ad2c300fe6e47c6148a6b1a13cf920d85d", - "0xae59f2a83a1384ef0b5613e8843cc9a934f7126430df7cd7f5a8508e3d83aba83bf3d18be7380570b24ba0e00e05e0e8", - "0xb403e4d0495a0137a710c43393798593bf131cb8d49beb0f3b3d344554dfc3355ebee14e884f543bb94bf9aae40aac59", - "0xa73b722287df7558c503f89d113fe0c017765c73181eeaa9ebe6de5c8a15ffe76fdb85ab93051a6f565653046624216a", - "0xa7d1a28fe1d36b17e37cf5eac7e27549ce9f6eddcb36203b58797d3372371f3b195cd3432db54aae4bf99768969f5b60", - "0xa3447ece13c415c457b899d4a8b8ff388ba25bc920b5711f8687cc86e9c1b3f3af42c490ec6352fa8609b044e642e3f3", - "0xb12f2ac1e033b6a627e7f7822317f629c896c8f8dd94ad91512855882dbb10b8e80a1e29c3e39138402f1f7e0de673bc", - "0xa7c65988996741bf59888415fc2264495050cb13500b6597d9d0e034898121b605784f681962cfdc80b0af291c316e7e", - "0x8c40cfc07dd7a4bcf514f2e87a1830c911e8168b0b8531a2838d2a14e790922b76c4642ae237b7547d8a3625decc7f0a", - "0xb480d70b57434467a40d6dd066f51b9e637abd2f49dcfa6450460aeec2bc895347e21aa82baa1bec7589b6a5a694fa73", - "0xa919a033c24e96af1eb0cb1ede3684e9a3bc338c7ef37b67cc9e9982586f74072cc540981e2d1a2524e99144bb21a64c", - "0x921e0b350907e9993a596b80f827b2d40aad60e9c62f4b65a67d3fa4c0acfa924c93352dad6eb3e868264bb24904e3a9", - "0x8d5419cea0bfebaa9c1509cd748c8af3869aedc3ae27fdbca3a0f08b3751a3b870e8dd3640f4abd4b46a2a1e745758bc", - "0x8b25e6eb600de81fdd03584fb9db9a7bf4c154ef1482553d7bef880bdc5baa7b64abac6db96fcfc4408329adf8fa351b", - "0x88cdb72bee7a6768b7c24d124dd5e8b29f0c866a0624e5a7c4759962ce1d71de7faa97f7baa56d5f51e35bca43862bee", - "0xaf1d59add7df3b3ba234b0b4f758349225b9cee65691c102294eb7e6fb683d7588fca33ed97eda361060253acfdc36af", - "0xb19370b8fe123f1dd2ea6d5bc75e151b0d1514224f5824437166fce77ac41ac5ecc1e7c1e75b75e948acf04c420efea3", - "0xa1ebfe84f1c012524cb475e68ae6c7cec79fb3372f1380321a0e306d15828613589567efe8bb5784360aed568e26db49", - "0xa0f964e3cb594c359e2308defd3eaec476a638b6e1c216157009e11f7c7d0c33fb9e62c4243057cbca49ba315d4b508f", - "0x9391e5087374e45f03d36f6919463c473938a653adf3880571850374ef0a0e521b25ef84b6012a19a02ec88f0ca3891c", - "0xaeb86d4426d2836e6e10c3277583a37b6684ba35f4f30d2d073043f0a0148f763b99fc42c3935026b56c32e5cd0cecfe", - "0xaa98c07dcfb1b0a708486d83763511c7004896856e851bd83d25a9551efc28f059c3fb8752ece0296964e8c13ec829b0", - "0xa466fd8dc1aea7022a86e12a119b16de35412a1b461680f6a1cec408e9b9c1418a8e406fd4a5656c73488adddf17dfba", - "0x8c9b0e18a033c27731fb3d22b7c83ba7a86fdc2234e8f2a19d7659aa67bad7a85ef25264e8eb81af529feb3fa9340ef3", - "0xa371feccc2f1a1b96ad8a9a7d8db0c06fefb1f2800933134299027459b0eb8cd101b9a37c76c22dcbded01a74b13d465", - "0xaeb34fc2758d8b68d17f15ab3c299344ed630f7351c498a5fe7986f7e14d62e74ac9a8f5d2de7c6289771210539383d2", - "0xaff9e961d0acc71a077e3af52ced373bc694f9154302abc908710e500e908f33bdd10b3c41bb8fa8066758a18d64c667", - "0x98bd5a8751e598896e9aec90649294934f81c36d2d0fb60070e9b96eb47d0988f71d9b68f4c475477eb4c996a9265c13", - "0xb25a92c6260f389f6443a572960e0a52ab9c9250d8760ed148082584b2347ec7d103358c033266bec02374e69d0102fd", - "0xb876968bedba7f4712f5e5eea605c1e5fc40bc5773c61f08c32e0c0f3ec575eed3e13e48809983153beccdbca2123edb", - "0x8c4091ef8946c9b27490099d5c0b47c404b5a1113500592515deab1c3f2778bbe933b09c9824a3a7ccad2141f9b5dcc4", - "0xab85f95d318ce235929531e2e397d09b9906c58958fdff1209a514624a099d3b8c103a51b2fcfa0b17a8f008744b5d71", - "0x9016714cbe49fac5e7b3e493574078c462e18f6363f413270c23da6327731f71e2dba5dbf1da6bbe0e29f57f0c33f869", - "0x8c90df700c0e2d104ce7b76be7899209136498999f78195cd888aec6f069778d657e5032ad7db56381470dd1f519dcf9", - "0x83dea8472e8418aa069a0837a5c44835aa1e00979a217f6295aa35548f509fbafc7db5b31b8767621e4f89957892e8f4", - "0x80a1d673220144973ab70d977b94cd3d6b8fff7f82f23bd4b30ea393952951d2f07c24e6d411b2ec19f3bec13583d9fe", - "0x804864b58f9747bb3ae54c588dff46eb6e16b6d98e0f711828e97d9f019297b743aa2202f823e3153ef5bc4b95da3501", - "0xb08eaae2eca2c64001e1da7d0e345f96dbd3e09888f9ab86f178718ea5a04321a8b8633e72dea68cc05687042808e3b3", - "0xb962f91819dc570c2cf131b89882fb2a44a999b94fd1ea8b83f400e9b66075a35c89f0fe0e8dbc3a597cdd1aa3135888", - "0xa5f33e8f04a2d7aab44e832f8ab4640519aa4ef88b58e0a398e45347492b040043e494de4b355f07cb4bc728b67f1ac9", - "0x8ed80bfb4cd15bb87175cff427c6a1bfc3e6292bc5c2d04dd42b497bc068baac5602d41366448ee7f37d85a5d8437750", - "0x83441e746afadf64583571a9918ba5122ca987e76a6e37f98514b1a8a178380366d10ded5c70d4feb08be6fa6d4bc25a", - "0x8807fb8adb2aaa6833960f435ace162c01a9cd0692a4cf038c89ef7405600868efe7bdb3e8a3db48901367ebafb0a1c0", - "0x82c64b1f77fb78dec00cab089cb7a88ae16c72c94d0870bc92df11587feb62277eb941d2f7d3d2fb033d7bfee12013bb", - "0xab2f1e3f1fcde3b8b2c07135acf3a492ae7675d9bc971ba57e06c99fdfb39e1f68d1c826cd9bba872749cab375e44009", - "0xb4a25f1f5a2aeabc29870ab9a815721f3cc031ab1a55417b457ca6504e5e96e4fd0d2d364ae17738726c8f40cae9c36b", - "0x9519efa4774cb4de4ea834376d6213d946fe6882e2b36342f683762fe50d754765dc301569a836febb2c7c9dbcf44f64", - "0xa75de0d0320e8cee962d6ed4b07db718615e75543fb25f0d28ec5e76f56d72b18d648ae42d7bd3da18f54ec1e4497a08", - "0xa2a17aac11e732097b25c0b9f7b97d807dd78ecd33d88aea5ee0a46a42198d379a241e888ddba940b3307e9c560ec45e", - "0x936ebfc2234d46282ec4de88958553759d766f682d6f9669d2b77a2cb0cf9cea9b1ac02014ac3f5cd47dc5d8af2da314", - "0xb33def3135e7ad61a660ef1266d61216220c7e0bdd867b727ff3deea904072e33a195e4febe64ee1e263349fc9096cdc", - "0x94337e4f14752676a703fab8544ea0ab7acea0ef924b85b05ffb84e4476f1087acc9a6d6250893a32b82f02651a179e2", - "0x8f22942bbeca0118747a22d0aa13438e40bd6a383e310eafacbffa1490f5758504da4a11e6320e1c55b3daabc72c63f9", - "0x86e3ed934fc613d0b3269cf368e32e67f4add59e4dc1ecb1f016fbdc6c53101c2435f95fc36625aa8c69c596acd9b0bc", - "0x86f04807460e1d93f8eea2a284119d889659b5a6b124d41dfb2825b31685361e8163fc3a253a49cf878e316463c9ace8", - "0xb043b2a99b94661ef8b270842fe4d3c51891ec23ba749d9c999982553ecade6f658242b373982c9a3669a886889e4f33", - "0x8b6a33a68ba7b5932ce11b3f0e23c3da580510fa37668f2154c59c3bf788dd2276a2a8c66a6bba1a68084e8b9bbf378e", - "0xb54581c88d4880fa4a0ec6d3c17b6f0ba339e8f7100242efd3b820ac942d75d1f898259d6f1e64a3870fc301d9dea2b5", - "0x9449dc9bce23c7e3b41eb34789dc7765c2f7855f9670c1d145bbd1b2d1b47a9318862ef3738511b4f89cb16669c0af18", - "0x926245ae9d4eb213ebcb88ab2d7e2a7d198557721051fef4cc966cd11be3490a3f83d4ff48f5fb60cbad9c5de4b98d1c", - "0x8518dab07ab15887c68d0de9fe3c0c09ea6bfddb99c145b3f6ff84659e7799da93e97bdd17884b228772398caa8c2ed3", - "0x9969575cbd7953b6308391e9ce2cf4da466b3e730c9cec0e88522258639be35fd31abdedd94b445d7075919482513103", - "0x8b1f28002c19b17d6ac1a6f50afc3448f390b3209b1a76a9a024ceaa274de4588ce82a891a03e878ea08747ae5d98211", - "0xa611963d1bc45b60ffe6756a743ab379e4022bb3fb263f5f305a615c92432199c7e1060a79aa42f7662fa89a0812a4d3", - "0xa3c7706ab74e976464fc341e5a9f7284264c1610fbff02fc36b88e15d6859fbf40fd8c5e93c8237b97acaa0900a03764", - "0xaa623fb8892dbbf4fc02004a44e07c21a422e5553e4b02fcca24dc1f416a54eed36f2f7376dc1e66218e850772676e99", - "0x8133cccf10b1686bf53143bd3520515ec72e7295f6945c43bcef7304de597b767265a3a9f7b281fa353acbc3cf6997f1", - "0x852e4aaf4da9dafc988d0da13a7f31fe8403f6bdab88dec363eb8cb8d3e64c48ff34102f6660642749d11d69b613f8de", - "0xa616028c6cd54a6514fd9f7aa9ff13000eaaf39f582441f73a3ed8208a513b580eb7874b5cd0b1e9a542c40c5887bdef", - "0xa48ec58bc3bd4b512c21d3d55618e9c51836efa97cad42bf79e748542804114714db23d79ad03e410e0989055c9bd46b", - "0xab480f3750420119ccfcf8d32c4a18ca580ce88bffe81433c1d6999c221c8aac482de5c0e41a5531806bd17897698d6c", - "0x8522bf3b7157cd29e948afc8f479d6192364a11f85dd5c58d4ea0443aa6b655f55a80e6a3152fc02a8eea4c0815fcf19", - "0x86c91a6021e738103031c1ece906ff43227eb23088e5ce1b6a1cd58664d4a80d7bbcb0d56c3b0e02cba1e1c2ca22e058", - "0x8ee51a59ce6becf098256e19c9aae5ef0c2c9e66c587d9a32cb4ba1ee0b64c13e2e008908e35f43314316508956654ce", - "0xb94766a0fb91c8de2338a68c4ab08ce5bcf62f6efa221067807dc647b595fe5a342d7122111540a1ca6ea7743b6ee772", - "0x83f917b8f6aaeb9eb2eb742546e3f2dfc9cfe00cfec60051010113d55dba2421974098c157dc2601902d8f40bc84693b", - "0x996e489890dad3c4dc35faf53d870bf1cd76f1dc24e0cc8a1f899bdb44e89dbfc77fb11f7b33c270a1394c909f7a27f5", - "0xa89936283190b2d1ce8d166b36694afddb4c3df01bfb1fa7bae69c55d1acb4e68e5e29867ea33eee8031029b3c6409b1", - "0xb08e5a5d6797ca252d12428b2086e528a6e5c3965d2e5ff2bf83bc71ae9c0346a4ceb3bb2f2e3f8a1685fc343f36997e", - "0xa05bd12a7a6d52d234a1b3e9ddea7b18d6d41026a0d18251b1761f1cc863064dacf821707cfeef2dd1c02536f584ed94", - "0x87c638feef9c88a9f89d10b56fe4bef6406c1d734cd1f01006e2f2b331196a49c7184c10786e855b3de8978927df42bb", - "0xaa194f3e4d0fc1d3107f9564b13e6274bbbfc7b8c1e73ce6677cc66d9319dc34b5a0e790d6d44c614c11feb50530a252", - "0xb2ab7be7ee9d72d1015e94d006020e758b73f200dde81e89e52cd33f25aced0cd84b8c300413d32565c253edbcd2fb1f", - "0x8ec08b22265aaaf27a84a6cca5f0875a3ebc70fb36c4f5e59d60c55bdf2a4fe11ab7ba4b387f5d668e67682a0978fa46", - "0x93643b9541db11b48e0c84caccc8da9ff7696717aa176ce6d863446ef8d887f3159b0ab6fe1f79fac883a371f6736e93", - "0x8325654fd8388ac96935149165fa3238d0848151a04be57f2386c3304056013efb49febee0a871cfc2ee3c11bb029042", - "0xa2c15cbe5d5167f55f2a454390b61d99601614037fd67fd198968531ca2f84f3c214b971ef300a20a114fabc6c67db0f", - "0xb40ed63b0367174b5b4b08396afe2385b0f75ec2569fa3cf60f87e1b17fdee888dd66057be2cfb185e9f32df59b7a8eb", - "0xa466d2c8052a115f121177979620385bb07148e202631979f4ffb01e7e0f6fbce28747df9bf70b2168653096aa704fbc", - "0x99395136290cd020cfba0ca896642c245182e2020ca2299be8ebb2f62e2fc62fe0be593838f62681f6632fbdffd640c9", - "0x8e4f081d9a724bb54fafb66297a32f84687493464550c09259cc6f8abf770d076a514ae1d6726cb29349e27ef69a74b8", - "0xa8d5c941e7c03dba0232c763590e93e3d99fa519b0a65996d20dd20deed1d0192738f3b339edac68ad42016223733582", - "0x877baee9ee979be8ce3bef02422e57799dcadc34fefd8bf2baaf945f267883f67211ac5c06246f7b49f1ea5c99550a63", - "0xb6fcc2a73dbbba54760d244bc13e1564a3c61097e9b525b247cc8687ca08625a7330fc6b15e45a3ee508b4d34853d852", - "0xadf720dde6e9b5c63e361d69a2ab46ed73e0deb82f8e30f27ca2b19c2d8fc43e18ac04b4fa029f553f8d7dd79457ecda", - "0x8956c9038f3338f541bae9ef1f5bfad039d532dbbbe7814e3a3d5442d393ea6114aa666559d8a7e3a026c758a17c79d6", - "0x8d6de7f95f30a5a4b3d441781c7f819a0265852ab78b8416227089b489787c8ae9dffbb0bf88acf1b4c4d6b8a29c1a53", - "0x81d4efd71c9d08e9f6d7f7d7a2fa5089e80cc3f8dcc685686aabf3b4c8bd531b4aa07e328c0fde32b638f23eb78de588", - "0xa30053b681ed8328b5d64587b0d38edef0e366a2762cf5068dae177e4f4084c4333f9a5fa5fede93db80f7a8fd5fbf57", - "0xb340ddfaab2dcded58930e5dc2b72cbedd0e79ef652f34356fcf72054a87fc2373bd3aaf8a88af8d4633f73dfa7d9a28", - "0xb9f3a7809be0bf834bd7affa2059d9371b848dd5e5fa93e83e90d9e078a2fd3aea64410a72457c32d33ff1ca11dc9300", - "0xa9a8ce26a38dcf277ed66d75e111b07348101e93d03f446ea72bd903198122f8a08569f7125f6d4ecaeda8c093a00ec4", - "0x81e78b705b44533e2e997f549f46723a5e6b88241d7a86ca20448ae3ab140e967347abaeb8700594a0cddf1e82285abe", - "0x84724094dae5b7ece30cc01b5f2acc8787de57dc0c37a437c3e8e26fc03069b6e8562302a0f1c95de85937f07fe63d3e", - "0x97a715861e5bb715a17a948d6b6a389b89744e8ccd3699fdea9ac3d890fad027b78d436f8012b0abeedd078a20ba91e1", - "0xb710b2e7d87771416aa34ba2d93a044bb118f279fff62c1224c150ebc30f21abff212019f0f38c334daa5a96598ab900", - "0x853034af5ad08c563ed096ab2d0590ea644d372cb400bfb03867092768d90b7432d35c5506378d001f986c59769d6d56", - "0xb340ab52f751e9d516348faddb45f0115ba0619ec9db820f870007e3a4d305ba2bd0b2a58a7576296531fb78886b16f8", - "0xb8ed8feff520009743ca3313899a118df025a61e6e03bd5fd27898a23beab472746ca3636c22ea3835e9526e17c06dc9", - "0x87af435e3e4ef611d6da74c8d98e8d3f3de64ac8748105dc20287a7dc866f57d10a2b854f7e0e09235eee647dae1ab86", - "0x84108b1f0f0ff73a179cb1be1b2ecb4268e7fd2fac3dfc7f6f99889c90a33b4310946909b9eef31b256b8d0e3ba56bf8", - "0xa6b9fe966293e60bd384a1e4d472b0a72544aba41b31172ac8bfc3e19beaf51da54a66625d73a9ae22c7c4d1b0840a30", - "0x92e82e92aa615e198ba3c83c039b0adcf4393b3fbf9721b2e47ab17a84bded2bc8bc2bfe257d2d76162a87e8bc7ce759", - "0xb9286dd48800606b7ff9c3fe2abf5c49ef0a6b981711b5ba1f62952d6fc4a9999bfdf061c4664a019120f15e341925d0", - "0xb5da5dbceaa7e82f30fa5fde88b03ea88e7003a50eeb53e3f3aeaa63aa586900525b42fe1b699451b5d915d1b83c3705", - "0xb06072869fb8526d3077cc61a3c55d54a7a1197bbbcc875aeaf617d7d1eff3dd3ac243e2c76caf57dcdfe306edcab4d7", - "0xb132db9ee3ed16e6d76db9e6e3dcdc2b142cd70b9582518bbdf5415b3bb476ad900d50004dc0ab6b87ba697c6314b4c9", - "0xadca92336f3546ea50b034525fdf548a36049ca82d9d3cec10073e7cca186227cd662d4d66673e7214a6ed58cf75da6f", - "0x81bbb3fa241f9514575fb3f6cba8e34301187681354c94e7976a4205c0bb238dab52b29a76a5f0e0d4cb1bc82f8857c7", - "0x91008dda2bb7dfffd6746e3544ef540d9a1ac7ee9c68ca9984a1d81041a18fa9f35b8c4bdb44ef3a860c37481d5e9a14", - "0x8224195cf18ca0d8f01521a0ea92c9c598c556746c825a4dda49ecbe324d570a96775eb81dde1d3a14aa3660d50e27a4", - "0x8b355eeadef5fc7cececee71aec3ed30349df8f43f25da1d75d62ab00fc73702b405fab6d422053c2b0fbc7469ace9a3", - "0xa4d657dbf2bb30c1e57e0b63960663bd86ce17204979a9ab82624943ea370119f040b58b067a05ff6d1867a22a58698a", - "0x9379a367c918b2be61a9a42a495ec03f0168a4ec36f753dd37eac6e9f58a26c8510ae7b579a89afdee1d192edefb4bb3", - "0x85b37bddc80754f0432573204a1a4b86a550bfe9689f6c710a61810aa94dedeb28763ece40f28fb3a6f3791ca4c86b8b", - "0xb41c3269b96e190e40cc16e6c7cc8054cd0b7902a43c69b79d8ce471a417d3096b2271badfcdc59deb6271ad3e5a35b4", - "0x941185020a227b7a995f59805c8900f7f6ecff1e7b948a8b714f85a54449a0d41e28db5e17874e018eab72ade20eede0", - "0x8a0795ce082f74e4633acb1649b52b46ea2b4360860fef6ec107910e245b30466bfee8ce59a6854f866f55ec5cc7bbd1", - "0x931fa63550530af5a7ee24964b8b4d0c66c2bd59108131f375c7de86bce59cf52890191ec8540666c895e832dc312360", - "0x8fb86918190a3455014a5cbd15c7b490d68c10cb7b505e9233b3eacdf52e63299d49ded75fd74f8c2bcb3632a9c29d14", - "0x92c896826c9d871a83c4609f9988cec0db6fc980c8b88a7baeea2856ec2a0a56c3d5a846a87d03393dea966b534aa8c4", - "0xa9d4c780c94384f5a13cab61c734836f5729482cde62f2888648a44317b749135b511668834d49296ed47c0a3b9fa8b8", - "0xb7c26da09c3998367063fad19340f53217e8545535d376815773e201ef49e9e1b6bf1423b0b6bb363586f5f05307fc89", - "0x8c445b3655f1f554c2a7f6f7d035121939a8987837dcb1a1663586614dcf2cf47f73633950d8803e2781baaac52c12c8", - "0x8764f924f41d8c5c91fcd77de26ee3bbb86d5a5bfbcc45188be453c8dbe4b875fbc5ef5b01ea3a26b889d7b45417f173", - "0x8605a8186d5716dd5f955a7125619bc72ff385cdecb187a9a646a4bdf6595d67f00e777836261f3a69c19d2e2cae27d6", - "0xa97dca2185e4fcd7583b1e695333d55f54edd751da436b8982de8c344b5f57e35ddb61ad4a611dcde08e287c78c757c9", - "0xb11c576a049f93e0731652f1a1ade62b0124cb7b4e2b13f6505206c27ebf7998ebdb3d887bed01e43ce5c24714903aff", - "0xa46dc516b8ab4aabe35f38af1236052564b01d66c558e7107175064a5226713e8550912867eafe4da133f56950df57c8", - "0xa13e75bca5bd3b08030205cef4faa56a49e5d7da94bc41c708deb2f65343c1687aff26368915a490b89006185f18fda4", - "0x8ef5135a6f1f635a4966aa540cb877dc98c6a88fe462be3226c1a270c82cad8e091aa49ad39862f012edb3c93d15fb4c", - "0x99158ace79ceed67b6d8e884050c6fb7c7a1509e41f0d2b9069ce8dea392f17f88303d0942cf3c0af3ea52d3194123a3", - "0x8805c76ada9dc7e57545a5e1a874e6105592601213e22c1601b0b157b622e51f004a1da754a8fccc8f2a2241c14e21a6", - "0xac3dfe87e17ccda6196f621008716a14be4b983d187265eabb8f5eba7268cf770a70ffa19d1c7e77fab0373eca7a4045", - "0xad78a31ad6f2c84f6e5348f33631d876daa3d5978f6d1a77db80aa219e12c9ea656e9c18e6316f899bbf6c2469cdee37", - "0x8c8726f8f6fdc40516bb64b6c624a6eb4caa931e3a9ca8ce2c31c282ad59f0624ea290b804ba84e339e83422070df419", - "0x9303d1906cf416a184e15f13cf7dbdca5fb296b078079782c9044b9cbfdf06b0c965305a8d88678b53f0a10220e56f4f", - "0x99b9735a77cdc1c675988e613b3e8843e2b0469030a33f5c14383803a1b20e328d45d2fde6ff0d15f6bc2eb8da4f4d88", - "0x892a18f4ceae3fe7cde8f32b84c6bd3d9ca867143a30fab4f939281cec12587929faf07225725bf33ddf154b90972214", - "0xa100a35a2865bb465830ce2f68406d8a92bdeb21056bcba28c0ce8ce5ddfec6e293e926d764499e53facbbacd3f72994", - "0xb797ab22a57520a0584edff499cd1aa1663d8b3f411faa542022c5f1a645a3f952f9164f61d200e4500673a8d95a938c", - "0xb1a457d100def2e26b2b30617ee866264a3ea649bcd9edc7be132f5cad02f3209f5dccb02b95a462b5af9a71fb88a341", - "0x84c1f6d4f29869a359cf89118b1a80224cb574393fb557d1c61730a1fb1884895c4cb07f23c52165975b89fe9d6f5a77", - "0xb6d53e49025bcd1d7960ce46d4f64ff8f29e4239fde1b19e5167d506b086152da3bc3b86fec8ea531a20afe1c785fa59", - "0x9635b053c03d1be0bdf81e9876c63e8541b793ddeeb2a8f3ab0e44fb78f81a9e61f8c68ce393c7c959b62b67f9724409", - "0xa19ca9ac5a345c96a607f979a958d83eef4350ebc9cea0e0aa11469dc554fcc39d9b22f8a3c92de599ca08ff4152ec23", - "0x8e7d45d35f6fb95799846fab51b0ff2415857bb54b049694c1ebf93f45167b8497c3341b656f194edd5804195a7c96bd", - "0x87c05c7d5834394507ad3d363dd0ca5132a7763644e354c3b7a803fa594d951084d37942f59211660f10098cf49adcdd", - "0xb276246af578557aad38190878111e804db0f29846185d7033c913a31e7657d035114448ddfed2f3d75c04c79ee01e79", - "0x868bbcf14f96547192053823e4f85b50fb988da5c4cf73f5cbf23953252b665ef7aea4421c8baec90522f58f027a2b19", - "0xac2be3dcb8082b64a3745ce0d2b97cf341483713e1bcbb37369123d6723968d3bad1410467aac7fcd3b623bfb1d90d9b", - "0xb1e5cf361e0857373814e8db7fc275ccc1dbac8559e8487cc892bf82d4c6be00d1b2ffe40289692a70072c5f80dbacf6", - "0x98e16a5854635c72bce6e263bb57c071194df076e1ddd81e645884367b730d4d557ebb8c74b3c582e09046d2b9ad8078", - "0xa0016bfaa348d44a3ef814b348f7d56fa83b78baeed4a7b58617b6f4772dfa990e912ebf91c2321307884be85dbf81fa", - "0x85690a2c5cec392b6f98cd2d03e4204cc51868612543c7a3112066ebeefd4304c5c8b21da44534224398648b413634f8", - "0xa3a1d00d0fdd8c8cfee153347d590ed78cce48eeeb7ad42032a95baa73cc458d46882d0e9707f3dd519b1844f236bcdb", - "0xaaf2774fb26da59c115a28d86f0c6320368fc6d2c0bc2b7e4516cdfce3058cb423b0026b6c75030ddace9ccb7f058227", - "0xaf507cef7320bd003526fdf43c04af46beaaca5b6ddcad835ae14da60a2ce732b453d8164553e95f2b776df55ddb5efa", - "0xb2656c07a8ba2a2248d0313a7795b99f5acc120648c08e3a77fff5cb9b861827c94d4f2f99a2f2dec1d1667ca3ab26af", - "0xb426b97a51f0439f2da2d0d934693aaf52482bbb48893de12fbdbed1b2155e30791e7098baa18f93ecc45f8dea4f22aa", - "0xa71a7e08426518ef7307c2a1be7aaacd843794601c0d49f4f0e474098ea0faff74fb5ae2bee416aab849afe04be434cb", - "0xb6d510022dd3b9ca35e93ddd2ae77877967dd6966706f339b2197d2891bf523b5d55b7cdc80274368333f9249b62a7fb", - "0x95d2f6cec1b4038f56c571ee0f5aa14fe5fe7b9a2efab89eab4c51a696d2ada549a42095245bea14d7f7ffc69ade417b", - "0x89147eec9de685483d0a5e21b877cb550518a1bbcba0ee65e9519c294fb0c422a729bb0f5a8c8e3fe77070e1a89fcdb2", - "0xa66e7116eb277ba900c06fa48baf274e2a6865977698a504dcc1d0c20f90a7030bb2a841fdbfaa5c8ef6d81aac4fced7", - "0x815053a8483ce2a84a34f81909bc3eabefdce59140f0fda6da77ec005e5dcfdbc6f289d0f0513efbbeef0358daf94025", - "0xb480d2b6320ebf29f3781f04dd88e835ad81d2c63b716f6f244fd2b113ba3781001a34189df586cd629e70c2baa0e5cb", - "0xa74281bddc3a93503a695f0375121b3bdf98db4b2b053eb2cf0773647f6f69d1d98a61efcf82e2a823035ce803b82001", - "0xb84fb99a6943447cad21bfe2b34dd8da43d349e53e85b73fba8a5fd0fe3f41e7dc629960b3325d08af1544e5dc66de28", - "0xa8d11ccfb0dec31b39efeee74c58536f29abb02d06dfa11acb7134cac626a17ff4e204d1d138a472c63c629b6f8406c4", - "0xb5017d42a2388d90bcf4d0b6e015c63612a0864b2b379e9cebcf2e869e5fd45d2713bc549ea472d77e82fa8750f364b7", - "0x83c8e090de4ab6ed169a033aa4ab84f7f3e2b54186106790b30741e9d87d9a5d61bd6a285447e0d1a8e1865ee618a91d", - "0x8db64f3a1680cf461f9afaed4e714709d37559071bcee52e13feb5627c1fa7c093fc8923ede3e70db07563d2d1eae69f", - "0xb6d20dce2f50b78b094949e64edc2ce1b077a3258692ecc2cdaa01ec19add246d0832a319bb0d4153198e3a35091d86e", - "0xa61e585ed55dedfad57352d2abbf8ab336a999a5abbaefeb5d0da9fb0d5bb791119e52034844ffeecca9655675d17228", - "0x8ff58b27196f589ce0d3461e0c00d695da47a79809719b4bd4a229ea7bc9319469744f2254be4469092b1a27532439e8", - "0xb5edaf7c3f9dad7a54908da0e7a153d69a6bdb99fde07fc42928a0dd38031e32dec81c864147066412a8ca240e7dfd0d", - "0xade064bb3f87431a32b361074a89dd280cc1160a57fb3cf21eea5066e886c7bfc3655fe39099a1913b8b53242b23b2ff", - "0x9169621f97887db46384b43ca24b1447e23fcf5abf141e70fcd1834e9d691b9bfc6e8059d060bebdf9922608593bb972", - "0x8727bb06fadf0633fb8137a54d6912cedda0bbeb0f93af97deef3490b1b47e58fdb37a972dbab1534a5172ff0c840114", - "0x91991b98243bd7c138bcb60cf703a9d0828f6791eff5c2c1c5cc7e8edda258d3cf72680bff2c563c8e964f87450a3037", - "0xa1bddb74f5892597ac687451b932449305d6deba20e97e10989bae311d532a7b72a3fab08dd832589e6a22c0fcb548dc", - "0xafc52ed64208e4beb029d1428697fea6add02210f613551d1e5ba6011c5d13f66ce26b3dd2a39b30186c566b1af66c06", - "0x929bb88a9e30862be5f45c002c11537780d151f9836edeadcaa4a617b0bf958046ce331e15bee646f9eeb4d9ff854661", - "0xb3376241793ab9f1732997cdf515b9114f88bb2c25c0bd3f3b22e5b665e1ae94fa3f6a9f88de37b7792c3aafddc682a2", - "0x88fef7680a7fb665043264c9733dcbd23e20628909278711aad2e54f2eb8fa3d07011f593069b6ba7ed312d9ddc3a950", - "0xb031434d514d0878b7011ce2840e23e94a4386034dce422f37fde539aa35cedad1511f9eec39fc23c7396f43ec22cf92", - "0xa4a32f1e58c4ccb2cb4ac6c2dd8acafa292810c77126844f33287c8d522bb8c32dd89ce8f7c1dc9a273165b0879a45ba", - "0x82e5b11b9fad7c7d5e2a8abf03943aef271ffa43ed8127dfd85c7957b59d7cea56039116edd0b0b992262751c347f75f", - "0xa650327144db1806cefedd1daec1de3164b77c02a0aa652371ca0401b50ec3b7a392ef6a80de6d4724892d71cf48eb07", - "0xa88d8370d88379b52bcaaf596c32faba155db4857bbc7eccf89b5d67a97ae481e53e81de6c9461a6719d179f3ffbaf16", - "0xaae8b3d1b1bb0d71f19e37867885a1fd550f7805fd1306881515d77e5f6a990e0bb40c685e350ed09eb4a55298f3a393", - "0xac024fdd79688628ee188a7a9d39cd1306883c260dbda9e79eaf2d2f57051b5379834dccfc641302cd12a8a24fa2224b", - "0x90cda91b9e71b7bbc091b9e8e28d79f0fce42255e24a7a3bbf3348651405c46d3b6e2e33c5fb5e99fb4d0fbc326f77a7", - "0x91325730bf7d71855ce0462a2fd0840f3753964b296f7901e1ad761f48fd133371fcb805c315c4d8cb2ffe34e98ab9cb", - "0xb9e1a298ce9efdc004903b21e573c113c771b1bb5b25e2e88baac6dd7bded130016b2f09e50a4346c59adee607a71760", - "0xa703a60c430e365bdf9023c923a04fd9db427ca0da2da8dad6b0f0d5d352524943839d859c15dca64b590ace6cb1ca89", - "0x995a5ef468a38caf66d58880245064a7c0ab520ebf3c9e9f956110a9dd68658baae22ae274a48d59464c8d37d2f8b643", - "0x889c6e4516ece0e0fdb8c99aa482f367f2cef0ae2ce0987b6b602f6c856b02fab27114a6f4b82050738bc98a48ef5837", - "0xb432ce5f638aa48ba952b9c2e06ce822d531c9a24011d14650cac0722a4c5ad1bf22109a2f429cbdd22a567ce6f03094", - "0x86fe41234d309118d1256a9ac79b7bf01da1fdfcfd579b655f31b7c4cdab6f687d46855d56bb11bedd4b0be17e892b2d", - "0x905ec536f23dfdcc4f8128fc1c00daa877eb3caded7637dc911aff0e6279eab12f1748949e4bf015e4f8e30626d3177a", - "0xb6b9f47cb82244d7b1102b37cb52f5c9336e4c05e4c90f5e448fa92444bef12d2fbcfc39af9e1fd05811f5f864f12047", - "0xab56e7c534ee1f921351dfed3f3eaa127869849b44540b39b0dc021b3dc4dc94027e4161f7f3ed40bf42a1d08315264e", - "0xb9c62b4e679dbb3405733bbe0740450e72ccf39bf953142cce65fe014f132d5af5864ad96167027012c98dc8b8889e8f", - "0x82b8036a3fb6f648c6fb0492334fb3dc8f57c32779d4eef78ac2becb0b93f046dd68c2fea3b5039c21ce8e1efefcc685", - "0x8525738182748d6f901650cc328ae498cc3c712300441042441f66c683e06dd741b644e8e98732552e55839b66f86b82", - "0xb625cca7bf4ce510f21e8197b223dc49e7ce245c5a5d1e901438eecf7160a0bd37d0196191b1d934779f4b6a387b6db4", - "0xb63d753d728670f3b63d4c24acc4a3d4859e5f15ad775e502fc50d7ca42b0d2484a8649eaaef9eb22cef28a23e10d5e3", - "0x8e951028c0b4c5a691a219a6dbf348ef66edef60796094d5f6abaff1ad5802b53a5abec9b8b3b3b98f8b5858672847ee", - "0xb6b71004d898a3bddbcf7f730b8d5c0d8bba0f3b508155412446732ed9abbc1d03a90864f4689e6ab207aed495830e1b", - "0x98f33a74e36c035d9476b198dbf3a75573856264d45313e5bdd89db291dceaf4084917a2242b0a30d3b1ba4ee3016c42", - "0x912fdb4358fe617d7981bf9a9986dade7fe279a0445d7b14951ed77eb88c77c4aff4162467e40fdaa9dafe78da0ab4f1", - "0xb17bdf7a896480ae70b3696cffefbca468b57493d5db59362dd85a3da296e1162356358080c8b0a7f3fde798a3ad1d15", - "0xb47ebba84e62bf453ab223496a892fea2244ba6c37615c3db31c2ecc16a5f9519dd79aa710ec1220a2cebd254f7690f2", - "0xb3361190434ab75e46a40e0ce21ccc251fd0139bce90664bd33d9eb6400317c3210509e4ffeef604c7b05b260544e19f", - "0x966916b3966d7d33be49fa4eba925aa2f92adc2d0228d1144ef633dc5d67fd8231087c488b492688fa142a8cdb45ca70", - "0x8ffb1491d4448af82b7cab5409ad26d99ef6ef08158c73a9ee9626c5a84d2fc6d852e2c786c94b47b5931c7194d5b82a", - "0xa2d4a5bb458688b8f593f39cce2b27fc05f8ee3985f4c5be453706e8f174d5a6585c2070c0bdbb54aa1d8e79b5ab40e9", - "0xac180389d0432699bafff42a4c3da59bd32ab1bd1c4b4a4829580577fb3c5eaf8aed4dc61a93262f23ac44255e6c2b11", - "0x87f8fe99acc93080e2a2ae51eba24f0b146c1355855a202dedb7deb8e1cb5c6ad8664ba0e93ded5ce253597fe015fdc1", - "0xa554d88dcef521dbf5e4823bcc9145c8ea98c598cab56c85f101ca7be82297dd7f361d457966bc69584adda6d40ecab5", - "0x86ee126cc839d869c7e91f0f8d919929f66c1f67675ae8c5eaf6bc35866580c49d45ec8edf0891b546ec2fe7bebbd304", - "0x970d74575be6cabcd2e33a8dacf25b378ce750478bb44086e1821c97b6b27055b7f00cc8ca189954f0150de7381c80c6", - "0x963badd0cac713d8a23dabb8ac7da5e9a83ca7d580ec81dbbe3e5d53c5c9370b86222ca685215eb282c8f67a977b4b66", - "0x8d2735c85136625b3f8c4196a8f892e276845ca7c876648498143f1897637807a9a5162bb90773099a7b0cdfaa292263", - "0xa1a8507bb8a300e1df882651b0155e46a0f58399375f4e5f993251663b5935a76a02e60999a4851fa082a89d5cec2e63", - "0xb712dd139d791a95486d8fe48e35bb8bbddf890435dbf8dbb670699dcfb143fc582d4bdc8a6751f6bf58a13dd8c2871c", - "0x8f108fcadbaa43dff904a23c89d809746a9f732be817c2c882ac3493624aa5e49af7dd9b46de7d9d01ae982bb78761cf", - "0x80e270c6620756d3d127457fa2e51592604f85479a1004d63c184d7d2ffe2eea4ff75faa436f24bd1494f4eaf90543be", - "0x81f039fce432a5d3bf9649ad0fc2d93de831f5b9c0d0e5fa92d35b5bf4a52c739d478289c2386efc964026134f91ac0a", - "0x89401011d51b6106855487a37459351f18c39f08ce90b15e52a876cf36e969a9c9fa6cad94a55b844ad45fcf1807f705", - "0xad66c149ad105ce8b53d38c410d73a3cb3ec910a9f0ae798f3aa5207501c7ee39b85f10e91b4cd91e6b280f3912c492d", - "0xb709445e56d02a558a1496bd2b9115d2635855b18984cfb908cbd54cd279d29ecab21cce704cd9ebcf34456dd1195d79", - "0x851059069d9fef4eadf6ba508ca330ecb7436ccb91d09f5d0416874f9fbcdc56472d2adbaebc63a63f190b6abe7650d9", - "0xa933c1b614e6d5a58c599b7189d06bfa3683995f449d636805c8307195d8e98b62ced873997898d6b1b01f6e6a52b743", - "0xa692ba436613db22bc30c3f36a516971158d5489bf2c50c39d0627a74048a6d0b229606823f37a0832913425ddc30d06", - "0x830999596d203b96329185c100bb7360189a50f7930286c36544d20e57b8418c71d8db331e4352a98f380c68a49b2005", - "0xa56d7c262bb3d443fc0cacb2b61f24554ce35b8984fa3418bb4e271d5fe4f5378ef7b12c6cd02f537820040bcee95f98", - "0x844a4e9a8c9eea0b6f929a80da9f4e4e273e999fbe182d3855b0a40577afaced6f8ea285595573e99e13b6a71b985c03", - "0xb34df6205fc429c9b7cec189b2634d49a4877f22bb8060b9f7baf8c2eac4e1d476ed1f30fff1f4c019c65fce96abc554", - "0xb3a97648b3b79cc513246d3d1722afdf7645e7216af233645fca6a2756686592635facec913d19acf99ee586436cb58f", - "0xb9cac906123f2a4aa13d5d7eaac84e84eeb0a1b7919526de5198b5475fb815ce556f8451c953bb0bc910c93c6fb3fab7", - "0xa5e441019d492897de73d31a44a0055fd04e8cac894d626d0457ffe9de5394d0bf851dc5941790cba388b403b86864ab", - "0x8e3081cc7999d91d787e4c0937c9e22c959d2ba4be6fa04eb97471997ef150836a910ef28455f117dd54fa9ec655148d", - "0x98eb793d88faa691ecac3a7c78b25eb3a833ccfd0275186a63b1b1517bd2b984d9908c84e55f044b31c2dc5e251d0414", - "0xb38b5454c2debaf1a4e9e467c6205cfe26d52d1c1dde5356c089abfd6a90dbae89525442419f108c7c8e82e34ec3d5a8", - "0x942545089077b9f27304d2d6ceb3d549e983f100417e88332bf05bebfe8d42b75a96171ab3bcd049acc859f3cc9ad1fc", - "0xb9d444777403590be63076b5dbd9325ad58c1eb244dde2c9628234b62ba74f6b0e956642af2d08cc65f82a1b2e24bfbd", - "0xaee8deefc7ac67882ed7ee6c01c08d7739b6642deb2614064c69ea38c5c65e06cf609bcaf7db74545199cfa6122f23eb", - "0xb3e476268770abfe0cd64a4f878c58c027ff352569d8cf571bb067368e777eba6c003d344746fd006c8bbd474fc3360d", - "0x858137d63f90f66b9ef2a38d7ebfdae1bb89e5bc1d9032c96d699ef276aa2d7461366c00de8c47de9231d9ec436572b6", - "0xa3dc8fe541c9cdf89d83753347d8c573c49e8471dc07b5d41bc48ad1b10a3fdc218adaeb72bda0f362c8af8e1194df45", - "0xac75940ae476a6ff07cacf70a379096786d10a5a5244fa5c466bdd8af69b1f98e97a3a27877739dd4b223627e0ce6d55", - "0x8c6809f893c5fd03ca80d845147a82d8d54bb7dc6a688733b1404dafc360c45d5ea742f98f6a70ac2decfcead05d876e", - "0xb0818eee75f08ab207832c591aa783193aee5742147eebf75cf7f1eee6a6d8855b309db4f7ab51a16ab77bf619e14fef", - "0xb339ac167debc92cc9132dce076bce281e7f1b9c88978d36e1b5b9bdeabc974af318ff142f746319681764bc4db191e3", - "0xa51dc040c75a8a8bc3b0ecef47ca313ae13d9560c782ee014257ee728a09432c4486a3f87b5ebab690231735fceadf80", - "0x802500a52dc271c52f893b620952604b79d25ad243489dca7cd679b22907fa85947c88dc04463268d25dcccc8a6c34fd", - "0x97b136a881f500b07e5b0b79fccb84b93dd108074f92a1cd76e441239041ff389dbf03483fe76cf7c22a5f40b73b51f3", - "0x9155dfb5d7f7915e50da7a715d1a5ac5b43d7093546d6d342ec8b69d47a86cfcb9dc11d8079f123854033b8d3e1ec928", - "0x9423ac1e11f70b5d0cbbae48b7a5be8350387460631126ebda095b3b33a7ee2845776aa20ad60e2bfaf975722d43064d", - "0xafa907dc76e03d10cfbcc226e50e3bcee56baa4acd8db2cef8e484ee7b7bc536e1765e764180663710c4396e22fb4dc0", - "0x8b6fb4bc641fe2147d3394322418e2e8e091718e3b54dab8d0d6bba687bc300d73cf1b17f81e2812f0943a8bbc1de538", - "0xa8bb533bf42f56edf112b03d34eb64f6dccd57251244f39daeb6531af650d0368f6e4a0f9171aaf4f5a5b4a17debeb56", - "0x8d763490dbc9a9b73bd571833afce20654348cd553a69678ec89454c4cdac044ed3ef0458cabdb60ff35af5e63405961", - "0x8d3ebac80c55b7ce726f4cdac41c7e2f6a5ff4ffcd5f1803c463ae524243f136dcd15f9bc74f8b271ce90a4776c94868", - "0xab63cd85311fb9889041e692bc9d5c1153b26a805b511721154d28f11dc8ab84733387fd20cfa30c566ab2f8e066af4c", - "0xa506ba11063b14f25c26c92667dbd9eb67c8585d05d3980284aa19a09ae97599a1cf8d7cf45b70a32063f1fa3174d3bc", - "0xb834434632307602d9e046de6f625af5de673996108911c6b05d6bd3e2aee17246b2d860f01dc2d6415fa61c73110e13", - "0x8248b69f51196ce1e15fcdc25d487153896d1f74818a5617500cf0bedd5180028e6567533536919156860e34ba275f1e", - "0x86a5ed8b6a1e9d8d17b69640220bb80c9065198c8f7610d4ee6a60d2d808508771a84d6bc679ee4db34f43f94315e0ff", - "0x8fde55abc106b2afdac3b8796f83c8ce1b90405532fd586d349340c4d7a4f4c46e2a56fe2663fba770a8004dc7b9d523", - "0x82489db9dccdd13293499194068bb4ee8fff51f74f1b504d203c5deb5216287a6d614a2e0a769d4c929bc103582c92b8", - "0x82b2d71281cf886e80e09ff907c1f9213dc444c058e965f964bd17fd36dc0382da2449fdbc3aa7b6d07004d6722a5848", - "0xb0729dd38dd64c441e81a94fac0c8b5b3588081e43a5b0298bb576b16a9713acbdf09b9bc2499c677064619cb3a172c8", - "0x97c4bd5c97182e80f55e82648e387c4a3362c6088381e96b67cf0f04bcdac3dc670890904180a5388b97002c70481235", - "0x98d99f80ae9c59c921c6ff71ef01c2ba283f531ec32666cca1fe7dfd9bbfb09f197e9112af1761068cba8d6319af5d74", - "0xb0569d892ce82d87a3d809f4c86a88ce627ed420dd106ae49b88b8c470ddb081a3dbdbd92d7fc032a7082650e4197ed2", - "0x8ff68d42ec2dc5b13ff5c7ef506c619c4bbb0f62fd4c08e320953e5cddded2aa34624c6c5768b546cc2f00add0dda58f", - "0x8b53131206c80638dcff21d7f2dabdbc6faec545f19ab1f4f2bb858d6b01d87adf886072c3a744d58124b8a7a0c87573", - "0x8b9c9aa127ddb950cad4fc21cd7c8eb802cef6db7290664b1773b9744836450e48af503009d4bb266ceac83d765b3b9c", - "0xac61e051add512e749588e2549ff55f3e6fee5378443cbf64c80cfd7b260cfa63f16fc3e242aa140ea243435be28179b", - "0x9240700fdcde974f319a90ec4a9b92a0323424fe39e513c7412c621cb33072d193476118636bd2655867ed2816e03034", - "0xb6b05975d0653079034f9792d5d8cf5743e1737e1b3860e431a1e159199efa5a55b2d3283f6d270c9ed3156a233e858c", - "0xa2ea8fc31294943a3a6d02509cf8b75a7b5d94de917ced468fa64a6c24ead4edef11c34782eed848792b0570219fb77b", - "0xad0b54dc5dceb242c05a7f7c529289c8caed93ebe743f6609df653aedffbd7eaffceb53a18dfd109f28d14c80e1f7935", - "0x81e4d4667900eb5a8434e2153503b2318f63708499534a8d58382931791eb0ad0522b41cecc7eb0e6ddf99002bd0127c", - "0xa4c5c329fe159bdeeaecbaf479c60c8f43a58ce613e135e9e9eed4af6bf5b6116bdbfea31c82bf0ba87c3f651e1464f8", - "0xb95eaf48a9128df7f970754af926f9865c2078cabb4da4918d8b45e95d72748750ffd12f1d8d3f76cac0936ad0097d16", - "0x8567385d52e6f6dceeee52f6b690781f7c05c26f0d20912bacc38c23afe8f64925ba18f8b6464d4a0557670ed0cea232", - "0x8f7483cacd15fb7e49b2f8deb7ab05e64bac18ac9dba475666649c2cdbc5d6df0d5e789fdaaaa997a3b524521f0470ae", - "0x9252efa0698c0cb30dd431a72a0f5f2f14429f6ba50bb60f7039df45777557afe3ae732b9283b4a814d2146a8cd8b7b9", - "0xa54da5287928a02cd5eedabe70cff80e56db252e2811842545beb14f25ab67788460a71ab8ee47cf0c1a5f8d01635256", - "0x991a80279c622565a03929c94590f33cf0621a79b70a2168a41a4376bb3f0dd12a9ed9b16c0b6a4a59c50b5802449874", - "0x924ff5d3a6f0ff4ee58c3674319971257543d2e19f0ce3fd0b0edb214faee920f8d6199ca794a173363a9fa06c96d7b4", - "0x96b136b8df76ba24e4dcd68065c650fdc224fdfc9c1ab6410e008fa5b9580680c3c85801fa217917c620c86dcb5ce3eb", - "0x95934e64af642e7d45ada1bbe8b9fe972877a674252005afc34ec2e857f755ea0d77e7759ddb24255f21252d6c739305", - "0xab14c6bdd6d1ccaf69e0dfc6c832751afb70f89e4800c6fafd22db2e7e5d6f2addab8b1267c8f3fb85cee51c761e69f0", - "0x87e2edb8dec1253547cece2a7e6934b0299715e634d599316af0f076c61726c7f2aec83eaddcc9add1c397cbc9fed0ca", - "0x91170baea88ba00fe00db375e8d948f58061f9e7b36a4573031b9996757afcc2c7e9c2d9642bc51402aa586569f0a398", - "0x89d99b120e4565b0538b2ef4f8d8c05997cdbdf61840e068054e7f21677cdc1dc2f63adab1b6814225d14275c737b0e0", - "0x880c2b79bff714665e9b3a0a647773a212ec5f0dea37ee6b29ed6850692055013e108a86affbe44d1abd0ae80a748950", - "0xb564181f9ea65ca25b1ae7f25eee84b73f9db109ad1939e6b9351663ac0b083fc13e6518ad8eaafa3caba9ab959bf7c5", - "0x93cd91391deaa726320574bb46706fd8e30ffc2308290c79abfe2d234d0f0f59ee4c38791e3bbd8c3f840a920489ebaf", - "0x8e846d48e7b120b59c6556a0394d25f744dfda0cd58d4e70029837753a82afb63a015e79157fe8c810cc68bb481d19d6", - "0xb36904e7dd71bada7c9b9172e4a6748287cfa0cb6960ccfb7202a36c57bc28d351e1f5371c2b449437cd266f2d22e7f7", - "0x8947c11af34a42f314983ba9c673e62fcf44c6c1f733a697351e1b8422a75338a85bb19149fc130d01492ee18b3c9492", - "0x905afc0103e34fa9787102fbb80967b8c917bd03abb02731fe49ba1acff1e96059227616cd21080563e92dd021117a84", - "0x88c7acdc65e6373e4c8ac6a13d1bce1d534aeef2965a4d9f887b2e823c7ee7921db1397df5cb5e7f12030e310172d6e7", - "0xb028c19082411efe8a46c8abfb9936c005e766e2ad3120be774172f16419e2b04ba3f31132ed2bc209e7214c2d7b2b61", - "0xb6b3a561d583870193226391ebf51ef47185ab6efb3556ae59106b6f266776064e5cdb66f0c93748e60d557db64e8f84", - "0x93732aa1473dc2e50610eab2c8152f2d96992fea840ac2d82c6e2c5760d8c1c05e8ecbd69e14d03713f43e77ced9d3bd", - "0x9734c433ad41a8fd91e161de033a2a55189ae31e2af406d1fae443a182bf1977dddff93f6fe2ac7d9c4fb955c26ed59e", - "0xa1f305d17c36c06c515d30fdfb560f899e80a2e2461d0bd947032e5ec764116c7ccbd528ea42a3b9351e3c9b45904431", - "0xb517f46b582655e551f766930637e8dc2a762dd7a2c54fce429fdc4cd503e9fe4bfbf323f50860be2c18b3a17d528654", - "0xb395b5c48b1cb0daa8c156188b390a78441c8f16ecc8650520f9f2914bd1d992b83849bb11ec17a47f9f2d40d138e3d1", - "0x9147b715b62fd50e59bc96d210e10f1062c87a90263b5586746325deeea89e759464be55a09b0548766e13bc910c4d3f", - "0xa7dfe5e7a39767d14d531d371b92fc4979d326ed0f966eeb7b4b4252d78117bf5295b3c17d1fd636dc2c0097cac901c2", - "0xaa3f9fb858b30675e9e57170a1835717521eafe4bd0a0690b10020c7a753951576b4c7dc80cf9f042894fd5741b41b1a", - "0xa1f11dec034733e862cdd4aefaf0252a9e8175b6d4c834b7a7d30ab884bb6ed6a1d92bb0e958f0082085cd80157a0e40", - "0xa1751d7452b7c5596fb801466d8d07a70831e568b8ca66fdd75e5898739709795a5768726ebe13c469b1d51092d86a60", - "0x80acf49051b7caa6efe78318792d05401f5246c5b3bef25170b2a49adfeec8048ad5a5e6d50cc498b23896176a9d9669", - "0x94156df9959c678578ec6e12ac068f3a28d69a981443fc35161d14b1f0327b8424746d62869ea9377a86ca6fd2c95b5e", - "0x95dd91b1e9b457de913a65f198dcdceb1fca75692853bd5ed44eda6343f32126e6aa2a309411e019dbdb9519c865b96d", - "0xb2516bc36a726cf2dd5553e319c64fc508682c7446a2a5ae696e43f1a8c129ca9602f5a41bfbb57865a9dad1d56728d3", - "0x90cd63b4f9216fb70635e4dcbc9a6c5874cabeabe4f9ea83bb923146d03366d9befa48b20a64f3a2cfdb0c3a84007ab2", - "0xa55bfe9b33781501f10d5632e8f5330841eba2d0a64b0aaaa92db56f014b5e44dbeda3b1f5b2e4c17eb6a243977b2a82", - "0xb9e84b3c617708971f5e174fb8718906f9bd353f8b0fec8fa03d1a6e4bec20430212396a5406595343cd15777c5a3f8b", - "0x97deb79dd82185555442f91fb9a70cbd30a564751528fa0df0a681315b8a71bab5073716908ee0546d70dc41efa3b53c", - "0xac77c2fe555584b9cba7438a4e3904958f671c49536f185cf1f3b25c5a57ea65e15554de22def94c5c623e8c99e47a9a", - "0xa27c62d39508552d79d2899bac6138783f308e3befab65a96a1ae4ab108b799628cf37db1ec72859a0ce1ac68f68b106", - "0xa2aa287741f03e31f2c87fc37e228279b1acb886f32c6438b3e9807b8126da875fca7f194295c45531e939a13048a882", - "0x84df8999c4c5ecc807819248957d68909d16ef64d94a820dd0d266cddb6775c9c7464f0b2385b7bdde8fc0f2169a4177", - "0x8388e1a1babb941e03806b392fdc1bbe1a01438292ea1db4087b010de0805be78cfa56d20e9ef7c8b6be5a04bab1b1e0", - "0x8cb6ec409cec27e7c4537ee2e5bcf82a33e7cd4761d19059e902b6068a9744e897a6010e2ab42ce72625cbc433892ec5", - "0xb6e71cf74455b0f506e03eecc0976831ec9a56eb8fd0e39e5e12ae199180a4c6e5123174ddea6ce6cfd7a414cf0afc5f", - "0x815dd267d9f67b4d229a798a499b70ea2a611f3bf2a9d3698d1105890a2b6462fcc7c6ebff0d5d709707ee4ffa981689", - "0xb4e5b7fbab4d8a66d1b167a5acaa4d53949e1fbdb00107e62b727b4b4b2cc70e2685cd4a16266e8d13ab176f9be09c10", - "0x8d1bae7566ff551f06baacd8c640d0d04accdd49fbfedda0841914aa1bceaf9f3f27344b80bdf5f9b93ada438a4e6d68", - "0xadb054123e27afd4a691d2cd808a3232ab58f56fbd514935caf47b8193b4c64aaafed4d08a7a10ec4deb66be8c292e64", - "0x8ab5255246e01478ba7dc6807c84850308a719f8f8433eb049d5b11cbc361c08930722e7e5878ad99fe1586b3d11cb1f", - "0x90e862be1e3d0824106da33aec437a87dbd2599aeb58d46b4a39a5f651097d49943c3248a154e09e309eaa7abff3d500", - "0xabf16f35e3b2b29a72cd96802c900fbc54100484299198b2d40cc6071945781cc9bb3eb43f6ebe433a14c1aeb172929c", - "0x867a0f396374cca7303845d8a1e4bcebaa53cc0fc2e790dd58cdd0b5ff2b9a99e18ad4e57aa2b061826545935a5607b5", - "0xa6b6a2e22932d7c9ba8f27b1e1de8559631a81effc77ed2cd7c45c48e49ea7d2f68c59d07a155757493ad82f733d93ee", - "0x885e4c3904c545c0eecc9cd02e16d359ce69a78e3a355e7fbe6485762d4523f2604f2f663a4521152a8bdb6fd4a9d4be", - "0xa668f417391b07a35c5d40ee5212cb7bdaffcf040a4f20a3d7e70e9d715bd908d4f8fca87a7dbf7b676e088ac8651ee8", - "0xa70d67f3379e1ee0708c34c4c7a7f552267ff679460b9d8891549077c724becb99ff79b35bd80420a4290f293ed4133f", - "0xa523cca782ced0d8a3f7e19707f9c64ff38495f739e035bcfb5483f202b209c07c50c764eb28d3bd8cf68ae093c46f19", - "0x8ce98e5f96889ebada090449ae198208cae5c247cc5f6fe7800b4c2254b0e7f2475b632cbd5021a0871b466c5b943dc8", - "0xa69cfdeb27ce1163ae6b6b4b5d46b49507c7e62789f2f90f7f5a0fdce79de988c755cc9afd8397b1c02976e03589f985", - "0xacbffc94dc0445f7797a0d83e5107ad3ec8bf61620fa83e73a999ce4f9b6bbabb00245a619aa6f9b082a2711bad5ce8a", - "0xb64162794503c86e478c23f060228105bab4f3f5d46582bd455a94526aa6d71f4c9630d8d63854c8c67aff3904681e0c", - "0xb1288073c012a0b2b7e31708e874106031a8cc98b2c94ad0ef1d7b9df42f429f58caef5494f6d581baf12970cded2a17", - "0x8d7ad217c3c1cb74cc301540a0e43be6d74d5a3c0383ab7c9dae57e25f8725781735b58301ebc014476171725299782a", - "0x924a33c759249af270617767101385910494724a51fc63600836ca00d06f0ca86a4a0a85e5e87cc29e404ff8e04d036c", - "0xa7b21ad39bcacc96cd857328a83e5d26cddd0a5bb2326da9a8f593927ae7b5927704acda9ee217176618c964d0452d54", - "0xa5c3616c308bef98807a852e16f146859b0b1f31ea8a721941d90abcbe37eeacb4403c6568480b6d6e773bbb94a89307", - "0xaefaa1033e47673ca2b68e4c945e6ed892e223146d4fd24219304c2667777c1b18a19488b73053cf7b0e6e09ba1278e3", - "0xb308c690176bc43051f51839d3ae1636f6de5a57c626e8def464820ce2f96ca09ff26294a3dbc9b4573cfc42dd03bbb0", - "0x8f7b1253ea9e257195ee92c54de41f2e7a310c90602a628ba3180e059e5bba79d6bb8110d1964c59daf4b65cd9735704", - "0xa387f003f7731b81bace54c8501a3a2a25d8a910cbb28dd603ed16ce61ef1df34e233dc8579071856d7198a071efedf6", - "0x955ad5523828c0fbe8ad6a77a191661ee9c8005b741b7b0439b76711b6992795758d76133399d266df5e494e4f86cd67", - "0xa44441964f5cad7b54d0105f162ed3ec40d12870fe8c5c30bf16238440143b330ba986d6adb00c9626747e99449f765c", - "0xa52df726de07cccbc77e81abf4f1712657c9261f65feee8815ef0e8a4ca61b8e470503801f1da8a23fe6d52f3306807c", - "0xb5d239222c3d852f2c49997e76d97b70bcfe73d85e81258d9781f5f7de87f9c81648bcf58cfffd554e4685b2f860e6d8", - "0x96f0193aecbeb1540678f1a3369401e916ee75d2a85f7124c555466a3def91a5d8b5f774e3156a163e1010690d457c5d", - "0x886b9f4965120d942b076d053571837780232e139c3efcc6bd6c64eabddbed2d55c3a9a06001bd7a2ccebb36135edf4b", - "0x897a1e4e9f4eaf755807bed984ef0bfea251740386a168061f4386819acaa337fa6d3f038b4cff9a11926e68f7888f90", - "0x989d9706f8396ba422a34b55897b9e261ac1ba0c7a7a11a30562ebfab92473b9e9b604ea8baa6067137a4ded070fda10", - "0x96376812651020f68c6a1f0aecd04591fdb628051f01daae179f7008ae33af5abb42e8f304662c9b6e2584e8b02ba6a6", - "0x9344e6f3ce42ada6281d0fff654f408e61f0acce81e23ce47466bf1145a99cf60dfba9a22304efbb1f428c92357d644e", - "0xb90c5463445156c8de69d8c35db656a76f3e195c325808396a829c11c06a7503f3c092816b3f23a263d56d3f2c075ff7", - "0xb4dc6d948f4b67b513ce27fd12bc8efe43813c119d01b2da391d01c1cb0abb7d51350a5446e0a72a6f8bbbde2ee4b0c4", - "0x84d208ab983941bde208fd71d58c7f9335e14db237cec42df453155a3a8dcb21dec8696a1334cfe5d035c192fc44e88f", - "0x9577996c78372d2d6c9de27d497afb29c918bd894bfefad9059bd85cf2ab520ce1d517994724e1567f12e385c126f26a", - "0xb778b9054776a2b8ee81be356050b977bc8aca0d0a202be91d56ba89d8a384bd29c5c652ea084709d4fb365b107962b9", - "0xb7ea99f8c841678dc854527ad0c8ffc700b43b5b36b3d18303e51175b3901b144c53e22eea6ce7cd500f6879a80a8c21", - "0xb466aa7d1a5ae3d9aea240c8114b3dc3af38f7d8f1e323800a6382de5766f19626d07cd6ca6eddfc4d71a43d2d49a07a", - "0x8a72b1ee7993f16400396982b6a5198f0de08821431bc66489189d5b364b0e36daff5077b48aff1d55c9a88580cd1dc2", - "0xa7c4dd6095f8cf61f42c5901ab67e9d1ad21a42d1eae9ca5e147a9396507c7a21747c2794f71ac66002840f4fa4e1dd0", - "0xabe40e33cca787e7c521e2e97fb5f95cd4ca7ad6148a505afdc94e0c003e4903b1524164a1df2b2a1330fd800ac33b7d", - "0xab8e1930b1e592aa2379cff636e7fda9fd7f05b358f47d9cbadcfe35fbdee5bf06469fefc052f62159c10942ea2bc5af", - "0xb28edfbfdcc27c3892d64e7e05a2aebb173808c020186c225590b03d91dacb866108370f2c14ac97a6d20d95a8e32f8a", - "0x97d4841704bacb06bce2778104e4437c930fdd9320d85cac383d11ce9246525ad5167cbd63ef04a8ea39c8fbe3d88169", - "0xb4b178a1c3ccd3344831936b784203919cffb611cd18def1a52ffa2a8e4286f9f9681bd48dff9b2abfe62da5fd619fa7", - "0xafb01a4777a128b02fc22e282e0c4ab1d86246d8e0813a7e85c51907bce079766ae40c31d3c440d5f99c92e89d3a683e", - "0x91cd070a607c20140c1f35b25057bfa20290b1435e99c5b33068c4e5755ff8f1aa2be61fba28dcfc131cf881aa1c39ec", - "0xaaac82ccda92c6090970f60a56668c011ac20dcab26347ad40585a60b5a9b5a9af883307c55526d4eca1b110a079fd3d", - "0xa7480de83b4cbb2eedece3d3b27b8d934e9183f448d56d5f49e0b63e24575014a94e09d406d7ca5afda08da9f4eafbc1", - "0x8e568ae356775b06d963b440f75bad9b5977b7bcfb8fbd1dbb4daad5192521bd189674654d4ab86ded4a6be5fee27ef7", - "0xa501a84cd0b4138383572fdd09242e3a748e593f55400fa7600698d4f916a2fc1feb96557a34f4ef0f13eee580fe9f54", - "0x8be0f6b98d52b83e9deccf09d05fc4a7b4ae1cb11972f01baee4fabdb804cee2b0e9b44a1385238f755d2c1ce395cfa5", - "0xafd01e3658ed9204d39fcdda2d79239b8c45dcf80fda8a680a8797b6be294e7e8bf56ce345896c3f08446e9a2a053a61", - "0x851f0def025a50910bfb6c2fbe5ca62a31426747d9cf4634c8faa714a014fa22586c3eabde84e23ca77371ae25d720d9", - "0x90a1aa7bbe7842cd361d0ab2e16203a56318885d2d32409370ffb64ef0ffd3c8c57658573a0714b04dd1595aabfc8f21", - "0xaf56f30bbd796de5cbf6f3d041c2f649d0f36e0a1335ba923eb1487e0b29d0ab307a1143e6cabb79674ddc01dd9a5cd9", - "0x8429afa5476d0f3a4eed4104fdeafb79f80e94e709b59aa44b4caf0a94bf75fb3efadf76e96389179eafc389fe896efa", - "0x91d8399bcc3b82f0044b9a697b2bc402285f6d2e7b76eec25ffecab769f3fbdd45d20897d28a8676f090edf847eb3c70", - "0xa06f8d37404ae58c35732db58c4c6270e4311c691ecaa7d89b3e9b2bb1421ee3c3cde555d066340c0f2479faea1ae162", - "0x8011fcbb711ba6511960245c69a73fa99167eeb4d238426bc31ce359a90a2339d5936042b281f3ff3eb976357db96885", - "0x8dff2bc19830b4a58d2cc3c1598d060da34c8fde11635062dd4222c96dcbf2bef79b339c63fefdb1653153ef9af79c48", - "0x84ae7869e2405e326bd167249f89c2e018817d3edf59f3db8adc25f64836ea4606c78158cb30020a54551283bcd9f39e", - "0xb7be6cfbb7cbb7788fd60fbfcae3504d441b0af3b46317944e00a23079c605c08fd116311432be5b378ed8a11da219e7", - "0xa3656ce4a79484e365b6b7f81a9dd72a09746da16175a61794bc5fcc0c3dd608751ea2cfcf7bb0c14421e0b05d94df75", - "0x929d5603a936bedc69ede2d1277692012d0c820a23915ac6e776b832b9f4e0e6276fb3b257c7abbca32ea166d4482149", - "0x82d47138de8b6ed4bdaf69526ace4f6fdc50fe5abee63f1c6d4447fe4948a84a63b7963c8a65858442856e282fabaf26", - "0x8f8b2d05e77e9e4e2cc5229ea98c5c02ef9d9b6939ce6663d98d8e2dbed73af3d41628662c354972c1b48157f8d3c459", - "0x9353ee31f477b51558f4ba5ca437d801f59d01ed995a8801552f8c578d1937997bd76c31aedab99fb5532789e72469b0", - "0x941f777fc9115fe948f3a979e1ab87f133238935acdc19d33e1d86a1a64924eb9008e91bdff8d650f5e3ad06db931234", - "0x8ee79ecb7d07e3a5fb80ec15c085898e97876448685891e09ebee9aacd5cd0147715dc60b6f75b601fbe83598f1a939b", - "0xa96a50de4fa25367706c99abe9dba95ce1717483f50692bde7c8c3a6b427d02d59ef6e8bee82762fe774f5afa528f0d0", - "0xa451ff58246340157fd94d028ce1ebe5ce49e5ed87d9b61884c8ad903ef9b697c4ab9e5acf66180a89a17749046c99fe", - "0xb12739d77fb96e9e93065fe279307eafb11c795da2b9efba4cb239425faf687b9762235700d9f2cd5df9cd9fb2b36e3f", - "0xa665e34895d02e68f1dee7ad8090558514720ff3e809cf46cc06d1e1274d219fd08781fd430367a3f09e2c591dfd7cf4", - "0xa262410cb60409720ce68935e91c743aed5eccb4a0427449f32a4acca103f9d65413290ffe2cbc895e2e1cef96ba7c6e", - "0x9597cf4d49db896796132aed4bdfbec71ebba08a617a611d1fece75bbfcce902e8ba228b662d0ec5fb927429f082eb38", - "0x80a045d2bd30aff2267a5385be12c646b65b24a586c4f5cb1bdb81387e3ff8edd2358cc8e3c4c5c60cab88d4dce61d90", - "0x80323f4a9fc78bc89aaa172f739bbd4f57f9321a7b8e9eddb74ee5c99d6c7b8dfe847f7103110f0a44d4e7c20ed29114", - "0x943b749ab77112be7593bb2ac11094c66c94bb89d5ee2cc05316ad623a3997a38aec741ec93c24770adc670b6ad12e91", - "0xa8e1b4935aad8a84112a99fd5a4d3786ccf1c985aca0b256c46d52a8801a132024391431cc2cfee760c25eb18289041e", - "0x8abbe403bf13bad914a4d5bb0c8709f5b264a7a81ba0542850cb89c3c45bc294f62b22a36d7f829ca80830a3be5832aa", - "0x9084defe85d170967c08d1f5e74ad1dd592c2b013b01b84b5fe3f2ceb260bde2e52ca22e4006b07f01e3dc7a36774e94", - "0xa34cf1cfca622dda528589b5e5e36a4a68cee7e18cc621736e3e165b06a5df9a8e9f3ddc406838c1fe94ebdc44bfaa18", - "0x8c5f5d7e98828d0a063d90d5f78bc493c46316fec3245d47ef8900241fffd5316e0d6d6f1653cb3b22bbf05286736c06", - "0xae7f2beef46b6645a6d0b3ca664c826be205ca6de35bd2809a4871f19368bd2c009ad7de0cb4c269c2934357e87c7f64", - "0xabae2cd1ff7320d0803b6b5345ef9dd364fcc001d91fa456199dde24f474ff3d0ce94d460be9659caffe7ae0a6814217", - "0xb86710fd69a6eeca8a813c7c1d643b487a32cadd70013a4aff4b7331ec08d1469fb17a06d06926e68f546e7f5238e1f5", - "0xb42e9dd8d0f12f95a16112ef7ea60e6f4c76a39cb64e7f6bb4fde6ed1fc54fe8317e93160d736d97d87ff7af69ac2a41", - "0x86e5561a7b621e68afda9d63945dc69bcd615cc099c84ac51ebf6350b25c9c07ab371ed5b160a86488e8213d143335fe", - "0x831c730524214b8368bdc619e5c7e55a0731b6c5ddd035e9d7cd90577a472a429e779efb0ce47320c8d3b62997eec0de", - "0xa3bcbb6c321b329ea2bb944f48ac984f8bb6cbcd38a5f80e8780257765756cd902d252a51804879104527bc7b251c1b5", - "0x8b1a0ee0219a010653f816de88b05116269325c42811d717544468b3bf477953900394a71d56b6dea13e4e6ef9c9c5cf", - "0xa5d06e2a43d965e47d400343c093d64bd5d4adcbe3928093c80439f815938b9e952bf59da7fb26f459a5439fe60fd49c", - "0xb92df54cd0515bb9868a8dadb2a808d3e62fec12be3c708fa6c845c833c3321017e2f8d71f10b064fdde02b098e22962", - "0xafd8fb1d8ced274650ecb6c370c5bbe8f09d263391af7c2f2290b5c99196ddeaeedc8b9b6173b6fa349872f58c83149e", - "0xb359418883d3425b1bb896a9a9e2a3068c19abbb18ebaccadb85dee713b14bca5b83992cf239cfbb73adbe2f07c63f04", - "0xb8cb045dcb0735b02d6e81d9aa9906ab2f19df2e2adb5bff0533022c03a9a530bb27fcd45173faac63a8d13bf0f41565", - "0xb8b8ed443891d3ecd807d3f92d8c2afe714a423b97019cec3690c24002cd0444548ba6b454e1f9934f01a419206896b8", - "0xa3c28de7e71c54dfba701b7e1053a1719032bf48d0e520bf8d513d155d588d08d14af3cf1e9ba3083f8e59dc947ef99b", - "0xa94d1569107e659db2ca58864eb8feb03c83ca083c75a6d949805faaf7095a4f981cbd1f89a464aa46107a891ba758f7", - "0xa9c98b0501a8c5102ec0daffddce83ab40bd097c4ccce66a8f8a61a3fc28564ce5dec37940092640b36a0ef6efbea4a2", - "0xa473b784935b52ce73755894177ead52cd9f2a10521e9c034298fc0461aa6cfb03d499812189eddbce4b3cfb58774a3f", - "0x8c7a7984739a3db7b28b7ef10f081e2cbec93e1da39738d082898fc73e83db206fb52cbec476c9809c7de61ff5871b71", - "0x88b87148a573e576d0a8fa912858b7a9876001d8364bdaa7dd2759dd908555119f6f580c0d3a663ff5c2a2bcb05fef99", - "0xb169b58fa10256b2753221aa33dc4f0f3308a294b98300528347ea4e89128a1a1da502990c9f2d266fcc10031b3c5a07", - "0x85b1f0e49528ec8e51112771686b5b9f18e4cab091f6f97dc9a327221fde48be87f59cb46e6caac6e1af1a8c70135e66", - "0x954021598c24f5889a642b9d20488a04e3c78c5b04bafcd0a01f377cf24b49f64b1d820ee8a73f8cc193e1de9a106a6f", - "0x8954b280ae11638d6e9c27f826fe66c0ec584fccefda8665f75e0699ed640e8e74fb1945753f84baf606d2fcc804b1a4", - "0x899303d3bfcf48d28aa49e915ddfe263117ab87384f611bf7d555ed141dd728a39b97eca74b6b623a20d44458f35a157", - "0x8d792116aaba18c94069cbaf0da373c4e61662189e8bd0f24dd675531ee6f99d574f91844ace86e3d018c4603ff0e2c6", - "0x876c457846f27161c796f2d063aac7f73c2197ce707872c828af81ffabe91a6f353c6e887464c921718425d046c0a722", - "0xa0382a36d4f8007d444643bd5d85d0b6c3c892c7ef8158f51c878b13af6a5b7c8b098ac7a6e3241a6e13b4ae987addc9", - "0x81d668e292ae20a5a39b55e6798255c39368d9b83ca46e986c343ff9cf4f3140e3f07115995b8fc2554dc0372e4acfdf", - "0x85e58c950d8089ebd5d0a9d852c9e78d1e066c4cf1f1e64b4e76356745d3eddc13f1abf177dd32f0aede2f190053f8c9", - "0x9420d1c470588085057549b7e6544aca2ca329ac9b232187d8ac894b7a878d6d3ea351357174089943b43a83f057ab8e", - "0xb6ea12644c6ae73b8b9752f8eb8bf06312ca14d93fddeb5f79b92167ed78338964d620379387ffc9e12ac0e323f3500e", - "0x82767d1ca19c7672d38216bf17a8ca0a52aed5dca77684da56419430f9129ed25b6c547fce50c834746cab666ddd43cc", - "0xb1864c611fdb1b641708a5be8140ca0ac52d59d7c3fa3eaa10bd815f7f5e34413751f829f5fc0faa259064b73d43f4c8", - "0x92f67f02d17a1ead3b01478687cf26b40fb32f055f3b34feff21de083852460e02afb834f61c37fb91100709891379ac", - "0xb640a52bf00e4b29623c9b150635391b4dd42f0016e827daaad7aeff6e6a64fae4d67193664bc5bb368c72b138c76efe", - "0x941c8aed9a85a005059b83d35f6a70dae2e2b5f645719b567de0da3bbf1d2b85704ac977970a7206bd98609182090160", - "0xaa976af6c9809550721103fc8bb8359cc4313f672421a4ddd760bc4ddd86a036c1b4145049d9c8165335309fb608d6e4", - "0xafb11dfe01bb6a9d2cc2c040e18152645b4aa972fa01b6cb4285312bcb11a317107e743efb53aeb4bb6f8a7fb7741f50", - "0x95f8f780fae2878792aa3f60eab8954ecb107942bf07f0e2854173595eb2d4b914f4aa208f98a63b0ebcfbca46840123", - "0xb1dbec7871209fea98676e68d7a02dd82179a74e389bb9dc0eaeb2ac2d446d26810146586b637869ddec4caac8281bcb", - "0x931c9d571e50dfd2e1bee0c36f42085e4aa4e7d80a1c3bf99573d9d09ff710f6fa27f30712daba107d43d263b226d130", - "0xb080bc730ed34724851d00be3bba84093a296d6320fe7671a83364ab1faf922189ffe997eca0e1ce4ac2c4435d7b7f10", - "0x8dbbdb4f82398c891d16dbd4169716e078de5d677d3d550fd3853ff6ac8d15d278f17a2950333545bab823fad09a4922", - "0xa71bb5b71699082cc45037805fcd95e410c29575d229a556a7c5f2969fb6f957f0c63861048c65d5b73fc4680a5c3c70", - "0xb5bc06a742016a20c60d61cf55006cd7c2c7b8f367968f279815087b2bda7009c1773b9c91b8a4b78717b2bdf6a5e96e", - "0x91aa31c68634a81c9784d0d6adf4dc85c981e122635d4e1f25604c8184da860b1292d65324d4bb9bd8a002572cc96bff", - "0x85484fa47e235410e2ebc49f4dbbea9847ea064f6f6776dceb0d868280fe88bf6c5bb72e24c0ed3cb0a3f1d96ef8c9ce", - "0x88ab35f32986f0bbd8502dc81506cb18638298e856934fa374480dc455463482ca780385537e7ea73c4c863107b74f7a", - "0xb3022847a668b6d5d52d0af14d594c3e842afaab5416e3ffef21224bede0e1bbecb0799ddb7e095623a3a6f28b6d5f43", - "0x8802d0e6e5203d0018d243301c57934ca85a002f91e5763b2f7372816c7b3ddf719c3b743f2530d9b7330f4f35d69d83", - "0x85709fddeaaddead7a27d3f75e5ac568b0c9691c797f1505f5b33678158f5dff96ab98b921bfbc83368c6763420bf949", - "0xa45ddf8ed1c273d61578bf6830fabd4927f82e3efe7732d64a1c82887b9693dcabdad1e7a65f09bde459fef89c0eef82", - "0x970fb837063e059b1c2b4ec88011131e8cdc459daa1e704095bd327b7c94115c57cc1d9e8b4a29d6cc4f20895e309c61", - "0xb789aabda019356bc5c5dcb015f8e7c5da25150988af0d44cfb11d8702da22fbb43f69c4af889dddc0647745d162d91e", - "0x8ccd44696d8c52454c833b0b256ed0073527616ce49ef25a113cb9f16d41f39d27e3bf169ef0d3b2fe78f2a5910ec83a", - "0x9846a3ae6a2c339b09f53b6cb1262114d1ce2fa3ea43d77b04816eea6451e8620f0030ba428eff80d72d8e907c8f9e3d", - "0x80c18de89a31e2c8309353be974e42ca97dcebefc1a914e76b57609b9cb7c1c6298e2ee1bb35ab9d587f195010d24337", - "0xa43ac7ac3798af0923ef5bcf2342550aef8551c198a31b0bc9015ecb24fd2633bdcffd84a2c84f9eb72b4e67084caed4", - "0x8cc1551213a33114c8e6b3e00c68dd26b9cb3728376b498c95aeec60e7506a3346641ed5297fd4ead33c0e42b85079be", - "0xafb54536b43e311eef9f584b8f1074866f6d33cfc75a3294aad5aea870cdbc3c97ab6e849ef719e2e1e4386a8a360fe2", - "0xa2c5a2240256c64673372b63359b646dcadb108d785b4fb16a165f4b3b0ab3dc3dd5058582b25ed7b728d56d5aa45649", - "0xb35e3e4407b63edf3eb567fdbe03eef00dadddcf41b799c80a9c9e26ddcf0c6b0b9dc4df0a0c5d54bf31ac8273740a32", - "0xa3ce737baa7e1c1c69931a5b2fe1493a06fa0dcfc0b819ef8242b4fdae8d63bec8d15561d4fa24ef6d6c3a326d0abafa", - "0x910a67b377fb17d3f9cd1f994db52eb5f35a4aa002bc1b7208b259b12c64c095e4dd65ffe54772f8e2773923a890bc97", - "0x908c5ee131dea3f444a9ee2052c93a657d28f2f58e604bf08e51500a306decb2db964f68e87d5ac2f8207cc4e92adb09", - "0x8f3de5e756409b575ac2786590fc85de506f0adb51450f5c24669bb3a688f080c1cc37cb8e7a3c8db8e25c49a4bd76cc", - "0xaa62ceaef91fdf09d2ac2edbc07fcc651584a7e7d7d93e7bd4bb4c42105144c2da32326b3ae320b36a2df8aed07e5610", - "0x959fc29ce63dcac2b4dbe8180577cecf9bfbb6db6505d76aada43ddfde5f48ec8f6fed14fac19389f6c9ed3555ef7882", - "0x984cbe54156763d6ae078d6a8205cb6f9d63eee390dc5990f5d8e85b9a19fef563653d3dcc190c9b18c2232a916b1409", - "0x923b448808d9ac04488e8345d3fbf9aa57cc3b3f375af138b674daa0e5a864faaeabed08f94010478543f3e1248c699c", - "0x8c0823bf2706d9aa4c074673e9d221eb021de2baffe8b703e64e676b6801da73440b7793254fe4c8c48d2ff395e44bfd", - "0x93c9cb050494824aba0d57320e2d1dfc95c988bec46dc8d73f7036be9ce0d7de02e56ad1ea3dd8fc129100800aa639bd", - "0x9339fa01caba0f4837efca7a3d983fda1f6a479f63890db7f7beb837e3f6535b1f1d0788884dbeb73fa657410a4ad308", - "0x953f213ec904d4540b356d53eb88f646a98581a6deeebdf99a6646cf612e5b07110839d46c57b76545f6879f12371b10", - "0x99a4576f12de20fbecd3906e48dcc784cdbdf7fa0843c570c6f59f13cf3a559cc1f4882fc1d31015304090f83306280b", - "0xb07fb8b73793a236e58b7181df5a0a2e8d50c1d3069c475c6e178e32d14b6e75c45af60a8b54823c23ffbb316bd4a98e", - "0x98781507866499ce396730ee91a08e91d3be337690f7195750bd43a601a8f78e9475d5ebb43e347934429a4ff3db58b3", - "0x972a5a21354beadf80d8a6e449cc4f072d6b747de293f075b8e0925c89660db9195a30188dfc8b73dba02467ae02913f", - "0x827dd2e21ca88891b9b37e10f0d6b6304438cd6aaf9cb125ea7ed822078a233f3e1b49a8bc65f843e9551691b46cf91f", - "0xad3a4ebaccc157a7b880db6990a937e2d230875f509ce864fb0b7ba5febc7f4668191bf3aa55b85f3c17ce8b7d75b349", - "0x976672c981d106fe1835188e202adf6ce77314f8d7c8f797aacf25f00d57f8cfea31b057f6afcb40b9f97df6ea387979", - "0x8c77ba87e3e8fd5de85802a764b719d67f4edbdace75433af7fe966d3d18a94778c4414572b84f98bc6b5993a3225212", - "0x84ca9b0435001c246143e50487a55349bf38300cde86219d29199e2e9137e262a1619ee7d6f6c44b9551319f1ea8526f", - "0xab06e3313800a7dbb970145c1e89b7849db8a1e3169560fe2c64956a1f9d1d3157d531713a8d7c2213356b22fd3014ed", - "0xa0d04163ae987227aaba1ae82f74fd1b44387501fa47fa61b66728a0697155f48bb44b9eb5e87050a5bdb7605038db12", - "0x8e76d3e50853ba5945610251dd18736b8541bf72bd643f6b561cab1c028dd044c820fcf79a5b45882e7dde0ba6db164d", - "0x967ec8fdee2e6d61f0ca2cc255f4e72c41a9c1a62150318be0fa508b424487d9254ad135fbe8dcda55caa51b9901eda1", - "0xae25c496f872f7380d9c64fc9bee0dfdc0f05cc1d2d6ea7747e166cae7e67c17a72a24a9e351de15f52baad355625d7c", - "0xb8a95f3bc67ad2a2d3cfbbf2de2003b7bc894b3f99f0364fd181eb11d154a7193b1df9b671a3a8eb8bbabafeee2d1a86", - "0xb79996f818d94842175b06650a1e7819cb12c96b6ba37e61fa14b67684c2879e7d3884fa6bae06faba009329db2b0d1c", - "0x856e1478ef99338f144757fe4be68d935f0069a05b0a6209be2fac9ebc5cc669c6a80825d3c74801a54ff8b1a3777da8", - "0x8024798b150aa722dc599f288cdf455479763a9bf776da74d5f9cf76026500e5a0282d840e5ae5451a0e28d507b397a5", - "0x97cb767ebfc0a6cfe25666089f85e5a3705c108096a38151baa22308276ebf7cb3c04079ecd130cb8cae1689508d4bcb", - "0x874ff07def0f8d32f2ffce7cf31a43e8bc5e633b279acd7138ae938e46889e486c092ac34924aed9a4e1f93a91994211", - "0xab5b6bec8c81133b6edddcd048fbd526d59fc8a1f5acd7aa72d07852696faf5e8d305e85077450188cddd43d6c1aad27", - "0x8402f5173327a95438797cee3b107407e8b277759c942bf1b1f975dc63ab89b8c43f0f4ce2a11de6e5727e9952b8923b", - "0xa5179a16297f7a0913ba61d69879014b9adb5e41813ac33acb8973e2b43cbc17a2f9a7d98210b39471a47b534f0eea23", - "0x8f7cf3928b51b0b1bce18a34da935e7e2558595e4ebc50cc1cb698f0bf3c1ea0050aadbcec33786118d791218e1734b1", - "0x81552a8927942591572429892e5a1572c8bc4fa7901395a5a2de3ce9f1ead122e4e5ffef6cc8434b3b18af1aa62e45b3", - "0x8999a1bf4f22fdc884f9310e7a3f5baa0d32c04e887c51a20736cff3216f3dac5bbede43632d29704536d7f275b0be9b", - "0x85d9952816412a890a3e479025d1c0c8858337498ae28731ae23332c16a788cfe51fa9836bee73d03b253803096100a9", - "0xb6a736447acaa6f4d83998973cd2bc3e12d34c6c076880e6765513c04087eeee5b5dfe9394c120a85bec8fbe127f1f54", - "0x89302db4ea39662024c748ff5b22108c0f34850f0fda476051a89a3eba0e02a2294a4511666987d89f3b3bbcc334fdf3", - "0x88ef018d32e6b379cea9ce35d1c12af698d8241c4c7468a5d558099f5101f04ac8e49d09b6bf031a811970faf02ed0ac", - "0xb33afb11f73195a7132430dc3961252251aef42150e9aa63a8c7cae724f467543a4afec289bf27e10ccabcad2f7f812a", - "0xb706315deef0529725fa6c4520e5d320a028b4607d47fa6c4c8ca3558afd68ed65dc072a354d263d20405bb13ca119f0", - "0x8ba035d75939c1a3cfc72a9ad3aa4ade105880345eaad9286a182950368e688a182f6175357a2e62d977ff7ae08959cf", - "0xb47ca04b80107eefd3a596be2e990f5f021cafc6b7fb64cbb78802f9bb7bd2cec4f37755c451bb1fc8786a076e90bad9", - "0xb6fb1676fbdf6cf95add7173c264b820042511e34dbcafa76273ef5e4500ad1208b274855985f0eff5196e7200e5a8b5", - "0x8c7493894853f4e9fef5a0143dc134f03eeeaa10c80c5a72afb12f10ca5207df1c7bcefba6728d61f3f89d3978902629", - "0x97d14d9debd4228be04f2352e57d9c8138d4e40228c269100930e2a3f6eb6e66f2f99807be0c9857082ff8b9a089049e", - "0x86e327360a19f6ddc8d0362cf92fa84677737064a94d9d0c1031bae92b85abed36193428199b0f66720be0d6edb0d28c", - "0xac79bf758fe91d47d1ddfba62bba87f5e64d93f82309d4d07b62d78ad6ae95908e1989299db99ec52c5ad8c8f3d7132f", - "0x804712afd93328864a52a9f9ca1ae148de26fdec7d9f51d1bf8c0385959ddfb639ae0904c855180dd418ac21f9a8a7d0", - "0xa789e15cf3c1e911fca7f6759a2c5d0a281c6ab744f29709b8d0623c1fc197ed9bf56b89fb0953baf261ffc4bd8d1552", - "0xb738474bd1788f326c5145ca2a468d914ead6dbc338680f62ee21b1e5fed49fa501236d70dce5363a72147b0a8974c8c", - "0xa34019db5e8d5cb680a78c1692978ce0f3f8b21c1615ff65f3d103ed5a1e32884680c90d1dc18f0edcd8a506b1003806", - "0xb1b1f26ed57a7bf77257e2ab1bf314b22e47f8a4f4c5cd154beaafdc34b257e9b976b26c8d9f1508498b6e8c9e9fd2ff", - "0xa5f645d7a75741f536e5218d4a38ac75f5f2d759e62bde2c9b628a4cac299b477a833bca98010b6c2a8f30b85f407250", - "0xb3947ca7df831d68107713bbd52fa300411bc14362c37c2617780f5d621e211b8bcf5fb7099d903748420818d840234a", - "0xad16157ac24899485e7eae71eabf9e4262d01b8f8bde30d7c79fd00ffb4c347d765bf2b4a30c260c9fe2e5153a3e1c69", - "0xb1bcde4588570576872430e704a64d7130f921667c13101c3fb771efc1e0bd72c4ad4b8e35cbb98d709062257f0ef59f", - "0xab45dce0e29201e79cb1da61cc4353840eb8b47db99319ff7802d2e18f00e3fa8d5e70aa6a797684b4a6741277ae033e", - "0xb6977424f2f5b968a4eaa9dc1ac1018ca51e6158a6a4c189f0adc94ea1c2f30bb33c57960a5c972a1800cca2650e2f6e", - "0x899f48fedeee4badd5b69632f78a244756458529f27c75d05e9c54cb29579abcbe4ff7567445ccef96274c8cf5b7d31e", - "0xa8225095071acb2610d28d9ce2645280a84c702f5f5040df7a4134de1144fe1a1b07d3e28d4ff5e2517b4b2bbae674f9", - "0xb48316873f8245854568a37ad9c5fe9d5e6d9ebd60c9cbbf9e6f63c512bd8568e3a736039422d21d383378c77d8f10b7", - "0x8b40afa65e47ba365e723b9e24bd4a13335455e059346187356ff9abe99cf71eae383ee33bc184a9ec17b32d0800f158", - "0x96c3b7ad1e31b8d4ac0e14358655e21e687beac6f6b7b48dd3750641315ac7088352976e9804b9c625a712f9d4fcfc4e", - "0x914dcb36d621753286340077d16b36bdaa1414eac7a8e7ee84404a37f8fadda837bf4c5a932e8b0f3e8e673832e9b3f6", - "0xb20a438985a4bdaea41b98e831537027f4bf19ea9c1ac5fd37546eef117cd3d41b9c1b125d2302ae3d41176ab5d9e9dd", - "0x94a4cf3cc42d7055b55cf58959a7715232a273e66ec6f83fbcdb79d01769f7e6b1e328f6b0a910d1f8cf7a5ba4934779", - "0xa62b07dc466c2f83dcac7fa98215ce5bece548164e32b4bb3aac055b3c0aa68ef5cad58bf7d392e3b1d54ea6f0d9f0d7", - "0x9870784890da6cb0223daa367163cdd41ead23c300d246d62debe980fc3e7de0b42576309ae35da914474b8ed2c5acdf", - "0xb0f28a74169391fbb179ffe8647f3e6228e75b409c49ba81d34ce780b12d408d2db5968e9664b9de6a7416d2f6d1c1cc", - "0x857697b0222cce1458ff591e1add39f5632cb3aa2e589a64166738d8c00855e354c2ed44c4cee8dd707188790fffe6b1", - "0xb3566bb224742d0871ec5d15ee890755d7e6727aa7e2f374abe965ef8380b49422897545e2cf8fd3f58bc81f05abf173", - "0x88271995f9c647da82820b042e59011121ac723b4d0a2e461cfc1351d89cc10eb7d18830adf1c7b9fca55ed3e910aedf", - "0x863a43548db29c9cf35f24c1d5f7aa984ba21bb924dd9e09210a1feadb1e0ddca98df47e970c230879faa5e7434b118b", - "0xaf5c71b27157a2391247622a5029ba11d17ab4329001b01b3236f38d67ddd6b8902aebb48ee9c963983c16f6d8c53d26", - "0x97abbcd4fff0d1ee2ea777788cc146c1b32601fd93a5ff9908fdc2de225b954d8fc0c9874c258dcb90ecc7fd169823c3", - "0x94129bc418ff5d00ba3a3389b62986fcda5714ad25d25091db12a66e138a35a9e38999c4cf38fe7cdb1340c099c467ab", - "0x8a785f303473e763578a5bff75a069764e346224fa2dd8ee3105ca779cccd5939ed8c21f7369bab9947a4ca92d3b605e", - "0xb37d1644a00401b213f29191a238f4c9c32ba550db2ab3b4c9d1f02021a8f6972ab0fc76d0bc5b9c6291d5edb9632658", - "0x8e42a2c87d7feadf1a2dad9dc822b40029eeb8afb785ce574a340929c4c6ddfe4d750bd3a482e62bfef1bdfdc36f5bd9", - "0x8837b0408f48c8b975ae777b0516c569dad0daf607da51f187bc2c67d3f67315340848fabf7ca78dfa46b05e3fe33005", - "0x96d53e8e9b14e602dec666fcbff8ac2a7ca0474605b294365bab5f5228d8cf0a17a772cf2f37f7de3607f7ea6127d0e0", - "0xb286888ab9afd161a714fcb1954f6046754c1e3e398cf639bc215327057ae68ed69113481da88627839b551cb9660be3", - "0xae5747c882d3ad685e481b0b42907f0934a717ef5b0bcf110fe3125d40628959b879186464f95bc4a69d90754034c141", - "0xb1ca38e7b1f87e4c878d4b878afbca750fdc2509f625a06871da830c1f68a6cb20dde7d51ec73a78454ffdf54451ed47", - "0x82225700e9b32f416618b074479302629114761fc721ff492d792d4d1a4d6fec887185aa81395703fc8d07a67fa4d87d", - "0xa132ead3cac6087bc7bf5153f29ea858a115249d779419e5c52f4d1f2180f4631caf45ab7cf90129af68bf67665b8dd6", - "0xafd4778ab2921b6c9c3d3e2d8ab7e33882f7fde3c8f66d6704f0859f3bec468178eb25a103b78ab092de5b694f2d2ff6", - "0xaa0123ab3e8af146e974c9fc72dce94554cbab212336c8aebe618ea082c15ef02c6b47b6c575d262d1cc6b0cf2d11f47", - "0xa5e35113393e82c0ff3a39afc99a60f90993d5e4995e5d21a9a395ae31074ed5e2244214d4dd877c3d89e52fac6c4252", - "0xb2f476cd5d9df15e624713f22716ff01790f2fe16957b526677bdd3d203fa8af98ae58daaffca10f84a7c61e29ba1d79", - "0x82d6d062828337677ae19ce13d27ef45ee55270a27e674946c7c1c762bf43af6391d77454dda4dc613b519f4cde43636", - "0x8e86b1803d4ee07791269ec9175dc3d3b595197c089551e5bec3edc55c77532235e64848aba62e43936d3e9438635f5a", - "0x845b7233e40eab725c241853013d1884d782457ec188ff7ea535926c36da0893882fea2c9609f96b6d85392471b71d2c", - "0xa2090ef73e125c0809f2bddcdd7b74b4f4eae452d76afebdf47691d2afacd1db7c6a3032e9a4c4ca744bb496258b8ead", - "0x98e759616bf468bb4feedbebaa8df381d01cb4b0009a5ca5fc980426e160698abd6fcd2095cf964eca6f8d92fe1bfc42", - "0x8a29df48ccec0ecb8b3d904078897d996ecea1d2db6b40b79fe51bc5dad04358d7f7edb6543d7d1cf0c1f54544c3d85e", - "0x9422e88414d88e5d84b17f9d2f1c50fb48e9c5b8de215dcd7c52bb26a6ea71cf92c90f3004c4fcb34040eacf5b60b06b", - "0xa643123915445bf0e528d36dd7f2da9a3b993f93a7fc9f6148049fe14eb5a0063575d971ec955aeffbdce069d0bc2937", - "0x81741f92a157bfe12aaabf0d81121e5a8c7df2dae86f5fdba826167c4558103363c653a928babf4ad7e3e80634d26375", - "0x904fe8e258be2500bc5566c3890a9372c9404935ba19396e8cd30289cf02bda13ff3d776bef56dd87ce57aba0a8539bf", - "0x811997c1d70feed33ae3684eee512a46ea91400b39638d405a8bd6f1d0169706f48d1c04beb1c5afc5b10879390a1a0f", - "0xa4fff30378dcf1f04eb97951b85abc0f5257b9e53b7bee814a5acf060919d73504db14d55edaf54e4030b4c1d7278c57", - "0xac84f2568084ee7a715b2387e3fa3b15e6940a27ea99b4fc9889c828179c55f29992b68d719323c2ede8ded3a4e16553", - "0x8fa542c15bd29bcf72a34b3c56eac1e7d4e4f3b15b82672cd956d23a9b9863233816ffbcc6738a225c86d9dd13d1c3d8", - "0x90d94517e7f1236e49ed6903db72c0de3098b42fbc76afae7abc1b09a903cf92cb1bb6a6ec8c29331e05b8946c2e9e2b", - "0x916c0d6b1fb7c74c0001599211ca37812f30c309cb6cae529c876221c5e7187876d37268776451df2aa44f91a3a17a11", - "0xb9ae0c4f0c00e8b07b489e015711346caedfc7cbbcb36acf3a2ffadf2a8f11118f93cb70181c27443d42634b2f2f6d90", - "0x97a51eb35da8b67e82d55fed968ac9aa42cf2d1348ac146d534344c4c7535c29ce25dacf9901abcd7e8c43a83e34e25f", - "0xb2f035822c552cfe3325da99f28aa31b847477a644402d43379947ee219fed751497cfffd3536c91f2474a94bf758203", - "0xaa2fc0777c208a2efb2884dff33c459f2f6c9dd4cba360a65918c8604cb02fd493c8e5d26069953bba56039f8bb694ea", - "0x84c63bbbea15e06775bd39f39995afc613586fcbaf32c9ada1410dfdeff09b8e7f3dd0c99b23c678ee72e44543ee6443", - "0x8259332662ff222d4d2f817bb94033d458e650e5f6e2c31ca84c6f3a4b3d2e8d1f40593083337a17193cddd960ea86c7", - "0x899fc292aafc17d01c06cac50a78edf1f11c8c3253f4b32116625537351a1e82ee3cac67725973e3563fdd73781985b1", - "0x92d3b9aab29854556666588d94c3b49d159c9ba9046487583469ace7a6b8ffa61164839dee24d28dc2fd36b9387b6225", - "0xb54f62077e1e42e18586a03b3d3fbe3fd680dda6988bee8aadc95dcde46c215167b261433d6cfaad8e2b3b6207662af8", - "0xa6c021aa10019319f944f8a77455ad5b153a502dc9eabd9d306be3830a4fa2539e3cb380355953c3581f12348b919214", - "0x8cdbc2c995699cc83768dd23383fe492a1bebcdfa55fc4b9d1113e510a6f4432ae55fd57db732eb56265dba6ad452c46", - "0xaa474f1710bf6556538fe389694b4fb737713dbbc9c93d8a879dd3aee8e004c2441dd14b5f4cdd4a98e804d031ce00ca", - "0x95448d62b1503e71d47ef4f5a01c60c938fc3cfd9280d7b6d3490ef331131130630425adcc53c9c96f262a80c3251e4e", - "0xa4535757aedbf6d7b9bbea99f4bb7bdfd1c99d5d976bd8d4f8c69ee09c9902ea81884d8b6f4fc084e12702fcbb9e4b3d", - "0x87796bbc38d5c2d9a56a65ca91a40530b57fc2a768e9e08a2081734bde163f39e069edc99e87a84b539606164993f30b", - "0x8cb7647e60f023066c4835c956186b9e997a7425cc38465e95be568ab448b7303977c7ddaca73b78f6bc137f25e5e020", - "0x90584dbd8f672a349682effe2f775f2bccb1911b06d20cd02f3a6e30311c6678e5082ab87ee47af72e0c064a43592bea", - "0x8886147e87a552c74767faa64516438d6473ae275e72b4cdc174825696a4d7878297b1ecd0fe1a62fa4559ed232e9e26", - "0xb739745959c324a62943a225140daa51faa8e41c8e20ebd68d6f000351101a89341641933dcb2ac5b3a45ebbbf7fb26c", - "0x814f858b4c709694472eae1c82cfb7370191ad6d0cc5aad69084fb8e9d81e90ac2fae52b4051af25f1b806c273f61e0c", - "0xa00426131acb84ee08684f2fc2a3ef01290e48e6b5f96bcb0459adb62f4190a4b2616eff2a2712991c48adc551ddaf64", - "0xb37a1e92b72e3ba42b79dd997bbeb031a392e42606254965597ea4b8a2ca51f8c324619fc2b9f886e17b632ea3bee629", - "0x90817db93eed264f49445d1d3a14ddc0d5ca93191b6baae278b4c48231a56b25725ba6f7ac0e9c7326755f0082b79587", - "0x95b7f470ef1630dee768698a31398e8cb407df3b651a15493c38f6be6c7eb387148124a2cb1fe1237117617017c12203", - "0xac49be639391aa5dc08e8678cc690ff617e9a0ab40166285f90c2d889c87ac70c455a972e61cfc339db59be4394a0ad1", - "0xa6f5a698508f8047edc45bd605ad4e88245de20013e7a4e51994e99fc60d81dc945504b24f23f7241f28059f4b5d6756", - "0xa4d30a6db06153074871c6adb0ef4e562c1491c1f9841c110359dc41a3bc0bfcba3b49fa53c29b8258a814b8ba1ba328", - "0xb25a500efa7d38f797395cbec660250f4a00d104459cdf7a15b541db3917e26bb7568526444d469d363040fd094680ab", - "0x8444d11f8a0c686e2b22642ba1b28cc556ab7311686028e3fb4040fcce22959b7b6cf244b77c711ba86e350e17411823", - "0x8ce90bfdfa93cbe58421be78e30e471b2c6e6beb1f9b3f85031cbe269305e18d25a2170819f2699346bdd735b6f5d664", - "0xb73970a3dc993e28b71bc236b3391acbd85a8cc622b79e669109f9d3ad7ce7a01a8686e75d85408c54bb70ff9771ca80", - "0xa64cebe05fd027069a18f152a18be155ed65b6b563696e395e045c8b2f0455fa75c2ff41c1247e596451b36ddf258460", - "0xafec84a7a480b09cecdeafd025ee3ee02e3b3338b02d26cb3b7575ecb895057650f0955978d1d732ca2e6b391ed97728", - "0x8caaf53038bfad6e0651e61e9a44a39027d456ff3ea46ee9d8e190698d5a66938d5c5723dd7bc75f0ddab660e178383f", - "0xa91607e39108d2540b4b5c9d33d96328f56ce9574ac9d1d4a81ab5c938443c3d7014e19f77cd55ef7be0a408e44efa43", - "0xa3f4c6629a3c0f34ea060a8b976096e6fd3a91c24d2b056e9b6b60088bb0c706e25dfb31079f42e0ec031aa840f46afa", - "0x96b9c7d3f47ec35ab0270cc57841e9f3b3f5bce3d26faf6abf6cf657b6e949ce0bd1ccdcf9d490beebce722aea48caef", - "0xabd2433b4003b7d861b35e99b51e2eedaea4831776e7c289beae2b561ad69a771233e3d6bc4a7f869d0744c5be61b5a9", - "0xa989e5080d39d4031aea86c03b77abe069ea9b7fbc515c6a79c825eedd6a9bf6a0ced1891eed20edc605f9e25a691f74", - "0x93ca5b311d28e4dfbf4de84a1e1530a9153599e0853c9abd3671a1ce04995e00f7d3092895461137fd78c72d24a99494", - "0x8acebb0309595f4eeb990b7a1543f0633690b7469ce89884d5654a7bd2d2543f09232693a04e1e1b445e6e0041c8b242", - "0xabe3858cea5a873a7576d641571965736d55d46f9040fec219803740dc2a5b43c72689e94c9b61d3c3c44dd3a821b694", - "0x947cd395aef4faeca9b78b6cfcc8b2f8f361de884b29181266fd95b21ca6176e7944058e20cc77c7757fbca4fe445394", - "0x8c2e50234c75d645f3c887693e2439ef42433eff328111b9c37aa3ad5a3b21678ee44ee2959a91610006b27a0f5363b2", - "0x967253e02e34069ac676063aae9a493bc6d52b8bcbf1da6243bfeaa9fe05f8c840ada0a282df9c0180d05eb866402441", - "0xa16a4c9a11686a5294d8329983c8a4aa0e6e5ad0003ab319b493842e8d072aaef45c3335d9a64bfde6bba120a48a72a3", - "0x85187b866fbc72e5b42b91d76e7ec2647b93bedecb060b7475236d7d152d17f901c778b704f7c2c1d3d269341890c233", - "0x83b192d925e3f4a1fafcf22cb48083b2f88632ba93c1d498539bbc4997f61f74a0a3b8d4947253a0daaca8999c407b87", - "0x8338eb3e7f549988435f4f618f4ae1942c6756bdc28a80dba7ccc530bef161c0bbd20f77c8c4d63c94e60bc12f7cd387", - "0xadc869c5acec5e58459eb804c2141e03e3582ce1fef7d40fc1dffa3ca0f923919e291a2ca4a60129e2a470cdb395be31", - "0x9279068c28840f2c34e48e9a7e7e9406907ac14bdf4eec7b8c28ebcfe16a18fcb047015e4309f19e6fd73d6e6c454157", - "0x98c4fb637a868f161f2f4222f93f3bdf13a61ec1f4e4c20435c150fca1bc0c01c790da91afb6227ed2a6aa540d70366c", - "0x9209fc7b307f40294bd9cce166277a7ade9c64277c600b3ff09579fbfffa471a887061e9cb5fac97c423eada30a8a52c", - "0xb1d63569d5d47d052f3a6e8b2c390bfac1e92098291a2adb209f20af875ebb2a8669533155b1d15b232690e26d399ab2", - "0xa2c975c004e69e7b0f22636141d34adfb2dd1396c7355e95fcd0493e931eb7eb99b4df0f0f202945d7bf837809a29ed2", - "0x818f48e65e337913c52e9256af134f4311be84dc332e3ac4cb5ef659b9c6e9cb34f04b0bcc0e2a3a574c9c3cc51d7368", - "0xb92b63d0b363a368a348a4abb10661c38ced99a3132afa6cf002b81e6cac26f862c9d0a6886aede555d7bc453753cd37", - "0xb4051275cef361cdebd254115275b0b86692d3802241cae5e2c75accee7df98d3165cd1de86226f382e736b12d9dbac3", - "0xad89d85749c23e045bcb95c3433eb8038139a51c8edaf06b5cb235549a2f9ad17589097ff8a350e934c8662a8879a3d4", - "0x802010e6dbf4265cdb5b5362c0b197317f2435253237561a3a7bc6766f98b129ee06d370382998ae70080624fd65831e", - "0x8ed6a5b601a5ee11e983035f3109075444b063aff693b3601f87c0d76d2ac253459de48d0fee32330c3785d38eab5cc2", - "0xa6c8bee787c4b87137f70c2c54ad3ad0955269c7ea57ddabb1a215e613e250944cada7f241430c0ef09f8eee29fadaa7", - "0xa3fc6a643e1ce110b08344f8913ea7f8c9e44bdf1a02978df8dcd3671d9b357397df9857fb11ba220521d1ce40064ee5", - "0x94089626bd9c81247f45e25e573bd6bf727a0e1a7dcd630dd5e661f65d4b6f35bdc16b64da648dfda404b5eab39d9152", - "0x88362a160a95f01026a2e52aee3521e8496340f96a35351892034198740d8b6159175c60b910a4ee05af488dfa578c8b", - "0xb55a5b875f5594bf41949c212543517bb1ce34db3a896f93d0216813261aa95f73663c789ea0ceb2bf8815255bd328ca", - "0x8f9acdca0158df5ecea4d574e0ef0c256ab271d9d3d3bb4100761f5062f0a1a5d2b8a23685097a1a2b2a08287a2e2c94", - "0xb6d4e3bd49a17fe7d929b41fb223eaf93141453f7dc233eaa74424290014a63ca6a099174b687048d59cefd41fc720db", - "0xac0fa8aeca20a0b4189e96c57c85a2174338550855f9d0ff0c553e773a1a1c32fe3f8db7c8362bddf601e41380c9177a", - "0x82f05710f08f12b206b2ad6a2d06161c884b2511ad90b43fbfcdf54933c2360b7c85dfa4f598b5bdce8809a803d483a0", - "0xa2ca711642fd498cfeb897e4072d13e43b5cdb2480449975188fdfbd4b471070cad941af03a2dd8938d3c376366fd199", - "0x90c27a1df934339bd0821cacaac41fa70496900044aadfccf6e5fe28ceaebae5cbc500fd6f2f88c5552b7fafea79d06e", - "0x818651b7c7a6f691fc47a61ae4960bba7239007e14930f3a8cc9c95dcc0b03643047671f819e30d89c2d1891640fc13e", - "0xa88f01062ded714e7f2f1523644222cd8e8cb8e535eda88738f4b4b19079f4f7be664abedcdb618ad1de3e74689042df", - "0x8174282a183f3f393667352fdd60460d2199de16752c372a44465f8b71ca134c410d1d81f15afac839748447875f8643", - "0xa358c3e53dd70e1a608f36a1fdbe225e28c13b5817dba890ed8e82adcb7ae86fa68ff6cbda7e02e8116c11587ae1ded1", - "0x8aa0bc208a84d5a58b0206a8fe5ee3c8d224ccb86b11b7c9d924e16b2853a6c3623502dd60b94f8d720810e0079078b8", - "0x8bca870eb6cc5f7b5f6b84f88b49d9a3994e61ca3f2ad963f28f925e58430887f5362ed4bdc2a2a38b5fb9e774a75cbb", - "0xab86840fe84b1eab81675eeee17f85a500dfcc95dc4872e57b39919ccc71b702585ab9ac66146d264d2bc8fa39338a72", - "0x87c46966a4bbf2523dde607852a40b26cf3431d0bde9b2c609997c0f29c5932d28014026862abb7d4107b28ab8e2ba70", - "0xa91666a8c846a9944ee7ab243ab535e4124ca8bbb756777609aad848527b354060c484acc19c333459c09012670f03f7", - "0xb7145784894c6df87d2ce6a06cbaa713e46097b5f83db60e5449e62ed5bf382a7fc3136e5599226a2fe7951847527c4c", - "0x951bdbaaa06ba8b427fc4ec6bb44e93e70692bcef6369fa06c7a6882227d27f09465f37f0a5868ce43ade188a5f37f8c", - "0xb69662dd5dcc9ce7bf24be8a0e85e80c8e5af9b030e740796f91de548569bafa2fbcb19d98e13900c76cae3fb601a8ca", - "0x9630a7eb15718a2324518f78f26a71e3c801a8e2eab3236be7623807321c128ccd79c74ab657ea8e115d6ff3078a6887", - "0xa2f98c2084f8cd556cc1bab19398e98921ef56f6445f63444384efe5d7c895690c57d0d94cfd24e99f63f5e31859e34c", - "0x8c3994d3cb76fc6ac22ba2049ea4547db92ef78f009d24f08695b282c95e395f2c1477bd52d3f569d64551aa5e259b5b", - "0xb58571076faaaa547df9522b48c684b310500850339d79d2349dd8211bc2c8307d13cd5bb7571e0b5baaa013b502e410", - "0x93e07feb14f691e66be756b37467f290da9a6677b8ff565964f010fc20ed9c58d8c712c4abaf012c787bbb22cd1473d9", - "0xb4bc6159db1578111190b19aa678281eb2fcf7a82c7f699da7473720493e66e0ab54429da7af24315ed9f7399863c954", - "0x93cfc98563f25b45c15a07780ae0a38c4ada52ffc1350233a3b45417c16cef92e7926354b761d0e0de55aea4c1314406", - "0x820c37c923807790d77d2cec39f0eca63fa3ac6eaf0a1978522f0b1d293a5c46af3a0b4ca542cf39e796afc1fb3d7195", - "0xb87fec722faec6a739355fd30a2757e5d184c07b5bbab8581b74eabc2da413faa6d11ccd65cc93f886c788239b1eefb7", - "0xa183bac7f647a0c15b14089879a8aadb712f079bcf2078d3c65851137a00dd3ed7e47263c064feb19362f98180aa425b", - "0x996233b2010c20e0246295735b6d5b3e932f2aeaf0b35aa3dee66b6296f39e2e7ee95a7e1a15838ff3389ecc8052e315", - "0x85c943e09a6c77e15d49ef4fe57d89744fcdb705ca370cdf70b3d84aeeccbf2155868f6790333f88fe36e08042ce195d", - "0xb88f82b35ae14a3e6fb972c47123236bb7db08b9f9f3828033fbf5a895b09b9b0de423f1caa04b3e8e754409b21f3a52", - "0xa12c957409b6dd335964532ce3c045aabd280188b4d6ee809cef479e51dba030cbecc86b0ea8777cc8828c087006c5ec", - "0x87227fb4299efa535240793cf0079e952e971a18ee62cd71a62d6a5db921da669c5d8eb1bbda318ed5f3b03b38798a73", - "0x84b5c7585fb1c98d031a0bf6fa8ad5484c7766025af552cdd72e7ae59247deb845f8678862c44ebe640a7333cef8391b", - "0xa94cdb0f42ae3afb4b1878f960669bd99008c7ddc24f2fed45ca521c60472e5587fa9bf97b315efee1f74619a4d9b762", - "0x969a9bd21a6a90aa30fea44e397cc88118fd5abeb68839556194f9ab0076806aa320928a8ec94a47c4eade15498f5175", - "0xb2fb215bbe7acc3baa04b0aa9be654afdc450faabe2702a0c9fa760c9e9389a58aa5e3a4c6af4f6f5c934640d90b59d0", - "0x8be6a43071464e6c7dfb0e9a791a658214c1a95adc88f144d8349ecaa0e76b8ea5f88cfe95e82694bc170e49a43ec4cd", - "0xb75d56cfa1f3b61428d24784d51dd65b78b977bbb52cd71401ac7d3c2643f3dc097d6e7668104af402cf7e7e6ddfbaaf", - "0x811005c49d1be377ebd2fd3bea9771089a0f5675c55e9da5f31fe13cfc9d1ff8520f085918279ccbdb0363eda16f8056", - "0xa487f7000c16429f2b7bd7e8bf4990bf26f666f8aeb11a99114d33e24f946cb0e3e025ec8c0b0721f9be101504c8a1ca", - "0x99b72e711ba7b97083976b2db7b97346000a78bff9b20ed910eaad02f6c03b45fb3f0f1217b328c3e2d87b481eaab52b", - "0x828429d387a0b83ac8e377b32db1c893a4555ca253b8e3159399cd053c5de726655a2ad39348c8e7ef11b37b0bca78e6", - "0x835de10c73da7f0c07295a3306ffb18991334c86e5fa4c6df2d8091e8989f6454c8b43721b13696e1f665480a64284de", - "0xa4ea48f0cc5915993c83309df99247dcd7df4c15c723d168759175010fbe8d84adab8393707cb338fb90a6a77b69745e", - "0x9976bc842b06ffbc5afb309eef8964908802e9a5c733de4a8292d5d5773ecafb6daeecc63a8dc8847d76b77d4c3915ef", - "0xaae89156b013e4adb4bd8e7b6007937f0ece09af077fd407798e4155dc09a129d44fe8f8b5f6cf6b3c84746181d7f4a3", - "0x81891cf2d70a8c326c6870a8158edb79babf302b4f9d51229bbafdf038cee39b97f01694eb719df99a62478bbf909a85", - "0x97bdcb526108ef3cc2205aac074ef3001d528f56b4889573e0f4a3a2504232adf23880f7fa2f57bb787ff30b89599da9", - "0x9778949a95fc663c742e70596faf91ccaf8b5b93b78bc2d4993480722ffe10bab3c8df3ae50535d258b6e749a0abb86e", - "0x88bffdb927dd88c1ba3eefe7da3fd6a42ae494bf282e094893e144330cf08e3f518f47aa1dd76d6f249cf83e6bb9d4a7", - "0xb53effa345fe59917f4f1ae754e9f6b8fec9bd34cee85872b3fc47e52fee29c418b9406aa50c8d5a2e853d6f2056a49c", - "0xa616775e7e77e846066fcea413f0023dd491d8176dc450b0941368680571cdd236f0f83883d268842fa61dcbf3e4864a", - "0x8b5ae13dbbd07ad19bd2c7bdb48eb3c760244fe0caa73d28df3f0c31f5418f8b06862526f5a84bb16b2a92eb2c7ebc28", - "0xa62294830750dbf43ea497155b185d611d75e767aafa8c2c30256f8a4875b6fdadaac429e8363848b39e964cab2aaabb", - "0x94b6973fb87c2efef5efc0e7dd7ecff5ffbe76320fed8a20d730f9e3751efe9e5db39981f45477ddfe037e18cb971930", - "0xb931b6f789947b5298c258c8f0b221ca927c447f190f0d8afe2f18ce9b90979eb3247e77e128a1d6c57d3bf5523e787c", - "0x968259d9d001a08c0329bc19b2438b48dceb5942bc6ff9892d78fc36014f1b60a5ce7deecc7a808e41aeb4e26143aa41", - "0xa52c1906f103e3fbee8c12fecd93f7b7d6f37eb733147bed841b32caabc880fd6e72884380a3cf93129d9800ee7877a7", - "0x969dd12f0f6ef0b868e21539dcba5dc7327033eb546570f5bbf91b13f9c2ba6070da430538c40bc53a2ace4794514038", - "0xa853a74380d78710c212bcfa92d3f9c433b8ccc6936356f3bdf923b8e420e1017bc530ce73bb8d04bf7a25b20594c753", - "0xa84dfbbd3d9f409badc8ac2da5a0db98124df9d01bd71b1cf5b2b9c32866309304848a4bc1fcad1130bddfb9636c1b56", - "0xa9599f55173e77dad54cfce6ddc77bc48588f36b79a98c156963a2f5397262ae07634a98ab9bfe1aa6357f78aaf89d89", - "0x91e429b5ad0bafc09b5eefe600e179ef56f1ee045765ab3d5ecbd73eb201305a6de4382038b1350abc70bd1435151a0d", - "0x8785056b83a726622c565985e815847b63745fb66b138d24c985d6f42d5762c61ccd5172d4a3237222c881e5f036b98d", - "0x85869796ef180f500dae84f669b76a9b245e2ff4614a58d74820c22e439837b7d9866f480b72d88f44682be54c6dafb8", - "0xa118baf9c17d85e22ac3315f5ba9aa4e230ca2a031906f99bc43fc750a0f96aaa5e6774d1cf16b492726a37db7b51327", - "0xac8e33f32c1cd14c6de14e75f83b8518bf1bf6f0a70e23ea0e5a29f096e2992f1259a121bbccc5252b9668c605240435", - "0x97babe93e2016d29af74f776e167d82f1cf2242202bdcbaac4a1eba2b3fbd9e7ce57cdfbfe799a0f6a06a0e6838c4e99", - "0xa70acd7e1f159adf7381d3f3ec2cc42b56232601f18ee62fb650e13a80954cd06d39a57217ebf4d8927e28c910671ae0", - "0xb33ef5c10d0588df0b9d2d963912b294a2375a26bd25225f002cdc206a1cc058465c64180d348cccc899baf3d677033f", - "0x93086926eb1be21ab929b0098767611bdf1a853b6b67045c14f00714f806f8655be37150be1da05c5d2e6d9c66057bf9", - "0x8890aad532a6c9b818ddb9f1ea12d627010b4120fd4969bd239a9654a05116272d4cf783ff8256de337bc01f9b4154d5", - "0xb878977630f647a5ed7c99f59ca5eb653cd647277b899b918e5f667eb17b6dc433b56c2f3a2a18a785a4b5a9ae95f372", - "0x975582fadbc276c9afc4d8ef767a66684df5f56e898d2a8749cbc2763982c013e5fd0ad0ca7ebc67758124a609b59663", - "0xac45e154a651857f0464db38afb2fb25853e8bb1eb476df31908b13b4fc86491d4f831c0a15ed6bed0c873b3dcff55e3", - "0xa778d373e361753964a7fe4e1d28702c62a919e5203b941b04b0e74cdd3b4e838cd9b6dac3c39dd523f3227f1b5e6766", - "0xb1bab7994941f8de4235e2e188b302bba847c1618ebdec7fb782241e9eca8d32dd506d254d865e0319c66396535cc484", - "0x8c4ae5b346325f1d1609328e41d20108c4388bbe021361a86a1f9681caf1e6fd64896d72641ba8c984e153537317420a", - "0x8cd312c6a23e56657624d21f230a2c22d102badbfb2e38a8c067186abc5a459d0c78433ae7b54334591862c18864d7fd", - "0x8739d71181c5a657c6fcfee1df71756c3b6b8c48e8d97460fb64eb891abfd23433ccd08574a677fff600ffa5519a2363", - "0xad3c8d1e9eaa6f9122fb14d323318bb0338c5f9f29c719715cbeb15a2738493930542769b596098a5f505359c0314381", - "0xa6d78b78227f8c1203e502caab1213092f320e77a6e9729e1659cf81e981cf905170e99b56c4eed1326320acc6aa60fe", - "0x8e5ba0e69e0f08a49ea4fa28ce0792f7ff6c032844ceef11be90b2215940d4b0f3e4acd5e4b189b189b0a0ef8045aa26", - "0xb7b31957e7a85a640b851d4241c7b2f6af64f59ac221783b88c1b51cc4185f5ae6446a3c7137ee072c2eeb97c929d7ce", - "0xb066bb41c5818d6008349dc6015ab780633cd961b5d947062e14618c1ee1abfe42139c86b77e7f5be0c275fc3f5b8909", - "0xa6597158957e1a0af153183151fbc4c73bbf8156c77f7b409d0f97470b5e127beee6d9246bde770127d3e3ad400cddd4", - "0x82a6de6344e5bd0c5ca95f3be1ccd42fc974403269874603944c08ae1cd3ca887e66fc51ed61da8b7af3cce02f152e6a", - "0x89fd363aea11ddb2dc71288bb534a4a6e32feb5b9e4b88d132f4181f2579f8f8f39d19fcdb3d3d7ea118b9f4431520ba", - "0xb70c945986c8227d239201857e12cc7cebc932c3bda8913c82b62c872b18f866313738788e11bddd630bb5953492fec4", - "0xb4e3a1e8f82d988c85cbb58d9cec69bc63fadb4c1c9a09f36b5a61f1ee96baac1a9458bfd0f3250e1734ab3fc6c0a0d6", - "0x8d01d1eff496e8bdad1e6fb4b60e4bef0ada39a378c8b57cce2c115e611e0c4fa54f9b599e4c34dac925bc06e940eceb", - "0x90857123505746f7bff08e03b1a90f67051a71ba47b49e7bc63f1a8ec30e02a98aecf56465d3b4746aae166081099da8", - "0x98b9d3b7fe1d5449bf6498c30036e3f60c8b90962fe04ede9ebf605d07497f617d99d51f0f0c0c14381377de765ecfd4", - "0x891e7867e70582ade85730a21c19f6fc239760f76f8bbd8c6dafeddfaabd6fa4845f62d649b454fd3af8ae7028ee5f9c", - "0x945136f71f290d8cc6bf282b554cdf8ff92200feb7901987a1348f2d2edd3bd7b7bff6f57ec25fa302a27121a1a959af", - "0xb574d8379842467c5f3cdabc2da9a45e5a6083efd7298b077ccef2c4c3bab03abf1dc11507f4c896d745ffd41e4dd319", - "0x946fea5c1b1d840c10a5a732c7dc23c2bc5eeeedba6956f85ad78fc1ee4a95b309c0d4a4919d1f7155814e3f36fe322e", - "0x98befb2f7d0a860de662f28968fb6200cee5a33cd7a6c376405a9cc6f0718b90fcc5cd5b9142e84e95223a3dfbd10f29", - "0x8c5f208ca23aeae44a056bc21e04b570c13bfd49b14681cc085d5b318e62e4c088f0bea9dde52376967649919b59829b", - "0xb14478442f8e985618272d4c56d43a28e10112ea200b52fbb32b19a4d1eae965fd5ee1e9772416d52dc0e2db57d3ecd6", - "0xaa308b19a57159ff702bceeb69a385f91339db7e683db991b1414bf3af9916d8e52dec4c492d7e8b5a5a011680defc1b", - "0xa8ac18a1adeeaadc192e15b7e92b611c936ba9cc9aee01f03c2a236827ba527039c719f16149d7aa5fb297cd63878766", - "0xaa09af54f9a5fab6a61552421c13ca62f99fae212a9606f8a5a050997271ab6dbc763390bb98d90b1af3bbe9e8d9513f", - "0x96b8ce26b346a0d3fc99b6e463f0c619196cd85340b795fe1c1c0cd4f1b3a9f2bef5454e0bc7d51d75ce26f217460602", - "0xa3efa46273c392704ba0718a44f306edfea03b1a6be0bc1e5c67c62c89671c650eb8ac9bacc35372ade6bed12321f1ff", - "0xb484881108a43a1dbc16a6e7369a04286f896aaa1dae847b4019fa287c18e9d82c8ba4ad22cea2837bc220524a9a7a17", - "0x827b63d83e15ef61d54dfc365ed8a4f9e200d526884ec4b1d35032227245410ad7e1b1dd3c1d0ad48ddc4720f0fb5e1c", - "0xb513c3ddafb01b6189590b74d20348db74e400c106202dacd9ea9552ee2c642909a7a73ed7ab45a986eda3a0701be79d", - "0x831f4030463c84cc6cced28dfce0b3e6b6ead01afa200ddffd807f31ddd4ab93a8699ccc9d26970407628d521118ba6c", - "0x86312e006a800720329b82f6feb2934e2cc156958ba394278caa6766ee10800d2fb8907aa92346dcf6d389c4f66f5e1f", - "0xab6841da372a286fde1dbbc57cfe967cb4bebd6fe2ab9e317cb9f9eda04a4db0d5844ffa8db72eb9cc6bf311667ff6e5", - "0xb8238dca3f2be29bfc4aa65a9f59bd4e2c17fae78114a69bba1170782b993afacee3755e768317a923fd32d860f6a74f", - "0x923c1b60c052a3ed4736da7e84e52b0e9e154627cd90cae335dbdf05af109ceeaa016954d6e47fbfc40d9d5649c198d9", - "0x96a69d18c838512d95d97735263a8cde752b2bc790b3827ce048e177a063dd11e2a69b86b3184873503a68170b2ec255", - "0xaed7c3af469a93c22afb47a904bc70b7d88262ecdad48ea6a6c07eba7398410bf5a97a786beb11843cf40ddea9a37098", - "0xa6b50f6369ae558dda3ceb8cc9d99382a1e62d0d9804b903086845479b9381fadf8d4595c2f342307c94d09e02e0ba2c", - "0x89fd703d457580a76544bbaecf65f93d3335d7a22e93d14afbaa61e5236d9c8d8b005e403e9f5e7a103b0386971a5e65", - "0x8e909a3638208c8f885820af8bca6ae839128ce0d902a2b7b6f9713d21da8c943a7984d9aeee7fb104df4cbd1092967d", - "0xb41e2d7a1a0082eef43e886eab5e781bd961a83155d6a14d62756ab7144a208f4e4574d47d2ea094a7fb50d0ddd7a241", - "0xacc6c63450d124014a1db846bf578c44e910436c83190fae428fc3125ff2065d4c6a1165aea799b46093a86126d4c483", - "0x8dc63127435cf2f269a83024b562b3f4da18aee3399ed5255c295e6b80c357cd8f1887de94bcea29f84db3193570c417", - "0x8c4cc72a98d42b9c5807322f596ac0b48b08b16ec956ea4a70c641a16a70895644e5b14aee85a4046673849249115abf", - "0x992afaccf05d79a3147d2985970c1793459130ddfb18a9d31f3036c260790109c9ee6a19a812f5d55779c2facf87785c", - "0x91394d3e84649cbfe018d9c709604f6aeed53e91cd57e2b29d0e84aca3c413f1e0135c6bcbc70784dc8985a30b9f3fb5", - "0xa33fc126a8f9652c6905b1f522bee45848ce42d7f4c3a4cb3f6ce0e6e64c82de74e175c2ab6b5a867a8d42322d257ea8", - "0x962d5fb816010a14140767c2486cd629f7793b304a96cb82ab85a867bd9a407bc8ed44131f87238c34a1e4ba41adb1f4", - "0xb09048879ce26a34f56e1d4b2cbd6eb2a55d4ddcf3738c462329ba6726fc0a21b8c1bb55169cb8338b2debf20dc0927f", - "0xa9f9ddcb86b7427e268973bc7f3239212716df394394fa535b9fa5225b6191d234a44a126766eb470ade18c469a2d166", - "0x87cba6afb115c0b3a515b08cc842e7cc2c705880c82184174837c0a06e1687ef668609c2ca080840fff80282caec7390", - "0xada109548c449990dd8f1bd42c9cbf194a62625d165416ca469c334222446fad7a743b1f584ec3f20526c3c44d870696", - "0xa69a0c58fdfac715311dbd37c4464f0475120362234f5366ffc65227e8178e037ae91efa5a52cda5fe28243f47390175", - "0x98e726cf884c6f706fa25fe25be154afaecc0c3bcfe682a85afed225bb44ea07cd1767b4d9f2201358ef19770330f0bb", - "0x988ad5bc57f0621e3ce1256720f1161e785371fd478c467c39e554e2de27df5ab8176508658aa90ed7179bc212ed0dac", - "0xad0ff6dbfb397da51fa4d9d959ba5819adbf1a1ee31f68fbd62ae07a9cbce0641588cb1008dcd0056c17d74e83c7114b", - "0x94c703cd32b9f52c41b07aee3e3c19b8c2b4182da80487ed7991d568ea8827f0cdbd1e977d482dbc102c4de2058903c9", - "0x906fc2a06cda5d82445e50bf475dc4ff2c17e64c982539c26545798f9e4dce0bd4daa8d55b027cc0a8e1b26c3e45cb08", - "0xb09a51f22a9a24cde25f805cb18754e27d3d168e64db4ff13a202839a81c69dee8372b5872faa0d23fea283609cf4391", - "0x93c908f514e97502d170310bc495d02948d99eca51e71f9a4779ebabae1431e1f7ba465af485546a9fc97c025877df50", - "0x8337859db710ed7e276a47e90cb3912c829151cc2bd3dbbd4dd04acc90c9cb4870c44f4233b407db8240a93aaaf7302a", - "0xb13b16ea0943e235f8cb064d6dfaba9bd8dac67e9b6275a4c3f54a1770b9c905d8855517415ef6489843848108dc85ff", - "0xb28489f0de1a76839a898b8d2f302c95610f17e893a6e7f2e824fec773cde6f2b219038a3f1fa212bed98c97daa66d5d", - "0xaf13afb48d56caffa32f941ac5542ec2b7fc0d6dbc3b16e51bd2a8b633f0a041ba1e098d9a68c470da00e630f08f24bc", - "0x81465afadc45ec24825cba7c9edbb36858bd2ca8f8b0b9d7240152b58a411b08503b530932e7b6ec3b0f8612869cb244", - "0xb2e6b7438fb6f70b52b8726aa870f18191920bcb213a375817d100297b507908d79567d0c1780b3f25be807a8ddcb853", - "0xaa7ed2b0b2bb2070b5f001578efb3d1085950c19f9d06584a2d31e1c235e6d6d1d7f081ca6fa2b0b0616b01b9a467005", - "0x91a245f1aa8c8ffe03f7db1412e5466f0345196727eb8e6f98b80c01672e4260e90724a538d26b094e678a3d85f2dda6", - "0xb9ecde963c8176d6a726b129f229d803d1a6259533e432eecd7404703c972ec7296ba35015acb1f4b5ab2653a3991902", - "0x8cf535bff6e98f75d98c5d2a691a5d1aa645c7ea18d677d07d3a11a9cfa222a7b8edd048529d163120a5aca411161314", - "0xad2e51afe96dd0e942a7da5a37587ca1359fc17cf66ab70cf37ab70ea34f90054fa52503d6527e89e464f8058f5cde79", - "0x97337d62f83ecbaa1f402c3964dabfaeb279b916ca08430a61fad6c31d60087c7e3a9decd541651a2b6e68fb2816bf45", - "0x898b7581288bc7f97987138b7481d29e2cfd5255ebef133177d9060790a0973ba07de62cdf38568c336c237cb084b7c5", - "0xab53c0759663bd976de62f9f98fc82fa4f58c146b8a6a3053d4dad836c762063ad69a54d51b5499e9def86f8d4bd7ce5", - "0xb35ba58109d44c14be159333b999c1e471fb61f5ed48f9d2a6bc689eb045864f3fe88a6ecae12315183703e2b1fc1ae3", - "0x858a20e233f2860c24c5a3f4a820cac7544eb3ce91a2d8284f12013b13120121fea3c4f25427c3524a1e883aead429e6", - "0x965be1a56adffa51f5d80761327cf69656e6c37577225b36a34afc2f8a959d8799ad0ecc3eff4470d49eb22ebf8f198b", - "0x8e575ee39077bd865d70fca2d93473f51dbc99ef4f715f4a3b1d9eb21deb2afcd0873b4dc53035b77e05f52029f069e0", - "0xa5c670a73da241f5888c5cb44c27eff2b8ad3f491e0b128e5f1d498aa6d76640c9e625f3c5399ad8e99b666e4b2a9759", - "0x920e1524255b03cbe500edb230696c55b7774963535c063121c9e9987ab74d504f2f1cfa14ba7ca82a6f66745fb0b392", - "0x8a0bb7cb267b8e1e0cddee208734632b28313b3ad89f9c2168f341be5390bea3f09eaa51f8923b87697799a53201dc26", - "0x859ab9b3cd602e78dbee8d8d5c3a9eb4270f130ea4a1b417ca5612be753d20106cb2724097840ca8919a9a96e73f96b9", - "0xa76126d9a997fb0e7e2b27ac682dda1c6b99067313371387084be1f6e7a9a59bfac395e91f089e14cecafd151674a446", - "0x8aeb466c58e2829790975fa08dd31f18a51a63777070d2e24605adb1a77b5e0e5c5e0bcb483076d23a6fddee5f402f8d", - "0xa8959f312f2ce0d7d974a4998bb709bb98ff6456413ef4ae9bcaa8d687b8b5ecad91414bce8f704aa44a83d8a0c86462", - "0xb9545c8567827fb28d070624579702ab96af4f06fce515ad880893b97ad9a374c4c91d6288e2a584ef14b1ce4930a6bc", - "0xace41f9c2756ced611da16e21704a367b122ee1c2feb83145103203ace1a5cce0ebd3bf295caaeff05281672c92574cf", - "0x93b90e75f56601191e3b568368bf1d24f97512cd42cac1da8b42f0019e07fa04cd5f835b7e9503fe4702632df27ddc19", - "0x973c8feba289eb473448c32b141ab4a6f861222626b3f2fa265a431a54675dfe5eb479100a33c172ff935464d6e85f90", - "0xa6b0798ce89512644490d65ce3d0441ad950f9a25f6fe2c9a766a58b9c8222fa6cba753f314cc7ad6b6e3da881c67abf", - "0xa79c560dfa179075e0d1506adf5082318915c15048328b15ddca5748ebc6ed8b10fc5d7a50bfaf8942cf9ddc6912be0b", - "0x8841b34df170519d07edffc4d33a0e70c518dcf53ea8d0a9f13563822312a65d16f99cf596bb95eb0daf85435d4bc0a9", - "0x88527539258323edc2c296dc742cc26b9a4a984ca299a81705c702a425ebc7345a79b4df84b2d440a1e73a09fa18b5d4", - "0x88026753926a068e1cbf49a9a0fa545846cc7ca9abc0778e44f5b7510c1b6b73e9a9b1aff754199379522b2a985c0881", - "0xaa2c306ccf91f967b5cdcb50808771ede36acb9a6cd84fa15c6de4c873cc2d0a64e79778a2822a109b41f5205fccc30f", - "0x9751fd8bc2a07ffe0566e5587e652d3d8d5250517278bcf8754e14717b8941c07c194f14fa757f9a2f3461ca6376bdee", - "0x919746e5eaa18b734ef59c0de09ee8ec65e348efa659219d9eb6a3a77b8edd9449a6dab727e9860ca0d3837b161c0352", - "0xa271c146794d6a65c6fb87797262c22b1d938ecb2624e03809233469296d33ac0de6909183c8efa438b5f3971c8c3eed", - "0x82fbadd9049248914a15755dff692bf689eb2650fdc6e44e8f3ae003b8f15a0f2858c7a2a8dd137b3448420c5f434229", - "0xb90992cad6df98d2fd1c75bf90978205280d993d439c44d6721cb95d52eb042e01b418e45c3c48ed667aad577f3fd1c1", - "0xa0c3d1e8b80ed4a979a22d6a9647bd57f22ac8d73c37ec3d56d06dc178a5c9d5ad3ffd6dba9eb7844c1f40b8c89d3d33", - "0xb513aaf2f0a07fff3543d8687426d07773677ca4d23554ca517e50bcb891164d1f5651944a2f6e0a634475f6d33bf0dc", - "0xa0b179aa2ecf572ac4a3ed898aa34679be3cf3d8d9bc74e33609345cf1743e915124a59ffcff41bec012ed2a99447e6a", - "0x8e991c5549126d64e0b515a03d266e19097eee38d08121d942973d568f8ae23a15b037337cead0686f7c944b9fda3e39", - "0x93cab29e1bb116a39ce1a82897776da1bcac06ea131a7dd141a688ecd4089c5a0b8616d6721b1c6d92309ae0820a367a", - "0x8d4e0159fd3534172b2481764cae7746b1a47e9b7b9465fcec0824ef234674fc567c16ca7116dc90ba9b2ac3eef28d60", - "0x88cbd9ff6ca551a7efca30b5f59fedaca8f9efaacd4e9bdd17ef0dcfe4f3449b52c7d3066716f3e7fd478f264d10714e", - "0x873c71b2feef5230c31f13d8425f8b0eb0171eacb9225f484a36da3cc796d62594fa4771af9ce1e7ba951f5377e5db76", - "0x939eb99d7fefc9fd5b3dabaaa5b331365121374a4ced862b8cbe0cb3c574fb1f0cf4932106088a1d87522acc31ba7f77", - "0xb283f1d54bcc72e31ef572498821ded40485f38d2ffc67f70bac68a100612b020a538b36001d94228a4dc97da8fdaf17", - "0xb2e4c2be605c8ab3038b4e91bca7e77e127c5c3106913ec1341102e138bc8aa1d218c3d3c2ec1d36fb8e044b4bc211a5", - "0x82e73cb5b2cfd78c17131e871e92026643bb56916ae64f009a009555903df878fa3a2019b82f7e71a3ef7eb503c792d1", - "0xa6d828a5b7de0e7818975b65244f6efeefc712c29f2f17b27f3264e19856d869c350ab39750ba63d6d46afa3aeb369fd", - "0x865b17027e9d5bdf2de0afa2f524f67b6defed87b14e0af5f4b6b1966c2de45550fd2b6b39b1be88ee9cb06e755f917d", - "0xac8b47f9b7e675b556445d103271e6bd3b39b94d15ee1f3108fd1b777d439c75437c78ec3b281f7104af6d0efbf0ecbd", - "0x85c2f71ae18105fe499aa4da0a835de3e92ce05d0f28ccbcffdd2860898ae9909e1c05831ca4fed96545133bb3170302", - "0x8bdb4a72b06562591ee44799bd7400ebe71f6737290420dd4ba2bffe0050d8ea4d586b7e329541a52611e945ff1b16b8", - "0xaee4843c9ab02026ae723531112170bc7464f51460bd4ba5166fed54ecda0f53342cdf94f4354a5bc1b941e8ab085a80", - "0x84de368006db07c89a7a43b7de54a63637ed72379a41d029430f6b4ebe404866896d2e84996998f7b2c20324143649f8", - "0xa8375f69c01289cebbc97843f417d0146f68c6416981032bc1f42d3e09845d5131eb9b4d68fd0ba7f5b1223b83e51bab", - "0xb1ae126dda1a88fee9265ed8e5bccb810014044d83c70e01e7f80059a685067f4204cd15809b230caf5dd77738a64e38", - "0x8177544c7b1f77181735c660102da20fbf9a2ca4efa79b21c92f1cd2b912630aa6c830b7513980656bd275097be59d1b", - "0x874fe8038905065ff3b77f1e53904854fa4fcbdc4c8959fd2df2e3967b3b84100c6f63fc44338c01fb26c042c454991a", - "0xb19324d737364cabef3d2ee4785e8f19cae399afc06fedff8fdc120e0ce525b3755161183a1f5ad11ee758104081a49b", - "0x8e7525bffe35c1f5c2db63ee911e7e0197951ebd25868660e6672a3e7d4fb309738268b36677921be3be2f323ca718cd", - "0x846c51c7d22e7d43f6e2addb7fb25113c25ddaa51252a898fc1d7d5b510f772534c4f5d37ed3074d124cb259e2bf8110", - "0xaafe2a16cbc901730178841c39803ed84d3a78f92f42f84a1c01be4aa3b63ed7ad9d054ceaa8a2d56eadddecb287e8b2", - "0x8781c9810ffe3d93fbee3b576a15b8786c3efd6b5a96b75819b1f93daf413d4fd0f56d1ec277e8f5adcb784b17f08371", - "0xad66011f0e2927ee1924725bcf8a296069f74df78ec66ef6aa8792f68425e48e9d7f717d022f68a079501770ce192fce", - "0xacd0ef46fafb06f336565d86e0b22f9e5500d2f73d047c827d6a207af40b706440afdaceb32e6571deaa1a79f5e6fe27", - "0x8f65bb98baaae22e84a3ff375e7598b5c61ebec676fbb5a4f79c8463c427eaa96ebc51b1fb504840b7b0206ca6c2c72c", - "0xa4078341325d7debf766e43679b8b68331dc13651455a73912afe062525d2ea909d8829ac08771d9b32f2eea28b64022", - "0x88eb29841b022f2ec9029ecd1a137173cfb79addde1c7cd4be425e5806ea6ee295b11a0459a940ba79f789689a8fdb81", - "0xb762b9923a40a1965847bc7d046723c3b8f0d63323303aa3b25e93b4af8e527f1afb3dafda831f50baaf79926d1b1e78", - "0xa21551dffcdb069cb8f30b625c8404dfe5efec83227e3a1a67ef0c9c9b49c658bbb31240c3ff6f8c68e02f67974c732c", - "0xb4735a6610c371754001425772aa5314b759c24da50b38a9390969c27e215ad9d463a76762323b7954756a8d5ee7936f", - "0x81bd78e545938f8a3e53ecc2e88dc26bfbc30941cbfd009572d9b38f8eee47a85209a318cafe8cbe055eccd5e62d50e4", - "0x82ea5495db9dd48da97723bcfce02788549c6006773eb9f4aa4f0f3ae13414430edfecb5cd095259179ec2014b6ee1d9", - "0x8493147b8f0818c2d5e75acda498139f95fa6f904b47f87a8c07e258c60f39bb1faa1d29cf0834c8a1ef1d6015d37b42", - "0xa491233ab353f9daad86e60fd48b6f70dce60dbe36775958d8e270725cbbda96578b17a0c4925ba1298e630c6b9ca9a3", - "0xa8c148b9e1373afa54778b6d4f76cb12f40eb6e07404a7f27b271fbce0d18d621675f1dfcb30e3908d7df1d962de2e5f", - "0x9826a45c29ee22cc26ae399be25cabd16180a291669fc822a44d14cfac81aa7ce85112d7f2c30effc7e83d131c9937cf", - "0xa793c75e716aed4048a2893f11eeba78ec565ac250bdae16594d056f06f4aa7d2a156e1617fc26def4e39984fb28936d", - "0xb6c454d822343cd1b1ef7161cd2ddc28145134d4e59e6d69634c010ad1bd44120aa8458eafc28f6103ece7e34b368e1f", - "0xa3340a0edc3fa82fe4f31ca2d19d295aa09c74cda3bfc3534c66eb71bbb7903843bafa92f7120de4505c7ec819a89664", - "0xa18e5218cd4349985f412ffc7741b5db21bb14c6e00431daba194771300e740f75fd46aef1876543967e8719bc6517de", - "0x885ce63a88617bee05144bc67d08f1c7072d8c4e09b23b7359f020995aa8cc9654378d382de6340ddf0803717687eddf", - "0x8d8a0b614be7df01a12e534bac102b1881905a9d084146b3d0cf2086dc7d400129e0de8e48fc966adf9d8ec8c1336429", - "0x8baad19f604bad656398a4010b20ffb6ec6131681d120220dbf2cc5779de1ee146d0b773bdbdf4e8e30aa0f464f2b18b", - "0xa39ae3d204491871c2e88d7772055b35af341ba66531ce3575f47c749eb8e380d63a7939d3408cd51356cca29c76d4b3", - "0x813afd593876667ebf0fff2b8a8a5bfd0f42a4fe2e4a0b7c78b6183605706c97dfc40b627340e1d9527f618719d60e88", - "0xa013e458d678fb302bcb6f002a52e3e0ace443009eecc9113ab5b78f4663acadb8ca9cd757a7cab1e850aa23f09ed698", - "0xb6e14f351fc47b9e46a83984756812cfac795cac5ebbc6f00d673ee23209d0d91a6bd7d576c7d35ec3c7e7cafb758a46", - "0xb94246a346966caf6fc1e0081a211f27b38f058dbb9dff915e3e65391dd36d66c51324667e3d7469a865c0cc064589ab", - "0xa1bf4bcc7420bd17acba90ee67af96e73502777e1723255a73b1ae3e92fc77e80a287ce7c3d4088040e0edd64577c8c7", - "0x8b6f5eb9b6bc7320349b19876864baa6cd8e07da4f70653d7369740184ad416c40b4395c04750f5d8b54b3b3ba68295f", - "0x83250b957d920b1b738f4d0f44f9eefc01b5b0582128f5ddb5a282a11ee207ba1ea7867f00588f8b891bbde2e56b4c43", - "0x8eab312cac9de78c9fece9d67a6b26d58c4e15d5e0668ca2cca2d9c51636eea5210a893f9321c2fb232e09f9d0b40fa6", - "0xb4d1e5f284d50360dffd2a0d21c4b31d81f521158aa21bf333a525cc68e690ce8ce4f0eff8e71a0c2d5853e2fed18231", - "0xb1f194c28bbe217a2c98ca8212fdca744f0806d60e51f9da81548155cfb97a39e2a98e753be8b2196c83f7db8caad2e9", - "0xa7905cbb59722d9463c6317ae59adc10d5bcd6e9788f2a54f4ff4a4de03df3f830d6b8faebcda541d429a7e42d158c9b", - "0x8a3b31d0d0b33e7011dafe25ba5c3b7629cdb5dd5b31385d786fd80888fb8409812b96d28fedf6a401a93310b045c620", - "0x86e4990bf50b27bac76926dbc65a1ca34a759d005e56eca56fd0e00ce82635dffed6f3052740cac2f1f37204699bba9d", - "0x8f0b6a0b66f1f5fa3d12af444963c6a45785a36dbd9e1a5f42830b5534ca8773a05fb618292e718cfe8a066b8fea7980", - "0xb7f206827d715b33989de5c02f0886d3a457d0ae34931ddfdfe2dbab121b69ccb560a71fdafcc3ff204217611674f5d3", - "0xa6e2ffb0c5f29043984c54f5fe6449ac4b7a86f2982c909606b88316ef1f0a54638d565f44d2fe8cf00279e0eee435a9", - "0x8cdde76212e73f9430cac210b52507583e81aae0bea491f6cbe27e5e2c8fdda373ce8c7a5f16bcf8c6238b282d06639d", - "0x8030955eecc9b8e2161d40f1f514d58024d25e07c6710916824ed8982b8bcf7ebebc5643f0260e1ef6150b6901dc2dbc", - "0x8938fc90e836d7bdf1cfefb2716cc25aff34e7c4dcf66b349d1fc6681580de7f58665faac29494b30bfa0c743d6f33e3", - "0xb182f9b4a5d838e9d534e41ecbf06330c56a8a64800eee561de1fc2dd9486565ae6099f40d0f1901066f45047641bd65", - "0x81f98b85de7b6c581613f4a42e0cb0dd9e6336399b39d38a4751c2a9f195b65c9e5592fa1728b91d83cac0ebfec7d675", - "0x94681572da95137ce41d913360cd567b570a87c9a439f2b67b90d623561b75bd3dd0486a90a63d49eaeb412cb97768da", - "0x8e64922606ce05375556901b8c042d4f41a18fafeca24c1d56998e0bc9054bcee7ab079c3729a67d908d0d7967a85edb", - "0x8e10e8952b24125321d0cd9ba922affc93908b3abdce47eed22fb2d44cd556842c31c36de6d4c28b4a1b5dd84e07df81", - "0xb6d464020a51bbb53670c81d5f1474ef439e006851d5d5a3fcf74219614a2a9c983737f20b254d38a2fc7333b35fb3a6", - "0x91801712ba264cc2eb65e8a3d5a032674a89f4c2dff95ef9d80d3a9285f3c2cc108e775dc326881484756814c2a03637", - "0x986e5a00f13326735bfc6b41b086623101f01dd55f2a88bf995a3b81054da86bb2f97fcf647d58e90428e8e9555eb020", - "0xb2875b4ebbab678fcafd270a0238a208b19803012fdb3c23f06c74bfd45929a9856b7a0f9881b75c7e97fa9d35e49d1a", - "0xb3d1acb9c844d8d2232834a81862c59548cfa849e8e5408ee66b4c8b86ddac0fc231b2538a752eb6c1ceee92ca443d1f", - "0xad0b1b5d6bb50c43f5f3b692c5d3569d2117b01caa7f0ffff502d5ab727f7702a2d458b89d77d218d3f92351b4c2b92c", - "0x95b1b99dc260ae6ac7c387bedd43fba793274b15768d93df13c88ff6cf637732cb6b1719467919b444c3b5166f4f0107", - "0xa0c3c8b59016056742145e7f4ca6568d4393124efac6540645152bf71173dea3d0058bb11b3093228ca4729cdd5b3033", - "0x9278afba60643257d9f21a4033df3b57743c3b43d66d96ccf413154a63db054fbc3a36f2ef378169f4f19987964c0bce", - "0xb558754c97f9824a72644de1725127dd36e284fc07ce89006b990f09db77c48ad6728e5c1891a691460bd5416ad65faa", - "0x833a02af76172f868a25e850d35f4d164889bab8381fa9c8d9880ab0564a3769ce3961cde72bc94ed73a1723daa35cef", - "0xaca496f3e528a2e3eceee138291107ddddd68bb088b2e49ea76d0c4136c6924b3251d7661ff467a36dff29f07ed09102", - "0xa9367961ae88a19038c446df3eadb280da005d120c16f48ffeabbe4cb8e5e2784902cfa1192876ab934bc90606baf2cf", - "0xb43feb49373dc36cb46e050e3cea43e636a64289efa3af790dd3fe960446492b858f51b3be62c6b75b510d8e2b985573", - "0x8cf24955965468125fba2c5a5799672845ea6ce97cd307b09236ef1a3cfe55c88958ffa311e8bc8335bf261a84275d93", - "0x88ceac98b512e5bb915554af92318a5d07a494e0b8734c4415e192e7405d6b06d170fbbe254e3bf387759f6d4961c34c", - "0x8a9044ddde945daf3e0cb3f897ca00d0d4e6a5f7c99aaa3929f0beb9a44d2ed23c191e37c57140ebf3ec759f50f84d57", - "0x8b2a2c0fb51e7c5fa51e8c593bcf118696b8411bc93e35cfe5de6c5465c6e80bba64398d7c6b71badda616b918bcc7d0", - "0xad8bba2b7d5577f971a1a561b17a9d8f6b7c35fba55e3e421a0d8d77b520eccd52122f02afaf3899218b652980986a92", - "0xa8d743b56896d44bec838e10ac1ba5a43f58c26655c71be0a5417d936260453a8e91752c87334676c5dd1dcdeef4fbd7", - "0xb0b0540f8d2d1ebdcd74d6e4007324de8f8bdea0531880520d79773c0b8eda65ed49e672c5a294fca6b4560442085829", - "0x96da830d1c1625d002008e9a364005b2ef16cf56f5aa4a758ee963388493cbf90aa75c25dd67d496af17212537ad44ab", - "0x89e819577a95195056b872f8f790d001fde3253a23120e2c41b3ced7fe8e9bae0df74907b7d65ddf9bbd6d2efa18eba3", - "0x90a139ffc7dc0992c023651517db4c195aa2f618dc3002f4a0a43013b6c6022d8d9844a49cfbaca543c9cf5d9b2210f3", - "0xa2061f543b216fc9c801d885ed681f9253f67cac40528b23aa8a709f24e0992fa42a10f1bddc7f10af2c22209343ca26", - "0xb5f53715b9146966f386f214477743e2fd2b771bcf90b192a5863c06d7225be34edb6bf71389085edf344e60afd88561", - "0x9634ce27272f3c687035789fa4eaea2aaa71db5b5531b21b8e029645827b40561a5901b33afd80a3aeb5777aa89850f8", - "0x9674736cdb4a823bf982d54876794e99c7672eaea7455be90e815abd03ac06ce1fd9e73bb987a515863c6cb4ae597835", - "0x90379303e285b19fd7816a6d02c0b8f94e6291b56c196d76aa389cbf813dee7ebf64e45555ebe8a281daeecfd7aa5b00", - "0x8a1f759f6cd6e5134f67b96e0edce7170e4be1b39afaa7af1c2de989116a6ec9d38a2c077c8e6e65ce0bdf729f20f1c7", - "0xb416f9937a51a298548e91cbe8fff71585335c00e69602423adc9cd72d18821987b8fb5ede32fd8bd2166e2ba9aaa792", - "0xa423073148046c81f840a481d57909f7ef621a51827e44706da9e1f0e27fccb8f88652097a9880ca64c41f6386aa9069", - "0xa173305a5aa2a17349eca704fee25593f5c2fdc5cd8cb932a1bbc0ef34bf54ec2f867ca93d8e6aa33679cbb71fe11083", - "0x87c6756d14d815ac8237ed4a75fb11206f615585ed527ad582841526371366ab19f602c7448a21722adbf2d987d89b81", - "0x8a1a6f06d6375d2bfbdc7531e9177a45330458da2581f65ad129367c400cd77f548aa748bb470bc560c0b02ee5b802ab", - "0xa24a05c30d0fcc8334f6974c30d13a5593bd3b388e2146ba006f232bcd6886edffaf7e48ed6126efd3e651997dcceb12", - "0xb35c5f8a5842d97cbe19105305cae1f971da5662c52eb979975efa0753bb60a050206fc0babac5b5083799e9ce8a68e0", - "0x939ca5532c922d00d08ec5914e6c58f8a1302a1214a1cbd5c844b334ddc84e694768edaf1a2af02289ad74970800198a", - "0x911d6104a240f84e0f6502597405b47a7faf5e68717f6d389baca62bf82fbb7207ce8d0c584fd9d57d3afe1f957b7cc6", - "0x88777ba7a4bdaecee78d42687cb4fd6dcf04402b43524e2ae67506d93acfdc29d9dae640c05d01c90caee1d04cf3d35a", - "0x9226e684606f8169e38dc21a14911d0306b1c9ce5244500e4b108eb1a0c0783486acaafd2e0b3b60c413bb003448ff21", - "0xb2f527adbb9feef9553bf508f192b5ca211d0e491925a2331bb294fcde7d8e0fd72b441e9f07c838640dd35fba03e1a7", - "0xb474e6d6ce22ea272a93a3c078197f40c01b9121c6f3083a8e587c367200b8c97ad94e156883475603f0a66d0340fa52", - "0x95c4d9896df11d2b5a8205a19d6331ea02a2de038aded8e6fea6d79bf5a6648d5d986bd29430e4cb5a6afde8b07a9a48", - "0xa12bc53ba6b6f8350b400fde04518a741a1d755123a6ad1d435c7642492c7df28f7091f17b254e793561923de781eae8", - "0x8a0578ac03070bc920a3b5a7a33d976b3133501309af5339b0cc70536965465b4f7288af70db3d5be16bc2a1e5c26a86", - "0xa66e27284ce6114e48ab56d7f623dc37a6e79cc5f487cb2bdf0acee099cae744cf3a9de53b111492b5ef99b0deaae0a7", - "0x832a338951022c80444ad8c6d0285e86db54254d2689defecac2ed87f5eb4d876373af6d76e3d613523e32c3966142f2", - "0x81e83f01bac3ac3fb67e780b28de30b32247a774aaaae118db3d45c8e74d1d4f1defbf9c2a7ffdf176f5c1cf4ae4167e", - "0xa1b214ba7265f692b4637352c6139bae8bcaf3e7db5806fad0014ded93048fa4a36ac9c9e0b7cca0a86cd45bbbba2fe1", - "0xa7ab6f470a421e72fb703a9d153362056ce80c40264a3ee5698168130cab8e827df5ce3e6321ce9a669c87a2e5c67499", - "0xaefafd219f2d062a378474c48d2650b51901b6bce00e4ba0b509395a6fb39699037577da353cbde187e65de87ad01575", - "0x93db16a0a77d1b181f33ef10300112fd8db5b2eea26732bffa3b1fbebb792c6ecdf2899cf6f26b505dfb46deb81b217d", - "0xa63b6d9d1cc2f31ac5f836133ae66bc9de3e07ced5026f5bc90116599461dbdc03cd7680c1bb43dade9218ebfe1bc1fc", - "0x984b49ca86d38a486f6315f4f9e6ad521901a06f8862ce1fc095d9c66bb2164e334718c71d7472ed765367db5fede105", - "0xab49ae93955a38f45f756afc4248a37773ba8d0a19779253fca3b744854715b9c9b10c09a37d3426614ffd3a8ced7bcb", - "0xb22166dd64c83fe16feecc09d4b1df2d967ce7f4ab526ae39799dd5a5a4a9ebb1d4a432c5efb90e0875a4eb6b079e2fd", - "0xaabad619d887b69b9562066fba2179c69c684b8cc9318c9e39646f4a5381535c024ab277a0f0be46abc95283b337212a", - "0x99f5d484db149e9f8dc9c6758647c4e3702d88986600a3226874d612bb4b5e92a76b1dfbdb0909b8f21afc773ec67c7b", - "0xadc8bb04eb8c56dc4ce97c3fc1670da10db134cff2edc214ee3221079251b968e2dbc087c56c01c9260b49506958a6ac", - "0xad625ddf5cd211102543e0943a7770a9b45cf3550d12dbb484cb5522b70cb626f9ac795b07a305be3e6949d7ad475f66", - "0x8f9f5b2b43624e89e8535dc73fc54b744f247572b2920679bdf6a3ef346e654ec40fe8f81a0f7c7ce7cd5b48f3975359", - "0xb70b1642f28bad56bb24b342eeddf5c3782e0cf6e0d5007c252413bb44b32586da1e3b4d8b45a72c91e44e07334da68b", - "0x81b0311e557c47ec22c5f5d1f757c6193cfffae357dd2460019247178b13733484dc8630fe2e13037a1a3d681c69066d", - "0x951c9f1504b19acdac1c04aaf535d3cd3e39c431b2b5d9def9b374468e93d378ecc3f5aa02c91ddb93eea431b327ca4b", - "0xa85e1f4c292723d18a49cc9323dc7af12bb5a8d0c95d71881ae235ce123c50018907f46bfc846dda1a01b14ec45dce14", - "0x8a46c8b86bf9890df60de4c210cd7865892d0c11fdf2747620289d73bad597e6b482c208dc310c25955dae8392d8f278", - "0xab65408622c63b67842a80c4ed665258ab617ccd07871fa3f0fde2e5ddfeec49f01d7501790a60b3a05d7579b087b787", - "0x8706913d42b557d9ea4d7b70697069281504b3c4e1172a2291e3b3e0a0305c8d0bff6b7721356d971d2fe58e32d4556f", - "0x8d9b8f3c113ca1215dcd15d4c37913d023c8c5d04f617319af76bb7bab72fb756c5bd992db6b9e765cd7695c316360f3", - "0x942d4d3351b2a9bfaab2500b27d24fc2d7237e791993a7d0335f36fe6456c5a1a8bd28dde9228fb139e95288d6de5bbb", - "0xab014e9cc7d3ca08f1d3d24473ddbd693331f4bf21ebdee0fc997aa2faadb43db6a1195644c459b52a969f3d98a85b8b", - "0x8b679da80561955990c91da9093837953f4ff7fdc658b51639c462b578a2b31443421712c6b7742fddbe0ced48c21cb9", - "0xa9132ac18b1bce93e815f6d2f8a0d2f433ae4d6fa04269eb0f5f25864a8009b01531c7c3ebe87f07454927a010ab6dbc", - "0x8ab02c113149efc877967c92621a8ef618bf423017e78b9cd30cbb13c51200c6ce27c46be75e19ba843d64a3050d4467", - "0xa881043298341efc28c60d850d90d90279fa6d8428953337ba57b74eefd858e362c6118a82ebb025c9c102c91b4aeafc", - "0x92e4a587479c64b8df955c6bf1abf1d7979a978e45d96f05bc1b9648f10428d77890be9ee03bc1b1982f5ae7b926f0a3", - "0x90c21a22826e2e9978dd7522f51353fb33224cb65603779de41db3ba41e01d664e131233bf873e28d6c71294b565c533", - "0x88e8ccbdc54ff06380c2243203d3f8c8a75fcfe638d6e6a010c0b3a39d5cda31f8d2cc416ee5264267aad2b457c94e50", - "0xa256198394b458f6468dc91c35f579da0ef02a55fd93e98b25e43b1bcb650ff889df4899236765c1a6b35cf49da940bb", - "0xb5c7d9c03c36cbca068abc6778053727e77d9b58c5dc33b11629f1ade1c228b1c964f5a7d8dea16057e76662c4d79f18", - "0x9588e133517f0d49622222b4de5c124b1aa4260971e43e4aa767fba8055540f2848954886b7f245583ea527fe2fd1de7", - "0xb66025d75169bfc7ea366cd32419e24fbff829709e3e9587d7d59620b3a7b72034d3303106f965f5f7a71d66b7f314f8", - "0x891357bbe44e60627b975c10c872a34b78d6b264380e351f3a86dbf99abf8e2dd8d20c52dd6073086e48e1ca782e2ac1", - "0x8a066a3482526a92476bb8c3e5caf07575c725d72203d67ce98f654f5ee8b7f979187416fe3d7ae0128800b253d7209d", - "0x80a9e3d8900046b71fcd5b7034d1e0f57d95d2756da8307a11aec0553e5715518a125a653d356f399409545256a1984c", - "0x924a13fb2da7a899cebf2ac09c8c0a183491777100de1aa056a6c2bceffd5a63e255f16a9066e4ed89ef28096a1230bd", - "0x866cfc8116d2e0216d8049d5ae2ef0e3fffd377028850716a4bc2cfe16c5a6be023334bd6ddafa0c77913dd4ff0a34ff", - "0x95eb74bebbbc59d793e3fbae8e98c258451bf9bc5097df4edd832e9f1c30a1446a59e1f75a44832d0658d5ecc13dfc86", - "0x972517b2d72ab53193db5d682db2de7790a418ce4952c29d64e1f9107d51a782f4084591b7c775648f103445b797e8e5", - "0xa14ad2cb69da568f2f958ef4253d7a6daf574c6976f4f5d771ae7673853ca22eca81e20400092bac84453b6eedf5aea2", - "0xad95bfcec6c06cdc11d316b7ad33fe65555e985bb33b15c9f481a09caba1e5990601ed6a88038c0ae2e04b1607e2da48", - "0xb7e3bf3a585af1029d83f12cf75acda894fc4441cd7b3d56efb6991ea91b07512bcd7d6d68738557a48f0446b2cb21af", - "0xa57efb1e2d2e10e41f356768385375a21d9f78bdb34d618117581bf7a15024eba43570c3956ddb85a025d39476f831d2", - "0xa66d3622b1cdd472a2a4491881de035c2eb4f1c94927902a3bb9f10739f900130907c6b002982e03785c43ac30b8109d", - "0xa79f2417d32fd772e46f3bca61ac788af8fab174e1e1e48a84ac557f7e80a9cb4e2d7b467365ad18f9777f4cb5bb2b8f", - "0xb952b976e3b6660326c0ed357ff25ee1291b74891f3eb7bcea39dec2ebb11e287d6e26ae0506425a20e5e445273cc63b", - "0x8c23929e9740ab51d9b82c6b7840067e7163e6c7b9b9441e1bf867ca2e532926981c98641e6c798ef12d35108abc1dd6", - "0xa519578772c9ed2d691a8c423d360e4bad76afa422f1a5218a7a08ed52c9a5935ce2ae4c0be182eac0712259a43f849d", - "0xb1529dd189cbf3bcca50e97199bfb85b42f2b26edd95b35758d988d1d3740f5d0d2e249763874fdfadcefad9ea1b3d02", - "0xaa3fed8d14a4f38df75b9eed7f187a31cbb7a748bd3225dacd8325a71dfb680729fcc91ad8cf0b67ce314e1fa8ba02c4", - "0xb77c28abce17732a08e682491182f63fb55640e60384932f6a9c6d3d7886508c9e67a841cb93e59448d2d59fceec4620", - "0xb7a24c58e3b85d60d654ed14d78993a9cc78c130442c8cca42921ade8ec94bbd0653c9fe5c69ad1fb2aa46ffba04da39", - "0xb7d08f3ce97901261514a5dbae582848e75515c5f9f41f5e70ec17a8d0db3067ddb19aa1c86803bdbb757230b148bb21", - "0xa5b8a6818be4d59079d88f72d7aa4957c48ff5898f3fd01def48ff6bc7aaf9840aa91f2f05617d340092dd9299115c2e", - "0x8e548db6b871fb23ca1cb8538d44b77ad02f4cae4d33c8c43228b820abee1aa913ff9acf2483725b195b4e65e2e92063", - "0x9509189e063812fa04f4e26f87b33a2289a05c229ed1038fde0dacecd87aa55ae0fdc678a1c86bf13b81f4b3a872426a", - "0xb355f24a5dfb7a8f3ea717111a038487632bf00d67cc2cfa2ab61e1cace7bc7f5bc9e04b190aa6be0652627ee219bf76", - "0xa9b335f235df51b92f40f44f19150e182a938b9abb3bdd8e8c447b2b128050d228e0115a268af4c1bc2ca49552b4e0a6", - "0xb306d3e6cd7ab56f5f7572fe51175ac6b29b189220fe2d380b959d131a35804da5ce95adcfa51d799f18e27d8d5eee0c", - "0xaa49cd2bd34c37ce1f05e192fa6837f964c068170ab97989e1cb22ea7e13c2400417a51282519e74d8fb6983ba89a549", - "0xb1d4fff41d95613e30427ae2ae1d3df8c9d06389e1e0f404f8cd40199d6c4277b7a898d06f1579be107fc5744247c36f", - "0x99d220454889f476931b0cba3570eb1a8eae30b4c3617513833a551aab0a2630125f72dafc64a766b1a322dd42dc385a", - "0x8267ae38c9c8532c7d4ec4455279a5ed4f2e48746cb0f2619937157534b0e5466c5f4b99b7c342c095f71f3b77fd5882", - "0x8bba0794cc4ca00eac50309a92878084a6a22e4c23206c68b8d7268f9e7f615da4b9d0f3e006d9dd84bc3dcf32261e27", - "0xadc965bd7c7bb2a52cd3f4d2cd3fbd72a196876a678863c6a67a25b4a2330d1d3be603220de22c8c3f60c1411df57b7d", - "0xa7d5f38a3c4ca0541d5ab101af9c27b04c5bfaa42a1715e882c5e7715e460c4666aac4b6272b9fc54514346fc49d0560", - "0xaf94b91ad9b0f01df1d41a459f16ffbe30710325617651cf1da000eec43876161957f079a27b70018ba34d1d5d68cf6f", - "0xa0e2a492da4614f41741157d3a1d19a2370ecc8e178d813e22b902cf7454b8237f1ce3c16270eb6f3ead1f92797e36f2", - "0x8dfcd39155d7b8073b0a1a9a617fa75218f569520d4817f3ead375850ea8a3e3dca64c44e83f54afc37173d927070601", - "0x98302358e5b740b73e1a6c568b99affc6de3c7245ae96d9c712d377fd363d8b8f49dbb714aa8d39b5b947b6de341ece7", - "0xa2fe0f9fad663cbbf4bb05f61edfc90716564d5ee5a9529ac3cb8f06f96329248cda85c43f24a2382a9056e9a53520ac", - "0xac50b0727ca2ba80692c0b7f564417916695ea3760ce9fd71593050912bb97366d29ae5ed05ce52984e52218854b8e3e", - "0x86f56bea946a4516336a90328fb4b24cc7f82d8710d0d1e34c2e27b6af73c4f4a9d6a848dcc56a87d6259a99ac444557", - "0xb33d0244948c430a58b210943e41aa3cfecc9a823dd3e160634ccc45ea2680987db2912ab2a173ab6cb9cc2b7e16f7d5", - "0x8808f8c2c2377cf52e7314820d88234d7819a6108fe9e1c6a675dc47cd59f81f95594ba2f5fa10e3719580f53edda641", - "0xad34a814be6019931972a76b3300a4fc9ce763d6f4fa1ea131a67d575c00c827b9ae7260d88577b4b3689e90a845137e", - "0x9370abc67ad0fedf30b929d1613e336c6e99e4bf83ce969e61e5d77061b48a1a493f28fe2eff436d4a979af700a83b5d", - "0xb0db136c8f4ba2fb7148b1451b18f694769f5e53650d68342f15817b04734ef8ae59681a5754df617d755a687b6ee45e", - "0x9149909d24382054a05fc0b057613d059721f132a19017a92198b30e48fbbc5f8f0b5f5db55347dbd9d190ca88f9a28e", - "0x883d1d170fb0fa95b55b10b32ebed24b1232dbfb5c783148a63a765fda200e796aaec52747441704967914433a01a323", - "0x8f55fd5ea11c4fac277112d72489ac1de28fe163a756b125f27acb78aa6651c70d1cd8c45e0daae417bf894149ed2d57", - "0x8d08685f99aa8525b008b868f5486e24a08568a5afba9b729f7d26370fb1b162937db28b935d67e4d22f7fda69a3a6a4", - "0xb1882e23d784ab48b2f9e58114c5920bc9d0c4c01d2d7fa5111561df0cf2d738e31a32963cfa58939af87e79428659da", - "0xa3eba902d376063e48634c9436802cdc6b89d3a7c7cd03b26a3fccc7218dca85a3ed939eb53956d2e001805aa5c2d63c", - "0xb97330c40d51a4b71f91f56292b628379ba735509a66c7df054112578b9df40d3aa32598bc64c03c78a3311a17997bd1", - "0xb84f3d2af2aae2aefdfec9a0693f6bd71eaf4d477cd72d80f4919235a471607c5483b354c9d46628a76d6b6fe7c586af", - "0x8a1c39bea7fa580de967d8ced7e3860a9031b07842d71f8c5941b8877cd55ba15ef7aec6116ba38ba290b887b4530685", - "0xb120fccf939e7d7959c2c1e70d7a7aa3b84684dd1ca8e5cfa9d281fd06d23eb67a629b1a27052614c3ba639ff9c90dde", - "0x827a8e0dc841af0e2c4a9ca36c84a0ea60099aecfa40294344f82878b6909f5581f7b34fa9510883113795bd09b5e4bb", - "0x88c24cc54dac5a2982be5ac49684d99f95574bb8cc44afae4f6e18231ebea0f2ab65b49870840bd3e8f2c9247f62c7c0", - "0xb91fc3f2cf743f4ed42e49007514d43dea1d7bab388a18de6f71367fb8f2e9b8e88ed9f7492b647e548396ef3e3d7765", - "0xa175000c4765a57c57b219b21f8302cfd85aedbc3340fa1690119bbe7cd93dac4fd0ba676b1784ebac83efe3e78d4bf6", - "0x881a373630ebc24dfe17e27b3f176de6651347ae741d55675675e9e6904ebf157e787d86eec42ecebfe4eb8f28de6fc7", - "0xa47c8b155c8ce8e16f38deb345a051fe0c9b217cb7a266fce78d7694134247887789645a82c0ac24341f51da8ee6ef00", - "0xadfa5bcc682d4449adcc436649b444dc61157154e24d68615b0ceab50eced1ab55e15b45562dd8e00785806e9ef2b7e7", - "0xb7d2ecddf47e9fd25dcb283eb80e323300bf5c3ee3344abbc3a1f2a3296c631577a1fadfbf685abb336d5d7059d17166", - "0x8833f6b388e46e1f8fef1086777466277cd418051ac0323e2cdac5902d7ae45eefef93ce90b088bbd618e0381c1ada78", - "0xb6abf44c5aee5d0fbfdbcbf1e77354d5a2ccc239b894e1e06d7ffa76584683f707384319ab0e0d17afd93a854d7d26b2", - "0xa8c61859a9553a83bac398c14c987b20c8dc27d63112115b8aad26bca275cf98913783c802ebe3b7c3d878c130407b34", - "0xa5de7a519f8de4daad9137f2c2838544219834cd70457ef09467d869f4dc32098b7a8d4fa85e1eb283632f6d09971318", - "0x98c33a315a66cd8ab9ca8a58c87e5ec588107a6416c4ea498d0b91bf7597f53a405e437ca0a9d9c6acea27ad0ddbf4cf", - "0xb2909b1f8752f4eec25180a17163ab215fc20c4a931d4471d3be0ab64207a65c7e462fc0707791286a92ff2f2b7dcb0f", - "0x8b96c2fec34cda02e98510a3ed80a980b0cbf4ec03e3c4260f84027cc7453acfedb5f708c401d26db137032c6cb4a31b", - "0xaff645dd6ffe8b5076c83a823daca4149f0769bea3293b61330ebd97a17fe16758e4fbbcb5bea7449595c6e261127b34", - "0xa45f8b3b7196449f9952cadc8d87a787a28b4ed89f8c7599e7db361cd0f0aac6bfa464024ded5c0ffc660e417594fd41", - "0x85016b5f7ea9863557eccb0e742cfbf0b09630f0bad3de55aec92b95d4645055cac60d03602586b34f774bd356dd5554", - "0x94fd89dff2fc6099e5ab90149458a4c794eb1857f1dd9a2c84b88099412477dccfc2996cca2abee68d23a05265dcf271", - "0x945a52621ec19d26f7c8abb5d01e4f5630924b75a349ce74219377a137f4a0d386172da523edaa522d27902444023cd9", - "0xafbd452dcc57f5db6b3fdd55368807320459c16559d944ee8ecd1af6acfe9d58c13f37961f78030883f8ad7dbfac66e7", - "0x8ce96b3be871a1f33d559a6e55e4d86a0b92ec3954417f8d98676264596c3296296532097b9b20c83c341527a0c929b6", - "0xac6a4dcd58486d25a4db1751a60ca4d02b80c939b39ca165a37d9a0a52d8675b3753719f136a59ac400bde3efd036c8c", - "0xac87a37a14a5d48842d30432935929a0e9dce5642142a8c5b95e377ad1bf52120dc64697f0508b7c258af24a0ef484ae", - "0x859f0ba02d496861455d9c39c269a1ae5bd224319918fdc3648311c93303c0e13301ae7f3f77eab4ae43f1184a912b64", - "0x96d9b1d2d2fe70b8fcac136a65b62a4ded85aad9d350c19bb955750a0b24f93174e9cd00c0e0a1987793e1180dfdf66c", - "0xa7f5135873a1c08c7c8d46adfed19d0ed0e33168d463ca74f75116168355318ad588ebcca1946d7669c5106bc9f5a8f1", - "0x830b0587587b80df078ecfe0857a4b4cfc05b722c0f4f3e1217048ee18749e9940cd0200c1f7a0f60de832a5a44e9f1a", - "0xb6625ed0199097acc9aae20611f02d2fb837e4695762cdeeb4dd722517ba5a344e5011f14d5076783f3c32bb5c4a027f", - "0xa17be2e528c463aa4ce4bba2df5b005f88e363b87be7324239413ecd5bd68e350d290370e1080ab9911a0d54856536da", - "0x834064460f0e5f38950cf5ec197818712f01950ee1f32b1987dcf7f4098d20e1d91fae6d48e8a054390693a2e572f888", - "0x86217b9bd269408ac92b5cffda5716bb3bf8674b7e222668d72939a626f4ab64f30efddf85108c0764127cdbcbad7d69", - "0x8d7cf47b0648be0bcbd3ad1062d90010e5ee84e397895ce98160d5a568d60a19582c985944ec27bb284459789ad8f6eb", - "0xac056e3ed3487427142b3a4e4f9db53f1a752e1994f178577c46dad71be5fad4d03d24ae7019804c41232705a4bffaa1", - "0x94b83d67af6735e81b2e392e6af8ee4dbafb0071d84486389f36f222dfd015da718c621acdc4360630403762dffcbe3f", - "0x8ad27bb51c6cb860c21954f5d09dfefcbe3a9a0bff3e24fd1f74850edcbcc76b5b389a616ea0c0796b239b0c22357a44", - "0xaf9990dc4c9f536385811528f207a8352b083a4abe6dc016eb5eece0ad74da65b2c6c475a78cd0ecce0b2b550e4412cc", - "0x816dcb8ff8556540b54dcc1efbd2242dada0acc1e3d3da13ae581d905a9106bdfb8c138eee93992a23e7740593e8ad80", - "0xb8fcf8e11e5924d3d38643b2a4bed4b54e69f816f40d4020e76655eba8ffee758c16cdc2d970d3c8c1163cf501044c03", - "0xa50e0ef4ddfba6d969e7dd864a20cafc7fa6aa232fa7a806c3d53c3e029cf110828c5a9c354ea42aca5688896f27e6fb", - "0xa560435900c48879ff3f89067daa8e512482f061c68474d951c608ebb5a69c7863a28fd1e216eb4b140e32124e50fc73", - "0xb9202d152b7b708ee61c4fde6cf423b481854538d2580bc43462610f12141b89ce779c7398a35c27ea6ed0afa5332bb2", - "0xa9b3f8be28f9546bc70f680dfb9b08c1eea6fc381cb6f3ebfbe33bcab48294347d4e64004c11dde5eb409ecb19941ad1", - "0x8cb3086d265060f8e52a96fcecddfd261886002c1821a8f59a1ddde19a6bb1354b17cd19a9cbec19149dc219a4c394c5", - "0x906e8dea406ba0f0ef43ff623f8521039a9455a2290cae4ca9bb6494ee0aa812528267d1349bd5d339113dc9d1616b28", - "0xb9b5212b76d5824d66b8df7cdd5effcb05ccab5df6ce67558872c99d1e484ab8d21037bc0e22f5c4082b192972b80acc", - "0xa1fe817596bbb5bed93a5dc4c03e14eab627484cdc7ab7e4fba569ad0aaa93b34c4fc8680c4f8180d8190113218d26fc", - "0x82fe7a20fe93564cfaf7eade8d4d1394d1b4e36048cb8632bf366d3d8084ee52c74d65c4c69d9d24208f7916278aa592", - "0x81f42f9a3b8007e5f02c26770947f884c715bce1e600f38f164a390f159e2e5b6f8522ef566bf36422b14340bb6d3556", - "0xb53d3c89bf2a4b29bdd8f1bfc001c2533f86d869fbdb383fe9cd93ef0c49da0692361baa9f537094e1af662a3461f8af", - "0x8fbeee613823ebfd514e991d81babc05176d5c115907ec36dbf83a69eaaacd622f1f36be2e47b984cd6ac66a6b35816d", - "0xa9068ba463ac13d4dba25f9bbe3c93baa35828563f357c53a7009cf0c809a23502e023a32f651e29f14424c5daab2884", - "0x87468aa4c942476b3ac3000e740c4dc72d320884357dd99eb25e81d7b52a859b9ebeb55f3070022bcea3855a9a198e9a", - "0xa5f1219eb902234ffe8ba809df590080ce8329ee574eb346f6b4372892d66b0725f048465221655b70b3d4c2deba9fa0", - "0x8d9663d4b48cb86201d343b20a8e7a6ec47a4bce0e85a905be31121a01fbef95d9f29d83530faf79dda163c6c76ec514", - "0x9921ea9176744e15f64b20ac6e95ec132052eb853ef47e9334108778fee60d9d9b53fa0b8011c6a4aaae472eb11cc61f", - "0xa04c2c5e2c5a7673652919aecbc5fe09a636fcae2d06003ca6775018112b606e50bd2d6ae6ec6131d2a9999837186bd0", - "0xa00ddb29776d2747e3a6e68eb51a7cb00ca0066a9aac5a2da632f355db515b32e2c441fde870c9731a9dcc8d9834557b", - "0x85afeeae8bfd92c51522320cded430c2fef57b1950f9f966f47ce6354e492e9c40f950a7ef6d5202fc79fc020f7a6260", - "0xb047d214201744cf7e675af5fbd29579c3b26020c5e0a53e2ce078778b3d3a673f0fd87eae8af8f0fba3bf0f8341b63c", - "0xb8aa5364d914020158d11fe82c2b77197ed2b1a12492435200204e20a9209d3c0b4fdb6fd3f0b1db71ee3b986400ff46", - "0xa59a903fcafaa8b5876a3eb1d79a7db17c37457dca018e393324d8db3be7c2aa3ed2303eb3530d6fe1612fd75dd92e08", - "0xb1929c1711ce44465daada15808099234c0c5c8f43b050b2792b6ef9b77825996a74abdcd84d6ef08d648e77cf804357", - "0x85bdc33f8dda0d853074e0657688899befb6356c38f0ec2ac27c46c39fff06617edbb1c5cd220314335bd1b792f1e240", - "0x862047e51f9119f5a0a607469496c0574b0087d566bc58cb5b61a9a841a3cb693b89837a7c927c542ca03d0106055438", - "0x84ba54c002150e5989f59064b68989413abb5f289f3ccba215b923f86f76c19718be51d503ce3bcec68322a7c7d5446d", - "0xadc9ea06c11bf3f0d704b321005020917e731e6706f18a5aeb1b56dab3de39a94fe8aca3c248a47565ca5ce82face9f8", - "0x868324c4ef80bae55510316f3a8b13aa40e60c8a3d55f4994439d1dca6f5928c4cb202769d78c21597d8737e391536d2", - "0xa6e3b57e9909b5fbea2114c352b34235a4d4147417e480580c291308b4b9cd696b36278480893667e8ba01fe3bce571f", - "0xb92e1d6ba0a2a244ac5ae2e7b20e152591c1c466c9b4c658c72cc5985ded0392b04ec00e32291f1652d21dcb633919a6", - "0xa3e2bb4dc07ffb1e8dc9055ab45bf22864980f64b612548ca7feac85ecdc426f773d6d48bb7e6c7a578025bfe99307e8", - "0xaf764cdb70d5afdbb49dddd519451218db4e97ef3ee622585880425c3d85a8df88613f4b51ad40a1f6635e45b2efa5f5", - "0xa426230b8ed77eca3d1ef7f4735fcfe0e51ae37efea5b96ea3bf313c241bd703b91a592f035e98056315c9822ffe8c26", - "0x96a3ae7f1b80690f97372d086d2d13ea2b40802bd053980f73cddfd37045364ebe38064a8cf3531e9bcbfed421040f20", - "0x8cdfbf0663bde624b703d7e6c36c5753282487147e10e5a24fdec75836f7034e4c38f3fa3df373476af969a4f835cec9", - "0xb7f7a549cdfcca30b78349b831ea5173bf5b91d56dbb999b2dbf6b85d8c22ca8a9a62b38e37dcad7ee5136a32edd5743", - "0x82ca90321c43d616670a7d85447afaa9034459b796b51792c970fd5b8f124e47a13ef661291a4ea58a495e68aa36dd87", - "0xa824a36e4e2db2bbc513d39e4e2a841fa81106437eeb4fca9ebd78050667d0b284b7217a35ee3eac67d8be58c0af317a", - "0x9370dd0c0f9c7585761eb5f06e7899d75eac07e13c140c64d4c506b90495fb8ea914f222608c478708d4b47163dc9907", - "0x88c07e19252e905faf129e3e877dff8dfe93e81b3903b150aa33a93a7eda2820a8471be935825d709dc662d06c9f99b7", - "0x81e936c00425f7db8f0dd88b16c3c5208e8d95a5072e69524f3b5de45f4e2dd25f0aba8ef17016bd914bc8f5a42fcb6b", - "0xb23227dceec18d6dda92a15b7dc8623d9928d545db93b3547fb068c99cacb3fcf3d7f88e4357801de8a652b919dd907a", - "0xb23f1627219587773c17070bbb190e1280ab27c5d7e98b43adea0e1f5017790149b71f90c3691301bd514d20238c5e6c", - "0x821b7bff6349c204ce50e00e296982536baff68031165ae4c639122195e7295ea0c82ce66fe32a1b762f6a311aec384c", - "0xa26c15bf1ef4d5543c4a006e4ad2a450d44c93c62c0f0b035698530cbbf925f6705d375e1dc8b2c6fd9a2c69f4126b77", - "0xb5c5bfff4697fe13a5177fd87a8e293fd1c6782cfb3d1f95c5ddcb13c309dd1ddbeb14cd359c9f3029b57ba52996c9a1", - "0x87a0d37f04155bc22ade44f567dd8a81445facff15d643886cbe6534aa44505e331bb75c9ea2f27624154a5890aaa2cf", - "0xad85c0e6345e2333a0ff76b769592f2b24fd0661984498dec6fbd2d9b0cec5f139bd71331a28b13aa490baa7fe27b635", - "0xa9e6298b90aa8d3f4385858e08f393b3bd61376ac3dc44a0907ccfb372813bbfab1388d544c1a4907aac38a87dab2abc", - "0xb5cfc8bbe4cd3ac1a66b1c8138c5c68e643f7f4c310cbf1483f6e48d4f7e2d1cf24b2704fc687032eb03978f18239072", - "0x9493895ce0c815b60b0ab3a989f63c6ba4c752976160f3e52290a724ddaac9075e07dfa913e113807e0e57725b1cd593", - "0xb1e800c2aa32d34d34b24dcf890f6ccde7da60b98c4646a5471fea7cc6df8862b7a9c4c40f38d0554e33e2984fd564ae", - "0x90a18f877f149a314767f5dc15c8726efe5d20a8e15ad4922c6042420a2cd82018be813debf02c6d69b96e8a27c0c5dc", - "0x8fe35142442c103e7bca602445b87cb017c76befc83d66894d4f810e343b3a571f3fba14d94521340ee7c5ccb13338dc", - "0xb43547cfaaae899fc6295f496f213916e5adf9b0d75805c32df0f969fbc1b4f8584759b2a06b81546b48004d72f2e8d9", - "0x9410d55865098325c7b559eb4e84fef8a3ae890e1d6053b3f173ce22e60ec6563041ad8cedaa2dedbb59f3dd645dd1b1", - "0xb127d9e4b8280e10434d53207a7191782464ae83b4463cd8a32026e5d8a7a8c5306ba43ed9b7ea637d65f64d6a08bcec", - "0x87de8fe67524c7d107d7033d4107659206c347c47cbbdf85e3441b53c933417feedcfb049465c67f4c4156219a4f63ac", - "0xa582f976e77b861731595ea8450c6b525e371c6548cbf7911f05560d4c7a4b62a425d5c785190628d1aa1f8f27c43b51", - "0xa195e358742d924fe2a7f955eb21ced7b211cfcd5dc3e598e0d2713c3639b72f986aa586b7a22a75f8547bfb46cd52a4", - "0x97c249b70ca2f9da728a256d18d600bb923005ebad3e1d541ebd580af3fe07123fdf87f4e8f98fdf9dc8ddd826ab7344", - "0x8fc7891e2f540d8f20464a36056f227ac2ef3bcf2b6edd4cd2d9024a48fce19480fba36afc7f5c4bd7234787b8d17f31", - "0x9047512fa27e2d8d901516b5714133c1845494b6f2aeb2a0570dd8533852f00a8d9a8ca64979310e83ac73fbeccc33ef", - "0xa1be9cba454617af0dd38865ec29e7d0777d7c68968c856f90b5bd63a7cc4274fd8b179be54143bed972b921864424df", - "0xb086ccc8a705999184f51e9b45c76975ca8b108b32a3955e122711fc1ee007d8417a85c9cef217f28d6c7799b60aae4a", - "0xab0938a72118ee2980b28dbea9f7100c6f54fa82d93fba8bfa81b6bc34f9d2033a987e5d6d3816fe0bad53cb88bb8c2b", - "0x90fca0bddc14f70282f11998fb4c289fad5c0e78c8e8f9e7a811f20413459a05c9d107ae771e9da501854022d827f2b8", - "0x84cc69b7200f63c2214375a7a0a5ccc14bc02ae45bb6f3b27f67ac538d01e628c66b40e5c40cee38bc8634f1a3c3cc6d", - "0x8a07a1cc0a96e6c6da0d27a540e235c2ab6a95d087e624c90cdccd781a9bea6abc0456d896574950a9e21e7d87fdc582", - "0x87f2084a2f2218d7f9eb7725617ea37db0a19fb0bcfd7c95501f62fec0bb6bde3950690420a40d93e26f426fc622c825", - "0x8c9fc9b470dcf8e576af943edaad28c29f53ac7e24657618c21d910eeba6d7b16f16c418bdd5cea3d639c3919e93b7dc", - "0x8f026883d9d8c7c2a5c04e4c7220ba7061a48392a8a7794a3e290a94967d14caf040a3da3513fd9b4e695376e706006b", - "0x83bef852b9f387a2aed0d3537e77c895799c639310cac98e7b699e9f5d74b2b21cbca58ef910c6583e0b849d665ad379", - "0xb08a03accdc64474490706edce9df7853b78b719ee731c195f70871b7586ed274778d99b84ec3cb8cc0b5e38c102bce0", - "0x99fada688669b2ea8d9b7cd730b057292ec3fabd30cb733ea3f7cb76f756b244cfb26df03b9c087b6d9c58f5233dd1b1", - "0x8eb0fc7ab6b4238f2317620191dbe835d4ebaad0882e22e8f0857053d25d6d9077754251202472d875303669dbb806ef", - "0x8fac2cb38c3a1e361aae5313ebdc1c7e0b7d1a440482fbbe24389a7fcd381169fb325c79e430be170452326cd4931732", - "0x92bacde1472436209032f0592973a5a40d505a9b2c9de648eed1ce392d0c18e23aed9114a9634ad3a7e6afc4ea80ff21", - "0xa28b394018434be07323a2356fcfd6c70b3a4b1c6b6ea44da1da66c389a659235e0dc941019bc5053ca41f10d9b6db2e", - "0xa6d23d7fe7ef475bfe6486ad4a99ea376c6a6db3e70a0a7af421ef6e6c4d6b9cff68d03a7239a56eac784769f63b2bf0", - "0xa1232e6747573e19df98a088fdba57116745612cfdd4ff21f8df82a66c7d5df7e0a6c0cd73117121a516dfaabd0f5016", - "0x8dc574376016b73f6730103cc45c952c5df5d047d0b4ab3da0303f66f43f7d147b5eba5300750e885c621e72b4a64b43", - "0xa66e9eaec79c958e624655fc2adb7b89ff3da0393898e028bb07cbd6511ca8d9318e1d60dc11cf0265a498290e756ecb", - "0x8e5299b661dc0e088527904d2c2fc0256613a1fc2b92bb92c633acf145edbeeb053e82b468a3877f6f14f0878fab57b6", - "0x969943ce7b54f6e728724b26cfdf4df90faf9f9796bafb910ba66d96cf34062fee6ed9121abd193c9e322950c8eadbcb", - "0xad29ce021d7fc875d1e61ad3a99e112ff092ffd7900a608bad30517e50e2270e0f8dc7fb5cd42f1bb995c17d86268f48", - "0xa55fd82520f4d35434066bf93a9601c96549cb4714d9ac05c32e16803daf8763e23c3125d2005eb229bf5d7e2a91ec3e", - "0xa95eccc21af531c5e1a36ce88eda6b87732f5fa680e851bdeaef73421c1c87c8e66bc314b07ab472ecb67a08ec53cd4c", - "0x8f48b5a0636bd89a1ee259223065449523984cf3bd9be78c9242276c848d2140bd94d1a6670e446b51b178ff694b5c7f", - "0x8a58b340e30f0cbabcba1c565b68eae66405fa2242b43a7f7d3bdce279af42fcb4ef58c85fe89cc2dc56a41a51f058b9", - "0x99103a659e26c9a4d19404db4220dcc5defbfacfdd969eb7d70b4fbf9b2c91c92112c0097e8f0c32ddcfc35741da21ee", - "0xa088cc15a45094cffac52c38df427b7144d621cd1d12ae87d74c00a039d757e36fe3cc2fb35fda9b33b375553585497c", - "0xa0f1d15bc388f6602c975b4b9cb23ab83fe31124acd946195b999620c3c20c6610157a891114a43e3af551d7b8c3e4be", - "0xa571057592f3a9008bdf726254c364975705a71bce4e084a37915c5317f635528088a2f50afdbe7240c14d813e8e979e", - "0xa31e425feee58f8372e2bd4c21c48c299850df34044f27db3e4df55fc5e7c042cd19be59500acb375fd3478379f06053", - "0x94645ca6400f80d9a90f5b1c5b515816d6049ab04a552109c9c16b41366a7f3931d49338d944ee8eaf2ef5c77062c362", - "0xa61fba865027b7ccb03a4ea966081325eb64db5a64c5d765d2893f6a19411d40dd957d8a0b34733aeb3f002a4e0279bf", - "0x8199b89ea13ef8eb2f54d27bdcc781a5a7fe5bfef4ba4444bd651ac6021f4d90250b2f2cd8c63fa3ef237ac6eb1bab36", - "0xb39e1e98d07c95a4fc19ab175147547e5a20e66c044f29e4855818db4a7d0e7e2c24192aa8c89fe378f8d8ab3e9f0e1b", - "0xb807bb0069474e190b40bb2b34165351e73a392ffb0de83879ddb181989a22bccaebfdc90748f54de81c41ea244e7ebd", - "0x8b058266df90032a1a9befc7abb759b64a23ab628edd051da8b81db4211c72fd63093dbd94796b0690ff2b0c0fe16cd9", - "0x865decd95200fe45947a4249d2d8551ca5d7b3d7955adf10f94ada3e69d684e5c5b8939fee9a4457f22d21bbd3ce9670", - "0x95fb5ce7af13976320b36422b5cd9dd46379d13110fce619969308ed6a250cf3eb84c73e8ba1d32edc01aa2f6e367707", - "0xa1a176350aed82d5ac01a072ac7f3cc1535e20fb597ebc7e417921537f9bfc4cfc0d490d4df831f0f8ecedb6be970a15", - "0x974ddd091c1aaab7ed356b65c244748a713e98b133c5606436e531c31b31f6ccdcad2037b12c68fb54af4b19bd1d82ab", - "0x8ae9b7a8cd856087300ca90799ec3265b92f84da8ee9e98c6ede1be378dc040d0fe68b8ffc94b146f2521b9fe3d19e54", - "0xae17df60b83e4530af584991b545bf4b3cc1045416dc15250a6b75a9a04defae4c0f60b8bfbeb54c8a21fa84fee58e69", - "0xaca1e75d4a05282b0cbe6256925c0f269a4a8323888bece4a48aa0b5e7bde7fbf1d3e4f5cc38fe6a38aaa091ccbba4f6", - "0xac19171d3ee2f2e5021418c37a0eb25c083de6a6396290ed35b4771abcd07fda745fd082e3c32c117bbab7d9fec2b67c", - "0xad8a35eebd3bb28e08b9ef64bf2d8b75ead69db29c96544d71686ccc0819ebc6823e49b3b879ce0e5ee3131153900632", - "0x9479f12dab191269b020b70132996cb32059ac087e2a3f3e559d297494189e1d6849c340ace032946e12bd4923a3908e", - "0x8885e680de6c158cd67d66c142b2b4ac42b96e97eab8e2dcb90c3b454dd214bc530fbab6b5d5746064b9813775b6d5a0", - "0xa16d8d27d9b2fa04c7eb8436062a53ee0a4d679bb205d7d7cfc24e5f28e3752a9959847e9e31496bb0cb1c11caadc30d", - "0x951b00c69dfd9fc80b17733b843c440c58095989bb8744fc9db63a4a6116f24d5f224a279478fba8cf57753261bde217", - "0x8a693564994a1dd298f0b279e618b46bed349c53236fed9d8e05ad9383ce55fed02b8a361fb8c09ec5ffa8a271cee015", - "0xa09fbd62995a33904b4a34ac55c80f6d4cbd39a902f5db1038d909f1a2d385c3f5eab04b157b5662558bf35ed29cabc4", - "0x8662373988373409a4b31d45c5110afc32aa008bccbeab39d5b09a0e10370dd879684e722a8856b0da278e2bb91d67a2", - "0x8980d3cb8a82b3a827ba65f44e50efed0a6f37d6c150d70e4dafb67b1db173b46ca29d487ef9db92d37ca8312d246008", - "0xa279558faa11850aa4f0dd9ca8bddf69cb98bcd4edfbb0c19f22d1bff85d808e8f2cc026d95afd09fec2d15c116bcf73", - "0xa3fadf9c3066c93aa6a31d2346ad0a1d012c12ca7a24630aee46a087eafe5fa518d20647856d44ac03576bb3a9f81a76", - "0x8a8a19b09417e1b1607aeb54841fa69f58e094b46971c6a5cd0fbeb2aaa98c56599ac242272e6973ca0a9d2c09ff8d77", - "0x858a636f510b494edc76e86b1718228f076b8a21306b02abd086dc2a96c7a034704d743ca5d89b17903fe7b2e43e6fe7", - "0xb031b789e4073b82bb8c78f9d3fc2b901d75278733a4fa0a5aaf985a298269a735217e85eacc0dd554375d610a425359", - "0xb8603ce7cff755f5e07eaeb4d74dff179cde405234bbd7b3f62fd903054aaa34a9b868b04617d7d407c2b8e377227f07", - "0xaa41829c941acb3f9f0e2008e852fe855e153960cd3c85c4b8ab9f97ca91b7a5aa18f997cd023ba9e03a653f238a4f46", - "0xa35639f920619dff592176aad2b4b071d5c960f149c3a75311b16841d1872f29aeeb7c155cc9bff41ea7ee56f799de78", - "0xb252195aaa52e9a34936ccd1aeb40d28fc262cc4570d4f9685da8c591080e97438edf64d4d4d074491344bb5e86b6b23", - "0xabe2e52d10620b503dd1aa584e005d857294565ad90dd89217a77fcce4bea7b0c72d54dca7a1c31b5a9042a9602557cb", - "0x818085f2f1b525d9b2322c8785bf27a6759af9aeb231b0977cdcc7d7e77cab5de056e522dc791e72b8d9b93a9c41e833", - "0x930f64d40ab26be006e91deb854c5b22bf6951308dc406b2c7c7791d5dcec470529957fbcfd6a3c9655d544d974de7ad", - "0x92b28bdbea8c7588ad3a27992c19d73bd3a478b276f0e11c4e82ee2482e4e167cbcfddd17a1ac6bebdd862be65f54098", - "0xafa6a85fb906f5ffe52b6e9715435dcdf9f7892a396d740d67560fc42248d23bef470989663a73190ac9da442cfe6a82", - "0x82d3338e58fb316d66694ff4674a5d99bf0b13204dd251fdec95d48382f2d2ac60176a19e5ecbaab5e00af2a39a338b9", - "0xb30cd35eb15b3910b8b8f91cf04c223d79d587a7ef713030f0ab93f446caeef52c60ada365f8d3d645b477e7fca61d94", - "0x89554d2a9a11dd7e56f0b568f2a50c72d86362d95eab5d94a2386397012e18bef7c9e01a2d71fd325c0692e4d316dd16", - "0xad58326fea1c00e0f8aa92923661be4b3ecc79164d68e91c4d1366c9894b6d049a4f31c9bef6e5f21466ec014ba6b94a", - "0x8915a16afb0e68a84fd12a9203f8f348954920126d88136ec027a81f541b67c421b84ebb3d6e8f085c38c2499c28ea61", - "0x8e246e1acf655572863481367da007e94bc1bdc1f28aeaa1fb163dc05a51c3526a2bb9bda0a14fc6d658d85a9322e44d", - "0xaf83f9ad3c7c1504fcf60084e0948624fccfe3a9892dbcba8f166d0d67b475ce57ba008f286069da20a0da0cffe3b4ae", - "0xaec86d2d803612e8d27a01e3382e0a876164baaf2f3b8c4e9455ea00bc2e525378018e6a41ed9686c6408148e852bec7", - "0x871bdd8c84abeb1456ef73595360de6cf9f92ca9e6a8b6b816ec7497be60a9f509ef2c91332d17cb5fbd347bb0113d2d", - "0x9503ce513df28b61d721fd5e8667272a28f210ef787bee58538f786acd16f04a412387c6f5e6313c43f426a70aab65b3", - "0xb2cb0526e7e524ca9fe918e951c19460aca911d2b5ebf97c2bc74aeb69778a28316dec8916a4e3628b46bc51586c1bd9", - "0x98f52ee1896b632dff5153e3d1fe389c6200b14cdda6b27e12d4a4182763b63e0f587386aed78c97a32114dc286b975b", - "0xabbea974929c9ba70551231e3833d5cecc71c60988826771f792f190ca77c80efee7607dc1d6bf01a53796d8d9b73017", - "0xa4cfea1d06cf840bd599b14c011b6b204b2cf6f57fc7d7f310052062a4fe8870f02504e6c837c2b556c925921e543556", - "0xb957529d7e5d1fc45c5a822a6e0e480e46af2f5cc3801c31996b9b1acacfdd8d142265148b3e1453a0df0c5e6cffc5e6", - "0xb7411aaebb1b6a6a75568f81d052e60fa7752a64c20dd7cd5457f999f0185807987de8fb72ed94ca9d1148c19ecbe1d6", - "0x84be67a5ca80a1fd0f43cce4c00a465f167445e42232c2d2cad5e1097a62d3ad564041a55f0c76a340387503f15e0ac4", - "0x98803688f8e7b445c7ad14277b9f5f12acfba4f9a4ba6df9e2b7dadb726f1bee5098fd15e0b5308b6686a38864f84912", - "0xb085eaa421e566276bcd45d8b9fb71475c4530d63e90914eb2a33c17333d5628c1ec8a45691cbae82ccad97d4addcc94", - "0xa08ff7dc59dadb183dd0b5d336b6174464604bb2b49315e0c42f34ea08a8bca9dc9207750638bb7ebb6387257411956a", - "0x94d72607cd8a75b2fe2e9333959bb9d5b54d74ec36fb8c123c157b19a17f01f310b3311116b34bcfac305e9deabc79db", - "0x85fa61a796226ce555f8195c792ff6f3d483f62dac41c17b7e8295bd49ae6039574896548728fad4ce966be84a62a6ca", - "0x829ab1087ebb61db05c59e3c9d03e7010f8c546db117a6409bb813f6fa04061833771c8aa4c5e2981bd1ee551df0ea59", - "0x97f5c5261db0b130bb8352fbcf65002327bd6d8a7d4fee2a9bc293173c8c54be37ae229c5488c1983bc1f7857c66188c", - "0x8756439e5978ba19e2cef95dc55f706d53a05d1fa964c64d89b0e95470b5344b2f8d44680080626c37c77a00ff0e6b00", - "0x915d33f90980089c33f403ba4fc5c689ea7f1656f5c4e1110db987c59eb981b6a46dd9fe82c8efe7d1e3504f1d2c4d2b", - "0xab5bbb84884ef036c9b00a84f7d5ffa2931854e2afa5a63121fe64d548531af4203495b977bfb9301bb1e4679d42665a", - "0x9830b846a9041e4539eb858a179b4db6da89b507424e6d858ca4334d973ddae255bbfb04ae25c3276ccbe97c46f5816d", - "0x8e35f4563b8a5c9a76cc1da87ab21cd894de393dd61bc977cf22d3de454de350836e032ccf7d6ea55e2e6b83c4424146", - "0xb6338ced0f05806c625905cc51b7e772c5db3bac144e897339f67b6949f4d648d41b7d23bd3f299f4879507951ec031a", - "0xb3afa470fc71b92f415b879a814feb0702b6adfa08e395cee4f7d8b0e3537288f16c83b28ad4e2db02c1fd6d39e6afad", - "0xb4fcf7af3196bec84fe1f6e3bbebb8abadbcd46de02a37966d0ebe20972fd890803d570e4a201f2a89f479e09f19191d", - "0xa21fe1f8f57691165d0c7d8436765562cc935288f24fe765351be335f906c6c4dd1d0714b134c51255b14511c957319e", - "0x880a3a8f6b4ba410be06628a011e6bfd38d86919cf8014b4b4e1c930f8e3035749579389690f21bddc4d4699de8a4b1c", - "0x907d93a7666d847a140367c0a0ff80a96d6a8295b07cc4ee52d3be987f431d8dcb95d3717dfd248a5643c5395ec2891a", - "0xb8f38c78b8a2c487874c1a6a92d15cf0dcfd26319d4cf65c2f4fa9432203ba5ffefb02b7324022c34bfe0da369d96d65", - "0x8bd4ebb6d720fe52d626a621670a06c8a304faefca3846df1f619f4d456e14f8bdc547fa7210b8639b89c6584ea5c5d3", - "0x8ebdaa288a71a2d3188d6294ad0948a4f72c1eb6a2e921ec82cecda4d315a86e3e6233b5ffdc7219f34a99e9b4555317", - "0x83320fb9dc62119655bb0055192471ae06b7641dd4af64670a4d9475155733555ad06a93ad2fae72e029049601780654", - "0x80b3d022738318238dd32f122bd88cf2f734a61e315ece521e9e038f4a9bd7b54b5e67784f5949fbcc5fa911dd4b048a", - "0x891a23b4bf5cb8558b4540b66fb6b9fa54e9d0b2c084f660c8bc77af5ddb97cb5d8042b538f61330d9fa8ccbee6c8a41", - "0x8e5651d9c95aee23835bb1a06eea76efc9d5c881cf87ee847ee5149fdbf3d67dcd8ad0675dec8fca6cef25368348abaa", - "0x86bf1d094bc4fc7c21b21cfc7adbc750db0b27c35bb160d306b26fefb2699cbbb1fe624df1b9e7f6f895f1b81a829361", - "0xaebc3cb2623344315875029378c71ab7ed3cdc9d3d42d4b835b373c8420adefd177a44e532f3f06f74f0a40d53713e5a", - "0x9527f659e93a740b4c50d0d3d9aaf1a85936f04866ffde1aed30ab2fa1c1d565b46bec5fdfa510fc3ea934137bbd46df", - "0x8488673a4bc29c3ce9133cbf41c546fab4ff28c5d46048a21e710a8df4f2bd1c77d0ee242dfd962a30d646e5ebee8c01", - "0x8cf29773c0e0fdb75bf6f52d7066e7d6e9a3ef056bbb70a98026464b32316189addb5766822f57df63bb68b78c85e1de", - "0x810c6c1aa53f9c3fd0018651b1bf25215fe24687b568f21a121e0bebee576a75e5f0d08ac9c6c21085e52228b314c6f8", - "0xb529a87fe47402aa9ddaceac63a060a6640418891f931036c6e4098a1b308821a6f1a244fd5c1c22a6ed5f72f6bcf825", - "0xace9284ce89b5c81049d329db2376a85feeacdd9f735cf00038adc51865bb78bd9bd5d060efd0b727c509ec19988f99b", - "0xa2e7a949c951bddc99e68d80b3f3fc4ab960b682229fdd794f9eadc80bee91dfd5eda0052149d05c74fa33bb40d75ecb", - "0x86bac22daefca9143e0b1d25534900b1f7711ade4437642043c4a8c65f0d963cd1f0f958c7391e5a663dd3c59ed9de60", - "0xb7d2a6e2d44edcaad19498ab3368bfb87f9ab039cf2249d6e76091dc3db0c3bf45012779c02811cc818e95796e6ad9c3", - "0xab474f74e1ebb3dc696e7a6bfd8845ef15fb6411fa28426c486f7b0f789a6af3016ed5f7da2a36215729f5cca0b36b4d", - "0x86616a1a9dcb50d1896f3eb937bde67f213558feb401aae9898e41cf1fe33b443170c7c2dbd1648c9e3cdd0c24289286", - "0xa466169a2d95a5fadb6a69c7061cd2911c3eabc0b1a2488e216f8cdbd2c5bd87e80908b002b9efa51a67f02d7af2155b", - "0x8368af8b7c0f55f3c4f7036fbefc9d6a0ee9ff61197cea8ce48546753bdbc0b61eab604b8fe2c1aa91ced7a879e5899c", - "0x996c91779ff9767232ae603c5b1da5bbe0e303c4c2c72ad2d5944ee1297af3535f6bb3548fd1fe8a92cf4b281e1d83ab", - "0xad4a93d1ceecedd27389c658b38dd71cb13c729b27e490381d8c3ed4815b11ecbc37b1f82c0656e0ebf77e5bc35196b1", - "0xa3538c7ea3dddfbae80d67caa9fc547938bf77114559f9fc5180d9d0ab837d7fb1b27bc37405686f212f2e98b0028e59", - "0x8abc9fe135fbd48414f2ba28344d9f49ec2d5ce94fcb314ab8dc31c754f3ab7e6ab268184a67dafe8b1fb811a762c112", - "0x99ace100d8db88a83f1727b7b48baa1cf45b971d08112e452f5003566815ccba0ac3f8b1df6504f55a392efac8e3e70a", - "0x91ff50978ce629651f1501708908d75b490c18615e933191cd37613a83d4b605b0b48d024d27807637e662056d76276e", - "0x8e4104331ff1a40cbee9f489a814cf5bbd6fe4eaa1cbe1e13625fc3e6697b27d933265e5ef8728cfa8fc4ba5b19a614d", - "0xa442360d49bc9ce3e75eb40bf2ba05e9437fa594e8b8de34bbc822cc7b706dfa0dd10bd6bccb702d8556cd1320924821", - "0xb6ed6cb0aa34d5793e929e0d9b9651e41da3457a0b20c1bfa93a8f65bbb65bc07c7507482d71c1c285f5f663ae60019e", - "0x86d64df2dcd36d0c7348c77480c8af33dfd889bae7bb045888eecbd317cf3e4731b96ac18f410a99ed33a3f22d448f77", - "0xb8dd25415275d5ef8260bf5195ddb9b15b78a8799e4d15cca7d6317a18eab7bcb8fc759be61360915a28a8fcb5d6ddfe", - "0xa325a334c84dc1d9acc0a9315b399d4be93351c7049f474702ab58b4cccfd69aa50b8731ffd598ef0144ca96477c273a", - "0x9012a2dfedda5147cb9ceac681fa9e75e2901eeb3c94d87465a44d11250de4bc66d1e00ff674f2da1d800b43f686df9e", - "0xa1049d59da2a942d4d2aabfc0d972ebf3babef9c5d8fc5598ea23a048c2e58f7f17b4d860e437276bdae221d9e41e3b5", - "0x8c9d9a8826c812943d74c4d4f0fd2f1c8087135c37bcd9647b722b59801b01775a644e38c24b43e8e70f83bccc4afa27", - "0xb9cebd7bc7b041c18bd35b970f87e9b5183e4ace034e21117001fff8a05b2a7f9ab65cf6ab8b818b8192d1db5649312c", - "0x826710d6432ef97625db25104fc8dc3225bea594a10cdd4473d5ab72be57b74928ff356d210032a61ca590bc68509880", - "0xa18422ceb8c61af305277628e154d3a9c49f47e230a44c6216128d73db7c3ca9eca9f87e29cb2126f1c312f423c61463", - "0x919d357886de9eaddcfc46cd43e2b3dda3f82e926a3aedf02ebda9159faa00736bd2cd7aa044c76ae238a3a95a5bef38", - "0xa822d5a726f5c38e9d4a750ddec80bb965a6e5374a3d87757e2e48a18421f3142c3985450d1833f3ff4ca36e3b838c89", - "0x86bfb86eece6f6ea8f51985e312171b9bc821e0c3ab4cace556da28dd7bf89cfd5be3fbdadcacc19f2371c6a11c564d5", - "0x91b42643b297d8eb2c1bb3f16b57ab2964de99dd22bcfa07db1d0010715ebde94d11851df575f4f1ae602644e454fe0b", - "0xa5e444ed3d5fb3c5afd2c9c24d676adbf396f5d1d47bd532edbc72c83845970582ec49ed026b3b982c9c1ea725192cfd", - "0x8448387a14d84aac6afef682a27be67e5b05d31b59346748d2940072eec771adb53339f335daf4463f555da2d8543f18", - "0xa5034b66a26bad0f753be56dec722fc98a072bcdaeab0bb9cf35a56a573d9424cfbadbbaa8ae30690f7c6c6495331fc8", - "0x9317ac32da1772099f41564ddd8247e3532528b240db753a1fa6fb35cc039c6a4ac4546597bb2fb28721684bdfebdb88", - "0x8b4b0001a6234335502c8b17c4de274b83b0610960b5c46b9075c6e41f357ef0d8c94e9b14bff8be7849435512626ce7", - "0xb1aa903511fe4219acabf8761a8e4316cc4f8955ac8640c68a7b547cfc65365a8fe0843a4098f9f17a4c9beb75624393", - "0x8384f4953395aba4939b24b0669853df78f2fcc01b2145c08d3fc333ee2a7d4adc12f2d81c37d0cc187ef45b5f69f59d", - "0x92beb5a3c14637f84ee7a3c9b4d9b305b10af8963c087b86047e9fa959f41ff362d56eaccfe887bad1ccbedc488abe2e", - "0x8c60e16dbdfed2d1c8cf3f1bb0b0f462489293892f9d2e0221b1691321a771b163fbb599daec4cbd917da75f5f662de7", - "0xa8a6e3041a0c2a12c76f51139b221b03ccd1afaea3b72ba2c3533b797d5f67d8b90d3474b4f6f8e19a77894fb90842e4", - "0x966aaf74560bd4d164ee46c7d393b2c628e307019ca4289dbfb6a9a991608ad80efc1ee6e9847a19382ff8f3004aac8e", - "0xadeaec475d4bfb6075be90cc37d61d45ce14da77f8a9a508b9f829ddf2abf91683aa2fd0372d3025a660c94b0f612685", - "0xb3392bd1ad0c202d4a95912e0e06d8c64d7e2a8818dba8f895abcd0f6932efa9a0bff8a2aac107046d3478782fe42d33", - "0xab38804443da16d32f11c0e364449ed351dd36b7c82b5c7ababcc33a930acefe09fdb5261da04f6dfab29421fb1cc017", - "0xa34e0df9e953841bc44c09e16d69235a26ff390a6d128339ac97aaae5616865f86153d8d7466519dec6c52ad592dd3ad", - "0x99581db106391e6816403b1e9d13747aa05bfbfa5b46696cdfdedd1627b60e1ddb92215d138e007770512e93bc6184f7", - "0xae60c3b1ae3594aa4e3f08eeba3951157921aa6511148c6d32003d42157654d4a3a39efb1bb317135620f09729d134d0", - "0xadab0bc35ca3fefb14729259b16907a34e10ddb6d78a23f28596d3d9b244709651be7719537df33bcf003c0e43bb1a66", - "0xa31b7b2f3411f986b3415870ae42f90bb678a9fc44c942f6613cc4f90f3dbffa4b5fb8bf3abfa4361dd8e396d9a3c5ab", - "0xa69b188a8662eee48fc98201fde6f0d14f6b54db83ab79c2ec2f4b6be809773231704fae2cb281fed8b05107c63f2fda", - "0xb79e1e7a9045af6537981f54dfeed0a1335606301b73eff001880798f01ae9c0fef6e427e171afbb1d0a78135ba912cc", - "0xb1b883fbe379995b3741836a849516a0f21b18f42a34db2c8cba01f86711a2baa5d14910a110f1058e36431dec163cbf", - "0x87bc463b90123cd9e177f2284d72a7f4a1d4151659e1e4e8900bc21986f641af2f9a3386aba56601e6fb64da630b84a1", - "0x97a51bb7d717495f943db162837d3bf700ee0653da9a94b36153443584602156e168fde97d77407d0861641d8d373094", - "0x8b02561709564d0721b5247775dc66c6c09cf68a8ea62fd7dd361905ebcd98bdbb2c554fa272de71c6d22b04d33e6902", - "0xa914b9406e71c09deae875bbd628af3f54de5ccf811365cf229dfc69541d996689d05679eb02d42a0adda02be6e32d2d", - "0x85dcc5f3f77f72cf0818bd04c037cef560f0b0eece3191e06fcbb54228d11f7afbb8d9f8675b404bb39ffd04a3b65bae", - "0xb15bcb96a98bc6cc7b802dc29b79a903223b1712a10a22e776f45c530a4f767665dab1a3c6d1b52157f4b79055d5ac81", - "0x965e353e665b3734042b61951e105c1800718eb3c46759952755321ff5c639327d045c58fe90befa896e96b826910298", - "0x96776a5cd26b69f08a68af0201b2f739cdfb9553b466271063a6c8b8307f2a3f51294ea12c7e8118c0e6b884886e1bd9", - "0xa369453bfbe7ef0b2445231704abba25527b073bf735a968758975fad789c74110a573bc7ec50001368209a0ff385500", - "0x8e54dc4f2a557703b2d8bdb74ff107bbb239034ed363818197b2569c03572c14cff21273e94802159563d50205edd651", - "0xa1c66a1a82c60dcbd139b8ef4de62be423e7641a6b94ce0d0468e60bb1b000d268755946a028d3961d8b4d3722016ad1", - "0xb14b3c26dd9d17d6fd8eeefc7f706c177ebbee9b8d05f9b01174deb37649f77f97ef1a1abc0cd4ca7a13618a4036067d", - "0x8fe8f9754c5ee102bf96ba6b6f29a14fbf83cfe3c5f81b5358ccd4db87fd8c5d88760172373bdfaba7eaac98ab1fa863", - "0xa8c308c15242bd9c7b28e110715315a1f9818ebe03662027a6f1feac13a5dc9bb111d29444d13546d8e441e49960b0a6", - "0x85d87035d74a1f4662f42a8c6d26782daceded0aecee9232b78139b1b50fb764e87cdc5d1ca9d6905735dd9c3dd00dbb", - "0x986c31370f594d4c8a9096c091cb1484c1c0755804819a9462ad1b67937c6b378d97f1e4c27900405b21de2646be70ca", - "0x832b4b427f3347b0073c24f54e17ac16d5a80d04261c1d464f89dce89f42750d69dc8a83ee0481e410f94cf1d108c0ab", - "0xb13e54d91d5d414223caf43a7fad36930300594b8cb3ba97c0c873cfefedc165d05f05deec8d74a0412d5f0932813088", - "0x89c931d595849c8e54f50d550ae4a5d71c4bc74af436965bc32adbfe829a48ab15c856a69070b4a4602e0113131ce4cf", - "0xb03731793db466b74112a1b9dec3344fa952e81bfcc7fb2bef3cb20f566c3b2bf60c16a93f84f77f4c83d8f2a535a2d2", - "0x92e8fc80d49001139363e3201c93db8326c41322db51937ab641ee7f1b2f7d03089e20eab19afd27abc23de039ab4b0f", - "0xb27d95c90dfa91886aa91c9c8c85ce09fc875279028bef49abfeaf44437a0528ade620c8c2b3d712ab594e73c5c032f5", - "0xa42e2598731a792975feb5b24bf00b1e7cba1620922f8c2319dd5878419ce6099663b448299c0623ce400875c48e12a1", - "0xb062840f63b555a254e3bc36e9075d57c816ed2e9cb0e262f9de0f3692456d94eef702489e5b11c9746b949b5e84c06b", - "0x886226745d906664c476615dd41deef6c338ee10380657fdb75cf9ef28b4d9f56e69c8d0ef01e9cb80eeb42f3e5773ba", - "0x854a3649dd5b22def4f246eb0d1f1a206d3dfe42b5e44b5fa963a7c5b8bdaaf7f35b542b3e9cc53187e66a2315ed9f9e", - "0xb5a48cef68a056955ef4c080c85e4044e9f8a562f2beac9fbb5e19f8d618718c86794338c6dae8f94b6f5e9f8e98404b", - "0x8f8bea7304cab80d0009b417c198bfffd166eed6f6db19f28b7616e8b0733cf0a4d54d204361d7f8f179985c8c3a16ad", - "0x8af81f10339e2f75f6b6fe22a641298bf90c8676260abeeef90bcd52f46ef013f5aa4bd9d0b5ec15be61b7c3e0f32350", - "0xb0397c64034598c825f9ef653ff16f680325546695ee6e9c2957d3c87593161a063c5219304ce6a16b7db75f1a2c5f7f", - "0x8d2e7677ab6fbe2b0f5ab6dc356190bb3ecd7fc468c698d512a6c69f22ea97b71fa961c88635897a5b539ea51b70b4a0", - "0xb4e91a693cca1007fdaeb7e679c6837bb8eae0bf61aae447560ca6eb5ba918cbd9952b41769657978413106b359e169d", - "0xa8331a907ba7d95a5e4090a7680d1bce3cd803db49fb84a48996e96514701de1602c4eeb4b5e0b1c2a106c4f678a72a7", - "0xb54dd28a97a5f934a34c2817af91a41e57f88d7eb5fb584d3b6692f2d1c4b2a4e273c4de5fa33a0fd1fa02c9d7cd1fb1", - "0xb8b780e0f6059ea27aec9f3693ac9adf0b93f75fe7fac5230deee1e7471df0bce9b5b2f260a6a0a346afa723860471b2", - "0x980e9847ec83d61442a86cf8c7464b357694dbe67aa5de3a8c88ccd1a038256453101366dcdfe11a565065d78ce89807", - "0x9350a17e397bdc3d2bfbb84ddc79a48bdc6ef5c3d8c1ea39251e762fddf9962b69cdd42c563d03f64615b72c9dab07bd", - "0xa34d24b69089cb5ffc5f06eb2acfeba13c96a1096d1af9620aea28753baf3d0aad0bcb30037ef3a5ac36b178816e878d", - "0xa7c8b9108fceb4e0377eed48af9846530114df986cbdd35f6d54026104fe6bfb3b58e57fa2b3a750225528f8dcb8bb9b", - "0xb0f71f6a04cc7119db96033f89530853d58a445565de2efd269b1e3956397c35a49c328102325b780fa5d0cf5adc2a4a", - "0x92be082f04722fdf3abca7ebfd162b7062939c3410ec204d5478dc8de2bae2b25e2654441d29fe2c350895512d333ab0", - "0x95e7afbcac22dc2d04c5635d7a8c6293f6ce29bc6c932850d24ab5216b449251bdf7aaea838ef40e0e4eee1de8f63bfe", - "0xae0a877b488865f21194470677e12ea7e357c5d63f6bc454f608e33df9a3b20e9aaea5b6aa42e8999779b8b445831c39", - "0x98df977479667e23b897b91f2db8f4cdee7ece7fc3ecf8a07d752efae090d8bd34d781353ec1394550d8a207bffe582c", - "0xaaa0f1bfece62a63f3bc76862b8789e2593b4bb40b3c413956e9e5c4eec604e39d722cbe1db210396eca7c2293489099", - "0xb362703d2b72909d06407d139531fc144e68ba94e55643cc3cbb9ed24145223aff460b1627b41eb9a3b709978aee5a58", - "0xb020025128804bd642fdb1d2b70b07d181e5ba30a5ee16f6bd00d7e2d0c6af782e454cec107304823be61647e65221fd", - "0xa409894c0030081a2c7f8fba27bd0ac53997a31b35a33498d78bbcfa0b7ec0a89b9efa99dc1b8770ba889060f39d56e2", - "0x862f9eace3f54288749ca8402c22ddd7829f0454d17ff4891727c86eace899cbf72d302065f5f581169f00186c23b4dc", - "0x91432f2a823c3ce95bdeb5854e8dc7faea5031fd65c82dc69e4adbc5ead2e5a5b58a9cd1428d3f526cf94a217f37d7de", - "0x9543a9038fdecaffecc4d3023fd67f7976dcdbc7014e82edb4573479b1789b4c610c3964643e031f69ac7a3e3dfbe241", - "0xb4f31d580987f47c550eabd2d276678a294a612ac26806a06634b8812a571391151d84c29b6b82218cd84dac85bdcc88", - "0x8d922ae4eecb45ebc23eb1a8404aa1524b281d0f0ceda58ea93a0cfd4184efb894c047f0a46e2d007704f5506544907e", - "0x98973979672d1d52e561cae7331b730a577c422258c22720edc344aae35ce071be1b017816d58bb29b9cf5c433fd64b4", - "0xa46be974ea72c5e5bd16de591bf27087d97b9030fb4a74743bde5e480052a0de58bd962dbbf0e0fbb0559566c3d9780b", - "0xb2b4464973322d865207949afa4dadbd888c9b0230737561c3b76a1953a85ea9439fbb1db9d0d42083c62419db512450", - "0xae811a9eac5f4ee6cb3a4dab456a3e5d95cb1ab303c19e76fc4b36ef6b4c83ec0b2803ab8680ad1663bdec0ea2f19aaf", - "0x95a426f3d2ae6c6069f888010bb20c392bcbb65d0986125e0f0066d4206f4f443f70dcba8a789da042b57a36980e75be", - "0xa9ec01a5777d10275153ba7441f2e27ba3d6f1a875f204469220ad999bb8a0372369278bf5a11640ac0709771b814a31", - "0xadf1091e24bdf10d848f1a0920eabca0a2087220fa0c3f8e5b4c72ca0424ff3e0c77ad4c259c12c3cd1c0eb0cf32c67f", - "0xb9a57eb8642729541088164b9974775934d7a4c56a3a3ff2a190d549b294fa87002810f31251170b0407c7e9695cfba2", - "0x8625501e5c56948812b72b3495747412e03ede90096be089cb8040069e49cddfe697766ee72505bf715802fc77c08fa3", - "0x8a745aeeddd1be100474d96aedc737208ef19a93a8ad72c10bdc0218073fde6209510048eb46e271553b04d8e6529f46", - "0x8b8d9ac3b91ac0333094c85e81fe2b8cd5c2207073a33f66bb1939e8f1c84ef064a8b2ee984a9f450f0a6e54bb56ccc4", - "0x8cace31444da99fa5dadc7c46f689fa427949d8c089af3d90c604fbdbe0dab052392fbad4b5aeab707e4caa9e739f366", - "0x8750c8bd1f1abe5acfb29ecab0923008cb4455ae8a1db01bf3317df05e1e02f9df3c74e879d9c57b8f59877591939ab4", - "0x8904a39ad86cb42c06692d4801b3718bb63a07a2dc5ef13de16f668b08968db34966457ff2e4cb770dc61a216f4abc5e", - "0x967d1750b0db53e977bb9ba65aa820d7970f8c75d5355cf12a3f4c509dee7e9b6c0f7a828474b167c25b15d74f0e9cb3", - "0xb37297bb6c2d9175e0a7654c5bc6d248f47f7183c3b10375f07e21e9f3e66f6581caebfcf468dc0f8c04132a2a0ede55", - "0x803862e6fbca945cb6c0ba202257df5c7e1e1fadd78b842970206575f70c9757d4a54e9b1a0a269dd37c4f830a40d2d6", - "0xa7a27f2fc7a1e6d276522177f0ae6630dcf5205d08c19319c315bacb165b687d125832d97ed689960885bb7cf42fdf36", - "0x87fbc08506fdf980cdd534d4ecc4dcfbd381f3937dafa09db402e07a67e1cde579e680d3f77865b5669f35fc00901530", - "0x8fab8bd57f76d187f1cc22e40b51736b1b0234e70813ca02559ded9c7835cb3dc71a24c8f679081510c32f330d6ca45b", - "0x8fb917b7dd71e1728bbf32fcb55343890aa6fc890740f41f42e9620b5bc3ef8b1ec67d9c047e4a9de0863a5eec18e5f9", - "0xb7429e758850bb7f69db000d49763df43d18af11460ee0f158b741dd6b7575527c5c8068cf54f7f78098f9ddb92a82db", - "0x8bd3c73c1b6f88ed2696d53d2a0617f74bfada964d2eef2afb5e1cf00bfb646f552643c55d5453cc622c9ecfb23ad492", - "0x8e025e91b30b0f328cd6b79df9603698f1715eb6209e64ef8705cdde5ee1c4ec737a61d9b8a4e72e83b2157c621e1590", - "0xac0b91bbb5ce5bbc8e8d6c0d9d4e51b3960169c608b6220a352aeb13072133aa9d934b4e69d7c5c39fde68d467fa8069", - "0x88255d08bde3b967dfb1dd338dfbdec12a2079851aa796be070a1d70204048c34f2739b7461840136b03429a8b83b1f8", - "0x97a83477e765f3f17eef0d3243ba9bbdcc50fc581f904e92a853a076adeba917279fc0e01aeca42de1aed8af9579bca1", - "0xb0d9f1afb807e0e6f839632393edef25731ab2141cfa1cd965e986940a4916c8788733a39def0cf67afedc516dcc6ce4", - "0xb563e9ed9ba2134011d7bea6314af5d71f63caa1bcbf878c26d7162dfc76343c38308955215252962fd0c9c87200f1f7", - "0x838d4e97bd67822c22cda558f0d35f971a0ab7debd3da3f15c27f7d4b3301b2c17b60cdbca0da8e067f23fc97d136ae7", - "0xa7bccea326cccbbc4773de402fdf2cbc21a028197be98cebf6e691a7679fc948e6bc4981a14fbf493a254eedc444dd7a", - "0x8b2687732f7aebb153bd6030dfca0b6d257b8f2945eb8221ffd36ede50d463172cfc4bb17dc30bd21d8572aae2540d6f", - "0xa4a3e87ec5984c3a755cb39fb04472583a0d2c771552b0701788f03b4618c87690a13a905420358701777e8b5ff2d190", - "0x904c4dee5dfff129de3fb8cd0a6e0576c18ed3d77f49b8f0601997013cdd4ecadb86690389555ebe8a436d8084024f2f", - "0xad1d9c7a6236b22474fe8d45fde2e1f072101b5cb7018ac73c0541c0f9ebec4d5c4469e0570cc188cb5f5ba1d8958be1", - "0x87fc8ca6f737cfdedee973f1586b94d971564a1fada0e4d65894222edcca6552764f1ca0b02782066f19f871ba5842d8", - "0x851829a8b39eb6b91523548ad600bb876408cabed98d30958056367c686bdedbc876e1903b8af8ffa3d3618e3580e3db", - "0xb99270859bfe7d8a4c1a22e2d88a644dfd2f100c54070ffd9c4e1044140fc0d21d62c61214a2b81a9cfadf944327ef8e", - "0xb89a2ddc91c59dc2ed9b8d11e4838002e731b0bcc576e0e18106238cd3282264b14cebebd8a64f004521cbdc690c4182", - "0x8be96bb11a025d265b7b3ff3151e9e227a8416e6118595ac29bf836ef751504eaa3a9cc05bbdcdeabde11f2dc3d20c3d", - "0x87185ed410be571fb532e32d0ff4ef68e98ba2d3d5fbe92180cf1fe8ddfbcc89fd1e03694a9fde1a12ab905db89323d6", - "0x97ef023f71850ddb282f244b3f39579eab69ce5bf3fe5dd2159329b7def4982cdbdb4e71476471acfea0f7ba5a7fd061", - "0x9944324d804fd3978e7e137e6e65348d7473ea23c591136d55779b5a31f45f9e4d866d8a18c76a3a0e8cf2ee61cdd041", - "0xb9930c9aff260105d4d15fb749aa33436f6fb52cf9d50e39dca19d9cc7938d752773f06756af86369e1f5fd5aa71d5ea", - "0xa85ac6bc027ade2a9bbbab2b231241cbbe56e562fe621ea19208a8ea36e1baced89ec9ab8e2f83b602539e5c053f5764", - "0x9917d40d37549caae646848e18ffcb49f5c6c4e396ebe7e74129a41b0cfe2726b4dad34d51f4bc706063e654da898824", - "0xa25f8a4d8ab34724a732dacd2b316c80a6544d4b8c1f45115c4f55c3efae6129b83623ffb31da80e2601f70ca51ead16", - "0x932b54b2bd26670936843a92346d231f2f3e3659542f4d4def73fb36ac0350733853130a5e5e9d8e386d34f817f5a91d", - "0x871bf29d7263bce62a02690681d4e1c3c2f9c2751de4e35810ece13c9480eab93b80a00230ef0ffb858a829ee6bd96e2", - "0xab9643bb1c32dc2e8c05ef49bbde9937072af214c19c3932be137b7b75268edbcdd81d1370089be44462b8032bba3c57", - "0xb67d510c460a2f14b7cebaf9a15642a14b2542c13ebb1d1690596447ddfce6a86327ffa377c28891f6bbd8febc2c17ca", - "0x93a5ad5019a8e680bd053a524e0ffaf8cb18adfcdb22ccb6cbed67012316bcebed65294bcc0cf4f4e2915dbf19ff0948", - "0xac7a7fc1140b1197f2aa424b053e8ceaf48abf41819efaff87a2e63cd6e962c278942c2b97742ffbbedc5cd426a8df50", - "0xaf0115d9c2f887ff97ee15a1116ab06af1920f2f42700b75cc010d4c8038eea941c9bcc8e7cf4a41036813143ab3e8eb", - "0x90c768d880b6ac17ed7ff9bcf76cbd5c1c4879247a757d8cc8b31c4c7bb0ec763d746e6e06e361afa8ee158e36ccaffc", - "0xb3f10561432a97c95d02c1a6f317bb1ab5b98cc88cf5d56e1492ca84eb2ae1db92e9e31fa454de562e719b71442e69f0", - "0x8d94729b5fb0afc196064991f9b3c8e04c0858199aa951f49421ab890079804179fe00707978f15637b8d16246794001", - "0x968515d07a0f0eb52adf439d8f70ecd1f6655072abbeea45431dad26c8937f4aaeda552a22a420598d2136f576a966d9", - "0x91f50e6f292e2bbbe226b221cedb9db36bcd459bfd74fd6356b0620766d96869266315e8503435af1719d8ff765467ea", - "0x968b328d79e183ec560e8f0de113298755cb23a893a631406defdd26ecd52e4b6f28934ad2e219382837fbb8f87f4360", - "0x94c126a9035059d2d817c4fb16cb13fe6047c356fc495aeb369acb1c45e89306554631f50d313707e82624b6107d2fa0", - "0x90ee85deb494043a1cb280d687e3f55345085e576484308755df1bdb6f734e7dd25fd2489cea746be5d2c6384e7986e0", - "0x92a4f64d98e7e633271bdafb1eb88474013b5ed2c65137c243729df0d79ccdc6b210118ed3587ad00d3f0f757400e47b", - "0xaf31031fcc867a53760216cc1f467901aeaa3b28438fb3ec90d6a1c8a46590062c40fac939bc3c7e7a7deff8f83c262f", - "0x94306afe09f20d5de9ea26f37f5fc8df1e29b3a6963caa94df031efd428545493d53e0d8d7af12ee525e2e21cba23324", - "0xab6285371b981d5443ecea699af9da916f9320b3ed0a11c15503f3b10eada3ff5dc95d24a54f5aaab97d3312de5b985b", - "0x8e9735364ae128f790dfcbedcfe0e11b991602dce0c90ab3cfd4aac74526c30a798fcb51a8ebcc5528d88c724e9c2b02", - "0x89a3c12bcc68129b14fdc1d9b5db8d26d11c6c9467f5bff7c40abb8ec23b193746697421ea4400d5ebe53bb3fbfe59d9", - "0x8368e44144947f9ecfa5d126f4a57bb1d3728fe3c5d3bf83310174d638a10cea02ae79fca91d5489ecc9fa679feab49c", - "0xa0474ff532e1a6a3dc8f16ae27e77d6ab72b62535ba0d3ed09da5c692c6fd34424141cd68470922e1e147fb7f7479d5e", - "0xb9ae0e47fa8d999135f78c733cdcad786b96087a340f86e4cc2bdf019b07fd4a76f9b4b041eb397f61bda20c31d27838", - "0xa7262ca18a7179924d28161d64e6b6cec5da35b7eaf695642dbc127a7bf4a52bffad82b8d3fcd010b308dd72eb567f26", - "0xa23ecfac8a3f978f9ca8810458973f528846de6bb92fa6422b9547d55d29d7db7d8bdc5181e9ee2257a458466f714449", - "0xb04c32403400f82677d831314956acd3cb507520ff14d856cf8ec4fab37a4428a5d24ecfabfd2c6086e4ea6d26b005e5", - "0x9669b2725cd5965305c6ea48331e693086f4c1c4ca7dec26bc6290e9a8e70f9f0bedca6e36985c39ea35b412abc7f4b5", - "0xa6f68cecace45317a758658463c5fc1f005283d8c3d3de9364e7dea453007d8d4bc849a21205d61ef81019e0d25858fa", - "0x8ee19ccc1c83b2c4d7c7b712bb370c129201bfb340c5b49d991207c995f870de2d0efaa88e05bc9eac567c4c36e20175", - "0x8a530ece1992d1de92c4e845e71a1ab24e53a8a0679aa5bdeefc60fd890ca3cee2121f66c6f4b29c437440e7644e65d0", - "0x924338d7f356da9b8477b3aeaad6f754a8d8f6a791d70c3ff23c2a6d4488efde9b9fc351319f3ea1f545dd11cd23ab76", - "0x8eb76f86e057cfe9f655ba29bac89cc97db07f0487c86e7b41555b5549897bd3d042cd2ede35e312cbea357df622c6c2", - "0xa2c0da965489d15ced574f5e27cd4781a1dce8fa4f17762a25fef1320096b9eddd92a007d58a194ef57def3aaf4e925b", - "0xa3fc89753e8896d796859c9e5a00d184be7d37c4d5741ae8a60cae9a7a30c5d840325d6479701e1f61e37065fce81870", - "0x8b2f90cdb3add567b94f4c7fc89a8a57a0f06877639c33df2697f7c39e52c1869aadc98a2f8b85a58fbb02bb1bc1a441", - "0xaeb2c22d9186725ea40d3a4bf551482bddeef42c0ad33801e35361d3695769429449c2a13955cccab55778d3ff29b664", - "0x80bce007abd8ebe2238d465a312c2d125d1a695184b93108d728075595c7716786f9188e90ae37fea89009d087e71b07", - "0x86f5df2b83383c737bb6db4e435f496ebfd56b51300612c402bea9ac2f439ee7e98cbc2655d31646472ef983aa6ccbbe", - "0x880e8a19af5ad76f14cdf94396b8dacf061e02eeaba02d4c29ddf0d07c6d2a737c987d69ea2eee52f0db5a4dec318932", - "0x8b82368968f9b5bb175c95862ad403beee68d199a20d5dd618395daf11ff0c2e1fbf3a31c23d3e582556276b44e70b99", - "0x94a062abbdc5ba740077fb9de722ad2ccf3f6ffc8b4a9dfbb0bf2ff789bd529e7b9d8da520d0342af91808fc00431638", - "0x890b4ee1e9837a4c215616819dadbd3c6ed7586ad391498012a54d735c6df0b72c2dc3969d1b24cf6fe822f37f9c10e7", - "0xa7dfcf43c9c22fd22f47db490e8f0b8f42597a5b4ae3e7bc4a9b12252b32f89412a2aed946eec19b133cee89b4a70322", - "0xacbd9e85b9d9c3b068220f893d7b6368984f6cdb1cd06a784cc9571f0c632775ef890dbd51371e8701894cbf667d04f2", - "0xa9b1f84f053ef6f41c59b1758836a82d53932cc4b8ee9c2cafe705150e1c717e3f5c15fc21a2532c071e9dd9bccb4dac", - "0xb2c63345748a28d04680e3e451d1f7d430bc8ff2031b6bd4237a1f55dfadaec20d1854ac158cd6a1466dae525c1b9b06", - "0xa23e7b2e5b8f3e3b0e350e1a461708be9c1434d49fe2e51473e2e360bb0be140a96f8ddac99e3b804557cc25d3e44776", - "0xa4c4729a38f5f32f155ca4d1994b61802ee418b276486e2dcd681fec13316f3b6d4a8e76eb9f48e2df0339543b11326c", - "0x93be67dbdec2655edfe40dcdcc0a7e761b7259a9d909ebb12fd7c9a5d4efa10de065d2eb049660ed01ede2f26388d43e", - "0x932480849f97e32fb14d4a69af4073c377e949af7293951b3ca371a306d9e2096157f51c8e5036a44eb73c7c842c5aa9", - "0x8b5e79ddafd675ff88d8f65176321a08183429d42d7fc1e7cc3cfccdef0dc5824ee40f279a05edbf4d50418a4cab2126", - "0x962bd6fcf7c7f2a9c569d584658a735bd16440de2ffae236c68ccbf2ddc5e13836efb163690062537d52f7d8bbb24222", - "0xaf80793655c0b3ec3029673c50a7f212d297f9f80d7d1c7cb1409d292f3bd7dbb8b24581017d9f3964e3432f46e79ca1", - "0x94c8cf3c737c102e9e91216752c82b17e4e42074e08ce44e701c2f8ac7c08902b911cabf38c4c5bd41400eeb1fc97acb", - "0x8708ea7af8c86b2a1964edf64a9e9c56c7febffa742c3ff2e3088a61d3ccd63e135811212878ba7ad8a819e1859f4e95", - "0xab8e726d468417c168c892c10c7e2297e50c67e4283e5b48c3f3b014981ec482e211374f779faa0c1ead906f5dd4114d", - "0xa93911e672aa3d8dd686280cf062f128bd8eefc058fbaea52cc0a9bb255fda84e65ea546f662fc75fee4c5b24bdc61fd", - "0x8aae6d9289d8adf0f81e7990cc79cb704d0a975f03b9ec02be66089d62954fd9a8b005c5ba8179cede366d25ccf40869", - "0x91e44ca55de8ad3ab42816504813cd9ed9c6d64abf6373e8891f909cb49c8a951ee823cd1f947058d542f0bf6290a11c", - "0xa377f97e075b66e740b8476f085d50ce8ac21f206802384e2e072f6b9700a5f9cf0e6f2236307775c0e0d6ae8459d864", - "0x953c08d9f2a9d6ccb22cab906efda69ec1c228aa5c2ab93822b6f71c007fa3bced68c6a70ac605c6145e4af770b60de0", - "0x86d8dcf5a9ba81cf6a3149b2fff96e36639767e9de461bbd3ccc870634e8db331b98a888d7e8d3d70b6ed241d8ce54da", - "0x88db73952866ec07c49b484c6b18de70d439e67d971c1b436684d489253cb96d793cc4d9a4362b51dffce837dbd03bf6", - "0x970b7aa9070334b0649bea1f0b4e53fded64665f87e055e3527e0e567cb57a0e97d369aa16a005155cb69000073d7695", - "0x928c8aaf72b3f51e38c866ab457f75cbd7131b676817a3c6d522fb8f876b01a9ab3a84238eaadaa0a095ccd6c1ac060b", - "0x9561e78d16061b5361ba0be11387c3f6029415f83bcc8477b8729e88c55f4bfe74b59c1b24bec0eebd9779cdfcfbc96c", - "0xaef133788d1e04ac64f573f3ffab473209dfdcaf2c675fddcff83724d17b91d6340830409b391a94405d6ade005cd01b", - "0xb8ad4ab0a1ad6383e4cb12d479cde732f202687ebf886184507371ac277446b3bd1648c49c89343920e5d57fa6b255c3", - "0xa8d00257e331f342b79b3d25b74d300b070326b358f690edbaad5e325293d8b55078605a43ecd9dfd27206013db4c286", - "0xaa71abee2720052cce7a5a0c3968e64c2c540cc852dfe08b742fefe005dbfd10397f66386744c9bfbbaa40779c2ae190", - "0x80c6680857b88afd3ae8801677720100f0fdcb9e49c82f74b8ca5a27aef34e6df03462cf9ef5f80169c72da15be567b2", - "0x8c2f2865e132869fca5232ba5e6844ac0540a53a66971ad54ff91f186e011e5412450df9099fbe0b987be443867dfdb6", - "0x89cf681e0506baaa528043a15ab3bae09b8817853e889f0b3de138992e52612fa3e50d54b3686cbca6428a7644f58208", - "0x89ddf69b72b9ddf7d535f742bd3c942000061a5a54987f2ccc7a09e035be978cb32f653df9568526c5857a5df4321f59", - "0x9908a3288b9a9972c3f3f0d5923d9840756b74f31ae0b24ef2188465efaa5247b1ed13b774991bbe4c065c7e71b487ea", - "0x9454ea9a664390fb1ba79fbb5c0cc765d8ccd32a02d946a14353290fa2a1ba911605ff2e302c340e9ed6fbe8543ee6a9", - "0xaa4f4d9ef843ca3ba334d73af634a0ee800b3393f8f7701cd152274f4296eb79d63869d452b5e83976eca246203d6f03", - "0x8fce1e2e59dfc4fb46f3741d27772579fbf2f48acf1a38c49b0e5dae7d35f2624af3a46a48b89bd835b7d452ab0cec80", - "0x810ec0e58504ed556e788e23067296a8e4b4ef31257d508f05e5245bfe6d2c2f658fca8c81538c6c9ea6ed05a8f249a9", - "0xb6667bad0a7d49cd2dc60af85e373fdaac2af0d34fdee51a9fbc1fe8b77470c162a04da38228fe68b7d5247d43026734", - "0x8982971d57bdf35e0f34e867fecbe0c140d94101484ef4ea01b796633beba184f980c3ced28b24ff42de1dc504dbc854", - "0x86d8d1f3edef9e61058a58d966169a05f07fed0d93bd4f4a7cfca5a872b2aad0d1a78f8ec7784828e5813c8da577469c", - "0xb491624c3d5e517c9019258db6284d7533778e44b1a0060dec5f655a7b79057141079115f5cb1d8d97a90af33cd7563e", - "0x856e1cd4f9ab7cf323f5988bb5d272857d2fa90527f800362569a39defd93e37be2a60c11f498c482654f55560356f7c", - "0xa08884d0e642c479fc8e5a9837d1babbe63f3165c02a57b19d0547fa1fdc18ee382ea82a86cfd3135dec8f2aff793f53", - "0xb1a4de5ea703fa5ac8a70ec515bc65203a9415f6da109b67fa32843a39d7fa6232c9c13920d78c0f16e99fa5f6a27e83", - "0x931a2ee3220ac7888157c426d1b33b8a56f8879fecf1461af4cd6c85f94e193bd6ae6f8dc3946fc689e42bee213f0027", - "0xa844a78e65ea6f75bb55a5db1e78b88896caa1d54b624f218eeb302397dc98a084a2ff4b964acd0650667160928ceea4", - "0xb9c214280a15b423654a36b11646c928fb42ed2a692aedc01441c67522760df29c6ae7bbcb9237938a823188ad4d83f4", - "0xa19575f9bbdfccf970bb3754818e49c709d1bf0af015541182fc7203f7aab51cad31544072d52c0234a3b649d03d9a52", - "0x8cd1127b7485ea7f349e2c89a4b78fab3e5fabe5a95ff0cee10a3f4fd48940e431ca5e526f6342f7da93e32e8eaa1448", - "0x9906abc725e445092dd7dd0aef90f989e3c78aee96f3c0a67ccb62fb2a51363c71d783490fa5fdda0ff9ea69f5b9233b", - "0x8996df92e014c226e5ac724075c19d19a9204b2e086ed5e75a9bfa1f8391c4c77fd5c0b85a29f28b302a4af29d38735e", - "0x90225c9490b39d151a80a9f4d9a7f2595961c44779a54d5e195ec95096f77e30157c6c629cb1c36d995f6c3ee129ad20", - "0x85925b1dfe3884ae3a0e993b67b6c49685deeab6cf0d9997309961b7f727cd6133797bf04d21ef7b620d1d6392450b64", - "0x88a6c518e577a820d83f87e9d5f236f43d262756b1bae0fde72af241fcc192417ca9724428d17a3f9dd813712a772cac", - "0x8f501fd5634fddd00a8411c0e9c6947bab0dded26339821bc3543a64c519d9575c3129f6619c6079d5e95237c11cfeac", - "0xaf2b42945d7c81bc422a1bcdeb80027a1a243f89c65c173294d6c92e4cb3cd650740cac17408e7ba1a34d37d865b9bc5", - "0xabfa5e76f1112602ddf152aceaa9f588beda3aba1115d0823d6a6b074d28389fd4c8178e8a845262200b9af024a33a88", - "0x9732a0e3efcef5ad4d43811bcaffaa1418c791d3fd6ca4489d6cbbb7c44b55156d218f0fe86f2ec96ac306fefab2e694", - "0x8837a6c4e60839ffb0b59e94b37d81bf1ea392d44cc81717c1c9104468d31fb5fc3c35b1efd9991b5e7e9819c66a7221", - "0xb6545fd0b455748ac3482e0ead3b0157563cea7bf6bdd5ae2af7afe1ade921e5ba80466885ba73a89657a735c92658a2", - "0xb72fc49fd3be541bc26cb968ba4eb3078ce7c71fe0ac06340f7ac25c0befb86af03c4cf8f31c857f9e5d946401e86bb0", - "0x929f424548e29c3b47fbbd59ec00d17b00ee1c4f6b966c1fa7e0f8528d52078278f2852da976b8931fe813b0c3b71ac9", - "0xb37861ba981001aa6192cff06c13f041410aa60f965ea03dd48068b4295d61d2fa276c3f477f985f50189e33308c1876", - "0xa73c7cdffd646cffb255d2519d8e08dd8d9a9eca0610211177e259230b8f8c7ec8727015853197a0f11eec8b59d4f2bc", - "0x8da1260ce51220ad107c3127e871715bd738639cd90824d1c9f5b6181304f363b8bdbdb42c21e4e360cbdee496b573a9", - "0xaac6bbc35bce8b54820ef8d7219a4092c49aa5d4fbb187968cb91ac04bc44fa119766f8c630a727ba184cad19278d9c8", - "0xb964de0bd31847ada13dc3f6e1bdc679f421e262c03353e39f0ef1df720ba05e6d806dba15b6e10df559519ca125fc39", - "0xa62e4336b61f85eaa415f57e21cebc7d54c68f6febab02de76bc04a69658ab1d2f7cf0104da79448e32e2b7c92b684c8", - "0x897c6ca595bb2884b643ce8e69078431979d7e6e1b2dcc6effaf5a62fc906db6466f85020bf5930597adbd99e2ff90d3", - "0x932956e0ba09f6499f1ed231732a444b0adf17080237d9345d06d4262fe8a5fb0f704c919513ed42473751069c57dafe", - "0xa24b9cb4ea9c2203a95b0056bb95342c4fa0d91bcc25595fea0161e7d6f45595f7ea171e0ac1bbde13a6d8ca6ad10bf5", - "0xa7714728bc3318f6ac005e350de94f59495ef3972b328c673c5e608fa9059be3277b48f03a5a9634c3d03397af7d089f", - "0xb98732aec7a0a9a7998ba51e2b76e5232379482d0047f4876cd39918119776ae2683590f7fe5e44d12b3b3efdd916e8a", - "0x87700c3fe20cad8fa3041976c87ee761941d323f2d64a9818f20fcdf0259f796a11e55cdee31446bd19307cbe8becf09", - "0xa37cd03fd348694b2ea5cf081696d12dc4ae108da8d48695bf74c921b90612d18c1aa71b1071bbcc02829e05ba1363ab", - "0x830e4e7ac24fb3f64294e5c64563ab5708ebf0e133540b35b985390d68c420a6d680d779fc87245bb1f5c58e59c5ff39", - "0xb5922242a82565753dd2c1438008462d531f820af1b565756d4d27a30e3406ecc503b1e5b628012ea0329fd75561dd7b", - "0x91068438d2bfbb0666824d3cc2be488f2eaf3a8a9f21805838f9f2d582ca6bcb103b2f0f313b57bc86f87704aad7c7d1", - "0xa9a2133fe55e99114e526904f5fb3e2e141f31963562887a9fe6a262496dc405c756bf6dfdd6acb8853ef5a0a5146037", - "0x8e48e79f9eb1f8757b4c4afc4e3d6da4d368bb25b4d54e3a1f34f3af13d8037b0d465b29894f68272b79cc60fa676071", - "0x9378b90495b0e6468dce3102a97e9965a5d21fa4a6494d401888b8777bd58616b99d49177f2eb2796476ae84d20b67b7", - "0xb0aea247d7d7c7767519b87dd66f56c306d9eec88b0db8060bb97370099892957e2c950fa2e05f24f8ad097889cab087", - "0x89d0d48769ad81699d5b83f26ac49a29c3e835caee03469e93c11e5f4b8470eb02b52290bb2c37f06afb0746630803fb", - "0x94de42d8554583b24317d9ea283dad5849e2f124f659d0afa11414898ffdc4347a9c4ebe783dded21679337b58b67f4d", - "0xb76c3047eaecaf4a4e6fb6176c7f4a1d393fec3a360f4c711d6293a993aee39d5aea654fc6429c2e4d4955b12fea5c8e", - "0xa307fcef0915e3e3a27b94ddb9561e5d210a091714b73afbc0b3fa5e8140e8c3818f4914903975e8f78d0492d7784c25", - "0x95079c4a5008fb6ae0d653c00ad901a108df0b8c442a68492740eacd15048106b7c4cb5ee88bc6b1dc089987935bdba1", - "0xb65a354aa8e92d6ca2e11f4ed3c1ed011852bab8f0e5b8157a10c26db2748be688512423c11d582b3dc1da57b9d6a826", - "0xa32c2fc62c38eb19dea24b545d2537dfe596423f8ae530e562ba7eaac34139fb443d88f18f39d65d36a65ed1277973ef", - "0x81b83b37927e9a6a7c34cfe587dc9cfbd560db3ac57a8a88161fe4ae9a7c66843d32f6f568c927e2ff8f21d8b4299475", - "0x8b6993ef73c2021842060ec0424464412242aeb711da2c43d3985f9d15e4d936eb7a1b5098bfe892fcd3b6ba8bf42369", - "0x965535b46a18f94a1203fafa4dee5963742511ab77e98e471e03376847850357d543dc6ef2dbb765cbc1f03f66ebbc14", - "0xa9386ef496b4f96bd591847baf6dcf8520f7cb5aaf1713025ee894b40b10f243aef06c553376663488377fb8b1b0a022", - "0xa6bae4486fc16ec1f12817f2d47871c8bb61f5f1a2db5f828c6e2c06bca64b1ff7cf4c059a10d6bc2f561fc3a12aa38d", - "0xa2b6cda6a75fac16f324935cc1820bfdf013ae02c209802befecac0288d90263a7f84762dfb7c9aa1351415c03288714", - "0xaac87216619a8c50b5d54432ed5681b1cbb2c7084f33e9a91889bfbb94fd18c8071b79ebdb403ad81fea495bc1e37dcc", - "0x8bb3b3a7ceca82e4268ab52c00322d5d0822427e43c1d8b88b2f43c3dfae7100f6a29832d16454e093579cbaa1074062", - "0xa2363b4506b1464391a194412a73d47d0cd4ea1ffa541cf8b936c97a46bfeaebd1fec409c6aa2109d277bfae0ea7f0fb", - "0xb56911be2bbf1e564715191a526c2ae73bb6e85c45e3dc22bd9dd87cde620de93875c48b11e02ea66eebb68f533f353e", - "0x81609eacf4b2e78a9d7f469e0882ad24c86ad98dd18f466d321aa32a762171cfc334dcc049962ef5e63248ef48244229", - "0x866b26d3dbab7837edec84217c85653c6abaa617e0ba2657d67757fd1c7dfc0c7f83f6198fb85a49b402108d6fedeea6", - "0x9771f5796d5d47d22100c7ff7d191795677d53796f4a1e1aada949b372ec12decb6c49e28f2662e729d44f0e09eac063", - "0xa9fdfbfbe114c7f93806b988c50f8ae4e13a4d433f2e40c72b81d0ed7fe879db5e89216a0b0c8392a6d9d54f57760ecc", - "0x965336222244229fac41336464c36dac8700d5289c0aba78016db76e436289a0797af8c96d52583618f8c6dbe7b3562d", - "0x99719ac482b72d54fa515395847e9a65b733da84f7d10a0be82f34afc20159d64411aacca15041726251fd90ae06a9f4", - "0xab96b7ac88842ad0ab61f7550b7b4697d6a3b651cfa3c10ad404e7505c742e2c1364bbfd08ad0039ca3b81ffa9d6a6e5", - "0xae96088cf12f76140888582f6f6404b6f2666c048950166e37bbe46c1398fec343fcacd3e8f332f7afa222ca13fbdb87", - "0xb5b5c1ad493b2e72ce8ba698351f596cb85841f7f7055e31325cadbb4fec3e8045b335643190d6b97c3049d10551764c", - "0x85f066c7ffd2bfc4519f42f0778ce0e46195466768322a22673a073ebb66cd77c7b8b3a14157845cdb369d3f40911421", - "0x99f4f10397cb7ff47a2d9d2f29021d1ca96f0da01f8afd76f72457cba6e6376f925fcee28ce77475b90c9466042ac414", - "0x85116606b18f6e5404e9176570bf6d7a9d86116e5a29721a1b20d6b28a733886e2085a7563cbff45d1f11bf3d552ea12", - "0xa17d81b236fb138ed820d335dde2640ac3a44cccb5f11fc6bea5fe3132c4a9247b874e75fba55bdf8093f0f56310a999", - "0x8a16a5cfe10c5dbecb4fd9f4b0c370162071f88198e016111937199b87d006d1b24f3f412d853d7c6541e1c68076b70a", - "0x8cb83fd2b1afbad7c454430fb9dbf6530230b782c7dfb01443c2c16563e833c5b230f4c4268dc37a55a681a5f0bef420", - "0xb8851a8dd6a3a17619e7c84b18f29ac9680b456c03e8c8489376e6de9a22ea75d1730787ca5d269af44eeae47f87bc24", - "0xa8f990c9290456e849ae4cc0c320580fcfd50263af8945d01b00baddf801aa0a7bef2ac119d4d1b4be6290615c781656", - "0xb0fa1c28c8c67ff87427691047c362aa35de0be9b0121d83b116b23170ad2b712a0b5bdf6a57a25c59201ba165d5f0d6", - "0xafcd2f5e66a277cef775b636abb598ee9d7e3bc1b48b521da787dc561cea8d7ad963f593c3ac6f23a66a27c15876b775", - "0x92888863568ef01b40d51f467e8364cb1b09808238644bbee5ed118b392475e90c6a1e03a0ef826dff5ada8d10be716c", - "0xa8ddad388f2dc94294371d0ebbce02016c463a65bcf3a5366419a7a910d3d24748fb5716ddd81cbab44a2362ee3c077e", - "0x8b8ef4f818ca3de1683064ea7e968edc8d9fe2675b8bb2ae637a5784a20cd909d18eed45140189eb9f080c53c06376fd", - "0xa52d9c49db4819cf6280c220a6cd306a5851b771de3032f28c7f8750c20e80cbfda57323a55a8c03085b41f4f236b5ba", - "0xb01fbfa0f80ef574a1d6733899002a8672cc309e1014fec8e81ea1e96a7be9c247a570f825b7862e814e1f006a8227ac", - "0xb07e163eb0f96a51d74aa8a7fab5d23e44e37b1b1027ae9c4155280d8d159f0cdeecd3258c098a7358c5bf2fcf1eb7e2", - "0x80c4512a5bb5e8255488fed7b7e297988732473f0ccc1192cab716a88d035e23cc374a937fca7da87e18048ab026d9f7", - "0xb3e343b13c1d4c98b7706edbf362eab12b1fa87510d5cf168e510844b24c8a9624f1e7e0babf455c6d425741c23e1ca6", - "0x83e4b53953ef683c512756b3fea37756b3c562c88a15cddd902eeecf0de82d0345fb05feeba511e8a6de91aa1f722ef7", - "0x922512dd5ce444df62fded2c53a73385570804e7305cde401116c06dff5ec7812b776b8cccdfdafe422f1ba53b2b56f5", - "0x8d1f7feee880abfe9f09708ccf72f376013b2910886edcceb76018759b88b95cab9c0e8f176faf042729b405a10242f5", - "0xabb7cd087d0cea2cdbb47cdf9be2c6a0c6ec113e1ad5fac083f66a027697d477ec69f46b9aff40c239ad9959b9854e11", - "0xb10592443daa8708f8c882da795da07465efb9829305170bc3bdd781cb6651b503d3b21eca027486d413f1748f40f068", - "0xb14dcb895ab22335373d2b736628c1ed0e815072fd3844867ae24638aec60f8591c6885869ad0bfe509fa3fa3101a5f0", - "0x89631708996651bba6b2113626a2fe1ef0f2ea2f21857b2a1e5544ad31e8a53e755b6d611546ebbba4b2213acde65e72", - "0x82e9436700fcc5b842ac2f0482de4248ec9d1f206db3dd36917c00c7749bda257fedaec513d8a9ef3765057bf5aff25e", - "0xb1c2b26d93658451fb4e9cfcd77209dbfea909b2212c000fcc576ef29b808061c9f58827682cfa09e357c1722c3215b1", - "0x8be32f59768777a785d8b257f941215f37db8912183aef4a39a856b88cc680ae7124789c58cb3c6c6f06a951dc96a1ce", - "0x8cb60a3d0c9a1efb89f89f78e6f0e4bcf5eabeae6cb215e98cd7f9eb58699ed70dabed73a8b95daf32a5e4bf0d411d3f", - "0x8ec7156d6b672e631ebd88467f40caa9ba5411ab727602f3146b468bc00ae54fe44b3228572670215a0dbd59feb66e2d", - "0x97b7162101d740aedc894bd5f74b8cfa7ca7e7fe8363b05491c15e8cd54f21b0b09eb41f756b9089c379ea0ab189c468", - "0x8524c9de6be47cb6808df761ed03c505932ba715e757dfb3c97b6deb462433d98953ee6cbc7a576b6837e68eb16d3188", - "0xb024c8fc3fa4f602ab73448418548d9896200065a95e8a001f6c8d4cc3f53f18ec8b85524377fd93e2d2a18eb4c48b57", - "0xb344dc93d3057465592460b7f35dc015f4f8025fbcb44a645dcc3dfb37044d5681d8abd81bd544272dc57cd50048f29a", - "0xa7b270b94d9870f8afec3bf2ed58afb76f4ea576a2175502630d0d3f92f9152c1ab0c019f175f566eed29713dd97712d", - "0xb86dd953c40d4f5574bc7489323d71e9798f7c6f2dff8d41f6295655c5a275179ffb4bb8d2408b88226c98583a7c26b1", - "0xb73074289a5b08aa695de03ce2f5b106107c6cf2bee8061e3195056e799b0bd8b4172deff7f413ce8e477391ee6294cd", - "0x98b801a58ac7e083da541ba058c64b00ba709d4d0ba1683e5d83dfb80a29272fc2a33a18f32351b103b227abd5123da1", - "0xa7cf232c6ec6b9dfb32d729b9d4216688f6d2b6e68053ddfb293ebd5774218c69133baaccec7ba3da9b221af619c2ed1", - "0x8cc1d33ffedcea05f3c593e5b63dbfebdf26d05a5719cbf642997be929336b92457fd9df0d6be6c063918ada8fa2d322", - "0x8d273497dd9f822984f1d8dffd471cc703d03c342f022b2bb24492209a3889f010c4f7ec124f9fb9f884a1a11f84a414", - "0xb62cd013944d8d9d72fbe54897a94e804c93eb84a24beb0880cd98fd5d48fccf5dedf5076abcb1b857adcc986b729cb1", - "0xa1bc703a67ee709f7776b2871f2a88d8574c9e2910690c9242c162ad926ef2263d5260f5c19015ddd5ee1c8ad1a444ae", - "0x87de434e8ab5b1d067188cb9c12ed936c26ddb0ee76c4c9cee9bd1ea916e411a354bfab2ce77ed8c8ab5d8c62038f933", - "0xab128e9de30bad31dc2eaad851da1e39741ea61bd203b48e5671e37f7b4e3db86687574d3cea1f561bbea84f68cd17c2", - "0xb54576c9c4bc3b43270b83b89eb75cb7e89057c99e14021ca42237dce393dc6a8614c5af5c2f69160001b2ecbb407c9f", - "0x93adf38f161ea886f41e4af8e42c69c53a51074db9ecd7b7e4e36c858426237167aa49b79737625c9f9826dfd22f39ed", - "0xa6907c8dc4073d3d4d40df8302c1637c15f9197aad8511dc95c210f6a60b06f3aab2622b826d16596af27e42f2c9d5b2", - "0xa8b0c4a3a5d3dd5b6a85802039f48fc80350f6f0be2e55bdf75e3197a22f6547ff4a7dce38ef3667006128141364625b", - "0x8a5f4c17c729509309b2ac7e0dbadfbf0baabbcfb1fab02f91d055238faa3b66aae850ac9b8d7b7245f0a26bc5253c99", - "0x8bfc5d594700287da2a85a78630c616af8e555cbd7864ea604ba38eb75742fabf6aca12ed99a2439e2e046d8f048a29d", - "0xb0f91b7546613341cd95ea112e04b0963fbf7795f118c393fbdc37e37dc25244d10d987c13d6fa6eff3c4721fd0a328c", - "0xa70b6fdc66ce4c2e7f1d830b7765b7f0640ceb7306cc85778488964cbcc336ac378b74b9c4ec27358f27378380b3dec1", - "0x87387cd6b643721aac8e1a8533c27602d9632168d3a3532059163dc5e4613838bb4f07803e6852b23c58f7103d134f92", - "0x888722a5a56f5b6b00daba53991ab6fccc32a029a7f1748f779b57732285e56e09ecdb7d4848abb3dbf3e167cf8033c7", - "0xb7f5b9ffa8ba66f54cac387c197058eb9025cb3761550c78429db95f9e1e3b49c208ce86b6126c162a62939e1080895a", - "0xa53f38c068233b584211157c66d9d2452c811bcd340d8cfafd32b070c326169306975e558745d63e1617f4b4528a2399", - "0xb1c3e9b0f19993f973f038bc45be6a834b1cd3d56f112c857711c8e6c30303eeb0b205bd5dfe75e46b1f4d4bbb68fabb", - "0xa81fc28620e640ccb57dedd40c79b73b0c51565dc61097527b2341bbaa3e1c9ccf20f9d8da1c16704e881b24df0b7335", - "0x910a7f4960a0ec2aae66cbe2ac98f43646b017de84ef3d486c19b7809aa16813400bc2dccfc80e09c63422a9d4d88f56", - "0xa463868e3a8c2d2a7c49850be2740e01c7892c83063d381f405282b4c521cb6e3317361abaa92042c38bb68695c10bb9", - "0x991957100ea0f66cd4ebd23d9f6bc7aa62220f6ecb71ac947cbffc6f36f7891173670977bc58a6f57b9a1e8766100c2c", - "0x961dcbd2e6cb94574a33fd48b5d87e0623411574666d5d37f4ff7dc587980e2644cf056e002746088c3c3b0ee7044510", - "0xa27cdb374cdbff217370015d78c7f5c4674ec9948339475cc80a5805979a4c6a2614b6904f943374e96bb838549ea517", - "0xa567bd4a59f9df9f5f30a70cd9f6cea7dc0e19b7fca37fef5846aeb1697dcf7925a4735367be0828f2ded5007f711f03", - "0x823937a900e3b8705b657d40470357d91eeb339324f0fed58695ad72dda7c64f2a6b7bb7ae4a20cd1b2016cb9edbdd1a", - "0xb07f2248802ba7dce15b2698a60a4896323d37ecae6666a71cdf85878094bbd4e9c0e4733bd8bc6e9c850e57727e1d86", - "0xadfcdea69c5608f02630db045e5679f9f0768fbfa9c8e97bc9cf9cafe1f747d3813e7bb1adc6085cd57102afd71db133", - "0x908153d3eb2eb2b93c15aa606531b08981bcfc8b76684c2483bf869f335f9d8773a9aa3986ee54d9392856daaf82b684", - "0x8fbb2acf533e7d6e96e9b68e77f7a1df2ea6c652cd8862b946c93c084436d7349ef4a0c453197a9769e986322e9174b5", - "0xb83cf4ddee6140c9df0a08a39bfda79c0d55516fd799c1c24b59397b87a33ea5a0885b2998dadc354cb6f65a4bd946a5", - "0x957a52cb24f19106d80d4115a8a0843d047d157c4a8535775593c1dba9be24318dd434bf43a82aa7755897f895d2ed15", - "0xad93dbc2c055f9d7e42717391cfae64962a78bddbb9fd102a05cea520654d4a9cb6634234d3a188693c87c5b4c78959e", - "0x8dc4b8e49de9b05c33d2a98973e223c01ed5745eeaada3a4c0e474cc22430644a53a60c3d6efb1212ca298c4331763f7", - "0x948b0172df27db83e70fbfdc896ed82696876ac4c51842d270d9ce1e7f1fcc9487d781eab97f40074861401b809dd7a0", - "0xace190f75cc102a79412fceebc013bda8cf329798db4b4dba658e63228ca7f141bf0849d30139ffdededf98986f3066e", - "0x8f968dd6d7e06008a1374743b965a6204c11a610ad92705e8dbe6df3c58baf36b47c5d9988e4a417c80ffd5e7725da7f", - "0xb8ba0d5b36cc41f6839543d43166a08bf512f7b56040891ab80efefc774db28c833ecd444a703c75547fa1404fa1ec22", - "0xa29944dd0e5c861eb37c744c429a0dce596cdb2e5b0b2d2438a259a0faaf3d261faee1434bd28ebb2e6adab59ff3951d", - "0x85c70422fde0ac6e7a0574512eff2a19df8049757bf78b5a7d7870848626850f17e8b0a5661e5292f3df0f613886306e", - "0xa5ff5c3ca2c70b88d15633f9c38d2e065bcfb0e447adca33301a0d4f05b761049c8f795444f11e39357fe6bc0d7f1880", - "0xa2167cdb114d7707f1010e0be9cad488fe56cef65941c35a5878a100adbe522a8abdf7eab7bc689b8727fafb174032c2", - "0xad3f526ef9ed367b2a25c95453135510472581a758760d47eb9f9b57b04f8c061152e5a792584d6ca7124dfeb7e21703", - "0x86443033ece13fd386485115765aa95673be72b0543fac2138e1488d22419591176423213ec06e5e4549a025eb6aafd8", - "0x887e4ccd58603e6c9cc99bd2740bb1db2fc4127e8d3ec9cf41bcfa3589b0fe1931ed2a6140ae1199d323d2870882ef6b", - "0xb701f7d7637662ea7024d31e94245a5f745c7ca889f4f7a8362537df82b0164eae83da5a107a21c0ca889926aa50de49", - "0xab6bc11d6049cc5945011d3973eb2dbd5a3d786b3824bc125786e734887254a6ed61fdc2a97ea34e6b37b13cd97eb781", - "0x9901a1f44122bf5aec7cea28e9405c33567eb118752edc60f3cf6c80642370f86557cbd76478d6b0ea33be92a22c746f", - "0xb9f453650996f14629642bef8fea66c90105c8523f3875d86694100f8022d4fff2915ac9f9b9efd6f425751b113d5623", - "0xa5bf9385a1c94c09ec326c49b6b603f2de987b2878faf0263ed109410536543095c07320f762fb6fe56ee72a082daed6", - "0xab003c86dd62c801cb16b727fbd1037aeacbec0f76e8abda4c6d87066cf0a33dc1c61721f2134c3b78975efe013cddb7", - "0x8dd8c580c288168f896fd7ffbcf5c8167a55d34f17b2c362d0ada0f33a08cc0568b37b01cf0cef1fd5e2c2e250fcdf7b", - "0xacfe675aca83a774d3f526ad461f0deeebfc73a15ab3d37b224f8740ac2d9df238373e6cd1f03ca78a9daa0a796c96f0", - "0xa45cf3242600fb9733dd5e0dda1994e8d37fc483885a34a76cc16bd245f6d9c8d15bef360ef59d0a2c3cd59114280b87", - "0xb64097145d97cdc8b7a84edd1da7e84f8aa44c3c2a4823e6e8485fc3a44d94cde7d7ce8bfb3da5d583386461ccb42afe", - "0xa10ec5859c274c0972ec39ac80e461c29995b35d03603dc97dc30ff826ef24c5e52d5dc9296319ffc672b9e1d57d7936", - "0x9702ee805b70a1bfac7318e8470667ee20739e3db3030bbcb9a08568e96c9c8d2af2cbeb26837c43e04997457c623486", - "0xacb3f5a663333d1b4d54dd76a23c6601fd4540e2b050ec2a7fbf0b850b6f592837155e6bee5ca942406643f98bb2ca83", - "0xa577b96723f64d2671f1253fca72df86ef3247006c92cedcfb26eba4b4f4ba71bfffe1d5eb87b0192378d0303177fdba", - "0x8c397ac56cb723df015d0ef202fe486d1acb42f8129d3e4b37125a7ff9e81aefb1e19f477d528be1e6b18e7bced27ba3", - "0xa7a6e6994324a80ee0a85e8e8cf818f5f8d16d299f21b2fca8a9f65231982584afe921e76723701abea873275ce0c15f", - "0x82c8ee7a39e49528efa60ce1cbcb3017904de6beaeb751c406b64a94aa73af72932e32b63c1d3fa22619498fc46de6bf", - "0xa1d0193ac8bdd44ffcd37092a5dcf6e775824e5dee4c0aea5bd46f2e25b312fe58e1e6b9dccf9dd14f240d7ced4fe071", - "0x82c48967f22b8aa1dc63dbda0f66ff2f40f3ca9a7b3e752e1a950dd7daadf9afd81ae1fe3d0e440259dccbc1520d4e22", - "0xa69d43e6f962b728d223f6d474a923dd13c04eb8858f7fdd2df2c25dd4d13a0a69e71132f450613e8e2d9a65938f31f5", - "0xa613b731fe0d23ebf376cb1f3707ab9b2d428d1ea3a95faca9988a1ff4fcbde0a077b38b5942590e594307acf88c9db8", - "0xa7d2f249ec666f59dc51f9c31db6168f33a94b17ab95123d4b19aa00dbe9e1cdf340dc6f64bffc6dabb11912e10edbba", - "0x8e64b8f99ada5f317c6e2fd64ac17c4d6e5314c82848efe1eb97a5a52e6bf08923360dcb44c05d3fa59a82119610a898", - "0x865d9512ec4a18ab31e4062b2ea6c43ef32c7c58d89bb0afdad9fe57dadaddd2150f78a0e85086454812855bf09f31ef", - "0xb2d23f01a0d182abcd6862ab6f4bf924ccaac399ec143fe2614908dddec102e2feb8555479bfb71ec3476cbdd71b1137", - "0xb50d176e628e06258b518be78c6dcbc3c9b2b4a1ed4ba10ee822b3ebfeaedc4fa69c61c1985e1bb20ea9f3d6df7a27e5", - "0x8174953f4023e31e39f1cc3bad674bf2f1605ec9fc053948bb60dbf2cabade885376f8c76f45b638c95fdb14f5bc562c", - "0x92b95a12d1fb1ec489943b3a2a1c8e3c8c6a30d0767125b87fb491f9d4f8de0629afa39fb5c8a84078b08bcc26e88c4c", - "0x93f4b80d76689d5936aff6cf195d579ff5328ccd0f04db42522a225f96b0bde2088706356675f185186548006940898e", - "0xa5f7f4577943741def19df611c2ad3d459c386a5e3c765eaa5a0cb6c192873675cccbe62845418dbe47d7a9822e4038b", - "0xb59bdb196d59928326572807b2ff2edfc93a28632542b270ed462567d32bc36cefc40300619aafe4cd1e91c38d6c9c30", - "0x90df4b921e13ca1e63e8a5c9968ff64bbcc5b829e3421d74bf7f678aa1dccc1db9ed9dfe5aff05539bcc5379dd59e575", - "0x837b0b6813249c456631b2f2fea9402a2303a454a114149bc35efb400813397366eabeb4477f2cfe037f722d78a5849a", - "0xab5b33ae561312d9791bcafc8faf6d65f2c4260f126f11ab5c20c7626d88f2c00177588ec62ca763a7ca44c6ed60eb0f", - "0xb0ed2e48cf650a4267c3da1378b8164cf6b774200a5898155716f85f7abda093a60b802ce549811644e5f075d2b26067", - "0x8d47a4e27f448773fa2d592f052bbdbdf30cbef152db6d8cbeb3d7b1a0dc0f2c820ed7572eacddcb51c19a8268426b33", - "0xa56ccd0961bf238ccd197e5bbf430d7c637ff6e01590febab3529776403682ee32d0a776c3dbc6581f60002dac86c38a", - "0x9163bbdbf468be88a391698ab1f40a919517beb6c780062d4bab3bf8fd42eed6546a8c743e249fd61c3c347ea60ee185", - "0x8d59f46606f063e68198457917004ae50ebb99cccb07755825782ddb25b96c3cf8973a6c5092c9db71a4b8ed075876af", - "0x8ebffeae4fef7a83d81f31a88589e05f885dd0c0b4360545b22a18369a3e6762f187ea6a173f25419e70031861936463", - "0x96013c6b47119e017c8bf473b3d643d0bea1cc12d84d412c2b9f6f55f22543a6e15ff7e818e9799af43984ca2ec3bfb3", - "0xaf46ef7696d9908fb343a6754de37e31cbb41dc1a4ab8203d2a2483d1cb0dd3183e5015d8048ff146ec34a6c3f2eae21", - "0xae047ec4584a962a7ae9359292c98f4d8e0916dd98a414e2e15429ff30ffadb3e0296282f0f7e257495e8ec4bc0e5328", - "0xa16de787896a056d31e3f174418aa3882c03c879a292246a43dafb81f8e0e05564f1cd3ecfa251cdb159f63777fc6346", - "0x97d1c4a94182ada88aa3cac95520711802cd3889e3e057e99a72a69589fd222b241d35a54b04f42503756ec3c1a3d701", - "0x86be4ebe8b92f5bfceba757e1e2eb649f9803c8cb10130b88b13caab6bc04dac4e26d538b7adef68413b047ab9031252", - "0x95d4c0b08caa283ffa9e307f78db15470fca5b577189a33bcdf14c36d4ae3f571d004c5aa1e69172a4068e72b7dc25d3", - "0x965b7053a1d14f9091de5df8bf033a07b9f8d39a6d66979ab5424bbfa32b803075afc2d74e71235a0f881bacb6704689", - "0xa93e72836e2efc704f87065dac0463ddd4b063eab125d834df583d8833873f575a0179781b72aeb2a35533a34a395481", - "0xa2997d7c377060d910654550316ea7374a0329fcf30e743d613e2ebaa15b1bc6c936c2529f5466ef0e60ff53aa2b709f", - "0xaf5259d4d08617d9be068d1b79a8209497972910938831a435487395512187691d0cb021bd57eff0f694f32efc1487ab", - "0xa78b8318838b1049f308200782c4409fc6c97ca5bb6af28996eb191027c8935b7a43a41961ec046e6c8539376c1aa293", - "0xa4a6a9ec652d1c95883d21d3767b13a7e1dee73be907dacad197cfee025755db3cc7a8fb9f40146912f8a3f4c2c49c14", - "0xa8a8ab62334a3c67793fa0691a0d2e80ac1681ce64a02df93b78e4a2f6fbf3af9b160d9ca6b4e835d58ed60d8ce627d1", - "0x980c32e492464a6f36ce12ed06541e3b2eb098934c0ebccdcc984cdbfee4a506d15afe1a30a84d642322c2987d9d51a6", - "0x8ea8c1adfd73747db98705e9fe1aec441484d2e4862b84817cdf1262fcce91f56cd26073d4dd23b04f836e0962428593", - "0xb0f20edb8552d2b08613cb574e9de1c4dce1eae55ba9ab05dd7f2ca3590a7496d63d55af88b3dff881e16d8bf9147037", - "0x915af4e9a28b12ea126668db7de6ff0c2cc9935b138022fadbb1f385f327fdc927388c945b93d252cb51803c242f7e1f", - "0xa553e08f67c61ecc5c8955f7251cfe18cde02e6170845e70db1761bc00f42a31cc10de26d4c904200166311f32a3e56a", - "0x99f4b066a805512e16addb0bcb08d76f017213ca6aa6afb5c2fc621805c4e123bbe0aa85eb5a0f89d3112635905093e0", - "0x9236c5b0f4d2e58033735d7bd5d53ccbe82c05aa290149286a16a318043ffedfdca9d2d07817601d4216fed50c1082f0", - "0x90a4c7898c58c9af83f94095f6afd5ca65664f16c0af4c8121407cf0864fdeb09958500b2bd0b78950aa9051e3480928", - "0xa589666688e6e7f8e4d99b84d21a1f9ebfe681fad346a237de20a11a2b210eb99c4d3e2f645b23a85c93bcccd51f63f8", - "0xa010849ed4df0e3a8eb61f7fd114d05a8669bfa36cb95d089bb1964ea8f5fa26be0cd10fcd9b38b259722c5a14ba3a1f", - "0xb21f974a10a2dfe9987370ef4b6af294cbe8f4bbe35ce9400d0538c5f71287498054d73606e26f93e2f19584aa18e285", - "0x81fea77bad05c3bfa8d5d8409b189fd5c759613cd69ddb19b2d46673d4df944b2c7293989f79580d229d20959c74b18f", - "0xac962b0819a03d2a2fa42c492f54c3d293c6d5ead403c50f7a1ccc2faad58beeb0dfe888a928e505fea9e09807e13a23", - "0xb78b913f2ad9622d20c175ed23f80f235b5336343b0353f82383fa6aab99aef77cb489df822bb168e56496c1854f623d", - "0x8c06abf72913ffcb6b59bb8201c00034b447011880733aa6b563acc423e90bdae19f2a7a286943b55488fc863d09269c", - "0xb34168972fcd90c78286bfc6078ce559e3c216d8d1885ecd5044bf9f23a4ad15bfc9830aabb4273472c43e2980048586", - "0x88350e0ffe9b5576dd0afabc6d8445d25b2b9a0945c71e6b9a5587649ac5d95cbd722db5ea1e65d3fb8230c794fda5fc", - "0xa3bec1fc362a33f38795158f1b869e9ee857a7f2e1acb92c6a7dcfffa64443a5b7f6dffb656452e7f855051ae849be3e", - "0xa21f64c49334720883e1243a27575648f53637a644c308ff24f5c26bfe65cc690a5e46b8e432171f31c4229aff4db416", - "0x85dcd8ebef8f7f44372912b4a3a0dfe66a56f16c3757a8ec01b71aa81eeda9f8e5082f92e3ae8cbf3c1eddf5e6ffed03", - "0xaf3c1a770f34f2acc504f38ffa7a18cc4b38f8f84f310cdf2d7346b18824ebc7c7663cc0e00b44cfb5494fe6081aff84", - "0xa5dc7c5989fb5cea87c2d878d8436d858458171b667ab5200dc2cafd8af2d9c2bfe2515b0c002cdc9c3e61e4cfe4b444", - "0xb136dcd4577ef3a3a8bc946cf2ec79d3fab301114ee2a692a6c219e252c9128db63fedebc6bd6695a8ae58e7d28501e8", - "0x91d3a1ba625632d59dc963ed54c0310d0447df3e1d9151299722d10f8b26161bb73e0020d408b748fa6fd1db51adabd3", - "0xb89f1a2497b04b3f1b018dc88b52133e1d7470f97f846965fbc934d34dbc8d38f2d8b07d218e62c609de33b61831cc9c", - "0x92fec43fc5af23fda5dfab78234f5ea7813d8de34f8ec269c5fa35dd916b9143ff0033d35e7a284c0ef4f41047e98fe4", - "0x8a0b89cd35ecf5b6db26c39705b416a4b950aafaf3b00a9d75f864955e9074aac03826ff9393456871107563eacc024a", - "0xb04db63ebce71161fd42bb878e89155bc9e887329e164481077c6a1db092477370a362810d291444f5463437e0ec5906", - "0x88ecd5275592f8b133928770e2273a0e0c23424d72b9e505130b0599ba28d1c11eceb2318a49dee054a8ba0971874357", - "0x8eb0271197fb9f1eeedaadd8eb603b8753ada11abf04ce90950034f51f756ed6ec6a6182a47e1f3ae51e3a1f3ecdf467", - "0x81cc996bc6b12ac56a1ae3add4483ae4f2e2284e9d304f5fa1723231d0e5b196813b6dbbc20b70f5d51fcbb65bf284bd", - "0x8e1d94ecca2928c4c68fbc13199b6281f8782c75c119b763e0eb122f81c35f8fd079d1bd76b498393371a08dac95dd1d", - "0xa92f98bc09f8a91fd165bb8d05e3b5ec50121d760b353d7e4ea23c0e04ff29614ad9028a4a16bdfe323f2af647e515ce", - "0x82e8dc99a14da065200699e458150dc6d49ec0e098bbd91ab8f1fc1767e8732f53855499c8f24da7b9dd681100633be0", - "0xa67b6cb4eeab4fe5f4ebdf5649b7d61bf5fbf7b6cd2d357fdf348ba32dbfa9d6830b1265ea76a1c666b266e30d119182", - "0xa64e3af1d0e600bde18d7f53a4e8d89d296eab4bcd9cc3a9f476c5b8425e6e8082066948fbf40689f626e27e4830edfd", - "0x8f66b59782cbccdb31cb1bb2d6385307633ba4db31c375c0a8424a497b2fdf309e7ec1c95490324b9a909bb43041998d", - "0xb93f4817eb1d91ac78eb650c110f7c29df40df47ed1d5d3209c3abe5cf59a5e7aee3d1cd232bcce77e157b1a9daa2557", - "0x864b6cd72029640fc041fd3efa71bb210edb40589a26981724b944192c3c2543352b4b757836a7b0b13bf830f22b8374", - "0x9064a0ac94f2f133e287b796363f6d27e9646a8b531cd9ac0eb45b99fa73f327238161a43f7c4fc914036d69abd1473f", - "0xa40e60d4aaf9f50f7bfebd0e714fcfeba64e0f7ccaa0f4829144a7efeaf15a7cda2d62d771a76f98a45cda9196b0522b" + "g1_lagrange": [ + "0xa0413c0dcafec6dbc9f47d66785cf1e8c981044f7d13cfe3e4fcbb71b5408dfde6312493cb3c1d30516cb3ca88c03654", + "0x8b997fb25730d661918371bb41f2a6e899cac23f04fc5365800b75433c0a953250e15e7a98fb5ca5cc56a8cd34c20c57", + "0x83302852db89424d5699f3f157e79e91dc1380f8d5895c5a772bb4ea3a5928e7c26c07db6775203ce33e62a114adaa99", + "0xa759c48b7e4a685e735c01e5aa6ef9c248705001f470f9ad856cd87806983e917a8742a3bd5ee27db8d76080269b7c83", + "0x967f8dc45ebc3be14c8705f43249a30ff48e96205fb02ae28daeab47b72eb3f45df0625928582aa1eb4368381c33e127", + "0xa418eb1e9fb84cb32b370610f56f3cb470706a40ac5a47c411c464299c45c91f25b63ae3fcd623172aa0f273c0526c13", + "0x8f44e3f0387293bc7931e978165abbaed08f53acd72a0a23ac85f6da0091196b886233bcee5b4a194db02f3d5a9b3f78", + "0x97173434b336be73c89412a6d70d416e170ea355bf1956c32d464090b107c090ef2d4e1a467a5632fbc332eeb679bf2d", + "0xa24052ad8d55ad04bc5d951f78e14213435681594110fd18173482609d5019105b8045182d53ffce4fc29fc8810516c1", + "0xb950768136b260277590b5bec3f56bbc2f7a8bc383d44ce8600e85bf8cf19f479898bcc999d96dfbd2001ede01d94949", + "0x92ab8077871037bd3b57b95cbb9fb10eb11efde9191690dcac655356986fd02841d8fdb25396faa0feadfe3f50baf56d", + "0xa79b096dff98038ac30f91112dd14b78f8ad428268af36d20c292e2b3b6d9ed4fb28480bb04e465071cc67d05786b6d1", + "0xb9ff71461328f370ce68bf591aa7fb13027044f42a575517f3319e2be4aa4843fa281e756d0aa5645428d6dfa857cef2", + "0x8d765808c00b3543ff182e2d159c38ae174b12d1314da88ea08e13bd9d1c37184cb515e6bf6420531b5d41767987d7ce", + "0xb8c9a837d20c3b53e6f578e4a257bb7ef8fc43178614ec2a154915b267ad2be135981d01ed2ee1b5fbd9d9bb27f0800a", + "0xa9773d92cf23f65f98ef68f6cf95c72b53d0683af2f9bf886bb9036e4a38184b1131b26fd24397910b494fbef856f3aa", + "0xb41ebe38962d112da4a01bf101cb248d808fbd50aaf749fc7c151cf332032eb3e3bdbd716db899724b734d392f26c412", + "0x90fbb030167fb47dcc13d604a726c0339418567c1d287d1d87423fa0cb92eec3455fbb46bcbe2e697144a2d3972142e4", + "0xb11d298bd167464b35fb923520d14832bd9ed50ed841bf6d7618424fd6f3699190af21759e351b89142d355952149da1", + "0x8bc36066f69dc89f7c4d1e58d67497675050c6aa002244cebd9fc957ec5e364c46bab4735ea3db02b73b3ca43c96e019", + "0xab7ab92c5d4d773068e485aa5831941ebd63db7118674ca38089635f3b4186833af2455a6fb9ed2b745df53b3ce96727", + "0xaf191ca3089892cb943cd97cf11a51f38e38bd9be50844a4e8da99f27e305e876f9ed4ab0628e8ae3939066b7d34a15f", + "0xa3204c1747feabc2c11339a542195e7cb6628fd3964f846e71e2e3f2d6bb379a5e51700682ea1844eba12756adb13216", + "0x903a29883846b7c50c15968b20e30c471aeac07b872c40a4d19eb1a42da18b649d5bbfde4b4cf6225d215a461b0deb6d", + "0x8e6e9c15ffbf1e16e5865a5fef7ed751dc81957a9757b535cb38b649e1098cda25d42381dc4f776778573cdf90c3e6e0", + "0xa8f6dd26100b512a8c96c52e00715c4b2cb9ac457f17aed8ffe1cf1ea524068fe5a1ddf218149845fc1417b789ecfc98", + "0xa5b0ffc819451ea639cfd1c18cbc9365cc79368d3b2e736c0ae54eba2f0801e6eb0ee14a5f373f4a70ca463bdb696c09", + "0x879f91ccd56a1b9736fbfd20d8747354da743fb121f0e308a0d298ff0d9344431890e41da66b5009af3f442c636b4f43", + "0x81bf3a2d9755e206b515a508ac4d1109bf933c282a46a4ae4a1b4cb4a94e1d23642fad6bd452428845afa155742ade7e", + "0x8de778d4742f945df40004964e165592f9c6b1946263adcdd5a88b00244bda46c7bb49098c8eb6b3d97a0dd46148a8ca", + "0xb7a57b21d13121907ee28c5c1f80ee2e3e83a3135a8101e933cf57171209a96173ff5037f5af606e9fd6d066de6ed693", + "0xb0877d1963fd9200414a38753dffd9f23a10eb3198912790d7eddbc9f6b477019d52ddd4ebdcb9f60818db076938a5a9", + "0x88da2d7a6611bc16adc55fc1c377480c828aba4496c645e3efe0e1a67f333c05a0307f7f1d2df8ac013602c655c6e209", + "0x95719eb02e8a9dede1a888c656a778b1c69b7716fbe3d1538fe8afd4a1bc972183c7d32aa7d6073376f7701df80116d8", + "0x8e8a1ca971f2444b35af3376e85dccda3abb8e8e11d095d0a4c37628dfe5d3e043a377c3de68289ef142e4308e9941a0", + "0xb720caaff02f6d798ac84c4f527203e823ff685869e3943c979e388e1c34c3f77f5c242c6daa7e3b30e511aab917b866", + "0x86040d55809afeec10e315d1ad950d269d37cfee8c144cd8dd4126459e3b15a53b3e68df5981df3c2346d23c7b4baaf4", + "0x82d8cabf13ab853db0377504f0aec00dba3a5cd3119787e8ad378ddf2c40b022ecfc67c642b7acc8c1e3dd03ab50993e", + "0xb8d873927936719d2484cd03a6687d65697e17dcf4f0d5aed6f5e4750f52ef2133d4645894e7ebfc4ef6ce6788d404c8", + "0xb1235594dbb15b674a419ff2b2deb644ad2a93791ca05af402823f87114483d6aa1689b7a9bea0f547ad12fe270e4344", + "0xa53fda86571b0651f5affb74312551a082fffc0385cfd24c1d779985b72a5b1cf7c78b42b4f7e51e77055f8e5e915b00", + "0xb579adcfd9c6ef916a5a999e77a0cb21d378c4ea67e13b7c58709d5da23a56c2e54218691fc4ac39a4a3d74f88cc31f7", + "0xab79e584011713e8a2f583e483a91a0c2a40771b77d91475825b5acbea82db4262132901cb3e4a108c46d7c9ee217a4e", + "0xa0fe58ea9eb982d7654c8aaf9366230578fc1362f6faae0594f8b9e659bcb405dff4aac0c7888bbe07f614ecf0d800a6", + "0x867e50e74281f28ecd4925560e2e7a6f8911b135557b688254623acce0dbc41e23ac3e706a184a45d54c586edc416eb0", + "0x89f81b61adda20ea9d0b387a36d0ab073dc7c7cbff518501962038be19867042f11fcc7ff78096e5d3b68c6d8dc04d9b", + "0xa58ee91bb556d43cf01f1398c5811f76dc0f11efdd569eed9ef178b3b0715e122060ec8f945b4dbf6eebfa2b90af6fa6", + "0xac460be540f4c840def2eef19fc754a9af34608d107cbadb53334cf194cc91138d53b9538fcd0ec970b5d4aa455b224a", + "0xb09b91f929de52c09d48ca0893be6eb44e2f5210a6c394689dc1f7729d4be4e11d0474b178e80cea8c2ac0d081f0e811", + "0x8d37a442a76b06a02a4e64c2504aea72c8b9b020ab7bcc94580fe2b9603c7c50d7b1e9d70d2a7daea19c68667e8f8c31", + "0xa9838d4c4e3f3a0075a952cf7dd623307ec633fcc81a7cf9e52e66c31780de33dbb3d74c320dc7f0a4b72f7a49949515", + "0xa44766b6251af458fe4f5f9ed1e02950f35703520b8656f09fc42d9a2d38a700c11a7c8a0436ac2e5e9f053d0bb8ff91", + "0xad78d9481c840f5202546bea0d13c776826feb8b1b7c72e83d99a947622f0bf38a4208551c4c41beb1270d7792075457", + "0xb619ffa8733b470039451e224b777845021e8dc1125f247a4ff2476cc774657d0ff9c5279da841fc1236047de9d81c60", + "0xaf760b0a30a1d6af3bc5cd6686f396bd41779aeeb6e0d70a09349bd5da17ca2e7965afc5c8ec22744198fbe3f02fb331", + "0xa0cc209abdb768b589fcb7b376b6e1cac07743288c95a1cf1a0354b47f0cf91fca78a75c1fcafa6f5926d6c379116608", + "0x864add673c89c41c754eeb3cd8dcff5cdde1d739fce65c30e474a082bb5d813cba6412e61154ce88fdb6c12c5d9be35b", + "0xb091443b0ce279327dc37cb484e9a5b69b257a714ce21895d67539172f95ffa326903747b64a3649e99aea7bb10d03f7", + "0xa8c452b8c4ca8e0a61942a8e08e28f17fb0ef4c5b018b4e6d1a64038280afa2bf1169202f05f14af24a06ca72f448ccd", + "0xa23c24721d18bc48d5dcf70effcbef89a7ae24e67158d70ae1d8169ee75d9a051d34b14e9cf06488bac324fe58549f26", + "0x92a730e30eb5f3231feb85f6720489dbb1afd42c43f05a1610c6b3c67bb949ec8fde507e924498f4ffc646f7b07d9123", + "0x8dbe5abf4031ec9ba6bb06d1a47dd1121fb9e03b652804069250967fd5e9577d0039e233441b7f837a7c9d67ba18c28e", + "0xaa456bcfef6a21bb88181482b279df260297b3778e84594ebddbdf337e85d9e3d46ca1d0b516622fb0b103df8ec519b7", + "0xa3b31ae621bd210a2b767e0e6f22eb28fe3c4943498a7e91753225426168b9a26da0e02f1dc5264da53a5ad240d9f51b", + "0xaa8d66857127e6e71874ce2202923385a7d2818b84cb73a6c42d71afe70972a70c6bdd2aad1a6e8c5e4ca728382a8ea8", + "0xac7e8e7a82f439127a5e40558d90d17990f8229852d21c13d753c2e97facf077cf59582b603984c3dd3faebd80aff4f5", + "0x93a8bcf4159f455d1baa73d2ef2450dcd4100420de84169bbe28b8b7a5d1746273f870091a87a057e834f754f34204b1", + "0x89d0ebb287c3613cdcae7f5acc43f17f09c0213fc40c074660120b755d664109ffb9902ed981ede79e018ddb0c845698", + "0xa87ccbfad431406aadbee878d9cf7d91b13649d5f7e19938b7dfd32645a43b114eef64ff3a13201398bd9b0337832e5a", + "0x833c51d0d0048f70c3eefb4e70e4ff66d0809c41838e8d2c21c288dd3ae9d9dfaf26d1742bf4976dab83a2b381677011", + "0x8bcd6b1c3b02fffead432e8b1680bad0a1ac5a712d4225e220690ee18df3e7406e2769e1f309e2e803b850bc96f0e768", + "0xb61e3dbd88aaf4ff1401521781e2eea9ef8b66d1fac5387c83b1da9e65c2aa2a56c262dea9eceeb4ad86c90211672db0", + "0x866d3090db944ecf190dd0651abf67659caafd31ae861bab9992c1e3915cb0952da7c561cc7e203560a610f48fae633b", + "0xa5e8971543c14274a8dc892b0be188c1b4fbc75c692ed29f166e0ea80874bc5520c2791342b7c1d2fb5dd454b03b8a5b", + "0x8f2f9fc50471bae9ea87487ebd1bc8576ef844cc42d606af5c4c0969670fdf2189afd643e4de3145864e7773d215f37f", + "0xb1bb0f2527db6d51f42b9224383c0f96048bbc03d469bf01fe1383173ef8b1cc9455d9dd8ba04d46057f46949bfc92b5", + "0xaa7c99d906b4d7922296cfe2520473fc50137c03d68b7865c5bfb8adbc316b1034310ec4b5670c47295f4a80fb8d61e9", + "0xa5d1da4d6aba555919df44cbaa8ff79378a1c9e2cfdfbf9d39c63a4a00f284c5a5724e28ecbc2d9dba27fe4ee5018bd5", + "0xa8db53224f70af4d991b9aae4ffe92d2aa5b618ad9137784b55843e9f16cefbfd25ada355d308e9bbf55f6d2f7976fb3", + "0xb6536c4232bb20e22af1a8bb12de76d5fec2ad9a3b48af1f38fa67e0f8504ef60f305a73d19385095bb6a9603fe29889", + "0x87f7e371a1817a63d6838a8cf4ab3a8473d19ce0d4f40fd013c03d5ddd5f4985df2956531cc9f187928ef54c68f4f9a9", + "0xae13530b1dbc5e4dced9d909ea61286ec09e25c12f37a1ed2f309b0eb99863d236c3b25ed3484acc8c076ad2fa8cd430", + "0x98928d850247c6f7606190e687d5c94a627550198dbdbea0161ef9515eacdb1a0f195cae3bb293112179082daccf8b35", + "0x918528bb8e6a055ad4db6230d3a405e9e55866da15c4721f5ddd1f1f37962d4904aad7a419218fe6d906fe191a991806", + "0xb71e31a06afe065773dd3f4a6e9ef81c3292e27a3b7fdfdd452d03e05af3b6dd654c355f7516b2a93553360c6681a73a", + "0x8870b83ab78a98820866f91ac643af9f3ff792a2b7fda34185a9456a63abdce42bfe8ad4dc67f08a6392f250d4062df4", + "0x91eea1b668e52f7a7a5087fabf1cab803b0316f78d9fff469fbfde2162f660c250e4336a9eea4cb0450bd30ac067bc8b", + "0x8b74990946de7b72a92147ceac1bd9d55999a8b576e8df68639e40ed5dc2062cfcd727903133de482b6dca19d0aaed82", + "0x8ebad537fece090ebbab662bdf2618e21ca30cf6329c50935e8346d1217dcbe3c1fe1ea28efca369c6003ce0a94703c1", + "0xa8640479556fb59ebd1c40c5f368fbd960932fdbb782665e4a0e24e2bdb598fc0164ce8c0726d7759cfc59e60a62e182", + "0xa9a52a6bf98ee4d749f6d38be2c60a6d54b64d5cbe4e67266633dc096cf28c97fe998596707d31968cbe2064b72256bf", + "0x847953c48a4ce6032780e9b39d0ed4384e0be202c2bbe2dfda3910f5d87aa5cd3c2ffbfcfae4dddce16d6ab657599b95", + "0xb6f6e1485d3ec2a06abaecd23028b200b2e4a0096c16144d07403e1720ff8f9ba9d919016b5eb8dc5103880a7a77a1d3", + "0x98dfc2065b1622f596dbe27131ea60bef7a193b12922cecb27f8c571404f483014f8014572e86ae2e341ab738e4887ef", + "0xacb0d205566bacc87bbe2e25d10793f63f7a1f27fd9e58f4f653ceae3ffeba511eaf658e068fad289eeb28f9edbeb35b", + "0xae4411ed5b263673cee894c11fe4abc72a4bf642d94022a5c0f3369380fcdfc1c21e277f2902972252503f91ada3029a", + "0xac4a7a27ba390a75d0a247d93d4a8ef1f0485f8d373a4af4e1139369ec274b91b3464d9738eeaceb19cd6f509e2f8262", + "0x87379c3bf231fdafcf6472a79e9e55a938d851d4dd662ab6e0d95fd47a478ed99e2ad1e6e39be3c0fc4f6d996a7dd833", + "0x81316904b035a8bcc2041199a789a2e6879486ba9fddcba0a82c745cc8dd8374a39e523b91792170cd30be7aa3005b85", + "0xb8206809c6cd027ed019f472581b45f7e12288f89047928ba32b4856b6560ad30395830d71e5e30c556f6f182b1fe690", + "0x88d76c028f534a62e019b4a52967bb8642ede6becfa3807be68fdd36d366fc84a4ac8dc176e80a68bc59eb62caf5dff9", + "0x8c3b8be685b0f8aad131ee7544d0e12f223f08a6f8edaf464b385ac644e0ddc9eff7cc7cb5c1b50ab5d71ea0f41d2213", + "0x8d91410e004f76c50fdc05784157b4d839cb5090022c629c7c97a5e0c3536eeafee17a527b54b1165c3cd81774bb54ce", + "0xb25c2863bc28ec5281ce800ddf91a7e1a53f4c6d5da1e6c86ef4616e93bcf55ed49e297216d01379f5c6e7b3c1e46728", + "0x865f7b09ac3ca03f20be90c48f6975dd2588838c2536c7a3532a6aa5187ed0b709cd03d91ff4048061c10d0aa72b69ce", + "0xb3f7477c90c11596eb4f8bbf34adbcb832638c4ff3cdd090d4d477ee50472ac9ddaf5be9ad7eca3f148960d362bbd098", + "0x8db35fd53fca04faecd1c76a8227160b3ab46ac1af070f2492445a19d8ff7c25bbaef6c9fa0c8c088444561e9f7e4eb2", + "0xa478b6e9d058a2e01d2fc053b739092e113c23a6a2770a16afbef044a3709a9e32f425ace9ba7981325f02667c3f9609", + "0x98caa6bd38916c08cf221722a675a4f7577f33452623de801d2b3429595f988090907a7e99960fff7c076d6d8e877b31", + "0xb79aaaacefc49c3038a14d2ac468cfec8c2161e88bdae91798d63552cdbe39e0e02f9225717436b9b8a40a022c633c6e", + "0x845a31006c680ee6a0cc41d3dc6c0c95d833fcf426f2e7c573fa15b2c4c641fbd6fe5ebb0e23720cc3467d6ee1d80dc4", + "0xa1bc287e272cf8b74dbf6405b3a5190883195806aa351f1dc8e525aa342283f0a35ff687e3b434324dedee74946dd185", + "0xa4fd2dc8db75d3783a020856e2b3aa266dc6926e84f5c491ef739a3bddd46dc8e9e0fc1177937839ef1b18d062ffbb9e", + "0xacbf0d3c697f57c202bb8c5dc4f3fc341b8fc509a455d44bd86acc67cad2a04495d5537bcd3e98680185e8aa286f2587", + "0xa5caf423a917352e1b8e844f5968a6da4fdeae467d10c6f4bbd82b5eea46a660b82d2f5440d3641c717b2c3c9ed0be52", + "0x8a39d763c08b926599ab1233219c49c825368fad14d9afc7c0c039224d37c00d8743293fd21645bf0b91eaf579a99867", + "0xb2b53a496def0ba06e80b28f36530fbe0fb5d70a601a2f10722e59abee529369c1ae8fd0f2db9184dd4a2519bb832d94", + "0xa73980fcef053f1b60ebbb5d78ba6332a475e0b96a0c724741a3abf3b59dd344772527f07203cf4c9cb5155ebed81fa0", + "0xa070d20acce42518ece322c9db096f16aed620303a39d8d5735a0df6e70fbeceb940e8d9f5cc38f3314b2240394ec47b", + "0xa50cf591f522f19ca337b73089557f75929d9f645f3e57d4f241e14cdd1ea3fb48d84bcf05e4f0377afbb789fbdb5d20", + "0x82a5ffce451096aca8eeb0cd2ae9d83db3ed76da3f531a80d9a70a346359bf05d74863ce6a7c848522b526156a5e20cd", + "0x88e0e84d358cbb93755a906f329db1537c3894845f32b9b0b691c29cbb455373d9452fadd1e77e20a623f6eaf624de6f", + "0xaa07ac7b84a6d6838826e0b9e350d8ec75e398a52e9824e6b0da6ae4010e5943fec4f00239e96433f291fef9d1d1e609", + "0xac8887bf39366034bc63f6cc5db0c26fd27307cbc3d6cce47894a8a019c22dd51322fb5096edc018227edfafc053a8f6", + "0xb7d26c26c5b33f77422191dca94977588ab1d4b9ce7d0e19c4a3b4cd1c25211b78c328dbf81e755e78cd7d1d622ad23e", + "0x99a676d5af49f0ba44047009298d8474cabf2d5bca1a76ba21eff7ee3c4691a102fdefea27bc948ccad8894a658abd02", + "0xb0d09a91909ab3620c183bdf1d53d43d39eb750dc7a722c661c3de3a1a5d383ad221f71bae374f8a71867505958a3f76", + "0x84681a883de8e4b93d68ac10e91899c2bbb815ce2de74bb48a11a6113b2a3f4df8aceabda1f5f67bc5aacac8c9da7221", + "0x9470259957780fa9b43521fab3644f555f5343281c72582b56d2efd11991d897b3b481cafa48681c5aeb80c9663b68f7", + "0xab1b29f7ece686e6fa968a4815da1d64f3579fed3bc92e1f3e51cd13a3c076b6cf695ed269d373300a62463dc98a4234", + "0x8ab415bfcd5f1061f7687597024c96dd9c7cb4942b5989379a7a3b5742f7d394337886317659cbeacaf030234a24f972", + "0xb9b524aad924f9acc63d002d617488f31b0016e0f0548f050cada285ce7491b74a125621638f19e9c96eabb091d945be", + "0x8c4c373e79415061837dd0def4f28a2d5d74d21cb13a76c9049ad678ca40228405ab0c3941df49249847ecdefc1a5b78", + "0xa8edf4710b5ab2929d3db6c1c0e3e242261bbaa8bcec56908ddadd7d2dad2dca9d6eb9de630b960b122ebeea41040421", + "0x8d66bb3b50b9df8f373163629f9221b3d4b6980a05ea81dc3741bfe9519cf3ebba7ab98e98390bae475e8ede5821bd5c", + "0x8d3c21bae7f0cfb97c56952bb22084b58e7bb718890935b73103f33adf5e4d99cd262f929c6eeab96209814f0dbae50a", + "0xa5c66cfab3d9ebf733c4af24bebc97070e7989fe3c73e79ac85fb0e4d40ae44fb571e0fad4ad72560e13ed453900d14f", + "0x9362e6b50b43dbefbc3254471372297b5dcce809cd3b60bf74a1268ab68bdb50e46e462cbd78f0d6c056330e982846af", + "0x854630d08e3f0243d570cc2e856234cb4c1a158d9c1883bf028a76525aaa34be897fe918d5f6da9764a3735fa9ebd24a", + "0x8c7d246985469ff252c3f4df6c7c9196fc79f05c1c66a609d84725c78001d0837c7a7049394ba5cf7e863e2d58af8417", + "0xae050271e01b528925302e71903f785b782f7bf4e4e7a7f537140219bc352dc7540c657ed03d3a297ad36798ecdb98cd", + "0x8d2ae9179fcf2b0c69850554580b52c1f4a5bd865af5f3028f222f4acad9c1ad69a8ef6c7dc7b03715ee5c506b74325e", + "0xb8ef8de6ce6369a8851cd36db0ccf00a85077e816c14c4e601f533330af9e3acf0743a95d28962ed8bfcfc2520ef3cfe", + "0xa6ecad6fdfb851b40356a8b1060f38235407a0f2706e7b8bb4a13465ca3f81d4f5b99466ac2565c60af15f022d26732e", + "0x819ff14cdea3ab89d98e133cd2d0379361e2e2c67ad94eeddcdb9232efd509f51d12f4f03ebd4dd953bd262a886281f7", + "0x8561cd0f7a6dbcddd83fcd7f472d7dbcba95b2d4fb98276f48fccf69f76d284e626d7e41314b633352df8e6333fd52a1", + "0xb42557ccce32d9a894d538c48712cb3e212d06ac05cd5e0527ccd2db1078ee6ae399bf6a601ffdab1f5913d35fc0b20c", + "0x89b4008d767aad3c6f93c349d3b956e28307311a5b1cec237e8d74bb0dee7e972c24f347fd56afd915a2342bd7bc32f0", + "0x877487384b207e53f5492f4e36c832c2227f92d1bb60542cfeb35e025a4a7afc2b885fae2528b33b40ab09510398f83e", + "0x8c411050b63c9053dd0cd81dacb48753c3d7f162028098e024d17cd6348482703a69df31ad6256e3d25a8bbf7783de39", + "0xa8506b54a88d17ac10fb1b0d1fe4aa40eae7553a064863d7f6b52ccc4236dd4b82d01dca6ba87da9a239e3069ba879fb", + "0xb1a24caef9df64750c1350789bb8d8a0db0f39474a1c74ea9ba064b1516db6923f00af8d57c632d58844fb8786c3d47a", + "0x959d6e255f212b0708c58a2f75cb1fe932248c9d93424612c1b8d1e640149656059737e4db2139afd5556bcdacf3eda2", + "0x84525af21a8d78748680b6535bbc9dc2f0cf9a1d1740d12f382f6ecb2e73811d6c1da2ad9956070b1a617c61fcff9fe5", + "0xb74417d84597a485d0a8e1be07bf78f17ebb2e7b3521b748f73935b9afbbd82f34b710fb7749e7d4ab55b0c7f9de127d", + "0xa4a9aecb19a6bab167af96d8b9d9aa5308eab19e6bfb78f5a580f9bf89bdf250a7b52a09b75f715d651cb73febd08e84", + "0x9777b30be2c5ffe7d29cc2803a562a32fb43b59d8c3f05a707ab60ec05b28293716230a7d264d7cd9dd358fc031cc13e", + "0x95dce7a3d4f23ac0050c510999f5fbf8042f771e8f8f94192e17bcbfa213470802ebdbe33a876cb621cf42e275cbfc8b", + "0xb0b963ebcbbee847ab8ae740478544350b3ac7e86887e4dfb2299ee5096247cd2b03c1de74c774d9bde94ae2ee2dcd59", + "0xa4ab20bafa316030264e13f7ef5891a2c3b29ab62e1668fcb5881f50a9acac6adbe3d706c07e62f2539715db768f6c43", + "0x901478a297669d608e406fe4989be75264b6c8be12169aa9e0ad5234f459ca377f78484ffd2099a2fe2db5e457826427", + "0x88c76e5c250810c057004a03408b85cd918e0c8903dc55a0dd8bb9b4fc2b25c87f9b8cf5943eb19fbbe99d36490050c5", + "0x91607322bbad4a4f03fc0012d0821eff5f8c516fda45d1ec1133bface6f858bf04b25547be24159cab931a7aa08344d4", + "0x843203e07fce3c6c81f84bc6dc5fb5e9d1c50c8811ace522dc66e8658433a0ef9784c947e6a62c11bf705307ef05212e", + "0x91dd8813a5d6dddcda7b0f87f672b83198cd0959d8311b2b26fb1fae745185c01f796fbd03aad9db9b58482483fdadd8", + "0x8d15911aacf76c8bcd7136e958febd6963104addcd751ce5c06b6c37213f9c4fb0ffd4e0d12c8e40c36d658999724bfd", + "0x8a36c5732d3f1b497ebe9250610605ee62a78eaa9e1a45f329d09aaa1061131cf1d9df00f3a7d0fe8ad614a1ff9caaae", + "0xa407d06affae03660881ce20dab5e2d2d6cddc23cd09b95502a9181c465e57597841144cb34d22889902aff23a76d049", + "0xb5fd856d0578620a7e25674d9503be7d97a2222900e1b4738c1d81ff6483b144e19e46802e91161e246271f90270e6cf", + "0x91b7708869cdb5a7317f88c0312d103f8ce90be14fb4f219c2e074045a2a83636fdc3e69e862049fc7c1ef000e832541", + "0xb64719cc5480709d1dae958f1d3082b32a43376da446c8f9f64cb02a301effc9c34d9102051733315a8179aed94d53cc", + "0x94347a9542ff9d18f7d9eaa2f4d9b832d0e535fe49d52aa2de08aa8192400eddabdb6444a2a78883e27c779eed7fdf5a", + "0x840ef44a733ff1376466698cd26f82cf56bb44811e196340467f932efa3ae1ef9958a0701b3b032f50fd9c1d2aed9ab5", + "0x90ab3f6f67688888a31ffc2a882bb37adab32d1a4b278951a21646f90d03385fc976715fc639a785d015751171016f10", + "0xb56f35d164c24b557dbcbc8a4bfa681ec916f8741ffcb27fb389c164f4e3ed2be325210ef5bdaeae7a172ca9599ab442", + "0xa7921a5a80d7cf6ae81ba9ee05e0579b18c20cd2852762c89d6496aa4c8ca9d1ca2434a67b2c16d333ea8e382cdab1e3", + "0xa506bcfbd7e7e5a92f68a1bd87d07ad5fe3b97aeee40af2bf2cae4efcd77fff03f872732c5b7883aa6584bee65d6f8cb", + "0xa8c46cff58931a1ce9cbe1501e1da90b174cddd6d50f3dfdfb759d1d4ad4673c0a8feed6c1f24c7af32865a7d6c984e5", + "0xb45686265a83bff69e312c5149db7bb70ac3ec790dc92e392b54d9c85a656e2bf58596ce269f014a906eafc97461aa5f", + "0x8d4009a75ccb2f29f54a5f16684b93202c570d7a56ec1a8b20173269c5f7115894f210c26b41e8d54d4072de2d1c75d0", + "0xaef8810af4fc676bf84a0d57b189760ddc3375c64e982539107422e3de2580b89bd27aa6da44e827b56db1b5555e4ee8", + "0x888f0e1e4a34f48eb9a18ef4de334c27564d72f2cf8073e3d46d881853ac1424d79e88d8ddb251914890588937c8f711", + "0xb64b0aa7b3a8f6e0d4b3499fe54e751b8c3e946377c0d5a6dbb677be23736b86a7e8a6be022411601dd75012012c3555", + "0x8d57776f519f0dd912ea14f79fbab53a30624e102f9575c0bad08d2dc754e6be54f39b11278c290977d9b9c7c0e1e0ad", + "0xa018fc00d532ceb2e4de908a15606db9b6e0665dd77190e2338da7c87a1713e6b9b61554e7c1462f0f6d4934b960b15c", + "0x8c932be83ace46f65c78e145b384f58e41546dc0395270c1397874d88626fdeda395c8a289d602b4c312fe98c1311856", + "0x89174838e21639d6bdd91a0621f04dc056907b88e305dd66e46a08f6d65f731dea72ae87ca5e3042d609e8de8de9aa26", + "0xb7b7f508bb74f7a827ac8189daa855598ff1d96fa3a02394891fd105d8f0816224cd50ac4bf2ed1cf469ace516c48184", + "0xb31877ad682583283baadd68dc1bebd83f5748b165aadd7fe9ef61a343773b88bcd3a022f36d6c92f339b7bfd72820a9", + "0xb79d77260b25daf9126dab7a193df2d7d30542786fa1733ffaf6261734770275d3ca8bae1d9915d1181a78510b3439db", + "0x91894fb94cd4c1dd2ceaf9c53a7020c5799ba1217cf2d251ea5bc91ed26e1159dd758e98282ebe35a0395ef9f1ed15a0", + "0xab59895cdafd33934ceedfc3f0d5d89880482cba6c99a6db93245f9e41987efd76e0640e80aef31782c9a8c7a83fccec", + "0xaa22ea63654315e033e09d4d4432331904a6fc5fb1732557987846e3c564668ca67c60a324b4af01663a23af11a9ce4b", + "0xb53ba3ef342601467e1f71aa280e100fbabbd38518fa0193e0099505036ee517c1ac78e96e9baeb549bb6879bb698fb0", + "0x943fd69fd656f37487cca3605dc7e5a215fddd811caf228595ec428751fc1de484a0cb84c667fe4d7c35599bfa0e5e34", + "0x9353128b5ebe0dddc555093cf3e5942754f938173541033e8788d7331fafc56f68d9f97b4131e37963ab7f1c8946f5f1", + "0xa76cd3c566691f65cfb86453b5b31dbaf3cab8f84fe1f795dd1e570784b9b01bdd5f0b3c1e233942b1b5838290e00598", + "0x983d84b2e53ffa4ae7f3ba29ef2345247ea2377686b74a10479a0ef105ecf90427bf53b74c96dfa346d0f842b6ffb25b", + "0x92e0fe9063306894a2c6970c001781cff416c87e87cb5fbac927a3192655c3da4063e6fa93539f6ff58efac6adcc5514", + "0xb00a81f03c2b8703acd4e2e4c21e06973aba696415d0ea1a648ace2b0ea19b242fede10e4f9d7dcd61c546ab878bc8f9", + "0xb0d08d880f3b456a10bf65cff983f754f545c840c413aea90ce7101a66eb0a0b9b1549d6c4d57725315828607963f15a", + "0x90cb64d03534f913b411375cce88a9e8b1329ce67a9f89ca5df8a22b8c1c97707fec727dbcbb9737f20c4cf751359277", + "0x8327c2d42590dfcdb78477fc18dcf71608686ad66c49bce64d7ee874668be7e1c17cc1042a754bbc77c9daf50b2dae07", + "0x8532171ea13aa7e37178e51a6c775da469d2e26ec854eb16e60f3307db4acec110d2155832c202e9ba525fc99174e3b0", + "0x83ca44b15393d021de2a511fa5511c5bd4e0ac7d67259dce5a5328f38a3cce9c3a269405959a2486016bc27bb140f9ff", + "0xb1d36e8ca812be545505c8214943b36cabee48112cf0de369957afa796d37f86bf7249d9f36e8e990f26f1076f292b13", + "0x9803abf45be5271e2f3164c328d449efc4b8fc92dfc1225d38e09630909fe92e90a5c77618daa5f592d23fc3ad667094", + "0xb268ad68c7bf432a01039cd889afae815c3e120f57930d463aece10af4fd330b5bd7d8869ef1bcf6b2e78e4229922edc", + "0xa4c91a0d6f16b1553264592b4cbbbf3ca5da32ab053ffbdd3dbb1aed1afb650fb6e0dc5274f71a51d7160856477228db", + "0xad89d043c2f0f17806277ffdf3ecf007448e93968663f8a0b674254f36170447b7527d5906035e5e56f4146b89b5af56", + "0x8b6964f757a72a22a642e4d69102951897e20c21449184e44717bd0681d75f7c5bfa5ee5397f6e53febf85a1810d6ed1", + "0xb08f5cdaabec910856920cd6e836c830b863eb578423edf0b32529488f71fe8257d90aed4a127448204df498b6815d79", + "0xaf26bb3358be9d280d39b21d831bb53145c4527a642446073fee5a86215c4c89ff49a3877a7a549486262f6f57a0f476", + "0xb4010b37ec4d7c2af20800e272539200a6b623ae4636ecbd0e619484f4ab9240d02bc5541ace3a3fb955dc0a3d774212", + "0x82752ab52bdcc3cc2fc405cb05a2e694d3df4a3a68f2179ec0652536d067b43660b96f85f573f26fbd664a9ef899f650", + "0x96d392dde067473a81faf2d1fea55b6429126b88b160e39b4210d31d0a82833ffd3a80e07d24d495aea2d96be7251547", + "0xa76d8236d6671204d440c33ac5b8deb71fa389f6563d80e73be8b043ec77d4c9b06f9a586117c7f957f4af0331cbc871", + "0xb6c90961f68b5e385d85c9830ec765d22a425f506904c4d506b87d8944c2b2c09615e740ed351df0f9321a7b93979cae", + "0xa6ec5ea80c7558403485b3b1869cdc63bde239bafdf936d9b62a37031628402a36a2cfa5cfbb8e26ac922cb0a209b3ba", + "0x8c3195bbdbf9bc0fc95fa7e3d7f739353c947f7767d1e3cb24d8c8602d8ea0a1790ac30b815be2a2ba26caa5227891e2", + "0xa7f8a63d809f1155722c57f375ea00412b00147776ae4444f342550279ef4415450d6f400000a326bf11fea6c77bf941", + "0x97fa404df48433a00c85793440e89bb1af44c7267588ae937a1f5d53e01e1c4d4fc8e4a6d517f3978bfdd6c2dfde012f", + "0xa984a0a3836de3d8d909c4629a2636aacb85393f6f214a2ef68860081e9db05ad608024762db0dc35e895dc00e2d4cdd", + "0x9526cf088ab90335add1db4d3a4ac631b58cbfbe88fa0845a877d33247d1cfeb85994522e1eb8f8874651bfb1df03e2a", + "0xac83443fd0afe99ad49de9bf8230158c118e2814c9c89db5ac951c240d6c2ce45e7677221279d9e97848ec466b99aafe", + "0xaeeefdbaba612e971697798ceaf63b247949dc823a0ad771ae5b988a5e882b338a98d3d0796230f49d533ec5ba411b39", + "0xae3f248b5a7b0f92b7820a6c5ae21e5bd8f4265d4f6e21a22512079b8ee9be06393fd3133ce8ebac0faf23f4f8517e36", + "0xa64a831b908eee784b8388b45447d2885ec0551b26b0c2b15e5f417d0a12c79e867fb7bd3d008d0af98b44336f8ec1ad", + "0xb242238cd8362b6e440ba21806905714dd55172db25ec7195f3fc4937b2aba146d5cbf3cf691a1384b4752dc3b54d627", + "0x819f97f337eea1ffb2a678cc25f556f1aab751c6b048993a1d430fe1a3ddd8bb411c152e12ca60ec6e057c190cd1db9a", + "0xb9d7d187407380df54ee9fef224c54eec1bfabf17dc8abf60765b7951f538f59aa26fffd5846cfe05546c35f59b573f4", + "0xaa6e3c14efa6a5962812e3f94f8ce673a433f4a82d07a67577285ea0eaa07f8be7115853122d12d6d4e1fdf64c504be1", + "0x82268bee9c1662d3ddb5fb785abfae6fb8b774190f30267f1d47091d2cd4b3874db4372625aa36c32f27b0eee986269b", + "0xb236459565b7b966166c4a35b2fa71030b40321821b8e96879d95f0e83a0baf33fa25721f30af4a631df209e25b96061", + "0x8708d752632d2435d2d5b1db4ad1fa2558d776a013655f88e9a3556d86b71976e7dfe5b8834fdec97682cd94560d0d0d", + "0xae1424a68ae2dbfb0f01211f11773732a50510b5585c1fb005cb892b2c6a58f4a55490b5c5b4483c6fce40e9d3236a52", + "0xb3f5f722af9dddb07293c871ce97abbccba0093ca98c8d74b1318fa21396fc1b45b69c15084f63d728f9908442024506", + "0x9606f3ce5e63886853ca476dc0949e7f1051889d529365c0cb0296fdc02abd088f0f0318ecd2cf36740a3634132d36f6", + "0xb11a833a49fa138db46b25ff8cdda665295226595bc212c0931b4931d0a55c99da972c12b4ef753f7e37c6332356e350", + "0xafede34e7dab0a9e074bc19a7daddb27df65735581ca24ad70c891c98b1349fcebbcf3ba6b32c2617fe06a5818dabc2d", + "0x97993d456e459e66322d01f8eb13918979761c3e8590910453944bdff90b24091bb018ac6499792515c9923be289f99f", + "0x977e3e967eff19290a192cd11df3667d511b398fb3ac9a5114a0f3707e25a0edcb56105648b1b85a8b7519fc529fc6f6", + "0xb873a7c88bf58731fe1bf61ff6828bf114cf5228f254083304a4570e854e83748fc98683ddba62d978fff7909f2c5c47", + "0xad4b2691f6f19da1d123aaa23cca3e876247ed9a4ab23c599afdbc0d3aa49776442a7ceaa996ac550d0313d9b9a36cee", + "0xb9210713c78e19685608c6475bfa974b57ac276808a443f8b280945c5d5f9c39da43effa294bfb1a6c6f7b6b9f85bf6c", + "0xa65152f376113e61a0e468759de38d742caa260291b4753391ee408dea55927af08a4d4a9918600a3bdf1df462dffe76", + "0x8bf8c27ad5140dde7f3d2280fd4cc6b29ab76537e8d7aa7011a9d2796ee3e56e9a60c27b5c2da6c5e14fc866301dc195", + "0x92fde8effc9f61393a2771155812b863cff2a0c5423d7d40aa04d621d396b44af94ddd376c28e7d2f53c930aea947484", + "0x97a01d1dd9ee30553ce676011aea97fa93d55038ada95f0057d2362ae9437f3ed13de8290e2ff21e3167dd7ba10b9c3f", + "0x89affffaa63cb2df3490f76f0d1e1d6ca35c221dd34057176ba739fa18d492355e6d2a5a5ad93a136d3b1fed0bb8aa19", + "0x928b8e255a77e1f0495c86d3c63b83677b4561a5fcbbe5d3210f1e0fc947496e426d6bf3b49394a5df796c9f25673fc4", + "0x842a0af91799c9b533e79ee081efe2a634cac6c584c2f054fb7d1db67dde90ae36de36cbf712ec9cd1a0c7ee79e151ea", + "0xa65b946cf637e090baf2107c9a42f354b390e7316beb8913638130dbc67c918926eb87bec3b1fe92ef72bc77a170fa3b", + "0xaafc0f19bfd71ab5ae4a8510c7861458b70ad062a44107b1b1dbacbfa44ba3217028c2824bd7058e2fa32455f624040b", + "0x95269dc787653814e0be899c95dba8cfa384f575a25e671c0806fd80816ad6797dc819d30ae06e1d0ed9cb01c3950d47", + "0xa1e760f7fa5775a1b2964b719ff961a92083c5c617f637fc46e0c9c20ab233f8686f7f38c3cb27d825c54dd95e93a59b", + "0xac3b8a7c2317ea967f229eddc3e23e279427f665c4705c7532ed33443f1243d33453c1088f57088d2ab1e3df690a9cc9", + "0xb787beeddfbfe36dd51ec4efd9cf83e59e84d354c3353cc9c447be53ae53d366ed1c59b686e52a92f002142c8652bfe0", + "0xb7a64198300cb6716aa7ac6b25621f8bdec46ad5c07a27e165b3f774cdf65bcfdbf31e9bae0c16b44de4b00ada7a4244", + "0xb8ae9f1452909e0c412c7a7fe075027691ea8df1347f65a5507bc8848f1d2c833d69748076db1129e5b4fb912f65c86c", + "0x9682e41872456b9fa67def89e71f06d362d6c8ca85c9c48536615bc401442711e1c9803f10ab7f8ab5feaec0f9df20a6", + "0x88889ff4e271dc1c7e21989cc39f73cde2f0475acd98078281591ff6c944fadeb9954e72334319050205d745d4df73df", + "0x8f79b5b8159e7fd0d93b0645f3c416464f39aec353b57d99ecf24f96272df8a068ad67a6c90c78d82c63b40bb73989bb", + "0x838c01a009a3d8558a3f0bdd5e22de21af71ca1aefc8423c91dc577d50920e9516880e87dce3e6d086e11cd45c9052d9", + "0xb97f1c6eee8a78f137c840667cc288256e39294268a3009419298a04a1d0087c9c9077b33c917c65caf76637702dda8a", + "0x972284ce72f96a61c899260203dfa06fc3268981732bef74060641c1a5068ead723e3399431c247ca034b0dae861e8df", + "0x945a8d52d6d3db6663dbd3110c6587f9e9c44132045eeffba15621576d178315cb52870fa5861669f84f0bee646183fe", + "0xa0a547b5f0967b1c3e5ec6c6a9a99f0578521489180dfdfbb5561f4d166baac43a2f06f950f645ce991664e167537eed", + "0xa0592cda5cdddf1340033a745fd13a6eff2021f2e26587116c61c60edead067e0f217bc2bef4172a3c9839b0b978ab35", + "0xb9c223b65a3281587fa44ec829e609154b32f801fd1de6950e01eafb07a8324243b960d5735288d0f89f0078b2c42b5b", + "0x99ebfc3b8f9f98249f4d37a0023149ed85edd7a5abe062c8fb30c8c84555258b998bdcdd1d400bc0fa2a4aaa8b224466", + "0x955b68526e6cb3937b26843270f4e60f9c6c8ece2fa9308fe3e23afa433309c068c66a4bc16ee2cf04220f095e9afce4", + "0xb766caeafcc00378135ae53397f8a67ed586f5e30795462c4a35853de6681b1f17401a1c40958de32b197c083b7279c1", + "0x921bf87cad947c2c33fa596d819423c10337a76fe5a63813c0a9dc78a728207ae7b339407a402fc4d0f7cba3af6da6fc", + "0xa74ba1f3bc3e6c025db411308f49b347ec91da1c916bda9da61e510ec8d71d25e0ac0f124811b7860e5204f93099af27", + "0xa29b4d144e0bf17a7e8353f2824cef0ce85621396babe8a0b873ca1e8a5f8d508b87866cf86da348470649fceefd735c", + "0xa8040e12ffc3480dd83a349d06741d1572ef91932c46f5cf03aee8454254156ee95786fd013d5654725e674c920cec32", + "0x8c4cf34ca60afd33923f219ffed054f90cd3f253ffeb2204a3b61b0183417e366c16c07fae860e362b0f2bfe3e1a1d35", + "0x8195eede4ddb1c950459df6c396b2e99d83059f282b420acc34220cadeed16ab65c856f2c52568d86d3c682818ed7b37", + "0x91fff19e54c15932260aa990c7fcb3c3c3da94845cc5aa8740ef56cf9f58d19b4c3c55596f8d6c877f9f4d22921d93aa", + "0xa3e0bf7e5d02a80b75cf75f2db7e66cb625250c45436e3c136d86297d652590ec97c2311bafe407ad357c79ab29d107b", + "0x81917ff87e5ed2ae4656b481a63ced9e6e5ff653b8aa6b7986911b8bc1ee5b8ef4f4d7882c3f250f2238e141b227e510", + "0x915fdbe5e7de09c66c0416ae14a8750db9412e11dc576cf6158755fdcaf67abdbf0fa79b554cac4fe91c4ec245be073f", + "0x8df27eafb5c3996ba4dc5773c1a45ca77e626b52e454dc1c4058aa94c2067c18332280630cc3d364821ee53bf2b8c130", + "0x934f8a17c5cbb827d7868f5c8ca00cb027728a841000a16a3428ab16aa28733f16b52f58c9c4fbf75ccc45df72d9c4df", + "0xb83f4da811f9183c25de8958bc73b504cf790e0f357cbe74ef696efa7aca97ad3b7ead1faf76e9f982c65b6a4d888fc2", + "0x87188213c8b5c268dc2b6da413f0501c95749e953791b727450af3e43714149c115b596b33b63a2f006a1a271b87efd0", + "0x83e9e888ab9c3e30761de635d9aabd31248cdd92f7675fc43e4b21fd96a03ec1dc4ad2ec94fec857ffb52683ac98e360", + "0xb4b9a1823fe2d983dc4ec4e3aaea297e581c3fc5ab4b4af5fa1370caa37af2d1cc7fc6bfc5e7da60ad8fdce27dfe4b24", + "0x856388bc78aef465dbcdd1f559252e028c9e9a2225c37d645c138e78f008f764124522705822a61326a6d1c79781e189", + "0xa6431b36db93c3b47353ba22e7c9592c9cdfb9cbdd052ecf2cc3793f5b60c1e89bc96e6bae117bfd047f2308da00dd2f", + "0xb619972d48e7e4291542dcde08f7a9cdc883c892986ded2f23ccb216e245cd8d9ad1d285347b0f9d7611d63bf4cee2bc", + "0x8845cca6ff8595955f37440232f8e61d5351500bd016dfadd182b9d39544db77a62f4e0102ff74dd4173ae2c181d24ef", + "0xb2f5f7fa26dcd3b6550879520172db2d64ee6aaa213cbef1a12befbce03f0973a22eb4e5d7b977f466ac2bf8323dcedd", + "0x858b7f7e2d44bdf5235841164aa8b4f3d33934e8cb122794d90e0c1cac726417b220529e4f896d7b77902ab0ccd35b3a", + "0x80b0408a092dae2b287a5e32ea1ad52b78b10e9c12f49282976cd738f5d834e03d1ad59b09c5ccaccc39818b87d06092", + "0xb996b0a9c6a2d14d984edcd6ab56bc941674102980d65b3ad9733455f49473d3f587c8cbf661228a7e125ddbe07e3198", + "0x90224fcebb36865293bd63af786e0c5ade6b67c4938d77eb0cbae730d514fdd0fe2d6632788e858afd29d46310cf86df", + "0xb71351fdfff7168b0a5ec48397ecc27ac36657a8033d9981e97002dcca0303e3715ce6dd3f39423bc8ef286fa2e9e669", + "0xae2a3f078b89fb753ce4ed87e0c1a58bb19b4f0cfb6586dedb9fcab99d097d659a489fb40e14651741e1375cfc4b6c5f", + "0x8ef476b118e0b868caed297c161f4231bbeb863cdfa5e2eaa0fc6b6669425ce7af50dc374abceac154c287de50c22307", + "0x92e46ab472c56cfc6458955270d3c72b7bde563bb32f7d4ab4d959db6f885764a3d864e1aa19802fefaa5e16b0cb0b54", + "0x96a3f68323d1c94e73d5938a18a377af31b782f56212de3f489d22bc289cf24793a95b37f1d6776edf88114b5c1fa695", + "0x962cc068cfce6faaa27213c4e43e44eeff0dfbb6d25b814e82c7da981fb81d7d91868fa2344f05fb552362f98cfd4a72", + "0x895d4e4c4ad670abf66d43d59675b1add7afad7438ada8f42a0360c704cee2060f9ac15b4d27e9b9d0996bb801276fe3", + "0xb3ad18d7ece71f89f2ef749b853c45dc56bf1c796250024b39a1e91ed11ca32713864049c9aaaea60cde309b47486bbf", + "0x8f05404e0c0258fdbae50e97ccb9b72ee17e0bd2400d9102c0dad981dac8c4c71585f03e9b5d50086d0a2d3334cb55d1", + "0x8bd877e9d4591d02c63c6f9fc9976c109de2d0d2df2bfa5f6a3232bab5b0b8b46e255679520480c2d7a318545efa1245", + "0x8d4c16b5d98957c9da13d3f36c46f176e64e5be879f22be3179a2c0e624fe4758a82bf8c8027410002f973a3b84cd55a", + "0x86e2a8dea86427b424fa8eada881bdff896907084a495546e66556cbdf070b78ba312bf441eb1be6a80006d25d5097a3", + "0x8608b0c117fd8652fdab0495b08fadbeba95d9c37068e570de6fddfef1ba4a1773b42ac2be212836141d1bdcdef11a17", + "0xa13d6febf5fb993ae76cae08423ca28da8b818d6ef0fde32976a4db57839cd45b085026b28ee5795f10a9a8e3098c683", + "0x8e261967fa6de96f00bc94a199d7f72896a6ad8a7bbb1d6187cca8fad824e522880e20f766620f4f7e191c53321d70f9", + "0x8b8e8972ac0218d7e3d922c734302803878ad508ca19f5f012bc047babd8a5c5a53deb5fe7c15a4c00fd6d1cb9b1dbd0", + "0xb5616b233fb3574a2717d125a434a2682ff68546dccf116dd8a3b750a096982f185614b9fb6c7678107ff40a451f56fa", + "0xaa6adf9b0c3334b0d0663f583a4914523b2ac2e7adffdb026ab9109295ff6af003ef8357026dbcf789896d2afded8d73", + "0xacb72df56a0b65496cd534448ed4f62950bb1e11e50873b6ed349c088ee364441821294ce0f7c61bd7d38105bea3b442", + "0xabae12df83e01ec947249fedd0115dc501d2b03ff7232092979eda531dbbca29ace1d46923427c7dde4c17bdf3fd7708", + "0x820b4fc2b63a9fda7964acf5caf19a2fc4965007cb6d6b511fcafcb1f71c3f673a1c0791d3f86e3a9a1eb6955b191cc0", + "0xaf277259d78c6b0f4f030a10c53577555df5e83319ddbad91afbd7c30bc58e7671c56d00d66ec3ab5ef56470cd910cee", + "0xad4a861c59f1f5ca1beedd488fb3d131dea924fffd8e038741a1a7371fad7370ca5cf80dc01f177fbb9576713bb9a5b3", + "0xb67a5162982ce6a55ccfb2f177b1ec26b110043cf18abd6a6c451cf140b5af2d634591eb4f28ad92177d8c7e5cd0a5e8", + "0x96176d0a83816330187798072d449cbfccff682561e668faf6b1220c9a6535b32a6e4f852e8abb00f79abb87493df16b", + "0xb0afe6e7cb672e18f0206e4423f51f8bd0017bf464c4b186d46332c5a5847647f89ff7fa4801a41c1b0b42f6135bcc92", + "0x8fc5e7a95ef20c1278c645892811f6fe3f15c431ebc998a32ec0da44e7213ea934ed2be65239f3f49b8ec471e9914160", + "0xb7793e41adda6c82ba1f2a31f656f6205f65bf8a3d50d836ee631bc7ce77c153345a2d0fc5c60edf8b37457c3729c4ec", + "0xa504dd7e4d6b2f4379f22cc867c65535079c75ccc575955f961677fa63ecb9f74026fa2f60c9fb6323c1699259e5e9c8", + "0xab899d00ae693649cc1afdf30fb80d728973d2177c006e428bf61c7be01e183866614e05410041bc82cb14a33330e69c", + "0x8a3bd8b0b1be570b65c4432a0f6dc42f48a2000e30ab089cf781d38f4090467b54f79c0d472fcbf18ef6a00df69cc6f3", + "0xb4d7028f7f76a96a3d7803fca7f507ae11a77c5346e9cdfccb120a833a59bda1f4264e425aa588e7a16f8e7638061d84", + "0xb9c7511a76ea5fb105de905d44b02edb17008335766ee357ed386b7b3cf19640a98b38785cb14603c1192bee5886c9b6", + "0x8563afb12e53aed71ac7103ab8602bfa8371ae095207cb0d59e8fd389b6ad1aff0641147e53cb6a7ca16c7f37c9c5e6b", + "0x8e108be614604e09974a9ed90960c28c4ea330a3d9a0cb4af6dd6f193f84ab282b243ecdf549b3131036bebc8905690c", + "0xb794d127fbedb9c5b58e31822361706ffac55ce023fbfe55716c3c48c2fd2f2c7660a67346864dfe588812d369cb50b6", + "0xb797a3442fc3b44f41baefd30346f9ac7f96e770d010d53c146ce74ce424c10fb62758b7e108b8abfdc5fafd89d745cb", + "0x993bb71e031e8096442e6205625e1bfddfe6dd6a83a81f3e2f84fafa9e5082ab4cad80a099f21eff2e81c83457c725c3", + "0x8711ab833fc03e37acf2e1e74cfd9133b101ff4144fe30260654398ae48912ab46549d552eb9d15d2ea57760d35ac62e", + "0xb21321fd2a12083863a1576c5930e1aecb330391ef83326d9d92e1f6f0d066d1394519284ddab55b2cb77417d4b0292f", + "0x877d98f731ffe3ee94b0b5b72d127630fa8a96f6ca4f913d2aa581f67732df6709493693053b3e22b0181632ac6c1e3b", + "0xae391c12e0eb8c145103c62ea64f41345973311c3bf7281fa6bf9b7faafac87bcf0998e5649b9ef81e288c369c827e07", + "0xb83a2842f36998890492ab1cd5a088d9423d192681b9a3a90ec518d4c541bce63e6c5f4df0f734f31fbfdd87785a2463", + "0xa21b6a790011396e1569ec5b2a423857b9bec16f543e63af28024e116c1ea24a3b96e8e4c75c6537c3e4611fd265e896", + "0xb4251a9c4aab3a495da7a42e684ba4860dbcf940ad1da4b6d5ec46050cbe8dab0ab9ae6b63b5879de97b905723a41576", + "0x8222f70aebfe6ac037f8543a08498f4cadb3edaac00336fc00437eb09f2cba758f6c38e887cc634b4d5b7112b6334836", + "0x86f05038e060594c46b5d94621a1d9620aa8ba59a6995baf448734e21f58e23c1ea2993d3002ad5250d6edd5ba59b34f", + "0xa7c0c749baef811ab31b973c39ceb1d94750e2bc559c90dc5eeb20d8bb6b78586a2b363c599ba2107d6be65cd435f24e", + "0x861d46a5d70b38d6c1cd72817a2813803d9f34c00320c8b62f8b9deb67f5b5687bc0b37c16d28fd017367b92e05da9ca", + "0xb3365d3dab639bffbe38e35383686a435c8c88b397b717cd4aeced2772ea1053ceb670f811f883f4e02975e5f1c4ac58", + "0xa5750285f61ab8f64cd771f6466e2c0395e01b692fd878f2ef2d5c78bdd8212a73a3b1dfa5e4c8d9e1afda7c84857d3b", + "0x835a10809ccf939bc46cf950a33b36d71be418774f51861f1cd98a016ade30f289114a88225a2c11e771b8b346cbe6ef", + "0xa4f59473a037077181a0a62f1856ec271028546ca9452b45cedfcb229d0f4d1aabfc13062b07e536cc8a0d4b113156a2", + "0x95cd14802180b224d44a73cc1ed599d6c4ca62ddcaa503513ccdc80aaa8be050cc98bd4b4f3b639549beb4587ac6caf9", + "0x973b731992a3e69996253d7f36dd7a0af1982b5ed21624b77a7965d69e9a377b010d6dabf88a8a97eec2a476259859cc", + "0xaf8a1655d6f9c78c8eb9a95051aa3baaf9c811adf0ae8c944a8d3fcba87b15f61021f3baf6996fa0aa51c81b3cb69de1", + "0x835aad5c56872d2a2d6c252507b85dd742bf9b8c211ccb6b25b52d15c07245b6d89b2a40f722aeb5083a47cca159c947", + "0xabf4e970b02bef8a102df983e22e97e2541dd3650b46e26be9ee394a3ea8b577019331857241d3d12b41d4eacd29a3ac", + "0xa13c32449dbedf158721c13db9539ae076a6ce5aeaf68491e90e6ad4e20e20d1cdcc4a89ed9fd49cb8c0dd50c17633c1", + "0x8c8f78f88b7e22dd7e9150ab1c000f10c28e696e21d85d6469a6fe315254740f32e73d81ab1f3c1cf8f544c86df506e8", + "0xb4b77f2acfe945abf81f2605f906c10b88fb4d28628487fb4feb3a09f17f28e9780445dfcee4878349d4c6387a9d17d4", + "0x8d255c235f3812c6ecc646f855fa3832be5cb4dbb9c9e544989fafdf3f69f05bfd370732eaf954012f0044aa013fc9c6", + "0xb982efd3f34b47df37c910148ac56a84e8116647bea24145a49e34e0a6c0176e3284d838dae6230cb40d0be91c078b85", + "0x983f365aa09bd85df2a6a2ad8e4318996b1e27d02090755391d4486144e40d80b1fbfe1c798d626db92f52e33aa634da", + "0x95fd1981271f3ea3a41d654cf497e6696730d9ff7369f26bc4d7d15c7adb4823dd0c42e4a005a810af12d234065e5390", + "0xa9f5219bd4b913c186ef30c02f995a08f0f6f1462614ea5f236964e02bdaa33db9d9b816c4aee5829947840a9a07ba60", + "0x9210e6ceb05c09b46fd09d036287ca33c45124ab86315e5d6911ff89054f1101faaa3e83d123b7805056d388bcec6664", + "0x8ed9cbf69c6ff3a5c62dd9fe0d7264578c0f826a29e614bc2fb4d621d90c8c9992438accdd7a614b1dca5d1bb73dc315", + "0x85cf2a8cca93e00da459e3cecd22c342d697eee13c74d5851634844fc215f60053cf84b0e03c327cb395f48d1c71a8a4", + "0x8818a18e9a2ec90a271b784400c1903089ffb0e0b40bc5abbbe12fbebe0f731f91959d98c5519ef1694543e31e2016d4", + "0x8dabc130f296fa7a82870bf9a8405aaf542b222ed9276bba9bd3c3555a0f473acb97d655ee7280baff766a827a8993f0", + "0xac7952b84b0dc60c4d858f034093b4d322c35959605a3dad2b806af9813a4680cb038c6d7f4485b4d6b2ff502aaeca25", + "0xad65cb6d57b48a2602568d2ec8010baed0eb440eec7638c5ec8f02687d764e9de5b5d42ad5582934e592b48471c22d26", + "0xa02ab8bd4c3d114ea23aebdd880952f9495912817da8c0c08eabc4e6755439899d635034413d51134c72a6320f807f1c", + "0x8319567764b8295402ec1ebef4c2930a138480b37e6d7d01c8b4c9cd1f2fc3f6e9a44ae6e380a0c469b25b06db23305f", + "0xafec53b2301dc0caa8034cd9daef78c48905e6068d692ca23d589b84a6fa9ddc2ed24a39480597e19cb3e83eec213b3f", + "0xac0b4ffdb5ae08e586a9cdb98f9fe56f4712af3a97065e89e274feacfb52b53c839565aee93c4cfaaccfe51432c4fab0", + "0x8972cbf07a738549205b1094c5987818124144bf187bc0a85287c94fdb22ce038c0f11df1aa16ec5992e91b44d1af793", + "0xb7267aa6f9e3de864179b7da30319f1d4cb2a3560f2ea980254775963f1523b44c680f917095879bebfa3dc2b603efcf", + "0x80f68f4bfc337952e29504ee5149f15093824ea7ab02507efd1317a670f6cbc3611201848560312e3e52e9d9af72eccf", + "0x8897fee93ce8fc1e1122e46b6d640bba309384dbd92e46e185e6364aa8210ebf5f9ee7e5e604b6ffba99aa80a10dd7d0", + "0xb58ea6c02f2360be60595223d692e82ee64874fda41a9f75930f7d28586f89be34b1083e03bbc1575bbfdda2d30db1ea", + "0x85a523a33d903280d70ac5938770453a58293480170c84926457ac2df45c10d5ff34322ab130ef4a38c916e70d81af53", + "0xa2cbf045e1bed38937492c1f2f93a5ba41875f1f262291914bc1fc40c60bd0740fb3fea428faf6da38b7c180fe8ac109", + "0x8c09328770ed8eb17afc6ac7ddd87bb476de18ed63cab80027234a605806895959990c47bd10d259d7f3e2ecb50074c9", + "0xb4b9e19edb4a33bde8b7289956568a5b6b6557404e0a34584b5721fe6f564821091013fbb158e2858c6d398293bb4b59", + "0x8a47377df61733a2aa5a0e945fce00267f8e950f37e109d4487d92d878fb8b573317bb382d902de515b544e9e233458d", + "0xb5804c9d97efeff5ca94f3689b8088c62422d92a1506fd1d8d3b1b30e8a866ad0d6dad4abfa051dfc4471250cac4c5d9", + "0x9084a6ee8ec22d4881e9dcc8a9eb3c2513523d8bc141942370fd191ad2601bf9537a0b1e84316f3209b3d8a54368051e", + "0x85447eea2fa26656a649f8519fa67279183044791d61cf8563d0783d46d747d96af31d0a93507bbb2242666aa87d3720", + "0x97566a84481027b60116c751aec552adfff2d9038e68d48c4db9811fb0cbfdb3f1d91fc176a0b0d988a765f8a020bce1", + "0xae87e5c1b9e86c49a23dceda4ecfd1dcf08567f1db8e5b6ec752ebd45433c11e7da4988573cdaebbb6f4135814fc059e", + "0xabee05cf9abdbc52897ac1ce9ed157f5466ed6c383d6497de28616238d60409e5e92619e528af8b62cc552bf09970dc2", + "0xae6d31cd7bf9599e5ee0828bab00ceb4856d829bba967278a73706b5f388465367aa8a6c7da24b5e5f1fdd3256ef8e63", + "0xac33e7b1ee47e1ee4af472e37ab9e9175260e506a4e5ce449788075da1b53c44cb035f3792d1eea2aa24b1f688cc6ed3", + "0x80f65b205666b0e089bb62152251c48c380a831e5f277f11f3ef4f0d52533f0851c1b612267042802f019ec900dc0e8f", + "0x858520ad7aa1c9fed738e3b583c84168f2927837ad0e1d326afe9935c26e9b473d7f8c382e82ef1fe37d2b39bb40a1ee", + "0xb842dd4af8befe00a97c2d0f0c33c93974761e2cb9e5ab8331b25170318ddd5e4bdbc02d8f90cbfdd5f348f4f371c1f7", + "0x8bf2cb79bc783cb57088aae7363320cbeaabd078ffdec9d41bc74ff49e0043d0dad0086a30e5112b689fd2f5a606365d", + "0x982eb03bbe563e8850847cd37e6a3306d298ab08c4d63ab6334e6b8c1fa13fce80cf2693b09714c7621d74261a0ff306", + "0xb143edb113dec9f1e5105d4a93fbe502b859e587640d3db2f628c09a17060e6aec9e900e2c8c411cda99bc301ff96625", + "0xaf472d9befa750dcebc5428fe1a024f18ec1c07bca0f95643ce6b5f4189892a910285afb03fd7ed7068fbe614e80d33c", + "0xa97e3bc57ede73ecd1bbf02de8f51b4e7c1a067da68a3cd719f4ba26a0156cbf1cef2169fd35a18c5a4cced50d475998", + "0xa862253c937cf3d75d7183e5f5be6a4385d526aeda5171c1c60a8381fea79f88f5f52a4fab244ecc70765d5765e6dfd5", + "0x90cb776f8e5a108f1719df4a355bebb04bf023349356382cae55991b31720f0fd03206b895fa10c56c98f52453be8778", + "0xa7614e8d0769dccd520ea4b46f7646e12489951efaef5176bc889e9eb65f6e31758df136b5bf1e9107e68472fa9b46ec", + "0xac3a9b80a3254c42e5ed3a090a0dd7aee2352f480de96ad187027a3bb6c791eddfc3074b6ffd74eea825188f107cda4d", + "0x82a01d0168238ef04180d4b6e0a0e39024c02c2d75b065017c2928039e154d093e1af4503f4d1f3d8a948917abb5d09f", + "0x8fab000a2b0eef851a483aec8d2dd85fe60504794411a2f73ed82e116960547ac58766cb73df71aea71079302630258d", + "0x872451a35c6db61c63e9b8bb9f16b217f985c20be4451c14282c814adb29d7fb13f201367c664435c7f1d4d9375d7a58", + "0x887d9ff54cc96b35d562df4a537ff972d7c4b3fd91ab06354969a4cfede0b9fc68bbffb61d0dbf1a58948dc701e54f5a", + "0x8cb5c2a6bd956875d88f41ae24574434f1308514d44057b55c9c70f13a3366ed054150eed0955a38fda3f757be73d55f", + "0x89ad0163cad93e24129d63f8e38422b7674632a8d0a9016ee8636184cab177659a676c4ee7efba3abe1a68807c656d60", + "0xb9ec01c7cab6d00359b5a0b4a1573467d09476e05ca51a9227cd16b589a9943d161eef62dcc73f0de2ec504d81f4d252", + "0x8031d17635d39dfe9705c485d2c94830b6fc9bc67b91300d9d2591b51e36a782e77ab5904662effa9382d9cca201f525", + "0x8be5a5f6bc8d680e5092d6f9a6585acbaaaa2ddc671da560dcf5cfa4472f4f184b9597b5b539438accd40dda885687cc", + "0xb1fc0f052fae038a2e3de3b3a96b0a1024b009de8457b8b3adb2d315ae68a89af905720108a30038e5ab8d0d97087785", + "0x8b8bdc77bd3a6bc7ca5492b6f8c614852c39a70d6c8a74916eaca0aeb4533b11898b8820a4c2620a97bf35e275480029", + "0xaf35f4dc538d4ad5cdf710caa38fd1eb496c3fa890a047b6a659619c5ad3054158371d1e88e0894428282eed9f47f76b", + "0x8166454a7089cc07758ad78724654f4e7a1a13e305bbf88ddb86f1a4b2904c4fc8ab872d7da364cdd6a6c0365239e2ad", + "0xab287c7d3addce74ce40491871c768abe01daaa0833481276ff2e56926b38a7c6d2681ffe837d2cc323045ad1a4414f9", + "0xb90317f4505793094d89365beb35537f55a6b5618904236258dd04ca61f21476837624a2f45fef8168acf732cab65579", + "0x98ae5ea27448e236b6657ab5ef7b1cccb5372f92ab25f5fa651fbac97d08353a1dae1b280b1cd42b17d2c6a70a63ab9d", + "0xadcf54e752d32cbaa6cb98fbca48d8cd087b1db1d131d465705a0d8042c8393c8f4d26b59006eb50129b21e6240f0c06", + "0xb591a3e4db18a7345fa935a8dd7994bbac5cc270b8ebd84c8304c44484c7a74afb45471fdbe4ab22156a30fae1149b40", + "0x806b53ac049a42f1dcc1d6335505371da0bf27c614f441b03bbf2e356be7b2fb4eed7117eabcce9e427a542eaa2bf7d8", + "0x800482e7a772d49210b81c4a907f5ce97f270b959e745621ee293cf8c71e8989363d61f66a98f2d16914439544ca84c7", + "0x99de9eafdad3617445312341644f2bb888680ff01ce95ca9276b1d2e5ef83fa02dab5e948ebf66c17df0752f1bd37b70", + "0x961ee30810aa4c93ae157fbe9009b8e443c082192bd36a73a6764ff9b2ad8b0948fe9a73344556e01399dd77badb4257", + "0xae0a361067c52efbe56c8adf982c00432cd478929459fc7f74052c8ee9531cd031fe1335418fde53f7c2ef34254eb7ac", + "0xa3503d16b6b27eb20c1b177bcf90d13706169220523a6271b85b2ce35a9a2b9c5bed088540031c0a4ebfdae3a4c6ab04", + "0x909420122c3e723289ca4e7b81c2df5aff312972a2203f4c45821b176e7c862bf9cac7f7df3adf1d59278f02694d06e7", + "0x989f42380ae904b982f85d0c6186c1aef5d6bcba29bcfbb658e811b587eb2749c65c6e4a8cc6409c229a107499a4f5d7", + "0x8037a6337195c8e26a27ea4ef218c6e7d79a9720aaab43932d343192abc2320fe72955f5e431c109093bda074103330a", + "0xb312e168663842099b88445e940249cc508f080ab0c94331f672e7760258dbd86be5267e4cf25ea25facb80bff82a7e9", + "0xaaa3ff8639496864fcdbfdda1ac97edc4f08e3c9288b768f6c8073038c9fbbf7e1c4bea169b4d45c31935cdf0680d45e", + "0x97dbd3df37f0b481a311dfc5f40e59227720f367912200d71908ef6650f32cc985cb05b981e3eea38958f7e48d10a15d", + "0xa89d49d1e267bb452d6cb621b9a90826fe55e9b489c0427b94442d02a16f390eed758e209991687f73f6b5a032321f42", + "0x9530dea4e0e19d6496f536f2e75cf7d814d65fde567055eb20db48fd8d20d501cd2a22fb506db566b94c9ee10f413d43", + "0x81a7009b9e67f1965fa7da6a57591c307de91bf0cd35ab4348dc4a98a4961e096d004d7e7ad318000011dc4342c1b809", + "0x83440a9402b766045d7aca61a58bba2aa29cac1cf718199e472ba086f5d48093d9dda4d135292ba51d049a23964eceae", + "0xa06c9ce5e802df14f6b064a3d1a0735d429b452f0e2e276042800b0a4f16df988fd94cf3945921d5dd3802ab2636f867", + "0xb1359e358b89936dee9e678a187aad3e9ab14ac40e96a0a68f70ee2583cdcf467ae03bef4215e92893f4e12f902adec8", + "0x835304f8619188b4d14674d803103d5a3fa594d48e96d9699e653115dd05fdc2dda6ba3641cf7ad53994d448da155f02", + "0x8327cba5a9ff0d3f5cd0ae55e77167448926d5fcf76550c0ad978092a14122723090c51c415e88e42a2b62eb07cc3981", + "0xb373dcdaea85f85ce9978b1426a7ef4945f65f2d3467a9f1cc551a99766aac95df4a09e2251d3f89ca8c9d1a7cfd7b0e", + "0xab1422dc41af2a227b973a6fd124dfcb2367e2a11a21faa1d381d404f51b7257e5bc82e9cf20cd7fe37d7ae761a2ab37", + "0xa93774a03519d2f20fdf2ef46547b0a5b77c137d6a3434b48d56a2cbef9e77120d1b85d0092cf8842909213826699477", + "0x8eb967a495a38130ea28711580b7e61bcd1d051cd9e4f2dbf62f1380bd86e0d60e978d72f6f31e909eb97b3b9a2b867c", + "0xae8213378da1287ba1fe4242e1acaec19b877b6fe872400013c6eac1084b8d03156792fa3020201725b08228a1e80f49", + "0xb143daf6893d674d607772b3b02d8ac48f294237e2f2c87963c0d4e26d9227d94a2a13512457c3d5883544bbc259f0ef", + "0xb343bd2aca8973888e42542218924e2dda2e938fd1150d06878af76f777546213912b7c7a34a0f94186817d80ffa185c", + "0xb188ebc6a8c3007001aa347ae72cc0b15d09bc6c19a80e386ee4b334734ec0cc2fe8b493c2422f38d1e6d133cc3db6fe", + "0xb795f6a8b9b826aaeee18ccd6baf6c5adeeec85f95eb5b6d19450085ec7217e95a2d9e221d77f583b297d0872073ba0e", + "0xb1c7dbd998ad32ae57bfa95deafa147024afd57389e98992c36b6e52df915d3d5a39db585141ec2423173e85d212fed8", + "0x812bcdeb9fe5f12d0e1df9964798056e1f1c3de3b17b6bd2919b6356c4b86d8e763c01933efbe0224c86a96d5198a4be", + "0xb19ebeda61c23d255cbf472ef0b8a441f4c55b70f0d8ed47078c248b1d3c7c62e076b43b95c00a958ec8b16d5a7cb0d7", + "0xb02adc9aaa20e0368a989c2af14ff48b67233d28ebee44ff3418bb0473592e6b681af1cc45450bd4b175df9051df63d9", + "0x8d87f0714acee522eb58cec00360e762adc411901dba46adc9227124fa70ee679f9a47e91a6306d6030dd4eb8de2f3c1", + "0x8be54cec21e74bcc71de29dc621444263737db15f16d0bb13670f64e42f818154e04b484593d19ef95f2ee17e4b3fe21", + "0xab8e20546c1db38d31493b5d5f535758afb17e459645c1b70813b1cf7d242fd5d1f4354a7c929e8f7259f6a25302e351", + "0x89f035a1ed8a1e302ac893349ba8ddf967580fcb6e73d44af09e3929cde445e97ff60c87dafe489e2c0ab9c9986cfa00", + "0x8b2b0851a795c19191a692af55f7e72ad2474efdc5401bc3733cfdd910e34c918aaebe69d5ea951bdddf3c01cabbfc67", + "0xa4edb52c2b51495ccd1ee6450fc14b7b3ede8b3d106808929d02fb31475bacb403e112ba9c818d2857651e508b3a7dd1", + "0x9569341fded45d19f00bcf3cbf3f20eb2b4d82ef92aba3c8abd95866398438a2387437e580d8b646f17cf6fde8c5af23", + "0xaa4b671c6d20f72f2f18a939a6ff21cc37e0084b44b4a717f1be859a80b39fb1be026b3205adec2a66a608ec2bcd578f", + "0x94902e980de23c4de394ad8aec91b46f888d18f045753541492bfbb92c59d3daa8de37ae755a6853744af8472ba7b72b", + "0xaf651ef1b2a0d30a7884557edfad95b6b5d445a7561caebdc46a485aedd25932c62c0798465c340a76f6feaa196dd712", + "0xb7b669b8e5a763452128846dd46b530dca4893ace5cc5881c7ddcd3d45969d7e73fbebdb0e78aa81686e5f7b22ec5759", + "0x82507fd4ebe9fa656a7f2e084d64a1fa6777a2b0bc106d686e2d9d2edafc58997e58cb6bfd0453b2bf415704aa82ae62", + "0xb40bce2b42b88678400ecd52955bbdadd15f8b9e1b3751a1a3375dc0efb5ca3ee258cf201e1140b3c09ad41217d1d49e", + "0xb0210d0cbb3fbf3b8cdb39e862f036b0ff941cd838e7aaf3a8354e24246e64778d22f3de34572e6b2a580614fb6425be", + "0x876693cba4301b251523c7d034108831df3ce133d8be5a514e7a2ca494c268ca0556fa2ad8310a1d92a16b55bcd99ea9", + "0x8660281406d22a4950f5ef050bf71dd3090edb16eff27fa29ef600cdea628315e2054211ed2cc6eaf8f2a1771ef689fd", + "0xa610e7e41e41ab66955b809ba4ade0330b8e9057d8efc9144753caed81995edeb1a42a53f93ce93540feca1fae708dac", + "0xa49e2c176a350251daef1218efaccc07a1e06203386ede59c136699d25ca5cb2ac1b800c25b28dd05678f14e78e51891", + "0x83e0915aa2b09359604566080d411874af8c993beba97d4547782fdbe1a68e59324b800ff1f07b8db30c71adcbd102a8", + "0xa19e84e3541fb6498e9bb8a099c495cbfcad113330e0262a7e4c6544495bb8a754b2208d0c2d895c93463558013a5a32", + "0x87f2bd49859a364912023aca7b19a592c60214b8d6239e2be887ae80b69ebdeb59742bdebcfa73a586ab23b2c945586c", + "0xb8e8fdddae934a14b57bc274b8dcd0d45ebb95ddbaabef4454e0f6ce7d3a5a61c86181929546b3d60c447a15134d08e1", + "0x87e0c31dcb736ea4604727e92dc1d9a3cf00adcff79df3546e02108355260f3dd171531c3c0f57be78d8b28058fcc8c0", + "0x9617d74e8f808a4165a8ac2e30878c349e1c3d40972006f0787b31ea62d248c2d9f3fc3da83181c6e57e95feedfd0e8c", + "0x8949e2cee582a2f8db86e89785a6e46bc1565c2d8627d5b6bf43ba71ffadfab7e3c5710f88dcb5fb2fc6edf6f4fae216", + "0xad3fa7b0edceb83118972a2935a09f409d09a8db3869f30be3a76f67aa9fb379cabb3a3aff805ba023a331cad7d7eb64", + "0x8c95718a4112512c4efbd496be38bf3ca6cdcaad8a0d128f32a3f9aae57f3a57bdf295a3b372a8c549fda8f4707cffed", + "0x88f3261d1e28a58b2dee3fcc799777ad1c0eb68b3560f9b4410d134672d9533532a91ea7be28a041784872632d3c9d80", + "0xb47472a41d72dd2e8b72f5c4f8ad626737dde3717f63d6bc776639ab299e564cbad0a2ad5452a07f02ff49a359c437e5", + "0x9896d21dc2e8aad87b76d6df1654f10cd7bceed4884159d50a818bea391f8e473e01e14684814c7780235f28e69dca6e", + "0x82d47c332bbd31bbe83b5eb44a23da76d4a7a06c45d7f80f395035822bc27f62f59281d5174e6f8e77cc9b5c3193d6f0", + "0x95c74cd46206e7f70c9766117c34c0ec45c2b0f927a15ea167901a160e1530d8522943c29b61e03568aa0f9c55926c53", + "0xa89d7757825ae73a6e81829ff788ea7b3d7409857b378ebccd7df73fdbe62c8d9073741cf038314971b39af6c29c9030", + "0x8c1cd212d0b010905d560688cfc036ae6535bc334fa8b812519d810b7e7dcf1bb7c5f43deaa40f097158358987324a7f", + "0xb86993c383c015ed8d847c6b795164114dd3e9efd25143f509da318bfba89389ea72a420699e339423afd68b6512fafb", + "0x8d06bd379c6d87c6ed841d8c6e9d2d0de21653a073725ff74be1934301cc3a79b81ef6dd0aad4e7a9dc6eac9b73019bc", + "0x81af4d2d87219985b9b1202d724fe39ef988f14fef07dfe3c3b11714e90ffba2a97250838e8535eb63f107abfe645e96", + "0x8c5e0af6330a8becb787e4b502f34f528ef5756e298a77dc0c7467433454347f3a2e0bd2641fbc2a45b95e231c6e1c02", + "0x8e2a8f0f04562820dc8e7da681d5cad9fe2e85dd11c785fb6fba6786c57a857e0b3bd838fb849b0376c34ce1665e4837", + "0xa39be8269449bfdfc61b1f62077033649f18dae9bef7c6163b9314ca8923691fb832f42776f0160b9e8abd4d143aa4e1", + "0x8c154e665706355e1cc98e0a4cabf294ab019545ba9c4c399d666e6ec5c869ca9e1faf8fb06cd9c0a5c2f51a7d51b70a", + "0xa046a7d4de879d3ebd4284f08f24398e9e3bf006cd4e25b5c67273ade248689c69affff92ae810c07941e4904296a563", + "0xafd94c1cb48758e5917804df03fb38a6da0e48cd9b6262413ea13b26973f9e266690a1b7d9d24bbaf7e82718e0e594b0", + "0x859e21080310c8d6a38e12e2ac9f90a156578cdeb4bb2e324700e97d9a5511cd6045dc39d1d0de3f94aeed043a24119d", + "0xa219fb0303c379d0ab50893264919f598e753aac9065e1f23ef2949abc992577ab43c636a1d2c089203ec9ddb941e27d", + "0xb0fdb639d449588a2ca730afcba59334e7c387342d56defdfb7ef79c493f7fd0e5277eff18e7203e756c7bdda5803047", + "0x87f9c3b7ed01f54368aca6dbcf2f6e06bff96e183c4b2c65f8baa23b377988863a0a125d5cdd41a072da8462ced4c070", + "0x99ef7a5d5ac2f1c567160e1f8c95f2f38d41881850f30c461a205f7b1b9fb181277311333839b13fb3ae203447e17727", + "0xaeaca9b1c2afd24e443326cc68de67b4d9cedb22ad7b501a799d30d39c85bb2ea910d4672673e39e154d699e12d9b3dc", + "0xa11675a1721a4ba24dd3d0e4c3c33a6edf4cd1b9f6b471070b4386c61f77452266eae6e3f566a40cfc885eada9a29f23", + "0xb228334445e37b9b49cb4f2cc56b454575e92173ddb01370a553bba665adadd52df353ad74470d512561c2c3473c7bb9", + "0xa18177087c996572d76f81178d18ed1ceebc8362a396348ce289f1d8bd708b9e99539be6fccd4acb1112381cfc5749b4", + "0x8e7b8bf460f0d3c99abb19803b9e43422e91507a1c0c22b29ee8b2c52d1a384da4b87c292e28eff040db5be7b1f8641f", + "0xb03d038d813e29688b6e6f444eb56fec3abba64c3d6f890a6bcf2e916507091cdb2b9d2c7484617be6b26552ed1c56cb", + "0xa1c88ccd30e934adfc5494b72655f8afe1865a84196abfb376968f22ddc07761210b6a9fb7638f1413d1b4073d430290", + "0x961b714faebf172ad2dbc11902461e286e4f24a99a939152a53406117767682a571057044decbeb3d3feef81f4488497", + "0xa03dc4059b46effdd786a0a03cc17cfee8585683faa35bb07936ded3fa3f3a097f518c0b8e2db92fd700149db1937789", + "0xadf60180c99ca574191cbcc23e8d025b2f931f98ca7dfcebfc380226239b6329347100fcb8b0fcb12db108c6ad101c07", + "0x805d4f5ef24d46911cbf942f62cb84b0346e5e712284f82b0db223db26d51aabf43204755eb19519b00e665c7719fcaa", + "0x8dea7243e9c139662a7fe3526c6c601eee72fd8847c54c8e1f2ad93ef7f9e1826b170afe58817dac212427164a88e87f", + "0xa2ba42356606d651b077983de1ad643650997bb2babb188c9a3b27245bb65d2036e46667c37d4ce02cb1be5ae8547abe", + "0xaf2ae50b392bdc013db2d12ce2544883472d72424fc767d3f5cb0ca2d973fc7d1f425880101e61970e1a988d0670c81b", + "0x98e6bec0568d3939b31d00eb1040e9b8b2a35db46ddf4369bdaee41bbb63cc84423d29ee510a170fb5b0e2df434ba589", + "0x822ff3cd12fbef4f508f3ca813c04a2e0b9b799c99848e5ad3563265979e753ee61a48f6adc2984a850f1b46c1a43d35", + "0x891e8b8b92a394f36653d55725ef514bd2e2a46840a0a2975c76c2a935577f85289026aaa74384da0afe26775cbddfb9", + "0xb2a3131a5d2fe7c8967047aa66e4524babae941d90552171cc109527f345f42aa0df06dcbb2fa01b33d0043917bbed69", + "0x80c869469900431f3eeefafdbe07b8afd8cee7739e659e6d0109b397cacff85a88247698f87dc4e2fe39a592f250ac64", + "0x9091594f488b38f9d2bb5df49fd8b4f8829d9c2f11a197dd1431ed5abbc5c954bbde3387088f9ee3a5a834beb7619bce", + "0xb472e241e6956146cca57b97a8a204668d050423b4e76f857bad5b47f43b203a04c8391ba9d9c3e95093c071f9d376a1", + "0xb7dd2de0284844392f7dfb56fe7ca3ede41e27519753ffc579a0a8d2d65ceb8108d06b6b0d4c3c1a2588951297bd1a1e", + "0x902116ce70d0a079ac190321c1f48701318c05f8e69ee09694754885d33a835a849cafe56f499a2f49f6cda413ddf9a7", + "0xb18105cc736787fafaf7c3c11c448bce9466e683159dff52723b7951dff429565e466e4841d982e3aaa9ee2066838666", + "0x97ab9911f3f659691762d568ae0b7faa1047b0aed1009c319fa79d15d0db8db9f808fc385dc9a68fa388c10224985379", + "0xb2a2cba65f5b927e64d2904ba412e2bac1cf18c9c3eda9c72fb70262497ecf505b640827e2afebecf10eebbcf48ccd3e", + "0xb36a3fd677baa0d3ef0dac4f1548ff50a1730286b8c99d276a0a45d576e17b39b3cbadd2fe55e003796d370d4be43ce3", + "0xa5dfec96ca3c272566e89dc453a458909247e3895d3e44831528130bc47cc9d0a0dac78dd3cad680a4351d399d241967", + "0x8029382113909af6340959c3e61db27392531d62d90f92370a432aec3eb1e4c36ae1d4ef2ba8ec6edb4d7320c7a453f6", + "0x971d85121ea108e6769d54f9c51299b0381ece8b51d46d49c89f65bedc123bab4d5a8bc14d6f67f4f680077529cbae4c", + "0x98ff6afc01d0bec80a278f25912e1b1ebff80117adae72e31d5b9fa4d9624db4ba2065b444df49b489b0607c45e26c4c", + "0x8fa29be10fb3ab30ce25920fec0187e6e91e458947009dabb869aade7136c8ba23602682b71e390c251f3743164cbdaa", + "0xb3345c89eb1653418fe3940cf3e56a9a9c66526389b98f45ca02dd62bfb37baa69a4baaa7132d7320695f8ea6ad1fd94", + "0xb72c7f5541c9ac6b60a7ec9f5415e7fb14da03f7164ea529952a29399f3a071576608dbbcc0d45994f21f92ddbeb1e19", + "0xaa3450bb155a5f9043d0ef95f546a2e6ade167280bfb75c9f09c6f9cdb1fffb7ce8181436161a538433afa3681c7a141", + "0x92a18fecaded7854b349f441e7102b638ababa75b1b0281dd0bded6541abe7aa37d96693595be0b01fe0a2e2133d50f9", + "0x980756ddf9d2253cfe6c94960b516c94889d09e612810935150892627d2ecee9a2517e04968eea295d0106850c04ca44", + "0xae68c6ccc454318cdd92f32b11d89116a3b8350207a36d22a0f626718cad671d960090e054c0c77ac3162ae180ecfd4b", + "0x99f31f66eaaa551749ad91d48a0d4e3ff4d82ef0e8b28f3184c54e852422ba1bdafd53b1e753f3a070f3b55f3c23b6a2", + "0xa44eaeaa6589206069e9c0a45ff9fc51c68da38d4edff1d15529b7932e6f403d12b9387019c44a1488a5d5f27782a51f", + "0xb80b5d54d4b344840e45b79e621bd77a3f83fb4ce6d8796b7d6915107b3f3c34d2e7d95bdafd120f285669e5acf2437a", + "0xb36c069ec085a612b5908314d6b84c00a83031780261d1c77a0384c406867c9847d5b0845deddfa512cc04a8df2046fb", + "0xb09dbe501583220f640d201acea7ee3e39bf9eda8b91aa07b5c50b7641d86d71acb619b38d27835ce97c3759787f08e9", + "0x87403d46a2bf63170fff0b857acacf42ee801afe9ccba8e5b4aea967b68eac73a499a65ca46906c2eb4c8f27bc739faa", + "0x82b93669f42a0a2aa5e250ffe6097269da06a9c02fcd1801abbad415a7729a64f830754bafc702e64600ba47671c2208", + "0x8e3a3029be7edb8dd3ab1f8216664c8dc50d395f603736061d802cef77627db7b859ef287ed850382c13b4d22d6a2d80", + "0x968e9ec7194ff424409d182ce0259acd950c384c163c04463bc8700a40b79beba6146d22b7fa7016875a249b7b31c602", + "0x8b42c984bbe4996e0c20862059167c6bdc5164b1ffcd928f29512664459212d263e89f0f0e30eed4e672ffa5ed0b01b5", + "0x96bac54062110dada905363211133f1f15dc7e4fd80a4c6e4a83bc9a0bcbbaba11cd2c7a13debcf0985e1a954c1da66b", + "0xa16dc8a653d67a7cd7ae90b2fffac0bf1ca587005430fe5ba9403edd70ca33e38ba5661d2ed6e9d2864400d997626a62", + "0xa68ab11a570a27853c8d67e491591dcba746bfbee08a2e75ae0790399130d027ed387f41ef1d7de8df38b472df309161", + "0x92532b74886874447c0300d07eda9bbe4b41ed25349a3da2e072a93fe32c89d280f740d8ff70d5816793d7f2b97373cc", + "0x88e35711b471e89218fd5f4d0eadea8a29405af1cd81974427bc4a5fb26ed60798daaf94f726c96e779b403a2cd82820", + "0xb5c72aa4147c19f8c4f3a0a62d32315b0f4606e0a7025edc5445571eaf4daff64f4b7a585464821574dd50dbe1b49d08", + "0x9305d9b4095258e79744338683fd93f9e657367b3ab32d78080e51d54eec331edbc224fad5093ebf8ee4bd4286757eb8", + "0xb2a17abb3f6a05bcb14dc7b98321fa8b46d299626c73d7c6eb12140bf4c3f8e1795250870947af817834f033c88a59d6", + "0xb3477004837dbd8ba594e4296f960fc91ab3f13551458445e6c232eb04b326da803c4d93e2e8dcd268b4413305ff84da", + "0x924b4b2ebaafdcfdfedb2829a8bf46cd32e1407d8d725a5bd28bdc821f1bafb3614f030ea4352c671076a63494275a3f", + "0x8b81b9ef6125c82a9bece6fdcb9888a767ac16e70527753428cc87c56a1236e437da8be4f7ecfe57b9296dc3ae7ba807", + "0x906e19ec8b8edd58bdf9ae05610a86e4ea2282b1bbc1e8b00b7021d093194e0837d74cf27ac9916bdb8ec308b00da3da", + "0xb41c5185869071760ac786078a57a2ab4e2af60a890037ac0c0c28d6826f15c2cf028fddd42a9b6de632c3d550bfbc14", + "0xa646e5dec1b713ae9dfdf7bdc6cd474d5731a320403c7dfcfd666ffc9ae0cff4b5a79530e8df3f4aa9cb80568cb138e9", + "0xb0efad22827e562bd3c3e925acbd0d9425d19057868608d78c2209a531cccd0f2c43dc5673acf9822247428ffa2bb821", + "0xa94c19468d14b6f99002fc52ac06bbe59e5c472e4a0cdb225144a62f8870b3f10593749df7a2de0bd3c9476ce682e148", + "0x803864a91162f0273d49271dafaab632d93d494d1af935aefa522768af058fce52165018512e8d6774976d52bd797e22", + "0xa08711c2f7d45c68fb340ac23597332e1bcaec9198f72967b9921204b9d48a7843561ff318f87908c05a44fc35e3cc9d", + "0x91c3cad94a11a3197ae4f9461faab91a669e0dddb0371d3cab3ed9aeb1267badc797d8375181130e461eadd05099b2a2", + "0x81bdaaf48aae4f7b480fc13f1e7f4dd3023a41439ba231760409ce9292c11128ab2b0bdbbf28b98af4f97b3551f363af", + "0x8d60f9df9fd303f625af90e8272c4ecb95bb94e6efc5da17b8ab663ee3b3f673e9f6420d890ccc94acf4d2cae7a860d8", + "0xa7b75901520c06e9495ab983f70b61483504c7ff2a0980c51115d11e0744683ce022d76e3e09f4e99e698cbd21432a0d", + "0x82956072df0586562fda7e7738226f694e1c73518dd86e0799d2e820d7f79233667192c9236dcb27637e4c65ef19d493", + "0xa586beb9b6ffd06ad200957490803a7cd8c9bf76e782734e0f55e04a3dc38949de75dc607822ec405736c576cf83bca3", + "0xa179a30d00def9b34a7e85607a447eea0401e32ab5abeee1a281f2acd1cf6ec81a178020666f641d9492b1bdf66f05a3", + "0x83e129705c538787ed8e0fdc1275e6466a3f4ee21a1e6abedd239393b1df72244723b92f9d9d9339a0cab6ebf28f5a16", + "0x811bd8d1e3722b64cd2f5b431167e7f91456e8bba2cc669d3fbbce7d553e29c3c19f629fcedd2498bc26d33a24891d17", + "0xa243c030c858f1f60cccd26b45b024698cc6d9d9e6198c1ed4964a235d9f8d0baf9cde10c8e63dfaa47f8e74e51a6e85", + "0xab839eb82e23ca52663281f863b55b0a3d6d4425c33ffb4eeb1d7979488ab068bf99e2a60e82cea4dc42c56c26cbfebe", + "0x8b896f9bb21d49343e67aec6ad175b58c0c81a3ca73d44d113ae4354a0065d98eb1a5cafedaf232a2bb9cdc62152f309", + "0xaf6230340cc0b66f5bf845540ed4fc3e7d6077f361d60762e488d57834c3e7eb7eacc1b0ed73a7d134f174a01410e50c", + "0x88975e1b1af678d1b5179f72300a30900736af580dd748fd9461ef7afccc91ccd9bed33f9da55c8711a7635b800e831f", + "0xa97486bb9047391661718a54b8dd5a5e363964e495eae6c692730264478c927cf3e66dd3602413189a3699fbeae26e15", + "0xa5973c161ab38732885d1d2785fd74bf156ba34881980cba27fe239caef06b24a533ffe6dbbbeca5e6566682cc00300a", + "0xa24776e9a840afda0003fa73b415d5bd6ecd9b5c2cc842b643ee51b8c6087f4eead4d0bfbd987eb174c489a7b952ff2a", + "0xa8a6ee06e3af053b705a12b59777267c546f33ba8a0f49493af8e6df4e15cf8dd2d4fb4daf7e84c6b5d3a7363118ff03", + "0xa28e59ce6ad02c2ce725067c0123117e12ac5a52c8f5af13eec75f4a9efc4f696777db18a374fa33bcae82e0734ebd16", + "0x86dfc3b78e841c708aff677baa8ee654c808e5d257158715097c1025d46ece94993efe12c9d188252ad98a1e0e331fec", + "0xa88d0275510f242eab11fdb0410ff6e1b9d7a3cbd3658333539815f1b450a84816e6613d15aa8a8eb15d87cdad4b27a2", + "0x8440acea2931118a5b481268ff9f180ee4ede85d14a52c026adc882410825b8275caa44aff0b50c2b88d39f21b1a0696", + "0xa7c3182eab25bd6785bacf12079d0afb0a9b165d6ed327814e2177148539f249eb9b5b2554538f54f3c882d37c0a8abe", + "0x85291fbe10538d7da38efdd55a7acebf03b1848428a2f664c3ce55367aece60039f4f320b1771c9c89a35941797f717c", + "0xa2c6414eeb1234728ab0de94aa98fc06433a58efa646ca3fcbd97dbfb8d98ae59f7ce6d528f669c8149e1e13266f69c9", + "0x840c8462785591ee93aee2538d9f1ec44ba2ca61a569ab51d335ac873f5d48099ae8d7a7efa0725d9ff8f9475bfa4f56", + "0xa7065a9d02fb3673acf7702a488fbc01aa69580964932f6f40b6c2d1c386b19e50b0e104fcac24ea26c4e723611d0238", + "0xb72db6d141267438279e032c95e6106c2ccb3164b842ba857a2018f3a35f4b040da92680881eb17cd61d0920d5b8f006", + "0xa8005d6c5960e090374747307ef0be2871a7a43fa4e76a16c35d2baab808e9777b496e9f57a4218b23390887c33a0b55", + "0x8e152cea1e00a451ca47c20a1e8875873419700af15a5f38ee2268d3fbc974d4bd5f4be38008fa6f404dbdedd6e6e710", + "0xa3391aed1fcd68761f06a7d1008ec62a09b1cb3d0203cd04e300a0c91adfed1812d8bc1e4a3fd7976dc0aae0e99f52f1", + "0x967eb57bf2aa503ee0c6e67438098149eac305089c155f1762cf5e84e31f0fbf27c34a9af05621e34645c1ec96afaec8", + "0x88af97ddc4937a95ec0dcd25e4173127260f91c8db2f6eac84afb789b363705fb3196235af631c70cafd09411d233589", + "0xa32df75b3f2c921b8767638fd289bcfc61e08597170186637a7128ffedd52c798c434485ac2c7de07014f9e895c2c3d8", + "0xb0a783832153650aa0d766a3a73ec208b6ce5caeb40b87177ffc035ab03c7705ecdd1090b6456a29f5fb7e90e2fa8930", + "0xb59c8e803b4c3486777d15fc2311b97f9ded1602fa570c7b0200bada36a49ee9ef4d4c1474265af8e1c38a93eb66b18b", + "0x982f2c85f83e852022998ff91bafbb6ff093ef22cf9d5063e083a48b29175ccbd51b9c6557151409e439096300981a6c", + "0x939e3b5989fefebb9d272a954659a4eb125b98c9da6953f5e628d26266bd0525ec38304b8d56f08d65abc4d6da4a8dbb", + "0x8898212fe05bc8de7d18503cb84a1c1337cc2c09d1eeef2b475aa79185b7322bf1f8e065f1bf871c0c927dd19faf1f6d", + "0x94b0393a41cd00f724aee2d4bc72103d626a5aecb4b5486dd1ef8ac27528398edf56df9db5c3d238d8579af368afeb09", + "0x96ac564450d998e7445dd2ea8e3fc7974d575508fa19e1c60c308d83b645864c029f2f6b7396d4ff4c1b24e92e3bac37", + "0x8adf6638e18aff3eb3b47617da696eb6c4bdfbecbbc3c45d3d0ab0b12cbad00e462fdfbe0c35780d21aa973fc150285e", + "0xb53f94612f818571b5565bbb295e74bada9b5f9794b3b91125915e44d6ddcc4da25510eab718e251a09c99534d6042d9", + "0x8b96462508d77ee083c376cd90807aebad8de96bca43983c84a4a6f196d5faf6619a2351f43bfeec101864c3bf255519", + "0xaeadf34657083fc71df33bd44af73bf5281c9ca6d906b9c745536e1819ea90b56107c55e2178ebad08f3ba75b3f81c86", + "0x9784ba29b2f0057b5af1d3ab2796d439b8753f1f749c73e791037461bdfc3f7097394283105b8ab01788ea5255a96710", + "0x8756241bda159d4a33bf74faba0d4594d963c370fb6a18431f279b4a865b070b0547a6d1613cf45b8cfb5f9236bbf831", + "0xb03ebfd6b71421dfd49a30460f9f57063eebfe31b9ceaa2a05c37c61522b35bdc09d7db3ad75c76c253c00ba282d3cd2", + "0xb34e7e6341fa9d854b2d3153bdda0c4ae2b2f442ab7af6f99a0975d45725aa48e36ae5f7011edd249862e91f499687d4", + "0xb462ee09dc3963a14354244313e3444de5cc37ea5ccfbf14cd9aca8027b59c4cb2a949bc30474497cab8123e768460e6", + "0xaea753290e51e2f6a21a9a0ee67d3a2713f95c2a5c17fe41116c87d3aa77b1683761264d704df1ac34f8b873bc88ef7b", + "0x98430592afd414394f98ddfff9f280fcb1c322dbe3510f45e1e9c4bb8ee306b3e0cf0282c0ee73ebb8ba087d4d9e0858", + "0xb95d3b5aaf54ffca11f4be8d57f76e14afdb20afc859dc7c7471e0b42031e8f3d461b726ecb979bdb2f353498dfe95ea", + "0x984d17f9b11a683132e0b5a9ee5945e3ff7054c2d5c716be73b29078db1d36f54c6e652fd2f52a19da313112e97ade07", + "0xab232f756b3fff3262be418a1af61a7e0c95ceebbc775389622a8e10610508cd6784ab7960441917a83cc191c58829ea", + "0xa28f41678d6e60de76b0e36ab10e4516e53e02e9c77d2b5af3cfeee3ce94cfa30c5797bd1daab20c98e1cad83ad0f633", + "0xb55395fca84dd3ccc05dd480cb9b430bf8631ff06e24cb51d54519703d667268c2f8afcde4ba4ed16bece8cc7bc8c6e0", + "0x8a8a5392a0e2ea3c7a8c51328fab11156004e84a9c63483b64e8f8ebf18a58b6ffa8fe8b9d95af0a2f655f601d096396", + "0xab480000fe194d23f08a7a9ec1c392334e9c687e06851f083845121ce502c06b54dda8c43092bcc1035df45cc752fe9b", + "0xb265644c29f628d1c7e8e25a5e845cabb21799371814730a41a363e1bda8a7be50fee7c3996a365b7fcba4642add10db", + "0xb8a915a3c685c2d4728f6931c4d29487cad764c5ce23c25e64b1a3259ac27235e41b23bfe7ae982921b4cb84463097df", + "0x8efa7338442a4b6318145a5440fc213b97869647eeae41b9aa3c0a27ee51285b73e3ae3b4a9423df255e6add58864aa9", + "0x9106d65444f74d217f4187dfc8fcf3810b916d1e4275f94f6a86d1c4f3565b131fd6cde1fa708bc05fe183c49f14941a", + "0x948252dac8026bbbdb0a06b3c9d66ec4cf9532163bab68076fda1bd2357b69e4b514729c15aaa83b5618b1977bbc60c4", + "0xae6596ccfdf5cbbc5782efe3bb0b101bb132dbe1d568854ca24cacc0b2e0e9fabcb2ca7ab42aecec412efd15cf8cb7a2", + "0x84a0b6c198ff64fd7958dfd1b40eac9638e8e0b2c4cd8cf5d8cdf80419baee76a05184bce6c5b635f6bf2d30055476a7", + "0x8893118be4a055c2b3da593dbca51b1ae2ea2469911acfb27ee42faf3e6c3ad0693d3914c508c0b05b36a88c8b312b76", + "0xb097479e967504deb6734785db7e60d1d8034d6ca5ba9552887e937f5e17bb413fccac2c1d1082154ed76609127860ad", + "0xa0294e6b9958f244d29943debf24b00b538b3da1116269b6e452bb12dc742226712fd1a15b9c88195afeb5d2415f505c", + "0xb3cc15f635080bc038f61b615f62b5b5c6f2870586191f59476e8368a73641d6ac2f7d0c1f54621982defdb318020230", + "0x99856f49b9fe1604d917c94d09cc0ed753d13d015d30587a94e6631ffd964b214e607deb8a69a8b5e349a7edf4309206", + "0xa8571e113ea22b4b4fce41a094da8c70de37830ae32e62c65c2fa5ad06a9bc29e884b945e73d448c72b176d6ecebfb58", + "0xa9e9c6e52beb0013273c29844956b3ce291023678107cdc785f7b44eff5003462841ad8780761b86aefc6b734adde7cf", + "0x80a784b0b27edb51ef2bad3aee80e51778dcaa0f3f5d3dcb5dc5d4f4b2cf7ae35b08de6680ea9dac53f8438b92eb09ef", + "0x827b543e609ea328e97e373f70ad72d4915a2d1daae0c60d44ac637231070e164c43a2a58db80a64df1c624a042b38f9", + "0xb449c65e8195202efdcb9bdb4e869a437313b118fef8b510cbbf8b79a4e99376adb749b37e9c20b51b31ed3310169e27", + "0x8ea3028f4548a79a94c717e1ed28ad4d8725b8d6ab18b021063ce46f665c79da3c49440c6577319dab2d036b7e08f387", + "0x897798431cfb17fe39f08f5f854005dc37b1c1ec1edba6c24bc8acb3b88838d0534a75475325a5ea98b326ad47dbad75", + "0x89cf232e6303b0751561960fd4dea5754a28c594daf930326b4541274ffb03c7dd75938e411eb9a375006a70ce38097f", + "0x9727c6ae7f0840f0b6c8bfb3a1a5582ceee705e0b5c59b97def7a7a2283edd4d3f47b7971e902a3a2079e40b53ff69b8", + "0xb76ed72b122c48679d221072efc0eeea063cb205cbf5f9ef0101fd10cb1075b8628166c83577cced654e1c001c7882f7", + "0xae908c42d208759da5ee9b405df85a6532ea35c6f0f6a1288d22870f59d98edc896841b8ac890a538e6c8d1e8b02d359", + "0x809d12fe4039a0ec80dc9be6a89acaab7797e5f7f9b163378f52f9a75a1d73b2e9ae6e3dd49e32ced439783c1cabbef5", + "0xa4149530b7f85d1098ba534d69548c6c612c416e8d35992fc1f64f4deeb41e09e49c6cf7aadbed7e846b91299358fe2d", + "0xa49342eacd1ec1148b8df1e253b1c015f603c39de11fa0a364ccb86ea32d69c34fd7aa6980a1fadcd8e785a57fa46f60", + "0x87d43eff5a006dc4dddcf76cc96c656a1f3a68f19f124181feab86c6cc9a52cb9189cdbb423414defdd9bb0ca8ff1ddc", + "0x861367e87a9aa2f0f68296ba50aa5dbc5713008d260cc2c7e62d407c2063064749324c4e8156dc21b749656cfebce26b", + "0xb5303c2f72e84e170e66ae1b0fbd51b8c7a6f27476eaf5694b64e8737d5c84b51fe90100b256465a4c4156dd873cddb0", + "0xb62849a4f891415d74f434cdc1d23c4a69074487659ca96e1762466b2b7a5d8525b056b891d0feea6fe6845cba8bc7fb", + "0x923dd9e0d6590a9307e8c4c23f13bae3306b580e297a937711a8b13e8de85e41a61462f25b7d352b682e8437bf2b4ab3", + "0x9147379860cd713cd46c94b8cdf75125d36c37517fbecf81ace9680b98ce6291cd1c3e472f84249cc3b2b445e314b1b6", + "0xa808a4f17ac21e3fb5cfef404e61fae3693ca3e688d375f99b6116779696059a146c27b06de3ac36da349b0649befd56", + "0x87787e9322e1b75e66c1f0d9ea0915722a232770930c2d2a95e9478c4b950d15ab767e30cea128f9ed65893bfc2d0743", + "0x9036a6ee2577223be105defe1081c48ea7319e112fff9110eb9f61110c319da25a6cea0464ce65e858635b079691ef1f", + "0xaf5548c7c24e1088c23b57ee14d26c12a83484c9fd9296edf1012d8dcf88243f20039b43c8c548c265ef9a1ffe9c1c88", + "0xa0fff520045e14065965fb8accd17e878d3fcaf9e0af2962c8954e50be6683d31fa0bf4816ab68f08630dbac6bfce52a", + "0xb4c1b249e079f6ae1781af1d97a60b15855f49864c50496c09c91fe1946266915b799f0406084d7783f5b1039116dd8b", + "0x8b0ffa5e7c498cb3879dddca34743b41eee8e2dea3d4317a6e961b58adb699ef0c92400c068d5228881a2b08121226bf", + "0x852ae8b19a1d80aa8ae5382e7ee5c8e7670ceb16640871c56b20b96b66b3b60e00015a3dde039446972e57b49a999ddd", + "0xa49942f04234a7d8492169da232cfff8051df86e8e1ba3db46aede02422c689c87dc1d99699c25f96cb763f5ca0983e5", + "0xb04b597b7760cf5dcf411ef896d1661e6d5b0db3257ac2cf64b20b60c6cc18fa10523bb958a48d010b55bac7b02ab3b1", + "0xa494591b51ea8285daecc194b5e5bd45ae35767d0246ac94fae204d674ee180c8e97ff15f71f28b7aeb175b8aea59710", + "0x97d2624919e78406e7460730680dea8e71c8571cf988e11441aeea54512b95bd820e78562c99372d535d96f7e200d20d", + "0xac693ddb00e48f76e667243b9b6a7008424043fb779e4f2252330285232c3fccac4da25cbd6d95fe9ad959ff305a91f6", + "0x8d20ca0a71a64a3f702a0825bb46bd810d03bebfb227683680d474a52f965716ff99e19a165ebaf6567987f4f9ee3c94", + "0xa5c516a438f916d1d68ca76996404792e0a66e97b7f18fc54c917bf10cf3211b62387932756e39e67e47b0bd6e88385a", + "0xb089614d830abc0afa435034cec7f851f2f095d479cacf1a3fb57272da826c499a52e7dcbc0eb85f4166fb94778e18e9", + "0xa8dacc943765d930848288192f4c69e2461c4b9bc6e79e30eeef9a543318cf9ae9569d6986c65c5668a89d49993f8e07", + "0xab5a9361fa339eec8c621bdad0a58078983abd8942d4282b22835d7a3a47e132d42414b7c359694986f7db39386c2e19", + "0x94230517fb57bd8eb26c6f64129b8b2abd0282323bf7b94b8bac7fab27b4ecc2c4290c294275e1a759de19f2216134f3", + "0xb8f158ea5006bc3b90b285246625faaa6ac9b5f5030dc69701b12f3b79a53ec7e92eeb5a63bbd1f9509a0a3469ff3ffc", + "0x8b6944fd8cb8540957a91a142fdcda827762aa777a31e8810ca6d026e50370ee1636fc351724767e817ca38804ebe005", + "0x82d1ee40fe1569c29644f79fa6c4033b7ed45cd2c3b343881f6eb0de2e79548fded4787fae19bed6ee76ed76ff9f2f11", + "0xa8924c7035e99eaed244ca165607e7e568b6c8085510dcdbaf6ebdbed405af2e6c14ee27d94ffef10d30aa52a60bf66d", + "0x956f82a6c2ae044635e85812581e4866c5fa2f427b01942047d81f6d79a14192f66fbbe77c9ffeaef4e6147097fdd2b5", + "0xb1100255a1bcf5e05b6aff1dfeb6e1d55b5d68d43a7457ba10cc76b61885f67f4d0d5179abda786e037ae95deb8eea45", + "0x99510799025e3e5e8fbf06dedb14c060c6548ba2bda824f687d3999dc395e794b1fb6514b9013f3892b6cf65cb0d65aa", + "0x8f9091cebf5e9c809aab415942172258f894e66e625d7388a05289183f01b8d994d52e05a8e69f784fba41db9ea357f0", + "0xa13d2eeb0776bdee9820ecb6693536720232848c51936bb4ef4fe65588d3f920d08a21907e1fdb881c1ad70b3725e726", + "0xa68b8f18922d550284c5e5dc2dda771f24c21965a6a4d5e7a71678178f46df4d8a421497aad8fcb4c7e241aba26378a0", + "0x8b7601f0a3c6ad27f03f2d23e785c81c1460d60100f91ea9d1cab978aa03b523150206c6d52ce7c7769c71d2c8228e9e", + "0xa8e02926430813caa851bb2b46de7f0420f0a64eb5f6b805401c11c9091d3b6d67d841b5674fa2b1dce0867714124cd8", + "0xb7968ecba568b8193b3058400af02c183f0a6df995a744450b3f7e0af7a772454677c3857f99c140bbdb2a09e832e8e0", + "0x8f20b1e9ba87d0a3f35309b985f3c18d2e8800f1ca7f0c52cadef773f1496b6070c936eea48c4a1cae83fd2524e9d233", + "0x88aef260042db0d641a51f40639dbeeefa9e9811df30bee695f3791f88a2f84d318f04e8926b7f47bf25956cb9e3754f", + "0x9725345893b647e9ba4e6a29e12f96751f1ae25fcaec2173e9a259921a1a7edb7a47159b3c8767e44d9e2689f5aa0f72", + "0x8c281e6f72752cb11e239e4df9341c45106eb7993c160e54423c2bffe10bc39d42624b45a1f673936ef2e1a02fc92f1a", + "0x90aba2f68bddb2fcce6c51430dacdfeec43ea8dc379660c99095df11017691ccf5faa27665cf4b9f0eea7728ae53c327", + "0xb7022695c16521c5704f49b7ddbdbec9b5f57ce0ceebe537bc0ebb0906d8196cc855a9afeb8950a1710f6a654464d93f", + "0x8fe1b9dd3c6a258116415d36e08374e094b22f0afb104385a5da48be17123e86fb8327baacc4f0d9ebae923d55d99bb5", + "0x817e85d8e3d19a4cbc1dec31597142c2daa4871bda89c2177fa719c00eda3344eb08b82eb92d4aa91a9eaacb3fc09783", + "0xb59053e1081d2603f1ca0ba553804d6fa696e1fd996631db8f62087b26a40dfef02098b0326bb75f99ec83b9267ca738", + "0x990a173d857d3ba81ff3789b931bfc9f5609cde0169b7f055fa3cb56451748d593d62d46ba33f80f9cafffe02b68dd14", + "0xb0c538dbba4954b809ab26f9f94a3cf1dcb77ce289eaec1d19f556c0ae4be1fa03af4a9b7057837541c3cc0a80538736", + "0xac3ba42f5f44f9e1fc453ce49c4ab79d0e1d5c42d3b30b1e098f3ab3f414c4c262fa12fb2be249f52d4aaf3c5224beb9", + "0xaf47467eb152e59870e21f0d4da2f43e093daf40180ab01438030684b114d025326928eaab12c41b81a066d94fce8436", + "0x98d1b58ba22e7289b1c45c79a24624f19b1d89e00f778eef327ec4856a9a897278e6f1a9a7e673844b31dde949153000", + "0x97ccb15dfadc7c59dca08cfe0d22df2e52c684cf97de1d94bc00d7ba24e020025130b0a39c0f4d46e4fc872771ee7875", + "0xb699e4ed9a000ff96ca296b2f09dce278832bc8ac96851ff3cff99ed3f6f752cfc0fea8571be28cd9b5a7ec36f1a08ee", + "0xb9f49f0edb7941cc296435ff0a912e3ad16848ee8765ab5f60a050b280d6ea585e5b34051b15f6b8934ef01ceb85f648", + "0xac3893df7b4ceab23c6b9054e48e8ba40d6e5beda8fbe90b814f992f52494186969b35d8c4cdc3c99890a222c9c09008", + "0xa41293ad22fae81dea94467bc1488c3707f3d4765059173980be93995fa4fcc3c9340796e3eed0beeb0ba0d9bb4fa3aa", + "0xa0543e77acd2aeecde13d18d258aeb2c7397b77f17c35a1992e8666ea7abcd8a38ec6c2741bd929abba2f766138618cc", + "0x92e79b22bc40e69f6527c969500ca543899105837b6b1075fa1796755c723462059b3d1b028e0b3df2559fa440e09175", + "0xa1fa1eac8f41a5197a6fb4aa1eae1a031c89f9c13ff9448338b222780cf9022e0b0925d930c37501a0ef7b2b00fdaf83", + "0xb3cb29ff73229f0637335f28a08ad8c5f166066f27c6c175164d0f26766a927f843b987ee9b309ed71cbf0a65d483831", + "0x84d4ab787f0ac00f104f4a734dc693d62d48c2aeb03913153da62c2ae2c27d11b1110dcef8980368dd84682ea2c1a308", + "0xab6a8e4bbc78d4a7b291ad3e9a8fe2d65f640524ba3181123b09d2d18a9e300e2509ccf7000fe47e75b65f3e992a2e7e", + "0xb7805ebe4f1a4df414003dc10bca805f2ab86ca75820012653e8f9b79c405196b0e2cab099f2ab953d67f0d60d31a0f9", + "0xb12c582454148338ea605d22bd00a754109063e22617f1f8ac8ddf5502c22a181c50c216c3617b9852aa5f26af56b323", + "0x86333ad9f898947e31ce747728dc8c887479e18d36ff3013f69ebef807d82c6981543b5c3788af93c4d912ba084d3cba", + "0xb514efa310dc4ad1258add138891e540d8c87142a881b5f46563cc58ecd1488e6d3a2fca54c0b72a929f3364ca8c333e", + "0xaa0a30f92843cf2f484066a783a1d75a7aa6f41f00b421d4baf20a6ac7886c468d0eea7ca8b17dd22f4f74631b62b640", + "0xb3b7dc63baec9a752e8433c0cdee4d0f9bc41f66f2b8d132faf925eef9cf89aae756fc132c45910f057122462605dc10", + "0xb9b8190dac5bfdeb59fd44f4da41a57e7f1e7d2c21faba9da91fa45cbeca06dcf299c9ae22f0c89ece11ac46352d619f", + "0x89f8cf36501ad8bdfeab863752a9090e3bfda57cf8fdeca2944864dc05925f501e252c048221bcc57136ab09a64b64b2", + "0xb0cbfaf317f05f97be47fc9d69eda2dd82500e00d42612f271a1fe24626408c28881f171e855bd5bd67409f9847502b4", + "0xa7c21a8fcede581bfd9847b6835eda62ba250bea81f1bb17372c800a19c732abe03064e64a2f865d974fb636cab4b859", + "0x95f9df524ba7a4667351696c4176b505d8ea3659f5ff2701173064acc624af69a0fad4970963736383b979830cb32260", + "0x856a74fe8b37a2e3afeac858c8632200485d438422a16ae3b29f359e470e8244995c63ad79c7e007ed063f178d0306fd", + "0xb37faa4d78fdc0bb9d403674dbea0176c2014a171c7be8527b54f7d1a32a76883d3422a3e7a5f5fcc5e9b31b57822eeb", + "0x8d37234d8594ec3fe75670b5c9cc1ec3537564d4739b2682a75b18b08401869a4264c0f264354219d8d896cded715db4", + "0xb5289ee5737f0e0bde485d32096d23387d68dab8f01f47821ab4f06cc79a967afe7355e72dc0c751d96b2747b26f6255", + "0x9085e1fdf9f813e9c3b8232d3c8863cd84ab30d45e8e0d3d6a0abd9ebc6fd70cdf749ff4d04390000e14c7d8c6655fc7", + "0x93a388c83630331eca4da37ea4a97b3b453238af474817cc0a0727fd3138dcb4a22de38c04783ec829c22cb459cb4e8e", + "0xa5377116027c5d061dbe24c240b891c08cdd8cd3f0899e848d682c873aff5b8132c1e7cfe76d2e5ed97ee0eb1d42cb68", + "0xa274c84b04338ed28d74683e2a7519c2591a3ce37c294d6f6e678f7d628be2db8eff253ede21823e2df7183e6552f622", + "0x8bc201147a842453a50bec3ac97671397bc086d6dfc9377fa38c2124cdc286abda69b7324f47d64da094ae011d98d9d9", + "0x9842d0c066c524592b76fbec5132bc628e5e1d21c424bec4555efca8619cc1fd8ea3161febcb8b9e8ab54702f4e815e2", + "0xa19191b713a07efe85c266f839d14e25660ee74452e6c691cd9997d85ae4f732052d802d3deb018bdd847caa298a894b", + "0xa24f71fc0db504da4e287dd118a4a74301cbcd16033937ba2abc8417956fcb4ae19b8e63b931795544a978137eff51cb", + "0xa90eec4a6a3a4b8f9a5b93d978b5026fcf812fe65585b008d7e08c4aaf21195a1d0699f12fc16f79b6a18a369af45771", + "0x8b551cf89737d7d06d9b3b9c4c1c73b41f2ea0af4540999c70b82dabff8580797cf0a3caf34c86c59a7069eb2e38f087", + "0xb8d312e6c635e7a216a1cda075ae77ba3e1d2fd501dc31e83496e6e81ed5d9c7799f8e578869c2e0e256fb29f5de10a7", + "0x8d144bdb8cae0b2cdb5b33d44bbc96984a5925202506a8cc65eb67ac904b466f5a7fe3e1cbf04aa785bbb7348c4bb73c", + "0xa101b3d58b7a98659244b88de0b478b3fb87dc5fc6031f6e689b99edf498abd43e151fd32bd4bbd240e0b3e59c440359", + "0x907453abca7d8e7151a05cc3d506c988007692fe7401395dc93177d0d07d114ab6cca0cc658eb94c0223fe8658295cad", + "0x825329ffbe2147ddb68f63a0a67f32d7f309657b8e5d9ab5bb34b3730bfa2c77a23eaaadb05def7d9f94a9e08fdc1e96", + "0x88ee923c95c1dac99ae7ed6067906d734d793c5dc5d26339c1bb3314abe201c5dccb33b9007351885eb2754e9a8ea06c", + "0x98bc9798543f5f1adc9f2cfcfa72331989420e9c3f6598c45269f0dc9b7c8607bbeaf03faa0aea2ddde2b8f17fdceff5", + "0x8ee87877702a79aef923ab970db6fa81561b3c07d5bf1a072af0a7bad765b4cbaec910afe1a91703feacc7822fa38a94", + "0x8060b9584aa294fe8adc2b22f67e988bc6da768eae91e429dcc43ddc53cfcc5d6753fdc1b420b268c7eb2fb50736a970", + "0xb344a5524d80a2f051870c7001f74fcf348a70fcf78dbd20c6ff9ca85d81567d2318c8b8089f2c4f195d6aec9fc15fa6", + "0x8f5a5d893e1936ed062149d20eb73d98b62b7f50ab5d93a6429c03656b36688d1c80cb5010e4977491e51fa0d7dd35d5", + "0x86fa32ebbf97328c5f5f15564e1238297e289ec3219b9a741724e9f3ae8d5c15277008f555863a478b247ba5dc601d44", + "0x9557e55377e279f4b6b5e0ffe01eca037cc13aac242d67dfcd0374a1e775c5ed5cb30c25fe21143fee54e3302d34a3ea", + "0x8cb6bcbc39372d23464a416ea7039f57ba8413cf3f00d9a7a5b356ab20dcb8ed11b3561f7bce372b8534d2870c7ee270", + "0xb5d59075cb5abde5391f64b6c3b8b50adc6e1f654e2a580b6d6d6eff3f4fbdd8fffc92e06809c393f5c8eab37f774c4b", + "0xafcfb6903ef13e493a1f7308675582f15af0403b6553e8c37afb8b2808ad21b88b347dc139464367dc260df075fea1ad", + "0x810fbbe808375735dd22d5bc7fc3828dc49fdd22cc2d7661604e7ac9c4535c1df578780affb3b895a0831640a945bcad", + "0x8056b0c678803b416f924e09a6299a33cf9ad7da6fe1ad7accefe95c179e0077da36815fde3716711c394e2c5ea7127f", + "0x8b67403702d06979be19f1d6dc3ec73cc2e81254d6b7d0cc49cd4fdda8cd51ab0835c1d2d26fc0ecab5df90585c2f351", + "0x87f97f9e6d4be07e8db250e5dd2bffdf1390665bc5709f2b631a6fa69a7fca958f19bd7cc617183da1f50ee63e9352b5", + "0xae151310985940471e6803fcf37600d7fa98830613e381e00dab943aec32c14162d51c4598e8847148148000d6e5af5c", + "0x81eb537b35b7602c45441cfc61b27fa9a30d3998fad35a064e05bc9479e9f10b62eba2b234b348219eea3cadcaac64bb", + "0x8a441434934180ab6f5bc541f86ebd06eadbee01f438836d797e930fa803a51510e005c9248cecc231a775b74d12b5e9", + "0x81f3c250a27ba14d8496a5092b145629eb2c2e6a5298438670375363f57e2798207832c8027c3e9238ad94ecdadfc4df", + "0xa6217c311f2f3db02ceaa5b6096849fe92b6f4b6f1491535ef8525f6ccee6130bed2809e625073ecbaddd4a3eb3df186", + "0x82d1c396f0388b942cf22b119d7ef1ad03d3dad49a74d9d01649ee284f377c8daddd095d596871669e16160299a210db", + "0xa40ddf7043c5d72a7246bd727b07f7fff1549f0e443d611de6f9976c37448b21664c5089c57f20105102d935ab82f27b", + "0xb6c03c1c97adf0c4bf4447ec71366c6c1bff401ba46236cd4a33d39291e7a1f0bb34bd078ba3a18d15c98993b153a279", + "0x8a94f5f632068399c359c4b3a3653cb6df2b207379b3d0cdace51afdf70d6d5cce6b89a2b0fee66744eba86c98fb21c2", + "0xb2f19e78ee85073f680c3bba1f07fd31b057c00b97040357d97855b54a0b5accb0d3b05b2a294568fcd6a4be6f266950", + "0xa74632d13bbe2d64b51d7a9c3ae0a5a971c19f51cf7596a807cea053e6a0f3719700976d4e394b356c0329a2dced9aa2", + "0xafef616d341a9bc94393b8dfba68ff0581436aa3a3adb7c26a1bbf2cf19fa877066191681f71f17f3cd6f9cf6bf70b5a", + "0x8ce96d93ae217408acf7eb0f9cbb9563363e5c7002e19bbe1e80760bc9d449daee2118f3878b955163ed664516b97294", + "0x8414f79b496176bc8b8e25f8e4cfee28f4f1c2ddab099d63d2aca1b6403d26a571152fc3edb97794767a7c4686ad557c", + "0xb6c61d01fd8ce087ef9f079bf25bf10090db483dd4f88c4a786d31c1bdf52065651c1f5523f20c21e75cea17df69ab73", + "0xa5790fd629be70545093631efadddc136661f63b65ec682609c38ef7d3d7fa4e56bdf94f06e263bc055b90cb1c6bcefe", + "0xb515a767e95704fb7597bca9e46f1753abacdc0e56e867ee3c6f4cd382643c2a28e65312c05ad040eaa3a8cbe7217a65", + "0x8135806a02ead6aa92e9adb6fefb91349837ab73105aaa7be488ef966aa8dfaafdfa64bbae30fcbfa55dd135a036a863", + "0x8f22435702716d76b1369750694540742d909d5e72b54d0878245fab7c269953b1c6f2b29c66f08d5e0263ca3a731771", + "0x8e0f8a8e8753e077dac95848212aeffd51c23d9b6d611df8b102f654089401954413ecbedc6367561ca599512ae5dda7", + "0x815a9084e3e2345f24c5fa559deec21ee1352fb60f4025c0779be65057f2d528a3d91593bd30d3a185f5ec53a9950676", + "0x967e6555ccba395b2cc1605f8484c5112c7b263f41ce8439a99fd1c71c5ed14ad02684d6f636364199ca48afbbde13be", + "0x8cd0ccf17682950b34c796a41e2ea7dd5367aba5e80a907e01f4cdc611e4a411918215e5aebf4292f8b24765d73314a6", + "0xa58bf1bbb377e4b3915df6f058a0f53b8fb8130fdec8c391f6bc82065694d0be59bb67ffb540e6c42cc8b380c6e36359", + "0x92af3151d9e6bfb3383d85433e953c0160859f759b0988431ec5893542ba40288f65db43c78a904325ef8d324988f09d", + "0x8011bbb05705167afb47d4425065630f54cb86cd462095e83b81dfebf348f846e4d8fbcf1c13208f5de1931f81da40b9", + "0x81c743c104fc3cb047885c9fa0fb9705c3a83ee24f690f539f4985509c3dafd507af3f6a2128276f45d5939ef70c167f", + "0xa2c9679b151c041aaf5efeac5a737a8f70d1631d931609fca16be1905682f35e291292874cb3b03f14994f98573c6f44", + "0xa4949b86c4e5b1d5c82a337e5ce6b2718b1f7c215148c8bfb7e7c44ec86c5c9476048fc5c01f57cb0920876478c41ad6", + "0x86c2495088bd1772152e527a1da0ef473f924ea9ab0e5b8077df859c28078f73c4e22e3a906b507fdf217c3c80808b5c", + "0x892e0a910dcf162bcea379763c3e2349349e4cda9402949255ac4a78dd5a47e0bf42f5bd0913951576b1d206dc1e536a", + "0xa7009b2c6b396138afe4754b7cc10dee557c51c7f1a357a11486b3253818531f781ea8107360c8d4c3b1cd96282353c0", + "0x911763ef439c086065cc7b4e57484ed6d693ea44acee4b18c9fd998116da55fbe7dcb8d2a0f0f9b32132fca82d73dff6", + "0xa722000b95a4a2d40bed81870793f15ba2af633f9892df507f2842e52452e02b5ea8dea6a043c2b2611d82376e33742a", + "0x9387ac49477bd719c2f92240d0bdfcf9767aad247ca93dc51e56106463206bc343a8ec855eb803471629a66fffb565d6", + "0x92819a1fa48ab4902939bb72a0a4e6143c058ea42b42f9bc6cea5df45f49724e2530daf3fc4f097cceefa2a8b9db0076", + "0x98eac7b04537653bc0f4941aae732e4b1f84bd276c992c64a219b8715eb1fb829b5cbd997d57feb15c7694c468f95f70", + "0xb275e7ba848ce21bf7996e12dbeb8dadb5d0e4f1cb5a0248a4f8f9c9fe6c74e3c93f4b61edbcb0a51af5a141e1c14bc7", + "0x97243189285aba4d49c53770c242f2faf5fd3914451da4931472e3290164f7663c726cf86020f8f181e568c72fd172d1", + "0x839b0b3c25dd412bee3dc24653b873cc65454f8f16186bb707bcd58259c0b6765fa4c195403209179192a4455c95f3b8", + "0x8689d1a870514568a074a38232e2ceb4d7df30fabeb76cff0aed5b42bf7f02baea12c5fadf69f4713464dbd52aafa55f", + "0x8958ae7b290f0b00d17c3e9fdb4dbf168432b457c7676829299dd428984aba892de1966fc106cfc58a772862ecce3976", + "0xa422bc6bd68b8870cfa5bc4ce71781fd7f4368b564d7f1e0917f6013c8bbb5b240a257f89ecfdbecb40fe0f3aa31d310", + "0xaa61f78130cebe09bc9a2c0a37f0dd57ed2d702962e37d38b1df7f17dc554b1d4b7a39a44182a452ce4c5eb31fa4cfcc", + "0xb7918bd114f37869bf1a459023386825821bfadce545201929d13ac3256d92a431e34f690a55d944f77d0b652cefeffc", + "0x819bba35fb6ace1510920d4dcff30aa682a3c9af9022e287751a6a6649b00c5402f14b6309f0aeef8fce312a0402915e", + "0x8b7c9ad446c6f63c11e1c24e24014bd570862b65d53684e107ba9ad381e81a2eaa96731b4b33536efd55e0f055071274", + "0x8fe79b53f06d33386c0ec7d6d521183c13199498594a46d44a8a716932c3ec480c60be398650bbfa044fa791c4e99b65", + "0x9558e10fb81250b9844c99648cf38fa05ec1e65d0ccbb18aa17f2d1f503144baf59d802c25be8cc0879fff82ed5034ad", + "0xb538a7b97fbd702ba84645ca0a63725be1e2891c784b1d599e54e3480e4670d0025526674ef5cf2f87dddf2290ba09f0", + "0x92eafe2e869a3dd8519bbbceb630585c6eb21712b2f31e1b63067c0acb5f9bdbbcbdb612db4ea7f9cc4e7be83d31973f", + "0xb40d21390bb813ab7b70a010dff64c57178418c62685761784e37d327ba3cb9ef62df87ecb84277c325a637fe3709732", + "0xb349e6fbf778c4af35fbed33130bd8a7216ed3ba0a79163ebb556e8eb8e1a7dad3456ddd700dad9d08d202491c51b939", + "0xa8fdaedecb251f892b66c669e34137f2650509ade5d38fbe8a05d9b9184bb3b2d416186a3640429bd1f3e4b903c159dd", + "0xac6167ebfee1dbab338eff7642f5e785fc21ef0b4ddd6660333fe398068cbd6c42585f62e81e4edbb72161ce852a1a4f", + "0x874b1fbf2ebe140c683bd7e4e0ab017afa5d4ad38055aaa83ee6bbef77dbc88a6ce8eb0dcc48f0155244af6f86f34c2d", + "0x903c58e57ddd9c446afab8256a6bb6c911121e6ccfb4f9b4ed3e2ed922a0e500a5cb7fa379d5285bc16e11dac90d1fda", + "0x8dae7a0cffa2fd166859cd1bf10ff82dd1932e488af377366b7efc0d5dec85f85fe5e8150ff86a79a39cefc29631733a", + "0xaa047857a47cc4dfc08585f28640420fcf105b881fd59a6cf7890a36516af0644d143b73f3515ab48faaa621168f8c31", + "0x864508f7077c266cc0cb3f7f001cb6e27125ebfe79ab57a123a8195f2e27d3799ff98413e8483c533b46a816a3557f1f", + "0x8bcd45ab1f9cbab36937a27e724af819838f66dfeb15923f8113654ff877bd8667c54f6307aaf0c35027ca11b6229bfd", + "0xb21aa34da9ab0a48fcfdd291df224697ce0c1ebc0e9b022fdee8750a1a4b5ba421c419541ed5c98b461eecf363047471", + "0xa9a18a2ab2fae14542dc336269fe612e9c1af6cf0c9ac933679a2f2cb77d3c304114f4d219ca66fe288adde30716775b", + "0xb5205989b92c58bdda71817f9a897e84100b5c4e708de1fced5c286f7a6f01ae96b1c8d845f3a320d77c8e2703c0e8b1", + "0xa364059412bbcc17b8907d43ac8e5df90bc87fd1724b5f99832d0d24559fae6fa76a74cff1d1eac8cbac6ec80b44af20", + "0xae709f2c339886b31450834cf29a38b26eb3b0779bd77c9ac269a8a925d1d78ea3837876c654b61a8fe834b3b6940808", + "0x8802581bba66e1952ac4dab36af371f66778958f4612901d95e5cac17f59165e6064371d02de8fb6fccf89c6dc8bd118", + "0xa313252df653e29c672cbcfd2d4f775089cb77be1077381cf4dc9533790e88af6cedc8a119158e7da5bf6806ad9b91a1", + "0x992a065b4152c7ef11515cd54ba9d191fda44032a01aed954acff3443377ee16680c7248d530b746b8c6dee2d634e68c", + "0xb627b683ee2b32c1ab4ccd27b9f6cce2fe097d96386fa0e5c182ad997c4c422ab8dfc03870cd830b8c774feb66537282", + "0xb823cf8a9aee03dadd013eb9efe40a201b4b57ef67efaae9f99683005f5d1bf55e950bf4af0774f50859d743642d3fea", + "0xb8a7449ffac0a3f206677097baf7ce00ca07a4d2bd9b5356fbcb83f3649b0fda07cfebad220c1066afba89e5a52abf4b", + "0xb2dd1a2f986395bb4e3e960fbbe823dbb154f823284ebc9068502c19a7609790ec0073d08bfa63f71e30c7161b6ef966", + "0x98e5236de4281245234f5d40a25b503505af140b503a035fc25a26159a9074ec81512b28f324c56ea2c9a5aa7ce90805", + "0x89070847dc8bbf5bc4ed073aa2e2a1f699cf0c2ca226f185a0671cecc54e7d3e14cd475c7752314a7a8e7476829da4bc", + "0xa9402dc9117fdb39c4734c0688254f23aed3dce94f5f53f5b7ef2b4bf1b71a67f85ab1a38ec224a59691f3bee050aeb3", + "0x957288f9866a4bf56a4204218ccc583f717d7ce45c01ea27142a7e245ad04a07f289cc044f8cf1f21d35e67e39299e9c", + "0xb2fb31ccb4e69113763d7247d0fc8edaae69b550c5c56aecacfd780c7217dc672f9fb7496edf4aba65dacf3361268e5b", + "0xb44a4526b2f1d6eb2aa8dba23bfa385ff7634572ab2afddd0546c3beb630fbfe85a32f42dd287a7fec069041411537f7", + "0x8db5a6660c3ac7fd7a093573940f068ee79a82bc17312af900b51c8c439336bc86ca646c6b7ab13aaaa008a24ca508ab", + "0x8f9899a6d7e8eb4367beb5c060a1f8e94d8a21099033ae582118477265155ba9e72176a67f7f25d7bad75a152b56e21a", + "0xa67de0e91ade8d69a0e00c9ff33ee2909b8a609357095fa12319e6158570c232e5b6f4647522efb7345ce0052aa9d489", + "0x82eb2414898e9c3023d57907a2b17de8e7eea5269029d05a94bfd7bf5685ac4a799110fbb375eb5e0e2bd16acf6458ae", + "0x94451fc7fea3c5a89ba701004a9693bab555cb622caf0896b678faba040409fdfd14a978979038b2a81e8f0abc4994d2", + "0xac879a5bb433998e289809a4a966bd02b4bf6a9c1cc276454e39c886efcf4fc68baebed575826bde577ab5aa71d735a9", + "0x880c0f8f49c875dfd62b4ddedde0f5c8b19f5687e693717f7e5c031bc580e58e13ab497d48b4874130a18743c59fdce3", + "0xb582af8d8ff0bf76f0a3934775e0b54c0e8fed893245d7d89cae65b03c8125b7237edc29dc45b4fe1a3fe6db45d280ee", + "0x89f337882ed3ae060aaee98efa20d79b6822bde9708c1c5fcee365d0ec9297f694cae37d38fd8e3d49717c1e86f078e7", + "0x826d2c1faea54061848b484e288a5f4de0d221258178cf87f72e14baaa4acc21322f8c9eab5dde612ef497f2d2e1d60b", + "0xa5333d4f227543e9cd741ccf3b81db79f2f03ca9e649e40d6a6e8ff9073e06da83683566d3b3c8d7b258c62970fb24d1", + "0xa28f08c473db06aaf4c043a2fae82b3c8cfaa160bce793a4c208e4e168fb1c65115ff8139dea06453c5963d95e922b94", + "0x8162546135cc5e124e9683bdfaa45833c18553ff06a0861c887dc84a5b12ae8cd4697f6794c7ef6230492c32faba7014", + "0xb23f0d05b74c08d6a7df1760792be83a761b36e3f8ae360f3c363fb196e2a9dd2de2e492e49d36561366e14daa77155c", + "0xb6f70d6c546722d3907c708d630dbe289771d2c8bf059c2e32b77f224696d750b4dda9b3a014debda38e7d02c9a77585", + "0x83bf4c4a9f3ca022c631017e7a30ea205ba97f7f5927cba8fc8489a4646eac6712cb821c5668c9ffe94d69d524374a27", + "0xb0371475425a8076d0dd5f733f55aabbe42d20a7c8ea7da352e736d4d35a327b2beb370dfcb05284e22cfd69c5f6c4cc", + "0xa0031ba7522c79211416c2cca3aa5450f96f8fee711552a30889910970ba13608646538781a2c08b834b140aadd7166f", + "0x99d273c80c7f2dc6045d4ed355d9fc6f74e93549d961f4a3b73cd38683f905934d359058cd1fc4da8083c7d75070487f", + "0xb0e4b0efa3237793e9dcce86d75aafe9879c5fa23f0d628649aef2130454dcf72578f9bf227b9d2b9e05617468e82588", + "0xa5ab076fa2e1c5c51f3ae101afdd596ad9d106bba7882b359c43d8548b64f528af19afa76cd6f40da1e6c5fca4def3fa", + "0x8ce2299e570331d60f6a6eff1b271097cd5f1c0e1113fc69b89c6a0f685dabea3e5bc2ac6bd789aa492ab189f89be494", + "0x91b829068874d911a310a5f9dee001021f97471307b5a3de9ec336870ec597413e1d92010ce320b619f38bed7c4f7910", + "0xb14fe91f4b07bf33b046e9285b66cb07927f3a8da0af548ac2569b4c4fb1309d3ced76d733051a20814e90dd5b75ffd1", + "0xabaab92ea6152d40f82940277c725aa768a631ee0b37f5961667f82fb990fc11e6d3a6a2752b0c6f94563ed9bb28265c", + "0xb7fe28543eca2a716859a76ab9092f135337e28109544f6bd2727728d0a7650428af5713171ea60bfc273d1c821d992c", + "0x8a4917b2ab749fc7343fc64bdf51b6c0698ff15d740cc7baf248c030475c097097d5a473bcc00d8c25817563fe0447b4", + "0xaa96156d1379553256350a0a3250166add75948fb9cde62aa555a0a9dc0a9cb7f2f7b8428aff66097bf6bfedaf14bbe2", + "0xae4ffeb9bdc76830d3eca2b705f30c1bdede6412fa064260a21562c8850c7fb611ec62bc68479fe48f692833e6f66d8d", + "0xb96543caaba9d051600a14997765d49e4ab10b07c7a92cccf0c90b309e6da334fdd6d18c96806cbb67a7801024fbd3c7", + "0x97b2b9ad76f19f500fcc94ca8e434176249f542ac66e5881a3dccd07354bdab6a2157018b19f8459437a68d8b86ba8e0", + "0xa8d206f6c5a14c80005849474fde44b1e7bcf0b2d52068f5f97504c3c035b09e65e56d1cf4b5322791ae2c2fdbd61859", + "0x936bad397ad577a70cf99bf9056584a61bd7f02d2d5a6cf219c05d770ae30a5cd902ba38366ce636067fc1dd10108d31", + "0xa77e30195ee402b84f3882e2286bf5380c0ed374a112dbd11e16cef6b6b61ab209d4635e6f35cdaaa72c1a1981d5dabe", + "0xa46ba4d3947188590a43c180757886a453a0503f79cc435322d92490446f37419c7b999fdf868a023601078070e03346", + "0x80d8d4c5542f223d48240b445d4d8cf6a75d120b060bc08c45e99a13028b809d910b534d2ac47fb7068930c54efd8da9", + "0x803be9c68c91b42b68e1f55e58917a477a9a6265e679ca44ee30d3eb92453f8c89c64eafc04c970d6831edd33d066902", + "0xb14b2b3d0dfe2bb57cee4cd72765b60ac33c1056580950be005790176543826c1d4fbd737f6cfeada6c735543244ab57", + "0xa9e480188bba1b8fb7105ff12215706665fd35bf1117bacfb6ab6985f4dbc181229873b82e5e18323c2b8f5de03258e0", + "0xa66a0f0779436a9a3999996d1e6d3000f22c2cac8e0b29cddef9636393c7f1457fb188a293b6c875b05d68d138a7cc4a", + "0x848397366300ab40c52d0dbbdafbafef6cd3dadf1503bb14b430f52bb9724188928ac26f6292a2412bc7d7aa620763c8", + "0x95466cc1a78c9f33a9aaa3829a4c8a690af074916b56f43ae46a67a12bb537a5ac6dbe61590344a25b44e8512355a4a7", + "0x8b5f7a959f818e3baf0887f140f4575cac093d0aece27e23b823cf421f34d6e4ff4bb8384426e33e8ec7b5eed51f6b5c", + "0x8d5e1368ec7e3c65640d216bcc5d076f3d9845924c734a34f3558ac0f16e40597c1a775a25bf38b187213fbdba17c93b", + "0xb4647c1b823516880f60d20c5cc38c7f80b363c19d191e8992226799718ee26b522a12ecb66556ed3d483aa4824f3326", + "0xac3abaea9cd283eb347efda4ed9086ea3acf495043e08d0d19945876329e8675224b685612a6badf8fd72fb6274902b1", + "0x8eae1ce292d317aaa71bcf6e77e654914edd5090e2e1ebab78b18bb41b9b1bc2e697439f54a44c0c8aa0d436ebe6e1a9", + "0x94dc7d1aec2c28eb43d93b111fa59aaa0d77d5a09501220bd411768c3e52208806abf973c6a452fd8292ff6490e0c9e2", + "0x8fd8967f8e506fef27d17b435d6b86b232ec71c1036351f12e6fb8a2e12daf01d0ee04451fb944d0f1bf7fd20e714d02", + "0x824e6865be55d43032f0fec65b3480ea89b0a2bf860872237a19a54bc186a85d2f8f9989cc837fbb325b7c72d9babe2c", + "0x8bd361f5adb27fd6f4e3f5de866e2befda6a8454efeb704aacc606f528c03f0faae888f60310e49440496abd84083ce2", + "0xb098a3c49f2aaa28b6b3e85bc40ce6a9cdd02134ee522ae73771e667ad7629c8d82c393fba9f27f5416986af4c261438", + "0xb385f5ca285ff2cfe64dcaa32dcde869c28996ed091542600a0b46f65f3f5a38428cca46029ede72b6cf43e12279e3d3", + "0x8196b03d011e5be5288196ef7d47137d6f9237a635ab913acdf9c595fa521d9e2df722090ec7eb0203544ee88178fc5f", + "0x8ed1270211ef928db18e502271b7edf24d0bbd11d97f2786aee772d70c2029e28095cf8f650b0328cc8a4c38d045316d", + "0xa52ab60e28d69b333d597a445884d44fd2a7e1923dd60f763951e1e45f83e27a4dac745f3b9eff75977b3280e132c15d", + "0x91e9fe78cdac578f4a4687f71b800b35da54b824b1886dafec073a3c977ce7a25038a2f3a5b1e35c2c8c9d1a7312417c", + "0xa42832173f9d9491c7bd93b21497fbfa4121687cd4d2ab572e80753d7edcbb42cfa49f460026fbde52f420786751a138", + "0x97b947126d84dcc70c97be3c04b3de3f239b1c4914342fa643b1a4bb8c4fe45c0fcb585700d13a7ed50784790c54bef9", + "0x860e407d353eac070e2418ef6cb80b96fc5f6661d6333e634f6f306779651588037be4c2419562c89c61f9aa2c4947f5", + "0xb2c9d93c3ba4e511b0560b55d3501bf28a510745fd666b3cb532db051e6a8617841ea2f071dda6c9f15619c7bfd2737f", + "0x8596f4d239aeeac78311207904d1bd863ef68e769629cc379db60e019aaf05a9d5cd31dc8e630b31e106a3a93e47cbc5", + "0x8b26e14e2e136b65c5e9e5c2022cee8c255834ea427552f780a6ca130a6446102f2a6f334c3f9a0308c53df09e3dba7e", + "0xb54724354eb515a3c8bed0d0677ff1db94ac0a07043459b4358cb90e3e1aa38ac23f2caa3072cf9647275d7cd61d0e80", + "0xb7ce9fe0e515e7a6b2d7ddcb92bc0196416ff04199326aea57996eef8c5b1548bd8569012210da317f7c0074691d01b7", + "0xa1a13549c82c877253ddefa36a29ea6a23695ee401fdd48e65f6f61e5ebd956d5e0edeff99484e9075cb35071fec41e2", + "0x838ba0c1e5bd1a6da05611ff1822b8622457ebd019cb065ece36a2d176bd2d889511328120b8a357e44569e7f640c1e6", + "0xb916eccff2a95519400bbf76b5f576cbe53cf200410370a19d77734dc04c05b585cfe382e8864e67142d548cd3c4c2f4", + "0xa610447cb7ca6eea53a6ff1f5fe562377dcb7f4aaa7300f755a4f5e8eba61e863c51dc2aa9a29b35525b550fbc32a0fe", + "0x9620e8f0f0ee9a4719aa9685eeb1049c5c77659ba6149ec4c158f999cfd09514794b23388879931fe26fea03fa471fd3", + "0xa9dcf8b679e276583cf5b9360702a185470d09aea463dc474ee9c8aee91ef089dacb073e334e47fbc78ec5417c90465c", + "0x8c9adee8410bdd99e5b285744cee61e2593b6300ff31a8a83b0ec28da59475a5c6fb9346fe43aadea2e6c3dad2a8e30a", + "0x97d5afe9b3897d7b8bb628b7220cf02d8ee4e9d0b78f5000d500aaf4c1df9251aaaabfd1601626519f9d66f00a821d4e", + "0x8a382418157b601ce4c3501d3b8409ca98136a4ef6abcbf62885e16e215b76b035c94d149cc41ff92e42ccd7c43b9b3d", + "0xb64b8d11fb3b01abb2646ac99fdb9c02b804ce15d98f9fe0fbf1c9df8440c71417487feb6cdf51e3e81d37104b19e012", + "0x849d7d044f9d8f0aab346a9374f0b3a5d14a9d1faa83dbacccbdc629ad1ef903a990940255564770537f8567521d17f0", + "0x829dbb0c76b996c2a91b4cbbe93ba455ca0d5729755e5f0c92aaee37dff7f36fcdc06f33aca41f1b609c784127b67d88", + "0x85a7c0069047b978422d264d831ab816435f63938015d2e977222b6b5746066c0071b7f89267027f8a975206ed25c1b0", + "0x84b9fbc1cfb302df1acdcf3dc5d66fd1edfe7839f7a3b2fb3a0d5548656249dd556104d7c32b73967bccf0f5bdcf9e3b", + "0x972220ac5b807f53eac37dccfc2ad355d8b21ea6a9c9b011c09fe440ddcdf7513e0b43d7692c09ded80d7040e26aa28f", + "0x855885ed0b21350baeca890811f344c553cf9c21024649c722453138ba29193c6b02c4b4994cd414035486f923472e28", + "0x841874783ae6d9d0e59daea03e96a01cbbe4ecaced91ae4f2c8386e0d87b3128e6d893c98d17c59e4de1098e1ad519dd", + "0x827e50fc9ce56f97a4c3f2f4cbaf0b22f1c3ce6f844ff0ef93a9c57a09b8bf91ebfbd2ba9c7f83c442920bffdaf288cc", + "0xa441f9136c7aa4c08d5b3534921b730e41ee91ab506313e1ba5f7c6f19fd2d2e1594e88c219834e92e6fb95356385aa7", + "0x97d75b144471bf580099dd6842b823ec0e6c1fb86dd0da0db195e65524129ea8b6fd4a7a9bbf37146269e938a6956596", + "0xa4b6fa87f09d5a29252efb2b3aaab6b3b6ea9fab343132a651630206254a25378e3e9d6c96c3d14c150d01817d375a8e", + "0xa31a671876d5d1e95fe2b8858dc69967231190880529d57d3cab7f9f4a2b9b458ac9ee5bdaa3289158141bf18f559efb", + "0x90bee6fff4338ba825974021b3b2a84e36d617e53857321f13d2b3d4a28954e6de3b3c0e629d61823d18a9763313b3bf", + "0x96b622a63153f393bb419bfcf88272ea8b3560dbd46b0aa07ada3a6223990d0abdd6c2adb356ef4be5641688c8d83941", + "0x84c202adeaff9293698022bc0381adba2cd959f9a35a4e8472288fd68f96f6de8be9da314c526d88e291c96b1f3d6db9", + "0x8ca01a143b8d13809e5a8024d03e6bc9492e22226073ef6e327edf1328ef4aff82d0bcccee92cb8e212831fa35fe1204", + "0xb2f970dbad15bfbefb38903c9bcc043d1367055c55dc1100a850f5eb816a4252c8c194b3132c929105511e14ea10a67d", + "0xa5e36556472a95ad57eb90c3b6623671b03eafd842238f01a081997ffc6e2401f76e781d049bb4aa94d899313577a9cf", + "0x8d1057071051772f7c8bedce53a862af6fd530dd56ae6321eaf2b9fc6a68beff5ed745e1c429ad09d5a118650bfd420a", + "0x8aadc4f70ace4fcb8d93a78610779748dcffc36182d45b932c226dc90e48238ea5daa91f137c65ed532352c4c4d57416", + "0xa2ea05ae37e673b4343232ae685ee14e6b88b867aef6dfac35db3589cbcd76f99540fed5c2641d5bb5a4a9f808e9bf0d", + "0x947f1abad982d65648ae4978e094332b4ecb90f482c9be5741d5d1cf5a28acf4680f1977bf6e49dd2174c37f11e01296", + "0xa27b144f1565e4047ba0e3f4840ef19b5095d1e281eaa463c5358f932114cbd018aa6dcf97546465cf2946d014d8e6d6", + "0x8574e1fc3acade47cd4539df578ce9205e745e161b91e59e4d088711a7ab5aa3b410d517d7304b92109924d9e2af8895", + "0xa48ee6b86b88015d6f0d282c1ae01d2a5b9e8c7aa3d0c18b35943dceb1af580d08a65f54dc6903cde82fd0d73ce94722", + "0x8875650cec543a7bf02ea4f2848a61d167a66c91ffaefe31a9e38dc8511c6a25bde431007eefe27a62af3655aca208dc", + "0x999b0a6e040372e61937bf0d68374e230346b654b5a0f591a59d33a4f95bdb2f3581db7c7ccb420cd7699ed709c50713", + "0x878c9e56c7100c5e47bbe77dc8da5c5fe706cec94d37fa729633bca63cace7c40102eee780fcdabb655f5fa47a99600e", + "0x865006fb5b475ada5e935f27b96f9425fc2d5449a3c106aa366e55ebed3b4ee42adc3c3f0ac19fd129b40bc7d6bc4f63", + "0xb7a7da847f1202e7bc1672553e68904715e84fd897d529243e3ecda59faa4e17ba99c649a802d53f6b8dfdd51f01fb74", + "0x8b2fb4432c05653303d8c8436473682933a5cb604da10c118ecfcd2c8a0e3132e125afef562bdbcc3df936164e5ce4f2", + "0x808d95762d33ddfa5d0ee3d7d9f327de21a994d681a5f372e2e3632963ea974da7f1f9e5bac8ccce24293509d1f54d27", + "0x932946532e3c397990a1df0e94c90e1e45133e347a39b6714c695be21aeb2d309504cb6b1dde7228ff6f6353f73e1ca2", + "0x9705e7c93f0cdfaa3fa96821f830fe53402ad0806036cd1b48adc2f022d8e781c1fbdab60215ce85c653203d98426da3", + "0xaa180819531c3ec1feb829d789cb2092964c069974ae4faad60e04a6afcce5c3a59aec9f11291e6d110a788d22532bc6", + "0x88f755097f7e25cb7dd3c449520c89b83ae9e119778efabb54fbd5c5714b6f37c5f9e0346c58c6ab09c1aef2483f895d", + "0x99fc03ab7810e94104c494f7e40b900f475fde65bdec853e60807ffd3f531d74de43335c3b2646b5b8c26804a7448898", + "0xaf2dea9683086bed1a179110efb227c9c00e76cd00a2015b089ccbcee46d1134aa18bda5d6cab6f82ae4c5cd2461ac21", + "0xa500f87ba9744787fdbb8e750702a3fd229de6b8817594348dec9a723b3c4240ddfa066262d002844b9e38240ce55658", + "0x924d0e45c780f5bc1c1f35d15dfc3da28036bdb59e4c5440606750ecc991b85be18bc9a240b6c983bc5430baa4c68287", + "0x865b11e0157b8bf4c5f336024b016a0162fc093069d44ac494723f56648bc4ded13dfb3896e924959ea11c96321afefc", + "0x93672d8607d4143a8f7894f1dcca83fb84906dc8d6dd7dd063bb0049cfc20c1efd933e06ca7bd03ea4cb5a5037990bfe", + "0x826891efbdff0360446825a61cd1fa04326dd90dae8c33dfb1ed97b045e165766dd070bd7105560994d0b2044bdea418", + "0x93c4a4a8bcbc8b190485cc3bc04175b7c0ed002c28c98a540919effd6ed908e540e6594f6db95cd65823017258fb3b1c", + "0xaeb2a0af2d2239fda9aa6b8234b019708e8f792834ff0dd9c487fa09d29800ddceddd6d7929faa9a3edcb9e1b3aa0d6b", + "0x87f11de7236d387863ec660d2b04db9ac08143a9a2c4dfff87727c95b4b1477e3bc473a91e5797313c58754905079643", + "0x80dc1db20067a844fe8baceca77f80db171a5ca967acb24e2d480eae9ceb91a3343c31ad1c95b721f390829084f0eae6", + "0x9825c31f1c18da0de3fa84399c8b40f8002c3cae211fb6a0623c76b097b4d39f5c50058f57a16362f7a575909d0a44a2", + "0xa99fc8de0c38dbf7b9e946de83943a6b46a762167bafe2a603fb9b86f094da30d6de7ed55d639aafc91936923ee414b3", + "0xad594678b407db5d6ea2e90528121f84f2b96a4113a252a30d359a721429857c204c1c1c4ff71d8bb5768c833f82e80e", + "0xb33d985e847b54510b9b007e31053732c8a495e43be158bd2ffcea25c6765bcbc7ca815f7c60b36ad088b955dd6e9350", + "0x815f8dfc6f90b3342ca3fbd968c67f324dae8f74245cbf8bc3bef10e9440c65d3a2151f951e8d18959ba01c1b50b0ec1", + "0x94c608a362dd732a1abc56e338637c900d59013db8668e49398b3c7a0cae3f7e2f1d1bf94c0299eeafe6af7f76c88618", + "0x8ebd8446b23e5adfcc393adc5c52fe172f030a73e63cd2d515245ca0dd02782ceed5bcdd9ccd9c1b4c5953dfac9c340c", + "0x820437f3f6f9ad0f5d7502815b221b83755eb8dc56cd92c29e9535eb0b48fb8d08c9e4fcc26945f9c8cca60d89c44710", + "0x8910e4e8a56bf4be9cc3bbf0bf6b1182a2f48837a2ed3c2aaec7099bfd7f0c83e14e608876b17893a98021ff4ab2f20d", + "0x9633918fde348573eec15ce0ad53ac7e1823aac86429710a376ad661002ae6d049ded879383faaa139435122f64047c6", + "0xa1f5e3fa558a9e89318ca87978492f0fb4f6e54a9735c1b8d2ecfb1d1c57194ded6e0dd82d077b2d54251f3bee1279e1", + "0xb208e22d04896abfd515a95c429ff318e87ff81a5d534c8ac2c33c052d6ffb73ef1dccd39c0bbe0734b596c384014766", + "0x986d5d7d2b5bde6d16336f378bd13d0e671ad23a8ec8a10b3fc09036faeeb069f60662138d7a6df3dfb8e0d36180f770", + "0xa2d4e6c5f5569e9cef1cddb569515d4b6ace38c8aed594f06da7434ba6b24477392cc67ba867c2b079545ca0c625c457", + "0xb5ac32b1d231957d91c8b7fc43115ce3c5c0d8c13ca633374402fa8000b6d9fb19499f9181844f0c10b47357f3f757ce", + "0x96b8bf2504b4d28fa34a4ec378e0e0b684890c5f44b7a6bb6e19d7b3db2ab27b1e2686389d1de9fbd981962833a313ea", + "0x953bfd7f6c3a0469ad432072b9679a25486f5f4828092401eff494cfb46656c958641a4e6d0d97d400bc59d92dba0030", + "0x876ab3cea7484bbfd0db621ec085b9ac885d94ab55c4bb671168d82b92e609754b86aaf472c55df3d81421d768fd108a", + "0x885ff4e67d9ece646d02dd425aa5a087e485c3f280c3471b77532b0db6145b69b0fbefb18aa2e3fa5b64928b43a94e57", + "0xb91931d93f806d0b0e6cc62a53c718c099526140f50f45d94b8bbb57d71e78647e06ee7b42aa5714aed9a5c05ac8533f", + "0xa0313eeadd39c720c9c27b3d671215331ab8d0a794e71e7e690f06bcd87722b531d6525060c358f35f5705dbb7109ccb", + "0x874c0944b7fedc6701e53344100612ddcb495351e29305c00ec40a7276ea5455465ffb7bded898886c1853139dfb1fc7", + "0x8dc31701a01ee8137059ca1874a015130d3024823c0576aa9243e6942ec99d377e7715ed1444cd9b750a64b85dcaa3e5", + "0x836d2a757405e922ec9a2dfdcf489a58bd48b5f9683dd46bf6047688f778c8dee9bc456de806f70464df0b25f3f3d238", + "0xb30b0a1e454a503ea3e2efdec7483eaf20b0a5c3cefc42069e891952b35d4b2c955cf615f3066285ed8fafd9fcfbb8f6", + "0x8e6d4044b55ab747e83ec8762ea86845f1785cc7be0279c075dadf08aca3ccc5a096c015bb3c3f738f647a4eadea3ba5", + "0xad7735d16ab03cbe09c029610aa625133a6daecfc990b297205b6da98eda8c136a7c50db90f426d35069708510d5ae9c", + "0x8d62d858bbb59ec3c8cc9acda002e08addab4d3ad143b3812098f3d9087a1b4a1bb255dcb1635da2402487d8d0249161", + "0x805beec33238b832e8530645a3254aeef957e8f7ea24bcfc1054f8b9c69421145ebb8f9d893237e8a001c857fedfc77e", + "0xb1005644be4b085e3f5775aa9bd3e09a283e87ddada3082c04e7a62d303dcef3b8cf8f92944c200c7ae6bb6bdf63f832", + "0xb4ba0e0790dc29063e577474ffe3b61f5ea2508169f5adc1e394934ebb473e356239413a17962bc3e5d3762d72cce8c2", + "0xa157ba9169c9e3e6748d9f1dd67fbe08b9114ade4c5d8fc475f87a764fb7e6f1d21f66d7905cd730f28a1c2d8378682a", + "0x913e52b5c93989b5d15e0d91aa0f19f78d592bc28bcfdfddc885a9980c732b1f4debb8166a7c4083c42aeda93a702898", + "0x90fbfc1567e7cd4e096a38433704d3f96a2de2f6ed3371515ccc30bc4dd0721a704487d25a97f3c3d7e4344472702d8d", + "0x89646043028ffee4b69d346907586fd12c2c0730f024acb1481abea478e61031966e72072ff1d5e65cb8c64a69ad4eb1", + "0xb125a45e86117ee11d2fb42f680ab4a7894edd67ff927ae2c808920c66c3e55f6a9d4588eee906f33a05d592e5ec3c04", + "0xaad47f5b41eae9be55fb4f67674ff1e4ae2482897676f964a4d2dcb6982252ee4ff56aac49578b23f72d1fced707525e", + "0xb9ddff8986145e33851b4de54d3e81faa3352e8385895f357734085a1616ef61c692d925fe62a5ed3be8ca49f5d66306", + "0xb3cb0963387ed28c0c0adf7fe645f02606e6e1780a24d6cecef5b7c642499109974c81a7c2a198b19862eedcea2c2d8c", + "0xac9c53c885457aaf5cb36c717a6f4077af701e0098eebd7aa600f5e4b14e6c1067255b3a0bc40e4a552025231be7de60", + "0x8e1a8d823c4603f6648ec21d064101094f2a762a4ed37dd2f0a2d9aa97b2d850ce1e76f4a4b8cae58819b058180f7031", + "0xb268b73bf7a179b6d22bd37e5e8cb514e9f5f8968c78e14e4f6d5700ca0d0ca5081d0344bb73b028970eebde3cb4124e", + "0xa7f57d71940f0edbd29ed8473d0149cae71d921dd15d1ff589774003e816b54b24de2620871108cec1ab9fa956ad6ce6", + "0x8053e6416c8b120e2b999cc2fc420a6a55094c61ac7f2a6c6f0a2c108a320890e389af96cbe378936132363c0d551277", + "0xb3823f4511125e5aa0f4269e991b435a0d6ceb523ebd91c04d7add5534e3df5fc951c504b4fd412a309fd3726b7f940b", + "0xae6eb04674d04e982ca9a6add30370ab90e303c71486f43ed3efbe431af1b0e43e9d06c11c3412651f304c473e7dbf39", + "0x96ab55e641ed2e677591f7379a3cd126449614181fce403e93e89b1645d82c4af524381ff986cae7f9cebe676878646d", + "0xb52423b4a8c37d3c3e2eca8f0ddbf7abe0938855f33a0af50f117fab26415fb0a3da5405908ec5fdc22a2c1f2ca64892", + "0x82a69ce1ee92a09cc709d0e3cd22116c9f69d28ea507fe5901f5676000b5179b9abe4c1875d052b0dd42d39925e186bb", + "0xa84c8cb84b9d5cfb69a5414f0a5283a5f2e90739e9362a1e8c784b96381b59ac6c18723a4aa45988ee8ef5c1f45cc97d", + "0xafd7efce6b36813082eb98257aae22a4c1ae97d51cac7ea9c852d4a66d05ef2732116137d8432e3f117119725a817d24", + "0xa0f5fe25af3ce021b706fcff05f3d825384a272284d04735574ce5fb256bf27100fad0b1f1ba0e54ae9dcbb9570ecad3", + "0x8751786cb80e2e1ff819fc7fa31c2833d25086534eb12b373d31f826382430acfd87023d2a688c65b5e983927e146336", + "0x8cf5c4b17fa4f3d35c78ce41e1dc86988fd1135cd5e6b2bb0c108ee13538d0d09ae7102609c6070f39f937b439b31e33", + "0xa9108967a2fedd7c322711eca8159c533dd561bedcb181b646de98bf5c3079449478eab579731bee8d215ae8852c7e21", + "0xb54c5171704f42a6f0f4e70767cdb3d96ffc4888c842eece343a01557da405961d53ffdc34d2f902ea25d3e1ed867cad", + "0xae8d4b764a7a25330ba205bf77e9f46182cd60f94a336bbd96773cf8064e3d39caf04c310680943dc89ed1fbad2c6e0d", + "0xaa5150e911a8e1346868e1b71c5a01e2a4bb8632c195861fb6c3038a0e9b85f0e09b3822e9283654a4d7bb17db2fc5f4", + "0x9685d3756ce9069bf8bb716cf7d5063ebfafe37e15b137fc8c3159633c4e006ff4887ddd0ae90360767a25c3f90cba7f", + "0x82155fd70f107ab3c8e414eadf226c797e07b65911508c76c554445422325e71af8c9a8e77fd52d94412a6fc29417cd3", + "0xabfae52f53a4b6e00760468d973a267f29321997c3dbb5aee36dc1f20619551229c0c45b9d9749f410e7f531b73378e8", + "0x81a76d921f8ef88e774fd985e786a4a330d779b93fad7def718c014685ca0247379e2e2a007ad63ee7f729cd9ed6ce1b", + "0x81947c84bc5e28e26e2e533af5ae8fe10407a7b77436dbf8f1d5b0bbe86fc659eae10f974659dc7c826c6dabd03e3a4b", + "0x92b8c07050d635b8dd4fd09df9054efe4edae6b86a63c292e73cc819a12a21dd7d104ce51fa56af6539dedf6dbe6f7b6", + "0xb44c579e3881f32b32d20c82c207307eca08e44995dd2aac3b2692d2c8eb2a325626c80ac81c26eeb38c4137ff95add5", + "0x97efab8941c90c30860926dea69a841f2dcd02980bf5413b9fd78d85904588bf0c1021798dbc16c8bbb32cce66c82621", + "0x913363012528b50698e904de0588bf55c8ec5cf6f0367cfd42095c4468fcc64954fbf784508073e542fee242d0743867", + "0x8ed203cf215148296454012bd10fddaf119203db1919a7b3d2cdc9f80e66729464fdfae42f1f2fc5af1ed53a42b40024", + "0xab84312db7b87d711e9a60824f4fe50e7a6190bf92e1628688dfcb38930fe87b2d53f9e14dd4de509b2216856d8d9188", + "0x880726def069c160278b12d2258eac8fa63f729cd351a710d28b7e601c6712903c3ac1e7bbd0d21e4a15f13ca49db5aa", + "0x980699cd51bac6283959765f5174e543ed1e5f5584b5127980cbc2ef18d984ecabba45042c6773b447b8e694db066028", + "0xaeb019cb80dc4cb4207430d0f2cd24c9888998b6f21d9bf286cc638449668d2eec0018a4cf3fe6448673cd6729335e2b", + "0xb29852f6aa6c60effdffe96ae88590c88abae732561d35cc19e82d3a51e26cb35ea00986193e07f90060756240f5346e", + "0xa0fa855adc5ba469f35800c48414b8921455950a5c0a49945d1ef6e8f2a1881f2e2dfae47de6417270a6bf49deeb091d", + "0xb6c7332e3b14813641e7272d4f69ecc7e09081df0037d6dab97ce13a9e58510f5c930d300633f208181d9205c5534001", + "0x85a6c050f42fce560b5a8d54a11c3bbb8407abbadd859647a7b0c21c4b579ec65671098b74f10a16245dc779dff7838e", + "0x8f3eb34bb68759d53c6677de4de78a6c24dd32c8962a7fb355ed362572ef8253733e6b52bc21c9f92ecd875020a9b8de", + "0xa17dd44181e5dab4dbc128e1af93ec22624b57a448ca65d2d9e246797e4af7d079e09c6e0dfb62db3a9957ce92f098d5", + "0xa56a1b854c3183082543a8685bb34cae1289f86cfa8123a579049dbd059e77982886bfeb61bf6e05b4b1fe4e620932e7", + "0xaedae3033cb2fb7628cb4803435bdd7757370a86f808ae4cecb9a268ad0e875f308c048c80cbcac523de16b609683887", + "0x9344905376aa3982b1179497fac5a1d74b14b7038fd15e3b002db4c11c8bfc7c39430db492cdaf58b9c47996c9901f28", + "0xa3bfafdae011a19f030c749c3b071f83580dee97dd6f949e790366f95618ca9f828f1daaeabad6dcd664fcef81b6556d", + "0x81c03d8429129e7e04434dee2c529194ddb01b414feda3adee2271eb680f6c85ec872a55c9fa9d2096f517e13ed5abcc", + "0x98205ef3a72dff54c5a9c82d293c3e45d908946fa74bb749c3aabe1ab994ea93c269bcce1a266d2fe67a8f02133c5985", + "0x85a70aeed09fda24412fadbafbbbf5ba1e00ac92885df329e147bfafa97b57629a3582115b780d8549d07d19b7867715", + "0xb0fbe81c719f89a57d9ea3397705f898175808c5f75f8eb81c2193a0b555869ba7bd2e6bc54ee8a60cea11735e21c68c", + "0xb03a0bd160495ee626ff3a5c7d95bc79d7da7e5a96f6d10116600c8fa20bedd1132f5170f25a22371a34a2d763f2d6d0", + "0xa90ab04091fbca9f433b885e6c1d60ab45f6f1daf4b35ec22b09909d493a6aab65ce41a6f30c98239cbca27022f61a8b", + "0xb66f92aa3bf2549f9b60b86f99a0bd19cbdd97036d4ae71ca4b83d669607f275260a497208f6476cde1931d9712c2402", + "0xb08e1fdf20e6a9b0b4942f14fa339551c3175c1ffc5d0ab5b226b6e6a322e9eb0ba96adc5c8d59ca4259e2bdd04a7eb0", + "0xa2812231e92c1ce74d4f5ac3ab6698520288db6a38398bb38a914ac9326519580af17ae3e27cde26607e698294022c81", + "0xabfcbbcf1d3b9e84c02499003e490a1d5d9a2841a9e50c7babbef0b2dd20d7483371d4dc629ba07faf46db659459d296", + "0xb0fe9f98c3da70927c23f2975a9dc4789194d81932d2ad0f3b00843dd9cbd7fb60747a1da8fe5a79f136a601becf279d", + "0xb130a6dba7645165348cb90f023713bed0eefbd90a976b313521c60a36d34f02032e69a2bdcf5361e343ed46911297ec", + "0x862f0cffe3020cea7a5fd4703353aa1eb1be335e3b712b29d079ff9f7090d1d8b12013011e1bdcbaa80c44641fd37c9f", + "0x8c6f11123b26633e1abb9ed857e0bce845b2b3df91cc7b013b2fc77b477eee445da0285fc6fc793e29d5912977f40916", + "0x91381846126ea819d40f84d3005e9fb233dc80071d1f9bb07f102bf015f813f61e5884ffffb4f5cd333c1b1e38a05a58", + "0x8add7d908de6e1775adbd39c29a391f06692b936518db1f8fde74eb4f533fc510673a59afb86e3a9b52ade96e3004c57", + "0x8780e086a244a092206edcde625cafb87c9ab1f89cc3e0d378bc9ee776313836160960a82ec397bc3800c0a0ec3da283", + "0xa6cb4cd9481e22870fdd757fae0785edf4635e7aacb18072fe8dc5876d0bab53fb99ce40964a7d3e8bcfff6f0ab1332f", + "0xaf30ff47ecc5b543efba1ba4706921066ca8bb625f40e530fb668aea0551c7647a9d126e8aba282fbcce168c3e7e0130", + "0x91b0bcf408ce3c11555dcb80c4410b5bc2386d3c05caec0b653352377efdcb6bab4827f2018671fc8e4a0e90d772acc1", + "0xa9430b975ef138b6b2944c7baded8fe102d31da4cfe3bd3d8778bda79189c99d38176a19c848a19e2d1ee0bddd9a13c1", + "0xaa5a4eef849d7c9d2f4b018bd01271c1dd83f771de860c4261f385d3bdcc130218495860a1de298f14b703ec32fa235f", + "0xb0ce79e7f9ae57abe4ff366146c3b9bfb38b0dee09c28c28f5981a5d234c6810ad4d582751948affb480d6ae1c8c31c4", + "0xb75122748560f73d15c01a8907d36d06dc068e82ce22b84b322ac1f727034493572f7907dec34ebc3ddcc976f2f89ed7", + "0xb0fc7836369a3e4411d34792d6bd5617c14f61d9bba023dda64e89dc5fb0f423244e9b48ee64869258931daa9753a56f", + "0x8956d7455ae9009d70c6e4a0bcd7610e55f37494cf9897a8f9e1b904cc8febc3fd2d642ebd09025cfff4609ad7e3bc52", + "0xad741efe9e472026aa49ae3d9914cb9c1a6f37a54f1a6fe6419bebd8c7d68dca105a751c7859f4389505ede40a0de786", + "0xb52f418797d719f0d0d0ffb0846788b5cba5d0454a69a2925de4b0b80fa4dd7e8c445e5eac40afd92897ed28ca650566", + "0xa0ab65fb9d42dd966cd93b1de01d7c822694669dd2b7a0c04d99cd0f3c3de795f387b9c92da11353412f33af5c950e9a", + "0xa0052f44a31e5741a331f7cac515a08b3325666d388880162d9a7b97598fde8b61f9ff35ff220df224eb5c4e40ef0567", + "0xa0101cfdc94e42b2b976c0d89612a720e55d145a5ef6ef6f1f78cf6de084a49973d9b5d45915349c34ce712512191e3c", + "0xa0dd99fcf3f5cead5aaf08e82212df3a8bb543c407a4d6fab88dc5130c1769df3f147e934a46f291d6c1a55d92b86917", + "0xa5939153f0d1931bbda5cf6bdf20562519ea55fbfa978d6dbc6828d298260c0da7a50c37c34f386e59431301a96c2232", + "0x9568269f3f5257200f9ca44afe1174a5d3cf92950a7f553e50e279c239e156a9faaa2a67f288e3d5100b4142efe64856", + "0xb746b0832866c23288e07f24991bbf687cad794e7b794d3d3b79367566ca617d38af586cdc8d6f4a85a34835be41d54f", + "0xa871ce28e39ab467706e32fec1669fda5a4abba2f8c209c6745df9f7a0fa36bbf1919cf14cb89ea26fa214c4c907ae03", + "0xa08dacdd758e523cb8484f6bd070642c0c20e184abdf8e2a601f61507e93952d5b8b0c723c34fcbdd70a8485eec29db2", + "0x85bdb78d501382bb95f1166b8d032941005661aefd17a5ac32df9a3a18e9df2fc5dc2c1f07075f9641af10353cecc0c9", + "0x98d730c28f6fa692a389e97e368b58f4d95382fad8f0baa58e71a3d7baaea1988ead47b13742ce587456f083636fa98e", + "0xa557198c6f3d5382be9fb363feb02e2e243b0c3c61337b3f1801c4a0943f18e38ce1a1c36b5c289c8fa2aa9d58742bab", + "0x89174f79201742220ac689c403fc7b243eed4f8e3f2f8aba0bf183e6f5d4907cb55ade3e238e3623d9885f03155c4d2b", + "0xb891d600132a86709e06f3381158db300975f73ea4c1f7c100358e14e98c5fbe792a9af666b85c4e402707c3f2db321e", + "0xb9e5b2529ef1043278c939373fc0dbafe446def52ddd0a8edecd3e4b736de87e63e187df853c54c28d865de18a358bb6", + "0x8589b2e9770340c64679062c5badb7bbef68f55476289b19511a158a9a721f197da03ece3309e059fc4468b15ac33aa3", + "0xaad8c6cd01d785a881b446f06f1e9cd71bca74ba98674c2dcddc8af01c40aa7a6d469037498b5602e76e9c91a58d3dbd", + "0xabaccb1bd918a8465f1bf8dbe2c9ad4775c620b055550b949a399f30cf0d9eb909f3851f5b55e38f9e461e762f88f499", + "0xae62339d26db46e85f157c0151bd29916d5cc619bd4b832814b3fd2f00af8f38e7f0f09932ffe5bba692005dab2d9a74", + "0x93a6ff30a5c0edf8058c89aba8c3259e0f1b1be1b80e67682de651e5346f7e1b4b4ac3d87cbaebf198cf779524aff6bf", + "0x8980a2b1d8f574af45b459193c952400b10a86122b71fca2acb75ee0dbd492e7e1ef5b959baf609a5172115e371f3177", + "0x8c2f49f3666faee6940c75e8c7f6f8edc3f704cca7a858bbb7ee5e96bba3b0cf0993996f781ba6be3b0821ef4cb75039", + "0xb14b9e348215b278696018330f63c38db100b0542cfc5be11dc33046e3bca6a13034c4ae40d9cef9ea8b34fef0910c4e", + "0xb59bc3d0a30d66c16e6a411cb641f348cb1135186d5f69fda8b0a0934a5a2e7f6199095ba319ec87d3fe8f1ec4a06368", + "0x8874aca2a3767aa198e4c3fec2d9c62d496bc41ff71ce242e9e082b7f38cdf356089295f80a301a3cf1182bde5308c97", + "0xb1820ebd61376d91232423fc20bf008b2ba37e761199f4ef0648ea2bd70282766799b4de814846d2f4d516d525c8daa7", + "0xa6b202e5dedc16a4073e04a11af3a8509b23dfe5a1952f899adeb240e75c3f5bde0c424f811a81ea48d343591faffe46", + "0xa69becee9c93734805523b92150a59a62eed4934f66056b645728740d42223f2925a1ad38359ba644da24d9414f4cdda", + "0xad72f0f1305e37c7e6b48c272323ee883320994cb2e0d850905d6655fafc9f361389bcb9c66b3ff8d2051dbb58c8aa96", + "0xb563600bd56fad7c8853af21c6a02a16ed9d8a8bbeea2c31731d63b976d83cb05b9779372d898233e8fd597a75424797", + "0xb0abb78ce465bf7051f563c62e8be9c57a2cc997f47c82819300f36e301fefd908894bb2053a9d27ce2d0f8c46d88b5b", + "0xa071a85fb8274bac2202e0cb8e0e2028a5e138a82d6e0374d39ca1884a549c7c401312f00071b91f455c3a2afcfe0cda", + "0xb931c271513a0f267b9f41444a5650b1918100b8f1a64959c552aff4e2193cc1b9927906c6fa7b8a8c68ef13d79aaa52", + "0xa6a1bb9c7d32cb0ca44d8b75af7e40479fbce67d216b48a2bb680d3f3a772003a49d3cd675fc64e9e0f8fabeb86d6d61", + "0xb98d609858671543e1c3b8564162ad828808bb50ded261a9f8690ded5b665ed8368c58f947365ed6e84e5a12e27b423d", + "0xb3dca58cd69ec855e2701a1d66cad86717ff103ef862c490399c771ad28f675680f9500cb97be48de34bcdc1e4503ffd", + "0xb34867c6735d3c49865e246ddf6c3b33baf8e6f164db3406a64ebce4768cb46b0309635e11be985fee09ab7a31d81402", + "0xacb966c554188c5b266624208f31fab250b3aa197adbdd14aee5ab27d7fb886eb4350985c553b20fdf66d5d332bfd3fe", + "0x943c36a18223d6c870d54c3b051ef08d802b85e9dd6de37a51c932f90191890656c06adfa883c87b906557ae32d09da0", + "0x81bca7954d0b9b6c3d4528aadf83e4bc2ef9ea143d6209bc45ae9e7ae9787dbcd8333c41f12c0b6deee8dcb6805e826a", + "0xaba176b92256efb68f574e543479e5cf0376889fb48e3db4ebfb7cba91e4d9bcf19dcfec444c6622d9398f06de29e2b9", + "0xb9f743691448053216f6ece7cd699871fff4217a1409ceb8ab7bdf3312d11696d62c74b0664ba0a631b1e0237a8a0361", + "0xa383c2b6276fa9af346b21609326b53fb14fdf6f61676683076e80f375b603645f2051985706d0401e6fbed7eb0666b6", + "0xa9ef2f63ec6d9beb8f3d04e36807d84bda87bdd6b351a3e4a9bf7edcb5618c46c1f58cfbf89e64b40f550915c6988447", + "0xa141b2d7a82f5005eaea7ae7d112c6788b9b95121e5b70b7168d971812f3381de8b0082ac1f0a82c7d365922ebd2d26a", + "0xb1b76ef8120e66e1535c17038b75255a07849935d3128e3e99e56567b842fb1e8d56ef932d508d2fb18b82f7868fe1a9", + "0x8e2e234684c81f21099f5c54f6bbe2dd01e3b172623836c77668a0c49ce1fe218786c3827e4d9ae2ea25c50a8924fb3c", + "0xa5caf5ff948bfd3c4ca3ffbdfcd91eec83214a6c6017235f309a0bbf7061d3b0b466307c00b44a1009cf575163898b43", + "0x986415a82ca16ebb107b4c50b0c023c28714281db0bcdab589f6cb13d80e473a3034b7081b3c358e725833f6d845cb14", + "0xb94836bf406ac2cbacb10e6df5bcdfcc9d9124ae1062767ca4e322d287fd5e353fdcebd0e52407cb3cd68571258a8900", + "0x83c6d70a640b33087454a4788dfd9ef3ed00272da084a8d36be817296f71c086b23b576f98178ab8ca6a74f04524b46b", + "0xad4115182ad784cfe11bcfc5ce21fd56229cc2ce77ac82746e91a2f0aa53ca6593a22efd2dc4ed8d00f84542643d9c58", + "0xab1434c5e5065da826d10c2a2dba0facccab0e52b506ce0ce42fbe47ced5a741797151d9ecc99dc7d6373cfa1779bbf6", + "0x8a8b591d82358d55e6938f67ea87a89097ab5f5496f7260adb9f649abb289da12b498c5b2539c2f9614fb4e21b1f66b0", + "0x964f355d603264bc1f44c64d6d64debca66f37dff39c971d9fc924f2bc68e6c187b48564a6dc82660a98b035f8addb5d", + "0xb66235eaaf47456bc1dc4bde454a028e2ce494ece6b713a94cd6bf27cf18c717fd0c57a5681caaa2ad73a473593cdd7a", + "0x9103e3bb74304186fa4e3e355a02da77da4aca9b7e702982fc2082af67127ebb23a455098313c88465bc9b7d26820dd5", + "0xb6a42ff407c9dd132670cdb83cbad4b20871716e44133b59a932cd1c3f97c7ac8ff7f61acfaf8628372508d8dc8cad7c", + "0x883a9c21c16a167a4171b0f084565c13b6f28ba7c4977a0de69f0a25911f64099e7bbb4da8858f2e93068f4155d04e18", + "0x8dbb3220abc6a43220adf0331e3903d3bfd1d5213aadfbd8dfcdf4b2864ce2e96a71f35ecfb7a07c3bbabf0372b50271", + "0xb4ad08aee48e176bda390b7d9acf2f8d5eb008f30d20994707b757dc6a3974b2902d29cd9b4d85e032810ad25ac49e97", + "0x865bb0f33f7636ec501bb634e5b65751c8a230ae1fa807a961a8289bbf9c7fe8c59e01fbc4c04f8d59b7f539cf79ddd5", + "0x86a54d4c12ad1e3605b9f93d4a37082fd26e888d2329847d89afa7802e815f33f38185c5b7292293d788ad7d7da1df97", + "0xb26c8615c5e47691c9ff3deca3021714662d236c4d8401c5d27b50152ce7e566266b9d512d14eb63e65bc1d38a16f914", + "0x827639d5ce7db43ba40152c8a0eaad443af21dc92636cc8cc2b35f10647da7d475a1e408901cd220552fddad79db74df", + "0xa2b79a582191a85dbe22dc384c9ca3de345e69f6aa370aa6d3ff1e1c3de513e30b72df9555b15a46586bd27ea2854d9d", + "0xae0d74644aba9a49521d3e9553813bcb9e18f0b43515e4c74366e503c52f47236be92dfbd99c7285b3248c267b1de5a0", + "0x80fb0c116e0fd6822a04b9c25f456bdca704e2be7bdc5d141dbf5d1c5eeb0a2c4f5d80db583b03ef3e47517e4f9a1b10", + "0xac3a1fa3b4a2f30ea7e0a114cdc479eb51773573804c2a158d603ad9902ae8e39ffe95df09c0d871725a5d7f9ba71a57", + "0xb56b2b0d601cba7f817fa76102c68c2e518c6f20ff693aad3ff2e07d6c4c76203753f7f91686b1801e8c4659e4d45c48", + "0x89d50c1fc56e656fb9d3915964ebce703cb723fe411ab3c9eaa88ccc5d2b155a9b2e515363d9c600d3c0cee782c43f41", + "0xb24207e61462f6230f3cd8ccf6828357d03e725769f7d1de35099ef9ee4dca57dbce699bb49ed994462bee17059d25ce", + "0xb886f17fcbcbfcd08ac07f04bb9543ef58510189decaccea4b4158c9174a067cb67d14b6be3c934e6e2a18c77efa9c9c", + "0xb9c050ad9cafd41c6e2e192b70d080076eed59ed38ea19a12bd92fa17b5d8947d58d5546aaf5e8e27e1d3b5481a6ce51", + "0xaaf7a34d3267e3b1ddbc54c641e3922e89303f7c86ebebc7347ebca4cffad5b76117dac0cbae1a133053492799cd936f", + "0xa9ee604ada50adef82e29e893070649d2d4b7136cc24fa20e281ce1a07bd736bf0de7c420369676bcbcecff26fb6e900", + "0x9855315a12a4b4cf80ab90b8bd13003223ba25206e52fd4fe6a409232fbed938f30120a3db23eab9c53f308bd8b9db81", + "0x8cd488dd7a24f548a3cf03c54dec7ff61d0685cb0f6e5c46c2d728e3500d8c7bd6bba0156f4bf600466fda53e5b20444", + "0x890ad4942ebac8f5b16c777701ab80c68f56fa542002b0786f8fea0fb073154369920ac3dbfc07ea598b82f4985b8ced", + "0x8de0cf9ddc84c9b92c59b9b044387597799246b30b9f4d7626fc12c51f6e423e08ee4cbfe9289984983c1f9521c3e19d", + "0xb474dfb5b5f4231d7775b3c3a8744956b3f0c7a871d835d7e4fd9cc895222c7b868d6c6ce250de568a65851151fac860", + "0x86433b6135d9ed9b5ee8cb7a6c40e5c9d30a68774cec04988117302b8a02a11a71a1e03fd8e0264ef6611d219f103007", + "0x80b9ed4adbe9538fb1ef69dd44ec0ec5b57cbfea820054d8d445b4261962624b4c70ac330480594bc5168184378379c3", + "0x8b2e83562ccd23b7ad2d17f55b1ab7ef5fbef64b3a284e6725b800f3222b8bdf49937f4a873917ada9c4ddfb090938c2", + "0xabe78cebc0f5a45d754140d1f685e387489acbfa46d297a8592aaa0d676a470654f417a4f7d666fc0b2508fab37d908e", + "0xa9c5f8ff1f8568e252b06d10e1558326db9901840e6b3c26bbd0cd5e850cb5fb3af3f117dbb0f282740276f6fd84126f", + "0x975f8dc4fb55032a5df3b42b96c8c0ffecb75456f01d4aef66f973cb7270d4eff32c71520ceefc1adcf38d77b6b80c67", + "0xb043306ed2c3d8a5b9a056565afd8b5e354c8c4569fda66b0d797a50a3ce2c08cffbae9bbe292da69f39e89d5dc7911e", + "0x8d2afc36b1e44386ba350c14a6c1bb31ff6ea77128a0c5287584ac3584282d18516901ce402b4644a53db1ed8e7fa581", + "0x8c294058bed53d7290325c363fe243f6ec4f4ea2343692f4bac8f0cb86f115c069ccb8334b53d2e42c067691ad110dba", + "0xb92157b926751aaf7ef82c1aa8c654907dccab6376187ee8b3e8c0c82811eae01242832de953faa13ebaff7da8698b3e", + "0xa780c4bdd9e4ba57254b09d745075cecab87feda78c88ffee489625c5a3cf96aa6b3c9503a374a37927d9b78de9bd22b", + "0x811f548ef3a2e6a654f7dcb28ac9378de9515ed61e5a428515d9594a83e80b35c60f96a5cf743e6fab0d3cb526149f49", + "0x85a4dccf6d90ee8e094731eec53bd00b3887aec6bd81a0740efddf812fd35e3e4fe4f983afb49a8588691c202dabf942", + "0xb152c2da6f2e01c8913079ae2b40a09b1f361a80f5408a0237a8131b429677c3157295e11b365b1b1841924b9efb922e", + "0x849b9efee8742502ffd981c4517c88ed33e4dd518a330802caff168abae3cd09956a5ee5eda15900243bc2e829016b74", + "0x955a933f3c18ec0f1c0e38fa931e4427a5372c46a3906ebe95082bcf878c35246523c23f0266644ace1fa590ffa6d119", + "0x911989e9f43e580c886656377c6f856cdd4ff1bd001b6db3bbd86e590a821d34a5c6688a29b8d90f28680e9fdf03ba69", + "0xb73b8b4f1fd6049fb68d47cd96a18fcba3f716e0a1061aa5a2596302795354e0c39dea04d91d232aec86b0bf2ba10522", + "0x90f87456d9156e6a1f029a833bf3c7dbed98ca2f2f147a8564922c25ae197a55f7ea9b2ee1f81bf7383197c4bad2e20c", + "0x903cba8b1e088574cb04a05ca1899ab00d8960580c884bd3c8a4c98d680c2ad11410f2b75739d6050f91d7208cac33a5", + "0x9329987d42529c261bd15ecedd360be0ea8966e7838f32896522c965adfc4febf187db392bd441fb43bbd10c38fdf68b", + "0x8178ee93acf5353baa349285067b20e9bb41aa32d77b5aeb7384fe5220c1fe64a2461bd7a83142694fe673e8bbf61b7c", + "0xa06a8e53abcff271b1394bcc647440f81fb1c1a5f29c27a226e08f961c3353f4891620f2d59b9d1902bf2f5cc07a4553", + "0xaaf5fe493b337810889e777980e6bbea6cac39ac66bc0875c680c4208807ac866e9fda9b5952aa1d04539b9f4a4bec57", + "0xaa058abb1953eceac14ccfa7c0cc482a146e1232905dcecc86dd27f75575285f06bbae16a8c9fe8e35d8713717f5f19f", + "0x8f15dd732799c879ca46d2763453b359ff483ca33adb1d0e0a57262352e0476c235987dc3a8a243c74bc768f93d3014c", + "0xa61cc8263e9bc03cce985f1663b8a72928a607121005a301b28a278e9654727fd1b22bc8a949af73929c56d9d3d4a273", + "0x98d6dc78502d19eb9f921225475a6ebcc7b44f01a2df6f55ccf6908d65b27af1891be2a37735f0315b6e0f1576c1f8d8", + "0x8bd258b883f3b3793ec5be9472ad1ff3dc4b51bc5a58e9f944acfb927349ead8231a523cc2175c1f98e7e1e2b9f363b8", + "0xaeacc2ecb6e807ad09bedd99654b097a6f39840e932873ace02eabd64ccfbb475abdcb62939a698abf17572d2034c51e", + "0xb8ccf78c08ccd8df59fd6eda2e01de328bc6d8a65824d6f1fc0537654e9bc6bf6f89c422dd3a295cce628749da85c864", + "0x8f91fd8cb253ba2e71cc6f13da5e05f62c2c3b485c24f5d68397d04665673167fce1fc1aec6085c69e87e66ec555d3fd", + "0xa254baa10cb26d04136886073bb4c159af8a8532e3fd36b1e9c3a2e41b5b2b6a86c4ebc14dbe624ee07b7ccdaf59f9ab", + "0x94e3286fe5cd68c4c7b9a7d33ae3d714a7f265cf77cd0e9bc19fc51015b1d1c34ad7e3a5221c459e89f5a043ee84e3a9", + "0xa279da8878af8d449a9539bec4b17cea94f0242911f66fab275b5143ab040825f78c89cb32a793930609415cfa3a1078", + "0xac846ceb89c9e5d43a2991c8443079dc32298cd63e370e64149cec98cf48a6351c09c856f2632fd2f2b3d685a18bbf8b", + "0xa847b27995c8a2e2454aaeb983879fb5d3a23105c33175839f7300b7e1e8ec3efd6450e9fa3f10323609dee7b98c6fd5", + "0xa2f432d147d904d185ff4b2de8c6b82fbea278a2956bc406855b44c18041854c4f0ecccd472d1d0dff1d8aa8e281cb1d", + "0x94a48ad40326f95bd63dff4755f863a1b79e1df771a1173b17937f9baba57b39e651e7695be9f66a472f098b339364fc", + "0xa12a0ccd8f96e96e1bc6494341f7ebce959899341b3a084aa1aa87d1c0d489ac908552b7770b887bb47e7b8cbc3d8e66", + "0x81a1f1681bda923bd274bfe0fbb9181d6d164fe738e54e25e8d4849193d311e2c4253614ed673c98af2c798f19a93468", + "0xabf71106a05d501e84cc54610d349d7d5eae21a70bd0250f1bebbf412a130414d1c8dbe673ffdb80208fd72f1defa4d4", + "0x96266dc2e0df18d8136d79f5b59e489978eee0e6b04926687fe389d4293c14f36f055c550657a8e27be4118b64254901", + "0x8df5dcbefbfb4810ae3a413ca6b4bf08619ca53cd50eb1dde2a1c035efffc7b7ac7dff18d403253fd80104bd83dc029e", + "0x9610b87ff02e391a43324a7122736876d5b3af2a137d749c52f75d07b17f19900b151b7f439d564f4529e77aa057ad12", + "0xa90a5572198b40fe2fcf47c422274ff36c9624df7db7a89c0eb47eb48a73a03c985f4ac5016161c76ca317f64339bce1", + "0x98e5e61a6ab6462ba692124dba7794b6c6bde4249ab4fcc98c9edd631592d5bc2fb5e38466691a0970a38e48d87c2e43", + "0x918cefb8f292f78d4db81462c633daf73b395e772f47b3a7d2cea598025b1d8c3ec0cbff46cdb23597e74929981cde40", + "0xa98918a5dc7cf610fe55f725e4fd24ce581d594cb957bb9b4e888672e9c0137003e1041f83e3f1d7b9caab06462c87d4", + "0xb92b74ac015262ca66c33f2d950221e19d940ba3bf4cf17845f961dc1729ae227aa9e1f2017829f2135b489064565c29", + "0xa053ee339f359665feb178b4e7ee30a85df37debd17cacc5a27d6b3369d170b0114e67ad1712ed26d828f1df641bcd99", + "0x8c3c8bad510b35da5ce5bd84b35c958797fbea024ad1c97091d2ff71d9b962e9222f65a9b776e5b3cc29c36e1063d2ee", + "0xaf99dc7330fe7c37e850283eb47cc3257888e7c197cb0d102edf94439e1e02267b6a56306d246c326c4c79f9dc8c6986", + "0xafecb2dc34d57a725efbd7eb93d61eb29dbe8409b668ab9ea040791f5b796d9be6d4fc10d7f627bf693452f330cf0435", + "0x93334fedf19a3727a81a6b6f2459db859186227b96fe7a391263f69f1a0884e4235de64d29edebc7b99c44d19e7c7d7a", + "0x89579c51ac405ad7e9df13c904061670ce4b38372492764170e4d3d667ed52e5d15c7cd5c5991bbfa3a5e4e3fa16363e", + "0x9778f3e8639030f7ef1c344014f124e375acb8045bd13d8e97a92c5265c52de9d1ffebaa5bc3e1ad2719da0083222991", + "0x88f77f34ee92b3d36791bdf3326532524a67d544297dcf1a47ff00b47c1b8219ff11e34034eab7d23b507caa2fd3c6b9", + "0xa699c1e654e7c484431d81d90657892efeb4adcf72c43618e71ca7bd7c7a7ebbb1db7e06e75b75dc4c74efd306b5df3f", + "0x81d13153baebb2ef672b5bdb069d3cd669ce0be96b742c94e04038f689ff92a61376341366b286eee6bf3ae85156f694", + "0x81efb17de94400fdacc1deec2550cbe3eecb27c7af99d8207e2f9be397e26be24a40446d2a09536bb5172c28959318d9", + "0x989b21ebe9ceab02488992673dc071d4d5edec24bff0e17a4306c8cb4b3c83df53a2063d1827edd8ed16d6e837f0d222", + "0x8d6005d6536825661b13c5fdce177cb37c04e8b109b7eb2b6d82ea1cb70efecf6a0022b64f84d753d165edc2bba784a3", + "0xa32607360a71d5e34af2271211652d73d7756d393161f4cf0da000c2d66a84c6826e09e759bd787d4fd0305e2439d342", + "0xaaad8d6f6e260db45d51b2da723be6fa832e76f5fbcb77a9a31e7f090dd38446d3b631b96230d78208cae408c288ac4e", + "0xabcfe425255fd3c5cffd3a818af7650190c957b6b07b632443f9e33e970a8a4c3bf79ac9b71f4d45f238a04d1c049857", + "0xaeabf026d4c783adc4414b5923dbd0be4b039cc7201219f7260d321f55e9a5b166d7b5875af6129c034d0108fdc5d666", + "0xaf49e740c752d7b6f17048014851f437ffd17413c59797e5078eaaa36f73f0017c3e7da020310cfe7d3c85f94a99f203", + "0x8854ca600d842566e3090040cd66bb0b3c46dae6962a13946f0024c4a8aca447e2ccf6f240045f1ceee799a88cb9210c", + "0xb6c03b93b1ab1b88ded8edfa1b487a1ed8bdce8535244dddb558ffb78f89b1c74058f80f4db2320ad060d0c2a9c351cc", + "0xb5bd7d17372faff4898a7517009b61a7c8f6f0e7ed4192c555db264618e3f6e57fb30a472d169fea01bf2bf0362a19a8", + "0x96eb1d38319dc74afe7e7eb076fcd230d19983f645abd14a71e6103545c01301b31c47ae931e025f3ecc01fb3d2f31fa", + "0xb55a8d30d4403067def9b65e16f867299f8f64c9b391d0846d4780bc196569622e7e5b64ce799b5aefac8f965b2a7a7b", + "0x8356d199a991e5cbbff608752b6291731b6b6771aed292f8948b1f41c6543e4ab1bedc82dd26d10206c907c03508df06", + "0x97f4137445c2d98b0d1d478049de952610ad698c91c9d0f0e7227d2aae690e9935e914ec4a2ea1fbf3fc1dddfeeacebb", + "0xaf5621707e0938320b15ddfc87584ab325fbdfd85c30efea36f8f9bd0707d7ec12c344eff3ec21761189518d192df035", + "0x8ac7817e71ea0825b292687928e349da7140285d035e1e1abff0c3704fa8453faaae343a441b7143a74ec56539687cc4", + "0x8a5e0a9e4758449489df10f3386029ada828d1762e4fb0a8ffe6b79e5b6d5d713cb64ed95960e126398b0cdb89002bc9", + "0x81324be4a71208bbb9bca74b77177f8f1abb9d3d5d9db195d1854651f2cf333cd618d35400da0f060f3e1b025124e4b2", + "0x849971d9d095ae067525b3cbc4a7dfae81f739537ade6d6cec1b42fb692d923176197a8770907c58069754b8882822d6", + "0x89f830825416802477cc81fdf11084885865ee6607aa15aa4eb28e351c569c49b8a1b9b5e95ddc04fa0ebafe20071313", + "0x9240aeeaff37a91af55f860b9badd466e8243af9e8c96a7aa8cf348cd270685ab6301bc135b246dca9eda696f8b0e350", + "0xacf74db78cc33138273127599eba35b0fb4e7b9a69fe02dae18fc6692d748ca332bd00b22afa8e654ed587aab11833f3", + "0xb091e6d37b157b50d76bd297ad752220cd5c9390fac16dc838f8557aed6d9833fc920b61519df21265406216315e883f", + "0xa6446c429ebf1c7793c622250e23594c836b2fbcaf6c5b3d0995e1595a37f50ea643f3e549b0be8bbdadd69044d72ab9", + "0x93e675353bd60e996bf1c914d5267eeaa8a52fc3077987ccc796710ef9becc6b7a00e3d82671a6bdfb8145ee3c80245a", + "0xa2f731e43251d04ed3364aa2f072d05355f299626f2d71a8a38b6f76cf08c544133f7d72dd0ab4162814b674b9fc7fa6", + "0x97a8b791a5a8f6e1d0de192d78615d73d0c38f1e557e4e15d15adc663d649e655bc8da3bcc499ef70112eafe7fb45c7a", + "0x98cd624cbbd6c53a94469be4643c13130916b91143425bcb7d7028adbbfede38eff7a21092af43b12d4fab703c116359", + "0x995783ce38fd5f6f9433027f122d4cf1e1ff3caf2d196ce591877f4a544ce9113ead60de2de1827eaff4dd31a20d79a8", + "0x8cf251d6f5229183b7f3fe2f607a90b4e4b6f020fb4ba2459d28eb8872426e7be8761a93d5413640a661d73e34a5b81f", + "0xb9232d99620652a3aa7880cad0876f153ff881c4ed4c0c2e7b4ea81d5d42b70daf1a56b869d752c3743c6d4c947e6641", + "0x849716f938f9d37250cccb1bf77f5f9fde53096cdfc6f2a25536a6187029a8f1331cdbed08909184b201f8d9f04b792f", + "0x80c7c4de098cbf9c6d17b14eba1805e433b5bc905f6096f8f63d34b94734f2e4ebf4bce8a177efd1186842a61204a062", + "0xb790f410cf06b9b8daadceeb4fd5ff40a2deda820c8df2537e0a7554613ae3948e149504e3e79aa84889df50c8678eeb", + "0x813aab8bd000299cd37485b73cd7cba06e205f8efb87f1efc0bae8b70f6db2bc7702eb39510ad734854fb65515fe9d0f", + "0x94f0ab7388ac71cdb67f6b85dfd5945748afb2e5abb622f0b5ad104be1d4d0062b651f134ba22385c9e32c2dfdcccce1", + "0xab6223dca8bd6a4f969e21ccd9f8106fc5251d321f9e90cc42cea2424b3a9c4e5060a47eeef6b23c7976109b548498e8", + "0x859c56b71343fce4d5c5b87814c47bf55d581c50fd1871a17e77b5e1742f5af639d0e94d19d909ec7dfe27919e954e0c", + "0xaae0d632b6191b8ad71b027791735f1578e1b89890b6c22e37de0e4a6074886126988fe8319ae228ac9ef3b3bcccb730", + "0x8ca9f32a27a024c3d595ecfaf96b0461de57befa3b331ab71dc110ec3be5824fed783d9516597537683e77a11d334338", + "0xa061df379fb3f4b24816c9f6cd8a94ecb89b4c6dc6cd81e4b8096fa9784b7f97ab3540259d1de9c02eb91d9945af4823", + "0x998603102ac63001d63eb7347a4bb2bf4cf33b28079bb48a169076a65c20d511ccd3ef696d159e54cc8e772fb5d65d50", + "0x94444d96d39450872ac69e44088c252c71f46be8333a608a475147752dbb99db0e36acfc5198f158509401959c12b709", + "0xac1b51b6c09fe055c1d7c9176eea9adc33f710818c83a1fbfa073c8dc3a7eb3513cbdd3f5960b7845e31e3e83181e6ba", + "0x803d530523fc9e1e0f11040d2412d02baef3f07eeb9b177fa9bfa396af42eea898a4276d56e1db998dc96ae47b644cb2", + "0x85a3c9fc7638f5bf2c3e15ba8c2fa1ae87eb1ceb44c6598c67a2948667a9dfa41e61f66d535b4e7fda62f013a5a8b885", + "0xa961cf5654c46a1a22c29baf7a4e77837a26b7f138f410e9d1883480ed5fa42411d522aba32040b577046c11f007388e", + "0xad1154142344f494e3061ef45a34fab1aaacf5fdf7d1b26adbb5fbc3d795655fa743444e39d9a4119b4a4f82a6f30441", + "0xb1d6c30771130c77806e7ab893b73d4deb590b2ff8f2f8b5e54c2040c1f3e060e2bd99afc668cf706a2df666a508bbf6", + "0xa00361fd440f9decabd98d96c575cd251dc94c60611025095d1201ef2dedde51cb4de7c2ece47732e5ed9b3526c2012c", + "0xa85c5ab4d17d328bda5e6d839a9a6adcc92ff844ec25f84981e4f44a0e8419247c081530f8d9aa629c7eb4ca21affba6", + "0xa4ddd3eab4527a2672cf9463db38bc29f61460e2a162f426b7852b7a7645fbd62084fd39a8e4d60e1958cce436dd8f57", + "0x811648140080fe55b8618f4cf17f3c5a250adb0cd53d885f2ddba835d2b4433188e41fc0661faac88e4ff910b16278c0", + "0xb85c7f1cfb0ed29addccf7546023a79249e8f15ac2d14a20accbfef4dd9dc11355d599815fa09d2b6b4e966e6ea8cff1", + "0xa10b5d8c260b159043b020d5dd62b3467df2671afea6d480ca9087b7e60ed170c82b121819d088315902842d66c8fb45", + "0x917e191df1bcf3f5715419c1e2191da6b8680543b1ba41fe84ed07ef570376e072c081beb67b375fca3565a2565bcabb", + "0x881fd967407390bfd7badc9ab494e8a287559a01eb07861f527207c127eadea626e9bcc5aa9cca2c5112fbac3b3f0e9c", + "0x959fd71149af82cc733619e0e5bf71760ca2650448c82984b3db74030d0e10f8ab1ce1609a6de6f470fe8b5bd90df5b3", + "0xa3370898a1c5f33d15adb4238df9a6c945f18b9ada4ce2624fc32a844f9ece4c916a64e9442225b6592afa06d2e015f2", + "0x817efb8a791435e4236f7d7b278181a5fa34587578c629dbc14fbf9a5c26772290611395eecd20222a4c58649fc256d8", + "0xa04c9876acf2cfdc8ef96de4879742709270fa1d03fe4c8511fbef2d59eb0aaf0336fa2c7dfe41a651157377fa217813", + "0x81e15875d7ea7f123e418edf14099f2e109d4f3a6ce0eb65f67fe9fb10d2f809a864a29f60ad3fc949f89e2596b21783", + "0xb49f529975c09e436e6bc202fdc16e3fdcbe056db45178016ad6fdece9faad4446343e83aed096209690b21a6910724f", + "0x879e8eda589e1a279f7f49f6dd0580788c040d973748ec4942dbe51ea8fbd05983cc919b78f0c6b92ef3292ae29db875", + "0x81a2b74b2118923f34139a102f3d95e7eee11c4c2929c2576dee200a5abfd364606158535a6c9e4178a6a83dbb65f3c4", + "0x8913f281d8927f2b45fc815d0f7104631cb7f5f7278a316f1327d670d15868daadd2a64e3eb98e1f53fe7e300338cc80", + "0xa6f815fba7ef9af7fbf45f93bc952e8b351f5de6568a27c7c47a00cb39a254c6b31753794f67940fc7d2e9cc581529f4", + "0xb3722a15c66a0014ce4d082de118def8d39190c15678a472b846225585f3a83756ae1b255b2e3f86a26168878e4773b2", + "0x817ae61ab3d0dd5b6e24846b5a5364b1a7dc2e77432d9fed587727520ae2f307264ea0948c91ad29f0aea3a11ff38624", + "0xb3db467464415fcad36dc1de2d6ba7686772a577cc2619242ac040d6734881a45d3b40ed4588db124e4289cfeec4bbf6", + "0xad66a14f5a54ac69603b16e5f1529851183da77d3cc60867f10aea41339dd5e06a5257982e9e90a352cdd32750f42ee4", + "0xadafa3681ef45d685555601a25a55cf23358319a17f61e2179e704f63df83a73bdd298d12cf6cef86db89bd17119e11d", + "0xa379dc44cb6dd3b9d378c07b2ec654fec7ca2f272de6ba895e3d00d20c9e4c5550498a843c8ac67e4221db2115bedc1c", + "0xb7bf81c267a78efc6b9e5a904574445a6487678d7ef70054e3e93ea6a23f966c2b68787f9164918e3b16d2175459ed92", + "0xb41d66a13a4afafd5760062b77f79de7e6ab8ccacde9c6c5116a6d886912fb491dc027af435b1b44aacc6af7b3c887f2", + "0x9904d23a7c1c1d2e4bab85d69f283eb0a8e26d46e8b7b30224438015c936729b2f0af7c7c54c03509bb0500acb42d8a4", + "0xae30d65e9e20c3bfd603994ae2b175ff691d51f3e24b2d058b3b8556d12ca4c75087809062dddd4aaac81c94d15d8a17", + "0x9245162fab42ac01527424f6013310c3eb462982518debef6c127f46ba8a06c705d7dc9f0a41e796ba8d35d60ae6cc64", + "0x87fab853638d7a29a20f3ba2b1a7919d023e9415bfa78ebb27973d8cbc7626f584dc5665d2e7ad71f1d760eba9700d88", + "0x85aac46ecd330608e5272430970e6081ff02a571e8ea444f1e11785ea798769634a22a142d0237f67b75369d3c484a8a", + "0x938c85ab14894cc5dfce3d80456f189a2e98eddbc8828f4ff6b1df1dcb7b42b17ca2ff40226a8a1390a95d63dca698dd", + "0xa18ce1f846e3e3c4d846822f60271eecf0f5d7d9f986385ac53c5ace9589dc7c0188910448c19b91341a1ef556652fa9", + "0x8611608a9d844f0e9d7584ad6ccf62a5087a64f764caf108db648a776b5390feb51e5120f0ef0e9e11301af3987dd7dc", + "0x8106333ba4b4de8d1ae43bc9735d3fea047392e88efd6a2fa6f7b924a18a7a265ca6123c3edc0f36307dd7fb7fe89257", + "0xa91426fa500951ff1b051a248c050b7139ca30dde8768690432d597d2b3c4357b11a577be6b455a1c5d145264dcf81fc", + "0xb7f9f90e0e450f37b081297f7f651bad0496a8b9afd2a4cf4120a2671aaaa8536dce1af301258bfbfdb122afa44c5048", + "0x84126da6435699b0c09fa4032dec73d1fca21d2d19f5214e8b0bea43267e9a8dd1fc44f8132d8315e734c8e2e04d7291", + "0xaff064708103884cb4f1a3c1718b3fc40a238d35cf0a7dc24bdf9823693b407c70da50df585bf5bc4e9c07d1c2d203e8", + "0xa8b40fc6533752983a5329c31d376c7a5c13ce6879cc7faee648200075d9cd273537001fb4c86e8576350eaac6ba60c2", + "0xa02db682bdc117a84dcb9312eb28fcbde12d49f4ce915cc92c610bb6965ec3cc38290f8c5b5ec70afe153956692cda95", + "0x86decd22b25d300508472c9ce75d3e465b737e7ce13bc0fcce32835e54646fe12322ba5bc457be18bfd926a1a6ca4a38", + "0xa18666ef65b8c2904fd598791f5627207165315a85ee01d5fb0e6b2e10bdd9b00babc447da5bd63445e3337de33b9b89", + "0x89bb0c06effadefdaf34ffe4b123e1678a90d4451ee856c863df1e752eef41fd984689ded8f0f878bf8916d5dd8e8024", + "0x97cfcba08ebec05d0073992a66b1d7d6fb9d95871f2cdc36db301f78bf8069294d1c259efef5c93d20dc937eedae3a1a", + "0xac2643b14ece79dcb2e289c96776a47e2bebd40dd6dc74fd035df5bb727b5596f40e3dd2d2202141e69b0993717ede09", + "0xa5e6fd88a2f9174d9bd4c6a55d9c30974be414992f22aa852f552c7648f722ed8077acf5aba030abd47939bb451b2c60", + "0x8ad40a612824a7994487731a40b311b7349038c841145865539c6ada75c56de6ac547a1c23df190e0caaafecddd80ccc", + "0x953a7cea1d857e09202c438c6108060961f195f88c32f0e012236d7a4b39d840c61b162ec86436e8c38567328bea0246", + "0x80d8b47a46dae1868a7b8ccfe7029445bbe1009dad4a6c31f9ef081be32e8e1ac1178c3c8fb68d3e536c84990cc035b1", + "0x81ecd99f22b3766ce0aca08a0a9191793f68c754fdec78b82a4c3bdc2db122bbb9ebfd02fc2dcc6e1567a7d42d0cc16a", + "0xb1dd0446bccc25846fb95d08c1c9cc52fb51c72c4c5d169ffde56ecfe800f108dc1106d65d5c5bd1087c656de3940b63", + "0xb87547f0931e164e96de5c550ca5aa81273648fe34f6e193cd9d69cf729cb432e17aa02e25b1c27a8a0d20a3b795e94e", + "0x820a94e69a927e077082aae66f6b292cfbe4589d932edf9e68e268c9bd3d71ef76cf7d169dd445b93967c25db11f58f1", + "0xb0d07ddf2595270c39adfa0c8cf2ab1322979b0546aa4d918f641be53cd97f36c879bb75d205e457c011aca3bbd9f731", + "0x8700b876b35b4b10a8a9372c5230acecd39539c1bb87515640293ad4464a9e02929d7d6a6a11112e8a29564815ac0de4", + "0xa61a601c5bb27dcb97e37c8e2b9ce479c6b192a5e04d9ed5e065833c5a1017ee5f237b77d1a17be5d48f8e7cc0bcacf6", + "0x92fb88fe774c1ba1d4a08cae3c0e05467ad610e7a3f1d2423fd47751759235fe0a3036db4095bd6404716aa03820f484", + "0xb274f140d77a3ce0796f5e09094b516537ccaf27ae1907099bff172e6368ba85e7c3ef8ea2a07457cac48ae334da95b3", + "0xb2292d9181f16581a9a9142490b2bdcdfb218ca6315d1effc8592100d792eb89d5356996c890441f04f2b4a95763503e", + "0x8897e73f576d86bc354baa3bd96e553107c48cf5889dcc23c5ba68ab8bcd4e81f27767be2233fdfa13d39f885087e668", + "0xa29eac6f0829791c728d71abc49569df95a4446ecbfc534b39f24f56c88fe70301838dfc1c19751e7f3c5c1b8c6af6a0", + "0x9346dc3720adc5df500a8df27fd9c75ef38dc5c8f4e8ed66983304750e66d502c3c59b8e955be781b670a0afc70a2167", + "0x9566d534e0e30a5c5f1428665590617e95fd05d45f573715f58157854ad596ece3a3cfec61356aee342308d623e029d5", + "0xa464fb8bffe6bd65f71938c1715c6e296cc6d0311a83858e4e7eb5873b7f2cf0c584d2101e3407b85b64ca78b2ac93ce", + "0xb54088f7217987c87e9498a747569ac5b2f8afd5348f9c45bf3fd9fbf713a20f495f49c8572d087efe778ac7313ad6d3", + "0x91fa9f5f8000fe050f5b224d90b59fcce13c77e903cbf98ded752e5b3db16adb2bc1f8c94be48b69f65f1f1ad81d6264", + "0x92d04a5b0ac5d8c8e313709b432c9434ecd3e73231f01e9b4e7952b87df60cbfa97b5dedd2200bd033b4b9ea8ba45cc1", + "0xa94b90ad3c3d6c4bbe169f8661a790c40645b40f0a9d1c7220f01cf7fc176e04d80bab0ced9323fcafb93643f12b2760", + "0x94d86149b9c8443b46196f7e5a3738206dd6f3be7762df488bcbb9f9ee285a64c997ed875b7b16b26604fa59020a8199", + "0x82efe4ae2c50a2d7645240c173a047f238536598c04a2c0b69c96e96bd18e075a99110f1206bc213f39edca42ba00cc1", + "0xab8667685f831bc14d4610f84a5da27b4ea5b133b4d991741a9e64dceb22cb64a3ce8f1b6e101d52af6296df7127c9ad", + "0x83ba433661c05dcc5d562f4a9a261c8110dac44b8d833ae1514b1fc60d8b4ee395b18804baea04cb10adb428faf713c3", + "0xb5748f6f660cc5277f1211d2b8649493ed8a11085b871cd33a5aea630abd960a740f08c08be5f9c21574600ac9bf5737", + "0xa5c8dd12af48fb710642ad65ebb97ca489e8206741807f7acfc334f8035d3c80593b1ff2090c9bb7bd138f0c48714ca8", + "0xa2b382fd5744e3babf454b1d806cc8783efeb4761bc42b6914ea48a46a2eae835efbe0a18262b6bc034379e03cf1262b", + "0xb3145ffaf603f69f15a64936d32e3219eea5ed49fdfd2f5bf40ea0dfd974b36fb6ff12164d4c2282d892db4cf3ff3ce1", + "0x87a316fb213f4c5e30c5e3face049db66be4f28821bd96034714ec23d3e97849d7b301930f90a4323c7ccf53de23050c", + "0xb9de09a919455070fed6220fc179c8b7a4c753062bcd27acf28f5b9947a659c0b364298daf7c85c4ca6fca7f945add1f", + "0x806fbd98d411b76979464c40ad88bc07a151628a27fcc1012ba1dfbaf5b5cc9d962fb9b3386008978a12515edce934bc", + "0xa15268877fae0d21610ae6a31061ed7c20814723385955fac09fdc9693a94c33dea11db98bb89fdfe68f933490f5c381", + "0x8d633fb0c4da86b2e0b37d8fad5972d62bff2ac663c5ec815d095cd4b7e1fe66ebef2a2590995b57eaf941983c7ad7a4", + "0x8139e5dd9cf405e8ef65f11164f0440827d98389ce1b418b0c9628be983a9ddd6cf4863036ccb1483b40b8a527acd9ed", + "0x88b15fa94a08eac291d2b94a2b30eb851ff24addf2cc30b678e72e32cfcb3424cf4b33aa395d741803f3e578ddf524de", + "0xb5eaf0c8506e101f1646bcf049ee38d99ea1c60169730da893fd6020fd00a289eb2f415947e44677af49e43454a7b1be", + "0x8489822ad0647a7e06aa2aa5595960811858ddd4542acca419dd2308a8c5477648f4dd969a6740bb78aa26db9bfcc555", + "0xb1e9a7b9f3423c220330d45f69e45fa03d7671897cf077f913c252e3e99c7b1b1cf6d30caad65e4228d5d7b80eb86e5e", + "0xb28fe9629592b9e6a55a1406903be76250b1c50c65296c10c5e48c64b539fb08fe11f68cf462a6edcbba71b0cee3feb2", + "0xa41acf96a02c96cd8744ff6577c244fc923810d17ade133587e4c223beb7b4d99fa56eae311a500d7151979267d0895c", + "0x880798938fe4ba70721be90e666dfb62fcab4f3556fdb7b0dc8ec5bc34f6b4513df965eae78527136eb391889fe2caf9", + "0x98d4d89d358e0fb7e212498c73447d94a83c1b66e98fc81427ab13acddb17a20f52308983f3a5a8e0aaacec432359604", + "0x81430b6d2998fc78ba937a1639c6020199c52da499f68109da227882dc26d005b73d54c5bdcac1a04e8356a8ca0f7017", + "0xa8d906a4786455eb74613aba4ce1c963c60095ffb8658d368df9266fdd01e30269ce10bf984e7465f34b4fd83beba26a", + "0xaf54167ac1f954d10131d44a8e0045df00d581dd9e93596a28d157543fbe5fb25d213806ed7fb3cba6b8f5b5423562db", + "0x8511e373a978a12d81266b9afbd55035d7bc736835cfa921903a92969eeba3624437d1346b55382e61415726ab84a448", + "0x8cf43eea93508ae586fa9a0f1354a1e16af659782479c2040874a46317f9e8d572a23238efa318fdfb87cc63932602b7", + "0xb0bdd3bacff077173d302e3a9678d1d37936188c7ecc34950185af6b462b7c679815176f3cce5db19aac8b282f2d60ad", + "0xa355e9b87f2f2672052f5d4d65b8c1c827d24d89b0d8594641fccfb69aef1b94009105f3242058bb31c8bf51caae5a41", + "0xb8baa9e4b950b72ff6b88a6509e8ed1304bc6fd955748b2e59a523a1e0c5e99f52aec3da7fa9ff407a7adf259652466c", + "0x840bc3dbb300ea6f27d1d6dd861f15680bd098be5174f45d6b75b094d0635aced539fa03ddbccb453879de77fb5d1fe9", + "0xb4bc7e7e30686303856472bae07e581a0c0bfc815657c479f9f5931cff208d5c12930d2fd1ff413ebd8424bcd7a9b571", + "0x89b5d514155d7999408334a50822508b9d689add55d44a240ff2bdde2eee419d117031f85e924e2a2c1ca77db9b91eea", + "0xa8604b6196f87a04e1350302e8aa745bba8dc162115d22657b37a1d1a98cb14876ddf7f65840b5dbd77e80cd22b4256c", + "0x83cb7acdb9e03247515bb2ce0227486ccf803426717a14510f0d59d45e998b245797d356f10abca94f7a14e1a2f0d552", + "0xaeb3266a9f16649210ab2df0e1908ac259f34ce1f01162c22b56cf1019096ee4ea5854c36e30bb2feb06c21a71e8a45c", + "0x89e72e86edf2aa032a0fc9acf4d876a40865fbb2c8f87cb7e4d88856295c4ac14583e874142fd0c314a49aba68c0aa3c", + "0x8c3576eba0583c2a7884976b4ed11fe1fda4f6c32f6385d96c47b0e776afa287503b397fa516a455b4b8c3afeedc76db", + "0xa31e5b633bda9ffa174654fee98b5d5930a691c3c42fcf55673d927dbc8d91c58c4e42e615353145431baa646e8bbb30", + "0x89f2f3f7a8da1544f24682f41c68114a8f78c86bd36b066e27da13acb70f18d9f548773a16bd8e24789420e17183f137", + "0xada27fa4e90a086240c9164544d2528621a415a5497badb79f8019dc3dce4d12eb6b599597e47ec6ac39c81efda43520", + "0x90dc1eb21bf21c0187f359566fc4bf5386abea52799306a0e5a1151c0817c5f5bc60c86e76b1929c092c0f3ff48cedd2", + "0xb702a53ebcc17ae35d2e735a347d2c700e9cbef8eadbece33cac83df483b2054c126593e1f462cfc00a3ce9d737e2af5", + "0x9891b06455ec925a6f8eafffba05af6a38cc5e193acaaf74ffbf199df912c5197106c5e06d72942bbb032ce277b6417f", + "0x8c0ee71eb01197b019275bcf96cae94e81d2cdc3115dbf2d8e3080074260318bc9303597e8f72b18f965ad601d31ec43", + "0x8aaf580aaf75c1b7a5f99ccf60503506e62058ef43b28b02f79b8536a96be3f019c9f71caf327b4e6730134730d1bef5", + "0xae6f9fc21dd7dfa672b25a87eb0a41644f7609fab5026d5cedb6e43a06dbbfd6d6e30322a2598c8dedde88c52eaed626", + "0x8159b953ffece5693edadb2e906ebf76ff080ee1ad22698950d2d3bfc36ac5ea78f58284b2ca180664452d55bd54716c", + "0xab7647c32ca5e9856ac283a2f86768d68de75ceeba9e58b74c5324f8298319e52183739aba4340be901699d66ac9eb3f", + "0xa4d85a5701d89bcfaf1572db83258d86a1a0717603d6f24ac2963ffcf80f1265e5ab376a4529ca504f4396498791253c", + "0x816080c0cdbfe61b4d726c305747a9eb58ac26d9a35f501dd32ba43c098082d20faf3ccd41aad24600aa73bfa453dfac", + "0x84f3afac024f576b0fd9acc6f2349c2fcefc3f77dbe5a2d4964d14b861b88e9b1810334b908cf3427d9b67a8aee74b18", + "0x94b390655557b1a09110018e9b5a14490681ade275bdc83510b6465a1218465260d9a7e2a6e4ec700f58c31dc3659962", + "0xa8c66826b1c04a2dd4c682543242e7a57acae37278bd09888a3d17747c5b5fec43548101e6f46d703638337e2fd3277b", + "0x86e6f4608a00007fa533c36a5b054c5768ccafe41ad52521d772dcae4c8a4bcaff8f7609be30d8fab62c5988cbbb6830", + "0x837da4cf09ae8aa0bceb16f8b3bfcc3b3367aecac9eed6b4b56d7b65f55981ef066490764fb4c108792623ecf8cad383", + "0x941ff3011462f9b5bf97d8cbdb0b6f5d37a1b1295b622f5485b7d69f2cb2bcabc83630dae427f0259d0d9539a77d8424", + "0xb99e5d6d82aa9cf7d5970e7f710f4039ac32c2077530e4c2779250c6b9b373bc380adb0a03b892b652f649720672fc8c", + "0xa791c78464b2d65a15440b699e1e30ebd08501d6f2720adbc8255d989a82fcded2f79819b5f8f201bed84a255211b141", + "0x84af7ad4a0e31fcbb3276ab1ad6171429cf39adcf78dc03750dc5deaa46536d15591e26d53e953dfb31e1622bc0743ab", + "0xa833e62fe97e1086fae1d4917fbaf09c345feb6bf1975b5cb863d8b66e8d621c7989ab3dbecda36bc9eaffc5eaa6fa66", + "0xb4ef79a46a2126f53e2ebe62770feb57fd94600be29459d70a77c5e9cc260fa892be06cd60f886bf48459e48eb50d063", + "0xb43b8f61919ea380bf151c294e54d3a3ff98e20d1ee5efbfe38aa2b66fafbc6a49739793bd5cb1c809f8b30466277c3a", + "0xab37735af2412d2550e62df9d8b3b5e6f467f20de3890bf56faf1abf2bf3bd1d98dc3fa0ad5e7ab3fce0fa20409eb392", + "0x82416b74b1551d484250d85bb151fabb67e29cce93d516125533df585bc80779ab057ea6992801a3d7d5c6dcff87a018", + "0x8145d0787f0e3b5325190ae10c1d6bee713e6765fb6a0e9214132c6f78f4582bb2771aaeae40d3dad4bafb56bf7e36d8", + "0xb6935886349ecbdd5774e12196f4275c97ec8279fdf28ccf940f6a022ebb6de8e97d6d2173c3fe402cbe9643bed3883b", + "0x87ef9b4d3dc71ac86369f8ed17e0dd3b91d16d14ae694bc21a35b5ae37211b043d0e36d8ff07dcc513fb9e6481a1f37f", + "0xae1d0ded32f7e6f1dc8fef495879c1d9e01826f449f903c1e5034aeeabc5479a9e323b162b688317d46d35a42d570d86", + "0xa40d16497004db4104c6794e2f4428d75bdf70352685944f3fbe17526df333e46a4ca6de55a4a48c02ecf0bde8ba03c0", + "0x8d45121efba8cc308a498e8ee39ea6fa5cae9fb2e4aab1c2ff9d448aa8494ccbec9a078f978a86fcd97b5d5e7be7522a", + "0xa8173865c64634ba4ac2fa432740f5c05056a9deaf6427cb9b4b8da94ca5ddbc8c0c5d3185a89b8b28878194de9cdfcd", + "0xb6ec06a74d690f6545f0f0efba236e63d1fdfba54639ca2617408e185177ece28901c457d02b849fd00f1a53ae319d0a", + "0xb69a12df293c014a40070e3e760169b6f3c627caf9e50b35a93f11ecf8df98b2bc481b410eecb7ab210bf213bbe944de", + "0x97e7dc121795a533d4224803e591eef3e9008bab16f12472210b73aaf77890cf6e3877e0139403a0d3003c12c8f45636", + "0xacdfa6fdd4a5acb7738cc8768f7cba84dbb95c639399b291ae8e4e63df37d2d4096900a84d2f0606bf534a9ccaa4993f", + "0x86ee253f3a9446a33e4d1169719b7d513c6b50730988415382faaf751988c10a421020609f7bcdef91be136704b906e2", + "0xaac9438382a856caf84c5a8a234282f71b5fc5f65219103b147e7e6cf565522285fbfd7417b513bdad8277a00f652ca1", + "0x83f3799d8e5772527930f5dc071a2e0a65471618993ec8990a96ccdeee65270e490bda9d26bb877612475268711ffd80", + "0x93f28a81ac8c0ec9450b9d762fae9c7f8feaace87a6ee6bd141ef1d2d0697ef1bbd159fe6e1de640dbdab2b0361fca8a", + "0xa0825c95ba69999b90eac3a31a3fd830ea4f4b2b7409bde5f202b61d741d6326852ce790f41de5cb0eccec7af4db30c1", + "0x83924b0e66233edd603c3b813d698daa05751fc34367120e3cf384ea7432e256ccee4d4daf13858950549d75a377107d", + "0x956fd9fa58345277e06ba2ec72f49ed230b8d3d4ff658555c52d6cddeb84dd4e36f1a614f5242d5ca0192e8daf0543c2", + "0x944869912476baae0b114cced4ff65c0e4c90136f73ece5656460626599051b78802df67d7201c55d52725a97f5f29fe", + "0x865cb25b64b4531fb6fe4814d7c8cd26b017a6c6b72232ff53defc18a80fe3b39511b23f9e4c6c7249d06e03b2282ed2", + "0x81e09ff55214960775e1e7f2758b9a6c4e4cd39edf7ec1adfaad51c52141182b79fe2176b23ddc7df9fd153e5f82d668", + "0xb31006896f02bc90641121083f43c3172b1039334501fbaf1672f7bf5d174ddd185f945adf1a9c6cf77be34c5501483d", + "0x88b92f6f42ae45e9f05b16e52852826e933efd0c68b0f2418ac90957fd018df661bc47c8d43c2a7d7bfcf669dab98c3c", + "0x92fc68f595853ee8683930751789b799f397135d002eda244fe63ecef2754e15849edde3ba2f0cc8b865c9777230b712", + "0x99ca06a49c5cd0bb097c447793fcdd809869b216a34c66c78c7e41e8c22f05d09168d46b8b1f3390db9452d91bc96dea", + "0xb48b9490a5d65296802431852d548d81047bbefc74fa7dc1d4e2a2878faacdfcb365ae59209cb0ade01901a283cbd15d", + "0xaff0fdbef7c188b120a02bc9085d7b808e88f73973773fef54707bf2cd772cd066740b1b6f4127b5c349f657bd97e738", + "0x966fd4463b4f43dd8ccba7ad50baa42292f9f8b2e70da23bb6780e14155d9346e275ef03ddaf79e47020dcf43f3738bd", + "0x9330c3e1fadd9e08ac85f4839121ae20bbeb0a5103d84fa5aadbd1213805bdcda67bf2fb75fc301349cbc851b5559d20", + "0x993bb99867bd9041a71a55ad5d397755cfa7ab6a4618fc526179bfc10b7dc8b26e4372fe9a9b4a15d64f2b63c1052dda", + "0xa29b59bcfab51f9b3c490a3b96f0bf1934265c315349b236012adbd64a56d7f6941b2c8cc272b412044bc7731f71e1dc", + "0xa65c9cefe1fc35d089fe8580c2e7671ebefdb43014ac291528ff4deefd4883fd4df274af83711dad610dad0d615f9d65", + "0x944c78c56fb227ae632805d448ca3884cd3d2a89181cead3d2b7835e63297e6d740aa79a112edb1d4727824991636df5", + "0xa73d782da1db7e4e65d7b26717a76e16dd9fab4df65063310b8e917dc0bc24e0d6755df5546c58504d04d9e68c3b474a", + "0xaf80f0b87811ae3124f68108b4ca1937009403f87928bbc53480e7c5408d072053ace5eeaf5a5aba814dab8a45502085", + "0x88aaf1acfc6e2e19b8387c97da707cb171c69812fefdd4650468e9b2c627bd5ccfb459f4d8e56bdfd84b09ddf87e128f", + "0x92c97276ff6f72bab6e9423d02ad6dc127962dbce15a0dd1e4a393b4510c555df6aa27be0f697c0d847033a9ca8b8dfd", + "0xa0e07d43d96e2d85b6276b3c60aadb48f0aedf2de8c415756dc597249ea64d2093731d8735231dadc961e5682ac59479", + "0xadc9e6718a8f9298957d1da3842a7751c5399bbdf56f8de6c1c4bc39428f4aee6f1ba6613d37bf46b9403345e9d6fc81", + "0x951da434da4b20d949b509ceeba02e24da7ed2da964c2fcdf426ec787779c696b385822c7dbea4df3e4a35921f1e912c", + "0xa04cbce0d2b2e87bbf038c798a12ec828423ca6aca08dc8d481cf6466e3c9c73d4d4a7fa47df9a7e2e15aae9e9f67208", + "0x8f855cca2e440d248121c0469de1f94c2a71b8ee2682bbad3a78243a9e03da31d1925e6760dbc48a1957e040fae9abe8", + "0xb642e5b17c1df4a4e101772d73851180b3a92e9e8b26c918050f51e6dd3592f102d20b0a1e96f0e25752c292f4c903ff", + "0xa92454c300781f8ae1766dbbb50a96192da7d48ef4cbdd72dd8cbb44c6eb5913c112cc38e9144615fdc03684deb99420", + "0x8b74f7e6c2304f8e780df4649ef8221795dfe85fdbdaa477a1542d135b75c8be45bf89adbbb6f3ddf54ca40f02e733e9", + "0x85cf66292cbb30cec5fd835ab10c9fcb3aea95e093aebf123e9a83c26f322d76ebc89c4e914524f6c5f6ee7d74fc917d", + "0xae0bfe0cdc97c09542a7431820015f2d16067b30dca56288013876025e81daa8c519e5e347268e19aa1a85fa1dc28793", + "0x921322fc6a47dc091afa0ad6df18ed14cde38e48c6e71550aa513918b056044983aee402de21051235eecf4ce8040fbe", + "0x96c030381e97050a45a318d307dcb3c8377b79b4dd5daf6337cded114de26eb725c14171b9b8e1b3c08fe1f5ea6b49e0", + "0x90c23b86b6111818c8baaf53a13eaee1c89203b50e7f9a994bf0edf851919b48edbac7ceef14ac9414cf70c486174a77", + "0x8bf6c301240d2d1c8d84c71d33a6dfc6d9e8f1cfae66d4d0f7a256d98ae12b0bcebfa94a667735ee89f810bcd7170cff", + "0xa41a4ffbbea0e36874d65c009ee4c3feffff322f6fc0e30d26ee4dbc1f46040d05e25d9d0ecb378cef0d24a7c2c4b850", + "0xa8d4cdd423986bb392a0a92c12a8bd4da3437eec6ef6af34cf5310944899287452a2eb92eb5386086d5063381189d10e", + "0xa81dd26ec057c4032a4ed7ad54d926165273ed51d09a1267b2e477535cf6966835a257c209e4e92d165d74fa75695fa3", + "0x8d7f708c3ee8449515d94fc26b547303b53d8dd55f177bc3b25d3da2768accd9bc8e9f09546090ebb7f15c66e6c9c723", + "0x839ba65cffcd24cfffa7ab3b21faabe3c66d4c06324f07b2729c92f15cad34e474b0f0ddb16cd652870b26a756b731d3", + "0x87f1a3968afec354d92d77e2726b702847c6afcabb8438634f9c6f7766de4c1504317dc4fa9a4a735acdbf985e119564", + "0x91a8a7fd6542f3e0673f07f510d850864b34ac087eb7eef8845a1d14b2b1b651cbdc27fa4049bdbf3fea54221c5c8549", + "0xaef3cf5f5e3a2385ead115728d7059e622146c3457d266c612e778324b6e06fbfb8f98e076624d2f3ce1035d65389a07", + "0x819915d6232e95ccd7693fdd78d00492299b1983bc8f96a08dcb50f9c0a813ed93ae53c0238345d5bea0beda2855a913", + "0x8e9ba68ded0e94935131b392b28218315a185f63bf5e3c1a9a9dd470944509ca0ba8f6122265f8da851b5cc2abce68f1", + "0xb28468e9b04ee9d69003399a3cf4457c9bf9d59f36ab6ceeb8e964672433d06b58beeea198fedc7edbaa1948577e9fa2", + "0xa633005e2c9f2fd94c8bce2dd5bb708fe946b25f1ec561ae65e54e15cdd88dc339f1a083e01f0d39610c8fe24151aaf0", + "0x841d0031e22723f9328dd993805abd13e0c99b0f59435d2426246996b08d00ce73ab906f66c4eab423473b409e972ce0", + "0x85758d1b084263992070ec8943f33073a2d9b86a8606672550c17545507a5b3c88d87382b41916a87ee96ff55a7aa535", + "0x8581b06b0fc41466ef94a76a1d9fb8ae0edca6d018063acf6a8ca5f4b02d76021902feba58972415691b4bdbc33ae3b4", + "0x83539597ff5e327357ee62bc6bf8c0bcaec2f227c55c7c385a4806f0d37fb461f1690bad5066b8a5370950af32fafbef", + "0xaee3557290d2dc10827e4791d00e0259006911f3f3fce4179ed3c514b779160613eca70f720bff7804752715a1266ffa", + "0xb48d2f0c4e90fc307d5995464e3f611a9b0ef5fe426a289071f4168ed5cc4f8770c9332960c2ca5c8c427f40e6bb389f", + "0x847af8973b4e300bb06be69b71b96183fd1a0b9d51b91701bef6fcfde465068f1eb2b1503b07afda380f18d69de5c9e1", + "0xa70a6a80ce407f07804c0051ac21dc24d794b387be94eb24e1db94b58a78e1bcfb48cd0006db8fc1f9bedaece7a44fbe", + "0xb40e942b8fa5336910ff0098347df716bff9d1fa236a1950c16eeb966b3bc1a50b8f7b0980469d42e75ae13ced53cead", + "0xb208fabaa742d7db3148515330eb7a3577487845abdb7bd9ed169d0e081db0a5816595c33d375e56aeac5b51e60e49d3", + "0xb7c8194b30d3d6ef5ab66ec88ad7ebbc732a3b8a41731b153e6f63759a93f3f4a537eab9ad369705bd730184bdbbdc34", + "0x9280096445fe7394d04aa1bc4620c8f9296e991cc4d6c131bd703cb1cc317510e6e5855ac763f4d958c5edfe7eebeed7", + "0xabc2aa4616a521400af1a12440dc544e3c821313d0ab936c86af28468ef8bbe534837e364598396a81cf8d06274ed5a6", + "0xb18ca8a3325adb0c8c18a666d4859535397a1c3fe08f95eebfac916a7a99bbd40b3c37b919e8a8ae91da38bc00fa56c0", + "0x8a40c33109ecea2a8b3558565877082f79121a432c45ec2c5a5e0ec4d1c203a6788e6b69cb37f1fd5b8c9a661bc5476d", + "0x88c47301dd30998e903c84e0b0f2c9af2e1ce6b9f187dab03528d44f834dc991e4c86d0c474a2c63468cf4020a1e24a0", + "0x920c832853e6ab4c851eecfa9c11d3acc7da37c823be7aa1ab15e14dfd8beb5d0b91d62a30cec94763bd8e4594b66600", + "0x98e1addbe2a6b8edc7f12ecb9be81c3250aeeca54a1c6a7225772ca66549827c15f3950d01b8eb44aecb56fe0fff901a", + "0x8cfb0fa1068be0ec088402f5950c4679a2eb9218c729da67050b0d1b2d7079f3ddf4bf0f57d95fe2a8db04bc6bcdb20c", + "0xb70f381aafe336b024120453813aeab70baac85b9c4c0f86918797b6aee206e6ed93244a49950f3d8ec9f81f4ac15808", + "0xa4c8edf4aa33b709a91e1062939512419711c1757084e46f8f4b7ed64f8e682f4e78b7135920c12f0eb0422fe9f87a6a", + "0xb4817e85fd0752d7ebb662d3a51a03367a84bac74ebddfba0e5af5e636a979500f72b148052d333b3dedf9edd2b4031b", + "0xa87430169c6195f5d3e314ff2d1c2f050e766fd5d2de88f5207d72dba4a7745bb86d0baca6e9ae156582d0d89e5838c7", + "0x991b00f8b104566b63a12af4826b61ce7aa40f4e5b8fff3085e7a99815bdb4471b6214da1e480214fac83f86a0b93cc5", + "0xb39966e3076482079de0678477df98578377a094054960ee518ef99504d6851f8bcd3203e8da5e1d4f6f96776e1fe6eb", + "0xa448846d9dc2ab7a0995fa44b8527e27f6b3b74c6e03e95edb64e6baa4f1b866103f0addb97c84bef1d72487b2e21796", + "0x894bec21a453ae84b592286e696c35bc30e820e9c2fd3e63dd4fbe629e07df16439c891056070faa490155f255bf7187", + "0xa9ec652a491b11f6a692064e955f3f3287e7d2764527e58938571469a1e29b5225b9415bd602a45074dfbfe9c131d6ca", + "0xb39d37822e6cbe28244b5f42ce467c65a23765bd16eb6447c5b3e942278069793763483dafd8c4dd864f8917aad357fe", + "0x88dba51133f2019cb266641c56101e3e5987d3b77647a2e608b5ff9113dfc5f85e2b7c365118723131fbc0c9ca833c9c", + "0xb566579d904b54ecf798018efcb824dccbebfc6753a0fd2128ac3b4bd3b038c2284a7c782b5ca6f310eb7ea4d26a3f0a", + "0xa97a55c0a492e53c047e7d6f9d5f3e86fb96f3dddc68389c0561515343b66b4bc02a9c0d5722dff1e3445308240b27f7", + "0xa044028ab4bcb9e1a2b9b4ca4efbf04c5da9e4bf2fff0e8bd57aa1fc12a71e897999c25d9117413faf2f45395dee0f13", + "0xa78dc461decbeaeed8ebd0909369b491a5e764d6a5645a7dac61d3140d7dc0062526f777b0eb866bff27608429ebbdde", + "0xb2c2a8991f94c39ca35fea59f01a92cb3393e0eccb2476dfbf57261d406a68bd34a6cff33ed80209991688c183609ef4", + "0x84189eefb521aff730a4fd3fd5b10ddfd29f0d365664caef63bb015d07e689989e54c33c2141dd64427805d37a7e546e", + "0x85ac80bd734a52235da288ff042dea9a62e085928954e8eacd2c751013f61904ed110e5b3afe1ab770a7e6485efb7b5e", + "0x9183a560393dcb22d0d5063e71182020d0fbabb39e32493eeffeb808df084aa243eb397027f150b55a247d1ed0c8513e", + "0x81c940944df7ecc58d3c43c34996852c3c7915ed185d7654627f7af62abae7e0048dd444a6c09961756455000bd96d09", + "0xaa8c34e164019743fd8284b84f06c3b449aae7996e892f419ee55d82ad548cb300fd651de329da0384243954c0ef6a60", + "0x89a7b7bdfc7e300d06a14d463e573d6296d8e66197491900cc9ae49504c4809ff6e61b758579e9091c61085ba1237b83", + "0x878d21809ba540f50bd11f4c4d9590fb6f3ab9de5692606e6e2ef4ed9d18520119e385be5e1f4b3f2e2b09c319f0e8fc", + "0x8eb248390193189cf0355365e630b782cd15751e672dc478b39d75dc681234dcd9309df0d11f4610dbb249c1e6be7ef9", + "0xa1d7fb3aecb896df3a52d6bd0943838b13f1bd039c936d76d03de2044c371d48865694b6f532393b27fd10a4cf642061", + "0xa34bca58a24979be442238cbb5ece5bee51ae8c0794dd3efb3983d4db713bc6f28a96e976ac3bd9a551d3ed9ba6b3e22", + "0x817c608fc8cacdd178665320b5a7587ca21df8bdd761833c3018b967575d25e3951cf3d498a63619a3cd2ad4406f5f28", + "0x86c95707db0495689afd0c2e39e97f445f7ca0edffad5c8b4cacd1421f2f3cc55049dfd504f728f91534e20383955582", + "0x99c3b0bb15942c301137765d4e19502f65806f3b126dc01a5b7820c87e8979bce6a37289a8f6a4c1e4637227ad5bf3bf", + "0x8aa1518a80ea8b074505a9b3f96829f5d4afa55a30efe7b4de4e5dbf666897fdd2cf31728ca45921e21a78a80f0e0f10", + "0x8d74f46361c79e15128ac399e958a91067ef4cec8983408775a87eca1eed5b7dcbf0ddf30e66f51780457413496c7f07", + "0xa41cde4a786b55387458a1db95171aca4fd146507b81c4da1e6d6e495527c3ec83fc42fad1dfe3d92744084a664fd431", + "0x8c352852c906fae99413a84ad11701f93f292fbf7bd14738814f4c4ceab32db02feb5eb70bc73898b0bc724a39d5d017", + "0xa5993046e8f23b71ba87b7caa7ace2d9023fb48ce4c51838813174880d918e9b4d2b0dc21a2b9c6f612338c31a289df8", + "0x83576d3324bf2d8afbfb6eaecdc5d767c8e22e7d25160414924f0645491df60541948a05e1f4202e612368e78675de8a", + "0xb43749b8df4b15bc9a3697e0f1c518e6b04114171739ef1a0c9c65185d8ec18e40e6954d125cbc14ebc652cf41ad3109", + "0xb4eebd5d80a7327a040cafb9ccdb12b2dfe1aa86e6bc6d3ac8a57fadfb95a5b1a7332c66318ff72ba459f525668af056", + "0x9198be7f1d413c5029b0e1c617bcbc082d21abe2c60ec8ce9b54ca1a85d3dba637b72fda39dae0c0ae40d047eab9f55a", + "0x8d96a0232832e24d45092653e781e7a9c9520766c3989e67bbe86b3a820c4bf621ea911e7cd5270a4bfea78b618411f6", + "0x8d7160d0ea98161a2d14d46ef01dff72d566c330cd4fabd27654d300e1bc7644c68dc8eabf2a20a59bfe7ba276545f9b", + "0xabb60fce29dec7ba37e3056e412e0ec3e05538a1fc0e2c68877378c867605966108bc5742585ab6a405ce0c962b285b6", + "0x8fabffa3ed792f05e414f5839386f6449fd9f7b41a47595c5d71074bd1bb3784cc7a1a7e1ad6b041b455035957e5b2dc", + "0x90ff017b4804c2d0533b72461436b10603ab13a55f86fd4ec11b06a70ef8166f958c110519ca1b4cc7beba440729fe2d", + "0xb340cfd120f6a4623e3a74cf8c32bfd7cd61a280b59dfd17b15ca8fae4d82f64a6f15fbde4c02f424debc72b7db5fe67", + "0x871311c9c7220c932e738d59f0ecc67a34356d1429fe570ca503d340c9996cb5ee2cd188fad0e3bd16e4c468ec1dbebd", + "0xa772470262186e7b94239ba921b29f2412c148d6f97c4412e96d21e55f3be73f992f1ad53c71008f0558ec3f84e2b5a7", + "0xb2a897dcb7ffd6257f3f2947ec966f2077d57d5191a88840b1d4f67effebe8c436641be85524d0a21be734c63ab5965d", + "0xa044f6eacc48a4a061fa149500d96b48cbf14853469aa4d045faf3dca973be1bd4b4ce01646d83e2f24f7c486d03205d", + "0x981af5dc2daa73f7fa9eae35a93d81eb6edba4a7f673b55d41f6ecd87a37685d31bb40ef4f1c469b3d72f2f18b925a17", + "0x912d2597a07864de9020ac77083eff2f15ceb07600f15755aba61251e8ce3c905a758453b417f04d9c38db040954eb65", + "0x9642b7f6f09394ba5e0805734ef6702c3eddf9eea187ba98c676d5bbaec0e360e3e51dc58433aaa1e2da6060c8659cb7", + "0x8ab3836e0a8ac492d5e707d056310c4c8e0489ca85eb771bff35ba1d658360084e836a6f51bb990f9e3d2d9aeb18fbb5", + "0x879e058e72b73bb1f4642c21ffdb90544b846868139c6511f299aafe59c2d0f0b944dffc7990491b7c4edcd6a9889250", + "0xb9e60b737023f61479a4a8fd253ed0d2a944ea6ba0439bbc0a0d3abf09b0ad1f18d75555e4a50405470ae4990626f390", + "0xb9c2535d362796dcd673640a9fa2ebdaec274e6f8b850b023153b0a7a30fffc87f96e0b72696f647ebe7ab63099a6963", + "0x94aeff145386a087b0e91e68a84a5ede01f978f9dd9fe7bebca78941938469495dc30a96bba9508c0d017873aeea9610", + "0x98b179f8a3d9f0d0a983c30682dd425a2ddc7803be59bd626c623c8951a5179117d1d2a68254c95c9952989877d0ee55", + "0x889ecf5f0ee56938273f74eb3e9ecfb5617f04fb58e83fe4c0e4aef51615cf345bc56f3f61b17f6eed3249d4afd54451", + "0xa0f2b2c39bcea4b50883e2587d16559e246248a66ecb4a4b7d9ab3b51fb39fe98d83765e087eee37a0f86b0ba4144c02", + "0xb2a61e247ed595e8a3830f7973b07079cbda510f28ad8c78c220b26cb6acde4fbb5ee90c14a665f329168ee951b08cf0", + "0x95bd0fcfb42f0d6d8a8e73d7458498a85bcddd2fb132fd7989265648d82ac2707d6d203fac045504977af4f0a2aca4b7", + "0x843e5a537c298666e6cf50fcc044f13506499ef83c802e719ff2c90e85003c132024e04711be7234c04d4b0125512d5d", + "0xa46d1797c5959dcd3a5cfc857488f4d96f74277c3d13b98b133620192f79944abcb3a361d939a100187f1b0856eae875", + "0xa1c7786736d6707a48515c38660615fcec67eb8a2598f46657855215f804fd72ab122d17f94fcffad8893f3be658dca7", + "0xb23dc9e610abc7d8bd21d147e22509a0fa49db5be6ea7057b51aae38e31654b3aa044df05b94b718153361371ba2f622", + "0xb00cc8f257d659c22d30e6d641f79166b1e752ea8606f558e4cad6fc01532e8319ea4ee12265ba4140ac45aa4613c004", + "0xac7019af65221b0cc736287b32d7f1a3561405715ba9a6a122342e04e51637ba911c41573de53e4781f2230fdcb2475f", + "0x81a630bc41b3da8b3eb4bf56cba10cd9f93153c3667f009dc332287baeb707d505fb537e6233c8e53d299ec0f013290c", + "0xa6b7aea5c545bb76df0f230548539db92bc26642572cb7dd3d5a30edca2b4c386f44fc8466f056b42de2a452b81aff5b", + "0x8271624ff736b7b238e43943c81de80a1612207d32036d820c11fc830c737972ccc9c60d3c2359922b06652311e3c994", + "0x8a684106458cb6f4db478170b9ad595d4b54c18bf63b9058f095a2fa1b928c15101472c70c648873d5887880059ed402", + "0xa5cc3c35228122f410184e4326cf61a37637206e589fcd245cb5d0cec91031f8f7586b80503070840fdfd8ce75d3c88b", + "0x9443fc631aed8866a7ed220890911057a1f56b0afe0ba15f0a0e295ab97f604b134b1ed9a4245e46ee5f9a93aa74f731", + "0x984b6f7d79835dffde9558c6bb912d992ca1180a2361757bdba4a7b69dc74b056e303adc69fe67414495dd9c2dd91e64", + "0xb15a5c8cba5de080224c274d31c68ed72d2a7126d347796569aef0c4e97ed084afe3da4d4b590b9dda1a07f0c2ff3dfb", + "0x991708fe9650a1f9a4e43938b91d45dc68c230e05ee999c95dbff3bf79b1c1b2bb0e7977de454237c355a73b8438b1d9", + "0xb4f7edc7468b176a4a7c0273700c444fa95c726af6697028bed4f77eee887e3400f9c42ee15b782c0ca861c4c3b8c98a", + "0x8c60dcc16c51087eb477c13e837031d6c6a3dc2b8bf8cb43c23f48006bc7173151807e866ead2234b460c2de93b31956", + "0x83ad63e9c910d1fc44bc114accfb0d4d333b7ebe032f73f62d25d3e172c029d5e34a1c9d547273bf6c0fead5c8801007", + "0x85de73213cc236f00777560756bdbf2b16841ba4b55902cf2cad9742ecaf5d28209b012ceb41f337456dfeca93010cd7", + "0xa7561f8827ccd75b6686ba5398bb8fc3083351c55a589b18984e186820af7e275af04bcd4c28e1dc11be1e8617a0610b", + "0x88c0a4febd4068850557f497ea888035c7fc9f404f6cc7794e7cc8722f048ad2f249e7dc62743e7a339eb7473ad3b0cd", + "0x932b22b1d3e6d5a6409c34980d176feb85ada1bf94332ef5c9fc4d42b907dabea608ceef9b5595ef3feee195151f18d8", + "0xa2867bb3f5ab88fbdae3a16c9143ab8a8f4f476a2643c505bb9f37e5b1fd34d216cab2204c9a017a5a67b7ad2dda10e8", + "0xb573d5f38e4e9e8a3a6fd82f0880dc049efa492a946d00283019bf1d5e5516464cf87039e80aef667cb86fdea5075904", + "0xb948f1b5ab755f3f5f36af27d94f503b070696d793b1240c1bdfd2e8e56890d69e6904688b5f8ff5a4bdf5a6abfe195f", + "0x917eae95ebc4109a2e99ddd8fec7881d2f7aaa0e25fda44dec7ce37458c2ee832f1829db7d2dcfa4ca0f06381c7fe91d", + "0x95751d17ed00a3030bce909333799bb7f4ab641acf585807f355b51d6976dceee410798026a1a004ef4dcdff7ec0f5b8", + "0xb9b7bd266f449a79bbfe075e429613e76c5a42ac61f01c8f0bbbd34669650682efe01ff9dbbc400a1e995616af6aa278", + "0xac1722d097ce9cd7617161f8ec8c23d68f1fb1c9ca533e2a8b4f78516c2fd8fb38f23f834e2b9a03bb06a9d655693ca9", + "0xa7ad9e96ffd98db2ecdb6340c5d592614f3c159abfd832fe27ee9293519d213a578e6246aae51672ee353e3296858873", + "0x989b8814d5de7937c4acafd000eec2b4cd58ba395d7b25f98cafd021e8efa37029b29ad8303a1f6867923f5852a220eb", + "0xa5bfe6282c771bc9e453e964042d44eff4098decacb89aecd3be662ea5b74506e1357ab26f3527110ba377711f3c9f41", + "0x8900a7470b656639721d2abbb7b06af0ac4222ab85a1976386e2a62eb4b88bfb5b72cf7921ddb3cf3a395d7eeb192a2e", + "0x95a71b55cd1f35a438cf5e75f8ff11c5ec6a2ebf2e4dba172f50bfad7d6d5dca5de1b1afc541662c81c858f7604c1163", + "0x82b5d62fea8db8d85c5bc3a76d68dedd25794cf14d4a7bc368938ffca9e09f7e598fdad2a5aac614e0e52f8112ae62b9", + "0x997173f07c729202afcde3028fa7f52cefc90fda2d0c8ac2b58154a5073140683e54c49ed1f254481070d119ce0ce02a", + "0xaeffb91ccc7a72bbd6ffe0f9b99c9e66e67d59cec2e02440465e9636a613ab3017278cfa72ea8bc4aba9a8dc728cb367", + "0x952743b06e8645894aeb6440fc7a5f62dd3acf96dab70a51e20176762c9751ea5f2ba0b9497ccf0114dc4892dc606031", + "0x874c63baeddc56fbbca2ff6031f8634b745f6e34ea6791d7c439201aee8f08ef5ee75f7778700a647f3b21068513fce6", + "0x85128fec9c750c1071edfb15586435cc2f317e3e9a175bb8a9697bcda1eb9375478cf25d01e7fed113483b28f625122d", + "0x85522c9576fd9763e32af8495ae3928ed7116fb70d4378448926bc9790e8a8d08f98cf47648d7da1b6e40d6a210c7924", + "0x97d0f37a13cfb723b848099ca1c14d83e9aaf2f7aeb71829180e664b7968632a08f6a85f557d74b55afe6242f2a36e7c", + "0xabaa472d6ad61a5fccd1a57c01aa1bc081253f95abbcba7f73923f1f11c4e79b904263890eeb66926de3e2652f5d1c70", + "0xb3c04945ba727a141e5e8aec2bf9aa3772b64d8fd0e2a2b07f3a91106a95cbcb249adcd074cbe498caf76fffac20d4ef", + "0x82c46781a3d730d9931bcabd7434a9171372dde57171b6180e5516d4e68db8b23495c8ac3ab96994c17ddb1cf249b9fb", + "0xa202d8b65613c42d01738ccd68ed8c2dbc021631f602d53f751966e04182743ebc8e0747d600b8a8676b1da9ae7f11ab", + "0xae73e7256e9459db04667a899e0d3ea5255211fb486d084e6550b6dd64ca44af6c6b2d59d7aa152de9f96ce9b58d940d", + "0xb67d87b176a9722945ec7593777ee461809861c6cfd1b945dde9ee4ff009ca4f19cf88f4bbb5c80c9cbab2fe25b23ac8", + "0x8f0b7a317a076758b0dac79959ee4a06c08b07d0f10538a4b53d3da2eda16e2af26922feb32c090330dc4d969cf69bd3", + "0x90b36bf56adbd8c4b6cb32febc3a8d5f714370c2ac3305c10fa6d168dffb2a026804517215f9a2d4ec8310cdb6bb459b", + "0xaa80c19b0682ead69934bf18cf476291a0beddd8ef4ed75975d0a472e2ab5c70f119722a8574ae4973aceb733d312e57", + "0xa3fc9abb12574e5c28dcb51750b4339b794b8e558675eef7d26126edf1de920c35e992333bcbffcbf6a5f5c0d383ce62", + "0xa1573ff23ab972acdcd08818853b111fc757fdd35aa070186d3e11e56b172fb49d840bf297ac0dd222e072fc09f26a81", + "0x98306f2be4caa92c2b4392212d0cbf430b409b19ff7d5b899986613bd0e762c909fc01999aa94be3bd529d67f0113d7f", + "0x8c1fc42482a0819074241746d17dc89c0304a2acdae8ed91b5009e9e3e70ff725ba063b4a3e68fdce05b74f5180c545e", + "0xa6c6113ebf72d8cf3163b2b8d7f3fa24303b13f55752522c660a98cd834d85d8c79214d900fa649499365e2e7641f77a", + "0xab95eea424f8a2cfd9fb1c78bb724e5b1d71a0d0d1e4217c5d0f98b0d8bbd3f8400a2002abc0a0e4576d1f93f46fefad", + "0x823c5a4fd8cf4a75fdc71d5f2dd511b6c0f189b82affeacd2b7cfcad8ad1a5551227dcc9bfdb2e34b2097eaa00efbb51", + "0xb97314dfff36d80c46b53d87a61b0e124dc94018a0bb680c32765b9a2d457f833a7c42bbc90b3b1520c33a182580398d", + "0xb17566ee3dcc6bb3b004afe4c0136dfe7dd27df9045ae896dca49fb36987501ae069eb745af81ba3fc19ff037e7b1406", + "0xb0bdc0f55cfd98d331e3a0c4fbb776a131936c3c47c6bffdc3aaf7d8c9fa6803fbc122c2fefbb532e634228687d52174", + "0xaa5d9e60cc9f0598559c28bb9bdd52aa46605ab4ffe3d192ba982398e72cec9a2a44c0d0d938ce69935693cabc0887ea", + "0x802b6459d2354fa1d56c592ac1346c428dadea6b6c0a87bf7d309bab55c94e1cf31dd98a7a86bd92a840dd51f218b91b", + "0xa526914efdc190381bf1a73dd33f392ecf01350b9d3f4ae96b1b1c3d1d064721c7d6eec5788162c933245a3943f5ee51", + "0xb3b8fcf637d8d6628620a1a99dbe619eabb3e5c7ce930d6efd2197e261bf394b74d4e5c26b96c4b8009c7e523ccfd082", + "0x8f7510c732502a93e095aba744535f3928f893f188adc5b16008385fb9e80f695d0435bfc5b91cdad4537e87e9d2551c", + "0x97b90beaa56aa936c3ca45698f79273a68dd3ccd0076eab48d2a4db01782665e63f33c25751c1f2e070f4d1a8525bf96", + "0xb9fb798324b1d1283fdc3e48288e3861a5449b2ab5e884b34ebb8f740225324af86e4711da6b5cc8361c1db15466602f", + "0xb6d52b53cea98f1d1d4c9a759c25bf9d8a50b604b144e4912acbdbdc32aab8b9dbb10d64a29aa33a4f502121a6fb481c", + "0x9174ffff0f2930fc228f0e539f5cfd82c9368d26b074467f39c07a774367ff6cccb5039ac63f107677d77706cd431680", + "0xa33b6250d4ac9e66ec51c063d1a6a31f253eb29bbaed12a0d67e2eccfffb0f3a52750fbf52a1c2aaba8c7692346426e7", + "0xa97025fd5cbcebe8ef865afc39cd3ea707b89d4e765ec817fd021d6438e02fa51e3544b1fd45470c58007a08efac6edd", + "0xb32a78480edd9ff6ba2f1eec4088db5d6ceb2d62d7e59e904ecaef7bb4a2e983a4588e51692b3be76e6ffbc0b5f911a5", + "0xb5ab590ef0bb77191f00495b33d11c53c65a819f7d0c1f9dc4a2caa147a69c77a4fff7366a602d743ee1f395ce934c1e", + "0xb3fb0842f9441fb1d0ee0293b6efbc70a8f58d12d6f769b12872db726b19e16f0f65efbc891cf27a28a248b0ef9c7e75", + "0x9372ad12856fefb928ccb0d34e198df99e2f8973b07e9d417a3134d5f69e12e79ff572c4e03ccd65415d70639bc7c73e", + "0xaa8d6e83d09ce216bfe2009a6b07d0110d98cf305364d5529c170a23e693aabb768b2016befb5ada8dabdd92b4d012bb", + "0xa954a75791eeb0ce41c85200c3763a508ed8214b5945a42c79bfdcfb1ec4f86ad1dd7b2862474a368d4ac31911a2b718", + "0x8e2081cfd1d062fe3ab4dab01f68062bac802795545fede9a188f6c9f802cb5f884e60dbe866710baadbf55dc77c11a4", + "0xa2f06003b9713e7dd5929501ed485436b49d43de80ea5b15170763fd6346badf8da6de8261828913ee0dacd8ff23c0e1", + "0x98eecc34b838e6ffd1931ca65eec27bcdb2fdcb61f33e7e5673a93028c5865e0d1bf6d3bec040c5e96f9bd08089a53a4", + "0x88cc16019741b341060b95498747db4377100d2a5bf0a5f516f7dec71b62bcb6e779de2c269c946d39040e03b3ae12b7", + "0xad1135ccbc3019d5b2faf59a688eef2500697642be8cfbdf211a1ab59abcc1f24483e50d653b55ff1834675ac7b4978f", + "0xa946f05ed9972f71dfde0020bbb086020fa35b482cce8a4cc36dd94355b2d10497d7f2580541bb3e81b71ac8bba3c49f", + "0xa83aeed488f9a19d8cfd743aa9aa1982ab3723560b1cd337fc2f91ad82f07afa412b3993afb845f68d47e91ba4869840", + "0x95eebe006bfc316810cb71da919e5d62c2cebb4ac99d8e8ef67be420302320465f8b69873470982de13a7c2e23516be9", + "0xa55f8961295a11e91d1e5deadc0c06c15dacbfc67f04ccba1d069cba89d72aa3b3d64045579c3ea8991b150ac29366ae", + "0xb321991d12f6ac07a5de3c492841d1a27b0d3446082fbce93e7e1f9e8d8fe3b45d41253556261c21b70f5e189e1a7a6f", + "0xa0b0822f15f652ce7962a4f130104b97bf9529797c13d6bd8e24701c213cc37f18157bd07f3d0f3eae6b7cd1cb40401f", + "0x96e2fa4da378aa782cc2d5e6e465fc9e49b5c805ed01d560e9b98abb5c0de8b74a2e7bec3aa5e2887d25cccb12c66f0c", + "0x97e4ab610d414f9210ed6f35300285eb3ccff5b0b6a95ed33425100d7725e159708ea78704497624ca0a2dcabce3a2f9", + "0x960a375b17bdb325761e01e88a3ea57026b2393e1d887b34b8fa5d2532928079ce88dc9fd06a728b26d2bb41b12b9032", + "0x8328a1647398e832aadc05bd717487a2b6fcdaa0d4850d2c4da230c6a2ed44c3e78ec4837b6094f3813f1ee99414713f", + "0xaa283834ebd18e6c99229ce4b401eda83f01d904f250fedd4e24f1006f8fa0712a6a89a7296a9bf2ce8de30e28d1408e", + "0xb29e097f2caadae3e0f0ae3473c072b0cd0206cf6d2e9b22c1a5ad3e07d433e32bd09ed1f4e4276a2da4268633357b7f", + "0x9539c5cbba14538b2fe077ecf67694ef240da5249950baaabea0340718b882a966f66d97f08556b08a4320ceb2cc2629", + "0xb4529f25e9b42ae8cf8338d2eface6ba5cd4b4d8da73af502d081388135c654c0b3afb3aa779ffc80b8c4c8f4425dd2b", + "0x95be0739c4330619fbe7ee2249c133c91d6c07eab846c18c5d6c85fc21ac5528c5d56dcb0145af68ed0c6a79f68f2ccd", + "0xac0c83ea802227bfc23814a24655c9ff13f729619bcffdb487ccbbf029b8eaee709f8bddb98232ef33cd70e30e45ca47", + "0xb503becb90acc93b1901e939059f93e671900ca52c6f64ae701d11ac891d3a050b505d89324ce267bc43ab8275da6ffe", + "0x98e3811b55b1bacb70aa409100abb1b870f67e6d059475d9f278c751b6e1e2e2d6f2e586c81a9fb6597fda06e7923274", + "0xb0b0f61a44053fa6c715dbb0731e35d48dba257d134f851ee1b81fd49a5c51a90ebf5459ec6e489fce25da4f184fbdb1", + "0xb1d2117fe811720bb997c7c93fe9e4260dc50fca8881b245b5e34f724aaf37ed970cdad4e8fcb68e05ac8cf55a274a53", + "0xa10f502051968f14b02895393271776dee7a06db9de14effa0b3471825ba94c3f805302bdddac4d397d08456f620999d", + "0xa3dbad2ef060ae0bb7b02eaa4a13594f3f900450faa1854fc09620b01ac94ab896321dfb1157cf2374c27e5718e8026a", + "0xb550fdec503195ecb9e079dcdf0cad559d64d3c30818ef369b4907e813e689da316a74ad2422e391b4a8c2a2bef25fc0", + "0xa25ba865e2ac8f28186cea497294c8649a201732ecb4620c4e77b8e887403119910423df061117e5f03fc5ba39042db1", + "0xb3f88174e03fdb443dd6addd01303cf88a4369352520187c739fc5ae6b22fa99629c63c985b4383219dab6acc5f6f532", + "0x97a7503248e31e81b10eb621ba8f5210c537ad11b539c96dfb7cf72b846c7fe81bd7532c5136095652a9618000b7f8d3", + "0xa8bcdc1ce5aa8bfa683a2fc65c1e79de8ff5446695dcb8620f7350c26d2972a23da22889f9e2b1cacb3f688c6a2953dc", + "0x8458c111df2a37f5dd91a9bee6c6f4b79f4f161c93fe78075b24a35f9817da8dde71763218d627917a9f1f0c4709c1ed", + "0xac5f061a0541152b876cbc10640f26f1cc923c9d4ae1b6621e4bb3bf2cec59bbf87363a4eb72fb0e5b6d4e1c269b52d5", + "0xa9a25ca87006e8a9203cbb78a93f50a36694aa4aad468b8d80d3feff9194455ca559fcc63838128a0ab75ad78c07c13a", + "0xa450b85f5dfffa8b34dfd8bc985f921318efacf8857cf7948f93884ba09fb831482ee90a44224b1a41e859e19b74962f", + "0x8ed91e7f92f5c6d7a71708b6132f157ac226ecaf8662af7d7468a4fa25627302efe31e4620ad28719318923e3a59bf82", + "0xab524165fd4c71b1fd395467a14272bd2b568592deafa039d8492e9ef36c6d3f96927c95c72d410a768dc0b6d1fbbc9b", + "0xb662144505aa8432c75ffb8d10318526b6d5777ac7af9ebfad87d9b0866c364f7905a6352743bd8fd79ffd9d5dd4f3e6", + "0xa48f1677550a5cd40663bb3ba8f84caaf8454f332d0ceb1d94dbea52d0412fe69c94997f7749929712fd3995298572f7", + "0x8391cd6e2f6b0c242de1117a612be99776c3dc95cb800b187685ea5bf7e2722275eddb79fd7dfc8be8e389c4524cdf70", + "0x875d3acb9af47833b72900bc0a2448999d638f153c5e97e8a14ec02d0c76f6264353a7e275e1f1a5855daced523d243b", + "0x91f1823657d30b59b2f627880a9a9cb530f5aca28a9fd217fe6f2f5133690dfe7ad5a897872e400512db2e788b3f7628", + "0xad3564332aa56cea84123fc7ca79ea70bb4fef2009fa131cb44e4b15e8613bd11ca1d83b9d9bf456e4b7fee9f2e8b017", + "0x8c530b84001936d5ab366c84c0b105241a26d1fb163669f17c8f2e94776895c2870edf3e1bc8ccd04d5e65531471f695", + "0x932d01fa174fdb0c366f1230cffde2571cc47485f37f23ba5a1825532190cc3b722aeb1f15aed62cf83ccae9403ba713", + "0x88b28c20585aca50d10752e84b901b5c2d58efef5131479fbbe53de7bce2029e1423a494c0298e1497669bd55be97a5d", + "0xb914148ca717721144ebb3d3bf3fcea2cd44c30c5f7051b89d8001502f3856fef30ec167174d5b76265b55d70f8716b5", + "0x81d0173821c6ddd2a068d70766d9103d1ee961c475156e0cbd67d54e668a796310474ef698c7ab55abe6f2cf76c14679", + "0x8f28e8d78e2fe7fa66340c53718e0db4b84823c8cfb159c76eac032a62fb53da0a5d7e24ca656cf9d2a890cb2a216542", + "0x8a26360335c73d1ab51cec3166c3cf23b9ea51e44a0ad631b0b0329ef55aaae555420348a544e18d5760969281759b61", + "0x94f326a32ed287545b0515be9e08149eb0a565025074796d72387cc3a237e87979776410d78339e23ef3172ca43b2544", + "0xa785d2961a2fa5e70bffa137858a92c48fe749fee91b02599a252b0cd50d311991a08efd7fa5e96b78d07e6e66ffe746", + "0x94af9030b5ac792dd1ce517eaadcec1482206848bea4e09e55cc7f40fd64d4c2b3e9197027c5636b70d6122c51d2235d", + "0x9722869f7d1a3992850fe7be405ec93aa17dc4d35e9e257d2e469f46d2c5a59dbd504056c85ab83d541ad8c13e8bcd54", + "0xb13c4088b61a06e2c03ac9813a75ff1f68ffdfee9df6a8f65095179a475e29cc49119cad2ce05862c3b1ac217f3aace9", + "0x8c64d51774753623666b10ca1b0fe63ae42f82ed6aa26b81dc1d48c86937c5772eb1402624c52a154b86031854e1fb9f", + "0xb47e4df18002b7dac3fee945bf9c0503159e1b8aafcce2138818e140753011b6d09ef1b20894e08ba3006b093559061b", + "0x93cb5970076522c5a0483693f6a35ffd4ea2aa7aaf3730c4eccd6af6d1bebfc1122fc4c67d53898ae13eb6db647be7e2", + "0xa68873ef80986795ea5ed1a597d1cd99ed978ec25e0abb57fdcc96e89ef0f50aeb779ff46e3dce21dc83ada3157a8498", + "0x8cab67f50949cc8eee6710e27358aea373aae3c92849f8f0b5531c080a6300cdf2c2094fe6fecfef6148de0d28446919", + "0x993e932bcb616dbaa7ad18a4439e0565211d31071ef1b85a0627db74a05d978c60d507695eaeea5c7bd9868a21d06923", + "0xacdadff26e3132d9478a818ef770e9fa0d2b56c6f5f48bd3bd674436ccce9bdfc34db884a73a30c04c5f5e9764cb2218", + "0xa0d3e64c9c71f84c0eef9d7a9cb4fa184224b969db5514d678e93e00f98b41595588ca802643ea225512a4a272f5f534", + "0x91c9140c9e1ba6e330cb08f6b2ce4809cd0d5a0f0516f70032bf30e912b0ed684d07b413b326ab531ee7e5b4668c799b", + "0x87bc2ee7a0c21ba8334cd098e35cb703f9af57f35e091b8151b9b63c3a5b0f89bd7701dbd44f644ea475901fa6d9ef08", + "0x9325ccbf64bf5d71b303e31ee85d486298f9802c5e55b2c3d75427097bf8f60fa2ab4fcaffa9b60bf922c3e24fbd4b19", + "0x95d0506e898318f3dc8d28d16dfd9f0038b54798838b3c9be2a2ae3c2bf204eb496166353fc042220b0bd4f6673b9285", + "0x811de529416331fe9c416726d45df9434c29dcd7e949045eb15740f47e97dde8f31489242200e19922cac2a8b7c6fd1f", + "0xade632d04a4c8bbab6ca7df370b2213cb9225023e7973f0e29f4f5e52e8aeaabc65171306bbdd12a67b195dfbb96d48f", + "0x88b7f029e079b6ae956042c0ea75d53088c5d0efd750dd018adaeacf46be21bf990897c58578c491f41afd3978d08073", + "0x91f477802de507ffd2be3f4319903119225b277ad24f74eb50f28b66c14d32fae53c7edb8c7590704741af7f7f3e3654", + "0x809838b32bb4f4d0237e98108320d4b079ee16ed80c567e7548bd37e4d7915b1192880f4812ac0e00476d246aec1dbc8", + "0x84183b5fc4a7997a8ae5afedb4d21dce69c480d5966b5cbdafd6dd10d29a9a6377f3b90ce44da0eb8b176ac3af0253bb", + "0x8508abbf6d3739a16b9165caf0f95afb3b3ac1b8c38d6d374cf0c91296e2c1809a99772492b539cda184510bce8a0271", + "0x8722054e59bab2062e6419a6e45fc803af77fde912ef2cd23055ad0484963de65a816a2debe1693d93c18218d2b8e81a", + "0x8e895f80e485a7c4f56827bf53d34b956281cdc74856c21eb3b51f6288c01cc3d08565a11cc6f3e2604775885490e8c5", + "0xafc92714771b7aa6e60f3aee12efd9c2595e9659797452f0c1e99519f67c8bc3ac567119c1ddfe82a3e961ee9defea9a", + "0x818ff0fd9cefd32db87b259e5fa32967201016fc02ef44116cdca3c63ce5e637756f60477a408709928444a8ad69c471", + "0x8251e29af4c61ae806fc5d032347fb332a94d472038149225298389495139ce5678fae739d02dfe53a231598a992e728", + "0xa0ea39574b26643f6f1f48f99f276a8a64b5481989cfb2936f9432a3f8ef5075abfe5c067dc5512143ce8bf933984097", + "0xaf67a73911b372bf04e57e21f289fc6c3dfac366c6a01409b6e76fea4769bdb07a6940e52e8d7d3078f235c6d2f632c6", + "0xb5291484ef336024dd2b9b4cf4d3a6b751133a40656d0a0825bcc6d41c21b1c79cb50b0e8f4693f90c29c8f4358641f9", + "0x8bc0d9754d70f2cb9c63f991902165a87c6535a763d5eece43143b5064ae0bcdce7c7a8f398f2c1c29167b2d5a3e6867", + "0x8d7faff53579ec8f6c92f661c399614cc35276971752ce0623270f88be937c414eddcb0997e14724a783905a026c8883", + "0x9310b5f6e675fdf60796f814dbaa5a6e7e9029a61c395761e330d9348a7efab992e4e115c8be3a43d08e90d21290c892", + "0xb5eb4f3eb646038ad2a020f0a42202532d4932e766da82b2c1002bf9c9c2e5336b54c8c0ffcc0e02d19dde2e6a35b6cc", + "0x91dabfd30a66710f1f37a891136c9be1e23af4abf8cb751f512a40c022a35f8e0a4fb05b17ec36d4208de02d56f0d53a", + "0xb3ded14e82d62ac7a5a036122a62f00ff8308498f3feae57d861babaff5a6628d43f0a0c5fc903f10936bcf4e2758ceb", + "0xa88e8348fed2b26acca6784d19ef27c75963450d99651d11a950ea81d4b93acd2c43e0ecce100eaf7e78508263d5baf3", + "0xb1f5bbf7c4756877b87bb42163ac570e08c6667c4528bf68b5976680e19beeff7c5effd17009b0718797077e2955457a", + "0xad2e7b516243f915d4d1415326e98b1a7390ae88897d0b03b66c2d9bd8c3fba283d7e8fe44ed3333296a736454cef6d8", + "0x8f82eae096d5b11f995de6724a9af895f5e1c58d593845ad16ce8fcae8507e0d8e2b2348a0f50a1f66a17fd6fac51a5c", + "0x890e4404d0657c6c1ee14e1aac132ecf7a568bb3e04137b85ac0f84f1d333bd94993e8750f88eee033a33fb00f85dcc7", + "0x82ac7d3385e035115f1d39a99fc73e5919de44f5e6424579776d118d711c8120b8e5916372c6f27bed4cc64cac170b6c", + "0x85ee16d8901c272cfbbe966e724b7a891c1bd5e68efd5d863043ad8520fc409080af61fd726adc680b3f1186fe0ac8b8", + "0x86dc564c9b545567483b43a38f24c41c6551a49cabeebb58ce86404662a12dbfafd0778d30d26e1c93ce222e547e3898", + "0xa29f5b4522db26d88f5f95f18d459f8feefab02e380c2edb65aa0617a82a3c1a89474727a951cef5f15050bcf7b380fb", + "0xa1ce039c8f6cac53352899edb0e3a72c76da143564ad1a44858bd7ee88552e2fe6858d1593bbd74aeee5a6f8034b9b9d", + "0x97f10d77983f088286bd7ef3e7fdd8fa275a56bec19919adf33cf939a90c8f2967d2b1b6fc51195cb45ad561202a3ed7", + "0xa25e2772e8c911aaf8712bdac1dd40ee061c84d3d224c466cfaae8e5c99604053f940cde259bd1c3b8b69595781dbfec", + "0xb31bb95a0388595149409c48781174c340960d59032ab2b47689911d03c68f77a2273576fbe0c2bf4553e330656058c7", + "0xb8b2e9287ad803fb185a13f0d7456b397d4e3c8ad5078f57f49e8beb2e85f661356a3392dbd7bcf6a900baa5582b86a1", + "0xa3d0893923455eb6e96cc414341cac33d2dbc88fba821ac672708cce131761d85a0e08286663a32828244febfcae6451", + "0x82310cb42f647d99a136014a9f881eb0b9791efd2e01fc1841907ad3fc8a9654d3d1dab6689c3607214b4dc2aca01cee", + "0x874022d99c16f60c22de1b094532a0bc6d4de700ad01a31798fac1d5088b9a42ad02bef8a7339af7ed9c0d4f16b186ee", + "0x94981369e120265aed40910eebc37eded481e90f4596b8d57c3bec790ab7f929784bd33ddd05b7870aad6c02e869603b", + "0xa4f1f50e1e2a73f07095e0dd31cb45154f24968dae967e38962341c1241bcd473102fff1ff668b20c6547e9732d11701", + "0xae2328f3b0ad79fcda807e69a1b5278145225083f150f67511dafc97e079f860c3392675f1752ae7e864c056e592205b", + "0x875d8c971e593ca79552c43d55c8c73b17cd20c81ff2c2fed1eb19b1b91e4a3a83d32df150dbfd5db1092d0aebde1e1f", + "0xadd2e80aa46aae95da73a11f130f4bda339db028e24c9b11e5316e75ba5e63bc991d2a1da172c7c8e8fee038baae3433", + "0xb46dbe1cb3424002aa7de51e82f600852248e251465c440695d52538d3f36828ff46c90ed77fc1d11534fe3c487df8ef", + "0xa5e5045d28b4e83d0055863c30c056628c58d4657e6176fd0536f5933f723d60e851bb726d5bf3c546b8ce4ac4a57ef8", + "0x91fec01e86dd1537e498fff7536ea3ca012058b145f29d9ada49370cd7b7193ac380e116989515df1b94b74a55c45df3", + "0xa7428176d6918cd916a310bdc75483c72de660df48cac4e6e7478eef03205f1827ea55afc0df5d5fa7567d14bbea7fc9", + "0x851d89bef45d9761fe5fdb62972209335193610015e16a675149519f9911373bac0919add226ef118d9f3669cfdf4734", + "0xb74acf5c149d0042021cb2422ea022be4c4f72a77855f42393e71ffd12ebb3eec16bdf16f812159b67b79a9706e7156d", + "0x99f35dce64ec99aa595e7894b55ce7b5a435851b396e79036ffb249c28206087db4c85379df666c4d95857db02e21ff9", + "0xb6b9a384f70db9e298415b8ab394ee625dafff04be2886476e59df8d052ca832d11ac68a9b93fba7ab055b7bc36948a4", + "0x898ee4aefa923ffec9e79f2219c7389663eb11eb5b49014e04ed4a336399f6ea1691051d86991f4c46ca65bcd4fdf359", + "0xb0f948217b0d65df7599a0ba4654a5e43c84db477936276e6f11c8981efc6eaf14c90d3650107ed4c09af4cc8ec11137", + "0xaa6286e27ac54f73e63dbf6f41865dd94d24bc0cf732262fcaff67319d162bb43af909f6f8ee27b1971939cfbba08141", + "0x8bca7cdf730cf56c7b2c8a2c4879d61361a6e1dba5a3681a1a16c17a56e168ace0e99cf0d15826a1f5e67e6b8a8a049a", + "0xa746d876e8b1ce225fcafca603b099b36504846961526589af977a88c60d31ba2cc56e66a3dec8a77b3f3531bf7524c9", + "0xa11e2e1927e6704cdb8874c75e4f1842cef84d7d43d7a38e339e61dc8ba90e61bbb20dd3c12e0b11d2471d58eed245be", + "0xa36395e22bc1d1ba8b0459a235203177737397da5643ce54ded3459d0869ff6d8d89f50c73cb62394bf66a959cde9b90", + "0x8b49f12ba2fdf9aca7e5f81d45c07d47f9302a2655610e7634d1e4bd16048381a45ef2c95a8dd5b0715e4b7cf42273af", + "0x91cffa2a17e64eb7f76bccbe4e87280ee1dd244e04a3c9eac12e15d2d04845d876eb24fe2ec6d6d266cce9efb281077f", + "0xa6b8afabf65f2dee01788114e33a2f3ce25376fb47a50b74da7c3c25ff1fdc8aa9f41307534abbf48acb6f7466068f69", + "0x8d13db896ccfea403bd6441191995c1a65365cab7d0b97fbe9526da3f45a877bd1f4ef2edef160e8a56838cd1586330e", + "0x98c717de9e01bef8842c162a5e757fe8552d53269c84862f4d451e7c656ae6f2ae473767b04290b134773f63be6fdb9d", + "0x8c2036ace1920bd13cf018e82848c49eb511fad65fd0ff51f4e4b50cf3bfc294afb63cba682c16f52fb595a98fa84970", + "0xa3520fdff05dbad9e12551b0896922e375f9e5589368bcb2cc303bde252743b74460cb5caf99629325d3620f13adc796", + "0x8d4f83a5bfec05caf5910e0ce538ee9816ee18d0bd44c1d0da2a87715a23cd2733ad4d47552c6dc0eb397687d611dd19", + "0xa7b39a0a6a02823452d376533f39d35029867b3c9a6ad6bca181f18c54132d675613a700f9db2440fb1b4fa13c8bf18a", + "0x80bcb114b2544b80f404a200fc36860ed5e1ad31fe551acd4661d09730c452831751baa9b19d7d311600d267086a70bc", + "0x90dcce03c6f88fc2b08f2b42771eedde90cc5330fe0336e46c1a7d1b5a6c1641e5fcc4e7b3d5db00bd8afca9ec66ed81", + "0xaec15f40805065c98e2965b1ae12a6c9020cfdb094c2d0549acfc7ea2401a5fb48d3ea7d41133cf37c4e096e7ff53eb9", + "0x80e129b735dba49fa627a615d6c273119acec8e219b2f2c4373a332b5f98d66cbbdd688dfbe72a8f8bfefaccc02c50c1", + "0xa9b596da3bdfe23e6799ece5f7975bf7a1979a75f4f546deeaf8b34dfe3e0d623217cb4cf4ccd504cfa3625b88cd53f1", + "0xabcbbb70b16f6e517c0ab4363ab76b46e4ff58576b5f8340e5c0e8cc0e02621b6e23d742d73b015822a238b17cfd7665", + "0xa046937cc6ea6a2e1adae543353a9fe929c1ae4ad655be1cc051378482cf88b041e28b1e9a577e6ccff2d3570f55e200", + "0x831279437282f315e65a60184ef158f0a3dddc15a648dc552bdc88b3e6fe8288d3cfe9f0031846d81350f5e7874b4b33", + "0x993d7916fa213c6d66e7c4cafafc1eaec9a2a86981f91c31eb8a69c5df076c789cbf498a24c84e0ee77af95b42145026", + "0x823907a3b6719f8d49b3a4b7c181bd9bb29fcf842d7c70660c4f351852a1e197ca46cf5e879b47fa55f616fa2b87ce5e", + "0x8d228244e26132b234930ee14c75d88df0943cdb9c276a8faf167d259b7efc1beec2a87c112a6c608ad1600a239e9aae", + "0xab6e55766e5bfb0cf0764ed909a8473ab5047d3388b4f46faeba2d1425c4754c55c6daf6ad4751e634c618b53e549529", + "0xab0cab6860e55a84c5ad2948a7e0989e2b4b1fd637605634b118361497332df32d9549cb854b2327ca54f2bcb85eed8f", + "0xb086b349ae03ef34f4b25a57bcaa5d1b29bd94f9ebf87e22be475adfe475c51a1230c1ebe13506cb72c4186192451658", + "0x8a0b49d8a254ca6d91500f449cbbfbb69bb516c6948ac06808c65595e46773e346f97a5ce0ef7e5a5e0de278af22709c", + "0xac49de11edaaf04302c73c578cc0824bdd165c0d6321be1c421c1950e68e4f3589aa3995448c9699e93c6ebae8803e27", + "0x884f02d841cb5d8f4c60d1402469216b114ab4e93550b5bc1431756e365c4f870a9853449285384a6fa49e12ce6dc654", + "0xb75f3a28fa2cc8d36b49130cb7448a23d73a7311d0185ba803ad55c8219741d451c110f48b786e96c728bc525903a54f", + "0x80ae04dbd41f4a35e33f9de413b6ad518af0919e5a30cb0fa1b061b260420780bb674f828d37fd3b52b5a31673cbd803", + "0xb9a8011eb5fcea766907029bf743b45262db3e49d24f84503687e838651ed11cb64c66281e20a0ae9f6aa51acc552263", + "0x90bfdd75e2dc9cf013e22a5d55d2d2b8a754c96103a17524488e01206e67f8b6d52b1be8c4e3d5307d4fe06d0e51f54c", + "0xb4af353a19b06203a815ec43e79a88578cc678c46f5a954b85bc5c53b84059dddba731f3d463c23bfd5273885c7c56a4", + "0xaa125e96d4553b64f7140e5453ff5d2330318b69d74d37d283e84c26ad672fa00e3f71e530eb7e28be1e94afb9c4612e", + "0xa18e060aee3d49cde2389b10888696436bb7949a79ca7d728be6456a356ea5541b55492b2138da90108bd1ce0e6f5524", + "0x93e55f92bdbccc2de655d14b1526836ea2e52dba65eb3f87823dd458a4cb5079bf22ce6ef625cb6d6bfdd0995ab9a874", + "0x89f5a683526b90c1c3ceebbb8dc824b21cff851ce3531b164f6626e326d98b27d3e1d50982e507d84a99b1e04e86a915", + "0x83d1c38800361633a3f742b1cb2bfc528129496e80232611682ddbe403e92c2ac5373aea0bca93ecb5128b0b2b7a719e", + "0x8ecba560ac94905e19ce8d9c7af217bf0a145d8c8bd38e2db82f5e94cc3f2f26f55819176376b51f154b4aab22056059", + "0xa7e2a4a002b60291924850642e703232994acb4cfb90f07c94d1e0ecd2257bb583443283c20fc6017c37e6bfe85b7366", + "0x93ed7316fa50b528f1636fc6507683a672f4f4403e55e94663f91221cc198199595bd02eef43d609f451acc9d9b36a24", + "0xa1220a8ebc5c50ceed76a74bc3b7e0aa77f6884c71b64b67c4310ac29ce5526cb8992d6abc13ef6c8413ce62486a6795", + "0xb2f6eac5c869ad7f4a25161d3347093e2f70e66cd925032747e901189355022fab3038bca4d610d2f68feb7e719c110b", + "0xb703fa11a4d511ca01c7462979a94acb40b5d933759199af42670eb48f83df202fa0c943f6ab3b4e1cc54673ea3aab1e", + "0xb5422912afbfcb901f84791b04f1ddb3c3fbdc76d961ee2a00c5c320e06d3cc5b5909c3bb805df66c5f10c47a292b13d", + "0xad0934368da823302e1ac08e3ede74b05dfdbfffca203e97ffb0282c226814b65c142e6e15ec1e754518f221f01b30f7", + "0xa1dd302a02e37df15bf2f1147efe0e3c06933a5a767d2d030e1132f5c3ce6b98e216b6145eb39e1e2f74e76a83165b8d", + "0xa346aab07564432f802ae44738049a36f7ca4056df2d8f110dbe7fef4a3e047684dea609b2d03dc6bf917c9c2a47608f", + "0xb96c5f682a5f5d02123568e50f5d0d186e4b2c4c9b956ec7aabac1b3e4a766d78d19bd111adb5176b898e916e49be2aa", + "0x8a96676d56876fc85538db2e806e1cba20fd01aeb9fa3cb43ca6ca94a2c102639f65660db330e5d74a029bb72d6a0b39", + "0xab0048336bd5c3def1a4064eadd49e66480c1f2abb4df46e03afbd8a3342c2c9d74ee35d79f08f4768c1646681440984", + "0x888427bdf76caec90814c57ee1c3210a97d107dd88f7256f14f883ad0f392334b82be11e36dd8bfec2b37935177c7831", + "0xb622b282becf0094a1916fa658429a5292ba30fb48a4c8066ce1ddcefb71037948262a01c95bab6929ed3a76ba5db9fe", + "0xb5b9e005c1f456b6a368a3097634fb455723abe95433a186e8278dceb79d4ca2fbe21f8002e80027b3c531e5bf494629", + "0xa3c6707117a1e48697ed41062897f55d8119403eea6c2ee88f60180f6526f45172664bfee96bf61d6ec0b7fbae6aa058", + "0xb02a9567386a4fbbdb772d8a27057b0be210447348efe6feb935ceec81f361ed2c0c211e54787dc617cdffed6b4a6652", + "0xa9b8364e40ef15c3b5902e5534998997b8493064fa2bea99600def58279bb0f64574c09ba11e9f6f669a8354dd79dc85", + "0x9998a2e553a9aa9a206518fae2bc8b90329ee59ab23005b10972712389f2ec0ee746033c733092ffe43d73d33abbb8ef", + "0x843a4b34d9039bf79df96d79f2d15e8d755affb4d83d61872daf540b68c0a3888cf8fc00d5b8b247b38524bcb3b5a856", + "0x84f7128920c1b0bb40eee95701d30e6fc3a83b7bb3709f16d97e72acbb6057004ee7ac8e8f575936ca9dcb7866ab45f7", + "0x918d3e2222e10e05edb34728162a899ad5ada0aaa491aeb7c81572a9c0d506e31d5390e1803a91ff3bd8e2bb15d47f31", + "0x9442d18e2489613a7d47bb1cb803c8d6f3259d088cd079460976d87f7905ee07dea8f371b2537f6e1d792d36d7e42723", + "0xb491976970fe091995b2ed86d629126523ccf3e9daf8145302faca71b5a71a5da92e0e05b62d7139d3efac5c4e367584", + "0xaa628006235dc77c14cef4c04a308d66b07ac92d377df3de1a2e6ecfe3144f2219ad6d7795e671e1cb37a3641910b940", + "0x99d386adaea5d4981d7306feecac9a555b74ffdc218c907c5aa7ac04abaead0ec2a8237300d42a3fbc464673e417ceed", + "0x8f78e8b1556f9d739648ea3cab9606f8328b52877fe72f9305545a73b74d49884044ba9c1f1c6db7d9b7c7b7c661caba", + "0x8fb357ae49932d0babdf74fc7aa7464a65d3b6a2b3acf4f550b99601d3c0215900cfd67f2b6651ef94cfc323bac79fae", + "0x9906f2fa25c0290775aa001fb6198113d53804262454ae8b83ef371b5271bde189c0460a645829cb6c59f9ee3a55ce4d", + "0x8f4379b3ebb50e052325b27655ca6a82e6f00b87bf0d2b680d205dd2c7afdc9ff32a9047ae71a1cdf0d0ce6b9474d878", + "0xa85534e88c2bd43c043792eaa75e50914b21741a566635e0e107ae857aed0412035f7576cf04488ade16fd3f35fdbb87", + "0xb4ce93199966d3c23251ca7f28ec5af7efea1763d376b0385352ffb2e0a462ef95c69940950278cf0e3dafd638b7bd36", + "0xb10cb3d0317dd570aa73129f4acf63c256816f007607c19b423fb42f65133ce21f2f517e0afb41a5378cccf893ae14d0", + "0xa9b231c9f739f7f914e5d943ed9bff7eba9e2c333fbd7c34eb1648a362ee01a01af6e2f7c35c9fe962b11152cddf35de", + "0x99ff6a899e156732937fb81c0cced80ae13d2d44c40ba99ac183aa246103b31ec084594b1b7feb96da58f4be2dd5c0ed", + "0x8748d15d18b75ff2596f50d6a9c4ce82f61ecbcee123a6ceae0e43cab3012a29b6f83cf67b48c22f6f9d757c6caf76b2", + "0xb88ab05e4248b7fb634cf640a4e6a945d13e331237410f7217d3d17e3e384ddd48897e7a91e4516f1b9cbd30f35f238b", + "0x8d826deaeeb84a3b2d2c04c2300ca592501f992810582d6ae993e0d52f6283a839dba66c6c72278cff5871802b71173b", + "0xb36fed027c2f05a5ef625ca00b0364b930901e9e4420975b111858d0941f60e205546474bb25d6bfa6928d37305ae95f", + "0xaf2fcfc6b87967567e8b8a13a4ed914478185705724e56ce68fb2df6d1576a0cf34a61e880997a0d35dc2c3276ff7501", + "0xac351b919cd1fbf106feb8af2c67692bfcddc84762d18cea681cfa7470a5644839caace27efee5f38c87d3df306f4211", + "0x8d6665fb1d4d8d1fa23bd9b8a86e043b8555663519caac214d1e3e3effbc6bee7f2bcf21e645f77de0ced279d69a8a8b", + "0xa9fc1c2061756b2a1a169c1b149f212ff7f0d2488acd1c5a0197eba793cffa593fc6d1d1b40718aa75ca3ec77eff10e1", + "0xaff64f0fa009c7a6cf0b8d7a22ddb2c8170c3cb3eec082e60d5aadb00b0040443be8936d728d99581e33c22178c41c87", + "0x82e0b181adc5e3b1c87ff8598447260e839d53debfae941ebea38265575546c3a74a14b4325a030833a62ff6c52d9365", + "0xb7ad43cbb22f6f892c2a1548a41dc120ab1f4e1b8dea0cb6272dd9cb02054c542ecabc582f7e16de709d48f5166cae86", + "0x985e0c61094281532c4afb788ecb2dfcba998e974b5d4257a22040a161883908cdd068fe80f8eb49b8953cfd11acf43a", + "0xae46895c6d67ea6d469b6c9c07b9e5d295d9ae73b22e30da4ba2c973ba83a130d7eef39717ec9d0f36e81d56bf742671", + "0x8600177ea1f7e7ef90514b38b219a37dedfc39cb83297e4c7a5b479817ef56479d48cf6314820960c751183f6edf8b0e", + "0xb9208ec1c1d7a1e99b59c62d3e4e61dfb706b0e940d09d3abfc3454c19749083260614d89cfd7e822596c3cdbcc6bb95", + "0xa1e94042c796c2b48bc724352d2e9f3a22291d9a34705993357ddb6adabd76da6fc25dac200a8cb0b5bbd99ecddb7af6", + "0xb29c3adedd0bcad8a930625bc4dfdc3552a9afd5ca6dd9c0d758f978068c7982b50b711aa0eb5b97f2b84ee784637835", + "0xaf0632a238bb1f413c7ea8e9b4c3d68f2827bd2e38cd56024391fba6446ac5d19a780d0cfd4a78fe497d537b766a591a", + "0xaaf6e7f7d54f8ef5e2e45dd59774ecbeecf8683aa70483b2a75be6a6071b5981bbaf1627512a65d212817acdfab2e428", + "0x8c751496065da2e927cf492aa5ca9013b24f861d5e6c24b30bbf52ec5aaf1905f40f9a28175faef283dd4ed4f2182a09", + "0x8952377d8e80a85cf67d6b45499f3bad5fd452ea7bcd99efc1b066c4720d8e5bff1214cea90fd1f972a7f0baac3d29be", + "0xa1946ee543d1a6e21f380453be4d446e4130950c5fc3d075794eb8260f6f52d0a795c1ff91d028a648dc1ce7d9ab6b47", + "0x89f3fefe37af31e0c17533d2ca1ce0884cc1dc97c15cbfab9c331b8debd94781c9396abef4bb2f163d09277a08d6adf0", + "0xa2753f1e6e1a154fb117100a5bd9052137add85961f8158830ac20541ab12227d83887d10acf7fd36dcaf7c2596d8d23", + "0x814955b4198933ee11c3883863b06ff98c7eceb21fc3e09df5f916107827ccf3323141983e74b025f46ae00284c9513b", + "0x8cc5c6bb429073bfef47cae7b3bfccb0ffa076514d91a1862c6bda4d581e0df87db53cc6c130bf8a7826304960f5a34e", + "0x909f22c1f1cdc87f7be7439c831a73484a49acbf8f23d47087d7cf867c64ef61da3bde85dc57d705682b4c3fc710d36e", + "0x8048fee7f276fcd504aed91284f28e73693615e0eb3858fa44bcf79d7285a9001c373b3ef71d9a3054817ba293ebe28c", + "0x94400e5cf5d2700ca608c5fe35ce14623f71cc24959f2bc27ca3684092850f76b67fb1f07ca9e5b2ca3062cf8ad17bd4", + "0x81c2ae7d4d1b17f8b6de6a0430acc0d58260993980fe48dc2129c4948269cdc74f9dbfbf9c26b19360823fd913083d48", + "0x8c41fe765128e63f6889d6a979f6a4342300327c8b245a8cfe3ecfbcac1e09c3da30e2a1045b24b78efc6d6d50c8c6ac", + "0xa5dd4ae51ae48c8be4b218c312ade226cffce671cf121cb77810f6c0990768d6dd767badecb5c69921d5574d5e8433d3", + "0xb7642e325f4ba97ae2a39c1c9d97b35aafd49d53dba36aed3f3cb0ca816480b3394079f46a48252d46596559c90f4d58", + "0xae87375b40f35519e7bd4b1b2f73cd0b329b0c2cb9d616629342a71c6c304338445eda069b78ea0fbe44087f3de91e09", + "0xb08918cb6f736855e11d3daca1ddfbdd61c9589b203b5493143227bf48e2c77c2e8c94b0d1aa2fab2226e0eae83f2681", + "0xac36b84a4ac2ebd4d6591923a449c564e3be8a664c46092c09e875c2998eba16b5d32bfd0882fd3851762868e669f0b1", + "0xa44800a3bb192066fa17a3f29029a23697240467053b5aa49b9839fb9b9b8b12bcdcbfc557f024b61f4f51a9aacdefcb", + "0x9064c688fec23441a274cdf2075e5a449caf5c7363cc5e8a5dc9747183d2e00a0c69f2e6b3f6a7057079c46014c93b3b", + "0xaa367b021469af9f5b764a79bb3afbe2d87fe1e51862221672d1a66f954b165778b7c27a705e0f93841fab4c8468344d", + "0xa1a8bfc593d4ab71f91640bc824de5c1380ab2591cfdafcbc78a14b32de3c0e15f9d1b461d85c504baa3d4232c16bb53", + "0x97df48da1799430f528184d30b6baa90c2a2f88f34cdfb342d715339c5ebd6d019aa693cea7c4993daafc9849063a3aa", + "0xabd923831fbb427e06e0dd335253178a9e5791395c84d0ab1433c07c53c1209161097e9582fb8736f8a60bde62d8693e", + "0x84cd1a43f1a438b43dc60ffc775f646937c4f6871438163905a3cebf1115f814ccd38a6ccb134130bff226306e412f32", + "0x91426065996b0743c5f689eb3ca68a9f7b9e4d01f6c5a2652b57fa9a03d8dc7cd4bdbdab0ca5a891fee1e97a7f00cf02", + "0xa4bee50249db3df7fd75162b28f04e57c678ba142ce4d3def2bc17bcb29e4670284a45f218dad3969af466c62a903757", + "0x83141ebcc94d4681404e8b67a12a46374fded6df92b506aff3490d875919631408b369823a08b271d006d5b93136f317", + "0xa0ea1c8883d58d5a784da3d8c8a880061adea796d7505c1f903d07c287c5467f71e4563fc0faafbc15b5a5538b0a7559", + "0x89d9d480574f201a87269d26fb114278ed2c446328df431dc3556e3500e80e4cd01fcac196a2459d8646361ebda840df", + "0x8bf302978973632dd464bec819bdb91304712a3ec859be071e662040620422c6e75eba6f864f764cffa2799272efec39", + "0x922f666bc0fd58b6d7d815c0ae4f66d193d32fc8382c631037f59eeaeae9a8ca6c72d08e72944cf9e800b8d639094e77", + "0x81ad8714f491cdff7fe4399f2eb20e32650cff2999dd45b9b3d996d54a4aba24cc6c451212e78c9e5550368a1a38fb3f", + "0xb58fcf4659d73edb73175bd9139d18254e94c3e32031b5d4b026f2ed37aa19dca17ec2eb54c14340231615277a9d347e", + "0xb365ac9c2bfe409b710928c646ea2fb15b28557e0f089d39878e365589b9d1c34baf5566d20bb28b33bb60fa133f6eff", + "0x8fcae1d75b53ab470be805f39630d204853ca1629a14158bac2f52632277d77458dec204ff84b7b2d77e641c2045be65", + "0xa03efa6bebe84f4f958a56e2d76b5ba4f95dd9ed7eb479edc7cc5e646c8d4792e5b0dfc66cc86aa4b4afe2f7a4850760", + "0xaf1c823930a3638975fb0cc5c59651771b2719119c3cd08404fbd4ce77a74d708cefbe3c56ea08c48f5f10e6907f338f", + "0x8260c8299b17898032c761c325ac9cabb4c5b7e735de81eacf244f647a45fb385012f4f8df743128888c29aefcaaad16", + "0xab2f37a573c82e96a8d46198691cd694dfa860615625f477e41f91b879bc58a745784fccd8ffa13065834ffd150d881d", + "0x986c746c9b4249352d8e5c629e8d7d05e716b3c7aab5e529ca969dd1e984a14b5be41528baef4c85d2369a42d7209216", + "0xb25e32da1a8adddf2a6080725818b75bc67240728ad1853d90738485d8924ea1e202df0a3034a60ffae6f965ec55cf63", + "0xa266e627afcebcefea6b6b44cbc50f5c508f7187e87d047b0450871c2a030042c9e376f3ede0afcf9d1952f089582f71", + "0x86c3bbca4c0300606071c0a80dbdec21ce1dd4d8d4309648151c420854032dff1241a1677d1cd5de4e4de4385efda986", + "0xb9a21a1fe2d1f3273a8e4a9185abf2ff86448cc98bfa435e3d68306a2b8b4a6a3ea33a155be3cb62a2170a86f77679a5", + "0xb117b1ea381adce87d8b342cba3a15d492ff2d644afa28f22424cb9cbc820d4f7693dfc1a4d1b3697046c300e1c9b4c8", + "0x9004c425a2e68870d6c69b658c344e3aa3a86a8914ee08d72b2f95c2e2d8a4c7bb0c6e7e271460c0e637cec11117bf8e", + "0x86a18aa4783b9ebd9131580c8b17994825f27f4ac427b0929a1e0236907732a1c8139e98112c605488ee95f48bbefbfc", + "0x84042243b955286482ab6f0b5df4c2d73571ada00716d2f737ca05a0d2e88c6349e8ee9e67934cfee4a1775dbf7f4800", + "0x92c2153a4733a62e4e1d5b60369f3c26777c7d01cd3c8679212660d572bd3bac9b8a8a64e1f10f7dbf5eaa7579c4e423", + "0x918454b6bb8e44a2afa144695ba8d48ae08d0cdfef4ad078f67709eddf3bb31191e8b006f04e82ea45a54715ef4d5817", + "0xacf0b54f6bf34cf6ed6c2b39cf43194a40d68de6bcf1e4b82c34c15a1343e9ac3737885e1a30b78d01fa3a5125463db8", + "0xa7d60dbe4b6a7b054f7afe9ee5cbbfeca0d05dc619e6041fa2296b549322529faddb8a11e949562309aecefb842ac380", + "0x91ffb53e6d7e5f11159eaf13e783d6dbdfdb1698ed1e6dbf3413c6ea23492bbb9e0932230a9e2caac8fe899a17682795", + "0xb6e8d7be5076ee3565d5765a710c5ecf17921dd3cf555c375d01e958a365ae087d4a88da492a5fb81838b7b92bf01143", + "0xa8c6b763de2d4b2ed42102ef64eccfef31e2fb2a8a2776241c82912fa50fc9f77f175b6d109a97ede331307c016a4b1a", + "0x99839f86cb700c297c58bc33e28d46b92931961548deac29ba8df91d3e11721b10ea956c8e16984f9e4acf1298a79b37", + "0x8c2e2c338f25ea5c25756b7131cde0d9a2b35abf5d90781180a00fe4b8e64e62590dc63fe10a57fba3a31c76d784eb01", + "0x9687d7df2f41319ca5469d91978fed0565a5f11f829ebadaa83db92b221755f76c6eacd7700735e75c91e257087512e3", + "0x8795fdfb7ff8439c58b9bf58ed53873d2780d3939b902b9ddaaa4c99447224ced9206c3039a23c2c44bcc461e2bb637f", + "0xa803697b744d2d087f4e2307218d48fa88620cf25529db9ce71e2e3bbcc65bac5e8bb9be04777ef7bfb5ed1a5b8e6170", + "0x80f3d3efbbb9346ddd413f0a8e36b269eb5d7ff6809d5525ff9a47c4bcab2c01b70018b117f6fe05253775612ff70c6b", + "0x9050e0e45bcc83930d4c505af35e5e4d7ca01cd8681cba92eb55821aececcebe32bb692ebe1a4daac4e7472975671067", + "0x8d206812aac42742dbaf233e0c080b3d1b30943b54b60283515da005de05ea5caa90f91fedcfcba72e922f64d7040189", + "0xa2d44faaeb2eff7915c83f32b13ca6f31a6847b1c1ce114ea240bac3595eded89f09b2313b7915ad882292e2b586d5b4", + "0x961776c8576030c39f214ea6e0a3e8b3d32f023d2600958c098c95c8a4e374deeb2b9dc522adfbd6bda5949bdc09e2a2", + "0x993fa7d8447407af0fbcd9e6d77f815fa5233ab00674efbcf74a1f51c37481445ae291cc7b76db7c178f9cb0e570e0fc", + "0xabd5b1c78e05f9d7c8cc99bdaef8b0b6a57f2daf0f02bf492bec48ea4a27a8f1e38b5854da96efff11973326ff980f92", + "0x8f15af4764bc275e6ccb892b3a4362cacb4e175b1526a9a99944e692fe6ccb1b4fc19abf312bb2a089cb1f344d91a779", + "0xa09b27ccd71855512aba1d0c30a79ffbe7f6707a55978f3ced50e674b511a79a446dbc6d7946add421ce111135a460af", + "0x94b2f98ce86a9271fbd4153e1fc37de48421fe3490fb3840c00f2d5a4d0ba8810c6a32880b002f6374b59e0a7952518b", + "0x8650ac644f93bbcb88a6a0f49fee2663297fd4bc6fd47b6a89b9d8038d32370438ab3a4775ec9b58cb10aea8a95ef7b6", + "0x95e5c2f2e84eed88c6980bbba5a1c0bb375d5a628bff006f7516d45bb7d723da676add4fdd45956f312e7bab0f052644", + "0xb3278a3fa377ac93af7cfc9453f8cb594aae04269bbc99d2e0e45472ff4b6a2f97a26c4c57bf675b9d86f5e77a5d55d1", + "0xb4bcbe6eb666a206e2ea2f877912c1d3b5bdbd08a989fc4490eb06013e1a69ad1ba08bcdac048bf29192312be399077b", + "0xa76d70b78c99fffcbf9bb9886eab40f1ea4f99a309710b660b64cbf86057cbcb644d243f6e341711bb7ef0fedf0435a7", + "0xb2093c1ee945dca7ac76ad5aed08eae23af31dd5a77c903fd7b6f051f4ab84425d33a03c3d45bf2907bc93c02d1f3ad8", + "0x904b1f7534e053a265b22d20be859912b9c9ccb303af9a8d6f1d8f6ccdc5c53eb4a45a1762b880d8444d9be0cd55e7f9", + "0x8f664a965d65bc730c9ef1ec7467be984d4b8eb46bd9b0d64e38e48f94e6e55dda19aeac82cbcf4e1473440e64c4ca18", + "0x8bcee65c4cc7a7799353d07b114c718a2aae0cd10a3f22b7eead5185d159dafd64852cb63924bf87627d176228878bce", + "0x8c78f2e3675096fef7ebaa898d2615cd50d39ca3d8f02b9bdfb07e67da648ae4be3da64838dffc5935fd72962c4b96c7", + "0x8c40afd3701629421fec1df1aac4e849384ef2e80472c0e28d36cb1327acdf2826f99b357f3d7afdbc58a6347fc40b3c", + "0xa197813b1c65a8ea5754ef782522a57d63433ef752215ecda1e7da76b0412ee619f58d904abd2e07e0c097048b6ae1dd", + "0xa670542629e4333884ad7410f9ea3bd6f988df4a8f8a424ca74b9add2312586900cf9ae8bd50411f9146e82626b4af56", + "0xa19875cc07ab84e569d98b8b67fb1dbbdfb59093c7b748fae008c8904a6fd931a63ca8d03ab5fea9bc8d263568125a9b", + "0xb57e7f68e4eb1bd04aafa917b1db1bdab759a02aa8a9cdb1cba34ba8852b5890f655645c9b4e15d5f19bf37e9f2ffe9f", + "0x8abe4e2a4f6462b6c64b3f10e45db2a53c2b0d3c5d5443d3f00a453e193df771eda635b098b6c8604ace3557514027af", + "0x8459e4fb378189b22b870a6ef20183deb816cefbf66eca1dc7e86d36a2e011537db893729f500dc154f14ce24633ba47", + "0x930851df4bc7913c0d8c0f7bd3b071a83668987ed7c397d3d042fdc0d9765945a39a3bae83da9c88cb6b686ed8aeeb26", + "0x8078c9e5cd05e1a8c932f8a1d835f61a248b6e7133fcbb3de406bf4ffc0e584f6f9f95062740ba6008d98348886cf76b", + "0xaddff62bb29430983fe578e3709b0949cdc0d47a13a29bc3f50371a2cb5c822ce53e2448cfaa01bcb6e0aa850d5a380e", + "0x9433add687b5a1e12066721789b1db2edf9b6558c3bdc0f452ba33b1da67426abe326e9a34d207bfb1c491c18811bde1", + "0x822beda3389963428cccc4a2918fa9a8a51cf0919640350293af70821967108cded5997adae86b33cb917780b097f1ca", + "0xa7a9f52bda45e4148ed56dd176df7bd672e9b5ed18888ccdb405f47920fdb0844355f8565cefb17010b38324edd8315f", + "0xb35c3a872e18e607b2555c51f9696a17fa18da1f924d503b163b4ec9fe22ed0c110925275cb6c93ce2d013e88f173d6a", + "0xadf34b002b2b26ab84fc1bf94e05bd8616a1d06664799ab149363c56a6e0c807fdc473327d25632416e952ea327fcd95", + "0xae4a6b9d22a4a3183fac29e2551e1124a8ce4a561a9a2afa9b23032b58d444e6155bb2b48f85c7b6d70393274e230db7", + "0xa2ea3be4fc17e9b7ce3110284038d46a09e88a247b6971167a7878d9dcf36925d613c382b400cfa4f37a3ebea3699897", + "0x8e5863786b641ce3140fbfe37124d7ad3925472e924f814ebfc45959aaf3f61dc554a597610b5defaecc85b59a99b50f", + "0xaefde3193d0f700d0f515ab2aaa43e2ef1d7831c4f7859f48e52693d57f97fa9e520090f3ed700e1c966f4b76048e57f", + "0x841a50f772956622798e5cd208dc7534d4e39eddee30d8ce133383d66e5f267e389254a0cdae01b770ecd0a9ca421929", + "0x8fbc2bfd28238c7d47d4c03b1b910946c0d94274a199575e5b23242619b1de3497784e646a92aa03e3e24123ae4fcaba", + "0x926999579c8eec1cc47d7330112586bdca20b4149c8b2d066f527c8b9f609e61ce27feb69db67eea382649c6905efcf9", + "0xb09f31f305efcc65589adf5d3690a76cf339efd67cd43a4e3ced7b839507466e4be72dd91f04e89e4bbef629d46e68c0", + "0xb917361f6b95f759642638e0b1d2b3a29c3bdef0b94faa30de562e6078c7e2d25976159df3edbacbf43614635c2640b4", + "0x8e7e8a1253bbda0e134d62bfe003a2669d471b47bd2b5cde0ff60d385d8e62279d54022f5ac12053b1e2d3aaa6910b4c", + "0xb69671a3c64e0a99d90b0ed108ce1912ff8ed983e4bddd75a370e9babde25ee1f5efb59ec707edddd46793207a8b1fe7", + "0x910b2f4ebd37b7ae94108922b233d0920b4aba0bd94202c70f1314418b548d11d8e9caa91f2cd95aff51b9432d122b7f", + "0x82f645c90dfb52d195c1020346287c43a80233d3538954548604d09fbab7421241cde8593dbc4acc4986e0ea39a27dd9", + "0x8fee895f0a140d88104ce442fed3966f58ff9d275e7373483f6b4249d64a25fb5374bbdc6bce6b5ab0270c2847066f83", + "0x84f5bd7aab27b2509397aeb86510dd5ac0a53f2c8f73799bf720f2f87a52277f8d6b0f77f17bc80739c6a7119b7eb062", + "0x9903ceced81099d7e146e661bcf01cbaccab5ba54366b85e2177f07e2d8621e19d9c9c3eee14b9266de6b3f9b6ea75ae", + "0xb9c16ea2a07afa32dd6c7c06df0dec39bca2067a9339e45475c98917f47e2320f6f235da353fd5e15b477de97ddc68dd", + "0x9820a9bbf8b826bec61ebf886de2c4f404c1ebdc8bab82ee1fea816d9de29127ce1852448ff717a3fe8bbfe9e92012e5", + "0x817224d9359f5da6f2158c2c7bf9165501424f063e67ba9859a07ab72ee2ee62eb00ca6da821cfa19065c3282ca72c74", + "0x94b95c465e6cb00da400558a3c60cfec4b79b27e602ca67cbc91aead08de4b6872d8ea096b0dc06dca4525c8992b8547", + "0xa2b539a5bccd43fa347ba9c15f249b417997c6a38c63517ca38394976baa08e20be384a360969ff54e7e721db536b3e5", + "0x96caf707e34f62811ee8d32ccf28d8d6ec579bc33e424d0473529af5315c456fd026aa910c1fed70c91982d51df7d3ca", + "0x8a77b73e890b644c6a142bdbac59b22d6a676f3b63ddafb52d914bb9d395b8bf5aedcbcc90429337df431ebd758a07a6", + "0x8857830a7351025617a08bc44caec28d2fae07ebf5ffc9f01d979ce2a53839a670e61ae2783e138313929129790a51a1", + "0xaa3e420321ed6f0aa326d28d1a10f13facec6f605b6218a6eb9cbc074801f3467bf013a456d1415a5536f12599efa3d3", + "0x824aed0951957b00ea2f3d423e30328a3527bf6714cf9abbae84cf27e58e5c35452ba89ccc011de7c68c75d6e021d8f1", + "0xa2e87cc06bf202e953fb1081933d8b4445527dde20e38ed1a4f440144fd8fa464a2b73e068b140562e9045e0f4bd3144", + "0xae3b8f06ad97d7ae3a5e5ca839efff3e4824dc238c0c03fc1a8d2fc8aa546cdfd165b784a31bb4dec7c77e9305b99a4b", + "0xb30c3e12395b1fb8b776f3ec9f87c70e35763a7b2ddc68f0f60a4982a84017f27c891a98561c830038deb033698ed7fc", + "0x874e507757cd1177d0dff0b0c62ce90130324442a33da3b2c8ee09dbca5d543e3ecfe707e9f1361e7c7db641c72794bb", + "0xb53012dd10b5e7460b57c092eaa06d6502720df9edbbe3e3f61a9998a272bf5baaac4a5a732ad4efe35d6fac6feca744", + "0x85e6509d711515534d394e6cacbed6c81da710074d16ef3f4950bf2f578d662a494d835674f79c4d6315bced4defc5f0", + "0xb6132b2a34b0905dcadc6119fd215419a7971fe545e52f48b768006944b4a9d7db1a74b149e2951ea48c083b752d0804", + "0x989867da6415036d19b4bacc926ce6f4df7a556f50a1ba5f3c48eea9cefbb1c09da81481c8009331ee83f0859185e164", + "0x960a6c36542876174d3fbc1505413e29f053ed87b8d38fef3af180491c7eff25200b45dd5fe5d4d8e63c7e8c9c00f4c8", + "0x9040b59bd739d9cc2e8f6e894683429e4e876a8106238689ff4c22770ae5fdae1f32d962b30301fa0634ee163b524f35", + "0xaf3fcd0a45fe9e8fe256dc7eab242ef7f582dd832d147444483c62787ac820fafc6ca55d639a73f76bfa5e7f5462ab8f", + "0xb934c799d0736953a73d91e761767fdb78454355c4b15c680ce08accb57ccf941b13a1236980001f9e6195801cffd692", + "0x8871e8e741157c2c326b22cf09551e78da3c1ec0fc0543136f581f1550f8bab03b0a7b80525c1e99812cdbf3a9698f96", + "0xa8a977f51473a91d178ee8cfa45ffef8d6fd93ab1d6e428f96a3c79816d9c6a93cd70f94d4deda0125fd6816e30f3bea", + "0xa7688b3b0a4fc1dd16e8ba6dc758d3cfe1b7cf401c31739484c7fa253cce0967df1b290769bcefc9d23d3e0cb19e6218", + "0x8ae84322662a57c6d729e6ff9d2737698cc2da2daeb1f39e506618750ed23442a6740955f299e4a15dda6db3e534d2c6", + "0xa04a961cdccfa4b7ef83ced17ab221d6a043b2c718a0d6cc8e6f798507a31f10bf70361f70a049bc8058303fa7f96864", + "0xb463e39732a7d9daec8a456fb58e54b30a6e160aa522a18b9a9e836488cce3342bcbb2e1deab0f5e6ec0a8796d77197d", + "0xb1434a11c6750f14018a2d3bcf94390e2948f4f187e93bb22070ca3e5393d339dc328cbfc3e48815f51929465ffe7d81", + "0x84ff81d73f3828340623d7e3345553610aa22a5432217ef0ebd193cbf4a24234b190c65ca0873c22d10ea7b63bd1fbed", + "0xb6fe2723f0c47757932c2ddde7a4f8434f665612f7b87b4009c2635d56b6e16b200859a8ade49276de0ef27a2b6c970a", + "0x9742884ed7cd52b4a4a068a43d3faa02551a424136c85a9313f7cb58ea54c04aa83b0728fd741d1fe39621e931e88f8f", + "0xb7d2d65ea4d1ad07a5dee39e40d6c03a61264a56b1585b4d76fc5b2a68d80a93a42a0181d432528582bf08d144c2d6a9", + "0x88c0f66bada89f8a43e5a6ead2915088173d106c76f724f4a97b0f6758aed6ae5c37c373c6b92cdd4aea8f6261f3a374", + "0x81f9c43582cb42db3900747eb49ec94edb2284999a499d1527f03315fd330e5a509afa3bff659853570e9886aab5b28b", + "0x821f9d27d6beb416abf9aa5c79afb65a50ed276dbda6060103bc808bcd34426b82da5f23e38e88a55e172f5c294b4d40", + "0x8ba307b9e7cb63a6c4f3851b321aebfdb6af34a5a4c3bd949ff7d96603e59b27ff4dc4970715d35f7758260ff942c9e9", + "0xb142eb6c5f846de33227d0bda61d445a7c33c98f0a8365fe6ab4c1fabdc130849be597ef734305894a424ea715372d08", + "0xa732730ae4512e86a741c8e4c87fee8a05ee840fec0e23b2e037d58dba8dde8d10a9bc5191d34d00598941becbbe467f", + "0xadce6f7c30fd221f6b10a0413cc76435c4bb36c2d60bca821e5c67409fe9dbb2f4c36ef85eb3d734695e4be4827e9fd3", + "0xa74f00e0f9b23aff7b2527ce69852f8906dab9d6abe62ecd497498ab21e57542e12af9918d4fd610bb09e10b0929c510", + "0xa593b6b0ef26448ce4eb3ab07e84238fc020b3cb10d542ff4b16d4e2be1bcde3797e45c9cf753b8dc3b0ffdb63984232", + "0xaed3913afccf1aa1ac0eb4980eb8426d0baccebd836d44651fd72af00d09fac488a870223c42aca3ceb39752070405ae", + "0xb2c44c66a5ea7fde626548ba4cef8c8710191343d3dadfd3bb653ce715c0e03056a5303a581d47dde66e70ea5a2d2779", + "0x8e5029b2ccf5128a12327b5103f7532db599846e422531869560ceaff392236434d87159f597937dbf4054f810c114f4", + "0x82beed1a2c4477e5eb39fc5b0e773b30cfec77ef2b1bf17eadaf60eb35b6d0dd9d8cf06315c48d3546badb3f21cd0cca", + "0x90077bd6cc0e4be5fff08e5d07a5a158d36cebd1d1363125bc4fae0866ffe825b26f933d4ee5427ba5cd0c33c19a7b06", + "0xa7ec0d8f079970e8e34f0ef3a53d3e0e45428ddcef9cc776ead5e542ef06f3c86981644f61c5a637e4faf001fb8c6b3e", + "0xae6d4add6d1a6f90b22792bc9d40723ee6850c27d0b97eefafd5b7fd98e424aa97868b5287cc41b4fbd7023bca6a322c", + "0x831aa917533d077da07c01417feaa1408846363ba2b8d22c6116bb858a95801547dd88b7d7fa1d2e3f0a02bdeb2e103d", + "0x96511b860b07c8a5ed773f36d4aa9d02fb5e7882753bf56303595bcb57e37ccc60288887eb83bef08c657ec261a021a2", + "0x921d2a3e7e9790f74068623de327443666b634c8443aba80120a45bba450df920b2374d96df1ce3fb1b06dd06f8cf6e3", + "0xaa74451d51fe82b4581ead8e506ec6cd881010f7e7dd51fc388eb9a557db5d3c6721f81c151d08ebd9c2591689fbc13e", + "0xa972bfbcf4033d5742d08716c927c442119bdae336bf5dff914523b285ccf31953da2733759aacaa246a9af9f698342c", + "0xad1fcd0cae0e76840194ce4150cb8a56ebed728ec9272035f52a799d480dfc85840a4d52d994a18b6edb31e79be6e8ad", + "0xa2c69fe1d36f235215432dad48d75887a44c99dfa0d78149acc74087da215a44bdb5f04e6eef88ff7eff80a5a7decc77", + "0xa94ab2af2b6ee1bc6e0d4e689ca45380d9fbd3c5a65b9bd249d266a4d4c07bf5d5f7ef2ae6000623aee64027892bf8fe", + "0x881ec1fc514e926cdc66480ac59e139148ff8a2a7895a49f0dff45910c90cdda97b66441a25f357d6dd2471cddd99bb3", + "0x884e6d3b894a914c8cef946a76d5a0c8351843b2bffa2d1e56c6b5b99c84104381dd1320c451d551c0b966f4086e60f9", + "0x817c6c10ce2677b9fc5223500322e2b880583254d0bb0d247d728f8716f5e05c9ff39f135854342a1afecd9fbdcf7c46", + "0xaaf4a9cb686a14619aa1fc1ac285dd3843ac3dd99f2b2331c711ec87b03491c02f49101046f3c5c538dc9f8dba2a0ac2", + "0x97ecea5ce53ca720b5d845227ae61d70269a2f53540089305c86af35f0898bfd57356e74a8a5e083fa6e1ea70080bd31", + "0xa22d811e1a20a75feac0157c418a4bfe745ccb5d29466ffa854dca03e395b6c3504a734341746b2846d76583a780b32e", + "0x940cbaa0d2b2db94ae96b6b9cf2deefbfd059e3e5745de9aec4a25f0991b9721e5cd37ef71c631575d1a0c280b01cd5b", + "0xae33cb4951191258a11044682de861bf8d92d90ce751b354932dd9f3913f542b6a0f8a4dc228b3cd9244ac32c4582832", + "0xa580df5e58c4274fe0f52ac2da1837e32f5c9db92be16c170187db4c358f43e5cfdda7c5911dcc79d77a5764e32325f5", + "0x81798178cb9d8affa424f8d3be67576ba94d108a28ccc01d330c51d5a63ca45bb8ca63a2f569b5c5fe1303cecd2d777f", + "0x89975b91b94c25c9c3660e4af4047a8bacf964783010820dbc91ff8281509379cb3b24c25080d5a01174dd9a049118d5", + "0xa7327fcb3710ed3273b048650bde40a32732ef40a7e58cf7f2f400979c177944c8bc54117ba6c80d5d4260801dddab79", + "0x92b475dc8cb5be4b90c482f122a51bcb3b6c70593817e7e2459c28ea54a7845c50272af38119406eaadb9bcb993368d0", + "0x9645173e9ecefc4f2eae8363504f7c0b81d85f8949a9f8a6c01f2d49e0a0764f4eacecf3e94016dd407fc14494fce9f9", + "0x9215fd8983d7de6ae94d35e6698226fc1454977ae58d42d294be9aad13ac821562ad37d5e7ee5cdfe6e87031d45cd197", + "0x810360a1c9b88a9e36f520ab5a1eb8bed93f52deefbe1312a69225c0a08edb10f87cc43b794aced9c74220cefcc57e7d", + "0xad7e810efd61ed4684aeda9ed8bb02fb9ae4b4b63fda8217d37012b94ff1b91c0087043bfa4e376f961fff030c729f3b", + "0x8b07c95c6a06db8738d10bb03ec11b89375c08e77f0cab7e672ce70b2685667ca19c7e1c8b092821d31108ea18dfd4c7", + "0x968825d025ded899ff7c57245250535c732836f7565eab1ae23ee7e513201d413c16e1ba3f5166e7ac6cf74de8ceef4f", + "0x908243370c5788200703ade8164943ad5f8c458219186432e74dbc9904a701ea307fd9b94976c866e6c58595fd891c4b", + "0x959969d16680bc535cdc6339e6186355d0d6c0d53d7bbfb411641b9bf4b770fd5f575beef5deec5c4fa4d192d455c350", + "0xad177f4f826a961adeac76da40e2d930748effff731756c797eddc4e5aa23c91f070fb69b19221748130b0961e68a6bb", + "0x82f8462bcc25448ef7e0739425378e9bb8a05e283ce54aae9dbebaf7a3469f57833c9171672ad43a79778366c72a5e37", + "0xa28fb275b1845706c2814d9638573e9bc32ff552ebaed761fe96fdbce70395891ca41c400ae438369264e31a2713b15f", + "0x8a9c613996b5e51dadb587a787253d6081ea446bf5c71096980bf6bd3c4b69905062a8e8a3792de2d2ece3b177a71089", + "0x8d5aefef9f60cb27c1db2c649221204dda48bb9bf8bf48f965741da051340e8e4cab88b9d15c69f3f84f4c854709f48a", + "0x93ebf2ca6ad85ab6deace6de1a458706285b31877b1b4d7dcb9d126b63047efaf8c06d580115ec9acee30c8a7212fa55", + "0xb3ee46ce189956ca298057fa8223b7fd1128cf52f39159a58bca03c71dd25161ac13f1472301f72aef3e1993fe1ab269", + "0xa24d7a8d066504fc3f5027ccb13120e2f22896860e02c45b5eba1dbd512d6a17c28f39155ea581619f9d33db43a96f92", + "0xae9ceacbfe12137db2c1a271e1b34b8f92e4816bad1b3b9b6feecc34df0f8b3b0f7ed0133acdf59c537d43d33fc8d429", + "0x83967e69bf2b361f86361bd705dce0e1ad26df06da6c52b48176fe8dfcbeb03c462c1a4c9e649eff8c654b18c876fdef", + "0x9148e6b814a7d779c19c31e33a068e97b597de1f8100513db3c581190513edc4d544801ce3dd2cf6b19e0cd6daedd28a", + "0x94ccdafc84920d320ed22de1e754adea072935d3c5f8c2d1378ebe53d140ea29853f056fb3fb1e375846061a038cc9bc", + "0xafb43348498c38b0fa5f971b8cdd3a62c844f0eb52bc33daf2f67850af0880fce84ecfb96201b308d9e6168a0d443ae3", + "0x86d5736520a83538d4cd058cc4b4e84213ed00ebd6e7af79ae787adc17a92ba5359e28ba6c91936d967b4b28d24c3070", + "0xb5210c1ff212c5b1e9ef9126e08fe120a41e386bb12c22266f7538c6d69c7fd8774f11c02b81fd4e88f9137b020801fe", + "0xb78cfd19f94d24e529d0f52e18ce6185cb238edc6bd43086270fd51dd99f664f43dd4c7d2fe506762fbd859028e13fcf", + "0xa6e7220598c554abdcc3fdc587b988617b32c7bb0f82c06205467dbedb58276cc07cae317a190f19d19078773f4c2bbb", + "0xb88862809487ee430368dccd85a5d72fa4d163ca4aad15c78800e19c1a95be2192719801e315d86cff7795e0544a77e4", + "0x87ecb13a03921296f8c42ceb252d04716f10e09c93962239fcaa0a7fef93f19ab3f2680bc406170108bc583e9ff2e721", + "0xa810cd473832b6581c36ec4cb403f2849357ba2d0b54df98ef3004b8a530c078032922a81d40158f5fb0043d56477f6e", + "0xa247b45dd85ca7fbb718b328f30a03f03c84aef2c583fbdc9fcc9eb8b52b34529e8c8f535505c10598b1b4dac3d7c647", + "0x96ee0b91313c68bac4aa9e065ce9e1d77e51ca4cff31d6a438718c58264dee87674bd97fc5c6b8008be709521e4fd008", + "0x837567ad073e42266951a9a54750919280a2ac835a73c158407c3a2b1904cf0d17b7195a393c71a18ad029cbd9cf79ee", + "0xa6a469c44b67ebf02196213e7a63ad0423aab9a6e54acc6fcbdbb915bc043586993454dc3cd9e4be8f27d67c1050879b", + "0x8712d380a843b08b7b294f1f06e2f11f4ad6bcc655fdde86a4d8bc739c23916f6fad2b902fe47d6212f03607907e9f0e", + "0x920adfb644b534789943cdae1bdd6e42828dda1696a440af2f54e6b97f4f97470a1c6ea9fa6a2705d8f04911d055acd1", + "0xa161c73adf584a0061e963b062f59d90faac65c9b3a936b837a10d817f02fcabfa748824607be45a183dd40f991fe83f", + "0x874f4ecd408c76e625ea50bc59c53c2d930ee25baf4b4eca2440bfbffb3b8bc294db579caa7c68629f4d9ec24187c1ba", + "0x8bff18087f112be7f4aa654e85c71fef70eee8ae480f61d0383ff6f5ab1a0508f966183bb3fc4d6f29cb7ca234aa50d3", + "0xb03b46a3ca3bc743a173cbc008f92ab1aedd7466b35a6d1ca11e894b9482ea9dc75f8d6db2ddd1add99bfbe7657518b7", + "0x8b4f3691403c3a8ad9e097f02d130769628feddfa8c2b3dfe8cff64e2bed7d6e5d192c1e2ba0ac348b8585e94acd5fa1", + "0xa0d9ca4a212301f97591bf65d5ef2b2664766b427c9dd342e23cb468426e6a56be66b1cb41fea1889ac5d11a8e3c50a5", + "0x8c93ed74188ca23b3df29e5396974b9cc135c91fdefdea6c0df694c8116410e93509559af55533a3776ac11b228d69b1", + "0x82dd331fb3f9e344ebdeeb557769b86a2cc8cc38f6c298d7572a33aea87c261afa9dbd898989139b9fc16bc1e880a099", + "0xa65faedf326bcfd8ef98a51410c78b021d39206704e8291cd1f09e096a66b9b0486be65ff185ca224c45918ac337ddeb", + "0xa188b37d363ac072a766fd5d6fa27df07363feff1342217b19e3c37385e42ffde55e4be8355aceaa2f267b6d66b4ac41", + "0x810fa3ba3e96d843e3bafd3f2995727f223d3567c8ba77d684c993ba1773c66551eb5009897c51b3fe9b37196984f5ec", + "0x87631537541852da323b4353af45a164f68b304d24c01183bf271782e11687f3fcf528394e1566c2a26cb527b3148e64", + "0xb721cb2b37b3c477a48e3cc0044167d51ff568a5fd2fb606e5aec7a267000f1ddc07d3db919926ae12761a8e017c767c", + "0x904dfad4ba2cc1f6e60d1b708438a70b1743b400164cd981f13c064b8328d5973987d4fb9cf894068f29d3deaf624dfb", + "0xa70491538893552c20939fae6be2f07bfa84d97e2534a6bbcc0f1729246b831103505e9f60e97a8fa7d2e6c1c2384579", + "0x8726cf1b26b41f443ff7485adcfddc39ace2e62f4d65dd0bb927d933e262b66f1a9b367ded5fbdd6f3b0932553ac1735", + "0xae8a11cfdf7aa54c08f80cb645e3339187ab3886babe9fae5239ba507bb3dd1c0d161ca474a2df081dcd3d63e8fe445e", + "0x92328719e97ce60e56110f30a00ac5d9c7a2baaf5f8d22355d53c1c77941e3a1fec7d1405e6fbf8959665fe2ba7a8cad", + "0x8d9d6255b65798d0018a8cccb0b6343efd41dc14ff2058d3eed9451ceaad681e4a0fa6af67b0a04318aa628024e5553d", + "0xb70209090055459296006742d946a513f0cba6d83a05249ee8e7a51052b29c0ca9722dc4af5f9816a1b7938a5dac7f79", + "0xaab7b766b9bf91786dfa801fcef6d575dc6f12b77ecc662eb4498f0312e54d0de9ea820e61508fc8aeee5ab5db529349", + "0xa8104b462337748b7f086a135d0c3f87f8e51b7165ca6611264b8fb639d9a2f519926cb311fa2055b5fadf03da70c678", + "0xb0d2460747d5d8b30fc6c6bd0a87cb343ddb05d90a51b465e8f67d499cfc5e3a9e365da05ae233bbee792cdf90ec67d5", + "0xaa55f5bf3815266b4a149f85ed18e451c93de9163575e3ec75dd610381cc0805bb0a4d7c4af5b1f94d10231255436d2c", + "0x8d4c6a1944ff94426151909eb5b99cfd92167b967dabe2bf3aa66bb3c26c449c13097de881b2cfc1bf052862c1ef7b03", + "0x8862296162451b9b6b77f03bf32e6df71325e8d7485cf3335d66fd48b74c2a8334c241db8263033724f26269ad95b395", + "0x901aa96deb26cda5d9321190ae6624d357a41729d72ef1abfd71bebf6139af6d690798daba53b7bc5923462115ff748a", + "0x96c195ec4992728a1eb38cdde42d89a7bce150db43adbc9e61e279ea839e538deec71326b618dd39c50d589f78fc0614", + "0xb6ff8b8aa0837b99a1a8b46fb37f20ad4aecc6a98381b1308697829a59b8442ffc748637a88cb30c9b1f0f28a926c4f6", + "0x8d807e3dca9e7bef277db1d2cfb372408dd587364e8048b304eff00eacde2c723bfc84be9b98553f83cba5c7b3cba248", + "0x8800c96adb0195c4fc5b24511450dee503c32bf47044f5e2e25bd6651f514d79a2dd9b01cd8c09f3c9d3859338490f57", + "0x89fe366096097e38ec28dd1148887112efa5306cc0c3da09562aafa56f4eb000bf46ff79bf0bdd270cbde6bf0e1c8957", + "0xaf409a90c2776e1e7e3760b2042507b8709e943424606e31e791d42f17873a2710797f5baaab4cc4a19998ef648556b0", + "0x8d761863c9b6edbd232d35ab853d944f5c950c2b643f84a1a1327ebb947290800710ff01dcfa26dc8e9828481240e8b1", + "0x90b95e9be1e55c463ed857c4e0617d6dc3674e99b6aa62ed33c8e79d6dfcf7d122f4f4cc2ee3e7c5a49170cb617d2e2e", + "0xb3ff381efefabc4db38cc4727432e0301949ae4f16f8d1dea9b4f4de611cf5a36d84290a0bef160dac4e1955e516b3b0", + "0xa8a84564b56a9003adcadb3565dc512239fc79572762cda7b5901a255bc82656bb9c01212ad33d6bef4fbbce18dacc87", + "0x90a081890364b222eef54bf0075417f85e340d2fec8b7375995f598aeb33f26b44143ebf56fca7d8b4ebb36b5747b0eb", + "0xade6ee49e1293224ddf2d8ab7f14bb5be6bc6284f60fd5b3a1e0cf147b73cff57cf19763b8a36c5083badc79c606b103", + "0xb2fa99806dd2fa3de09320b615a2570c416c9bcdb052e592b0aead748bbe407ec9475a3d932ae48b71c2627eb81986a6", + "0x91f3b7b73c8ccc9392542711c45fe6f236057e6efad587d661ad5cb4d6e88265f86b807bb1151736b1009ab74fd7acb4", + "0x8800e2a46af96696dfbdcbf2ca2918b3dcf28ad970170d2d1783b52b8d945a9167d052beeb55f56c126da7ffa7059baa", + "0x9862267a1311c385956b977c9aa08548c28d758d7ba82d43dbc3d0a0fd1b7a221d39e8399997fea9014ac509ff510ac4", + "0xb7d24f78886fd3e2d283e18d9ad5a25c1a904e7d9b9104bf47da469d74f34162e27e531380dbbe0a9d051e6ffd51d6e7", + "0xb0f445f9d143e28b9df36b0f2c052da87ee2ca374d9d0fbe2eff66ca6fe5fe0d2c1951b428d58f7314b7e74e45d445ea", + "0xb63fc4083eabb8437dafeb6a904120691dcb53ce2938b820bb553da0e1eecd476f72495aacb72600cf9cad18698fd3db", + "0xb9ffd8108eaebd582d665f8690fe8bb207fd85185e6dd9f0b355a09bac1bbff26e0fdb172bc0498df025414e88fe2eda", + "0x967ed453e1f1a4c5b7b6834cc9f75c13f6889edc0cc91dc445727e9f408487bbf05c337103f61397a10011dfbe25d61d", + "0x98ceb673aff36e1987d5521a3984a07079c3c6155974bb8b413e8ae1ce84095fe4f7862fba7aefa14753eb26f2a5805f", + "0x85f01d28603a8fdf6ce6a50cb5c44f8a36b95b91302e3f4cd95c108ce8f4d212e73aec1b8d936520d9226802a2bd9136", + "0x88118e9703200ca07910345fbb789e7a8f92bd80bbc79f0a9e040e8767d33df39f6eded403a9b636eabf9101e588482a", + "0x90833a51eef1b10ed74e8f9bbd6197e29c5292e469c854eed10b0da663e2bceb92539710b1858bbb21887bd538d28d89", + "0xb513b905ec19191167c6193067b5cfdf5a3d3828375360df1c7e2ced5815437dfd37f0c4c8f009d7fb29ff3c8793f560", + "0xb1b6d405d2d18f9554b8a358cc7e2d78a3b34269737d561992c8de83392ac9a2857be4bf15de5a6c74e0c9d0f31f393c", + "0xb828bd3e452b797323b798186607849f85d1fb20c616833c0619360dfd6b3e3aa000fd09dafe4b62d74abc41072ff1a9", + "0x8efde67d0cca56bb2c464731879c9ac46a52e75bac702a63200a5e192b4f81c641f855ca6747752b84fe469cb7113b6c", + "0xb2762ba1c89ac3c9a983c242e4d1c2610ff0528585ed5c0dfc8a2c0253551142af9b59f43158e8915a1da7cc26b9df67", + "0x8a3f1157fb820d1497ef6b25cd70b7e16bb8b961b0063ad340d82a79ee76eb2359ca9e15e6d42987ed7f154f5eeaa2da", + "0xa75e29f29d38f09c879f971c11beb5368affa084313474a5ecafa2896180b9e47ea1995c2733ec46f421e395a1d9cffe", + "0x8e8c3dd3e7196ef0b4996b531ec79e4a1f211db5d5635e48ceb80ff7568b2ff587e845f97ee703bb23a60945ad64314a", + "0x8e7f32f4a3e3c584af5e3d406924a0aa34024c42eca74ef6cc2a358fd3c9efaf25f1c03aa1e66bb94b023a2ee2a1cace", + "0xab7dce05d59c10a84feb524fcb62478906b3fa045135b23afbede3bb32e0c678d8ebe59feabccb5c8f3550ea76cae44b", + "0xb38bb4b44d827f6fd3bd34e31f9186c59e312dbfadd4a7a88e588da10146a78b1f8716c91ad8b806beb8da65cab80c4c", + "0x9490ce9442bbbd05438c7f5c4dea789f74a7e92b1886a730544b55ba377840740a3ae4f2f146ee73f47c9278b0e233bc", + "0x83c003fab22a7178eed1a668e0f65d4fe38ef3900044e9ec63070c23f2827d36a1e73e5c2b883ec6a2afe2450171b3b3", + "0x9982f02405978ddc4fca9063ebbdb152f524c84e79398955e66fe51bc7c1660ec1afc3a86ec49f58d7b7dde03505731c", + "0xab337bd83ccdd2322088ffa8d005f450ced6b35790f37ab4534313315ee84312adc25e99cce052863a8bedee991729ed", + "0x8312ce4bec94366d88f16127a17419ef64285cd5bf9e5eda010319b48085966ed1252ed2f5a9fd3e0259b91bb65f1827", + "0xa60d5a6327c4041b0c00a1aa2f0af056520f83c9ce9d9ccd03a0bd4d9e6a1511f26a422ea86bd858a1f77438adf07e6c", + "0xb84a0a0b030bdad83cf5202aa9afe58c9820e52483ab41f835f8c582c129ee3f34aa096d11c1cd922eda02ea1196a882", + "0x8077d105317f4a8a8f1aadeb05e0722bb55f11abcb490c36c0904401107eb3372875b0ac233144829e734f0c538d8c1d", + "0x9202503bd29a6ec198823a1e4e098f9cfe359ed51eb5174d1ca41368821bfeebcbd49debfd02952c41359d1c7c06d2b1", + "0xabc28c155e09365cb77ffead8dc8f602335ef93b2f44e4ef767ce8fc8ef9dd707400f3a722e92776c2e0b40192c06354", + "0xb0f6d1442533ca45c9399e0a63a11f85ff288d242cea6cb3b68c02e77bd7d158047cae2d25b3bcd9606f8f66d9b32855", + "0xb01c3d56a0db84dc94575f4b6ee2de4beca3230e86bed63e2066beb22768b0a8efb08ebaf8ac3dedb5fe46708b084807", + "0x8c8634b0432159f66feaabb165842d1c8ac378f79565b1b90c381aa8450eb4231c3dad11ec9317b9fc2b155c3a771e32", + "0x8e67f623d69ecd430c9ee0888520b6038f13a2b6140525b056dc0951f0cfed2822e62cf11d952a483107c5c5acac4826", + "0x9590bb1cba816dd6acd5ac5fba5142c0a19d53573e422c74005e0bcf34993a8138c83124cad35a3df65879dba6134edd", + "0x801cd96cde0749021a253027118d3ea135f3fcdbe895db08a6c145641f95ebd368dd6a1568d995e1d0084146aebe224a", + "0x848b5d196427f6fc1f762ee3d36e832b64a76ec1033cfedc8b985dea93932a7892b8ef1035c653fb9dcd9ab2d9a44ac8", + "0xa1017eb83d5c4e2477e7bd2241b2b98c4951a3b391081cae7d75965cadc1acaec755cf350f1f3d29741b0828e36fedea", + "0x8d6d2785e30f3c29aad17bd677914a752f831e96d46caf54446d967cb2432be2c849e26f0d193a60bee161ea5c6fe90a", + "0x935c0ba4290d4595428e034b5c8001cbd400040d89ab00861108e8f8f4af4258e41f34a7e6b93b04bc253d3b9ffc13bf", + "0xaac02257146246998477921cef2e9892228590d323b839f3e64ea893b991b463bc2f47e1e5092ddb47e70b2f5bce7622", + "0xb921fde9412970a5d4c9a908ae8ce65861d06c7679af577cf0ad0d5344c421166986bee471fd6a6cecb7d591f06ec985", + "0x8ef4c37487b139d6756003060600bb6ebac7ea810b9c4364fc978e842f13ac196d1264fbe5af60d76ff6d9203d8e7d3f", + "0x94b65e14022b5cf6a9b95f94be5ace2711957c96f4211c3f7bb36206bd39cfbd0ea82186cab5ad0577a23214a5c86e9e", + "0xa31c166d2a2ca1d5a75a5920fef7532681f62191a50d8555fdaa63ba4581c3391cc94a536fc09aac89f64eafceec3f90", + "0x919a8cc128de01e9e10f5d83b08b52293fdd41bde2b5ae070f3d95842d4a16e5331cf2f3d61c765570c8022403610fa4", + "0xb23d6f8331eef100152d60483cfa14232a85ee712c8538c9b6417a5a7c5b353c2ac401390c6c215cb101f5cee6b5f43e", + "0xab357160c08a18319510a571eafff154298ce1020de8e1dc6138a09fcb0fcbcdd8359f7e9386bda00b7b9cdea745ffdc", + "0xab55079aea34afa5c0bd1124b9cdfe01f325b402fdfa017301bf87812eaa811ea5798c3aaf818074d420d1c782b10ada", + "0xade616010dc5009e7fc4f8d8b00dc716686a5fa0a7816ad9e503e15839d3b909b69d9dd929b7575376434ffec0d2bea8", + "0x863997b97ed46898a8a014599508fa3079f414b1f4a0c4fdc6d74ae8b444afa350f327f8bfc2a85d27f9e2d049c50135", + "0x8d602ff596334efd4925549ed95f2aa762b0629189f0df6dbb162581657cf3ea6863cd2287b4d9c8ad52813d87fcd235", + "0xb70f68c596dcdeed92ad5c6c348578b26862a51eb5364237b1221e840c47a8702f0fbc56eb520a22c0eed99795d3903e", + "0x9628088f8e0853cefadee305a8bf47fa990c50fa96a82511bbe6e5dc81ef4b794e7918a109070f92fc8384d77ace226f", + "0x97e26a46e068b605ce96007197ecd943c9a23881862f4797a12a3e96ba2b8d07806ad9e2a0646796b1889c6b7d75188c", + "0xb1edf467c068cc163e2d6413cc22b16751e78b3312fe47b7ea82b08a1206d64415b2c8f2a677fa89171e82cc49797150", + "0xa44d15ef18745b251429703e3cab188420e2d974de07251501799b016617f9630643fcd06f895634d8ecdd579e1bf000", + "0xabd126df3917ba48c618ee4dbdf87df506193462f792874439043fa1b844466f6f4e0ff2e42516e63b5b23c0892b2695", + "0xa2a67f57c4aa3c2aa1eeddbfd5009a89c26c2ce8fa3c96a64626aba19514beb125f27df8559506f737de3eae0f1fc18f", + "0xa633e0132197e6038197304b296ab171f1d8e0d0f34dcf66fe9146ac385b0239232a8470b9205a4802ab432389f4836d", + "0xa914b3a28509a906c3821463b936455d58ff45dcbe158922f9efb2037f2eb0ce8e92532d29b5d5a3fcd0d23fa773f272", + "0xa0e1412ce4505daf1a2e59ce4f0fc0e0023e335b50d2b204422f57cd65744cc7a8ed35d5ef131a42c70b27111d3115b7", + "0xa2339e2f2b6072e88816224fdd612c04d64e7967a492b9f8829db15367f565745325d361fd0607b0def1be384d010d9e", + "0xa7309fc41203cb99382e8193a1dcf03ac190a7ce04835304eb7e341d78634e83ea47cb15b885601956736d04cdfcaa01", + "0x81f3ccd6c7f5b39e4e873365f8c37b214e8ab122d04a606fbb7339dc3298c427e922ec7418002561d4106505b5c399ee", + "0x92c121cf914ca549130e352eb297872a63200e99b148d88fbc9506ad882bec9d0203d65f280fb5b0ba92e336b7f932e8", + "0xa4b330cf3f064f5b131578626ad7043ce2a433b6f175feb0b52d36134a454ca219373fd30d5e5796410e005b69082e47", + "0x86fe5774112403ad83f9c55d58317eeb17ad8e1176d9f2f69c2afb7ed83bc718ed4e0245ceab4b377f5f062dcd4c00e7", + "0x809d152a7e2654c7fd175b57f7928365a521be92e1ed06c05188a95864ddb25f7cab4c71db7d61bbf4cae46f3a1d96ce", + "0xb82d663e55c2a5ada7e169e9b1a87bc1c0177baf1ec1c96559b4cb1c5214ce1ddf2ab8d345014cab6402f3774235cf5a", + "0x86580af86df1bd2c385adb8f9a079e925981b7184db66fc5fe5b14cddb82e7d836b06eaeef14924ac529487b23dae111", + "0xb5f5f4c5c94944ecc804df6ab8687d64e27d988cbfeae1ba7394e0f6adbf778c5881ead7cd8082dd7d68542b9bb4ecd5", + "0xa6016916146c2685c46e8fdd24186394e2d5496e77e08c0c6a709d4cd7dfa97f1efcef94922b89196819076a91ad37b5", + "0xb778e7367ded3b6eab53d5fc257f7a87e8faf74a593900f2f517220add2125be3f6142022660d8181df8d164ad9441ce", + "0x8581b2d36abe6f553add4d24be761bec1b8efaa2929519114346615380b3c55b59e6ad86990e312f7e234d0203bdf59b", + "0x9917e74fd45c3f71a829ff5498a7f6b5599b48c098dda2339bf04352bfc7f368ccf1a407f5835901240e76452ae807d7", + "0xafd196ce6f9335069138fd2e3d133134da253978b4ce373152c0f26affe77a336505787594022e610f8feb722f7cc1fb", + "0xa477491a1562e329764645e8f24d8e228e5ef28c9f74c6b5b3abc4b6a562c15ffb0f680d372aed04d9e1bf944dece7be", + "0x9767440d58c57d3077319d3a330e5322b9ba16981ec74a5a14d53462eab59ae7fd2b14025bfc63b268862094acb444e6", + "0x80986d921be3513ef69264423f351a61cb48390c1be8673aee0f089076086aaebea7ebe268fd0aa7182695606116f679", + "0xa9554c5c921c07b450ee04e34ec58e054ac1541b26ce2ce5a393367a97348ba0089f53db6660ad76b60278b66fd12e3e", + "0x95097e7d2999b3e84bf052c775581cf361325325f4a50192521d8f4693c830bed667d88f482dc1e3f833aa2bd22d2cbf", + "0x9014c91d0f85aefd28436b5228c12f6353c055a9326c7efbf5e071e089e2ee7c070fcbc84c5fafc336cbb8fa6fec1ca1", + "0x90f57ba36ee1066b55d37384942d8b57ae00f3cf9a3c1d6a3dfee1d1af42d4b5fa9baeb0cd7e46687d1d6d090ddb931d", + "0x8e4b1db12fd760a17214c9e47f1fce6e43c0dbb4589a827a13ac61aaae93759345697bb438a00edab92e0b7b62414683", + "0x8022a959a513cdc0e9c705e0fc04eafd05ff37c867ae0f31f6d01cddd5df86138a426cab2ff0ac8ff03a62e20f7e8f51", + "0x914e9a38829834c7360443b8ed86137e6f936389488eccf05b4b4db7c9425611705076ecb3f27105d24b85c852be7511", + "0x957fb10783e2bd0db1ba66b18e794df710bc3b2b05776be146fa5863c15b1ebdd39747b1a95d9564e1772cdfc4f37b8a", + "0xb6307028444daed8ed785ac9d0de76bc3fe23ff2cc7e48102553613bbfb5afe0ebe45e4212a27021c8eb870721e62a1f", + "0x8f76143597777d940b15a01b39c5e1b045464d146d9a30a6abe8b5d3907250e6c7f858ff2308f8591e8b0a7b3f3c568a", + "0x96163138ac0ce5fd00ae9a289648fd9300a0ca0f63a88481d703ecd281c06a52a3b5178e849e331f9c85ca4ba398f4cc", + "0xa63ef47c3e18245b0482596a09f488a716df3cbd0f9e5cfabed0d742843e65db8961c556f45f49762f3a6ac8b627b3ef", + "0x8cb595466552e7c4d42909f232d4063e0a663a8ef6f6c9b7ce3a0542b2459cde04e0e54c7623d404acb5b82775ac04f6", + "0xb47fe69960eb45f399368807cff16d941a5a4ebad1f5ec46e3dc8a2e4d598a7e6114d8f0ca791e9720fd786070524e2b", + "0x89eb5ff83eea9df490e5beca1a1fbbbbcf7184a37e2c8c91ede7a1e654c81e8cd41eceece4042ea7918a4f4646b67fd6", + "0xa84f5d155ed08b9054eecb15f689ba81e44589e6e7207a99790c598962837ca99ec12344105b16641ca91165672f7153", + "0xa6cc8f25c2d5b2d2f220ec359e6a37a52b95fa6af6e173c65e7cd55299eff4aa9e6d9e6f2769e6459313f1f2aecb0fab", + "0xafcde944411f017a9f7979755294981e941cc41f03df5e10522ef7c7505e5f1babdd67b3bf5258e8623150062eb41d9b", + "0x8fab39f39c0f40182fcd996ade2012643fe7731808afbc53f9b26900b4d4d1f0f5312d9d40b3df8baa4739970a49c732", + "0xae193af9726da0ebe7df1f9ee1c4846a5b2a7621403baf8e66c66b60f523e719c30c6b4f897bb14b27d3ff3da8392eeb", + "0x8ac5adb82d852eba255764029f42e6da92dcdd0e224d387d1ef94174038db9709ac558d90d7e7c57ad4ce7f89bbfc38c", + "0xa2066b3458fdf678ee487a55dd5bfb74fde03b54620cb0e25412a89ee28ad0d685e309a51e3e4694be2fa6f1593a344c", + "0x88d031745dd0ae07d61a15b594be5d4b2e2a29e715d081649ad63605e3404b0c3a5353f0fd9fad9c05c18e93ce674fa1", + "0x8283cfb0ef743a043f2b77ecaeba3005e2ca50435585b5dd24777ee6bce12332f85e21b446b536da38508807f0f07563", + "0xb376de22d5f6b0af0b59f7d9764561f4244cf8ffe22890ecd3dcf2ff1832130c9b821e068c9d8773136f4796721e5963", + "0xae3afc50c764f406353965363840bf28ee85e7064eb9d5f0bb3c31c64ab10f48c853e942ee2c9b51bae59651eaa08c2f", + "0x948b204d103917461a01a6c57a88f2d66b476eae5b00be20ec8c747650e864bc8a83aee0aff59cb7584b7a3387e0ee48", + "0x81ab098a082b07f896c5ffd1e4446cb7fb44804cbbf38d125208b233fc82f8ec9a6a8d8dd1c9a1162dc28ffeec0dde50", + "0xa149c6f1312821ced2969268789a3151bdda213451760b397139a028da609c4134ac083169feb0ee423a0acafd10eceb", + "0xb0ac9e27a5dadaf523010f730b28f0ebac01f460d3bbbe277dc9d44218abb5686f4fac89ae462682fef9edbba663520a", + "0x8d0e0073cca273daaaa61b6fc54bfe5a009bc3e20ae820f6c93ba77b19eca517d457e948a2de5e77678e4241807157cb", + "0xad61d3a2edf7c7533a04964b97499503fd8374ca64286dba80465e68fe932e96749b476f458c6fc57cb1a7ca85764d11", + "0x90eb5e121ae46bc01a30881eaa556f46bd8457a4e80787cf634aab355082de34ac57d7f497446468225f7721e68e2a47", + "0x8cdac557de7c42d1f3780e33dec1b81889f6352279be81c65566cdd4952d4c15d79e656cbd46035ab090b385e90245ef", + "0x82b67e61b88b84f4f4d4f65df37b3e3dcf8ec91ea1b5c008fdccd52da643adbe6468a1cfdb999e87d195afe2883a3b46", + "0x8503b467e8f5d6048a4a9b78496c58493a462852cab54a70594ae3fd064cfd0deb4b8f336a262155d9fedcaa67d2f6fd", + "0x8db56c5ac763a57b6ce6832930c57117058e3e5a81532b7d19346346205e2ec614eb1a2ee836ef621de50a7bc9b7f040", + "0xad344699198f3c6e8c0a3470f92aaffc805b76266734414c298e10b5b3797ca53578de7ccb2f458f5e0448203f55282b", + "0x80602032c43c9e2a09154cc88b83238343b7a139f566d64cb482d87436b288a98f1ea244fd3bff8da3c398686a900c14", + "0xa6385bd50ecd548cfb37174cdbb89e10025b5cadaf3cff164c95d7aef5a33e3d6a9bf0c681b9e11db9ef54ebeee2a0c1", + "0xabf2d95f4aa34b0581eb9257a0cc8462b2213941a5deb8ba014283293e8b36613951b61261cc67bbd09526a54cbbff76", + "0xa3d5de52f48df72c289ff713e445991f142390798cd42bd9d9dbefaee4af4f5faf09042d126b975cf6b98711c3072553", + "0x8e627302ff3d686cff8872a1b7c2a57b35f45bf2fc9aa42b049d8b4d6996a662b8e7cbac6597f0cb79b0cc4e29fbf133", + "0x8510702e101b39a1efbf4e504e6123540c34b5689645e70d0bac1ecc1baf47d86c05cef6c4317a4e99b4edaeb53f2d00", + "0xaa173f0ecbcc6088f878f8726d317748c81ebf501bba461f163b55d66099b191ec7c55f7702f351a9c8eb42cfa3280e2", + "0xb560a697eafab695bcef1416648a0a664a71e311ecbe5823ae903bd0ed2057b9d7574b9a86d3fe22aa3e6ddce38ea513", + "0x8df6304a3d9cf40100f3f687575419c998cd77e5cc27d579cf4f8e98642de3609af384a0337d145dd7c5635172d26a71", + "0x8105c7f3e4d30a29151849673853b457c1885c186c132d0a98e63096c3774bc9deb956cf957367e633d0913680bda307", + "0x95373fc22c0917c3c2044ac688c4f29a63ed858a45c0d6d2d0fe97afd6f532dcb648670594290c1c89010ecc69259bef", + "0x8c2fae9bcadab341f49b55230310df93cac46be42d4caa0d42e45104148a91e527af1b4209c0d972448162aed28fab64", + "0xb05a77baab70683f76209626eaefdda2d36a0b66c780a20142d23c55bd479ddd4ad95b24579384b6cf62c8eb4c92d021", + "0x8e6bc6a7ea2755b4aaa19c1c1dee93811fcde514f03485fdc3252f0ab7f032c315614f6336e57cea25dcfb8fb6084eeb", + "0xb656a27d06aade55eadae2ad2a1059198918ea6cc3fd22c0ed881294d34d5ac7b5e4700cc24350e27d76646263b223aa", + "0xa296469f24f6f56da92d713afcd4dd606e7da1f79dc4e434593c53695847eefc81c7c446486c4b3b8c8d00c90c166f14", + "0x87a326f57713ac2c9dffeb3af44b9f3c613a8f952676fc46343299122b47ee0f8d792abaa4b5db6451ced5dd153aabd0", + "0xb689e554ba9293b9c1f6344a3c8fcb6951d9f9eac4a2e2df13de021aade7c186be27500e81388e5b8bcab4c80f220a31", + "0x87ae0aa0aa48eac53d1ca5a7b93917de12db9e40ceabf8fdb40884ae771cfdf095411deef7c9f821af0b7070454a2608", + "0xa71ffa7eae8ace94e6c3581d4cb2ad25d48cbd27edc9ec45baa2c8eb932a4773c3272b2ffaf077b40f76942a1f3af7f2", + "0x94c218c91a9b73da6b7a495b3728f3028df8ad9133312fc0c03e8c5253b7ccb83ed14688fd4602e2fd41f29a0bc698bd", + "0xae1e77b90ca33728af07a4c03fb2ef71cd92e2618e7bf8ed4d785ce90097fc4866c29999eb84a6cf1819d75285a03af2", + "0xb7a5945b277dab9993cf761e838b0ac6eaa903d7111fca79f9fde3d4285af7a89bf6634a71909d095d7619d913972c9c", + "0x8c43b37be02f39b22029b20aca31bff661abce4471dca88aa3bddefd9c92304a088b2dfc8c4795acc301ca3160656af2", + "0xb32e5d0fba024554bd5fe8a793ebe8003335ddd7f585876df2048dcf759a01285fecb53daae4950ba57f3a282a4d8495", + "0x85ea7fd5e10c7b659df5289b2978b2c89e244f269e061b9a15fcab7983fc1962b63546e82d5731c97ec74b6804be63ef", + "0x96b89f39181141a7e32986ac02d7586088c5a9662cec39843f397f3178714d02f929af70630c12cbaba0268f8ba2d4fa", + "0x929ab1a2a009b1eb37a2817c89696a06426529ebe3f306c586ab717bd34c35a53eca2d7ddcdef36117872db660024af9", + "0xa696dccf439e9ca41511e16bf3042d7ec0e2f86c099e4fc8879d778a5ea79e33aa7ce96b23dc4332b7ba26859d8e674d", + "0xa8fe69a678f9a194b8670a41e941f0460f6e2dbc60470ab4d6ae2679cc9c6ce2c3a39df2303bee486dbfde6844e6b31a", + "0x95f58f5c82de2f2a927ca99bf63c9fc02e9030c7e46d0bf6b67fe83a448d0ae1c99541b59caf0e1ccab8326231af09a5", + "0xa57badb2c56ca2c45953bd569caf22968f76ed46b9bac389163d6fe22a715c83d5e94ae8759b0e6e8c2f27bff7748f3f", + "0x868726fd49963b24acb5333364dffea147e98f33aa19c7919dc9aca0fd26661cfaded74ede7418a5fadbe7f5ae67b67b", + "0xa8d8550dcc64d9f1dd7bcdab236c4122f2b65ea404bb483256d712c7518f08bb028ff8801f1da6aed6cbfc5c7062e33b", + "0x97e25a87dae23155809476232178538d4bc05d4ff0882916eb29ae515f2a62bfce73083466cc0010ca956aca200aeacc", + "0xb4ea26be3f4bd04aa82d7c4b0913b97bcdf5e88b76c57eb1a336cbd0a3eb29de751e1bc47c0e8258adec3f17426d0c71", + "0x99ee555a4d9b3cf2eb420b2af8e3bc99046880536116d0ce7193464ac40685ef14e0e3c442f604e32f8338cb0ef92558", + "0x8c64efa1da63cd08f319103c5c7a761221080e74227bbc58b8fb35d08aa42078810d7af3e60446cbaff160c319535648", + "0x8d9fd88040076c28420e3395cbdfea402e4077a3808a97b7939d49ecbcf1418fe50a0460e1c1b22ac3f6e7771d65169a", + "0xae3c19882d7a9875d439265a0c7003c8d410367627d21575a864b9cb4918de7dbdb58a364af40c5e045f3df40f95d337", + "0xb4f7bfacab7b2cafe393f1322d6dcc6f21ffe69cd31edc8db18c06f1a2b512c27bd0618091fd207ba8df1808e9d45914", + "0x94f134acd0007c623fb7934bcb65ef853313eb283a889a3ffa79a37a5c8f3665f3d5b4876bc66223610c21dc9b919d37", + "0xaa15f74051171daacdc1f1093d3f8e2d13da2833624b80a934afec86fc02208b8f55d24b7d66076444e7633f46375c6a", + "0xa32d6bb47ef9c836d9d2371807bafbbbbb1ae719530c19d6013f1d1f813c49a60e4fa51d83693586cba3a840b23c0404", + "0xb61b3599145ea8680011aa2366dc511a358b7d67672d5b0c5be6db03b0efb8ca5a8294cf220ea7409621f1664e00e631", + "0x859cafc3ee90b7ececa1ed8ef2b2fc17567126ff10ca712d5ffdd16aa411a5a7d8d32c9cab1fbf63e87dce1c6e2f5f53", + "0xa2fef1b0b2874387010e9ae425f3a9676d01a095d017493648bcdf3b31304b087ccddb5cf76abc4e1548b88919663b6b", + "0x939e18c73befc1ba2932a65ede34c70e4b91e74cc2129d57ace43ed2b3af2a9cc22a40fbf50d79a63681b6d98852866d", + "0xb3b4259d37b1b14aee5b676c9a0dd2d7f679ab95c120cb5f09f9fbf10b0a920cb613655ddb7b9e2ba5af4a221f31303c", + "0x997255fe51aaca6e5a9cb3359bcbf25b2bb9e30649bbd53a8a7c556df07e441c4e27328b38934f09c09d9500b5fabf66", + "0xabb91be2a2d860fd662ed4f1c6edeefd4da8dc10e79251cf87f06029906e7f0be9b486462718f0525d5e049472692cb7", + "0xb2398e593bf340a15f7801e1d1fbda69d93f2a32a889ec7c6ae5e8a37567ac3e5227213c1392ee86cfb3b56ec2787839", + "0x8ddf10ccdd72922bed36829a36073a460c2118fc7a56ff9c1ac72581c799b15c762cb56cb78e3d118bb9f6a7e56cb25e", + "0x93e6bc0a4708d16387cacd44cf59363b994dc67d7ada7b6d6dbd831c606d975247541b42b2a309f814c1bfe205681fc6", + "0xb93fc35c05998cffda2978e12e75812122831523041f10d52f810d34ff71944979054b04de0117e81ddf5b0b4b3e13c0", + "0x92221631c44d60d68c6bc7b287509f37ee44cbe5fdb6935cee36b58b17c7325098f98f7910d2c3ca5dc885ad1d6dabc7", + "0xa230124424a57fad3b1671f404a94d7c05f4c67b7a8fbacfccea28887b78d7c1ed40b92a58348e4d61328891cd2f6cee", + "0xa6a230edb8518a0f49d7231bc3e0bceb5c2ac427f045819f8584ba6f3ae3d63ed107a9a62aad543d7e1fcf1f20605706", + "0x845be1fe94223c7f1f97d74c49d682472585d8f772762baad8a9d341d9c3015534cc83d102113c51a9dea2ab10d8d27b", + "0xb44262515e34f2db597c8128c7614d33858740310a49cdbdf9c8677c5343884b42c1292759f55b8b4abc4c86e4728033", + "0x805592e4a3cd07c1844bc23783408310accfdb769cca882ad4d07d608e590a288b7370c2cb327f5336e72b7083a0e30f", + "0x95153e8b1140df34ee864f4ca601cb873cdd3efa634af0c4093fbaede36f51b55571ab271e6a133020cd34db8411241f", + "0x82878c1285cfa5ea1d32175c9401f3cc99f6bb224d622d3fd98cc7b0a27372f13f7ab463ce3a33ec96f9be38dbe2dfe3", + "0xb7588748f55783077c27fc47d33e20c5c0f5a53fc0ac10194c003aa09b9f055d08ec971effa4b7f760553997a56967b3", + "0xb36b4de6d1883b6951f59cfae381581f9c6352fcfcf1524fccdab1571a20f80441d9152dc6b48bcbbf00371337ca0bd5", + "0x89c5523f2574e1c340a955cbed9c2f7b5fbceb260cb1133160dabb7d41c2f613ec3f6e74bbfab3c4a0a6f0626dbe068f", + "0xa52f58cc39f968a9813b1a8ddc4e83f4219e4dd82c7aa1dd083bea7edf967151d635aa9597457f879771759b876774e4", + "0x8300a67c2e2e123f89704abfde095463045dbd97e20d4c1157bab35e9e1d3d18f1f4aaba9cbe6aa2d544e92578eaa1b6", + "0xac6a7f2918768eb6a43df9d3a8a04f8f72ee52f2e91c064c1c7d75cad1a3e83e5aba9fe55bb94f818099ac91ccf2e961", + "0x8d64a2b0991cf164e29835c8ddef6069993a71ec2a7de8157bbfa2e00f6367be646ed74cbaf524f0e9fe13fb09fa15fd", + "0x8b2ffe5a545f9f680b49d0a9797a4a11700a2e2e348c34a7a985fc278f0f12def6e06710f40f9d48e4b7fbb71e072229", + "0x8ab8f71cd337fa19178924e961958653abf7a598e3f022138b55c228440a2bac4176cea3aea393549c03cd38a13eb3fc", + "0x8419d28318c19ea4a179b7abb43669fe96347426ef3ac06b158d79c0acf777a09e8e770c2fb10e14b3a0421705990b23", + "0x8bacdac310e1e49660359d0a7a17fe3d334eb820e61ae25e84cb52f863a2f74cbe89c2e9fc3283745d93a99b79132354", + "0xb57ace3fa2b9f6b2db60c0d861ace7d7e657c5d35d992588aeed588c6ce3a80b6f0d49f8a26607f0b17167ab21b675e4", + "0x83e265cde477f2ecc164f49ddc7fb255bb05ff6adc347408353b7336dc3a14fdedc86d5a7fb23f36b8423248a7a67ed1", + "0xa60ada971f9f2d79d436de5d3d045f5ab05308cae3098acaf5521115134b2a40d664828bb89895840db7f7fb499edbc5", + "0xa63eea12efd89b62d3952bf0542a73890b104dd1d7ff360d4755ebfa148fd62de668edac9eeb20507967ea37fb220202", + "0xa0275767a270289adc991cc4571eff205b58ad6d3e93778ddbf95b75146d82517e8921bd0d0564e5b75fa0ccdab8e624", + "0xb9b03fd3bf07201ba3a039176a965d736b4ef7912dd9e9bf69fe1b57c330a6aa170e5521fe8be62505f3af81b41d7806", + "0xa95f640e26fb1106ced1729d6053e41a16e4896acac54992279ff873e5a969aad1dcfa10311e28b8f409ac1dab7f03bb", + "0xb144778921742418053cb3c70516c63162c187f00db2062193bb2c14031075dbe055d020cde761b26e8c58d0ea6df2c1", + "0x8432fbb799e0435ef428d4fefc309a05dd589bce74d7a87faf659823e8c9ed51d3e42603d878e80f439a38be4321c2fa", + "0xb08ddef14e42d4fd5d8bf39feb7485848f0060d43b51ed5bdda39c05fe154fb111d29719ee61a23c392141358c0cfcff", + "0x8ae3c5329a5e025b86b5370e06f5e61177df4bda075856fade20a17bfef79c92f54ed495f310130021ba94fb7c33632b", + "0x92b6d3c9444100b4d7391febfc1dddaa224651677c3695c47a289a40d7a96d200b83b64e6d9df51f534564f272a2c6c6", + "0xb432bc2a3f93d28b5e506d68527f1efeb2e2570f6be0794576e2a6ef9138926fdad8dd2eabfa979b79ab7266370e86bc", + "0x8bc315eacedbcfc462ece66a29662ca3dcd451f83de5c7626ef8712c196208fb3d8a0faf80b2e80384f0dd9772f61a23", + "0xa72375b797283f0f4266dec188678e2b2c060dfed5880fc6bb0c996b06e91a5343ea2b695adaab0a6fd183b040b46b56", + "0xa43445036fbaa414621918d6a897d3692fdae7b2961d87e2a03741360e45ebb19fcb1703d23f1e15bb1e2babcafc56ac", + "0xb9636b2ffe305e63a1a84bd44fb402442b1799bd5272638287aa87ca548649b23ce8ce7f67be077caed6aa2dbc454b78", + "0x99a30bf0921d854c282b83d438a79f615424f28c2f99d26a05201c93d10378ab2cd94a792b571ddae5d4e0c0013f4006", + "0x8648e3c2f93d70b392443be116b48a863e4b75991bab5db656a4ef3c1e7f645e8d536771dfe4e8d1ceda3be8d32978b0", + "0xab50dc9e6924c1d2e9d2e335b2d679fc7d1a7632e84964d3bac0c9fe57e85aa5906ec2e7b0399d98ddd022e9b19b5904", + "0xab729328d98d295f8f3272afaf5d8345ff54d58ff9884da14f17ecbdb7371857fdf2f3ef58080054e9874cc919b46224", + "0x83fa5da7592bd451cad3ad7702b4006332b3aae23beab4c4cb887fa6348317d234bf62a359e665b28818e5410c278a09", + "0x8bdbff566ae9d368f114858ef1f009439b3e9f4649f73efa946e678d6c781d52c69af195df0a68170f5f191b2eac286b", + "0x91245e59b4425fd4edb2a61d0d47c1ccc83d3ced8180de34887b9655b5dcda033d48cde0bdc3b7de846d246c053a02e8", + "0xa2cb00721e68f1cad8933947456f07144dc69653f96ceed845bd577d599521ba99cdc02421118971d56d7603ed118cbf", + "0xaf8cd66d303e808b22ec57860dd909ca64c27ec2c60e26ffecfdc1179d8762ffd2739d87b43959496e9fee4108df71df", + "0x9954136812dffcd5d3f167a500e7ab339c15cfc9b3398d83f64b0daa3dd5b9a851204f424a3493b4e326d3de81e50a62", + "0x93252254d12511955f1aa464883ad0da793f84d900fea83e1df8bca0f2f4cf5b5f9acbaec06a24160d33f908ab5fea38", + "0x997cb55c26996586ba436a95566bd535e9c22452ca5d2a0ded2bd175376557fa895f9f4def4519241ff386a063f2e526", + "0xa12c78ad451e0ac911260ade2927a768b50cb4125343025d43474e7f465cdc446e9f52a84609c5e7e87ae6c9b3f56cda", + "0xa789d4ca55cbba327086563831b34487d63d0980ba8cf55197c016702ed6da9b102b1f0709ce3da3c53ff925793a3d73", + "0xa5d76acbb76741ce85be0e655b99baa04f7f587347947c0a30d27f8a49ae78cce06e1cde770a8b618d3db402be1c0c4b", + "0x873c0366668c8faddb0eb7c86f485718d65f8c4734020f1a18efd5fa123d3ea8a990977fe13592cd01d17e60809cb5ff", + "0xb659b71fe70f37573ff7c5970cc095a1dc0da3973979778f80a71a347ef25ad5746b2b9608bad4ab9a4a53a4d7df42d7", + "0xa34cbe05888e5e5f024a2db14cb6dcdc401a9cbd13d73d3c37b348f68688f87c24ca790030b8f84fef9e74b4eab5e412", + "0x94ce8010f85875c045b0f014db93ef5ab9f1f6842e9a5743dce9e4cb872c94affd9e77c1f1d1ab8b8660b52345d9acb9", + "0xadefa9b27a62edc0c5b019ddd3ebf45e4de846165256cf6329331def2e088c5232456d3de470fdce3fa758bfdd387512", + "0xa6b83821ba7c1f83cc9e4529cf4903adb93b26108e3d1f20a753070db072ad5a3689643144bdd9c5ea06bb9a7a515cd0", + "0xa3a9ddedc2a1b183eb1d52de26718151744db6050f86f3580790c51d09226bf05f15111691926151ecdbef683baa992c", + "0xa64bac89e7686932cdc5670d07f0b50830e69bfb8c93791c87c7ffa4913f8da881a9d8a8ce8c1a9ce5b6079358c54136", + "0xa77b5a63452cb1320b61ab6c7c2ef9cfbcade5fd4727583751fb2bf3ea330b5ca67757ec1f517bf4d503ec924fe32fbd", + "0x8746fd8d8eb99639d8cd0ca34c0d9c3230ed5a312aab1d3d925953a17973ee5aeb66e68667e93caf9cb817c868ea8f3d", + "0x88a2462a26558fc1fbd6e31aa8abdc706190a17c27fdc4217ffd2297d1b1f3321016e5c4b2384c5454d5717dc732ed03", + "0xb78893a97e93d730c8201af2e0d3b31cb923d38dc594ffa98a714e627c473d42ea82e0c4d2eeb06862ee22a9b2c54588", + "0x920cc8b5f1297cf215a43f6fc843e379146b4229411c44c0231f6749793d40f07b9af7699fd5d21fd69400b97febe027", + "0xa0f0eafce1e098a6b58c7ad8945e297cd93aaf10bc55e32e2e32503f02e59fc1d5776936577d77c0b1162cb93b88518b", + "0x98480ba0064e97a2e7a6c4769b4d8c2a322cfc9a3b2ca2e67e9317e2ce04c6e1108169a20bd97692e1cb1f1423b14908", + "0x83dbbb2fda7e287288011764a00b8357753a6a44794cc8245a2275237f11affdc38977214e463ad67aec032f3dfa37e9", + "0x86442fff37598ce2b12015ff19b01bb8a780b40ad353d143a0f30a06f6d23afd5c2b0a1253716c855dbf445cc5dd6865", + "0xb8a4c60c5171189414887847b9ed9501bff4e4c107240f063e2d254820d2906b69ef70406c585918c4d24f1dd052142b", + "0x919f33a98e84015b2034b57b5ffe9340220926b2c6e45f86fd79ec879dbe06a148ae68b77b73bf7d01bd638a81165617", + "0x95c13e78d89474a47fbc0664f6f806744b75dede95a479bbf844db4a7f4c3ae410ec721cb6ffcd9fa9c323da5740d5ae", + "0xab7151acc41fffd8ec6e90387700bcd7e1cde291ea669567295bea1b9dd3f1df2e0f31f3588cd1a1c08af8120aca4921", + "0x80e74c5c47414bd6eeef24b6793fb1fa2d8fb397467045fcff887c52476741d5bc4ff8b6d3387cb53ad285485630537f", + "0xa296ad23995268276aa351a7764d36df3a5a3cffd7dbeddbcea6b1f77adc112629fdeffa0918b3242b3ccd5e7587e946", + "0x813d2506a28a2b01cb60f49d6bd5e63c9b056aa56946faf2f33bd4f28a8d947569cfead3ae53166fc65285740b210f86", + "0x924b265385e1646287d8c09f6c855b094daaee74b9e64a0dddcf9ad88c6979f8280ba30c8597b911ef58ddb6c67e9fe3", + "0x8d531513c70c2d3566039f7ca47cd2352fd2d55b25675a65250bdb8b06c3843db7b2d29c626eed6391c238fc651cf350", + "0x82b338181b62fdc81ceb558a6843df767b6a6e3ceedc5485664b4ea2f555904b1a45fbb35f6cf5d96f27da10df82a325", + "0x92e62faaedea83a37f314e1d3cb4faaa200178371d917938e59ac35090be1db4b4f4e0edb78b9c991de202efe4f313d8", + "0x99d645e1b642c2dc065bac9aaa0621bc648c9a8351efb6891559c3a41ba737bd155fb32d7731950514e3ecf4d75980e4", + "0xb34a13968b9e414172fb5d5ece9a39cf2eb656128c3f2f6cc7a9f0c69c6bae34f555ecc8f8837dc34b5e470e29055c78", + "0xa2a0bb7f3a0b23a2cbc6585d59f87cd7e56b2bbcb0ae48f828685edd9f7af0f5edb4c8e9718a0aaf6ef04553ba71f3b7", + "0x8e1a94bec053ed378e524b6685152d2b52d428266f2b6eadd4bcb7c4e162ed21ab3e1364879673442ee2162635b7a4d8", + "0x9944adaff14a85eab81c73f38f386701713b52513c4d4b838d58d4ffa1d17260a6d056b02334850ea9a31677c4b078bd", + "0xa450067c7eceb0854b3eca3db6cf38669d72cb7143c3a68787833cbca44f02c0be9bfbe082896f8a57debb13deb2afb1", + "0x8be4ad3ac9ef02f7df09254d569939757101ee2eda8586fefcd8c847adc1efe5bdcb963a0cafa17651befaafb376a531", + "0x90f6de91ea50255f148ac435e08cf2ac00c772a466e38155bd7e8acf9197af55662c7b5227f88589b71abe9dcf7ba343", + "0x86e5a24f0748b106dee2d4d54e14a3b0af45a96cbee69cac811a4196403ebbee17fd24946d7e7e1b962ac7f66dbaf610", + "0xafdd96fbcda7aa73bf9eeb2292e036c25753d249caee3b9c013009cc22e10d3ec29e2aa6ddbb21c4e949b0c0bccaa7f4", + "0xb5a4e7436d5473647c002120a2cb436b9b28e27ad4ebdd7c5f122b91597c507d256d0cbd889d65b3a908531936e53053", + "0xb632414c3da704d80ac2f3e5e0e9f18a3637cdc2ebeb613c29300745582427138819c4e7b0bec3099c1b8739dac1807b", + "0xa28df1464d3372ce9f37ef1db33cc010f752156afae6f76949d98cd799c0cf225c20228ae86a4da592d65f0cffe3951b", + "0x898b93d0a31f7d3f11f253cb7a102db54b669fd150da302d8354d8e02b1739a47cb9bd88015f3baf12b00b879442464e", + "0x96fb88d89a12049091070cb0048a381902965e67a8493e3991eaabe5d3b7ff7eecd5c94493a93b174df3d9b2c9511755", + "0xb899cb2176f59a5cfba3e3d346813da7a82b03417cad6342f19cc8f12f28985b03bf031e856a4743fd7ebe16324805b0", + "0xa60e2d31bc48e0c0579db15516718a03b73f5138f15037491f4dae336c904e312eda82d50862f4debd1622bb0e56d866", + "0x979fc8b987b5cef7d4f4b58b53a2c278bd25a5c0ea6f41c715142ea5ff224c707de38451b0ad3aa5e749aa219256650a", + "0xb2a75bff18e1a6b9cf2a4079572e41205741979f57e7631654a3c0fcec57c876c6df44733c9da3d863db8dff392b44a3", + "0xb7a0f0e811222c91e3df98ff7f286b750bc3b20d2083966d713a84a2281744199e664879401e77470d44e5a90f3e5181", + "0x82b74ba21c9d147fbc338730e8f1f8a6e7fc847c3110944eb17a48bea5e06eecded84595d485506d15a3e675fd0e5e62", + "0xa7f44eef817d5556f0d1abcf420301217d23c69dd2988f44d91ea1f1a16c322263cbacd0f190b9ba22b0f141b9267b4f", + "0xaadb68164ede84fc1cb3334b3194d84ba868d5a88e4c9a27519eef4923bc4abf81aab8114449496c073c2a6a0eb24114", + "0xb5378605fabe9a8c12a5dc55ef2b1de7f51aedb61960735c08767a565793cea1922a603a6983dc25f7cea738d0f7c40d", + "0xa97a4a5cd8d51302e5e670aee78fe6b5723f6cc892902bbb4f131e82ca1dfd5de820731e7e3367fb0c4c1922a02196e3", + "0x8bdfeb15c29244d4a28896f2b2cb211243cd6a1984a3f5e3b0ebe5341c419beeab3304b390a009ffb47588018034b0ea", + "0xa9af3022727f2aa2fca3b096968e97edad3f08edcbd0dbca107b892ae8f746a9c0485e0d6eb5f267999b23a845923ed0", + "0x8e7594034feef412f055590fbb15b6322dc4c6ab7a4baef4685bd13d71a83f7d682b5781bdfa0d1c659489ce9c2b8000", + "0x84977ca6c865ebee021c58106c1a4ad0c745949ecc5332948002fd09bd9b890524878d0c29da96fd11207621136421fe", + "0x8687551a79158e56b2375a271136756313122132a6670fa51f99a1b5c229ed8eea1655a734abae13228b3ebfd2a825dd", + "0xa0227d6708979d99edfc10f7d9d3719fd3fc68b0d815a7185b60307e4c9146ad2f9be2b8b4f242e320d4288ceeb9504c", + "0x89f75583a16735f9dd8b7782a130437805b34280ccea8dac6ecaee4b83fe96947e7b53598b06fecfffdf57ffc12cc445", + "0xa0056c3353227f6dd9cfc8e3399aa5a8f1d71edf25d3d64c982910f50786b1e395c508d3e3727ac360e3e040c64b5298", + "0xb070e61a6d813626144b312ded1788a6d0c7cec650a762b2f8df6e4743941dd82a2511cd956a3f141fc81e15f4e092da", + "0xb4e6db232e028a1f989bb5fc13416711f42d389f63564d60851f009dcffac01acfd54efa307aa6d4c0f932892d4e62b0", + "0x89b5991a67db90024ddd844e5e1a03ef9b943ad54194ae0a97df775dde1addf31561874f4e40fbc37a896630f3bbda58", + "0xad0e8442cb8c77d891df49cdb9efcf2b0d15ac93ec9be1ad5c3b3cca1f4647b675e79c075335c1f681d56f14dc250d76", + "0xb5d55a6ae65bb34dd8306806cb49b5ccb1c83a282ee47085cf26c4e648e19a52d9c422f65c1cd7e03ca63e926c5e92ea", + "0xb749501347e5ec07e13a79f0cb112f1b6534393458b3678a77f02ca89dca973fa7b30e55f0b25d8b92b97f6cb0120056", + "0x94144b4a3ffc5eec6ba35ce9c245c148b39372d19a928e236a60e27d7bc227d18a8cac9983851071935d8ffb64b3a34f", + "0x92bb4f9f85bc8c028a3391306603151c6896673135f8a7aefedd27acb322c04ef5dac982fc47b455d6740023e0dd3ea3", + "0xb9633a4a101461a782fc2aa092e9dbe4e2ad00987578f18cd7cf0021a909951d60fe79654eb7897806795f93c8ff4d1c", + "0x809f0196753024821b48a016eca5dbb449a7c55750f25981bb7a4b4c0e0846c09b8f6128137905055fc43a3f0deb4a74", + "0xa27dc9cdd1e78737a443570194a03d89285576d3d7f3a3cf15cc55b3013e42635d4723e2e8fe1d0b274428604b630db9", + "0x861f60f0462e04cd84924c36a28163def63e777318d00884ab8cb64c8df1df0bce5900342163edb60449296484a6c5bf", + "0xb7bc23fb4e14af4c4704a944253e760adefeca8caee0882b6bbd572c84434042236f39ae07a8f21a560f486b15d82819", + "0xb9a6eb492d6dd448654214bd01d6dc5ff12067a11537ab82023fc16167507ee25eed2c91693912f4155d1c07ed9650b3", + "0x97678af29c68f9a5e213bf0fb85c265303714482cfc4c2c00b4a1e8a76ed08834ee6af52357b143a1ca590fb0265ea5a", + "0x8a15b499e9eca5b6cac3070b5409e8296778222018ad8b53a5d1f6b70ad9bb10c68a015d105c941ed657bf3499299e33", + "0xb487fefede2e8091f2c7bfe85770db2edff1db83d4effe7f7d87bff5ab1ace35e9b823a71adfec6737fede8d67b3c467", + "0x8b51b916402aa2c437fce3bcad6dad3be8301a1a7eab9d163085b322ffb6c62abf28637636fe6114573950117fc92898", + "0xb06a2106d031a45a494adec0881cb2f82275dff9dcdd2bc16807e76f3bec28a6734edd3d54f0be8199799a78cd6228ad", + "0xaf0a185391bbe2315eb97feac98ad6dd2e5d931d012c621abd6e404a31cc188b286fef14871762190acf086482b2b5e2", + "0x8e78ee8206506dd06eb7729e32fceda3bebd8924a64e4d8621c72e36758fda3d0001af42443851d6c0aea58562870b43", + "0xa1ba52a569f0461aaf90b49b92be976c0e73ec4a2c884752ee52ffb62dd137770c985123d405dfb5de70692db454b54a", + "0x8d51b692fa1543c51f6b62b9acb8625ed94b746ef96c944ca02859a4133a5629da2e2ce84e111a7af8d9a5b836401c64", + "0xa7a20d45044cf6492e0531d0b8b26ffbae6232fa05a96ed7f06bdb64c2b0f5ca7ec59d5477038096a02579e633c7a3ff", + "0x84df867b98c53c1fcd4620fef133ee18849c78d3809d6aca0fb6f50ff993a053a455993f216c42ab6090fa5356b8d564", + "0xa7227c439f14c48e2577d5713c97a5205feb69acb0b449152842e278fa71e8046adfab468089c8b2288af1fc51fa945b", + "0x855189b3a105670779997690876dfaa512b4a25a24931a912c2f0f1936971d2882fb4d9f0b3d9daba77eaf660e9d05d5", + "0xb5696bd6706de51c502f40385f87f43040a5abf99df705d6aac74d88c913b8ecf7a99a63d7a37d9bdf3a941b9e432ff5", + "0xab997beb0d6df9c98d5b49864ef0b41a2a2f407e1687dfd6089959757ba30ed02228940b0e841afe6911990c74d536c4", + "0xb36b65f85546ebfdbe98823d5555144f96b4ab39279facd19c0de3b8919f105ba0315a0784dce4344b1bc62d8bb4a5a3", + "0xb8371f0e4450788720ac5e0f6cd3ecc5413d33895083b2c168d961ec2b5c3de411a4cc0712481cbe8df8c2fa1a7af006", + "0x98325d8026b810a8b7a114171ae59a57e8bbc9848e7c3df992efc523621729fd8c9f52114ce01d7730541a1ada6f1df1", + "0x8d0e76dbd37806259486cd9a31bc8b2306c2b95452dc395546a1042d1d17863ef7a74c636b782e214d3aa0e8d717f94a", + "0xa4e15ead76da0214d702c859fb4a8accdcdad75ed08b865842bd203391ec4cba2dcc916455e685f662923b96ee0c023f", + "0x8618190972086ebb0c4c1b4a6c94421a13f378bc961cc8267a301de7390c5e73c3333864b3b7696d81148f9d4843fd02", + "0x85369d6cc7342e1aa15b59141517d8db8baaaeb7ab9670f3ba3905353948d575923d283b7e5a05b13a30e7baf1208a86", + "0x87c51ef42233c24a6da901f28c9a075d9ba3c625687c387ad6757b72ca6b5a8885e6902a3082da7281611728b1e45f26", + "0xaa6348a4f71927a3106ad0ea8b02fc8d8c65531e4ab0bd0a17243e66f35afe252e40ab8eef9f13ae55a72566ffdaff5c", + "0x96a3bc976e9d03765cc3fee275fa05b4a84c94fed6b767e23ca689394501e96f56f7a97cffddc579a6abff632bf153be", + "0x97dbf96c6176379fdb2b888be4e757b2bca54e74124bd068d3fa1dbd82a011bbeb75079da38e0cd22a761fe208ecad9b", + "0xb70cf0a1d14089a4129ec4e295313863a59da8c7e26bf74cc0e704ed7f0ee4d7760090d0ddf7728180f1bf2c5ac64955", + "0x882d664714cc0ffe53cbc9bef21f23f3649824f423c4dbad1f893d22c4687ab29583688699efc4d5101aa08b0c3e267a", + "0x80ecb7cc963e677ccaddbe3320831dd6ee41209acf4ed41b16dc4817121a3d86a1aac9c4db3d8c08a55d28257088af32", + "0xa25ba667d832b145f9ce18c3f9b1bd00737aa36db020e1b99752c8ef7d27c6c448982bd8d352e1b6df266b8d8358a8d5", + "0x83734841c13dee12759d40bdd209b277e743b0d08cc0dd1e0b7afd2d65bfa640400eefcf6be4a52e463e5b3d885eeac6", + "0x848d16505b04804afc773aebabb51b36fd8aacfbb0e09b36c0d5d57df3c0a3b92f33e7d5ad0a7006ec46ebb91df42b8c", + "0x909a8d793f599e33bb9f1dc4792a507a97169c87cd5c087310bc05f30afcd247470b4b56dec59894c0fb1d48d39bb54e", + "0x8e558a8559df84a1ba8b244ece667f858095c50bb33a5381e60fcc6ba586b69693566d8819b4246a27287f16846c1dfa", + "0x84d6b69729f5aaa000cd710c2352087592cfbdf20d5e1166977e195818e593fa1a50d1e04566be23163a2523dc1612f1", + "0x9536d262b7a42125d89f4f32b407d737ba8d9242acfc99d965913ab3e043dcac9f7072a43708553562cac4cba841df30", + "0x9598548923ca119d6a15fd10861596601dd1dedbcccca97bb208cdc1153cf82991ea8cc17686fbaa867921065265970c", + "0xb87f2d4af6d026e4d2836bc3d390a4a18e98a6e386282ce96744603bab74974272e97ac2da281afa21885e2cbb3a8001", + "0x991ece62bf07d1a348dd22191868372904b9f8cf065ae7aa4e44fd24a53faf6d851842e35fb472895963aa1992894918", + "0xa8c53dea4c665b30e51d22ca6bc1bc78aaf172b0a48e64a1d4b93439b053877ec26cb5221c55efd64fa841bbf7d5aff4", + "0x93487ec939ed8e740f15335b58617c3f917f72d07b7a369befd479ae2554d04deb240d4a14394b26192efae4d2f4f35d", + "0xa44793ab4035443f8f2968a40e043b4555960193ffa3358d22112093aadfe2c136587e4139ffd46d91ed4107f61ea5e0", + "0xb13fe033da5f0d227c75927d3dacb06dbaf3e1322f9d5c7c009de75cdcba5e308232838785ab69a70f0bedea755e003f", + "0x970a29b075faccd0700fe60d1f726bdebf82d2cc8252f4a84543ebd3b16f91be42a75c9719a39c4096139f0f31393d58", + "0xa4c3eb1f7160f8216fc176fb244df53008ff32f2892363d85254002e66e2de21ccfe1f3b1047589abee50f29b9d507e3", + "0x8c552885eab04ba40922a8f0c3c38c96089c95ff1405258d3f1efe8d179e39e1295cbf67677894c607ae986e4e6b1fb0", + "0xb3671746fa7f848c4e2ae6946894defadd815230b906b419143523cc0597bc1d6c0a4c1e09d49b66b4a2c11cde3a4de3", + "0x937a249a95813a5e2ef428e355efd202e15a37d73e56cfb7e57ea9f943f2ce5ca8026f2f1fd25bf164ba89d07077d858", + "0x83646bdf6053a04aa9e2f112499769e5bd5d0d10f2e13db3ca89bd45c0b3b7a2d752b7d137fb3909f9c62b78166c9339", + "0xb4eac4b91e763666696811b7ed45e97fd78310377ebea1674b58a2250973f80492ac35110ed1240cd9bb2d17493d708c", + "0x82db43a99bc6573e9d92a3fd6635dbbb249ac66ba53099c3c0c8c8080b121dd8243cd5c6e36ba0a4d2525bae57f5c89c", + "0xa64d6a264a681b49d134c655d5fc7756127f1ee7c93d328820f32bca68869f53115c0d27fef35fe71f7bc4fdaed97348", + "0x8739b7a9e2b4bc1831e7f04517771bc7cde683a5e74e052542517f8375a2f64e53e0d5ac925ef722327e7bb195b4d1d9", + "0x8f337cdd29918a2493515ebb5cf702bbe8ecb23b53c6d18920cc22f519e276ca9b991d3313e2d38ae17ae8bdfa4f8b7e", + "0xb0edeab9850e193a61f138ef2739fc42ceec98f25e7e8403bfd5fa34a7bc956b9d0898250d18a69fa4625a9b3d6129da", + "0xa9920f26fe0a6d51044e623665d998745c9eca5bce12051198b88a77d728c8238f97d4196f26e43b24f8841500b998d0", + "0x86e655d61502b979eeeeb6f9a7e1d0074f936451d0a1b0d2fa4fb3225b439a3770767b649256fe481361f481a8dbc276", + "0x84d3b32fa62096831cc3bf013488a9f3f481dfe293ae209ed19585a03f7db8d961a7a9dd0db82bd7f62d612707575d9c", + "0x81c827826ec9346995ffccf62a241e3b2d32f7357acd1b1f8f7a7dbc97022d3eb51b8a1230e23ce0b401d2e535e8cd78", + "0x94a1e40c151191c5b055b21e86f32e69cbc751dcbdf759a48580951834b96a1eed75914c0d19a38aefd21fb6c8d43d0c", + "0xab890222b44bc21b71f7c75e15b6c6e16bb03371acce4f8d4353ff3b8fcd42a14026589c5ed19555a3e15e4d18bfc3a3", + "0xaccb0be851e93c6c8cc64724cdb86887eea284194b10e7a43c90528ed97e9ec71ca69c6fac13899530593756dd49eab2", + "0xb630220aa9e1829c233331413ee28c5efe94ea8ea08d0c6bfd781955078b43a4f92915257187d8526873e6c919c6a1de", + "0xadd389a4d358c585f1274b73f6c3c45b58ef8df11f9d11221f620e241bf3579fba07427b288c0c682885a700cc1fa28d", + "0xa9fe6ca8bf2961a3386e8b8dcecc29c0567b5c0b3bcf3b0f9169f88e372b80151af883871fc5229815f94f43a6f5b2b0", + "0xad839ae003b92b37ea431fa35998b46a0afc3f9c0dd54c3b3bf7a262467b13ff3c323ada1c1ae02ac7716528bdf39e3e", + "0x9356d3fd0edcbbb65713c0f2a214394f831b26f792124b08c5f26e7f734b8711a87b7c4623408da6a091c9aef1f6af3c", + "0x896b25b083c35ac67f0af3784a6a82435b0e27433d4d74cd6d1eafe11e6827827799490fb1c77c11de25f0d75f14e047", + "0x8bfa019391c9627e8e5f05c213db625f0f1e51ec68816455f876c7e55b8f17a4f13e5aae9e3fb9e1cf920b1402ee2b40", + "0x8ba3a6faa6a860a8f3ce1e884aa8769ceded86380a86520ab177ab83043d380a4f535fe13884346c5e51bee68da6ab41", + "0xa8292d0844084e4e3bb7af92b1989f841a46640288c5b220fecfad063ee94e86e13d3d08038ec2ac82f41c96a3bfe14d", + "0x8229bb030b2fc566e11fd33c7eab7a1bb7b49fed872ea1f815004f7398cb03b85ea14e310ec19e1f23e0bdaf60f8f76c", + "0x8cfbf869ade3ec551562ff7f63c2745cc3a1f4d4dc853a0cd42dd5f6fe54228f86195ea8fe217643b32e9f513f34a545", + "0xac52a3c8d3270ddfe1b5630159da9290a5ccf9ccbdef43b58fc0a191a6c03b8a5974cf6e2bbc7bd98d4a40a3581482d7", + "0xab13decb9e2669e33a7049b8eca3ca327c40dea15ad6e0e7fa63ed506db1d258bc36ac88b35f65cae0984e937eb6575d", + "0xb5e748eb1a7a1e274ff0cc56311c198f2c076fe4b7e73e5f80396fe85358549df906584e6bb2c8195b3e2be7736850a5", + "0xb5cb911325d8f963c41f691a60c37831c7d3bbd92736efa33d1f77a22b3fde7f283127256c2f47e197571e6fe0b46149", + "0x8a01dc6ed1b55f26427a014faa347130738b191a06b800e32042a46c13f60b49534520214359d68eb2e170c31e2b8672", + "0xa72fa874866e19b2efb8e069328362bf7921ec375e3bcd6b1619384c3f7ee980f6cf686f3544e9374ff54b4d17a1629c", + "0x8db21092f7c5f110fba63650b119e82f4b42a997095d65f08f8237b02dd66fdf959f788df2c35124db1dbd330a235671", + "0x8c65d50433d9954fe28a09fa7ba91a70a590fe7ba6b3060f5e4be0f6cef860b9897fa935fb4ebc42133524eb071dd169", + "0xb4614058e8fa21138fc5e4592623e78b8982ed72aa35ee4391b164f00c68d277fa9f9eba2eeefc890b4e86eba5124591", + "0xab2ad3a1bce2fbd55ca6b7c23786171fe1440a97d99d6df4d80d07dd56ac2d7203c294b32fc9e10a6c259381a73f24a1", + "0x812ae3315fdc18774a8da3713a4679e8ed10b9405edc548c00cacbe25a587d32040566676f135e4723c5dc25df5a22e9", + "0xa464b75f95d01e5655b54730334f443c8ff27c3cb79ec7af4b2f9da3c2039c609908cd128572e1fd0552eb597e8cef8d", + "0xa0db3172e93ca5138fe419e1c49a1925140999f6eff7c593e5681951ee0ec1c7e454c851782cbd2b8c9bc90d466e90e0", + "0x806db23ba7d00b87d544eed926b3443f5f9c60da6b41b1c489fba8f73593b6e3b46ebfcab671ee009396cd77d5e68aa1", + "0x8bfdf2c0044cc80260994e1c0374588b6653947b178e8b312be5c2a05e05767e98ea15077278506aee7df4fee1aaf89e", + "0x827f6558c16841b5592ff089c9c31e31eb03097623524394813a2e4093ad2d3f8f845504e2af92195aaa8a1679d8d692", + "0x925c4f8eab2531135cd71a4ec88e7035b5eea34ba9d799c5898856080256b4a15ed1a746e002552e2a86c9c157e22e83", + "0xa9f9a368f0e0b24d00a35b325964c85b69533013f9c2cfad9708be5fb87ff455210f8cb8d2ce3ba58ca3f27495552899", + "0x8ac0d3bebc1cae534024187e7c71f8927ba8fcc6a1926cb61c2b6c8f26bb7831019e635a376146c29872a506784a4aaa", + "0x97c577be2cbbfdb37ad754fae9df2ada5fc5889869efc7e18a13f8e502fbf3f4067a509efbd46fd990ab47ce9a70f5a8", + "0x935e7d82bca19f16614aa43b4a3474e4d20d064e4bfdf1cea2909e5c9ab72cfe3e54dc50030e41ee84f3588cebc524e9", + "0x941aafc08f7c0d94cebfbb1f0aad5202c02e6e37f2c12614f57e727efa275f3926348f567107ee6d8914dd71e6060271", + "0xaf0fbc1ba05b4b5b63399686df3619968be5d40073de0313cbf5f913d3d4b518d4c249cdd2176468ccaa36040a484f58", + "0xa0c414f23f46ca6d69ce74c6f8a00c036cb0edd098af0c1a7d39c802b52cfb2d5dbdf93fb0295453d4646e2af7954d45", + "0x909cf39e11b3875bb63b39687ae1b5d1f5a15445e39bf164a0b14691b4ddb39a8e4363f584ef42213616abc4785b5d66", + "0xa92bac085d1194fbd1c88299f07a061d0bdd3f980b663e81e6254dbb288bf11478c0ee880e28e01560f12c5ccb3c0103", + "0x841705cd5cd76b943e2b7c5e845b9dd3c8defe8ef67e93078d6d5e67ade33ad4b0fd413bc196f93b0a4073c855cd97d4", + "0x8e7eb8364f384a9161e81d3f1d52ceca9b65536ae49cc35b48c3e2236322ba4ae9973e0840802d9fa4f4d82ea833544f", + "0xaed3ab927548bc8bec31467ba80689c71a168e34f50dcb6892f19a33a099f5aa6b3f9cb79f5c0699e837b9a8c7f27efe", + "0xb8fbf7696210a36e20edabd77839f4dfdf50d6d015cdf81d587f90284a9bcef7d2a1ff520728d7cc69a4843d6c20dedd", + "0xa9d533769ce6830211c884ae50a82a7bf259b44ac71f9fb11f0296fdb3981e6b4c1753fe744647b247ebc433a5a61436", + "0x8b4bdf90d33360b7f428c71cde0a49fb733badba8c726876945f58c620ce7768ae0e98fc8c31fa59d8955a4823336bb1", + "0x808d42238e440e6571c59e52a35ae32547d502dc24fd1759d8ea70a7231a95859baf30b490a4ba55fa2f3aaa11204597", + "0x85594701f1d2fee6dc1956bc44c7b31db93bdeec2f3a7d622c1a08b26994760773e3d57521a44cfd7e407ac3fd430429", + "0xa66de045ce7173043a6825e9dc440ac957e2efb6df0a337f4f8003eb0c719d873a52e6eba3cb0d69d977ca37d9187674", + "0x87a1c6a1fdff993fa51efa5c3ba034c079c0928a7d599b906336af7c2dcab9721ceaf3108c646490af9dff9a754f54b3", + "0x926424223e462ceb75aed7c22ade8a7911a903b7e5dd4bc49746ddce8657f4616325cd12667d4393ac52cdd866396d0e", + "0xb5dc96106593b42b30f06f0b0a1e0c1aafc70432e31807252d3674f0b1ea5e58eac8424879d655c9488d85a879a3e572", + "0x997ca0987735cc716507cb0124b1d266d218b40c9d8e0ecbf26a1d65719c82a637ce7e8be4b4815d307df717bde7c72a", + "0x92994d3f57a569b7760324bb5ae4e8e14e1633d175dab06aa57b8e391540e05f662fdc08b8830f489a063f59b689a688", + "0xa8087fcc6aa4642cb998bea11facfe87eb33b90a9aa428ab86a4124ad032fc7d2e57795311a54ec9f55cc120ebe42df1", + "0xa9bd7d1de6c0706052ca0b362e2e70e8c8f70f1f026ea189b4f87a08ce810297ebfe781cc8004430776c54c1a05ae90c", + "0x856d33282e8a8e33a3d237fb0a0cbabaf77ba9edf2fa35a831fdafcadf620561846aa6cbb6bdc5e681118e1245834165", + "0x9524a7aa8e97a31a6958439c5f3339b19370f03e86b89b1d02d87e4887309dbbe9a3a8d2befd3b7ed5143c8da7e0a8ad", + "0x824fdf433e090f8acbd258ac7429b21f36f9f3b337c6d0b71d1416a5c88a767883e255b2888b7c906dd2e9560c4af24c", + "0x88c7fee662ca7844f42ed5527996b35723abffd0d22d4ca203b9452c639a5066031207a5ae763dbc0865b3299d19b1ec", + "0x919dca5c5595082c221d5ab3a5bc230f45da7f6dec4eb389371e142c1b9c6a2c919074842479c2844b72c0d806170c0c", + "0xb939be8175715e55a684578d8be3ceff3087f60fa875fff48e52a6e6e9979c955efef8ff67cfa2b79499ea23778e33b0", + "0x873b6db725e7397d11bc9bed9ac4468e36619135be686790a79bc6ed4249058f1387c9a802ea86499f692cf635851066", + "0xaeae06db3ec47e9e5647323fa02fac44e06e59b885ad8506bf71b184ab3895510c82f78b6b22a5d978e8218e7f761e9f", + "0xb99c0a8359c72ab88448bae45d4bf98797a26bca48b0d4460cd6cf65a4e8c3dd823970ac3eb774ae5d0cea4e7fadf33e", + "0x8f10c8ec41cdfb986a1647463076a533e6b0eec08520c1562401b36bb063ac972aa6b28a0b6ce717254e35940b900e3c", + "0xa106d9be199636d7add43b942290269351578500d8245d4aae4c083954e4f27f64740a3138a66230391f2d0e6043a8de", + "0xa469997908244578e8909ff57cffc070f1dbd86f0098df3cfeb46b7a085cfecc93dc69ee7cad90ff1dc5a34d50fe580c", + "0xa4ef087bea9c20eb0afc0ee4caba7a9d29dfa872137828c721391273e402fb6714afc80c40e98bbd8276d3836bffa080", + "0xb07a013f73cd5b98dae0d0f9c1c0f35bff8a9f019975c4e1499e9bee736ca6fcd504f9bc32df1655ff333062382cff04", + "0xb0a77188673e87cc83348c4cc5db1eecf6b5184e236220c8eeed7585e4b928db849944a76ec60ef7708ef6dac02d5592", + "0xb1284b37e59b529f0084c0dacf0af6c0b91fc0f387bf649a8c74819debf606f7b07fc3e572500016fb145ec2b24e9f17", + "0x97b20b5b4d6b9129da185adfbf0d3d0b0faeba5b9715f10299e48ea0521709a8296a9264ce77c275a59c012b50b6519a", + "0xb9d37e946fae5e4d65c1fbfacc8a62e445a1c9d0f882e60cca649125af303b3b23af53c81d7bac544fb7fcfc7a314665", + "0x8e5acaac379f4bb0127efbef26180f91ff60e4c525bc9b798fc50dfaf4fe8a5aa84f18f3d3cfb8baead7d1e0499af753", + "0xb0c0b8ab1235bf1cda43d4152e71efc1a06c548edb964eb4afceb201c8af24240bf8ab5cae30a08604e77432b0a5faf0", + "0x8cc28d75d5c8d062d649cbc218e31c4d327e067e6dbd737ec0a35c91db44fbbd0d40ec424f5ed79814add16947417572", + "0x95ae6219e9fd47efaa9cb088753df06bc101405ba50a179d7c9f7c85679e182d3033f35b00dbba71fdcd186cd775c52e", + "0xb5d28fa09f186ebc5aa37453c9b4d9474a7997b8ae92748ecb940c14868792292ac7d10ade01e2f8069242b308cf97e5", + "0x8c922a0faa14cc6b7221f302df3342f38fc8521ec6c653f2587890192732c6da289777a6cd310747ea7b7d104af95995", + "0xb9ad5f660b65230de54de535d4c0fcae5bc6b59db21dea5500fdc12eea4470fb8ea003690fdd16d052523418d5e01e8c", + "0xa39a9dd41a0ff78c82979483731f1cd68d3921c3e9965869662c22e02dde3877802e180ba93f06e7346f96d9fa9261d2", + "0x8b32875977ec372c583b24234c27ed73aef00cdff61eb3c3776e073afbdeade548de9497c32ec6d703ff8ad0a5cb7fe4", + "0x9644cbe755a5642fe9d26cfecf170d3164f1848c2c2e271d5b6574a01755f3980b3fc870b98cf8528fef6ecef4210c16", + "0x81ea9d1fdd9dd66d60f40ce0712764b99da9448ae0b300f8324e1c52f154e472a086dda840cb2e0b9813dc8ce8afd4b5", + "0x906aaa4a7a7cdf01909c5cfbc7ded2abc4b869213cbf7c922d4171a4f2e637e56f17020b852ad339d83b8ac92f111666", + "0x939b5f11acbdeff998f2a080393033c9b9d8d5c70912ea651c53815c572d36ee822a98d6dfffb2e339f29201264f2cf4", + "0xaba4898bf1ccea9b9e2df1ff19001e05891581659c1cbbde7ee76c349c7fc7857261d9785823c9463a8aea3f40e86b38", + "0x83ca1a56b8a0be4820bdb5a9346357c68f9772e43f0b887729a50d2eb2a326bbcede676c8bf2e51d7c89bbd8fdb778a6", + "0x94e86e9fe6addfe2c3ee3a547267ed921f4230d877a85bb4442c2d9350c2fa9a9c54e6fe662de82d1a2407e4ab1691c2", + "0xa0cc3bdef671a59d77c6984338b023fa2b431b32e9ed2abe80484d73edc6540979d6f10812ecc06d4d0c5d4eaca7183c", + "0xb5343413c1b5776b55ea3c7cdd1f3af1f6bd802ea95effe3f2b91a523817719d2ecc3f8d5f3cc2623ace7e35f99ca967", + "0x92085d1ed0ed28d8cabe3e7ff1905ed52c7ceb1eac5503760c52fb5ee3a726aba7c90b483c032acc3f166b083d7ec370", + "0x8ec679520455275cd957fca8122724d287db5df7d29f1702a322879b127bff215e5b71d9c191901465d19c86c8d8d404", + "0xb65eb2c63d8a30332eb24ee8a0c70156fc89325ebbb38bacac7cf3f8636ad8a472d81ccca80423772abc00192d886d8a", + "0xa9fe1c060b974bee4d590f2873b28635b61bfcf614e61ff88b1be3eee4320f4874e21e8d666d8ac8c9aba672efc6ecae", + "0xb3fe2a9a389c006a831dea7e777062df84b5c2803f9574d7fbe10b7e1c125817986af8b6454d6be9d931a5ac94cfe963", + "0x95418ad13b734b6f0d33822d9912c4c49b558f68d08c1b34a0127fcfa666bcae8e6fda8832d2c75bb9170794a20e4d7c", + "0xa9a7df761e7f18b79494bf429572140c8c6e9d456c4d4e336184f3f51525a65eb9582bea1e601bdb6ef8150b7ca736a5", + "0xa0de03b1e75edf7998c8c1ac69b4a1544a6fa675a1941950297917366682e5644a4bda9cdeedfaf9473d7fccd9080b0c", + "0xa61838af8d95c95edf32663a68f007d95167bf6e41b0c784a30b22d8300cfdd5703bd6d16e86396638f6db6ae7e42a85", + "0x8866d62084d905c145ff2d41025299d8b702ac1814a7dec4e277412c161bc9a62fed735536789cb43c88693c6b423882", + "0x91da22c378c81497fe363e7f695c0268443abee50f8a6625b8a41e865638a643f07b157ee566de09ba09846934b4e2d7", + "0x941d21dd57c9496aa68f0c0c05507405fdd413acb59bc668ce7e92e1936c68ec4b065c3c30123319884149e88228f0b2", + "0xa77af9b094bc26966ddf2bf9e1520c898194a5ccb694915950dadc204facbe3066d3d89f50972642d76b14884cfbaa21", + "0x8e76162932346869f4618bde744647f7ab52ab498ad654bdf2a4feeb986ac6e51370841e5acbb589e38b6e7142bb3049", + "0xb60979ace17d6937ece72e4f015da4657a443dd01cebc7143ef11c09e42d4aa8855999a65a79e2ea0067f31c9fc2ab0f", + "0xb3e2ffdd5ee6fd110b982fd4fad4b93d0fca65478f986d086eeccb0804960bfaa1919afa743c2239973ea65091fe57d2", + "0x8ce0ce05e7d7160d44574011da687454dbd3c8b8290aa671731b066e2c82f8cf2d63cb8e932d78c6122ec610e44660e6", + "0xab005dd8d297045c39e2f72fb1c48edb501ccf3575d3d04b9817b3afee3f0bb0f3f53f64bda37d1d9cde545aae999bae", + "0x95bd7edb4c4cd60e3cb8a72558845a3cce6bb7032ccdf33d5a49ebb6ddf203bc3c79e7b7e550735d2d75b04c8b2441e8", + "0x889953ee256206284094e4735dbbb17975bafc7c3cb94c9fbfee4c3e653857bfd49e818f64a47567f721b98411a3b454", + "0xb188423e707640ab0e75a061e0b62830cde8afab8e1ad3dae30db69ffae4e2fc005bababbdcbd7213b918ed4f70e0c14", + "0xa97e0fafe011abd70d4f99a0b36638b3d6e7354284588f17a88970ed48f348f88392779e9a038c6cbc9208d998485072", + "0x87db11014a91cb9b63e8dfaa82cdebca98272d89eb445ee1e3ff9dbaf2b3fad1a03b888cffc128e4fe208ed0dddece0f", + "0xaad2e40364edd905d66ea4ac9d51f9640d6fda9a54957d26ba233809851529b32c85660fa401dbee3679ec54fa6dd966", + "0x863e99336ca6edf03a5a259e59a2d0f308206e8a2fb320cfc0be06057366df8e0f94b33a28f574092736b3c5ada84270", + "0xb34bcc56a057589f34939a1adc51de4ff6a9f4fee9c7fa9aa131e28d0cf0759a0c871b640162acdfbf91f3f1b59a3703", + "0x935dd28f2896092995c5eff1618e5b6efe7a40178888d7826da9b0503c2d6e68a28e7fac1a334e166d0205f0695ef614", + "0xb842cd5f8f5de5ca6c68cb4a5c1d7b451984930eb4cc18fd0934d52fdc9c3d2d451b1c395594d73bc3451432bfba653f", + "0x9014537885ce2debad736bc1926b25fdab9f69b216bf024f589c49dc7e6478c71d595c3647c9f65ff980b14f4bb2283b", + "0x8e827ccca1dd4cd21707140d10703177d722be0bbe5cac578db26f1ef8ad2909103af3c601a53795435b27bf95d0c9ed", + "0x8a0b8ad4d466c09d4f1e9167410dbe2edc6e0e6229d4b3036d30f85eb6a333a18b1c968f6ca6d6889bb08fecde017ef4", + "0x9241ee66c0191b06266332dc9161dede384c4bb4e116dbd0890f3c3790ec5566da4568243665c4725b718ac0f6b5c179", + "0xaeb4d5fad81d2b505d47958a08262b6f1b1de9373c2c9ba6362594194dea3e002ab03b8cbb43f867be83065d3d370f19", + "0x8781bc83bb73f7760628629fe19e4714b494dbed444c4e4e4729b7f6a8d12ee347841a199888794c2234f51fa26fc2b9", + "0xb58864f0acd1c2afa29367e637cbde1968d18589245d9936c9a489c6c495f54f0113ecdcbe4680ac085dd3c397c4d0c3", + "0x94a24284afaeead61e70f3e30f87248d76e9726759445ca18cdb9360586c60cc9f0ec1c397f9675083e0b56459784e2e", + "0xaed358853f2b54dcbddf865e1816c2e89be12e940e1abfa661e2ee63ffc24a8c8096be2072fa83556482c0d89e975124", + "0xb95374e6b4fc0765708e370bc881e271abf2e35c08b056a03b847e089831ef4fe3124b9c5849d9c276eb2e35b3daf264", + "0xb834cdbcfb24c8f84bfa4c552e7fadc0028a140952fd69ed13a516e1314a4cd35d4b954a77d51a1b93e1f5d657d0315d", + "0x8fb6d09d23bfa90e7443753d45a918d91d75d8e12ec7d016c0dfe94e5c592ba6aaf483d2f16108d190822d955ad9cdc3", + "0xaa315cd3c60247a6ad4b04f26c5404c2713b95972843e4b87b5a36a89f201667d70f0adf20757ebe1de1b29ae27dda50", + "0xa116862dca409db8beff5b1ccd6301cdd0c92ca29a3d6d20eb8b87f25965f42699ca66974dd1a355200157476b998f3b", + "0xb4c2f5fe173c4dc8311b60d04a65ce1be87f070ac42e13cd19c6559a2931c6ee104859cc2520edebbc66a13dc7d30693", + "0x8d4a02bf99b2260c334e7d81775c5cf582b00b0c982ce7745e5a90624919028278f5e9b098573bad5515ce7fa92a80c8", + "0x8543493bf564ce6d97bd23be9bff1aba08bd5821ca834f311a26c9139c92a48f0c2d9dfe645afa95fec07d675d1fd53b", + "0x9344239d13fde08f98cb48f1f87d34cf6abe8faecd0b682955382a975e6eed64e863fa19043290c0736261622e00045c", + "0xaa49d0518f343005ca72b9e6c7dcaa97225ce6bb8b908ebbe7b1a22884ff8bfb090890364e325a0d414ad180b8f161d1", + "0x907d7fd3e009355ab326847c4a2431f688627faa698c13c03ffdd476ecf988678407f029b8543a475dcb3dafdf2e7a9c", + "0x845f1f10c6c5dad2adc7935f5cd2e2b32f169a99091d4f1b05babe7317b9b1cdce29b5e62f947dc621b9acbfe517a258", + "0x8f3be8e3b380ea6cdf9e9c237f5e88fd5a357e5ded80ea1fc2019810814de82501273b4da38916881125b6fa0cfd4459", + "0xb9c7f487c089bf1d20c822e579628db91ed9c82d6ca652983aa16d98b4270c4da19757f216a71b9c13ddee3e6e43705f", + "0x8ba2d8c88ad2b872db104ea8ddbb006ec2f3749fd0e19298a804bb3a5d94de19285cc7fb19fee58a66f7851d1a66c39f", + "0x9375ecd3ed16786fe161af5d5c908f56eeb467a144d3bbddfc767e90065b7c94fc53431adebecba2b6c9b5821184d36e", + "0xa49e069bfadb1e2e8bff6a4286872e2a9765d62f0eaa4fcb0e5af4bbbed8be3510fb19849125a40a8a81d1e33e81c3eb", + "0x9522cc66757b386aa6b88619525c8ce47a5c346d590bb3647d12f991e6c65c3ab3c0cfc28f0726b6756c892eae1672be", + "0xa9a0f1f51ff877406fa83a807aeb17b92a283879f447b8a2159653db577848cc451cbadd01f70441e351e9ed433c18bc", + "0x8ff7533dcff6be8714df573e33f82cf8e9f2bcaaa43e939c4759d52b754e502717950de4b4252fb904560fc31dce94a4", + "0x959724671e265a28d67c29d95210e97b894b360da55e4cf16e6682e7912491ed8ca14bfaa4dce9c25a25b16af580494f", + "0x92566730c3002f4046c737032487d0833c971e775de59fe02d9835c9858e2e3bc37f157424a69764596c625c482a2219", + "0xa84b47ceff13ed9c3e5e9cdf6739a66d3e7c2bd8a6ba318fefb1a9aecf653bb2981da6733ddb33c4b0a4523acc429d23", + "0xb4ddf571317e44f859386d6140828a42cf94994e2f1dcbcc9777f4eebbfc64fc1e160b49379acc27c4672b8e41835c5d", + "0x8ab95c94072b853d1603fdd0a43b30db617d13c1d1255b99075198e1947bfa5f59aed2b1147548a1b5e986cd9173d15c", + "0x89511f2eab33894fd4b3753d24249f410ff7263052c1fef6166fc63a79816656b0d24c529e45ccce6be28de6e375d916", + "0xa0866160ca63d4f2be1b4ea050dac6b59db554e2ebb4e5b592859d8df339b46fd7cb89aaed0951c3ee540aee982c238a", + "0x8fcc5cbba1b94970f5ff2eb1922322f5b0aa7d918d4b380c9e7abfd57afd8b247c346bff7b87af82efbce3052511cd1b", + "0x99aeb2a5e846b0a2874cca02c66ed40d5569eb65ab2495bc3f964a092e91e1517941f2688e79f8cca49cd3674c4e06dc", + "0xb7a096dc3bad5ca49bee94efd884aa3ff5615cf3825cf95fbe0ce132e35f46581d6482fa82666c7ef5f1643eaee8f1ca", + "0x94393b1da6eaac2ffd186b7725eca582f1ddc8cdd916004657f8a564a7c588175cb443fc6943b39029f5bbe0add3fad8", + "0x884b85fe012ccbcd849cb68c3ad832d83b3ef1c40c3954ffdc97f103b1ed582c801e1a41d9950f6bddc1d11f19d5ec76", + "0xb00061c00131eded8305a7ce76362163deb33596569afb46fe499a7c9d7a0734c084d336b38d168024c2bb42b58e7660", + "0xa439153ac8e6ca037381e3240e7ba08d056c83d7090f16ed538df25901835e09e27de2073646e7d7f3c65056af6e4ce7", + "0x830fc9ca099097d1f38b90e6843dc86f702be9d20bdacc3e52cae659dc41df5b8d2c970effa6f83a5229b0244a86fe22", + "0xb81ea2ffaaff2bb00dd59a9ab825ba5eed4db0d8ac9c8ed1a632ce8f086328a1cddd045fbe1ace289083c1325881b7e7", + "0xb51ea03c58daf2db32c99b9c4789b183365168cb5019c72c4cc91ac30b5fb7311d3db76e6fa41b7cd4a8c81e2f6cdc94", + "0xa4170b2c6d09ca5beb08318730419b6f19215ce6c631c854116f904be3bc30dd85a80c946a8ab054d3e307afaa3f8fbc", + "0x897cc42ff28971ff54d2a55dd6b35cfb8610ac902f3c06e3a5cea0e0a257e870c471236a8e84709211c742a09c5601a6", + "0xa18f2e98d389dace36641621488664ecbb422088ab03b74e67009b8b8acacaaa24fdcf42093935f355207d934adc52a8", + "0x92adcfb678cc2ba19c866f3f2b988fdcb4610567f3ab436cc0cb9acaf5a88414848d71133ebdbec1983e38e6190f1b5f", + "0xa86d43c2ce01b366330d3b36b3ca85f000c3548b8297e48478da1ee7d70d8576d4650cba7852ed125c0d7cb6109aa7f3", + "0x8ed31ceed9445437d7732dce78a762d72ff32a7636bfb3fd7974b7ae15db414d8184a1766915244355deb354fbc5803b", + "0x9268f70032584f416e92225d65af9ea18c466ebc7ae30952d56a4e36fd9ea811dde0a126da9220ba3c596ec54d8a335e", + "0x9433b99ee94f2d3fbdd63b163a2bdf440379334c52308bd24537f7defd807145a062ff255a50d119a7f29f4b85d250e3", + "0x90ce664f5e4628a02278f5cf5060d1a34f123854634b1870906e5723ac9afd044d48289be283b267d45fcbf3f4656aaf", + "0xaaf21c4d59378bb835d42ae5c5e5ab7a3c8c36a59e75997989313197752b79a472d866a23683b329ea69b048b87fa13e", + "0xb83c0589b304cec9ede549fde54f8a7c2a468c6657da8c02169a6351605261202610b2055c639b9ed2d5b8c401fb8f56", + "0x9370f326ea0f170c2c05fe2c5a49189f20aec93b6b18a5572a818cd4c2a6adb359e68975557b349fb54f065d572f4c92", + "0xac3232fa5ce6f03fca238bef1ce902432a90b8afce1c85457a6bee5571c033d4bceefafc863af04d4e85ac72a4d94d51", + "0x80d9ea168ff821b22c30e93e4c7960ce3ad3c1e6deeebedd342a36d01bd942419b187e2f382dbfd8caa34cca08d06a48", + "0xa387a3c61676fb3381eefa2a45d82625635a666e999aba30e3b037ec9e040f414f9e1ad9652abd3bcad63f95d85038db", + "0xa1b229fe32121e0b391b0f6e0180670b9dc89d79f7337de4c77ea7ad0073e9593846f06797c20e923092a08263204416", + "0x92164a9d841a2b828cedf2511213268b698520f8d1285852186644e9a0c97512cafa4bfbe29af892c929ebccd102e998", + "0x82ee2fa56308a67c7db4fd7ef539b5a9f26a1c2cc36da8c3206ba4b08258fbb3cec6fe5cdbd111433fb1ba2a1e275927", + "0x8c77bfe9e191f190a49d46f05600603fa42345592539b82923388d72392404e0b29a493a15e75e8b068dddcd444c2928", + "0x80b927f93ccf79dcf5c5b20bcf5a7d91d7a17bc0401bb7cc9b53a6797feac31026eb114257621f5a64a52876e4474cc1", + "0xb6b68b6501c37804d4833d5a063dd108a46310b1400549074e3cac84acc6d88f73948b7ad48d686de89c1ec043ae8c1a", + "0xab3da00f9bdc13e3f77624f58a3a18fc3728956f84b5b549d62f1033ae4b300538e53896e2d943f160618e05af265117", + "0xb6830e87233b8eace65327fdc764159645b75d2fd4024bf8f313b2dd5f45617d7ecfb4a0b53ccafb5429815a9a1adde6", + "0xb9251cfe32a6dc0440615aadcd98b6b1b46e3f4e44324e8f5142912b597ee3526bea2431e2b0282bb58f71be5b63f65e", + "0xaf8d70711e81cdddfb39e67a1b76643292652584c1ce7ce4feb1641431ad596e75c9120e85f1a341e7a4da920a9cdd94", + "0x98cd4e996594e89495c078bfd52a4586b932c50a449a7c8dfdd16043ca4cda94dafbaa8ad1b44249c99bbcc52152506e", + "0xb9fc6d1c24f48404a4a64fbe3e43342738797905db46e4132aee5f086aaa4c704918ad508aaefa455cfe1b36572e6242", + "0xa365e871d30ba9291cedaba1be7b04e968905d003e9e1af7e3b55c5eb048818ae5b913514fb08b24fb4fbdccbb35d0b8", + "0x93bf99510971ea9af9f1e364f1234c898380677c8e8de9b0dd24432760164e46c787bc9ec42a7ad450500706cf247b2d", + "0xb872f825a5b6e7b9c7a9ddfeded3516f0b1449acc9b4fd29fc6eba162051c17416a31e5be6d3563f424d28e65bab8b8f", + "0xb06b780e5a5e8eb4f4c9dc040f749cf9709c8a4c9ef15e925f442b696e41e5095db0778a6c73bcd329b265f2c6955c8b", + "0x848f1a981f5fc6cd9180cdddb8d032ad32cdfa614fc750d690dbae36cc0cd355cbf1574af9b3ffc8b878f1b2fafb9544", + "0xa03f48cbff3e9e8a3a655578051a5ae37567433093ac500ed0021c6250a51b767afac9bdb194ee1e3eac38a08c0eaf45", + "0xb5be78ce638ff8c4aa84352b536628231d3f7558c5be3bf010b28feac3022e64691fa672f358c8b663904aebe24a54ed", + "0xa9d4da70ff676fa55d1728ba6ab03b471fa38b08854d99e985d88c2d050102d8ccffbe1c90249a5607fa7520b15fe791", + "0x8fe9f7092ffb0b69862c8e972fb1ecf54308c96d41354ed0569638bb0364f1749838d6d32051fff1599112978c6e229c", + "0xae6083e95f37770ecae0df1e010456f165d96cfe9a7278c85c15cffd61034081ce5723e25e2bede719dc9341ec8ed481", + "0xa260891891103089a7afbd9081ea116cfd596fd1015f5b65e10b0961eb37fab7d09c69b7ce4be8bf35e4131848fb3fe4", + "0x8d729fa32f6eb9fd2f6a140bef34e8299a2f3111bffd0fe463aa8622c9d98bfd31a1df3f3e87cd5abc52a595f96b970e", + "0xa30ec6047ae4bc7da4daa7f4c28c93aedb1112cfe240e681d07e1a183782c9ff6783ac077c155af23c69643b712a533f", + "0xac830726544bfe7b5467339e5114c1a75f2a2a8d89453ce86115e6a789387e23551cd64620ead6283dfa4538eb313d86", + "0x8445c135b7a48068d8ed3e011c6d818cfe462b445095e2fbf940301e50ded23f272d799eea47683fc027430ce14613ef", + "0x95785411715c9ae9d8293ce16a693a2aa83e3cb1b4aa9f76333d0da2bf00c55f65e21e42e50e6c5772ce213dd7b4f7a0", + "0xb273b024fa18b7568c0d1c4d2f0c4e79ec509dafac8c5951f14192d63ddbcf2d8a7512c1c1b615cc38fa3e336618e0c5", + "0xa78b9d3ea4b6a90572eb27956f411f1d105fdb577ee2ffeec9f221da9b45db84bfe866af1f29597220c75e0c37a628d8", + "0xa4be2bf058c36699c41513c4d667681ce161a437c09d81383244fc55e1c44e8b1363439d0cce90a3e44581fb31d49493", + "0xb6eef13040f17dd4eba22aaf284d2f988a4a0c4605db44b8d2f4bf9567ac794550b543cc513c5f3e2820242dd704152e", + "0x87eb00489071fa95d008c5244b88e317a3454652dcb1c441213aa16b28cd3ecaa9b22fec0bdd483c1df71c37119100b1", + "0x92d388acdcb49793afca329cd06e645544d2269234e8b0b27d2818c809c21726bc9cf725651b951e358a63c83dedee24", + "0xae27e219277a73030da27ab5603c72c8bd81b6224b7e488d7193806a41343dff2456132274991a4722fdb0ef265d04cd", + "0x97583e08ecb82bbc27c0c8476d710389fa9ffbead5c43001bd36c1b018f29faa98de778644883e51870b69c5ffb558b5", + "0x90a799a8ce73387599babf6b7da12767c0591cadd36c20a7990e7c05ea1aa2b9645654ec65308ee008816623a2757a6a", + "0xa1b47841a0a2b06efd9ab8c111309cc5fc9e1d5896b3e42ed531f6057e5ade8977c29831ce08dbda40348386b1dcc06d", + "0xb92b8ef59bbddb50c9457691bc023d63dfcc54e0fd88bd5d27a09e0d98ac290fc90e6a8f6b88492043bf7c87fac8f3e4", + "0xa9d6240b07d62e22ec8ab9b1f6007c975a77b7320f02504fc7c468b4ee9cfcfd945456ff0128bc0ef2174d9e09333f8d", + "0x8e96534c94693226dc32bca79a595ca6de503af635f802e86442c67e77564829756961d9b701187fe91318da515bf0e6", + "0xb6ba290623cd8dd5c2f50931c0045d1cfb0c30877bc8fe58cbc3ff61ee8da100045a39153916efa1936f4aee0892b473", + "0xb43baa7717fac02d4294f5b3bb5e58a65b3557747e3188b482410388daac7a9c177f762d943fd5dcf871273921213da8", + "0xb9cf00f8fb5e2ef2b836659fece15e735060b2ea39b8e901d3dcbdcf612be8bf82d013833718c04cd46ffaa70b85f42e", + "0x8017d0c57419e414cbba504368723e751ef990cc6f05dad7b3c2de6360adc774ad95512875ab8337d110bf39a42026fa", + "0xae7401048b838c0dcd4b26bb6c56d79d51964a0daba780970b6c97daee4ea45854ea0ac0e4139b3fe60dac189f84df65", + "0x887b237b0cd0f816b749b21db0b40072f9145f7896c36916296973f9e6990ede110f14e5976c906d08987c9836cca57f", + "0xa88c3d5770148aee59930561ca1223aceb2c832fb5417e188dca935905301fc4c6c2c9270bc1dff7add490a125eb81c6", + "0xb6cf9b02c0cd91895ad209e38c54039523f137b5848b9d3ad33ae43af6c20c98434952db375fe378de7866f2d0e8b18a", + "0x84ef3d322ff580c8ad584b1fe4fe346c60866eb6a56e982ba2cf3b021ecb1fdb75ecc6c29747adda86d9264430b3f816", + "0xa0561c27224baf0927ad144cb71e31e54a064c598373fcf0d66aebf98ab7af1d8e2f343f77baefff69a6da750a219e11", + "0xaa5cc43f5b8162b016f5e1b61214c0c9d15b1078911c650b75e6cdfb49b85ee04c6739f5b1687d15908444f691f732de", + "0xad4ac099b935589c7b8fdfdf3db332b7b82bb948e13a5beb121ebd7db81a87d278024a1434bcf0115c54ca5109585c3d", + "0x8a00466abf3f109a1dcd19e643b603d3af23d42794ef8ca2514dd507ecea44a031ac6dbc18bd02f99701168b25c1791e", + "0xb00b5900dfad79645f8bee4e5adc7b84eb22e5b1e67df77ccb505b7fc044a6c08a8ea5faca662414eb945f874f884cea", + "0x950e204e5f17112250b22ea6bb8423baf522fc0af494366f18fe0f949f51d6e6812074a80875cf1ed9c8e7420058d541", + "0x91e5cbf8bb1a1d50c81608c9727b414d0dd2fb467ebc92f100882a3772e54f94979cfdf8e373fdef7c7fcdd60fec9e00", + "0xa093f6a857b8caaff80599c2e89c962b415ecbaa70d8fd973155fa976a284c6b29a855f5f7a3521134d00d2972755188", + "0xb4d55a3551b00da54cc010f80d99ddd2544bde9219a3173dfaadf3848edc7e4056ab532fb75ac26f5f7141e724267663", + "0xa03ea050fc9b011d1b04041b5765d6f6453a93a1819cd9bd6328637d0b428f08526466912895dcc2e3008ee58822e9a7", + "0x99b12b3665e473d01bc6985844f8994fb65cb15745024fb7af518398c4a37ff215da8f054e8fdf3286984ae36a73ca5e", + "0x9972c7e7a7fb12e15f78d55abcaf322c11249cd44a08f62c95288f34f66b51f146302bce750ff4d591707075d9123bd2", + "0xa64b4a6d72354e596d87cda213c4fc2814009461570ccb27d455bbe131f8d948421a71925425b546d8cf63d5458cd64b", + "0x91c215c73b195795ede2228b7ed1f6e37892e0c6b0f4a0b5a16c57aa1100c84df9239054a173b6110d6c2b7f4bf1ce52", + "0x88807198910ec1303480f76a3683870246a995e36adaeadc29c22f0bdba8152fe705bd070b75de657b04934f7d0ccf80", + "0xb37c0026c7b32eb02cacac5b55cb5fe784b8e48b2945c64d3037af83ece556a117f0ff053a5968c2f5fa230e291c1238", + "0x94c768384ce212bc2387e91ce8b45e4ff120987e42472888a317abc9dcdf3563b62e7a61c8e98d7cdcbe272167d91fc6", + "0xa10c2564936e967a390cb14ef6e8f8b04ea9ece5214a38837eda09e79e0c7970b1f83adf017c10efd6faa8b7ffa2c567", + "0xa5085eed3a95f9d4b1269182ea1e0d719b7809bf5009096557a0674bde4201b0ddc1f0f16a908fc468846b3721748ce3", + "0x87468eb620b79a0a455a259a6b4dfbc297d0d53336537b771254dd956b145dc816b195b7002647ea218552e345818a3f", + "0xace2b77ffb87366af0a9cb5d27d6fc4a14323dbbf1643f5f3c4559306330d86461bb008894054394cbfaefeaa0bc2745", + "0xb27f56e840a54fbd793f0b7a7631aa4cee64b5947e4382b2dfb5eb1790270288884c2a19afebe5dc0c6ef335d4531c1c", + "0x876e438633931f7f895062ee16c4b9d10428875f7bc79a8e156a64d379a77a2c45bf5430c5ab94330f03da352f1e9006", + "0xa2512a252587d200d2092b44c914df54e04ff8bcef36bf631f84bde0cf5a732e3dc7f00f662842cfd74b0b0f7f24180e", + "0x827f1bc8f54a35b7a4bd8154f79bcc055e45faed2e74adf7cf21cca95df44d96899e847bd70ead6bb27b9c0ed97bbd8b", + "0xa0c92cf5a9ed843714f3aea9fe7b880f622d0b4a3bf66de291d1b745279accf6ba35097849691370f41732ba64b5966b", + "0xa63f5c1e222775658421c487b1256b52626c6f79cb55a9b7deb2352622cedffb08502042d622eb3b02c97f9c09f9c957", + "0x8cc093d52651e65fb390e186db6cc4de559176af4624d1c44cb9b0e836832419dacac7b8db0627b96288977b738d785d", + "0xaa7b6a17dfcec146134562d32a12f7bd7fe9522e300859202a02939e69dbd345ed7ff164a184296268f9984f9312e8fc", + "0x8ac76721f0d2b679f023d06cbd28c85ae5f4b43c614867ccee88651d4101d4fd352dbdb65bf36bfc3ebc0109e4b0c6f9", + "0x8d350f7c05fc0dcd9a1170748846fb1f5d39453e4cb31e6d1457bed287d96fc393b2ecc53793ca729906a33e59c6834a", + "0xb9913510dfc5056d7ec5309f0b631d1ec53e3a776412ada9aefdaf033c90da9a49fdde6719e7c76340e86599b1f0eec2", + "0x94955626bf4ce87612c5cfffcf73bf1c46a4c11a736602b9ba066328dc52ad6d51e6d4f53453d4ed55a51e0aad810271", + "0xb0fcab384fd4016b2f1e53f1aafd160ae3b1a8865cd6c155d7073ecc1664e05b1d8bca1def39c158c7086c4e1103345e", + "0x827de3f03edfbde08570b72de6662c8bfa499b066a0a27ebad9b481c273097d17a5a0a67f01553da5392ec3f149b2a78", + "0xab7940384c25e9027c55c40df20bd2a0d479a165ced9b1046958353cd69015eeb1e44ed2fd64e407805ba42df10fc7bf", + "0x8ad456f6ff8cd58bd57567d931f923d0c99141978511b17e03cab7390a72b9f62498b2893e1b05c7c22dd274e9a31919", + "0xac75399e999effe564672db426faa17a839e57c5ef735985c70cd559a377adec23928382767b55ed5a52f7b11b54b756", + "0xb17f975a00b817299ac7af5f2024ea820351805df58b43724393bfb3920a8cd747a3bbd4b8286e795521489db3657168", + "0xa2bed800a6d95501674d9ee866e7314063407231491d794f8cf57d5be020452729c1c7cefd8c50dc1540181f5caab248", + "0x9743f5473171271ffdd3cc59a3ae50545901a7b45cd4bc3570db487865f3b73c0595bebabbfe79268809ee1862e86e4a", + "0xb7eab77c2d4687b60d9d7b04e842b3880c7940140012583898d39fcc22d9b9b0a9be2c2e3788b3e6f30319b39c338f09", + "0x8e2b8f797a436a1b661140e9569dcf3e1eea0a77c7ff2bc4ff0f3e49af04ed2de95e255df8765f1d0927fb456a9926b1", + "0x8aefea201d4a1f4ff98ffce94e540bb313f2d4dfe7e9db484a41f13fc316ed02b282e1acc9bc6f56cad2dc2e393a44c9", + "0xb950c17c0e5ca6607d182144aa7556bb0efe24c68f06d79d6413a973b493bfdf04fd147a4f1ab03033a32004cc3ea66f", + "0xb7b8dcbb179a07165f2dc6aa829fad09f582a71b05c3e3ea0396bf9e6fe73076f47035c031c2101e8e38e0d597eadd30", + "0xa9d77ed89c77ec1bf8335d08d41c3c94dcca9fd1c54f22837b4e54506b212aa38d7440126c80648ab7723ff18e65ed72", + "0xa819d6dfd4aef70e52b8402fe5d135f8082d40eb7d3bb5c4d7997395b621e2bb10682a1bad2c9caa33dd818550fc3ec6", + "0x8f6ee34128fac8bbf13ce2d68b2bb363eb4fd65b297075f88e1446ddeac242500eeb4ef0735e105882ff5ba8c44c139b", + "0xb4440e48255c1644bcecf3a1e9958f1ec4901cb5b1122ee5b56ffd02cad1c29c4266999dbb85aa2605c1b125490074d4", + "0xa43304a067bede5f347775d5811cf65a6380a8d552a652a0063580b5c5ef12a0867a39c7912fa219e184f4538eba1251", + "0xa891ad67a790089ffc9f6d53e6a3d63d3556f5f693e0cd8a7d0131db06fd4520e719cfcc3934f0a8f62a95f90840f1d4", + "0xaea6df8e9bb871081aa0fc5a9bafb00be7d54012c5baf653791907d5042a326aeee966fd9012a582cc16695f5baf7042", + "0x8ffa2660dc52ed1cd4eff67d6a84a8404f358a5f713d04328922269bee1e75e9d49afeec0c8ad751620f22352a438e25", + "0x87ec6108e2d63b06abed350f8b363b7489d642486f879a6c3aa90e5b0f335efc2ff2834eef9353951a42136f8e6a1b32", + "0x865619436076c2760d9e87ddc905023c6de0a8d56eef12c98a98c87837f2ca3f27fd26a2ad752252dbcbe2b9f1d5a032", + "0x980437dce55964293cb315c650c5586ffd97e7a944a83f6618af31c9d92c37b53ca7a21bb5bc557c151b9a9e217e7098", + "0x95d128fc369df4ad8316b72aea0ca363cbc7b0620d6d7bb18f7076a8717a6a46956ff140948b0cc4f6d2ce33b5c10054", + "0x8c7212d4a67b9ec70ebbca04358ad2d36494618d2859609163526d7b3acc2fc935ca98519380f55e6550f70a9bc76862", + "0x893a2968819401bf355e85eee0f0ed0406a6d4a7d7f172d0017420f71e00bb0ba984f6020999a3cdf874d3cd8ebcd371", + "0x9103c1af82dece25d87274e89ea0acd7e68c2921c4af3d8d7c82ab0ed9990a5811231b5b06113e7fa43a6bd492b4564f", + "0x99cfd87a94eab7d35466caa4ed7d7bb45e5c932b2ec094258fb14bf205659f83c209b83b2f2c9ccb175974b2a33e7746", + "0x874b6b93e4ee61be3f00c32dd84c897ccd6855c4b6251eb0953b4023634490ed17753cd3223472873cbc6095b2945075", + "0x84a32c0dc4ea60d33aac3e03e70d6d639cc9c4cc435c539eff915017be3b7bdaba33349562a87746291ebe9bc5671f24", + "0xa7057b24208928ad67914e653f5ac1792c417f413d9176ba635502c3f9c688f7e2ee81800d7e3dc0a340c464da2fd9c5", + "0xa03fb9ed8286aacfa69fbd5d953bec591c2ae4153400983d5dbb6cd9ea37fff46ca9e5cceb9d117f73e9992a6c055ad2", + "0x863b2de04e89936c9a4a2b40380f42f20aefbae18d03750fd816c658aee9c4a03df7b12121f795c85d01f415baaeaa59", + "0x8526eb9bd31790fe8292360d7a4c3eed23be23dd6b8b8f01d2309dbfdc0cfd33ad1568ddd7f8a610f3f85a9dfafc6a92", + "0xb46ab8c5091a493d6d4d60490c40aa27950574a338ea5bbc045be3a114af87bdcb160a8c80435a9b7ad815f3cb56a3f3", + "0xaeadc47b41a8d8b4176629557646202f868b1d728b2dda58a347d937e7ffc8303f20d26d6c00b34c851b8aeec547885d", + "0xaebb19fc424d72c1f1822aa7adc744cd0ef7e55727186f8df8771c784925058c248406ebeeaf3c1a9ee005a26e9a10c6", + "0x8ff96e81c1a4a2ab1b4476c21018fae0a67e92129ee36120cae8699f2d7e57e891f5c624902cb1b845b944926a605cc3", + "0x8251b8d2c43fadcaa049a9e7aff838dae4fb32884018d58d46403ac5f3beb5c518bfd45f03b8abb710369186075eb71c", + "0xa8b2a64f865f51a5e5e86a66455c093407933d9d255d6b61e1fd81ffafc9538d73caaf342338a66ba8ee166372a3d105", + "0xaad915f31c6ba7fdc04e2aaac62e84ef434b7ee76a325f07dc430d12c84081999720181067b87d792efd0117d7ee1eab", + "0xa13db3bb60389883fd41d565c54fb5180d9c47ce2fe7a169ae96e01d17495f7f4fa928d7e556e7c74319c4c25d653eb2", + "0xa4491b0198459b3f552855d680a59214eb74e6a4d6c5fa3b309887dc50ebea2ecf6d26c040550f7dc478b452481466fb", + "0x8f017f13d4b1e3f0c087843582b52d5f8d13240912254d826dd11f8703a99a2f3166dfbdfdffd9a3492979d77524276b", + "0x96c3d5dcd032660d50d7cd9db2914f117240a63439966162b10c8f1f3cf74bc83b0f15451a43b31dbd85e4a7ce0e4bb1", + "0xb479ec4bb79573d32e0ec93b92bdd7ec8c26ddb5a2d3865e7d4209d119fd3499eaac527615ffac78c440e60ef3867ae0", + "0xb2c49c4a33aa94b52b6410b599e81ff15490aafa7e43c8031c865a84e4676354a9c81eb4e7b8be6825fdcefd1e317d44", + "0x906dc51d6a90c089b6704b47592805578a6eed106608eeb276832f127e1b8e858b72e448edcbefb497d152447e0e68ff", + "0xb0e81c63b764d7dfbe3f3fddc9905aef50f3633e5d6a4af6b340495124abedcff5700dfd1577bbbed7b6bf97d02719cb", + "0x9304c64701e3b4ed6d146e48a881f7d83a17f58357cca0c073b2bb593afd2d94f6e2a7a1ec511d0a67ad6ff4c3be5937", + "0xb6fdbd12ba05aa598d80b83f70a15ef90e5cba7e6e75fa038540ee741b644cd1f408a6cecfd2a891ef8d902de586c6b5", + "0xb80557871a6521b1b3c74a1ba083ae055b575df607f1f7b04c867ba8c8c181ea68f8d90be6031f4d25002cca27c44da2", + "0xaa7285b8e9712e06b091f64163f1266926a36607f9d624af9996856ed2aaf03a580cb22ce407d1ade436c28b44ca173f", + "0x8148d72b975238b51e6ea389e5486940d22641b48637d7dfadfa603a605bfc6d74a016480023945d0b85935e396aea5d", + "0x8a014933a6aea2684b5762af43dcf4bdbb633cd0428d42d71167a2b6fc563ece5e618bff22f1db2ddb69b845b9a2db19", + "0x990d91740041db770d0e0eb9d9d97d826f09fd354b91c41e0716c29f8420e0e8aac0d575231efba12fe831091ec38d5a", + "0x9454d0d32e7e308ddec57cf2522fb1b67a2706e33fb3895e9e1f18284129ab4f4c0b7e51af25681d248d7832c05eb698", + "0xa5bd434e75bac105cb3e329665a35bce6a12f71dd90c15165777d64d4c13a82bceedb9b48e762bd24034e0fc9fbe45f4", + "0xb09e3b95e41800d4dc29c6ffdaab2cd611a0050347f6414f154a47ee20ee59bf8cf7181454169d479ebce1eb5c777c46", + "0xb193e341d6a047d15eea33766d656d807b89393665a783a316e9ba10518e5515c8e0ade3d6e15641d917a8a172a5a635", + "0xade435ec0671b3621dde69e07ead596014f6e1daa1152707a8c18877a8b067bde2895dd47444ffa69db2bbef1f1d8816", + "0xa7fd3d6d87522dfc56fb47aef9ce781a1597c56a8bbfd796baba907afdc872f753d732bfda1d3402aee6c4e0c189f52d", + "0xa298cb4f4218d0464b2fab393e512bbc477c3225aa449743299b2c3572f065bc3a42d07e29546167ed9e1b6b3b3a3af3", + "0xa9ee57540e1fd9c27f4f0430d194b91401d0c642456c18527127d1f95e2dba41c2c86d1990432eb38a692fda058fafde", + "0x81d6c1a5f93c04e6d8e5a7e0678c1fc89a1c47a5c920bcd36180125c49fcf7c114866b90e90a165823560b19898a7c16", + "0xa4b7a1ec9e93c899b9fd9aaf264c50e42c36c0788d68296a471f7a3447af4dbc81e4fa96070139941564083ec5b5b5a1", + "0xb3364e327d381f46940c0e11e29f9d994efc6978bf37a32586636c0070b03e4e23d00650c1440f448809e1018ef9f6d8", + "0x8056e0913a60155348300e3a62e28b5e30629a90f7dd4fe11289097076708110a1d70f7855601782a3cdc5bdb1ca9626", + "0xb4980fd3ea17bac0ba9ee1c470b17e575bb52e83ebdd7d40c93f4f87bebeaff1c8a679f9d3d09d635f068d37d5bd28bd", + "0x905a9299e7e1853648e398901dfcd437aa575c826551f83520df62984f5679cb5f0ea86aa45ed3e18b67ddc0dfafe809", + "0xab99553bf31a84f2e0264eb34a08e13d8d15e2484aa9352354becf9a15999c76cc568d68274b70a65e49703fc23540d0", + "0xa43681597bc574d2dae8964c9a8dc1a07613d7a1272bdcb818d98c85d44e16d744250c33f3b5e4d552d97396b55e601f", + "0xa54e5a31716fccb50245898c99865644405b8dc920ded7a11f3d19bdc255996054b268e16f2e40273f11480e7145f41e", + "0x8134f3ad5ef2ad4ba12a8a4e4d8508d91394d2bcdc38b7c8c8c0b0a820357ac9f79d286c65220f471eb1adca1d98fc68", + "0x94e2f755e60471578ab2c1adb9e9cea28d4eec9b0e92e0140770bca7002c365fcabfe1e5fb4fe6cfe79a0413712aa3ef", + "0xad48f8d0ce7eb3cc6e2a3086ad96f562e5bed98a360721492ae2e74dc158586e77ec8c35d5fd5927376301b7741bad2b", + "0x8614f0630bdd7fbad3a31f55afd9789f1c605dc85e7dc67e2edfd77f5105f878bb79beded6e9f0b109e38ea7da67e8d5", + "0x9804c284c4c5e77dabb73f655b12181534ca877c3e1e134aa3f47c23b7ec92277db34d2b0a5d38d2b69e5d1c3008a3e3", + "0xa51b99c3088e473afdaa9e0a9f7e75a373530d3b04e44e1148da0726b95e9f5f0c7e571b2da000310817c36f84b19f7f", + "0xac4ff909933b3b76c726b0a382157cdc74ab851a1ac6cef76953c6444441804cc43abb883363f416592e8f6cfbc4550b", + "0xae7d915eb9fc928b65a29d6edbc75682d08584d0014f7bcf17d59118421ae07d26a02137d1e4de6938bcd1ab8ef48fad", + "0x852f7e453b1af89b754df6d11a40d5d41ea057376e8ecacd705aacd2f917457f4a093d6b9a8801837fa0f62986ad7149", + "0x92c6bf5ada5d0c3d4dd8058483de36c215fa98edab9d75242f3eff9db07c734ad67337da6f0eefe23a487bf75a600dee", + "0xa2b42c09d0db615853763552a48d2e704542bbd786aae016eb58acbf6c0226c844f5fb31e428cb6450b9db855f8f2a6f", + "0x880cc07968266dbfdcfbc21815cd69e0eddfee239167ac693fb0413912d816f2578a74f7716eecd6deefa68c6eccd394", + "0xb885b3ace736cd373e8098bf75ba66fa1c6943ca1bc4408cd98ac7074775c4478594f91154b8a743d9c697e1b29f5840", + "0xa51ce78de512bd87bfa0835de819941dffbf18bec23221b61d8096fc9436af64e0693c335b54e7bfc763f287bdca2db6", + "0xa3c76166a3bdb9b06ef696e57603b58871bc72883ee9d45171a30fe6e1d50e30bc9c51b4a0f5a7270e19a77b89733850", + "0xacefc5c6f8a1e7c24d7b41e0fc7f6f3dc0ede6cf3115ffb9a6e54b1d954cbca9bda8ad7a084be9be245a1b8e9770d141", + "0xb420ed079941842510e31cfad117fa11fb6b4f97dfbc6298cb840f27ebaceba23eeaf3f513bcffbf5e4aae946310182d", + "0x95c3bb5ef26c5ed2f035aa5d389c6b3c15a6705b9818a3fefaed28922158b35642b2e8e5a1a620fdad07e75ad4b43af4", + "0x825149f9081ecf07a2a4e3e8b5d21bade86c1a882475d51c55ee909330b70c5a2ac63771c8600c6f38df716af61a3ea1", + "0x873b935aae16d9f08adbc25353cee18af2f1b8d5f26dec6538d6bbddc515f2217ed7d235dcfea59ae61b428798b28637", + "0x9294150843a2bedcedb3bb74c43eb28e759cf9499582c5430bccefb574a8ddd4f11f9929257ff4c153990f9970a2558f", + "0xb619563a811cc531da07f4f04e5c4c6423010ff9f8ed7e6ec9449162e3d501b269fb1c564c09c0429431879b0f45df02", + "0x91b509b87eb09f007d839627514658c7341bc76d468920fe8a740a8cb96a7e7e631e0ea584a7e3dc1172266f641d0f5c", + "0x8b8aceace9a7b9b4317f1f01308c3904d7663856946afbcea141a1c615e21ccad06b71217413e832166e9dd915fbe098", + "0x87b3b36e725833ea0b0f54753c3728c0dbc87c52d44d705ffc709f2d2394414c652d3283bab28dcce09799504996cee0", + "0xb2670aad5691cbf308e4a6a77a075c4422e6cbe86fdba24e9f84a313e90b0696afb6a067eebb42ba2d10340d6a2f6e51", + "0x876784a9aff3d54faa89b2bacd3ff5862f70195d0b2edc58e8d1068b3c9074c0da1cfa23671fe12f35e33b8a329c0ccd", + "0x8b48b9e758e8a8eae182f5cbec96f67d20cca6d3eee80a2d09208eb1d5d872e09ef23d0df8ebbb9b01c7449d0e3e3650", + "0xb79303453100654c04a487bdcadc9e3578bc80930c489a7069a52e8ca1dba36c492c8c899ce025f8364599899baa287d", + "0x961b35a6111da54ece6494f24dacd5ea46181f55775b5f03df0e370c34a5046ac2b4082925855325bb42bc2a2c98381d", + "0xa31feb1be3f5a0247a1f7d487987eb622e34fca817832904c6ee3ee60277e5847945a6f6ea1ac24542c72e47bdf647df", + "0xa12a2aa3e7327e457e1aae30e9612715dd2cfed32892c1cd6dcda4e9a18203af8a44afb46d03b2eed89f6b9c5a2c0c23", + "0xa08265a838e69a2ca2f80fead6ccf16f6366415b920c0b22ee359bcd8d4464ecf156f400a16a7918d52e6d733dd64211", + "0xb723d6344e938d801cca1a00032af200e541d4471fd6cbd38fb9130daa83f6a1dffbbe7e67fc20f9577f884acd7594b2", + "0xa6733d83ec78ba98e72ddd1e7ff79b7adb0e559e256760d0c590a986e742445e8cdf560d44b29439c26d87edd0b07c8c", + "0xa61c2c27d3f7b9ff4695a17afedf63818d4bfba390507e1f4d0d806ce8778d9418784430ce3d4199fd3bdbc2504d2af3", + "0x8332f3b63a6dc985376e8b1b25eeae68be6160fbe40053ba7bcf6f073204f682da72321786e422d3482fd60c9e5aa034", + "0xa280f44877583fbb6b860d500b1a3f572e3ee833ec8f06476b3d8002058e25964062feaa1e5bec1536d734a5cfa09145", + "0xa4026a52d277fcea512440d2204f53047718ebfcae7b48ac57ea7f6bfbc5de9d7304db9a9a6cbb273612281049ddaec5", + "0x95cdf69c831ab2fad6c2535ede9c07e663d2ddccc936b64e0843d2df2a7b1c31f1759c3c20f1e7a57b1c8f0dbb21b540", + "0x95c96cec88806469c277ab567863c5209027cecc06c7012358e5f555689c0d9a5ffb219a464f086b45817e8536b86d2f", + "0xafe38d4684132a0f03d806a4c8df556bf589b25271fbc6fe2e1ed16de7962b341c5003755da758d0959d2e6499b06c68", + "0xa9b77784fda64987f97c3a23c5e8f61b918be0f7c59ba285084116d60465c4a2aaafc8857eb16823282cc83143eb9126", + "0xa830f05881ad3ce532a55685877f529d32a5dbe56cea57ffad52c4128ee0fad0eeaf0da4362b55075e77eda7babe70e5", + "0x992b3ad190d6578033c13ed5abfee4ef49cbc492babb90061e3c51ee4b5790cdd4c8fc1abff1fa2c00183b6b64f0bbbe", + "0xb1015424d9364aeff75de191652dc66484fdbec3e98199a9eb9671ec57bec6a13ff4b38446e28e4d8aedb58dd619cd90", + "0xa745304604075d60c9db36cada4063ac7558e7ec2835d7da8485e58d8422e817457b8da069f56511b02601289fbb8981", + "0xa5ba4330bc5cb3dbe0486ddf995632a7260a46180a08f42ae51a2e47778142132463cc9f10021a9ad36986108fefa1a9", + "0xb419e9fd4babcaf8180d5479db188bb3da232ae77a1c4ed65687c306e6262f8083070a9ac32220cddb3af2ec73114092", + "0xa49e23dc5f3468f3bf3a0bb7e4a114a788b951ff6f23a3396ae9e12cbff0abd1240878a3d1892105413dbc38818e807c", + "0xb7ecc7b4831f650202987e85b86bc0053f40d983f252e9832ef503aea81c51221ce93279da4aa7466c026b2d2070e55d", + "0x96a8c35cb87f84fa84dcd6399cc2a0fd79cc9158ef4bdde4bae31a129616c8a9f2576cd19baa3f497ca34060979aed7d", + "0x8681b2c00aa62c2b519f664a95dcb8faef601a3b961bb4ce5d85a75030f40965e2983871d41ea394aee934e859581548", + "0x85c229a07efa54a713d0790963a392400f55fbb1a43995a535dc6c929f20d6a65cf4efb434e0ad1cb61f689b8011a3bc", + "0x90856f7f3444e5ad44651c28e24cc085a5db4d2ffe79aa53228c26718cf53a6e44615f3c5cda5aa752d5f762c4623c66", + "0x978999b7d8aa3f28a04076f74d11c41ef9c89fdfe514936c4238e0f13c38ec97e51a5c078ebc6409e517bfe7ccb42630", + "0xa099914dd7ed934d8e0d363a648e9038eb7c1ec03fa04dbcaa40f7721c618c3ef947afef7a16b4d7ac8c12aa46637f03", + "0xab2a104fed3c83d16f2cda06878fa5f30c8c9411de71bfb67fd2fc9aa454dcbcf3d299d72f8cc12e919466a50fcf7426", + "0xa4471d111db4418f56915689482f6144efc4664cfb0311727f36c864648d35734351becc48875df96f4abd3cfcf820f9", + "0x83be11727cd30ea94ccc8fa31b09b81c9d6a9a5d3a4686af9da99587332fe78c1f94282f9755854bafd6033549afec91", + "0x88020ff971dc1a01a9e993cd50a5d2131ffdcbb990c1a6aaa54b20d8f23f9546a70918ea57a21530dcc440c1509c24ad", + "0xae24547623465e87905eaffa1fa5d52bb7c453a8dbd89614fa8819a2abcedaf455c2345099b7324ae36eb0ad7c8ef977", + "0xb59b0c60997de1ee00b7c388bc7101d136c9803bf5437b1d589ba57c213f4f835a3e4125b54738e78abbc21b000f2016", + "0xa584c434dfe194546526691b68fa968c831c31da42303a1d735d960901c74011d522246f37f299555416b8cf25c5a548", + "0x80408ce3724f4837d4d52376d255e10f69eb8558399ae5ca6c11b78b98fe67d4b93157d2b9b639f1b5b64198bfe87713", + "0xabb941e8d406c2606e0ddc35c113604fdd9d249eacc51cb64e2991e551b8639ce44d288cc92afa7a1e7fc599cfc84b22", + "0xb223173f560cacb1c21dba0f1713839e348ad02cbfdef0626748604c86f89e0f4c919ed40b583343795bdd519ba952c8", + "0xaf1c70512ec3a19d98b8a1fc3ff7f7f5048a27d17d438d43f561974bbdd116fcd5d5c21040f3447af3f0266848d47a15", + "0x8a44809568ebe50405bede19b4d2607199159b26a1b33e03d180e6840c5cf59d991a4fb150d111443235d75ecad085b7", + "0xb06207cdca46b125a27b3221b5b50cf27af4c527dd7c80e2dbcebbb09778a96df3af67e50f07725239ce3583dad60660", + "0x993352d9278814ec89b26a11c4a7c4941bf8f0e6781ae79559d14749ee5def672259792db4587f85f0100c7bb812f933", + "0x9180b8a718b971fd27bc82c8582d19c4b4f012453e8c0ffeeeffe745581fc6c07875ab28be3af3fa3896d19f0c89ac5b", + "0x8b8e1263eb48d0fe304032dd5ea1f30e73f0121265f7458ba9054d3626894e8a5fef665340abd2ede9653045c2665938", + "0x99a2beee4a10b7941c24b2092192faf52b819afd033e4a2de050fd6c7f56d364d0cf5f99764c3357cf32399e60fc5d74", + "0x946a4aad7f8647ea60bee2c5fcdeb6f9a58fb2cfca70c4d10e458027a04846e13798c66506151be3df9454b1e417893f", + "0xa672a88847652d260b5472d6908d1d57e200f1e492d30dd1cecc441cdfc9b76e016d9bab560efd4d7f3c30801de884a9", + "0x9414e1959c156cde1eb24e628395744db75fc24b9df4595350aaad0bc38e0246c9b4148f6443ef68b8e253a4a6bcf11c", + "0x9316e9e4ec5fab4f80d6540df0e3a4774db52f1d759d2e5b5bcd3d7b53597bb007eb1887cb7dc61f62497d51ffc8d996", + "0x902d6d77bb49492c7a00bc4b70277bc28c8bf9888f4307bb017ac75a962decdedf3a4e2cf6c1ea9f9ba551f4610cbbd7", + "0xb07025a18b0e32dd5e12ec6a85781aa3554329ea12c4cd0d3b2c22e43d777ef6f89876dd90a9c8fb097ddf61cf18adc5", + "0xb355a849ad3227caa4476759137e813505ec523cbc2d4105bc7148a4630f9e81918d110479a2d5f5e4cd9ccec9d9d3e3", + "0xb49532cfdf02ee760109881ad030b89c48ee3bb7f219ccafc13c93aead754d29bdafe345be54c482e9d5672bd4505080", + "0x9477802410e263e4f938d57fa8f2a6cac7754c5d38505b73ee35ea3f057aad958cb9722ba6b7b3cfc4524e9ca93f9cdc", + "0x9148ea83b4436339580f3dbc9ba51509e9ab13c03063587a57e125432dd0915f5d2a8f456a68f8fff57d5f08c8f34d6e", + "0xb00b6b5392b1930b54352c02b1b3b4f6186d20bf21698689bbfc7d13e86538a4397b90e9d5c93fd2054640c4dbe52a4f", + "0x926a9702500441243cd446e7cbf15dde16400259726794694b1d9a40263a9fc9e12f7bcbf12a27cb9aaba9e2d5848ddc", + "0xa0c6155f42686cbe7684a1dc327100962e13bafcf3db97971fc116d9f5c0c8355377e3d70979cdbd58fd3ea52440901c", + "0xa277f899f99edb8791889d0817ea6a96c24a61acfda3ad8c3379e7c62b9d4facc4b965020b588651672fd261a77f1bfc", + "0x8f528cebb866b501f91afa50e995234bef5bf20bff13005de99cb51eaac7b4f0bf38580cfd0470de40f577ead5d9ba0f", + "0x963fc03a44e9d502cc1d23250efef44d299befd03b898d07ce63ca607bb474b5cf7c965a7b9b0f32198b04a8393821f7", + "0xab087438d0a51078c378bf4a93bd48ef933ff0f1fa68d02d4460820df564e6642a663b5e50a5fe509527d55cb510ae04", + "0xb0592e1f2c54746bb076be0fa480e1c4bebc4225e1236bcda3b299aa3853e3afb401233bdbcfc4a007b0523a720fbf62", + "0x851613517966de76c1c55a94dc4595f299398a9808f2d2f0a84330ba657ab1f357701d0895f658c18a44cb00547f6f57", + "0xa2fe9a1dd251e72b0fe4db27be508bb55208f8f1616b13d8be288363ec722826b1a1fd729fc561c3369bf13950bf1fd6", + "0xb896cb2bc2d0c77739853bc59b0f89b2e008ba1f701c9cbe3bef035f499e1baee8f0ff1e794854a48c320586a2dfc81a", + "0xa1b60f98e5e5106785a9b81a85423452ee9ef980fa7fa8464f4366e73f89c50435a0c37b2906052b8e58e212ebd366cf", + "0xa853b0ebd9609656636df2e6acd5d8839c0fda56f7bf9288a943b06f0b67901a32b95e016ca8bc99bd7b5eab31347e72", + "0xb290fa4c1346963bd5225235e6bdf7c542174dab4c908ab483d1745b9b3a6015525e398e1761c90e4b49968d05e30eea", + "0xb0f65a33ad18f154f1351f07879a183ad62e5144ad9f3241c2d06533dad09cbb2253949daff1bb02d24d16a3569f7ef0", + "0xa00db59b8d4218faf5aeafcd39231027324408f208ec1f54d55a1c41228b463b88304d909d16b718cfc784213917b71e", + "0xb8d695dd33dc2c3bc73d98248c535b2770ad7fa31aa726f0aa4b3299efb0295ba9b4a51c71d314a4a1bd5872307534d1", + "0xb848057cca2ca837ee49c42b88422303e58ea7d2fc76535260eb5bd609255e430514e927cc188324faa8e657396d63ec", + "0x92677836061364685c2aaf0313fa32322746074ed5666fd5f142a7e8f87135f45cd10e78a17557a4067a51dfde890371", + "0xa854b22c9056a3a24ab164a53e5c5cf388616c33e67d8ebb4590cb16b2e7d88b54b1393c93760d154208b5ca822dc68f", + "0x86fff174920388bfab841118fb076b2b0cdec3fdb6c3d9a476262f82689fb0ed3f1897f7be9dbf0932bb14d346815c63", + "0x99661cf4c94a74e182752bcc4b98a8c2218a8f2765642025048e12e88ba776f14f7be73a2d79bd21a61def757f47f904", + "0x8a8893144d771dca28760cba0f950a5d634195fd401ec8cf1145146286caffb0b1a6ba0c4c1828d0a5480ce49073c64c", + "0x938a59ae761359ee2688571e7b7d54692848eb5dde57ffc572b473001ea199786886f8c6346a226209484afb61d2e526", + "0x923f68a6aa6616714cf077cf548aeb845bfdd78f2f6851d8148cba9e33a374017f2f3da186c39b82d14785a093313222", + "0xac923a93d7da7013e73ce8b4a2b14b8fd0cc93dc29d5de941a70285bdd19be4740fedfe0c56b046689252a3696e9c5bc", + "0xb49b32c76d4ec1a2c68d4989285a920a805993bc6fcce6dacd3d2ddae73373050a5c44ba8422a3781050682fa0ef6ba2", + "0x8a367941c07c3bdca5712524a1411bad7945c7c48ffc7103b1d4dff2c25751b0624219d1ccde8c3f70c465f954be5445", + "0xb838f029df455efb6c530d0e370bbbf7d87d61a9aea3d2fe5474c5fe0a39cf235ceecf9693c5c6c5820b1ba8f820bd31", + "0xa8983b7c715eaac7f13a001d2abc462dfc1559dab4a6b554119c271aa8fe00ffcf6b6949a1121f324d6d26cb877bcbae", + "0xa2afb24ad95a6f14a6796315fbe0d8d7700d08f0cfaf7a2abe841f5f18d4fecf094406cbd54da7232a159f9c5b6e805e", + "0x87e8e95ad2d62f947b2766ff405a23f7a8afba14e7f718a691d95369c79955cdebe24c54662553c60a3f55e6322c0f6f", + "0x87c2cbcecb754e0cc96128e707e5c5005c9de07ffd899efa3437cadc23362f5a1d3fcdd30a1f5bdc72af3fb594398c2a", + "0x91afd6ee04f0496dc633db88b9370d41c428b04fd991002502da2e9a0ef051bcd7b760e860829a44fbe5539fa65f8525", + "0x8c50e5d1a24515a9dd624fe08b12223a75ca55196f769f24748686315329b337efadca1c63f88bee0ac292dd0a587440", + "0x8a07e8f912a38d94309f317c32068e87f68f51bdfa082d96026f5f5f8a2211621f8a3856dda8069386bf15fb2d28c18f", + "0x94ad1dbe341c44eeaf4dc133eed47d8dbfe752575e836c075745770a6679ff1f0e7883b6aa917462993a7f469d74cab5", + "0x8745f8bd86c2bb30efa7efb7725489f2654f3e1ac4ea95bd7ad0f3cfa223055d06c187a16192d9d7bdaea7b050c6a324", + "0x900d149c8d79418cda5955974c450a70845e02e5a4ecbcc584a3ca64d237df73987c303e3eeb79da1af83bf62d9e579f", + "0x8f652ab565f677fb1a7ba03b08004e3cda06b86c6f1b0b9ab932e0834acf1370abb2914c15b0d08327b5504e5990681c", + "0x9103097d088be1f75ab9d3da879106c2f597e2cc91ec31e73430647bdd5c33bcfd771530d5521e7e14df6acda44f38a6", + "0xb0fec7791cfb0f96e60601e1aeced9a92446b61fedab832539d1d1037558612d78419efa87ff5f6b7aab8fd697d4d9de", + "0xb9d2945bdb188b98958854ba287eb0480ef614199c4235ce5f15fc670b8c5ffe8eeb120c09c53ea8a543a022e6a321ac", + "0xa9461bb7d5490973ebaa51afc0bb4a5e42acdccb80e2f939e88b77ac28a98870e103e1042899750f8667a8cc9123bae9", + "0xa37fdf11d4bcb2aed74b9f460a30aa34afea93386fa4cdb690f0a71bc58f0b8df60bec56e7a24f225978b862626fa00e", + "0xa214420e183e03d531cf91661466ea2187d84b6e814b8b20b3730a9400a7d25cf23181bb85589ebc982cec414f5c2923", + "0xad09a45a698a6beb3e0915f540ef16e9af7087f53328972532d6b5dfe98ce4020555ece65c6cbad8bd6be8a4dfefe6fd", + "0xab6742800b02728c92d806976764cb027413d6f86edd08ad8bb5922a2969ee9836878cd39db70db0bd9a2646862acc4f", + "0x974ca9305bd5ea1dc1755dff3b63e8bfe9f744321046c1395659bcea2a987b528e64d5aa96ac7b015650b2253b37888d", + "0x84eee9d6bce039c52c2ebc4fccc0ad70e20c82f47c558098da4be2f386a493cbc76adc795b5488c8d11b6518c2c4fab8", + "0x875d7bda46efcb63944e1ccf760a20144df3b00d53282b781e95f12bfc8f8316dfe6492c2efbf796f1150e36e436e9df", + "0xb68a2208e0c587b5c31b5f6cb32d3e6058a9642e2d9855da4f85566e1412db528475892060bb932c55b3a80877ad7b4a", + "0xba006368ecab5febb6ab348644d9b63de202293085ed468df8bc24d992ae8ce468470aa37f36a73630c789fb9c819b30", + "0x90a196035150846cd2b482c7b17027471372a8ce7d914c4d82b6ea7fa705d8ed5817bd42d63886242585baf7d1397a1c", + "0xa223b4c85e0daa8434b015fd9170b5561fe676664b67064974a1e9325066ecf88fc81f97ab5011c59fad28cedd04b240", + "0x82e8ec43139cf15c6bbeed484b62e06cded8a39b5ce0389e4cbe9c9e9c02f2f0275d8d8d4e8dfec8f69a191bef220408", + "0x81a3fc07a7b68d92c6ee4b6d28f5653ee9ec85f7e2ee1c51c075c1b130a8c5097dc661cf10c5aff1c7114b1a6a19f11a", + "0x8ed2ef8331546d98819a5dd0e6c9f8cb2630d0847671314a28f277faf68da080b53891dd75c82cbcf7788b255490785d", + "0xacecabf84a6f9bbed6b2fc2e7e4b48f02ef2f15e597538a73aea8f98addc6badda15e4695a67ecdb505c1554e8f345ec", + "0xb8f51019b2aa575f8476e03dcadf86cc8391f007e5f922c2a36b2daa63f5a503646a468990cd5c65148d323942193051", + "0xaaa595a84b403ec65729bc1c8055a94f874bf9adddc6c507b3e1f24f79d3ad359595a672b93aab3394db4e2d4a7d8970", + "0x895144c55fcbd0f64d7dd69e6855cfb956e02b5658eadf0f026a70703f3643037268fdd673b0d21b288578a83c6338dd", + "0xa2e92ae6d0d237d1274259a8f99d4ea4912a299816350b876fba5ebc60b714490e198a916e1c38c6e020a792496fa23c", + "0xa45795fda3b5bb0ad1d3c628f6add5b2a4473a1414c1a232e80e70d1cfffd7f8a8d9861f8df2946999d7dbb56bf60113", + "0xb6659bf7f6f2fef61c39923e8c23b8c70e9c903028d8f62516d16755cd3fba2fe41c285aa9432dc75ab08f8a1d8a81fc", + "0xa735609a6bc5bfd85e58234fc439ff1f58f1ff1dd966c5921d8b649e21f006bf2b8642ad8a75063c159aaf6935789293", + "0xa3c622eb387c9d15e7bda2e3e84d007cb13a6d50d655c3f2f289758e49d3b37b9a35e4535d3cc53d8efd51f407281f19", + "0x8afe147b53ad99220f5ef9d763bfc91f9c20caecbcf823564236fb0e6ede49414c57d71eec4772c8715cc65a81af0047", + "0xb5f0203233cf71913951e9c9c4e10d9243e3e4a1f2cb235bf3f42009120ba96e04aa414c9938ea8873b63148478927e8", + "0x93c52493361b458d196172d7ba982a90a4f79f03aa8008edc322950de3ce6acf4c3977807a2ffa9e924047e02072b229", + "0xb9e72b805c8ac56503f4a86c82720afbd5c73654408a22a2ac0b2e5caccdfb0e20b59807433a6233bc97ae58cf14c70a", + "0xaf0475779b5cee278cca14c82da2a9f9c8ef222eb885e8c50cca2315fea420de6e04146590ed0dd5a29c0e0812964df5", + "0xb430ccab85690db02c2d0eb610f3197884ca12bc5f23c51e282bf3a6aa7e4a79222c3d8761454caf55d6c01a327595f9", + "0x830032937418b26ee6da9b5206f3e24dc76acd98589e37937e963a8333e5430abd6ce3dd93ef4b8997bd41440eed75d6", + "0x8820a6d73180f3fe255199f3f175c5eb770461ad5cfdde2fb11508041ed19b8c4ce66ad6ecebf7d7e836cc2318df47ca", + "0xaef1393e7d97278e77bbf52ef6e1c1d5db721ccf75fe753cf47a881fa034ca61eaa5098ee5a344c156d2b14ff9e284ad", + "0x8a4a26c07218948c1196c45d927ef4d2c42ade5e29fe7a91eaebe34a29900072ce5194cf28d51f746f4c4c649daf4396", + "0x84011dc150b7177abdcb715efbd8c201f9cb39c36e6069af5c50a096021768ba40cef45b659c70915af209f904ede3b6", + "0xb1bd90675411389bb66910b21a4bbb50edce5330850c5ab0b682393950124252766fc81f5ecfc72fb7184387238c402e", + "0x8dfdcd30583b696d2c7744655f79809f451a60c9ad5bf1226dc078b19f4585d7b3ef7fa9d54e1ac09520d95cbfd20928", + "0xb351b4dc6d98f75b8e5a48eb7c6f6e4b78451991c9ba630e5a1b9874c15ac450cd409c1a024713bf2cf82dc400e025ef", + "0xa462b8bc97ac668b97b28b3ae24b9f5de60e098d7b23ecb600d2194cd35827fb79f77c3e50d358f5bd72ee83fef18fa0", + "0xa183753265c5f7890270821880cce5f9b2965b115ba783c6dba9769536f57a04465d7da5049c7cf8b3fcf48146173c18", + "0xa8a771b81ed0d09e0da4d79f990e58eabcd2be3a2680419502dd592783fe52f657fe55125b385c41d0ba3b9b9cf54a83", + "0xa71ec577db46011689d073245e3b1c3222a9b1fe6aa5b83629adec5733dd48617ebea91346f0dd0e6cdaa86e4931b168", + "0xa334b8b244f0d598a02da6ae0f918a7857a54dce928376c4c85df15f3b0f2ba3ac321296b8b7c9dd47d770daf16c8f8c", + "0xa29037f8ef925c417c90c4df4f9fb27fb977d04e2b3dd5e8547d33e92ab72e7a00f5461de21e28835319eae5db145eb7", + "0xb91054108ae78b00e3298d667b913ebc44d8f26e531eae78a8fe26fdfb60271c97efb2dee5f47ef5a3c15c8228138927", + "0x926c13efbe90604f6244be9315a34f72a1f8d1aab7572df431998949c378cddbf2fe393502c930fff614ff06ae98a0ce", + "0x995c758fd5600e6537089b1baa4fbe0376ab274ff3e82a17768b40df6f91c2e443411de9cafa1e65ea88fb8b87d504f4", + "0x9245ba307a7a90847da75fca8d77ec03fdfc812c871e7a2529c56a0a79a6de16084258e7a9ac4ae8a3756f394336e21c", + "0x99e0cfa2bb57a7e624231317044c15e52196ecce020db567c8e8cb960354a0be9862ee0c128c60b44777e65ac315e59f", + "0xad4f6b3d27bbbb744126601053c3dc98c07ff0eb0b38a898bd80dce778372846d67e5ab8fb34fb3ad0ef3f235d77ba7f", + "0xa0f12cae3722bbbca2e539eb9cc7614632a2aefe51410430070a12b5bc5314ecec5857b7ff8f41e9980cac23064f7c56", + "0xb487f1bc59485848c98222fd3bc36c8c9bb3d2912e2911f4ceca32c840a7921477f9b1fe00877e05c96c75d3eecae061", + "0xa6033db53925654e18ecb3ce715715c36165d7035db9397087ac3a0585e587998a53973d011ac6d48af439493029cee6", + "0xa6b4d09cd01c70a3311fd131d3710ccf97bde3e7b80efd5a8c0eaeffeb48cca0f951ced905290267b115b06d46f2693b", + "0xa9dff1df0a8f4f218a98b6f818a693fb0d611fed0fc3143537cbd6578d479af13a653a8155e535548a2a0628ae24fa58", + "0xa58e469f65d366b519f9a394cacb7edaddac214463b7b6d62c2dbc1316e11c6c5184ce45c16de2d77f990dcdd8b55430", + "0x989e71734f8119103586dc9a3c5f5033ddc815a21018b34c1f876cdfc112efa868d5751bf6419323e4e59fa6a03ece1c", + "0xa2da00e05036c884369e04cf55f3de7d659cd5fa3f849092b2519dd263694efe0f051953d9d94b7e121f0aee8b6174d7", + "0x968f3c029f57ee31c4e1adea89a7f92e28483af9a74f30fbdb995dc2d40e8e657dff8f8d340d4a92bf65f54440f2859f", + "0x932778df6f60ac1639c1453ef0cbd2bf67592759dcccb3e96dcc743ff01679e4c7dd0ef2b0833dda548d32cb4eba49e2", + "0xa805a31139f8e0d6dae1ac87d454b23a3dc9fc653d4ca18d4f8ebab30fc189c16e73981c2cb7dd6f8c30454a5208109d", + "0xa9ba0991296caa2aaa4a1ceacfb205544c2a2ec97088eace1d84ee5e2767656a172f75d2f0c4e16a3640a0e0dec316e0", + "0xb1e49055c968dced47ec95ae934cf45023836d180702e20e2df57e0f62fb85d7ac60d657ba3ae13b8560b67210449459", + "0xa94e1da570a38809c71e37571066acabff7bf5632737c9ab6e4a32856924bf6211139ab3cedbf083850ff2d0e0c0fcfc", + "0x88ef1bb322000c5a5515b310c838c9af4c1cdbb32eab1c83ac3b2283191cd40e9573747d663763a28dad0d64adc13840", + "0xa987ce205f923100df0fbd5a85f22c9b99b9b9cbe6ddfa8dfda1b8fe95b4f71ff01d6c5b64ca02eb24edb2b255a14ef0", + "0x84fe8221a9e95d9178359918a108de4763ebfa7a6487facb9c963406882a08a9a93f492f8e77cf9e7ea41ae079c45993", + "0xaa1cf3dc7c5dcfa15bbbc811a4bb6dbac4fba4f97fb1ed344ab60264d7051f6eef19ea9773441d89929ee942ed089319", + "0x8f6a7d610d59d9f54689bbe6a41f92d9f6096cde919c1ab94c3c7fcecf0851423bc191e5612349e10f855121c0570f56", + "0xb5af1fa7894428a53ea520f260f3dc3726da245026b6d5d240625380bfb9c7c186df0204bb604efac5e613a70af5106e", + "0xa5bce6055ff812e72ce105f147147c7d48d7a2313884dd1f488b1240ee320f13e8a33f5441953a8e7a3209f65b673ce1", + "0xb9b55b4a1422677d95821e1d042ab81bbf0bf087496504021ec2e17e238c2ca6b44fb3b635a5c9eac0871a724b8d47c3", + "0x941c38e533ce4a673a3830845b56786585e5fe49c427f2e5c279fc6db08530c8f91db3e6c7822ec6bb4f956940052d18", + "0xa38e191d66c625f975313c7007bbe7431b5a06ed2da1290a7d5d0f2ec73770d476efd07b8e632de64597d47df175cbb0", + "0x94ba76b667abf055621db4c4145d18743a368d951565632ed4e743dd50dd3333507c0c34f286a5c5fdbf38191a2255cd", + "0xa5ca38c60be5602f2bfa6e00c687ac96ac36d517145018ddbee6f12eb0faa63dd57909b9eeed26085fe5ac44e55d10ab", + "0xb00fea3b825e60c1ed1c5deb4b551aa65a340e5af36b17d5262c9cd2c508711e4dc50dc2521a2c16c7c901902266e64a", + "0x971b86fc4033485e235ccb0997a236206ba25c6859075edbcdf3c943116a5030b7f75ebca9753d863a522ba21a215a90", + "0xb3b31f52370de246ee215400975b674f6da39b2f32514fe6bd54e747752eedca22bb840493b44a67df42a3639c5f901f", + "0xaffbbfac9c1ba7cbfa1839d2ae271dd6149869b75790bf103230637da41857fc326ef3552ff31c15bda0694080198143", + "0xa95d42aa7ef1962520845aa3688f2752d291926f7b0d73ea2ee24f0612c03b43f2b0fe3c9a9a99620ffc8d487b981bc2", + "0x914a266065caf64985e8c5b1cb2e3f4e3fe94d7d085a1881b1fefa435afef4e1b39a98551d096a62e4f5cc1a7f0fdc2e", + "0x81a0b4a96e2b75bc1bf2dbd165d58d55cfd259000a35504d1ffb18bc346a3e6f07602c683723864ffb980f840836fd8d", + "0x91c1556631cddd4c00b65b67962b39e4a33429029d311c8acf73a18600e362304fb68bccb56fde40f49e95b7829e0b87", + "0x8befbacc19e57f7c885d1b7a6028359eb3d80792fe13b92a8400df21ce48deb0bb60f2ddb50e3d74f39f85d7eab23adc", + "0x92f9458d674df6e990789690ec9ca73dacb67fc9255b58c417c555a8cc1208ace56e8e538f86ba0f3615573a0fbac00d", + "0xb4b1b3062512d6ae7417850c08c13f707d5838e43d48eb98dd4621baf62eee9e82348f80fe9b888a12874bfa538771f8", + "0xa13c4a3ac642ede37d9c883f5319e748d2b938f708c9d779714108a449b343f7b71a6e3ef4080fee125b416762920273", + "0xaf44983d5fc8cceee0551ef934e6e653f2d3efa385e5c8a27a272463a6f333e290378cc307c2b664eb923c78994e706e", + "0xa389fd6c59fe2b4031cc244e22d3991e541bd203dd5b5e73a6159e72df1ab41d49994961500dcde7989e945213184778", + "0x8d2141e4a17836c548de9598d7b298b03f0e6c73b7364979a411c464e0628e21cff6ac3d6decdba5d1c4909eff479761", + "0x980b22ef53b7bdf188a3f14bc51b0dbfdf9c758826daa3cbc1e3986022406a8aa9a6a79e400567120b88c67faa35ce5f", + "0xa28882f0a055f96df3711de5d0aa69473e71245f4f3e9aa944e9d1fb166e02caa50832e46da6d3a03b4801735fd01b29", + "0x8db106a37d7b88f5d995c126abb563934dd8de516af48e85695d02b1aea07f79217e3cdd03c6f5ca57421830186c772b", + "0xb5a7e50da0559a675c472f7dfaee456caab6695ab7870541b2be8c2b118c63752427184aad81f0e1afc61aef1f28c46f", + "0x9962118780e20fe291d10b64f28d09442a8e1b5cffd0f3dd68d980d0614050a626c616b44e9807fbee7accecae00686a", + "0xb38ddf33745e8d2ad6a991aefaf656a33c5f8cbe5d5b6b6fd03bd962153d8fd0e01b5f8f96d80ae53ab28d593ab1d4e7", + "0x857dc12c0544ff2c0c703761d901aba636415dee45618aba2e3454ff9cbc634a85c8b05565e88520ff9be2d097c8b2b1", + "0xa80d465c3f8cc63af6d74a6a5086b626c1cb4a8c0fee425964c3bd203d9d7094e299f81ce96d58afc20c8c9a029d9dae", + "0x89e1c8fbde8563763be483123a3ed702efac189c6d8ab4d16c85e74bbaf856048cc42d5d6e138633a38572ba5ec3f594", + "0x893a594cf495535f6d216508f8d03c317dcf03446668cba688da90f52d0111ac83d76ad09bf5ea47056846585ee5c791", + "0xaadbd8be0ae452f7f9450c7d2957598a20cbf10139a4023a78b4438172d62b18b0de39754dd2f8862dbd50a3a0815e53", + "0xae7d39670ecca3eb6db2095da2517a581b0e8853bdfef619b1fad9aacd443e7e6a40f18209fadd44038a55085c5fe8b2", + "0x866ef241520eacb6331593cfcb206f7409d2f33d04542e6e52cba5447934e02d44c471f6c9a45963f9307e9809ab91d9", + "0xb1a09911ad3864678f7be79a9c3c3eb5c84a0a45f8dcb52c67148f43439aeaaa9fd3ed3471276b7e588b49d6ebe3033a", + "0xadd07b7f0dbb34049cd8feeb3c18da5944bf706871cfd9f14ff72f6c59ad217ebb1f0258b13b167851929387e4e34cfe", + "0xae048892d5c328eefbdd4fba67d95901e3c14d974bfc0a1fc68155ca9f0d59e61d7ba17c6c9948b120cf35fd26e6fee9", + "0x9185b4f3b7da0ddb4e0d0f09b8a9e0d6943a4611e43f13c3e2a767ed8592d31e0ba3ebe1914026a3627680274291f6e5", + "0xa9c022d4e37b0802284ce3b7ee9258628ab4044f0db4de53d1c3efba9de19d15d65cc5e608dbe149c21c2af47d0b07b5", + "0xb24dbd5852f8f24921a4e27013b6c3fa8885b973266cb839b9c388efad95821d5d746348179dcc07542bd0d0aefad1ce", + "0xb5fb4f279300876a539a27a441348764908bc0051ebd66dc51739807305e73db3d2f6f0f294ffb91b508ab150eaf8527", + "0xace50841e718265b290c3483ed4b0fdd1175338c5f1f7530ae9a0e75d5f80216f4de37536adcbc8d8c95982e88808cd0", + "0xb19cadcde0f63bd1a9c24bd9c2806f53c14c0b9735bf351601498408ba503ddbd2037c891041cbba47f58b8c483f3b21", + "0xb6061e63558d312eb891b97b39aa552fa218568d79ee26fe6dd5b864aea9e3216d8f2e2f3b093503be274766dac41426", + "0x89730fdb2876ab6f0fe780d695f6e12090259027e789b819956d786e977518057e5d1d7f5ab24a3ae3d5d4c97773bd2b", + "0xb6fa841e81f9f2cad0163a02a63ae96dc341f7ae803b616efc6e1da2fbea551c1b96b11ad02c4afbdf6d0cc9f23da172", + "0x8fb66187182629c861ddb6896d7ed3caf2ad050c3dba8ab8eb0d7a2c924c3d44c48d1a148f9e33fb1f061b86972f8d21", + "0x86022ac339c1f84a7fa9e05358c1a5b316b4fc0b83dbe9c8c7225dc514f709d66490b539359b084ce776e301024345fa", + "0xb50b9c321468da950f01480bb62b6edafd42f83c0001d6e97f2bd523a1c49a0e8574fb66380ea28d23a7c4d54784f9f0", + "0xa31c05f7032f30d1dac06678be64d0250a071fd655e557400e4a7f4c152be4d5c7aa32529baf3e5be7c4bd49820054f6", + "0xb95ac0848cd322684772119f5b682d90a66bbf9dac411d9d86d2c34844bbd944dbaf8e47aa41380455abd51687931a78", + "0xae4a6a5ce9553b65a05f7935e61e496a4a0f6fd8203367a2c627394c9ce1e280750297b74cdc48fd1d9a31e93f97bef4", + "0xa22daf35f6e9b05e52e0b07f7bd1dbbebd2c263033fb0e1b2c804e2d964e2f11bc0ece6aca6af079dd3a9939c9c80674", + "0x902150e0cb1f16b9b59690db35281e28998ce275acb313900da8b2d8dfd29fa1795f8ca3ff820c31d0697de29df347c1", + "0xb17b5104a5dc665cdd7d47e476153d715eb78c6e5199303e4b5445c21a7fa7cf85fe7cfd08d7570f4e84e579b005428c", + "0xa03f49b81c15433f121680aa02d734bb9e363af2156654a62bcb5b2ba2218398ccb0ff61104ea5d7df5b16ea18623b1e", + "0x802101abd5d3c88876e75a27ffc2f9ddcce75e6b24f23dba03e5201281a7bd5cc7530b6a003be92d225093ca17d3c3bb", + "0xa4d183f63c1b4521a6b52226fc19106158fc8ea402461a5cccdaa35fee93669df6a8661f45c1750cd01308149b7bf08e", + "0x8d17c22e0c8403b69736364d460b3014775c591032604413d20a5096a94d4030d7c50b9fe3240e31d0311efcf9816a47", + "0x947225acfcce5992eab96276f668c3cbe5f298b90a59f2bb213be9997d8850919e8f496f182689b5cbd54084a7332482", + "0x8df6f4ed216fc8d1905e06163ba1c90d336ab991a18564b0169623eb39b84e627fa267397da15d3ed754d1f3423bff07", + "0x83480007a88f1a36dea464c32b849a3a999316044f12281e2e1c25f07d495f9b1710b4ba0d88e9560e72433addd50bc2", + "0xb3019d6e591cf5b33eb972e49e06c6d0a82a73a75d78d383dd6f6a4269838289e6e07c245f54fed67f5c9bb0fd5e1c5f", + "0x92e8ce05e94927a9fb02debadb99cf30a26172b2705003a2c0c47b3d8002bf1060edb0f6a5750aad827c98a656b19199", + "0xac2aff801448dbbfc13cca7d603fd9c69e82100d997faf11f465323b97255504f10c0c77401e4d1890339d8b224f5803", + "0xb0453d9903d08f508ee27e577445dc098baed6cde0ac984b42e0f0efed62760bd58d5816cf1e109d204607b7b175e30c", + "0xae68dc4ba5067e825d46d2c7c67f1009ceb49d68e8d3e4c57f4bcd299eb2de3575d42ea45e8722f8f28497a6e14a1cfe", + "0xb22486c2f5b51d72335ce819bbafb7fa25eb1c28a378a658f13f9fc79cd20083a7e573248d911231b45a5cf23b561ca7", + "0x89d1201d1dbd6921867341471488b4d2fd0fc773ae1d4d074c78ae2eb779a59b64c00452c2a0255826fca6b3d03be2b1", + "0xa2998977c91c7a53dc6104f5bc0a5b675e5350f835e2f0af69825db8af4aeb68435bdbcc795f3dd1f55e1dd50bc0507f", + "0xb0be4937a925b3c05056ed621910d535ccabf5ab99fd3b9335080b0e51d9607d0fd36cb5781ff340018f6acfca4a9736", + "0xaea145a0f6e0ba9df8e52e84bb9c9de2c2dc822f70d2724029b153eb68ee9c17de7d35063dcd6a39c37c59fdd12138f7", + "0x91cb4545d7165ee8ffbc74c874baceca11fdebbc7387908d1a25877ca3c57f2c5def424dab24148826832f1e880bede0", + "0xb3b579cb77573f19c571ad5eeeb21f65548d7dff9d298b8d7418c11f3e8cd3727c5b467f013cb87d6861cfaceee0d2e3", + "0xb98a1eeec2b19fecc8378c876d73645aa52fb99e4819903735b2c7a885b242787a30d1269a04bfb8573d72d9bbc5f0f0", + "0x940c1f01ed362bd588b950c27f8cc1d52276c71bb153d47f07ec85b038c11d9a8424b7904f424423e714454d5e80d1cd", + "0xaa343a8ecf09ce11599b8cf22f7279cf80f06dbf9f6d62cb05308dbbb39c46fd0a4a1240b032665fbb488a767379b91b", + "0x87c3ac72084aca5974599d3232e11d416348719e08443acaba2b328923af945031f86432e170dcdd103774ec92e988c9", + "0x91d6486eb5e61d2b9a9e742c20ec974a47627c6096b3da56209c2b4e4757f007e793ebb63b2b246857c9839b64dc0233", + "0xaebcd3257d295747dd6fc4ff910d839dd80c51c173ae59b8b2ec937747c2072fa85e3017f9060aa509af88dfc7529481", + "0xb3075ba6668ca04eff19efbfa3356b92f0ab12632dcda99cf8c655f35b7928c304218e0f9799d68ef9f809a1492ff7db", + "0x93ba7468bb325639ec2abd4d55179c69fd04eaaf39fc5340709227bbaa4ad0a54ea8b480a1a3c8d44684e3be0f8d1980", + "0xa6aef86c8c0d92839f38544d91b767c582568b391071228ff5a5a6b859c87bf4f81a7d926094a4ada1993ddbd677a920", + "0x91dcd6d14207aa569194aa224d1e5037b999b69ade52843315ca61ba26abe9a76412c9e88259bc5cf5d7b95b97d9c3bc", + "0xb3b483d31c88f78d49bd065893bc1e3d2aa637e27dedb46d9a7d60be7660ce7a10aaaa7deead362284a52e6d14021178", + "0x8e5730070acf8371461ef301cc4523e8e672aa0e3d945d438a0e0aa6bdf8cb9c685dcf38df429037b0c8aff3955c6f5b", + "0xb8c6d769890a8ee18dc4f9e917993315877c97549549b34785a92543cbeec96a08ae3a28d6e809c4aacd69de356c0012", + "0x95ca86cd384eaceaa7c077c5615736ca31f36824bd6451a16142a1edc129fa42b50724aeed7c738f08d7b157f78b569e", + "0x94df609c6d71e8eee7ab74226e371ccc77e01738fe0ef1a6424435b4570fe1e5d15797b66ed0f64eb88d4a3a37631f0e", + "0x89057b9783212add6a0690d6bb99097b182738deff2bd9e147d7fd7d6c8eacb4c219923633e6309ad993c24572289901", + "0x83a0f9f5f265c5a0e54defa87128240235e24498f20965009fef664f505a360b6fb4020f2742565dfc7746eb185bcec0", + "0x91170da5306128931349bc3ed50d7df0e48a68b8cc8420975170723ac79d8773e4fa13c5f14dc6e3fafcad78379050b1", + "0xb7178484d1b55f7e56a4cc250b6b2ec6040437d96bdfddfa7b35ed27435860f3855c2eb86c636f2911b012eb83b00db8", + "0xac0b00c4322d1e4208e09cd977b4e54d221133ff09551f75b32b0b55d0e2be80941dda26257b0e288c162e63c7e9cf68", + "0x9690ed9e7e53ed37ff362930e4096b878b12234c332fd19d5d064824084245952eda9f979e0098110d6963e468cf513e", + "0xb6fa547bb0bb83e5c5be0ed462a8783fba119041c136a250045c09d0d2af330c604331e7de960df976ff76d67f8000cd", + "0x814603907c21463bcf4e59cfb43066dfe1a50344ae04ef03c87c0f61b30836c3f4dea0851d6fa358c620045b7f9214c8", + "0x9495639e3939fad2a3df00a88603a5a180f3c3a0fe4d424c35060e2043e0921788003689887b1ed5be424d9a89bb18bb", + "0xaba4c02d8d57f2c92d5bc765885849e9ff8393d6554f5e5f3e907e5bfac041193a0d8716d7861104a4295d5a03c36b03", + "0x8ead0b56c1ca49723f94a998ba113b9058059321da72d9e395a667e6a63d5a9dac0f5717cec343f021695e8ced1f72af", + "0xb43037f7e3852c34ed918c5854cd74e9d5799eeddfe457d4f93bb494801a064735e326a76e1f5e50a339844a2f4a8ec9", + "0x99db8422bb7302199eb0ff3c3d08821f8c32f53a600c5b6fb43e41205d96adae72be5b460773d1280ad1acb806af9be8", + "0x8a9be08eae0086c0f020838925984df345c5512ff32e37120b644512b1d9d4fecf0fd30639ca90fc6cf334a86770d536", + "0x81b43614f1c28aa3713a309a88a782fb2bdfc4261dd52ddc204687791a40cf5fd6a263a8179388596582cccf0162efc2", + "0xa9f3a8b76912deb61d966c75daf5ddb868702ebec91bd4033471c8e533183df548742a81a2671de5be63a502d827437d", + "0x902e2415077f063e638207dc7e14109652e42ab47caccd6204e2870115791c9defac5425fd360b37ac0f7bd8fe7011f8", + "0xaa18e4fdc1381b59c18503ae6f6f2d6943445bd00dd7d4a2ad7e5adad7027f2263832690be30d456e6d772ad76f22350", + "0xa348b40ba3ba7d81c5d4631f038186ebd5e5f314f1ea737259151b07c3cc8cf0c6ed4201e71bcc1c22fefda81a20cde6", + "0xaa1306f7ac1acbfc47dc6f7a0cb6d03786cec8c8dc8060388ccda777bca24bdc634d03e53512c23dba79709ff64f8620", + "0x818ccfe46e700567b7f3eb400e5a35f6a5e39b3db3aa8bc07f58ace35d9ae5a242faf8dbccd08d9a9175bbce15612155", + "0xb7e3da2282b65dc8333592bb345a473f03bd6df69170055fec60222de9897184536bf22b9388b08160321144d0940279", + "0xa4d976be0f0568f4e57de1460a1729129252b44c552a69fceec44e5b97c96c711763360d11f9e5bf6d86b4976bf40d69", + "0x85d185f0397c24c2b875b09b6328a23b87982b84ee880f2677a22ff4c9a1ba9f0fea000bb3f7f66375a00d98ebafce17", + "0xb4ccbb8c3a2606bd9b87ce022704663af71d418351575f3b350d294f4efc68c26f9a2ce49ff81e6ff29c3b63d746294e", + "0x93ffd3265fddb63724dfde261d1f9e22f15ecf39df28e4d89e9fea03221e8e88b5dd9b77628bacaa783c6f91802d47cc", + "0xb1fd0f8d7a01378e693da98d03a2d2fda6b099d03454b6f2b1fa6472ff6bb092751ce6290059826b74ac0361eab00e1e", + "0xa89f440c71c561641589796994dd2769616b9088766e983c873fae0716b95c386c8483ab8a4f367b6a68b72b7456dd32", + "0xaf4fe92b01d42d03dd5d1e7fa55e96d4bbcb7bf7d4c8c197acd16b3e0f3455807199f683dcd263d74547ef9c244b35cc", + "0xa8227f6e0a344dfe76bfbe7a1861be32c4f4bed587ccce09f9ce2cf481b2dda8ae4f566154bc663d15f962f2d41761bd", + "0xa7b361663f7495939ed7f518ba45ea9ff576c4e628995b7aea026480c17a71d63fc2c922319f0502eb7ef8f14a406882", + "0x8ddcf382a9f39f75777160967c07012cfa89e67b19714a7191f0c68eaf263935e5504e1104aaabd0899348c972a8d3c6", + "0x98c95b9f6f5c91f805fb185eedd06c6fc4457d37dd248d0be45a6a168a70031715165ea20606245cbdf8815dc0ac697f", + "0x805b44f96e001e5909834f70c09be3efcd3b43632bcac5b6b66b6d227a03a758e4b1768ce2a723045681a1d34562aaeb", + "0xb0e81b07cdc45b3dca60882676d9badb99f25c461b7efe56e3043b80100bb62d29e1873ae25eb83087273160ece72a55", + "0xb0c53f0abe78ee86c7b78c82ae1f7c070bb0b9c45c563a8b3baa2c515d482d7507bb80771e60b38ac13f78b8af92b4a9", + "0xa7838ef6696a9e4d2e5dfd581f6c8d6a700467e8fd4e85adabb5f7a56f514785dd4ab64f6f1b48366f7d94728359441b", + "0x88c76f7700a1d23c30366a1d8612a796da57b2500f97f88fdf2d76b045a9d24e7426a8ffa2f4e86d3046937a841dad58", + "0xad8964baf98c1f02e088d1d9fcb3af6b1dfa44cdfe0ed2eae684e7187c33d3a3c28c38e8f4e015f9c04d451ed6f85ff6", + "0x90e9d00a098317ececaa9574da91fc149eda5b772dedb3e5a39636da6603aa007804fa86358550cfeff9be5a2cb7845e", + "0xa56ff4ddd73d9a6f5ab23bb77efa25977917df63571b269f6a999e1ad6681a88387fcc4ca3b26d57badf91b236503a29", + "0x97ad839a6302c410a47e245df84c01fb9c4dfef86751af3f9340e86ff8fc3cd52fa5ff0b9a0bd1d9f453e02ca80658a6", + "0xa4c8c44cbffa804129e123474854645107d1f0f463c45c30fd168848ebea94880f7c0c5a45183e9eb837f346270bdb35", + "0xa72e53d0a1586d736e86427a93569f52edd2f42b01e78aee7e1961c2b63522423877ae3ac1227a2cf1e69f8e1ff15bc3", + "0x8559f88a7ef13b4f09ac82ae458bbae6ab25671cfbf52dae7eac7280d6565dd3f0c3286aec1a56a8a16dc3b61d78ce47", + "0x8221503f4cdbed550876c5dc118a3f2f17800c04e8be000266633c83777b039a432d576f3a36c8a01e8fd18289ebc10b", + "0x99bfbe5f3e46d4d898a578ba86ed26de7ed23914bd3bcdf3c791c0bcd49398a52419077354a5ab75cea63b6c871c6e96", + "0xaa134416d8ff46f2acd866c1074af67566cfcf4e8be8d97329dfa0f603e1ff208488831ce5948ac8d75bfcba058ddcaa", + "0xb02609d65ebfe1fe8e52f21224a022ea4b5ea8c1bd6e7b9792eed8975fc387cdf9e3b419b8dd5bcce80703ab3a12a45f", + "0xa4f14798508698fa3852e5cac42a9db9797ecee7672a54988aa74037d334819aa7b2ac7b14efea6b81c509134a6b7ad2", + "0x884f01afecbcb987cb3e7c489c43155c416ed41340f61ecb651d8cba884fb9274f6d9e7e4a46dd220253ae561614e44c", + "0xa05523c9e71dce1fe5307cc71bd721feb3e1a0f57a7d17c7d1c9fb080d44527b7dbaa1f817b1af1c0b4322e37bc4bb1e", + "0x8560aec176a4242b39f39433dd5a02d554248c9e49d3179530815f5031fee78ba9c71a35ceeb2b9d1f04c3617c13d8f0", + "0x996aefd402748d8472477cae76d5a2b92e3f092fc834d5222ae50194dd884c9fb8b6ed8e5ccf8f6ed483ddbb4e80c747", + "0x8fd09900320000cbabc40e16893e2fcf08815d288ec19345ad7b6bb22f7d78a52b6575a3ca1ca2f8bc252d2eafc928ec", + "0x939e51f73022bc5dc6862a0adf8fb8a3246b7bfb9943cbb4b27c73743926cc20f615a036c7e5b90c80840e7f1bfee0e7", + "0xa0a6258700cadbb9e241f50766573bf9bdb7ad380b1079dc3afb4054363d838e177b869cad000314186936e40359b1f2", + "0x972699a4131c8ed27a2d0e2104d54a65a7ff1c450ad9da3a325c662ab26869c21b0a84d0700b98c8b5f6ce3b746873d7", + "0xa454c7fe870cb8aa6491eafbfb5f7872d6e696033f92e4991d057b59d70671f2acdabef533e229878b60c7fff8f748b1", + "0xa167969477214201f09c79027b10221e4707662e0c0fde81a0f628249f2f8a859ce3d30a7dcc03b8ecca8f7828ad85c7", + "0x8ff6b7265175beb8a63e1dbf18c9153fb2578c207c781282374f51b40d57a84fd2ef2ea2b9c6df4a54646788a62fd17f", + "0xa3d7ebeccde69d73d8b3e76af0da1a30884bb59729503ff0fb0c3bccf9221651b974a6e72ea33b7956fc3ae758226495", + "0xb71ef144c9a98ce5935620cb86c1590bd4f48e5a2815d25c0cdb008fde628cf628c31450d3d4f67abbfeb16178a74cfd", + "0xb5e0a16d115134f4e2503990e3f2035ed66b9ccf767063fe6747870d97d73b10bc76ed668550cb82eedc9a2ca6f75524", + "0xb30ffaaf94ee8cbc42aa2c413175b68afdb207dbf351fb20be3852cb7961b635c22838da97eaf43b103aff37e9e725cc", + "0x98aa7d52284f6c1f22e272fbddd8c8698cf8f5fbb702d5de96452141fafb559622815981e50b87a72c2b1190f59a7deb", + "0x81fbacda3905cfaf7780bb4850730c44166ed26a7c8d07197a5d4dcd969c09e94a0461638431476c16397dd7bdc449f9", + "0x95e47021c1726eac2e5853f570d6225332c6e48e04c9738690d53e07c6b979283ebae31e2af1fc9c9b3e59f87e5195b1", + "0xac024a661ba568426bb8fce21780406537f518075c066276197300841e811860696f7588188bc01d90bace7bc73d56e3", + "0xa4ebcaf668a888dd404988ab978594dee193dad2d0aec5cdc0ccaf4ec9a7a8228aa663db1da8ddc52ec8472178e40c32", + "0xa20421b8eaf2199d93b083f2aff37fb662670bd18689d046ae976d1db1fedd2c2ff897985ecc6277b396db7da68bcb27", + "0x8bc33d4b40197fd4d49d1de47489d10b90d9b346828f53a82256f3e9212b0cbc6930b895e879da9cec9fedf026aadb3e", + "0xaaafdd1bec8b757f55a0433eddc0a39f818591954fd4e982003437fcceb317423ad7ee74dbf17a2960380e7067a6b4e2", + "0xaad34277ebaed81a6ec154d16736866f95832803af28aa5625bf0461a71d02b1faba02d9d9e002be51c8356425a56867", + "0x976e9c8b150d08706079945bd0e84ab09a648ecc6f64ded9eb5329e57213149ae409ae93e8fbd8eda5b5c69f5212b883", + "0x8097fae1653247d2aed4111533bc378171d6b2c6d09cbc7baa9b52f188d150d645941f46d19f7f5e27b7f073c1ebd079", + "0x83905f93b250d3184eaba8ea7d727c4464b6bdb027e5cbe4f597d8b9dc741dcbea709630bd4fd59ce24023bec32fc0f3", + "0x8095030b7045cff28f34271386e4752f9a9a0312f8df75de4f424366d78534be2b8e1720a19cb1f9a2d21105d790a225", + "0xa7b7b73a6ae2ed1009c49960374b0790f93c74ee03b917642f33420498c188a169724945a975e5adec0a1e83e07fb1b2", + "0x856a41c54df393b6660b7f6354572a4e71c8bfca9cabaffb3d4ef2632c015e7ee2bc10056f3eccb3dbed1ad17d939178", + "0xa8f7a55cf04b38cd4e330394ee6589da3a07dc9673f74804fdf67b364e0b233f14aec42e783200a2e4666f7c5ff62490", + "0x82c529f4e543c6bca60016dc93232c115b359eaee2798a9cf669a654b800aafe6ab4ba58ea8b9cdda2b371c8d62fa845", + "0x8caab020c1baddce77a6794113ef1dfeafc5f5000f48e97f4351b588bf02f1f208101745463c480d37f588d5887e6d8c", + "0x8fa91b3cc400f48b77b6fd77f3b3fbfb3f10cdff408e1fd22d38f77e087b7683adad258804409ba099f1235b4b4d6fea", + "0x8aa02787663d6be9a35677d9d8188b725d5fcd770e61b11b64e3def8808ea5c71c0a9afd7f6630c48634546088fcd8e2", + "0xb5635b7b972e195cab878b97dea62237c7f77eb57298538582a330b1082f6207a359f2923864630136d8b1f27c41b9aa", + "0x8257bb14583551a65975946980c714ecd6e5b629672bb950b9caacd886fbd22704bc9e3ba7d30778adab65dc74f0203a", + "0xab5fe1cd12634bfa4e5c60d946e2005cbd38f1063ec9a5668994a2463c02449a0a185ef331bd86b68b6e23a8780cb3ba", + "0xa7d3487da56cda93570cc70215d438204f6a2709bfb5fda6c5df1e77e2efc80f4235c787e57fbf2c74aaff8cbb510a14", + "0xb61cff7b4c49d010e133319fb828eb900f8a7e55114fc86b39c261a339c74f630e1a7d7e1350244ada566a0ff3d46c4b", + "0x8d4d1d55d321d278db7a85522ccceca09510374ca81d4d73e3bb5249ace7674b73900c35a531ec4fa6448fabf7ad00dc", + "0x966492248aee24f0f56c8cfca3c8ec6ba3b19abb69ae642041d4c3be8523d22c65c4dafcab4c58989ccc4e0bd2f77919", + "0xb20c320a90cb220b86e1af651cdc1e21315cd215da69f6787e28157172f93fc8285dcd59b039c626ed8ca4633cba1a47", + "0xaae9e6b22f018ceb5c0950210bb8182cb8cb61014b7e14581a09d36ebd1bbfebdb2b82afb7fdb0cf75e58a293d9c456d", + "0x875547fb67951ad37b02466b79f0c9b985ccbc500cfb431b17823457dc79fb9597ec42cd9f198e15523fcd88652e63a4", + "0x92afce49773cb2e20fb21e4f86f18e0959ebb9c33361547ddb30454ee8e36b1e234019cbdca0e964cb292f7f77df6b90", + "0x8af85343dfe1821464c76ba11c216cbef697b5afc69c4d821342e55afdac047081ec2e3f7b09fc14b518d9a23b78c003", + "0xb7de4a1648fd63f3a918096ea669502af5357438e69dac77cb8102b6e6c15c76e033cfaa80dafc806e535ede5c1a20aa", + "0xac80e9b545e8bd762951d96c9ce87f629d01ffcde07efc2ef7879ca011f1d0d8a745abf26c9d452541008871304fac00", + "0xa4cf0f7ed724e481368016c38ea5816698a5f68eb21af4d3c422d2ba55f96a33e427c2aa40de1b56a7cfac7f7cf43ab0", + "0x899b0a678bb2db2cae1b44e75a661284844ebcdd87abf308fedeb2e4dbe5c5920c07db4db7284a7af806a2382e8b111a", + "0xaf0588a2a4afce2b1b13c1230816f59e8264177e774e4a341b289a101dcf6af813638fed14fb4d09cb45f35d5d032609", + "0xa4b8df79e2be76e9f5fc5845f06fe745a724cf37c82fcdb72719b77bdebea3c0e763f37909373e3a94480cc5e875cba0", + "0x83e42c46d88930c8f386b19fd999288f142d325e2ebc86a74907d6d77112cb0d449bc511c95422cc810574031a8cbba9", + "0xb5e39534070de1e5f6e27efbdd3dc917d966c2a9b8cf2d893f964256e95e954330f2442027dc148c776d63a95bcde955", + "0x958607569dc28c075e658cd4ae3927055c6bc456eef6212a6fea8205e48ed8777a8064f584cda38fe5639c371e2e7fba", + "0x812adf409fa63575113662966f5078a903212ffb65c9b0bbe62da0f13a133443a7062cb8fd70f5e5dd5559a32c26d2c8", + "0xa679f673e5ce6a3cce7fa31f22ee3785e96bcb55e5a776e2dd3467bef7440e3555d1a9b87cb215e86ee9ed13a090344b", + "0xafedbb34508b159eb25eb2248d7fe328f86ef8c7d84c62d5b5607d74aae27cc2cc45ee148eb22153b09898a835c58df4", + "0xb75505d4f6b67d31e665cfaf5e4acdb5838ae069166b7fbcd48937c0608a59e40a25302fcc1873d2e81c1782808c70f0", + "0xb62515d539ec21a155d94fc00ea3c6b7e5f6636937bce18ed5b618c12257fb82571886287fd5d1da495296c663ebc512", + "0xab8e1a9446bbdd588d1690243b1549d230e6149c28f59662b66a8391a138d37ab594df38e7720fae53217e5c3573b5be", + "0xb31e8abf4212e03c3287bb2c0a153065a7290a16764a0bac8f112a72e632185a654bb4e88fdd6053e6c7515d9719fadb", + "0xb55165477fe15b6abd2d0f4fddaa9c411710dcc4dd712daba3d30e303c9a3ee5415c256f9dc917ecf18c725b4dbab059", + "0xa0939d4f57cacaae549b78e87cc234de4ff6a35dc0d9cd5d7410abc30ebcd34c135e008651c756e5a9d2ca79c40ef42b", + "0x8cf10e50769f3443340844aad4d56ec790850fed5a41fcbd739abac4c3015f0a085a038fbe7fae9f5ad899cce5069f6b", + "0x924055e804d82a99ea4bb160041ea4dc14b568abf379010bc1922fde5d664718c31d103b8b807e3a1ae809390e708c73", + "0x8ec0f9d26f71b0f2e60a179e4fd1778452e2ffb129d50815e5d7c7cb9415fa69ae5890578086e8ef6bfde35ad2a74661", + "0x98c7f12b15ec4426b59f737f73bf5faea4572340f4550b7590dfb7f7ffedb2372e3e555977c63946d579544c53210ad0", + "0x8a935f7a955c78f69d66f18eee0092e5e833fa621781c9581058e219af4d7ceee48b84e472e159dda6199715fb2f9acf", + "0xb78d4219f95a2dbfaa7d0c8a610c57c358754f4f43c2af312ab0fe8f10a5f0177e475332fb8fd23604e474fc2abeb051", + "0x8d086a14803392b7318c28f1039a17e3cfdcece8abcaca3657ec3d0ac330842098a85c0212f889fabb296dfb133ce9aa", + "0xa53249f417aac82f2c2a50c244ce21d3e08a5e5a8bd33bec2a5ab0d6cd17793e34a17edfa3690899244ce201e2fb9986", + "0x8619b0264f9182867a1425be514dc4f1ababc1093138a728a28bd7e4ecc99b9faaff68c23792264bc6e4dce5f52a5c52", + "0x8c171edbbbde551ec19e31b2091eb6956107dd9b1f853e1df23bff3c10a3469ac77a58335eee2b79112502e8e163f3de", + "0xa9d19ec40f0ca07c238e9337c6d6a319190bdba2db76fb63902f3fb459aeeb50a1ac30db5b25ee1b4201f3ca7164a7f4", + "0xb9c6ec14b1581a03520b8d2c1fbbc31fb8ceaef2c0f1a0d0080b6b96e18442f1734bea7ef7b635d787c691de4765d469", + "0x8cb437beb4cfa013096f40ccc169a713dc17afee6daa229a398e45fd5c0645a9ad2795c3f0cd439531a7151945d7064d", + "0xa6e8740cc509126e146775157c2eb278003e5bb6c48465c160ed27888ca803fa12eee1f6a8dd7f444f571664ed87fdc1", + "0xb75c1fecc85b2732e96b3f23aefb491dbd0206a21d682aee0225838dc057d7ed3b576176353e8e90ae55663f79e986e4", + "0xad8d249b0aea9597b08358bce6c77c1fd552ef3fbc197d6a1cfe44e5e6f89b628b12a6fb04d5dcfcbacc51f46e4ae7bb", + "0xb998b2269932cbd58d04b8e898d373ac4bb1a62e8567484f4f83e224061bc0f212459f1daae95abdbc63816ae6486a55", + "0x827988ef6c1101cddc96b98f4a30365ff08eea2471dd949d2c0a9b35c3bbfa8c07054ad1f4c88c8fbf829b20bb5a9a4f", + "0x8692e638dd60babf7d9f2f2d2ce58e0ac689e1326d88311416357298c6a2bffbfebf55d5253563e7b3fbbf5072264146", + "0xa685d75b91aea04dbc14ab3c1b1588e6de96dae414c8e37b8388766029631b28dd860688079b12d09cd27f2c5af11adf", + "0xb57eced93eec3371c56679c259b34ac0992286be4f4ff9489d81cf9712403509932e47404ddd86f89d7c1c3b6391b28c", + "0xa1c8b4e42ebcbd8927669a97f1b72e236fb19249325659e72be7ddaaa1d9e81ca2abb643295d41a8c04a2c01f9c0efd7", + "0x877c33de20d4ed31674a671ba3e8f01a316581e32503136a70c9c15bf0b7cb7b1cba6cd4eb641fad165fb3c3c6c235fd", + "0xa2a469d84ec478da40838f775d11ad38f6596eb41caa139cc190d6a10b5108c09febae34ffdafac92271d2e73c143693", + "0x972f817caedb254055d52e963ed28c206848b6c4cfdb69dbc961c891f8458eaf582a6d4403ce1177d87bc2ea410ef60a", + "0xaccbd739e138007422f28536381decc54bb6bd71d93edf3890e54f9ef339f83d2821697d1a4ac1f5a98175f9a9ecb9b5", + "0x8940f8772e05389f823b62b3adc3ed541f91647f0318d7a0d3f293aeeb421013de0d0a3664ea53dd24e5fbe02d7efef6", + "0x8ecce20f3ef6212edef07ec4d6183fda8e0e8cad2c6ccd0b325e75c425ee1faba00b5c26b4d95204238931598d78f49d", + "0x97cc72c36335bd008afbed34a3b0c7225933faba87f7916d0a6d2161e6f82e0cdcda7959573a366f638ca75d30e9dab1", + "0x9105f5de8699b5bdb6bd3bb6cc1992d1eac23929c29837985f83b22efdda92af64d9c574aa9640475087201bbbe5fd73", + "0x8ffb33c4f6d05c413b9647eb6933526a350ed2e4278ca2ecc06b0e8026d8dbe829c476a40e45a6df63a633090a3f82ef", + "0x8bfc6421fdc9c2d2aaa68d2a69b1a2728c25b84944cc3e6a57ff0c94bfd210d1cbf4ff3f06702d2a8257024d8be7de63", + "0xa80e1dc1dddfb41a70220939b96dc6935e00b32fb8be5dff4eed1f1c650002ff95e4af481c43292e3827363b7ec4768a", + "0x96f714ebd54617198bd636ba7f7a7f8995a61db20962f2165078d9ed8ee764d5946ef3cbdc7ebf8435bb8d5dd4c1deac", + "0x8cdb0890e33144d66391d2ae73f5c71f5a861f72bc93bff6cc399fc25dd1f9e17d8772592b44593429718784802ac377", + "0x8ccf9a7f80800ee770b92add734ed45a73ecc31e2af0e04364eefc6056a8223834c7c0dc9dfc52495bdec6e74ce69994", + "0xaa0875f423bd68b5f10ba978ddb79d3b96ec093bfbac9ff366323193e339ed7c4578760fb60f60e93598bdf1e5cc4995", + "0xa9214f523957b59c7a4cb61a40251ad72aba0b57573163b0dc0f33e41d2df483fb9a1b85a5e7c080e9376c866790f8cb", + "0xb6224b605028c6673a536cc8ff9aeb94e7a22e686fda82cf16068d326469172f511219b68b2b3affb7933af0c1f80d07", + "0xb6d58968d8a017c6a34e24c2c09852f736515a2c50f37232ac6b43a38f8faa7572cc31dade543b594b61b5761c4781d0", + "0x8a97cefe5120020c38deeb861d394404e6c993c6cbd5989b6c9ebffe24f46ad11b4ba6348e2991cbf3949c28cfc3c99d", + "0x95bf046f8c3a9c0ce2634be4de3713024daec3fc4083e808903b25ce3ac971145af90686b451efcc72f6b22df0216667", + "0xa6a4e2f71b8fa28801f553231eff2794c0f10d12e7e414276995e21195abc9c2983a8997e41af41e78d19ff6fbb2680b", + "0x8e5e62a7ca9c2f58ebaab63db2ff1fb1ff0877ae94b7f5e2897f273f684ae639dff44cc65718f78a9c894787602ab26a", + "0x8542784383eec4f565fcb8b9fc2ad8d7a644267d8d7612a0f476fc8df3aff458897a38003d506d24142ad18f93554f2b", + "0xb7db68ba4616ea072b37925ec4fb39096358c2832cc6d35169e032326b2d6614479f765ae98913c267105b84afcb9bf2", + "0x8b31dbb9457d23d416c47542c786e07a489af35c4a87dadb8ee91bea5ac4a5315e65625d78dad2cf8f9561af31b45390", + "0xa8545a1d91ac17257732033d89e6b7111db8242e9c6ebb0213a88906d5ef407a2c6fdb444e29504b06368b6efb4f4839", + "0xb1bd85d29ebb28ccfb05779aad8674906b267c2bf8cdb1f9a0591dd621b53a4ee9f2942687ee3476740c0b4a7621a3ae", + "0xa2b54534e152e46c50d91fff03ae9cd019ff7cd9f4168b2fe7ac08ef8c3bbc134cadd3f9d6bd33d20ae476c2a8596c8a", + "0xb19b571ff4ae3e9f5d95acda133c455e72c9ea9973cae360732859836c0341c4c29ab039224dc5bc3deb824e031675d8", + "0x940b5f80478648bac025a30f3efeb47023ce20ee98be833948a248bca6979f206bb28fc0f17b90acf3bb4abd3d14d731", + "0x8f106b40588586ac11629b96d57808ad2808915d89539409c97414aded90b4ff23286a692608230a52bff696055ba5d6", + "0xae6bda03aa10da3d2abbc66d764ca6c8d0993e7304a1bdd413eb9622f3ca1913baa6da1e9f4f9e6cf847f14f44d6924d", + "0xa18e7796054a340ef826c4d6b5a117b80927afaf2ebd547794c400204ae2caf277692e2eabb55bc2f620763c9e9da66d", + "0x8d2d25180dc2c65a4844d3e66819ccfcf48858f0cc89e1c77553b463ec0f7feb9a4002ce26bc618d1142549b9850f232", + "0x863f413a394de42cc8166c1c75d513b91d545fff1de6b359037a742c70b008d34bf8e587afa2d62c844d0c6f0ea753e7", + "0x83cd0cf62d63475e7fcad18a2e74108499cdbf28af2113cfe005e3b5887794422da450b1944d0a986eb7e1f4c3b18f25", + "0xb4f8b350a6d88fea5ab2e44715a292efb12eb52df738c9b2393da3f1ddee68d0a75b476733ccf93642154bceb208f2b8", + "0xb3f52aaa4cd4221cb9fc45936cc67fd3864bf6d26bf3dd86aa85aa55ecfc05f5e392ecce5e7cf9406b4b1c4fce0398c8", + "0xb33137084422fb643123f40a6df2b498065e65230fc65dc31791c330e898c51c3a65ff738930f32c63d78f3c9315f85b", + "0x91452bfa75019363976bb7337fe3a73f1c10f01637428c135536b0cdc7da5ce558dae3dfc792aa55022292600814a8ef", + "0xad6ba94c787cd4361ca642c20793ea44f1f127d4de0bb4a77c7fbfebae0fcadbf28e2cb6f0c12c12a07324ec8c19761d", + "0x890aa6248b17f1501b0f869c556be7bf2b1d31a176f9978bb97ab7a6bd4138eed32467951c5ef1871944b7f620542f43", + "0x82111db2052194ee7dd22ff1eafffac0443cf969d3762cceae046c9a11561c0fdce9c0711f88ac01d1bed165f8a7cee3", + "0xb1527b71df2b42b55832f72e772a466e0fa05743aacc7814f4414e4bcc8d42a4010c9e0fd940e6f254cafedff3cd6543", + "0x922370fa49903679fc565f09c16a5917f8125e72acfeb060fcdbadbd1644eb9f4016229756019c93c6d609cda5d5d174", + "0xaa4c7d98a96cab138d2a53d4aee8ebff6ef903e3b629a92519608d88b3bbd94de5522291a1097e6acf830270e64c8ee1", + "0xb3dc21608a389a72d3a752883a382baaafc61ecc44083b832610a237f6a2363f24195acce529eb4aed4ef0e27a12b66e", + "0x94619f5de05e07b32291e1d7ab1d8b7337a2235e49d4fb5f3055f090a65e932e829efa95db886b32b153bdd05a53ec8c", + "0xade1e92722c2ffa85865d2426fb3d1654a16477d3abf580cfc45ea4b92d5668afc9d09275d3b79283e13e6b39e47424d", + "0xb7201589de7bed094911dd62fcd25c459a8e327ac447b69f541cdba30233063e5ddffad0b67e9c3e34adcffedfd0e13d", + "0x809d325310f862d6549e7cb40f7e5fc9b7544bd751dd28c4f363c724a0378c0e2adcb5e42ec8f912f5f49f18f3365c07", + "0xa79c20aa533de7a5d671c99eb9eb454803ba54dd4f2efa3c8fec1a38f8308e9905c71e9282955225f686146388506ff6", + "0xa85eeacb5e8fc9f3ed06a3fe2dc3108ab9f8c5877b148c73cf26e4e979bf5795edbe2e63a8d452565fd1176ed40402b2", + "0x97ef55662f8a1ec0842b22ee21391227540adf7708f491436044f3a2eb18c471525e78e1e14fa292507c99d74d7437c6", + "0x93110d64ed5886f3d16ce83b11425576a3a7a9bb831cd0de3f9a0b0f2270a730d68136b4ef7ff035ede004358f419b5c", + "0xac9ed0a071517f0ae4f61ce95916a90ba9a77a3f84b0ec50ef7298acdcd44d1b94525d191c39d6bd1bb68f4471428760", + "0x98abd6a02c7690f5a339adf292b8c9368dfc12e0f8069cf26a5e0ce54b4441638f5c66ea735142f3c28e00a0024267e6", + "0xb51efb73ba6d44146f047d69b19c0722227a7748b0e8f644d0fc9551324cf034c041a2378c56ce8b58d06038fb8a78de", + "0x8f115af274ef75c1662b588b0896b97d71f8d67986ae846792702c4742ab855952865ce236b27e2321967ce36ff93357", + "0xb3c4548f14d58b3ab03c222da09e4381a0afe47a72d18d50a94e0008797f78e39e99990e5b4757be62310d400746e35a", + "0xa9b1883bd5f31f909b8b1b6dcb48c1c60ed20aa7374b3ffa7f5b2ed036599b5bef33289d23c80a5e6420d191723b92f7", + "0x85d38dffd99487ae5bb41ab4a44d80a46157bbbe8ef9497e68f061721f74e4da513ccc3422936b059575975f6787c936", + "0xadf870fcb96e972c033ab7a35d28ae79ee795f82bc49c3bd69138f0e338103118d5529c53f2d72a9c0d947bf7d312af2", + "0xab4c7a44e2d9446c6ff303eb49aef0e367a58b22cc3bb27b4e69b55d1d9ee639c9234148d2ee95f9ca8079b1457d5a75", + "0xa386420b738aba2d7145eb4cba6d643d96bda3f2ca55bb11980b318d43b289d55a108f4bc23a9606fb0bccdeb3b3bb30", + "0x847020e0a440d9c4109773ecca5d8268b44d523389993b1f5e60e541187f7c597d79ebd6e318871815e26c96b4a4dbb1", + "0xa530aa7e5ca86fcd1bec4b072b55cc793781f38a666c2033b510a69e110eeabb54c7d8cbcb9c61fee531a6f635ffa972", + "0x87364a5ea1d270632a44269d686b2402da737948dac27f51b7a97af80b66728b0256547a5103d2227005541ca4b7ed04", + "0x8816fc6e16ea277de93a6d793d0eb5c15e9e93eb958c5ef30adaf8241805adeb4da8ce19c3c2167f971f61e0b361077d", + "0x8836a72d301c42510367181bb091e4be377777aed57b73c29ef2ce1d475feedd7e0f31676284d9a94f6db01cc4de81a2", + "0xb0d9d8b7116156d9dde138d28aa05a33e61f8a85839c1e9071ccd517b46a5b4b53acb32c2edd7150c15bc1b4bd8db9e3", + "0xae931b6eaeda790ba7f1cd674e53dc87f6306ff44951fa0df88d506316a5da240df9794ccbd7215a6470e6b31c5ea193", + "0x8c6d5bdf87bd7f645419d7c6444e244fe054d437ed1ba0c122fde7800603a5fadc061e5b836cb22a6cfb2b466f20f013", + "0x90d530c6d0cb654999fa771b8d11d723f54b8a8233d1052dc1e839ea6e314fbed3697084601f3e9bbb71d2b4eaa596df", + "0xb0d341a1422588c983f767b1ed36c18b141774f67ef6a43cff8e18b73a009da10fc12120938b8bba27f225bdfd3138f9", + "0xa131b56f9537f460d304e9a1dd75702ace8abd68cb45419695cb8dee76998139058336c87b7afd6239dc20d7f8f940cc", + "0xaa6c51fa28975f709329adee1bbd35d49c6b878041841a94465e8218338e4371f5cb6c17f44a63ac93644bf28f15d20f", + "0x88440fb584a99ebd7f9ea04aaf622f6e44e2b43bbb49fb5de548d24a238dc8f26c8da2ccf03dd43102bda9f16623f609", + "0x9777b8695b790e702159a4a750d5e7ff865425b95fa0a3c15495af385b91c90c00a6bd01d1b77bffe8c47d01baae846f", + "0x8b9d764ece7799079e63c7f01690c8eff00896a26a0d095773dea7a35967a8c40db7a6a74692f0118bf0460c26739af4", + "0x85808c65c485520609c9e61fa1bb67b28f4611d3608a9f7a5030ee61c3aa3c7e7dc17fff48af76b4aecee2cb0dbd22ac", + "0xad2783a76f5b3db008ef5f7e67391fda4e7e36abde6b3b089fc4835b5c339370287935af6bd53998bed4e399eda1136d", + "0x96f18ec03ae47c205cc4242ca58e2eff185c9dca86d5158817e2e5dc2207ab84aadda78725f8dc080a231efdc093b940", + "0x97de1ab6c6cc646ae60cf7b86df73b9cf56cc0cd1f31b966951ebf79fc153531af55ca643b20b773daa7cab784b832f7", + "0x870ba266a9bfa86ef644b1ef025a0f1b7609a60de170fe9508de8fd53170c0b48adb37f19397ee8019b041ce29a16576", + "0xad990e888d279ac4e8db90619d663d5ae027f994a3992c2fbc7d262b5990ae8a243e19157f3565671d1cb0de17fe6e55", + "0x8d9d5adcdd94c5ba3be4d9a7428133b42e485f040a28d16ee2384758e87d35528f7f9868de9bd23d1a42a594ce50a567", + "0x85a33ed75d514ece6ad78440e42f7fcdb59b6f4cff821188236d20edae9050b3a042ce9bc7d2054296e133d033e45022", + "0x92afd2f49a124aaba90de59be85ff269457f982b54c91b06650c1b8055f9b4b0640fd378df02a00e4fc91f7d226ab980", + "0x8c0ee09ec64bd831e544785e3d65418fe83ed9c920d9bb4d0bf6dd162c1264eb9d6652d2def0722e223915615931581c", + "0x8369bedfa17b24e9ad48ebd9c5afea4b66b3296d5770e09b00446c5b0a8a373d39d300780c01dcc1c6752792bccf5fd0", + "0x8b9e960782576a59b2eb2250d346030daa50bbbec114e95cdb9e4b1ba18c3d34525ae388f859708131984976ca439d94", + "0xb682bface862008fea2b5a07812ca6a28a58fd151a1d54c708fc2f8572916e0d678a9cb8dc1c10c0470025c8a605249e", + "0xa38d5e189bea540a824b36815fc41e3750760a52be0862c4cac68214febdc1a754fb194a7415a8fb7f96f6836196d82a", + "0xb9e7fbda650f18c7eb8b40e42cc42273a7298e65e8be524292369581861075c55299ce69309710e5b843cb884de171bd", + "0xb6657e5e31b3193874a1bace08f42faccbd3c502fb73ad87d15d18a1b6c2a146f1baa929e6f517db390a5a47b66c0acf", + "0xae15487312f84ed6265e4c28327d24a8a0f4d2d17d4a5b7c29b974139cf93223435aaebe3af918f5b4bb20911799715f", + "0x8bb4608beb06bc394e1a70739b872ce5a2a3ffc98c7547bf2698c893ca399d6c13686f6663f483894bccaabc3b9c56ad", + "0xb58ac36bc6847077584308d952c5f3663e3001af5ecf2e19cb162e1c58bd6c49510205d453cffc876ca1dc6b8e04a578", + "0x924f65ced61266a79a671ffb49b300f0ea44c50a0b4e3b02064faa99fcc3e4f6061ea8f38168ab118c5d47bd7804590e", + "0x8d67d43b8a06b0ff4fafd7f0483fa9ed1a9e3e658a03fb49d9d9b74e2e24858dc1bed065c12392037b467f255d4e5643", + "0xb4d4f87813125a6b355e4519a81657fa97c43a6115817b819a6caf4823f1d6a1169683fd68f8d025cdfa40ebf3069acb", + "0xa7fd4d2c8e7b59b8eed3d4332ae94b77a89a2616347402f880bc81bde072220131e6dbec8a605be3a1c760b775375879", + "0x8d4a7d8fa6f55a30df37bcf74952e2fa4fd6676a2e4606185cf154bdd84643fd01619f8fb8813a564f72e3f574f8ce30", + "0x8086fb88e6260e9a9c42e9560fde76315ff5e5680ec7140f2a18438f15bc2cc7d7d43bfb5880b180b738c20a834e6134", + "0x916c4c54721de03934fee6f43de50bb04c81f6f8dd4f6781e159e71c40c60408aa54251d457369d133d4ba3ed7c12cb4", + "0x902e5bf468f11ed9954e2a4a595c27e34abe512f1d6dc08bbca1c2441063f9af3dc5a8075ab910a10ff6c05c1c644a35", + "0xa1302953015e164bf4c15f7d4d35e3633425a78294406b861675667eec77765ff88472306531e5d3a4ec0a2ff0dd6a9e", + "0x87874461df3c9aa6c0fa91325576c0590f367075f2f0ecfeb34afe162c04c14f8ce9d608c37ac1adc8b9985bc036e366", + "0x84b50a8a61d3cc609bfb0417348133e698fe09a6d37357ce3358de189efcf35773d78c57635c2d26c3542b13cc371752", + "0xacaed2cff8633d12c1d12bb7270c54d65b0b0733ab084fd47f81d0a6e1e9b6f300e615e79538239e6160c566d8bb8d29", + "0x889e6a0e136372ca4bac90d1ab220d4e1cad425a710e8cdd48b400b73bb8137291ceb36a39440fa84305783b1d42c72f", + "0x90952e5becec45b2b73719c228429a2c364991cf1d5a9d6845ae5b38018c2626f4308daa322cab1c72e0f6c621bb2b35", + "0x8f5a97a801b6e9dcd66ccb80d337562c96f7914e7169e8ff0fda71534054c64bf2a9493bb830623d612cfe998789be65", + "0x84f3df8b9847dcf1d63ca470dc623154898f83c25a6983e9b78c6d2d90a97bf5e622445be835f32c1e55e6a0a562ea78", + "0x91d12095cd7a88e7f57f254f02fdb1a1ab18984871dead2f107404bcf8069fe68258c4e6f6ebd2477bddf738135400bb", + "0xb771a28bc04baef68604d4723791d3712f82b5e4fe316d7adc2fc01b935d8e644c06d59b83bcb542afc40ebafbee0683", + "0x872f6341476e387604a7e93ae6d6117e72d164e38ebc2b825bc6df4fcce815004d7516423c190c1575946b5de438c08d", + "0x90d6b4aa7d40a020cdcd04e8b016d041795961a8e532a0e1f4041252131089114a251791bf57794cadb7d636342f5d1c", + "0x899023ba6096a181448d927fed7a0fe858be4eac4082a42e30b3050ee065278d72fa9b9d5ce3bc1372d4cbd30a2f2976", + "0xa28f176571e1a9124f95973f414d5bdbf5794d41c3839d8b917100902ac4e2171eb940431236cec93928a60a77ede793", + "0x838dbe5bcd29c4e465d02350270fa0036cd46f8730b13d91e77afb7f5ed16525d0021d3b2ae173a76c378516a903e0cb", + "0x8e105d012dd3f5d20f0f1c4a7e7f09f0fdd74ce554c3032e48da8cce0a77260d7d47a454851387770f5c256fa29bcb88", + "0x8f4df0f9feeb7a487e1d138d13ea961459a6402fd8f8cabb226a92249a0d04ded5971f3242b9f90d08da5ff66da28af6", + "0xad1cfda4f2122a20935aa32fb17c536a3653a18617a65c6836700b5537122af5a8206befe9eaea781c1244c43778e7f1", + "0x832c6f01d6571964ea383292efc8c8fa11e61c0634a25fa180737cc7ab57bc77f25e614aac9a2a03d98f27b3c1c29de2", + "0x903f89cc13ec6685ac7728521898781fecb300e9094ef913d530bf875c18bcc3ceed7ed51e7b482d45619ab4b025c2e9", + "0xa03c474bb915aad94f171e8d96f46abb2a19c9470601f4c915512ec8b9e743c3938450a2a5b077b4618b9df8809e1dc1", + "0x83536c8456f306045a5f38ae4be2e350878fa7e164ea408d467f8c3bc4c2ee396bd5868008c089183868e4dfad7aa50b", + "0x88f26b4ea1b236cb326cd7ad7e2517ec8c4919598691474fe15d09cabcfc37a8d8b1b818f4d112432ee3a716b0f37871", + "0xa44324e3fe96e9c12b40ded4f0f3397c8c7ee8ff5e96441118d8a6bfad712d3ac990b2a6a23231a8f691491ac1fd480f", + "0xb0de4693b4b9f932191a21ee88629964878680152a82996c0019ffc39f8d9369bbe2fe5844b68d6d9589ace54af947e4", + "0x8e5d8ba948aea5fd26035351a960e87f0d23efddd8e13236cc8e4545a3dda2e9a85e6521efb8577e03772d3637d213d9", + "0x93efc82d2017e9c57834a1246463e64774e56183bb247c8fc9dd98c56817e878d97b05f5c8d900acf1fbbbca6f146556", + "0x8731176363ad7658a2862426ee47a5dce9434216cef60e6045fa57c40bb3ce1e78dac4510ae40f1f31db5967022ced32", + "0xb10c9a96745722c85bdb1a693100104d560433d45b9ac4add54c7646a7310d8e9b3ca9abd1039d473ae768a18e489845", + "0xa2ac374dfbb464bf850b4a2caf15b112634a6428e8395f9c9243baefd2452b4b4c61b0cb2836d8eae2d57d4900bf407e", + "0xb69fe3ded0c4f5d44a09a0e0f398221b6d1bf5dbb8bc4e338b93c64f1a3cac1e4b5f73c2b8117158030ec03787f4b452", + "0x8852cdbaf7d0447a8c6f211b4830711b3b5c105c0f316e3a6a18dcfbb9be08bd6f4e5c8ae0c3692da08a2dfa532f9d5c", + "0x93bbf6d7432a7d98ade3f94b57bf9f4da9bc221a180a370b113066dd42601bb9e09edd79e2e6e04e00423399339eebda", + "0xa80941c391f1eeafc1451c59e4775d6a383946ff22997aeaadf806542ba451d3b0f0c6864eeba954174a296efe2c1550", + "0xa045fe2bb011c2a2f71a0181a8f457a3078470fb74c628eab8b59aef69ffd0d649723bf74d6885af3f028bc5a104fb39", + "0xb9d8c35911009c4c8cad64692139bf3fc16b78f5a19980790cb6a7aea650a25df4231a4437ae0c351676a7e42c16134f", + "0x94c79501ded0cfcbab99e1841abe4a00a0252b3870e20774c3da16c982d74c501916ec28304e71194845be6e3113c7ab", + "0x900a66418b082a24c6348d8644ddb1817df5b25cb33044a519ef47cc8e1f7f1e38d2465b7b96d32ed472d2d17f8414c6", + "0xb26f45d393b8b2fcb29bdbb16323dc7f4b81c09618519ab3a39f8ee5bd148d0d9f3c0b5dfab55b5ce14a1cb9206d777b", + "0xaa1a87735fc493a80a96a9a57ca40a6d9c32702bfcaa9869ce1a116ae65d69cefe2f3e79a12454b4590353e96f8912b4", + "0xa922b188d3d0b69b4e4ea2a2aa076566962844637da12c0832105d7b31dea4a309eee15d12b7a336be3ea36fcbd3e3b7", + "0x8f3841fcf4105131d8c4d9885e6e11a46c448226401cf99356c291fadb864da9fa9d30f3a73c327f23f9fd99a11d633e", + "0x9791d1183fae270e226379af6c497e7da803ea854bb20afa74b253239b744c15f670ee808f708ede873e78d79a626c9a", + "0xa4cad52e3369491ada61bf28ada9e85de4516d21c882e5f1cd845bea9c06e0b2887b0c5527fcff6fc28acd3c04f0a796", + "0xb9ac86a900899603452bd11a7892a9bfed8054970bfcbeaa8c9d1930db891169e38d6977f5258c25734f96c8462eee3b", + "0xa3a154c28e5580656a859f4efc2f5ebfa7eaa84ca40e3f134fa7865e8581586db74992dbfa4036aa252fba103773ddde", + "0x95cc2a0c1885a029e094f5d737e3ecf4d26b99036453a8773c77e360101f9f98676ee246f6f732a377a996702d55691f", + "0x842651bbe99720438d8d4b0218feb60481280c05beb17750e9ca0d8c0599a60f873b7fbdcc7d8835ba9a6d57b16eec03", + "0x81ee54699da98f5620307893dcea8f64670609fa20e5622265d66283adeac122d458b3308c5898e6c57c298db2c8b24f", + "0xb97868b0b2bc98032d68352a535a1b341b9ff3c7af4e3a7f3ebc82d3419daa1b5859d6aedc39994939623c7cd878bd9b", + "0xb60325cd5d36461d07ef253d826f37f9ee6474a760f2fff80f9873d01fd2b57711543cdc8d7afa1c350aa753c2e33dea", + "0x8c205326c11d25a46717b780c639d89714c7736c974ae71287e3f4b02e6605ac2d9b4928967b1684f12be040b7bf2dd3", + "0x95a392d82db51e26ade6c2ccd3396d7e40aff68fa570b5951466580d6e56dda51775dce5cf3a74a7f28c3cb2eb551c4d", + "0x8f2cc8071eb56dffb70bda6dd433b556221dc8bba21c53353c865f00e7d4d86c9e39f119ea9a8a12ef583e9a55d9a6b6", + "0x9449a71af9672aaf8856896d7e3d788b22991a7103f75b08c0abbcc2bfe60fda4ed8ce502cea4511ff0ea52a93e81222", + "0x857090ab9fdb7d59632d068f3cc8cf27e61f0d8322d30e6b38e780a1f05227199b4cd746aac1311c36c659ef20931f28", + "0x98a891f4973e7d9aaf9ac70854608d4f7493dffc7e0987d7be9dd6029f6ea5636d24ef3a83205615ca1ff403750058e1", + "0xa486e1365bbc278dd66a2a25d258dc82f46b911103cb16aab3945b9c95ae87b386313a12b566df5b22322ede0afe25ad", + "0xa9a1eb399ed95d396dccd8d1ac718043446f8b979ec62bdce51c617c97a312f01376ab7fb87d27034e5f5570797b3c33", + "0xb7abc3858d7a74bb446218d2f5a037e0fae11871ed9caf44b29b69c500c1fa1dcfad64c9cdccc9d80d5e584f06213deb", + "0x8cfb09fe2e202faa4cebad932b1d35f5ca204e1c2a0c740a57812ac9a6792130d1312aabd9e9d4c58ca168bfebd4c177", + "0xa90a305c2cd0f184787c6be596fa67f436afd1f9b93f30e875f817ac2aae8bdd2e6e656f6be809467e6b3ad84adb86b1", + "0x80a9ef993c2b009ae172cc8f7ec036f5734cf4f4dfa06a7db4d54725e7fbfae5e3bc6f22687bdbb6961939d6f0c87537", + "0x848ade1901931e72b955d7db1893f07003e1708ff5d93174bac5930b9a732640f0578839203e9b77eb27965c700032d3", + "0x93fdf4697609c5ae9c33b9ca2f5f1af44abeb2b98dc4fdf732cf7388de086f410730dc384d9b7a7f447bb009653c8381", + "0x89ce3fb805aea618b5715c0d22a9f46da696b6fa86794f56fdf1d44155a33d42daf1920bcbe36cbacf3cf4c92df9cbc7", + "0x829ce2c342cf82aa469c65f724f308f7a750bd1494adc264609cd790c8718b8b25b5cab5858cf4ee2f8f651d569eea67", + "0xaf2f0cee7bf413204be8b9df59b9e4991bc9009e0d6dbe6815181df0ec2ca93ab8f4f3135b1c14d8f53d74bff0bd6f27", + "0xb87998cecf7b88cde93d1779f10a521edd5574a2fbd240102978639ec57433ba08cdb53849038a329cebbe74657268d2", + "0xa64542a1261a6ed3d720c2c3a802303aad8c4c110c95d0f12e05c1065e66f42da494792b6bfc5b9272363f3b1d457f58", + "0x86a6fd042e4f282fadf07a4bfee03fc96a3aea49f7a00f52bf249a20f1ec892326855410e61f37fbb27d9305eb2fc713", + "0x967ea5bc403b6db269682f7fd0df90659350d7e1aa66bc4fab4c9dfcd75ed0bba4b52f1cebc5f34dc8ba810793727629", + "0xa52990f9f3b8616ce3cdc2c74cd195029e6a969753dcf2d1630438700e7d6ebde36538532b3525ac516f5f2ce9dd27a3", + "0xa64f7ff870bab4a8bf0d4ef6f5c744e9bf1021ed08b4c80903c7ad318e80ba1817c3180cc45cb5a1cae1170f0241655f", + "0xb00f706fa4de1f663f021e8ad3d155e84ce6084a409374b6e6cd0f924a0a0b51bebaaaf1d228c77233a73b0a5a0df0e9", + "0x8b882cc3bff3e42babdb96df95fb780faded84887a0a9bab896bef371cdcf169d909f5658649e93006aa3c6e1146d62e", + "0x9332663ef1d1dcf805c3d0e4ce7a07d9863fb1731172e766b3cde030bf81682cc011e26b773fb9c68e0477b4ae2cfb79", + "0xa8aa8151348dbd4ef40aaeb699b71b4c4bfd3218560c120d85036d14f678f6736f0ec68e80ce1459d3d35feccc575164", + "0xa16cd8b729768f51881c213434aa28301fa78fcb554ddd5f9012ee1e4eae7b5cb3dd88d269d53146dea92d10790faf0b", + "0x86844f0ef9d37142faf3b1e196e44fbe280a3ba4189aa05c356778cb9e3b388a2bff95eed305ada8769935c9974e4c57", + "0xae2eec6b328fccf3b47bcdac32901ac2744a51beb410b04c81dea34dee4912b619466a4f5e2780d87ecefaebbe77b46d", + "0x915df4c38d301c8a4eb2dc5b1ba0ffaad67cbb177e0a80095614e9c711f4ef24a4cef133f9d982a63d2a943ba6c8669d", + "0xae6a2a4dedfc2d1811711a8946991fede972fdf2a389b282471280737536ffc0ac3a6d885b1f8bda0366eb0b229b9979", + "0xa9b628c63d08b8aba6b1317f6e91c34b2382a6c85376e8ef2410a463c6796740ae936fc4e9e0737cb9455d1daa287bd8", + "0x848e30bf7edf2546670b390d5cf9ab71f98fcb6add3c0b582cb34996c26a446dee5d1bde4fdcde4fc80c10936e117b29", + "0x907d6096c7c8c087d1808dd995d5d2b9169b3768c3f433475b50c2e2bd4b082f4d543afd8b0b0ddffa9c66222a72d51d", + "0xa59970a2493b07339124d763ac9d793c60a03354539ecbcf6035bc43d1ea6e35718202ae6d7060b7d388f483d971573c", + "0xb9cfef2af9681b2318f119d8611ff6d9485a68d8044581b1959ab1840cbca576dbb53eec17863d2149966e9feb21122f", + "0xad47271806161f61d3afa45cdfe2babceef5e90031a21779f83dc8562e6076680525b4970b2f11fe9b2b23c382768323", + "0x8e425a99b71677b04fe044625d338811fbb8ee32368a424f6ab2381c52e86ee7a6cecedf777dc97181519d41c351bc22", + "0x86b55b54d7adefc12954a9252ee23ae83efe8b5b4b9a7dc307904413e5d69868c7087a818b2833f9b004213d629be8ad", + "0xa14fda6b93923dd11e564ae4457a66f397741527166e0b16a8eb91c6701c244fd1c4b63f9dd3515193ec88fa6c266b35", + "0xa9b17c36ae6cd85a0ed7f6cabc5b47dc8f80ced605db327c47826476dc1fb8f8669aa7a7dc679fbd4ee3d8e8b4bd6a6f", + "0x82a0829469c1458d959c821148f15dacae9ea94bf56c59a6ab2d4dd8b3d16d73e313b5a3912a6c1f131d73a8f06730c4", + "0xb22d56d549a53eaef549595924bdb621ff807aa4513feedf3fdcbf7ba8b6b9cfa4481c2f67fc642db397a6b794a8b63a", + "0x974c59c24392e2cb9294006cbe3c52163e255f3bd0c2b457bdc68a6338e6d5b6f87f716854492f8d880a6b896ccf757c", + "0xb70d247ba7cad97c50b57f526c2ba915786e926a94e8f8c3eebc2e1be6f4255411b9670e382060049c8f4184302c40b2", + "0xad80201fe75ef21c3ddbd98cf23591e0d7a3ba1036dfe77785c32f44755a212c31f0ceb0a0b6f5ee9b6dc81f358d30c3", + "0x8c656e841f9bb90b9a42d425251f3fdbc022a604d75f5845f479ed4be23e02aaf9e6e56cde351dd7449c50574818a199", + "0x8b88dd3fa209d3063b7c5b058f7249ee9900fbc2287d16da61a0704a0a1d71e45d9c96e1cda7fdf9654534ec44558b22", + "0x961da00cc8750bd84d253c08f011970ae1b1158ad6778e8ed943d547bceaf52d6d5a212a7de3bf2706688c4389b827d2", + "0xa5dd379922549a956033e3d51a986a4b1508e575042b8eaa1df007aa77cf0b8c2ab23212f9c075702788fa9c53696133", + "0xac8fcfde3a349d1e93fc8cf450814e842005c545c4844c0401bc80e6b96cdb77f29285a14455e167c191d4f312e866cd", + "0xac63d79c799783a8466617030c59dd5a8f92ee6c5204676fd8d881ce5f7f8663bdbeb0379e480ea9b6340ab0dc88e574", + "0x805874fde19ce359041ae2bd52a39e2841acabfd31f965792f2737d7137f36d4e4722ede8340d8c95afa6af278af8acb", + "0x8d2f323a228aa8ba7b7dc1399138f9e6b41df1a16a7069003ab8104b8b68506a45141bc5fe66acf430e23e13a545190b", + "0xa1610c721a2d9af882bb6b39bea97cff1527a3aea041d25934de080214ae77c959e79957164440686d15ab301e897d4d", + "0xaba16d29a47fc36f12b654fde513896723e2c700c4190f11b26aa4011da57737ad717daa02794aa3246e4ae5f0b0cc3a", + "0xa406db2f15fdd135f346cc4846623c47edd195e80ba8c7cb447332095314d565e4040694ca924696bb5ee7f8996ea0ba", + "0x8b30e2cd9b47d75ba57b83630e40f832249af6c058d4f490416562af451993eec46f3e1f90bc4d389e4c06abd1b32a46", + "0xaacf9eb7036e248e209adbfc3dd7ce386569ea9b312caa4b240726549db3c68c4f1c8cbf8ed5ea9ea60c7e57c9df3b8e", + "0xb20fcac63bf6f5ee638a42d7f89be847f348c085ddcbec3fa318f4323592d136c230495f188ef2022aa355cc2b0da6f9", + "0x811eff750456a79ec1b1249d76d7c1547065b839d8d4aaad860f6d4528eb5b669473dcceeeea676cddbc3980b68461b7", + "0xb52d14ae33f4ab422f953392ae76a19c618cc31afc96290bd3fe2fb44c954b5c92c4789f3f16e8793f2c0c1691ade444", + "0xa7826dafeeba0db5b66c4dfcf2b17fd7b40507a5a53ac2e42942633a2cb30b95ba1739a6e9f3b7a0e0f1ec729bf274e2", + "0x8acfd83ddf7c60dd7c8b20c706a3b972c65d336b8f9b3d907bdd8926ced271430479448100050b1ef17578a49c8fa616", + "0xaf0c69f65184bb06868029ad46f8465d75c36814c621ac20a5c0b06a900d59305584f5a6709683d9c0e4b6cd08d650a6", + "0xb6cc8588191e00680ee6c3339bd0f0a17ad8fd7f4be57d5d7075bede0ea593a19e67f3d7c1a20114894ee5bfcab71063", + "0xa82fd4f58635129dbb6cc3eb9391cf2d28400018b105fc41500fbbd12bd890b918f97d3d359c29dd3b4c4e34391dfab0", + "0x92fc544ed65b4a3625cf03c41ddff7c039bc22d22c0d59dcc00efd5438401f2606adb125a1d5de294cca216ec8ac35a3", + "0x906f67e4a32582b71f15940523c0c7ce370336935e2646bdaea16a06995256d25e99df57297e39d6c39535e180456407", + "0x97510337ea5bbd5977287339197db55c60533b2ec35c94d0a460a416ae9f60e85cee39be82abeeacd5813cf54df05862", + "0x87e6894643815c0ea48cb96c607266c5ee4f1f82ba5fe352fb77f9b6ed14bfc2b8e09e80a99ac9047dfcf62b2ae26795", + "0xb6fd55dd156622ad7d5d51b7dde75e47bd052d4e542dd6449e72411f68275775c846dde301e84613312be8c7bce58b07", + "0xb98461ac71f554b2f03a94e429b255af89eec917e208a8e60edf5fc43b65f1d17a20de3f31d2ce9f0cb573c25f2f4d98", + "0x96f0dea40ca61cefbee41c4e1fe9a7d81fbe1f49bb153d083ab70f5d0488a1f717fd28cedcf6aa18d07cce2c62801898", + "0x8d7c3ab310184f7dc34b6ce4684e4d29a31e77b09940448ea4daac730b7eb308063125d4dd229046cf11bfd521b771e0", + "0x96f0564898fe96687918bbf0a6adead99cf72e3a35ea3347e124af9d006221f8e82e5a9d2fe80094d5e8d48e610f415e", + "0xad50fcb92c2675a398cf07d4c40a579e44bf8d35f27cc330b57e54d5ea59f7d898af0f75dccfe3726e5471133d70f92b", + "0x828beed62020361689ae7481dd8f116902b522fb0c6c122678e7f949fdef70ead011e0e6bffd25678e388744e17cdb69", + "0x8349decac1ca16599eee2efc95bcaabf67631107da1d34a2f917884bd70dfec9b4b08ab7bc4379d6c73b19c0b6e54fb8", + "0xb2a6a2e50230c05613ace9e58bb2e98d94127f196f02d9dddc53c43fc68c184549ca12d713cb1b025d8260a41e947155", + "0x94ff52181aadae832aed52fc3b7794536e2a31a21fc8be3ea312ca5c695750d37f08002f286b33f4023dba1e3253ecfa", + "0xa21d56153c7e5972ee9a319501be4faff199fdf09bb821ea9ce64aa815289676c00f105e6f00311b3a5b627091b0d0fc", + "0xa27a60d219f1f0c971db73a7f563b371b5c9fc3ed1f72883b2eac8a0df6698400c9954f4ca17d7e94e44bd4f95532afb", + "0xa2fc56fae99b1f18ba5e4fe838402164ce82f8a7f3193d0bbd360c2bac07c46f9330c4c7681ffb47074c6f81ee6e7ac6", + "0xb748e530cd3afb96d879b83e89c9f1a444f54e55372ab1dcd46a0872f95ce8f49cf2363fc61be82259e04f555937ed16", + "0x8bf8993e81080c7cbba1e14a798504af1e4950b2f186ab3335b771d6acaee4ffe92131ae9c53d74379d957cb6344d9cd", + "0x96774d0ef730d22d7ab6d9fb7f90b9ead44285219d076584a901960542756700a2a1603cdf72be4708b267200f6c36a9", + "0xb47703c2ab17be1e823cc7bf3460db1d6760c0e33862c90ca058845b2ff234b0f9834ddba2efb2ee1770eb261e7d8ffd", + "0x84319e67c37a9581f8b09b5e4d4ae88d0a7fb4cbb6908971ab5be28070c3830f040b1de83ee663c573e0f2f6198640e4", + "0x96811875fa83133e0b3c0e0290f9e0e28bca6178b77fdf5350eb19344d453dbd0d71e55a0ef749025a5a2ca0ad251e81", + "0x81a423423e9438343879f2bfd7ee9f1c74ebebe7ce3cfffc8a11da6f040cc4145c3b527bd3cf63f9137e714dbcb474ef", + "0xb8c3535701ddbeec2db08e17a4fa99ba6752d32ece5331a0b8743676f421fcb14798afc7c783815484f14693d2f70db8", + "0x81aee980c876949bf40782835eec8817d535f6f3f7e00bf402ddd61101fdcd60173961ae90a1cf7c5d060339a18c959d", + "0x87e67b928d97b62c49dac321ce6cb680233f3a394d4c9a899ac2e8db8ccd8e00418e66cdfd68691aa3cb8559723b580c", + "0x8eac204208d99a2b738648df96353bbb1b1065e33ee4f6bba174b540bbbd37d205855e1f1e69a6b7ff043ca377651126", + "0x848e6e7a54ad64d18009300b93ea6f459ce855971dddb419b101f5ac4c159215626fadc20cc3b9ab1701d8f6dfaddd8b", + "0x88aa123d9e0cf309d46dddb6acf634b1ade3b090a2826d6e5e78669fa1220d6df9a6697d7778cd9b627db17eea846126", + "0x9200c2a629b9144d88a61151b661b6c4256cc5dadfd1e59a8ce17a013c2d8f7e754aabe61663c3b30f1bc47784c1f8cf", + "0xb6e1a2827c3bdda91715b0e1b1f10dd363cef337e7c80cac1f34165fc0dea7c8b69747e310563db5818390146ce3e231", + "0x92c333e694f89f0d306d54105b2a5dcc912dbe7654d9e733edab12e8537350815be472b063e56cfde5286df8922fdecb", + "0xa6fac04b6d86091158ebb286586ccfec2a95c9786e14d91a9c743f5f05546073e5e3cc717635a0c602cad8334e922346", + "0xa581b4af77feebc1fb897d49b5b507c6ad513d8f09b273328efbb24ef0d91eb740d01b4d398f2738125dacfe550330cd", + "0x81c4860cccf76a34f8a2bc3f464b7bfd3e909e975cce0d28979f457738a56e60a4af8e68a3992cf273b5946e8d7f76e2", + "0x8d1eaa09a3180d8af1cbaee673db5223363cc7229a69565f592fa38ba0f9d582cedf91e15dabd06ebbf2862fc0feba54", + "0x9832f49b0147f4552402e54593cfa51f99540bffada12759b71fcb86734be8e500eea2d8b3d036710bdf04c901432de9", + "0x8bdb0e8ec93b11e5718e8c13cb4f5de545d24829fd76161216340108098dfe5148ed25e3b57a89a516f09fa79043734d", + "0xab96f06c4b9b0b2c0571740b24fca758e6976315053a7ecb20119150a9fa416db2d3a2e0f8168b390bb063f0c1caf785", + "0xab777f5c52acd62ecf4d1f168b9cc8e1a9b45d4ec6a8ff52c583e867c2239aba98d7d3af977289b367edce03d9c2dfb1", + "0xa09d3ce5e748da84802436951acc3d3ea5d8ec1d6933505ed724d6b4b0d69973ab0930daec9c6606960f6e541e4a3ce2", + "0x8ef94f7be4d85d5ad3d779a5cf4d7b2fc3e65c52fb8e1c3c112509a4af77a0b5be994f251e5e40fabeeb1f7d5615c22b", + "0xa7406a5bf5708d9e10922d3c5c45c03ef891b8d0d74ec9f28328a72be4cdc05b4f2703fa99366426659dfca25d007535", + "0xb7f52709669bf92a2e070bfe740f422f0b7127392c5589c7f0af71bb5a8428697c762d3c0d74532899da24ea7d8695c2", + "0xb9dfb0c8df84104dbf9239ccefa4672ef95ddabb8801b74997935d1b81a78a6a5669a3c553767ec19a1281f6e570f4ff", + "0xae4d5c872156061ce9195ac640190d8d71dd406055ee43ffa6f9893eb24b870075b74c94d65bc1d5a07a6573282b5520", + "0xafe6bd3eb72266d333f1807164900dcfa02a7eb5b1744bb3c86b34b3ee91e3f05e38fa52a50dc64eeb4bdb1dd62874b8", + "0x948043cf1bc2ef3c01105f6a78dc06487f57548a3e6ef30e6ebc51c94b71e4bf3ff6d0058c72b6f3ecc37efd7c7fa8c0", + "0xa22fd17c2f7ffe552bb0f23fa135584e8d2d8d75e3f742d94d04aded2a79e22a00dfe7acbb57d44e1cdb962fb22ae170", + "0x8cd0f4e9e4fb4a37c02c1bde0f69359c43ab012eb662d346487be0c3758293f1ca560122b059b091fddce626383c3a8f", + "0x90499e45f5b9c81426f3d735a52a564cafbed72711d9279fdd88de8038e953bc48c57b58cba85c3b2e4ce56f1ddb0e11", + "0x8c30e4c034c02958384564cac4f85022ef36ab5697a3d2feaf6bf105049675bbf23d01b4b6814711d3d9271abff04cac", + "0x81f7999e7eeea30f3e1075e6780bbf054f2fb6f27628a2afa4d41872a385b4216dd5f549da7ce6cf39049b2251f27fb7", + "0xb36a7191f82fc39c283ffe53fc1f5a9a00b4c64eee7792a8443475da9a4d226cf257f226ea9d66e329af15d8f04984ec", + "0xaad4da528fdbb4db504f3041c747455baff5fcd459a2efd78f15bdf3aea0bdb808343e49df88fe7a7c8620009b7964a3", + "0x99ebd8c6dd5dd299517fb6381cfc2a7f443e6e04a351440260dd7c2aee3f1d8ef06eb6c18820b394366ecdfd2a3ce264", + "0x8873725b81871db72e4ec3643084b1cdce3cbf80b40b834b092767728605825c19b6847ad3dcf328438607e8f88b4410", + "0xb008ee2f895daa6abd35bd39b6f7901ae4611a11a3271194e19da1cdcc7f1e1ea008fe5c5440e50d2c273784541ad9c5", + "0x9036feafb4218d1f576ef89d0e99124e45dacaa6d816988e34d80f454d10e96809791d5b78f7fd65f569e90d4d7238c5", + "0x92073c1d11b168e4fa50988b0288638b4868e48bbc668c5a6dddf5499875d53be23a285acb5e4bad60114f6cf6c556e9", + "0x88c87dfcb8ba6cbfe7e1be081ccfadbd589301db2cb7c99f9ee5d7db90aa297ed1538d5a867678a763f2deede5fd219a", + "0xb42a562805c661a50f5dea63108002c0f27c0da113da6a9864c9feb5552225417c0356c4209e8e012d9bcc9d182c7611", + "0x8e6317d00a504e3b79cd47feb4c60f9df186467fe9ca0f35b55c0364db30528f5ff071109dabb2fc80bb9cd4949f0c24", + "0xb7b1ea6a88694f8d2f539e52a47466695e39e43a5eb9c6f23bca15305fe52939d8755cc3ac9d6725e60f82f994a3772f", + "0xa3cd55161befe795af93a38d33290fb642b8d80da8b786c6e6fb02d393ea308fbe87f486994039cbd7c7b390414594b6", + "0xb416d2d45b44ead3b1424e92c73c2cf510801897b05d1724ff31cbd741920cd858282fb5d6040fe1f0aa97a65bc49424", + "0x950ee01291754feace97c2e933e4681e7ddfbc4fcd079eb6ff830b0e481d929c93d0c7fb479c9939c28ca1945c40da09", + "0x869bd916aee8d86efe362a49010382674825d49195b413b4b4018e88ce43fe091b475d0b863ff0ba2259400f280c2b23", + "0x9782f38cd9c9d3385ec286ebbc7cba5b718d2e65a5890b0a5906b10a89dc8ed80d417d71d7c213bf52f2af1a1f513ea7", + "0x91cd33bc2628d096269b23faf47ee15e14cb7fdc6a8e3a98b55e1031ea0b68d10ba30d97e660f7e967d24436d40fad73", + "0x8becc978129cc96737034c577ae7225372dd855da8811ae4e46328e020c803833b5bdbc4a20a93270e2b8bd1a2feae52", + "0xa36b1d8076783a9522476ce17f799d78008967728ce920531fdaf88303321bcaf97ecaa08e0c01f77bc32e53c5f09525", + "0xb4720e744943f70467983aa34499e76de6d59aa6fadf86f6b787fdce32a2f5b535b55db38fe2da95825c51002cfe142d", + "0x91ad21fc502eda3945f6de874d1b6bf9a9a7711f4d61354f9e5634fc73f9c06ada848de15ab0a75811d3250be862827d", + "0x84f78e2ebf5fc077d78635f981712daf17e2475e14c2a96d187913006ad69e234746184a51a06ef510c9455b38acb0d7", + "0x960aa7906e9a2f11db64a26b5892ac45f20d2ccb5480f4888d89973beb6fa0dfdc06d68d241ff5ffc7f1b82b1aac242d", + "0xa99365dcd1a00c66c9db6924b97c920f5c723380e823b250db85c07631b320ec4e92e586f7319e67a522a0578f7b6d6c", + "0xa25d92d7f70cf6a88ff317cfec071e13774516da664f5fac0d4ecaa65b8bf4eb87a64a4d5ef2bd97dfae98d388dbf5cc", + "0xa7af47cd0041295798f9779020a44653007444e8b4ef0712982b06d0dcdd434ec4e1f7c5f7a049326602cb605c9105b7", + "0xaefe172eac5568369a05980931cc476bebd9dea573ba276d59b9d8c4420784299df5a910033b7e324a6c2dfc62e3ef05", + "0xb69bc9d22ffa645baa55e3e02522e9892bb2daa7fff7c15846f13517d0799766883ee09ae0869df4139150c5b843ca8a", + "0x95a10856140e493354fdd12722c7fdded21b6a2ffbc78aa2697104af8ad0c8e2206f44b0bfee077ef3949d46bbf7c16b", + "0x891f2fcd2c47cbea36b7fa715968540c233313f05333f09d29aba23c193f462ed490dd4d00969656e89c53155fdfe710", + "0xa6c33e18115e64e385c843dde34e8a228222795c7ca90bc2cc085705d609025f3351d9be61822c69035a49fb3e48f2d5", + "0xb87fb12f12c0533b005adad0487f03393ff682e13575e3cb57280c3873b2c38ba96a63c49eef7a442753d26b7005230b", + "0xb905c02ba451bfd411c135036d92c27af3b0b1c9c2f1309d6948544a264b125f39dd41afeff4666b12146c545adc168a", + "0x8b29c513f43a78951cf742231cf5457a6d9d55edf45df5481a0f299a418d94effef561b15d2c1a01d1b8067e7153fda9", + "0xb9941cccd51dc645920d2781c81a317e5a33cb7cf76427b60396735912cb6d2ca9292bb4d36b6392467d390d2c58d9f3", + "0xa8546b627c76b6ef5c93c6a98538d8593dbe21cb7673fd383d5401b0c935eea0bdeeefeb1af6ad41bad8464fb87bbc48", + "0xaa286b27de2812de63108a1aec29d171775b69538dc6198640ac1e96767c2b83a50391f49259195957d457b493b667c9", + "0xa932fb229f641e9abbd8eb2bd874015d97b6658ab6d29769fc23b7db9e41dd4f850382d4c1f08af8f156c5937d524473", + "0xa1412840fcc86e2aeec175526f2fb36e8b3b8d21a78412b7266daf81e51b3f68584ed8bd42a66a43afdd8c297b320520", + "0x89c78be9efb624c97ebca4fe04c7704fa52311d183ffd87737f76b7dadc187c12c982bd8e9ed7cd8beb48cdaafd2fd01", + "0xa3f5ddec412a5bec0ce15e3bcb41c6214c2b05d4e9135a0d33c8e50a78eaba71e0a5a6ea8b45854dec5c2ed300971fc2", + "0x9721f9cec7a68b7758e3887548790de49fa6a442d0396739efa20c2f50352a7f91d300867556d11a703866def2d5f7b5", + "0xa23764e140a87e5991573521af039630dd28128bf56eed2edbed130fd4278e090b60cf5a1dca9de2910603d44b9f6d45", + "0xa1a6494a994215e48ab55c70efa8ffdddce6e92403c38ae7e8dd2f8288cad460c6c7db526bbdf578e96ca04d9fe12797", + "0xb1705ea4cb7e074efe0405fc7b8ee2ec789af0426142f3ec81241cacd4f7edcd88e39435e4e4d8e7b1df64f3880d6613", + "0x85595d061d677116089a6064418b93eb44ff79e68d12bd9625078d3bbc440a60d0b02944eff6054433ee34710ae6fbb4", + "0x9978d5e30bedb7526734f9a1febd973a70bfa20890490e7cc6f2f9328feab1e24f991285dbc3711d892514e2d7d005ad", + "0xaf30243c66ea43b9f87a061f947f7bce745f09194f6e95f379c7582b9fead920e5d6957eaf05c12ae1282ada4670652f", + "0xa1930efb473f88001e47aa0b2b2a7566848cccf295792e4544096ecd14ee5d7927c173a8576b405bfa2eec551cd67eb5", + "0xb0446d1c590ee5a45f7e22d269c044f3848c97aec1d226b44bfd0e94d9729c28a38bccddc3a1006cc5fe4e3c24f001f2", + "0xb8a8380172df3d84b06176df916cf557966d4f2f716d3e9437e415d75b646810f79f2b2b71d857181b7fc944018883a3", + "0xa563afec25b7817bfa26e19dc9908bc00aa8fc3d19be7d6de23648701659009d10e3e4486c28e9c6b13d48231ae29ac5", + "0xa5a8e80579de886fb7d6408f542791876885947b27ad6fa99a8a26e381f052598d7b4e647b0115d4b5c64297e00ce28e", + "0x8f87afcc7ad33c51ac719bade3cd92da671a37a82c14446b0a2073f4a0a23085e2c8d31913ed2d0be928f053297de8f6", + "0xa43c455ce377e0bc434386c53c752880687e017b2f5ae7f8a15c044895b242dffde4c92fb8f8bb50b18470b17351b156", + "0x8368f8b12a5bceb1dba25adb3a2e9c7dc9b1a77a1f328e5a693f5aec195cd1e06b0fe9476b554c1c25dac6c4a5b640a3", + "0x919878b27f3671fc78396f11531c032f3e2bd132d04cc234fa4858676b15fb1db3051c0b1db9b4fc49038216f11321ce", + "0xb48cd67fb7f1242696c1f877da4bdf188eac676cd0e561fbac1a537f7b8229aff5a043922441d603a26aae56a15faee4", + "0xa3e0fdfd4d29ea996517a16f0370b54787fefe543c2fe73bfc6f9e560c1fd30dad8409859e2d7fa2d44316f24746c712", + "0x8bb156ade8faf149df7bea02c140c7e392a4742ae6d0394d880a849127943e6f26312033336d3b9fdc0092d71b5efe87", + "0x8845e5d5cc555ca3e0523244300f2c8d7e4d02aaebcb5bd749d791208856c209a6f84dd99fd55968c9f0ab5f82916707", + "0xa3e90bb5c97b07789c2f32dff1aec61d0a2220928202f5ad5355ae71f8249237799d6c8a22602e32e572cb12eabe0c17", + "0xb150bcc391884c996149dc3779ce71f15dda63a759ee9cc05871f5a8379dcb62b047098922c0f26c7bd04deb394c33f9", + "0x95cd4ad88d51f0f2efcfd0c2df802fe252bb9704d1afbf9c26a248df22d55da87bdfaf41d7bc6e5df38bd848f0b13f42", + "0xa05a49a31e91dff6a52ac8b9c2cfdd646a43f0d488253f9e3cfbce52f26667166bbb9b608fc358763a65cbf066cd6d05", + "0xa59c3c1227fdd7c2e81f5e11ef5c406da44662987bac33caed72314081e2eed66055d38137e01b2268e58ec85dd986c0", + "0xb7020ec3bd73a99861f0f1d88cf5a19abab1cbe14b7de77c9868398c84bb8e18dbbe9831838a96b6d6ca06e82451c67b", + "0x98d1ff2525e9718ee59a21d8900621636fcd873d9a564b8dceb4be80a194a0148daf1232742730b3341514b2e5a5436c", + "0x886d97b635975fc638c1b6afc493e5998ca139edba131b75b65cfe5a8e814f11bb678e0eeee5e6e5cd913ad3f2fefdfc", + "0x8fb9fd928d38d5d813b671c924edd56601dd7163b686c13f158645c2f869d9250f3859aa5463a39258c90fef0f41190a", + "0xaac35e1cd655c94dec3580bb3800bd9c2946c4a9856f7d725af15fbea6a2d8ca51c8ad2772abed60ee0e3fb9cb24046b", + "0xb8d71fa0fa05ac9e443c9b4929df9e7f09a919be679692682e614d24227e04894bfc14a5c73a62fb927fedff4a0e4aa7", + "0xa45a19f11fbbb531a704badbb813ed8088ab827c884ee4e4ebf363fa1132ff7cfa9d28be9c85b143e4f7cdbc94e7cf1a", + "0x82b54703a4f295f5471b255ab59dce00f0fe90c9fb6e06b9ee48b15c91d43f4e2ef4a96c3118aeb03b08767be58181bb", + "0x8283264c8e6d2a36558f0d145c18576b6600ff45ff99cc93eca54b6c6422993cf392668633e5df396b9331e873d457e5", + "0x8c549c03131ead601bc30eb6b9537b5d3beb7472f5bb1bcbbfd1e9f3704477f7840ab3ab7f7dc13bbbbcdff886a462d4", + "0xafbb0c520ac1b5486513587700ad53e314cb74bfbc12e0b5fbdcfdaac36d342e8b59856196a0d84a25cff6e6e1d17e76", + "0x89e4c22ffb51f2829061b3c7c1983c5c750cad158e3a825d46f7cf875677da5d63f653d8a297022b5db5845c9271b32b", + "0xafb27a86c4c2373088c96b9adf4433f2ebfc78ac5c526e9f0510670b6e4e5e0057c0a4f75b185e1a30331b9e805c1c15", + "0xa18e16b57445f88730fc5d3567bf5a176861dc14c7a08ed2996fe80eed27a0e7628501bcb78a1727c5e9ac55f29c12c4", + "0x93d61bf88b192d6825cf4e1120af1c17aa0f994d158b405e25437eaeefae049f7b721a206e7cc8a04fdc29d3c42580a1", + "0xa99f2995a2e3ed2fd1228d64166112038de2f516410aa439f4c507044e2017ea388604e2d0f7121256fadf7fbe7023d1", + "0x914fd91cffc23c32f1c6d0e98bf660925090d873367d543034654389916f65f552e445b0300b71b61b721a72e9a5983c", + "0xb42a578a7787b71f924e7def425d849c1c777156b1d4170a8ee7709a4a914e816935131afd9a0412c4cb952957b20828", + "0x82fb30590e84b9e45db1ec475a39971cf554dc01bcc7050bc89265740725c02e2be5a972168c5170c86ae83e5b0ad2c0", + "0xb14f8d8e1e93a84976289e0cf0dfa6f3a1809e98da16ee5c4932d0e1ed6bf8a07697fdd4dd86a3df84fb0003353cdcc0", + "0x85d7a2f4bda31aa2cb208b771fe03291a4ebdaf6f1dc944c27775af5caec412584c1f45bc741fca2a6a85acb3f26ad7d", + "0xaf02e56ce886ff2253bc0a68faad76f25ead84b2144e5364f3fb9b648f03a50ee9dc0b2c33ebacf7c61e9e43201ef9ef", + "0x87e025558c8a0b0abd06dfc350016847ea5ced7af2d135a5c9eec9324a4858c4b21510fb0992ec52a73447f24945058e", + "0x80fff0bafcd058118f5e7a4d4f1ae0912efeb281d2cbe4d34ba8945cc3dbe5d8baf47fb077343b90b8d895c90b297aca", + "0xb6edcf3a40e7b1c3c0148f47a263cd819e585a51ef31c2e35a29ce6f04c53e413f743034c0d998d9c00a08ba00166f31", + "0xabb87ed86098c0c70a76e557262a494ff51a30fb193f1c1a32f8e35eafa34a43fcc07aa93a3b7a077d9e35afa07b1a3d", + "0xa280214cd3bb0fb7ecd2d8bcf518cbd9078417f2b91d2533ec2717563f090fb84f2a5fcfdbbeb2a2a1f8a71cc5aa5941", + "0xa63083ca7238ea2b57d15a475963cf1d4f550d8cd76db290014a0461b90351f1f26a67d674c837b0b773b330c7c3d534", + "0xa8fa39064cb585ece5263e2f42f430206476bf261bd50f18d2b694889bd79d04d56410664cecad62690e5c5a20b3f6ff", + "0x85ba52ce9d700a5dcf6c5b00559acbe599d671ce5512467ff4b6179d7fad550567ce2a9c126a50964e3096458ea87920", + "0xb913501e1008f076e5eac6d883105174f88b248e1c9801e568fefaffa1558e4909364fc6d9512aa4d125cbd7cc895f05", + "0x8eb33b5266c8f2ed4725a6ad147a322e44c9264cf261c933cbbe230a43d47fca0f29ec39756b20561dabafadd5796494", + "0x850ebc8b661a04318c9db5a0515066e6454fa73865aa4908767a837857ecd717387f614acb614a88e075d4edc53a2f5a", + "0xa08d6b92d866270f29f4ce23a3f5d99b36b1e241a01271ede02817c8ec3f552a5c562db400766c07b104a331835c0c64", + "0x8131804c89bb3e74e9718bfc4afa547c1005ff676bd4db9604335032b203390cfa54478d45c6c78d1fe31a436ed4be9f", + "0x9106d94f23cc1eacec8316f16d6f0a1cc160967c886f51981fdb9f3f12ee1182407d2bb24e5b873de58cb1a3ee915a6b", + "0xa13806bfc3eae7a7000c9d9f1bd25e10218d4e67f59ae798b145b098bca3edad2b1040e3fc1e6310e612fb8818f459ac", + "0x8c69fbca502046cb5f6db99900a47b34117aef3f4b241690cdb3b84ca2a2fc7833e149361995dc41fa78892525bce746", + "0x852c473150c91912d58ecb05769222fa18312800c3f56605ad29eec9e2d8667b0b81c379048d3d29100ed2773bb1f3c5", + "0xb1767f6074426a00e01095dbb1795beb4e4050c6411792cbad6537bc444c3165d1058bafd1487451f9c5ddd209e0ae7e", + "0x80c600a5fe99354ce59ff0f84c760923dc8ff66a30bf47dc0a086181785ceb01f9b951c4e66df800ea6d705e8bc47055", + "0xb5cf19002fbc88a0764865b82afcb4d64a50196ea361e5c71dff7de084f4dcbbc34ec94a45cc9e0247bd51da565981aa", + "0x93e67a254ea8ce25e112d93cc927fadaa814152a2c4ec7d9a56eaa1ed47aec99b7e9916b02e64452cc724a6641729bbb", + "0xace70b32491bda18eee4a4d041c3bc9effae9340fe7e6c2f5ad975ee0874c17f1a7da7c96bd85fccff9312c518fac6e9", + "0xab4cfa02065017dd7f1aadc66f2c92f78f0f11b8597c03a5d69d82cb2eaf95a4476a836ac102908f137662472c8d914b", + "0xa40b8cd8deb8ae503d20364d64cab7c2801b7728a9646ed19c65edea6a842756a2f636283494299584ad57f4bb12cd0b", + "0x8594e11d5fc2396bcd9dbf5509ce4816dbb2b7305168021c426171fb444d111da5a152d6835ad8034542277011c26c0e", + "0x8024de98c26b4c994a66628dc304bb737f4b6859c86ded552c5abb81fd4c6c2e19d5a30beed398a694b9b2fdea1dd06a", + "0x8843f5872f33f54df8d0e06166c1857d733995f67bc54abb8dfa94ad92407cf0179bc91b0a50bbb56cdc2b350d950329", + "0xb8bab44c7dd53ef9edf497dcb228e2a41282c90f00ba052fc52d57e87b5c8ab132d227af1fcdff9a12713d1f980bcaae", + "0x982b4d7b29aff22d527fd82d2a52601d95549bfb000429bb20789ed45e5abf1f4b7416c7b7c4b79431eb3574b29be658", + "0x8eb1f571b6a1878e11e8c1c757e0bc084bab5e82e897ca9be9b7f4b47b91679a8190bf0fc8f799d9b487da5442415857", + "0xa6e74b588e5af935c8b243e888582ef7718f8714569dd4992920740227518305eb35fab674d21a5551cca44b3e511ef2", + "0xa30fc2f3a4cb4f50566e82307de73cd7bd8fe2c1184e9293c136a9b9e926a018d57c6e4f308c95b9eb8299e94d90a2a1", + "0xa50c5869ca5d2b40722c056a32f918d47e0b65ca9d7863ca7d2fb4a7b64fe523fe9365cf0573733ceaadebf20b48fff8", + "0x83bbdd32c04d17581418cf360749c7a169b55d54f2427390defd9f751f100897b2d800ce6636c5bbc046c47508d60c8c", + "0xa82904bdf614de5d8deaff688c8a5e7ac5b3431687acbcda8fa53960b7c417a39c8b2e462d7af91ce6d79260f412db8e", + "0xa4362e31ff4b05d278b033cf5eebea20de01714ae16d4115d04c1da4754269873afc8171a6f56c5104bfd7b0db93c3e7", + "0xb5b8daa63a3735581e74a021b684a1038cea77168fdb7fdf83c670c2cfabcfc3ab2fc7359069b5f9048188351aef26b5", + "0xb48d723894b7782d96ac8433c48faca1bdfa5238019c451a7f47d958097cce3ae599b876cf274269236b9d6ff8b6d7ca", + "0x98ffff6a61a3a6205c7820a91ca2e7176fab5dba02bc194c4d14942ac421cb254183c705506ab279e4f8db066f941c6c", + "0xae7db24731da2eaa6efc4f7fcba2ecc26940ddd68038dce43acf2cee15b72dc4ef42a7bfdd32946d1ed78786dd7696b3", + "0xa656db14f1de9a7eb84f6301b4acb2fbf78bfe867f48a270e416c974ab92821eb4df1cb881b2d600cfed0034ac784641", + "0xaa315f8ecba85a5535e9a49e558b15f39520fce5d4bf43131bfbf2e2c9dfccc829074f9083e8d49f405fb221d0bc4c3c", + "0x90bffba5d9ff40a62f6c8e9fc402d5b95f6077ed58d030c93e321b8081b77d6b8dac3f63a92a7ddc01585cf2c127d66c", + "0xabdd733a36e0e0f05a570d0504e73801bf9b5a25ff2c78786f8b805704997acb2e6069af342538c581144d53149fa6d3", + "0xb4a723bb19e8c18a01bd449b1bb3440ddb2017f10bb153da27deb7a6a60e9bb37619d6d5435fbb1ba617687838e01dd0", + "0x870016b4678bab3375516db0187a2108b2e840bae4d264b9f4f27dbbc7cc9cac1d7dc582d7a04d6fd1ed588238e5e513", + "0x80d33d2e20e8fc170aa3cb4f69fffb72aeafb3b5bb4ea0bc79ab55da14142ca19b2d8b617a6b24d537366e3b49cb67c3", + "0xa7ee76aec273aaae03b3b87015789289551969fb175c11557da3ab77e39ab49d24634726f92affae9f4d24003050d974", + "0x8415ea4ab69d779ebd42d0fe0c6aef531d6a465a5739e429b1fcf433ec45aa8296c527e965a20f0ec9f340c9273ea3cf", + "0x8c7662520794e8b4405d0b33b5cac839784bc86a5868766c06cbc1fa306dbe334978177417b31baf90ce7b0052a29c56", + "0x902b2abecc053a3dbdea9897ee21e74821f3a1b98b2d560a514a35799f4680322550fd3a728d4f6d64e1de98033c32b8", + "0xa05e84ed9ecab8d508d670c39f2db61ad6e08d2795ec32a3c9d0d3737ef3801618f4fc2a95f90ec2f068606131e076c5", + "0x8b9208ff4d5af0c2e3f53c9375da666773ac57197dfabb0d25b1c8d0588ba7f3c15ee9661bb001297f322ea2fbf6928b", + "0xa3c827741b34a03254d4451b5ab74a96f2b9f7fb069e2f5adaf54fd97cc7a4d516d378db5ca07da87d8566d6eef13726", + "0x8509d8a3f4a0ed378e0a1e28ea02f6bf1d7f6c819c6c2f5297c7df54c895b848f841653e32ba2a2c22c2ff739571acb8", + "0xa0ce988b7d3c40b4e496aa83a09e4b5472a2d98679622f32bea23e6d607bc7de1a5374fb162bce0549a67dad948519be", + "0xaa8a3dd12bd60e3d2e05f9c683cdcb8eab17fc59134815f8d197681b1bcf65108cba63ac5c58ee632b1e5ed6bba5d474", + "0x8b955f1d894b3aefd883fb4b65f14cd37fc2b9db77db79273f1700bef9973bf3fd123897ea2b7989f50003733f8f7f21", + "0xac79c00ddac47f5daf8d9418d798d8af89fc6f1682e7e451f71ea3a405b0d36af35388dd2a332af790bc83ca7b819328", + "0xa0d44dd2a4438b809522b130d0938c3fe7c5c46379365dbd1810a170a9aa5818e1c783470dd5d0b6d4ac7edbb7330910", + "0xa30b69e39ad43dd540a43c521f05b51b5f1b9c4eed54b8162374ae11eac25da4f5756e7b70ce9f3c92c2eeceee7431ed", + "0xac43220b762c299c7951222ea19761ab938bf38e4972deef58ed84f4f9c68c230647cf7506d7cbfc08562fcca55f0485", + "0xb28233b46a8fb424cfa386a845a3b5399d8489ceb83c8f3e05c22c934798d639c93718b7b68ab3ce24c5358339e41cbb", + "0xac30d50ee8ce59a10d4b37a3a35e62cdb2273e5e52232e202ca7d7b8d09d28958ee667fae41a7bb6cdc6fe8f6e6c9c85", + "0xb199842d9141ad169f35cc7ff782b274cbaa645fdb727761e0a89edbf0d781a15f8218b4bf4eead326f2903dd88a9cc1", + "0x85e018c7ddcad34bb8285a737c578bf741ccd547e68c734bdb3808380e12c5d4ef60fc896b497a87d443ff9abd063b38", + "0x8c856e6ba4a815bdb891e1276f93545b7072f6cb1a9aa6aa5cf240976f29f4dee01878638500a6bf1daf677b96b54343", + "0xb8a47555fa8710534150e1a3f13eab33666017be6b41005397afa647ea49708565f2b86b77ad4964d140d9ced6b4d585", + "0x8cd1f1db1b2f4c85a3f46211599caf512d5439e2d8e184663d7d50166fd3008f0e9253272f898d81007988435f715881", + "0xb1f34b14612c973a3eceb716dc102b82ab18afef9de7630172c2780776679a7706a4874e1df3eaadf541fb009731807f", + "0xb25464af9cff883b55be2ff8daf610052c02df9a5e147a2cf4df6ce63edcdee6dc535c533590084cc177da85c5dc0baa", + "0x91c3c4b658b42d8d3448ae1415d4541d02379a40dc51e36a59bd6e7b9ba3ea51533f480c7c6e8405250ee9b96a466c29", + "0x86dc027b95deb74c36a58a1333a03e63cb5ae22d3b29d114cfd2271badb05268c9d0c819a977f5e0c6014b00c1512e3a", + "0xae0e6ff58eb5fa35da5107ebeacf222ab8f52a22bb1e13504247c1dfa65320f40d97b0e6b201cb6613476687cb2f0681", + "0x8f13415d960b9d7a1d93ef28afc2223e926639b63bdefce0f85e945dfc81670a55df288893a0d8b3abe13c5708f82f91", + "0x956f67ca49ad27c1e3a68c1faad5e7baf0160c459094bf6b7baf36b112de935fdfd79fa4a9ea87ea8de0ac07272969f4", + "0x835e45e4a67df9fb51b645d37840b3a15c171d571a10b03a406dd69d3c2f22df3aa9c5cbe1e73f8d767ce01c4914ea9a", + "0x919b938e56d4b32e2667469d0bdccb95d9dda3341aa907683ee70a14bbbe623035014511c261f4f59b318b610ac90aa3", + "0x96b48182121ccd9d689bf1dfdc228175564cd68dc904a99c808a7f0053a6f636c9d953e12198bdf2ea49ea92772f2e18", + "0xac5e5a941d567fa38fdbcfa8cf7f85bb304e3401c52d88752bcd516d1fa9bac4572534ea2205e38423c1df065990790f", + "0xac0bd594fb85a8d4fc26d6df0fa81f11919401f1ecf9168b891ec7f061a2d9368af99f7fd8d9b43b2ce361e7b8482159", + "0x83d92c69ca540d298fe80d8162a1c7af3fa9b49dfb69e85c1d136a3ec39fe419c9fa78e0bb6d96878771fbd37fe92e40", + "0xb35443ae8aa66c763c2db9273f908552fe458e96696b90e41dd509c17a5c04ee178e3490d9c6ba2dc0b8f793c433c134", + "0x923b2d25aa45b2e580ffd94cbb37dc8110f340f0f011217ee1bd81afb0714c0b1d5fb4db86006cdd2457563276f59c59", + "0x96c9125d38fca1a61ac21257b696f8ac3dae78def50285e44d90ea293d591d1c58f703540a7e4e99e070afe4646bbe15", + "0xb57946b2332077fbcdcb406b811779aefd54473b5559a163cd65cb8310679b7e2028aa55c12a1401fdcfcac0e6fae29a", + "0x845daedc5cf972883835d7e13c937b63753c2200324a3b8082a6c4abb4be06c5f7c629d4abe4bfaf1d80a1f073eb6ce6", + "0x91a55dfd0efefcd03dc6dacc64ec93b8d296cb83c0ee72400a36f27246e7f2a60e73b7b70ba65819e9cfb73edb7bd297", + "0x8874606b93266455fe8fdd25df9f8d2994e927460af06f2e97dd4d2d90db1e6b06d441b72c2e76504d753badca87fb37", + "0x8ee99e6d231274ff9252c0f4e84549da173041299ad1230929c3e3d32399731c4f20a502b4a307642cac9306ccd49d3c", + "0x8836497714a525118e20849d6933bb8535fb6f72b96337d49e3133d936999c90a398a740f42e772353b5f1c63581df6d", + "0xa6916945e10628f7497a6cdc5e2de113d25f7ade3e41e74d3de48ccd4fce9f2fa9ab69645275002e6f49399b798c40af", + "0x9597706983107eb23883e0812e1a2c58af7f3499d50c6e29b455946cb9812fde1aa323d9ed30d1c0ffd455abe32303cd", + "0xa24ee89f7f515cc33bdbdb822e7d5c1877d337f3b2162303cfc2dae028011c3a267c5cb4194afa63a4856a6e1c213448", + "0x8cd25315e4318801c2776824ae6e7d543cb85ed3bc2498ba5752df2e8142b37653cf9e60104d674be3aeb0a66912e97a", + "0xb5085ecbe793180b40dbeb879f4c976eaaccaca3a5246807dced5890e0ed24d35f3f86955e2460e14fb44ff5081c07ba", + "0x960188cc0b4f908633a6840963a6fa2205fc42c511c6c309685234911c5304ef4c304e3ae9c9c69daa2fb6a73560c256", + "0xa32d0a70bf15d569b4cda5aebe3e41e03c28bf99cdd34ffa6c5d58a097f322772acca904b3a47addb6c7492a7126ebac", + "0x977f72d06ad72d4aa4765e0f1f9f4a3231d9f030501f320fe7714cc5d329d08112789fa918c60dd7fdb5837d56bb7fc6", + "0x99fa038bb0470d45852bb871620d8d88520adb701712fcb1f278fed2882722b9e729e6cdce44c82caafad95e37d0e6f7", + "0xb855e8f4fc7634ada07e83b6c719a1e37acb06394bc8c7dcab7747a8c54e5df3943915f021364bd019fdea103864e55f", + "0x88bc2cd7458532e98c596ef59ea2cf640d7cc31b4c33cef9ed065c078d1d4eb49677a67de8e6229cc17ea48bace8ee5a", + "0xaaa78a3feaa836d944d987d813f9b9741afb076e6aca1ffa42682ab06d46d66e0c07b8f40b9dbd63e75e81efa1ef7b08", + "0xb7b080420cc4d808723b98b2a5b7b59c81e624ab568ecdfdeb8bf3aa151a581b6f56e983ef1b6f909661e25db40b0c69", + "0xabee85c462ac9a2c58e54f06c91b3e5cd8c5f9ab5b5deb602b53763c54826ed6deb0d6db315a8d7ad88733407e8d35e2", + "0x994d075c1527407547590df53e9d72dd31f037c763848d1662eebd4cefec93a24328c986802efa80e038cb760a5300f5", + "0xab8777640116dfb6678e8c7d5b36d01265dfb16321abbfc277da71556a34bb3be04bc4ae90124ed9c55386d2bfb3bda0", + "0x967e3a828bc59409144463bcf883a3a276b5f24bf3cbfdd7a42343348cba91e00b46ac285835a9b91eef171202974204", + "0x875a9f0c4ffe5bb1d8da5e3c8e41d0397aa6248422a628bd60bfae536a651417d4e8a7d2fb98e13f2dad3680f7bd86d3", + "0xacaa330c3e8f95d46b1880126572b238dbb6d04484d2cd4f257ab9642d8c9fc7b212188b9c7ac9e0fd135c520d46b1bf", + "0xaceb762edbb0f0c43dfcdb01ea7a1ac5918ca3882b1e7ebc4373521742f1ed5250d8966b498c00b2b0f4d13212e6dd0b", + "0x81d072b4ad258b3646f52f399bced97c613b22e7ad76373453d80b1650c0ca87edb291a041f8253b649b6e5429bb4cff", + "0x980a47d27416ac39c7c3a0ebe50c492f8c776ea1de44d5159ac7d889b6d554357f0a77f0e5d9d0ff41aae4369eba1fc2", + "0x8b4dfd5ef5573db1476d5e43aacfb5941e45d6297794508f29c454fe50ea622e6f068b28b3debe8635cf6036007de2e3", + "0xa60831559d6305839515b68f8c3bc7abbd8212cc4083502e19dd682d56ca37c9780fc3ce4ec2eae81ab23b221452dc57", + "0x951f6b2c1848ced9e8a2339c65918e00d3d22d3e59a0a660b1eca667d18f8430d737884e9805865ef3ed0fe1638a22d9", + "0xb02e38fe790b492aa5e89257c4986c9033a8b67010fa2add9787de857d53759170fdd67715ca658220b4e14b0ca48124", + "0xa51007e4346060746e6b0e4797fc08ef17f04a34fe24f307f6b6817edbb8ce2b176f40771d4ae8a60d6152cbebe62653", + "0xa510005b05c0b305075b27b243c9d64bcdce85146b6ed0e75a3178b5ff9608213f08c8c9246f2ca6035a0c3e31619860", + "0xaaff4ef27a7a23be3419d22197e13676d6e3810ceb06a9e920d38125745dc68a930f1741c9c2d9d5c875968e30f34ab5", + "0x864522a9af9857de9814e61383bebad1ba9a881696925a0ea6bfc6eff520d42c506bbe5685a9946ed710e889765be4a0", + "0xb63258c080d13f3b7d5b9f3ca9929f8982a6960bdb1b0f8676f4dca823971601672f15e653917bf5d3746bb220504913", + "0xb51ce0cb10869121ae310c7159ee1f3e3a9f8ad498827f72c3d56864808c1f21fa2881788f19ece884d3f705cd7bd0c5", + "0x95d9cecfc018c6ed510e441cf84c712d9909c778c16734706c93222257f64dcd2a9f1bd0b400ca271e22c9c487014274", + "0x8beff4d7d0140b86380ff4842a9bda94c2d2be638e20ac68a4912cb47dbe01a261857536375208040c0554929ced1ddc", + "0x891ff49258749e2b57c1e9b8e04b12c77d79c3308b1fb615a081f2aacdfb4b39e32d53e069ed136fdbd43c53b87418fa", + "0x9625cad224e163d387738825982d1e40eeff35fe816d10d7541d15fdc4d3eee48009090f3faef4024b249205b0b28f72", + "0x8f3947433d9bd01aa335895484b540a9025a19481a1c40b4f72dd676bfcf332713714fd4010bde936eaf9470fd239ed0", + "0xa00ec2d67789a7054b53f0e858a8a232706ccc29a9f3e389df7455f1a51a2e75801fd78469a13dbc25d28399ae4c6182", + "0xa3f65884506d4a62b8775a0ea0e3d78f5f46bc07910a93cd604022154eabdf1d73591e304d61edc869e91462951975e1", + "0xa14eef4fd5dfac311713f0faa9a60415e3d30b95a4590cbf95f2033dffb4d16c02e7ceff3dcd42148a4e3bc49cce2dd4", + "0x8afa11c0eef3c540e1e3460bc759bb2b6ea90743623f88e62950c94e370fe4fd01c22b6729beba4dcd4d581198d9358f", + "0xafb05548a69f0845ffcc5f5dc63e3cdb93cd270f5655173b9a950394b0583663f2b7164ba6df8d60c2e775c1d9f120af", + "0x97f179e01a947a906e1cbeafa083960bc9f1bade45742a3afee488dfb6011c1c6e2db09a355d77f5228a42ccaa7bdf8e", + "0x8447fca4d35f74b3efcbd96774f41874ca376bf85b79b6e66c92fa3f14bdd6e743a051f12a7fbfd87f319d1c6a5ce217", + "0xa57ca39c23617cd2cf32ff93b02161bd7baf52c4effb4679d9d5166406e103bc8f3c6b5209e17c37dbb02deb8bc72ddd", + "0x9667c7300ff80f0140be002b0e36caab07aaee7cce72679197c64d355e20d96196acaf54e06e1382167d081fe6f739c1", + "0x828126bb0559ce748809b622677267ca896fa2ee76360fd2c02990e6477e06a667241379ca7e65d61a5b64b96d7867de", + "0x8b8835dea6ba8cf61c91f01a4b3d2f8150b687a4ee09b45f2e5fc8f80f208ae5d142d8e3a18153f0722b90214e60c5a7", + "0xa98e8ff02049b4da386e3ee93db23bbb13dfeb72f1cfde72587c7e6d962780b7671c63e8ac3fbaeb1a6605e8d79e2f29", + "0x87a4892a0026d7e39ef3af632172b88337cb03669dea564bcdb70653b52d744730ebb5d642e20cb627acc9dbb547a26b", + "0x877352a22fc8052878a57effc159dac4d75fe08c84d3d5324c0bab6d564cdf868f33ceee515eee747e5856b62cfa0cc7", + "0x8b801ba8e2ff019ee62f64b8cb8a5f601fc35423eb0f9494b401050103e1307dc584e4e4b21249cd2c686e32475e96c3", + "0xa9e7338d6d4d9bfec91b2af28a8ed13b09415f57a3a00e5e777c93d768fdb3f8e4456ae48a2c6626b264226e911a0e28", + "0x99c05fedf40ac4726ed585d7c1544c6e79619a0d3fb6bda75a08c7f3c0008e8d5e19ed4da48de3216135f34a15eba17c", + "0xa61cce8a1a8b13a4a650fdbec0eeea8297c352a8238fb7cac95a0df18ed16ee02a3daa2de108fa122aca733bd8ad7855", + "0xb97f37da9005b440b4cb05870dd881bf8491fe735844f2d5c8281818583b38e02286e653d9f2e7fa5e74c3c3eb616540", + "0xa72164a8554da8e103f692ac5ebb4aece55d5194302b9f74b6f2a05335b6e39beede0bf7bf8c5bfd4d324a784c5fb08c", + "0xb87e8221c5341cd9cc8bb99c10fe730bc105550f25ed4b96c0d45e6142193a1b2e72f1b3857373a659b8c09be17b3d91", + "0xa41fb1f327ef91dcb7ac0787918376584890dd9a9675c297c45796e32d6e5985b12f9b80be47fc3a8596c245f419d395", + "0x90dafa3592bdbb3465c92e2a54c2531822ba0459d45d3e7a7092fa6b823f55af28357cb51896d4ec2d66029c82f08e26", + "0xa0a9adc872ebc396557f484f1dd21954d4f4a21c4aa5eec543f5fa386fe590839735c01f236574f7ff95407cd12de103", + "0xb8c5c940d58be7538acf8672852b5da3af34f82405ef2ce8e4c923f1362f97fc50921568d0fd2fe846edfb0823e62979", + "0x85aaf06a8b2d0dac89dafd00c28533f35dbd074978c2aaa5bef75db44a7b12aeb222e724f395513b9a535809a275e30b", + "0x81f3cbe82fbc7028c26a6c1808c604c63ba023a30c9f78a4c581340008dbda5ec07497ee849a2183fcd9124f7936af32", + "0xa11ac738de75fd60f15a34209d3825d5e23385796a4c7fc5931822f3f380af977dd0f7b59fbd58eed7777a071e21b680", + "0x85a279c493de03db6fa6c3e3c1b1b29adc9a8c4effc12400ae1128da8421954fa8b75ad19e5388fe4543b76fb0812813", + "0x83a217b395d59ab20db6c4adb1e9713fc9267f5f31a6c936042fe051ce8b541f579442f3dcf0fa16b9e6de9fd3518191", + "0x83a0b86e7d4ed8f9ccdc6dfc8ff1484509a6378fa6f09ed908e6ab9d1073f03011dc497e14304e4e3d181b57de06a5ab", + "0xa63ad69c9d25704ce1cc8e74f67818e5ed985f8f851afa8412248b2df5f833f83b95b27180e9e7273833ed0d07113d3b", + "0x99b1bc2021e63b561fe44ddd0af81fcc8627a91bfeecbbc989b642bc859abc0c8d636399701aad7bbaf6a385d5f27d61", + "0xb53434adb66f4a807a6ad917c6e856321753e559b1add70824e5c1e88191bf6993fccb9b8b911fc0f473fb11743acacd", + "0x97ed3b9e6fb99bf5f945d4a41f198161294866aa23f2327818cdd55cb5dc4c1a8eff29dd8b8d04902d6cd43a71835c82", + "0xb1e808260e368a18d9d10bdea5d60223ba1713b948c782285a27a99ae50cc5fc2c53d407de07155ecc16fb8a36d744a0", + "0xa3eb4665f18f71833fec43802730e56b3ee5a357ea30a888ad482725b169d6f1f6ade6e208ee081b2e2633079b82ba7d", + "0xab8beb2c8353fc9f571c18fdd02bdb977fc883313469e1277b0372fbbb33b80dcff354ca41de436d98d2ed710faa467e", + "0xaa9071cfa971e4a335a91ad634c98f2be51544cb21f040f2471d01bb97e1df2277ae1646e1ea8f55b7ba9f5c8c599b39", + "0x80b7dbfdcaf40f0678012acc634eba44ea51181475180d9deb2050dc4f2de395289edd0223018c81057ec79b04b04c49", + "0x89623d7f6cb17aa877af14de842c2d4ab7fd576d61ddd7518b5878620a01ded40b6010de0da3cdf31d837eecf30e9847", + "0xa773bb024ae74dd24761f266d4fb27d6fd366a8634febe8235376b1ae9065c2fe12c769f1d0407867dfbe9f5272c352f", + "0x8455a561c3aaa6ba64c881a5e13921c592b3a02e968f4fb24a2243c36202795d0366d9cc1a24e916f84d6e158b7aeac7", + "0x81d8bfc4b283cf702a40b87a2b96b275bdbf0def17e67d04842598610b67ea08c804d400c3e69fa09ea001eaf345b276", + "0xb8f8f82cb11fea1c99467013d7e167ff03deb0c65a677fab76ded58826d1ba29aa7cf9fcd7763615735ea3ad38e28719", + "0x89a6a04baf9cccc1db55179e1650b1a195dd91fb0aebc197a25143f0f393524d2589975e3fbfc2547126f0bced7fd6f2", + "0xb81b2162df045390f04df07cbd0962e6b6ca94275a63edded58001a2f28b2ae2af2c7a6cba4ecd753869684e77e7e799", + "0xa3757f722776e50de45c62d9c4a2ee0f5655a512344c4cbec542d8045332806568dd626a719ef21a4eb06792ca70f204", + "0x8c5590df96ec22179a4e8786de41beb44f987a1dcc508eb341eecbc0b39236fdfad47f108f852e87179ccf4e10091e59", + "0x87502f026ed4e10167419130b88c3737635c5b9074c364e1dd247cef5ef0fc064b4ae99b187e33301e438bbd2fe7d032", + "0xaf925a2165e980ced620ff12289129fe17670a90ae0f4db9d4b39bd887ccb1f5d2514ac9ecf910f6390a8fc66bd5be17", + "0x857fca899828cf5c65d26e3e8a6e658542782fc72762b3b9c73514919f83259e0f849a9d4838b40dc905fe43024d0d23", + "0x87ffebdbfb69a9e1007ebac4ffcb4090ff13705967b73937063719aa97908986effcb7262fdadc1ae0f95c3690e3245d", + "0xa9ff6c347ac6f4c6ab993b748802e96982eaf489dc69032269568412fc9a79e7c2850dfc991b28211b3522ee4454344b", + "0xa65b3159df4ec48bebb67cb3663cd744027ad98d970d620e05bf6c48f230fa45bf17527fe726fdf705419bb7a1bb913e", + "0x84b97b1e6408b6791831997b03cd91f027e7660fd492a93d95daafe61f02427371c0e237c75706412f442991dfdff989", + "0xab761c26527439b209af0ae6afccd9340bbed5fbe098734c3145b76c5d2cd7115d9227b2eb523882b7317fbb09180498", + "0xa0479a8da06d7a69c0b0fee60df4e691c19c551f5e7da286dab430bfbcabf31726508e20d26ea48c53365a7f00a3ad34", + "0xa732dfc9baa0f4f40b5756d2e8d8937742999623477458e0bc81431a7b633eefc6f53b3b7939fe0a020018549c954054", + "0x901502436a1169ba51dc479a5abe7c8d84e0943b16bc3c6a627b49b92cd46263c0005bc324c67509edd693f28e612af1", + "0xb627aee83474e7f84d1bab9b7f6b605e33b26297ac6bbf52d110d38ba10749032bd551641e73a383a303882367af429b", + "0x95108866745760baef4a46ef56f82da6de7e81c58b10126ebd2ba2cd13d339f91303bf2fb4dd104a6956aa3b13739503", + "0x899ed2ade37236cec90056f3569bc50f984f2247792defafcceb49ad0ca5f6f8a2f06573705300e07f0de0c759289ff5", + "0xa9f5eee196d608efe4bcef9bf71c646d27feb615e21252cf839a44a49fd89da8d26a758419e0085a05b1d59600e2dc42", + "0xb36c6f68fed6e6c85f1f4a162485f24817f2843ec5cbee45a1ebfa367d44892e464949c6669f7972dc7167af08d55d25", + "0xaaaede243a9a1b6162afbc8f571a52671a5a4519b4062e3f26777664e245ba873ed13b0492c5dbf0258c788c397a0e9e", + "0x972b4fb39c31cbe127bf9a32a5cc10d621ebdd9411df5e5da3d457f03b2ab2cd1f6372d8284a4a9400f0b06ecdbfd38e", + "0x8f6ca1e110e959a4b1d9a5ce5f212893cec21db40d64d5ac4d524f352d72198f923416a850bf845bc5a22a79c0ea2619", + "0xa0f3c93b22134f66f04b2553a53b738644d1665ceb196b8494b315a4c28236fb492017e4a0de4224827c78e42f9908b7", + "0x807fb5ee74f6c8735b0b5ca07e28506214fe4047dbeb00045d7c24f7849e98706aea79771241224939cb749cf1366c7d", + "0x915eb1ff034224c0b645442cdb7d669303fdc00ca464f91aaf0b6fde0b220a3a74ff0cb043c26c9f3a5667b3fdaa9420", + "0x8fda6cef56ed33fefffa9e6ac8e6f76b1af379f89761945c63dd448801f7bb8ca970504a7105fac2f74f652ccff32327", + "0x87380cffdcffb1d0820fa36b63cc081e72187f86d487315177d4d04da4533eb19a0e2ff6115ceab528887819c44a5164", + "0x8cd89e03411a18e7f16f968b89fb500c36d47d229f6487b99e62403a980058db5925ce249206743333538adfad168330", + "0x974451b1df33522ce7056de9f03e10c70bf302c44b0741a59df3d6877d53d61a7394dcee1dd46e013d7cb9d73419c092", + "0x98c35ddf645940260c490f384a49496a7352bb8e3f686feed815b1d38f59ded17b1ad6e84a209e773ed08f7b8ff1e4c2", + "0x963f386cf944bb9b2ddebb97171b64253ea0a2894ac40049bdd86cda392292315f3a3d490ca5d9628c890cfb669f0acb", + "0x8d507712152babd6d142ee682638da8495a6f3838136088df9424ef50d5ec28d815a198c9a4963610b22e49b4cdf95e9", + "0x83d4bc6b0be87c8a4f1e9c53f257719de0c73d85b490a41f7420e777311640937320557ff2f1d9bafd1daaa54f932356", + "0x82f5381c965b7a0718441131c4d13999f4cdce637698989a17ed97c8ea2e5bdb5d07719c5f7be8688edb081b23ede0f4", + "0xa6ebecab0b72a49dfd01d69fa37a7f74d34fb1d4fef0aa10e3d6fceb9eccd671225c230af89f6eb514250e41a5f91f52", + "0x846d185bdad6e11e604df7f753b7a08a28b643674221f0e750ebdb6b86ec584a29c869e131bca868972a507e61403f6a", + "0x85a98332292acb744bd1c0fd6fdcf1f889a78a2c9624d79413ffa194cc8dfa7821a4b60cde8081d4b5f71f51168dd67f", + "0x8f7d97c3b4597880d73200d074eb813d95432306e82dafc70b580b8e08cb8098b70f2d07b4b3ac6a4d77e92d57035031", + "0x8185439c8751e595825d7053518cbe121f191846a38d4dbcb558c3f9d7a3104f3153401adaaaf27843bbe2edb504bfe3", + "0xb3c00d8ece1518fca6b1215a139b0a0e26d9cba1b3a424f7ee59f30ce800a5db967279ed60958dd1f3ee69cf4dd1b204", + "0xa2e6cb6978e883f9719c3c0d44cfe8de0cc6f644b98f98858433bea8bbe7b612c8aca5952fccce4f195f9d54f9722dc2", + "0x99663087e3d5000abbec0fbda4e7342ec38846cc6a1505191fb3f1a337cb369455b7f8531a6eb8b0f7b2c4baf83cbe2b", + "0xab0836c6377a4dbc7ca6a4d6cf021d4cd60013877314dd05f351706b128d4af6337711ed3443cb6ca976f40d74070a9a", + "0x87abfd5126152fd3bac3c56230579b489436755ea89e0566aa349490b36a5d7b85028e9fb0710907042bcde6a6f5d7e3", + "0x974ba1033f75f60e0cf7c718a57ae1da3721cf9d0fb925714c46f027632bdd84cd9e6de4cf4d00bc55465b1c5ebb7384", + "0xa607b49d73689ac64f25cec71221d30d53e781e1100d19a2114a21da6507a60166166369d860bd314acb226596525670", + "0xa7c2b0b915d7beba94954f2aa7dd08ec075813661e2a3ecca5d28a0733e59583247fed9528eb28aba55b972cdbaf06eb", + "0xb8b3123e44128cc8efbe3270f2f94e50ca214a4294c71c3b851f8cbb70cb67fe9536cf07d04bf7fe380e5e3a29dd3c15", + "0xa59a07e343b62ad6445a0859a32b58c21a593f9ddbfe52049650f59628c93715aa1f4e1f45b109321756d0eeec8a5429", + "0x94f51f8a4ed18a6030d0aaa8899056744bd0e9dc9ac68f62b00355cddab11da5da16798db75f0bfbce0e5bdfe750c0b6", + "0x97460a97ca1e1fa5ce243b81425edc0ec19b7448e93f0b55bc9785eedeeafe194a3c8b33a61a5c72990edf375f122777", + "0x8fa859a089bc17d698a7ee381f37ce9beadf4e5b44fce5f6f29762bc04f96faff5d58c48c73631290325f05e9a1ecf49", + "0xabdf38f3b20fc95eff31de5aa9ef1031abfa48f1305ee57e4d507594570401503476d3bcc493838fc24d6967a3082c7f", + "0xb8914bfb82815abb86da35c64d39ab838581bc0bf08967192697d9663877825f2b9d6fbdcf9b410463482b3731361aef", + "0xa8187f9d22b193a5f578999954d6ec9aa9b32338ccadb8a3e1ce5bad5ea361d69016e1cdfac44e9d6c54e49dd88561b9", + "0xaac262cb7cba7fd62c14daa7b39677cabc1ef0947dd06dd89cac8570006a200f90d5f0353e84f5ff03179e3bebe14231", + "0xa630ef5ece9733b8c46c0a2df14a0f37647a85e69c63148e79ffdcc145707053f9f9d305c3f1cf3c7915cb46d33abd07", + "0xb102c237cb2e254588b6d53350dfda6901bd99493a3fbddb4121d45e0b475cf2663a40d7b9a75325eda83e4ba1e68cb3", + "0x86a930dd1ddcc16d1dfa00aa292cb6c2607d42c367e470aa920964b7c17ab6232a7108d1c2c11fc40fb7496547d0bbf8", + "0xa832fdc4500683e72a96cce61e62ac9ee812c37fe03527ad4cf893915ca1962cee80e72d4f82b20c8fc0b764376635a1", + "0x88ad985f448dabb04f8808efd90f273f11f5e6d0468b5489a1a6a3d77de342992a73eb842d419034968d733f101ff683", + "0x98a8538145f0d86f7fbf9a81c9140f6095c5bdd8960b1c6f3a1716428cd9cca1bf8322e6d0af24e6169abcf7df2b0ff6", + "0x9048c6eba5e062519011e177e955a200b2c00b3a0b8615bdecdebc217559d41058d3315f6d05617be531ef0f6aef0e51", + "0x833bf225ab6fc68cdcacf1ec1b50f9d05f5410e6cdcd8d56a3081dc2be8a8d07b81534d1ec93a25c2e270313dfb99e3b", + "0xa84bcd24c3da5e537e64a811b93c91bfc84d7729b9ead7f79078989a6eb76717d620c1fad17466a0519208651e92f5ff", + "0xb7cdd0a3fbd79aed93e1b5a44ca44a94e7af5ed911e4492f332e3a5ed146c7286bde01b52276a2fcc02780d2109874dd", + "0x8a19a09854e627cb95750d83c20c67442b66b35896a476358f993ba9ac114d32c59c1b3d0b8787ee3224cf3888b56c64", + "0xa9abd5afb8659ee52ada8fa5d57e7dd355f0a7350276f6160bec5fbf70d5f99234dd179eb221c913e22a49ec6d267846", + "0x8c13c4274c0d30d184e73eaf812200094bbbd57293780bdadbceb262e34dee5b453991e7f37c7333a654fc71c69d6445", + "0xa4320d73296ff8176ce0127ca1921c450e2a9c06eff936681ebaffb5a0b05b17fded24e548454de89aca2dcf6d7a9de4", + "0xb2b8b3e15c1f645f07783e5628aba614e60157889db41d8161d977606788842b67f83f361eae91815dc0abd84e09abd5", + "0xad26c3aa35ddfddc15719b8bb6c264aaec7065e88ac29ba820eb61f220fef451609a7bb037f3722d022e6c86e4f1dc88", + "0xb8615bf43e13ae5d7b8dd903ce37190800cd490f441c09b22aa29d7a29ed2c0417b7a08ead417868f1de2589deaadd80", + "0x8d3425e1482cd1e76750a76239d33c06b3554c3c3c87c15cb7ab58b1cee86a4c5c4178b44e23f36928365a1b484bde02", + "0x806893a62e38c941a7dd6f249c83af16596f69877cc737d8f73f6b8cd93cbc01177a7a276b2b8c6b0e5f2ad864db5994", + "0x86618f17fa4b0d65496b661bbb5ba3bc3a87129d30a4b7d4f515b904f4206ca5253a41f49fd52095861e5e065ec54f21", + "0x9551915da1304051e55717f4c31db761dcdcf3a1366c89a4af800a9e99aca93a357bf928307f098e62b44a02cb689a46", + "0x8f79c4ec0ec1146cb2a523b52fe33def90d7b5652a0cb9c2d1c8808a32293e00aec6969f5b1538e3a94cd1efa3937f86", + "0xa0c03e329a707300081780f1e310671315b4c6a4cedcb29697aedfabb07a9d5df83f27b20e9c44cf6b16e39d9ded5b98", + "0x86a7cfa7c8e7ce2c01dd0baec2139e97e8e090ad4e7b5f51518f83d564765003c65968f85481bbb97cb18f005ccc7d9f", + "0xa33811770c6dfda3f7f74e6ad0107a187fe622d61b444bbd84fd7ef6e03302e693b093df76f6ab39bb4e02afd84a575a", + "0x85480f5c10d4162a8e6702b5e04f801874d572a62a130be94b0c02b58c3c59bdcd48cd05f0a1c2839f88f06b6e3cd337", + "0x8e181011564b17f7d787fe0e7f3c87f6b62da9083c54c74fd6c357a1f464c123c1d3d8ade3cf72475000b464b14e2be3", + "0x8ee178937294b8c991337e0621ab37e9ffa4ca2bdb3284065c5e9c08aad6785d50cf156270ff9daf9a9127289710f55b", + "0x8bd1e8e2d37379d4b172f1aec96f2e41a6e1393158d7a3dbd9a95c8dd4f8e0b05336a42efc11a732e5f22b47fc5c271d", + "0x8f3da353cd487c13136a85677de8cedf306faae0edec733cf4f0046f82fa4639db4745b0095ff33a9766aba50de0cbcf", + "0x8d187c1e97638df0e4792b78e8c23967dac43d98ea268ca4aabea4e0fa06cb93183fd92d4c9df74118d7cc27bf54415e", + "0xa4c992f08c2f8bac0b74b3702fb0c75c9838d2ce90b28812019553d47613c14d8ce514d15443159d700b218c5a312c49", + "0xa6fd1874034a34c3ea962a316c018d9493d2b3719bb0ec4edbc7c56b240802b2228ab49bee6f04c8a3e9f6f24a48c1c2", + "0xb2efed8e799f8a15999020900dc2c58ece5a3641c90811b86a5198e593d7318b9d53b167818ccdfbe7df2414c9c34011", + "0x995ff7de6181ddf95e3ead746089c6148da3508e4e7a2323c81785718b754d356789b902e7e78e2edc6b0cbd4ff22c78", + "0x944073d24750a9068cbd020b834afc72d2dde87efac04482b3287b40678ad07588519a4176b10f2172a2c463d063a5cd", + "0x99db4b1bb76475a6fd75289986ef40367960279524378cc917525fb6ba02a145a218c1e9caeb99332332ab486a125ac0", + "0x89fce4ecd420f8e477af4353b16faabb39e063f3f3c98fde2858b1f2d1ef6eed46f0975a7c08f233b97899bf60ccd60a", + "0x8c09a4f07a02b80654798bc63aada39fd638d3e3c4236ccd8a5ca280350c31e4a89e5f4c9aafb34116e71da18c1226b8", + "0x85325cfa7ded346cc51a2894257eab56e7488dbff504f10f99f4cd2b630d913003761a50f175ed167e8073f1b6b63fb0", + "0xb678b4fbec09a8cc794dcbca185f133578f29e354e99c05f6d07ac323be20aecb11f781d12898168e86f2e0f09aca15e", + "0xa249cfcbca4d9ba0a13b5f6aac72bf9b899adf582f9746bb2ad043742b28915607467eb794fca3704278f9136f7642be", + "0x9438e036c836a990c5e17af3d78367a75b23c37f807228362b4d13e3ddcb9e431348a7b552d09d11a2e9680704a4514f", + "0x925ab70450af28c21a488bfb5d38ac994f784cf249d7fd9ad251bb7fd897a23e23d2528308c03415074d43330dc37ef4", + "0xa290563904d5a8c0058fc8330120365bdd2ba1fdbaef7a14bc65d4961bb4217acfaed11ab82669e359531f8bf589b8db", + "0xa7e07a7801b871fc9b981a71e195a3b4ba6b6313bc132b04796a125157e78fe5c11a3a46cf731a255ac2d78a4ae78cd0", + "0xb26cd2501ee72718b0eebab6fb24d955a71f363f36e0f6dff0ab1d2d7836dab88474c0cef43a2cc32701fca7e82f7df3", + "0xa1dc3b6c968f3de00f11275092290afab65b2200afbcfa8ddc70e751fa19dbbc300445d6d479a81bda3880729007e496", + "0xa9bc213e28b630889476a095947d323b9ac6461dea726f2dc9084473ae8e196d66fb792a21905ad4ec52a6d757863e7d", + "0xb25d178df8c2df8051e7c888e9fa677fde5922e602a95e966db9e4a3d6b23ce043d7dc48a5b375c6b7c78e966893e8c3", + "0xa1c8d88d72303692eaa7adf68ea41de4febec40cc14ae551bb4012afd786d7b6444a3196b5d9d5040655a3366d96b7cd", + "0xb22bd44f9235a47118a9bbe2ba5a2ba9ec62476061be2e8e57806c1a17a02f9a51403e849e2e589520b759abd0117683", + "0xb8add766050c0d69fe81d8d9ea73e1ed05f0135d093ff01debd7247e42dbb86ad950aceb3b50b9af6cdc14ab443b238f", + "0xaf2cf95f30ef478f018cf81d70d47d742120b09193d8bb77f0d41a5d2e1a80bfb467793d9e2471b4e0ad0cb2c3b42271", + "0x8af5ef2107ad284e246bb56e20fef2a255954f72de791cbdfd3be09f825298d8466064f3c98a50496c7277af32b5c0bc", + "0x85dc19558572844c2849e729395a0c125096476388bd1b14fa7f54a7c38008fc93e578da3aac6a52ff1504d6ca82db05", + "0xae8c9b43c49572e2e166d704caf5b4b621a3b47827bb2a3bcd71cdc599bba90396fd9a405261b13e831bb5d44c0827d7", + "0xa7ba7efede25f02e88f6f4cbf70643e76784a03d97e0fbd5d9437c2485283ad7ca3abb638a5f826cd9f6193e5dec0b6c", + "0x94a9d122f2f06ef709fd8016fd4b712d88052245a65a301f5f177ce22992f74ad05552b1f1af4e70d1eac62cef309752", + "0x82d999b3e7cf563833b8bc028ff63a6b26eb357dfdb3fd5f10e33a1f80a9b2cfa7814d871b32a7ebfbaa09e753e37c02", + "0xaec6edcde234df502a3268dd2c26f4a36a2e0db730afa83173f9c78fcb2b2f75510a02b80194327b792811caefda2725", + "0x94c0bfa66c9f91d462e9194144fdd12d96f9bbe745737e73bab8130607ee6ea9d740e2cfcbbd00a195746edb6369ee61", + "0xab7573dab8c9d46d339e3f491cb2826cabe8b49f85f1ede78d845fc3995537d1b4ab85140b7d0238d9c24daf0e5e2a7e", + "0x87e8b16832843251fe952dadfd01d41890ed4bb4b8fa0254550d92c8cced44368225eca83a6c3ad47a7f81ff8a80c984", + "0x9189d2d9a7c64791b19c0773ad4f0564ce6bea94aa275a917f78ad987f150fdb3e5e26e7fef9982ac184897ecc04683f", + "0xb3661bf19e2da41415396ae4dd051a9272e8a2580b06f1a1118f57b901fa237616a9f8075af1129af4eabfefedbe2f1c", + "0xaf43c86661fb15daf5d910a4e06837225e100fb5680bd3e4b10f79a2144c6ec48b1f8d6e6b98e067d36609a5d038889a", + "0x82ac0c7acaa83ddc86c5b4249aae12f28155989c7c6b91e5137a4ce05113c6cbc16f6c44948b0efd8665362d3162f16a", + "0x8f268d1195ab465beeeb112cd7ffd5d5548559a8bc01261106d3555533fc1971081b25558d884d552df0db1cddda89d8", + "0x8ef7caa5521f3e037586ce8ac872a4182ee20c7921c0065ed9986c047e3dda08294da1165f385d008b40d500f07d895f", + "0x8c2f98f6880550573fad46075d3eba26634b5b025ce25a0b4d6e0193352c8a1f0661064027a70fe8190b522405f9f4e3", + "0xb7653f353564feb164f0f89ec7949da475b8dad4a4d396d252fc2a884f6932d027b7eb2dc4d280702c74569319ed701a", + "0xa026904f4066333befd9b87a8fad791d014096af60cdd668ef919c24dbe295ff31f7a790e1e721ba40cf5105abca67f4", + "0x988f982004ada07a22dd345f2412a228d7a96b9cae2c487de42e392afe1e35c2655f829ce07a14629148ce7079a1f142", + "0x9616add009067ed135295fb74d5b223b006b312bf14663e547a0d306694ff3a8a7bb9cfc466986707192a26c0bce599f", + "0xad4c425de9855f6968a17ee9ae5b15e0a5b596411388cf976df62ecc6c847a6e2ddb2cea792a5f6e9113c2445dba3e5c", + "0xb698ac9d86afa3dc69ff8375061f88e3b0cff92ff6dfe747cebaf142e813c011851e7a2830c10993b715e7fd594604a9", + "0xa386fa189847bb3b798efca917461e38ead61a08b101948def0f82cd258b945ed4d45b53774b400af500670149e601b7", + "0x905c95abda2c68a6559d8a39b6db081c68cef1e1b4be63498004e1b2f408409be9350b5b5d86a30fd443e2b3e445640a", + "0x9116dade969e7ce8954afcdd43e5cab64dc15f6c1b8da9d2d69de3f02ba79e6c4f6c7f54d6bf586d30256ae405cd1e41", + "0xa3084d173eacd08c9b5084a196719b57e47a0179826fda73466758235d7ecdb87cbcf097bd6b510517d163a85a7c7edd", + "0x85bb00415ad3c9be99ff9ba83672cc59fdd24356b661ab93713a3c8eab34e125d8867f628a3c3891b8dc056e69cd0e83", + "0x8d58541f9f39ed2ee4478acce5d58d124031338ec11b0d55551f00a5a9a6351faa903a5d7c132dc5e4bb026e9cbd18e4", + "0xa622adf72dc250e54f672e14e128c700166168dbe0474cecb340da175346e89917c400677b1bc1c11fcc4cc26591d9db", + "0xb3f865014754b688ca8372e8448114fff87bf3ca99856ab9168894d0c4679782c1ced703f5b74e851b370630f5e6ee86", + "0xa7e490b2c40c2446fcd91861c020da9742c326a81180e38110558bb5d9f2341f1c1885e79b364e6419023d1cbdc47380", + "0xb3748d472b1062e54572badbb8e87ac36534407f74932e7fc5b8392d008e8e89758f1671d1e4d30ab0fa40551b13bb5e", + "0x89898a5c5ec4313aabc607b0049fd1ebad0e0c074920cf503c9275b564d91916c2c446d3096491c950b7af3ac5e4b0ed", + "0x8eb8c83fef2c9dd30ea44e286e9599ec5c20aba983f702e5438afe2e5b921884327ad8d1566c72395587efac79ca7d56", + "0xb92479599e806516ce21fb0bd422a1d1d925335ebe2b4a0a7e044dd275f30985a72b97292477053ac5f00e081430da80", + "0xa34ae450a324fe8a3c25a4d653a654f9580ed56bbea213b8096987bbad0f5701d809a17076435e18017fea4d69f414bc", + "0x81381afe6433d62faf62ea488f39675e0091835892ecc238e02acf1662669c6d3962a71a3db652f6fe3bc5f42a0e5dc5", + "0xa430d475bf8580c59111103316fe1aa79c523ea12f1d47a976bbfae76894717c20220e31cf259f08e84a693da6688d70", + "0xb842814c359754ece614deb7d184d679d05d16f18a14b288a401cef5dad2cf0d5ee90bad487b80923fc5573779d4e4e8", + "0x971d9a2627ff2a6d0dcf2af3d895dfbafca28b1c09610c466e4e2bff2746f8369de7f40d65b70aed135fe1d72564aa88", + "0x8f4ce1c59e22b1ce7a0664caaa7e53735b154cfba8d2c5cc4159f2385843de82ab58ed901be876c6f7fce69cb4130950", + "0x86cc9dc321b6264297987000d344fa297ef45bcc2a4df04e458fe2d907ad304c0ea2318e32c3179af639a9a56f3263cf", + "0x8229e0876dfe8f665c3fb19b250bd89d40f039bbf1b331468b403655be7be2e104c2fd07b9983580c742d5462ca39a43", + "0x99299d73066e8eb128f698e56a9f8506dfe4bd014931e86b6b487d6195d2198c6c5bf15cccb40ccf1f8ddb57e9da44a2", + "0xa3a3be37ac554c574b393b2f33d0a32a116c1a7cfeaf88c54299a4da2267149a5ecca71f94e6c0ef6e2f472b802f5189", + "0xa91700d1a00387502cdba98c90f75fbc4066fefe7cc221c8f0e660994c936badd7d2695893fde2260c8c11d5bdcdd951", + "0x8e03cae725b7f9562c5c5ab6361644b976a68bada3d7ca508abca8dfc80a469975689af1fba1abcf21bc2a190dab397d", + "0xb01461ad23b2a8fa8a6d241e1675855d23bc977dbf4714add8c4b4b7469ccf2375cec20e80cedfe49361d1a30414ac5b", + "0xa2673bf9bc621e3892c3d7dd4f1a9497f369add8cbaa3472409f4f86bd21ac67cfac357604828adfee6ada1835365029", + "0xa042dff4bf0dfc33c178ba1b335e798e6308915128de91b12e5dbbab7c4ac8d60a01f6aea028c3a6d87b9b01e4e74c01", + "0x86339e8a75293e4b3ae66b5630d375736b6e6b6b05c5cda5e73fbf7b2f2bd34c18a1d6cefede08625ce3046e77905cb8", + "0xaf2ebe1b7d073d03e3d98bc61af83bf26f7a8c130fd607aa92b75db22d14d016481b8aa231e2c9757695f55b7224a27f", + "0xa00ee882c9685e978041fd74a2c465f06e2a42ffd3db659053519925be5b454d6f401e3c12c746e49d910e4c5c9c5e8c", + "0x978a781c0e4e264e0dad57e438f1097d447d891a1e2aa0d5928f79a9d5c3faae6f258bc94fdc530b7b2fa6a9932bb193", + "0xaa4b7ce2e0c2c9e9655bf21e3e5651c8503bce27483017b0bf476be743ba06db10228b3a4c721219c0779747f11ca282", + "0xb003d1c459dacbcf1a715551311e45d7dbca83a185a65748ac74d1800bbeaba37765d9f5a1a221805c571910b34ebca8", + "0x95b6e531b38648049f0d19de09b881baa1f7ea3b2130816b006ad5703901a05da57467d1a3d9d2e7c73fb3f2e409363c", + "0xa6cf9c06593432d8eba23a4f131bb7f72b9bd51ab6b4b772a749fe03ed72b5ced835a349c6d9920dba2a39669cb7c684", + "0xaa3d59f6e2e96fbb66195bc58c8704e139fa76cd15e4d61035470bd6e305db9f98bcbf61ac1b95e95b69ba330454c1b3", + "0xb57f97959c208361de6d7e86dff2b873068adb0f158066e646f42ae90e650079798f165b5cd713141cd3a2a90a961d9a", + "0xa76ee8ed9052f6a7a8c69774bb2597be182942f08115baba03bf8faaeaee526feba86120039fe8ca7b9354c3b6e0a8e6", + "0x95689d78c867724823f564627d22d25010f278674c6d2d0cdb10329169a47580818995d1d727ce46c38a1e47943ebb89", + "0xab676d2256c6288a88e044b3d9ffd43eb9d5aaee00e8fc60ac921395fb835044c71a26ca948e557fed770f52d711e057", + "0x96351c72785c32e5d004b6f4a1259fb8153d631f0c93fed172f18e8ba438fbc5585c1618deeabd0d6d0b82173c2e6170", + "0x93dd8d3db576418e22536eba45ab7f56967c6c97c64260d6cddf38fb19c88f2ec5cd0e0156f50e70855eee8a2b879ffd", + "0xad6ff16f40f6de3d7a737f8e6cebd8416920c4ff89dbdcd75eabab414af9a6087f83ceb9aff7680aa86bff98bd09c8cc", + "0x84de53b11671abc9c38710e19540c5c403817562aeb22a88404cdaff792c1180f717dbdfe8f54940c062c4d032897429", + "0x872231b9efa1cdd447b312099a5c164c560440a9441d904e70f5abfc3b2a0d16be9a01aca5e0a2599a61e19407587e3d", + "0x88f44ac27094a2aa14e9dc40b099ee6d68f97385950f303969d889ee93d4635e34dff9239103bdf66a4b7cbba3e7eb7a", + "0xa59afebadf0260e832f6f44468443562f53fbaf7bcb5e46e1462d3f328ac437ce56edbca617659ac9883f9e13261fad7", + "0xb1990e42743a88de4deeacfd55fafeab3bc380cb95de43ed623d021a4f2353530bcab9594389c1844b1c5ea6634c4555", + "0x85051e841149a10e83f56764e042182208591396d0ce78c762c4a413e6836906df67f38c69793e158d64fef111407ba3", + "0x9778172bbd9b1f2ec6bbdd61829d7b39a7df494a818e31c654bf7f6a30139899c4822c1bf418dd4f923243067759ce63", + "0x9355005b4878c87804fc966e7d24f3e4b02bed35b4a77369d01f25a3dcbff7621b08306b1ac85b76fe7b4a3eb5f839b1", + "0x8f9dc6a54fac052e236f8f0e1f571ac4b5308a43acbe4cc8183bce26262ddaf7994e41cf3034a4cbeca2c505a151e3b1", + "0x8cc59c17307111723fe313046a09e0e32ea0cce62c13814ab7c6408c142d6a0311d801be4af53fc9240523f12045f9ef", + "0x8e6057975ed40a1932e47dd3ac778f72ee2a868d8540271301b1aa6858de1a5450f596466494a3e0488be4fbeb41c840", + "0x812145efbd6559ae13325d56a15940ca4253b17e72a9728986b563bb5acc13ec86453796506ac1a8f12bd6f9e4a288c3", + "0x911da0a6d6489eb3dab2ec4a16e36127e8a291ae68a6c2c9de33e97f3a9b1f00da57a94e270a0de79ecc5ecb45d19e83", + "0xb72ea85973f4b2a7e6e71962b0502024e979a73c18a9111130e158541fa47bbaaf53940c8f846913a517dc69982ba9e1", + "0xa7a56ad1dbdc55f177a7ad1d0af78447dc2673291e34e8ab74b26e2e2e7d8c5fe5dc89e7ef60f04a9508847b5b3a8188", + "0xb52503f6e5411db5d1e70f5fb72ccd6463fa0f197b3e51ca79c7b5a8ab2e894f0030476ada72534fa4eb4e06c3880f90", + "0xb51c7957a3d18c4e38f6358f2237b3904618d58b1de5dec53387d25a63772e675a5b714ad35a38185409931157d4b529", + "0xb86b4266e719d29c043d7ec091547aa6f65bbf2d8d831d1515957c5c06513b72aa82113e9645ad38a7bc3f5383504fa6", + "0xb95b547357e6601667b0f5f61f261800a44c2879cf94e879def6a105b1ad2bbf1795c3b98a90d588388e81789bd02681", + "0xa58fd4c5ae4673fa350da6777e13313d5d37ed1dafeeb8f4f171549765b84c895875d9d3ae6a9741f3d51006ef81d962", + "0x9398dc348d078a604aadc154e6eef2c0be1a93bb93ba7fe8976edc2840a3a318941338cc4d5f743310e539d9b46613d2", + "0x902c9f0095014c4a2f0dccaaab543debba6f4cc82c345a10aaf4e72511725dbed7a34cd393a5f4e48a3e5142b7be84ed", + "0xa7c0447849bb44d04a0393a680f6cd390093484a79a147dd238f5d878030d1c26646d88211108e59fe08b58ad20c6fbd", + "0x80db045535d6e67a422519f5c89699e37098449d249698a7cc173a26ccd06f60238ae6cc7242eb780a340705c906790c", + "0x8e52b451a299f30124505de2e74d5341e1b5597bdd13301cc39b05536c96e4380e7f1b5c7ef076f5b3005a868657f17c", + "0x824499e89701036037571761e977654d2760b8ce21f184f2879fda55d3cda1e7a95306b8abacf1caa79d3cc075b9d27f", + "0x9049b956b77f8453d2070607610b79db795588c0cec12943a0f5fe76f358dea81e4f57a4692112afda0e2c05c142b26f", + "0x81911647d818a4b5f4990bfd4bc13bf7be7b0059afcf1b6839333e8569cdb0172fd2945410d88879349f677abaed5eb3", + "0xad4048f19b8194ed45b6317d9492b71a89a66928353072659f5ce6c816d8f21e69b9d1817d793effe49ca1874daa1096", + "0x8d22f7b2ddb31458661abd34b65819a374a1f68c01fc6c9887edeba8b80c65bceadb8f57a3eb686374004b836261ef67", + "0x92637280c259bc6842884db3d6e32602a62252811ae9b019b3c1df664e8809ffe86db88cfdeb8af9f46435c9ee790267", + "0xa2f416379e52e3f5edc21641ea73dc76c99f7e29ea75b487e18bd233856f4c0183429f378d2bfc6cd736d29d6cadfa49", + "0x882cb6b76dbdc188615dcf1a8439eba05ffca637dd25197508156e03c930b17b9fed2938506fdd7b77567cb488f96222", + "0xb68b621bb198a763fb0634eddb93ed4b5156e59b96c88ca2246fd1aea3e6b77ed651e112ac41b30cd361fadc011d385e", + "0xa3cb22f6b675a29b2d1f827cacd30df14d463c93c3502ef965166f20d046af7f9ab7b2586a9c64f4eae4fad2d808a164", + "0x8302d9ce4403f48ca217079762ce42cee8bc30168686bb8d3a945fbd5acd53b39f028dce757b825eb63af2d5ae41169d", + "0xb2eef1fbd1a176f1f4cd10f2988c7329abe4eb16c7405099fb92baa724ab397bc98734ef7d4b24c0f53dd90f57520d04", + "0xa1bbef0bd684a3f0364a66bde9b29326bac7aa3dde4caed67f14fb84fed3de45c55e406702f1495a3e2864d4ee975030", + "0x976acdb0efb73e3a3b65633197692dedc2adaed674291ae3df76b827fc866d214e9cac9ca46baefc4405ff13f953d936", + "0xb9fbf71cc7b6690f601f0b1c74a19b7d14254183a2daaafec7dc3830cba5ae173d854bbfebeca985d1d908abe5ef0cda", + "0x90591d7b483598c94e38969c4dbb92710a1a894bcf147807f1bcbd8aa3ac210b9f2be65519aa829f8e1ccdc83ad9b8cf", + "0xa30568577c91866b9c40f0719d46b7b3b2e0b4a95e56196ac80898a2d89cc67880e1229933f2cd28ee3286f8d03414d7", + "0x97589a88c3850556b359ec5e891f0937f922a751ac7c95949d3bbc7058c172c387611c0f4cb06351ef02e5178b3dd9e4", + "0x98e7bbe27a1711f4545df742f17e3233fbcc63659d7419e1ca633f104cb02a32c84f2fac23ca2b84145c2672f68077ab", + "0xa7ddb91636e4506d8b7e92aa9f4720491bb71a72dadc47c7f4410e15f93e43d07d2b371951a0e6a18d1bd087aa96a5c4", + "0xa7c006692227a06db40bceac3d5b1daae60b5692dd9b54772bedb5fea0bcc91cbcdb530cac31900ffc70c5b3ffadc969", + "0x8d3ec6032778420dfa8be52066ba0e623467df33e4e1901dbadd586c5d750f4ccde499b5197e26b9ea43931214060f69", + "0x8d9a8410518ea64f89df319bfd1fc97a0971cdb9ad9b11d1f8fe834042ea7f8dce4db56eeaf179ff8dda93b6db93e5ce", + "0xa3c533e9b3aa04df20b9ff635cb1154ce303e045278fcf3f10f609064a5445552a1f93989c52ce852fd0bbd6e2b6c22e", + "0x81934f3a7f8c1ae60ec6e4f212986bcc316118c760a74155d06ce0a8c00a9b9669ec4e143ca214e1b995e41271774fd9", + "0xab8e2d01a71192093ef8fafa7485e795567cc9db95a93fb7cc4cf63a391ef89af5e2bfad4b827fffe02b89271300407f", + "0x83064a1eaa937a84e392226f1a60b7cfad4efaa802f66de5df7498962f7b2649924f63cd9962d47906380b97b9fe80e1", + "0xb4f5e64a15c6672e4b55417ee5dc292dcf93d7ea99965a888b1cc4f5474a11e5b6520eacbcf066840b343f4ceeb6bf33", + "0xa63d278b842456ef15c278b37a6ea0f27c7b3ffffefca77c7a66d2ea06c33c4631eb242bbb064d730e70a8262a7b848a", + "0x83a41a83dbcdf0d22dc049de082296204e848c453c5ab1ba75aa4067984e053acf6f8b6909a2e1f0009ed051a828a73b", + "0x819485b036b7958508f15f3c19436da069cbe635b0318ebe8c014cf1ef9ab2df038c81161b7027475bcfa6fff8dd9faf", + "0xaa40e38172806e1e045e167f3d1677ef12d5dcdc89b43639a170f68054bd196c4fae34c675c1644d198907a03f76ba57", + "0x969bae484883a9ed1fbed53b26b3d4ee4b0e39a6c93ece5b3a49daa01444a1c25727dabe62518546f36b047b311b177c", + "0x80a9e73a65da99664988b238096a090d313a0ee8e4235bc102fa79bb337b51bb08c4507814eb5baec22103ec512eaab0", + "0x86604379aec5bddda6cbe3ef99c0ac3a3c285b0b1a15b50451c7242cd42ae6b6c8acb717dcca7917838432df93a28502", + "0xa23407ee02a495bed06aa7e15f94cfb05c83e6d6fba64456a9bbabfa76b2b68c5c47de00ba169e710681f6a29bb41a22", + "0x98cff5ecc73b366c6a01b34ac9066cb34f7eeaf4f38a5429bad2d07e84a237047e2a065c7e8a0a6581017dadb4695deb", + "0x8de9f68a938f441f3b7ab84bb1f473c5f9e5c9e139e42b7ccee1d254bd57d0e99c2ccda0f3198f1fc5737f6023dd204e", + "0xb0ce48d815c2768fb472a315cad86aa033d0e9ca506f146656e2941829e0acb735590b4fbc713c2d18d3676db0a954ac", + "0x82f485cdefd5642a6af58ac6817991c49fac9c10ace60f90b27f1788cc026c2fe8afc83cf499b3444118f9f0103598a8", + "0x82c24550ed512a0d53fc56f64cc36b553823ae8766d75d772dacf038c460f16f108f87a39ceef7c66389790f799dbab3", + "0x859ffcf1fe9166388316149b9acc35694c0ea534d43f09dae9b86f4aa00a23b27144dda6a352e74b9516e8c8d6fc809c", + "0xb8f7f353eec45da77fb27742405e5ad08d95ec0f5b6842025be9def3d9892f85eb5dd0921b41e6eff373618dba215bca", + "0x8ccca4436f9017e426229290f5cd05eac3f16571a4713141a7461acfe8ae99cd5a95bf5b6df129148693c533966145da", + "0xa2c67ecc19c0178b2994846fea4c34c327a5d786ac4b09d1d13549d5be5996d8a89021d63d65cb814923388f47cc3a03", + "0xaa0ff87d676b418ec08f5cbf577ac7e744d1d0e9ebd14615b550eb86931eafd2a36d4732cc5d6fab1713fd7ab2f6f7c0", + "0x8aef4730bb65e44efd6bb9441c0ae897363a2f3054867590a2c2ecf4f0224e578c7a67f10b40f8453d9f492ac15a9b2d", + "0x86a187e13d8fba5addcfdd5b0410cedd352016c930f913addd769ee09faa6be5ca3e4b1bdb417a965c643a99bd92be42", + "0xa0a4e9632a7a094b14b29b78cd9c894218cdf6783e61671e0203865dc2a835350f465fbaf86168f28af7c478ca17bc89", + "0xa8c7b02d8deff2cd657d8447689a9c5e2cd74ef57c1314ac4d69084ac24a7471954d9ff43fe0907d875dcb65fd0d3ce5", + "0x97ded38760aa7be6b6960b5b50e83b618fe413cbf2bcc1da64c05140bcc32f5e0e709cd05bf8007949953fac5716bad9", + "0xb0d293835a24d64c2ae48ce26e550b71a8c94a0883103757fb6b07e30747f1a871707d23389ba2b2065fa6bafe220095", + "0x8f9e291bf849feaa575592e28e3c8d4b7283f733d41827262367ea1c40f298c7bcc16505255a906b62bf15d9f1ba85fb", + "0x998f4e2d12708b4fd85a61597ca2eddd750f73c9e0c9b3cf0825d8f8e01f1628fd19797dcaed3b16dc50331fc6b8b821", + "0xb30d1f8c115d0e63bf48f595dd10908416774c78b3bbb3194192995154d80ea042d2e94d858de5f8aa0261b093c401fd", + "0xb5d9c75bb41f964cbff3f00e96d9f1480c91df8913f139f0d385d27a19f57a820f838eb728e46823cbff00e21c660996", + "0xa6edec90b5d25350e2f5f0518777634f9e661ec9d30674cf5b156c4801746d62517751d90074830ac0f4b09911c262f1", + "0x82f98da1264b6b75b8fbeb6a4d96d6a05b25c24db0d57ba3a38efe3a82d0d4e331b9fc4237d6494ccfe4727206457519", + "0xb89511843453cf4ecd24669572d6371b1e529c8e284300c43e0d5bb6b3aaf35aeb634b3cb5c0a2868f0d5e959c1d0772", + "0xa82bf065676583e5c1d3b81987aaae5542f522ba39538263a944bb33ea5b514c649344a96c0205a3b197a3f930fcda6c", + "0xa37b47ea527b7e06c460776aa662d9a49ff4149d3993f1a974b0dd165f7171770d189b0e2ea54fd5fccb6a14b116e68a", + "0xa1017677f97dda818274d47556d09d0e4ccacb23a252f82a6cfe78c630ad46fb9806307445a59fb61262182de3a2b29c", + "0xb01e9fcac239ba270e6877b79273ddd768bf8a51d2ed8a051b1c11e18eff3de5920e2fcbfbd26f06d381eddd3b1f1e1b", + "0x82fcd53d803b1c8e4ed76adc339b7f3a5962d37042b9683aabac7513ac68775d4a566a9460183926a6a95dbe7d551a1f", + "0xa763e78995d55cd21cdb7ef75d9642d6e1c72453945e346ab6690c20a4e1eeec61bb848ef830ae4b56182535e3c71d8f", + "0xb769f4db602251d4b0a1186782799bdcef66de33c110999a5775c50b349666ffd83d4c89714c4e376f2efe021a5cfdb2", + "0xa59cbd1b785efcfa6e83fc3b1d8cf638820bc0c119726b5368f3fba9dce8e3414204fb1f1a88f6c1ff52e87961252f97", + "0x95c8c458fd01aa23ecf120481a9c6332ebec2e8bb70a308d0576926a858457021c277958cf79017ddd86a56cacc2d7db", + "0x82eb41390800287ae56e77f2e87709de5b871c8bdb67c10a80fc65f3acb9f7c29e8fa43047436e8933f27449ea61d94d", + "0xb3ec25e3545eb83aed2a1f3558d1a31c7edde4be145ecc13b33802654b77dc049b4f0065069dd9047b051e52ab11dcdd", + "0xb78a0c715738f56f0dc459ab99e252e3b579b208142836b3c416b704ca1de640ca082f29ebbcee648c8c127df06f6b1e", + "0xa4083149432eaaf9520188ebf4607d09cf664acd1f471d4fb654476e77a9eaae2251424ffda78d09b6cb880df35c1219", + "0x8c52857d68d6e9672df3db2df2dbf46b516a21a0e8a18eec09a6ae13c1ef8f369d03233320dd1c2c0bbe00abfc1ea18b", + "0x8c856089488803066bff3f8d8e09afb9baf20cecc33c8823c1c0836c3d45498c3de37e87c016b705207f60d2b00f8609", + "0x831a3df39be959047b2aead06b4dcd3012d7b29417f642b83c9e8ce8de24a3dbbd29c6fdf55e2db3f7ea04636c94e403", + "0xaed84d009f66544addabe404bf6d65af7779ce140dc561ff0c86a4078557b96b2053b7b8a43432ffb18cd814f143b9da", + "0x93282e4d72b0aa85212a77b336007d8ba071eea17492da19860f1ad16c1ea8867ccc27ef5c37c74b052465cc11ea4f52", + "0xa7b78b8c8d057194e8d68767f1488363f77c77bddd56c3da2bc70b6354c7aa76247c86d51f7371aa38a4aa7f7e3c0bb7", + "0xb1c77283d01dcd1bde649b5b044eac26befc98ff57cbee379fb5b8e420134a88f2fc7f0bf04d15e1fbd45d29e7590fe6", + "0xa4aa8de70330a73b2c6458f20a1067eed4b3474829b36970a8df125d53bbdda4f4a2c60063b7cccb0c80fc155527652f", + "0x948a6c79ba1b8ad7e0bed2fae2f0481c4e41b4d9bbdd9b58164e28e9065700e83f210c8d5351d0212e0b0b68b345b3a5", + "0x86a48c31dcbbf7b082c92d28e1f613a2378a910677d7db3a349dc089e4a1e24b12eee8e8206777a3a8c64748840b7387", + "0x976adb1af21e0fc34148917cf43d933d7bfd3fd12ed6c37039dcd5a4520e3c6cf5868539ba5bf082326430deb8a4458d", + "0xb93e1a4476f2c51864bb4037e7145f0635eb2827ab91732b98d49b6c07f6ac443111aa1f1da76d1888665cb897c3834e", + "0x8afd46fb23bf869999fa19784b18a432a1f252d09506b8dbb756af900518d3f5f244989b3d7c823d9029218c655d3dc6", + "0x83f1e59e3abeed18cdc632921672673f1cb6e330326e11c4e600e13e0d5bc11bdc970ae12952e15103a706fe720bf4d6", + "0x90ce4cc660714b0b673d48010641c09c00fc92a2c596208f65c46073d7f349dd8e6e077ba7dcef9403084971c3295b76", + "0x8b09b0f431a7c796561ecf1549b85048564de428dac0474522e9558b6065fede231886bc108539c104ce88ebd9b5d1b0", + "0x85d6e742e2fb16a7b0ba0df64bc2c0dbff9549be691f46a6669bca05e89c884af16822b85faefefb604ec48c8705a309", + "0xa87989ee231e468a712c66513746fcf03c14f103aadca0eac28e9732487deb56d7532e407953ab87a4bf8961588ef7b0", + "0xb00da10efe1c29ee03c9d37d5918e391ae30e48304e294696b81b434f65cf8c8b95b9d1758c64c25e534d045ba28696f", + "0x91c0e1fb49afe46c7056400baa06dbb5f6e479db78ee37e2d76c1f4e88994357e257b83b78624c4ef6091a6c0eb8254d", + "0x883fb797c498297ccbf9411a3e727c3614af4eccde41619b773dc7f3259950835ee79453debf178e11dec4d3ada687a0", + "0xa14703347e44eb5059070b2759297fcfcfc60e6893c0373eea069388eba3950aa06f1c57cd2c30984a2d6f9e9c92c79e", + "0xafebc7585b304ceba9a769634adff35940e89cd32682c78002822aab25eec3edc29342b7f5a42a56a1fec67821172ad5", + "0xaea3ff3822d09dba1425084ca95fd359718d856f6c133c5fabe2b2eed8303b6e0ba0d8698b48b93136a673baac174fd9", + "0xaf2456a09aa777d9e67aa6c7c49a1845ea5cdda2e39f4c935c34a5f8280d69d4eec570446998cbbe31ede69a91e90b06", + "0x82cada19fed16b891ef3442bafd49e1f07c00c2f57b2492dd4ee36af2bd6fd877d6cb41188a4d6ce9ec8d48e8133d697", + "0x82a21034c832287f616619a37c122cee265cc34ae75e881fcaea4ea7f689f3c2bc8150bbf7dbcfd123522bfb7f7b1d68", + "0x86877217105f5d0ec3eeff0289fc2a70d505c9fdf7862e8159553ef60908fb1a27bdaf899381356a4ef4649072a9796c", + "0x82b196e49c6e861089a427c0b4671d464e9d15555ffb90954cd0d630d7ae02eb3d98ceb529d00719c2526cd96481355a", + "0xa29b41d0d43d26ce76d4358e0db2b77df11f56e389f3b084d8af70a636218bd3ac86b36a9fe46ec9058c26a490f887f7", + "0xa4311c4c20c4d7dd943765099c50f2fd423e203ccfe98ff00087d205467a7873762510cac5fdce7a308913ed07991ed7", + "0xb1f040fc5cc51550cb2c25cf1fd418ecdd961635a11f365515f0cb4ffb31da71f48128c233e9cc7c0cf3978d757ec84e", + "0xa9ebae46f86d3bd543c5f207ed0d1aed94b8375dc991161d7a271f01592912072e083e2daf30c146430894e37325a1b9", + "0x826418c8e17ad902b5fe88736323a47e0ca7a44bce4cbe27846ec8fe81de1e8942455dda6d30e192cdcc73e11df31256", + "0x85199db563427c5edcbac21f3d39fec2357be91fb571982ddcdc4646b446ad5ced84410de008cb47b3477ee0d532daf8", + "0xb7eed9cd400b2ca12bf1d9ae008214b8561fb09c8ad9ff959e626ffde00fee5ff2f5b6612e231f2a1a9b1646fcc575e3", + "0x8b40bf12501dcbac78f5a314941326bfcddf7907c83d8d887d0bb149207f85d80cd4dfbd7935439ea7b14ea39a3fded7", + "0x83e3041af302485399ba6cd5120e17af61043977083887e8d26b15feec4a6b11171ac5c06e6ad0971d4b58a81ff12af3", + "0x8f5b9a0eecc589dbf8c35a65d5e996a659277ef6ea509739c0cb7b3e2da9895e8c8012de662e5b23c5fa85d4a8f48904", + "0x835d71ed5e919d89d8e6455f234f3ff215462c4e3720c371ac8c75e83b19dfe3ae15a81547e4dc1138e5f5997f413cc9", + "0x8b7d2e4614716b1db18e9370176ea483e6abe8acdcc3dcdf5fb1f4d22ca55d652feebdccc171c6de38398d9f7bfdec7a", + "0x93eace72036fe57d019676a02acf3d224cf376f166658c1bf705db4f24295881d477d6fdd7916efcfceff8c7a063deda", + "0xb1ac460b3d516879a84bc886c54f020a9d799e7c49af3e4d7de5bf0d2793c852254c5d8fe5616147e6659512e5ccb012", + "0xacd0947a35cb167a48bcd9667620464b54ac0e78f9316b4aa92dcaab5422d7a732087e52e1c827faa847c6b2fe6e7766", + "0x94ac33d21c3d12ff762d32557860e911cd94d666609ddcc42161b9c16f28d24a526e8b10bb03137257a92cec25ae637d", + "0x832e02058b6b994eadd8702921486241f9a19e68ed1406dad545e000a491ae510f525ccf9d10a4bba91c68f2c53a0f58", + "0x9471035d14f78ff8f463b9901dd476b587bb07225c351161915c2e9c6114c3c78a501379ab6fb4eb03194c457cbd22bf", + "0xab64593e034c6241d357fcbc32d8ea5593445a5e7c24cac81ad12bd2ef01843d477a36dc1ba21dbe63b440750d72096a", + "0x9850f3b30045e927ad3ec4123a32ed2eb4c911f572b6abb79121873f91016f0d80268de8b12e2093a4904f6e6cab7642", + "0x987212c36b4722fe2e54fa30c52b1e54474439f9f35ca6ad33c5130cd305b8b54b532dd80ffd2c274105f20ce6d79f6e", + "0x8b4d0c6abcb239b5ed47bef63bc17efe558a27462c8208fa652b056e9eae9665787cd1aee34fbb55beb045c8bfdb882b", + "0xa9f3483c6fee2fe41312d89dd4355d5b2193ac413258993805c5cbbf0a59221f879386d3e7a28e73014f10e65dd503d9", + "0xa2225da3119b9b7c83d514b9f3aeb9a6d9e32d9cbf9309cbb971fd53c4b2c001d10d880a8ad8a7c281b21d85ceca0b7c", + "0xa050be52e54e676c151f7a54453bbb707232f849beab4f3bf504b4d620f59ed214409d7c2bd3000f3ff13184ccda1c35", + "0xadbccf681e15b3edb6455a68d292b0a1d0f5a4cb135613f5e6db9943f02181341d5755875db6ee474e19ace1c0634a28", + "0x8b6eff675632a6fad0111ec72aacc61c7387380eb87933fd1d098856387d418bd38e77d897e65d6fe35951d0627c550b", + "0xaabe2328ddf90989b15e409b91ef055cb02757d34987849ae6d60bef2c902bf8251ed21ab30acf39e500d1d511e90845", + "0x92ba4eb1f796bc3d8b03515f65c045b66e2734c2da3fc507fdd9d6b5d1e19ab3893726816a32141db7a31099ca817d96", + "0x8a98b3cf353138a1810beb60e946183803ef1d39ac4ea92f5a1e03060d35a4774a6e52b14ead54f6794d5f4022b8685c", + "0x909f8a5c13ec4a59b649ed3bee9f5d13b21d7f3e2636fd2bb3413c0646573fdf9243d63083356f12f5147545339fcd55", + "0x9359d914d1267633141328ed0790d81c695fea3ddd2d406c0df3d81d0c64931cf316fe4d92f4353c99ff63e2aefc4e34", + "0xb88302031681b54415fe8fbfa161c032ea345c6af63d2fb8ad97615103fd4d4281c5a9cae5b0794c4657b97571a81d3b", + "0x992c80192a519038082446b1fb947323005b275e25f2c14c33cc7269e0ec038581cc43705894f94bad62ae33a8b7f965", + "0xa78253e3e3eece124bef84a0a8807ce76573509f6861d0b6f70d0aa35a30a123a9da5e01e84969708c40b0669eb70aa6", + "0x8d5724de45270ca91c94792e8584e676547d7ac1ac816a6bb9982ee854eb5df071d20545cdfd3771cd40f90e5ba04c8e", + "0x825a6f586726c68d45f00ad0f5a4436523317939a47713f78fd4fe81cd74236fdac1b04ecd97c2d0267d6f4981d7beb1" ], - "setup_G2": [ + "g2_monomial": [ "0x93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", - "0x99aca9fb2f7760cecb892bf7262c176b334824f5727f680bba701a33e322cb6667531410dfc7c8e4321a3f0ea8af48cb1436638a2093123f046f0f504cc2a864825542873edbbc5d7ed17af125a4f2cf6433c6f4f61b81173726981dd989761d", - "0x88e2e982982bf8231e747e9dfcd14c05bd02623d1332734d2af26246c6869fb56ee6c994843f593178a040495ba61f4a083b0e18110b1d9f5224783d8f9a895e8ee744e87929430e9ba96bd29251cbf61240b256d1525600f3d562894d93d659", - "0xa2d33775e3d9e6af0d1b27d389e6c021a578e617a3d6627686db6288d4b3dffd7a847a00f7ef01828b7f42885b660e4204923402aca18fbae74ccd4e9c50dd8c2281b38dc09c022342ed1ac695d53f7081cb21f05fdfc0a3508c04759196fcd3", - "0xaf565445d2ad54c83a75c40e8895f5ad7219a8c728bce9d58d7a83716e095432993ebbd3f6911c66415a6f920d1a4d171478509b54a114308a020b33bf4487a7a8d0aa76ae4676a9b54e765a680f562d3a4fcb2e92c58b14b49b5b2917cc258f", - "0x8aa99cfaf514cef4801599cadd780d222194ca1ad69a34779c2bcfda93e5dbeb931e13914421b5809a6c81f12cf7038b04a35257cc9e94c33761e68565b1274aa6a6f9d66477229747a66b308b138f92aa4326a3bf23df65a1fe33b3b289bfe1", - "0x99ba36d8b4f56bde026099278548b1afc0a987cbd7c9baa51fc8e6cbb8237a17636f1a44a385cec69b05a5802059956a11fe793cabb939c38800f9c239ca2518e898ade1ec2513c9ee492071a35aabd78182392a09123d28dbc233313c9120c4", - "0xa7dc40c36afccb30a2eaff250860b28b227c195cf05674704c567d77d6655c446ae835f8fc8667e71147ab02afcb2dad0babe60cbfa37d7c2cddc68d2dec54f28a4142f8353590a3902d5ddaa22066ab563dd1435dda83f276387b9767d69120", - "0x939e6cc97a8b88572852a5b7f25e4838556307f60aeafb5d2b6961edbcafd4b48cb6ac980ffbacf4be963f324ba81e3d12de4f1459d8c746d0762c66ae1b166027f7fbe641d9c48f3c7d97b06d956b0be51dcc9aab65f3e99e1388e63bdd79f9", - "0xb391e156541dfd4003d1697cdb7ec815b309807320574906b2e652ef0175828b356d215cd374b1b34d9f470b3fa0e643113e67b2273268f922f04f072cfb89008358185b25cd631f82911a3f20f90f75758ffb99bebb8076458ae1e9d1ae898c", - "0xb9ac9c84934cc2a85c876eff65577e1dfce1935cd6392c877dd881a7d2f5c3e9344f28c04f90c62a6db4237ca00f9e0d00cb5f63e3f060fc7303916e19273b6fe455f331cabbe2fe5a22d584484f0d4176120fec9819fbb0a01e6d38695acfcd", - "0x88209eb030c5d78734bf2c2a5c539653fd3c24b4c08e624f9ddc4a6550efbdc1054a56eb0c807595aad6de56fda326aa196d032a8b4b48d40140a2d77df3c7243eda6507936389a321a5811eb38e32ee433c788deeae1eb928b00940e2944bcc", - "0xa8632ddc9cf7cbc1e8b74a05b7d4a89618c64afe30367ca0c9550ae7d320bf4e51c5a69e1501a1d8bee4240d13d7835501aa39fdc401a74f4d5734e268a7ce29a1fcfdb0a8bc64e0dd4a9e8578d6985bc2bc6a3764ce7a3703f6fb2e52557a2b", - "0xa037ac67e8bb6f4193ac967e05d080a489f58ef8d3d30a89798246f3e4936121ee445b03e410a09e8ebc0db2e2477d110aad0ade99b0887f1eb016e750f42135866907f150bd6f4f99a8cb94281474166874808ebe03b118c5daab16dafdc38b", - "0xa50d9143116bffa3b237da8e1805327e81e9cd25e658289bd727d5f9e0020172cc8690dcfe31a240e5cbc48353b88c4908baa1dd7320165556e0aa633f62fcbe7870222d345a3bbcdb7ab6c07f0fd86be559964afabf56f0a8cbc0b4b91d477e", - "0xafa988ea6fa4f40c5ad07d2d580d29025ddf56d6ef1171a8b8de3464203f70b97d6f5ace72747345204b35150e06154d1477516a989ce8eea7871cc0d0de00a077c0fb23ad4837e409d0b885bf3f2dde11a30fa6273d662e68e09f461e52932f", - "0x97fa1a943ed8b81574304a3d03f4f15907f6e6e0cd36a66bd2ad2c75afafc70a61d3ff69b77ebe4dae9ca0fcedef80081062705e60bbb6ea0f1f398c84d2f8e4a3ac142ac66426c21ad5e9994ebbcc406af474c4aec5e32fadcb21875af7c9f1", - "0xb30a564614493886f14a5dd71c89457504f8c59a7ac01b665ed167e9a8f9ee5832198fd319ecd234196ee57031bdf3840bd5a923e203a1938bc795c704b5285389750e1fd10d7050061ba19db00a60a2c0384a7d661d7d48ebe6962272230859", - "0x84c8dea942cfae71cb02e705ec496d967425793ce8812e7ee53c2f23713abeaff566a658cd1c73dfd18187d16253a6ee0a623e82cd18e31cd1a1875d19c078835dc9292e141686150a88065226ada264740143e87c03a0f6c4da8c187438ebf4", - "0x8c3abae8aed60338f8c4ff80aab22f8a2ae56756a93566c906f490a97151d34a1c3318054e1c494c60cc53327ad86a2d02c6c76a406726ce4f88635bc32eff0db0b61762dc518b95fa8da82e87e4bf3de54f1d72180ef53ed7bc5413e6a9a510", - "0xa328230c92a6b1cef6a444bcb64edb992f71e3d7b93f0b6b8b408ba7c908db746d92ddb2c7588bab438ef3bc61be1c2f0dfc86ba2ff514b42b35c80f89b2e780f813ea1dfb977fbded2cd9b553b747fa952e227ebd8f071163d421fc337f04c9", - "0xb482cab423cd5f1c5df036070aade7aa016283d69619d664025c3feab866a0a5691d344b2ee2bedc5dedd1f9a73eae16003a3827c9e5bbe22ded32d848fba840ffad1141ad158f5c40bc8ae0d03781b9705d851a7f1391b096c576c0f4f2a6b0", - "0x919ee1df27fabcb21237a1b7b98f53d41d849e1b6a8f9e28c3fae2841c6b5a250e4041c737e6725476e5cd715e34d3880f58d80f61efaabc261bdc703e8750f48a923e9bf8980931b9fd9e40014c66c54b3e7c98241d76d1aa47af43313a65a1", - "0xac94830145dbe9a8f7e6e0fc1f5fb454502d22abcafdc2dd96c6933c604461fa83b2b37385f4bc454875a02a6d4157841250956783515d11c7456e7f11b745f12856d89f5feedaf6a61a483a6c33a21cd2ba0c18eb41a1a2e7fc33bb53e4c570", - "0xb209c699f1233735c5bb4bce848e4365fd76651ae2184d2279a90df0c2f69ffa2a24d84a9b9f274021072953c0d65e1a0202d490d6c37186af240114e445d87bff754b4824937e4f2c90a574061b1c4910fed88d90f698025a2a264e656cb8a4", - "0x93320dc0576b0d069de63c40e5582b4486d9adf5e69e77e3ebaf3da26976fe42147a65051501bc8383f99e7ba75479c70a6726c2cd08bf98c7481f1f819712292d833a879f21a1221a9610bc748fb5e911055122fdb4055cdc84e8bfe0f4df9b", - "0xa4380b240e998cdf668591f71a0c88ed143b0185a920787627ce65095f8223dc606fa5bce93377af100de92d663e675c0736d7f1973603a84a5c4162fb5e01c88c7493503ae1d7e9fbe8ece9b418397d68c21eeb88dae226e09875d372c646dd", - "0xaab48517d69135a16b36b685adfe9b2544a709135a21ba3e75981a2cba4ec81d1fe28ac0f72fde0c0001c15300ed6a810f58d3117bdd58d0149751d6508cf8a1a1ff7b63dd02d2730a9d6fe96c77c502fe8ed46d50a181ec4bb35e37dfbd6af4", - "0x8277265fe75ab89ce4ec65b33fb4084bec0a56d81faf2f7a9070d2ca3065678e03a790350eba56323a54e0285bc32fe8007d5259740fde226e16cbde8354eacd562294eb9b7f727ed72ffbdad86f467cf057c737b34b80a41deb92634ed866f5", - "0xaa40a24cb2ebe606d969392c03020070f044c95088d80f57f771b837c048342d2cd3474600d7660441090ffb8d2ffb7f0eddd67eb378e3e1477a6ba0bc38096d5d2d3355bc8b60f605f57f0c1899da591457440352381d2b38c0aa9acc7fe419", - "0x80815d10685808cb630820629bcd2fa9041c9b74433630c0b9c1b7f7e8edf1440b520217f76ec9a50c125cf4438aa66006a1928a9ed2321da7ea325c3d56b65462b72118ca2c99a0ea733aa11da9abbeda6cc71ffeed301ae70213a29e697dcd", - "0xac235d079f91b00b1fead7523da8f73a5409fa8970907af0c5d5e4c6a0996dccfcdb0d822d08c7fbc0c24799457d011d04312d20831825f23cf988141056a6814c8a1cac9efe37bdcbfa272aed24cd92810fea7c49b0d07683a5c53643872179", - "0xb8aa59534d75fa5ac1c2c3f963bf73899aff5210059dbde8a8635561c6249e5143affee3bd2fd57575213b52d9a73d5702525867a7dcbb1d0a49b98c2925556fc5463ff0209742046a24ab29e74257d6419401093cc4371944d811cc300b6a67", - "0x80bbfc5b816eea29a6d84e2217dee4d547306994d39e5592515e1b0807b67fe960d1d5addb0ff1a20c158bdb294c04bf093d28996121845a2c9268e2c9ac0f4067e889c6aaca62f8535d35b45036954bd069e3afa84f04721538c26003304c20", - "0xa535c17d0e151d0e03d42dd58ba8c715bee3fabca2890e0e016071d34184b6b34e770d2be29c8ec76b69bcc471d50f4d043c2c240e9b93a81cff7ee2724e02018dfd9b534e40be641fdb4884abcd83b76f517557ffba508f1ba2f56313f4de94", - "0xb237eb7465df0d325a3aa58269be2627e4978f9863f4f100ed4c303cb1f6549e606f2e3c9180824d8049191965c8dacd0a0c76cc56cb22cf1bcfdb39372c8aa29b4f7b34582b1719e6bd59c930d87d5ccd838743b585d6e229d5ed42337315c0", - "0x805c335a2a9d2de30809cf30808ef836d88e9453c510716f01696f14c72dd60505eca8f128970edc8e63a9aa1f8792ac0dd50dcc84fbf4cc8b32349c682a6a27bc7551c7aa273a94c1606d07710188d93579afe3be1781bded15a34ed6047922", - "0xb25dadf385ddd3c39bcb0a014d3d4f66127946b1aceae8809e3a03d66cc25e27142ca108316391f857fe82fdea4db2520cc73793b695eafbf3ade00ef7ec747b0457e49303f5e1a370f5263b436566fe24a0876e5fe088238c7be37a0718d65f", - "0xb0f753081cabe2c8fce73aba82ff67dbc9842598b3e7fa3ce2a1f534536f8ac63c532fe66552ac6b7adb28c73ed4c8a4184849be7c1756a4681ce29ebf5e1c3aa806b667ee6bd68f6397aba3215dc1caec6742f21d681e32cd1160d6a3b1d7ee", - "0xb798771eeb3d7a17c62ba5916cc034bba870da6b1ac14c2e1cae71af3ad4e0c0d1ff983f691e0e55289d5a33b131f2ec12430c9566dd71f4d8be9c79155357a5c30c5efcfd75bbe1bb6d5ada4d50604ea49ed838d3641f268ca6e25c9c4b6b72", - "0xb52554c017388b099804abbe565346591a086d9979e10140ddaccc0a3680e506db775d7cbeafde67563adf0f09f5c2420caf19629f4e8f03e6fe02e9416ecd5269989e482b90004a083967d1141387eb74865bac6bd17e7a6d5f58225e52d4b7", - "0xb520ff694520919023d44d53f98a7de2f78ff37b2d9193dcaa35556a6a0febf767781a4c961dce7c804bfdf81935f8f0082865253da52e79dfa1c5ff74d61495b2da76e167d46114709e877a7791a3a95e33a42f56b83f5f5afe271c67ae997c", - "0xb721401983440797a03d5b99f2088a0b249aa911969c34dd6c615b0060325da555d2ad99d931170c0868b0488a2234a4114cc0013d5163b833f5c45c5eb536421c016cf85788390176bb2dc4c196d6be26bbbfceae048b82f0d8039222e71c94", - "0xacd9d833ba0a8cbd8d1ba939a11ea0fa5607e1bc6e693ec318bdb097aedd042d76e695dcebebd142e2e4ac30b1905dff03ec36d9cc70577e4dbe5e9ed7c20c7afb13a7f0155f203c6b83b9f1ad3d20a0d4aef0fbbbcf466ffc1bcd482bc2f5e0", - "0x8cc1795de015f2b0e72116f169f3b4624b7738ceebea354e0bd9051c27b86f647ea36cad57ea6884c1a8adf9b45cd83514fa687e68878bbd613d793aa10986d5a0411f081689229e0d72133b3667b9f3f1a02211d0e680564eb1ea43393e1f36", - "0xaa9281c61113c343a108de1036570feefc72fb7a96ff11f73024de12b83f29631f5a8a5900e6f10b15227c6f7462881511271bf785ebdf95ce288100e5dab391f664f6ff76c72b65b34479a4f43e5e8eba292209d6654157286ad3242ac342db", - "0xaaf16866275082e59d415db317aa874267d048ee405a553e852e6d175711d31a1fee99912345915bce121f43bc3e00d81338e5fcd3c8a1012fb4f172a9fe15622dd368b4d9d5cb60d189f423b071791fe26cea7676aca8df07965cacf80b0cd0", - "0xaccc80b3d8a6ffa648487a3d3c0ce1aeeb5401edf3cf2e385ea4a6d5fc110054fcce38f01f1da7141bbed30eb7a0a6810c82212bbb9da75d6033082dbcf6bc6a5791f85aa0f045a10da5de015edbf369b4d23b32b0c058962d2ee88e6911f994", - "0x83f1089395a16077738cc7c9a6d6a3dc9033aac4abc508af5a1f007ca92e1a80b2e6f2dbda7fdcf0d5646de790a6201d0a9cfbcb6620a1426600e3a6a425ec004384f49fb9dcd166691a47177d45dcbcb761a11d46220b0aa09fc946131f7aa5", - "0x9246bb586d43cb817c2e15ed609156e9f1cd284ba2f4797bbfa51c0341e1ba382eaac059aa9f63fb88d228a1a932839a171e7c7d00199dc7c4d6c5ea038a02cbc3cc5297c70401520e70ebbcffacd6a703f62896f3c788f94dde3c33ab0ecbdb", - "0xa316cb7c74feb0563c56cc79015e2774fbeca458bf8e9fb07894f9d6bcd73f7fb9428e87c816e5629e4bf7f3ec567fbc091549471b75492dde08217cb334b716b4582b24384586e53388873a78a90ec01bd7c3bace9cfc52161467df16e27c33", - "0xade18c74bbe60d1d69f4a570f8e5fd8696c26cc9e02829040b6b14cb9c49a4b3263b5bd5e16ec0b29010b4be054c16ab09304e23442af7d7f5fcc60bc6c5634ab6e4aed7ef334b2785e4c7672d59a687278e42d310342db5e5975d716e6d1595", - "0xb7728800bb2039acf228fa3d8028569c426cb85d28b2b5820bbef938d5ca8c4df981d3e01a309e26ca101e8295d0f6990c03b8c239798323575874a4ee5bfe46cfe99b9657189142aacd8f8d1f26cf4c0e73c6397c31ba8f18102b9ea315b638", - "0x8fb14f2a9be193f54977ecd3021663108ea143627b9a9d9faff85d1a86b855f6c437eab435fad3304f245bd7732af07f1173494cdb802fb96e85d2db89e1643206e183f3b228ca8d3f586e71aa9308eaf0223100bf07942fc39e465016d1f775", - "0xac1e025e53d98fdb3380489dce82d9d4bd3a6c98f0a523b841cb09a6f26ddd4d22efd98776e78d10fd996995fd00e81e08d3c25dd14a54b25a9d483677a24bbb8d1cb41a443b2c71038e6893b1b30f70758424e0f2039a48060191389033ef55", - "0xa4c017311b9e930868132527a9849072b91db04fd36c619ae39c98da9e2174e6201d3c2ff1246c06b1b6815bbf3ea4a1116564f55ee2fe4c4d655e2294c0ded842cba209c255ca3d7b7f82d162f97890dfdeed087aa2f87cbfc61d61815da39d", - "0x89516315a3956b455843c2555248bd94dcb19993060fe75fdd51f7aa9c9147ab13997d8a98036a8f04bee5c91d78d2990907e35a52537a8ab3ed15f1a71afdcd38044a5b6e93f662b9d36c16933a881927cacae668c4c06ee6f004c9e3989bad", - "0xa1e78a011e210400c68ca76045f7da74119bff3cbe382efd2bd2ac76567c52d68d75536a91999d084043e1ce2d07d02e0b69fb99924101d2543521747536fbc51b0454aa9a4cbbec101121f597863a5c0fee2ca5eab35dff9b9085bef8b2b0d0", - "0x830fd8d083e39153ecab43cabb22e29d7b44a55fba467af4ddd3f069439d2972ef53c3518de788f96b3f4f64963987d0155ba27afc28643af3de8e476ff515a68285728167408f45d99e574680bda6bacdd4322e587e4aa99386e035c0e931ad", - "0xb89584da22237e3061d991b1a55a5e55dc637b8b671130d304587729348138ef87885180310efe9f9f6d3580b9d7fdcf0649e8a79d2dec8c25a9f53df0fac5d517db999029cbfdd7c2cbd3e9a5503e5d267d3d8ad752335915c92b850b14bafb", - "0x959b8030733799882c5e3735479924b013756e57b893f9792bab4043e2d362d77cf308166d782e3989caa771b8a0c0a01302cb7b5e8ca12e2d6cebd59d4cd173c9dc25f438bac597fab17b4ff44997a489c168e7204b7d7c21d0938f0a2e3b51", - "0xa0a9e5503d9afe0027891dab890c687fd5f5fac5741418490c64d7c15f59533dd603a50163c79402afa61bd02de486761983c94501da17e6bbe78c497f2122210071602f578adc0ebe7a4679f87fe77e09c8c122de69105f13455fea25f08e6f", - "0x9811487283ad620cd7c9b303ae2f348d0e6f5ee17b504baaa817ae207adb912a00d3cc36dbf48745eb899e6b6e22f09f0f9ba29d949ecd7350fbbfe87a8c7cdd5d0e687fc807751d07634aaf7c38baf3b24a0670c38fa6ccd7431436fc95525f", - "0x8a13aa5071c526e560def7d8583393942f07d88c9d8d26c98738fd65f57af2e3326dbb1edff0f39fe98eda4a13ed4fd71844254b954690154c4804e1c4a53df9dc4643f4b7b09d0860070f6b2318d0d63d28fb56bf5b6ff456a18dfc72fdfbbe", - "0xb9c90ff6bff5dd97d90aee27ea1c61c1afe64b054c258b097709561fe00710e9e616773fc4bdedcbf91fbd1a6cf139bf14d20db07297418694c12c6c9b801638eeb537cb3741584a686d69532e3b6c12d8a376837f712032421987f1e770c258" - ], - "setup_G1_lagrange": [ - "0x8d0c6eeadd3f8529d67246f77404a4ac2d9d7fd7d50cf103d3e6abb9003e5e36d8f322663ebced6707a7f46d97b7566d", - "0xa0d2392f030681c61c2a867862917e10f7678d882034bb89af3db87e6ab3883a304034643dc9688a04e41a5b831582bc", - "0x94298073048d70c74f36685e547d04b7311479daa05912e18ead64b2099a194bf48ec344273d58daf0b86b1d8f1d318d", - "0x85c4063d13499013dc2ccaa98c1606763e6b1e8cca20922d4cec12ecbaf006ea81ffabe6596d1ac7ba1daf7e63e30898", - "0x84c64bce36c6b5145c6880113366025ab9a8f88e3948d374e27be8b8f9f87402c70fec9b3c621a2d1d26764a84370d0c", - "0x8b206c823acf5294552ee54579fac0f45ea15bd273dbacd63b88cd7cddbcce23b56e52f8ea352e1e1d7dcd9b3991b413", - "0xb70aaa4038ba3f5ff306c647b4392d004950c53ad8f6713b5c9c21ac99f5c56cf57323dac500a1f4e9507c4746b07a2f", - "0x895f6d1fc70b52f838d81b24f4840729cd5988b649e9d6e6f6dbac4281d8818f39ebdae7e6ea139d7f98a832bd6f29f1", - "0xa71a2832bbaade974c9ef7505dfa24e1ba466a9951b7c2db56886be31c9c7b871f3ee76cb1fcc1aab4b906d6502bc9b5", - "0x9530ba64a21e27834609c00616bc63e8fc2dc7800e478ad728ec39c624f65bbc62cb48f59decb7fbf605ce1920d02622", - "0x8d0609affaf8619bb2f6c80699e5bc7783becbd5973630cdd227ae52d6d701c45f4270becca97701b40279fab588cf64", - "0x8f5d5b4c3bb8dc9a19e5a0f84df6322a79a00c7783c86254197d313a5b35d3965a1f7c0b9c4e39ec1e8f5d02d3aa0862", - "0x96aa47a3ba20b1cfe81eb26bef503225037fdf4c9df53bea1b520841875cd1db6aa8e0f34685da08b55a3ce7289e6de0", - "0xb4c27ee3f4b8c0031837160f0a75632f5b51b5850d52b530096443f54c2b264aeccc5c61b4fcc8de7074475f354fa0d8", - "0xacfd735cda20be1d6f425a7886629c91732fbb5a4e0350ca740a8fb5b39f2001071cec0b2a0f6ca35e1f35a5ea18d00f", - "0xae44d87b1d16d59504c602cbacde2c2791f1520391ca50154e6036d3953ca466cf93d6537da2adb729e6f9f4ffa87853", - "0x97b492872ce44941ea4668ffca83b82fac0f4021bd47e0a5ffeaaacb1b3fc924ee4d53b99f7bcafe0985caf0fbe5d1d3", - "0xb3fbe2f9103d293f49c6c6016d5913f041c9113295397388111a0fdf4245d8edd6e63b9a1a1c9c8f868d6e1988116880", - "0x805efa08fd2046c44c427b225c17bed8a1eb3320cdf94026fdc24c6d345a6cfebfd7475f85d2d1bf22018ca72d2761d3", - "0x9888bae0d83077d1dfde82fdffb1195565c31c519b80cba1e21aba58ee9ccb5677f74bfde13fa5723026514a7d839661", - "0x922e19d2646ba90c9f56278bddf74621cc4518ae2f042fb8245843e87cd82724c6d7c9a99907ac6de5f2187fd2e77cbe", - "0xa38f0e1faf97dd1e0804b44e4d150dbfa48318442d1c5255eb0c14ea56b50502f3c7cb216a0336e7c140398088dc01cf", - "0x93598ea391c8735799a1d4cd0456f34994ccdf4883fad57419f634f30fee595938bc66b066dade9ae52578818c00d899", - "0xa528dc920734cfaee9feacbc0baa5b73befb1ec6fbd422fcad09a9c1f8f8c40b5ea332b2cf04dc1d6d921e9da9ddfeb4", - "0xb38d45316bf78d11e796a34ee535814e6cde0e642f14108329c5b21f4fec18cd61f84a3025824bb8dc4cbd26b2ecc9bf", - "0x8eec35a7404c9a35dc6ad0260b7f0f7fd1bfe92a2e08bc72548b99ed9acdc378728a8ea9c6879a6e47e37edb0d28c193", - "0xa68a4446274ccd947c61bf736c5219dad680b99c6085a26719793e0d9dab26d5f8a0b28e71be6e1b9ea4ae39139f7f57", - "0xa0acb543f41ad12e3b2e096629ccdd719a001d0ff53bb151e9a37aa57852f7275a7bbd06dc2a06af9144524548164af5", - "0xb271e74cdbcf8b9143f8472174bdb068c23308ea807c60a554c185f7be6f231aac13347139837514171a876dfac5baa5", - "0x8195a460719000cd1df379ebbf7918f71301a50a2fa587505cc5b8c4534c3d2343f63d28e7ee991d7a1cebb15d380696", - "0x96202b60426773e8731dcbedbf613477f65940a19fb4be0f4f742b0c76ae9d88ecdb6d36cd4f12bb404dd5d360c819e2", - "0xb0a80fe60b71ca9e80157138de8787b8a786326179604b8a15a744e52662645987e5f859ef5c76492d560daf4624b9a7", - "0xa331ea8adf87daa5e2d458d0113c307edae1a84927bca7d484aca5f8c1b6378ab42981c44b0d916d7249f4b475f926f1", - "0xaa1a8f59ae0912abf191ea7e209ff401628278dfb2269db6d87cf33bd52af3dbffbe96513a8b210e965c853a554b787a", - "0xac4f4a0e1b1a155e1f22a9085b0b047fe54c8437dbbb8e9720fd6b0cdd76557d19ca2e885a48890f0247b1a72be0e287", - "0xa428465505eac7b9660eb0d495a7a00c8cc238de3a02ebbd2eb07e502e9868086e9584b59953cf1480c0b781295db339", - "0xb7b77e21e08f6357cbd3dcd3035c3e8ec84cdfa13c7baef6c67e0ef43095e61fd549694263d7def8b8adc3a0fdcc7987", - "0xabb991d17c5bdd264c592c55101e265cb3210c4157aee4079173fd51da1e0199eed1d6c890aab95817ec078561d771af", - "0x846a8e4f801faf5fbec078b09c362ee30a00b2b58a4871744d03cd118b913464233ff926e52b0c75fbfcf098ad25a1e6", - "0x947e91ffa32f38c1ccb72cca4bfabaee9e63ab74a16f034cabba25e462f7331ebe5a7ba393f69e91830415fa75b1b52e", - "0x8dc5e26adc693f4e300cab7385edca1a2fe14c8ee6dc0cd6d013cb5aa154dc380e9e81e259cbc59c1f38f7c4a57f1c7d", - "0x9818ef6605d6ea3b7bf4da5c6d6d8ed540bb94df4d14c974e1b79ed2fd1a0b897b8cf1ff671a181a697effd66b1644a5", - "0xb5eab6baf03af994fc32cc9dce388394c18c01cdafe7909fde948f3e00a72dc8f30d15977d0f114bd7c140f5f94cf005", - "0x83b2e9858d3b929f9a2ad66a91a2c0c44d15d288c17c12a1614301a6f2d61d31eaa540ca7781520fe4420afae0ec0208", - "0xab338fbd38bce4d1b7a759f71e5e5673746c52846eff3d0b6825e390aeeca8f9f123ee88c78fe4d520cc415cbae32bf1", - "0x81adb6322b8db95d1711304e5b59f37640ca88c03e6c7e15de932be5267dff7351fa17664113ecc528e8920f5bfdc0d1", - "0x89e2e0c0d769e4107232df741678a6bacb041d0154385450aaca8be9c3c18c42f817373962e7569d33935c35666a8a6a", - "0x8f0756fea8b34a2b471ec39e4448a6a6935e5432ec2859d222964a4c82777a340e1d702777aeb946fa405afc0438221a", - "0xa2bf90c505a6f03b3dd09d04e1e7cf301fe3415b273e263f15fdfe5d0e40f619b95e8bf00916d3eaa7d7f8c0bae41c8e", - "0x91d5c76b5542637588cd47279d0bd74a25dbda0d8ec0ff68b62d7e01e34a63fc3e06d116ee75c803864b1cf330f6c360", - "0xa9958c388d25315a979566174b0622446335cb559aff1992bd71910c47497536019c6854d31c0e22df07505963fc44ff", - "0x91d82b09d5726077eed6c19bcb398abe79d87ce16c413df6bf5932b8fd64b4c0fd19c9bf0fa8db657a4a4d4c0d8f5a2d", - "0xac6e0a86e0ee416855c3e9eef2526c43835f5245527ed0038bc83b4fcadb4ea5beb91143cc674486681a9f0e63f856b1", - "0xaaf00d6efd0c6efb9f7d6a42555abec05c5af8f324e2e579fc2ac83bdc937cc682d9bc2ffd250619c8bb098b8c84db80", - "0x963f5fcd8476d0dbeb03a62cde40e3deee25f55e7ded7572d8884975f38eddc5406fc4b0adff602a1cca90f7205a7fdc", - "0xa3805ee01512f644d2679511bd8607890ee9721e75ac9a85ab9fd6fceb1308d5b9b0e9907686b4e683b34aed0f34cd81", - "0xa483d7708465cd4e33b4407fe82c84ef6bc7fa21475d961fe2e99802d0c999b6474ef7a46dd615b219c9c7e9faec45ee", - "0xb6b5f9456f12d6781c41f17cdc9d259f9515994d5dee49bb701a33fa2e8dcbb2c8c13f822b51ad232fc5e05bff2f68ef", - "0x8766b721b0cf9b1a42614c7d29aad2d89da4996dc9e2a3baeba4b33ca74100ab0b83f55c546c963e3b6af1dcf9ca067c", - "0xac5e8da1154cf4be8df2bbd2e212b7f8077099b2010c99e739441198f65337c6f7ef0d9136453a7668fde6e1389c32c7", - "0xa9d6d2c8845e5f1fec183c5153f1f6e23421e28ce0c86b0ce993b30b87869065acad9e6d9927d9f03c590852821b2f9c", - "0xa320ca07c44f7ea3ff858fe18395a86f59559617f13ec96d1e8b4a3f01d9c066a45c8d8cf8f1f14a360bb774d55f5f18", - "0xb3adb00e1312dce73b74fbd2ea16f0fb0085bd0db10772e9c260e9ed9f8829ff690e3dfffacaddc8233d484bb69778b3", - "0x87b0c8d8a167d5199d0b0743c20fb83ec8a1c442f0204bcc53bf292ba382bef58a58a6d1e2467920e32c290fdc6dae7c", - "0xa74fa436a5adc280a68e0c56b28ac33647bdfc8c5326f4c99db6dbd1b98d91afb1f41f5fffd6bcc31c1f8789c148e2db", - "0x8a37349e4ba7558965077f7f9d839c61b7dcb857fcc7965c76a64a75e377bfea8cd09b7a269ce602cc4472affc483b69", - "0x8af813f62c5962ff96bf73e33f47fd5a8e3e55651d429e77d2ce64a63c535ecc5cfc749bb120c489b7ea1d9b2a5d233c", - "0x833021445b7d9817caa33d6853fa25efc38e9d62494d209627d26799432ea7b87a96de4694967151abc1252dd2d04dfc", - "0x8f78a715107e0ace3a41bff0385fd75c13bf1250f9e5ddecf39e81bacc1244b978e3464892f7fb2596957855b8bf9fc7", - "0xaed144134dc1cc6c671f70ebe71a3aadf7511eea382969bc5d499a678d2d8ce249ebf1a06b51183f61413eba0517012b", - "0xb39a53e82c5553943a5e45bc5116d8672ec44bed96b3541dead40344b287a7b02dbf7107372effb067edd946f47de500", - "0xb383844c3b20a8bc06098046ec6b406df9419ad86fac4a000905c01325426903a5e369af856d71ccd52fea362ed29db5", - "0x83815a7098283723eec6aa6451b5d99578bf28a02971375a1fe90c15a20963e129372ac4af7b306ee2e7316472c5d66d", - "0xb426b4e185806a31febd745fa8d26b6397832a04e33c9a7eb460cbf302b4c134a8a01d4e5e40bc9b73296c539e60b3ca", - "0xa6cabf8205711457e6363ef4379ebc1226001e1aaea3002b25bfd9e173f4368002f4461e79eeb9f4aa46f1b56c739ab9", - "0xa6e88ab01282313269cd2d8c0df1a79dada5b565d6623900af9e7e15351de2b0105cc55d3e9080e1e41efe48be32a622", - "0xb2b106db3d56d189ea57afa133ae4941b4eb1dc168357af488e46811c687713fc66bbd6f8500bbd13cdb45cb82c14d1d", - "0xb3a74780ff949d19e6438db280e53632c60dc544f41320d40297fe5bb7fcee7e7931111053c30fb1ed9019ab28965b44", - "0x8c67f32b9fdc04ec291cc0d928841ab09b08e87356e43fbbf7ac3ff0f955642628f661b6f0c8e2192a887489fddf07bb", - "0xb3be58bd628383352e6473fe9a1a27cf17242df0b1273f5867e9119e908969b9e9e7e294a83b9ea14825003cb652d80c", - "0xa867acf6ab03e50936c19a21d4040bfd97eb5a89852bd9967da0e326d67ce839937cab4e910d1149ecef9d5f1b2d8f08", - "0x8006b19126bd49cbb40d73a99a37c2e02d6d37065bbe0cfcee888280176184964bd8f222f85960667c5b36dfaee0ee35", - "0xac50967b8b7840bf9d51216d68a274f1d3431c7d4031fbac75a754befbbb707c2bb184867db6b9d957f3ba0fd0a26231", - "0xb5a794c928aff0c4271674eb0a02143ed9b4d3bc950584c7cd97b7d3c3f2e323798fd5ccc6fcc0eb2e417d87f4c542a2", - "0xa2ca3d6509f04b37091ce6697672ee6495b42d986d75bd2d2058faa100d09fd0a145350f2d280d2cb36516171bd97dbf", - "0x92cfa293469967a9207b37cd70392312faf81b52963bfbad5f9f3da00817d26e10faf469e0e720c3bb195f23dda8c696", - "0xa0dd5135da0a0e33fa922c623263b29518d7fa000e5beefc66faa4d6201516d058f155475c4806917a3259db4377c38a", - "0x8fc3ae8ea6231aa9afb245a0af437e88ebca2c9ab76850c731981afba90d5add0ea254053449355eccf39df55bd912ed", - "0x9727afe1f0804297717cec9dc96d2d27024a6ae6d352fee5d25377ee858ee801593df6124b79cb62ddc9235ec1ade4ac", - "0x8bcb2c53fcaa38e8e2e0fd0929bc4d9ddce73c0282c8675676950ff806cb9f56ebd398b269f9a8c2a6265b15faf25fca", - "0xa8bd9007fbbdd4b8c049d0eb7d3649bd6a3e5097372fa8ea4b8821ba955c9ef3f39ac8b19f39d3af98640c74b9595005", - "0x92c7e851c8bd6b09dfcbfdb644725c4f65e1c3dbd111df9d85d14a0bb2d7b657eb0c7db796b42bf447b3912ef1d3b8c3", - "0x98c499b494d5b2b8bea97d00ac3a6d826ab3045bb35424575c87117fc2a1958f3829813e266630749caf0fa6eeb76819", - "0x8df190d71e432fe8691d843f6eb563445805c372eb5b6b064ec4e939be3e07526b5b7f5a289ede44ae6116a91357b8b1", - "0xb5010243f7c760fb52a935f6d8ed8fc12c0c2f57db3de8bb01fdeedf7e1c87b08f3dd3c649b65751f9fd27afa6be34c7", - "0x889c8057402cc18649f5f943aed38d6ef609b66c583f75584f3b876c1f50c5dc7d738dc7642135742e1f13fa87be46c1", - "0x996087337f69a19a4ebe8e764acf7af8170a7ad733cd201b0e4efde6ea11039a1853e115ad11387e0fb30ab655a666d8", - "0x902732c429e767ab895f47b2e72f7facad5ef05a72c36a5f9762c2194eb559f22845bbb87c1acc985306ecb4b4fbbf79", - "0x8519b62a150ea805cdfc05788b8d4e797d8396a7306b41777c438c2e8b5c38839cfec5e7dc5d546b42b7b76e062982a7", - "0x862a53ba169e6842a72763f9082ff48fbfbb63129d5a26513917c2bca9ad6362c624ce6fc973cf464f2eb4892131eb04", - "0xb86cd67c809d75fdb9f1c9453a39870f448b138f2b4058d07a707b88bb37f29d42e33ce444f4fbe50d6be13339cae8a6", - "0x8cf5d8365dbbafc0af192feb4fc00c181e2c3babc5d253268ef5564934555fb1e9b1d85ec46f0ca4709b7d5b27169b89", - "0xb48f11a1809ec780bf6181fae3b8d14f8d4dc7d1721128854354be691c7fc7695d60624f84016c1cea29a02aaf28bfbc", - "0x8b46b695a08cb9a2f29ab9dd79ab8a39ec7f0086995b8685568e007cd73aa2cd650d4fae6c3fb109c35612f751ba225e", - "0x8d2f9f0a5a7de894d6c50baceb8d75c96082df1dcf893ac95f420a93acbbf910204903d2eb6012b1b0495f08aaf9992f", - "0xb334db00a770394a84ec55c1bd5440b7d9f2521029030ef3411b0c2e0a34c75c827fd629c561ea76bd21cd6cf47027f4", - "0x96e9ff76c42bcb36f2fb7819e9123420ed5608132f7c791f95cb657a61b13041e9ba2b36f798a0fdb484878cbe015905", - "0x99f8d701e889abd7815d43ba99e0a85776ec48311fa7cb719d049f73b5d530fa950746ffbbb7beb9e30c39d864891dc2", - "0x98169c20df7c15d7543991f9c68e40ac66607cbd43fc6195416e40009917039357e932d6e807f3a40bc4503ad01ae80a", - "0x84bd97dd9e4e2ba75d0dee7d4418c720d4746203d847ce2bdd6ed17d492023df48d7b1de27e3f5cb8660c4bb9519ae1b", - "0xa54319e06db7f5f826277a54734a875c5b3fd2fa09d36d8b73594137aa62774b7356560157bc9e3fdf1046dc57b6006a", - "0x90cfff7cd4e7c73b84f63455d31b0d428cb5eee53e378028591478511985bcc95eb94f79ad28af5b3bed864e422d7b06", - "0xa11c23cc8dce26ac35aea9abe911905a32616a259fa7da3a20f42dc853ad31b2634007aa110c360d3771ff19851f4fb4", - "0x9856fbee9095074ad0568498ff45f13fe81e84ea5edaf04127d9ee7e35e730c6d23fa7f8f49d092cf06b222f94ab7f36", - "0x818862dec89f0dc314629fffbca9b96f24dfde2d835fa8bde21b30dc99fe46d837d8f745e41b39b8cf26bfe7f338f582", - "0x831819d41524c50d19f7720bf48f65346b42fb7955ee6ecc192f7e9fed2e7010abccdfdeac2b0c7c599bc83ac70be371", - "0xb367e588eb96aa8a908d8cc354706fee97e092d1bc7a836dbcc97c6ed4de349643a783fb4ddf0dec85a32060318efa85", - "0xb7aaef729befd4ab2be5ec957d7d1dbe6178de1d05c2b230d8c4b0574a3363e2d51bc54ea0279a49cc7adffa15a5a43a", - "0xae2891d848822794ecb641e12e30701f571431821d281ceecbccaaa69b8cd8242495dc5dbf38f7d8ed98f6c6919038aa", - "0x872cf2f230d3fffce17bf6f70739084876dc13596415644d151e477ce04170d6ab5a40773557eeb3600c1ad953a0bfce", - "0xb853d0a14cef7893ba1efb8f4c0fdb61342d30fa66f8e3d2ca5208826ce1db5c8a99aa5b64c97e9d90857d53beb93d67", - "0x910b434536cec39a2c47ca396e279afdbc997a1c0192a7d8be2ba24126b4d762b4525a94cea593a7c1f707ba39f17c0c", - "0xb6511e9dea1fbccedd7b8bb0a790a71db3999bd4e3db91be2f1e25062fae9bb4e94e50d8ec0dcc67b7a0abce985200b2", - "0x936885c90ebe5a231d9c2eb0dfd8d08a55ecaa8e0db31c28b7416869b3cc0371448168cbec968d4d26d1cb5a16ebe541", - "0xb71c2ac873b27fe3da67036ca546d31ca7f7a3dc13070f1530fce566e7a707daeb22b80423d505f1835fe557173754f8", - "0x85acb64140915c940b078478b7d4dadd4d8504cde595e64f60bd6c21e426b4e422608df1ed2dd94709c190e8592c22d7", - "0xb5831c7d7c413278070a4ef1653cec9c4c029ee27a209a6ea0ad09b299309dea70a7aef4ff9c6bdeda87dcda8fa0c318", - "0xaa0e56e3205751b4b8f8fa2b6d68b25121f2b2468df9f1bd4ef55f236b031805a7d9fd6f3bba876c69cdba8c5ea5e05f", - "0xb021f5ae4ed50f9b53f66dd326e3f49a96f4314fc7986ace23c1f4be9955ec61d8f7c74961b5fdeabcd0b9bccbf92ce8", - "0x88df439f485c297469e04a1d407e738e4e6ac09a7a0e14e2df66681e562fdb637a996df4b9df4e185faab8914a5cef76", - "0x8e7ae06baa69cb23ca3575205920cb74ac3cda9eb316f4eef7b46e2bff549175a751226d5b5c65fe631a35c3f8e34d61", - "0x99b26ff174418d1efc07dfbed70be8e0cb86ac0cec84e7524677161f519977d9ca3e2bbe76face8fe9016f994dafc0ff", - "0xa5f17fe28992be57abd2d2dcaa6f7c085522795bfdf87ba9d762a0070ad4630a42aa1e809801bc9f2a5daf46a03e0c22", - "0x8d673c7934d0e072b9d844994f30c384e55cec8d37ce88d3ad21f8bb1c90ecc770a0eaf2945851e5dab697c3fc2814a9", - "0xa003ed4eb401cfe08d56405442ca572f29728cfff8f682ef4d0e56dd06557750f6a9f28a20c033bc6bbb792cc76cc1a8", - "0x8010408f845cf1185b381fed0e03c53b33b86ea4912426819d431477bd61c534df25b6d3cf40042583543093e5f4bb44", - "0x9021a1ae2eb501134e0f51093c9f9ac7d276d10b14471b14f4a9e386256e8c155bef59973a3d81c38bdab683cd5c10e0", - "0xa5abf269ceabbb1cf0b75d5b9c720a3d230d38f284ed787b6a05145d697a01909662a5b095269996e6fa021849d0f41f", - "0xb4b260af0a005220deb2266518d11dbc36d17e59fc7b4780ab20a813f2412ebd568b1f8adc45bf045fcbe0e60c65fd24", - "0xb8c4cb93bedbb75d058269dfccda44ae92fe37b3ab2ef3d95c4a907e1fadf77c3db0fa5869c19843e14b122e01e5c1f4", - "0xac818f7cdecc7b495779d8d0ff487f23ab36a61d0cf073e11000349747537b5b77044203585a55214bb34f67ef76f2d2", - "0x86215799c25356904611e71271327ca4882f19a889938839c80a30d319ddbe6c0f1dfa9d5523813a096048c4aef338cd", - "0xa9204889b9388bf713ca59ea35d288cd692285a34e4aa47f3751453589eb3b03a9cc49a40d82ec2c913c736752d8674d", - "0x893aecf973c862c71602ffb9f5ac7bf9c256db36e909c95fe093d871aab2499e7a248f924f72dea604de14abfc00e21c", - "0xb8882ee51cfe4acba958fa6f19102aa5471b1fbaf3c00292e474e3e2ec0d5b79af3748b7eea7489b17920ce29efc4139", - "0x8350813d2ec66ef35f1efa6c129e2ebaedc082c5160507bcf04018e170fc0731858ad417a017dadbd9ade78015312e7f", - "0x83f6829532be8cd92f3bf1fef264ee5b7466b96e2821d097f56cbb292d605a6fb26cd3a01d4037a3b1681d8143ae54d7", - "0x87d6258777347e4c1428ba3dcbf87fdd5113d5c30cf329e89fa3c9c1d954d031e8acacb4eed9dca8d44507c65e47e7cd", - "0xa05669a1e561b1c131b0f70e3d9fc846dc320dc0872334d07347e260d40b2e51fdbabeb0d1ae1fb89fba70af51f25a1a", - "0x819925c23fd4d851ea0eecc8c581f4a0047f5449c821d34eccc59a2911f1bd4c319dab6ece19411d028b7fdedece366b", - "0xb831b762254afd35364a04966d07b3c97e0b883c27444ff939c2ab1b649dc21ac8915b99dc6903623ed7adaae44870ac", - "0x93ec0190f47deffe74179879d3df8113a720423f5ca211d56db9654db20afe10371f3f8ec491d4e166609b9b9a82d0d4", - "0x8f4aa6313719bcfad7ca1ed0af2d2ee10424ea303177466915839f17d2c5df84cc28fcef192cbb91bb696dd383efd3b2", - "0x8d9c9fdf4b8b6a0a702959cf784ad43d550834e5ab2cd3bebede7773c0c755417ad2de7d25b7ff579f377f0800234b44", - "0x99d9427c20752f89049195a91cf85e7082f9150c3b5cb66b267be44c89d41e7cc269a66dacabacadab62f2fa00cc03be", - "0xb37709d1aca976cbbf3dc4f08d9c35924d1b8b0f1c465bd92e4c8ff9708e7d045c423183b04a0e0ab4c29efd99ef6f0e", - "0xa163f42fb371b138d59c683c2a4db4ca8cbc971ae13f9a9cc39d7f253b7ee46a207b804360e05e8938c73bf3193bab55", - "0x87a037aa558508773fc9a0b9ba18e3d368ffe47dfaf1afacee4748f72e9d3decc2f7c44b7bf0b0268873a9c2ef5fe916", - "0xa1f20cb535cc3aebd6e738491fe3446478f7609d210af56a4004d72500b3ec2236e93446783fe628c9337bcd89c1e8e1", - "0x9757aa358dfbba4f7116da00fe9af97f7ac6d390792ea07682b984aa853379ac525222ac8a83de802859c6dec9182ef7", - "0x815daca1eded189ec7cb7cbc8ad443f38e6ddb3fb1301d1e5a1b02586f1329035209b7c9232dc4dff3fc546cb5ac7835", - "0xaed86dfaf9c4f0a4b2a183f70f9041172002a773482a8ebf3d9d5f97d37ee7c6767badfda15476b3b243931235c7831c", - "0x8d032e681e89e41b29f26be02f80030fa888f6967061d2204c1ebb2279a3211d759d187bce6408c6830affa1337fb4e0", - "0x877bff5c2db06116f918a722b26422c920aeade1efa02fa61773fca77f0ea4a7e4ee0ecaaa5cfe98044c0ff91b627588", - "0xb9ee5310d0996a10a242738d846565bdb343a4049a24cd4868db318ea6168a32548efaf4ab84edfbf27ce8aec1be2d1c", - "0xb59f6928167323037c6296dd7697846e80a7a4b81320cfae9073ebd2002a03bdf6933e887f33ad83eda8468876c2c4fb", - "0x8167686245149dc116a175331c25301e18bb48a6627e2835ae3dd80dd373d029129c50ab2aebeaf2c2ccddc58dcc72ec", - "0x82b7dcc29803f916effb67c5ba96a1c067ed8ca43ad0e8d61a510ab067baefd4d6b49e3886b863da2de1d8f2979a4baa", - "0xb43824cd6f6872a576d64372dde466fef6decdbb5ad5db55791249fde0a483e4e40c6e1c221e923e096a038fe47dab5e", - "0xab1e9884cf5a8444140cf4a22b9a4311a266db11b392e06c89843ac9d027729fee410560bcd35626fd8de3aad19afc4a", - "0xa0dbd92a8d955eb1d24887ca739c639bdee8493506d7344aadb28c929f9eb3b4ebaae6bd7fd9ffe8abb83d0d29091e43", - "0x8352a47a70e343f21b55da541b8c0e35cd88731276a1550d45792c738c4d4d7dc664f447c3933daabd4dbb29bb83be4a", - "0x8ce4a1e3c4370346d6f58528a5ef1a85360d964f89e54867ba09c985c1e6c07e710a32cdda8da9fa0e3b26622d866874", - "0xb5e356d67dd70b6f01dd6181611d89f30ea00b179ae1fa42c7eadb0b077fb52b19212b0b9a075ebd6dc62c74050b2d2f", - "0xb68f2cd1db8e4ad5efdba3c6eaa60bfcc7b51c2b0ce8bb943a4bc6968995abe8a45fe7f12434e5b0076f148d942786be", - "0xb5c7b07f80cd05c0b0840a9f634845928210433b549fb0f84a36c87bf5f7d7eb854736c4083445c952348482a300226a", - "0x8cfd9ea5185ff9779dee35efe0252957d6a74693104fb7c2ea989252a1aa99d19abaab76b2d7416eb99145c6fdb89506", - "0x8cc8e2c5c6ddee7ef720052a39cab1ecc5e1d4c5f00fb6989731a23f6d87ac4b055abb47da7202a98c674684d103152a", - "0x8c95394c9ed45e1bf1b7cfe93b2694f6a01ff5fed8f6064e673ba3e67551829949f6885963d11860d005e6fabd5ac32c", - "0xadf00b86f4a295b607df157f14195d6b51e18e2757778fde0006289fabba8c0a4ab8fad5e3e68ddbb16ccb196cc5973f", - "0xb1714b95c4885aac0ee978e6bbabbc9596f92b8858cb953df077511d178527c462cbe1d97fdc898938bae2cd560f7b66", - "0xadf103f4344feb6b9c8104105d64475abc697e5f805e9b08aa874e4953d56605677ef7ff4b0b97987dc47257168ae94d", - "0xb0ce6ede9edb272d8769aed7c9c7a7c9df2fb83d31cc16771f13173bcdc209daf2f35887dcca85522d5fdae39f7b8e36", - "0xad698d1154f7eda04e2e65f66f7fcdb7b0391f248ba37d210a18db75dafd10aedc8a4d6f9299d5b6a77964c58b380126", - "0x904856cd3ecdbb1742239441f92d579beb5616a6e46a953cf2f1dd4a83a147679fc45270dcac3e9e3d346b46ab061757", - "0xb600b5b521af51cdfcb75581e1eccc666a7078d6a7f49f4fdb0d73c9b2dab4ce0ecafcbd71f6dd22636e135c634ee055", - "0xa170c5d31f6657f85078c48c7bbf11687ce032ab2ff4b9b3aee5af742baecf41ea1c2db83bcba00bccc977af7d0c5c8e", - "0xa9ef1cbb6a7acb54faf1bcbd4676cdeba36013ca5d1ac1914c3ff353954f42e152b16da2bdf4a7d423b986d62b831974", - "0xaa706d88d3bd2ce9e992547e285788295fd3e2bbf88e329fae91e772248aa68fdfdb52f0b766746a3d7991308c725f47", - "0x911a837dfff2062bae6bcd1fe41032e889eb397e8206cedadf888c9a427a0afe8c88dcb24579be7bfa502a40f6a8c1cc", - "0xae80382929b7a9b6f51fe0439528a7b1a78f97a8565ba8cddb9ee4ba488f2ab710e7923443f8759a10f670087e1292c4", - "0xb8962de382aaa844d45a882ffb7cd0cd1ab2ef073bce510a0d18a119f7a3f9088a7e06d8864a69b13dc2f66840af35ae", - "0x954538ffff65191538dca17ec1df5876cb2cd63023ff2665cc3954143e318ece7d14d64548929e939b86038f6c323fc1", - "0x89efa770de15201a41f298020d1d6880c032e3fb8de3690d482843eb859e286acabb1a6dc001c94185494759f47a0c83", - "0xa7a22d95b97c7c07b555764069adaa31b00b6738d853a5da0fe7dc47297d4912a0add87b14fa7db0a087a9de402ea281", - "0x9190d60740c0813ba2ae1a7a1400fa75d6db4d5ce88b4db0626922647f0c50796a4e724e9cc67d635b8a03c5f41978f7", - "0xab07c30b95477c65f35dc4c56d164e9346d393ad1c2f989326763a4cc04b2cb0386e263007cc5d0125631a09ad3b874c", - "0x9398d8e243147de3f70ce60f162c56c6c75f29feb7bc913512420ee3f992e3c3fb964d84ef8de70ef2c118db7d6d7fd5", - "0xb161b15b38cbd581f51ca991d1d897e0710cd6fdf672b9467af612cd26ec30e770c2553469de587af44b17e3d7fea9f7", - "0x8c5d0260b6eb71375c7ad2e243257065e4ea15501190371e9c33721a121c8111e68387db278e8f1a206c0cce478aaa2b", - "0xb54ac06a0fb7711d701c0cd25c01ef640e60e3cb669f76e530a97615680905b5c5eac3c653ce6f97ceca2b04f6248e46", - "0xb5c7f76e3ed6dc6c5d45494f851fa1b5eaf3b89adac7c34ad66c730e10488928f6ef0c399c4c26cbeb231e6e0d3d5022", - "0xb6cd90bdd011ac1370a7bbc9c111489da2968d7b50bf1c40330375d1a405c62a31e338e89842fe67982f8165b03480c7", - "0xb0afcaf8d01f5b57cdeb54393f27b27dc81922aa9eaccc411de3b03d920ae7b45295b090ef65685457b1f8045c435587", - "0xb2786c0460e5057f94d346c8ebe194f994f6556ab2904a1d1afd66c0ff36391b56f72ed769dcc58558ee5efaa2ed6785", - "0x965dbb0cb671be339afcb2d6f56e3c386fb5d28536d61d6073b420ee15dee79c205af2f089fbb07514a03c71bf54b4e2", - "0x90f2003e2286bba9cebff3a6791637ca83b6509201c6aed1d47f27097d383d5c2d8532bff9e3541d2c34259841cf26ab", - "0x902142d1224e1888ebbfef66aaf8d5b98c27927a00b950753a41d1d28a687a8286b51655da9a60db285b20dc81d5ea89", - "0xa5d364448bf0d0849e5104bdaef9cb2cc8c555f5d6d34239c68671fbe1252f7c8c75b83cea10159dee4da73298f39a12", - "0xb013a54c5b99e296d9419ad5c2aaf4545acd34405e57d13cb764e92132cc20d1a14b33e10caf22d898b608670c04f273", - "0xb92976dceda373331804d48a7847f508cafde8d15949df53dbda09d03908678db1e61ee637baad5f05b2b03ea6f5a870", - "0x968bcb308c7ad0813dc9b3170f23f419aecd7b42176f27fac698811795bf42659fea6b04dab4ef43595dcc990622041b", - "0xa9d0a20e9367ea831dccd37f4d97ea75e9aeec952947a7946d95e0d249c94024183ef79a624bdea782469824df0ee4e4", - "0x8521b9667453c3658703e5db365b13f0e0d2331ce611ff1e708f8124d8a81bb5e82871de4a66d45c1a6b0a3901bd901e", - "0xb9c88e76e69b0722c0a2f97e57dbc4a6f7456434cd694e2ff67f4e24740cffa4db03e2b18f07f22954ae7db2286e1fa2", - "0x8400e55aa9ab01d4cc0affd611127b5d8d9a9dbd897f3cb8e2050379983aa54249be17d7b7891977b2515bb44a483f65", - "0x8cbb967b4ed31dc40ea06822a94d54cbfc8845c66fbafa3474c8f5fe1ada97299ed4ca955d9d7a39af8821eabf711854", - "0xb4d266ee3fea264a6c563fd6bed46f958c2d7bd328225f6e47faf41a0916aef3b697574322f8b814dfb2f5c242022bf6", - "0x8f7c72d69a919450215ead660ffa9637642c5306354888d549fd4a42e11c649b389f67cc802a0184d10fdb261351140c", - "0xa5f9e494ea9b2393ec32c48aac76c04158ccef436d4e70ad930cba20c55fbf61e8f239f70b9d75462405c4b6317c71a1", - "0xb3befb259b52a44a6f44345859e315c20efa48c0c992b0b1621d903164a77667a93f13859790a5e4acb9f3ec6c5a3c6e", - "0xb9e4ca259b4ee490d0824207d4d05baf0910d3fe5561ff8b514d8aa5c646417ca76f36ab7c6a9d0fb04c279742f6167a", - "0x98fa8c32a39092edb3c2c65c811d2a553931010ccb18d2124d5b96debd8b637d42b8a80111289f2079d9ebca2131a6dc", - "0xa65e5aa4631ab168b0954e404006ce05ac088fd3d8692d48af2de5fd47edbf306c80e1c7529697754dbbba1b54164ba0", - "0xb94b7d37e4d970b4bb67bf324ebf80961a1b5a1fa7d9531286ab81a71d6c5f79886f8ef59d38ae35b518a10ed8176dcc", - "0xb5ed2f4b0a9ae9ace2e8f6a7fd6560d17c90ae11a74fa8bef2c6c0e38bfd2b9dd2984480633bca276cb73137467e2ce3", - "0xa18556fe291d87a2358e804ee62ddff2c1d53569858b8ae9b4949d117e3bfb4aefce1950be8b6545277f112bebeeb93d", - "0xa0d60b9def5d3c05856dff874b4b66ec6e6f0a55c7b33060cc26206c266017cdcf79b1d6f6be93ed7005a932f9c6a0b9", - "0x801fced58a3537c69c232ce846b7517efd958e57c4d7cd262dbec9038d71246dafad124aa48e47fe84ecc786433747c7", - "0xa5e9a8ea302524323aa64a7c26274f08d497df3d570676ecc86bd753c96a487a650389a85f0bc8f5ea94fe6819dc14e5", - "0xa8a2963dc9238a268045d103db101adc3b2f3ab4651b7703b2fe40ece06f66bf60af91369c712aa176df6ed3d64a82fa", - "0xa4a8ff0a9a98442357bcdd9a44665919c5d9da6a7d7d21ccdbbd8f3079b1e01125af054b43b37fc303941d0a2e7baee0", - "0x90ef893350f50d6f61ee13dfab6e3121f4a06a1908a707b5f0036cdc2fe483614de3b1445df663934036784342b0106f", - "0x84e74d5bc40aaab2cc1d52946b7e06781fbef9d8de6f8b50cd74955d6bdb724864c0e31d5ac57bf271a521db6a352bd6", - "0x832cdf653bbbd128e2e36e7360354a9e82813737c8ab194303d76667a27aa95252756c1514b9e4257db1875f70f73eb4", - "0xa0af8660ed32e6dbcc4d5d21b0a79a25ff49394224f14e6e47604cf3b00136de8f9ab92e82814a595bf65340271c16c3", - "0x9040b5caf5e4dc4118572a2df6176716b5b79d510877bbb4a1211b046596899ea193be4d889e11e464ffb445ab71907b", - "0xb9bf8354c70238ab084b028f59e379b8a65c21604034d1b8c9b975f35a476e3c0ba09dd25bf95c5d8ffb25832537319b", - "0xa7b492cc1df2a8f62c935d49770d5078586bd0fefda262eb5622033e867e0b9dc0ffc2ce61cd678136a3878d4cbb2b56", - "0x95a5ef06f38743bba187a7a977023b1d9d5ec9ef95ba4343ad149a7b8b0db0e8e528bfb268dc7e5c708bc614dc3d02c8", - "0x99dcf7f123df6c55aeff0a20885a73e84d861ec95cf9208ba90494f37a2dcaacebc8344f392547d3046616d9753c7217", - "0xb3e14f309281a3685ceb14f8921c1e021b7e93c9e9595596b9fb627e60d09ed9e5534733fcbdf2fbc8c981698f5e62ac", - "0x816a5e0463074f8c7fb2998e0f0cf89b55790bdbbb573715f6268afb0492453bd640dd07a9953d0400169d555fdf4ac8", - "0x8356d68f3fe7e02a751f579813bd888c9f4edcc568142307d1c9259caef692800e1581d14225e3a3585dac667928fa94", - "0x8d70ea3314c91bfc3f7c1dcf08328ae96f857d98c6aac12ad9eebc2f77e514afdbaf728dfcb192ed29e7ce9a0623ecbb", - "0xb68280e7f62ced834b55bc2fcc38d9ea0b1fbcd67cc1682622231894d707c51478ed5edf657d68e0b1b734d9f814b731", - "0xb712dd539e1d79a6222328615d548612eab564ace9737d0249aa2eefed556bbcf3101eba35a8d429d4a5f9828c2ac1fe", - "0x8da42ca096419f267f0680fd3067a5dbb790bc815606800ae87fe0263cae47c29a9a1d8233b19fe89f8cc8df6f64697e", - "0x8cb2ffd647e07a6754b606bde29582c0665ac4dde30ebdda0144d3479998948dae9eb0f65f82a6c5630210449fbd59f7", - "0x8064c3ef96c8e04398d49e665d6de714de6ee0fced836695baa2aa31139373fad63a7fc3d40600d69799c9df1374a791", - "0xaec99bea8ab4e6d4b246c364b5edc27631c0acc619687941d83fa5ba087dd41f8eaec024c7e5c97cf83b141b6fb135da", - "0x8db6051f48901308b08bb1feb8fd2bceaedde560548e79223bd87e485ea45d28c6dcec58030537406ed2b7a9e94e60cc", - "0xa5b812c92d0081833dcf9e54f2e1979a919b01302535d10b03b779330c6d25d2de1f374b77fe357db65d24f9cbcd5572", - "0x967d442485c44cf94971d035040e090c98264e3348f55deabd9b48366ec8fe0d5a52e4b2c9a96780a94fc1340338484e", - "0xa4b4110bef27f55d70f2765fc3f83c5ddcdfe7f8c341ea9d7c5bcee2f6341bcfbf7b170b52e51480e9b5509f3b52048f", - "0xa0d39e4eb013da967a6ac808625122a1c69bf589e3855482dedb6847bb78adc0c8366612c1886d485b31cda7304ec987", - "0xa92f756b44d44b4e22ad265b688b13c9358114557489b8fb0d9720a35e1773b3f0fa7805ac59b35d119a57fe0f596692", - "0xaa27e4b979af6742b49db8bf73c064afd83a9cfe9016131a10381f35a46169e8cfd1a466f295fcc432c217c7c9fa44a5", - "0x845961319cc10bcfbb1f3cb414a5c6a6d008fb3aac42c7d5d74e892cc998af97bc9a9120c3f794e4078135e16a416e38", - "0xa18dbe3015c26ae3e95034c01d7898e3c884d49cc82e71ddb2cf89d11cec34cc2a3dff0fafb464e8e59b82ce1a0a7a11", - "0xa954aed6d7124fa5bd5074bd65be4d28547a665fb4fe5a31c75a5313b77d1c6fc3c978e24c9591a2774f97f76632bdde", - "0x8f983b2da584bdff598fcb83c4caa367b4542f4417cc9fa05265ff11d6e12143c384b4398d3745a2d826235c72186a79", - "0xb2caa17d434982d8dd59a9427307dfe4416b0efc8df627dd5fc20d2c11046c93461d669cab2862c094eec6a9845990c6", - "0x8c2baa5a97ee3154cce9fa24f6b54b23e9d073e222220fdd0e83e210c0058fb45ce844382828b0cb21438cf4cad76ee6", - "0xb93437406e4755ccf1de89f5cbe89e939490a2a5cf1585d4363c21ae35b986cb0b981dec02be2940b4ec429cc7a64d4c", - "0xa90ac36c97b7ea2eddb65e98e0d08a61e5253019eeb138b9f68f82bb61cdbadf06245b9dfffe851dfa3aa0667c6ac4b8", - "0x8bcdd7b92f43b721ddbfd7596e104bc30b8b43bdaee098aac11222903c37f860df29d888a44aa19f6041da8400ddd062", - "0x98f62d96bdf4e93ed25b2184598081f77732795b06b3041515aa95ffda18eb2af5da1db0e7cfed3899143e4a5d5e7d6c", - "0xad541e3d7f24e4546b4ae1160c1c359f531099dab4be3c077e446c82cb41b9e20b35fa7569798a9f72c1fae312b140b4", - "0x8844a1471ff3f868c6465459a5e0f2fb4d93c65021641760f1bb84f792b151bc04b5a0421bbc72cf978e038edc046b8f", - "0xaf895aebe27f8357ae6d991c2841572c2063b8d0b05a2a35e51d9b58944c425c764f45a3f3b13f50b1b1f3d9025e52ad", - "0xadf85265bb8ee7fead68d676a8301129a6b4984149f0eb4701eae82ec50120ddad657d8798af533e2295877309366e9c", - "0x962e157fe343d7296b45f88d9495d2e5481e05ea44ca7661c1fdf8cc0ac87c403753ca81101c1294f248e09089c090eb", - "0xa7c8959548c7ae2338b083172fee07543dc14b25860538b48c76ef98ab8f2f126ecb53f8576b8a2b5813ecb152867f18", - "0xae71680366e11471e1c9a0bc7ea3095bc4d6ceb6cf15b51f1b6061b043f6d5941c9f869be7cb5513e8450dca16df2547", - "0x831290201f42ebf21f611ca769477b767cf0ee58d549fcd9e993fae39d07745813c5ce66afa61b55bb5b4664f400ece7", - "0xaf5879e992f86de4787f1bc6decbc4de7d340367b420a99a6c34ac4650d2a40cbe1cef5c6470fc6c72de8ee1fe6bcce4", - "0x8d3c27e1b2ef88d76ac0b1441d327567c761962779c8b1f746e3c976acb63b21d03e5e76589ce9bb0d9ba6e849ed3d53", - "0xab23b09c9f4151e22654d43c1523f009623b01fe1953d343107cef38b95bd10afd898964946d3cb8521bcbe893e1c84d", - "0x8a6acade9520e7a8c07f33d60a87fd53faa6fbf7f018735bffcbbb757c3bafb26f547ceb68e7b8b6bca74819bfcd521a", - "0x94db50080d557440a46b6b45ee8083bc90e9267d40489040cbed6234bebf350c788ec51557b969f95194102fde8e9713", - "0x8be8031f32504e0c44958d893649f76cec17af79efcd22bbedb78378f0a150845467e59f79a3f2a3b6a66bdf0d71d13c", - "0xa69a4ac47fd92e1926b5e14adcbebbef049848e8a00d4bb387340892e5a9333cae512f447201728d3b53c6cf980a5fdc", - "0x8fc713825277c5a8d9ef0a1f6219d141def6d8b30aff0d901026280a17d1265d563ff5192a0817e0e1a04ff447fb6643", - "0x8bf0a85569c4f0770ff09db30b8b2ea6c687630c7801302c17986c69a57c30f0781d14b3f98a10b50c4ecebc16a5b5ec", - "0x896baa4135d5621fd6b6a19c6d20b47415923c6e10f76c03a8879fd8354e853b0b98993aa44e334623d60166ba3e3ca9", - "0xb82cde1c2e75a519ef727b17f1e76f4a858857261be9d866a4429d9facf9ea71d16b8af53c26bde34739fe6ea99edc73", - "0xb1a9e1f2e34895a7c5711b983220580589713306837c14073d952fe2aef0297135de0be4b25cbfaed5e2566727fb32ef", - "0xb42ed0e9eaf02312d1dba19a044702038cf72d02944d3018960077effc6da86c5753036a85d93cd7233671f03d78d49a", - "0xa402e34849e911dbf0981328b9fe6fff834c1b8683591efd3b85aa7d249811d6b460a534d95e7a96fdd7f821a201c2c4", - "0xa774417470c1532f39923d499566af762fa176c9d533767efd457cc5e4a27f60e9217f4b84a9343ecb133d9a9aab96b7", - "0x83dc340541b9ef2eb8394d957cd07b996d2b52ac6eb5562cbba8f1a3312f941c424c12d1341a6dc19d18d289c681ef40", - "0xb2906c32d5756b5712e45dec53782494a81e80f887c6e1ef76e79c737625eccecb8fd17b20e6f84890d322b6ffde6eab", - "0xb89705c30cec4d50691bc9f4d461c902d6a4d147cf75ee2f1c542ad73e5f0dabe3d04cd41c6c04ab1422be4134cf1ad7", - "0x8c3293651f4c4fac688bf5837c208b15e5a19ce51b20dd80ffc7fca12d3e615b2773cfc3ed62a1b39c66808a116bde06", - "0x8fceb8ef481163527d1fc3abc7e1a5b3b6de2f654c3fe116d1367b177dcba2e0d2124a7216803513a3d53fc1e30435b9", - "0xb2a42c827da630aaa3eb20ed07d136aa11ba01b4c8efc0a57ebab7d5b851a15daa6ba118bcffbc20703916e430e30a87", - "0xa86340153abb3fe97414e2fde857e15aac27c9bb9b61258eea6766024f426ed0753f08f07f6b02b5375e1587ea3afcab", - "0xb006465e258e646f91ba889765113d3dc9bd657246c533cab6516d55ba054baa9d7276a3b0fa31730c3bd824845bf107", - "0xa08aadc09428719cde0050d064c0f42c5b7c4f6c158227d7636f870957d6cfe821b4c62d39279a7c98f5a75fcb7bbfba", - "0x885e7d47ce9b50d21b95116be195be25f15223a6a189387575cc76740174c3e9044f1196986d82856b3fb25cdd562049", - "0xb18c3780362d822cc06910743c4cbcef044823a22d12987fe2e56f3801e417f2e9cd31574ea1c5c6ee7673a14aa56e3e", - "0xa625570ef7d31c042d968018865aeeba34ee65a059ab1ec079c7a8ba1be9e24bce6afb7036c07d9d6c96ab014f95d661", - "0x8fc9bd4764adc4c300b5bd49a06dce885d1d8aff9bae68a47976d0cd42110aa6afa2d7b90b64e81c0f14de729f2fb851", - "0x91d88714cb669f5f00241aa5ab80dffb04109492ea9c72b59645eb1f85f3539c61db2ab418af986f42241df8b35445e9", - "0xb98f14e664df2590dd2d00b5b5c817e388e5d9fb074f718637c33b3d4969c89e82fdd12db8997f5ff3bf5bb5ca5dd839", - "0x86cb3d9f148cb2170317a4c22af7092155aa66ecff7ab1299b102fbbaa33ed2a284b97b08f529d2da9faea63fb98972c", - "0x92449f6b8a7c737ecef291c947cbd602c47d7fe47dc3426c2b413f3019169aa56e14c2a7216adce713e1c7bd5c08a83f", - "0xb08c1b9080bba88b44a65070948142d73c00730715fbdd01e13fc3415c5b4f3248ef514fa3ade4a918c9a820cccae97c", - "0xb0a05297da76e37c22be7383e60bba1cbc4f98ba650e12d4afcfcea569842003644a10ad73c9148958f7bf1ffa0a27d0", - "0x839092c1f4e9fb1ec0dde8176f013b0d706ab275079f00f8e774287dd658d1b5638d5fe206f5f2a141911a74bb120f75", - "0xa36bd669bdc055ece4b17ff6eac4c60a2f23324a5eb6d0d6c16a2fce44c39cfd52d1fa2b67f3f5e83504e36426fbfc40", - "0x8aa428323512cf769645e2913a72976d32da4c0062ffe468a6062fd009340f0f23c6b63285848a0e7631a907adb032a0", - "0x944800f7d43f41283eb56115ac39ccc5bf107ae5db6abcaba6936b896260cd09428a6b828c0bccebeb00541073dbf38e", - "0x8e700ca7c9e1538cf64e161dd8d16af56fc29d53c79648150d6d8c268b0c95c76acded723e29918690d66252bd75f5b3", - "0xb9c4ce35b5b16b4c39b6e85800c76b26e8d0999500fabc1e5b6234a7f8da18c621266ac0d5ebc085354297ff21ac89a5", - "0xa0c706d32063f1877f7e903048ce885f5d012008d4a8019dd00261a8bbc30834bffeba56cdeddc59167d54cc9e65f8fa", - "0x839813b736225087cbbcf24506ea7bf69138605036b764ec0514055ac174bbc67c786a405708eb39a6c14c8d7e0ec6ee", - "0xb1a5fef055a7e921c664f1a6d3cb8b21943c89b7e61524a307d8e45aa432e5765a27c32efdb32d88062cd80800a260de", - "0xb17f8202d9ed42f0f5cb1b1dbda60711de3b917a77f6069546fa3f86d21f372b8dd5cb86f1994b873ba9982404e08daf", - "0xb5211d54bd02d44d4d808ad57067606f3e9fa2cad244a5f2acef0edf82de3c496d2b800f7c05f175d01fa6ace28b44d1", - "0xaa9c6f8f489b35fdb7544116fe5102a34ff542de29262f156df4db4ea6e064f5ea20c4bd877d40377ed5d58114b68f19", - "0x826668b1f32e85844ff85dd7e2a8e7f4e0fd349162428bc9d91626b5ab21bdbacd1c9e30cf16f5809b8bf5da4f4fe364", - "0xb30d14917b49437f9fdbae13d50aee3d8a18da3a7f247b39e5d3e975c60bd269da32da4e4cc8844666fca0d65f4e3640", - "0x8c6918d8d94b36c6b9e772e9a432e66df16724e3b0660bde5ea397e6ef88028bb7d26184fbe266a1e86aef4a0dfe5faa", - "0x906d80ffd692c1dd03ab89be52e0a5a9e90a9cdbfc523d2b99c138ae81f45d24c34703f9cb5a666b67416e3bb6272bc4", - "0x8b07e8ba22b436e64f011cacf5e89c55cd3bfb72ae8b32a3a8922c4fccb29de6f73662d6e330da6aa6e732a2187ef3c9", - "0x9547466b4553a49adf59cc65d4c3c9401b2178947ebe3bd33c6e63cfb67d6be8729033158594f6f244b272c4487d6958", - "0xaafcccea41e05cb47223fa8dfec0dd55964268bd4d05e24469614077668655ac8a51d2ac2bfb22862f8f4fa817048c2f", - "0x870f8c1173e8fd365b0a2e55c66eea3ab55355990c311f3042377803d37e68d712edcc5a0a2e2f5a46df0c1c8e6310c2", - "0xb4288f792008f342935f18d8d9447fe4ddcfea350566e13dba451f58c68e27241af1367f2603a9dff6748e7fe0c53de4", - "0x91c58c0e537d3afdcf7783601dd9cda2aa9956e11f711b15403760cf15fc6dffb40ed643886854571da8c0f84e17adfe", - "0xa43fec8ee92febed32e7cdd4e6314a62d9d3052c7a9504057dfba6c71fdfbeff1cef945d8f087bd106b5bec7478ad51f", - "0x99cf5e0e3593a92f2ec12eb71d00eccec3eec8662333471b2cb3a7826b7daca2c4d57ffba18299189cf7364e2af5df6d", - "0xaf50f9ab890b7517ff1f1194c5b3b6f7f82eabc607687a8380be371a6a67b117aeb9b6f725556551b81f8117971706a2", - "0xaa352430887053602a54403bd0d24d6b5181b44aa976dfa190e21851699a88127dcc904c90a48ec44610056b5dcd36c4", - "0x964c821ea1902354736fa382a929c156bd67b9468d6920d47c27b9d0d304b6144118888d124c1f6785da596435ed2410", - "0xb2284a67af26b5f5aff87b4d8e12c78ab37c5eb6e92718fca8549f86f4f001b660fc4520456aff72c9bcddd686603942", - "0x83c54cbb997ea493dc75df4023071dce6da94268feaa2352373789616f012098270ba4fd60c791796a6f5062fb2cd35e", - "0x9143e8fee0b8f0f34c65c7750858093dcf165c6a83c026bfac2d5ffa746361eb4b6a14fdb43e403add901ac3735735a3", - "0x97d7748a5b278ee47b18c9e60689b12a0a05be47e58e78bf8c04b9e8b34e2e2f2d3ac3c25c76ab2e0a75e8a54777b7c8", - "0xb4e68f6f2d978a5411414c164c81ddb2a141b01ebe18c65a8626ca75d6432e5988310b50a888a78c3a0a242353525af5", - "0x8976f4cc3eaf2684718cf584712c4adaf00a4d9c521f395f937e13233b30329658b3deacfe7e29fac84c496047f2d36b", - "0xa40bcdf4b6e95f1535c88dddcbf2074ef2e746b7fd232bdfd2b88f2f6d4bbf21c6b263cf5fd3e12a03476f2f5ffe00d2", - "0x88c7b6337ee705acd8358ef6d2242d36b140afff0579a7784b3928a0c49698bd39c1f400e8a2e3eda5fbfb2e8f28fe51", - "0xa98612ba8b450a71d2075d51617ebeb7ca401ad3cbd9b8554850c65ef4f093ba78defb00638428c9f1f6f850d619287f", - "0xb7e71d3ffa18b185c1a6bd75668ff65d985efc0a0c19f3812cafde9adbfb59ffd108abeb376e6a8877fdf5061562f82b", - "0x8a3e5fd776cc26908a108a22b1b122d60cb8c4f483cbedcd8af78a85217bb5a887df3efed2b8b4ec66e68eb02a56ca93", - "0xb0d92b28b169d9422c75f9d5cb0a701e2e47b051e4eacd2fd1aa46e25581a711c16caf32f40de7c7721f5bf19f48b3f5", - "0x88895739d5152282f23e5909cf4beebda0425116eb45fc5a6a162e19207686d164506c53b745fb2e051bb493f6dbad74", - "0xadbccfed12085cd3930bd97534980888ee564dda49e510c4e3ca0c088894855ef6178d5b060bca8a8a1a427afdbec8a8", - "0x87d00674abd3d2e7047a07ed82d887e1d8b8155635887f232dd50d6a0de3fb8e45b80b5a05bc2ec0dea9497b4aa783ac", - "0x806e1d3dfadd91cbf10e0d6a5e61738d0dbff83407b523720dce8f21f8468b8a3fc8102acf6ba3cf632ca1cb2af54675", - "0x95a9dff67cf30e993071edede12623d60031fa684dfbe1654f278a1eb1eb7e1be47886d3f8a46c29b032da3176c0d857", - "0x9721973288384c70a9b191436029e85be57970ad001717edc76d44cbfa0dff74f8af61d5279c5cd5c92c9d0f6c793f63", - "0x95c22d1d9b51ef36ba30ee059dcd61d22be3c65f245d0a5179186874219c08e1a4266f687fc973e71f3e33df2b0f7fd3", - "0xb53ec083dd12cc42ae2bae46883a71f2a35443c9ce4ed43aa341eb5f616a53b64211ed5aac717fe09ef1d50f551ed9f0", - "0xa103dab6695c682400f60be8d5851ce07f12e4bd9f454d83b39c41ddcf1443bb14c719b00b4da477a03f341aa1e920cb", - "0xb522236988518e5363b1c4bb3f641ff91d3d4c4d64c5f065415b738160b4ce4b0c22e1e054a876aa6c6a52fa4a21dfa2", - "0xa6a00562f0879702cdba5befd256a09f44bf48e61780e0677ff8c3fda81d8e6dc76ba1b05e3494ca9a4cef057eba6610", - "0xb974a2ae631e0b348421f0cda5bd4ce7d73c22dd0fc30404c28852c33499818cab89fbf5c95436d56a0aab3bf2bbab51", - "0x9148cf2a7b7e773245d4df5a9d34cf6d9d42b1a26a4ca6bc3013feca6f3941d6c44f29ba9328b7fe6ce6d7f6565f8e4a", - "0xa34035c4a63e98528a135cc53bbbcfcda75572bc4c765f212507f33ac1a4f55563c1a2991624f7133c77b748bbe1a6da", - "0xa0c45923cfb7bd272ee113aecb21ae8c94dda7ad1fe051ddb37ab13d3bb7da5d52d86fff9f807273476c24f606a21521", - "0x81ec2ca57f4e7d47897d0c5b232c59d7b56fe9ce0a204be28256a7472808de93d99b43c824a0cd26391e6cac59171daa", - "0x8373852f14a3366d46c7a4fc470199f4eebe8ee40379bd5aae36e9dd3336decaead2a284975ba8c84d08236e6b87c369", - "0xb47e878a93779f71773af471ba372cb998f43baca1ae85ea7ff1b93a4dee9327e2fb79691c468ec6e61ab0eae7ceb9f1", - "0x8fc8f260f74303f26360464cfef5ee7eebcbb06073cef3b1b71dab806d7c22f6b3244ce21d0945b35c41f032f7929683", - "0x87e3c4e1dab00596e051ce780b9a8dba02ecdc358f6ddaeb4ec03c326e4b7da248404745392658eb1defff75b1ba25c8", - "0xaac95d8e3b7fe236a7ca347d12a13ec33073f2b2b5a220ecfd1986ca5c3889f0e6a9d9c377a721949aa8991c1821953a", - "0x91a483679437ae126a16f5dc3bba6e9bb199dfbba417f0dc479f22819b018c420edc79b602db6183c6591b1909df4488", - "0x94a4b2c663aa87a2417cad4daf21a88b84983a7b212ffcd18048a297b98e07dd4c059617136976fac1d9e94c8c25b8d2", - "0x83e2a690bfa93c79f878a63c0f69f57aabdd8bede16b5966ffba7903dc6ad76775df1fd5347e6f2825f6cd7640f45a45", - "0xa316af7ac11b7780d15312dc729499a1a63b61c4283e103ecce43c3b0cbb0f4bce6ff04e403f5c7cb670dee80c75ab99", - "0x8d0a911c54ee1f9f7e7794732ad87b434c3f356294d196a5e35eac871727fd32a49c27c2dfa10833f9e6f9c7ccbe0064", - "0x8b8db09028298a1f6362b346c8bfeced7cb5d13165a67c0559a9798a95b7a4a9810c02bb852289d47c59f507bd24ce77", - "0x962d57305c518f175ed5d0847fb52ddc4258ca0e4c9ddfc8c333a2ee9f8b4e48d25a3d7e644b785a5953e2e4063da224", - "0x92e0799491898271769250fe88b0cb9dadec98ac92f79de58c418d23ef8c47fcf21ddc90e0cd68bb8f1deb5da82da183", - "0x99855067125f6a6c3a3e58d3bd2700a73ef558926bd8320d2c805a68e94207b63eda6bdc5a925ec36556045900802d51", - "0xa724ae105ab4364a17ddb43d93da1e3fc6b50213f99b7be60954b24dc375c4f93a0737f4a10b4499b6f52667d5f3a64e", - "0x82070fb43a63fb50869b118f8940108f0a3e4cc5e4618948417e5cc3801996f2c869d22f90ca4ca1fdbef83c4778421a", - "0xb25c04365d6f24d5d3296c10d85a5de87d52a139ddbcbf9e0142074bc18b63a8bc5f5d135bd1e06c111702a4db4cee28", - "0x851093282dcda93e5c98d687a17a7ee828cf868f6c85d372d9ae87f55d0593d8f9f0c273d31f7afa031cf6aea6a7ef93", - "0x93f04f086fa48578210ed207065d80a40abcc82d8bfc99386a4044561d35748ff6c3da6489933c23644ad4b60726da8a", - "0x84b1b50d1e876ca5fc341bbedab5b3cc0f6a3f43ea7dd72605f74d0d9c781297b2f12b7872dd600924f1659a4cdf8089", - "0x81b0ba88c582d3956f6b49ca3e031c6400f2ec7e1cd73684f380f608101e9807f54866be0bb9a09c03953c4c74fbb3c8", - "0xa641af6ac644c41a55dee2ef55d3c37abdb19d52bc1835d88e7adda6b6ccd13987c5fd9cba9d318cabb541aa6a0c652e", - "0xa7b75b0624d04ad0901070e691eb2d2645b60f87e9d6b26e77a5fb843f846c32fc26e76ae93fd33fe3b857f87bc25162", - "0xa81ba3e2ed0f94c67cd02ba7360e134f8becf7ed2ed2db09b9f5ef0942f7073bfee74ca446067db6092f7b38f74ccc11", - "0xab80edcabab5830a24210420f880ebac4e41bf7650c11ba230f4889634dbf8e8e2309f36be892b071c67a3bab8fc7ed6", - "0x94d69b64675076fecad40fae4887fb13a8b991b325fa84e9d2d66e3b57646de71a58ad8fd8700fefb46975b18289250b", - "0xb44fc0df480cd753a041620fa655be9df74963ae03d4625847d5bb025ceb37f48d19c8c9c444546fba5fe5abb2868506", - "0xb56e2c51324d6200b3d9781b68b5b5e1617a68afccd28b3a12a4be498d2e3aafcd86514c373a9f3a001db733010c29cf", - "0xa359a0c172e5cd7ce25080dd2652d863d7c95a4a502ae277ac47f613be5991300f05978404a0acb3bcda93524dcf36e4", - "0xb01427a3dfdf8888727c0c9b01590b8ae372b7b4080d61e17ccb581bac21e61c4a58c75db7a410d1b2a367304e1e4943", - "0x95cb08be4a96c18fbf9d32a4bbf632242029d039a5fdea811488d3634cd86520d4f9806250a8c01855ee2481210f542a", - "0xb8594fe6c0717164058f08aedeed1853523f56cec5edbf0d2be271fa5e8bfd61f2974b0f3988d70f5baa2e7888c7ec1f", - "0x8f64ee89f59daf74fa1056803247c9d678783ee3917b12a201f30f7523957763e979ceaddb38bae20de40b9885728049", - "0xb6093ee4bdb837bcc59172e236f4bdbd439c0a5a50e2aa16636cbff81b51e92989eb5f80a3f75c37ae7b5b942e55b3d2", - "0x913b6fbb7b43e3e5c49e96cd8e82ed25c655e51c7b8ca82e8fbf92b01ac83c39d52f6f4efab5d39b0591a0538601a86f", - "0x81f42668479ca0bec589678dc0973bf716b632578690efe1a0f13de630f306fb4a189a98c2302572fd85d3877ee030b5", - "0x90ff89c38a9a7189f28d35a088657f52283670e7fec842fa91c265660ea2e73b0ad6c46703d649f406f787490b7a7e4b", - "0x9077b8b5f1e083183f3152ceb9c5491b5d4b86525a08879f7fb6d5e27f9f1a6867cf0d81b669a4a2d1f1654b67fa8d9c", - "0xa7a0275cf5b894adbf2e54a972310cfe113e811872111d6ee497d03750d9f6ffa5517b6c13a99b111a4a91e8e4dfeeee", - "0xa08976bf8125b7538313a584bbe710741d630cab067a204ad4501cc4938874ce7aa6a1a826259c2e82ef10a66f1f36fa", - "0x8aa45385b5b97f1f3e45f2bbf7a4f3e8ef068e628608484971c97adeb610ebd5deec31317e03eb6536808921062c04db", - "0x945b106b8f3ae85e60dfd34ef3dcc079bc6f0aab6df279ed000856efd51321462038ac0a1ca5db3ebf6379bc341e7c55", - "0xa4199c87a96f98cc9d8776fe6de131d2c706b481eb9e9a3bbc50a93d492d7fd724ea469f723fbcfb94920cb5b32c1d76", - "0xa5347b1b2f6149805de67546c5ed72253311099bf1473dbc63edcf14a0a5e68d401f5341338623fbe2e2715b8257e386", - "0xaf5dcd03ddc3769e83351d6b958d47a06d4e5224bd5b0ec40ffe6b319763fab8572002f4da294a9673d47762fd0e6e1d", - "0x82ec1031b7430419d83b3eea10a4af4c7027f32b91c3ae723de043233b4a2e0c022c9e0f5a1ac49753800f119159112d", - "0x8a744d911b67d03b69811f72e9b40d77084547e4da5c05ff33893468b029a08266fc07303f7005fd6099683ca42b3db4", - "0x93ab566bd62d3439b8fc620f3313ef0d4cb369f0f0c352cdaf8e5c9e50b9950ac3540b72f4bf5adcb9635f9f7ce74219", - "0xb2a211d72e314799bc2ac7030b8bbb8ef4c38ebd0ebb09d6cbd43bd40c6c61d80a3aad02cc73f5775a08b9657da20a48", - "0x98d60f0a98d28718e0c6dcccc35a53521ea7f2d8fe08ea474374a336b44cea4cd1c63b31f2ad10186822bfb54aca53e6", - "0x831f89cb94627cfe554d46ae1aad8c1cde7ebe86c4bd8fac4ef73ac2d5b491f5efa5dc4198cb8ffbec563e0606b91d89", - "0x8f8552583bc6cb3fb176b7202236ee4128faf0c8ec608f9150f8e011d8c80b42aab5242c434d622b6d43510eaef752c0", - "0x897bf27baaee0f9a8445200c3d688ae04789c380d1b795557841606a2031092328eb4c47fef31c27fdd64ba841d9d691", - "0xb57589a4af8184b4a8ceb6d8657a35522672229b91692c1cec3ac632951e707922a00086d55d7550d699c4828bcfaab1", - "0x98c2fe98095e026aa34074bcff1215e5a8595076167b6023311176e1c314b92b5a6d5faa9599d28fca286fadd4e3b26c", - "0xa034992e563bd31ede3360efd9987ecddc289bc31046aa8680903bb82345724805e6f6cf30f7889b6b95cf7319c3aea1", - "0x85c33d9f10cc7185f54d53c24095e621966065e0ff2689a9aa6bb3d63706796c37a95021738df990c2c19493c0d44b64", - "0xa8c1247d6de2215f45b50dd2dc24945ff9b93184bcc2159b69703b0bba246adcd1a70a12659f34c4ca4ba27dea6e3df5", - "0x83ebdad2834c97bf92aac8717bab2f5cb1f01026b964d78e2f3b44e99d7908e419165b345d2b2f125b903096584e6683", - "0xb0af6f7f81780ceb6e70adfd98e7702ec930c8ca854b50704c4a0fc8b887b9df60a6fe9038b487f3ed0eb8eb457307ea", - "0x933ec7e53882453898617f842ab2efae4756eb6f6ea0161cced5b62a0cdde4c08c7700d52f7546d4dd11a4c9e25d624e", - "0xadf6e6d4706025f85eb734f506dde66459c9537a1abf6189199cf219ae583b461e11c6242fce5f0795e4d9025270fabf", - "0x89e4316319483098761b0b065df4cfb542963b7a2556ba5425b6442fb0e596eb2a4f03e2dc8c617eebe8f243a12e7d10", - "0x90c5a147555759ebc4d0e15e957a548315f9994ef0c7a3f53f2d18da44fb93bf051d96ba8551597a6f3e701b926fd791", - "0xa151a9a5199c72c697b771cd81e550fc6f9596c752ae686ad988b316a7548360cf9785ab4645164d96cfdf9069a94020", - "0x80cba11a3977729d7948db5bcc186159f4cae7c0a835bb38bb781e287dd6c238508e748f23454405c9d5eed28e77df02", - "0xae4b92ea03cb8ad12ad3ec76869ad05acb09f9d07a3c9a87dec0e50d9a276fe5d3d515a8c446f3aa35cd7d340a22c369", - "0x8630062709a1f180f952de9f1ca3f41acce5420677f43d9619097e905a6237f1908d66db7a4dfdf1b2b92fb087e9944f", - "0x81defc33dd383d984c902c014424bddd5e53b013f67f791a919446daa103b09b972fa5242aba1b1dbe4a93149373f6c3", - "0x963891ecaea97e661bac2594642327a54f5a0beb38fcb1c642c44b0b61faab9c87b0c9f544a3369171b533d3ab22f8f1", - "0x932fadbff5f922ddcd4da942d57fe3e6da45c3d230808d800a3ca55f39b0b62f159be31a5924b395d577a259f48c6400", - "0x992ce13bd037723447f88aeb6c7722fd9510c7474192b174ea914ed57c195c44c298aec9a8cabac103f0a5b50051c70b", - "0xb032157b3e4fe69db6ce6bb10bdf706a853fbd0bee08c2ab89da51ad827425df5df498b90e7a30247a7f9e954ca986e5", - "0xb2478d4874578da3d5000893736bb65712e6aafe96e6fa5cf5878ae59ba0ce640dbe5d76ec2b5baca75af57def471719", - "0xa387c17b14dd54910fecf472f760e67cf71a95e9e965cc09484e19581ada65e79938b86136a93e287e615fbd4908e080", - "0x98f02be271d0f8841d8d561163f9e55e99b57aff121a93fba7a4654bcf15a0899811f00f5bcbfbebd98e365a0e332e97", - "0xa3c34f01d54cab52a8890391b8cf152cc9cdc16e7e53794ed11aa7b1a21e9a84d39ddcfbcb36c5df6891c12307efc2e0", - "0xa940331f491ec7ad4a9236ca581b280688d7015eb839ee6a64415827693d82d01710dc4bbd5352396be22781fea7a900", - "0xb10874ed88423731535094031c40c4b82af407160dfade4229ac8f4ef09d57b3db95c4a9d73c1a35704f6bd0d5f6c561", - "0xa9c5a4a7680261c1b0596f8ab631d73d4a7881b01e6559c628b5cdafa6dd2b6db2db64f3f2ab5841413a8a52b966a0da", - "0x8fc154564a61d5e799badc98b43a3587f804385a850adce9a115cbd2ad911f3fd4072b8e6b22fc6c025a6b7e7ea5a49f", - "0xb9caf7c6dcce3d378aa62c182b50bc9c6f651eb791d20fffa37ef4c9925962335fe0b3bc90190539312aa9ccf596b3b9", - "0x90c5b7acf5cb37596d1f64fc91dee90f625f4219fa05e03e29aebea416c8e13384f2996f8d56791bcf44ae67dc808945", - "0xab8d311fc78f8a1b98830555a447c230c03981f59089e3d8a73069d402a3c7485abe3db82faf6304aaca488a12dbe921", - "0x8a74fda6100c1f8810a8cacc41b62875dd46d5c4a869e3db46202d45a8d9c733b9299dda17ce2ad3e159122412a29372", - "0x8769dcacba90e6fc8cab8592f996c95a9991a3efecfb8646555f93c8e208af9b57cf15569e1d6e603edac0148a94eb87", - "0x854fd65eea71247df6963499bafc7d0e4e9649f970716d5c02fbd8708346dcde878253febb5797a0690bd45a2779fa04", - "0x83e12dc75ef79fd4cc0c89c99d2dace612956723fb2e888432ec15b858545f94c16fae6230561458ceee658738db55ba", - "0x8416ef9ac4e93deff8a571f10ed05588bef96a379a4bdcc1d4b31891a922951fa9580e032610ac1bb694f01cb78e099b", - "0x93aea6e5561c9470b69d6a3a1801c7eef59d792d2795a428970185c0d59b883ab12e5e30612d5b6cde60323d8b6a4619", - "0x91d383035aa4ec3d71e84675be54f763f03427d26c83afb229f9a59e748fb1919a81aca9c049f2f2b69c17207b0fb410", - "0xb1c438956f015aef0d89304beb1477a82aed7b01703c89372b0e6f114c1d6e02a1b90d961b4acbb411cd730e8cacc022", - "0xa1ee864a62ca6007681d1f859d868e0bcd9e0d27d1da220a983106dc695cb440980cfdb286e31768b0324b39ae797f18", - "0xb57881eba0712599d588258ceada1f9e59c246cc38959747d86e5a286d5780d72d09e77fd1284614122e73da30d5cf5c", - "0xa48f9ae05ba0e3a506ba2e8bbce0d04e10c9238fa3dffa273ef3ffe9ec2ed929198a46507c0c9d9b54653427f12160f9", - "0x8db18da7426c7779756790c62daf32ae40d4b797073cd07d74e5a7a3858c73850a3060f5a3506aae904c3219a149e35d", - "0xa2bf815f1a18d7be8ce0c452dfc421da00dcd17e794300cdd536e4c195b8c5b7ccc9729f78936940a527672ac538c470", - "0xa34c6f1f2398c5712acc84e2314f16d656055adcafad765575ae909f80ab706cf526d59e5a43074d671c55b3a4c3c718", - "0xb19357c82069a51a856f74cbb848d99166ce37bd9aca993467d5c480a1b54e6122ebddb6aa86d798188ea9f3087f7534", - "0xb440eac6f24d12c293d21f88e7c57c17be2bdb2a0569a593766ae90d43eccf813a884f09d45a0fb044ee0b74ff54146a", - "0xb585d42ef5c7f8d5a1f47aa1329f3b1a566c38bf812af522aa26553010a02bfd6e9cc78fdb940ef413e163c836396a5f", - "0xaca213b27f3718348e5496342c89fffc7335f6792283084458c4a1aa5fe0a1e534fcec8e7c002f36141308faae73ef2a", - "0xb24c07359769f8ffc33bb60c1f463ea2baad440687ef83d8b7c77931592d534b2c44953c405914ace5b90b65646c1913", - "0xb53dfaf381205a87ca4347328ff14a27541fa6436538f697824071d02d4a737ceb76a38dcc6e8dadef3b5bc6442f5109", - "0xb55972d8ed5197215c0a9144fc76f2cd562ca5f4e28c33a4df913363fd1388978b224c44814adb4c065c588a4ac1fe10", - "0xa3303bc650e120c2e9b8e964ad550eb6ac65ffe6b520768b3e8735565ae37eafdc00e3c15fae766d812f66956a460733", - "0xb11e53912ea0e40c3636d81d7637e10c94cc7ed9330a7e78171a66d02b7603f4cb9b3f6968104b158de254e65b81640f", - "0xb076bb9f6d396aa09c2f4706ea553b426fdfd87d7d69e438285b74d334e82f73973cb4dbd6cb1647493433dad65dbc41", - "0x9415828b1632175f0b733541e32c26a9c88fe12c721c23e595f2efceaa7f867f359e32564b7c032185686587ac935cf4", - "0x89579a112c306181c79aabdbf683e7806357febcb73bf5e8883862ae29618ef89498b62634404bb612d618fcd16da415", - "0x8761bcd55d04297c4f24899e8fb9f7c1fcd7449ae86371ee985b6a262e228f561c2584980694d9bf354bdf01543edb6a", - "0x9100c88bf5f6f00305de0c9cf73555f16a2016d71c50cb77438e8062bd549fa5407793a8a6a7e06398756777680a2069", - "0x9235dfef45aeff9c174898b0755881b7171ed86362854f0eabc3bc9256176c05a5dc27ca527c91c3fa70c0ec5fd5e160", - "0xac53b1d677cebab6a99381dd9072b8ac1abae9870ec04a1f8d2a59b6f1de797c1492b59af6948f5cf2b20599170f5bba", - "0x946542936b0c59156e8fd5c1623b41369bc2cbcc46ece80360dcb5e7cce718a3dd8a021f0b9c223062a4e43d910b634f", - "0xb1e9939b34e1fcc026e820fcfa9ce748b79499f8e81d24a3ef0457b3f507fe5fa37b975a47c143e92eb695623b4e253b", - "0x9382d9b5766f6ae960d8a8435e8b5666e57ef8e5f56219e7bfd02857afe5cb16f44d70a9e444cfb1008649ae9b863857", - "0x91770ed1215ed97dca1282b60b960be69c78e1473edb17cd833e712632f4338ff74bf435c3b257439497c72d535ae31f", - "0x8eb2cbe8681bb289781bf5250e8fa332141548234c5c428ff648700103a7cd31fdc2f17230992516c674aa0ab211af02", - "0xa823b71c82481bc6ac4f157d5c7f84b893a326bbb498c74222427ded463d231bc6e0240d572ab96266e60eb7c8486aea", - "0xa13ce4f482089d867e5babcd11c39fa9a9facd41a2c34ee2577de9ce9c249187e16f2b3a984cc55f9e45b9343462d6d2", - "0x8d80e7bc706059cf5151f9f90e761b033db35d16b80b34dc8b538adc8709d305a0c06933dcd391e96629cf3888c8bf87", - "0xabcd36cdd86c0fb57fb7c0d7a3b9af5fd9aed14e9f4e7e84b0796c5c0ad18c41585e8c46e511cef73dc486fe43f6a014", - "0xa947a5b6916f416fa5a69c31aba94add48584791148b27d0b3ed32c02a05dfc06f7fdc5006e3b2503bdf6e410e30f2fb", - "0xb158e621580659f1fa061d976b8591ac03b53ecd23d9eb2b08c1a20353d78438287749664d196020d469ef44b3b8752e", - "0x90a5a9540281e481ac4b8d29968f477cb006b56bd145529da855d65d7db0cf610062418c41a1d80c4a5a880c0abe62a0", - "0xb2c91808b6289d08a395204a5c416d4e50a8bb1a8d04a4117c596c4ad8f4dd9e3fb9ce5336d745fc6566086ae2b8e94f", - "0xaf6767c9b4a444b90aeb69dfddae5ee05d73b5d96e307ce0f3c12bccca7bc16475b237ba3bc401d8dafb413865edf71e", - "0x8dcecf624419f6517ef038748ac50797623b771d6111aa29194f7d44cfb30097ced26879e24f1b12a1f6b4591af4639b", - "0x954437559d082a718b0d6d7cec090532104ab4e85088e1fc8ee781d42e1a7f4cdb99960429707d72f195ff5d00928793", - "0x80f0b7d190baa6e6ab859dc5baab355e277b00ddcca32e5cebe192877ad1b90ead9e4e846ca0c94c26315465aeb21108", - "0xb8c29f181ed0bb6ac5f6a8d9016980303bb9a6e3bd63ce7a1a03b73829ac306d4fab306ac21c4d285e0d9acb289c8f2a", - "0xa7685079fe73ecaeabf2a0ef56bad8b8afb6aeca50f550c97bf27e6b4a8b6866601427fcd741dc9cb4ce67a223d52990", - "0xada2ebf6f2a05708d3757fbf91365ec4d8747eb4c9d7a8728de3198ceac5694516ab6fd6235568aecd8d6d21fef5ef48", - "0x846bc5da33d969c53ab98765396cab8dcdbb73b9836c9bda176470582a3427cb6de26d9732fab5395d042a66bdba704c", - "0x800a3a7ea83ce858b5ebc80820f4117efa5e3927a7350d9771cad9cb38b8299a5ad6d1593682bba281c23a48d8b2aa71", - "0xa002b18595dec90b5b7103a5e3ec55bdd7a5602ee2d3e5bd4d635730483d42745d339521c824128423dfe7571e66cbaf", - "0xb6b4e2067ac00a32f74b71007d8ab058c2ef6b7f57249cb02301085e1a1e71d5de8f24f79b463376fd5c848f2ab1c5bc", - "0xa3e03036db1b6117efe995bf238b0353ad6f12809630dca51f7daaaf69f7db18702e6b265208944bfb1e8d3897878a51", - "0xadd16712f66d48aab0885bd8f0f1fb8230227b8e0ffca751951c97077888e496d6bfab678cb8f9ffba34cee7a8027634", - "0xad211af2dd0748f85a9701b68c19edd4a7c420e497cb2e20afdc9df0e79663841e03b3c52b66d4474736f50d66c713ce", - "0x8c8a899ce0f16d797b342dc03c2212dda9ee02244c73c7511626dba845d11a0feb138441da5459c42f97209bf758cd9b", - "0xa17efc75c7d34326564ec2fdc3b7450e08ad5d1de4eb353de9d1cd919d90f4be99f7d8e236908b1f29cf07ae1ffe0f84", - "0x862d4a8b844e1b0dd9f4deff180456ebed5333b54290b84f23c0ddb2725ac20307e21cbb7343feac598756fe36d39053", - "0x9187fbb19e728a95629deda66a59e178f3fcd6e9d7877465aa5a02cea3baba2b684bd247b4afbf4aa466b64cb6460485", - "0x85ae5636688d06eab3be16e44fe148515d9448c6123af2365d2c997f511764f16830610a58d747adab6db5031bea3981", - "0x8aa8a82891f4e041ce6df3d6d5d7e5c9aaaffe08e0a345ac0a34df218272664c1b7be2450abb9bc428bd4077e6e5dcc4", - "0x8c3bcc85ea574dfe1b9ca8748565c88024e94374434612925b4e9a09fa9d49c0a56b8d0e44de7bd49a587ef71c4bff5f", - "0x9524f9dd866fe62faf8049a0a3f1572b024120d2e27d1be90ad8b8805b4e2c14a58614516281cc646c19460a6b75587c", - "0x84580d9c72cfa6726ff07e8d9628f0382dc84ce586d616c0c1bd1fd193d0a49305893eae97388de45ba79afe88052ee9", - "0xb5573e7b9e5f0e423548f0583423a5db453790ab4869bd83d4d860167e13fd78f49f9a1ffe93ddddf5d7cd6ec1402bc4", - "0xaff658033db3dad70170decb471aee2cf477cf4d7e03267a45f1af5fd18200f5505c7ce75516d70af0b0804ec5868a05", - "0x84a0eab4e732a0484c6c9ed51431e80cea807702fa99c8209f4371e55551088a12e33a11a7ef69012202b0bc2b063159", - "0xa68f8e730f8eb49420fe9d7d39bb986f0584c1775817e35bb3f7dae02fd860cddf44f1788dc9e10d5bf837886b51947f", - "0x946002dd6cf7a4fd3be4bf451440e3f3fd7e9b09f609fa4e64767180b43146095dfc4b6994287f8cfa6d1390d144be71", - "0xb7f19777d0da06f2ab53d6382751dc5e415249d2c96fce94ef971401935c1d1f7d3b678501e785cf04b237efe2fe736e", - "0x81e5c66dd404fc8ffd3ac5fe5e69ead7b32a5a7bc8605a2c19185efcc65c5073e7817be41e1c49143e191c63f35239c1", - "0xb5f49c523532dfa897034977b9151d753e8a0fc834fa326d0f3d6dacc7c7370a53fc6e80f6d5a90a3fbec9bbb61b4b7c", - "0x8fc8e78c07319877adfaa154a339e408a4ae7572c4fb33c8c5950376060667fbfc8ede31e1b067933d47e3fdbf8564d7", - "0x859cfef032a1a044532e2346975679545fbb3993a34497ce81bdcc312e8d51b021f153090724e4b08214f38276ee1e0d", - "0xae476722f456c79a9c9dfdc1c501efa37f2bff19ab33a049908409c7309d8dd2c2912aa138a57a8d5cb3790ca3c0ba2f", - "0x89acbbeffb37a19d89cfe8ed9aa8b6acf332767a4c54900428dd9ab3bf223b97315aca399c6971fe3b73a10a5e95a325", - "0x90a4a00418fdf4420a4f48e920622aae6feb5bf41fd21a54e44039378e24f0d93ccc858d2d8a302200c199987d7cb5e4", - "0xa3f316b0bd603143eba4c3d2f8efe51173c48afe3c25b4ca69d862c44922c441bd50d9a5040b7b42ba5685b44071c272", - "0xa22f4dc96fedd62b9a9f51812349e04d42d81d0103465c09295a26544e394a34abdc6ded37902d913d7f99752dbfb627", - "0xa49f51baf32d0b228f76796a0fef0fe48a0c43ec5d6af1aa437603d7332505be8b57b1c5e133bc5d413739f5ae2ce9d0", - "0xa9e4fe133057a0cd991898e119b735b31a79811307625277c97491ff5d864c428cfa42ae843601d7bb05c0313472d086", - "0xb987edfe0add1463a797ff3de10492b2b6b7ef0da67c221ab6f0f2b259445768a73fbe495de238c4abbe4d328e817c49", - "0xb7f0e4532a379a4c306bbef98b45af3b82b17175dfe0f884222ed954c12f27d8a5bdd0cdeb1df27ff5832ba42a6dd521", - "0x9471bc5ad5ec554acfd61b2eb97b752cb754536f95ae54ca2cbd1dc2b32eb618881f6d8a8b2802c1a4e58c927067d6cf", - "0xb4c84f09225cf963c7cc9d082efe51afbbbe33469dd90b072807438e6bde71db8352a31bb0efde6cd3529619812ef067", - "0x8f08005a83e716062d6659c7e86c7d3b51e27b22be70371c125046de08f10ea51db12d616fbf43e47a52e546e7acaac7", - "0xa8937e66a23f9d9b353224491f06e98750b04eca14a88021ee72caf41bdce17d128957c78127fba8ef3dc47598d768a7", - "0x80ad991de9bd3ad543cddeaa1d69ca4e749aaefb461644de9fc4bd18c3b4376c6555fc73517a8b1268d0e1e1628d3c1f", - "0xb22f98bca8fe5a048ba0e155c03e7df3e3cee2bfe8d50e110159abdb16b316d6948f983c056991a737b646b4d1807866", - "0xb0bb925c19ca875cf8cdbefa8879b950016cc98b1deb59df8b819018e8c0ad71ea7413733286f9a1db457066965ce452", - "0x95a991e66d00dd99a1f4753f6171046a5ab4f4d5d4fe0adfe9842795348a772d5a4a714dba06b4264b30f22dafa1322f", - "0xad91e781fa68527a37c7d43dd242455752da9c3f6065cd954c46ae23ce2db08f9df9fec3917e80912f391c7a7f2f7ffa", - "0xa202d3becbf28d899fe28f09a58a0a742617c1b9b03209eca1be7f072a8ada1f7eac2cc47e08788d85e1908eb9d3d8ee", - "0xa360ccb27e40d774d5a07b4ebed713e59a0d71b3ee3f02374e7582b59ec4a5ce22cc69c55e89742ba036dd9b4edd8f34", - "0xa10b897a946882b7c9e28abbb512a603ffa18f9274369843eb3491524a321df1f572eea349099ac6e749ea253c901ea0", - "0xb782a672cd344da368732ecd7e0a1476c2af04613d3eb6da0e322f80438af932bd6d49be7a6f69f7c877512731723d89", - "0xaeccee8dfd764e1adcfc4bf669e0fa87a94e7c79324333e958df47888bff5cec358b8b5bbb48db54822b54d11bbb4bc6", - "0xad4953913662a9ee8753a354864339f43916f2c2390d0a3f847c712b42718ee00ee14158d730709971941e8680d54560", - "0x92ccb31d6c9e8940c7e8a4873e7eb9de9fb2fa2bac344fa367062ea451fd49a6920a45218dca3ee968711397d2a01536", - "0x9448d9b2b3d12dde9b702f53373db8b8595f9d1f9de2ebee76de292f966f375316953aadf6bfc0e4e853e1fa12d8f02c", - "0x8919230878a7219da8c80a4b7d00b9169fb503e72d79789dd53863c243b8d0fb0a819d46fa636d805d0b9b1d15d1f2d9", - "0xb6581ab01215aac023f5e6f57419b6aa63c0743c07caf57d4e146b56b02d90ce1423f70489ac3a11e5c968cb924f937c", - "0xa793ec1b1fe56a76920296af06073caadfd6f1d7e30950f8ca13de3de45fe275ca4b361f5249d9405264c3a06ebb5502", - "0x86385b4a4e1bfb5efe7bfef8fd0dfeba7f4400852237cab60febb1dfa409e497a649e81284b5a15fe680b78927256756", - "0x85d10600de96103daa7c90657174b6cb4a1286df5379f1eda9f11c97f9df57043c290eb1ae83658530fe0fd264867b86", - "0xae01b2396d0f598c21659cd854c15edd4904a34d22278aef97c9260a14a8b250b52d972d304ac4b187c24d08795d5355", - "0xb91b3e4b6fc06e88081fe023ef1b773d82c628eb0f73a2731a9aa05b0dc89b7aeef2eea60125d302e696f45c407aeac2", - "0x986d0f478e33af7568eab6bb26a55c13ffd7cae27525b4abe2f3a994bdb11bbc73d59bdb9a2f6b6ba420a26f8f620ba6", - "0x9746f4fdeef35feaff1def0ea5366b64f21ed29749ae6349f9cb75987e7f931952f913f446100f2a6b182561f382e8eb", - "0xa34a116cfde1acbce0d7de037f72a7ca30ab126d8f4815b2b8bcb88e0e6c89015a4daaf4d4ce8eae23eb5d059cf9a5cf", - "0x80c3ea37f6a44f07cc9c9c881990f2a5deb9f9489a382718b18a287aa3c50ee6ebe8fd1b3afb84a3cf87f06556f4ca15", - "0x97cff3bc88cfc72ce5e561f7eeb95d4ffb32697e290190c7902e9570c56b3854753777fc417fd27536fc398c8fefb63b", - "0xb8807232455833e4072df9bffa388ae6e8099758c2a739194719af7d9ed4041974a6cd9605f089de8b43f0e12f181358", - "0x96f79fca72f75dc182c71f2343f0c43b06d98563fd02d2e1fbc031b96601608d8a726c811a74bb51ab8b0a3ce3632dc4", - "0xb5262761680a4235a8c1257de4735cdcadf08d5d12c6e9d4f628464d5c05dfff3884a9ef2af3b7724b5a8c97e6be74eb", - "0xb6ce0eada73433d98f8fae7d55e4ea2b9d9d7a0ae850d328dd06991f27b1f03e470868fb102800ff3efe4ee1698531b9", - "0xa37b7d9fe9d3fdfbc72c59cf6cacc7e7a89d534dea3d73121f7483331aec8ab3fbff58ffabb943b75d6f86df0ba43262", - "0x93fce9be8a27fcaa1283d90d3e87265a6221ee302ec708161a42bd00ffe8e726743d9e187e1bf4307c0e3f25afbb1d44", - "0xa4ea919021346ae7ea69d5e8f46d860b24c35c676b62f4e577c90e0c05c5646fe73721b143b7c38835dd4b443e6c3676", - "0xb79983a5948453f70dfa4c396ce1945204498fe79f40c0667291bd0fdd96ed0b9ea424571f7ade342275c854c9f03d9e", - "0x866f8e395ed730b614b70bf999cad6e87e9086c1f5aea8d69020b562ee285dd0fb93afaca0dd13a0713f74a3f9340f01", - "0xa3fef158782292c6139f9a0d01711aa4ed6f5cac11d4c499e9e65c60469ae3afbde44fb059845973a4b3bbca627b7eb7", - "0xb4a2c0321b68f056e7d8051beede396fa2f0704d8aa34224f79f7b7a62eb485fc81889cb617019622fd5b5fa604516f5", - "0x8f0e3edddbaead9059df94de4139e3a70693c9ea9bc6baaa5695dddfd67263b33926670159846292801941b9a0c6545b", - "0x9804e850f961e091dadd985d43d526ba8054d1bf9c573ed38f24bbd87aeaad4dcba4c321480abc515a16b3b28f27bb2a", - "0x95f330da28af29e362da3776f153f391703a0595323585220712dae2b54362cc6222070edd2f0dd970acfbe2e3147d5c", - "0x82d03b771231179cc31b29fe1e53379d77b5273b5c0a68d973accd7a757c7584dbb37f0507cdfde8807313ec733a6393", - "0x81b3c39a9f632086e97b7c1f0ec7e2eaf9dc3cb0d84dec18a4441dbdc9fe9878fde4bcfa686bca1a9522632a353a5566", - "0xa2db124ab2b493d5f9a1e4ca6b3144593c2fc8bfac129fd79da11dfbb7ef410a234fda9273a50a5ca05d7b37cc2088a2", - "0xaa8550633c9449228702690cc505c0fc4837ea40862058e8f9713622b34d49fdc3a979b9317993c5da53b5bb5b7f4974", - "0xae783bcf7a736fdc815d0205b4c2c2b2fee0a854765228f76c39638ba503e2d37f1e28f6bdf263923f96fead76b4187b", - "0xb5ec86092c1d250251e93bab2f24e321afd2cd24cf49adfcbed9e8bc5142343ae750206c556320551e50fc972142f0da", - "0xb3b5791b590a6e9b3f473d5148624014aa244495249322a5d75cde2c64117ff9d32f4b0698b0e4382e5e7f72933061f8", - "0x876c6a9162c17b16d6b35e6ce1ba32e26aec7dd1368bceab261ab880ad845c91e54b96a52c7d3aafbfbafc0e37139dca", - "0x902ddb5774d20b0707a704486457c29048776a5b88c377b14af6616c8ddf6cd34f49807df9c9d8866d6b39685cfb0f19", - "0x8b87f71f94bc96de927d77a5d7123fa9cdda8c76aff64a5e6112cbc2eca43b07f8376db3e330f8af6a1db9b948908a6a", - "0xa69a5922e572b13d6778218e3657f1e1eea9a9682f6eb1b731d676d03563e14a37ff69bc5e673c74090ecb0969a593f7", - "0xaff3510d78ba72f3cf5e3101847b7c4a956815aa77148689c07864e8a12dd0ef33d5f6c8cb486e0ea55850161f6afed0", - "0xaa9c459cb2a008d94cbee2c6b561d18b0d7c6ffa8a65cbf86ae2c14eec070ee9d5324f5d38f25a945ddcd70307e964c4", - "0x8310e15b050b1e40ece7530b22964bde0fd04f48dfffdec5a0d1fb8af0799a7fdc1d878139fb7cb8d043d3a52c2d1605", - "0xb8f0856ce2c4034ee4041d0383f25fb0eeefc00b82443311a466fc18608313683af2e70e333eb87e7c687e8498e8a1ce", - "0xa8200a75c158fbb78474cab8a543caecd430b5d8b9964fc45d2d494dd938021cd00c7c33413ad53aa437d508f460a42a", - "0xa310091472b5b42b02176b72d5f8120bdb173025de24b420e3ca3fb9a386c39092a1d1bb591c6f68ee97a268a7ff9e95", - "0xb23f1bf8bcec9cb5232b407115eead855fd06f5bf86ba322ad61d45460c84f0f36911aba303de788c9a0878207eac288", - "0xae4c129ad6d08be44690bb84370e48bfd92c5d87940750ee2c98c9a2604456f7f42727ab211989657bb202f6d907df04", - "0x95992057d654f3e189a859346aa9aa009f074cb193b7f5720fa70c2b7c9ce887d886f6cff93fa57c1f7c8eaa187603f6", - "0xad12d560273963da94151dd6be49c665d7624011c67d54ab41447452a866bc997e92a80bdd9ca56a03528e72c456dc76", - "0x8e4eda72e9cfcaa07265bb6a66d88e9ce3390ae1a6b8831045b36ea4156b53d23724824d0f0bca250ce850c5926fa38f", - "0x980fe29c1a267c556532c46130fb54a811944bdfea263f1afcdab248fa85591c22ac26167f4133372b18d9f5cce83707", - "0xa7da9f99ddde16c0eac63d534a6b6776ad89b48a5b9718a2f2331dce903a100a2b7855cf7b257565a326ddc76adc71a5", - "0x8ca854c55e256efd790940cb01125f293e60a390b5bd3e7a60e13ac11a24f350a7eb5ebddfa0a2890905ca0f1980b315", - "0x9440335818859b5e8f180893a8acedceabaaa44e320286506721c639a489b5bfb80b42b28902ee87237b0bd3dd49552a", - "0xb9da545a20a5e7d60fd0c376dcaf4b144f5c5a62c8ffa7b250c53ce44be69c4e0d5e4e11422ef90593ae58ae1df0e5d3", - "0xb75852a850687f477849fc51e0479703cd44428671c71bfdd27fe3e7930b97d2fc55f20348ca4e5bc08db2fc16a4f23c", - "0xb515081d8d099e4b6253c991ca2d3e42633f5832c64aa8f9cde23cb42c097c2c3717c46c5f178f16c58295f97b2b3fe7", - "0x9506c9902419243e73d3197e407985dd5113f16c6be492651bbbf9576621942710aea74522d6fb56d5b52c6ccdaa4307", - "0x952673ae27462a0f6c9545eede245c2f8e2fd6077b72a71f5672f1a5a02c263bc2a66f24f0e30376feb7a8187b715f08", - "0xa8f1e2085ed666a8f86b474d9589dc309d5c83bd53e745f8e09abe0dfbaf53e5384c68580672990344d4aa739438b4d8", - "0xad6e04d4a67a5a5529ceaf7de6e19416be5b4c436610aa576ac04aee3b73317da88f891121f966393a37f52b775a2dd8", - "0xa35a884736f08c7f76923ae7adb17fdac04e6c505178bca9502eaa2ed16d4d93fa953fb6dcf99e9e9962a6eb3eeead00", - "0xb8af72273360bab4b3ca302cf0659717cbfb335fbc9ad4ffdd3340113ece9e63b2bdbd611e5f6b740a4689286f9a452d", - "0xb1a1f4ba2640800c3ed3892e049f6e10f8a571efa3bbe21fe2d6cee8fded171c675a3bb8aa121e2d1d715de84bad2e2b", - "0x8102a6c3598b40da4d6e8eccfdd5dadc8d6262e38b69c5b211b0732f4c6e3045d79fba12770a0b2b66f1e9f4664b1510", - "0x90979587d75bf12819f63832beea7dcbef101f6814bf88db4575bfcd9cf0ea8eceba76d4d6db17630b73b46c1acfe011", - "0x8dd98f14d2beb5b5b79cc30f6825ec11ed76bd5a8864593ffc0c2baffab6872bad182e1c64b93aab8dd5adb465fa5cec", - "0x8083334dadc49c84f936c603a2857f174eda5659ab2b7214572f318aba3ebd7b1c50e7cbea57272b9edf106bd016df3b", - "0xa634d08d2e8641b852e89d7ccab1bab700c32fb143bcbea132f2a5fb2968d74ded2af4107f69818798f0128cc245a8cb", - "0x94fc2dccf746d5b3027f7cf4547edf97097cd11db8d6a304c1c2ca6b3aba28c1af17c08d2bbb66f88c14472e0196a45e", - "0xb257a6fb01424b35e414c1c002e60487abb3b889d74c60cbdbf591e222739c6f97b95f6962842401f5e2009e91b28c55", - "0x81955bdbf25741f3b85d5044898dc76ae51b1b805a51f7c72a389d3b4d94b2e3e0aa1ec271685bbcf192ed80db7367ab", - "0x86eb229b66c542514e42b113b9de7d4f146861a60f2a253264873e7de7da2ac206e156ff11f2de88491b9897174fe2f4", - "0x8b8db00533afbb56b3d7d7a9a4a6af3cebb523699ffcb974603e54f268b3ef739c41cd11850b9651d9640d72217c3402", - "0x8b7cbb72a6c4408d5f1b61001e65de459790444530245d47d4ee8e2d17716695283f21540bd7ac4f5a793a0d00bdf1d4", - "0x875920b9bab4bc1712e6af89ae2e58e9928c22095026070b07e338421b554d9f96e549ac3706c6c8d73f502913a27553", - "0x9455d192db7b039b3e8f0bc186c25ff07dfbe90dab911e3c62e3bd636db8019ed712cbb0ecd5cbb9a36c11034e102aba", - "0x8cb0b28e5d3838d69f6c12274d6b1250f8843938065d0665b347977fa3c1c685caef6930bae9483ed0d0a67005baad76", - "0x94df2e14aae1ae2882ab22a7baf3dc768c4a72b346c2d46bfd93d394458398f91315e85dc68be371f35d5720d6ca8e11", - "0xaacd94b416bfbeb5334032701214dd453ad6be312f303b7bec16a9b7d46ab95432a14c0fbf21a90f26aafb50ec7bb887", - "0xb43d26963665244633cbb9b3c000cacce068c688119e94cc0dac7df0e6ee30188e53befff255977788be888a74c60fc2", - "0xb40d67c9ad0078f61e8744be175e19c659a12065fe4363b0e88482b098b2431612e7c2fa7e519a092965de09ceafe25c", - "0x82cd4a4e547c798f89ce8b59687614aa128877e6d38b761646d03dc78f6cdd28054649fb3441bcd95c59b65a6d0dd158", - "0xa058e9700f05cef6e40c88b154d66a818298e71ae9c2cf23e2af99a0a7dc8f57fbe529d566cb4247432e3c1dee839b08", - "0x95c6f84406466346c0b4a2a7331ac266177fb08c493d9febb284c5ca0b141ccc17aa32407f579666b208fb187c0227dd", - "0x905d1d47a26b154f44d7531c53efbc3743ff70bd7dba50c9b9d26636767b0ae80de3963c56d4604399126f4ad41a0574", - "0x83dfa11c520b4abaefe1b2bc1ce117806e222f373cd4fb724f3c037c228e3379d27a364e68faa73984ba73a0845f1b9a", - "0xa16e54786ba308a9c0241aff8f1bf785dece387d93bd74aa31de0969e3431479e2c0abebff9939a6644d2b0af44f80bb", - "0x81ac565212365176f5be1c0217f4e7c9fdbc9fe90f16161367635d52edcf57af79290531d2e8b585e1223d33febd957d", - "0xa296f4b09915e5d80ff7274dc3ffc9b04f0427e049ea4ef83dca91095275e8a260ef0335c7b6585953b62682da8c8e99", - "0xa9150626208168a21ae871192ca9f11c1f7f6e41e8e02de00732de2324d0d69fe52f8762155c9913ee408a034552e49a", - "0xa42a56008ca340c6e9ff5a68c8778bb899ba5de9e7508c0cac355c157979a7ff6a6bd64f98b182114d3831cfa97ee72b", - "0xa4f05adf22c051812279258eea9eb00956b04ef095f2ca175f775ff53c710fb0020266adabd1dacaee814c4f1d965299", - "0x967492e78ac0bceb8ad726ea0d2292b760043d16d64a6b1bb896e32630a7bf405c2b20e4e00842ae519a21697ff8db2d", - "0xadbf05e9b5931ae3dd24d105b5c523c221a486a4123c727069b9e295a5bc94f3e647a3c2cde1f9f45dbd89df411453c9", - "0xa1759c0ebebd146ee3be0e5461a642938a8e6d0cdd2253ebd61645b227624c10c711e12615cd1e7ea9de9b83d63d1a25", - "0xa4c5945d635b9efc89ad51f5428862aefe3d868d8fb8661911338a6d9e12b6c4e5c15a25e8cb4a7edc889b9fa2b57592", - "0xaff127675ea6ad99cb51c6e17c055c9f8fd6c40130c195a78afdf4f9f7bc9c21eed56230adb316d681fc5cacc97187da", - "0x9071294e8ff05b246ff4526105742c8bf2d97a7e7913f4541080838ecfd2dbc67c7be664a8521af48dbc417c1b466a85", - "0x990880b0dd576b04f4b4ce6f0c5d9ff4606ec9d3f56743ac2f469ac6a78c33d25c3105cf54f675e300ac68073b61b97a", - "0xa8d1a62ce47a4648988633ed1f22b6dea50a31d11fdddf490c81de08599f6b665e785d9d2a56be05844bd27e6d2e0933", - "0x8ea5a6c06f2096ded450c9538da7d9e402a27d070f43646533c69de8ea7993545673a469c0e59c31520e973de71db1b4", - "0x99d3a098782520612b98a5b1862ae91bcb338ab97d1a75536e44b36a22885f1450a50af05c76da3dd5ca3c718e69fdd4", - "0xb987451526e0389b5fe94c8be92f4e792405745b0a76acd6f777053d0809868657ba630aa5945f4bd7ce51319f8996f7", - "0xafffccc5ddd41313888a4f9fee189f3d20d8b2918aa5ad0617009ea6d608e7968063c71bd5e6a1d7557880d9a639328d", - "0x8ac51a02505d5cadfd158dde44932ab33984c420aeceb032ed1ee3a72770d268f9e60ccf80ce8494dfc7434b440daafd", - "0xb6543e50bd9c6f8e0862850c3d89835ddd96231527681d4ab7ae039c4a3a5a0b133a6d40cdb35c8a6c8dbb8d421d3e2b", - "0xa2ba901f4fde2b62274d0c5b4dbbea8f89518571d8f95ec0705b303b91832f7027704790a30f7d9d2cdafde92f241b3e", - "0xa6974b09280591c86998a6854a7d790f2a6fbe544770e062845cfc8f25eb48c58f5dfb1b325b21f049d81998029ad221", - "0x890baeb336bbf6c16a65c839ffaab7b13dd3e55a3e7189f7732dbcb281b2901b6d8ba896650a55caa71f0c2219d9b70e", - "0xb694211e0556aebbe4baf9940326e648c34fda17a34e16aa4cefd0133558c8513ffb3b35e4ee436d9d879e11a44ec193", - "0x97cf9eb2611d467421a3e0bfe5c75382696b15346f781311e4c9192b7bca5eb8eaf24fa16156f91248053d44de8c7c6f", - "0x8247f88605bd576e97128d4115a53ab1f33a730dc646c40d76c172ca2aa8641c511dddad60ee3a6fbe1bb15cac94a36c", - "0xae7ecd1c4a5e9e6b46b67366bc85b540915623a63ab67e401d42ca1d34ae210a0d5487f2eef96d0021ebecfd8d4cd9a8", - "0xaec5123fff0e5d395babe3cb7c3813e2888eb8d9056ad4777097e4309fb9d0928f5c224c00260a006f0e881be6a3bf8f", - "0x8101724fa0ce7c40ea165e81f3c8d52aa55951cc49b4da0696d98c9fafd933e7b6c28119aa33f12928d9f2339a1075d1", - "0xa8360843bab19590e6f20694cdd8c15717a8539616f2c41a3e1690f904b5575adb0849226502a305baefb2ead2024974", - "0xade5cad933e6ed26bba796c9997b057c68821e87645c4079e38e3048ea75d8372758f8819cde85a3ab3ab8e44a7d9742", - "0xab1fe373fb2454174bd2bd1fe15251c6140b4ac07bda1a15e5eabf74b6f9a5b47581ef5f0dbd99fdf4d1c8c56a072af7", - "0xb425e1af8651e2be3891213ff47a4d92df7432b8d8ea045bb6670caf37800a4cd563931a4eb13bff77575cbcae8bc14f", - "0xb274799fe9dd410e7aed7436f0c562010b3da9106dc867405822b1e593f56478645492dbc101a871f1d20acf554c3be6", - "0xb01a62a9d529cc3156bc3e07f70e7a5614b8d005646c0d193c4feb68be0b449d02b8f0000da3404e75dbdfa9ca655186", - "0x878b95e692d938573cdb8c3a5841de0b05e5484a61e36ea14042f4eadb8b54a24038d2f09745455715d7562b38a8e0df", - "0xa89e998e979dba65c5b1a9000ad0fd9bb1b2e1c168970f2744982781306bbe338857e2fac49c8cafda23f7cc7c22f945", - "0x85880fdf30faed6acce9973225e8fe160e680a55fc77a31daacf9df185453ad0c0552eb3fd874698ad8e33c224f7f615", - "0xac28d20d4bbb35ba77366272474f90f0ed1519a0e4d5de737adee2de774ccd5f115949e309e85c5883dbc63daaa6e27b", - "0xa1758ac86db859e323f5231ad82d78acbe11d53d3ebf7e644e581b646eede079d86f90dc23b54e5de55f5b75f7ea7758", - "0xae4c0b84903f89353bf9a462370f0bf22c04628c38bb0caae23d6e2d91699a58bd064e3c2b1cbda7f0a675d129f67930", - "0x95f21a099ffc21a0f9064d9b94ce227b3ff0a8c5a2af06ff5ee6b7f3248a17a8ca2f78cd7929ef1d0784f81eddefcd48", - "0x8d06fbc1b468f12b381fd1e6108c63c0d898ddf123ea4e2e1247af115043c4f90b52796076277b722dd2b92708f80c21", - "0xa300f39039d8b2452e63b272c6d1f6d14a808b2cd646e04476545da65b71a6e29060f879409f6941c84bde9abe3c7d01", - "0xadecce1ccc5373072ba73930e47b17298e16d19dbb512eed88ad58d3046bb7eec9d90b3e6c9ba6b51e9119cf27ce53f2", - "0x941a7e03a64a2885d9e7bee604ddc186f93ff792877a04209bbee2361ab4cb2aed3291f51a39be10900a1a11479282ca", - "0xacbcb1ab19f3add61d4544c5e3c1f6022e5cc20672b5dc28586e0e653819bdae18cda221bb9017dfaa89c217f9394f63", - "0xb8d92cea7766d3562772b0f287df4d2e486657b7ab743ed31ec48fdc15b271c2b41d6264697282b359f5cb4d91200195", - "0x957360ecb5d242f06d13c1b6d4fcd19897fb50a9a27eb1bd4882b400dc3851d0871c0c52716c05c6c6cf3dee3d389002", - "0xabd2a23abbc903fbb00454c44b9fb4a03554a5ef04101b2f66b259101125058346d44d315b903c6d8d678132f30b1393", - "0xae9572beff080dd51d3c132006107a99c4271210af8fbe78beb98d24a40b782537c89308c5a2bddfdfe770f01f482550", - "0x82c7e5a5e723938eb698602dc84d629042c1999938ebd0a55411be894bccfb2c0206ac1644e11fddd7f7ab5ee3de9fdc", - "0xaba22f23c458757dc71adb1ce7ef158f50fdd1917b24d09cfc2fbbcbe430b2d60785ab141cf35ad9f3d0a2b3e2c7f058", - "0x8eff41278e6c512c7552469b74abedf29efa4632f800f1a1058a0b7a9d23da55d21d07fdbb954acb99de3a3e56f12df6", - "0x8abd591e99b7e0169459861a3c2429d1087b4f5c7b3814e8cee12ecc527a14a3bdda3472409f62f49a1eb4b473f92dbf", - "0x82dcbff4c49a9970893afc965f1264fcab9bae65e8fb057f883d4417b09e547924123493501c3d6c23a5160277d22a8e", - "0xb5a919fcb448a8203ad3a271c618e7824a33fd523ed638c9af7cfe2c23e3290e904d2cd217a7f1f7170a5545f7e49264", - "0x96d6834b592ddb9cf999ad314c89c09bedc34545eeda4698507676674b62c06cc9b5256483f4f114cd1ed9aaec2fba5e", - "0xa4e878cf4976eb5ff3b0c8f19b87de0ef10cd8ec06fe3cd0677bd6be80ba052ff721a4b836841bdffb1df79639d0446c", - "0x8e15787a8075fd45ab92503120de67beb6d37c1cc0843c4d3774e1f939ac5ed0a85dad7090d92fa217bd9d831319021b", - "0x8506c7fea5a90cd12b68fdbbae4486a630372e6fd97a96eea83a31863905def661c5cdead3cf8819515afe258dbcd4d9", - "0x952ef3bc16a93714d611072a6d54008b5e1bf138fd92e57f40a6efb1290d6a1ffcc0e55ff7e1a6f5d106702bd06807cd", - "0xa5f7761fa0be1e160470e3e9e6ab4715992587c0a81b028c9e2cf89d6f9531c2f83c31d42b71fca4cc873d85eba74f33", - "0xb4811f0df11ff05bf4c2c108a48eece601109304f48cde358400d4d2fa5c1fdaaf3627f31cb3a1bdd3c98862b221720d", - "0x9207ad280b0832f8687def16ad8686f6ce19beb1ca20c01b40dd49b1313f486f2cb837cfbbf243be64d1c2ab9d497c3f", - "0xb18a8c1e6363fadd881efb638013e980e4edb68c1313f3744e781ce38730e7777f0cba70ea97440318d93a77059d4a2b", - "0x901faf777867995aac092f23c99c61f97eeadf4ac6bcb7791c67fa3c495947baef494b2aace77077c966c5d427abbf92", - "0xa123281aca1c4f98f56cff7ff2ae36862449f234d1723b2f54ebfccd2740d83bd768f9f4008b4771e56c302d7bfc764f", - "0x8cffe1266468cad1075652d0765ff9b89f19b3d385e29b40f5395b5a3ad4b157eed62e94279ac3ec5090a6bad089d8b3", - "0x8d39870719bc4ebbcecba2c54322111b949a6ed22bda28a6cea4b150272e98c9ded48cc58fc5c6e3a6002327856726ec", - "0xb3d482c00301f6e7667aaeaf261150b322164a5a19a2fa3d7e7c7bf77dc12fa74f5b5685228ab8bf0daf4b87d9092447", - "0x801acb8e2204afb513187936d30eb7cab61f3fbb87bfd4cd69d7f3b3ddba8e232b93050616c5a2e6daa0e64cef6d106f", - "0xac11e18adda82d2a65e1363eb21bda612414b20202ecc0e2e80cc95679a9efa73029034b38fd8745ce7f85172a9ab639", - "0xb631d6990d0f975a3394f800f3df1174a850b60111567784f1c4d5bba709739d8af934acfa4efc784b8fc151e3e4e423", - "0xaeda6279b136b043415479a18b3bbff83f50e4207b113e30a9ccfd16bd1756065fc3b97553a97998a66013c6ac28f3d8", - "0x8840b305dc893f1cb7ad9dd288f40774ec29ea7545477573a6f1b23eaee11b20304939797fd4bcab8703567929ce93ad", - "0x963cc84505a28571b705166592bffa4ea5c4eeafe86be90b3e4ae7b699aaaca968a151fe3d1e89709fe0a3f0edf5d61a", - "0x8e1ec0d0e51f89afea325051fc2fa69ab77d6c7363cc762e470a9dfa28d4827de5e50f0b474c407b8c8713bad85c4acd", - "0x909f313420403cb36c11d392cf929a4c20514aa2cb2d9c80565f79029121efd5410ef74e51faba4e9ba6d06fcf9f1bd1", - "0xb2992b45da467e9c327ac4d8815467cf4d47518fc2094870d4355eb941534d102354fbda5ab7f53fbf9defa7e767ca13", - "0x9563b50feb99df160946da0b435ac26f9c8b26f4470c88a62755cdf57faebeefffff41c7bdc6711511b1f33e025f6870", - "0xa2a364d9536cd5537a4add24867deec61e38d3f5eb3490b649f61c72b20205a17545e61403d1fb0d3a6f382c75da1eb3", - "0x89b6d7c56251304b57b1d1a4255cb588bd7a851e33bf9070ee0b1d841d5c35870f359bc0fdc0c69afe4e0a99f3b16ec2", - "0xa8ae1ee0484fe46b13a627741ddcdae6a71c863b78aafe3852b49775a0e44732eaf54d81715b1dca06bb0f51a604b7e2", - "0xb814ecbfbc9645c46fc3d81c7917268e86314162d270aed649171db8c8603f2bd01370f181f77dbcbcc5caf263bedc6c", - "0x8e5d7cc8aad908f3b4e96af00e108754915fecebdb54f0d78d03153d63267b67682e72cd9b427839dca94902d2f3cda7", - "0x8fc5ff6d61dd5b1de8c94053aef5861009cb6781efcca5050172ef9502e727d648838f43df567f2e777b7d3a47c235dd", - "0x8788eea19d09e42b0e3e35eb9bcd14f643751c80c6e69a6ff3a9f1711e8031bbe82ccd854a74a5cfcf25dda663a49a62", - "0x95d441d8cd715596343182ddcecb8566d47eaa2d957d8aea1313bbed9d643a52b954443deb90a8037a7fa51c88eec942", - "0xa15efd36ef72783ccdc6336ef22a68cc46b1ecec0f660cfe8a055952a974342bf30f08cb808214bce69e516ff94c14c5", - "0xacc084d36907a16de09a5299f183391e597beaf9fa27d905f74dc227701a7678a0f5a5d1be83657de45c9270a287ec69", - "0xb3fd385764356346061570beb760ccf3808619618fd7521eb0feadc55b8153ef4986ff0cbfcbd4153ad4ea566989d72a", - "0x91ec6b26725532e8edfda109daa7ce578235f33bd858238dfa2eb6f3cd214115b44cce262a0f2f46727a96b7311d32e1", - "0x96b867ccddb73afe1049bda018c96cfe4083fff5bb499e6a4d9fd1a88a325144f9a08cb0aee310e1bb4f6a5793777e80", - "0xad10c18465910152676f1bc6a40986119607b5c272488e6422cfda2eb31da741af13a50f5de84037348014a869c8e686", - "0x86ade2dbc4cceb52b84afe1c874d1e3644691284c189761febc4804b520adf60b25817e46f3f3c08d2ab227d00b93076", - "0x998b949af82065c709fc8f63113a9fecdd1367fc84fc3b88857d92321ba795e630ce1396a39c2e056b5acd206ee011d8", - "0x8dec440bbd17b47dfd04e566c2d1b46f9133023b982fdc5eaeae51404bc83a593f8d10c30b24e13aec709549137cae47", - "0x89436ff47431b99f037cddaee08bb199be836587a7db6ed740317888638e5f4bebbb86b80549edff89678fc137dfb40a", - "0xa8e9960746769b3f76246c82cd722d46d66625e124d99a1f71a790c01cec842bcf6c23c19cc7011ec972cedf54dc8a4c", - "0x980979dafedfd75ff235b37e09e17361cfdda14a5ac3db0b90ed491abfd551916016b2254538da7f4b86ece3038b1b1c", - "0x8ec340ca7654720bb9d2f209985439ebbc3f9990ef27e7d7ae366e0c45b4ed973316943122119604ea9a87fc41ebd29f", - "0xab24440a40ab238d8cd811edb3ef99948ae0f33bf3d257b22c445204016cce22b6f06a1ca979fa72a36c4ddedc2b3195", - "0xa1bcd2473ac7cfebfa61c10e56cae5422c6b261a4a1be60b763fcbcdf2eae4ccf80695f09b062b6cf5654dfab0ee62a5", - "0x9027a613ce7bd827110a3a0e63e83f652e9bc7f4ce8da26c38b28ee893fd0c38bdb20f63a33470a73cb77f776244ab4a", - "0x86911cc8aeb628197a22bf44d95a0b49afb8332c38857fba8e390c27c527b8b45335e22b0f2e0a3395c16ced3c1ed2e8", - "0x8f0529a330a3e9967dce09357d774715fd305bd9e47b53b8b71a2a1303d390942a835aa02fb865a14cfed4f6f2f33fe6", - "0xb71ec81a64c834e7e6ef75b7f321a308943b4bad55b92f4dbaf46658613cebf7e4b5b1bc7f1cdc5d50d1a2a0690e2766", - "0x98d66aaed9fb92f4c7bb1b488ccbca5e570aa14433028867562a561d84f673ac72e971cbe2cb3cbbb0a702797dc45a7e", - "0x8380aa94d96c6b3efd178de39f92f12ca4edd49fe3fe098b2b7781e7f3e5f81ee71d196fb8e260d1d52f2e300e72e7bc", - "0x8c36296ff907893ac58cecadd957b29f5508ae75c6cc61b15ae147b789e38c0eace67963ae62eff556221b3d64a257a2", - "0x97e17676cbc0f62a93555375e82422ee49bc7cf56ad6c3d69bb1989d1dc043f9f7113d0ed84616dde310441b795db843", - "0xa952229615534c7e9a715409d68e33086cdaddf0aec51f4369c4017a94ec3d7113a045054d695fb9d7fd335527259012", - "0x817b90958246f15cbd73a9679e10192ca7f5325b41af6388b666d8436706dea94eafffbc3b8d53057f67ad726dbcd528", - "0x95776e378c8abd9223c55cd6a2608e42e851c827b6f71ad3d4dc255c400f9eccf4847c43155f2d56af0c881abef4acfa", - "0x8476c254f4b82858ecbe128ed7d4d69a6563fd9c5f7d4defc3c67e0bfa44e41cfd78b8e2a63b0773ce3076e01d3f6a7d", - "0xa64b0b189063d31bcae1d13931e92d5ab0cfc23bf40566ac34b5b8b711d0e7d941102e6beb140547512e1fe2d9342e6c", - "0x9678460acff1f6eae81a14d5c8049cdcd50779a8719b5c5861762a035b07f7fa1b1ada8b6173f9decf051fd5a55bebd8", - "0x88398758ce86ed0388b13413a73062adb8a026d6b044cd1e7f52142758bed397befee46f161f8a99900ae6a2b8f6b89f", - "0xa7dfaf40637c81d8b28358b6135bd7ad9cc59177bd9bc8e42ba54d687d974cdf56be0457638c46b6a18ceaa02d3c53f3", - "0xb0e885e5d48aa8d7af498c5e00b7862ed4be1dad52002f2135d98e8f2e89ca0b36cf95b3218aad71d5b4ada403b7045b", - "0x803b0e69a89e8de138123f8da76f6c3e433402d80d2baba98cde3b775a8eda4168530a49345962c4b25a57257ba9f0a7", - "0x8ce6ef80dadb4b1790167fbc48be10ef24248536834ff2b74887b1716c75cb5480c30aa8439c20474477f1ac69734e61", - "0x824764396e2b1e8dcc9f83827a665ef493faec007276f118b5a1f32526340b117c0df12bea630030a131bf389ec78fc3", - "0x874edb379ce4cc8247d071ef86e6efbd8890ba6fcb41ea7427942c140347ebf93e8cf369d1c91bd5f486eb69b45bce70", - "0xadadcb6eb4cafa1e2a9aef3efb5b09ffa2a5cf3ce21f886d96a136336be680dabc0a7c96ec327d172072f66d6dcdbb39", - "0xb993591b280e1f3527f083d238a8f7cf516d3cf00c3690d384881911c1495192a419b8e37872a565ce8007eb04ebe1b6", - "0xb125faaeca3f0b9af7cb51bb30a7c446adbb9a993b11600c8b533bff43c1278de5cdda8cb46a4df46f2e42adb995bce8", - "0xa7efe1b57326b57c2c01720d4fdf348d6a84d35f229d32a8f2eb5d2be4e561ef8aea4d4d0bcfcbf17da10a8e49835031", - "0xa6bd4f5a87574b90a37b44f778d5c7117d78eb38f3d7874bad15ae141b60eed4ab0a7281ed747297f92e0b3fe5f9cafa", - "0x94b5e3067ca1db3c4e82daf6189d7d00246b0360cb863940840358daa36cb33857fde4c01acd0457a90e15accee7d764", - "0xa5ff3ab12197b8a07dd80222a709271ab3b07beba453aacbaf225cfb055d729e5a17a20f0ff9e08febf307823cba4383", - "0xa76dd8aa2b6a957ed82ecec49b72085394af22843272f19360a5b5f700910c6ec65bf2a832e1d70aa53fd6baa43c24f6", - "0x8dfcbe4143ae63c6515f151e78e6690078a349a69bb1602b79f59dc51dea7d00d808cf3e9a88b3f390f29aaae6e69834", - "0x8c6134b95946a1dd54126952e805aeb682bc634c17fe642d5d3d8deffffd7693c90c4cd7d112890abfd874aa26736a93", - "0x933531875561d327c181a2e89aaaac0b53e7f506d59ef2dfc930c166446565bd3df03bab8f7d0da7c65624949cfbae2f", - "0xac6937c5e2193395e5bb69fd45aa6a9ae76b336ea7b6fd3e6aeac124365edcba7e918ec2c663fb5142df2f3ad03411a6", - "0xa8f0f968f2a61d61d2cf01625e6ac423b447d3e48378ea70d6ff38bc98c42e222fe3cbcb04662b19973a160dc9f868a2", - "0x94100a36f63d5c3a6cfb903c25a228389921684cc84f123390f38f90859f37ec9714942ffe6766f9b615101a3c009e43", - "0xb5321b07f5b1eb2c1c20b0c8ab407f72f9705b55a761ec5176c5bcc6e585a01cae78546c54117ca3428b2b63793f2e65", - "0x9922f61ed6763d1c4d12485c142b8ff02119066b5011c43e78da1ee51f10a1cf514329874061e67b55597ca01a7b92ab", - "0xa212eb2d72af0c45c9ef547d7c34ac5c4f81a4f5ec41459c4abd83d06ec6b09fdab52f801a2209b79612ae797fa4507b", - "0x8577d2d8f17c7d90a90bab477a432602d6918ca3d2af082fbb9e83644b93e21ca0bced7f90f6e9279eaa590f4e41dc4d", - "0x9002d424e3bebd908b95c5e6a47180b7e1d83e507bfb81d6ad7903aa106df4808c55f10aa34d1dccad3fab4d3f7a453e", - "0xb9050299bf9163f6ebeff57c748cb86f587aea153c2e06e334b709a7c48c4cbfba427babf6188786a0387b0c4f50b5ce", - "0x852ae1195cc657c4d4690d4b9a5dea8e0baaa59c8de363ba5fccd9e39ec50c6aa8d2087c8b7589b19248c84608f5d0a8", - "0xa02ff5781417ca0c476d82cf55b35615f9995dc7a482124bc486e29b0b06a215fbe3e79228c04547c143d32cd3bac645", - "0x8d7bc95e34bc914642e514a401448b23cf58bce767bab1277697327eb47c4a99214a78b04c92d2e3f99a654308b96e34", - "0xadb28445d3b1cc7d4e4dd1f8b992a668f6b6f777810465fdab231fd42f06b5bada290ba9ae0472110366fad033da514e", - "0xa0c72b15a609f56ff71da17b5b744d8701af24b99fbc24a88588213864f511bfa592775e9ab4d11959f4c8538dc015b8", - "0x933205a40379d5f5a7fb62cda17873fbbd99a0aaa8773ddf4cd2707966d8f3b93a107ebfe98b2bb222fe0de33ef68d03", - "0x90690c1a4635e2e165773249477fc07bf48b1fd4d27c1b41a8f83a898c8d3763efb289867f8d6b0d354d7f4c3f5c7320", - "0x99858d8c4f1be5a462e17a349b60991cb8ce9990895d6e42ae762ce144abc65b5a6f6e14df6592a4a07a680e0f103b2a", - "0xb354a7da06bd93fb5269e44925295b7c5049467b5cacce68cbb3cab60135b15e2010037a889cb927e6065053af9ccb77", - "0xaf01fc4ac396d9b15a4bbd8cc4fe7b30c32a9f544d39e88cdcb9b20c1c3056f56d92583a9781ddb039ec2eeda31fb653", - "0xa8d889fb7155f7900982cf2a65eb2121eb1cc8525bbee48fae70e5f6275c5b554e923d29ebbd9772b62109ff48fb7c99", - "0xb80edae6e26364c28749fd17c7c10eb96787053c7744a5cc6c44082ae96c5d3a4008c899a284f2747d25b72ecb9cb3d0", - "0xb495b37503d77e7aafc226fca575e974b7bb6af2b7488372b32055feecc465a9f2909729e6114b52a69d8726e08739cb", - "0xa877f18b1144ff22e10a4879539968a01321cecde898894cbe0c34348b5e6faa85e1597105c49653faed631b1e913ec7", - "0x8c235c558a065f64e06b4bb4f876fe549aab73302a25d8c06a60df9fad05843915ac91b507febca6fe78c69b51b597de", - "0xb4c31398b854ccc3847065e79329a3fdae960f200c1cce020234778d9c519a244ff1988c1fbc12eb3da2540a5fa33327", - "0xb7bd134b3460cb05abf5aed0bc3f9d0ccbfac4647324bedbdf5011da18d8b85dc4178dd128f6ddbe9d56ea58f59d0b5d", - "0x92594c786c810cf3b5d24c433c8a947f9277fe6c669e51ceb359f0ae8a2c4e513a6dad1ae71b7ded3cdca823a51e849b", - "0xb178535e043f1efcce10fbec720c05458e459fdda727753e0e412ef0114db957dc9793e58ec2c031008e8fb994145d59", - "0xb31da7189abf3e66042053f0261c248d4da142861bfd76a9aced19559be5284523d3e309ef69843772b05e03741a13fe", - "0xb190a8c1a477e4187fecff2a93033e77e02de20aae93dda1e154598814b78fdf8b9ff574c5f63047d97e736e69621462", - "0x98234bd1d079c52f404bf5e7f68b349a948ec1f770c999c3c98888a55d370982bfa976e7e32848a1ebb4c7694acc1740", - "0x99b9eeb33a6fb104bba5571a3822ebe612bf4b07d720d46bde17f0db0b8e8b52165f9b569be9356a302614e43df3e087", - "0xa1e3915b0dd90625b424303860d78e243dda73eecd01cba7c33100b30471d0a1ec378c29da0f5a297008b115be366160", - "0x975118bf6ca718671335a427b6f2946ee7ece2d09ccfb1df08aa1e98ff8863b6c8b174c608b6b2f4b1176fb3cbc1e30d", - "0x903cb1e469694b99360a5850e2ca4201cad23cfccce15de9441e9065eb3e6e87f51cba774ab9015852abd51194c25e57", - "0x821f7ff4d0b133e3be4e91d7ff241fa46c649ff61fc25a9fdcf23d685fe74cf6fade5729763f206876764a3d1a8e9b24", - "0xa1ee8db859439c17e737b4b789023d8b3ce15f3294ec39684f019e1ea94b234ec8a5402bc6e910c2ed1cd22ff3add4de", - "0xaf27383148757bdf6631c0ea8a5c382f65fc6ab09f3d342a808ca7e18401e437cd1df3b4383190fdf437a3b35cbcc069", - "0x8310551d240750cef8232cd935869bad092b81add09e2e638e41aa8a50042ce25742120b25fb54ebece0b9f9bdb3f255", - "0x8b1954e0761a6397e8da47dc07133434ebe2f32c1c80cd1f7f941f9965acdf3d0c0b1eb57f7ff45a55697d8b804e1d03", - "0x8c11612381c6be93df17851d9f516395a14a13c7816c8556d9510472b858184bf3cc5b9d14ded8d72e8fb4729f0b23ba", - "0xb413ac49121c7e8731e536b59d5f40d73a200c4e8300f8b9f2b01df95a3dc5fe85404027fc79b0e52946e8679b3a8e43", - "0x8451e5c1c83df9b590ec53d1f1717d44229ed0f0b6e7011d01ea355d8b351f572866b88032030af372bd9104124df55a", - "0x8d0a5c848ec43299bc3ea106847ed418876bc3cd09b2280c2a9b798c469661505ed147a8f4ffba33af0e1167fdb17508", - "0xa6aa97a1f10709582471000b54ec046925a6ad72f2b37c4435621c9f48026d3e332b8e205b6518f11b90b476405960a9", - "0x97696635b5a2a6c51de823eea97d529f6c94846abb0bd4c322b108825589eba9af97762484efaac04ee4847fb2fb7439", - "0x92fd142181fe6ca8d648736866fed8bc3a158af2a305084442155ba8ce85fa1dfb31af7610c1c52a1d38686ac1306b70", - "0xae3da824ecc863b5229a1a683145be51dd5b81c042b3910a5409ca5009ba63330e4983020271aa4a1304b63b2a2df69e", - "0xaecc0fe31432c577c3592110c2f4058c7681c1d15cd8ed8ffb137da4de53188a5f34ca3593160936119bdcf3502bff7c", - "0x821eac5545e7f345a865a65e54807e66de3b114a31ddeb716f38fe76fdd9d117bee0d870dd37f34b91d4c070a60d81f4", - "0x91a02abb7923f37d9d8aa9e22ded576c558188c5f6093c891c04d98ab9886893f82b25b962e9b87f3bf93d2c37a53cb9", - "0x99a96f5d6c612ee68e840d5f052bf6a90fecfd61891d8a973e64be2e2bdd5de555b1d8bffbd2d3c66621f6e8a5072106", - "0xb1d5ec8f833d8fbb0e320ff03141868d4a8fff09d6a401c22dbefadbb64323e6d65932879291090daf25658844c91f2e", - "0xa06afd66ebc68af507c7cf5ab514947ca7d6ccc89fb2e2e8cb6e5ae0f471473e5fba40bb84d05f2c0f97c87f9a50cb73", - "0x83de3ca182bcf1eac0cc1db6ad9b1c2a1ecd5e394e78add7faa36e039a1b13cb0d1d2639892489df080fbf43e5cef8d5", - "0xadf77fc7b342ff67a2eddaa4be2f04b4e6ceaca8ea89a9fc45cc892fcce8ac3cf8646cfa5aab10ac9d9706ce4c48a636", - "0x8509a430ef8dc9a0abc30ef8f8ccdb349d66d40390fb39f0d3281f3f44acb034625361270162822ef0743d458a82b836", - "0x8350fc09e8617826f708e8154a3280d8753e7dbbcf87e852f9b789fdbeb10bf3fed84fb76edd7b8239a920c449e2f4b7", - "0xa2e7a29da8391a5b2d762bf86cb6ae855cdfad49821175f83f4713dd0c342a0784beba98d4948356985a44d9b8b9d0f7", - "0xa99c50a1a88b8efe540e0f246439db73263648546d199ef0d5bc941524a07d7e02b3ef6e5b08dc9e316b0b4c6966823e", - "0xb34ba55136c341f4ca2927080a07476915b86aa820066230903f1f503afebd79f2acf52a0bc8589b148d3a9a4a99f536", - "0xaf637be5a3e71c172af1f2644d3674e022bc49c393df565ea5b05ce6401a27718c38a9232049dd18cbd5bf4f2ce65b32", - "0xa2972ba7bfa7f40c2e175bb35048a8ef9bc296d5e5a6c4ca7ab3728f4264d64f2d81d29dce518dc86849485ff9703d7d", - "0x8c9db203e8726299adeb331d6f4c235dc3873a8022138d35796fb7098887e95e06dcfad5d766ceaa2c4fb0f8857f37fa", - "0xa82bfbaa9a6379442109e89aad0c0cfc6a27d4a5db5480741a509d549c229cb847b46a974dde9f1398c6b3010530f612", - "0xb2d8ef6e091a76dfc04ab85a24dbe8b5a611c85f0ed529a752c2e4c04500de5b305c539d807184e05f120be2c4a05fc3", - "0x8c6ffc66a87d38cea485d16ee6c63ce79c56b64ae413b7593f99cc9c6d3cd78ef3fa2ab8a7943d2f0e182176642adadb", - "0xacbc92de68b2b04e3dc128109511a1cbe07518042f365d5634e8b651cb1ac435ea48eeeb2b921876239183096ef6edee", - "0x979c4e1165e0ecfa17ed59fb33f70797e000ddbb64acf5fc478cccde940451df051e51b6449c5b11a36afa7868af82e3", - "0xa5a017c5a94952aeae473976027124231abe50460cec4db3ebeb8b1290525776be7c15d108b749c2a1e4b018de827915", - "0x8b6922ab1db925eed24b2586e95f5c709b79d2408a8fa2a71057045ead3ebdd0cc72bee23d9064cd824166eda1e29318", - "0x89a991087a0b5805fcc5c6c5f6ac27e100da0d3713645aa9c90114e68ca9f185f21155eb7645a2c6c0616a47291fe129", - "0xae6ef954c942cbfd37f8f2dc58a649e2584d6777e7eb09ae6992ccde283ac4f4ec39e3a5cda7f7c60f467fb308d37f08", - "0x9335ca5ccac59b39eb2bcef09c54b778ebb690415ba13fe5c8e4b6091d9343a01cc9baa6228cefd8dba98f0710f714da", - "0xa0211c9328be2b46f90ff13614eeffb4c1285e55580db3874610653219926af1d83bda5b089fd37a7c7440a0f1d94984", - "0xa82e097dfa782c40808fac5d8ed1c4fccf6b95ef92e22276fd8d285303fcf18c46d8f752595a658ee5294088b9dc6fc0", - "0xad108fcd0ead65f7f839a1337d520f5bd0cb665ee7100fc3f0563ff1d2959eb01617de8eb7a67c9b98b7b4892082acdb", - "0xb89e6aeabcb3ee3cbf12e3c836bab29e59d49676bcf17a922f861d63141076833f4149fe9e9c3beed24edfacdf1e248b", - "0x8477501bd91211e3b1f66c3bfd399ef785271511bc9366366ce95ec5ea95d9288ab0928a6b7887aba62de4da754d3eaf", - "0xaeec40c04b279096946b743ad8171bf27988405e1321c04894d9a34e2cbd71f444ff0d14da6cda47e93aa6fe9c780d50", - "0xa703bd2d8a5c3521a8aad92afef5162aed64e9e6343d5b0096ca87b5b5d05e28ed31ba235ab1a626943533a57872dd01", - "0xb52d9dfc12c359efb548d7e2b36ddedaefdec0ef78eda8ac49a990b3eb0ed7668690a98d4d3c7bec4748a43df73f0271", - "0xaf887c008bad761ee267b9c1600054c9f17f9fc71acfe0d26d3b9b55536bca5c8aebe403a80aa66a1e3748bb150b20ef", - "0xad2f7a545ef2c2a2978f25cf2402813665c156bab52c9e436d962e54913c85d815f0ba1ce57f61e944f84d9835ce05ea", - "0x91a0a9b3cfd05baf9b7df8e1fb42577ec873f8a46bb69a777a6ac9f702735d6e75e66c9257822c781c47b9f78993a46b", - "0x939fdc380fb527f9a1ddecf9c9460f37e406cd06c59ce988e361404acbfcb6379f2664a078531705dbc0c375d724137b", - "0x8bbbe5d5a0d102b8e0c8a62e7542e13c8c8a6acb88859e78d8e1d01ec0ddff71d429fcb98099e09ff0aa673c8b399dc4", - "0xb67a70e4ef138f48258f7d905af753c962c3cc21b7b8ae8b311a2356c4753f8cd42fdee09ac5ed6de31296ead88c351a", - "0x8d21539e7dca02a271ce7d16431773bbe30e6a03f5aff517132d34cdd215ad0da2f06aa4a2a595be489234b233e0852e", - "0x892ae11513f572cc5dc8b734b716bb38c0876e50e5e942631bb380b754e9114c34b0606740301e29b27d88439fb32071", - "0xa8780dc9faa485f51b6f93a986bc4e15b166986b13d22ec2fefc6b25403b8b81c15cc9ac0025acc09d84932b15afa09b", - "0xb01af013360cd9f2bb9789a2b909c5e010fe6ff179f15997dee1a2ba9ef1ccec19545afdecfcb476f92fcdd482bb2b5a", - "0xb5202e5d5053d3af21375d50ad1ccd92538ef9916d17c60eb55c164767c3c74681886297b6f52e258c98d0304d195d3d", - "0x8f6adbcfbb0734bf3a4609d75cf2e10f74ed855a8b07cf04ac89a73d23b2e3e5cf270a1f2547b3d73e9da033a3c514b0", - "0x8abe529cd31e4cb2bd75fa2a5e45bd92cbe3b281e90ffc7dea01ba0df17c9a3df97a3fde373cce5d25b5814cf1128fed", - "0xb8bbf51187bb3bb124da3870e2dfecb326f25a9383e5cc3323813487457010b9055811669c3da87105050825dc98a743", - "0xa5c83875fe61ebbdd3fd478540d7e5a1ad0f8c790bad0b7dd3a44831e2c376c4fffbc6b988667afa1b67bfaa2dbbb256", - "0xa0606b3062e4beba9031ba2a8e6e90aa5a43ba7321003976e721fd4eedb56486f2c5b10ba7a7f5383272f4022092eacb", - "0xb485cc5e001de6bd1bbc9cd8d777098e426d88275aaa659232f317352e1ddff3478262d06b46a573c45409bc461883e1", - "0x916449580b64a9d8510e2f8c7aee0b467a0e93b11edc3d50725bcbc3ca53c2b8bb231fdc0fc0ed5270bf2df3f64750d9", - "0xb2e687caa9f148c2b20a27a91bada01a88bff47faaf6ed87815db26bb6cdd93672199661654763a6b8b4b2012f59dcca", - "0xb6933f7f9dabc8fb69197571366ac61295160d25881adf2fcc8aaabc9c5ed7cf229a493fd9e2f1c2f84facd1f55fee84", - "0xb01eb8b2cf88c75c3e31807cfc7a4d5cafded88b1974ba0a9d5aaeda95a788030898239e12843eda02873b0cabe30e2b", - "0xa3ca290fa6ce064514a3431b44ecdb390ef500629270202041f23bc2f74038147f338189c497949fb3126bae3a6e3524", - "0x93b0f8d02bd08af74918b1c22131865aa82aba9429dc47f6b51354ba72e33a8b56684b335a44661aa87774931eb85974", - "0x81eebeb9bd92546c37c98e0a5deba012c159f69331a89615cf40c5b95c73dcdbf3ceb46b8620d94ff44fcdad88020c1e", - "0xb350e497932382c453a27bb33d2a9e0dbadf4cd8a858b6b72d1f3a0921afc571371e22b051b97da3bb08694c4ca3a4e8", - "0x8c7052f63ba16f14fa85d885aa857d52f04b3a899a4108493799c90c0410de7549be85bec1f539f1608924668df48e5a", - "0xb397574d1fb43de0faaea67d1d9348d67b712b1adce300d6dc497bca94e0994eef8707c285c5c9ac0a66022655a8420b", - "0xa934661d2168ae1bd95b1143c2e5c19261708aeb795abad8ec87f23dc1b352fa436de997ebb4903d97cb875adb40dc2b", - "0xacf535fa1b77255210e1b8975e0e195624c9e9ffd150286ccd531a276cadc12047a4ded6362977891e145a2bd765e6b9", - "0x8cc32356015d7fd29738dcc13c8008cdbe487755dd87d449ab569c85d0556a1ec520dbce6c3698fc413d470c93cb0c92", - "0x8787c7b3b890e0d3734ac1c196588cacf0a3bde65e2cf42e961e23dbf784eef14c07337d3300ed430f518b03037bd558", - "0x99da90994030cbc2fb8a057350765acac66129a62514bbd3f4ec29d5aab8acdd5f4d69ca83efe7f62b96b36116181e79", - "0xa306424f71e8b58dfa0a0564b2b249f0d02c795c30eee5b0ad276db60423210bba33380fb45dbe2c7fedd6ee83794819", - "0xb207a35d31ce966282348792d53d354bbd29ac1f496f16f3d916e9adbf321dc8a14112ca44965eb67370a42f64ca1850", - "0x89e62e208147a7f57e72290eefccb9d681baa505d615ca33325dfa7b91919214646ca9bdc7749d89c9a2ce78c1b55936", - "0xac2d0ec2b26552335c6c30f56925baa7f68886a0917e41cfbc6358a7c82c1cb1b536246f59638fb2de84b9e66d2e57eb", - "0x8f1487659ecc3b383cebc23a1dc417e5e1808e5c8ae77c7c9d86d5ab705e8041ce5a906a700d1e06921f899f9f0ee615", - "0xa58f1d414f662f4b78b86cae7b0e85dfddae33c15431af47352b6e7168a96c1d307d8b93f9888871fc859f3ed61c6efc", - "0x94f3626a225ac8e38a592b9c894e3b9168f9cf7116d5e43e570368ee6ee4ab76e725a59029006a9b12d5c19ddce8f811", - "0xb5986e2601ad9b3260e691c34f78e1a015c3286fdd55101dcef7921f6cbcc910c79025d5b2b336d2b2f6fd86ee4e041e", - "0xb6e6798ddd0255fbe5cb04a551a32d4c5d21bdfd8444ff2c879afe722af8878d0a3a2fe92d63936f1f63fea2d213febf", - "0x86bea9bfffef8bc11758f93928c9fdfae916703b110c61fa7d8fe65653f8c62c6fecd4ff66a1f1a7f3c5e349492e334c", - "0x9595a4606284569f4b41d88111320840159fd3b446e00ec8afd7ddaa53dd5268db523f011074a092f8e931fc301a8081", - "0x83b540a6bc119bf604a7db5f6c0665c33b41c365c12c72ca4fa7b0724115bbb0ff1ae38532c3356e8bb3ac551285929f", - "0x92c6daf961ca4eb25293e1794cf85cda4333cf1c128207af8a434e7e0b45d365f0f5baaefc4ebd5cd9720c245139c6e2", - "0xb71465f3d7dba67990afc321384a8bb17f6d59243098dbed5abd9a6ffc7a3133b301dd0c6ca3843abbaa51d0953abbed", - "0xb15d93482d2ee5b1fec7921fcc5e218c1f4a9105a554220a4fb1895c7b1d7a41f90bbf8463d195eecf919fcbe8738c51", - "0xa79c98e70931ffd64f4dcf7157fbae601a358261e280fe607eb70cef7d87f03efa44cf6ba0f17fbb283a9c8a437d2fdb", - "0x9019d51a6873331f8fe04cb45e728a0c8724a93d904522a9915c748360ddf5cdbf426a47b24abf2005295ed2a676cbf0", - "0xb34cc339fec9a903a0c92ce265e64626029497762ff4dcaaf9bb3994298400ce80f4fb7dbe9ec55fe0c4a522c495cb69", - "0x8fda9be7abfe3b2033cad31661432300e2905aef45a6f9a884e97729224887a6ec13368075df88bd75c11d05247bef15", - "0x9417d120e70d6d5ca4b9369cba255805b5083c84d62dc8afec1a716ead1f874c71a98ad102dac4224467178fe3228f62", - "0xa0a06b64867eebb70d3ce8aaa62908a767fb55438a0af3edf9a8249cd115879cde9f7425778b66bb6778cb0afeb44512", - "0xa44309d3e1624b62754a3a4de28b4421f1969870f005ac5dc7e15183fa5b3ad182bcd09cca44924e03fbdb22f92f8cf8", - "0xaea80f1c3a8fc36cfb5c9357d59470915370b2bec05f51f1d0e1d4437657e2303ba2d1ac3f64cf88f2df412dff158160", - "0xb3f1557883d91b24485123d2f3ae0fce65caa533c09345ae6b30d2ac49953acee61c880c57975be7b4f5558d3a081305", - "0xb52cb1e56f0d147cfb58528b29c7a40bab7cfc9365f2409df7299bfc92614269ff9de3cb2500bbc4909f6a56cf4b9984", - "0xaa4f8fd0f5f87c177ee7242f7da76d352db161846cd31523a2100c069d9e4464170eec0bffc6d4da4f9e87017b415dbd", - "0xb5b61f52242985c718461a34504f82495d73cbb4bc51f9554b7fe9799491f26826d773656225f52a1531cd5bd6103cde", - "0xad12ba9697804ede96001181c048f95b24ba60761c93fb41f4b4a27e0f361e6b1434e9b61391bacaf0705fdaa4a3a90e", - "0x9319286cbda236f19192ae9eb8177e5a57a195c261082ba1385b20328fb83ed438f29d263dddae2f5278c09548830c4a", - "0x88b01ee88c3a7ae2c9f80317dddbaa2b7b0c3a3c23828f03ff196e244500410c9ac81c2e2d3e1f609d4b36ee1732738c", - "0x8e31f30600a9d629488d44a008c821c3c57f13734eaee5a19f0182a2de9e538fff7d982980d7fcc725c969f29f7c2572", - "0xb215740eea98b4bb14197a803a8975700ad2f25a25ef3628eae10166d56c823301f6dd62ce3f9ebf2d42d1f33d535004", - "0x8fb0fdb253d4bcc6693642779be13a5b816189532763dfd7da868cfacfdb87cb5ebe53b18b69dfd721f8d4baf3c1d22d", - "0x8cdd050a447f431ff792156d10381aaf83c6634a94b614dd5b428274538a9cc1f830073533b4fd0a734d6dd4f8d9c4ce", - "0x81b01ee8c72ac668ad9dd19ead2d69cac28c3525e613e036e87aa455c2da9651cc8fcc97c451a8c8a071a4eb69623cd1", - "0x8d9e02dc9ac83f861b3745bd69216232144c47cb468a7dbc49083ed961f978e34265b3f42c400339120bdc4644fe5711", - "0x89e9410455b34cba9db0a5ea738e150fae54dd000d61e614f3274a6c8102ba7cd05b0936f484a85711ad9da7946f51ea", - "0x91f9d4949678f8e6f4b8499899818bdd0f510da552b5d79d8e09bf3b69d706ab36524b5e86d3251318899b9223debf6b", - "0x8b3c38eec7e1926a4be5e6863038c2d38ab41057bcfa20f2b494e9a0c13bc74c3a44c653402eb62a98e934928d0ebccb", - "0xa5cfe465bfbf6e8bfbd19d5e2da2fc434bd71acd651371087450c041aa55e3c4f822361e113c6c3d58646ed3ba89d6da", - "0x918665b8810bcb8d573ca88b02a02c62eaa5a4a689efb5c564b0c9183f78144e75d91fd1603e17d2c77586cbe5932954", - "0x997dace0b739aeb52ba786faae5bdf1d48630a90321f9ceebfa9e86d189a3d79d7b04e459ac8e4adcfe83a5ce964eb1c", - "0xa5a1ca9f0ccc88017a616d481d912aab3f0e154b673f1131c5d9c9c3f5f147d25b6392b2c31e49f7bb7eb2697d05dbec", - "0xa76e99bec509eff01bf6767a06ac97ebc6671cb58bc3d4acc2803580a874885453dbba2e1bba26e45f8d2bda5f688860", - "0x956c1362c8123c5d9ebff7049e851235d69fa645f211ef98e2b6564f2871114a12224e0ec676738d77d23c709dd28a6c", - "0x885efede83b1a3e96417e9f2858ab0c7a576fc420e8f1f26cabf3b1abeec36bcaa63e535da177847f5e0afdb211bf347", - "0xaffca2257f292a2db52f8b1bab350093f16f27ef17e724728eeaec324e2513cd576f6d2e003cc1c6e881334cb2e8bf22", - "0x8dac963d34dcc9d479207a586715e938c232612107bb2d0af534d8da57ad678555d7c1887fadca6551c4f736ffa61739", - "0xb55e600a6bbde81f5a0384f17679d3facb93a7c62ca50c81a1d520cf6e8008ac0160e9763cb2ca6f2e65d93ca458783b", - "0x9485e6c5ab2ebfb51498017e3823547b6ab297d818521ceac85cd6c3aa2d85ae075a0a264ae748fc76ce96a601462ffa", - "0xb4d8abca786c0db304a6634fba9b2a40d055c737ed0f933e1739354befdae138dae3c8620a44138f50ebeaf13b91929f", - "0x8bde7ca39c7bda95b1677a206b16c3a752db76869ea23c4b445c2ff320f2ee01f7358d67a514982ee3d1fb92b7bd7229", - "0x8f8cd0acc689b6403ee401383e36cae5db2ff36fc2311bbadf8ebb6c31cbcc2ca4ffac4c049da5ba387761ef5ec93b02", - "0xa06f42d5f69a566ff959139c707355bbf7aa033c08d853dce43f74a9933e6d7b90e72010ef3fcb3d12e25852343d1d31", - "0xb10ece7cf6b69a76dba453b41049db0cdf13d116cf09c625312b150ee7437abd71d921eda872403d7d7ce7af1e6dccb7", - "0xa3d820318e0f3b54fba7a4567912a82d6e6adf22b67cfc39784683a8e75f77538e793d9708aae228fa48a71abb596195", - "0x8758fad55b68a260bea3bd113e078fd58d64a92f7935ff877f9f77d8adc0994b27040cfc850126c7777cfdfb2428a3e5", - "0xb504913ee96c10f00b848cd417c555a24bc549bf5c7306140eff0af2ada8cb5e76bed1adb188e494332b210fbf24e781", - "0xa00e019a40acc7aab84c1cc27c69920ad7205c2a3dc9e908a7ef59383695c9cb7093c4bcbc2945aab2655119552e3810", - "0xb1000b4c4f306672e39d634e5e2026886a99930d81b8670a5d4046db9621e44997c4b78f583374a09c60995f18a6fd4f", - "0xa6c5053c4e748540ad2b622c28896c9d4ca3978ca4784ac8f09da5314a245f5cdc5d6203c84e6e0bcb3081829720a56d", - "0x8e37e67a70205a5c7da95de94ac4d0ebd287c1c9922d60c18eec1705030dfcbf74ae179e377c008bf5a8bc29c7c07cce", - "0xa66bd7c0243319b553d5cb7013f17e3504216e8b51ba4f0947b008c53bcb6b4979286b614a4a828ee40d58b5ef83e527", - "0x97e2110b0fb485508a2d82ecc2ce1fbe9e12e188f06c7ef2ac81caeeb3aca2c00e5e6c031243b5ca870a9692e1c4e69b", - "0x8734ce8bbc862e12bea5f18d8a8d941d7b16a56ef714792fed912ca9c087497e69b6481fdf14efe1f9d1af0a77dac9b1", - "0xb441dddac94a6a6ae967e0e8d7ab9a52eb9525fb7039e42665e33d697e9a39c7dcef19c28932fb3736e5651d56944756", - "0x918b8997f2d99a3a6150d738daed2ff9eb1f5ed4a1c432d18eab4a898297f7ffbffd1e4ae9037acf589b1cd9e1185ef6", - "0xa0247b8ac4d708cf6b398dc2d5c127a291d98e8bef5f195f820c4fddb490574ba4f62647c2d725237a3e4856eec73af0", - "0xb45636e7e0a823c2a32e8529bb06fcccfd88e9964f61201ee116279223ed77458811d1b23bcb6b70508d16d4570a7afb", - "0xa99c1188fa22b30b04fda180d2733586ea6ef414618f1f766d240c71f66b453900d3645541c019361027aebe0a0f305f", - "0xb4c2f758e27fe233f7e590e8e0c6de88441164da3fcd5211a228318d3066dfdafc1d40246dd194f2b597f6fe9600b3d7", - "0x972530819445b11374c3043d7855d5f1d3c4922b3b205d0bf40162c51605375dd0b61f49cd7f3d39a533a86a13005989", - "0x992b533a13e5d790259bfdfdf1074f84a5e5a0a0d7be9cd6568cdc1662524f1a6666a46da36cea3792ba6707850f4d86", - "0x9875d130457e04dc6ea2607309bfbb900ad3cb5f3e0574f808d27b20cbf6f88389d87dca19998680c5bc30d1df30a41b", - "0xadea8494a69e83221edf360ab847272b5c47eba5404665fb743d98c0682732c30085ae3ec82bc1e8e4aba8454c9b1849", - "0x887d4c624ce05e224216c5f6fa13c5741012ac33330bc291754782f0bfe668decdc98c0e43a1ce28323effe6b639f477", - "0xab6b167aeb5e93ab155990b94895e7e7ff6dea91384854a42cc8a3b9983495b4b3c33ab1b60b2b6450ccf0418fada158", - "0xa7588d0b7c6a6bc32fc474aa0f4e51dfb8e6e010346ad32c59d6f99e6f0522424111a03a4f56ba4075da8009ee7a63e9", - "0x94d645cc3936db1563568193639badfc064dd5bda8d0631804ee00b09e141b200619e07506b5a8225130541436327194", - "0x8d695c03cc51530bdc01ee8afcd424e1460d2c009e1d7765c335368e5c563cf01a2373c32a36400c10e2bf23c185ed19", - "0xad824a0a7ed5528e1f9992cbb2050785e092b1ea73edd7fb92b174849794a5b04059e276f2941e945bc0f3e46172f2af", - "0xad6ed2af077a495d84f8eeed7d340b75c0d1c8b7c5a854dfc63ab40a3d0c2b0d45016d30b3373a13f0caae549f657976", - "0x82454126c666023c5028599a24be76d8776d49951dfe403ebf9a5739b8eb2480c6934a34010d32cd384c91c62a9aa251", - "0xb57070006793eca9fa2f5237453ed853994ad22c21deb9b835e1fb3fbc5ac73aec265a4a08de7afae1610dc8c42b7745", - "0xad94667c791cf58875eb77eb17b6ad02de44e4ba2ddc2efe4d0ff22a5e1a090c670354437847349fd61edc4ba5606f07", - "0xb2aac0c345ffc00badaab331c12a22019617b004d32c099c78fa406d683744d96d51d1237ad0842f9f54655186f8f95b", - "0x8fed51076cc939b354e3b69034a594e6c9c98425ccf546154ab087a195375128444732388d2eb28f82877de971ec2f58", - "0x8e521c0093deb9dff37888893db8ffebc139984e7701e68b94d053c544c1be0d85f0f98d84b2657933647b17e10a474c", - "0xa2c6c9a307aff9b1dea85f90fa9e3b8057fd854835055edeb73842a7ef7c5ae63d97c51fec19dd8f15d696a18a0424a6", - "0xa3390b25a9c11344ed1e8a0de44c848313026067a0f289481673c2c0e7883a8fc9f6cab6ccd9129729a6d8d0a2498dc2", - "0x82770c42b1c67bbd8698c7fe84dd38cc5f2ad69a898097a33b5d7c5638928eb1520df2cb29853d1fa86a0f1bcc1187e8", - "0xa6fdf7a4af67bc4708b1d589135df81607332a410741f6e1cc87b92362a4d7a1a791b191e145be915aa2d8531ee7a150", - "0xaecac69574188afc5b6394f48ba39607fe5bb2aa1bd606bc0848128a3630d7d27101eb2cea1fb3e6f9380353a1bb2acc", - "0xa23fd0c52c95d0dffb7c17ec45b79bf48ed3f760a3a035626f00b6fe151af2e8b83561d0b9f042eaae99fde4cbd0788d", - "0xa5f98068525cdd9b9af60e0353beb3ac5ac61e6d3bac1322e55c94b3d29909d414f7f3a3f897d5ae61f86226219215c6", - "0xb2a4d724faac0adf0637c303ff493a1d269b2cdbec5f514c027d2d81af0d740de04fb40c07344e224908f81f5e303c61", - "0xadeadb3521e1f32ef7def50512854b5d99552e540ec0a58ea8e601008de377538c44e593e99060af76f6126d40477641", - "0xa18b7fc2fcd78404fed664272e0fef08766a3e2bc2a46301451df158bd6c1c8aa8cf674dd4d5b3dedfaceb9dd8a68ae3", - "0x83bcfb49313d6db08b58c6827486224115ceef01ca96c620e105f06954298e301399cdd657a5ff6df0b0c696feec1a08", - "0x8c94391eba496e53428ec76dfe5fa38f773c55c0f34a567823316522a0664a3d92bff38ec21cf62ac061d7d1030650c5", - "0xb1fa196ccfd7d5f1535b2e1c002b5cde01165c444757c606b9848bc5f11b7960973038fb7cc3da24300fc1848e34c9af", - "0xb139f6c6449449638de220c9d294e53fc09865a171756d63bbf28ec7916bf554f587c24bddf51dd44372d15260d8fe25", - "0xb716242299d4ee72b5b218781b38ca5e005dcf52333364f85130615d1dbf56216af8ee2c9c652d82f7aab5345356538c", - "0x9909f24e4ad561aa31afd3a3b9456b2bd13a1d2e21e809a66af62fec5f95b504507ac50e81d2233da2b223f5443e7585", - "0xae863530a02cf3a757f72b945c8c0725d9f634d2ff26233478d1883595ff9a1eef69e8babffdbfa161452fc204f5b5a1", - "0x8eb82bde283b6a6e692b30236cbf41433b03eda8dad121282772edd56f144b1ebf5fb489d18c6ce8776135771cbb91e2", - "0x9296141fadf8dadc885fff4999c36efa25ec76c5637a8300a1a7dc9cf55bcedfe159e0ef33f90eee9be8c4f085734e10", - "0xb6c07f2e6fcbd6c42a8b51e52fbcd5df3aa9f7c3f0b3c31021de1aec2111d0a1c36b5ab489ba126af44fd43cf31c2594", - "0xa70ca669c357535b363d16b240fd9cb9c5ba1b648510afc21218ea034e9bf5f22717ae31ff43ef89dded95b7132fa58f", - "0xb350721f8f6b4d164fd08aca30cd4dece9b4a81aed0ac12119c9399bab691d5945814306f9a61f0106b76d4d96f7b9d6", - "0xb6886076c9d8c344bf3fb6975173d00fa82866012894f31c17e6fc784fbc0dd2d24d6a1cddd17f7379c74566a23219aa", - "0x87636e4a83ceadc170a4b2517b19525c98e2163900401996b7a995b2f3da8d6ba2ab92f909eade65074fac07cf42f6fa", - "0x8ff61d87c4699a067a54b8540e8642f4c7be09d3783ec18318bcba903c6714fcd61be69165e07e1ca561fe98e07507de", - "0x85485d6b569ac20e6b81a9e97ef724e038f4fee482f0c294c755c7b6dad91293814f143bfcfc157f6cfa50b77b677f37", - "0xa49256cb1970cc1011a7aed489128f9b6981f228c68d53b1214d28fbcfb921386cc7cf5059027e667a18073efa525a74", - "0x87bc710444b0c3e6682d19307bedc99c22952af76e2d851465ee4f60e5e1146a69f9e0f0314f38a18342e04ece8e3ed3", - "0xa671a6cabfd19121a421fdfe7732eccbb5105dfb68e8cbcf2b44ae8465c99e78c31b99730beca5bc47db6fc2f167203a", - "0xa2f3270c184629f6dfc5bf4bdd6e1b8a41e8840a1e4b152253c35c3d9e7ab4b8e3516dc999c31f567e246243e4a92141", - "0xb9795a5a44f3f68a2460be69ecacdbb4664991ebbedffed5c95952147ad739e2874c099029412b9653d980a2d4307462", - "0x959053faec9a966dd5a4a767a3154e4b8e4f56ca540ae53e373c565dda99fb626f725e5a5e3721c82918f8c5f2e9e0a3", - "0xb3ef9d6a1b3cd44a3e5112819fa91cb8a7becc3f5b164c6f759f93171d568497b01c8e743f4727b341a1296a0dbadf4f", - "0xb852dfdfbe2b8c77d938fad45f00737e14eacf71d5fecbb3e4f60052ec9efb502c38c1fcecaf71da69eabe8b33852a67", - "0x921c7007f26bdd4139e919dfe27d87b489a0bc5bd6fb341e949e4451f14c74add0489b108c9c9666a54c5455ac914a9f", - "0x86b63d73ba31c02e5337f4138e1684eccdc45ab5e4f30e952fb37d638b54ecec11010414d7a4b7aa91f7cc658f638845", - "0x853c55e0720b66708a648933407795571fc11ad5c234e97f92faabce9e592983dfb97a1705047ee803648ecf9fbb2e5c", - "0x995fe7d1dc09bb0c3c3f9557c4146534778f5ea9c1d731c57440fdcf8094f82debf19090b5d23298da1ed71c283b3ae5", - "0xb9c49c911a0c4d716b7baec130f9e615bfa7d504aa8766ed38878a93c22b1f6353503d4f7f425d4902239fb4689429df", - "0x80504d964246789a09dcd5c0298680afb6fe50bca3bb9c43d088f044df2424a1828de10e0dbdc5c0aac114fa6d9cf5d1", - "0x90249351f109f6b23a49a610aaa3b2032189fd50e5e87cdc3b20f23ed4998af3a8b292bf9fbab9bd1cbe0a1371081878", - "0xabb5f0148850f0d80b429c2b9e0038772432340ef0862ccb5dcb7347026ca95bf9a5857f538e295aebd3a6a5027adb4c", - "0xb92ac9c0f7e73150798348265e5f01f3c752480c72613c6894a95e9330bba1c642b21b9cbd8988442b5975476634b4fa", - "0xaf3fbcc825abd92c6d7ea259467f27045e288f27a505e6a3c9ec864aa08fcaca0d4123034513dbd4c82d4814075708ab", - "0xa738232a66030e0e9c78e093a92fcc545b10e62fb0ecb832bbbc71471b28eb6ec422a498c2402e2c6d74983df801e947", - "0xae60194ce2035edd1af253b9eefbb4b1b7609c9678256c89c3cb076c332a9f4442c3441ad2ecc9d73265359bdadc926c", - "0x8b2fd55e686f16725fc0addb4065f696275852320b03221fd22889825d66fae5bb986b03c47452e32b3a32c1fdfc8dfd", - "0x8e2e1a36673b7729b07e7bc5014584e1c03e9552f7440fbfda0a6a7f41953947fcdf8d666f843bfc03dcca5b06a14318", - "0x95a3df04368c069f3fd32a20b627c5f043e952167c9e80bf5914bbf2086879909c60e089bbd488725ab977c0e6051728", - "0x9856403b2211d0152d4eec10db7ec34c16ac35170714b75af3ebc398a676c171b24b6f370361de0f9057ba444293db14", - "0xa2cb484b758af5fd8e2baca7f0406f849c71255e58ef110d685cd0c1137893a25d85a4d8582e3ced7dd14287faa95476", - "0xb0f697b6a42f37916b90ab91994ae4a92c96dc71e4da527af41b9d510bc2db5a9b4f29183a758074b6437a1e62b2d1d7", - "0xb39c49266aae46f257b7ae57322972fb1483125298f9f04c30910a70fe5629dba0ec86b94cc6ba16df3537a55e06f189", - "0x86cd5595b5b769dfd9ceb68b11b451f6c5b2e7a9f6f6958eac8037db1c616e8a9defb68a0d6c2287494d1f18076072c1", - "0xb462e8fa9a372d4c1888fd20708c3bed1cb00c17f7d91a0481238d6584fbbf2d238e25931154f78a17296a12825d7053", - "0xa5ef28286628ba509bac34c9f13158d0013239fdca96b5165161f90b89d6e46295822ebdf63f22d7739911363a0e0e86", - "0xa629a95a24e2545862b41a97ecba61b1efa792fd5555dc0599c175947e9501bffc82b05a605fd5aabc06969ccf14fff4", - "0xaf83467e4b1f23a641630cc00c38d4225ff2b4277612b204d88de12a07d9de52fb4d54a2375a7fd91eb768623c255376", - "0xa630f29fb2e9a9e2096d7f3b2f6814ee046ebc515f6911d4bc54ad8a5a821a41511ff9dcfbe3176f35c444338ecd0288", - "0x950dedc11bd29e01ba9744bec681ad9462127c35e9fcadfacc9405ec86b985a1b1c4f9ac374c0f1fa248212e5e170503", - "0x82e8e7be8011ee0fd9c682d26a0ef992d0191e621d07fd46a3a5640ef93a42e1b98a33cad1f8017341a671d28caebb03", - "0xa075860554e712398dac2fb0375067a48d0e4ca655195cefc5ccb1feb8900d77124aa52a12e4f54f7dab2a8f1c905b5b", - "0x81d2183d868f08714046128df0525653a2dc2ff9e2c3b17900139c9e315b9f4f796e0fb9d1d8cbadbaa439931c0e0879", - "0x81fb1456969579515a75fb66560f873302088cde2edc67659b99a29172165482ca1f563758c750f00086b362ae405322", - "0xa13c15ab19203c89208c6af48d2734bb0873b70edb660d1d5953141f44db9012528d48fb05aa91d16638cbda2ca8f0cc", - "0x8ba46eef93e4ec8d7818124a0b9fcfe2bcf84a98db3545d2b3d0192cfadc81fc667dcc22ab833c3e71508d0f3c621fe4", - "0xb9bd60d2266a7d01e1665631a6ed6d80ffc0cd7f088f115a5d4ea785c518a8f97d955e2115b13c4960302b9825526c92", - "0xb26fa4e87142150250876083a70c229249099331410f0e09096077fdf97b31b88dc57a3e3568d2a66a39af161cf5dfec", - "0xb9d147564124728b813d8660ba15fa030c924f0e381ad51d4e0cf11cc92537c512499d3c2983dd15f2e24ca166070d70", - "0xb6fb44e1a111efb3890306fa911fafda88324335da07f7de729b2239921ef15b481630a89c80e228bec7ab6444a0b719", - "0xa6cd9c7acac052909ef0cf848b6012375486b59b7bac55b42c41f0255b332c1d45a801f6212d735be8341053bd5070b9", - "0x864258d69234786af5de874c02856fc64df51eff16d43bfb351b410402ab28f66895aec4025e370a4864f19ff30fd683", - "0x84370fa1243b64b3669dd62e1e041ff9bd62810752603486aac3cba69978bd5f525c93cbc5f120d6f2af24db31ec3638", - "0xb983c2cdc1a310446de71a7380b916f9866d16837855b7d4a3a6c56c54dab3e373a6fc6563b8309dc3b984d4e09275d6", - "0x914f8587f876470f7812fa11c6f67e2dd38bf3090e8928e91fe2fe5595bee96cbe5f93d26fdced6b4e7b94f75662b35d", - "0x8b47bcb111d91aa3d80e4ceef283824aa00d1faeb6fe4111aecd9819869c0e1f6f4b6fb2018aebb07a0f997412cda031", - "0x95b2befa98f9992450ca7ff715ae4da8c36dd8adcfef3f0097de6e3a0b68674b05cbf98734f9665051bb4562692641e0", - "0x8bcd1651a2bfce390873a958e5ff9ca62aac5edd1b2fd0f414d6bcf2f4cf5fa828e9004a9d0629621b5e80fbbd5edb90", - "0xaf79bed3c4d63239ac050e4fa1516c8ad990e2f3d5cb0930fc9d3ce36c81c1426e6b9fe26ac6a416d148bf5025d29f8b", - "0x881257e86b7ab5af385c567fde5badf67a8e7fff9b7521931b3ce3bac60485c0fe7497339194fb7d40e1fad727c5c558", - "0xa1b40b63482cd5109990dfb5a1f1084b114696cbbf444bf3b4200ab78c51dad62c84731879ea9d5d8d1220e297d6e78a", - "0xb472212baa2a31480791828ca5538c3dcc92e23f561b0412f8cc9e58839d1625ddcaf09c8078d31ac93470436843cd74", - "0x8f516d252b1863cd3608d852a2857052bb2a3570066d4332fa61cb684b10ac8d1a31c8d32f2a0d1c77eee2ad7a49643d", - "0x8d20b75c51daa56117eda2fd5d7a80a62226074b6a3ff201519f2054eecfeff0aa2b2f34b63bea3f53d7d0ce5c036db9", - "0x8282f433229e7948a286ba7f4a25deb0e0a3c5da8870562c3646757bef90ca1e8d3390b0a25b3f2bf45bf259a4569b77", - "0x8a2dbf4b55cc74f0a085d143a88ebc8c2a75a08eab2703d13a00b747eaddc259a3dd57f7330be938131835a6da9a6a68", - "0xaa0bc51617a938ea6a7b0570e98b8a80862dd9e1cf87e572b51b2a973e027bcd444ef08e0d7b5dee642e0da894435e91", - "0xaa7319ca1ac4fe3cc7835e255419eeb7d5b2d9680769cc0ca11283e6147295db75713b71a9312418a8f5505cd45b783d", - "0xab3f9c465663dc90fae327a2ee9cb7b55361a9b6fbe713540a7edd3cff1c716802fb8ad4dd8fb0c945d96b3b44c5795b", - "0x913a2ae88acffab12541fc08920ee13ab949f985a117efe9a5b2c76f69f327f60c5b5ad3fa5afa748034ac14298fc45a", - "0x9008f044183d2237b723b235953e4d8b47bec6a7b300d98075555478da173b599ba9c7c547c2f111ce1fae5ac646e7a3", - "0xa26b4cc42b353e1c18222d2e088d7f705c36be12e01179db440f10fcfa9691d31fc4fc7e7ee47876f1624e6d44be1021", - "0x995e75824f322294336bfa2c5d1a319f0d77f6a0709beabaf1b43015d8a78d62447eab907349524734170f0294d1ca7a", - "0x8b96f04a19dbe4edc71d1f2c6d3475ae77962e070ec5797752453283c027c6b29b6e58e8b7eb5c3f9770557be7e80b67", - "0x8621459865234734bcfaa492ca1b89899525198a7916ccc6f078fb24c8bf01154815bb5b12e1c3d0a10bd4f1e2ea2338", - "0xab52174541185b72650212e10a0fe2e18ccfd4b266a81233706e6988c4af751b89af87de0989875f7b5107d8d34c6108", - "0x966819d637bdd36db686be5a85065071cf17e1b2c53b0e59594897afc29354ecba73bf5fc6fa8d332959607f8c0a9c27", - "0xb7411209b5ab50b3292c3a30e16f50d46351b67b716b0efb7853f75dc4e59ec530a48c121b0b5410854cd830f6c4b3ea", - "0xa5dc04adbadce0af5dc1d6096bad47081110d4233c1bf59a5c48a8e8422858620f4be89bf1f770681be2f4684ee4cce7", - "0xaf77a8f83cffb5f8d17be0ab628dedcad63226c9b13ce4975fb047f44bfef7d85e7179aa485abb581624913eddbb27ec", - "0x82bf28dc58c893c93712ce297cc0d64f70acb73a641cb4954ccf9bf17597f6d85eecf5a77c8984ab9afbe588562a0ee9", - "0x988a7cef9a178e8edb91f3ec12f878fd68af2ac0762fa0a48a2423e24f765ed8f7837429fd8bc0e547e82e6894e63008", - "0xa5d5969311056d84b3ee87f49286fac0bd9a7220c196cea4f9dced3b858dcdba74718eab95b38bd5d38d2d1184679c98", - "0xaf4d51b3ded0aaad8f12bef66c0616e9398fc42618852ac958e6ab2984a720a6111ac55b249d7e4523051740e12b346f", - "0xac635b4a49f6fbb94a5f663660f28431ba9f7c5c18c36ebc84fd51e16077de7753595f64619b10c16510ecbc94c2052d", - "0xae25eb349735ced1fe8952c023a9b186a1f628a7ddf1a4b6f682354a88f98987ac35b80b33189b016182f3428a276936", - "0xae3ab269690fdd94134403691ba4f5ed291c837c1f5fdc56b63b44e716526e18abb54f68ca5d880e2fb7bea38e74c287", - "0xa748b03b2bd3fbc862572bc4ddc0579fa268ee7089bcfd0d07d0c5776afcd721302dbb67cb94128e0b1b25c75f28e09a", - "0x8f09a2aaa9ba3dfe7271f06648aba9cc1ea149e500a7902d94bb9c941a4b01d1bb80226fd0fd2a59ad72c4f85a2a95d0", - "0x853d55ad8446fd7034e67d79e55d73a0afcb5e473ed290e1c3c7aa5497e7f6e9bbf12d513fc29e394a3dc84158a6d630", - "0xb1610417fb404336354f384d0bf9e0eb085073005d236a0b25c515d28235cea5733d6fbd0ac0483d23d4960064306745", - "0x86de805b3d4f6fbb75233b2cf4d22fcc589faa2ac9688b26730cb5f487a3c6800c09bb041b2c6ab0807bfd61b255d4c9", - "0x893b38c72cf2566282ee558d8928588dca01def9ba665fcb9a8d0164ee00dedafbf9d7c6c13bcc6b823294b2e8a6a32c", - "0x8e50de7a70ac9a25b0b5cf4abc188d88141605e60ce16d74a17913a2aff3862dec8fbbf7c242cf956f0caae5bcc4c6bf", - "0xb5cf09886a4fb4ce9ea07d1601d648f9f9d1a435b5e1e216826c75197cd6dafd6b2b07d0425a4397a38d859a13fdb6dc", - "0x859dc05daf98e7f778a7e96591cc344159c1cbe1a7d017d77111db95b491da0a9272866d2638a731923ca559b2345ebe", - "0x8ff1792f77ecdfbd9962f791a89521561c7b82031a4e53725f32fe7d99634a97b43af04cbf3e0b0fdff4afa84c49eb99", - "0x81e2cd8a221b68ae46dd7ce97563bd58767dc4ce1192b50ff385423de92206ff585107865c693c707e9d4ed05f3149fb", - "0x8fce7da7574e915def0d1a3780aa47ef79b6d13c474192bd1f510539359494ddc07e5412f9aac4fc6c8725ade4529173", - "0xac02f5df60242734f5ead3b8a62f712fefdb33f434f019868a0b8ddf286770244e2ddfb35e04e5243ba1e42bcd98a6a5", - "0xa8d69783349a442c4a21ecb3abd478a63e2c24312cb2d2b3e10ea37829eb2226a9b8d05a8c9b56db79ffaa10d1f582d1", - "0xb25b5cca48bf01535aba6d435f0d999282845d07ac168f2ca7d5dba56ee556b37eab9221abdb1809767b2de7c01866c1", - "0x8af7e1d1f4df21857d84e5767c3abe9a04de3256652b882672b056a3ab9528e404a8597b1ad87b6644243f8c4cd3799f", - "0xa6718308dfa6992ae84fcb5361e172dbbb24a1258a6bd108fd7fc78f44cc1d91be36e423204a219a259be4ab030f27ff", - "0xb99cbe3552c1a5259e354c008b58767c53451932162e92231b1bebfc6a962eb97535966a9bd1dfd39010dfcda622d62a", - "0xa8458f6b8b259581f894e4b5ce04d865f80c5a900736ca5b7c303c64eaf11fe9cb75e094eece0424ba871b2aee9f7a46", - "0x914f763e646107b513c88f899335d0c93688ffa6e56c3d76bff6c7d35cb35a09f70dc9f2fe31673a364119c67cd21939", - "0x9210f2d39e04374f39b7650debe4aceeb21508f6110ab6fc0ab105ec7b99b825e65753d4d40f35fad283eeff22a63db0", - "0x98729cf927a4222c643b2aa45b3957b418bce3f20715dd9d07997a3c66daa48dd62355dbd95a73be9f1d1516d1910964", - "0xa602c399f1217264325b82e5467a67afed333651c9f97230baf86aec0dd4edeae1e973cafef2ea2236d6d5b26719954d", - "0xac9632921d45900bf3be122c229ba20b105b84d0f0cba208ccdce867d3e9addfb3ef6ece9955950d41e1b98e9191ef42", - "0xa76ce1f53e1dc82245679077cb3bca622558f2269f2d1a1d76b053896eba1c3fc29d6c94d67523beb38a47998b8c0aa7", - "0xb22b51fcc1b328caa67cc97fb4966cb27d0916488a43248309c745cd6e2225f55ad8736d049250fa0d647e5f8daa713c", - "0xb7645c1923a6243fa652494fe9033fa0da2d32a0fb3ab7fcb40a97d784282a1ffad3646c499961d4b16dadbc3cbb6fd6", - "0xacab12b490da690db77c4efdc8b2fe6c97ac4ba5afb5165d6647fdd743b4edbad4e78d939fc512bebcf73019c73bae40", - "0xad7a0fcd4e4ccb937a20e46232a6938fccf66c48a858cf14c8e3035d63db9d1486e68a6bf113227406087b94a0ece6a0", - "0xa78605beaa50c7db7f81ab5d77a8e64180feea00347c059b15dc44c7274f542dc4c6c3a9c3760240df5f196d40f3e78b", - "0x8763315981c8efa9b8ae531b5b21cfc1bbc3da3d6de8628a11dcc79dee8706bd8309f9524ec84915f234e685dd744b69", - "0xb4a6c48531190219bf11be8336ec32593b58ff8c789ee0b1024414179814df20402c94f5bfd3157f40eb50e4ef30c520", - "0x8dac8a3f152f608ce07b44aee9f0ed6030fa993fd902e3d12f5ac70bf19f9cde2168777d2683952a00b4b3027d7b45ea", - "0x8baf7dfae8a5840c5d94eabfe8960265f6287bb8bc9d0794a6d142266667a48bec99b11d91120907592950a0dddc97d9", - "0xb8595e6ea6b8734d8ae02118da161d3d8d47298d43128a47e13557976032dad8c2ccbfff7080261c741d84d973f65961", - "0x8b93979c51c8d49f4f3825826a5b9121c4351e0241b60434a3c94f2c84f0b46bbf8245f4d03068676166d0280cf4f90c", - "0xaceb0fdaf20bf3be6daebf53719604d3ab865807cc2523285f8fef6f3fc4f86f92a83ad65da39de5bd3d73718a9c4bd2", - "0x814dd41764a7d0f1a14a9c92e585f154a26c8dbf2f9bff7c63ae47f1ac588cec94f601ccc12e8a63a7a7fce75a4287f2", - "0xb47b711848e54fa5c73efc079d0a51a095fa6f176e1e4047e4dac4a1c609e72099df905958421aee0460a645cba14006", - "0xaaf7bd7e1282e9449c0bc3a61a4fca3e8e1f55b1c65b29e9c642bb30a8381ce6451f60c5e0403abc8cee91c121fa001f", - "0xb8b0e16e93b47f7828826e550f68e71a578a567055c83e031033c1b7f854e7fc8359662a32cc5f857b6de4aff49e8828", - "0xb3eb70b8c8743a64e1657be22a0d5aeb093070f85a5795f0c4cb35dc555958b857c6c6b7727f45bf5bedf6e6dc079f40", - "0xae68987acd1666f9d5fa8b51a6d760a7fb9f85bf9413a6c80e5a4837eb8e3651a12e4d1c5105bfb5cfa0d134d0d9cfc2", - "0xacd8fa5742b0bac8bd2e68c037b9a940f62284ff74c717f0db0c033bf8637e4f50774a25eb57f17b2db46e5a05e1d13d", - "0xa98dac386e7b00397f623f5f4b6c742c48ab3c75d619f3eaf87b1a0692baf7cb7deac13f61e7035423e339c5f9ae8abf", - "0x99169bd4d1b4c72852245ebfbc08f18a68fb5bcce6208dd6d78b512b0bc7461f5caf70472b8babf3e6be2b0276e12296", - "0x937d908967f12bf7f728fe7287988c9b3f06c1006d7cd082e079d9820d67080736910bc7e0e458df5bae77adb9a7cbc1", - "0x8c50e90ce67c6b297fd9406c8f9174058c29e861597a0f4ed2126d854a5632fa408dfa62ad9bb8b6b9b6b67b895d5a4d", - "0x8f4840a91b0a198226631a28e7a2e893fc6fed4d5eb3cb87b585aac7f4e780855a353631ad56731803296f931e68a8d0", - "0x96a4b8c64d3d29765e877345383bf0e59f4ac08798ac79dd530acd7f3e693256f85823ad3130fb373d21a546fe3ca883", - "0xb0dce7a6ab5e6e98b362442d6e365f8063ba9fef4b2461809b756b5da6f310839ac19b01d3fd96e6d6b178db4ff90ee1", - "0x8f012cb2be5f7cb842b1ffc5b9137cafef4bd807188c1791936248570138f59f646230a1876f45b38a396cbdd3d02e08", - "0x94a87b5ce36253491739ca5325e84d84aaff9556d83dcb718e93f3ff5d1eecf9ae09d0800a20b9e5c54a95dfebfcecd3", - "0xb993ec5f9e82cc9ceeb7c5755d768bc68af92cc84f109dfaf9cf5feb3aa54881e43c3f598ba74ed98e8d6163377440ca", - "0x92f845d4d06a5b27d16aef942f1e3bcbe479b10fef313f9ca995315983090511701b39ccbb86b62d0c7c90a2d1f0c071", - "0xb6ec6da0f9e7881e57fa3385f712e77f798abc523609a5b23e017bb05acc6898825541aed7fe2416c4873de129feceea", - "0x86b181183655badfe222161d4adf92a59371624a358d0ec10e72ee9fa439d8418f03d635435ec431161b79fd3fa0d611", - "0xb5e28eeed55fb5318b06a0f63dbf23e00128d3b70358f1c6549fd21c08ef34cb1372bc0d4b0906cc18005a2f4cd349bf", - "0x85c4d3fddda61dbfb802214aa0f7fc68e81230fb6a99f312848df76cddc7b6dfd02860e8a4feb085dad1c92d9c6c65e0", - "0x80f7fdec119309b2ac575562854f6c2918f80fc51346de4523ec32154d278f95364fdef6f93c7d3537a298dd88df7be6", - "0x9192c1949d058614c25f99d4db48f97d64e265a15254aa6ed429e1ef61d46aa12355769f1909a5545cd925d455a57dbe", - "0xa0b1e7d928efc4dcbd79db45df026ae59c20c1a4538d650c0415ab7cb0657bc1e9daeacc3053ee547e8f9c01bdbd59c4", - "0x893e84c41d3a56bca35652983c53c906143b9ad8d37b7c57f9dacbeb7b8dd34defc6a841f5b9857ffb90062bbd8e9dee", - "0xa7f89a448349dbc79854cf888980327f92aedc383c7fadd34fdc0eaa4f63d751315b4f979e14f188854ef4d16c9e8107", - "0x833f2774a96187805f8d6b139c22e7476bce93bc5507344d345008080fb01b36d702b96e4c045617a23a8ca1770b4901", - "0x80e46e86d68bd0a48ac6fa0b376d5bb93a5d6b14f08b3a47efa02bb604c8828c2047695f1f88fc5080e5548e1a37130f", - "0x943f42b7b4ad930059a26ad06b62e639f06c1c425d66066c55134e97c49abe412358c7cb994fcc1cf517ea296bca1f68", - "0x8b9d4fe835dc6a2cbf85738937bbfb03f0119ab8df04a7d68860716ce6ee757dbe388a1e8854ddb69fe0c9fa7ed51822", - "0x909030c7fde2591f9ea41ae6b8fa6095e6e1a14180dda478e23f9c1a87b42c082a1ea5489c98702f6ccd2ba5812d1133", - "0xa715ec1beb421b41c5155c7ef065bbb50b691d0fa76d7df7ee47683d9e4eb69b9ea3e62fc65196a405d6e5e29e6c2c60", - "0x8c9e801cb7ef780a535be5c2a59b03e56912acbfdb00447bfa22e8fc4b11dceecc528f848d5fba0eec4237d6f81f4c79", - "0xb96b6af857c3bc0344082bd08ec49a9bed478d4d35b85a2099b1849cd6997521c42225305f414cdd82aef94b9e1007d3", - "0x8764db720b4e44a4d2527f7f9b535a494a46c60e28eac06bf1569d0703c4284aefa6cb81fbba9d967286f9202d4b59ea", - "0xa66fd2f9158e1ffcdd576cba1413081f43eed00c7eb8f5919226f7b423f34ac783c1c06247819b238de150eb5a48d977", - "0x82c52e817ac3bb0833ea055dec58c276c86ca5181811cf7a483b3703a06ea1bee90ae3aeaa2cffeaeba0b15fe5bf99be", - "0x987d07cb276f7f03a492cfb82bba6d841981518286402d3e69e730a9a0e29689a3619298124030da494e2a91974e0258", - "0xb34f2c5740236bc6d4ae940920c5bc2d89ff62a3dd3a3ec9a0d904d812b16f483073db1e53b07f2b62e23f381d7bdbe5", - "0xa1c0679331ab779501516681b3db9eefb7e3c0affb689e33326306ada6d7115fafd2cc8c1c57b2fa6c2072552f90a86e", - "0x94805e30d7852fc746e0c105f36961cc62648e438e8b9182fc0140dbf566ec14a37ad6e7f61cacb82596fc82aed321e5", - "0xa42fb00b29a760141ff0faaeb7aca50b44e7bbc0a3f00e9fb8842da7abfcaae6fae9450abe6ba11e8ecf11d449cbe792", - "0x8fb36ce4cfa6187bfe8080ac86b0fa4994f20575fb853bd8ffa57c696179cc39f58ff3b4bd5a2542ff1c8b09015539df", - "0xa1c54e7aa64df7fb85ce26521ecfc319563b687ffecd7ca9b9da594bbef03f2d39f51f6aaff9a3b5872d59388c0511c6", - "0x855e48fdb8f771d4e824dbedff79f372fd2d9b71aa3c3ecf39e25bf935e2d6e0429934817d7356429d26bf5fd9f3dd79", - "0x8ae6157a8026352a564de5ee76b9abb292ae598694d0ea16c60f9379e3bb9838ce7fd21def755f331482dc1c880f2306", - "0xa78de754e826989de56fe4f52047b3ffd683c6ceaf3e569a7926f51f0a4c4203354f7b5cfa10c4880ba2a034d55a9b0d", - "0x97609477d0a1af746455bbd8cb2216adacc42f22bfd21f0d6124588cd4fec0c74d5bde2cdba04cdbfbff4ac6041b61b1", - "0xa03dc3173417381eb427a4949c2dbfa0835ef6032e038bf4f99297acf4f0ba34a5fc8ccf7e11f95d701f24ee45b70e27", - "0xaad6283e85cd1b873aeb8b5a3759b43343fdadc9c814a5bf2e8cf3137d686b3270f1ec2fb20d155bbfd38c7091f82c44", - "0x92ab94ed989203a283d9c190f84479c2b683615438d37018e9c8de29c2610bb8fccd97bb935dca000d97d91f11a98d65", - "0x8c0444a0b9feb3acb65a53014742e764fa07105e1c1db016aec84f7a3011d9adc168dbba8034da8d0d5db177a244d655", - "0x95a33d25e682f6c542d4e81716cc1c57ef19938409df38bf8f434bc03193b07cedd4e0563414ce00ab1eebbd3256f3e7", - "0x8716c30e3e4b3778f25c021946c6fb5813db765fde55e7e9083a8985c7c815e1b3d3b74925ba108d9a733ddf93b056af", - "0xa186aabc10f1fff820376fa4cc254211c850c23a224f967d602324daec041bbe0996bf359ed26806a8c18e13633a18a8", - "0xa1e8489f3db6487c81be0c93bade31e4d56ec89d1a1b00c7df847f5cd7b878671015f5eaa42ee02165087991936660b9", - "0x8f688c969c1304dfa6c1a370119d1988604026a2ab8e059016c5d33393d149aae6e56f3ee2b5d25edc20d4c6c9666ad9", - "0x91950b651fefd13d2fa383fd0fdc022138ce064ee3b0911157768ad67ed1fb862257c06211cf429fba0865e0b1d06fc8", - "0x86cff4080870d3e94ed5c51226a64d0e30266641272666c2348671a02049ae2e8530f5fb1c866c89b28740a9110e8478", - "0x88732c4d9e165d4bb40fb5f98c6d17744a91ff72ca344bc0623d4b215849a420f23338d571a03dd3e973877228334111", - "0xafcc476ad92f09cf2ac7297c5f2eb24d27896d7648ba3e78e1f538c353ceeb1e569917a2447f03f3d4d7735b92687ba5", - "0xb622aa475e70d9b47b56f8f5026e2304d207684726fb470a0f36da7cb17c30dd952813fab6c7eb9c14579aacca76f391", - "0x802cf5630c0407ae0d3c5cf3bef84e223e9eb81e7c697ea10ec12e029fc4697ce7385b5efab7014976dacc4eb834a841", - "0xa08596493f4cd1b8ac2ec8604496ee66aa77f79454bb8ab6fdf84208dc7607b81406c31845d386f6ac8326a9a90e7fc5", - "0xa54652ca9e6b7515cb16e5e60e9eabbccbc40bb52423d56f0532d0bac068aec659a16103342971f2cc68178f29f695db", - "0xa3ab54875cb4914c3a75b35d47855df50694310c49eb567f12bbc5fa56296e11f4930162700e85ba2dbfdd94c7339f91", - "0x94183a040285259c8f56bef0f03975a75d4def33222cc7f615f0463798f01b1c25756502385020750ac20ae247f649a1", - "0xb0004261cc47b0dc0b554b7c6ebf7adf3a5ece004f06e6db3bbac880634cdf100523b952256a796998a5c25359f12665", - "0xa25dfeb0e18ebe0eb47339190f6a16f8e116509ab2eef4920f0d3ff354e3ead5abe7f5050b2f74f00b0885ea75b4b590", - "0xab10ef2f5dc0ede54e20fa8b0bce4439543db8d8b31e7f8600f926b87ec5b8eea0ac2153685c7585e062ffac9e8633c3", - "0x8386eac1d34d033df85690807251e47d0eaacb5fe219df410ab492e9004e8adabb91de7c3e162de5388f30e03336d922", - "0xb6f44245a7d0cb6b1e1a68f5003a9461c3d950c60b2c802e904bc4bc976d79e051900168b17c5ac70a0aed531e442964", - "0xad12f06af4aa5030b506e6c6f3244f79f139f48aec9fc9e89bbfbd839674cfd5b74cea5b118fb8434ba035bda20180af", - "0x88511306dfe1e480a17dba764de9b11b9126b99f340ceb17598b1c1f1e5acbdd1932301806fe7e7e5e9aa487a35e85de", - "0xa17cdf656e1492e73321134a7678296a144c9c88c9a413932d1e4ca0983e63afc9cdc20fd34b5c6a545436b4db50f699", - "0xb555b11598a76de00df0f83f0a6b8c866c5b07f7ac2325f64fb4a0c2db5b84e0e094d747186c3c698ee4d0af259dc4c7", - "0x88014560587365e1138d5b95c2a69bdae5d64eb475838fee387b7eb4c41d8c11925c4402b33d6360f0da257907aa2650", - "0xb220634e6adee56e250e211e0339701b09bf1ea21cd68a6bd6ee79b37750da4efe9402001ba0b5f5cbbfcb6a29b20b0c", - "0xac5970adc08bc9acec46121b168af1b3f4697fb38a2f90a0fbc53416a2030da4c7e5864321225526662d26f162559230", - "0x97667115b459b270e6e0f42475f5bce4f143188efc886e0e0977fb6a31aba831a8e8149f39bc8f61848e19bcd60ceb52", - "0xb6c456b36c40a0914417dd7395da9ed608b1d09e228c4f0880719549367f6398116bf215db67efe2813aa2d8122048f2", - "0xab7aef0d6cda6b4e5b82d554bd8416a566d38ded953ffd61ef1fcca92df96cdcc75b99a266205ff84180ab1c3de852a4", - "0x81d354c70ce31174888c94e6cf28b426e7d5c4f324dc005cd3b13e22d3080f3881d883ca009800f21b0bb32fa323a0cf", - "0x94f3440965f12bee4916fcc46723135b56773adba612f5ce5400f58e4d4c21435e70518bdef4f81e595fa89e76d08fc6", - "0xa6683e7a1147f87cbeeb5601184cc10f81bca4c3c257fd7b796a2786c83381e7698fb5d1898eb5b5457571619e89e7d6", - "0x8ca29539600f8040793b3e25d28808127f7dc20c191827a26b830fff284739fb3fc111453ff7333d63bce334653a0875", - "0x98a69644048b63e92670e3e460f9587cf545a05882eb5cba0bcbd2d080636a0a48147048a26743509ab3729484b3cc12", - "0x84d40302889c03c3578c93aca9d09a1b072aadd51873a19ef4a371ca4427267615050c320165abece7f37c13a73d4857", - "0x87954271e3de3f0b061c6469d038108aac36f148c3c97aefb24bf1d3563f342ea6c1c1c44c703e1587a801708a5e03f8", - "0x86b6f5367e04c5caa3ec95fd5678c0df650371edac68f8719910adf1c3b9df902cc709a2bddc4b6dde334568ca8f98ac", - "0xa95fed2895a035811a5fee66ca796fdecce1157481dd422f8427033ed50c559692908d05f39cb6bea5b17f78a924633c", - "0x8ba05bdadde08a6592a506ea438dbdc3211b97ea853d1ad995681a1065ececce80f954552b1685ef8def4d2d6a72e279", - "0x90b6b7494687923e9c5eb350e4b4b2e2fa362764d9a9d2ebb60ee2ad15b761e0850c9a293123cf2ef74d087693e41015", - "0x8819ea00c5ea7b960eb96ab56a18c10a41fd77e150ab6c409258bc7f88a8d718d053e8f6cb5879825b86563e8740808d", - "0x91e42031d866a6c7b4fd336a2ae25da28f8bde7ace6ff15dc509708b693327884e270d889fff725e6403914546055c28", - "0x85763642052f21cf1d8bd15fd2dc0c2b91bba076751e4c4f7a31fbdb28787b4c6a74d434d6ef58b10f3ad5cde53ef56d", - "0x8b61c36c7342a1967a1e7b4c01cddf4dce0e2025bc4a4a827c64994825f53e45277550ceb73c34bb277323fb784aa3c6", - "0x80b9634a45c8b3770e993257bd14df6a17709243d5429969ab8b9a4645bf2a94f9b3cd3d759169887b4aa0eb50f4f78c", - "0xb5c44db9439dd8aa4edd151d95e48a25c1154e1525c337f97109f40131db81a4898344c8c3144c270bdc835c269b3477", - "0x863080fcbc227eea32d0dc844f42dc642fbda7efc398ab698be3a3c6f3bf8803dea6ba2b51fed6151f9522b4ab2a8722", - "0x8481e871129e9cb9d2d109c513cbba264053e75192e967f89659dcfcc1499de9ae7a1ac4f88f02289150231c70b4da01", - "0x834d8183698d3d2d1352c22c373222cb78d0f4c8cb15e0ad82073dde273b613515ebcd184aa020f48f8e6fc18f3e223c", - "0xa227e300f0c5bc1b8d9138411413d56c274cc014ae8747ec9713f3314d5fae48bb6f8cc896f232fd066182af12c924e4", - "0xab7242835e91ba273de1c21eb4fca8312bdda5b63b080888b96a67a819b50294a7f17a7dc0cd87fae5e7f34bc24c209a", - "0x86eb27c898a5d6c3618c3b8927acee195d45fe3f27b0991903520a26fb8021b279e2a8015fbbba5352223ae906c7c5d6", - "0xa61b1c200b0af25da8ad8e29f78d000a98683d1508ae92ee7f4326a7c88e0edb645b6cb5dde393ac74d322895e77ba24", - "0x887739318c710aae457b9fe709debff63bfbb3ffbbb48a582c758b45d6bf47a7d563f954b1f085c3bc633ffd68c93902", - "0xaacfcb0e2b0a868b1c41680487dc6600577ce00aa2edeee8c6141f4dc407217ddb4d37b79e7c9182258c750d12a91508", - "0xad8cd2cf5ccd350cd675a17f31b86a0e47499c6c4c11df640a5391bb10989c9c70df0a3ddeba9c89c51e15fedaf67644", - "0x8aba897d32c7ef615c4dfa9498436529c91c488a83efc07ba9600875c90c08b00f66a51469eb901451b6e18e7f38ffd7", - "0xaab8a600609b80e36b4a6772308bac77929a0c5d8d92bbc38e9999186a1c2bfdbef4f7a2b1efba9c17a68dc15a9373ab", - "0xb95811d1454307a30c2ac8588c8104804b06c1aec783fed75a6f12c9df626be57865850100f1ad28073e3867aca941cf", - "0x8b119d3bd4ee644469457df5d8a0020fd99b8b20bd65ab121cf95a7f55e50dd8945fcf1dff9d269d9d0b74b4edbc7726", - "0xa980b912df832ea09353fd755aa3eec9eb4cfd07ca04387f02a27feab26efa036fca54cc290bb0c04a8a42fdfd94ce2f", - "0x91288e84da1d4ee2a4dad2df712544da3a098fdb06a5470c981fb6d6f3dcc1c141b6f426d6196ff3df6f551287179820", - "0x98b0473bcffcbd478fd1b49895c61dd2311dab3cdec84f8e3402f8add126c439ffcb09cae3b7f8523754090d8487b5a9", - "0xabe76988cf3065801f62a1eb3cfe9f8185bd6ab6f126c1b4b4fde497ca9118d02a0db3fadccd4ca98826b30475fa67ef", - "0x94a316a0faa177273574e9e31989576a43e9feb4cc0f67aa14d5c1967c4e10fc99db3ef4fdca2e63800a0b75f4b84256", - "0x975ad39adadc7e69e34981be2e5dc379b325dc24dddacc0bb22311ff4a551a0020a8bdecf8ab8ac5830ca651b7b630ce", - "0x8b3bc73b640dc80ac828541b723a968fb1b51a70fa05872b5db2c2f9b16242c5fe2e8d1d01a1dbeaac67262e0088b7b0", - "0xaa8d892a6c23dbc028aae82c1534acb430a1e7891b2a9337cedb913ff286da5715209cffb4a11008eae2578f072836cb", - "0x8dee9747a3ae8ed43ce47d3b4db24905c651663e0f70e2d6d2ddb84841272848a1106c1aa6ba7800c5a9693c8ac2804e", - "0x81e2c651b8b448f7b2319173ecdc35005c2180a1263e685a7e3a8af05e27d57ec96d1b2af2cae4e16f6382b9f6ec917c", - "0x98a9a47635de61462943f4a9098747a9cf6a9072a6d71903d2173d17c073eff3fc59b2db4168515be31e6867210ecbcd", - "0x912b2398505c45b0bb4a749c3f690b1553b76f580b57007f82f7f6cce4fadd290d6df9048258978c8a95ef9c751a59a2", - "0x8ac8f0893fe642111ef98ae4e7b6378313a12041bbca52141e94d23152f78c2e4747ae50521fc9c5874f5eb06976e5cf", - "0x946b4a8eb05b529aaed56ac05e7abeb307b186a7835623fa4e85ed9eb41a4910663c09ea1bd932a2c467d28776b67811", - "0xa4be51abeddd40e1da6fdb395d1c741244988ff30e10705417b508574b32dce14d08b464486114709339549794df9254", - "0xb33b6b7d66cb013e7afeabbd7ed1e0734eb0364afe4f0f4c3093938eec15f808985fb7f3976969bf059fa95f4d8e335b", - "0xa808adbcf0049f394914482483ee0f711d9a865615ff39b5313ed997f7a0d202ad9ed6e6de5be8a5c1aaafe61df84bca", - "0x8856268be15a78465ad00b495162dc14f28d4ef4dcf2b5cba4f383472363716f66dabc961a6dbdda396e900551411e41", - "0xb16ba931e570e1bf124ea3bd3bdf79aed8aa556697ea333e6a7d3f11d41538f98dcde893d0d9ba7050442f1515fb83b1", - "0x91ecde1864c1a9c950fd28fa4c160958246b6f0aa9dda2a442f7222641433f1592d38763c77d3f036a3dbb535b8c6d8f", - "0x92cda991f69fbf8e55c6bf281b07fff5dbbb79d1222b8c55686480669247b60212aac27aa7cccd12fcee94e7a759b8af", - "0xb1d9b5b4e996b375d505d7250a54c12d32372c004a9cabf1497899054cb8b5584b1cef1105f87b6e97603ccbf2035260", - "0x86e98bde8b484fb809b100f150199f13a70c80813ad8b673bf38e291595e2e362ad1fa6470d07d6fbe2cf7aeac08effc", - "0xaa12f7c39ba0597a8b15405057822e083aca3cee6ed30c4e0861eeb22620823588d96c97bb1c3776b711041c4dc3d85d", - "0xb477b34f29334f3bae69c7781d574342b7c27326088f9a622559ab93075c7357953ae84eb40e3421f453e04e9b4d5877", - "0x9625067cb2120ce8220a469900aa1d1bb10db8fe1609988786b07eb2b88e0ddb35a3eccd4b6741e1fa2365c0db6b1134", - "0x997b92af7765f587d70ea9718e78a05498cd523fc675ad7b0e54a4aae75fbeac55d0c8d72471471439dacd5bfcfae78d", - "0x88b59eaea802e6a2cf0c0075bf3fd6891515adcb9adf480b793f87f1e38d2188c5ed61ac83d16268182771237047ec8a", - "0xa57d078b230b1532c706a81eaabfef190fb3eb2932f4764631e916a0f0d837d6014da84ede100abaf610519b01054976", - "0x94ed5c5b96f6afa9f2d5e57e1c847ae711839126ab6efb4b0cf10c4564ff63c819d206fdc706178eb6a0301df2434c01", - "0x980296511019c86cc32212bad6e4a77bc5668b82a2321a1ecabc759a8bbc516183a4787c7f75f9ee7f1338691dc426cc", - "0xb10ef97db257343474601fd95f9016c205e28bd22bf7b8f9e30c3b14aca1cc9a11e6404ff412ef269c55fb101fee5a37", - "0xb670d5d9c77fc6aa14212dd3eae100620f3510031b11a9625aa40bf31835c9fd717753b555bd159b1aa64a2104929340", - "0x862054fabf6d6d529a7584d1a48f72d2eb216caf959c782ec36c69c26aef4595415d19a28b041946811b34a629105241", - "0xae4bf2ccd7b0f3774653848b5b4d39e5517dcbcff30d8441d78bc387ff42b573f16b7b0a7366e6ca5cef1dd9f0816df9", - "0x8f810527badcb49f1542a0ccd12e3541efa084243f7106eae003458c176f4c1f01daae9d4a073c2cb2aced747e8a4576", - "0x8a32c2067aaf6baf32db67acd4974a22a6da33db5444028a7c8c4135f9c84e102dc3b2c635b15afa6dc907d0270daffb", - "0xb15fc057f306a60b20c8487125b6b334ab749cf70eb8a30c962f625bb203ebd0d2a315949ee3b7a99e3d91acec384806", - "0xa37f145d321359b21cba7be8b64dfae7c67a20b7b324f27c9db172d58e77a49fa02ed3d06d09d7644bf1fd81f4aab44b", - "0xb338d2e39a485ee4297adcf5e58e16c3cc331c5dffeade0be190907c1c5bdfed38537a6d81dc39a2cdfc1bc45e677886", - "0xb69d84d8511b3aedfdc7c7e66f68b24e12e5a2365dbbe014bddd2e99e54143428cf8b74cf12c0e71316038aa5300e87e", - "0xab210cc38661667450561a1857337879633f5d5bf2c434a3df74ff67f5c3ba69a7880872f19ae4dcbbb426462cd7d0fb", - "0x94538ef487a58a5ff93a5e9616494c5f066715d02be5b249d881a00bd0edfe2fe19dd7a5daa27f043d1dbb5ac69cf58d", - "0xafb47a899c1b25fe800241635fa05de9687a69722802ad45434f551971df91d8ca9edda0d835d82eb8f36ff9378ed7e8", - "0x827a10d7536230887283a9b1dedccc6b95ef89cb883c4ee7b9821058b0f559704d1636670c0ada2b253bf60b7cb8a820", - "0x97cc07965065d64409f19fb2c833b89ca3a249694b16b58818a6f49d3800926627ce0f87e5c0853ae868b4699cfdee5e", - "0xae0c93d44780ef48ea537cf4cb8713fd49227f4b233bc074e339d754b5953e637a7289c6f965162701e4b64e4eaec26d", - "0x80953053397c4c0ba9b8e434707f183f9ced2a4c00d5c83b7dc204e247ad7febc1855daeb906c53abfdf3fe3caca30c4", - "0x80f017e87b471b5216ebe25d807be6c027614572337f59f0b19d2d1f3125537478cb58e148f3f29b94985eac526cd92f", - "0x8a8e1c0d49801a8dd97e9e7c6955fc8b2c163a63bd6a4be90bb13e7809bb0dddc7a5025cc7d289a165d24048eac4e496", - "0x8530e5b5c551a2e513d04e046672902c29e3bb3436b54869c6dea21bab872d84c4b90465de25dff58669c87c4c7d2292", - "0xae3589d389766b94428e9bde35e937ed11aac7ead3ce1b8efe4916c9bfff231d83b7e904fe203884825b41022988897a", - "0xac02e629a900438350dd0df7134dfa33e3624169a5386ea7411177b40aa7a638e8d8aef8a528535efdbe1ca549911c0b", - "0xb1ac60b7270e789422c3871db0fa6c52946d709087b3b82e6eba0d54f478520b1dc366bb8b7f00ff4cf76e065c4146eb", - "0xa7465e1f8e57de1a087144d3c735fee2b8213fcbf2b9e987bb33c2d4f811de237bf007402e8d7f895563e88b864f7933", - "0x8ab0007ba8984dee8695ec831d3c07524c5d253e04ec074f4d9f8bd36e076b7160eb150d33d15de5dd6e6fb94f709006", - "0x9605bbe98dadd29504ce13078c1891eca955f08f366e681d8b5c691eadb74d6b1f2620220b823f90ef72eb4ab7098e16", - "0x942a083d07c9cb7f415fedef01e86af4019b14ef72d8ab39fe6bd474f61ba444b9aac7776bea7e975724adb737e6337a", - "0xb9a49a8c4e210022d013b42363ac3609f90ea94b111af014f2c5754fbc2270f6846fa6a8deb81b1513bb8a5d442ea8dc", - "0x99cd62b177d5d7ce922e980cc891b4f0a5a8fa5b96dfc3204673fbef2e7fb2d7553bbacd7b2e6eca4efb5e9a86096e2e", - "0x94e30b65b3edd7472111566dde7fab0e39a17e1f462686050f7134c7d3897e977550faf00174748cbeaec6c9c928baa8", - "0xa32fbcb29f3391d62092f2720e92b6ef4d687d8a3eae39395e0464669a64a38fe21a887f60bc9519d831b9efde27f0f4", - "0x8f1492c4890d8f9deecb4adada35656e078754dcf40b81291e7ef9666d11ba3747a478f9420a17409d7d242cecd2808f", - "0x8942960b319ef65812d74cb1d08a492334db58d41e8437e83ddf32e387d9f3ad36834f59e6a71d1afb31263773c3ec49", - "0x88d692f4976c99e763b027df9c2d95744d224724041dfbe35afc78b1f12626db60b9d0056b3673af3a1741eaf5f61b43", - "0x9920cd37eab256108249a34d3f1cc487829cc5f16d1bce3a2328fe48b4de735ebde56c8b5cf4e532a4d68792387257c5", - "0x87d34c9f5a913b806504a458c843eda9f00ff02ad982142543aa85551208cab36ebf8b3409f1c566a09a60001891a921", - "0xa2ee8339c96f790b3cf86435860219322428b03ea7909784f750fe222bc99128d1da2670ad0b1f45e71a6856c7744e09", - "0x84bd257f755de6e729cc3798777c8e688da0251a2c66d7ba2e0ce5470414db607f94572f5559f55648373ce70e0b560e", - "0x8d0e170714ddf5dde98b670846307ab7346d623f7e504874bfd19fbf2a96c85e91351ba198d09caa63489552b708fbc8", - "0x9484cc95d64f5a913ed15d380c2301a74da3d489b8689f92c03c6109a99f7431feb8a07d9f39905dcef25a8e04bcec9b", - "0xb14685f67dd781f8ef3f20b4370e8a77fef558aa212982f1014f14b1bdd8b375c8a782d1b8c79efc31b41eec5aa10731", - "0xb22fb1541aa7d2b792aa25d335d66e364193fdbf51b24a90677191cae443f0ce40a52faf5983d2cb5f91f0b62a5f20e1", - "0xb06fa9489123ab7209d85e8c34d7122eb0c35c88ee6c4c5e8ae03a5f1ae7c497c859b0d62e0e91f5e549952330aa95a4", - "0xb5cd71617ff848178650e6f54836d83947714d2e074d8954cfb361d9a01e578e8537d4a42eb345031e3566c294813f73", - "0x848d39ea2975d5de89125a5cbe421496d32414032c1e2fbc96af33501d3062745b94e27dfe1798acaf9626eabff66c79", - "0xad35955efd5a7b6d06b15d8738c32067ffa7dd21cf24afc8ea4772e11b79b657af706ce58a7adcc3947e026768d9cdaf", - "0xaff6d7c4861ff06da7cb9252e3bd447309ad553b2f529200df304953f76b712ac8b24925cf4d80a80b1adaa2396f259a", - "0xb4b88d35e03b7404fc14880b029c188feecb4d712057f7ba9dedb77a25d4023e5a2eb29c408fde2c0329718bdaf1ff63", - "0x88e96720e2f7c63236cca923e017ca665b867ba363bc72e653830caf585d802fad485199055b5dba94a4af2c3130a6f6", - "0x982675dc0299aeedba4b122b9b5f523ca06d54dc35da0f21b24f7c56c07f4280265fb64cec2f130993521272c3470504", - "0x95c77d418490e7e28293169cf7a491a7dcc138362f444c65b75d245c1b986d67c9e979a43c6bd8634dae3052df975124", - "0x8fd6c4dff54fb2edc0bdd44ccd1f18238c145859ccd40fbfbc1cf485264445b9d55ffd4089c31a9c7a0543cc411a0398", - "0xb153eb30af9807b5fe05d99735c97471d369c8a1af06b2e2f0b903b991eb787ab5a88c6e406e86225582acf8186ad5ef", - "0x826b55de54496751b0134583b35c0c2049b38de82821177e893feeeeb76ceeb747c7a18312cb79a6fc52f2c18f62f33e", - "0x91650d7205b232c495f1386bea0c36e136a22b645ffd4f5207f5870b9ce329c44524781c983adf2769f4c05b28a8f385", - "0xb8d51a39162ebb38625e341caacc030913f7971f178b3eee62dc96f979495a94763ea52152198919c6dd4733bc234f64", - "0xa1fbd3673f2ae18a61e402fe3129b7506d9142f2baca78f461579a99183c596b17d65821f00d797519e9d3c44884d8a6", - "0xb7c5f5407263398cf0ed3f0cf3e6fcebdd05c4b8fd4656a152cedcdbf9204315f265fd8a34a2206131585fad978a0d6c", - "0x94fa71804e90f0e530a3f2853164bc90929af242e8703671aa33d2baad57928f5336e67c9efdcbd92c5e32a220b4df07", - "0xb75dcea5ad5e3ed9d49062713c158ebc244c2e4455e7a930239998b16836b737dd632a00664fded275abe4f40a286952", - "0xa02f7b37fc30874898618bfcc5b8ff8d85ef19f455f2120c36f4014549d68a60a0473ddfd294530dfd47f87fbd5e992d", - "0x8b48e1626917b8ba70c945fe0d92d65cab0609f0a1371fd6614d262d49fe037f96991c697904d02031ec47aab4b32f48", - "0xb368f02c21d4af59c4d11027e583ca03ef727f2b2b7918ef623f529ceac76753a05a4ce724ce2e018da6ecc5c1c1261b", - "0xa95cba06eeae3b846fc19a36d840cbcf8036c6b0dc8c2a090afcf3434aaf5f51ef5d14b1e9189b1d8f6e4961bf39bbf8", - "0xb32ca4dfbeb1d3114163152361754e97d3300e0647d255c34ec3025d867ed99e36d67ebafe8255b8c29be41864c08edc", - "0x8e4eddefa27d4fe581f331314d203a6a0417c481085134d8376898f9260f133e2bf48576528d62adf29953ad303e63a7", - "0x92b7d5505833f00d5901ae16c87af028de6921c2d1752a4d08a594eb15446756ea905b0036ae6ffe6b8374e85eb49348", - "0xb50e9018d3c4e05ba9b28b74b6634043f622d06aa8123da7cd0bc482b3131912149214d51bdfd887484422e143c3c1c0", - "0xab980a2f5317dfcb92baa4e2b3eb64a9ac2a755da6c11094d57e781ae5cf43e351824f1dd3abb4c6df75065b3784210b", - "0xaaabb009dfcb0bae65a0aee26ed74872c226965c52a6ed0998209e020a9ee806297dba4b15845cf61e1a514de5d125db", - "0xa1fe78f67000ebb6e90fe33e1a9dd5489be6e15fedb93b2a37a961932b77137fe85d46e89a132ecf7bcfb7aa95e16757", - "0x85bc6e7d660180de2803d87b19ed719d3f195ea0a92baf9bfff6113c743f4237f51355b048549913e95be8ddf237864d", - "0x87a167968c4973105710e6d24ad550302ee47fe1f5079d0f9f9d49f829b9f5c1cd65d832d10fe63533e9ad1fa0ad20f5", - "0xb2ad1a7b95b8a89d58e0b05c8b04ae6b21b571d035ae56dc935f673d2813418e21a271cccaf9d03f0d6fa311f512d28c", - "0x8268e555319992d5ac50cb457516bd80c69888d4afa5795fcc693d48a297034f51e79f877487b6f7219cfdd34f373e14", - "0xb235411f1f6d89de3898642f9f110811e82b04ad7e960d1dd66ec7a9bf21de60e00cfabcd3004f3b5c4f89f5d9c7422a", - "0xb6963effcfe883f7ed782a3df3c40edd70f54ceca551859bcccb5d3e28fd2c1fcbdd7acc7af24a104687fd02b53c704d", - "0x862645c944e1e2909b941578cc5071afd7353fed1c2c99517e2de7573037704ef5d35accf6ec79b8269da27564209d50", - "0x90f585eeb1a053e2f18c1280c9d6a561c0bc510b5f43cd68370ed6daac4b3749852b66c371397b6a7c1ece05ee5906c9", - "0x876d9a3686feb79ce781e87ac3e3fbeef747b6ab031285e808c8a73f73f55b44507850dcaa745c0791d2cae8ad61d74e", - "0xa7ecc3b8c10de41a7bd9527228a0d3b695a651a5b5cb552a3664a887077d39ee60e649aecd68ed630da6288d9c3074ad", - "0x83529f1f2b4dc731ea05c1ee602fa2e4c3eebe2f963f3625959ba47657be30716d64e05e8b7e645a98bf71c237d9c189", - "0x834ca6b14428c30a4bc8d5a795596820af6f3606f85bee9f3008f3fb94b3adffa968d21a29e2588d7a473d8b5d3a8b42", - "0xb8d08cd8b73430984fd16e8db0525ae2b76253c92cccd7b3470add4d12d082eafb55a72bde04870924d0bdaf61f76c5d", - "0x96ef32df669690c2391f82136fc720231e4a185c90ba79eef7beaadedf7fbeb56ed264825564bdc7da01829b47f4aa88", - "0x93d637b2f04d71891a80a1ee93fd9c9046d671bc4c15c4e597cfcc36f4ae85a7efc111359628965fd10d36c39129b160", - "0x89f28dd3f7bc43749d0e3750c136385d4ffaf2c40354d3be38341416d755de7886d8108d83721b36f99feb3bccd73c88", - "0xac6392e274659f4c293e5cb19859828f101959c4c0939920a8dfed0e2df24a0cbf89a7aa983e947318c58791c893928e", - "0x83b2d4ce42c2fa0f672cd911365d1f1a3e19f1c38f32bedc82820ad665d83ae5fac4068e4eca6907bd116898966fed92", - "0xb5e0144d6e59a9d178d4ee9f8c5dba18d22747fcdf8dc4d96d4596a6e048e384cd1e211065f34109c9ed6b96010d37e5", - "0xb1a65e6b38c9e84b3937404d5b86c803c2dac2b369a97cbf532cfdd9478ee7972cf42677296ad23a094da748d910bc48", - "0x849d7f012df85c4c881b4d5c5859ab3fb12407b3258799cfc2cb0a48ae07305923d7c984ff168b3e7166698368a0653d", - "0x84d9b7ee22bf4e779c5b1dd5f2d378ef74878899e9dbb475dfdcd30c2d13460f97f71c2e142c4442160b467a84f1c57d", - "0x964e497ef289fac7e67673a6cb0e6f0462cd27fc417479ecb5eb882e83be594977fb0c15a360418886aece1aaf9f4828", - "0xae1226222098a38ce71f88ab72de6ededb2497e30580e7ae63d4829dcc9c093bdd486102b7a7441cb06253cf0df93772", - "0xa72865b66d79009b759022e53b9eedbd647ff4b1aab5d98b188100d01fc6b5d8c02b80eb6f53dc686f1fdda47d4722b8", - "0x93aa8d7d8400bdfa736521133c8485c973d6d989ec0a81db503074fe46957a3999880fd9e4e7f44de92adf6ac0abe99b", - "0xa75e5ab84399962ada1f9ebcfc29f64405a1b17cd0a983950d0595b17f66386393d95a5aa4c6c878408984141625141c", - "0x91b1e5e75f4b55ec2e8f922897537082a1414eedc2bc92608376a626d8752d5d94f22f0e78ea1970eb0e7969874ad203", - "0x83bf9c308424ef4711bfa2324d722f550d95f37d7f7b4de0487ccf952b89d7219ca94e7fa25bee60309efefd9a0e4716", - "0xa42060476c425ff7979456d3c5484bc205fb1ef2d7149554a4d483d48e2a19119f708c263e902943bcf20a47e6c7d605", - "0x8170c45ea126e6367aa5f4a44b27f7489a5dd50202cb7c69f27a2bdf86d22cf6b00613b0080d75fca22439eeaaaa9707", - "0x8e5a82da70617697e42c6b829e1889b550c9d481408fe4cf8dc9d01daccabdec01f9e1b8c27dc84902a615d539bf9bc6", - "0x80606c51401d0bf5f2700ebce694c807ab1f7d668920bdcccef2775e0939472419a8f404567bd4f9355095517eb4d628", - "0xa40314565d60d0ddf8995673e8c643b1baa77a143b3d29433263730a6871032260abc1320e95af8287b90aa316133da0", - "0xa87e07e84435f9e8a51ce155cd3096aa4b20d18e493c9dcbc0ac997ac180f3a255bf68ccd8195f2564d35ec60551a628", - "0x84d2ab98416643c457bf7ddd9f1aa82967ecea189db08f3558f56803fe7001693ed67ec6ca8574c81ec1293b84a7c542", - "0x937c3b955889ceae77f28054ce53d75f33cfe3a04f28e049cea8b8ade2a0440d5e2e8c4f377e6c1ae2115d68cc95fc16", - "0x885a911f16845fe587b15ce7cd18cc2a84295bf609732340f74e0f5275b698cffed3e9aa1440e19e6940a7fa8f24c89c", - "0xad90059a50c399996aaa0a10a8f637b7bab0dd5d9100301f0159a2c816596da55c30b2568d1717705fd2826b117a42d6", - "0x828de9ff1e095c189da1f1ee18009afe14613ac696025add6f4e330488e02d5f1a90be69edd9a17bfb3355a0ca77b525", - "0xb7aedb8394064a58dd802be6457555c0cf7b94805ed00cc66f38449773f4b1865feaee3a6f166eb51b2123b89d853a4d", - "0xb09c564ff37ccea34e90f2d50a40919a94c2e10d4fa58ffeaed656f88f9f4ae712d51c751b1b8f443dc6c9506d442301", - "0xb24882d66b2ebb0271ebb939c72308d81f653940e70d6f1bcaae352f829134aff7f37522cc42de9e7fe6243db2c4806f", - "0x8e6f8dd906e0d4eb8d883f527e926ad1d8156b500c4cfa27214450c8112267c319900de2443c87bed1e4bb4466297dd5", - "0xae42f4578e8d79b6aa2dca422ada767e63553a5ee913ff09cb18918116905b68f365720a1a8c54c62cce4475ba5cdd47", - "0xade639bcd5017ea83ec84689874175ed9835c91f4ec858039948010a50c2b62abc46b9aee66a26bf9387ab78f968b73e", - "0x8d310a57aeb123cc895ee2fd37edc3e36ce12743f1a794ad0e1a46d0f5e4c9a68b3f128719ed003e010f717ec8949f43", - "0x8606c086fcf3e2f92c1b483f7e2a4d034f08aef1a9d5db9e8a598718e544b82544268a0a54dfed65b4d0e6027a901d47", - "0x8ccd95dd673d8cfdfa5554c61bcdbe6bb5b026403a320856fe51571e7c59504fe1c035f2ad87d67827339d84c0e1a0c6", - "0x955a7cb4afcf70f2eb78756fc3a82e85ab4330eb89a87117294809beb197d1d474001e25306e8ad71daab6928abf6d64", - "0xae6b44ec6294736ea853ddeb18fc00cce0ac63b38170ff0416a7825cd9a0450e2f2b340d27a7f2e9c5ac479b4cb8a5fe", - "0xa88ec3f12b7020dd593c54376597b056e70c772c0ec62c24c5bfd258b02f772161b66e5dcd95c0c0fceb23433df9ff23", - "0xb4a83933b4de552dba45eedf3711f32714e58ae41d4dab8a6114daeb06e90a5a5732c70384150d04124ac6936ca9804b", - "0xb8b7c4fa549b0fa1dc9c1f0af0750d6573f1648767751882d41f0dd7e430e3934590757e1c8b436ac35381bdde808117", - "0xab598b911234a98cfde07234cfc0d2fddfc5cb9ea760212aa3e175a787ce012965c8fcfdf52d30347f5f1b79cf4a0f54", - "0xa9d354f9dfbd1976e5921dd80cbb56b2e15df53ce099ecb4368eff416998130d7830209282aaf1d4354129845f47eb80", - "0x8c889afff546c721969e4d8aae6e6716ad7c2e9c1914dd650e30419ee77d630efb54dfffb4ec4ff487687b1864bf5667", - "0x94ed2fa79116c7c8c554dc306b1617834dd3eab58baf8f0d085132c4688ca4a6bd38420281283678b38970a3f02b9a94", - "0x944fdc8f0516d22f1672193d183833d3e3b043e26807fb2123729a0216c299785b1c4e24b5aa56e9bbe74fa54d43e22a", - "0xa48521454a3e0c10a13d8e810fad9d0522c68eea841821a8e0e57811362f7064a8f9c50f79c780a02df7df8c277feaef", - "0x8f3d26670ab55e1bd63144e785203373b2b13b76cac305f0363e48a6339fac3364caa3fceb245527161fc2fac9890912", - "0xb4d6fe71001cb4141f6d8174dd7586d617cfccb54471e1fbce30debc2b1dead62cab29565abb140b682811c6231acb03", - "0x91dc8afc4934fcc53ef851462a055cc1c3c87d7d767e128806891738427606d2fbfa832664d2a7f95f8ffe2cf0c44dc6", - "0xb297eb432c74071764272c1b1663547ba753e66bf026643bfc0e42a9c5cdfb05a88083ad67d6ddfe6ab290678c607b29", - "0xb343d1df85be154faeb5b21741a5ac454ca93f70a0b83a98f5901d1be173a1b2969d43e646363c5d4975924e1912599e", - "0xb2d74a66e4dfc41128aee6a3f0ff1e5137a953ed7a2a0ab5a08d7ea75642f12bd150b965c8f786ad0caf55ef7c26be4f", - "0xa54141faa8dd9a567c3cd507e4fc9057535ffe352fa1e8a311538fe17e4a72df073fbf9371523e5390303db02321650e", - "0x8e229a58f1acc641202d2a7c7e120210b9924e048603b9f785a9787ad4688294140ef3f4508c8c332d2dedafff2485be", - "0x9523554c11d39b56e6a38b3b0fadb7a9a32a73c55e455efdcfda923aff1e9f457d1b7cbc859b5ecbb03094eae8b87d38", - "0xa199ffdff1812aaea10cd21a02b3e7bf3d8e80e501aa20bb2105b5f4cb3d37265abcda4fd4c298d6c555e43fa34517f8", - "0x97f1285229b07f6f9acd84559afef5daad4320de633c9898b8068c6cb3b19b4468b4445607559ddf719f97d2410e2872", - "0xa1dfff82908c90fc38ec7108c484735f104e6ce7f06097e1e80f6545702b6a0bc2a2706203cd85162edb7e9294fdedba", - "0xb12a706311c617d6c19e964e296072afce520c2711086b827cff43a18e26577e103434c0086d9d880c709df53947b48c", - "0x88503a6f48cef2f5cd3efa96a5aacc85dc3712a3b9abbb720a2cff582a6ea3c2afc49288b6832c8599f894950843ac11", - "0x83ed63e38dfbe062fe8c7e6bc2eeb5a116f1cc505c6b038990038de6051281f9062e761ea882906ccff69c9c5b8a4a25", - "0x911090d5d0231dde1189408dca939daddcb69a812ac408d1326060f0220781bcc131c9229e6015540f529d9fb33d9b0a", - "0x8a8352f1d9e5c7e80276e4448f997d420d5a7e0e2d5be58ae4106f47f867d1caa478b2e714d9c3263e93e5cc4c7be08b", - "0x9362f1ea9995f9b3850ebb7c8d5bf95927ab5ea25ee00e85d7456b3bf54459798b1fffde049d445c0d0587b0ab0a1694", - "0x8859502b391273f4a00b6c0e87e5cdae676b7baf6c402f12b3360db6a5dfb4931ece4da0e1e4d98c7a71c3d01a183a9b", - "0xa9a5edf474120f9bbec9485d8b1e6f83be68b10de3d765219b0bf3e5d2840e478f1fb2bf806d78a8b8ad22ec50cf7555", - "0x82c75daf983b06e49f0d75a042dfaae8cc92af050293d9059d6e8b01ca3ab2597e7adfc1159ed805513488944e739fa5", - "0xa5cf240f04a9bfa65b811702c923d209e01f9535e217fa55ae3e0d1eb3257d6749e5587e727091e860609d1df29a1305", - "0x95608ab8ade1c9fb814bad78d9cc99a36ad3e9562d5319830e4611ceea508ef76be04639294be9062f938667e33bce6e", - "0x8e44181f35c38b02133473de15560ae6588ac744cfdaf5cdfc34f30ca8e5ff6c85eb67dddc1c7d764f96ed7717c89f06", - "0x8007b6ddece0646b7e9b694931a6a59e65a5660c723ebdffb036cf3eb4564177725b1e858ed8bc8561220e9352f23166", - "0xa2d9d10fa3879de69c2a5325f31d36e26a7fb789dc3058ee12e6ccdda3394b8b33f6287ba1699fce7989d81f51390465", - "0x81993d0806f877ca59d7ffa97bd9b90c4ebf16455ea44b9fe894323c8de036c5cc64eacf3f53b51461f18fa701a5860d", - "0xa20030f457874d903b2940ec32fa482410efecb8a20e93f7406fc55ab444e6c93fa46561786e40e9bf1e3c7d5d130bc8", - "0x80c72d4985346ac71a231e7bbbb3e4a91bf50142af0927e8eb86069303eb4ce7fca1aa5b919d5efc82f2f09b41949acb", - "0x91b857d2f47f1408494940281127ba4b9ac93525788e700889aa43402eedea002e70eded017f5f5263741ed3ee53a36c", - "0x97445d007f08e285ea7f4d25e34890e955dac97448f87d8baa408e826763c06cbd58dd26416ba038d6c28f55bcea2d3a", - "0xa409c89526c2886f6a6439e2cd477351fc7f886d1a48acc221d628e11895a4eedd426112a368a0dbd02440cd577880a8", - "0xa2c6adc7866535f6ffc29e00be4a20fa301357e1b86dff6df5f8b395ad9fb1cdc981ff3f101a1d66672b9b22bd94ec0f", - "0x8887fc53ffc45e4335778325463b3242190f65ae5d086c294a1dd587f62dd0d6dc57ca0c784bf1acaa5bbba996af201c", - "0x9731d3261a7a0e8c7d2b11886cd7c0b6bb1f5c57816944cc146caa518565034cea250eeee44ddffaeb6e818c6b519f4d", - "0xafe91c706efb9ee9e9c871e46abde63573baa8b2ea2b61e426cd70d25de3cc8b46d94c142749094287a71f4dfadd3507", - "0xae7bdf6ecc4fc0d8d8a7fa7159aae063d035f96ca5a06b6438b6562a4eee2b48d9024dbe0a54cfd075eac39b7a517f2b", - "0xa382e5205bfa21a6259f42e9ebc11406b5da2aad47f7a722212fdd6fef39117dd158a9991ff95e82efa0826625168a1c", - "0x862760c80bf44c2d41c2a9a15c887889eaeea32acc894f92167fb6f72593377c228499f445ccb59794415597f038ac9e", - "0xb4e96595a91a611c4563d09f29a136a4c04f07be74dd71a6bbabc836617ecb95494e48971a8229f980b2189fd108d2e5", - "0xb5e7200357317c36244c2e902de660d3c86774f7da348aca126e2fc2e2ba765fa0facd29eebcb3db3d306260e91a6739", - "0xa64c7133156afee0613701189c37c1362e2b4414f7e99408e66370680c554de67832c30c211c2c678dab5cfcdcecb3f7", - "0x88f4cb67b1db497a91a0823ee3541378133eb98777842d73e43ab99efe8aa52fa02dfb611c1691be23684618394988d6", - "0x89a9382a147d7387d0ff9516ee0c75cd1f8ee23333f4a2c9693d1a8cbe03680bc5b10c43c238c2190db746cac409bf39", - "0xad510bcc067373d40b05a830bf96fac5487de1ad5b708a13f62484c09b00fba6c5b00b981004e5ab3f28e55c9a5bce26", - "0x8384156d7117675547279ad40dc6bf81e8f9a57b2d8cfebeea6b9cd1d8534dc0cf704068bc3ba0815010cd8731d93932", - "0xa818fb76e53165b2f86c7f2317d64cf5e45f48405a34560983cd88bfbd48369e258ce2952233a8ce09c464e07afcade6", - "0xab19a4ed90527e30796064634b66cdc023bc5966e2c282468f5abef7879fc52986d5bb873a796b077d10e7b374b60309", - "0xa17dafe2484d633fe295f8533662631b0bb93cdb4e7cd6115271f20336f602f7f8b073983cd23115093c7f9891c4eef5", - "0x804acbc149d0334c0b505a8b04f99c455a01592a12f64d1ec3b82b2f053ccc4107e47f418f813d6f400940c7c8700a4a", - "0x965e097a825d8511d095b247554ec736bcb3701ead3ba785bd425cbabd56f4b989764e0965a437fa63e7e16efd991fc0", - "0xb6701675ca27d7a4084f06f89bd61a250b4a292ee0521b2a857c88c32b75f2a70b97f98abce563a25d57555b631844e0", - "0xabbdf65fcbdf7d6551ccd8d6e5edc556f1ecd275ccd87ee2bda8ea577c74615f725aa66e0911e76661a77f5278e0c2b9", - "0xab715ae372c900239a0758a3524e42063afc605b8fb72f884dc82ab9b0ff16715f3fb2fd06f20f15f9e454f73a34e668", - "0xb45f41ea1d25a90af80a8a67c45dea881775fed000538a15edc72e64c7aa435a5e4375dcdedc5c652397c02b0bc61b16", - "0x86f7be9252f8ed9078e642c31a70a09639899f7ffcd7faaf1a039fec8f37e1fa318fba0ed1097f54fc55d79900265478", - "0xa30e5ed4277dd94007d58d5a3dc2f8d3e729d14d33a83d23c44ddfc31c6eac3c6fe5eb13b5b4be81b6230cfd13517163", - "0x87e723d916f5fcda13fab337af80354e8efe6b1c09ae5a8ceeb52df45bfca618eb4bec95fefef3404671fb21e80bf9db", - "0xa521b8a04dc3abd3e9e0454b9a395b3638e5394dc2d60e97fda61b0a1880d1d73a64a4633f3d7acbd379bde113240d03", - "0x851686c79c5403d5f05fbaac4959fcbfdfb51151bec55e10481b3c16e3be019e449907ae782ca154f76a805543d5755d", - "0x8ec1929e746b6c62b0c3fdd8f4e255e5c707e6e0d8d57ff9e409ae2dd6e76fdb50af923749992cf92d1b5f2f770bafbc", - "0x9175f7b6820d47205c9e44f8c684833e1e81da46c1fdf918a4dcafbc3231173f68370d442a20e45f8902bcab76a4e259", - "0xb4f66c698115333b5ac00c9fe09aa9e1e9c943fbb4cce09c7d8a6ed4f030e5d97b48e944fd6d3e69ac70f1ae49d35332", - "0xb958878b875eead61a4416a4597b1c567ddbb1eaaa971033f4a656f01a277822c1f4ea3972045156c2a5a28d159f5ddf", - "0x8188de8ad5258024d0280137a40909d24748137ac7c045dddd2bc794eac8edd5850b9d38f568fa8174b2c0593bb57e96", - "0x91152c7bafce7a0358152221081bc065796fa4736bfc7d78076a0a6845287cde2ee2a2c9b96f500297c0a00410634888", - "0xa5328ab939a2d3bd4c21e5f3894c02986b6590ad551c7734be3f4e70380eb7bc19629e9031b886ce3b4074ee4edee63a", - "0x97c4d49db40e266bcedaacb55edca4e1ebf50294679b271f3a2332c841705089b5ba96ef2064040fa56c36bb1375a8d9", - "0x85cf0514f340f9d865b32415710d7451b9d50342dbf2c99a91a502a9691c24cd3403cb20d84809101cd534408ddf74e8", - "0x950c3d167f59f03f803dcba3f34fe841d40adc31e5be7eefff2103d84e77a7cbe4f14bd9c3dfa51cde71feb3468a9c00", - "0x96a69624e29c0fde3b92caf75a63ac0f3921e483f52e398652f27a1ec4e3cc3202f17af1f66224731bc736a25638d3e4", - "0xaeac4170cf4b967227f66212f25edc76157eb4fb44c84190b520ecc2946470c37da505790e225fd1b0682bef7fc12657", - "0xa94146a04e3662c50c2580ae1dba969cbb3fb0f43a038729c9e8be6ed45860b2c7de74f248dfa50ccdbe2ecaf3f2b201", - "0x917b8e2880e85b8db723631c539992ec42536146e7091d4a3f87d37f051b5da934d84393523814f19962c78e6cb12ef8", - "0x931f140ff8f7de79e399f5cd8503558d566b5c2ab41671724dd38aed08dd378210f01ac8fa9911f3047993dbc10cf8c4", - "0x859eb9b560bc36273694f8ae1a70d25e7f206013597c4855a11328162ba1254bb736f1ae41240c8ec8dea8db035e08f2", - "0xb4ad2cb2c3a3e6ab1e174f2dbfb1787a8544f3c9109215aa6d33265ef269455e3cde9858738b4fe04711a9cf9050e7d4", - "0x8a3b342b87b19c0cdb866afff60317e722013c02dee458ba71e7123edc8b5a9f308c533b9074c7dd0d684948467502d1", - "0x89185ac5cc5ea8f10a1f2a3eb968bb5867376d3cff98ef7560b9a0060206c4046ff7001be10b9e4d7ad0836178eba7e4", - "0x845f48301f25868f6d0f55b678eab1f8458e3321137dba02b4cfbb782cbc09f736a7585bf62f485e06a4e205b54a10b7", - "0x931a6c523d4a66b51efadb7eefadba15bf639d52d1df5026d81fd1734e7f8d5b51b3f815f4370b618747e3e8eb19699c", - "0x8eb3a64fa83dcd8dd2258942aea3f11e9cf8207f2fdd7617507c6dae5ea603f9c89f19d1a75d56eaa74305a1284ce047", - "0x912a5050ed6058221d780235fb0233207c546236316176a104a9761bc332323cf03786dbac196d80a9084790506e0a88", - "0x945fe10ec8dc5e51aa6f8ba7dace6f489449810f664484e572bfe30c2fe6b64229f3c8801e2eb1a9cb92ff3c4428cdf7", - "0xb62383bf99c7822efd659e3ef667efee67956c5150aea57e412cbd6cd470807dfaad65c857fada374c82fcfca2516ad1", - "0xa727a31c45b2970d08a37e169ea578c21484dde15cb11f9c94eaaf3736652619ce9d3a44e7431d50b0e75b658ebbc1da", - "0x97bf54ea9b84b82e4616027bd903ef6152439f1c6a8e1bae6db1d10fdf016af2cac10ff539845833dfd1ddad1403aa8c", - "0xa08cf36437e010e59b2057aedb7192e04b16f1cc66382cdef3490b7ad1544ae51f03e87cba0fe43a275841c247a2a0cf", - "0xacafab9fa28c1a607df2246490b630ddda1ecf0885ad24c2ecb2c2c1b7b9c7de8066714bf5b9b25f61981d08576789ec", - "0x851f0375128d2782586223467d0a595f4c5baa79616622a32f7d6ce1f08af06f8a109bd6527f88d93367dba17be661e8", - "0xa2f1187c2a7cbf776653ff834ed703dd32e68eaf36f0700709be929f4c0ce5fa1d9930d1e3ea2aa01c7a16239e66cb33", - "0xb3721f4a5d24ca112f020cb3f849543bf0e7f84b470fb00126ae80aaaa6f2c208d8359cd82ad9fbafd3ef2ac70656fb2", - "0x98773ac3ce9528c73cfd8e7b95976ce597f67e146357642ac4fb6cb35046f3f39cf6c4a7b5af5c7740dda358aa0d2d08", - "0x92c883a5d820541692af75be1b25dd4a50a4b91f39f367a551a7d5ad6065a26b60d68221a01e4950559717b559c2626a", - "0xb82e46dd25fd1234dad26fbcd8bb5177d7b87d79d362ffb9c2f6a5c16eb2ff324d135996fcd6274d919634597869d772", - "0x82a53ed356ced5e94d77ee2a7f6e63f2ad8240aff2d17c5012cf5d1f18512c88c24793339b565dfbb659bd7c48dcbcd2", - "0x84d20c7859b35a1cae1ff2b486d50822f9e6858b6a1f089ce4c598970e63e7c0f7dfbcb3337845e897a9dedf9d449dd3", - "0x974892e5cf5ee809e9353d00e9cd5253d04826a8989d30cf488528c5dcdcad7650e23b4d228c3eb81f6647d2035a9e02", - "0xb2327854910dbf3d97fe668da5fc507e179c4bc941f39bdd62e8b6035f004449c467240f656417e501f32dee109f0365", - "0x88888f73475613d45d0b441276b1dd55835b69adfb27e26c4186936dae047b85478cca56be8dc06107b89a28f3bbb707", - "0x836ba22e40511feff81a5dace3df54e2c822b55e66874dd1a73929994ec29909ffc2a8e39bfc2d16e316b621eb4a5ec6", - "0xa754cedcccf4165a8d998f326f3f37d2989f92ca36d9da066a153c4aab5a62bb0011896bcbf90f14c18e00488d4123bd", - "0x86c26fa9584314292c4b7d6fe315f65dadd0f811c699e6e45c95a7a4ea4886c57dc5417b67edd78e597d037c7689568e", - "0xb205589648aa49ef56637712490e6867aa3b85b2b31e91437a249fb51bdb31401bff57b865c9e27293b30014b4604246", - "0xafab0843ede582e5a1898ee266235066b94ea378884eaf34919ceaacc0e2738e1074b6ed41e0a1dd9711563e24f0215d", - "0x996ed65fbcab7611eada5bd0fd592d3e44705098b8b1dfba6dcdbdcfa1382fe893fa55270a0df0be0e1938bd71ab997c", - "0x881bc448a5ef8c3756b67ecb1a378a5792525d0a5adb26cc22a36c5df69e14925f67c9cb747a2f7e5f86ba1435509d7c", - "0xb219303c02c9015c6a9a737b35fb38578ab6b85194950a0695f7d521206e1e12956cd010d4d6c3bc3fafd6415845d5d1", - "0x91748829bbd005d2ec37fc36fee97adaccb015208b74d2f89faa2e4295679f7685298f6a94b42d93c75ca9d256487427", - "0xa41d6fd33b9864ebc404d10a07b82ba9d733e904875f75526d9a1f1c1c08b27160dcdb9023c5d99b8ff8a3461d57281f", - "0xb68978d39c97d34f2b2fea61174e05e05e6e49cde587e818b584201cf59b7096cf1807b68f315119c6db8d6110b28a9f", - "0xb64e66cec798022d64ce52477475d27ea7340817fe7f570617f58c3a9c74071d7ea6b54743d4f520b62aecad9a3a6620", - "0x87b2b9e1c1786b7824f239a857024780a1457e51c64599b858118885833fb87a17d408bc09dcc0607d15ec1e53683a74", - "0x9814799bac07dab4f0c934cc3c051676ca13abd49cf8d4739864e5bb9f2a8474897695113f49239f28832a8658332846", - "0x806931a1526a843a9c2045943d616a8102b02b1f219535a1f1fbda659a1244f1bfead52ca7f1851ff8a97169b91c9ec0", - "0xb8678249595a9641c6404c35f89745b93d8e7b34d9d44da933a1b2f1606972624c5108f1c04eb42e454d0509f441ed9e", - "0x81426714851741045a4332eb32b6dfe6422a4a2e75b094fb7c3f37da85648c47ee8af1e54ba26f4e1b57ebe32d0e8392", - "0xb7a1875ea3f119fe0429fd9068548f65cf2869f8519dbbce0b143e66127cb618c81d7578e8391d676b2f3963e9d87f43", - "0x872220a803ea0c6294cdc55aceea42cfacfd7a482982bcb90c0361c351a900c46736a890609cd78f02fb5c8cc21fa04b", - "0x974f0380197b68205ff4bb2c9efe5626add52c0ad9441d7b83e6e59ddb2ed93ad4e9bbdbf33b3e0a206ed97e114ea0f2", - "0xa840f2d9a74fca343aedb32ac970a30cbb38991f010d015dc76eb38c5bb0bfe97dd8951de925a692057262e28f2b4e9d", - "0xb0913c3ce61f12f9fdc4be3366ed514c3efc438f82fc58c4de60fe76098fbc033a580ec6e4531b9799611c89a8063a66", - "0xa0180d533eee93b070dac618be1496f653a9a0e4e3455b58752bf1703ec68d0be33ec0b786f9431ef4208574b0ad316e", - "0xa4a6b871bc95d3aa57bed90e14a0a1dda6e7b92b7ae50e364593ce6773fbf736672b1f4c44e383af4c3cc33e017a545a", - "0xa3f44cf19fe52bacc4f911cab435a9accbe137bdbe05d34bdd8951531eb20b41d17e3540e8d81e6b3eea92c744562ee5", - "0xae6b6d0ff3b30ff0b7f9984ef741cba27ffb70d558de78b897199d586cf60622ec2d8a9d841712fe719cf0f97628842c", - "0x87abf72f98c81d6d3a57ab1e224fe4b502ab0d8090d8abc71791271550b721c220d4e2e7da3be94a20c0e63d98e39a50", - "0xb2f73ebdfe7133af57353052f4599776e16862905e64d97e1020c4bb84132e476d1ab79a9fb71611410f3f9d56c95433", - "0xae1a928253af2b210d31e1b64c765fcbd20a96b8d53823a6b9b6e7fc62249abf4a66c6a6aedb0b687e7384af9a845e0d", - "0x99c54398627833ca1435718154de171a47c709e4d5c58589fdabe62e72f2a7a11ae561bc31d7cbe92df4aff23e08cd0e", - "0x8a1310bbf1a31fae18189479f470977d324dec6518a5d374ab2ffcc8f64412fb765df57d2ddf69b9a6efaeb2b4c723b8", - "0x898312c6c0d3d3438229b19a8a233eca8f62f680c2897f4dd9bbcacde32c5996d56ac0e63e3e9360158761185491ce93", - "0x81b3f965815b97bc6988d945496a51e4a4d8582679c22d138f3d3bd467ed1f59545da2d66e7b4c2e0373628ae2682686", - "0xb9aca91c6e6f4199beb6976b28e0e35e36e8752618468d436b1cf00d8d23538d0747920e5b2c31f71e34dfe4d5c86a0d", - "0xb908f4aa18293295b8cacfda8f3ea731bc791074902c554764c603ab9a1de1bbc72654fd826bffc632d95ce9f79c27d9", - "0xa7316ae1baf4b1196961d53be7fe36535499287aba9bc5f3bed4323039b4121b65bb0bd15a14c1b9cd8b65ede3566da2", - "0x815e39208f205c5fac25ac9988c14a62ab01657c7737a24472d17b0e765644bc2cbb7ff1e8ea169b8b0b17b6996c4704", - "0x89a451d2b740cdaa83ccaa9efb4d0ff5822140783979a4fee89eda68329a08c018a75d58bd9325bdc648b0d08340b944", - "0x8cd08f768438c76bae6bee1809dd7be38ec42e49eb6a4d6862db7698f338bf6b4b409088e4f3d1c5bee430295b12a71f", - "0xa4bd8c312103a4bfeb25b0cfffec7a1c15e6e6513b35af685286333c1dce818ffeb52826f2f5bada6b67d109c4ab709e", - "0x93afbef5382d89fa539ca527f3e9b4a8e27ab69fd5d5023962cc6d8932b33cb4dfc5f14343e1a3749bfd5e100c9924e5", - "0x8d8e69d046992ec9ff14f21840809166cae8e0e9e7c8f14fb29daf163b05abe6611daa4010960e1141c5ab24373fb58e", - "0x96f8e72e96ba673c9265e9cc312f6b9c3b931745fc62d2444d59404bb08e5fb02ddb60715181feb9971cbd954526a616", - "0x8d444c2b8e4d0baadb79e3147a2ee20f1bfe30d72eb9a02f15d632185fb8f4e8c3116066f7de1ebfe38577aaccacb927", - "0x971410c0b10e3698f4f64148b3d2148fc6a4a22217fcf4253583530a9d6fbec77e2cf6f7bb5e819120a29c44653de3fc", - "0x99e7e1857bd5ee57007b7b99494b1f1c6bf1b0abd70c054770427d59a3c48eda71b7de7a0d7fcf6084a454469a439b41", - "0x8c8a4cd864894f7a870f35b242b01d17133cb5dfdf2e8007cd5f1753decc0d1fd41be04e1e724df89f1d727e760fdb15", - "0x890a24328bdeaaadf901b120497d1efa17d798f6f4406661e46ecdc64951f9d123d724ab1b2b49e0e9a10d532dd6f06c", - "0xa7cbe1f42981c9518608569a133b0b449e9d67c742d62f0d3358112c97e65ee3f08ec0ff4894ce538b64e134d168e5c8", - "0x87c976dea77b3b750c3a50847f25b851af95afbaad635f9bb9f7a6ba8f0c4faeb099dd777cf7eac41072a526474cb594", - "0x9882aa5e9bcc4ea2dd3de4bb5a0878a672bea924b50c58ae077563b6df0268910a60e969d3da1694ae7394ad0d9acd3d", - "0x90d35ce677327c461fb5dcb032202e851af1d205e9d21a34ed2b95635f13f8fb8dfa470ea202ccfa4b08140d0cf1d636", - "0xb3b4cbb521cce2b681e45e30a4d22078267e97ccdbdc611b2c9719705650dd87e0ca6e80cf2e174f8f8160be94232c36", - "0x95892b00478e6b27ed09efe23a2092c08e691b4120336109d51e24efbf8aba31d59abf3cf55c0cdab1c210670b9743ba", - "0x8643018957fb8ef752673ad73102d0b928796c6496e22f47b6454c9ed5df784306f4908641ae23695db46ebfcfb0b62b", - "0xb166ce57669bf0543019ecf832d85164c551c3a3a66c05b17874bccd5d0ae87245925d6f8edc62ac13dbd5db265823a2", - "0x89fb4800ce4b6c5900d58f1a216ad77a170ea186f3aa0e355840aeedcf374e92a15ae442800c9d60334544be020b17a4", - "0x8c65e586215a97bf11ffc591bce5147b4e20750e82486cc868070c7736c3de697debc1f335674aef24b7afdd41922d93", - "0x90f68ce0c97d2661d3df1040ce9c4fa106661a719e97c7b2d7c96f0a958930c57d6b78d823a2d41910261ae1f10e7b0e", - "0xadda85e1287371ccbe752aa2a3c1d5285595027ba4a47b67baf7b105a22fb8548fa2b5b3eb93ca6850ecc3995f76d3dd", - "0xb26535d218f48d6c846828f028c5b733594ce01186e22e412dd4f4a45b3d87d2ac1bfe5d54c987e4e8aaddeb86366d7d", - "0xa081bd86962ea3d4fd13df6481f3aeaabdd7ceae66f7bbb913e601131f95d016cf147d045253d28457a28b56f15643c8", - "0xb3d852cef4c8b4c7a694edbf6f0e103f3ae7f046a45945c77a1a85ec8dad3423636a89058fafc6628aabff4dbb95c2ba", - "0xb424ffc94e06e6addc90a6324e0482814229b5902e2a266d0c2d716e40651b952bc9f00d7dad9b6050377a70a72c7f24", - "0xb2cafd908cae0ca22eaa2d9a96175744897a20eb7b0a6d43b0098cb1c69e3cb55373888201e4ed32816655eb7d8a3dd7", - "0xb61177ecf1ae9d7e7852d98cbf6080d9f1e33c90f2436720b4ea4690437e8c7850c3754768fc1312cb4e838d855c5ccc", - "0x81b486644e1ae22cf0ba3a37e1df34dc186c82a99ab35ad6f475c37babdea574ddfbe5811d4aa020581292a793d66bd2", - "0x97ae848a823ea7a99f91834e537fb47208f616c08fe32c8f8fe06bd35c9b638698c513265d0b4de9e572a2f9692b98e2", - "0x81b8fef4ea5d399c65e78f40e47c559ada86d890777c549ce362e7ab81b3bfb00d5ff4ae4ee30fd7bda7ee90d28f85d8", - "0xaada6912cc748923ea40bf01922c06c84bc81b2ab0bb3664a0579b646f03d47ce88de733ac7f2cb9be4a8200584cdb71", - "0x89b48b9c79332f8f58eac9100ada5bb7decdc4b1555c5d383e2c1ce447efb0ebdff9c50bb52bc3042107f33a61ab2520", - "0xa32ecca8b870b2b6e9d10b5c1d8f925b3d629d271febad65abed316262bb283c60cade0e91047fbd0fac53ac6db372b9", - "0xb829cd1f13409e3573a8e109c9541b0a9546e98b6c879a11152b5564477ada4d8cb4b3079040e05a5cb63d75ef11eaab", - "0x91f3b100baa19e960b170fe9e03b799faac5b9c6f305c56115940bf81f6e64dcb9cda77e8de70ed73a21c0e8a74acc58", - "0xb25b5e872c84065aee04822bbcb4f3bdff57fbd7cea314c383765cc387786c17de3d5bb3de3ae3314bdede14542bfac6", - "0xa89bea9eca1f5a17a3efccfa4987d8e5366b0dba70ef1fef43aaea83c528428d1498c8b056ac27f16e8946ee93f7028e", - "0x818a1f7b0b8b06ea0514d6b4a0296da4f69cb18ac8e48c5579e6ba2880b06215fcbe81672566b8b94fcc3c0cadecb191", - "0x98dd6e6b4b4d63d9aa7464a2be08ae8babac4da7716a3f109340bc9187d59c6ca0c88e6877a67c65096f64a3ced22a4b", - "0xa2069c5bac4f6590042aefb37570cc20908b0df9d0130180f565ed8a53b4ea476a274de993561fb4d009f529fe7aa1cd", - "0x860b7ec2410f033a7b0c5ca08f88a0ad29f951a5ebd5383408a84367e92f1bd33bee3b87adef2466b7e33b47daabf30e", - "0xa408855a8414102c3cb49f47dda104edf0887e414723da59b6b6537ada7433529f6a4d1a4ad4fe311c279213cdd59356", - "0x8ca0d81dcb43b89a4c6742747d29598ede83a185a8301d78c6e7f1c02c938441360a1ab62a5e571e3eb16fe17131cbc0", - "0xaf7875a495cb4201cdb26e23b7c76492f47f8dd4c81251de2397d73d4c8d5f419cdbad69ba88ef0dc3552e460dbcd22e", - "0x80e901e433dca34f3d386f39b975e97f7fc16c7f692808221fb2ee60c1aaa8db079cc48c7d72fd548aaf8dde8d0b8f05", - "0xb6062319e13926416e57a0ffc65668bfa667e708a4e3f5cb26d8a6a32072f5b790d628052d5946c5068dd17cf4a81df8", - "0x90094b569e8975f8799863798912dbf89b12d2c2d62b3e5fac7efc245436fcd33af23b8c509ae28c6591d3f020966e06", - "0xa504f72d3d06a0c9b188a1035c7c6d80047451c378b6c5b2ffa1f8cecdb64871cb6440afb296974c0a528e5e563061a1", - "0x959061c4924e133a419e76e000e7c62204093576ff733ce0b8ae656ec6045ef94c5a1f3c934fb76fa9188c5eb397a548", - "0xa8b9d0b58de38cb86cb88fb039a7c4c0c79e9f07f03954af29013baa18fc2633883f8f9ca847209c61a8da378f9075d3", - "0xb16d8341da4ff003ed6d1bbdb3be4e35654a77277341fe604b4c4e4a1cb95e61362094fb3d20ab8482ea14661c8b9852", - "0x8ea4ca202e3aed58081a208a74b912d1a17f7b99a9aa836cfaa689a4a6aa9d9fbfe48425cad53b972000f23940db4c5c", - "0x96a372f55e9a25652db144ec077f17acc1be6aa8b4891e408f1909100cd62644a1c0296a3ddc38cd63ef46bef4e08462", - "0x87df40018ab3a47c3782e053dbd020f199fda791f3109253334a71be4159f893a197a494de8f94d6f09efa5811a99977", - "0xaff82d2ea6b3ad28d0ca1999a4b390641d727689dc2df6829a53e57d4f6418196f63a18495caf19d31fc23fdff26d5e2", - "0x9091053c4a18a22d13ad309313b6d2133a96df10fe167f96ec367f9b8c789ecca7667f47d486fc5ba8531323b9f035ac", - "0xa4842090515a1faccc3d8cadbb234b7024254eba5fdfcef0d15265c7cec9dc8727c496ad4e46565d1f08504c77e511d2", - "0xb1d8a37b1a97883d5804d0d2adaa8dbf0c2d334ef4b5095170b19613fb05e9c648484093d0c70d545cf9b043b449c707", - "0xb1ea40f3dd1c3d437072f8adf02c32024f32488dd59389d1c3dfe78aca3df0bab7767f6ded5943cc10f50555da6092f5", - "0xad219c6a8149f10391452892b65a3268743baa7402736f810a35d56cdfed83d2172b03f15c205f0dc5446baf855907a5", - "0xafe44c3e1373df9fc53a440807fa6af8ebc53f705e8ee44a162891684970b04fb55d60bc2595626b020532cb455ee868", - "0x859ae154b017eae9be9da5c02d151de747cc23094d8f96d5db7d397e529b12fb55666f55e846e2bbe5e6f5b59c9d8b05", - "0x8aa01354697de23e890fe54869cd3ec371f1be32064616ca3a556d3019541ba8e00d683f1396ca08e48988f7f7df5de4", - "0xb8f682487460b9d825302c40a7d6dd0353ff43bf24cd8807cdfa46c043e3f5a7db182b27a8350b28e91888802a015af4", - "0xb6d4d6c3ac40f8976b50be271cf64539eb66dc5d5b7cec06804dfe486d1e386037b01271cf81ef96dba5ea98a35a4b43", - "0x9385a2fd1cd3549b0056af53f9e4a6c2dfcd229801ffda266610118ade9a568b33e75b6964e52fcc49c8e3b900e1e380", - "0x98f4aa0e4ef039786cbd569536204e02b0b1338568d1d22bb5bc47b5e0633fb7ffe1da93eb9d825b40b9b7f291f84d51", - "0xb7b3460cf706dc270a773c66d50b949dabad07075021d373c41fbb56228355324d120703e523ea3f345ef7249bfff99d", - "0x81b826255f95201987513d7987cdc0ca0529524d0e043b315a47583136dbada23a114d50d885bb3f855fa8313eff801a", - "0xafdc6c35161645a14b54f7b7a799910e2e07c8a5efe1827031a2eecd5d9263b3baa367fdd867360fabc41e85ab687e74", - "0x817b361ce582153f2952f3042e235ee2d229e5a6b51c3d3da7bbe840b5c6ec2f01446125045848d15fd77dc46c8a8fe2", - "0xaeb599265398af6e5613297d97d2b70222534590fcbd534d68b24a0289b6366ac8188b753f6fd1000ee73ef44f8fb7af", - "0xa5a9e528b606557be64460c1ad302a43e741357827b92ddc50766a7e6287740fc23bd528d9faf23345ce8bff527d5bc7", - "0xa8d3b3b438d5f75efaae6ce7b67c2212899ece5b5bdc9bac655e271fd1846ea8560e646fdbded3d9363eefe29473d80d", - "0x984c7976d557e2f591e779c2885f5033da6f90d63a898d515b5da3adbffa526764cd8eb679b771573fdf7eed82c594ec", - "0x8ac748689cc3280e064807e68e27e234609e3cc87cb011f172204e1865ad7fdc78bec1672bd6e6fddcf4e7902b0f38bf", - "0x877bb392059540b1c8f45917254b8cc34fb7e423952bdc927e0a1622efec4113fa88988686b48134eb67ddebcb7c3ef4", - "0xac04b154ccd307ca20428091585e00121b61bae37b22d5d2a1565bc1134be3c81ccf3715fffebe90744164e5091b3d9a", - "0x90745c04278c3a47ceea491d9dc70a21a99d52648149b1ab623b5396b7d968fd3c4d1a2d08fc5638e8790463e0cf934e", - "0x80bf26ca7301e370f101cc69e7921e187cf5315b484fc80a872dec28bb65886569611a939958f4a3d2d3da4350011298", - "0x87cbf4d6f0c06cc5f24e0f173a5f2f9bf2083a619dcce69a8347c1a6cd1d03325544610f2984eb87a13241e6ab9a22b7", - "0x8909368817a515789ff4d19ed26afafa5729a24b303a368ea945a9287bc9facec9e1c8af19cbec8dab4acbb6a6ddf6c7", - "0xad8d2f82b08e0990dfd6b09fd54db3a30fd70aad218275550f173fd862347e1258a4716ca2bf4c40e4963850b2277eab", - "0xa9467ceacf9337cae4f2c7eeb3e03752ac7d77692b07d5e5d75c438fbe7dc2029ff84f7759372a0ddfa953b4ec7e9e38", - "0xa5feb7669e84b977cb1a50ff3a39c28f7ad1ecc33a893fdf1ddae7a0d8a4c5f6fbaff25cc56631b708af038a961f3b55", - "0x8f2e1fa07963ba18db890b44c3b9ae7f8992b702a5148679df69e4d9d4b1c082b2bd2ae53f96a4fe24b54f3dc1588f17", - "0x896778f35cbecb43f001277c306e38a9c637275101f1a09546f87378b10ccc025644bc650b3b6c36e4fd0c09fbb3df35", - "0x91dc702778176a4d089dc65502d703752dd9a766f125ffef26bdc38fe4abcae07cdea14102c3448d10f8dd6c852ee720", - "0xa5df3004cec6b68b937cadded0dd2f48bd3203a903a3e1c22498c1193f4567659ecaaf3deb7ed7cf43796da9188f5dc6", - "0xb18b4c8ffcb8599c24d9851abf8ee43047cbd4c9074c9cfbf88376a170da4554978988f550afde8a45306ca32713c204", - "0x8370bc38c84da04d236e3c5a6c063e1db6613dcc4b47239d23efdcb0cf86846955b60da3e50f17b17cd3f7e0c29302d9", - "0xab7d6bb6be10aa52ef43abbe90945e78e488561afb959dc2fe768f8fd660d267c7203a2b7bdfa1b44cd07898f4849e06", - "0x965c96047d82d76ec2cfe5035fd58d483cd2cb7f65c728ab3049562c5d1943096d6a5014c05babc697d79c07907cf284", - "0x9614f7006aef6f0478ebd37fbf17276fe48db877394590e348c724059f07c3d1da80d357120d3063cd2b2bc56c58d9d6", - "0x819c7b2a1a4bb4915b434b40a4e86dd7863ea85177b47a759bc8ecd8017f78d643982e8a091ee9a9e582f2b0208725a5", - "0x8e159a185b5790a3ed444b6daab45f430f72f4ac4026750cbd5c7cd7947b5e00f2b10eaaf5aadf8d23054c5b29245546", - "0xb48cb6f6c0aaea04833e10d735b67607846158b6663da380ef01c5bca3c9d537611716867dc2259883e5bc9daed57473", - "0x8b48ce8b5ab76b7d662c29d0f874f5eec178baf3f14221bffd5d20e952f54f3ed053182a486da1d1f400e0acef58f673", - "0xb6fd3cba177bfbcb5e7ebb1e3c1967cad5848c09c615ba2a6c277908f8b1f4f1ac5f184c33f2a401e8bdafcaed48bb88", - "0xabd8f44c4a447de8fde1c119f4fd43c75b4cc99de9c817a019d219d4b2ad2a73b60606c27e36e9856a86bf03e7fc861f", - "0xaf9f7e8b3e9e8599c7e355433c503a05171900a5754200520fd2afed072305be0e4aebb9764525d2c37a5a7eede72025", - "0xa0960a58bd2681804edd7684793e3cbb0e20d1d4bd8721b192baf9aee97266be14c4ee8b3a3715845dca157ba2fb2c1d", - "0x949a37213209adfbfa4e67c7bad591c128352efd9b881c1202cf526bf4f657140ef213acf0efeb827a0c51a1f18809c4", - "0x9192fae84a2a256f69a5e4a968d673bebf14ea9a2c3953f69fe0416f7b0fafa5166f3e4588d281f00d6deac1b6ec08bc", - "0xb1a249662f34a88d2798eae20c096268d19f1769d94879b8f1aa40a37b3764349b8e6ab970558436a88a5aa5c37e150d", - "0xaea87086dcd6de0b92886b3da0813ff271a7107ab1a3cb7021b85172c1e816a84dbb1a8fdb47e8a8eb5e6fcddd5b919a", - "0xa586b5078b3f113eec9f074430bcf9aabe4e82752e5b421c6e31d1c2a911512e34154bf8143b5197e820c5af42aa8ac7", - "0xa6eda122e400a6600f025daa383685a10f72f62317a621698bd0106b331077b05ac1afc68ece7a2e285c54a366921a3c", - "0x8875e9ba654ad7b1d57ede84e2b702600416d40f7475fe2df25dd1b95c0178a227ee187547898e5b9d1ce8ce9ebd15c9", - "0xaf2cb289f8c75f4ddae9e3ef9c1977fe4d4d513e411777b03b996f5baa372eb995b5ca96255fad9ace776168806ecc42", - "0x8d24c465d26bd93290f45ef035bb6dde4530d9d7d051baf583b1f8b98e9886de262c88b5709084710cffa7c767b4c27d", - "0x8cf35b1b28a7726645971805170392d522f5e7e6cb94157fe9c122a987051c1c90abe3c5bdb957ef97b1c45dd9bba05c", - "0x93e2bbd82a3cb872cea663f9248b21d4541d981f3f8d5af80a43920db5194857f69e2884753f6ed03b6d748dbfb33620", - "0x8b774b97657db654ebdafce3654d645f849203452e876e49dad7af562491cb6531bd056f51cb5b2e8f0a99e69bd8566b", - "0xb5333c49d3e1c4c52f70f3a52f0ad77165bed6ad9dcbfaf1364e7a8a0f24570e85a218e4c2193f63d58a7dd975ceb7a5", - "0xb4a34c443e4fdaab8e69fcda1fce5e72eaa50cf968f5d3d19084d049c5e005d63ab6e1d63dee038317da36f50ffb6b74", - "0x824a224009c6848b92d6e1c96e77cb913fee098aaac810e2c39a0e64d5adb058e626d6a99be58593d921198edd48b19c", - "0xa86f1fdd2e1ba11ebda82411b75536fc0c7d2cdb99424e0896d7db6cae0743ee9349ffa5bff8a8995e011337fa735a9d", - "0xb406b5b89b8bed7221628b0b24eb23b91f548e9079a3abd18be2ed49baf38536a2c1ec61ab1ddc17928f14b006623e7b", - "0x8a7ea88d1f7420e2aaf06ee90efa4af798e2ec7cd297aacd44141471ed500107fdd93bd43b6de540314ef576646a7535", - "0xa7a8c071e68bbae9aca110394cf56daad89404dff3e91ea3440670cd3d0423b67905e32b1ba7218fd4f24d2f8bd86ce7", - "0xb959830f152e4d31c357be1ded5782aed5d6970e823cf8809434cf4fddd364963bc7cfda15c8f6b53eda16ab20ca3451", - "0xb59232c8396c418238807ce07e0d248ad2045289e032678b811cc52730f99b480eb76f6adf985e6d5e38331d4bb2b9d5", - "0xa14092fddecc1df18847ab659f6cf7c8603769a4e96fbe386d8303b225cebbbe8f61d6ab3dca08e3ed027e7e39f2641f", - "0x941cb0632acd395439f615c6b4b7da9ed5abf39700a8f6e6f3d3b87a58a1a7dbb2478a6c9ff1990637ada7f7d883f103", - "0x951b8805ecb46c68101078847737e579206f2029e24b071bae6013e9dde8efa22bce28aa72c71708caf4e37f9789a803", - "0xb2cbf22e53f6535fa950dd8de4aa6a85e72784dd1b800c7f31ec5030709d93595768748785ff2dd196fbedf3b53cd9d7", - "0x8d84ea3a7eafb014b6bd6d57b02cab5ac3533aa7be4b86d2c5d53ce2d281304409071100d508ed276f09df81db9080ea", - "0xa2204b60836cba8bf29acd33709e6424226ae4d789ef6b280df8a62e30d940bc9f958ff44b5590d12fa99fcde2a4a7a9", - "0x86692c58214f326c70eb2aaf2d8b26eae66fb624f143a3c144fd00f0249e30e0c832733a7822fac05c8fe74293768ace", - "0xb1cb3d64eb5b9ca0e01211128f990506fba602cd1417da02237205aa42879ae2a6457386da5f06434bcb757f745f701d", - "0xb3eb4290a53d5ff9b4596e4854516f05283f2c9f616ec928a0934b81c61afc351835f7eca66704a18a8b6695571adb30", - "0xb0bfb1d44b039d067d7e0e2621e7c4444a648bce4231a6245179a58cd99758ec8c9e3f261d0adb22f9f1551fceb13e4a", - "0xa29320f71a9e23115672ea2b611764fe60df0374e0d3ff83237d78032e69c591a4bdec514e8b34f4b3aeb98181153081", - "0x8a6abe9c8a048002b2ff34154a02c2f13fc6dbae928da47c77f3e5b553ea93d8f763821a6ead3c6069677870fdff7ff3", - "0xb73ab66a62f427e1a5e315239a2e823e2a43550d245cff243c2799eb2e4701fabb7d5f9ce74a601b5ee65f6555dacf64", - "0xb64858e98b9c10de8c9264b841b87e7396ba1da52f0f25029339ca1d13f7f9d97f4de008cfe12a1e27b0a6b0f2c9e1ab", - "0x807d2440d1f79a03f7163f5669021f3518094881f190cb02922eb4e9b17312da5e729316fe7ba9bfffc21ed247b033cb", - "0xa7f06458d47ebe932c2af053823433a8a06061c48f44314fad8c34846261c8c3f7f63d585a7930937327ad7d7ca31a6f", - "0x82ac2215eba9352b37eb8980f03374f5e0a2f439c0508daa7a32cdce398dde2a600e65a36795a4f5cc95bbcf49b01936", - "0xa1882c83a2f946d54d74a008eac4aed70664db969e6799b142e0d0465e5662ba0d224a1cc33be339438d69bdad446ff6", - "0x8009776f7a34a3c8779e21511fa409b0c5a38e172d1331acc29a16114e002f5f2f001381adb5fb3427a100752d775114", - "0xb24441019af4a0df2dc68e3a736f358da0fd930c288398a18bb5a8d9a1e98ea376395f19d8e03a5f020b83fcb709f1af", - "0xac72b4de3920c4f3c9b8ea90035cd7ed74d34b79e79aab392f057c3e992ebe79050cc1c6ccf87120e4162b29419147de", - "0x973e75577cd2a131a0bd568fd44e43554ac5a9ea3bf10f02d1ad3ac6ce9dc7a8a7ea93aacf3325f7d252d094a0de1376", - "0x98a114de2a86f62c86862de37c328bf6a7fccff4d45a124addbe0eb64debe365409fcb72ce763f2a75030e1ff4060c64", - "0xaff753e1dd4707f1a359eaec06ebef1903242889a2cb705d59dd78a79eb5b894731f5a91547479506145ca5768877dec", - "0xb856e4234858b5aa515de843e8bd4141c15a4cc02c51640e98a8aaa1e40344f1ff8ef7c3b913ea2ae7411713daa558d2", - "0x863525eb2f8147a6d1d0d4304881795bfed348913cd7f38d815d929a426788b69e41f022dba5fdcaf56c85720e37fefe", - "0xa14ad76b145a6de2e0f8d4f615288c1512701a7b3010eb8a95941a2171bc23561e9c643764a08c4599040a3b4f5e936a", - "0xa18bfc66f6139dcb0485a193104fec2e7d52043837a4c0cadb95743e229712a05cf9ce4ccb482f36ff1ce021e04b574a", - "0x991c8e6678077d6e5f5733267c1819d8f7594e3b2c468b86a5c6346495a50701b1b05967e9590c15cef2f72bc10a38f9", - "0xa034e7f9b547b047c99b99a0dd45509b0ac520d09130519174611de5bcdb9998259e1543470b74dcd112d0305c058bad", - "0x95ffe0d02317b5c6d5bfddbcec7f3fdfb257b26ad1783bb5634d983012e2ea1c6b9778009e1b6d10564198562f849ac0", - "0xb3db442aa4adb33577583b2a4ad743f41efe0e1f87bfc66091d1d975333ffc00b4afc43057bcb88a7d68b0c9695d38dd", - "0xad2e97d10d7c53d231619e3f2e8155a27ea4f2fb3c0cecf5c7f14f4cfcdd21f62ea46d843b21df748b2892131633fed2", - "0x905d7aad6d3b56bad48694b6b20b27e370ebca8b91d0821e48e2f9cad39910c26cc11c77c266894db3d470485a63ed11", - "0x99bfadefca796ce6af04ede65ba5ef5bf683ff7e2852bb9c406fda77b95ef382289853dfe4d933525071e4cab8ce3936", - "0x94d9905ed4ef92107d0adb9ea38f085a2a24b8f792108bec702d747c215b1f14aafd486ea0c07ed42602b12d8f602b93", - "0xa78dce23ca09dda2d5e7fe923290062546825286d624de35ac5756b6c8ae030e211f4f9c9c8d18a924f5880e3b383d1f", - "0xabce9e2128ff51fa17e73d93e63d7134859b2f328eedbcefb337c39e752d6750d9cffe6abfcd359c135dc5a12018827b", - "0xa9ea7d91e8a3524acb3182bedd7e1614d37b48f8eb2d8f677eb682d38408b8d512786d8bb65811f4d96788b9378e59b3", - "0x912c9f804fb57dd1928f8274be58b42618f589fc72a7e5b6cb4d4b5d78c547f80737cdd77ebe5d2b71eaf60b8fd2b663", - "0xb7227ec9a62d5538974547f717fdd554ab522d8782667fc3e9962e9c79a21134ef168371bf3b67e28d0964e92cf44028", - "0x89440a781c812a19c758172bf722139598023ed0425374fbb0d91f33be7b7f62a36d7aa34696c4fb0da533bd5dd41532", - "0xb31e4a9792d6e9c625c95aa3c0cd3519410dec07940afab820ef9f63017415d237a47f957d0b591b6de399ffc2a8a893", - "0xa66ec47393df2693be161daaa88be0cf07b430c709ca97246d10a6080ae79db55c9e206b69a61f52512b868ba543e96b", - "0x90ca425dee74cc6a7e8eb1755cf9b7b76ba2a36ab851333b0fb7b35e8e6e189702456f2781ad87b4215993d62230ff4f", - "0x88b64741f93a2ae5d7b90b22a5e83c9d56bcee5c6bfcedb86f212acc776cc3ebd0b62cc025f596cd8db4f4b6a7aeebab", - "0xa1b6c7d2358bb201b42264f8fbebaa242ef105450bab21b4a2f16f368048c16ad1f3695841787eb33a0192f1f6b595eb", - "0x8a932f1cd227ceb18389791ed9ea1ff26571715ed1ab56601a994795713a8f7f031d1e8472ec3eb665b7bfbbca8ca623", - "0x8bb2e34a2bf77f9f657dfc51ff296a6279a4d7d15860924f72b184fb7d5680320c7769954b9dac73c4bfe9c698e65e58", - "0xaf54e7367891c09f2cea44cc7d908d37d058162ec40059d32ded3983a4cabfe5057953878cf23bfad5292dbd0e03c0e1", - "0x8a202532b9205385cf79f0299ddcb3156fd9fab09f9197bce762b5623f75c72ab1d74334ee6f0d289007befe222bf588", - "0x83bd0f5896eaad58cfa7c88fc5ed505cd223f815dcfe93881b7b696cdd08b8b5ede03ea5b98e195c1a99c74ac5394c1b", - "0xb4a84d9940e58e3b4f804e4dd506f8c242579cfa19323c6e59047e5a1e35150699a2fab2f4862dba2f0ee4ed1d8970f8", - "0x8c9ec477d057abebc2e2f6df5c4356a4f565bde09f499a131967d803d4bf36940ca2ed9d4a72adbe0a4a8b83fc686176", - "0x8598f43c32623fd5b563d1ec8048ffc36db3d7f9b3a784299811687976f64b60585b2a2707050a3c36523b75d1e26716", - "0xb55eb07014fe5ad3e5c9359259733945799e7429435d9bf5c72b2e0418776e329379433e17206f9f0a892d702a342917", - "0xa5ed942eda7b36a3b0f516fafd43d9133986e4c623b14c0f6405db04e29c2d0f22f1c588150f670dbb501edda6e6dd4b", - "0x92b6abb28cefab2e332c41c98bfa53d065b7d262638389603a43f4431e6caf837b986254c71f7cdacf4d6cc4064b0195", - "0xb01806178a28cc00d1561db03721eef6f6539676d93dd1fa76a13b42a31d38797e99b1848de92fd11821a342b04f3f72", - "0xa2f10303437acfbb5912e186bbff1c15b27ed194c02cbc1c5b482b0b732c41fa809136e8e314e26b5bfe57690fe3b250", - "0x9990207fcc711102e7e941b3ac105547a3e7301390e84f03086c99c6d3e14efff3a2e2b06e26227f496d88d5cdaa3af1", - "0xb903cdb0c2fd578612398c30fe76d435cd1c2bab755478761244abb1e18ba8506fd9c95b326422affbcaf237309959d7", - "0x99e0c12cae23f244f551d649302aac29bfdeb2c7b95578c591f512ad7ac562bd47e7c7317ac9bac52c9ea246617bdb48", - "0xb996d267ab5149c1c06168ee41e403be83f99c385be118928d6e2c042a782de0659d4d837f0c58b26df0ce22049a5836", - "0x989001b8414743765282f7e9517e4b8983a929341b8971d7dd8a87d246f6c8ba5e550c983566ddd932c22948f4fa5402", - "0xa0b006a2c9124375364b8fc5ddb543a7468fa6d321ea046d0fd2bfdaef79e5e3600b3d56190733491ca499add1298c7f", - "0x80881d6f3ee507089b7dfb847fc53dd443d4384ef6fce878d07d9b4a1171eefea98242580e8a6a69664699f31e675cfb", - "0xadc48ef53d88b9d70409ed89cc3be592c4bd5eb65d9b1b28f2167dc4b12406889c00f2465c554f3aff673debc2997ccf", - "0xa62f5d9f167b9f4a4aab40d9cd8c8a48c519f64a1985823e20e233191b037c02e511b0280487112a9f8b1f1503b02db7", - "0xb89aa2d4fb345a1d21133b0bd87f2326eb3285bd4da78b62174bf43d30a36340e4217dbe233afb925ab59e74c90fccf0", - "0x932ba22acdd2f9d9494da90958bf39d8793af22417647d2082d2c3e6a5e17a2d14b0c096139fa8fa3f03967ca2f84963", - "0xb67b107e71d96de1488b4154da83919d990502601c719e89feabe779049ddf7e4fb7e146eb05e754b70bbead4449efb1", - "0x84509de1b8dc35aa2966d8a48501f725d59b4c65f3abf314b2009b9a573365ae3163c1f276708c66af17de180aae0868", - "0x849153fe837a33fcb32c5fa6722c2db9753e984867c112a364eb880d87467782142d1c53a74b41df1dec7e900c877e1f", - "0x903d05c73ae043b69b18e980a058ce2254d008647a8d951175b9c47984164b34fc857108dcc29ad9df0806d7e90405f4", - "0xa6b05917ac32c0b0eeea18f1ef3af5343778c543592078fdf6a1b47165013e2676bfe6a592a24efab9d49c4bd92b8fc0", - "0x8648482f6947a5a8d892a39f098160aae1a648cb93e7724ea9e91b0d1a4f4150b91481f6e67d3bf29ff9d65ba4fa61a8", - "0xa6ecaabc38895013297ae020686f04ea739c4512d2e3d6f2d9caf3f54000fb031f202e804ee615eb3357714a18657bcf", - "0x912f5935acc2dd20d5ef42b2ad5b307c925324a84a3c78ff66bc5885751934bd92f244e9636b60a744d750a2a7621198", - "0xa0d6f261a776c5b114298f5de08d6e3372649b562051ea2470d3edfc376048793e18fc57ec84809b463dc72496d94329", - "0x940744cd3118d1598c248b38503f6f1fbdbe7a147e683e5b3635140aa91679f8d6c1472600f8e9c36117a60203be6b4e", - "0xab81737c839fe340f6f1fb7275811cb0c0d5fe8bbc265f6a56c6c68d0291bc7234eaa581ff26f8929d9a5bed4aac7002", - "0x8df47341160f1c728c3e31be17a32e42b54faaa1286ef2c7946882ca4dd46443b8428f3654616c6e4053f1cda2e11994", - "0xa721067e75c3c791f4d9f58d4810ac9621606e29c6badb593d6bb78c39968b45be1777ddb9bf03696d4d4be95b2dc1bf", - "0xa4e399213d3c4350c2d0cbe30757ba7e1f9680f58e214ff65433b36232323744c866a87d717851ba1dbd6769599f69a6", - "0xb0be851d1e43dee27abe68f85e2330d94521b5f1c1a356ad83fcd09162c0ca9c2e88bccbcc5bacfa59661764361867a3", - "0x86111bdd3dbfca232aa5802a6db41d639502e43a2e24cb06bb5d05c7f9b5ccac334d16b61d1c5eaac4fa0cab91113b46", - "0xa4f805b11c174c34250748b9beebfb7c8c243198fb13463911906ee4effe7d331258a077e374b639a0c5cdcdff166b7f", - "0x87e4cf2c6f46d2dbac726a121127502921decf0195d7165e7bbeec6f976adb2d1c375eaa57f419895a2c70193215dc4c", - "0x8ff06de2c1c4d0744483bb4f7c5c80bf9c97b4df23e86c0bb17f1498ea70e0ee3af20827da5e8cb9d7f279dc50d7bd85", - "0xab112c0116471b4dc3fd1e6d918f99158eb7a08153e891ddbba2fe5bf0eeb188209e3019176e758231c3df937438136c", - "0xa67f89194e99e028a5da57747268e5ef66fefb881144043429920d222d37aaf268ebf73ca1da659fcdac3b4e7a65092a", - "0xb4da1dcc791566140d6abeaa2923cb6b21a6e6aaa30bb4cc70011e931eefa71f96b7e05358c0654bad7ce45191ab9fa8", - "0x8283933231bca359db588c80e043ad6ea765fb0cba5ef233c5d514ba01ddd1b409efbadb368f26763402e4576dc4655f", - "0x97f568ce3edacd06f3e31a15462f5f9818a8c3fdbcf92b1ac5840b0b6e73166a154013dd52e85a18e8ead3fc9e54aca0", - "0xa9cd1601c41e5ab2018f986443914fb703ddb6b06a36c06fb58065f2fee8e1751071ef924ea3ad76f0c19baccb1b5f8b", - "0x92aad71bb7e929cc35a48020d16a5822f4f106a7f59985005a5ae5ba8e8016ec33727610393498f56b4f353b3d5161b8", - "0x89427780aa4e7ac894c681fbe2889153b94db883f17f109bc9caa93f0c259dda42aab502bbefaf572c56f70abbc42db8", - "0xaa8cf76ff847dfe59534432ed8520bb48bf412c28497747dce04d2b2a54ba843c3be1564630cb49ec0217167847ba590", - "0xa1570a6748a2303e74a31c2131d05ab372ec006ee92ef74c42f2e9a250663bebdfb3777e7ad91f50c954889a59c2d434", - "0xa4c2b1bbc48199c31ea8d8196729eab00ce0200350d4aa9f23347a3289355e5828cb2f93036a14d2d9ec575fb3835239", - "0x84819d0bedbaab5bf8afdf23f59a7ec5f50da3063cfdd1ef5fc4ca4c1fe68980b5c80e30a49f38e5816765e81dfc5a57", - "0xa57cfb5e877b88202f589be777605deafbfc85ed1357af03a18709cfb4b668a271199899243cd3750f1cb77ebc40bba7", - "0x8d95934bbb0efaf3339f27cb96de46e4486aa58a2c40dbc77c1c3ac7c27a228062824b9045c046631b2e286e8549603a", - "0xb99a8356abeee69f40cb3bd8c87e8039a1e076897dde430bfbf989dc495c48609a7122bc6c1d1c32ccac687b47d5558a", - "0xaac2edcf2fe5d3f1a84e8f1f27ece920eabe7793bf0ed5290cda380752e55d57a55a362c5253bebb71e4a55f2c437ff6", - "0xaf7c76876072c3b0091e22b9c5b27ce99bf1f0079ea1a7816ad9c06e9e5fc407595c7f4f9953e67d86fb2da656443dc3", - "0x9175b64d104f78d3310c9c02f82e04c8e9878d2044ea5ee9c799846a3d23afa5fa2aa4af7350956136c69a0eed03cb2e", - "0xb3328e953317494a3d976e7f7c3d264258a5d4b2c88e12d06786a9e7b2affd41086762ef6124c6a6e5b6b028db933c14", - "0xa49d166065e19d39299ee870229e4a04be81acd6af3a2201f3a291a025dd5f8bc3e676ee123cd4b9d8455f6a330b395b", - "0x85fa15bc8947ba03681d87b50bd2f8238b1c07849a7ed4e065053fad46aac9dd428186a6dd69dc61b5eba6ffec470831", - "0xb6fcb2f694a47d3879b374b8b2967dcd59bd82a5d67ae6289a7326c18791b1b374e12571e8c8ea16a4bfc5525ced3ec4", - "0xb6115f52566aa90ccac2aab6d2dbf46eca296d047db1eb29a1b8a2bc2eef7a24e90407f8dae528806aceb2a1e684d49e", - "0x9707e66220233f6a48a93e8dec7b253d19075eaa79238e519b82ce1ac5562cca184f8a1c14f708a96c34ad234673d646", - "0xa0822903fb3825eae07ee9d3482277c0b8fc811856dfe4a51cf24b373f603924166fc5485185f99c4547cd6476b62270", - "0x88dac6366c439daaeee2532b2ddbe206132cf6e12befbb8e99870ac684e04e62de150cba0e22e395a0b858948f40808b", - "0xa72dfba9caad3179f43fead0f75e33ba5342470d8c9cb7c86d30d2c7ce7244a8aafd1d558b0ec8e2a9436de2c2e95ccc", - "0x8d696046defcc32cc19954c559213100f0ba273ea12abb55ca7c42818071d853846bd4213af2c41ecd4442f6b4b511b1", - "0x89d6f2d52cf65414da15a2fb1911c53afbfb50bb5f2638844abfc325ff2651cd9130be4beff05dc4046adfc44394a182", - "0xafb91abd7c2a9cfe62855ede3c6960ad037fe8778364a2746ff7c214c55f84e19a474a9a0062b52a380d3170456ee9c6", - "0x87f724a16ec8fdae8c05788fa3f823ecc3613df46581a63fc79b58f7c0dc2519b6b23e3dd441a0ca6946dfe4bc6cd0ce", - "0x86760f90f6bedfba404b234e90fbf981d26c29b87f2fa272c09540afa0f22e6682d08c21627b8a153c0feb27150458e2", - "0xad4d0342f255a232252450ce4209507ba619abfd1ffcb9c5707cfa45f89be41d88f1837acea993a1c47211b110250b4d", - "0xace54b5889bccdf1d46c4ca21ed97cca57f7d12648381411d1b64afdfc64532a12d49655776ea24cf5eabe34145705ad", - "0x936dac693d0c1b1e5de1701f0bc46aef6e439e84bc368a23c0abe942eb539a2950e8929265786fcdb18d40a44bda14b9", - "0x94fafbc544decec1d489b9ad6b23410b9de4779f9f44aabd093d7fab08340a4646a8cba31633e49c04d2690b8369a1d7", - "0x98157e757f1a677c5d9d65c47759727a4dbc49fec2da4d9889c4ea90573fb42e2a8d72eaef92b782ac6f320970f09363", - "0x8eaa0498c191c810c7e1ca7398f7c80dd0a7e7d7829ed07039490f60e7c2ae108843c06fe38fa36d45d63da46cba887c", - "0xa0ae116e5b0d2dccf83f056ad876037225687904e0290fe513fdc6b2dbe4cbf5fac1d828352e64734895895840b3c57c", - "0xb592b318dbbd7ec4872aae5e64bdf2305db2e5e8cfe0ad77b691f542ba5e066dd20b09b0b08ff0d798bd79ad946ddf7f", - "0x879e50c8c3e7f414ad2b38632bc482b71759cd561aeb2215550186ebb4559e4cf744cdf980512d8321954b3458d21e11", - "0xaed5c6c7ce0407d7b2c04785fcb9deadb9b9413e37cef5b1d918f474cccc7de012fe1fa6f5fa93cb7ef9ac974d9fbc20", - "0x892274a9f0afc68fa74be276c2a16de5cec674193f96b27a80bbb9f3add163f85716b531f3c920b98577a0225f84e8ca", - "0x938fb7a53266b997a7669596577af82f5289b160b7fcf06d76eee2a094696f6f12b28c2c65b833a52529a116c42e6c7e", - "0x892083929b6067f5045b1208f3dc8f0ee25bd0533a8831f5c23bb4ff46a82d48f0a34523359df5061d84a86b718d5060", - "0x99159ae9574df6c16273eda66b6d8b79a327940e335b28c75d647f4744a009f4b5f0f385e2017bd3e7fbf59e629cd215", - "0xa03e5757ef7738eba32d396923ff7ef82db2c15bb6adc8770fcb37260b7bda3be62473bc352a9a2ef7ec8ebe0d7688bc", - "0xae3c24a85c9b1fa55158b2acd56d2016f70dca45a23f3ef7e0c6b096f4a7c54c14020d61bec7c7f87be4a595bf254209", - "0xa920a6f9cc803fe31352fca39c13f8ac1e8d494fcf11b206092227c2af38469b1fbc068b8fe014800b70f137107aafc4", - "0xb893853be57519ffa6410da605e7d3a746ebadec4788c7907f6e0dde9f20f5a6a01181148b874b3decf9b4814846a11a", - "0xb46f43918c5195729f6532439f815d1eb519e91005bc641a4a30ae88700982bf4ed07a342e77945780317c297c903755", - "0x8e431bf4497d0ef6538c93c4bdda520179301a0104eebcfd104efa1edea876818d7d31079656f01a5ff76c4f5fcd71df", - "0x92e3dbcb580dfb9cc998f878052b0c3be1c5119e5249ae9bad3538ebb0f0c4ab5a959b04033b96d61836ef07784e6b64", - "0xb712d9d63aa888156f4ec83e939c6bad53de18045f115f54fbf4261fb02f10a8a46a8d716ab43d4acbad3b02283c32fc", - "0xb2334e776988b4f772446a47c87416b4f19f9b44164a5f828424d3f35ef10baa56afe810d49b0b86b786b9c0227681a6", - "0xa3f25ad18e435ef585fa90e6cef65a8ba327e5e33701979e27e64ef7d8e09e2591e52bff9c5749d35643456d18625685", - "0xadcfa48ae43cac6fa9866b4cce10a243969965942c891d5e6c0e5b03bd4763f9b63779fbf40d26ac674534fe7cc478d7", - "0xa0eb3448e045038740e2ee666e88aa0f8b8e24b1b55d7d4964f01bfc0c581f7e9d4c0e79f8cfbfecfa8b024b216c8ea6", - "0x8110aa1d82f11965af4f4eedb4de09ee9c353481b2d7ee7a2bc2f302d2a5ae6c31ebc6451309ba7c305da41070b0f666", - "0xb074fdad419d42783ebda17f19863aa499eec71fda5aab6cdcc389276b7bf08053795d15890175ca3dc89f6d8d17758c", - "0xa14665846d95d7d5f0b5381502080c822776ec0994ccb1ae1ffbb3f19205ce9c7c9bf9c2d2ca098807ce99f29e4f07a0", - "0xb4884842670a333cb5548a842fa2971881e26b442dfab0b91d6bf3b4cbdf99adbbc9d14fe2bb46872cfcabedae85db30", - "0x94549b01cb47ba16c0cf6f7522c833545397de0b3388c25d03e60132eddada6401682f9ffd8c50d1a61b4d2dde37461f", - "0xa790c9b4cec96e4c54777f3e03cea5769b20382cdcaf1de494bac2b9425eaf453eff643c62ab284cc1af33bbd36013be", - "0xb1b45fd298ed11609aa1ae6c5ac655e365bb451de1b9fc92aad40422ba85c6a454f33b8142acabe55171328c13d92edf", - "0xa74cea9e7096e38327064f058a3cdaa34e6eafaa9c7d58f753c40be67998152380fbd612b9dc0751bda7befcdffcc749", - "0xb18978dfc5efb07b7ef992c7b0cf5d1b4ca551578b1dd13057b7aced8b1deb9f2036e1e3116248a803e922659d206545", - "0x8153c07603cdff6622835a9853b795274390abf7197d7a192193bec44acb43e8cd50b56c11a03f4a2a27124c36974f3d", - "0x86b987f30bb9a37cc91d22dffffcd346ec5773e846a6c2b8f9e03b25ffcae859c470c901c4e29695d325dfe4eee927bd", - "0xaf5e980b9507d10d5269c1a5d02bc16f4f009b663e413ea6a7c655250f3a21c608c12f4002269a05d3779907e7be7d69", - "0xa6f737fab2af9f27bfb8ca87f5fdab6ad51e73ccf074e90576db57b309dfa0a95f9624526dfa4feaef39c388802f2ae9", - "0xb7ed51f699f615f58a7ff4f99d52c4ce7a8d662843c1f4d91f1620fa119b80a0f6848f9fb6c4b9822dc019830e7dfd11", - "0xb71f27f291aa6ef0723ed79c13a1c7a1c40198ffb780a129d9d20e250406bc91f459705b2b6674c9bb412a7b5dd9ff07", - "0x9698cf8f638c3d2916fefa5f28c6050784479f84c2ee76a8aeda7e562630a6ae135b445ec4e29af8588ca5ad94a67f49", - "0x9270aa5030966a9990d8bc71b00b9a7a1d7c1ad8f4c7f78a31b3d7f86467332f21407c74a89ba4f574d723acaf0d2042", - "0xb1b82faceed8e2297cd49cc355471d15ff8dc2ccc78f6944c8f7a75d3ad1629a2e2f1d0a2ff7fa2b3c38cd19839aa5e9", - "0x8a8c4ed49dc9bd961773edf8d41d04385b11bbd3577024639a39319cc7068380236bf73fce0b83e6535bd3f95cef0e65", - "0x8d04ec1e7d148b7e66910ab45a0e6bf409612a3b560bfa784e26f2963152821c646a655cf17a0ce3d4ba4c4ebeeb4a1e", - "0x8e9d707f6186d93accb60813715ed1f6b3001ff6d2f87daf8b906bd0b988c1833b2ccd80dee9bdefb45901e81bb82971", - "0x9762317ca6a5e6fe0b2991e0fa54b5fbf419dd0550d70074957d65cd7ebf79ceba607dd40d709ed635c822b3b4da2cac", - "0x82b53cd9a1eca2f5d3256723dc4b6531ca422bd87bab36243c727d1952db58d7288ab11467305d875d172ce165b1e4a5", - "0xb4dbeafa05c87029ae257bee1ed7603645fab41f6ba7ac8b57ced5b4774a72ba3e671c2433a93acc3c498795b5cccc42", - "0xa916d3ab7f0e7cef294e11c97c910a19c338ad8e615406e6d1c8995b4a19c3b2527100cc6b97a950ec5a4f3f6db7d01a", - "0xb9a785c7123609bdc96f8dd74500c6c77831d9d246f73244de964910b4045ce3242c881271bb1a4bc207d67de7b62e97", - "0xb5f94084f695d0821c472e59c0b761e625b537c8ae3a09f11d9a57259e148cfadba1e43bf22c681b6b32390121cec208", - "0x8f91b36d8570f19a90cf3ed6d5bb25f49a3315ddb566280c091fe2795c4e25ed2c6a1ef8d2669b83f2d7bb78fc8c40f5", - "0x80f27359a73ed8fdd52762f0c7b9f676be2398b1f33c67877261480bf375f975f626c2ca3e7a9f59634db176ed672c98", - "0xb96b91e3d5148ca793edefe4ca776b949c9305acb6f3a3cf87767a684014d2c8f2937c2c672eef8510f17d2da5d51385", - "0x99c4e1ca2cabd4388ea2437dbdf809013d19be9bd09ff6088c8c0cfdb9ecf8fd514391a07b4288dd362434638b8834d9", - "0xb6fdfb812e145f74853892c14f77c29b0c877d8b00055fd084b81360425b3660cd42236ecc853eadb25253e1cd8445c4", - "0xa714af044ef500104576898b9409a9a326ef4286a45c3dae440bd9003fdf689c5f498f24a6f6d18502ce705c60a1cf14", - "0xa9444e201be4a4d8c72119b3d3b13098afee6e5d13c5448fa2e9845cc9188239778f29b208749c960571dfa02b484f05", - "0x91c826a6b8425f93ff395d9fdfa60dbfa655534c36c40a295906578540b9a0e6b94fd8d025b8b8611433022fbbc4fb0b", - "0xa355d76bc3cc48ba07026197130f25a593ec730d2ef0d5d2642bfcad745ecbe5c391324bc2485944060ff3100c952557", - "0xb5f9b5a289a6f9a7252cc1f381c892bdb6836a5998f323ee21ae387936148ad1ad7cc6eca37ecece36404b958ae01e8e", - "0xa3c7ae04a6208851f6cc40ff270047283b95218905396c5dedc490e405061cbefd1251ecf77837d08c5ec1c77d2776ce", - "0xaa02ee387dd2cc7a23cf5cd582da0bc84bb33a7158d76545cbd6e06b26a6f30565dc712d7a8594c29f0529a892138802", - "0x8aff025c841f167fadaf77a68284c355ace41d6df3a9f1e41a6e91454b336f0b69ea34cce495839b642a7c43997a8fd9", - "0x82eccf0b6b4b6460f676d677266451d50f775446df313fc89bdf4c96e082340f6811939d215a54ba0fe30c69b3e43e25", - "0xaf324d871b038ff45a04366817c31d2c1e810359776fb57ac44907c6157004e3705476574e676b405d48a48bfb596f59", - "0x9411dcca93ef5620ce375f379fea5c1017a2dd299e288e77b1ab126273631a299d7436f3bf3c860bf795e5faaaefa804", - "0x934fca809e66f582c690c3778ea49de2e7940c0aeb8d7edad68f2edccdfda853d2c4844abd366fbc2215348935e4b2e2", - "0xa1b1fa4c088418f2609d4dea0656b02a8ee664db25f40d53d8f4b1be89a55e5abecbf2c44c0499874abeb3d3a80acf71", - "0xae6ed7a0ba6280c679b0bf86111afad76fc5d930e9fb199df08134ba807f781d7e0b8b9b2c8c03b02d8cc20dbe949a28", - "0x937d200a72fe4ab8d52f6cb849e322bc5959632b85a93c89744b33e832e8dcf1dddd6ffac0c049b03c105afb8930f7f5", - "0xb4b4a46ebe0c5db16004933c08ad039d365db600a13d68be5346b1c840cce154f56c858874e866de8c3711e755c6e5dd", - "0xafcbcb7170c8caa2b77d2b3388dc2f640aeb9eff55798aeceb6eb6494438be05a2ae82f7034b2d439a45ad31d8c64b07", - "0xa2c676273081b8761f58e0b11306ddb6a4cde3d90e7c47b434468700c5b749932819b01efd7637ca820e10fc28dfb427", - "0xb445715162d834c9ee75ac2ff8932ace91c8242d67926b2a650217e4765e0531c2393c9438a52852d63dbbe2cceaafc5", - "0xa0c0ebdc1480fb238a25fbfc77fae0db6e5e74b91809f0ff20a819e56b8c3141549615d1bd7b99829898f6028e8c86be", - "0xb3d11933e9d1db8ca617934261ed26c6f5ca06ba16369e7541482bf99c4f86520d43fbb10f4effb2fdf3cc70a189fdb5", - "0x888ac610f8fd87a36b5646e1016eaf6dbca04aa0cc43f53a1046d74a658c4d2794606e79fb07fae57cf9d71ed339f4b6", - "0x979818dab00c58435dc0d0d21185943f95819d2a13531abd2d798e1773c4bbd90047f4eebe117868743db75604a50227", - "0xa6fbcd2656e475065fe44e995e8e2b5309b286b787a7597117e7acc3bb159e591a3e7304ef26f567b5720799d8ae1836", - "0xa03f0ac08d2101ec4d99ca1443eea0efa767a65448a8ecd73a7818a99e863a04392bec8c5b8e5192834e8f98d4683f13", - "0xb3c4ea8c6c3ee8aab2873d446ad702000b0e927e0991c9e30d83c6fe62a604efdc3ac92453313ff0d5e0ac6952922366", - "0xab25c857f26830631113d50145e961441b5e35d47b9e57f92466654dffebde43e4f78b0867d20929f97c2888c2f06509", - "0x98950aa5a70ef41f274775f021a284d4d801a2efe2dea38460db8a3a8c08c243836d176e69127c2cd17497b0ca393e9e", - "0xa9698113febfb6d87fcb84bad82ce52d85a279d3a2933bdd179d53cfe8d6c6c68770e549a1e2947e7528a0e82c95d582", - "0x832b504513266259db78478bd1b5a3b0f3bf2c6d25f1013e64bf0cfae9dc23da8ecd25f7f1047d2efb90e5f1d9b4b3cc", - "0xb588bba7bcc0d268ab260d5c1db2122cee7fd01583c7cc27a8ae6b48b29f34c6ea8a6acbb71b9b09c6156ec0a0766142", - "0xa73d2223c7afadc381951a2e9e7bcb7b5c232369f27108c9f3c2ced2dc173e0f49531d0ca527eb142fbb70285307433f", - "0x9152cd6b97bd3278465348dde2095892f46342aed0e3d48675848c05b9aee6ef5ad7fe26e0dcd4ab176532289d40eedd", - "0xa7812a95a43b020721f688dd726356dda8ebe4de79b4f0fdef78615795e29681bff7c6ff710ff5b2d6ae3fd81bdb8507", - "0x83724c16049e9eaae3269ea8e65caa212f0592e0190b47159bb3346208ccb9af3cfe8f6c3176fa566377da1046044ab8", - "0x877634ec37c7dcd3b83705b103c31013697012795f11e8abf88d54bc84f2c060f665f0c3b14ef8087d3c6a8a7982d64f", - "0xb3e53aaacef7a20327bdbba8cd84513534d2e12fd5e1dcf2849f43146e098143b539ebd555623d0ecc46f5ebb4051fca", - "0x952d58ecafca9b7ffc25768ee4f05ce138f0289d72978eb5e5d3b23a0daedcb17478890afdce42e30d924d680e13c561", - "0xa10dcc725f9a261de53dd3133858c126f6aa684cf26d92bce63a70e0ff5fff9610ad00d2b87e598b0a7548cfd1ffe713", - "0xb7bc5d0c6b665d5e6f4d0af1c539d8a636550a327e50a0915c898ac494c42b3100e5fae0074c282d1c5073bf4a5456fb", - "0x8adc330d3b49ddf3ed210166afc944491aaedb28cb4e67472aeb496f66ce59184c842aa583bfb1a26d67d03b85065134", - "0xb2df992a1310936394a1ebca94a7885b4c0a785638f92a7b567cfb4e68504ac5966a9e2b14891d0aa67d035a99e6583a", - "0x96f5da525d140739d19cebb706e2e1e0211edea1f518e040d361d5aca4c80f15be797f58cb4cd3908e4c360c18821243", - "0xb2c0d9173a3d4867c8842e9b58feb1fb47f139f25d1e2332d6b70a85a58811ef99324bf8e52e144e839a4fe2d484e37b", - "0xad95a7631ddb4846d9343d16533493524dfd22e8cbfc280a202343fccee86ab14446f6e7dad9bad9b4185c43fd5f862e", - "0x97f38ab82a51a7a792d459a90e7ea71c5a2f02d58e7d542eb3776d82413932737d9431bd6b74ec2a6a8b980d22d55887", - "0xad4e4c57ec3def5350c37659e8c15bd76d4c13d6de5453493123198dda2c2f40df349f20190e84d740a6b05e0b8f3deb", - "0xa691bc10810d11172a6662e46b6bbc48c351df32f325b319553377f525af44a50aaa02790c915b3a49824aa43f17fff0", - "0xa80ccac79bb4014ee366dbf6e380beb61552bd30ef649d4ec39ab307e4139b7775e776fab30831517674ff3d673566f6", - "0xb11e010b855d80e171705ab9e94364c45998e69d9120e4ca4127049b7a620c2eec1377356e7b877874e767f7c44afef4", - "0x96bfab7777769a1e00ce16ada6667a0d21d709e71bd0371c03002427d138d9172640cdd5c529c710fea74bb9d19270c7", - "0xa5bffd2c30e29633b4ecf637c1e792c0378252e2a99b385a093675940b48de2f262c275332ed4765f4a02467f98e3ddd", - "0x8d11929d67a6bd8a835b80660a89496250c766e713bddb2cd7052d67b92c39a38ce49005d38b4877856c4bef30fb9af4", - "0x8e704597a0dba1dbd1ff8c9755ddac3f334eeeb513fd1c6b78366603ebc1778231deb8e18f2889421f0091e2c24d3668", - "0x904fbb3f78a49e391a0544cf1faa96ba9402cba818359582258d00aff5319e3c214156cff8c603fbc53a45ede22443e9", - "0xaf12ac61eaa9c636481a46fd91903c8a16e7647534fc6fd9baa58ae2998c38ffbd9f03182062311c8adfef0a338aa075", - "0x87f2e544b2993349ab305ab8c3bf050e7764f47d3f3031e26e084e907523d49e1d46c63d0c97b790394f25868e12b932", - "0xa279a7bef6de9d4e183e2bedaf8c553fadfc623a9af8785fe7577cadced02b86e3dab1e97b492d4680c060ea0126abeb", - "0x8ece08667ed826f0a239cea72e11359f7e85d891826292b61d4edbdc672f8342e32c66bec3e6498016b8194168ba0e0d", - "0x90a15162586e991b302427bc0307790a957b53ab0e83c8b2216f6e6302bc496cb256f0f054ff2cccdfe042763de00976", - "0x9966c0413b086a983f031a39080efde41a9fedcaf8e92897ce92e0c573b37981f5ea266b39dc4f4fb926a1bce5e95ad7", - "0x9515be2f65a57e6960d71bfb1917d33f3f6d8b06f8f31df30fc76622949770fea90ff20be525ae3294c56bc91efb7654", - "0x86e71c9b4059dc4fd1ce7e28883e4f579a51449cab5899e371118cdb6afe2758b1485961ca637c299896dea7c732151b", - "0x8695b4ff746d573f8d150f564e69fe51c0726c5d14aa1d72d944f4195e96165eca7eba8cac583fd19d26718b0ce3eb61", - "0x813eecf402151c99c1a55b4c931716e95810fc4e6d117dfc44abbf5ef8dcdf3f971d90d7fa5e5def393681b9584637e0", - "0xa9caf7219eed1db14b7b8f626f20294a3305ed1f6c22f6a26962772c2fa3e50b5234f6d9ba7fa5c3448824c2a15271b3", - "0xb2b2ee20de9b334f2d82cbe0d2e426ca1f35f76218737d0069af9b727a1bfc12d40cf8b88d4afcbeaadf317b7f7ad418", - "0xb853960749521a17ff45f16ac46813d249c4e26e3c08fd33d31ef1ed2b2e157c9cb18bd2454fb5c62690bdd090a48f60", - "0x88772297d2972471b3db71f3ddbf5945a90154768ca49fa6729a5e2299f1795445fb3d4d969d1620e87dca618fbc8a6c", - "0xa2bb783fd13aee993e3efd3a963ebc8a8eacfc8450042f018f2040353de88c71ac784b0898bdff27f606c60a3d5ef2c6", - "0x9210903ac619edca0cb8c288ed6dcc93c472f45182cd6614a8e2390801ddea41d48a4ac04a40e2f0adfd48f91aabe2ea", - "0xa621d00f83260c22db9fa28757ea81dabcc78b10eeaaf58b06b401db6cc7a7d9a6831a16f171ead4e8506d0c46a752ca", - "0xb25c525bf6761a18bbd156ac141df2595940c7b011ed849dbb8ac3a2cd2da6b63ba4755324d70dc14c959deb29fb9ad3", - "0xa35111d0db3e862e1b06249d289e0fc6b110877d254f2ae1604fb21292c227a8b6d87dd17a7b31166038d6860b1bd249", - "0x90bf057309867d95f27637bd10ef15ceb788f07d38aca7ad7920042293d7c4a1a13d4ca1d6db202864d86d20a93e16cf", - "0xa88510e110b268d15dcd163ba1e403e44b656771399ac3a049dcb672a1201e88bf60bdd1d303158888a3d30d616cc0bd", - "0xb33b7e1f765e9cbd5eeb925e69c39b0a9ea3348ab17f1dbb84b66f4a4b3233e28cbdeb0903d6cfe49ec4fc2f27378ff9", - "0xb777da64fa64d9bc3d2d81b088933fce0e5fcc29c15536159c82af3622a2604c2b968991edea7b6882c9e6f76b544203", - "0x8ea598e402a056fd8031fbf3b9e392347999adc1bd5b68c5797a791a787d006e96918c799467af9ac7f5f57eb30b4f94", - "0xb6901a389bf3b3045e679d015c714d24f8bbe6183349b7f6b42f43409a09f0d5bd4b794012257d735c5fdf6d1812554b", - "0xb5866426336d1805447e6efc3f3deb629b945b2781f618df9a2cc48c96020846e9108f9d8507a42ba58d7617cb796c31", - "0xa18ccc6ad1caa8462fa9bec79510689dd2a68d2e8b8e0ddbeb50be4d77728e1d6a18748a11e27edd8d3336c212689a4d", - "0xabbd48c48a271b6b7c95518a9352d01a84fb165f7963b87cdc95d5891119a219571a920f0d9ceedc8f9f0de4ab9deb65", - "0x94a4e5f4d7e49229e435530b12a1ff0e9259a44a4f183fb1fe5b7b59970436e19cf932625f83f7b75702fd2456c3b801", - "0xaf0a6f2a0d0af7fc72e8cb690f0c4b4b57b82e1034cca3d627e8ef85415adec8eb5df359932c570b1ee077c1d7a5a335", - "0x9728025e03114b9e37ed43e9dcba54a2d67f1c99c34c6139e03d4f9c57c9e28b6b27941d9fca4051d32f9b89bec6537b", - "0x941601742d1e1ec8426591733a4f1c13785b0a9b0a6b2275909301a6a3c6c1e2fb1ffa5fdcc08d7fb69f836ae641ced5", - "0xb84b90480defd22f309e294379d1ca324a76b8f0ba13b8496b75a6657494e97d48b0ea5cfdb8e8ac7f2065360e4b1048", - "0x95cc438ee8e370fc857fd36c3679c5660cf6a6c870f56ef8adf671e6bf4b25d1dbad78872cc3989fdfe39b29fc30486d", - "0x8aafba32e4a30cad79c5800c8709241b4041b0c13185ea1aa9bc510858709870b931d70b5d9a629f47579b161f1d8af7", - "0x865b0155d9013e80cba57f204c21910edbd4d15e53ae4fee79992cb854dc8b8a73f0a9be92f74893e30eb70f270511bc", - "0xb9a49ce58d40b429ac7192cdbf76da31300efc88c827b1e441dd5bdb2f1c180d57808c48992492a2dc5231008629159f", - "0x8d1438b10f6cd996494d4c7b5a0841617ec7cf237c9e0956eac04fda3f9ded5110ec99776b816e3c78abd24eb4a9c635", - "0xaf2dd18211bb8a3e77c0a49d5773da6e29e4e6fa6632a6eeb56c4be233f6afe81655d977932548de2be16567c54ffbd7", - "0x92b92443f44464f2b48002a966664a4267eae559fa24051983bcf09d81bed5bcc15cb6ff95139d991707697a5d0cc1ab", - "0xa1864a2bac0c0dd5b2fb1a79913dd675fe0a5ae08603a9f69d8ca33268239ac7f2fed4f6bf6182a4775683cb9ccd92a8", - "0x948e8f1cf5bd594c5372845b940db4cb2cb5694f62f687952c73eb77532993de2e2d7d974a2ced58730d12c8255c30a2", - "0xaa825c08284fa74a99fcfc473576e8a9788277f72f8c87f29be1dd41229c286c2753ff7444c753767bd8180226763dfc", - "0x8384d8d51415e1a4d6fe4324504e958c1b86374cc0513ddf5bcbffabb3edcf4b7d401421e5d1aa9da9010f07ef502677", - "0x8b8223a42585409041d8a6e3326342df02b2fe0bcc1758ff950288e8e4677e3dc17b0641286eaf759a68e005791c249c", - "0xa98a98cc2fb14e71928da7f8ce53ab1fb339851c9f1f4bceb5f1d896c46906bd027ef5950ca53b3c8850407439efedd4", - "0x866f44d2e35a4dbffe6cd539b6ef5901924061e37f9a0e7007696fb23526379c9b8d095b417effe1eecda698de744dcb", - "0x91774f44bf15edafdf43957fdf254682a97e493eb49d0779c745cb5dbe5d313bf30b372edd343f6d2220475084430a2e", - "0xab52fc3766c499a5f5c838210aada2c3bcc1a2ec1a82f5227d4243df60809ee7be10026642010869cfbf53b335834608", - "0xa0e613af98f92467339c1f3dc4450b7af396d30cefd35713388ccd600a3d7436620e433bf294285876a92f2e845b90d0", - "0x8a1b5ca60a9ae7adc6999c2143c07a855042013d93b733595d7a78b2dc94a9daa8787e2e41b89197a0043343dbd7610f", - "0xae7e4557bc47b1a9af81667583d30d0da0d4a9bb0c922450c04ec2a4ae796c3f6b0ede7596a7a3d4e8a64c1f9ee8ff36", - "0x8d4e7368b542f9f028309c296b4f84d4bde4837350cf71cfe2fa9d4a71bce7b860f48e556db5e72bc21cf994ffdf8e13", - "0xaf6ed1fbff52dd7d67d6a0edfa193aa0aab1536979d27dba36e348759d3649779f74b559194b56e9378b41e896c4886f", - "0xa069ba90a349ac462cac0b44d02c52a4adf06f40428aef5a2ddff713de31f991f2247fc63426193a3ea1b1e50aa69ded", - "0x8750f5f4baf49a5987470f5022921108abe0ead3829ddef00e61aedd71f11b1cdd4be8c958e169440b6a8f8140f4fbf9", - "0xa0c53cefc08a8d125abd6e9731bd351d3d05f078117ff9c47ae6b71c8b8d8257f0d830481f941f0c349fc469f01c9368", - "0x94eea18c5ed056900c8285b05ba47c940dff0a4593b627fdd8f952c7d0122b2c26200861ef3e5c9688511857535be823", - "0x8e1b7bd80d13460787e5060064c65fbcdac000c989886d43c7244ccb5f62dcc771defc6eb9e00bae91b47e23aeb9a21f", - "0xb4b23f9dd17d12e145e7c9d3c6c0b0665d1b180a7cfdf7f8d1ab40b501c4b103566570dca2d2f837431b4bf698984cad", - "0x847a47c6b225a8eb5325af43026fb9ef737eede996257e63601f80302092516013fde27b93b40ff8a631887e654f7a54", - "0x9582d7afb77429461bd8ebb5781e6390a4dde12a9e710e183581031ccfacd9067686cfaf47584efaafeb1936eae495cc", - "0x8e4fd5dbd9002720202151608f49ef260b2af647bd618eb48ebeceeb903b5d855aa3e3f233632587a88dc4d12a482df9", - "0x87b99fe6a9c1d8413a06a60d110d9e56bb06d9f0268dc12e4ab0f17dd6ca088a16ade8f4fb7f15d3322cbe7bfd319ae1", - "0xb562d23002ed00386db1187f519018edd963a72fca7d2b9fcaab9a2213ac862803101b879d1d8ac28d1ccae3b4868a05", - "0xb4cc8b2acacf2ce7219a17af5d42ce50530300029bc7e8e6e2a3c14ff02a5b33f0a7fecb0bb4a7900ea63befa854a840", - "0x9789f0fe18d832ff72df45befa7cabf0a326b42ada3657d164c821c35ac7ed7b2e0eba3d67856e8c387626770059b0c3", - "0x986c6fe6771418549fa3263fa8203e48552d5ecb4e619d35483cb4e348d849851f09692821c9233ae9f16f36979c30c2", - "0xa9160182a9550c5756f35cea1fe752c647d1b64a12426a0b5b8d48af06a12896833ec5f5d9b90185764db0160905ca01", - "0x82614dbd89d54c1e0af4f6ffe8710e6e871f57ef833cbcb3d3d7c617a75ec31e2a459a89ebb716b18fc77867ff8d5d47", - "0x8fc298ffba280d903a7873d1b5232ce0d302201957226cddff120ffe8df9fee34e08420302c6b301d90e3d58f10beeb9", - "0x898da9ac8494e31705bdf684545eee1c99b564b9601877d226d0def9ec67a20e06f8c8ba2a5202cc57a643487b94af19", - "0x88218478d51c3ed2de35b310beedf2715e30208c18f046ee65e824f5e6fd9def921f6d5f75fd6dde47fa670c9520f91a", - "0x89703ae7dff9b3bc2a93b44cdbab12c3d8496063a3c658e21a7c2078e4c00be0eecae6379ee8c400c67c879748f1d909", - "0xa44d463477dece0d45abb0ebb5f130bfb9c0a3bbcd3be62adf84a47bbd6938568a89bc92a53ca638ff1a2118c1744738", - "0x95df2b4d392143ee4c39ad72f636d0ed72922de492769c6264015776a652f394a688f1d2b5cf46077d01fda8319ba265", - "0xaa989867375710ed07ad6789bfb32f85bdc71d207f6f838bd3bde9da5a169325481ac326076b72358808bd5c763ba5bb", - "0xb859d97d0173920d16bc01eb7d3ddd47273daac72f86c4c30392f8de05fee643e8d6aa8bebdbc5c2d89037bc68a8a105", - "0xb0249ec97411fa39aa06b3d9a6e04bbbcd5e99a7bc527273b6aa95e7ae5f437b495385adaefa4327231562d232c9f822", - "0x8209e156fe525d67e1c83ec2340d50d45eba5363f617f2e5738117cdcc4a829c4cc37639afd7745cbe929c66754fd486", - "0x99fd2728ceb4c62e5f0763337e6d28bf11fbe5df114217f002bc5cd3543c9f62a05a8a41b2e02295360d007eaab796a6", - "0x902ebc68b8372feeaf2e0b40bd6998a0e17981db9cc9d23f932c34fbcc680292a0d8adcea2ad3fb2c9ed89e7019445c2", - "0x8b5653f4770df67f87cb68970555b9131c3d01e597f514e0a399eec8056e4c5a7deed0371a27b3b2be426d8e860bf9f2", - "0x8f5af27fdc98a29c647de60d01b9e9fd0039013003b44ba7aa75a4b9c42c91feb41c8ae06f39e22d3aed0932a137affa", - "0x81babb9c1f5bcc0fd3b97d11dd871b1bbd9a56947794ff70ab4758ae9850122c2e78d53cb30db69ece23538dc4ee033e", - "0xb8b65d972734f8ecae10dd4e072fa73c9a1bf37484abcfa87e0d2fcecac57294695765f63be87e1ba4ec0eb95688403a", - "0xb0fe17d0e53060aef1947d776b06ab5b461a8ef41235b619ca477e3182fadaf9574f12ffc76420f074f82ac4a9aa7071", - "0xae265c0b90bf064d7a938e224cb1cd3b7eca3e348fbc4f50a29ac0930a803b96e0640992354aa14b303ea313cb523697", - "0x8bc10ffde3224e8668700a3450463ab460ec6f198e1deb016e2c9d1643cc2fe1b377319223f41ffeb0b85afd35400d40", - "0x8d5113b43aea2e0cc6f8ec740d6254698aff7881d72a6d77affd6e6b182909b4de8eb5f524714b5971b418627f15d218", - "0xae2ef0a401278b7b5d333f0588773ec62ead58807cdee679f72b1af343c1689c5f314989d9e6c9369f8da9ce76979db6", - "0xb9c1cb996a78d4f7793956daaa8d8825dd43c4c37877bc04026db4866144b1bf37aa804d2fe0a63c374cf89e55e9069f", - "0xa35f73851081f6540e536a24a28808d478a2bb1fd15ee7ff61b1562e44fbafc0004b9c92c9f96328d546b1287e523e48", - "0x82007f34e3383c628c8f490654369744592aa95a63a72be6e90848ad54f8bc2d0434b62f92a7c802c93017214ecf326e", - "0x9127db515b1ed3644c64eaf17a6656e6663838fed4c6612a444a6761636eaaeb6a27b72d0e6d438c863f67b0d3ec25c5", - "0x984c9fcc3deccf83df3bbbb9844204c68f6331f0f8742119ba30634c8c5d786cd708aa99555196cf6563c953816aec44", - "0xa0f9daf900112029474c56ddd9eb3b84af3ed2f52cd83b4eb34531cf5218e7c58b3cab4027b9fc17831e1b6078f3bf4a", - "0x90adbcc921369023866a23f5cea7b0e587d129ad71cab0449e2e2137838cea759dec27b0b922c59ac4870ef6146ea283", - "0x8c5650b6b9293c168af98cf60ad35c945a30f5545992a5a8c05d42e09f43b04d370c4d800f474b2323b4269281ca50f8", - "0x868d95be8b34a337b5da5d886651e843c073f324f9f1b4fbd1db14f74aba6559449f94c599f387856c5f8a7bc83b52a1", - "0x812df0401d299c9e95a8296f9c520ef12d9a3dd88749b51eab8c1b7cc97961608ab9fc241a7e2888a693141962c8fd6d", - "0xabda319119d8a4d089393846830eee19d5d6e65059bf78713b307d0b4aad245673608b0880aa31c27e96c8d02eff39c0", - "0x887f11ae9e488b99cb647506dcaa5e2518b169ee70a55cd49e45882fe5bfb35ffaf11feb2bf460c17d5e0490b7c1c14d", - "0xb36b6e9f95ffff917ca472a38fa7028c38dc650e1e906e384c10fe38a6f55e9b84b56ffa3a429d3b0c3e2cf8169e66a9", - "0xa0450514d20622b7c534f54be3260bab8309632ca21c6093aa0ccc975b8eed33a922cbcc30a730ccc506edf9b188a879", - "0x87cfaf7bcd5d26875ca665ac45f9decd3854701b0443332da0f9b213e69d6f5521ae0217ec375489cd4fad7b4babf724", - "0x842ad67c1baf7a9d4504c10c5c979ce0a4d1b86a263899e2b5757407c2adcdcf7ed58173ad9d156d84075ef8798cb1c4", - "0xac1a05755fe4d3fb2ab5b951bafe65cca7c7842022ca567b32cddf7741782cbf8c4990c1dd4ea05dc087a4712844aebb", - "0xa000c8cecc4fddeb926dc8dd619952bc51d00d7c662e025f973387a3fc8b1ef5c7c10b6a62e963eb785e0ec04cb1ffbe", - "0x8a573c9986dbeb469547dfd09f60078eab252d8ec17351fe373a38068af046b0037967f2b3722fa73ed73512afd038d2", - "0xb8dff15dff931f58ba05b6010716c613631d7dd9562ae5138dbec966630bcdb0e72552e4eefc0351a6a6b7912d785094", - "0x990e81fd459433522e8b475e67e847cb342c4742f0dbf71acc5754244ccd1d9ff75919168588d8f18b8aea17092dd2a4", - "0xb012f8644da2113bef7dd6cdc622a55cfa0734bd267b847d11bba2e257a97a2a465c2bb616c240e197ff7b23e2ce8d8e", - "0xa659bd590fde467766e2091c34a0b070772f79380be069eef1afecc470368a95afd9eed6520d542c09c0d1a9dca23bd0", - "0xb9239f318b849079477d1cf0a60a3d530391adacd95c449373da1c9f83f03c496c42097c3f9aca10c1b9b3dbe5d98923", - "0x851e9a6add6e4a0ee9994962178d06f6d4fbc0def97feef1ba4c86d3bcf027a59bafa0cf25876ca33e515a1e1696e5cc", - "0x803b9c5276eed78092de2f340b2f0d0165349a24d546e495bd275fe16f89a291e4c74c22fdee5185f8fce0c7fbced201", - "0x95915654ca4656d07575168fb7290f50dc5dcbbcdf55a44df9ec25a9754a6571ab8ca8a159bc27d9fa47c35ffd8f7ffd", - "0x88f865919764e8e765948780c4fdd76f79af556cd95e56105d603c257d3bfb28f11efca1dfb2ce77162f9a5b1700bac8", - "0xb1233131f666579b4cc8b37cfa160fc10551b1ec33b784b82685251464d3c095cdde53d0407c73f862520aa8667b1981", - "0xa91115a15cf4a83bda1b46f9b9719cfba14ffb8b6e77add8d5a0b61bea2e4ea8ce208e3d4ed8ca1aab50802b800e763a", - "0x93553b6c92b14546ae6011a34600a46021ce7d5b6fbfcda2a70335c232612205dbe6bfb1cc42db6d49bd4042c8919525", - "0x8c2a498e5d102e80c93786f13ccf3c9cab7f4c538ccf0aee8d8191da0dbca5d07dff4448383e0cf5146f6d7e629d64f8", - "0xa66ab92c0d2c07ea0c36787a86b63ee200499527c93b9048b4180fc77e0bb0aa919f4222c4bec46eeb3f93845ab2f657", - "0x917e4fc34081a400fc413335fdf5a076495ae19705f8542c09db2f55fa913d6958fa6d711f49ad191aec107befc2f967", - "0x940631a5118587291c48ac8576cdc7e4a904dd9272acb79407a7d3549c3742d9b3669338adbc1386724cc17ee0cc1ca3", - "0xae23ae3a531900550671fd10447a35d3653c5f03f65b0fdffe092844c1c95d0e67cab814d36e6388db5f8bd0667cd232", - "0xae545727fca94fd02f43e848f0fbbb1381fd0e568a1a082bf3929434cc73065bfbc9f2c840b270dda8cc2e08cd4d44b0", - "0x8a9bc9b90e98f55007c3a830233c7e5dc3c4760e4e09091ff30ee484b54c5c269e1292ce4e05c303f6462a2a1bd5de33", - "0xa5a2e7515ce5e5c1a05e5f4c42f99835f6fde14d47ecb4a4877b924246038f5bc1b91622e2ff97ed58737ed58319acfa", - "0x8fa9f5edf9153618b72b413586e10aaa6c4b6e5d2d9c3e8693ca6b87804c58dc4bf23a480c0f80cb821ebc3cf20ea4fc", - "0x925134501859a181913aadac9f07f73d82555058d55a7d5aaa305067fbd0c43017178702facc404e952ea5cfd39db59b", - "0x8b5ab1d9b5127cb590d6bddbf698ffe08770b6fc6527023d6c381f39754aecc43f985c47a46be23fe29f6ca170249b44", - "0xaa39c6b9626354c967d93943f4ef09d637e13c505e36352c385b66e996c19c5603b9f0488ad4014bb5fc2e051b2876cc", - "0x8e77399c6e9cb8345002195feb7408eb571e6a81c0418590d2d775af7414fc17e61fe0cd37af8e737b59b89c849d3a28", - "0xa0150aeca2ddc9627c7ea0af0dd4426726583389169bc8174fc1597cc8048299cc594b22d234a4e013dff7232b2d946c", - "0x98659422ef91f193e6104b09ff607d1ed856bb6baed2a6386c9457efbc748bd1bf436573d80465ebc54f8c340b697ea5", - "0x8d6fb015898d3672eb580e1ffdf623fc4b23076664623b66bfb18f450d29522e8cb9c90f00d28ccf00af34f730bff7ac", - "0x996a8538efa9e2937c1caad58dc6564e5c185ada6cdcef07d5ec0056eb1259b0e4cef410252a1b5dbaee0da0b98dac91", - "0xaa0ae2548149d462362a33f96c3ce9b5010ebf202602e81e0ef77e22cfc57ecf03946a3076b6171bea3d3dc9681187d7", - "0xa5ce876b29f6b89050700df46d679bed85690daf7bad5c0df65e6f3bde5673e6055e6c29a4f4dcb82b93ccecf3bad9cc", - "0x81d824bb283c2f55554340c3514e15f7f1db8e9e95dd60a912826b1cccb1096f993a6440834dad3f2a5de70071b4b4b5", - "0x914e7291da286a89dfc923749da8f0bf61a04faa3803d6d10633261a717184065dcc4980114ad852e359f79794877dd9", - "0xae49dc760db497c8e834510fe89419cc81f33fd2a2d33de3e5e680d9a95a0e6a3ccbdf7c0953beeb3d1caf0a08b3e131", - "0xb24f527d83e624d71700a4b238016835a2d06f905f3740f0005105f4b2e49fc62f7e800e33cdc900d805429267e42fc0", - "0xb03471ecaa7a3bf54503347f470a6c611e44a3cee8218ad3fcad61d286cfb7bb6a1113dad18475ec3354a71fcc4ec1e2", - "0x881289b82b30aff4c8f467c2a25fced6064e1eece97c0de083e224b21735da61c51592a60f2913e8c8ba4437801f1a83", - "0xb4ce59c0fc1e0ecad88e79b056c2fd09542d53c40f41dea0f094b7f354ad88db92c560b9aeb3c0ef48137b1a0b1c3f95", - "0xa1ffb30eb8ef0e3ea749b5f300241ebe748ed7cf480e283dfcda7380aa1c15347491be97e65bc96bdf3fe62d8b74b3ae", - "0xb8954a826c59d18c6bfab24719f8730cc901868a95438838cd61dac468a2d79b1d42f77284e86e3382bf4f2a22044927", - "0x818e7e7c59b6b5e22b3c2c19c163f2e787f2ff3758d395a4da02766948935eb44413c3ddd2bf45804a3c19744aa332f3", - "0xa29556e49866e4e6f01d4f042eed803beeda781462884a603927791bd3750331a11bc013138f3270c216ab3aa5d39221", - "0xb40885fa0287dc92859b8b030c7cca4497e96c387dcfe6ed13eb7f596b1eb18fb813e4ae139475d692f196431acb58fe", - "0x89cd634682fd99ee74843ae619832780cf7cd717f230ea30f0b1821caf2f312b41c91f459bdba723f780c7e3eed15676", - "0xb48c550db835750d45a7f3f06c58f8f3bf8766a441265ca80089ead0346f2e17cbb1a5e843557216f5611978235e0f83", - "0x90936ee810039783c09392857164ab732334be3a3b9c6776b8b19f5685379c623b1997fb0cdd43af5061d042247bc72f", - "0xa6258a6bae36525794432f058d4b3b7772ba6a37f74ef1c1106c80a380fc894cbeac4f340674b4e2f7a0f9213b001afd", - "0x8f26943a32cf239c4e2976314e97f2309a1c775777710393c672a4aab042a8c6ee8aa9ac168aed7c408a436965a47aeb", - "0x820f793573ca5cc3084fe5cef86894c5351b6078df9807d4e1b9341f9d5422dd29d19a73b0843a14ad63e8827a75d2da", - "0xa3c4fca786603cd28f2282ba02afe7cf9287529e0e924ca90d6cdfd1a3912478ebb3076b370ee72e00df5517134fe17f", - "0x8f3cdabd0b64a35b9ee9c6384d3a8426cc49ae6063632fb1a56a0ae94affa833955f458976ff309dafd0b2dd540786ae", - "0x945a0630cd8fa111cfd776471075e5d2bbe8eb7512408b5c79c8999bfaeca6c097f988fb1c38fa9c1048bac2bca19f2e", - "0x8a7f6c4e0ba1920c98d0b0235b4dda73b631f511e209b10c05c550f51e91b4ba3893996d1562f04ac7105a141464e0e9", - "0xab3c13d8b78203b4980412edc8a8f579e999bf79569e028993da9138058711d19417cf20b477ef7ed627fa4a234c727a", - "0x82b00d9a3e29ed8d14c366f7bb25b8cfe953b7be275db9590373a7d8a86ea927d56dc3070a09ef7f265f6dd99a7c896e", - "0xb6e48a282de57949821e0c06bc9ba686f79e76fb7cbf50ea8b4651ccd29bc4b6da67efea4662536ba9912d197b78d915", - "0xa749e9edcba6b4f72880d3f84a493f4e8146c845637009f6ff227ff98521dbbe556a3446340483c705a87e40d07364bc", - "0xb9b93c94bd0603ce5922e9c4c29a60066b64a767b3aed81d8f046f48539469f5886f14c09d83b5c4742f1b03f84bb619", - "0xafa70b349988f85ed438faafa982df35f242dd7869bda95ae630b7fd48b5674ef0f2b4d7a1ca8d3a2041eff9523e9333", - "0xa8e7e09b93010982f50bd0930842898c0dcd30cdb9b123923e9d5ef662b31468222fc50f559edc57fcfdc597151ebb6e", - "0x8ce73be5ac29b0c2f5ab17cae32c715a91380288137d7f8474610d2f28d06d458495d42b9cb156fb1b2a7dfdcc437e1c", - "0x85596c1d81f722826d778e62b604eb0867337b0204c9fae636399fa25bb81204b501e5a5912654d215ec28ff48b2cb07", - "0x96ff380229393ea94d9d07e96d15233f76467b43a3e245ca100cbecbdbb6ad8852046ea91b95bb03d8c91750b1dfe6e1", - "0xb7417d9860b09f788eb95ef89deb8e528befcfa24efddbc18deaf0b8b9867b92361662db49db8121aeea85a9396f64fd", - "0x97b07705332a59cdba830cc8490da53624ab938e76869b2ce56452e696dcc18eb63c95da6dffa933fb5ffb7585070e2d", - "0x971f757d08504b154f9fc1c5fd88e01396175b36acf7f7abcfed4fff0e421b859879ed268e2ac13424c043b96fbe99fc", - "0xb9adb5d3605954943a7185bddf847d4dbe7bafe970e55dc0ec84d484967124c26dd60f57800d0a8d38833b91e4da476a", - "0xb4856741667bb45cae466379d9d6e1e4191f319b5001b4f963128b0c4f01819785732d990b2f5db7a3452722a61cd8cc", - "0xa81ec9f2ab890d099fb078a0c430d64e1d06cbbe00b1f140d75fc24c99fe35c13020af22de25bbe3acf6195869429ba5", - "0x99dcea976c093a73c08e574d930d7b2ae49d7fe43064c3c52199307e54db9e048abe3a370b615798b05fe8425a260ba0", - "0xa1f7437c0588f8958b06beb07498e55cd6553429a68cd807082aa4cc031ab2d998d16305a618b3d92221f446e6cd766d", - "0x806e4e0958e0b5217996d6763293f39c4f4f77016b3373b9a88f7b1221728d14227fce01b885a43b916ff6c7a8bc2e06", - "0x8e210b7d1aff606a6fc9e02898168d48ec39bc687086a7fe4be79622dd12284a5991eb53c4adfe848251f20d5bfe9de0", - "0x82810111e10c654a6c07cbfd1aff66727039ebc3226eef8883d570f25117acf259b1683742f916ac287097223afc6343", - "0x92f0e28cca06fd543f2f620cc975303b6e9a3d7c96a760e1d65b740514ccd713dc7a27a356a4be733570ca199edd17ba", - "0x900810aa4f98a0d6e13baf5403761a0aeb6422249361380c52f98b2c79c651e3c72f7807b5b5e3a30d65d6ff7a2a9203", - "0xb0740bfefea7470c4c94e85185dbe6e20685523d870ff3ef4eb2c97735cef41a6ab9d8f074a37a81c35f3f8a7d259f0e", - "0xaf022e98f2f418efbbe2de6fefb2aa133c726174f0f36925a4eafd2c6fd6c744edb91386bafb205ce13561de4294f3a6", - "0x95e4592e21ba97e950abb463e1bc7b0d65f726e84c06a98eb200b1d8bfc75d4b8cff3f55924837009e88272542fd25ec", - "0xb13bd6b18cd8a63f76c9831d547c39bbd553bda66562c3085999c4da5e95b26b74803d7847af86b613a2e80e2f08caae", - "0xa5625658b474a95aba3e4888c57d82fb61c356859a170bc5022077aa6c1245022e94d3a800bf7bd5f2b9ab1348a8834e", - "0xa097ee9e6f1d43e686df800c6ce8cfc1962e5a39bb6de3cf5222b220a41b3d608922dae499bce5c89675c286a98fdabd", - "0x94230ba8e9a5e9749cd476257b3f14a6bf9683e534fb5c33ca21330617533c773cb80e508e96150763699ad6ecd5aee7", - "0xb5fea7e1f4448449c4bc5f9cc01ac32333d05f464d0ed222bf20e113bab0ee7b1b778cd083ceae03fdfd43d73f690728", - "0xa18a41a78a80a7db8860a6352642cdeef8a305714543b857ca53a0ee6bed70a69eeba8cfcf617b11586a5cc66af4fc4f", - "0x85d7f4b3ff9054944ac80a51ef43c04189d491e61a58abed3f0283d041f0855612b714a8a0736d3d25c27239ab08f2ec", - "0xb1da94f1e2aedd357cb35d152e265ccfc43120825d86733fa007fc1e291192e8ff8342306bef0c28183d1df0ccec99d0", - "0x852893687532527d0fbeea7543ac89a37195eadab2f8f0312a77c73bdeed4ad09d0520f008d7611539425f3e1b542cfd", - "0x99e3bd4d26df088fc9019a8c0b82611fd4769003b2a262be6b880651d687257ded4b4d18ccb102cba48c5e53891535e4", - "0x98c407bc3bbc0e8f24bedf7a24510a5d16bce1df22940515a4fbdacd20d06d522ef9405f5f9b9b55964915dd474e2b5c", - "0x80de0a12f917717c6fc9dc3ccc9732c28bae36cff4a9f229d5eaf0d3e43f0581a635ba2e38386442c973f7cb3f0fdfa7", - "0x94f9615f51466ae4bb9c8478200634b9a3d762d63f2a16366849096f9fc57f56b2e68fe0ca5d4d1327a4f737b3c30154", - "0xa3dcbe16499be5ccb822dfcd7c2c8848ba574f73f9912e9aa93d08d7f030b5076ca412ad4bf6225b6c67235e0ab6a748", - "0x98f137bf2e1aea18289750978feb2e379054021e5d574f66ca7b062410dcfe7abb521fab428f5b293bbe2268a9af3aa4", - "0x8f5021c8254ba426f646e2a15b6d96b337a588f4dfb8cbae2d593a4d49652ca2ada438878de5e7c2dbbd69b299506070", - "0x8cc3f67dd0edcdb51dfd0c390586622e4538c7a179512f3a4f84dd7368153a28b1cf343afd848ac167cb3fcaa6aee811", - "0x863690f09ac98484d6189c95bc0d9e8f3b01c489cb3f9f25bf7a13a9b6c1deaf8275ad74a95f519932149d9c2a41db42", - "0x8494e70d629543de6f937b62beca44d10a04875bd782c9a457d510f82c85c52e6d34b9c3d4415dd7a461abbcc916c3c4", - "0x925b5e1e38fbc7f20371b126d76522c0ea1649eb6f8af8efb389764ddcf2653775ef99a58a2dcf1812ce882964909798", - "0x94d0494dcc44893c65152e7d42f4fb0dc46af5dc5674d3c607227160447939a56d9f9ea2b3d3736074eef255f7ec7566", - "0xb0484d33f0ef80ff9b9d693c0721c77e518d0238918498ddf71f14133eb484defb9f9f7b9083d52bc6d6ba2012c7b036", - "0x8979e41e0bb3b501a7ebbd024567ce7f0171acfea8403a530fe9e791e6e859dfbd60b742b3186d7cf5ab264b14d34d04", - "0xaf93185677d39e94a2b5d08867b44be2ba0bb50642edca906066d80facde22df4e6a7a2bd8b2460a22bdf6a6e59c5fdd", - "0x90f0ef0d7e7ab878170a196da1b8523488d33e0fde7481f6351558b312d00fa2b6b725b38539063f035d2a56a0f5e8f1", - "0xa9ca028ccb373f9886574c2d0ea5184bc5b94d519aa07978a4814d649e1b6c93168f77ae9c6aa3872dd0eea17968ec22", - "0x82e7aa6e2b322f9f9c180af585b9213fb9d3ad153281f456a02056f2d31b20d0f1e8807ff0c85e71e7baca8283695403", - "0xaffce186f842c547e9db2dffc0f3567b175be754891f616214e8c341213cbf7345c9ecd2f704bb0f4b6eba8845c8d8a7", - "0xab119eb621fade27536e98c6d1bc596388bb8f5cad65194ea75c893edbe6b4d860006160f1a9053aea2946bd663e5653", - "0x99cd2c1c38ead1676657059dc9b43d104e8bd00ae548600d5fc5094a4d875d5b2c529fac4af601a262045e1af3892b5e", - "0xb531a43b0714cc638123487ef2f03dfb5272ff399ff1aa67e8bc6a307130d996910fb27075cbe53050c0f2902fc32ffe", - "0x923b59ac752c77d16b64a2d0a5f824e718460ef78d732b70c4c776fecc43718ecfaf35f11afbb544016232f445ecab66", - "0xa53439cd05e6e1633cdce4a14f01221efcd3f496ac1a38331365c3cadc30013e5a71600c097965927ee824b9983a79cb", - "0x8af976ffab688d2d3f9e537e2829323dda9abf7f805f973b7e0a01e25c88425b881466dee37b25fda4ea683a0e7b2c03", - "0x92e5f40230a9bfbb078fa965f58912abb753b236f6a5c28676fb35be9b7f525e25428160caeaf0e3645f2be01f1a6599", - "0x8c4e7b04e2f968be527feba16f98428508a157b7b4687399df87666a86583b4446a9f4b86358b153e1660bb80bd92e8b", - "0x97cd622d4d8e94dceb753c7a4d49ea7914f2eb7d70c9f56d1d9a6e5e5cc198a3e3e29809a1d07d563c67c1f8b8a5665a", - "0x967bfa8f411e98bec142c7e379c21f5561f6fd503aaf3af1a0699db04c716c2795d1cb909cccbcb917794916fdb849f1", - "0xb3c18a6caa5ca2be52dd500f083b02a4745e3bcaed47b6a000ce7149cee4ed7a78d2d7012bf3731b1c15c6f04cbd0bd1", - "0xb3f651f1f84026f1936872956a88f39fcfe3e5a767233349123f52af160f6c59f2c908c2b5691255561f0e70620c8998", - "0xae23b59dc2d81cec2aebcaaf607d7d29cf588f0cbf7fa768c422be911985ca1f532bb39405f3653cc5bf0dcba4194298", - "0xa1f4da396f2eec8a9b3252ea0e2d4ca205f7e003695621ae5571f62f5708d51ca3494ac09c824fca4f4d287a18beea9a", - "0xa036fa15e929abed7aac95aa2718e9f912f31e3defd224e5ed379bf6e1b43a3ad75b4b41208c43d7b2c55e8a6fedca72", - "0x80e8372d8a2979ee90afbdb842624ace72ab3803542365a9d1a778219d47f6b01531185f5a573db72213ab69e3ffa318", - "0xaf68b5cdc39e5c4587e491b2e858a728d79ae7e5817a93b1ea39d34aec23dea452687046c8feae4714def4d0ed71da16", - "0xb36658dfb756e7e9eec175918d3fe1f45b398679f296119cd53be6c6792d765ef5c7d5afadc5f3886e3f165042f4667f", - "0xad831da03b759716f51099d7c046c1a8e7bf8bb45a52d2f2bfd769e171c8c6871741ef8474f06e2aca6d2b141cf2971f", - "0x8bae1202dde053c2f59efc1b05cb8268ba9876e4bd3ff1140fa0cc5fa290b13529aede965f5efdff3f72e1a579efc9cc", - "0x86344afbc9fe077021558e43d2a032fcc83b328f72948dba1a074bb1058e8a8faec85b1c019fc9836f0d11d2585d69c8", - "0x831d1fc7aa28f069585d84c46bdc030d6cb12440cfaae28098365577fc911c4b8f566d88f80f3a3381be2ec8088bf119", - "0x899de139797ac1c8f0135f0656f04ad4f9b0fa2c83a264d320eb855a3c0b9a4907fc3dc01521d33c07b5531e6a997064", - "0x855bc752146d3e5b8ba7f382b198d7dc65321b93cdfc76250eabc28dba5bbf0ad1be8ccda1adf2024125107cb52c6a6e", - "0xaf0aeccab48eb35f8986cabf07253c5b876dd103933e1eee0d99dc0105936236b2a6c413228490ed3db4fa69aab51a80", - "0xae62e9d706fbf535319c909855909b3deba3e06eaf560803fa37bce3b5aab5ea6329f7609fea84298b9da48977c00c3b", - "0x823a8d222e8282d653082d55a9508d9eaf9703ce54d0ab7e2b3c661af745a8b6571647ec5bd3809ae6dddae96a220ea7", - "0xa4c87e0ea142fc287092bc994e013c85e884bc7c2dde771df30ca887a07f955325c387b548de3caa9efa97106da8176a", - "0xb55d925e2f614f2495651502cf4c3f17f055041fa305bb20195146d896b7b542b1e45d37fa709ca4bfc6b0d49756af92", - "0xb0ebe8947f8c68dc381d7bd460995340efcbb4a2b89f17077f5fde3a9e76aef4a9a430d1f85b2274993afc0f17fdbead", - "0x8baaa640d654e2652808afd68772f6489df7cad37b7455b9cd9456bdddae80555a3f84b68906cc04185b8462273dcfc9", - "0xadd9aa08f827e7dc292ac80e374c593cd40ac5e34ad4391708b3db2fe89550f293181ea11b5c0a341b5e3f7813512739", - "0x909e31846576c6bdd2c162f0f29eea819b6125098452caad42451491a7cde9fd257689858f815131194200bca54511f4", - "0xabc4b34098db10d71ce7297658ef03edfa7377bd7ed36b2ffbab437f8fd47a60e2bcfbc93ff74c85cfce74ca9f93106c", - "0x857dbecc5879c1b952f847139484ef207cecf80a3d879849080758ef7ac96acfe16a11afffb42daf160dc4b324279d9b", - "0xaab0b49beecbcf3af7c08fbf38a6601c21061bed7c8875d6e3c2b557ecb47fd93e2114a3b09b522a114562467fcd2f7d", - "0x94306dec35e7b93d43ed7f89468b15d3ce7d7723f5179cacc8781f0cf500f66f8c9f4e196607fd14d56257d7df7bf332", - "0x9201784d571da4a96ef5b8764f776a0b86615500d74ec72bc89e49d1e63a3763b867deca07964e2f3914e576e2ca0ded", - "0xaabe1260a638112f4280d3bdea3c84ce3c158b81266d5df480be02942cecf3de1ac1284b9964c93d2db33f3555373dcc", - "0x8ef28607ca2e0075aa07de9af5a0f2d0a97f554897cab8827dfe3623a5e9d007d92755d114b7c390d29e988b40466db9", - "0x87a9b1b097c3a7b5055cd9cb0c35ba6251c50e21c74f6a0bca1e87e6463efc38385d3acc9d839b4698dfa2eb4cb7a2ef", - "0xaee277e90d2ffce9c090295c575e7cd3bafc214d1b5794dd145e6d02d987a015cb807bd89fd6268cd4c59350e7907ee2", - "0x836ad3c9324eaa5e022e9835ff1418c8644a8f4cd8e4378bd4b7be5632b616bb6f6c53399752b96d77472f99ece123cd", - "0x8ffffdb67faa5f56887c834f9d489bb5b4dab613b72eac8abf7e4bcb799ccd0dbd88a2e73077cadf7e761cb159fb5ec5", - "0x9158f6cd4f5e88e6cdb700fddcbc5a99b2d31a7a1b37dce704bd9dd3385cca69607a615483350a2b1153345526c8e05d", - "0xa7ff0958e9f0ccff76742fc6b60d2dd91c552e408c84172c3a736f64acb133633540b2b7f33bc7970220b35ce787cd4e", - "0x8f196938892e2a79f23403e1b1fb4687a62e3a951f69a7874ec0081909eb4627973a7a983f741c65438aff004f03ba6f", - "0x97e3c1981c5cdb0a388f1e4d50b9b5b5f3b86d83417831c27b143698b432bb5dba3f2e590d6d211931ed0f3d80780e77", - "0x903a53430b87a7280d37816946245db03a49e38a789f866fe00469b7613ee7a22d455fb271d42825957282c8a4e159d9", - "0xb78955f686254c3994f610e49f1c089717f5fb030da4f9b66e9a7f82d72381ba77e230764ab593335ff29a1874848a09", - "0x938b6d04356b9d7c8c56be93b0049d0d0c61745af7790edf4ef04e64de2b4740b038069c95be5c91a0ba6a1bb38512a9", - "0xa769073b9648fe21bc66893a9ef3b8848d06f4068805a43f1c180fdd0d37c176b4546f8e5e450f7b09223c2f735b006f", - "0x863c30ebe92427cdd7e72d758f2c645ab422e51ecef6c402eb1a073fd7f715017cd58a2ad1afe7edccdf4ff01309e306", - "0xa617b0213d161964eccfc68a7ad00a3ee4365223b479576e887c41ef658f846f69edf928bd8da8785b6e9887031f6a57", - "0xa699834bf3b20d345082f13f360c5f8a86499e498e459b9e65b5a56ae8a65a9fcb5c1f93c949391b4795ef214c952e08", - "0x9921f1da00130f22e38908dd2e44c5f662ead6c4526ebb50011bc2f2819e8e3fca64c9428b5106fa8924db76b7651f35", - "0x98da928be52eb5b0287912fd1c648f8bbda00f5fd0289baf161b5a7dbda685db6ad6bdc121bc9ffa7ed6ae03a13dbee3", - "0x927b91d95676ff3c99de1312c20f19251e21878bfb47ad9f19c9791bc7fb9d6f5c03e3e61575c0760180d3445be86125", - "0xb8e4977a892100635310dfcb46d8b74931ac59ae687b06469b3cee060888a3b6b52d89de54e173d9e1641234754b32b1", - "0x98f6fd5f81ca6e2184abd7a3a59b764d4953d408cec155b4e5cf87cd1f6245d8bdd58b52e1e024e22903e85ae15273f1", - "0x909aaacbbfe30950cf7587faa190dc36c05e3c8131749cc21a0c92dc4afc4002275762ca7f66f91aa751b630ad3e324d", - "0x91712141592758f0e43398c075aaa7180f245189e5308e6605a6305d01886d2b22d144976b30460d8ce17312bb819e8f", - "0x947d85cb299b189f9116431f1c5449f0f8c3f1a70061aa9ebf962aa159ab76ee2e39b4706365d44a5dbf43120a0ac255", - "0xb39eced3e9a2e293e04d236976e7ee11e2471fe59b43e7b6dd32ab74f51a3d372afee70be1d90af017452ec635574e0e", - "0x8a4ba456491911fc17e1cadcbb3020500587c5b42cf6b538d1cb907f04c65c168add71275fbf21d3875e731404f3f529", - "0x8f6858752363e2a94c295e0448078e9144bf033ccd4d74f4f6b95d582f3a7638b6d3f921e2d89fcd6afd878b12977a9d", - "0xb7f349aa3e8feb844a56a42f82b6b00f2bfe42cab19f5a68579a6e8a57f5cf93e3cdb56cbbb9163ab4d6b599d6c0f6aa", - "0xa4a24dc618a6b4a0857fb96338ac3e10b19336efc26986e801434c8fdde42ca8777420722f45dfe7b67b9ed9d7ce8fb1", - "0xaafe4d415f939e0730512fc2e61e37d65c32e435991fb95fb73017493014e3f8278cd0d213379d2330b06902f21fe4e1", - "0x845cc6f0f0a41cc6a010d5cb938c0ef8183ff5ed623b70f7ea65a8bdbc7b512ea33c0ee8b8f31fdf5f39ec88953f0c1e", - "0x811173b4dd89d761c0bdffe224cd664ef303c4647e6cf5ef0ed665d843ed556b04882c2a4adfc77709e40af1cfdea40b", - "0x93ba1db7c20bfba22da123b6813cb38c12933b680902cef3037f01f03ab003f76260acc12e01e364c0d0cf8d45fca694", - "0xb41694db978b2cf0f4d2aa06fcfc4182d65fb7c9b5e909650705f779b28e47672c47707d0e5308cd680c5746c37e1bc7", - "0xa0e92c4c5be56a4ccf1f94d289e453a5f80e172fc90786e5b03c1c14ce2f3c392c349f76e48a7df02c8ae535326ea8fe", - "0x96cbeb1d0693f4f0b0b71ad30def5ccc7ad9ebe58dbe9d3b077f2ac16256cde10468875e4866d63e88ce82751aaf8ef6", - "0x935b87fd336f0bf366046e10f7c2f7c2a2148fa6f53af5607ad66f91f850894527ecec7d23d81118d3b2ee23351ed6ed", - "0xb7c2c1fa6295735f6b31510777b597bc8a7bfb014e71b4d1b5859be0d8d64f62a1587caafc669dfe865b365eb27bd94f", - "0xb25d93af43d8704ffd53b1e5c16953fd45e57a9a4b7acfcfa6dd4bf30ee2a8e98d2a76f3c8eba8dc7d08d9012b9694c6", - "0xb5a005cd9f891e33882f5884f6662479d5190b7e2aec1aa5a6d15a8cb60c9c983d1e7928e25e4cf43ec804eaea1d97b0", - "0x93f9f0725a06e4a0fb83892102b7375cf5438b5ebc9e7be5a655f3478d18706cf7dbb1cd1adcee7444c575516378aa1b", - "0x900d7cbf43fd6ac64961287fe593c08446874bfc1eb09231fc93de858ac7a8bca496c9c457bced5881f7bf245b6789e0", - "0x90c198526b8b265d75160ef3ed787988e7632d5f3330e8c322b8faf2ac51eef6f0ce5a45f3b3a890b90aecf1244a3436", - "0xb499707399009f9fe7617d8e73939cb1560037ad59ac9f343041201d7cc25379df250219fd73fa012b9ade0b04e92efa", - "0x94415f6c3a0705a9be6a414be19d478181d82752b9af760dda0dbd24a8ff0f873c4d89e61ad2c13ebf01de55892d07fa", - "0x90a9f0b9f1edb87751c696d390e5f253586aae6ebfc31eb3b2125d23877a497b4aa778de8b11ec85efe49969021eaa5a", - "0xa9942c56506e5cd8f9289be8205823b403a2ea233ba211cf72c2b3827064fd34cd9b61ff698a4158e7379891ca4120d8", - "0x83bb2ee8c07be1ab3a488ec06b0c85e10b83a531758a2a6741c17a3ccfa6774b34336926a50e11c8543d30b56a6ac570", - "0x8a08a3e5ebe10353e0b7fff5f887e7e25d09bb65becf7c74a03c60c166132efaada27e5aea242c8b9f43b472561ae3ed", - "0x957c7a24cefaa631fe8a28446bc44b09a3d8274591ade53ba489757b854db54820d98df47c8a0fbee0e094f8ad7a5dc4", - "0xb63556e1f47ed3ee283777ed46b69be8585d5930960d973f8a5a43508fc56000009605662224daec2de54ea52a8dcd82", - "0xabed2b3d16641f0f459113b105f884886d171519b1229758f846a488c7a474a718857323c3e239faa222c1ab24513766", - "0x882d36eed6756d86335de2f7b13d753f91c0a4d42ef50e30195cc3e5e4f1441afa5ff863022434acb66854eda5de8715", - "0xa65ea7f8745bb8a623b44e43f19158fd96e7d6b0a5406290f2c1348fc8674fbfc27beb4f724cc2b217c6042cb82bc178", - "0xa038116a0c76af090a069ca289eb2c3a615b96093efacfe68ea1610890b291a274e26b445d34f414cfec00c333906148", - "0x90294f452f8b80b0a47c3bcb6e30bdd6854e3b01deaf93f5e82a1889a4a1036d17ecb59b48efa7dc41412168d7a523dd", - "0x88faf969c8978a756f48c6114f7f33a1ca3fd7b5865c688aa9cd32578b1f7ba7c06120502f8dc9aee174ecd41597f055", - "0x8883763b2762dfff0d9be9ac19428d9fd00357ac8b805efda213993152b9b7eb7ba3b1b2623015d60778bffda07a724d", - "0xa30a1a5a9213636aa9b0f8623345dc7cf5c563b906e11cc4feb97d530a1480f23211073dcb81105b55193dcde5a381d2", - "0xb45ee93c58139a5f6be82572d6e14e937ef9fcbb6154a2d77cb4bf2e4b63c5aabc3277527ecf4e531fe3c58f521cc5e3", - "0xac5a73e4f686978e06131a333f089932adda6c7614217fcaf0e9423b96e16fd73e913e5e40bf8d7800bed4318b48d4b1", - "0xb6c1e6cdd14a48a7fe27cd370d2e3f7a52a91f3e8d80fb405f142391479f6c6f31aa5c59a4a0fdc9e88247c42688e0cf", - "0xab1760530312380152d05c650826a16c26223960fc8e3bf813161d129c01bac77583eff04ce8678ff52987a69886526b", - "0xa4252dffae7429d4f81dfaeeecc48ab922e60d6a50986cf063964f282e47407b7e9c64cf819da6f93735de000a70f0b2", - "0x94c19f96d5ecf4a15c9c5a24598802d2d21acbbd9ee8780b1bc234b794b8442437c36badc0a24e8d2cff410e892bb1d2", - "0x89fafe1799cf7b48a9ea24f707d912fccb99a8700d7287c6438a8879f3a3ca3e60a0f66640e31744722624139ba30396", - "0xb0108405df25cf421c2f1873b20b28552f4d5d1b4a0bf1c202307673927931cbd59f5781e6b8748ddb1206a5ec332c0b", - "0xaa0f0e7d09f12b48f1e44d55ec3904aa5707e263774126e0b30f912e2f83df9eb933ca073752e6b86876adaf822d14ba", - "0xb0cbe8abb58876d055c8150d9fdbde4fea881a517a2499e7c2ea4d55c518a3c2d00b3494f6a8fd1a660bfca102f86d2a", - "0xb1ef80ec903bac55f58b75933dc00f1751060690fd9dfb54cf448a7a4b779c2a80391f5fda65609274bd9e0d83f36141", - "0x8b52e05b1845498c4879bb12816097be7fc268ce1cf747f83a479c8e08a44159fc7b244cf24d55aca06dccf0b97d11e1", - "0xb632a2fc4fdb178687e983a2876ae23587fd5b7b5e0bb8c0eb4cfe6d921a2c99894762e2aaccdc5da6c48da3c3c72f6c", - "0x953ef80ab5f74274ae70667e41363ae6e2e98ccbd6b7d21f7283f0c1cafb120338b7a8b64e7c189d935a4e5b87651587", - "0xb929cfd311017c9731eed9d08d073f6cf7e9d4cd560cddd3fdcb1149ab20c6610a7674a66a3616785b13500f8f43ee86", - "0x870fb0d02704b6a328e68721fb6a4b0f8647681bfcb0d92ec3e241e94b7a53aecc365ed384e721c747b13fbf251002f1", - "0x979501159833a8ba5422ed9b86f87b5961711f5b474d8b0e891373fe2d0b98ff41a3a7a74a8b154615bb412b662a48be", - "0xb20f9c13cdeceef67f877b3878839ef425f645b16a69c785fe38f687c87a03b9de9ae31ac2edb1e1dd3a9f2c0f09d35d", - "0x8c7705ed93290731b1cf6f3bf87fc4d7159bb2c039d1a9f2246cda462d9cdf2beef62d9f658cfeea2e6aef7869a6fc00", - "0xaa439eb15705ad729b9163daee2598d98a32a8a412777c0d12fd48dc7796d422227a014705e445cc9d66f115c96bbc24", - "0xa32307e16f89749fe98b5df1effef0429801c067e0d8067794e56b01c4fef742ad5e7ab42a1a4cc4741808f47a0b7cb8", - "0xb31e65c549003c1207258a2912a72f5bad9844e18f16b0773ea7af8ff124390eb33b2f715910fc156c104572d4866b91", - "0x85608d918ed7b08a0dc03aee60ea5589713304d85eee7b4c8c762b6b34c9355d9d2e192575af0fd523318ae36e19ae1c", - "0xa6497dbaf0e7035160b7a787150971b19cf5ba272c235b0113542288611ebecefa2b22f08008d3f17db6a70a542c258d", - "0x87862adb1ac0510614ab909457c49f9ec86dc8bdf0e4682f76d2739df11f6ffcfb59975527f279e890d22964a1fba9b6", - "0x8717ac3b483b3094c3b642f3fafe4fbafc52a5d4f2f5d43c29d9cfe02a569daee34c178ee081144494f3a2ca6e67d7b1", - "0x855100ac1ec85c8b437fdd844abaa0ca4ac9830a5bdd065b68dafb37046fcf8625dd482dc0253476926e80a4c438c9ec", - "0xae74821bf265ca3c8702c557cf9ef0732ede7ef6ed658283af669d19c6f6b6055aca807cf2fa1a64785ec91c42b18ae5", - "0x812a745b1419a306f7f20429103d6813cbdea68f82ff635ac59da08630cd61bda6e0fa9a3735bfd4378f58ad179c1332", - "0x867dbbfe0d698f89451c37ca6d0585fd71ee07c3817e362ef6779b7b1d70b27c989cdd5f85ac33a0498db1c4d14521fe", - "0x84db735d3eb4ff7f16502dccc3b604338c3a4a301220ad495991d6f507659db4b9f81bba9c528c5a6114bcdba0160252", - "0xaadc83d1c4e5e32bf786cfb26f2f12a78c8024f1f5271427b086370cdef7a71d8a5bf7cd7690bae40df56c38b1ad2411", - "0xa27860eb0caaea37298095507f54f7729d8930ac1929de3b7a968df9737f4c6da3173bda9d64ff797ed4c6f3a1718092", - "0xa3cdcaa74235c0440a34171506ed03d1f72b150d55904ce60ec7b90fcd9a6f46f0e45feab0f9166708b533836686d909", - "0xb209a30bdac5c62e95924928f9d0d0b4113ebb8b346d7f3a572c024821af7f036222a3bd38bd8efd2ee1dbf9ac9556cd", - "0x83c93987eff8bc56506e7275b6bef0946672621ded641d09b28266657db08f75846dcbde80d8abc9470e1b24db4ca65b", - "0x800c09b3ee5d0251bdaef4a82a7fe8173de997cc1603a2e8df020dd688a0c368ad1ebef016b35136db63e774b266c74c", - "0x93fb52de00d9f799a9bce3e3e31aaf49e0a4fc865473feb728217bd70f1bc8a732ec37ac3582bf30ab60e8c7fdf3cb8d", - "0xa1aff6b4a50d02f079a8895c74443539231bfdf474600910febf52c9151da7b31127242334ac63f3093e83a047769146", - "0x8c4532d8e3abb5f0da851138bfa97599039bcd240d87bbdf4fd6553b2329abb4781074b63caf09bc724ceb4d36cb3952", - "0x8bd9b0ae3da5acda9eb3881172d308b03beec55014cd73b15026299541c42fd38bab4983a85c06894ebb7a2af2a23d4c", - "0x979441e7f5a0e6006812f21b0d236c5f505bb30f7d023cb4eb84ec2aa54a33ac91d87ece704b8069259d237f40901356", - "0xa1c6d2d82e89957d6a3e9fef48deb112eb00519732d66d55aa0f8161e19a01e83b9f7c42ac2b94f337dcc9865f0da837", - "0x97a0b8e04e889d18947d5bf77d06c25bbd62b19ce4be36aaa90ddbeafd93a07353308194199ba138efaadf1b928cd8d2", - "0x822f7fbe9d966b8ec3db0fc8169ab39334e91bf027e35b8cc7e1fe3ead894d8982505c092f15ddfe5d8f726b360ac058", - "0xa6e517eedd216949e3a10bf12c8c8ddbfde43cddcd2c0950565360a38444459191bdbc6c0af0e2e6e98bc6a813601c6d", - "0x858b5f15c46c074adb879b6ba5520966549420cb58721273119f1f8bc335605aeb4aa6dbe64aae9e573ca7cc1c705cdc", - "0xb5191bb105b60deb10466d8114d48fb95c4d72036164dd35939976e41406dff3ee3974c49f00391abfad51b695b3258c", - "0xb1b375353ed33c734f4a366d4afad77168c4809aff1b972a078fd2257036fd6b7a7edad569533abf71bc141144a14d62", - "0xa94c502a9cdd38c0a0e0187de1637178ad4fa0763887f97cc5bdd55cb6a840cb68a60d7dbb7e4e0e51231f7d92addcff", - "0x8fe2082c1b410486a3e24481ae0630f28eb5b488e0bb2546af3492a3d9318c0d4c52db1407e8b9b1d1f23a7ffbaf260a", - "0xb73fe7aa2b73f9cae6001af589bf8a9e73ea2bb3bb01b46743e39390c08d8e1be5e85a3d562857a9c9b802b780c78e6d", - "0x8e347f51330ae62275441ccd60f5ac14e1a925a54ced8a51893d956acc26914df1bb8595385d240aa9b0e5ada7b520ea", - "0x8dc573d6357c0113b026a0191a5807dbe42dcd2e19772d14b2ca735e1e67c70e319ef571db1f2a20e62254ed7fb5bcd6", - "0xa5dacbe51549fe412e64af100b8b5eba5ec2258cc2a7c27a34bc10177d1894baf8707886d2f2ef438f077596a07681e9", - "0x8349153c64961d637a5ff56f49003cb24106de19a5bbcf674016a466bfbe0877f5d1e74ccb7c2920665ef90a437b1b7e", - "0x96ad35429d40a262fdc8f34b379f2e05a411057d7852c3d77b9c6c01359421c71ef8620f23854e0f5d231a1d037e3a0d", - "0xb52385e40af0ed16e31c2154d73d1517e10a01435489fc801fbea65b92b3866ab46dab38d2c25e5fb603b029ae727317", - "0x8e801c7a3e8fa91d9c22ebd3e14a999023a7b5beea13ec0456f7845425d28c92452922ca35ec64012276acb3bbc93515", - "0xa8630870297d415e9b709c7f42aa4a32210b602f03a3015410123f0988aea2688d8bcfc6d07dc3602884abbf6199b23f", - "0x8cd518392e09df2a3771a736f72c05af60efc030d62dbbb9cd68dc6cbbe1fb0854eb78b6ed38337010eb1bb44a5d5d30", - "0x921aa4c66590f6c54bf2fa2b324f08cbe866329cc31f6e3477f97f73e1a1721d5eb50ed4eacc38051fe9eda76ba17632", - "0xa37e595cb63524cb033c5540b6343c3a292569fc115e813979f63fe1a3c384b554cecc2cae76b510b640fe3a18800c81", - "0xb0bb57e4e31ae3ce9f28cef158ed52dabfad5aa612f5fcc75b3f7f344b7cec56b989b5690dacd294e49c922d550ee36b", - "0xa3c618ce4d091e768c7295d37e3f9b11c44c37507ae1f89867441f564bf0108f67bf64b4cf45d73c2afc17a4dc8b2c68", - "0x999e6650eda5455e474c22a8c7a3fd5b547ec2875dc3043077ad70c332f1ccd02135e7b524fcbf3621d386dec9e614fa", - "0xb018f080888dec3c2ca7fcfeb0d3d9984699b8435d8823079fc9e1af4ca44e257fbe8da2f6f641ee6152b5c7110e3e3c", - "0xa2bcd4bcd9b40c341e9bba76b86481842f408166c9a7159205726f0776dcb7f15a033079e7589699e9e94ce24b2a77fd", - "0xb03de48f024a520bb9c54985ca356fd087ca35ac1dd6e95168694d9dae653138c9755e18d5981946a080e32004e238fe", - "0xa6c1a54973c0c32a410092441e20594aa9aa3700513ed90c8854956e98894552944b0b7ee9edf6e62e487dc4565baa2f", - "0x845d7abf577c27c4c1fafc955dcad99a1f2b84b2c978cfe4bd3cd2a6185979491f3f3b0ec693818739ed9184aba52654", - "0x9531bcfc0d3fcd4d7459484d15607d6e6181cee440ba6344b12a21daa62ff1153a4e9a0b5c3c33d373a0a56a7ad18025", - "0xa0bbf49b2dd581be423a23e8939528ceaae7fb8c04b362066fe7d754ca2546304a2a90e6ac25cdf6396bf0096fae9781", - "0xa1ec264c352e34ed2bf49681b4e294ffea7d763846be62b96b234d9a28905cdece4be310a56ec6a00fc0361d615b547c", - "0x87c575e85b5dfbfd215432cb355a86f69256fff5318e8fda457763ac513b53baa90499dc37574bdfad96b117f71cb45e", - "0x9972edfdeec56897bef4123385ee643a1b9dc24e522752b5a197ce6bd2e53d4b6b782b9d529ca50592ee65b60e4c9c3c", - "0xb8bcf8d4ab6ad37bdd6ad9913a1ba0aba160cb83d1d6f33a8524064a27ba74a33984cc64beeee9d834393c2636ff831a", - "0x83082b7ec5b224422d0ff036fbb89dc68918e6fde4077dfc0b8e2ee02595195ecadb60c9ab0ad69deb1bac9be75024fa", - "0x8b061fce6df6a0e5c486fd8d8809f6f3c93bd3378a537ff844970492384fb769d3845d0805edd7f0fcd19efabf32f197", - "0xb9597e717bb53e6afae2278dbc45d98959c7a10c87c1001ed317414803b5f707f3c559be6784119d08f0c06547ec60b1", - "0xb9d990fd7677dd80300714cfd09336e7748bbf26f4bb0597406fcb756d8828c33695743d7a3e3bd6ddf4f508149610ef", - "0xb45f7d2b00ceea3bf6131b230b5b401e13a6c63ba8d583a4795701226bf9eb5c88506f4a93219ac90ccbceef0bfd9d49", - "0xa8ccaa13ca7986bc34e4a4f5e477b11ae91abb45c8f8bf44a1f5e839289681495aba3daa8fb987e321d439bbf00be789", - "0xae0f59f7a94288a0ead9a398fdd088c2f16cccb68624de4e77b70616a17ddf7406ca9dc88769dadeb5673ff9346d6006", - "0xb28e965dcc08c07112ae3817e98f8d8b103a279ad7e1b7c3de59d9dbd14ab5a3e3266775a5b8bbf0868a14ae4ab110f1", - "0x84751c1a945a6db3df997fcbde9d4fe824bc7ba51aa6cb572bb5a8f9561bef144c952198a783b0b5e06f9dd8aa421be8", - "0xa83586db6d90ef7b4fa1cbda1de1df68ee0019f9328aded59b884329b616d888f300abb90e4964021334d6afdea058fd", - "0x8fcea1ce0abf212a56c145f0b8d47376730611e012b443b3d1563498299f55cbcbe8cbd02f10b78224818bb8cbbd9aaa", - "0x8d66c30a40c34f23bae0ea0999754d19c0eb84c6c0aa1b2cf7b0740a96f55dd44b8fee82b625e2dd6c3182c021340ac6", - "0x92c9b35076e2998f1a0f720d5a507a602bd6bd9d44ffc29ede964044b17c710d24ce3c0b4a53c12195de93278f9ec83b", - "0xa37d213913aff0b792ee93da5d7e876f211e10a027883326d582ad7c41deebdfce52f86b57d07868918585908ebd070a", - "0xa03995b4c6863f80dd02ed0169b4f1609dc48174ec736de78be1cdff386648426d031f6d81d1d2a7f2c683b31e7628c0", - "0xb08b628d481302aa68daf0fa31fd909064380d62d8ed23a49037cb38569058e4c16c80e600e84828d37a89a33c323d1f", - "0xa0ee2e2dd8e27661d7b607c61ac36f590909aa97f80bdfd5b42463ca147b610ac31a9f173cbecdd2260f0f9ea9e56033", - "0x967162fba8b69ffce9679aac49214debb691c6d9f604effd6493ce551abacbe4c8cc2b0ccee6c9927c3d3cfbdcb0be11", - "0x8deab0c5ed531ce99dadb98b8d37b3ff017f07438bc6d50840577f0f3b56be3e801181333b4e8a070135f9d82872b7f2", - "0xb1bfa00ec8c9365b3d5b4d77a718cb3a66ed6b6cf1f5cf5c5565d3aa20f63d3c06bb13d47d2524e159debf81325ba623", - "0x90109780e53aeacd540b9fe9fc9b88e83c73eaf3507e2b76edc67f97a656c06a8a9e1ec5bce58bfd98b59a6b9f81b89d", - "0x88a1009a39a40421fdcc0ffc3c78a4fbace96a4e53420b111218091223494e780a998ebecf5a0abd0243e1523df90b28", - "0x90b77146711ee8d91b0346de40eca2823f4e4671a12dad486a8ec104c01ef5ee7ab9bd0398f35b02b8cb62917455f8b3", - "0xb262c5e25f24ae7e0e321b66fdb73b3bf562ded566a2d6a0152cf8bafb56138d87b6a917a82f5ace65efc73cfc177d81", - "0xae65a438c7ea46c82925b5ec5f71314558ca5146f5d90311431d363cfeac0537223c02cbb50fa6535d72fc2d949f4482", - "0x8984208bfc193a6ef4720cc9d40c17f4be2f14595ef887980f2e61fa6927f9d73c00220937013b46290963116cbe66ac", - "0xa8f33a580508f667fac866456dce5d9246562188ad0f568eb1a2f28cf9fd3452dd20dc613adb1d07a5542319a37ecf1a", - "0xaedadd705fc086d8d2b647c62e209e2d499624ab37c8b19af80229f85e64a6e608d9cd414cb95ae38cf147d80ec3f894", - "0xae28077a235cd959f37dc3daedc3706f7a7c2ffe324e695f2e65f454bf5a9fc27b10149a6268ebfaa961ad67bb9b75d7", - "0xa234c7f5a5e0e30f2026d62657bd92d91a9907ec6a2177f91383f86abb919778121ff78afb8f52c473fe6fb731018b52", - "0x816a2ea7826b778f559a815267b6c6eb588558391c0a675d61bb19470d87489ba6c1e2486ea81dd5420a42ee7c35a8de", - "0x9218b61948c14234f549c438105ae98367ef6b727ad185f17ad69a6965c044bb857c585b84d72ef4c5fb46962974eed7", - "0xa628031217a0b1330b497351758cf72d90fb87d8bdf542ea32092e14ff32d5ef4ca700653794bb78514d4b0edfd7a8d7", - "0xab4e977141be639a78eb9ed17366f9642f9335873aca87cce2bae0dddc161621d0e23264a54a7395ae706d748c690ee9", - "0xb1538c4edff59bcf5668557d994bac77d508c757e382512c4368c1ded4242a41f6200b73fe8809fb528a7a0c1fc96feb", - "0x965caabe5590e2ff8c9f1048bbdda2817e7a2847e287944bfab40d94cb48389441ac42ff3a7b559760bfab42ff82e1e0", - "0xa64b7484d22c4b8047c7a8ef54dc88cb8d110c61ef28ba853821b61e87d318b2b4226f7f0d1f3cdf086a0e1666d0212c", - "0x8915ab7e41d974eef9a651b01c2521392e8899e6ab91c22aeee61605c78fb2b052399ba1d03473aa9cfb52d1a8ba4257", - "0x8dd26875d4a1716db2f75a621d01e971983267770e2da92399aecf08f74af1f7e73643ac6f0a9b610eda54e5460f70ed", - "0x83dabcb84c9cbce67e1a24ecbfa4473766b9519588b22288edbaa29aca34cefd9884f7310e7771f8f7a7cbced2e7eea0", - "0x956be00c67987fb4971afca261065a7f6fcef9fb6b1fcb1939f664bbc5b704223253ebfda48565624a68fb249742c2cf", - "0xa374824a24db1ab298bee759cee8d8260e0ac92cd1c196f896600fd57484a9f9be1912ded01203976ac4fab66c0e5091", - "0xa225f2ed0de4e06c500876e68e0c58be49535885378584a1442aae2140c38d3ca35c1bc41936a3baf8a78e7ab516f790", - "0x8e79c8de591a6c70e2ef2de35971888ab0ca6fd926fdb6e845fb4b63eb3831c5839f084201b951984f6d66a214b946b8", - "0x91babc849a9e67ab40192342c3d0d6ce58798101cb85c9bd7fc0ac4509ffc17b5ea19e58045cf1ca09ec0dee0e18c8f9", - "0x8b4897fc2aef5bbe0fa3c3015ca09fc9414fdb2315f54dbecc03b9ae3099be6c0767b636b007a804d8b248c56e670713", - "0x8f63ba42e7459ea191a8ad18de0b90b151d5acbf4751e2c790e7d8328e82c20de518132d6290ff3c23d2601f21c1558e", - "0xa1a035dc9b936587a16665ea25646d0bb2322f81960d9b6468c3234c9137f7c2b1e4f0b9dbe59e290a418007b0e7a138", - "0x81c4904c08f7bb2ac7b6d4ac4577f10dd98c318f35aac92fc31bab05eceb80a0556a7fc82614b8d95357af8a9c85a829", - "0x8c40e44e5e8e65f61e0a01f79057e1cb29966cc5074de790ea9c60454b25d7ea2b04c3e5decb9f27f02a7f3d3cb7014f", - "0xad8709e357094076eb1eb601539b7bcc37247a25fbc6ada5f74bb88b1b371917c2a733522190f076c44e9b8e2ae127fb", - "0x92d43cd82c943fd71b8700977244436c696df808c34d4633f0624700a3445f3ecc15b426c850f9fb60b9aa4708f2c7c0", - "0xb2cb8080697d1524a6dcb640b25e7255ae2e560613dbd27beaa8c5fc5c8d2524b7e6edd6db7ad0bb8a4e2e2735d4a6f7", - "0x971ca6393d9e312bfb5c33955f0325f34946d341ff7077151f0bcafd2e6cbd23e2ad62979454f107edc6a756a443e888", - "0xb6a563f42866afcee0df6c6c2961c800c851aa962d04543541a3cedeb3a6a2a608c1d8391cf405428cd40254e59138f3", - "0x986bd17bad9a8596f372a0185f7f9e0fb8de587cd078ae40f3cd1048305ba00954aff886b18d0d04640b718ea1f0d5a3", - "0xae32dbccfb7be8e9165f4e663b26f57c407f96750e0f3a5e8e27a7c0ca36bc89e925f64ddd116263be90ace4a27872c4", - "0x83725445ec8916c7c2dd46899241a03cf23568ac63ae2d34de3bce6d2db0bc1cfd00055d850b644a059fb26c62ed3585", - "0xa83f7e61c05b1c6797a36ad5ded01bf857a838147f088d33eb19a5f7652b88e55734e8e884d1d1103a50d4393dfcd7a8", - "0xaa010b4ec76260d88855347df9eaf036911d5d178302063d6fd7ecad009e353162177f92240fe5a239acd1704d188a9d", - "0xa88f4ba3cf4aff68ec1e3ded24622d4f1b9812350f6670d2909ea59928eb1d2e8d66935634d218aeac6d1a0fc6cae893", - "0xb819112b310b8372be40b2752c6f08426ef154b53ef2814ae7d67d58586d7023ffa29d6427a044a3b288e0c779866791", - "0xb5d1e728de5daf68e63b0bb1dee5275edae203e53614edeeeefff0f2f7ac4281191a33b7811de83b7f68111361ef42e1", - "0x953fb3ddc6f78045e53eaacfd83c5c769d32608b29391e05612e4e75725e54e82ad4960fbef96da8b2f35ba862968a3e", - "0x936471136fb2c1b3bb986a5207a225a8bf3b206a1a9db54dc3029e408e78c95bfb7539b67006d269c09df6354d7254ac", - "0xac353364b413cae799b13d7dc6fa09c322b47e60b9333e06499155e22d913929b92a45a0ad04ba90b29358f7b792d864", - "0xa0177419ead02ba3f0755a32eee3fd23ec81a13c01eab462f3b0af1e2dba42f81b47b2c8b1a90d8cec5a0afa371b7f11", - "0xb009eeb5db80d4244c130e6e3280af120917bb6fcebac73255c09f3f0c9da3b2aa718cd92d3d40e6b50737dbd23461aa", - "0xb8a43426c3746c1a5445535338c6a10b65474b684a2c81cd2f4b8ebecc91a57e2e0687df4a40add015cd12e351bbb3eb", - "0x94ff3698a6ac6e7df222675a00279c0ea42925dc6b748e3e74a62ea5d1e3fd70d5ab2d0c20b83704d389dd3a6063cf1a", - "0x90e4142e7ce15266144153e21b9893d3e14b3b4d980e5c87ce615ecd27efac87d86fa90354307857f75d7ebaeffe79ef", - "0xa5fd82c3f509ec9a36d72ba204a16f905e1e329f75cfd18aaa14fb00a212d21f3fac17e1a8e3bc5691ab0d07f8ec3cd0", - "0x962e6bfd75ea554f304a5fee1123e5bf2e048ccd3b401716b34c52740384579188ac98bc0d91269fc814de23f4b2dd34", - "0xb50b4e45c180badf9cd842cd769f78f963e077a9a4c016098dc19b18210580ad271ae1ba86de7760dd2e1f299c13f6a0", - "0x84cf08858d08eca6acc86158ffda3fbe920d1d5c04ac6f1fc677760e46e66599df697397373959acf319c31e47db115c", - "0xa697a38ba21caa66b7739ed0e74fe762a3da02144b67971fcad28c1132d7b83e0ac062cc71479f99e2219086d7d23374", - "0xad1f6d01dd7f0de814fe5fbb6f08c1190ff37f4a50754d7b6291fc547c0820506ea629aabacf749fec9c1bbfda22d2d0", - "0xb11fd7f8c120d8a370a223a1adc053a31bef7454b5522b848dec82de5482308fc68fdaf479875b7a4bc3fc94e1ea30eb", - "0x93ecf90ebfc190f30086bcaeee18cda972073a8469cf42a3b19f8c1ec5419dff2d6a5cc8ef412ccd9725b0f0a5f38f88", - "0x911f25aaa5260b56b3009fa5e1346a29f13a085cf8a61b36b2d851791f7bcf8456840eccbfc23797b63ecd312e2d5e12", - "0xa52f17a8b2db66c98291020b1db44ab23827e1790e418e078d1316185df6aa9f78292f43a12cd47131bd4b521d134060", - "0x9646fca10bf7401e91d9a49753c72f3ecb142f5ed13aba2c510a6c5ccb8d07b8e8d1581fc81321ad5e3996b6d81b5538", - "0xaa1da4a5665b91b62dda7f71bb19c8e3f6f49cc079d94fcd07b3604a74547e8334efa5a202822d0078158056bbda2822", - "0xa2432ae5feeaf38252c28aa491e92a68b47d5b4c6f44c1b3d7f3abc2f10b588f64a23c3357e742a0f5e4f216e7ca5827", - "0x83c7b47735cd0ef80658a387f34f259940096ebb9464c67919b278db4109fea294d09ea01a371b79b332cff6777c116d", - "0xa740a2959e86e413c62d6bdd1bc27efe9596ee363c2460535eab89ba1715e808b658bd9581b894b5d5997132b0c9c85c", - "0xb76947237fa9d71c3bece0b4f7119d7f94d2162d0ced52f2eac4de92b41da5b72ad332db9f31ebb2df1c02f400a76481", - "0xa20e1f2b7e9cc1443226d2b1a29696f627c83836116d64d2a5559d08b67e7e4efa9a849f5bb93a0dadb62450f5a9eaab", - "0xb44bff680fba52443a5b3bd25f69c5640006d544fca1d3dc11482ee8e03b4463aae59d1ec9d200aa6711ce72350580fb", - "0xa9490f5643bacec7e5adcda849ab3e7ff1f89026bf7597980b13a09887376f243158d0035e9d24fdee7cb6500e53ef29", - "0x96081010b82c04ad0bfc3605df622db27c10a91494685ef2e6e1839c218b91cbb56e043e9a25c7b18c5ddee7c6769517", - "0xa9522d59bcf887cbbbc130d8de3ff29a86df5d9343a918f5e52c65a28e4c33f6106ac4b48ecd849a33d39eeb2319d85b", - "0xaa5e0cea1a1db2283783788b4d77c09829563b75c503c154fdaa2247c9149918edac7737ef58c079e02dca7d8397b0eb", - "0x8c03f064e777d0c07c4f04c713a86bf581cc85155afe40e9065ead15139b47a50ead5c87ac032f01b142d63ff849758a", - "0xa34d672bf33def02ee7a63e6d6519676c052fa65ca91ed0fe5fdd785c231ba7af19f1e990fc33f5d1d17e75f6af270be", - "0x8680443393e8ac45a0b07c30a82ac18e67dcc8f20254bd5ede7bf99fc03e6123f2fcd64c0ca62f69d240f23acd777482", - "0xa4e00ab43d8ae5b13a6190f8ef5395ec17fbac4aa7dfa25b33e81b7e7bf63a4c28910b3a7dc9204dbc4168b08575a75e", - "0x8249259066ee5672b422c1889ab5ed620bddd1297f70b4197c40bb736afba05d513b91d3a82ee030336c311d952cd60c", - "0xa0651d8cf34fa971bde1ec037158a229e8e9ad4b5ca6c4a41adedb6d306a7772634f703dcfac36f9daf17289f33c23fb", - "0xb02ff6e8abff19969e265395ceaf465f43e7f1c3c9cfc91f1748042d9c352b284e49515a58078c877a37ff6915ee8bf4", - "0x927fb7351ac28254458a1a2ea7388e1fbd831fbc2feedb230818f73cc8c505b7ff61e150898ce1567fcb0d2c40881c7b", - "0xa9d3861f72090bc61382a81286bb71af93cdeefab9a83b3c59537ad21810104e0e054859eeafa13be10f8027b6fc33b8", - "0xa523306656730b1a31b9a370c45224b08baf45773d62952a0bf7d6c4684898ae78914cfafbd3e21406407cc39e12afdc", - "0x947a090e7703a3ea303a4a09b3ab6b6d3fda72912c9f42cc37627557028b4667f5398a6d64b9281fa2efbe16f6c61ed6", - "0xb41d24d40c10239c85d5b9bf1a3886d514a7a06b31ca982ea983e37162293350b12428eabc9f6a460473ad811e61ba40", - "0xb0bb9805724f4ca860e687985c0dc6b8f9017fe71147e5383cfbbbdcb2a42c93c7062ba42acdead9d992b6f48fc1d5ac", - "0xaec775aa97a78851893d3c5c209a91267f1daf4205bfb719c44a9ed2614d71854b95bb523cd04a7f818a4a70aa27d8fc", - "0xb53e52e32ca90b38987610585ad5b77ecd584bd22c55af7d7c9edf5fbcae9c9241b55200b51eaed0fbdb6f7be356368f", - "0xa2c5ac7822c2529f0201717b4922fb30fb037540ab222c97f0cdac341d09ccb1415e7908288fabef60177c0643ed21bf", - "0x92162fda0cbd1dafbed9419ae0837e470451403231ee086b49a21d20de2e3eed7ce64382153272b02cf099106688af70", - "0x8452d5df66682396718a76f219a9333a3559231e5f7f109a1f25c1970eb7c3408a5e32a479357f148af63b7a1d352451", - "0x831ea95d4feb520994bc4904017a557797e7ad455a431d94de03b873a57b24b127fcc9ff5b97c255c6c8d8e18c5c7e12", - "0x93d451d5e0885ccdbb113a267c31701e7c3d9e823d735dc9dfd6cfdcd82767012dc71396af53d3bedd2e0d9210acf57f", - "0xa2126f75a768dcc7ebddf2452aebf20ad790c844442b78e4027c0b511a054c27efb987550fcab877c46f2c7be4883ae0", - "0xaa4d2dcba2ccfc11a002639c30af6beb35e33745ecbab0627cf0f200fdae580e42d5a8569a9c971044405dfdafed4887", - "0xab13616069ef71d308e8bf6724e13737dc98b06a8f2d2631284429787d25d43c04b584793256ed358234e7cd9ad37d1f", - "0x9115ee0edc9f96a10edcafeb9771c74321106e7f74e48652df96e7ca5592a2f448659939291ff613dd41f42170b600ad", - "0x97b10a37243dc897ccc143da8c27e53ccc31f68220bffd344835729942bb5905ae16f71ccaed29ca189432d1c2cc09b1", - "0x875cf9c71ae29c3bde8cdcb9af5c7aca468fbb9243718f2b946e49314221a664959140c1ebc8622e4ed0ba81526302fd", - "0x86b193afbb7ff135ce5fc7eb0ee838a22e04806ceec7e02b3fb010e938fff733fc8e3a1d4b6cba970852d6307018b738", - "0xb3403a94f1483edce5d688e5ed4ab67933430ede39cd57e2cddb4b469479018757d37dd2687f7182b202967da12a6c16", - "0x83edfa0a6f77974c4047b03d7930e10251e939624afa2dcafbd35a9523c6bf684e1bb7915fc2e5b3ded3e6dc78daacf2", - "0x88ff3375fe33942e6d534f76ed0f1dfa35ae1d62c97c84e85f884da76092a83ecd08454096c83c3c67fac4cd966673d7", - "0xaf0726a2a92ee12a9411db66333c347e1a634c0ab8709cc0eab5043a2f4afac08a7ae3a15ce37f5042548c6764ae4cf6", - "0x81cfa33bb702e2f26169a006af0af0dcaa849cec2faf0f4784a06aa3c232d85a85b8123d49a1555cca7498d65e0317e4", - "0x910a16526176b6e01eb8fb2033ffbb8c9b48be6e65f4c52c582909681805b3d9e1c28e3b421be9b9829b32175b8d4d80", - "0x93d23befa411ca1adbdba726f762f2403e1cc740e44c9af3e895962e4047c2782ca7f2f9878512c37afd5a5a0abbd259", - "0x82fcf316027fedfe235905588b7651b41e703836f96cb7ac313b23b4e6c134bff39cd10b3bddb7458d418d2b9b3c471b", - "0x8febc47c5752c513c4e5573428ad0bb40e15a5e12dbfa4c1ef29453f0588f0b75c3591075fef698e5abcf4d50c818a27", - "0x83dab521d58b976dcea1576a8e2808dfaea9fa3e545902d0e0ce184d02dca8245d549134a238ab757950ad8bc11f56eb", - "0x898cfb9bf83c1c424eca817e8d0b99f5e482865070167adab0ecf04f3deeb3c71363b9f155c67b84d5e286c28238bef8", - "0xb845e388cc1a8e8b72a24d48219ac4fd7868ee5e30960f7074b27dada842aa206889122acfce9e28512038547b428225", - "0xb1ce4720e07e6eecc2a652f9edbad6bd5d787fbaff2a72a5ca33fa5a054dd3b4d5952563bc6db6d1ce1757a578bba480", - "0x8db6990dd10741cf5de36e47726d76a12ebe2235fdcb8957ab26dba9466e6707d4a795d4e12ec7400d961bd564bdee7e", - "0xa3ca7afd20e16c2a45f73fc36357763847ed0be11cb05bfd9722f92c7ba3fa708cf10d4e0ae726c3eccae23cc55fd2be", - "0x8701b085c45b36f3afb589207bbf245ef4c5c82aa967ecd0c334daa1f5a54093c5e0fcacd09be540801920f49766aa0f", - "0x84e3736727ba76191d9a6a2a3796f55bb3c3a8bbb6e41f58e892ea282c90530b53ab5490bbf1a066723399bb132160fb", - "0x87c02a01917333c7b8866f6b717b1e727b279894108f70574d1b6e9e8dc978eda8778342baf3c6464d6e0dd507163e76", - "0xb8da532dac81fafaed759e99c3ae011d75f3fda67a8c420c3b9747281fe32e31ac3c81e539940286440704c2c3e3b53e", - "0xa0cc63c3bef75a5c02942977a68a88cd3d103d829b6c0f070f64206da7e3638f10f42452788092de8fbbc626ce17b0d4", - "0xb5c9317b3f6b1d7ee6871506c0430cdf73e28b02c001ba6ca11061c7e121c91152d2b80c4f80e1d8f51ff5653bc0db5b", - "0xb798fb572da977dd3ef2dce64042b012a470d6bd2cb61a16267abe2b8399f74540d7c70462a6b2278d73567447e31994", - "0xb868eda58739effda68c834745cd2cf66a09f0f215607b65685bb5ca3eba71150f43a6e47b81a0c19fb58eeae3da56e8", - "0x9041c93a7e8f2c34812fd6e9744b154e898e1ef69db72bf36242c71e2c251f3db7e86cbd802da603a92cd0b06b62ea63", - "0xa834d648e974230582fc17b3a449f4f65b3297038a3a5401e975b9b60ff79b2006a33e1486d3428106580276993311e1", - "0xa3ce874da6ade9f0f854d7ae7651fc3ff63cec748a847527539fe0d67e6c99eaa3011065a4627c2192af7f9569f7ab57", - "0xae78ad16de150cc0400d3b6b424c608cd2b2d01a7a38ea9c4e504d8463c0af09613774dbefdd5198415b29904e0fbb63", - "0xb966db5a961067e743212d564595ef534e71dcd79b690a5a2c642d787059fc7959b9039b650372461a1f52910f7e857b", - "0x8069904f360af3edfd6cabd9b7f2adf5b61bd7feb0e9a040dc15c2a9d20052c3e5e0158f3065ec3200d19b91db603b71", - "0x9600917dbcd80a47f81c02c3aafecfcef77f031bf612a0f1a8bdef09de9656f4bb0f8e3e95f72ece1c22bd2824f145b6", - "0x834a0767b7b6199496c1faee0e3580c233cc0763e71eebc5d7c112a5a5e5bd95c0cf76a32ea5bb1b74f3cf00fbd2cfb4", - "0x99469a893579ed5da7d34ec228854c4666c58115d3cae86d4fc2d03d38f10d8c5dc8fb693763a96ab6be2045cc8d518b", - "0xa52cc0aecda6594de57d8ca13b146e77212cc55854929c03f2a8a6cdfa46296791c336aebcc2610d98612d5b4c0452df", - "0x97864434d55aa8a7aad0415d36f9558ce6e6c00452923db68a1e738232d0cb2d47e3b0b8f340c709112838adeaee4695", - "0xa4a7f2c45db3661b6af7ec759f9455ba043b0de6fd4787e3372cba215b9f7c641d5d817a0576e7aa28a46349d2fe0ae6", - "0x864e857652d95e1d168c1b9c294777fc9251a4d5b4b00a346b1f1c9c898af9a9b5ec0ac1f3a66f18a370b721dbd77b23", - "0xab8eac458fa8e7eb5539da3964ccd297a216448c3af4e4af0dcfed0ce29e877a85e29b9601dc7508a060b97a05f37e15", - "0xa6fd0782c5629c824fcd89ac80e81d95b97d8374c82010a1c69f30cef16ffc0f19e5da2d0648d2a36a636071cb4b69a7", - "0xad35a75fd8832643989d51d94ee6462d729e15f6444ffdf340dfb222af5d2b6b52e5df86082dbc7728fde7c1f28ac6b4", - "0x8e06831cc8a0c34245732ea610ea6aae6d02950299aa071a1b3df43b474e5baee815648784718b63acfd02a6655e8ea7", - "0x994ac097f913a4ce2a65236339fe523888ee43494499c5abf4ac3bce3e4b090f45d9abd750f4142a9f8f800a0115488c", - "0xa3e6a8e5e924f3a4f93e43f3f5aafb8b5831ce8169cddde7296c319d8964a0b6322a0aa69e1da1778fcc24b7de9d8b93", - "0x81a9bd04f4c6e75517de4b5e2713f746bd7f3f78a81a2d95adc87ba0e266d1f5e89c9cfb04b5159c1ff813f7968a27a4", - "0xb24de8f3a5b480981c6f29607b257ded665ecd8db73e2a69a32fcf44e926fdc7e6610598e10081cf270d2f879414b1ab", - "0xadc1b3f8ed1e7d5a26b0959ffe5afc19e235028b94cb7f364f6e57b6bf7f04742986f923fae9bf3802d163d4d0ebc519", - "0xa9fa5092b6dd0b4e1a338a06900b790abbc25e2f867b9fb319fdcdfb58600315a45a49584c614f0f9f8b844aa59dd785", - "0xb29c06b92b14215e7ef4120562893351ae8bf97cc5c3d64f4ecd0eb365b0e464cf27beec3f3ddac17ed5e725706b6343", - "0xadc0d532ba4c1c033da92ba31aa83c64054de79508d06ee335dcab5cabae204a05e427f6f8c2a556870a8230b4115fd0", - "0x9737150d439e6db2471d51e006891d9687593af4e38ee8e38bfa626abcefa768ca22d39133f865d0a25b8bbf7443d7db", - "0xa10d1e6a760f54d26c923c773b963534e5c2c0826c0a7462db2ea2c34d82890f9c58f0150db00aa2679aa0fdb1afcb08", - "0x816947dc6c08ee779e9c2229d73dbfd42c2b3b6749b98ec76dbad017f4b4d4f77b5916600b576691978287208c025d6f", - "0xa2dc52b6056219d999f07b11869c254e8b3977113fd9ba1a7f322377a5d20e16c2adf46efb7d8149e94989b3f063334a", - "0x8153900aae9cf48ebc7438b75c16f5478960ef9170e251708f0c2457967b7b31521c889b5fe843d2694a07c0e804fa48", - "0xa9e9d8d66c8774972cc1686809ce1fa5f0e16997ef2178b49bcd8654541b5b6e234cb55188f071477ba1cebcf770da45", - "0xb1fa775f9b2a9b05b4b1f0d6ad5635c7d7f4d3af8abaa01e28d32b62684f9921197ba040777711836bc78429bf339977", - "0xb1afbbd522b30e1ae2adf9a22993ab28b72a86a3d68d67b1833115e513632db075d047e21dfe442d6facc7b0a1b856bf", - "0x8779b7d22f42845a06ae31ac434e0044f5f9b4e704847fb93943e118e642a8b21265505ad9d6e418405d0cb529e00691", - "0xab2c6cef1c4e7c410e9e8deb74c84bedeb3c454ae98e3bc228eb13f6b7081b57977b3e849ba66346250e37c86842c10c", - "0x908d6c781d7d96aa2048c83e865896c720a66fdec7b06ab4b172192fe82f9ff6167815ffb66549d72bfb540bb35c36c6", - "0xb790440f205ece489e2703d5d1d01ba8921dd237c8814afb5cb521515ed4c3b0a6df45fd4bd65ba63592c2fe1d008df3", - "0xaec346251f9c78336b388c4e9069a1c6c3afbbb6bfaffdad050a9e70e92fb3cae3609067b4903552936f904c804b0ea6", - "0xa0e528cc2cb84b04cc91b4084e53ead4188682a6050b3857c34280899c8233aa8c1a9c6fa4fd6a7087acf1b36d67734a", - "0xaa8d7632be3e4340712a1461a0ad0ae90ba6d76e2916511c263f484c6c426939fa93ffbb702cd0341eea404d6ddffebb", - "0xa4ea871d8a1d4b925d890aefb9897847599b92e15ce14886b27ce5c879daa9edead26e02ccc33fcf37f40ff0783d4d9e", - "0xab63e4dc0dbdaf2ada03b3733aafe17e719d028b30dc9a7e5783c80933a39935dbe1ef0320bb03f9564cafdf7a4b029b", - "0x8219761bbaa39b96b835f9c2b4cec0bf53801f8e4f4a4498d19638be2fa0a193b2c1fbf94e26c1058d90a9ac145a7a12", - "0xa609ee5561828b0f634640c68a98da47cb872b714df7302ef6b24d253211e770acd0aa888802cd378e7fa036d829cd36", - "0x90793ff0736f3c80b5e0c5098b56cda8b0b2bca5032bb153d7b3aa3def277f2fc6cea60ac03edc82e3a9d06aff7d1c56", - "0x8760085283a479d15a72429971a0a5b885609fd61787a40adb3d3d7c139b97497aa6bcb11b08979e2354f1bc4dbf7a0d", - "0xb168ede8b9a528c60666057f746530fc52327546872dd03c8903f827d02c8313e58c38791fb46e154d4247ea4b859473", - "0x842c1149ca212736ebe7b6b2cb9a7c3b81ae893393c20a2f1a8c8bfef16d0a473ff865a1c130d90cc3626045f9088100", - "0xb41d0e2c7d55108a8526aa0b951a5c8d7e3734e22fe0a6a2dd25361a5d6dea45c4ab4a71440b582a2f9337940238fe20", - "0x8380bd49677e61123506dd482cdf76a8f1877ea54ed023d1deabfc05846103cfd213de2aef331cdf1baf69cfc6767be9", - "0xa026f92030666b723d937f507e5a40e3f3cfd414ad4b2712db0a7a245a31a46002504974ed8ba9d8e714f37353926a4e", - "0xb492e9e9917b29eb04cde0b012df15cbd04f3963d120b63c55dc4369e04f5ac7682b2c7dff8c03410936c26ca73ad34c", - "0x81fd9271b4ee36be0ba8f560d191e1b6616dd53c56d1d8deac8c1be7bc67bbc53d434cf70d04e7fa9de3e63415389693", - "0x835c3711abe85683d2344a3ee5f70e68342fd1aec025ad248efe66aab3e3d5790fad2f45bae0d7a53a80998fde45f0aa", - "0xb46599be80b8f7dbad0b17808dd5ca91d787929c0bef96fbbcf6c767727d07ed6785bad164d733ecb015eb6c8469a16d", - "0xb36bf5c17271d39f5ccb3d82a5e002957207a0cdf9ae7108a4946e6f3ed21a5d353fa940b6fe949c39422b452339bae9", - "0xa12f5444e602d6fb8be51a08b8bc4ec105dfd759d2afe98d51ff4edd673c92e4fc91ff32417ae8070e12169004f8aad3", - "0x892ce3ca0a2961a01f7f0149b8a98fdc0f8871c2d85e76daf7c8aed2a18624b978a4d0a84213f81f9d2a81f7ca4826d0", - "0xb1e6229ebd5b3d85e62d0474d1fed34564f1b5b9c5856fae36164dd0eff378d67d6717dda77536379006fb462bced9da", - "0xac852921dcb81e54e1e315fd6226219932f7b785c2ceb2035710e814899784d7001101f1515d68e3fb74cdbb4baf9e26", - "0x989a42d851123d708a213f3a02cfc926df15af058ec9b5a9df968fe16decbd781b5e65a4c17fbfedd2ac17126084700f", - "0xb1d0fc2f7c948e466445f307da7b64b3070057c79c07c7ebbbe6f8ed300a642b3567aed2e5f28988ac566ba62e0d2a79", - "0x83057263b41775bc29f1d59868a05b0f76d3bdf8a13c1014496feb4c0ee379bfd0d4079785252f51fbeb641e47a89b69", - "0xac9e6a208aa9c557155cf82b389bb4227db5ac4b22a0c7c8d1c3d98946df8b82b0c49d093ba55c8255e024a6d67c14b4", - "0x8294a11cd3f5111b1f8bd135be23b4de337ac45711db9566ebf6e162cd58e7859b1309eba8149b0f0a43e07f62a92411", - "0x8c15f3388b196603c05adec195c1d2cc589e3466da3169e9afd37157fa55cd34bfafbfc5ff10ac0e04aa6a0d0b2ce3db", - "0xb8faf8ba89c3115576ab6b340f6cc09edfea8f7331f5a5e8003960c584e839fcecf401113dfbb9a5c11a13721b35c263", - "0x955c63b1166514c02847402d0e92dccfe3c0dee3bc70d2375669afb061594c85651e6569f471a6969759e5f373277da4", - "0x963bd4f9ae7361d6936d209592a07d9a22cc9ef330cf0c5cb845cb4085d76e114aee66d7599bf5b9f11c6b1c05dade8d", - "0x85509b3c97e06e0db113b8b40022c8989a305cec39acab36ba3a73a4b4719573e5bdb82dc4795699c26d983465cd61b0", - "0xb870cfd7f691f88db8d1dfbe809b7b402eabd3b3299606b7dfdb7ef49415411f01d2a7e4f7ebd919ac82c7094f628166", - "0xa5533e7b58a6a9e5c25589134f501584163551247d36f50666eeb0a0745cf33e65bb8f7a9c2dc7fe7cb392414f1ece4a", - "0xb93d1ade01ff5678fcd5b5b4f06a32b706213748076cae3a375e20a97231133ec37c1c3202cbc4896b66c3410210f446", - "0x86ed3a58000a46fe2c37d4de515430a57d8f54ab4300294685534372fed1d68e192dd43d43ea190accf3dc9b22e1548b", - "0xa8c7d8dc30057bb8ad66b9cfda5e223334407730aeb0f51705922c18e7a07d960c470d463d1781899203e1b1ed1df484", - "0x8d86821d006e957e8544f95a98b110c89941bcc6985562e7a97285f5826b35b690963b2c141ff3f389d92ee18ec76d24", - "0xa4e1108cd3cf01810e74dbbf94340487011b80013b9bfdc04f019188c0d4d077a54b71a3f97a036601aad42a268531e8", - "0xa822cd61db07f64bea00de226102f5fc0adf8fa9f05a6c7478b0ff93e48f6cc3191302d22e1f369b571877d5eb96139c", - "0xb1ad4094d0bb4c325dfe072b17711962247dd7ff7e4bce4612e80a6f3c1bde04880ba1682f60d5f1451318afd4d3ba60", - "0x88e7beb0cfd7361288ea27f6b2cb18870e621152ff47994440c18d45284d21bad80d9806ed7d9d392a5cd791d5150ce2", - "0xaad3724a176cf4476595cdfb9e2c3261c37052324c0b5373a30b6cbeb481bccd303720840c49a84ddca916d470eb6929", - "0xa57983370d159e7078a273746fb22468000a6448b1a31d277272e35c6f548f97928e9015f1daf577511bd9cfee165237", - "0xa54136e9db381cdd6dfb3fff8bdec427d4dc1072f914f6fecfec13d7b8f95bb3b5f30ad7677288c008ce134edfb039a7", - "0xa25dfc4019f165db552f769f9c8e94fa7dbbf5c54a9b7cde76629cc08808c1039ecbd916560c2b6307696dd9db87d030", - "0xa917d25328b0754d70f36e795fe928e71ae77e93166c5e4788716c1ef431115c966f2aad0ce016f4bacc2649f7466647", - "0x842ce5e4ad7d8d4b8c58430e97ff40a9fce1f1c65ecba75fed2e215e101d1b2d7ab32c18df38dab722c329ab724e8866", - "0xa8eb2ed2986ff937a26a72699eb3b87ef88119179719ff1335f53094c690020123f27e44fc6b09f7a3874bf739b97629", - "0x96753c1f9c226f626122dad6981e9810a3cf3bbee15cfc88e617cfd42753e34593610861be147a7b8966bcdec55bba8d", - "0x94119d31606098f5b129931b51b4b42c4e3513a128b9bfb03cfeee78b77b9909b1c2fcf0a292e49d63bc4e5fe823dfef", - "0xa869654f5880d9c21a0af1ff4cfa926e03ec1f2d80fe5524605e04f484e09dc80d6769249f31fd378ff3926ab4cebc69", - "0xb2a539bdd8de4499c5f35cd8824974c2abb1933b3f50d0175dd044563ca829eaa0fc47bdac97eafa98434d1cd05d7c5d", - "0x85f53b2bfcde1986ce7279f3a2f5f841f87d75af5d197c897f261d4874bc6868c575ecf7556a32b7b33f7b2795454591", - "0x964f087ed02228b30f401d8aea35c1a7f76698e4075e1bb343398be74c716884e9ca1a31b81566e1ff7513cf76a2f0cd", - "0xa1c9d9c9bfbc9c4e281a2953d5991e7b22ff1a32ddaace9e8d9a42e080efb802b853d3276973b5189a5745943c9b4389", - "0xb0c45a9852663a427d7f50c608a6419fbd00f90e8452757a45269d25c0386ec29942f48a34aafc0187ef6020e581d290", - "0xaa3ca7b01862d5d2aea714fa06724b7dda7062b6608605cb712588b2c49fc3c7d89a8799e6e7c31e7a9ef28b1ad4d1f7", - "0x88f5e98ae8c5ae7add42f6d358a35667e590aa80e1869593cbf597d7ee466efa35b429f1836ba2199d8280fe7f60ce3a", - "0x8a3bff472e8008f7e50362acc1a0b53c09ac60430942544532722e938470376f0672662261992146765b7c75a380c318", - "0xb9847be7f7aee7532282c279dde928698a892a183ca3047ceda521e9e0a50d96fd3ce59f8e58f31af49508ade6d4ba51", - "0x98065dc23ea3df6d9f8459e81887d88d5752b7e7ba6050ec5c3f0dce93e463e0bf12be3c94ec74c16e2f7ba62e447845", - "0x994aff677b97ee790894dbdb21b1f9210734e008cee2aa2200c8f2579ea650b872f39776a13a8c31e95cc817091bae1c", - "0xb292811674e18912ebe79df1af4a132b04ab702c125c039e0213f735f658fafd36c38e5bbd7cad35842576431f5f3630", - "0x96520d750ec10bb10f75019f8f0e4a93ecbc6b678a710d76cd10aa27a6642ad1461bd58fc2aab8e0391b3f788339ed29", - "0x80d478da7fe246ad0e81a00141229e9d91ffb7fd1b29975c8ec358ed5e864e481bf01b927a9ba002c5ec4aa226d0cb57", - "0xae58049d93a11ae845dc5be2505e95657f83b95d83ff3591a3c565d587157be795ff4481f42d59eda95e6d523444e199", - "0x85f1f5ad988b9f8a7e24b6d6a22b9de9fb3fe408f95711389c444d7ba2243987225b04318aa97a4cde2cb4c30c05508f", - "0x922092d0cb828e764ce62f86cbc55c04dce07233cff041888fae48cfe93818780b4aec9b4ff4718275bb2bfa6bd9e9ba", - "0xa85ba97125feff0590a05fb78f19a7338639ad1748802918af4d59307bc994536c0ad638b97b9acd26a08b6b4370dfbf", - "0x8c46fcaa8d13266d650bd9366180e5ebbfa002c339e4424a030de19ed922e2daa9a353ae54921a42299607ae53feb075", - "0xb8549832230eb1ec6ee3c33c078deb47f556a0907d2a85fde7720391c82d2ed63dd753cf544a6a0a46eed4b8d1ecd9b8", - "0xb7b96f24504c7f8fbed9c1c654a2550feeee068407b809c43f1082c9558c8665806d911d5d244308169d8a531373bf56", - "0x81c483fd9d9ad7af7869d617ac592e7e951e39738da041d8c4110637689108eb29c8acadfc85366c70885cdf77b353c3", - "0xacf33bcfd9080dfdba828727fe36803327a94e8a3ee5b6e445274f0e8267ad3c943994a8dd6d09b8072912b57e1e25b8", - "0xb3475e7456ff96861bc11068198d51b69b899f5ff13022694b501d3adc8bac58a16204b12011d61e880c8459f4badbbb", - "0x8ceb9562026aa96d6e786ec2e5cd49200b5b424349a2214cd3ff5c8f1c2bf1b9872480428f5428e45cc61106cbfbd953", - "0xaf56f7e482c24a1367fd798201a20c464848ece431f2d8a31a6ef4f9bdbaa50991e748dcb4ef0c08fdac0ef8ddda3b80", - "0x896dae8b12549909d512fd5c02a2f72dde4086aef6c8007ddb26bb04dff51a707ae94ff87e45191fc10339967fa28958", - "0x8ed1c606840e07a2ac6ff16ac6e81ed3e1c90872ababfe68d56ed2dc50d9294579b9c3546dc63292874299a3162d59f9", - "0xb4d7a5c0836e419a46942281ce77d0aade8e39eb1bf1190dd274ca5070898a1c02ad9d165855629d6e1c96df1a6bd5f3", - "0xaebad8939ac117deb28b789d9846c2c80359dc260920ac8408dbae0b6228dbf496dac0023a3b4302bb9a53e8ada18e61", - "0x812d07c74a8650dc3f318c9b2dbf265f181041fb432fee989cedabd44b933dc6590e36c71dcf9dbe7b4bbf74ea0d7c50", - "0x87b131dd3489889e090839c392231e0ee198acac65bb2e9e63e7d6da322391d1685cfc8ba60699308054c4b0fd89c90c", - "0x8b12110ece0b99b2e653b4bc840a12bce5b85abf6fb953a2b23483b15b732a0068824f25fcaa100900e742886c7b4a0d", - "0x8765fc9b526a98512e5264c877bdca567e32fdcde95cdbcf4f4c88ce8501e1c7fab755f80b87b9b32d86d18856f1d005", - "0xac806a32a14019337dfdb5f781ecba5cdea8fb69b23e0e57a0f885e0082a9c330ba808621a48e24316604f6c6c550991", - "0xa711970fa40cf067c73e3edee9a111bf00cd927112205e9d36a21897529be9a051be45c336d6b56725dca3aeea0aed15", - "0x908adbc17fc18821f217d46c25656de811d4473779a41eacd70d2a0d7dd3010de4268a562378814e619e13ac594bb0c3", - "0x894251b79be5ae763f44853f6999289b3a9abda64d52797c6c7d6d31ff2a79e9b3906da72f9ebb95b61d6b29479e076f", - "0xaadcf11ea15bcb6d979c3ea320cff8dfcc23c5118ed075f35e77f71459b2141253060e3a90839adbcd3d040ad3bdc5e2", - "0xb4e55d7d2eeaaffb0267448ecce0b75166e4805dc0e261eb5634d4a3f3c08964a597302fd8f6b45ec48178619291dadc", - "0xa8e2a02c93d6bec7f42f9265269660b4b404940c3e3de9515b4d826ea7e71f18c6f90a71ce3fbe452d0713de73cb391e", - "0x8e2467accfe207cb1ba37d60662920f95338ee212927edb706228c25345734217740159310edf17687f58b333754cb65", - "0x90376b88f653381b3bab673c48c2b84fa82a091e18f710a732fef836e0d39043fcd5527aa97a3a385c0a77cf53746993", - "0xb16530e289198c235ab680f86851bcc177f0c16a58483d83a89213077b06d6840600b03834b6b7af0e22b1914f72de43", - "0x8c4fc3854f938ef1c2b5df065e4e75e9f299798afae8205706439491bdf9784c756134922e77af007e349a790afa52b7", - "0xa68aaec4341d29b92b35322f89b1ae3612e7b440c89a86135a07c261dc5799217a651460c92113d099b486817226d8cd", - "0xa653f965feefd2df24156478f0cf3755274ca395afb79e8c72d3b6e1d1f5ba7f3e4f9a4c5ee85355de6f3c81935ff579", - "0xaaf6c8d2717b57f6b14e06c742a11a3bc736bfc0327ca4b8a005b6e924f06871141d231737698a9a59286e44f244a168", - "0x8de32e3c104b4278e27aac695d224f134001c3619f15186466c57c0c46f67e2efe537501d0d9f52f4cdbc724a170b92d", - "0x8e9b5858b6d4ffe811f6498bd80e454f0d6b345d4729c946626c7cdc196c803a349a14515296aadb7258bb7a5b37e930", - "0x82fc711043aaf1d7a9c712d00eafd816a710f82eb10818ba6af09f591447f36814dbff6e6a1cb2b5c7f16c73930dbbca", - "0xb2f0205327fc8ff687f751e7b97788732afaef4fcf51bb17fd7579ed07501915790b70fc36624371fe4fb87a0179d850", - "0xadd87d5b1288d30f3449d3ccfa11cba4dc7756d85cee1cb6171b493680a625a01f273d0bb8e6332d0410250036b3acdd", - "0xa411f75ef7dd8de8062331ea40929db989e4d65ae8f33d3fa6cc19c98fa8a8ec2b7c7534a5c5eee9e5051626a6a2e47c", - "0x89d40a647781e7f2e8ab3a0f7dc7133669944c0cf627376433687a2ea15c137be26f582a6b07ff94b266ac0910009f7c", - "0xb2b5f808c26b40ed507922ed119b0fb95e0d6d8b084bbbba58ca456b4354d03110c99989b93207998334ea5d1b70fe49", - "0x8c8db028671969a1e80e595283ce5e678ee955d785043bb5fd39fdb68a00e4c15b462600a7ab1f41486b6883e725894e", - "0x958087ce0c75fe77b71770c2f645ef3360c1a9c98637693b988c5f6ce731f72b24ab8b734e8eb6258ee8b23914451f0d", - "0xaad6c00df131c1eec6c556bae642e6dcc031e70f63eee18682f711c7b2fcd9afbf1f18cf8a4af562759130add67bd4a3", - "0xb6d23c567291f019cd9008e727704e7e6679b274feb29abba0d92e036f349b1f0fa8c5271ec7384e8d70a2c3977b1f8a", - "0xa942c770e903d4150b5684e4b94bb72d0e171df2c7cae6f46e002c41c6b04d774ac6e2753ba8dccdbba3ad1e297a9ae5", - "0xaa542d1849390f86d797408ed7f6a31504aa65d583481a00e475028af20f8b69248a87a8ffab1dace0377db77fe5f9b2", - "0xa1ed3f9564a97f7cabe7c67e018eaeaa42db73a2f3d2332041ca9a7bea57436d848784d6dc402862c22a47f0692b1286", - "0x925c757750c91db8b1b3c220fcbdd80742b4a060abfb0a402071d215c780ef6b420132ec5a43043b9fd7a06bf1b323db", - "0x94e575daa7fa0bbb35b4386f510fc3877c9df57bcf15349c5923f30ad6a8df95372835cc078216b41a7192921c1e8973", - "0x9346a41174865d9ab31c7fb9a5329f322bfce06002386d3f5a2e2193de9bfff12bd0bd93307928f7b85e1097b2aaddff", - "0xa6e54c9324baa1bff7e9bf39c94fdd308ec6f210aad937112ec727565f8a6141375c04196831873bf506294854f6a20e", - "0x98d47b662504f400f1a0e14e24b43829490d022ade02a56288aaf148d466b45d89b5fc146cef67c9ba548cd37ad5e354", - "0xab690dd59a69904b6b3a4d5a42d17ea4898d9b00c6753aec216d5d4ea564f9a1642697df44d5a62f2c2ab19aaabf1532", - "0x8d0aa8d3c5ec944af49beb99e403cc0d6d1adc6003b960075358a4ff1cbfa02a83d6cb4d848d9e83b34882446a330883", - "0xaf9334b7300780c752f32eaa68f3dcecd07dc50d265083f37f9800b02c2595ba24dab89f5fc27c1ecfdbf5291b4d77bc", - "0x81c4a6aaf7d4ccee9925c512dae5da6d916a6dd59f7a4cc79d216a91201b4d300114a309e3ddb3291bb95f85bec2a8ea", - "0x8c804e810c0785789de26e12b1beff56a163769733be7a31f34f81093782d6410293768a166c9191ef8636fc8724a31e", - "0xa91222b48de238f6dfe79c84080cee618611bd0bdca15cfe44474829e42481f8511a82589e69964e19f8cba04e3f5f3f", - "0xb26a8885aa594b0c8ad4a1711d80bcf687df996442075dd1497db1b446d16c74e28bc6f0e92b2ecea9c3e15c9c7e828a", - "0x85940f45d324ad1d335bd1d7d6f81758f52213e63d5770d9fe0c0c9507d5550795e538b6a2dd463f73d789b5ce377aed", - "0x931a277c78082f416880620df3aeb6d0bff2103d19679dd092ea981f5323e438c50a0d094908034ff8a2cb47b1a44108", - "0x88dd85e4e2aa349a757b98661fc00d4538ec1d3f53daf44b16ffcf7f943dd4f2bba5b8ba3b05c529251dfeed73f6f1e9", - "0xb7fd7182cd33639710b8216c54a11bb02e199bbc54fe33492a809dbe17771a685d6238ea3ebcfc75e3b0d4ea5369bc9f", - "0x85d77194d910f8cdad7330e1bca9087529a40fece17492f1d17cc4790833891b6d01d24f036b6422175c732b438faeb5", - "0x9845265892d672d9517fbd22f88be4f225711b4abafa8327cc059f000656e4737188506051565d97912a0c19c3d063c0", - "0x90a81987aa841c7f640c298b816643a0ae00cd3609c3a31d0b01245283cc785d9bb27763131b31a4f21aeda4e50073e8", - "0x8b1256eb41a600bda8a06ac08b98a220ebfd52f89a0e4fdce32425db7a0481e9b7873ba3b7a24ad9fb782ee217dfdbf6", - "0x870548998deed85c59507cec7e69cc001c279bb2a99c45a4d030a35c107e69feb76afecb9e435e67965051d6d7a88220", - "0xb1504d194a0dd8df48d431ce991f89d7a0f72f573d21bd5bb46474c5005e43820877a44e62db555f194427ac8a4b9168", - "0xa00d7423ec2cf0c9e9da07f3dae092d09e1ff4be852e07e531aa54d62ad937bfb52c8bf44683ac3a70f6dfc125575da1", - "0x8019625ad3d218018803aacc2efcedba3a41c24aca8c5aab2005556e58fdf2ed614831277df7937aa594e97a2fc65e7d", - "0x8595596284f3add0155ecfee3fc0b66a6b6fc7923d82ca8302952e2ed906d119a1c053aed1123b51f73e1d30d93aba57", - "0xa8ba033f5e7d06177e9ae2d99c40ed4e99e14e1c1b61795997f62e21ed8af1531c4720f23d6a39b0f75c6cd91c58c700", - "0xa94f4167c0f6ae214bae75dd92c63299dd954b00b0d8b0416b8af929fe5aec6a259e44f83a183412d7ba4eb3a49728c0", - "0xa73ee3c3a0fd2a369e0a279c3e214fb662d0378eea3c95cfb91412d7213a1f05958bd0de8f2a4f80f9f80d7eef943b41", - "0x8ef6f3e241f6a761c9ab412629a49648c08b70b837c2cd8bea620bc93056ec73754e3e11f0df50f8e9fa67a9867501a9", - "0x80b473ac4ba8cb82b4ae684206cde124d10fcf619f55a6c90d035981e1b08b9e141b4e5fa9a9af0b7f0c281b355dd593", - "0xa566e2be0b41f01978dfffbb32f442b5e6706f5b9901110e645cf390f6a82869e3ca16887ffa35782a004d251d29c26e", - "0xa74e01eefa03546d00afdd24bf17015eee95d36de28c03c9b055e062cd5e8d8f20473c6d7ad21c94f9058fc5e84f9628", - "0xacefc74de146911275dfd19bbe43d72729e89e96da04aff58e5fcb90962856c0b24eb13f43e30329f5477a1b65ae9400", - "0xb5f113ef36e75de6d6d44130f38e460ad3ffc65cb9a5606828c4f7617981fecf76f5e862d7626ccb117aa757cc3c3e52", - "0x96d3aeb1d3a66b136244062b891fc7f93ce745b776478d361a375ae57bdba9b4fcb257becbae228c1a3aff4a1c4fb5e2", - "0xab26c4a110877e5495b674569a32025dad599637b5dafedcfe32f205dfa68cd46f3ddf4f132a8e5765883b5c83214a07", - "0x922a7a738066692193af32ccbab74edef067668ce3253e18a3275afcd5a6df7168deb2f5175c5fb413dc08fdaef63b17", - "0xa47542f8e4a3a35ef6049280d1a9442c920887d5f1a1483149e143ca412318495a36decb804f81c9f5a7672a14965a4c", - "0x8fde57991e72a2aebd3376b4d9fdd795943ba3833431e52b136683567e6ee2cc1c1847dc49dc9534983060c54bf22f7e", - "0xaddb041f01a99e7238ab2f9f2f94579861d0470b93b91cfb29f3a2e4c82386c868b2cfb6f3778b8a9cf908788acafe58", - "0xa8c4e1df726431c43703739776e2cc51f5ebac57051244991baf53582538120133a44ca603d0722a4b5193e1be3c5ec0", - "0x846379125968d1154376c5dc63100bdcd99b9403d182e3566fe48d79099099f51523cd81d21f0d1dcd622b715bdd851a", - "0xb828bf0d936d275abb40e3d73ef57fcd7ce97e9af35e194ae61463317bac6c1b0c3e4b40afe08a1061037bb7149108fc", - "0xabd07c71754973e698fa26c5019afd9551548f8369e2249b9902513f19a097057ee7065a1d88912e8f52e6e0fbfa6d82", - "0xa9e36b6fcc9a3cc98e76d5751c76c50e1f92b7670f8076ab6ca8a30de4ec14c34669e049fd39bd293cde8789b1ca67f0", - "0x8c060835496a04c7b51790790035862b20547e62fa8bb4e8857fb36891ec6309520af5c0f45d5ea46e3d228747d710a4", - "0x8cc472ec62b8dce244373f40a821db585628989b6a7c4d394edffbc6346c8be455f4528d528fff41f91f2c875bd9fc0f", - "0xb4a75571f84f93451f15b3a86479063d7324d2789b6d2f2f4f8af68c66fac32743dc09b51df29608d62aaba78f6904af", - "0x916484984743b5ac16d40d0544faf9184819d92f779254b7fb892eb68cefbe59e75be8a6336a585e120f6ccae0a1eeac", - "0xb906ae585a73119764024e9eb87d92e53ee0c673474fec43fec4d344a3bbf471ce3976d25e37d197604689bbc944f1ab", - "0x8552708487305f16f95db3e01fbbfb969398f5b6d116844cbb000c9befd03f15c767584bf9541a42141949a4dc787a3a", - "0xa6025a2773f78c247f78c0d895ade8a6baa76e5499085f6175935d98a05fc41c1359f7843e0c6c323f1be256c45f45e6", - "0x96dac695dd9288aeb6e32dce50e51ddf1fbd41de6146e3605c7a81f2253b17babf2bfda4f5a9d0c28352b9746c0dfa2c", - "0xa215b21f8eb2290f9d308278f2859a999eb3a31f4888f84a65f9ed05e1151c17777f91054d4d0de759ac5c3547d91929", - "0x8fd7c9a279e9b619acf927d501b35dc551979731a89eab91d38b2356c0d73569baddacb9d1096d20a75c917ecaedadd6", - "0xb985e8baa5195e2f1ea1091122d55aa321178d597f87b732b23eccb12b891638be1a992305a1ffcf5233af34339fa02c", - "0xae1a9604b7f569aa48d2daa1889e76d3d103065fc8c3deb9ae127a6d94145695cab3bef640fa781612e8082c6d616c47", - "0xa8fc67f9069f753360349eb874fa4dcadb2ec48d97c61abe568faee5f370ec3c87786c7faf0f73fc0ae7181a36eb89ca", - "0xa506d13acc3a9f80509fac936aef848cd30698631fff6130ed6217512ed9527d075f653cf6ef91f68e48a24c903eeb3a", - "0xa415093755cc012863043bf586b970bafdd87653ad14d1929672e04949bae4a753d16aa3eb5bd1afe3df3691b80f240f", - "0xace3b792a1960580348b6fae8513149242378a18382741bbc2fb2f785cb8bf87550da4b5e0df2955970ab3a31f99f5d7", - "0xa47d7fa7522664c8f9c404c18102f6f13a1db33ba8b0a56faa31a78a3decba3168c68f410115c5d9f240b3dc046dc9b4", - "0xa9c930db3ea948cd2dd6ea9d0f9a465a5018bbaf6e9958013f151f89a3040cc03ae0b8eaf74b0ff96b4e7a6cd8aa5b4f", - "0x88abd235e3e760166cdedff4be82cf6ba02d68f51c6d53b1de326769f1f635215890f9a4c35b06dd16a9b93f30f3a471", - "0x8f8d7b2fcdb70bfedde1ffd7f0b94108f0fa432f6ae81097988521dd2c4da928c10c5da3c7f33f11bd5331f2da8ec219", - "0xb7abdbd48cece30d8f795a58a94913d76842cb006892485a9382a0502826538ca4ff951cc1ef4493e45de8571360d20d", - "0xb3e7b125f350c52695f7c5ec4a30916ea6c11744f1151a18ea0510e6cf6ed6f6dba4beaa4ca56988d306bd80ec360056", - "0x9a004423c95e1f1714f98fb97ab798d6ab16cb5f6d6cad860635585d4d4b43ffcda63d8e931351189275e5a2cef28c2f", - "0xa8eab6ef917cacdc9b1932eb312309e1f85298d63e55ed9c89ab79da99d3eb60f1643d16be920e82d9285f60c7f7cab3", - "0x934df955485113d10c4dde476ec14a98771145aadf3c8b61af26b09b9948757fa1abcc945ac91466a18c18c2fdce40d0", - "0x99ed9146561597cff8add2196ff3a0f161dd5302685ceb846afca6efb5225f642e8f4a0970eecb01cdf18694fa697095", - "0xb37062dd12a81267bbbf89bc9d6e30784c0e11e713cc49c6c96440f800f2a6a2a7e7f6c7f6c9eed4bc3c8890f2787342", - "0x83a3d70055b6044e0207b3ece4da849755ab5798317b36b20c3555a392c27982f811e1c5007697554eeedc737b37f3ef", - "0xa85392c07ff8658935fbc52acec7221cd916c5fde8537a8444eefd507220e76f600350ae8f5dc3353911087b88b91045", - "0xb1ea23558ad805dde9cc1eade995cd8e7f46d9afa230908b5fbaaa09f48547f49c2bd277bff8ab176f1c240beedd2b09", - "0x8a16a48b9105d94700e8e5706b8d8a1ed14cffda5558a596974ea3191c5c3449da6e7efe2059e7baf4530a15f175ce16", - "0xac5fa54381fc565842417558e131df26e9505027759416165035357816a7e1859a7c14c228c79b4e5ba2ef6758e12ad8", - "0x8475e290c399cc9322c05264a516cf766bf5fdb6b9dec7283961da0b99012d499b244b33fc0eaf94b461ab777f2a9537", - "0xa7922f3c70e6857652805af7d435646c66d94eec174be997c4fe973d8f019990c4f757eeb730b2cfdf8154e6e97f7d5b", - "0xb90deb797fba3150cf265a23ea6bd49a382855cd4efe171cbcb1664683a9f1687cfcadfdca4e39cd971ec13aa5cdc296", - "0x91ca761dd9659007d2fe8970bbd336c19ed0d2845d0d8aaab397116affcc793de2da73d89e6625cf4dae5983cceffa56", - "0x9121ae9b60323ab1301e97555bcc74ddba0f5b1e62bfe9eaa2c239e1d685c4a614d397b32a59febed4db9968db44f38a", - "0x8477b07da4bbfe9087975f30d2c2333fccfcd7149f90e0e6fabecee627eee3ea324df31cf6a680393f5dedf68a35c9de", - "0x946a9c0f02fa6bf9f9d4933e7fc691749f4ac2f82a9b880666b5185189d4f3432da9096d0ea4d6baacbc079e19c887ce", - "0xb24663332914ea519435874d4c42d11842ea84dd3dc55292d5b0f27f64587848d095bacaec235a37003bdb5185daa6f2", - "0xb980f46f84ac21dea75b4650f9412f6123325842758589a9b47caa68545905061f03fcad23cc102e2ce8ffeb1ae634a8", - "0x90e9ebb060182d3043ea4210a2d934858559522a19eab9f0ff81a367484a05ec7cce78ee6a91dfff96145869db6a4e80", - "0xb04228a009c91847693eab29c9ea71d1d6ba07060bc2b0b3bb81c46a125baecb3e1412f6ce4305076a97d316d14e4665", - "0x8d3268370dbf38d378c7228c7b54e91f90f43cbfddc0d8468de11a4312616ca6372619209b89114152b16f334f4d2780", - "0x964a63ffae653e0249685e227d937937b079ec3da9c977dad2b2e052af5eb560ce7d175941f2ae0df90e3d0a20b77e75", - "0x855604c2910be885b14b27896e16d8dc339236b975398c771d29ac74e4278a2305fcf85203050a8faffddf64ea19cf78", - "0x8e0b1d61a4349411eec77cf3490555843187a25a93e1f45bf66ad3982b9cc141b07805f8cb252b0fcc125e0052a7c450", - "0xa03bc9588f971a1257cd0cfd2ca406c76aaeb634001864b0e4dda91e009d3361b33fc39f34922835031a423a13619a82", - "0xb703fa855c2c4e1641d2687717fe8c5061acab71cd2dab55cdb069a6865464c3080f7936ddfd320516b6791b36c64b8c", - "0xaad1cfa7295e463fc3d5374ea4b952020010d67a77c7a86fe2c351a5959cd50df6a0045ad588257567a99bfd0e9400b3", - "0x97906fb82abf5c1d9be8f72add8e6f175a6a5a4300b40295cb5ec8527cc7ec700fa03a7a494122d9605d212457452e41", - "0xa83366cf93ad9a07f617e4002a10b624270f60083559b045ab5a805aaa592ac37b90c1e8b5437158f3bd942cf33bb633", - "0xa585168e157e111bfa329d0ed6651a96509b20b30f6bb0691c6a5875d134d4a284867ab52511cdc19e360d10638e58a1", - "0xb17d480a0b39f2487b7f3878714658fda82f2147c5ecbccd4004eb92d267c4663b42c93bafb95ce24e2f2f0a9ea14b8f", - "0x9362297a1a3951d92db4fd8ea6b48c403d6d8d2f7e7b6310b9cf9b4e4ba9e84cfe1ae025830aab9466c32fd659144474", - "0xb1a62fbadfd4ea4909d8d0714c1e3ee9f95237fde20720f88d5ad25c274a6792158b99966d7b93151f769c832b6a132b", - "0x8d9af736949a33fe929548abe72384281365385862821a584f5198eed63bc5388f89fc574cda35a9eaabed0d336b86b6", - "0x90ee2235f4ec2c6089b5cb7b8a41c9bc39e4a57935022ef28bed490e2ab12680922af7395bda4f708809e2bfc62192c9", - "0x91f3a123d420bca34d3d751119bbebc435630c6605fb59a8d80d16a4895972e56cfe4cf1998e0a527c18ee38c2796617", - "0xa2c4fbb20e7fbaae103b86ca9d8dbc2828e6bf33d1d7ce153bd98e8880fe7ac62abbf7059194b1eee64f4526a36c63a9", - "0x91a7f93310ac74f385f11509f4bea9a4d74f2ce91cf2024fee32a4a44d5e636a73339c6b4027ee4d014a24b90de41ecb", - "0x914a6d405fee0a15e99704efb93fd240105572335f418d95e1f2de9afeb97f5f4b80aaf20bd5bf150b9da9abc2b6d6a5", - "0x9462cf2c7e57e224389269b9fdddc593b31e1b72ab5389346aa9759fad5d218039a4a5bc496f4bf7982481bc0086292a", - "0xb7596132d972e15dc24f2cd0cf55ee4a6cc3f5a0e66dff33021a95e5a742889e811afd1dc0cd465cee6336ad96f25162", - "0x99409bba2548f4ece04751308f815ecee71222869d8548fa142788fb19df5366d093a5131e57560237471bbd5279bbe5", - "0x8e7560988a844b5b844ad460b19c452a5a04346d8c51ca20d3b144a3670ecc60c064b2415c2eeebf140d6ae4ba5c5360", - "0x8cd9e18d311e178e00eb81ca839cfaa8e64e50a197de8461f07135fca28c1d895dd9c2401b923a4175ff711853497317", - "0x91ebf99c95e8f653402b3079ecbd533ed7cd3b6c857a710142354ce8330cebdee7cf0fd0400417883b66055bec9d0552", - "0xa9d0cf8cc6bbdc44426dcb716df667826426b4559056d73738bf3eaa6df373403861b6bbd6fa0454b1d2730e3b0015c4", - "0x928320b452ef21d2443dee360110550f531d7a4275b2cb227814150f3e9e360e05a884d6e3bc4415f202120ea5ac333e", - "0xb9551f2b2e7bb984618f2e7467e33b5b5303b8707f503f2e696e49c2990ea760c31e0944d52257c7a38b553a67cf621c", - "0xb2ec34126fe61345e5c6361fe55b8fb3218cdcc9103bba5b200252d50b758153cd549226b7aabedd265906401e755190", - "0xa8cf814926082a96a921d471036a9919a58e68d02ee671c215ea304759cd92a7c2c9ccebdd5e9ec5572164ad2abb22ad", - "0x8c0563c28c261bbe9a1ec4986f8b277324bf05b4fe5e2b79a862168e646bbea50ce7c4622b2aa7ca899c1a728c226d24", - "0xb558cdc334ea894d3a13347ea9e30f78a0a20621903d6c009c54feceba3ba81d2445a43572e088ae691f65489702e963", - "0xa62ba0b20f46c367cfd409beb300e39f1a6cd5be95e63457b6ad3cb66374aed754fd037b8e4215d651a7d8e1a442f762", - "0x8543e2c6135df471bd7a5c09f1313674c7f6847cb88f15eabf40b2bc9535d0ec606725b97103334a0c162a20d9f5bb53", - "0x8c0367d7058d63b425450f8ee9252e64234c0c2e61878c7c2d4b17bab22a72f40c75ac3bf8b64f264c00d9c5963af041", - "0xacb7207445993d563f1b6e7b179bbd6e87044399f80e6d15980acf7aaccb9d85071fecb22250afb3aba850712fbda240", - "0xb93725e66184bb03f0ab4078c737a7fb2b10294a3a09995958de3dcf5316b476ce9b5cd8d180017196d9482abdfcab88", - "0xafcb52bb7b8f45a945299da6fc6a877ba9f69f7f23d5f94b5f5d9a04c3cf3089333bbd50fc305e3907825003da73b9f6", - "0x961de781cb238cef52d43bc0dc7d8e3a75bca4c27ab37a2e9353137a9aa9403444a5841b595adeca75a3de5485ab97f6", - "0x9408c828d3ed6df40cc167d72ca9882a9c9cf8e765d6f9125e02e0d66ee0ac94f449803afb50bf1b92176feae92473d6", - "0xa85480591e7e033b9087fd0efe5cf3c88c10a75de4a5d7da4443df1cc1fa1aa59b6cde3ce7453fcabe555495e49ef6f7", - "0xa2611bd82344bc5d70d7e6cf3f0d25866b9f709ac4bf6f75d1006da2a11e2cd07a4c0ac71505e5062a04f71db7a3063b", - "0xac466aaa96febb5b810ba350c7a874797ce4bd6c9585f6b9d114d646894a67c9af9526ade4f7ec834d3a69e18ab643af", - "0xb73fc98a79fe77cdbc524c76a09cb9f2d5f8b0a5508846bed1ba5ea9ae3bb62120e01d3b8fb544d90ac9ae0c3d4ccefe", - "0xaed333c3403adc899a870082f70aadc770c9f880dc057f05a46d7400be9d893354121a0a31e5475898f437bf722eefcf", - "0x97f02133c72187178a8c48db26031f0b2c0317a6648d2be5f7450f00c37391cec935bea46b8144ec9fea5327ee959f27", - "0x940b582b41f1d0f09f0c5f51bab471e4eb143e91b1e96dde83e94650421d51f9c9baec10cc802fb83cd63b56d0b907c0", - "0xb1286a55a74a88a75da47671994916be428be1ca3f42783e497d6478eaa6aca69d50a421b210e9ed3283d578b651b8cf", - "0x97cd4e87e21c71d11f1df1c0b6518c00e1610661be4b13cdbdbb026d60fc3f4a2b8549326a648b3fdecb7de8f6aa9fb7", - "0x8f36bbcccee986c35328633bf6ee8f70b5dbf42d0f677c0f4e009d2289976e512af6af91a6ddcd87dc0df93bc4ecd02d", - "0x9253ad44ad182e67ab574d718733a69c05cd5bcc43e6292ef0519a9430460aa6a233fe26269da7298ea88cf406e733c0", - "0xb616b5ea74db0dcf8f10a2db79df6ec3566c06410f68a933eff150194608c591b2b175908d4b4ccaef1018b0fefc5693", - "0x80a712ba89394381cbb83fedcaae914cc4f21ab024b8da8a7bbad7762a22f82940451427b1a3f5d84c246d5ba0c7ccc7", - "0xa806909a5517a970879143ad789c6cb6256b82553b649f6865cdafbbc050b1f86528241b3cb600e784186e1a672b588f", - "0xb6ae801d1f0e4adf3ce57659d7c61f94abd3c8d1635ad28133a79eff0586fc48bdc195615335449e9bfee39e8a955eb2", - "0xb8a000561211844bef72adf3413f3b438a8789fcddf6676402ca6a1c2c63b9deed322030de2ae3a0aeb3cedbb89406c3", - "0x8bc3615b28e33fc24a7c989f8b4f719c914c4c65b35ad3d4cf15e2196e37c62e42ca34e8b1275e0f32589b969bdfc21b", - "0xb2f9637f370a79e7591e5056dac004f56b375f33645ae9f5a192cc6b7b6b3d8a1105cc00f10d8bc8ef250ecc2ac63c39", - "0xb51899978b9c5b737999fee1935a5b0944261e7005bea411b5903d2c16ea045a3b0bcd69395b6733752caed43bc4e343", - "0x873c71a01009dddb9885c48658f83aa6320e74bc152e09de8b631c763c2b4e2e8cbac921418a0d9085ff5c53a2b52d39", - "0x96470f48efd7d2ac2daea8753ef097c09c6fc128a54cc7ef758ff07e32c0b0ac7d122f97b53e88a29cc26874dfee5e0d", - "0x8dd2decbd3504b7961d65edb8d51b96377f4edd2e0d2cd8a4d98333f373c79a8d7ca8f8408718d0e7b5e48255857c339", - "0xb536ae387bdd0f6e40850c71fcaecb1051b2c8f7bf5cf92c6bda030de72a03e9212d00390c53a72a08e9fb2bff1249c0", - "0xb1566076f59064e3545adef74fd1acadc1bee0ae23543c30caf9e1ad1fc20ebe84ee25004c612525b26857253f5345b7", - "0xafd180e25444cb720342923b8897d38a6537bc33a0ca1fc9c6e4d524b280193618f19e2bcfbd07606b78b734fe6114ed", - "0x89b2a6c8811e5a6d07aa74c79dd854bdfc292cc104b525bc37e4c7c1f9485e19d759c8e27cd7cd73c46346f56ce3b189", - "0x8234196e196898b2501b79d0dc016f6df3d5878952cdb8a93735e4ce2ecf77d07924c701e084533a20f0c50a7d1ee376", - "0xadea7ce2efc77711f50138691ef1a2b946aaba08e7e3b21378708dd5a10bae933ed121e71834b43b14e2ea30a7b306e8", - "0xa566d406a35fae703b3d1ea1791d9207116002e5ee008d01e053a1ea4fe5af2feb63605b011ae6a14414028aa054b861", - "0xb83bbb063682386456719179b6f6bbc8cf6f791229600b7d402167737492f99437b45886695b26a28731e952e56f1ee1", - "0xa8f5fffc2c335d3ad5c7593e81f0862351413cc348392afa86d50921dabb929a5a1de20d604666af9e17a13bbc30bc3b", - "0x8d5dcdc1335f01847f6ef650ff64b26e7c4cecb934a7bbce11254e8ced9fa9e4fc87eec55248f69bf499180101c63f5a", - "0x83fec30b8bc62f9fc28301a03ef18158d6364738f1c42de311bbfba2e62b25d4c9ea9d6097698b24c84fff956a6748b9", - "0x96394fbe0c2d03cdaa56e13326aeb62344238ad3043ee2fb4f18ebf0a6f7f090f410032a2d15bfbeca9449202d59f2a0", - "0x94880f5928fe71a797362a37d05849d23e118742697f75bc87173a777e7b9d4383b8796a8a2bbee27fb781f363301dfe", - "0xaf229535896ab86fdf6d2ae676a0dbf44f868f6c7f17bd9a65567631c7aa2e29758f41de050ca5311bd1528bcc811532", - "0x8d4fa4968575b483b3ac16345e7f1ea3f81e8dad72c945a48b7b982054fe1030584be2f89b2f53af84d2490cda551b84", - "0x8052aeb115e4d242078c8726d376a13156cc832705243f14adaa3ef3889e1f2fcdfd46e087acab6fa85a74afde5f5eef", - "0xa1349c8a22788a1937a837fceecfaada9e93a63e582a09c56b53da52c9db1600254dc85f63f5eadfa30b89b31dcbdb30", - "0xa10178cdb263ff1a5e0cc034b6deaa160d00c3c3fe1fd1ff0c55fdf1ecb83d771070c10930f88832b75fef39a10024ea", - "0x938b17e4405934ea5ef29c2187d6787c5ff5d8c9a02665efb453117d462dbc50ef2c202cbc884305cd807a70b5cc177b", - "0x84f01f0da6b58c71788616be71fb3c259ceea7f8bd131a5661c5c03d0205feaff6dac2915919347b0559c381477b3d89", - "0x98787f0a2fac2b04bb7aa247ac77236bbe690aae64203e553be328a2c3bffb772e7a0244e585d27558cc64b089a5ee11", - "0xa14501d8b6b3a84b13b9006d521667e8d168f642ebf154c4e90ec8c75d11985fd0c9d86fc2efa6c7077dafecfdf0ab13", - "0x8215dee75eed04de83a3e910129bee8c48ce01cf1317ea477ff35c09a6f9e9771a8b05aa79e6b0f3e71b9874695e7a2a", - "0x85763c3072c7400a2c5668ef5cc53e6f4b8dff474146028a8be370ca9d8af9bf9ee10cd7d23d33eb6d6e257dd3af38d6", - "0x91bf62245c5a59d514d39bfb74db7f72ca7160c1c5d5be3844fff37e53e99d451e18a6747c65e33f98f48a55f38962c6", - "0x8c68817c6a6ea348d9aedce99929371c440fbad72718c2d239ffcaebb26ecc8a4e8c38c2819d945fdb7f02ffda70a5e0", - "0xa96ce2745866a22267a49faa7ea00ebf009ea8d0b0ca2c233c62759b9d5514306b5822dd2eee0124c9e28380e2f97aa4", - "0x8b18d5757c73843dcd55f0f0dc894bcd17e0ecf4c9fd901eacd38480844a15b4ce5e9598ccee039f9d93185137630cdb", - "0xa5b45c403b6735aaae14389bcee23ca10571f5437f1f5ab0c2b4e573dfd3341c638fff2cc780166af96b118d47ff2299", - "0xac849a0ccd354dd46bf55ea837d509b4ae3eefcbd5b8eb2582d301fd56c27b89950c6eefdd4e98e608ef4a6b75251311", - "0x89f13ac14bb064e9c6b49a482831ecea6344faec490bd18bb44028b83a0f22e21145861558029bd172ba7c5247c2cba7", - "0xaa57b057a2ac32c101e442c33831630c81b2e061a542e3e1d6897b2b7ca8a7241ef717a548b3f751d60d89be384ba5da", - "0x8a43db4e12682b98230364f25c75b49002f5002bd72a1674cf2a9d53197b5ef1b95e48429af98af503b0d5c3e0e017b2", - "0xa10cd7b8e1574d78c4e917cf833d3d845b878e8e8b60312e6a994bd4f391a5e8c38dcd774087b93c9241238f43f80937", - "0x8b61ccb949088286216cd628811df1a362a7f5c333654ce823e63ebd04b069d5b0f627fb6c96d54c7b853de8aab05472", - "0x887b902020ad45f70f2d5bcfa7324fcbe7be09fd2b1bd40f9ae43a89d487986e89867aee0945ea6a0fe8dfd051ffec56", - "0x822fcd260a7876cad31f54987053aab06108de336878b91b7a15d35013d6d4d6de2d4b30397bb6f1d5c1a7b48e9d1ced", - "0x80b89ff95d725858b50e84d825ea99fb6a8866f10b91a5d364671ccbb89cb292bada9537c30dbde56b989c8bdc355baa", - "0xb53cab156006c3a1766a57dd8013f4563a2e8250995dbeda99c5286a447618e8ac33ebf25704b9245266e009a0712dc5", - "0xb6e2da9c1156e68c15861a05cd572976b21773e60fc5f2f58c93f3e19c73ad6c2ee3239e6cb4654040c8e15df75a505d", - "0x8b7e187d473a0bd0b493adcdb91ca07c9310fd915dec46c2c9f36a5144eb7425dd35dfa50feb0e9ef747caed9f199944", - "0x9743ec3917e953e0a420406b53f4daa433adf4ad686207e9f296e7c83d1ffdbf81191b920ba635c85416e580178c16ff", - "0x98d1476fd4504a347c5261012298ca69c8593fec91919d37ddfdf84155b6f1c600cd8dbb92b93f3262da16cf40a0b3c6", - "0x94f50d52982a3c81ac47a7b3032dad505b4e556804f8606d63d821f2c1a4830917614630d943642ba375b30409546385", - "0xb5c0eb5f4cf3f719be1a9ad0103349269e8b798dbffe1b5b132370b9de1188a6d71dcbc3635dfdb4b888400f790b6ea4", - "0xb47fb45ec73392598866d27994c2feb0b0f3d7fc54303a2090757a64b6426d183ae41af16794ced349ede98b9b3fd48c", - "0xb5f45fd0aee6194dd207e11881694191e7538b830bfe10a9666493ae8b971d65bc72214a4d483de17c2530d24687d666", - "0xa50c149ea189387740d717290064a776e2af277deafcf5f0115bbbdc73c0840d630965a4e0214b738d1cb0d75737e822", - "0xb941afc772043928c62e5dbe5aa563fa29882bff9b5811673f72286ac04fddf9a9ed0f9faf348268fa593a57bc00ba6b", - "0x839051a7838937270bdf2f8990fd9aa7d72bfc86cffe0b057aa8eca7393abf16b70d71a6470d877f8ec6771efa5a8f26", - "0x835bc9d049418ab24dd1cbf76ed5811381e2f0b04035f15943327771f574f723b07c2b61a67a6f9ddc1a6a20b01f990d", - "0x8935cf5634d6ae7b21c797a7d56675e50f9d50240cb2461056632420f7f466fdcd944a777437dcb3342841ad4c3834bf", - "0xb5698fe3da1f9d1e176c9919fddd0d4d7376106774aa23a7a699f631566318d59b74ae8c033eba04d06f8cdcb4edbbed", - "0xad11421ba75d74c600e220f4bce2ca7eacb28e082b993b4368d91218e7b96029acfbdf15a2ab0b8133b7c8027b3c785b", - "0x886ef813644599051dafdaa65363795cf34a3009933c469bd66a676fdd47fc0d590c401cc2686d1ba61fce0f693426d4", - "0x8858fdf3e98e36d644257ab6076f7956f2e7eacc8530ec1da7f3e9001036cba7a0855fb5011925cdc95a69600de58b2d", - "0xb59eca7085a2f6dfeaa6a414b5216ff0160fbea28c0e2ad4f4ffd3d388e1cc2c23a32dbe517648221b75a92500af85e3", - "0xabec62d259bcd65b31892badad4ac8d2088366d9591cd0dab408a9b70ad517db39c2ef5df52348ba4334dce06a4e3ba5", - "0xa9acfe8f5a310779509621ed2946166ffb6168e68ecf6d5a3b2f6008df1728c8fceb811636c50d2e419b642a848a9ca9", - "0x9929bb1a3537362848fac3f1bcb7cfb503dac0a0b1bebbfd6ddf14c9a73731e2248cbaf0fbb16c7d9c40cc6737c3a555", - "0x981d06c7431e6f4654e32f1c5b27e7be89e7c38d59c4e2a872a0f0934cb852c6aeff2d2eaee8302131795590b8913f5e", - "0xa6ba9dd43354320f65fd5cdd5446cfa40080bcf3ef4a083a76ad4e6a609b0b088bcf26c4957bfab829dca6064410ca5f", - "0x9367ef28def311c79adfd87e617651fcc41ad8caf047d73ce9a1f327e8871e9b35d5b203fd0c0138e32e2ef91e20ba62", - "0x855d1bb508a9036f42116c8bbb830c576189798baee27c7c3477ef1b1fc5d7b0c2c7203457f1eb48d4b029dd6f646be2", - "0x8539a5d0528d3d601083e162b34cb33b5bf6736b4feeeab4941f10eea127c56b7e0b8d57f34b72f8f674d89c10bf302c", - "0xa3b71a9a9ac2dfcd681bfd8f6a5d9abf5df6950821705bdfb19db25f80d9b8a89fac7a922541cc681325679c629743d2", - "0x8e95929dfd4e5b56e5a8882aad6b7e783337e39055a228b36022646a13a853d574603de5fed12b6c1f2585621ead7afd", - "0x8b05c885575d6894cb67ba737db5915639a6f281bf249480df444ff9f02724e28ed7371ee7ec26d50d25f3966010f763", - "0x90f1a45de0cc0641181d54ee86630b5d182d24e7c30c2615803f16de90ec7c982a00b21f250ccebc2e94ef53a13e77e6", - "0x90f0e97a132092e51a4521c2ecaaa47e4e4f319e67a3cdbd00ed85c2f10dfb69c339bc9498e2abbffcd54b1fdc509a20", - "0xa9995234520cab9d1bdec1897b0b67571b718d5021c0fcf913140206b50ab515273b5f8a77e88fe96f718c80dd9be048", - "0xaebc6495d54d0e45a3c74388891dbcfab767f574fed0581566415af872dc5b3bd5d808c44f6e1fbdde7aa9ffd260b035", - "0xae757f8f4b1000a623a7d8e337a50c3681544520683207e09d05e08a6f39384b7aaadf72018e88b401e4a7bb636f6483", - "0xa626a28d5ce144cc0c6a30b90ec2c1412cbbc464ee96ac49035e5b3a37bb3e4ed74e8934c489b4563f2f7db1caf8b2ad", - "0x8c994e81dfd7a5c2f9d4425636611d5dd72d0b091a5862f8bec609d0cdd3c423eb95b0c999c48faa5dbb31e510c22b61", - "0xa1c0e59e076b908de760d9becff24883c6eb9f968eac356e719c75cce481f2f7bcb1a41ed983a00c1a3b9369a7ff18f9", - "0x8d7e199044fe2e552bc514668fe8171c3416515f7a5019f239c0384f0ade349e88df26cd30f6b67d02b83bf005d85de8", - "0x80190f2255199be690fb502d02ed159aa568c390a684f7840512efc3d2a62f28a49d5d1928ad99a5f975ad81a245acd5", - "0x889d84cefef33f5714e14d558f41d406072ba66b427bf27918b669c5be46261c3de0139610a2c2eadef8e6508e937bcb", - "0xa480a686d5085b854ccf9e261e7f1f2d40d978fc30b62b1a8fa9561127745529405820df21a680ee2258b8cefa5f0201", - "0xae6243400d416a8c13b80b6637726959ef07b8d9b6aff2bd3bb23aaaf97337c7a6b466c5db617bf2798e01d4ccc68e4d", - "0x85e0ff143657e465f3d934ee781de5cbd2bfd24f2fbbe6d65c698cdd93204a845f6ef1fa8941c2578463a06a8a418481", - "0x8f4f8b45f1a9f6c2a711776db70f20149dd6d0e28d125906ba9893c5e74e31c195b0906f04c922c8b556ced7cd3d611d", - "0x877b852c33483b25c4cd8da74b6b589d8aa96e217c3c4d813466c77ef83af95a94a47364aa8421f0396ce631ad87d543", - "0x852cb06bc4222ce125287a7a55a79ad0bf55596f26830dd6d79da3c60f80e3ba7b9a9b42b126dcb99d2cb9ce142783ef", - "0x810cd64c1dfce85d509eeb57a5c84efafe1d671454ef601a040de8d46fb33bc419577f6a6c404e28ffdfe315ffec558a", - "0xb60ff8bc804d101a32079b8ed52285fdbb47fd60c3c15cef17cfe7f6b0567de6b50128b9dbc49a1d9811b62b22c99143", - "0xa9df7068b26a6a58f7a499e67b17d34f2a2e8e5029c6e51e2b4c0d19324fb5cd9734c4c4d5034e1bfc274cd0c74a82d0", - "0xad93c50802ded1e21217a58b874c074ea52322492d589820691572084d8edaede8c2ce8021c6df8c0060f395f3c25ee8", - "0xa17b98e090f7ef5800477132b436c1fccc1802f34956711bfc176e36890c7df95a108e03f34659142434cbd8aee9dccd", - "0xacb14aea5575c293dc0a2b58c5350390801d57e9bcda876d87c56565043ddde1a544a88b48ad0d8ec3d41f690aef801e", - "0x88b8e26cbc83faa053fa247e26c95d1bbb77955b336e1b0e41d080633248238de8adc9b98688c98fdfc67e7286bc5be4", - "0x899f69823cf1b2204c8da91bb4f943c04d943137b08b1c46e160919e3378bd22a666a079a66e63d81c05336c742efdd2", - "0x8d7ffbc0b47a32408c9e88676ac4f87683cf37c37d214163ca630aec2d3cc014d88caff35022ff3b6d036eb8343d52a3", - "0xb7760f27db0704a6742855998a0c31333bb34d60ddebc95588e25b72445ae2030427aab088ec023f94563118980f3b74", - "0xad06ecc0f3745861c266bf93f00b30d41ed89d41e99ab63fedd795c970d3ad40560e57ab7333883a72e5575a059df39c", - "0x8687d28b1cbc8aa34a0e5dbdb540a517da9bda36160daaa7801fce99754f5d16eda3bc8e1df6b0722cfb49e177e9bcb6", - "0xa38332c3ebbd7f734c8e6ab23ae9756f47afbf7d1786fe45daebc8d7d005d6d8fd22f5dbd0fa8741e1bfb2014d3f9df7", - "0xb86f84426dee88188be9c5cc10a41599e53b7733ba6f2402392b0ea985effc7525756ca1b7b92041ae323337618b238f", - "0x958731a6f1881f652d340832728bc7fadd1acebd8daebd772b5acea634e9f7b7254b76d38a7065ea1b2cdea83b18a54f", - "0xadb90bff1f0d7d45b8ba28b536c0e0f7f4dc4b9a0354692ecf29539631d7a57d308db3e438e0f907810234c490b42153", - "0xa5188c775ad76617d3bb6e7f1f3b2449f48b7bb7a84035c316284396529564a227e3b9762a89c7114fa47b3ca7ba418a", - "0xa3826ef63c98793a5c8c5d5159e2e00cc85fb5e5124f06421b165de68c9495e93c2f23cd446adf6e6528967aa3ed3909", - "0x80eab97de89f3824ace5565b540b229adcc6ef9d2940e90de185af309234cd8aa4ae9c7ce1b409b3898c8fd10c8c2896", - "0x8824f5acd4c2330c459fdb9ece9313263a8b20419f50f8d49958dc21754c21a77bcf7fbf3e0041f78d8fb667a3342188", - "0x95091cf06911a997a09b643326c2fadbbe302555ab2521db806a762a5f4492636507ca71d7a093840236ac3c096614f7", - "0xa392c81a546196d7e78b61f3ceaadfb2771d09fe43f862c0af65f5e55ce490a0293b9ab754cb5ab03ff642a9a8213a23", - "0xafd76cce1dfa2c9e4af4f840376674f090af37d8c6541824963373f97b9dd1f405c50b2ff56165e1d4dde760e590738a", - "0x8fc4f513d3b40c10872603e1c29a4b2cf4c99320962644ce89f69ffb57f844344e1d472b2d43559119bdfb5a2c21749a", - "0x9951ca8e13b9a2b4a789e851c04c4f030470772da62f101074ef304612e9653b43b37d2c081b5d0a09196b3a167f5871", - "0xb4f16fc2a113403ab5fc1b6a9afddec77be7406413b70ee126f0e84796168a572940550d61e443e5635591d4b6c46ca9", - "0x8d71452cf39e7345c7298d514b9638a5cbe78af7652f0286d42632c5c6d7953ed284551fb40c77569a7721413cdbf79c", - "0x953625b58d52a308cb00ad87c44a3fd936786ada44000d45bb609ea9db6b156a0d0f9475e13ee5e053eaded19a09990a", - "0xa0983a3baa278ad5f5de734eb1b65a04f668408994e396fb0b054991ad2e56e27ac522b04fe37c9583b754e344f795b3", - "0x8eaa454257f77a6754b2c1c5ff0036fa5b03e214576fabc657902c737fcbf298b1795b43c5006e18894f951f5f7cd203", - "0x90183fdeae2ce2a295a567fa61b997b1f975d1be7b03d0101728cd707bb2a7111c222588ab22e573518fa1ef03719f54", - "0x8abec7f31f6b897a1d497368a42733a6bd14ffbb8b21d3e49fc4cd3c802da70e8886827c1aea0b18d1b44635f81ec461", - "0xa6d1e6fd24b0878ff264b725662e489451c590b2aadaf357d64210a3701fe763f529826fa6e0555267c1f5ecc2c52c05", - "0x8fe6d2a4ea0d91702cb2a8a1d802f5598f26d892f1a929ff056d2b928821e4b172c1c1c0505aa245813fe67074cf9834", - "0x82a026a408003583036f16268113ca6067ce13e89c6e9af0a760f4b2481851c62fadeeef0d361f51dcd9fa5674ec5750", - "0xa489a574b862d4056091ef630e089c163c16c2f104d95eb79a27ae1e898b26d6c1adc23edc1490f73bb545d3a6e3b348", - "0x939d85148547fc7b9894497841bd4430bc670bb670f0efeac424b529a9aebf2c02ac18a9d1402a12e4e590d623de09f0", - "0xa3ab52cf911a2ba7fb0cd242d7778ec0d4fa382960c9bd5b476bb1cd44ff1430a3871bbbcea0a0db2630c39ee639fd1e", - "0xb7629509d8c3a3b88b31f1af137a25c38f536284f11a5bbbe0d05b86a86bc92ebbf70f17c256dc8b0d48374e1985e6f3", - "0x8a8647ff33e0747dd6c6ceddcf7938a542656174a08a31b08337ea49b08d814e75f8363fb51676a2cd2746569e3bc14e", - "0xa7a7f8d94d32b7cee00b3ff272d644b8dca86b8da38c726f632c2bcdfa0afb13fd0a9a5685ddaeb6073df4d9cfa3d878", - "0xb7136eea8d05bfee2265b0e9addb4bdf060270894de30d593627891584b9446b363973de334b6105e0495cf8cb98e8f7", - "0xa9fcd33ea59315ad7611a3e87e8d1fd6730c8cbeeaebd254e4d59ed7d92c97670303a2d22e881ab16c58779331837529", - "0x965fd41741a0d898c2f2048945b2aefc49c735228c25deaf17fed82c4d52cf3f8e93b3fb8825ade632dc4940311b1542", - "0xb9f400a2c7ca7da8b36470ee5d26c672b529b98e6582012cbfc2a3c24b72e73f5633de4265c417c0d47c474155a603c6", - "0x85f333b0b1630a688a385f48bf0175cd13ecdd92fa5499494f4ad5aea0ef1b9d180fad8f936018538d842630ff72884c", - "0x8da95a735a1a98ed8e563099bd87d13a237dd7ec6880cfac56c6416b001e983a56f3d72dda7f68684bb33e4f64cadd30", - "0xa29b66a2095e1acce751f6aec8dfeae1e5b24187dfedb5d1635ca8deae19b580ef09329a18b3385ebb117cd71671f4dd", - "0xb001deeeaf5eaf99ac558c60677b667b9f3d57cf43a2c4d57fd74b125a6da72ea6c9dc81b110655e0df01ca7b8a7a7ed", - "0x912e11dfff77c778969836d5029747b494dd81d9f965f8be2c9db9e8b08f53858eface81862c3ee6a9aa10993d0d23f3", - "0xac166a00e9793cf86753aa002ca274cb6f62328869fe920f5632a69a5d30d8d3ce3f0c5487cb354165763ca41d83495a", - "0xb74df519ae1a8faeff2ccd29892886b327c7434360ab5c5355752667069a77d466a48cb57b1950d10b6c47c88b2a8538", - "0x8751679aeffa39da55f2c2a668f7b26fb8258f70c5454b13e2483e3ad452f3ac7cc4fa075783e72b4a121cd69936c176", - "0xae0cc16848b8bf8fffbb44047d6f1d32b52b19d3551d443a39fb25976a89d1a5d2909a4fc42ee81a98ad09d896bd90a9", - "0xa0c8acd6a2f0d4ab0e0a680fa4a67b076bbbf42b9ec512eb04be05fb2625f6d2ed7b4349eebe61eb9f7bd4f85e9de7fa", - "0x85c629ce0deeb75c18a3b1b4e14577b5666cf25453a89d27f1029a2984133a2b8e7766597e2ff9ee26a65649b816b650", - "0x938dbb477840d3ed27f903d09fd9959f6fec443fbc93324bc28300dd29e602bd3861fd29508da0dfdbb0fff7f09c5a6c", - "0xa7c76cd4a42ab7904d036fe6637471d9836ad15d0d26a07b1803b7fb8988b8c9edf522e0d337a1852131d0f658565ae7", - "0x838a30260cf341ae0cd7a9df84cbc36354c6bc7b8f50c95d154453c9e8ec5435d5f9b23de2a5d91b55adde3dbdb755b9", - "0x8f870b1f798c0516b679273c583c266c2020b8dea7e68be4b0628b85059d49e5a680709c3d6caabe767a0f03975c4626", - "0x89bad0b6499d671b362ae898fee34ad285aa8c77d33ca1d66e8f85b5d637bbd7ae2145caae7d9f47e94c25e9d16b8c4f", - "0xaf963d3dd3d983864c54b0ed1429c52b466383f07a1504215bbf998c071a099a3a1deb08d94b54630ac76d1d40cfc3da", - "0xb5686de207c3d60d4dcfe6a109c0b2f343ed1eb785941301b827b8c07a8f1311e481a56a4baab88edb3ddc4dace6a66a", - "0x95e5978739a3e875e76d927f7c68bdf7ab20966db9fa8859f46a837760dfe529afa9a371a184dfb89d2962c95d5fcf3b", - "0x96d2855e20c37ed7bd7f736e11cfba5f61bb78a68303a7ced418c4c29a889a4798c5680be721a46d548d63525637e6b0", - "0xb134bceb776cd5866e911f8e96016704c9a3caeadcabd7c0f37204497d789bc949e41b93e4c2d597e4c924853f1b21e3", - "0xa1949ff397013acde0303e5d64432bf6dd7f01caa03c5fc38e7c8ae705b9d5c2646b4b02d013004e5eb58e344703260c", - "0x8036a5f79d8aeb6df4810974cf8dbd0ac778906d2f82b969ac9dcfbe7ece832a7e8aad08a4dc520f7abeb24b1610ae84", - "0x982b6b0af8602a992c389232b525d4239edc3ae6ceea77d7729d1fffc829664dd647ff91c4cb9c7f7c25cea507f03167", - "0xb34c7d24fa56ab6acdb8af5b4fa694a1985a1741cc53a2b0c5833611e8ed6fb3b663a4d9a126bb4a1a469f2072199d66", - "0x8166366fec4ee2b3eda097dc200cdfa0533a742dfbe7082dfa14c1c1ecafc9d9fa71f518476634f29d06430869bd5e02", - "0x86c0251ac00b8200618c8b7ce696d1e88c587f91e38580b2d6ae48a3ef904e0ba1b20b7f432719ca40e7995f2281a696", - "0xafd89f3bc7843a1e45ac961e49c1971114c5238d9e21647804b1852b8f476a89c12d1edfb97fff71445e879d6bfd3b70", - "0x911d8bec4d4c3e73a2c35469b2167569f59705404425bd95440408fb788e122f96e9b1bd695f35c6b090f10135b20cd3", - "0xb3f6350ff7afaa0660f9dddd9559db7f164e89351a743fc695d987c88f89fc29136e3c5eb81963edabf2b6f2057120be", - "0xa371229680d1468777862e9c0e864156f9cd7c12ce7313a8de67b7bd34e3d1b6fa45ce891a81f8316f4afcbdecf3b6ca", - "0xa6a9a875ef9efe8ba72523e645b5773aa62c4fb41efd23da3fa38105472308b8d293be766342ee0a2f00758825bd3b6a", - "0xa840d495a184f4499b944ee08f07193a1e1bb8ab21f8ce7aa51d03bd8643f2bc2616c17b68d3fe7c0fb364136926a166", - "0xb55200ae7d6ebb0b04b748051c5907293184b126cf8a1c2f357e024f1a63220b573e2875df83d9b5e0c6e2ace9300c40", - "0xb1e0870f2e3719f42a48256ee58cc27f613308680f2d3645c0f6db0187042dddcfed0cb545423a1a0b851b3a16146d70", - "0xb43a22ff3f838ad43786dc120b7f89a399ed432c7d3aa4e2062ad4152021b6fa01d41b7698da596d6452570c49a62062", - "0x88b1dc50873564560affaa277b1c9d955aebdcdd4117dab1973306893b0e3f090899210102e7e1eef6f7cdf2f4e0e5db", - "0x9223c6246aa320b1b36eb1e28b5f9ccc2977e847850964f9762c7559da9546e508503050e5566ccb67262d570162b7a3", - "0xaeeed21b932752709f43dc0c2c7d27d20263b96a54175dd675677a40a093f02bba80e2e65afe3eb22732a7617bf4ff9d", - "0xb47cae580ae84f4e4303db8f684f559382f075ef6e95698b9a629e92b67bf004f64e7cf47e401768fa170c4259efbda1", - "0x849821e1ead81fe2dc49cd59f2bba305578c4ea0e8f4b8ae8fc275a1c4a6192f8819d5b6d7da786c94dfc16aacf3e236", - "0x8c60d9a8baefc72a3d3f9dd2e24cca40fb5ce36b19d075122391d9b371c904a0a15d2196c0f2ac9da3acf188d15b0fe8", - "0x946edfe168bbe5ddb0fa6c2890bb227d8418bfbebe2bafab84909825484f799407b610d8aab6a900c5ff9eb796cdc4bf", - "0xae7bf8ae71de5d7ea644d9541e49da1ec31eca6ff4c3fbec5480d30e07ef2c2046cc0a486af7b3615a6a908846341e99", - "0xb4d31a6f578463c9a5ccde0ea526c95b1981eb79468665395c0e550829abfdfa86689699d57830856e324092a423f231", - "0x93415ad3a732417cca9771b056ed42db7ce50879aca7c6f71883ad297eaf5a37fd4641d44a0b7e28b90c168834141340", - "0x98960617a413a3ba86d8257a7386355a69258943aa71834166bd624ea93b0af06178e86538e237f88fd039eacf7cb04a", - "0x881335200a487545e38d5b1ffda3080caf5729e1b980603bcdf9ea652cea7848335b83aeeaa321d3476ae4a8d9073582", - "0xb39e84c14666d51895b7a8341fd8319f9e0a58b2a50fc3d7925cce3037f7c75367b5fb5bf25ff4720c9992cab7b8b9f4", - "0x8ea4bab42ee3f0772d6bd24dff3643d8b61147b46ada374414d8d35c0c340e458e449d31023d96e66decf9c58e30cc34", - "0xa5198f6759a045b6a4ba28e4bc3bb638fad44c5a139064327580e285adf38ea82a7570acebf925e81a39d9025f3a6f2e", - "0x80267097e2d27c1b19ecf95d184dcff822d34e03326b9fc139a4f8b75b3f80777bb97a9dd284d9b755f14dd401d63c0e", - "0x946f346220bd3b6f733e94b61a1ad0b44e45c356fa6036dde5882d93b5613c98e23b20e91eddc6b3c5acea38085705af", - "0xa5f559e110cad99bbcae2d9362434aee7db0f3b6d72311291649dbda3f84c10e9760b66b988db3d30067bf18ae2e5238", - "0x8433b38e5c7b293ef532f8c70cef1ed9be7f31f60d5b532e65df7d2885203be78b7ad78ab3011bc54cd9f64c789bf837", - "0xa5a4c0a9b0e0b6bb912cf6ecd30738b0acc0146d77442449b486c3f32d7e60244f643a5cf9cc6da2de5408d0c5f17691", - "0xa81feb329fb51b72464bddcfcf4e02149d995b548d88c64ba143144ce16b652c9913c8ee948ee837596ec97cc43d8cc9", - "0x88e5a7e93a738d61330425bc21ade88d33d7160d124bf174eb3e12a00283654431036977c4f1a47a1bbbf2ef8449ac89", - "0xac75ad7c099383069e662bfd3624b92b64b5838246902e167fc31b9411efda89b2c6bbd1d61b9eb7d304faacf438d70b", - "0x8583bcd1c7cb9bb4bb6bcff803b0a991912b8403a63c0d997761ff77295ccc357d0292318601a8c61329ab28fed7bb83", - "0xa1f9aa0523f1dff00023a44a6c3a9e4e123be0f6722a1c6682ac3c6047efe9e62f4773daf4767e854e1fcbf8ee7339e2", - "0x85f65ebcf5c7e574174b7c4c4166a9a5368e7986b8c0ef846c2e13b75dea7311a87483503149ebfb3cb839b3ef35c82d", - "0xabc55eeb72699031a367b9675a2b91a8434e1f01467660903ced43a0b2a11a85ebdf48f95c13ff67e4e2958065a50ff3", - "0xa4ff77c9b86939a15647499b9412417b984bfb051e5bf27b35392a258a5dac297bbdbcf753a4be6729ffb16be924a2ff", - "0xaf0d41c15b5172efa801cc85ed101b76844dcd06712d0d21160893235a2dbedd15d187a9b31cf0d0ca6c14de6ab2b707", - "0x92661339199f18e5dd9a210783c1d173a26dfa315bd99a33d6f04bf506c871a2b47745c1909faa209d5e6c5c645124a4", - "0xb35813dafb52df709dfa47982bfb44e1bf704f9f46085b2a0e92511dff90e5597110f614f8915830821fc5ed69ae0083", - "0x934a05aa713fa276a4d47f1a28ef06591e5a9a69293c1651c223174df0af4927fc9cd43d374d89c1b4f7c8dc91abe44b", - "0x8f83a0ef05202c0b7170ac96f880135e2256fdf8964dae5aed5dd0f6452a6d8e123321e8c182b3aa6f1f8ab767caa735", - "0xb92db10c21c321cf1349fd34129d7180e5088daf2bbe570de6427299aab68992c011c2e2939a44247396f5427c1d914a", - "0x95ce1892d1ce25ef2bc88a23880055a4d829a3b31f3806635fd49bec32cca4e965b129b6dd3e90f7e3a2eb293ffc548d", - "0x970cf816ee7501ade36b0b59f87c7e352957f67f1f75bbacd8ed52893f9fc40572c76f49c23db44866af7e34a63cd3f9", - "0xa2fcd08581d3569fff699fd7ed1ede5f98f2b95956ecdf975a29af053d9f4f42600b3616ad6161e958c3ce60139c20a4", - "0xb032688b6cc8a7e63dcb82694f71f087b1ee74c4d5fa27323b1ead3ba21722d7fc49eda765725b5553db5260005049c3", - "0xb0b79e4329f1ad25ef6a603390baf889757cab5af10bfa6953a61f89aaace0442b9ef08e57ba778f1e97bf22f16f0ace", - "0xa2e6ac06f8973266cd0df447f82cec16614df65174c756e07f513e2c19aa82c10d8670047860960cfba3c5e4c42768c8", - "0x811e66df0f3721a1ae0293549a0e3cd789f93fb6be2cab8e16015a6d52482af9057b1b75e9456322a5a9e87235e024cd", - "0x8744a80b3d9e37da4c50c536007981a4958d7e531cb93916dbf985cdc22f4ff482a5cc4fe50915c049d2de66530f1881", - "0xb20b6e8c7be654c23c8ca440be2c37cf9cc9f4e81feedfd0cd7c56f37eda8f295fe5d415e9bac93d5f0a237edd8bc465", - "0xb33fd84377f31f7819150d464b5eb3ef66e06cb8712665cf0587d61e1b1c121d11cc647f3753bbc18604941c77edbc1f", - "0x83acb8a3ec5f477b6d44cd49f9e091bc2bf7c9dfee876cde12075a7db9262314cb66ad2e7557114e0c19373e31c6eff1", - "0xacfe4172327832ee207eb07da9cd37da3b009c776f7a8290529f0249f58da213254baddc7c3074fbaa1d226ba1e52b7c", - "0x81911b4dea863424b9d77a981987732382702e0294d8c8e1ec48e89678ecb0e64836b45205a120885fa8f8a3a4b9d4b0", - "0xb11f61b1302579a11077bb2f1f0db371ab943573b261be288dc76172eee8a5102b992a5b526092d160ffd20aac2d4856", - "0xab491f7f1e002a44944c02537f365e525ebb6d5614bba8e5e8e8bd12064c702a1759571ddbeee592a0ba8b73cfce8810", - "0x89211da3d92aed6b111de001b8b5a9231a1c2d09fb1cd2618ec457b635a6c8590fe119acca42fce76dce791c35b889c7", - "0xa5f076c8f7164bcab8af59021ef97a0afa93d0877e52241c3ff5a9a9f81227a55c119ed6a84d34b196e94ec851ca5ca0", - "0x80d91417d0d6c1adb5a3708165da1d54a83caaff482a4f65abf3fb335cbbc738c74ed19a8c451ca98befdf9b2d8b5f90", - "0xaecba33a67f66401614eec5fa945e763da284edb9dc713bad4ac03972630781a09a3e2a291aac0605a9560c5f3444de5", - "0x8a0aa1320bf5217a049b02ad02a4f892bfd6a3f5b48f472041d12f3aaab8dd197307f144f9de5f9e762c6b4971a121b4", - "0xa4120a569e446fe4129f998e51f09c1cc7b29dc2b353d6f6f05daad1a4ef99acfcbaa4950a58aacf7ee1b3fde0af33d0", - "0xaff71370d58b145758a5f24cf3c0c6667d22a1f950b8137c369fa845a5265cd645b422f24fa95e1cd7db1d68686120b6", - "0xa839f075a8a702809a51fbc94595eab4f269a2e7a027aa1f4fc472e77f586138bf5aa4e5570a560e139eb6cda4cca161", - "0x9484f1caa3e35cda0e3d36e43aff3dd8cf45a5a51fc34aafa3a63ed3543047ba9d6af2a9bc7c201c028499e6b4c41b28", - "0x84ddb374c5c9170903bb3e1054fad071b0a147a9ca2ebe2fdb491ebb2431d53b398872a39cc385f973e38579d8e60158", - "0xacaad8babaeaeb52c5b5a16ae689fa5ae15846f2d1f3596a52371bd8681819603822ee8d32ab8cda1bd5290d601e483f", - "0x946b69ca5361b60c3dc31db13669b05e5c0452f3c80e7e185f9667a36f351e9ed83bcb5c6dd2439ecd4490e3a87d260a", - "0x99f457221ac40df86f9b4bef0bf8812720b2f7218273a0aab08c4d4d4fb18a0fb0ef6ba9bf7fa53c116cc6f16742e44f", - "0x8bc0e812d8b718dbe48ead74a6bc7bac68897d01d097422be04110a25589bacd50d336d2c8b70d0dfde6c1b8bc372dc3", - "0x895d118dae2fb35a4b0de22be0d000ec0f0f317b9494db7c12f10d7db81b6f3eaf6d6f3fdfe952f86ec4143d7469368d", - "0x893bf3d7e579e800526bc317438a69590d33759931830daf965cec721baa793ea335e9624a86b84b8fed5effc3e2bbac", - "0xa112d30dda88c749ca15d6dc65bcbc7fe838b2d25329d44410a9a96db195c7ce6a6921196a61ba7c9d40efdb101a164d", - "0xb88b5340af052fc3b8e1a8cf7532206801e79d878f1fb02b32ac4f8e91b64e0ec9252d808b87c4579de15886a20aaef1", - "0x865f76475bb5da18c6a078c720c7b718e55d310876c98017c30ac31882ae347258b508ec34001918324250241d2df5b7", - "0xb6d8a15913eb1714061d5cacbd0bb05edd83ecdb848a89b864e7411598e9f7814d0c039ebe4735437c8370d2ff183751", - "0xa95fedce8351ae9c24d7fa06ebc5cd4e3aef87afaf04a7150e561a6a7f2347bdcec1e56b82d6e5f597fe7124f6cc503b", - "0x8526004ca0c802b073d50b0902ea69975949e7567b2e59ca2cf420bc53d91951d26096f2abb07a2955a51506e86488dd", - "0x99ccecaab68b6e5adadb9c848cb577de7e7ff4afc48d3b6b73bc0872730245b8a1c68cebf467074af6756d6226f4f4a7", - "0xb5497d5c0cd79b7e6022e295642e1f2161254379eb78ef45e47f02c84ef5a3f6b6297718e4fac8093bf017287e456917", - "0xb6943f30012b2093c351413c2b1b648afc14a5c4c0c338179d497e908451d2779919fe806181452ed386c1e8f8e8c25c", - "0xafdb56ce89bcd3247876c918cad68aad8da65d03c7c73ccbee0c4c39f3ad615aab87ffa0db5b3b63b4cc915d0b66deb7", - "0xa44659d7be2f11d4d4949571d7bf84a6f27f874d3281edc34ef1098d321a4dcad9a42632b39633f8f9d20a39f54a2464", - "0xa3e489b4db5832280dd58c62120262471b6fb4355c2ad307bd17c5c246b3f1e1b00f925930f5f5f6987de234fcbb7d16", - "0x87a4e3a190340ed4949597703083d338e9c17263ba8a39b67100589f0dddbc420d9557f9522c17c71ae04b76876f8db0", - "0xa35a3978e928eaac8c182a0a613c611ae7b4827c5e999f938eed06921c0294befdc21d02e68d035a2fc8d03c82641126", - "0xa6898d90265dcf0fb215629f04b07c7918e022667583efe0bfe02f258b446954876c6ca9e369ffe1bb079e2314ebda32", - "0x922fc52e648b6b2b6768c079c67ab425da72907a46add801715f8a2537280869d7071d527b833aa63ef562ce059a392b", - "0x8acbb7c4297196d8d1c131040c34cc7064656a148c2110b19c672abb094b1d084fafe967f7122ba9dd1523a4eaec3b42", - "0x82dbf2cdd581fe3b81b156792228eae2485710e6c21dd5fd14614dc341bb0afbebbc0f32340eda9f094b630afcfc17e8", - "0x907a095dca885da219e4558e9251ec765cf616e995c61546bc010963bf26f2d8adbd9b2ef61f2036e1740a627c20fbed", - "0xa7a83f849691d04640137989a2d0c90a7ed42a42b0ad328435d7e1fba557a27a58eec9170ab3d0099ec97da0c950765a", - "0xb7d435a801c2a5652cb479027f2c172eafa3df8ca0d896bbb9d49a42c42660fb382a8439bfed09ddf7e0214cb6066761", - "0x8bc6b5e79af5512589f90de8e69bc858277055cf7243f592cc4edd193f03f71d16c9300097ddafb79752c63f135c884c", - "0x913264fca800467bee58a429e1f245ef303f5dbeea90f0ce6bb3c7ae6d1bd0f99ea75d3d309634684d2178642c81b5d8", - "0x83ba558f9c23b785a123027c52924a1d7334c853a6165d4f5afd093b0b41951a36860ba0a20fa68f73d7db9df0e3ef38", - "0x875b2df7cb54ecdf7ba31181b9dc7dbe02761ab8ffb61757d42a735c8e20d44bad5b904e76dcec6bb44883fdb9f4ad84", - "0xaf3dc5d2dd29565de8f4c700d5f1ab71dadb4351f06e9ee2eb5ee7a9b5da827d0c6726c6dc780748a26aa3b4d10e6c2d", - "0xa113ff09296b25f550f6d0d3f37dd4517b14cf6d5517293bd3068aa3aea765a8640fcd4bf0ba96db5c00167267fbd574", - "0xa138c5cca485b9180ef091c9e327982bea203c165cb83564f416c36e813bea1ef1f6345f57c8a591df360541b7b758f5", - "0x85793441e917ed520d41dda6e762269fb9f9702e5ef83cee3e90652d324536bf4233425cd05b54a383609076ab84ea13", - "0xb422ac9de53d329e6321a8544c264d63cffc37965d627d7e180a999c3332644e21fedf10cd2f43cf6ba4fc542db91155", - "0xa85d31d4bfa583a493681e57bfccca677ec5b85870a53de37f7be7833b573f8c8dcf029cea4ae548d83048030d77d56d", - "0xab8a0702a371db496715a4ee8fcb6d430641b0f666d7fe3ef80c09df0bf570293cec1aa1675381c6bbd9ecc1f7cdccf9", - "0xb308ef2b87438d35957191294782e9f5014a3394fadad3e2ccaf6ebf20fd889a36dbb8ddb3634baa8e2e131618aa4e70", - "0x919e972e5b67cd65f377e937d67c27b4dd6fd42cfe394a34a70e8c253a1922f62ff36b9dcc7fbbc29b0960ad6a7fde88", - "0xa0e4d4be28301af38a910971c8391ef3ec822ce35757226a7fd96955cd79afa14accba484ef4e7073e46b4b240a5863f", - "0x9422f6d424c1736b4b9bb9762aa62944085e8662c4460319dac4877b1e705aa5cd8b6b3a91268363ec3857c185685f4b", - "0xb7cf9f2053119d284a37df4e4489b632594df64e5dc846652ee26b4715e352e6333118b125021481138e4ec3e9f9987b", - "0xaea983e81c823472df8652654be8a60a8bf40147d599f87e323397f06bf88c98e9c6db0f28414f6ea4091f3eb0f6a96d", - "0xaa20bf03cd8b6ffda09fe0ef693fc0aaa3bb372603e786700e52063a4f7ee742771c41cf5e67e6248f99b7fc73f68dbf", - "0x8748a4978198071d7d5ddc08f8c8f0675d895dc19df0889e70bd86d44c469c719b93f6526c7e7e916c7bfeb9a1379aaf", - "0xb8fcd863d55dab2f7b1c93844306e00056ba17338ddfa3f02689a0b58b30239beb687b64c79b8420ecea8d0d082d9ffa", - "0xabb1a35952dc8a74dd1cdbc8ae7294c6bfd1910edab6f05c879e9ed06c636a949fe0017ec67f8f6f73effcb5817cccae", - "0x8bef43422b1c59e354b7f46c08a8eb78e26c4d01c236a4fe781cefb7465293a4444f2bdc68c6a221cd585a2494d9a1d7", - "0x93527258940feff61befa18fcd6626fcff019d34a3ac8c6886599cbef75b15c15d689e8c1bd2177cc93c4c1792dee8d7", - "0xb7f114eea99c8278841180ec8886ad2bab1826554a1657b9eeb17aa815f31b59c3931913ddec40aa9923bc92f8975635", - "0x91a96446158b194a0a6ada2e37c8a45f3017c34034f757245f6f3b98c65d39d084e74d2a9dc271e5918faa53990ec63f", - "0xaea4ada0a853753db03f9790e20bab80d106f9b09e950f09aeaba5d869f0173bed673b866a96d6b0dd8123a539caac9a", - "0xb8e3e98ff0d3e512441e008a4a6783233045a4639e0c215c81984846b43ff98de99d7925cf717b1ca644f6229b6d16a2", - "0x8987ef81a75213894e11e0310e8ba60fe06e2b264cc61655e5b51bf41cc8c3d6c10696642ea3517770f93be360207621", - "0x8d4eff7335252f74af4a619c78625fd245df640f2086338dbb6c26b059f83fe70f3e81f5b6c12d62c0f784e572d56865", - "0xa56f6389b0bac338f20c615d7d11e16045a76cbea23ced0a9d9067f538421c378200bfd4523b7c96094ab67f47f98d42", - "0x83f5ab0727fd6ce8b3370ce3fac1f3a9c1930ea7ebbd16be61cc26f34aa1291ba4b5f16729d7d4f5924eaa4a1e31a04e", - "0x8cc62366874bf8751067a526ea32927584cef41174e2ec5a53079ee557067bc282f372b831cb2547c5e21a2f178c91b4", - "0xb609e141006dc8d8649457efc03f8710d49abb34bc26a33ed4e173e51b85d7acdf18d74aed161b074f679d88f5aa2bf3", - "0x873c7aa784c17b678443320950e494250baff8766db42619b9fc7ec4c3afa4eee290cd1f822b925d5b9e55c9cdd1af2f", - "0x859ba787f052d3665481c3dd58159ec8c238d918fb6d2787ebe275ef9acd377cb7aaa03a69820c78247bf51afee3d5bf", - "0x8eb1e6d2b0f51a3275b4a8be96957cb2d518b32c815dc0dfd5f75340c7dee73e5edc45db7c7d375c4ffaf8c59767d0c1", - "0x85f3876ff5edbb826a9592e68db3dcc975725bfdda4fcac197758a8b27e4f493e6c531b1342ba0f5a75f965273720345", - "0x8a1272f2678d4ba57e76c8758818965e6849971e8296b60ff85a522feeaaa3d23d3696c040d8bdaf1b380db392e988aa", - "0x85002b31ce31be7cc8757141a59a7cf9228b83144993d325b2241f5bfac09a02aca0c336307257f1a978c0bbf79fa4fe", - "0xb96bd26a6bbbc705c640285fd561943ef659fca73f25e8bf28cfcd21195752b40359d0edca0adc252d6e1784da267197", - "0x936cfe367b83a798ab495b220f19cfe2e5bde1b879c8a130f84516ac07e3e3addcc791dc0e83a69c3afc225bed008542", - "0xb1302f36190e204efd9b1d720bfaec162fcbba1b30400669dbcdd6e302c8c28f8b58b8bbde10f4512467dd78ed70d5e0", - "0x8291b49f56259c8d6b4fd71525725dd1f35b87858606fc3fe7e048ac48b8a23ba3f0b1907b7c0d0c5ef6fa76cddc23f0", - "0x97aca69d8e88ed8d468d538f863e624f6aed86424c6b7a861e3f45c8bf47c03e7b15d35e01f7add0a4157af171d9360c", - "0xb590d896e6b6f2e4dcffebfa67fc087fa518a9c8cb0834a5668cabe44e5c2b6f248f309b9cd74779030e172dba5d9e29", - "0x97e7099bff654bcb37b051a3e8a5a7672d6ab7e93747a97b062fc7ae00c95deef51f5ced2966499217147058e00da4be", - "0x83435b739426f1b57f54ebad423939a68ad3d520db8ca5b7e28d1142ebfb4df93f418b180a6c226c0ca28fa0651163a0", - "0x946c9144d982837c4dbc0b59544bdbc9f57e7c9ef0c82a7ad8cfddea78dedc379dbc97af54ba3ac751d844842a2990a4", - "0x90ba1eff9c25adba8c3e6ef5b0d46c13de304632fec0646ee3a7bee69da2bc29e162dd3fb98a37ed1184ae5da359cf0a", - "0xb17b7a5c0a48eb9784efb5ff8499230b45efeb801cf68e13fe16d0d308511af5aa60e3b9a5610f96d7c2242ae57d455b", - "0x9991245e5617c4ea71575e5b2efe444f09cbbed13b130da08f8e9809d62512e8298a88d41f6aa3dbf3bcbc90654ceb18", - "0xa1190c4cbccf2898a7fe025afd03f8652973a11cef59775fb47d69a6b4dcb9a5a0c554070421a5e10a75e43b63d37b79", - "0x857c0a5f291eb35a76be11543a8c3d798187bd0717e2cdee50d390b66322d0d9529520fd3377136cdc93cfee99b6403f", - "0x944d11e5f9a3493c67786df94f129352d892fbdc43e98206b8dbf83cce240f65305e1768b38e5576048a31dca5c18f31", - "0x818f361c5dae709e067a82b81beffbd9674de8df2bc1bfc3a27ddf326260e124e46b1e36697fb8de539b7736db093e9e", - "0xb07f5b737735a0d628e7ac2d335080b769bdb3acea38ad121e247a6e4307916ba1d029da5d341f079ea61eeaf7d8554e", - "0xa69e338803f3ee0fbbddc7ee481a13f6b64d25d71bae0d76f4b5145b54923cf1616c77ba0fd9ca37a3ae47208f490423", - "0xacaee66b94e226622e28a144f93f6b1b442b9c79d7a8a1740c4d53044d0675a661e7453509b9e716e469fe11ce45ee31", - "0x9402ca799d2e1cce0317ed49453ee0b2669b05e68ff101b89306db215c3941b3786ad3402d00369cb1dee020b56d3142", - "0x849440c539fc0df3c8d06e23e271e6faa50234d5c057b8561e9376415f4396e548351cc677b0abeafe4f51b855a3dc83", - "0x865b99587eb3dbc17e412647673f22b2e89185d1df1ec8ea04515585ad2edfb731be458123118dcd7b41b475026477b9", - "0x9390618833b5adbaf24bd38cf9fc6f25104717f314259bb4da5c7a1f6963ecdc04d07bed391d8cd765c3d53567b2b6b1", - "0x95383e8b1d0a629cec238b5ae2bda236a027f4e3b5f99ceace05f1d5a781ec1e7a43058f44ef0a5aee6b0db5697a0d89", - "0x91739b8946d90db3a5244f7485295cc58143ba0449c9e539df1ba3c166ecf85ff914c9941192963c32d35033ae2f0980", - "0xb5d88848d856d882db5947b9182025f0abf2bc4335b650fa0a48a578e2c87f32cc86d42d3b665ee2eab46d072bf1eccd", - "0x91f4c754549f5a53b1902ef84274ce9acf0bfd2e824e62eb127d67e3214ce05fc2430c05ea51e94dc6e8978f5d076bab", - "0x91fff8c75f8ad86afe78ec301de05e4ca71421d731419a17c747a9a0bf81129422c9499e4749107b168d1695dc90292f", - "0x99fbd7bede9cc1e2974c2a21c70788960c2dbf45a89552da8d73bb1d398b8399590707f2f4ba4b43cb356e703eb01b5e", - "0x80a51cd83e3d748c07b9ac82de1a697b09031e3edc7bf585f06cd0ffa8ea319517fcc2b735614b656677b54b4910814e", - "0x886b27de1f93311d1a31b6d698aa28b54fbd800decd8e25243d89e352ee38cb252d5648b5134a3e1ed021bae46e9da48", - "0x976e70c94db905f83b4ef72188d840874bf005814c0c772f3832aa65b1f21927403125eea7a07b6d3305b1a781b36ab7", - "0xb4adb9d1c49eb31462583580e3ffa625bea4f8b2a7d4927e4ff925c1759d4b3c1e43283d635b54fb0eabfbe1f4c12992", - "0xb66b466bd48485ebeedd47e749d86cbaa3deffbbee2e69cfaa5e9f3bd28b143d7c1c0255a7a1393a2cc1490b2c485571", - "0x8bded5bc0794513947ddb00ff6b780c5cc63a74e2a0b0284153c346a31c82e1eff07c073939da39e6f87a06c14ff1a80", - "0xaceea8c6f799589f6b7070abf69fec724e6679514e60f1eaf9a52c37e9cebb72abcc833a81d8da1a4f5194c1a7eeff63", - "0x89a9f76d053379687fd221ebcaf02c15c2c241bb673ef5298e32640a115d9e0f2331c3e185572cd65946dd6c5bd42412", - "0xa57b6f1e3fdd92eadc6220760f22d0685a82cada1c7a1bda96d36e48e2852f74f3a83c757dd8857e0aee59e978da4919", - "0x9106cf0891bb39ce87433c5f06a5c97a071d08ad44a7cbcd6918c0729c66bb317fbbee8aa45591cee332ad1234c7257d", - "0x96c18cca4a0f0299e0027ff697798085f9f698a7237052c5f191b1dba914e5a015ae356b80c17f0fdd31d08c5a939ebb", - "0xa892103c93df126c024825c07d8769bdac5f1d26ea9509ee26530dc594384b2a5095cc34e0b41ab3db0392a29792c9e8", - "0xb7c2dbc95edb6fc25802ea051803b7bea682f87a99f8a9fdcc3091c81d914b9493dfb18a8894c964805298a6c22b07f2", - "0x8e40948927d560a6840d7fb99802989ce72b43693e9dc7ed9dcda4bca7daedf75271cf656bcc22b3f999a550faad8648", - "0xb354de1c6f0603df3ed9036c610281e55b51a48950ee3ce57a00b4692232de7ca57d19722700e15cbe67a91fcec2f786", - "0xadf987b90737b933436d8036c1d3f0c9104f26c540052e22e703964f72739ac1261e4289b8f27dec47281a0f3f51378a", - "0x8ed5248e9c836fffa7c924178db593e1aaeb54bcf2e93c1983c1f3899cad538deeb2b836430fddc9b2f283e0797ea11e", - "0x907e5410e3bd5d7f55340e2f497bd1ca10bfcb4abed2c66a3cdf94dc40bbd7c43ac98754e0b4b223ea4c61eebf2f27f5", - "0x8e81b441ea0397db28840fb4b3c3bfe6d8e31418816f7bda36f9c1cfe4556daee30c43639d90a2dc9b02a3d65e5f4ab2", - "0x897085c477f5030f9fed06e181b05953a8cd2001d959dd6139738d40f1d673b2c7120b5348f678547acfdc90ffc9fcc6", - "0xb0bf2784c4b3808a04be5a00a0593035ce162b3886e1500247b48365eac8ec3d27c7e5e6372e030c779c75fb79772d0d", - "0xaf3fe6c75f2a1241ac885d5091ff3882cf01695d957d882e940f0c31f7a5b5e269c1a2bae7336e9a7cda2b1d23c03bd1", - "0xa6d94e065f85736d77080a4f775885ccb0dd5efdbe747e4595280bca0ebe12450257c1beadcbec77566ef57508c5d4df", - "0xa5c50fe56b5532bf391da639a2f2b6cbb2634fc6637416fea7c29a522dea024d4adaaa29b6d472b4d2cc3e3b85c72e2a", - "0xafc35f5a03b245a6286318ef489db05d397bbd16c17b4e92eeb56509f875246c0176c01804139eb67dc4247c2a36ff9e", - "0x99ba14ab5a9612c078f9bbaa0e68fd1d52ecceb2ed19bd9abf8f98dd4ed1f9c4fa6e4d41bcef69be2ff020b291749ca8", - "0x8018cdd3d96f331b4c470a4c3904bed44cadecbeec2544ca10e4352cf4ae1a856cf55f6383d666bf997ad3e16816006e", - "0xa9964790c318bb07b8fe61d230dd2161dd3160e186004647a925cfec4c583b4e33530bf5d93d8a14338b090055085b05", - "0xab89d8401df722101c2785cb3ef833017f58376ee82cedd3e9405b2534f259bb76063434a247652c7615a6de5194de65", - "0xa72c3d320a0d40936dee8edfb36703be633aefbb8f89530df04eb6aebe0305ef4f4b6709436f8036d417272a7e47e22a", - "0xb3457661ad62634cc25e2918921a97b0bf5c59ccc7063bc8eb53194783f07659f42f8978c589228af5b12696588d8b2f", - "0x926fa35cd3ed4c8ad78af6284b87ae53b2e25a1ff50398034142a2bbed5b989ba3181ff116838931742c0fbcd8b8a56c", - "0xae57fe506626432f27ae4f8791421c2df9efd9aaabe4b840ccf65fc3d0dd2f83e19eb63ae87bfa6898d37b5da869ddb2", - "0x99c0a26ac74211db77918156d7ae9bea6ecf48da3ce9e53829a9ad5ed41321227c94fbd7449ae2e44aae801811552b1b", - "0xabdd2635b61cb948e51b762a256cf9d159b9fcb39b2fb11ba2fed1cb53475a03fc6e024a6a824a67a689396119a36a7b", - "0xa5ca98b98da8bb8eb07b1e5e3c85a854db42addefacd141771a0c63a8e198421dccc55ef1d94662ca99a7d83b9173fc3", - "0xa821bb5cf1eb3aeae6318c8d554e2ea3137d73bb29d2e4450c9a33f441355ea77bb0e0e0ce7c819abc3ed119110a3a92", - "0x95cdfb19b3f7196c26d60586e2c1efaa93352a712f8c8ef6209f6f318cecd52d7bebdfbfee4be1f5903a1595f73bc985", - "0xaef6e6a400106e217f9888afcef0a1e1299b59017e77dc5453317dec0c32ae96873608bef3f1b504a7e4f45b06edc9c6", - "0x96399ad093299ba26dc09ae85dbec9a1801dea4a338dd5d578bcdcb91246db0059e54098ba8a56cbb24600a40095cf79", - "0xad8b018ac99857ad4b38bdf6d110bbef64029a4d9f08df85a278c6ddc362a5f64e1f3a919f798ccb2f85a7f4ca1260b4", - "0xb211f3b5dd91941d119c4fe05e2b4c7bb0ce0a8d7ef05932a96e850f549a78cd20cded0b3adb3f9f8b7058889ae2cb4e", - "0xab780dd363671765c9c9ab0f4e7096aacf5894e042b75f40a92df8eb272a6229078cd6eadcc500eead3650860aa82177", - "0xa4d96b16ab3abe77ead9b4477c81957e66a028f95557e390352743da53d1a7ba0c81d928a7ea8bc03b9900135ac36a6a", - "0xb4d4e028099bf0f28ac32141cd8de4ee7c3d62d4f519fad6abbb4ba39592750812220a4167d1da4c4f46df965f7cf43d", - "0xaa929c5f0bd8cb44a861bfb3d18340a58c61d82afa642447b71b1470a7b99fe3d5796bdd016b121838cb3594f5a92967", - "0xa038e66f0a28aba19d7079643788db3eed8e412fb9ab4c0f6cacf438af4657cc386a7c22ae97ccc8c33f19a572d6431c", - "0x89c1ff879faa80428910e00b632d31c0cebb0c67e8f5ded333d41f918032282fb59fbcbe26d3156592f9692213667560", - "0x8d899072c9d30e27065d73c79ce3130a09b6a4a4c7d9c4e4488fda4d52ad72bd5f1fd80f3a8936ef79cf362a60817453", - "0x8ffb84a897df9031f9a8e7af06855180562f7ca796489b51bb7cca8d0ca1d9766a4de197a3eb7e298b1dfb39bc6e9778", - "0x836ebd0b37e7ef4ff7b4fc5af157b75fa07a2244045c3852702eaafa119ca1260c654a872f1b3708b65671a2ece66ad2", - "0x9292dfd6d5bfc95f043f4eb9855c10cbcf90fbd03e7a256c163749b23a307b46a331bdbd202236dca0e8ea29e24906de", - "0x8bc37eaa720e293e32b7986061d2ffcbd654d8143e661aabe5602adc832ab535cffbe12a7b571d423675636a74b956e4", - "0x887455f368515340eb6f9b535f16a1cf3e22f0ceda2ead08c5caefccef4087e9f4b5d61c5b110ff3e28e4ab2ad9e97c5", - "0xa6e5ec36e7712056fec00de15b8696952b17891e48ebe2fa90c6f782c7d927b430917b36b4a25b3d8466da3ca2a4985d", - "0x895cae36ba786104ec45740c5dc4f2416b2adce6e806815e3994e98d9e1be372eaec50094fbb7089015684874631ab7e", - "0x9687444fe6250c246b1711a8f73992f15c3cac801e79c54ffd5e243ad539fdd98727043e4f62d36daf866750de1ba926", - "0xb17f75044c8e9ce311bb421a5427006b6fa1428706d04613bd31328f4549decd133e62f4b1917016e36eb02ea316a0ca", - "0x8538a84d2f9079dd272a7383ff03b7674f50b9c220e0399c794a2bcb825d643d0fc8095d972d5186b6f0fe9db0f7084f", - "0xaf07b37644cc216e7083bac1c4e6095fa898f3417699df172c1f6e55d6c13c11f5279edd4c7714d65360b5e4c3c6731e", - "0x87eed8fe7486c0794884c344c07d3964f8fc065aebb0bb3426506ab879b2e0dfaefa5cece213ec16c7b20e6f946c0bd2", - "0x8a4bf42f141d8bc47c9702779d692a72752510ef38e290d36f529f545a2295082a936c8420f59d74b200a8fff55167c4", - "0xa7170e5e00a504a3b37cb19facf399c227497a0b1e9c8a161d541cb553eb8211449c6ac26fe79a7ff7b1c17f33591d74", - "0xa9a2cc7232f07ef9f6d451680648f6b4985ecab5db0125787ac37280e4c07c8210bab254d0b758fd5e8c6bcf2ee2b9ff", - "0x8908d82ebfa78a3de5c56e052d9b5d442af67a510e88a76ba89e4919ae1620c5d15655f663810cfc0ee56c256a420737", - "0xa9d47f3d14047ca86c5db9b71f99568768eaa8a6eb327981203fdb594bdb0a8df2a4a307f22dcea19d74801f4648ea89", - "0xa7c287e0e202ebfc5be261c1279af71f7a2096614ee6526cd8b70e38bb5b0b7aca21a17140d0eddea2f2b849c251656a", - "0x97807451e61557d122f638c3f736ab4dab603538396dca0fcdf99f434a6e1f9def0521816b819b1c57ecdfa93bd077eb", - "0xa8486d60742446396c9d8bc0d4bed868171de4127e9a5a227f24cbf4efbbe5689bbd38f2105498706a6179340b00aed5", - "0xa03b97c2a543dfefa1deb316db9316191ab14e3dd58255ce1027b4e65060d02fb5cb0d6ac1a2bf45bfeac72537b26429", - "0xa7d25060f6861873410c296a4959a058174e9a1681ac41770788191df67fc1391545dab09de06b56cd73a811b676aa1b", - "0x96bb9c9aa85d205e085434d44f5021d8bbafc52cd2727b44e2a66094a4e5467b6294d24146b54c0d964c711e74a258d4", - "0xb07b17f11267e577191e920fa5966880f85ff7089ac59d5d550e46f3a5cdadd94f438a547cd1ec66f20a447e421f96c6", - "0x964e33e1571c97088fe7c8ca3430db60a8119f743a47aa0827e6e2fb9bae5ff3bf6cecd17b11dd34628546b6eb938372", - "0x82a0513a05870b96509a559164e6ff26988ea8a2227ac6da9adc96fe793485a9eb6bdcab09afac7be4aef9a5ae358199", - "0xb1185bc679623e7a37a873d90a2a6393fb5ccc86e74ba4ba6f71277df3623cde632feae4414d6429db6b4babde16dee0", - "0xb3d77504b7032b5593a674d3c0cd2efbf56b2b44ed7fe8669f752828045e4e68202a37bf441f674b9c134886d4cee1df", - "0x95ab31749ff1f7b3f165ce45af943c6ed1f1071448c37009643a5f0281875695c16c28fc8d8011a71a108a2d8758e57d", - "0xb234dee9c56c582084af6546d1853f58e158549b28670b6783b4b5d7d52f00e805e73044a8b8bd44f3d5e10816c57ecc", - "0x86da5d2343f652715c1df58a4581e4010cf4cbe27a8c72bb92e322152000d14e44cc36e37ff6a55db890b29096c599b9", - "0x8b7be904c50f36453eff8c6267edcb4086a2f4803777d4414c5c70c45b97541753def16833e691d6b68d9ef19a15cb23", - "0xb1f4e81b2cdb08bd73404a4095255fa5d28bcd1992a5fd7e5d929cfd5f35645793462805a092ec621946aaf5607ef471", - "0xa7f2ca8dacb03825ef537669baff512baf1ea39a1a0333f6af93505f37ed2e4bbd56cb9c3b246810feee7bacdf4c2759", - "0x996d0c6c0530c44c1599ffdf7042c42698e5e9efee4feb92f2674431bbddf8cf26d109f5d54208071079dfa801e01052", - "0xb99647e7d428f3baa450841f10e2dc704ce8125634cc5e7e72a8aa149bf1b6035adce8979a116a97c58c93e5774f72b7", - "0x95960a7f95ad47b4a917920f1a82fbbecd17a4050e443f7f85b325929c1e1f803cf3d812d2cedeab724d11b135dde7a3", - "0x8f9cd1efdf176b80e961c54090e114324616b2764a147a0d7538efe6b0c406ec09fd6f04a011ff40e0fa0b774dd98888", - "0xb99431d2e946ac4be383b38a49b26e92139b17e6e0f0b0dc0481b59f1ff029fb73a0fc7e6fff3e28d7c3678d6479f5a3", - "0xa888887a4241ce156bedf74f5e72bfa2c6d580a438e206932aefc020678d3d0eb7df4c9fe8142a7c27191837f46a6af6", - "0xab62224ea33b9a66722eb73cfd1434b85b63c121d92e3eebb1dff8b80dd861238acf2003f80f9341bfea6bde0bfcd38c", - "0x9115df3026971dd3efe7e33618449ff94e8fd8c165de0b08d4a9593a906bbed67ec3ed925b921752700f9e54cd00b983", - "0x95de78c37e354decd2b80f8f5a817d153309a6a8e2f0c82a9586a32051a9af03e437a1fb03d1b147f0be489ef76b578b", - "0xa7b8a6e383de7739063f24772460e36209be9e1d367fe42153ffe1bccb788a699e1c8b27336435cd7bf85d51ba6bfdd6", - "0x937a8af7ed18d1a55bf3bbe21e24363ae2cb4c8f000418047bf696501aaeec41f2ddf952fd80ef3373f61566faa276a9", - "0xab5e4931771aeb41c10fa1796d6002b06e512620e9d1c1649c282f296853c913f44e06e377a02f57192b8f09937282eb", - "0x893d88009754c84ec1c523a381d2a443cb6d3879e98a1965e41759420a088a7582e4d0456067b2f90d9d56af4ea94bba", - "0x91b2388a4146ebaaa977fec28ffbfb88ac2a1089a8a258f0451c4152877065f50402a9397ba045b896997208b46f3ebf", - "0x8ce0523192e4cc8348cd0c79354a4930137f6f08063de4a940ea66c0b31d5ea315ce9d9c5c2ec4fa6ee79d4df83840dd", - "0xb72f75c4ab77aca8df1a1b691b6ef1a3ff1c343dd9ed48212542e447d2ed3af3017c9ad6826991e9ef472348c21b72a4", - "0xaf0fa5a960f185326877daf735ad96c6bd8f8f99ab0ab22e0119c22a0939976ece5c6a878c40380497570dc397844dba", - "0xadf9f41393e1196e59b39499623da81be9f76df047ae2472ce5a45f83871bb2a0233e00233b52c5c2fa97a6870fbab0a", - "0x8d9fc3aecd8b9a9fca8951753eea8b3e6b9eb8819a31cca8c85a9606ce1bd3885edb4d8cdbc6f0c54449c12927285996", - "0x901969c1d6cac2adcdc83818d91b41dc29ef39c3d84a6f68740b262657ec9bd7871e09b0a9b156b39fa62065c61dacb1", - "0x9536a48ccd2c98f2dcbff3d81578bbb8f828bf94d8d846d985f575059cd7fb28dfa138b481d305a07b42fcb92bacfa11", - "0x8d336654833833558e01b7213dc0217d7943544d36d25b46ecc1e31a2992439679205b5b3ab36a8410311109daa5aa00", - "0x95113547163e969240701e7414bf38212140db073f90a65708c5970a6aaf3aba029590a94839618fc3f7dd4f23306734", - "0xa959d77a159b07b0d3d41a107c24a39f7514f8ce24efa046cfcf6ace852a1d948747f59c80eb06277dce1a2ba2ec8ea9", - "0x8d2cb52dd7f5c56ef479c0937b83b8519fa49eb19b13ea2ec67266a7b3d227fb8d0c2454c4618d63da1c8e5d4171ac7b", - "0x9941698c5078936d2c402d7db6756cc60c542682977f7e0497906a45df6b8d0ffe540f09a023c9593188ba1b8ce6dfcb", - "0x9631d9b7ec0fc2de8051c0a7b68c831ba5271c17644b815e8428e81bad056abb51b9ca2424d41819e09125baf7aaf2d4", - "0xa0f3d27b29a63f9626e1925eec38047c92c9ab3f72504bf1d45700a612682ad4bf4a4de41d2432e27b745b1613ff22f9", - "0x80e3701acfd01fc5b16ecfa0c6c6fd4c50fe60643c77de513f0ad7a1a2201e49479aa59056fd6c331e44292f820a6a2c", - "0xa758c81743ab68b8895db3d75030c5dd4b2ccc9f4a26e69eb54635378a2abfc21cba6ca431afb3f00be66cffba6ab616", - "0xa397acb2e119d667f1ab5f13796fd611e1813f98f554112c4c478956c6a0ebaceef3afae7ee71f279277df19e8e4543a", - "0xa95df7d52b535044a7c3cf3b95a03bafd4466bdb905f9b5f5290a6e5c2ac0f0e295136da2625df6161ab49abcdacb40f", - "0x8639fc0c48211135909d9e999459568dbdbbc7439933bab43d503e07e796a1f008930e8a8450e8346ab110ec558bcbb9", - "0xa837bcc0524614af9e7b677532fabfb48a50d8bec662578ba22f72462caabda93c35750eed6d77b936636bf165c6f14e", - "0x97d51535c469c867666e0e0d9ed8c2472aa27916370e6c3de7d6b2351a022e2a5330de6d23c112880b0dc5a4e90f2438", - "0xaadb093c06bd86bd450e3eb5aa20f542d450f9f62b4510e196f2659f2e3667b0fe026517c33e268af75a9c1b2bc45619", - "0x860cef2e0310d1a49a9dd6bc18d1ca3841ed1121d96a4f51008799b6e99eb65f48838cd1e0c134f7358a3346332f3c73", - "0xb11c4f9e7ef56db46636474a91d6416bcb4954e34b93abf509f8c3f790b98f04bd0853104ec4a1ff5401a66f27475fce", - "0x87cb52e90a96c5ee581dc8ab241e2fd5df976fe57cc08d9ffda3925a04398e7cffaf5a74c90a7319927f27c8a1f3cef5", - "0xb03831449f658a418a27fd91da32024fdf2b904baf1ba3b17bbf9400eaddc16c3d09ad62cc18a92b780c10b0543c9013", - "0x94e228af11cb38532e7256fa4a293a39ffa8f3920ed1c5ad6f39ce532e789bb262b354273af062add4ca04841f99d3aa", - "0x99eb3aeb61ec15f3719145cf80501f1336f357cc79fca6981ea14320faed1d04ebe0dbce91d710d25c4e4dc5b6461ebf", - "0x920a3c4b0d0fbe379a675e8938047ea3ec8d47b94430399b69dd4f46315ee44bd62089c9a25e7fa5a13a989612fe3d09", - "0xb6414a9a9650100a4c0960c129fa67e765fe42489e50868dd94e315e68d5471e11bfbc86faffb90670e0bec6f4542869", - "0x94b85e0b06580a85d45e57dae1cfd9d967d35bdfcd84169ef48b333c9321f2902278c2594c2e51fecd8dbcd221951e29", - "0xb2c0a0dd75e04a85def2a886ee1fda51f530e33b56f3c2cf61d1605d40217aa549eef3361d05975d565519c6079cc2ac", - "0xabb0ea261116c3f395360d5ac731a7514a3c290f29346dc82bacb024d5455d61c442fefe99cc94dddcae47e30c0e031f", - "0xa32d95ae590baa7956497eddf4c56bff5dfdc08c5817168196c794516610fcc4dbcd82cf9061716d880e151b455b01e0", - "0x8bd589fb6e3041f3ef9b8c50d29aed1a39e90719681f61b75a27489256a73c78c50c09dd9d994c83f0e75dfe40b4de84", - "0x82d01cdaf949d2c7f4db7bfadbf47e80ff9d9374c91512b5a77762488308e013689416c684528a1b16423c6b48406baf", - "0xb23e20deb7e1bbbc328cbe6e11874d6bdbb675704a55af1039b630a2866b53d4b48419db834a89b31ebed2cfc41278dd", - "0xa371559d29262abd4b13df5a6a5c23adab5a483f9a33a8d043163fcb659263322ee94f872f55b67447b0a488f88672d6", - "0x85b33ddf4a6472cacc0ed9b5ec75ed54b3157e73a2d88986c9afa8cb542e662a74797a9a4fec9111c67e5a81c54c82b3", - "0xaf1248bc47a6426c69011694f369dc0ec445f1810b3914a2ff7b830b69c7e4eaa4bafec8b10ed00b5372b0c78655a59b", - "0x94b261ed52d5637fd4c81187000bd0e5c5398ce25797b91c61b30d7b18d614ab9a2ca83d66a51faf4c3f98714e5b0ea5", - "0x953d4571c1b83279f6c5958727aaf9285d8b8cbdbfbaff51527b4a8cfdd73d3439ba862cdb0e2356e74987ff66d2c4d9", - "0xb765dae55d0651aca3b3eaef4ca477f0b0fda8d25c89dccd53a5573dd0c4be7faaadaa4e90029cdd7c09a76d4ce51b91", - "0xb6d7b7c41556c85c3894d0d350510b512a0e22089d3d1dd240ad14c2c2b0ce1f003388100f3154ad80ec50892a033294", - "0xa64561dc4b42289c2edf121f934bc6a6e283d7dce128a703f9a9555e0df7dda2825525dbd3679cd6ba7716de230a3142", - "0xa46c574721e8be4a3b10d41c71057270cca42eec94ca2268ee4ab5426c7ce894efa9fa525623252a6a1b97bcf855a0a5", - "0xa66d37f1999c9c6e071d2a961074c3d9fdcf9c94bf3e6c6ed82693095538dd445f45496e4c83b5333b9c8e0e64233adc", - "0xab13814b227a0043e7d1ff6365360e292aca65d39602d8e0a574d22d25d99ccb94417c9b73095632ff302e3d9a09d067", - "0xb2c445b69cff70d913143b722440d2564a05558d418c8ef847483b5196d7e581c094bae1dbb91c4499501cfa2c027759", - "0x87cbde089962d5f093324b71e2976edbe6ad54fb8834dd6e73da9585b8935fca1c597b4d525949699fdfa79686721616", - "0xa2c7e60966acb09c56cf9ad5bdcc820dcabf21ef7784970d10353048cf3b7df7790a40395561d1064e03109eaac0df98", - "0x8ea7b8af208678178553946b2ee9e68c0e751b34f3652409a5e66c40d3aee3a40ba6ffe2175ce16c6a81b78ecc597d02", - "0x960234239e1e3ea262e53d256ad41b2fe73f506b3d130732d0ee48819eb8a9c85bb5106a304874d8625afae682c34015", - "0x858459694c4e8fdafa6cdaee1184e1305ca6e102222b99b8e283dd9bb3ebf80e55d6c4d8831a072b813c8eceb8124d95", - "0xa30a8ce0f44aeb5590dc618c81c7cac441470ce79fd7881a8f2ea4ca5f9d848ebde762fcaee985cbd3d5990367403351", - "0xa83867643672248b07d3705813b56489453e7bc546cdba570468152d9a1bd04f0656034e7d03736ea156fc97c88dc37f", - "0xa7bb52e0fc58b940dc47ea4d0a583012ee41fad285aba1a60a6c54fa32cfe819146888c5d63222c93f90de15745efb2b", - "0x8627bcc853bdeaad37f1d0f7d6b30ada9b481ccdf79b618803673de8a142e8a4ce3e7e16caed1170a7332119bcdc10a9", - "0x8903d9dc3716b59e8e99e469bd9fde6f4bca857ce24f3a23db817012f1ea415c2b4656c7aeca31d810582bb3e1c08cc6", - "0x875169863a325b16f892ad8a7385be94d35e398408138bd0a8468923c05123d53dba4ce0e572ea48fcdadd9bd9faa47a", - "0xb255b98d46d6cc44235e6ce794cc0c1d3bd074c51d58436a7796ce6dc0ae69f4edaa3771b35d3b8a2a9acd2f6736fab3", - "0x9740c4d0ee40e79715a70890efda3455633ce3a715cbfc26a53e314ebbe61937b0346b4859df5b72eb20bcba96983870", - "0xa44ce22ab5ddc23953b02ec187a0f419db134522306a9078e1e13d5bf45d536450d48016a5e1885a346997003d024db0", - "0x90af81c08afdccd83a33f21d0dc0305898347f8bd77cc29385b9de9d2408434857044aec3b74cb72585338c122e83bb4", - "0x80e162a7656c9ae38efa91ae93e5bd6cb903f921f9f50874694b9a9e0e2d2595411963d0e3f0c2d536b86f83b6e4d6ef", - "0x8b49fa6babe47291f9d290df35e94e83be1946784b9c7867efd8bc97a12be453013939667164b24aeb53d8950288a442", - "0xa1df6435d718915df3da6dda61da1532a86e196dc7632703508679630f5f14d4cb44ce89eff489d7ff3fe599cc193940", - "0xafd44c143dbb94c71acc2a309c9c88b8847ef45d98479fccce9920db9b268e8e36f8db9f02ff4ee3cff01e548f719627", - "0xb2cf33d65d205e944b691292c2d9b0b124c9de546076dd80630742989f1ffd07102813c64d69ba2a902a928a08bce801", - "0xb9f295e9f9eca432b2d5c77d6316186027caca40a6d6713f41356497a507b6e8716fb471faf973aaa4e856983183c269", - "0xb3bd50c4b034473edce4b9be1171376a522899cb0c1a1ae7dc22dd2b52d20537cf4129797235084648ac4a3afc1fa854", - "0x8ef37683d7ca37c950ba4df72564888bedaf681931d942d0ea88ead5cc90f4cbef07985a3c55686a225f76f7d90e137d", - "0x82107855b330bc9d644129cebecf2efbfab90f81792c3928279f110250e727ce12790fd5117501c895057fa76a484fc0", - "0x816a5474c3b545fb0b58d3118cc3088a6d83aad790dbf93025ad8b94a2659cceba4fa6a6b994cb66603cc9aef683a5e3", - "0x8f633f9b31f3bb9b0b01ea1a8830f897ecd79c28f257a6417af6a5f64e6c78b66c586cf8d26586830bd007fb6279cd35", - "0xacb69d55a732b51693d4b11f7d14d21258d3a3af0936385a7ce61e9d7028a8fe0dd902bda09b33fb728bc8a1bc542035", - "0x8d099582ac1f46768c17bf5a39c13015cfe145958d7fc6ddfd2876ad3b1a55a383fbe940e797db2b2b3dc8a232f545dc", - "0x97a4dd488b70bf772348ececaca4cf87bc2875d3846f29fe6ef01190c5b030219b9e4f8137d49ea0cc50ca418024c488", - "0xb4d81148f93fa8ec0656bbfb5f9d96bbf5879fa533004a960faac9fd9f0fe541481935fdf1f9b5dd08dff38469ef81c5", - "0x8e9b2ae4fc57b817f9465610a77966caaff013229018f6c90fa695bd734cb713b78a345b2e9254b1aff87df58c1cd512", - "0x99eb7126e347c636e9a906e6bfdc7c8ca0c1d08580c08e6609889a5d515848c7ca0f32ab3a90c0e346f976a7883611f7", - "0x8ca87944aa3e398492b268bda0d97917f598bc0b28584aa629dfec1c3f5729d2874db422727d82219880577267641baa", - "0x88ab0e290dc9a6878d6b4e98891ff6bfc090e8f621d966493fcbe1336cc6848fcbb958d15abcfa77091d337da4e70e74", - "0x8956a2e1dc3ec5eb21f4f93a5e8f0600a06e409bb5ec54e062a1290dff9ce339b53fbbfc4d42b4eed21accea07b724d6", - "0x8d22220da9dc477af2bddb85c7073c742c4d43b7afee4761eba9346cadbcd522106ed8294281a7ef2e69883c28da0685", - "0x90dafd9a96db7e1d6bde424245305c94251d5d07e682198ae129cd77bd2907a86d34722cbde06683cc2ca67cebe54033", - "0xb5202e62cf8ea8e145b12394bd52fd09bda9145a5f78285b52fda4628c4e2ccfc2c208ecde4951bd0a59ac03fa8bc202", - "0x8959856793ba4acf680fb36438c9722da74d835a9fe25a08cf9e32d7800c890a8299c7d350141d2e6b9feceb2ebb636f", - "0xab0aa23c1cd2d095825a3456861871d298043b615ae03fcd9283f388f0deef3cc76899e7fde15899e3edf362b4b4657f", - "0x9603b333cc48fe39bea8d9824cfee6ac6c4e21668c162c196ecd1ff08ef4052ace96a785c36b8f7906fdcb6bc8802ddd", - "0x93bfecbc3c7cc03c563240e109850a74948f9fa078eb903b322368cda0b50888663a17953579578ba060b14dbf053024", - "0xb01f843b808cf7939a474de155a45462e159eb5044f00c6d77e0f7ec812720a3153209e971a971ccbf5ebee76ec4074f", - "0xb009e0567c3c75ed767247d06fa39049a4d95df3392d35a9808cb114accf934e78f765cd18a2290efef016f1918c7aeb", - "0xad35631df8331da3a12f059813dfa343d831225a392f9c7e641c7d23a6c1ad8df8e021201c9f6afb27c1575948d6bf68", - "0xa89c2a631d84128471c8ef3d24b6c35c97b4b9b5dad905c1a092fb9396ae0370e215a82308e13e90e7bb6ebcc455eb2a", - "0xb59c7f5fbfeb02f8f69e6cedef7ff104982551f842c890a14834f5e834b32de1148cf4b414a11809d53dd3f002b15d6a", - "0xaa6f267305b55fede2f3547bc751ba844ce189d0b4852022712b0aee474de54a257d4abcd95efe7854e33a912c774eba", - "0xafddd668f30cce70904577f49071432c49386ec27389f30a8223b5273b37e6de9db243aceb461a7dc8f1f231517463a9", - "0xb902a09da9157b3efa1d98f644371904397019d0c84915880628a646a3ad464a9d130fdc651315098179e11da643ad2e", - "0xb05f31957364b016c6f299ae4c62eede54cab8ea3871d49534828c8bdc6adbc6a04a708df268f50107d81d1384d983ae", - "0xb4c3f7284802e614ddf1f51640f29e7139aae891467d5f62778310372071793e56fbd770837b97d501191edd0da06572", - "0xb4eddb7c3775fb14fac7f63bb73b3cde0efa2f9a3b70e6a65d200765f6c4b466d3d76fcd4d329baee88e2aba183b8e69", - "0xa83e7dbae5a279f0cfd1c94e9849c58a3d4cecc6d6d44bb9b17508576ca347fca52c2c81371d946b11a09d4ed76ec846", - "0x8018ea17e2381c0233867670f9e04c8a47ace1207fdcf72dce61b6c280ba42d0a65f4b4e0b1070cc19c7bb00734974d9", - "0xaf90b541dfed22e181ff3ef4cf11f5e385fd215c1e99d988e4d247bc9dcee9f04f2182b961797c0bcc5f2aaa05c901a9", - "0xa37046e44cf35944e8b66df80c985b8a1aa7004a2fd0b81ac251638977d2ff1465f23f93ac0ce56296f88fdc591bbdd7", - "0xa735bd94d3be9d41fcd764ec0d8d7e732c9fc5038463f7728fd9d59321277e2c73a45990223bd571dab831545d46e7aa", - "0x94b32dcb86f5d7e83d70a5b48fe42c50f419be2f848f2d3d32ee78bf4181ab18077a7666eedb08607eece4de90f51a46", - "0xa7f0804cafbf513293485afc1b53117f0cbfaea10919e96d9e4eb06f0c96535e87065d93f3def1bbc42044dbb00eb523", - "0xaaaad1166d7f19f08583dd713275a71a856ab89312f84ca8078957664924bb31994b5c9a1210d0c41b085be4058ed52e", - "0xa1757aac9f64f953e68e680985a8d97c5aac8688b7d90f4db860166dd3d6119e8fca7d700a9530a2b9ba3932c5e74e33", - "0x98cada5db4a1430c272bfc1065fb685872e664ed200d84060ee9f797d0a00864f23943e0fb84ba122a961996a73dfb14", - "0xa5e609f716dc7729d1247f40f9368a2e4a15067e1dd6a231fece85eeefb7e7d4a5ac8918fb376debd79d95088750b2ca", - "0xb5365eb8caab8b1118619a626ff18ce6b2e717763f04f6fa8158cdca530c5779204efa440d088083f1a3685454aa0555", - "0xa6e01b8da5f008b3d09e51a5375d3c87c1da82dff337a212223e4d0cdb2d02576d59f4eef0652d6b5f2fc806d8c8149c", - "0xae310f613d81477d413d19084f117248ad756572c22a85b9e4c86b432e6c602c4a6db5edf2976e11f7353743d679e82a", - "0xa1f219c0b8e8bb8a9df2c6c030acbb9bbfa17ba3db0366f547da925a6abb74e1d7eb852bd5a34bae6ac61d033c37e9dc", - "0xa2087fa121c0cdd5ea495e911b4bc0e29f1d5c725aadfb497d84434d2291c350cdaa3dc8c85285f65a7d91b163789b7a", - "0x929c63c266da73d726435fa89d47041cfe39d4efa0edce7fc6eca43638740fbc82532fd44d24c7e7dd3a208536025027", - "0x91c1051dcc5f52ad89720a368dddd2621f470e184e746f5985908ba34e1d3e8078a32e47ab7132be780bea5277afecb0", - "0xae089b90ba99894d5a21016b1ea0b72a6e303d87e59fb0223f12e4bb92262e4d7e64bfdbdb71055d23344bc76e7794b2", - "0x8b69aa29a6970f9e66243494223bad07ac8f7a12845f60c19b1963e55a337171a67bdc27622153016fce9828473a3056", - "0x95ca6b08680f951f6f05fd0d180d5805d25caf7e5bda21c218c1344e661d0c723a4dfc2493642be153793c1b3b2caaa4", - "0xa4789dc0f2a07c794dab7708510d3c893d82ddbd1d7e7e4bbbeca7684d9e6f4520fb019b923a06c7efab0735f94aa471", - "0x93c4f57a3cf75085f5656b08040f4cd49c40f1aab6384a1def4c5c48a9fe4c03514f8e61aabe2cfa399ff1ccac06f869", - "0xb6c37f92c76a96b852cd41445aa46a9c371836dd40176cc92d06666f767695d2284a2780fdfd5efc34cf6b18bcfb5430", - "0x9113e4575e4b363479daa7203be662c13d7de2debcda1c142137228aeead2c1c9bc2d06d93a226302fa63cc75b7353ec", - "0xb70addeb5b842ac78c70272137f6a1cef6b1d3a551d3dd906d9a0e023c8f49f9b6a13029010f3309d0b4c8623a329faf", - "0xb976a5132b7eb42d5b759c2d06f87927ef66ecd6c94b1a08e4c9e02a4ce7feca3ac91f9479daa1f18da3d4a168c2ba77", - "0x8fdab795af64b16a7ddf3fad11ab7a85d10f4057cf7716784184960013baa54e7ba2050b0e036dc978ff8c9a25dc5832", - "0xb2c982ad13be67d5cdc1b8fac555d4d1ec5d25f84e58b0553a9836f8f9e1c37582d69ad52c086a880a08b4efcccd552e", - "0x810661d9075ae6942735215f2ab46d60763412e1f6334e4e00564b6e5f479fc48cf37225512abbccf249c0ca225fc935", - "0xa0c4bf00a20f19feff4004004f08231b4c6c86ac4ed57921eea28d7dea32034f3f4ab5b7ded7184f6c7ffbf5847232ad", - "0xb2bb5a9eea80bf067f3686a488529d9c2abd63fc9e1d4d921b1247ef86d40cd99e0a8b74f750e85c962af84e84e163a6", - "0x887ee493c96d50f619ba190ce23acddc5f31913e7a8f1895e6339d03794ecefd29da5f177d1d25bc8df8337ae963fc7b", - "0xb7966fb07029d040f2228efa2cfcd04341e4666c4cf0b653e6e5708631aa2dd0e8c2ac1a62b50c5a1219a2737b82f4f7", - "0x92234cfd6b07f210b82db868f585953aafbcbc9b07b02ded73ff57295104c6f44a16e2775ca7d7d8ee79babb20160626", - "0x8d3cd7f09c6fd1072bc326ff329e19d856e552ac2a9f20274bc9752527cd3274142aa2e32b65f285fb84bc3adaaea3cc", - "0x8caed1cb90d8cd61e7f66edc132672172f4fa315e594273bb0a7f58a75c30647ec7d52eda0394c86e6477fbc352f4fe8", - "0xae192194b09e9e17f35d8537f947b56f905766c31224e41c632c11cd73764d22496827859c72f4c1ab5fd73e26175a5d", - "0x8b7be56aac76d053969e46882d80a254e89f55c5ab434883cbafc634a2c882375898074a57bc24be3c7b2c56401a7842", - "0x98bc4a7a9b05ba19f6b85f3ee82b08bed0640fd7d24d4542eb7a7f7fde443e880bdb6f5499bd8cb64e1ddd7c5f529b19", - "0xa5a41eaa5e9c1d52b00d64ab72bc9def6b9d41972d80703e9bfe080199d4e476e8833a51079c6b0155b78c3ab195a2a7", - "0xa0823f6f66465fd9be3769c164183f8470c74e56af617f8afd99b742909d1a51f2e0f96a84397597afbd8eeaabb51996", - "0x801da41d47207bdd280cc4c4c9753a0f0e9d655e09e0be5f89aeed4ce875a904f3da952464399bf8efc2398940d5fba2", - "0xa719314085fd8c9beac4706c24875833d59a9a59b55bca5da339037c0a5fc03df46dbecb2b4efcfed67830942e3c4ea1", - "0xa75dde0a56070bb7e9237b144ea79f578d413a1cbbd1821cee04f14f533638b24f46d88a7001e92831843b37ed7a709f", - "0xa6b4ef8847a4b980146e1849e1d8ab38695635e0394ca074589f900ce41fa1bb255938dc5f37027523bac6a291779bef", - "0xb26d84dfd0b7bd60bcfdbea667350462a93dca8ff5a53d6fc226214dcb765fada0f39e446a1a87f18e4e4f4a7133155f", - "0xae7bd66cc0b72f14ac631ff329a5ca4958a80ba7597d6da049b4eb16ac3decde919ca5f6f9083e6e541b303fb336dc2f", - "0xa69306e6bfbbc10de0621cffb13c586e2fcfd1a80935e07c746c95651289aec99066126a6c33cb8eb93e87d843fc631f", - "0xa47e4815585865218d73c68ba47139568ea7ae23bfa863cb914a68454242dd79beaec760616b48eea74ceab6df2298dd", - "0xb2da3cfb07d0721cd226c9513e5f3ace98ed2bc0b198f6626b8d8582268e441fa839f5834f650e2db797655ca2afa013", - "0xb615d0819554f1a301a704d3fc4742bd259d04ad75d50bccee3a949b6226655f7d623301703506253cca464208a56232", - "0x85e06ed5797207f0e7ae85909e31776eb9dae8af2ec39cc7f6a42843d94ea1de8be2a3cdadfcbe779da59394d4ffeb45", - "0x8c3529475b5fdbc636ee21d763f5ec11b8cb040a592116fb609f8e89ca9f032b4fa158dd6e9ceab9aceb28e067419544", - "0xaccddb9c341f32be82b6fa2ef258802c9ae77cd8085c16ec6a5a83db4ab88255231b73a0e100c75b7369a330bfc82e78", - "0x93b8e4c6e7480948fa17444b59545a5b28538b8484a75ad6bc6044a1d2dbd76e7c44970757ca53188d951dc7347d6a37", - "0x90111721d68b29209f4dc4cfb2f75ab31d15c55701922e50a5d786fb01707ab53fcec08567cd366362c898df2d6e0e93", - "0xb60a349767df04bd15881c60be2e5cc5864d00075150d0be3ef8f6b778715bebca8be3be2aa9dbdc49f1a485aeb76cda", - "0xb8d5a967fdd3a9bcf89a774077db39ef72ca9316242f3e5f2a350202102d494b2952e4c22badecd56b72ba1eea25e64b", - "0x8499ebd860f31f44167183b29574447b37a7ee11efcc9e086d56e107b826b64646b1454f40f748ccac93883918c89a91", - "0x99c35e529782db30f7ccab7f31c225858cf2393571690b229ece838ec421a628f678854a1ddbd83fa57103ccebd92c7f", - "0x99817660d8b00cbe03ec363bcdc5a77885586c9e8da9e01a862aca0fc69bf900c09b4e929171bc6681681eae10450541", - "0x8055e130964c3c2ebd980d3dc327a40a416bcdbf29f480480a89a087677a1fb51c823b57392c1db72f4093597100b8d3", - "0x877eaddef845215f8e6f9ed24060c87e3ab6b1b8fbb8037d1a57e6a1e8ed34d00e64abb98d4bf75edb5c9788cbdccbef", - "0xb5432bbff60aeae47f2438b68b123196dfb4a65cc875b8e080501a4a44f834b739e121bec58d39ac36f908881e4aa8ab", - "0xb3c3f859b7d03ff269228c0f9a023b12e1231c73aba71ad1e6d86700b92adc28dfa3757c052bbc0ba2a1d11b7fda4643", - "0xab8a29f7519a465f394ef4a5b3d4924d5419ca1489e4c89455b66a63ac430c8c9d121d9d2e2ed8aa1964e02cd4ebac8c", - "0x866ae1f5c2a6e159f2e9106221402d84c059f40d166fab355d970773189241cd5ee996540d7c6fc4faf6f7bcff967dce", - "0x973a63939e8f1142a82b95e699853c1e78d6e05536782b9bb178c799b884f1bc60177163a79a9d200b5ff4628beeb9e7", - "0xa5fc84798d3e2d7632e91673e89e968f5a67b7c8bb557ea467650d6e05e7fe370e18d9f2bdd44c244978295cf312dc27", - "0xb328fe036bcd0645b0e6a15e79d1dd8a4e2eda128401a4e0a213d9f92d07c88201416fc76193bb5b1fe4cb4203bab194", - "0x99239606b3725695a570ae9b6fb0fb0a34ad2f468460031cfa87aa09a0d555ff606ff204be42c1596c4b3b9e124b8bd6", - "0xaf3432337ca9d6cce3574e23e5b7e4aa8eda11d306dc612918e970cc7e5c756836605a3391f090a630bac0e2c6c42e61", - "0x8a545b3cb962ce5f494f2de3301de99286c4d551eaa93a9a1d6fef86647321834c95bf754c62ec6c77116a21494f380d", - "0x8f9b8ea4c25469c93556f1d91be583a5f0531ac828449b793ba03c0a841c9c73f251f49dd05cbb415f5d26e6f6802c99", - "0xa87199e33628eeffd3aff114e81f53dd54fba61ba9a9a4d7efdbff64503f25bc418969ab76ef1cf9016dd344d556bb29", - "0xa2fda05a566480602274d7ffcaefdd9e94171286e307581142974f57e1db1fa21c30be9e3c1ac4c9f2b167f92e7c7768", - "0xa6235d6a23304b5c797efb2b476ed02cb0f93b6021a719ae5389eb1e1d032944ae4d69aec2f29fcd6cbc71a6d789a3ba", - "0xa7f4a73215f7e99e2182c6157dd0f22e71b288e696a8cff2450689a3998f540cfb82f16b143e90add01b386cb60d8a33", - "0x922d8f9cd55423f5f6a60d26de2f8a396ac4070a6e2dc956e50c2a911906aa364d4718aea29c5b61c12603534e331e7e", - "0x96d7fdf5465f028fc28f21fbfe14c2db2061197baf26849e6a0989a4ea7d5e09ab49a15ba43a5377b9354d01e30ce860", - "0x8f94c4255a0fc1bd0fa60e8178c17f2a8e927cac7941c5547d2f8f539e7c6ed0653cab07e9fb1f2c56cdd03bb876512a", - "0x95984c10a2917bfa6647ebce69bf5252d9e72d9d15921f79b2c6d7c15ee61342b4fb8a6d34838e07132b904f024ded04", - "0x93e65e765a574277d3a4d1d08ca2f2ff46e9921a7806ca8ca3d8055f22d6507744a649db7c78117d9168a1cbdb3bbc61", - "0x8d453b7364662dc6f36faf099aa7cbbe61151d79da7e432deba7c3ed8775cfe51eaf1ba7789779713829dde6828e189a", - "0xacffa3ee6c75160286090162df0a32a123afb1f9b21e17fd8b808c2c4d51a4270cab18fba06c91ef9d22e98a8dc26cdd", - "0xa5597cc458186efa1b3545a3926f6ecaaa6664784190e50eed1feac8de56631bee645c3bac1589fa9d0e85feb2be79d4", - "0x87ba9a898df9dfa7dabc4ab7b28450e4daf6013340e329408d1a305de959415ab7315251bad40511f917dfc43974e5f0", - "0xa598778cf01d6eef2c6aabc2678e1b5194ee8a284ebd18a2a51a3c28a64110d5117bcbf68869147934e600572a9e4c8a", - "0x84c69a4ad95861d48709f93ade5ac3800f811b177feb852ebcd056e35f5af5201f1d8a34ab318da8fe214812d0a7d964", - "0x9638a237e4aed623d80980d91eda45e24ebf48c57a25e389c57bd5f62fa6ffa7ca3fb7ae9887faf46d3e1288af2c153b", - "0x800f975721a942a4b259d913f25404d5b7b4c5bf14d1d7e30eee106a49cb833b92058dab851a32ee41faf4ef9cb0dea4", - "0xb9127a34a59fed9b5b56b6d912a29b0c7d3cb9581afc9bd174fc308b86fdb076f7d436f2abc8f61cef04c4e80cd47f59", - "0x8004eda83f3263a1ccfc8617bc4f76305325c405160fb4f8efeff0662d605e98ba2510155c74840b6fe4323704e903c4", - "0xaa857b771660d6799ff03ccad1ab8479e7f585a1624260418fc66dc3e2b8730cfa491d9e249505141103f9c52f935463", - "0x98b21083942400f34cde9adbe1977dee45ba52743dc54d99404ad9da5d48691ddea4946f08470a2faad347e9535690c7", - "0xa4b766b2faec600a6305d9b2f7317b46f425442da0dc407321fc5a63d4571c26336d2bccedf61097f0172ec90fb01f5f", - "0xb9736619578276f43583de1e4ed8632322ea8a351f3e1506c5977b5031d1c8ad0646fb464010e97c4ddb30499ddc3fb0", - "0x973444ffaff75f84c17f9a4f294a13affd10e2bceed6b4b327e4a32c07595ff891b887a9f1af34d19766d8e6cb42bfd1", - "0xb09ce4964278eff81a976fbc552488cb84fc4a102f004c87179cb912f49904d1e785ecaf5d184522a58e9035875440ef", - "0xb80c2aa3d0e52b4d8b02c0b706e54b70c3dbca80e5e5c6a354976721166ea0ca9f59c490b3e74272ef669179f53cb50d", - "0x8e52fa5096ff960c0d7da1aa4bce80e89527cdc3883eba0c21cb9a531088b9d027aa22e210d58cf7cbc82f1ec71eb44f", - "0x969f85db95f455b03114e4d3dc1f62a58996d19036513e56bee795d57bf4ed18da555722cd77a4f6e6c1a8e5efe2f5d7", - "0xab84b29b04a117e53caea394a9b452338364c45a0c4444e72c44132a71820b96a6754828e7c8b52282ad8dca612d7b6a", - "0x83e97e9ab3d9e453a139c9e856392f4cef3ec1c43bce0a879b49b27a0ce16f9c69063fd8e0debbe8fabafc0621bc200c", - "0x8c138ebdf3914a50be41be8aa8e2530088fb38af087fa5e873b58b4df8e8fd560e8090c7a337a5e36ef65566409ad8f3", - "0xa56da9db2f053516a2141c1a8ed368ae278ab33a572122450249056857376d1dffc76d1b34daf89c86b6fe1ead812a0c", - "0xa3233ea249f07531f5bc6e94e08cea085fd2b2765636d75ff5851f224f41a63085510db26f3419b031eb6b5143735914", - "0xb034bb6767ce818371c719b84066d3583087979ba405d8fbb2090b824633241e1c001b0cb0a7856b1af7a70e9a7b397e", - "0x8722803fe88877d14a4716e59b070dd2c5956bb66b7038f6b331b650e0c31230c8639c0d87ddc3c21efc005d74a4b5cc", - "0x8afe664cb202aacf3bd4810ebf820c2179c11c997f8c396692a93656aa249a0df01207c680157e851a30330a73e386b9", - "0xa999e86319395351d2b73ff3820f49c6516285e459224f82174df57deb3c4d11822fd92cbbed4fc5a0a977d01d241b19", - "0x9619408e1b58b6610d746b058d7b336d178e850065ba73906e08e748651e852f5e3aab17dcadcb47cc21ff61d1f02fcf", - "0x947cf9c2ed3417cd53ea498d3f8ae891efe1f1b5cd777e64cec05aba3d97526b8322b4558749f2d8a8f17836fb6e07aa", - "0xaec2fdae2009fda6852decb6f2ff24e4f8d8ca67c59f92f4b0cf7184be72602f23753ed781cf04495c3c72c5d1056ffe", - "0x8dba3d8c09df49fbfc9506f7a71579348c51c6024430121d1c181cad7c9f7e5e9313c1d151d46d4aa85fb0f68dd45573", - "0xb6334cb2580ae33720ebf91bb616294532a1d1640568745dcda756a3a096786e004c6375728a9c2c0fb320441e7d297a", - "0x9429224c1205d5ecd115c052b701c84c390f4e3915275bb8ce6504e08c2e9b4dd67b764dd2ea99f317b4c714f345b6ff", - "0xabe421db293f0e425cfd1b806686bdfd8fdbac67a33f4490a2dc601e0ddbf69899aa9a119360dad75de78c8c688ca08b", - "0x95c78bffed9ae3fff0f12754e2bd66eb6a9b6d66a9b7faaeb7a1c112015347374c9fe6ce14bf588f8b06a78e9a98f44c", - "0xac08f8b96b52c77d6b48999a32b337c5ad377adf197cda18dbdf6e2a50260b4ee23ca6b983f95e33f639363e11229ee4", - "0x911a0e85815b3b9f3ba417da064f760e84af94712184faeb9957ddd2991dee71c3f17e82a1a8fbeec192b0d73f0ebce7", - "0xaa640bd5cb9f050568a0ad37168f53b2f2b13a91e12b6980ca47ae40289cf14b5b89ddd0b4ca452ce9b1629da0ce4b5d", - "0x907486f31b4ecea0125c1827007ea0ecb1c55cadb638e65adc9810ca331e82bb2fd87e3064045f8d2c5d93dc6c2f5368", - "0x8cbfaf4ce0bbbf89208c980ff8b7bc8f3cfef90f0fe910f463cb1c0f8e17cce18db120142d267045a00ba6b5368f0dd3", - "0x9286f08f4e315df470d4759dec6c9f8eacef345fc0c0b533ad487bb6cfefa8c6c3821a22265c9e77d34170e0bc0d078b", - "0x94a3c088bc1a7301579a092b8ece2cefc9633671bc941904488115cd5cb01bd0e1d2deef7bdccb44553fd123201a7a53", - "0x8f3d0114fbf85e4828f34abb6d6fddfa12789d7029d9f1bb5e28bc161c37509afdab16c32c90ec346bc6a64a0b75726f", - "0xa8ed2d774414e590ec49cb9a3a726fafd674e9595dd8a1678484f2897d6ea0eea1a2ee8525afac097b1f35e5f8b16077", - "0x9878789ff33b11527355a317343f34f70c7c1aa9dc1eca16ca4a21e2e15960be8a050ec616ffb97c76d756ce4bce2e90", - "0x854e47719dae1fe5673cacf583935122139cf71a1e7936cf23e4384fbf546d48e9a7f6b65c3b7bf60028e5aa1234ba85", - "0xaf74bdda2c6772fe9a02d1b95e437787effad834c91c8174720cc6e2ea1f1f6c32a9d73094fc494c0d03eef60b1a0f05", - "0x80a3e22139029b8be32cb167d3bc9e62d16ca446a588b644e53b5846d9d8b7ab1ad921057d99179e41515df22470fb26", - "0x86c393afd9bd3c7f42008bba5fe433ec66c790ebd7aa15d4aeaf9bb39a42af3cfaf8c677f3580932bbd7ada47f406c8c", - "0x90433c95c9bb86a2c2ddcf10adccb521532ebd93db9e072671a4220f00df014e20cd9ce70c4397567a439b24893808dc", - "0x95b2c170f08c51d187270ddc4f619300b5f079bbc89dbca0656eae23eecc6339bf27fa5bf5fd0f5565d4021105e967d2", - "0x8e5eced897e2535199951d4cff8383be81703bca3818837333dd41a130aa8760156af60426ceadb436f5dea32af2814c", - "0xa254a460ebefbe91d6e32394e1c8f9075f3e7a2bb078430ac6922ab14d795b7f2df1397cb8062e667d809b506b0e28d4", - "0xac2062e8ca7b1c6afb68af0ebab31aebd56fc0a0f949ef4ea3e36baf148681619b7a908facf962441905782d26ecbdb5", - "0x8b96af45b283b3d7ffeec0a7585fc6b077ea5fd9e208e18e9f8997221b303ab0ce3b5bafa516666591f412109ce71aa5", - "0xafd73baada5a27e4fa3659f70083bf728d4dc5c882540638f85ea53bf2b1a45ddf50abc2458c79f91fb36d13998c7604", - "0xa5d2fff226e80cb2e9f456099812293333d6be31dd1899546e3ad0cd72b2a8bcb45ec5986e20faa77c2564b93983210c", - "0xa8c9b8de303328fbdaccf60f4de439cf28f5360cf4104581dc2d126bc2e706f49b7281723487ff0eaf92b4cc684bc167", - "0xa5d0d5849102bf1451f40e8261cb71fc57a49e032773cb6cd7b137f71ee32438d9e958077ffafce080a116ccc788a2d4", - "0x80716596f502d1c727d5d2f1469ce35f15e2dbd048d2713aa4975ee757d09c38d20665326bd63303cfe7e820b6de393d", - "0x97baf29b20f3719323cc1d5de23eaa4899dc4f4e58f6c356ec4c3ad3896a89317c612d74e0d3ab623fe73370c5972e2f", - "0xb58bdc9aa5061bf6e5add99a7443d7a8c7ba8f6875b8667d1acbe96fc3ecafbdcc2b4010cb6970a3b849fff84660e588", - "0xb6be68728776d30c8541d743b05a9affc191ad64918fdbd991d2ddd4b32b975c4d3377f9242defef3805c0bfb80fbac7", - "0xb0cddace33333b8a358acad84b9c83382f0569d3854b4b34450fd6f757d63c5bdab090e330b0f86e578f22c934d09c36", - "0x854bd205d6051b87f9914c8c2494075d7620e3d61421cc80f06b13cea64fd1e16c62c01f107a5987d10b8a95a8416ad9", - "0x80351254a353132300ba73a3d23a966f4d10ce9bf6eae82aedb6cdc30d71f9d08a9dd73cb6441e02a7b2ad93ad43159c", - "0x937aae24fb1b636929453fc308f23326b74c810f5755d9a0290652c9c2932ad52cc272b1c83bd3d758ef7da257897eae", - "0xb84d51ef758058d5694ffeac6d8ce70cef8d680a7902f867269c33717f55dd2e57b25347841d3c0872ae5f0d64f64281", - "0xa4b31bb7c878d5585193535b51f04135108134eff860f4eac941053155f053d8f85ff47f16268a986b2853480a6e75e6", - "0x93543f0828835186a4af1c27bdf97b5dd72b6dfa91b4bf5e759ff5327eaf93b0cb55d9797149e465a6b842c02635ffe5", - "0xafdac9e07652bf1668183664f1dd6818ef5109ee9b91827b3d7d5970f6a03e716adcc191e3e78b0c474442a18ad3fc65", - "0x9314077b965aa2977636ae914d4a2d3ce192641a976ffa1624c116828668edbfbe5a09e3a81cb3eed0694566c62a9757", - "0xb395ddcf5082de6e3536825a1c352802c557b3a5118b25c29f4c4e3565ecaaf4bdd543a3794d05156f91fc4ceadc0a11", - "0xb71f774aad394c36609b8730e5be244aaebfff22e0e849acc7ee9d33bedc3ec2e787e0b8b2ffe535560fcd9e15a0897e", - "0x92e9409fa430f943a49bce3371b35ac2efb5bc09c88f70ff7120f5e7da3258a4387dfc45c8b127f2ef2668679aeb314e", - "0x8ef55bef7b71952f05e20864b10f62be45c46e2dca0ef880a092d11069b8a4aa05f2e0251726aca1d5933d7dea98f3f8", - "0xaad3fba9e09fae885cdeef45dfafa901419f5156fb673818f92a4acc59d0e2e9870b025e711de590a63fd481164f3aa8", - "0xb444d52af545dd3a2d3dd94e6613816b154afea0c42b96468aceb0c721395de89e53e81a25db857ca2e692dcb24ba971", - "0x88b279fe173007e64fe58f2c4adba68a1f538dbd3d32d175aa0d026bbb05b72a0c9f5d02b8201a94adb75fe01f6aa8b2", - "0x88494cea4260741c198640a079e584cabfea9fcfb8bcf2520c9becd2419cde469b79021e5578a00d0f7dbc25844d2683", - "0x94f3cce58837c76584b26426b9abdb45f05fee34dd9e5914b6eae08e78b7262ed51c4317031dab1ad716f28b287f9fc2", - "0xb8c7ed564f54df01c0fbd5a0c741beed8183ce0d7842dc3a862a1b335de518810077314aa9d6054bb939663362f496da", - "0x81c153320d85210394d48340619d5eb41304daea65e927266f0262c8a7598321aba82ad6c3f78e5104db2afd2823baca", - "0xab6695a8d48a179e9cd32f205608359cf8f6a9aead016252a35b74287836aa395e76572f21a3839bec6a244aa49573e5", - "0x920ed571539b3002a9cd358095b8360400e7304e9a0717cc8c85ab4a0514a8ad3b9bf5c30cb997647066f93a7e683da9", - "0xa7ec7c194d1e5103bc976e072bf1732d9cb995984d9a8c70a8ee55ce23007f21b8549ad693f118aa974f693ed6da0291", - "0x87a042d6e40c2951a68afc3ccf9646baf031286377f37f6ac47e37a0ec04d5ac69043757d7dff7959e7cd57742017a8d", - "0xb9f054dd8117dd41b6e5b9d3af32ee4a9eebef8e4a5c6daa9b99c30a9024eabeae850ab90dbdb188ca32fd31fd071445", - "0xa8386da875799a84dc519af010eaf47cdbc4a511fe7e0808da844a95a3569ce94054efd32a4d3a371f6aba72c5993902", - "0x8b3343a7cf4ffb261d5f2dbd217fb43590e00feac82510bdf73b34595b10ee51acae878a09efebc5a597465777ef4c05", - "0x8312a5f1ea4f9e93578e0f50169286e97884a5ed17f1780275ab2b36f0a8aa1ab2e45c1de4c8bce87e99e3896af1fa45", - "0xb461198cb7572ac04c484a9454954e157bdd4db457816698b7290f93a10268d75a7e1211e757c6190df6144bbb605d91", - "0x9139764a099580d6f1d462c8bf7d339c537167be92c780e76acb6e638f94d3c54b40ed0892843f6532366861e85a515a", - "0x8bb70acb3c9e041b4fc20e92ba0f3f28f0d5c677bcb017af26f9171e07d28c3c0729bef72457231e3512f909455a13a2", - "0x93301a18e5064c55fcfe8e860fab72da1b89a824ca77c8932023b7c79e4a51df93a89665d308a8d3aa145e46ebe6a0ad", - "0xae3bca496fbd70ce44f916e2db875b2ce2e1ded84edd2cebc0503bdfdec40ec30e1d9afb4eb58c8fa23f7b44e71d88f8", - "0x93cb3a918c95c5d973c0cb7621b66081ed81fba109b09a5e71e81ca01ec6a8bb5657410fdec453585309ef5bf10d6263", - "0x95a50b9b85bb0fc8ff6d5f800d683f0f645e7c2404f7f63228a15b95ce85a1f8100e2e56c0acee19c36ed3346f190e87", - "0x816cc4d9337461caca888809b746ab3713054f5b0eac823b795a1a9de9417c58e32a9f020fef807908fa530cbf35dee8", - "0xa9c2890c2dd0d5d7aedc4cca7f92764086c50f92f0efd2642c59920d807086031bfe2d3ba574318db236c61a8f5f69c2", - "0xad0d5c8c80bddfe14bdaf507da96dc01dc9941aecc8ad3b64513d0a00d67c3f4b4659defb6839b8b18d8775e5344c107", - "0x9047c9fad6ef452e0219e58e52c686b620e2eb769571021e3524bd7eac504f03b84834b16b849d42b3d75c601fd36bb7", - "0xa04dd988fed91fb09cb747a3ac84efe639d7d355524cd7dee5477ecbcdec44d8ac1cec2c181755dcfdb77e9594fb3c5b", - "0xb0ea0c725debd1cec496ced9ce48f456f19af36e8b027094bf38fa37de9b9b2d10282363ea211a93a34a0a5387cace5d", - "0xb5fc46e2bb3e4653ea5e6884dcb3c14e401a6005685ee5a3983644b5b92300b7066289159923118df4332aac52045b8c", - "0x841fc5b26b23226e725e29802da86b35e4f5e3babc8b394f74e30fd5dec6d3840b19a9a096625ce79a4f1edae6369700", - "0x8fd2bbbeea452451def3659bbe0ceb396120ebe8f81eee1ea848691614422c81d7c3e6a7a38032b4120b25c5ffa8f0c2", - "0x9131ce3d25c3d418f50c0ab99e229d4190027ee162b8ba7c6670420ea821831dec1294ac00d66c50fac61c275a9e2c71", - "0x99ec6eafe0eb869d128158cee97b984fb589e1af07699247946e4a85db772289dff3084d224a6f208005c342f32bbd73", - "0xac100fbbe7c2bf00cc56fcd5aa1f27181f82c150c53bbb1e15d2c18a51ed13dcfa7bccab85821b8ddddf493603e38809", - "0xaffd73a458d70c0d9d221e0c2da4348fed731f6b34c0b3e2d5711ba432e85a1ec92e40b83b246a9031b61f5bc824be47", - "0x8ed30ed817816a817e9e07374ef1f94405a7e22dd0096aeaae54504382fc50e7d07b4f1186c1792fc25ea442cd7edc6b", - "0xa52370cfe99a35fa1405aeca9f922ad8d31905e41f390e514ea8d22ee66469637d6c2d4d3a7ee350d59af019ae5a10a4", - "0x8d0b439741c57b82c8e4b994cf3956b5aeaee048b17e0a1edb98253a8d7256f436d8b2f36b7e12504132dbf91f3376b1", - "0x8caac7e1a4486c35109cff63557a0f77d0e4ca94de0817e100678098a72b3787a1c5afc7244991cebcd1f468e18d91d4", - "0xa729a8e64b7405db5ebfb478bb83b51741569331b88de80680e9e283cc8299ba0de07fcf252127750f507e273dc4c576", - "0xa30545a050dad030db5583c768a6e593a7d832145b669ad6c01235813da749d38094a46ac3b965700230b8deacd91f82", - "0x9207e059a9d696c46fa95bd0925983cd8e42aefd6b3fb9d5f05420a413cbc9e7c91213648554228f76f2dd757bde0492", - "0xa83fa862ae3a8d98c1e854a8b17181c1025f4f445fbc3af265dc99e44bbd74cfa5cc25497fb63ee9a7e1f4a624c3202c", - "0x84cdfc490343b3f26b5ad9e1d4dcf2a2d373e05eb9e9c36b6b7b5de1ce29fda51383761a47dbd96deca593a441ccb28e", - "0x881a1aa0c60bb0284a58b0a44d3f9ca914d6d8fa1437315b9ad2a4351c4da3ee3e01068aa128284a8926787ea2a618d1", - "0xaace78e497b32fbff4df81b1b2de69dbc650645e790953d543282cb8d004a59caf17d9d385673a146a9be70bf08a2279", - "0xaa2da4760f1261615bffd1c3771c506965c17e6c8270c0f7c636d90428c0054e092247c3373eca2fb858211fdb17f143", - "0xacb79f291b19e0aa8edb4c4476a172834009c57e0dcc544c7ce95084488c3ad0c63ffd51c2b48855e429b6e1a9555433", - "0x814b58773a18d50a716c40317f8b80362b6c746a531776a9251c831d34fb63e9473197c899c0277838668babc4aa0ecb", - "0xb1f69522b0f7657d78bd1ee3020bcce3447116bf62c146d20684537d36cafb5a7a1531b86932b51a70e6d3ce0808a17e", - "0x8549712c251ef382f7abe5798534f8c8394aa8bcecdca9e7aa1a688dc19dc689dcd017a78b118f3bd585673514832fe4", - "0x912a04463e3240e0293cfc5234842a88513ff930c47bd6b60f22d6bc2d8404e10270d46bf6900fee338d8ac873ebb771", - "0xa327cb7c3fada842e5dd05c2eeedd6fcd8cf2bfb2f90c71c6a8819fb5783c97dd01bd2169018312d33078b2bc57e19f7", - "0xb4794f71d3eceed331024a4cee246cc427a31859c257e0287f5a3507bfbd4d3486cb7781c5c9c5537af3488d389fe03e", - "0x82ffcb418d354ed01688e2e8373a8db07197a2de702272a9f589aed08468eab0c8f14e6d0b3146e2eb8908e40e8389c5", - "0x910b73421298f1315257f19d0dfd47e79d7d2a98310fb293f704e387a4dc84909657f0f236b70b309910271b2f2b5d46", - "0xa15466397302ea22f240eb7316e14d88376677b060c0b0ae9a1c936eb8c62af8530732fc2359cfd64a339a1c564f749b", - "0xa8091975a0d94cdc82fbaff8091d5230a70d6ea461532050abbdfee324c0743d14445cfe6efe6959c89a7c844feaa435", - "0xa677d1af454c7b7731840326589a22c9e81efbbf2baf3fdeaf8ea3f263a522584fbca4405032c4cdf4a2a6109344dfc8", - "0x894e6ffa897b6e0b37237e6587a42bbc7f2dd34fb09c2e8ac79e2b25b18180e158c6dc2dd26761dba0cfed1fb4eb4080", - "0x928d31b87f4fe8fe599d2c9889b0ff837910427ba9132d2fba311685635458041321ae178a6331ed0c398efe9d7912f0", - "0xafc1c4a31f0db24b53ee71946c3c1e1a0884bd46f66b063a238e6b65f4e8a675faa844e4270892035ef0dae1b1442aa0", - "0xa294fcb23d87cf5b1e4237d478cac82ba570649d425b43b1e4feead6da1f031e3af0e4df115ca46689b9315268c92336", - "0x85d12fd4a8fcfd0d61cbf09b22a9325f0b3f41fb5eb4285b327384c9056b05422d535f74d7dc804fb4bab8fb53d556bd", - "0x91b107d9b0ea65c48128e09072acd7c5949a02dd2a68a42ff1d63cf528666966f221005c2e5ca0a4f85df28459cdede6", - "0x89aa5dc255c910f439732fcd4e21341707e8dd6689c67c60551a8b6685bd3547e3f47db4df9dfadd212405f644c4440b", - "0x8c307d6b827fa1adcf0843537f12121d68087d686e9cc283a3907b9f9f36b7b4d05625c33dab2b8e206c7f5aabd0c1e5", - "0x843f48dadf8523d2b4b0db4e01f3c0ea721a54d821098b578fcaa6433e8557cadfea50d16e85133fa78f044a3e8c1e5b", - "0x9942eb8bd88a8afa9c0e3154b3c16554428309624169f66606bfb2814e8bac1c93825780cf68607f3e7cffe7bf9be737", - "0xb7edb0c7637a5beb2332f2ae242ba4732837f9da0a83f00f9e9a77cf35516e6236eb013133ddc2f958ea09218fe260d3", - "0x9655fe4910bc1e0208afbcf0ff977a2e23faded393671218fba0d9927a70d76514a0c45d473a97ecb00cf9031b9d527c", - "0x8434bc8b4c5839d9e4404ff17865ded8dd76af56ef2a24ea194c579d41b40ed3450c4e7d52219807db93e8e6f001f8da", - "0xb6c6d844860353dab49818bed2c80536dbc932425fdaa29915405324a6368277cf94d5f4ab45ea074072fc593318edff", - "0xb2887e04047660aa5c83aad3fa29b79c5555dd4d0628832c84ba7bf1f8619df4c9591fcde122c174de16ca7e5a95d5e3", - "0x953ba5221360444b32911c8b24689078df3fbf58b53f3eec90923f53a22c0fc934db04dd9294e9ec724056076229cf42", - "0x926917529157063e4aade647990577394c34075d1cb682da1acf600639d53a350b33df6a569d5ebb753687374b86b227", - "0xb37894a918d6354dd28f850d723c1c5b839f2456e2a220f64ecadac88ae5c9e9cf9ab64b53aac7d77bf3c6dfa09632dc", - "0xb9d28148c2c15d50d1d13153071d1f6e83c7bb5cb5614adf3eb9edede6f707a36c0fa0eadb6a6135ead3c605dfb75bd1", - "0x9738d73ea0b9154ed38da9e6bd3a741be789ea882d909af93e58aa097edf0df534849f3b1ba03099a61ceb6a11f34c4d", - "0xafabbecbbf73705851382902ec5f1da88b84a06b3abfb4df8d33df6a60993867f853d0d9bd324d49a808503615c7858a", - "0xa9e395ddd855b12c87ba8fdb0ea93c5bd045e4f6f57611b27a2ee1b8129efe111e484abc27cb256ed9dcace58975d311", - "0xb501c2f3d8898934e45e456d36a8a5b0258aeea6ff7ac46f951f36da1ec01bd6d0914c4d83305eb517545f1f35e033cc", - "0x86f79688315241fe619b727b7f426dbd27bcc8f33aef043438c95c0751ada6f4cd0831b25ae3d53bcf61324d69ea01eb", - "0x83237e42fa773a4ccaa811489964f3fab100b9eea48c98bdef05fa119a61bde9efe7d0399369f87c775f4488120b4f2e", - "0xb89f437552cab77d0cd5f87aca52dd827fb6648c033351c00ab6d40ac0b1829b4fcdf8a7dad467d4408c691223987fbe", - "0x8e21061698cb1a233792976c2d8ab2eeb6e84925d59bb34434fff688be2b5b2973d737d9dda164bd407be852d48ef43f", - "0xb17a9e43aa4580f542e00c3212fbf974f1363f433c5502f034dfd5ed8c05ac88b901729d3b822bec391cca24cc9f5348", - "0xaac6d6cda3e207006c042a4d0823770632fc677e312255b4aff5ad1598dc1022cab871234ad3aa40b61dc033a5b0930b", - "0xb25e69f17b36a30dada96a39bc75c0d5b79d63e5088da62be9fcbddfd1230d11654890caa8206711d59836d6abbc3e03", - "0xaf59fe667dd9e7e4a9863c994fc4212de4714d01149a2072e97197f311be1f39e7ad3d472e446dcc439786bf21359ede", - "0x957952988f8c777516527b63e0c717fc637d89b0fd590bcb8c72d0e8a40901598930c5b2506ff7fea371c73a1b12a9be", - "0xa46becd9b541fc37d0857811062ca1c42c96181c7d285291aa48dc2f6d115fcff5f3dfdf4490d8c619da9b5ce7878440", - "0x87168fbd32c01a4e0be2b46fe58b74d6e6586e66bbb4a74ad94d5975ac09aa6fa48fd9d87f1919bd0d37b8ebe02c180c", - "0x895c4aa29de9601fc01298d54cfb62dd7b137e6f4f6c69b15dc3769778bfba5fc9cbd2fc57fd3fad78d6c5a3087f6576", - "0xb9cf19416228230319265557285f8da5b3ca503de586180f68cf055407d1588ecec2e13fc38817064425134f1c92b4d5", - "0x9302aaef005b22f7b41a0527b36d60801ff6e8aa26fe8be74685b5f3545f902012fcade71edca7aaa0560296dac5fca5", - "0xa0ccda9883027f6b29da1aaa359d8f2890ce1063492c875d34ff6bf2e7efea917e7369d0a2b35716e5afd68278e1a93a", - "0xa086ac36beeba9c0e5921f5a8afea87167f59670e72f98e788f72f4546af1e1b581b29fbdd9a83f24f44bd3ec14aee91", - "0x8be471bf799cab98edf179d0718c66bbc2507d3a4dac4b271c2799113ce65645082dc49b3a02a8c490e0ef69d7edbcb1", - "0x8a7f5b50a18baf9e9121e952b65979bda5f1c32e779117e21238fb9e7f49e15008d5c878581ac9660f6f79c73358934a", - "0xb3520a194d42b45cbab66388bee79aad895a7c2503b8d65e6483867036497d3e2e905d4d51f76871d0114ec13280d82f", - "0x8e6ca8342ec64f6dbe6523dc6d87c48065cd044ea45fa74b05fff548539fd2868eb6dd038d38d19c09d81d5a96364053", - "0xb126a0e8263a948ba8813bf5fb95d786ae7d1aa0069a63f3e847957822b5fe79a3a1afa0ce2318b9ba1025f229a92eb7", - "0x8e4461d6708cac53441a3d23ac4b5ff2b9a835b05008c26d7d9c0562a29403847cf760b7e9d0bcb24a6f498d2a8a9dd2", - "0xb280a761bab256dfe7a8d617863999e3b4255ddbdc11fe7fe5b3bb9633fc8f0cb4f28e594d3b5b0b649c8e7082c4666a", - "0xa3e3043bfd7461e38088ee6a165d2ca015de98350f1cb0efc8e39ed4fcdb12a717f0ede7fbf9dadb90496c47652cc0ce", - "0xa4c1f5b1b88ae3c397d171e64395afe0cd13c717677775a01dd0461d44a04ee30ec3da58a54c89a3ca77b19b5e51062c", - "0xa268638e0655b6d5a037061808619b9ae276bb883999d60c33a9f7f872c46d83d795d1f302b4820030c57604fa3686e7", - "0xac20176111c5c6db065668987227658c00a1572ce21fe15f25e62d816b56472c5d847dd9c781fb293c6d49cc33b1f98f", - "0xacc0e22d9b6b45c968c22fd16b4ece85e82a1b0ab72369bdd467857fee1a12b9635f5b339a9236cbd1acc791811d0e29", - "0xb56066e522bee1f31480ff8450f4d469ace8eb32730c55b7c9e8fa160070bdec618454e665b8cbc5483bc30b6cebbfb9", - "0x8c1772bdfacff85f174d35c36f2d2182ae7897ad5e06097511968bbb136b626c0c7e462b08a21aca70f8e456b0204bf8", - "0xb4de3cf4a064bf589be92513b8727df58f2da4cd891580ef79635ac8c195f15a6199327bb41864e2f614c8589b24f67e", - "0x8f3c534125613f2d17bf3e5b667c203cb3eab0dbca0638e222fe552fddf24783965aa111de844e8c3595304bfc41c33b", - "0x8e445b2711987fe0bf260521cb21a5b71db41f19396822059912743bf6ca146100c755c8b6e0e74f1bf2e34c03b19db9", - "0x87ff9adf319adb78c9393003b5bdda08421f95551d81b37520b413fe439e42acf82d47fa3b61476b53166bf4f8544f0e", - "0x83f3c00c55632e1937dcdc1857de4eccd072efa319b3953d737e1d37382b3cf8343d54a435588eb75aa05bf413b4caa0", - "0xb4d8ee1004bac0307030b8605a2e949ca2f8d237e9c1dcf1553bd1eb9b4156e2deb8c79331e84d2936ec5f1224b8b655", - "0x93b2812b6377622e67bf9a624898227b56ebe3c7a1d917487fc9e4941f735f83679f7ac137065eb4098ad1a4cfbc3892", - "0x81943d9eab6dcea8a120dde5356a0a665b1466709ebb18d1cbfa5f213a31819cb3cf2634e6d293b5b13caa158a9bb30b", - "0xa9042aae02efd4535681119e67a60211fc46851319eb389b42ebadcab1229c94199091fb1652beba3434f7b98c90785f", - "0x91db52b27fd9b1715df202106b373c4e63ce8ec7db8c818c9016ace5b08ef5f8c27e67f093395937ba4ce2f16edf9aef", - "0x83cb9b7b94bd6ead3ff2a7d40394f54612c9cb80c4e0adadffea39e301d1052305eb1fe0f7467268b5aba3b423a87246", - "0x8720fd6712a99d92dd3fdaae922743ab53fad50d183e119a59dae47cdac6fbea6064c732d02cb341eaea10723db048fa", - "0x8d40022c1254462a2ac2380a85381c370b1221e5a202d95c75bccba6d1e52972dd5585a1294a1e487bf6ae6651867167", - "0xb7bc06e08d8c72daba143627582f4b4f34cc2234b5cb5cd83536f2ef2e058631a3920468ea4d550aea01cad221d6a8a6", - "0xa6e1a6f70fba42d3b9ce5f04ffdcfca46fc94041840c0066a204030cf75ea9f9856113fea3a9f69ea0037d9a68e3a9d4", - "0x8b064c350083fce9a52da2e2e17bf44c4c9643d2d83667cbd9ad650bbeba55e2c408e746ccf693e56d08826e8a6d57fc", - "0x8d304a5405a0c0696917fcddc6795dd654567ca427f007d9b16be5de98febbf8692374e93f40822f63cf6f143c4d9499", - "0xb968db239efec353a44f20a7cf4c0d0fca4c4c2dc21e6cbb5d669e4fe624356a8341e1eec0955b70afb893f55e9a9e32", - "0x98971f745ce4ce5f1f398b1cd25d1697ada0cc7b329cee11d34b2d171e384b07aeb06ac7896c8283664a06d6dd82ec6b", - "0x881f5a20a80f728354fad9d0a32a79ffe0ba9bed644ed9d6a2d85444cda9821018159a3fa3d3d6b4fadbf6ea97e6aff6", - "0xb7c76cbb82919ec08cf0bd7430b868a74cb4021e43b5e291caa0495ca579798fab1b64855e2d301f3461dd4d153adeb6", - "0xb44c8c69b3df9b4e933fe6550982a6f76e18046e050229bd2456337e02efb75efa0dfe1b297ed9f5d7fa37fec69c8374", - "0xa5bd7781820ba857aee07e38406538b07ab5180317689a58676f77514747672dd525ea64512a0e4958896f8df85e9d4d", - "0xa8443d1dc91b4faa20a2626505b5b4ad49cc5c1fd7a240a0e65d12f52d31df1585ba52c21e604dcec65ec00b81ae21fe", - "0xa157ae42fc6302c54bcdd774e8b8bafc4f5d221717f7bf49668c620e47051b930dce262d55668e546272dd07ca7c8d3f", - "0x8732c10448b63e907ff95f53cd746f970c946fd84fcbfe4cf9ede63afbbfc66b293bbc7c470d691bbd149bb3c78bb351", - "0xa82192f4fd9a0c33489a0486b79d0f6c797c7eccb45f91f7f1e8e1dd1924ca9944b983951025b99ab5861d31841451fe", - "0x839efc6d199ddd43f34f6729b6b63f9ee05f18859bf8fd3f181fa71f4399a48bff7dde89b36e9dc1c572f1b9b6127cca", - "0x992ef084abe57adfd5eb65f880b411d5f4ed34c1aeb0d2cfac84fff4f92a9a855c521a965ba81b5eef2268e9a9e73048", - "0xa2518ab712fa652e6e0bd0840307ef3831094e9a18723fb8ec052adacbb87f488d33778c6ec3fd845003af62e75125d1", - "0xb630ac3c9e71b85dd9e9f2984bb5b762e8491d8edb99cad82c541faf5a22dd96f0fddb49d9a837b1955dea2d91284f28", - "0x8d886d1b7f818391b473deba4a9a01acce1fe2abe9152955e17ba39adc55400590c61582c4fef37a286e2151566576ed", - "0x884f100dc437639247f85e5d638fcc7583d21bf37a66ce11e05bfc12f5dbe78685b0e51b4594e10549c92bb980512e12", - "0x806d7bac2d24cfff6090ba9513698292d411cdea02976daa3c91c352b09f5a80a092cfa31304dcfcd9356eaf5164c81b", - "0x934ed65f8579ee458b9959295f69e4c7333775eb77084db69ad7096f07ad50ad88f65e31818b1942380f5b89e8d12f1b", - "0xaaf50ca5df249f0a7caf493334b6dca1700f34bd0c33fe8844fadd4afedbb87a09673426741ac7cbbb3bf4ab73f2d0f3", - "0xb2868642cfa0a4a8a2553691c2bef41dab9dff87a94d100eaa41645614ab4d0e839ec2f465cc998c50cd203f0c65df22", - "0xa326513112e0b46600d52be9aa04d8e47fe84e57b3b7263e2f3cf1a2c0e73269acb9636a99eb84417f3ae374c56e99b0", - "0x97b93efc047896ddf381e8a3003b9e1229c438cc93a6dbef174bb74be30fac47c2d7e7dc250830459bed61d950e9c924", - "0xb45e4f0a9806e44db75dbb80edc369be45f6e305352293bcae086f2193e3f55e6a75068de08d751151fdf9ebc6094fa1", - "0x87f2161c130e57e8b4bb15616e63fa1f20a1b44d3e1683967a285f0d4f0b810f9202e75af2efa9fc472687c007a163f7", - "0x8f6400a45666142752580a2dce55ef974f59235a209d32d2036c229c33a6189d51435b7ea184db36f765b0db574a9c52", - "0xa0ee079462805f91b2200417da4900227acde0d48c98e92c8011a05b01c9db78fc5c0157d15cb084b947a68588f146f4", - "0xab0612d9bb228b30366b48e8d6ae11026230695f6f0607c7fa7a6e427e520121ff0edea55d1f0880a7478c4a8060872d", - "0xad65dfde48f914de69f255bb58fa095a75afe9624fc8b7b586d23eb6cf34a4905e61186bc978e71ccb2b26b0381778a6", - "0x8c8a4847d138d221c0b6d3194879fd462fb42ed5bd99f34ebe5f5b1e1d7902903ec55e4b52c90217b8b6e65379f005a4", - "0xa41dca4449584353337aef1496b70e751502aeed9d51202de6d9723e155ca13be2d0db059748704653685a98eaa72a07", - "0xae40e5450fd994d1be245a7cd176a98dd26332b78da080159295f38802a7e7c9c17cc95da78d56558d84948cf48242cd", - "0x863878fda80ad64244b7493e3578908d4a804887ad1ad2c26f84404dcad69ea2851846ad2c6f2080e1ed64fe93bbec31", - "0xb262fb990535f162dc2b039057a1d744409a3f41dd4b70f93ff29ba41c264c11cb78a3579aad82f3fa2163b33a8ce0e1", - "0xa7f6eb552b9a1bb7c9cb50bc93d0dda4c7ecf2d4805535f10de0b6f2b3316688c5e19199d5c9ec2968e2d9e2bd0c6205", - "0xa50aa5869412dc7081c8d827299237910ecec3154587692548da73e71fa398ff035656972777950ba84e472f267ba475", - "0x924c3af750afc5dfad99d5f3ed3d6bdd359492cff81abcb6505696bb4c2b4664926cb1078a55851809f630e199955eb3", - "0xa1acffa31323ce6b9c2135fb9b5705664de8949f8235b4889803fbd1b27eb80eb3f6a81e5b7cc44e3a67b288b747cf2f", - "0x8dec9fd48db028c33c03d4d96c5eecea2b27201f2b33d22e08529e1ae06da89449fe260703ac7bb6d794be4c0c6ea432", - "0xaa6642922ccf912d60d678612fffe22ef4f77368a3c53a206c072ed07c024aa9dcde2df068c9821b4c12e5606cfe9be2", - "0xa16ddf02609038fcb9655031b1cb94afe30b801739e02a5743c6cd2f79b04b2524c2085ca32ec3a39df53de0280f555d", - "0xb067d48589e9d3428c6d6129e104c681e4af376a351f502840bbea6c3e11fcbfdf54dadf6f1729621720a75ff89786c3", - "0xb14a24079de311c729750bb4dd318590df1cd7ffc544a0a4b79432c9a2903d36a0d50ecd452b923730ade6d76a75c02c", - "0x97437bac649f70464ace93e9bec49659a7f01651bba762c4e626b5b6aa5746a3f0a8c55b555b1d0dc356d1e81f84c503", - "0xa6f4cb2ffc83564b1170e7a9a34460a58a4d6129bd514ff23371a9e38b7da6a214ac47f23181df104c1619c57dff8fe2", - "0x896d0f31dfc440cc6c8fde8831a2181f7257ffb73e1057fd39f1b7583ea35edf942ad67502cd895a1ad6091991eabc5e", - "0x9838007f920559af0de9c07e348939dfd9afe661b3c42053b4d9f11d79768cba268a2ee83bb07a655f8c970c0ee6844b", - "0xb41b8a47e3a19cadec18bff250068e1b543434ce94a414750852709cd603fc2e57cd9e840609890c8ff69217ea1f7593", - "0xa0fb4396646c0a2272059b5aeb95b513e84265b89e58c87d6103229f489e2e900f4414133ed2458ddf9528461cfa8342", - "0xae026cfa49babc1006a3e8905d6f237a56a3db9ddf7559b0e4de8d47d08c3f172bde117cdf28dfdfd7627bd47d6a3c85", - "0xa6a3f3e7006bc67290c0c40c1680bf9367982eb8aaf17ecb484a58c8e9c2a7c24932e2caa9aacc9b4fbf4c0abd087a46", - "0x9093e05bd814177a01a3b8d7b733db66294e1c688c56def6e1827c0f2d9a97cf202721641bf81fb837f8581ae68cb5ce", - "0x87feef4de24942044f47d193d4efc44e39a8c0f4042fba582f2491a063e3a4640cb81f69579b6f353b9208884a4f7ce6", - "0x975f9b94e78aac55bd4755f475e171e04f6fbddb6fd3d20a89a64a6346754a3ff64ecff8c04b612a1250e1d8d8a9e048", - "0x87cde4d0164922d654cf2dc08df009e923c62f1a2e3b905dfde30f958e9e4dd6070d9f889712acd6c658804f48f3edb1", - "0xae8e22e158dda90a185eec92602831b5d826e5a19aab8c6400dba38b024c7d31c4cf265eb7b206dd45834f020b3f53cd", - "0xa4475807adc28aa086e977b65bbd7c8512119318c89d2619ea03a6739a72c3fb90c9622451896c7113ad4d12a3004de6", - "0x97f1ae1e0d258a94532c7b73fa8ebdbbd53349a4d2d0a217fe56dfdd084dd879960bc6ff45ebb61b5dbf2054642800a4", - "0xb3c832bd3691332a658b0caaa7717db13f5b5df2b5776b38131ac334b5fd80d0b90b6993701e5d74d2b7f6b2fd1f6b9d", - "0xa4b6af590187eb1b2cb5ae2b8cffa45c5e76abdb37cec56fc9b07a457730f5af0706d9ce0a17da792bbece5056d05670", - "0x97b99a73a0e3145bf91f9dd611a67f894d608c954e9b8f5a4c77e07574064b3db47353eba8038062cebaad06a2500bab", - "0x8e5ca5a675de6e6d3916bd9ce5898bb379372afe3f310e70ff031bc8cc8fabfb7f3bfb784f409bb7eb06fdb4511ee477", - "0xaabbbee4da1f16b5bbe001c19debe04745932d36dfbbf023fbf1010a2b1d54eb92fa5e266ac1e9337e26e2ddba752f40", - "0xb13447c77496825f48e35c14f9b501c5056e6d5519f397a2580cea9a383a56a96994d88926aa681142fe2f1589c03185", - "0xb89c55db39ff0e73dde7435b61e8a4d3e10f51dd8096cbc7f678661962e6de3d16f2f17a0e729cc699234cb847f55378", - "0x82c36b7de53698a1bafbb311fefc6007fcefa47a806ebe33a4e7e0fc1c7b6b92a40a1860702cf9295a16c6b1433e3323", - "0x8daeec8c88543d09c494a15cc9a83c0b918d544311fd2a7d09e06cf39cdebfa0cfc0e8fc0e3b5954960b92332f98697c", - "0xb18e55a1a7ae16be3a453d2bfa7659a7ec2d283dd46bdc82decef6d3751eeafc4f86f2416a22955c7e750c0582d4f3eb", - "0xb50c743462e2915bf773848669e50a3bcdb5a9ac5f664e97eaccf568c7d64a6493d321be0225de16142ce82ce1e24f66", - "0xaf69c9643805fb860434424b1608aababc593aaebc6a75fc017f7f62bb2b1da932b0b9bd5e6dcbba328422dafc06efd8", - "0xb5947db4f809fd0d27af838b82eef8ab4fe78687a23ebc61c09c67eb7e8d0e6a310ecb907fd257859d5a2759a07c21cc", - "0x92c7960e163ca5bdf9196c7215102f8e9d88efc718843321c6e2a6170137b8ecec4ea5d5a5ce4c28012b6cdbd777dd01", - "0xb63f9509ed5e798add4db43b562e8f57df50d5844af6e5c7acf6c3b71637c0a2d2433f4a0627b944f0af584892208bb8", - "0x8ef28304a9bfe5220af6a9a6a942d2589606f5dc970d708ef18bc7ed08e433161020d36fb327c525398cd8ecb57002f9", - "0xb722e0410f896c4462d630a84a5a14e94289fc38ed6d513ca88a09005935cec334c480028efa1943c7a5e202ae8c8379", - "0xb56b6672b488e64d4dde43571f9ceaa7e61e336b0fd55bb769a57cd894a6300e724e5f88bad39a68bc307eb7406cb832", - "0x8bf493da411fd41502b61a47827731193652e6ce3810709e70869d9aae49e4b17a40437a7a0dcc0547dbac21f355c0da", - "0x9613b60a144c01f6a0e7d46ddde07402e2133a1fe005c049a56415ff90401765040b2fc55971d24b94c5fd69fec58941", - "0x85e2f02b291563d8eea3768cf6a4602c0ca36568ffcf3d93795d642044196ca6b0b28991ea5898e7974ee02831a0ec70", - "0xb08ef66703dd9ac46e0208487566fbf8d8654d08c00f03e46f112c204782ccc02a880a3f9dffd849088693cee33b7b6d", - "0xa0b19eeda6c71b0e83b1f95dffef4d370318bdea6ea31d0845695e6b48d5c428c3dbba1a0ded80964992c4a0695f12ee", - "0xb052642e5772d2ef6f49dd35c5e765c5f305006b2add3b4bee5909ca572161edf0e9c2bc3bc3bc7f56fd596360ef2201", - "0x8261af164c768fec80d63fca6cd07d1c0449e9ca665fe60c29babdbd8a2b20cf1f556a4b24cd7341712468a731c21b32", - "0x8a17016a1b2fc0fa0d9e3610ea80548fcf514e0a35e327f6b5f8069b425c0f0829af7e206013eab552be92b241be5ac5", - "0x8eea25c680172696f5600271761d27ef4c8cec9ab22f01f72b2c7c313a142fafaec39e6920b96fcace858883e02eff7a", - "0xb8e0c590106e125c5bca7e7a071cc408b93629da0d8d6381f1b73fbdf17024a0cf13f679f5203a99bbbcb664b4a94e88", - "0xb9943b29395258b7afdf1781cfaf131297a4f325540755df73401b2ec4a549f962952e9907413c39a95585c4aff38157", - "0x8286eab4a04f8113fb3f738a9bc9c2deaf3a22bf247151515568703da4efe6450ab3970f5c74e978a2db7e8d795331b7", - "0xa10cf383c8a7e3f0a0a5556b57532170ff46dabdcbb6a31c4617271634b99540aa575786c636d3809207cbf1d2f364d3", - "0xa5af7eb998140d01ba24baa0e8c71625aee6bd37db4c5ff607518f907892219ba8c9a03c326b273bfd7068232809b73c", - "0xaed5f461e38fccc8b3936f1328a9747efcbceb66312f6d6eddce57c59570852767159f1a7d9998f63342515fef4ba9bf", - "0xaec3e94b029aa692bfe2b8dbc6c3b0d132b504242e5ebe0cad79c065085e2fc05550e5cdaa2353892a40ff1a062dd9eb", - "0x87c23703960129396018d0347f5dd034abdbd57232b74195b6a29af34b6197b3cd63c60ac774d525add96ae54d5c0fb4", - "0x97964a7768216e1c84dece71ce9202cc64b6d483650aa6f6d67215f655f66cda14df0a0f251db55832c77bfd9b6316e2", - "0x8167aaf24c8a023d0aea16b8c24d993618b9d0c63619e11a28feab8f14952bafcb0918ed322cbc0ae1b2e1786071819b", - "0xb58318bd62852ffb712fc58f368c21b641dde7b3fa7d7269974c7a7b5b3e1641569fc7b5f32ca49de22f4f993506d92d", - "0xb172e7911d5cd3f53af388af847b928947c711185aebd3328f8e6ed1106c161ae0c1b67d3d9eb237e9e66eb0672edec0", - "0xa6834cf69b2c4433cf6e779bfbb736b12e73e71e149c38101d13dbacf6c5048db53994a6a039381df40bbd67de40fcd0", - "0x882604aa3bb19fffd6db744b5cf4a2431b157dac06d0617e0703684a118ca90b2d22a7758a1de7732a7144e68b11b7f7", - "0xaddc128ba52bf7553b9ba49eff42004d388a02c6b6e9809abe1c0d88f467e5ff6cb0c82a8fd901b80dfc9a001f7b9997", - "0xabf19604a3f0cffefa7a9ced81627f6aacb8d7267b52b825f25d813d9afa24af6d70da21450ed93eaff8b4d2a9b905a9", - "0xa3c67e7bf02dbca183d86924611a7149556ee17cb3469793624da496b6c25617a9071925dd02aab9cb028739cb79043d", - "0xb1cea4284a3ac4d5b1c6f0947c6ec8365b3281ed15495bf328a907a9a02cdd186e7cb1ef080385b3399df786855985a9", - "0xa6edb126314559e6129caf1111dc3c82ff914efce658b11f2c9b48081be1cf3f46bde482469d493379025a158d95ab1b", - "0x9843fd7dd424da1acc6f92f87fac364a8b0d4097d74b6b451386384966c85145d43fc6ecedf04271b0f963ac731fd93f", - "0x83852bedca03a97a2e63053cb102387866cbefe6707ebb6dae2d32a59c343079f1a863f299fd64d0ecbe024d0a1247d5", - "0xa570e645a0679ebc6f0ca03cc8f7367b03c3886f3d9c787992de7f3e93360a170d3ac9ae7720999c727a887b1dc762bb", - "0xad644c40555238f28844eed632c8972b63d2602098031d53b5599d1a874903e0d0c428e0ab12a209ea3fb31225578f1c", - "0xb64e9f92a14812ed31075f9fdd3324659a036ef2f293ef9ca6f6feb87d0c138e1ba74bc36a910afd22ff9b3c8ec7cfa5", - "0x8f2d75a86d517dafac09b65596f4b89c4a9c0a7003632407504153fa297c9e3228e236948a5d5224b8df49a087c8e0e3", - "0xb02d6ab9292ae336c8a74115f33765af2c9f62c331d70c087cf4c2979792bb3c2666f6699c017f8d4c6b378fd4bda86a", - "0xa923d660d2e55228b8bc74f87d966069bd77c34a776fa96f37b48539c85634482e514e2cb76cb8eb20efd85eb9c83fae", - "0x81d7ffb53090a6d512055ecfd582ca92805525a05654e39bb12653a6a8902a16e651ba7b687b36b8bea7186632c7e9e3", - "0x83e9b33e29b57ae53d9f72bd4622ff388252333b4fa32ad360a5b00f3ffc8813b9cb8a1361454d3bb7156c01b94b6a08", - "0xad7d6bffe4d67eb53b58daa3fc8a5a60790c54fa42226ae12847e94c6de3b4365b3be39855a4f6a5f12e4803cdaed96b", - "0xa7709fed85abbee5a2fa49c5238582ec565da08c132d4912821491985bf83b681eb4823634bfe826abd63a6c41a64ea7", - "0xb8fb6ed55741132a1053b6ca77bdf892e96b048488373ba4aa2f2225fae6d578724124eb6975e7518e2bf3d25d215763", - "0x85e0c53089529a09b5bce50f5760af6aeafef9395388aa4b6144ca59953169101783347ee46264ec0163713a25fe7c63", - "0x8f9e47a9c37b678e56c92b38d5b4dab05defc6b9c35b05e28431d54b1d69ac31878c82c1357d016f3e57ca07d82d9c16", - "0xa81f508136ee6ec9122c48584df51637f768ccfe8a0b812af02b122a0fafa9abcc24778bf54143abb79eccebbdde2aac", - "0x931a96d2257a4714d1ef20ac0704438481632647b993467e806b1acc4a381cc5a9dec257e63239ba285deb79f92122dd", - "0x99fb0ff747bcd44b512bf8a963b3183ce3f0e825a7b92ddd179253e65942a79494a515c0c0bc9345db136b774b0a76b0", - "0xa9dbb940b5f8ab92f2d85fc5999e982e3d990fe9df247cfc6f3a3f8934fb7b70e2d0362ba3a71edc5d0b039db2a5f705", - "0x99011a1e2670b1b142ec68b276ff6b38c1687eed310a79e2b902065bc798618c0cdee7b2009ad49623ed7ae0aa2b5219", - "0x9361e9f3aa859c07924c49f3d6e9b5d39a3df2fc1c10769202ec812955d7d3814c9e6982f4df3a8f3bdbfb4550cd1819", - "0xa8aa23f177ddc1e7a7856da3eac559791d8b3f188c0b3ae7021bcb35dfb72b0f043c3699597a9188200408bc3daf6ab7", - "0xa5a502ff673f6dab7ae4591a7b550c04ede22a45a960c6b5499644f721c62b12b9e08248e7f8b8a59a740b058d2a67e6", - "0xad374f80f0b52bc5a9491f79a547ce5e4a3ce4468a35d7dbca8a64083af35ab38eff9aa774ccba2e2e1e006e45cb0b85", - "0xab6851827125e3f869e2b7671a80e2dff3d2d01ce5bfbeb36cbaf30c3d974a2d36fd9f7c3d331bb96d24b33dbd21f307", - "0x96658f6a2d225a82f7ccee7f7a7e476967e31a0cd6c62859d3b13ee89702bb821547f70ffd31cb46a6a0d26a93158883", - "0x878f59ff2590bc3d44fdc674717589800748b78d543d3c0dbb50125b1d6011d6a083f10ab396e36b79f2d89b7cf51cdd", - "0xb8bdfb97829c5d973a15172bfe4cb39620af148d496900969bd7ca35de9b0e98eec87af4e20bef1022e5fb6c73952aa0", - "0xa292a78b452743998aee099f5a0b075e88762222da7a10398761030ffcc01128138d0f32fccf3296fcbea4f07b398b5f", - "0x85da44fdd7b852a766f66ba8804ed53e1fc54d282f9a6410106c45626df5a4380cbea2b76677fdfde32446a4d313742a", - "0x84bebf036073d121e11abc6180cba440465c6eaadc9a0c0853a5f1418f534d21cccf0cfc62533eaeae4653c7b4988046", - "0x923dec006a6af04ef675f5351afffffd2c62a17a98f4144221927c69f4553dd105e4fcc2227b5f493653d758cd7d0352", - "0xa51eda64f4a4410a1cfa080d1f8598e23b59856436eb20a241e11106989fbbb48f14c2251f608cbf9531c7c442b30bf7", - "0xac6d26ae7bab22d49b7fba7fe4b8cf6d70617977008c8290787c9da1a4759c17c5e441efb3dee706d5d64d9d2ace1de5", - "0xab5138b94d23c1bf920b2fb54039e8a3c41960a0fe6173261a5503da11ff7b3afdb43204f84a99e99888618a017aac1b", - "0x8c85647a91e652190eee4e98a1eec13a09a33f6532926427bf09e038f487e483f7930fbe6ff7a2126ccde989690dc668", - "0xa6026ab87cffec3e47b4c9673957d670cb48c9b968d2ad0e3d624d81c1082dcebbc70d0815cbd0325e0a900d703a6909", - "0xac4f6ff6baf8374a3c62bdd5a8d207d184ff993f6055bcee1e6dcc54173d756c37c24570d6462395add6f7871d60b1ae", - "0xa0dd6bc93930d0016557588f2598b7462ca48cbed637c8190be0fb4811e4576217ca9fc3c669c2a4db82e3f8bb24acaf", - "0xa67c1d79f7e7193a23e42928a5cc6a6e8e0c48b6b286607dbcfaaa0f10a7ba29ad62d1d57ca28c486794f0908bece29c", - "0x822f411bab4882202ed24e67c84e0c9a8da5b3389804ed9dfba0f672e3e1457ea76cad0cb935dbb3d7a39500fba5fe12", - "0x8a1198572323689300a9d7db2e2bcb7c519392e5d3d33e83cd64bcf1517a7dde52318a98203727b186597702c0eed258", - "0x8a84141b02f1d037c68d92567d71cda3a0b805d1e200b1d3fff3caf9902457cbfbaac33157b87ab0bb9e4fe3bac882c3", - "0x8070ace16d9eef8658fdcf21bed0d6938f948f31ca9d40b8bdb97fc20432cd2a7ef78eeefc991a87eae7f8c81adf9b19", - "0x9522e7123b733ce9ca58ab364509f308a1ead0915421ccede48071a983fd102e81e1634ffa07a9e03766f167f5c7cb5e", - "0x82cbdf97a755e952304f5a933fd4d74a3038009f242dac149595439130a815e9cc0065597c0b362130183a4c4a444173", - "0x81e904f9b65cd7049c75f64c7261e0cbb0cc15961ffcac063d09399d0d2b0553b19e7c233aca0f209f90cf50c7f5e0b2", - "0x8f5f6ea87429542ea04ad3eb5fc7eeb28fcf69c01c1a5d29b0de219524f6fba90c26069bfc9092379fe18cb46274393a", - "0xa4e5815481eb33b7990d2de1a3a591c1ab545b64fbeb4cff8c71b6bcb04d28940097899062bf43b27c5a8f899616703e", - "0xa7afe6066681e312882b3b181f462a1af2139d9bd2aefffae7976f3fc357bfd8fbd6ddd4e5e321412f107736e77f0cb6", - "0xb8ab102d7ff8d46b055095d8fb0ec2f658c9e18eee523c295b148b37f8342c120798113553b8bfebf2a11f27bc704cc4", - "0x862175ecc7e0e294c304a0352cd0f1d11b2603d326bb0e54e02b6cc8d04d01ac31c8864e9395aa1f3b90b76bc4397f5b", - "0xa4ea51ef3d82509f0e4eb6af705fa7530921cf9512cb5bf030571e69f4504a299297219a0a7e40db1b45165a5ea3a3f2", - "0xa6fb8b573e2ba6db0e8aba53a489e99bebe533c0fcd947dbfa732e00594f03f4e8609ccc44d8215986d38bc3d4e55d48", - "0x93fe8e0bdd5d66df2bd18be5963e864bddfcdcd3298590e7c3b11d99a070a4948fecef46453f19960bbfeada37979613", - "0xacbc45bc55c7080b45c69a3db80cbfc0267006dcf49c47330975aeff2a8ac07b206e1b1c3a515e50866ff510739b92c0", - "0x94a577df0983e4ee3d6b80c73d7e8e3bb78bd8390ff56fea350e51bdf5e0176b8494e7e81dc7b1d842ada961089cd1eb", - "0x81eb1fbe9e9c89f5818d0ef98e694da86e88625f0a37cfe88e6de69f90e58297e67f1d5c9d71263b523b63e42685975a", - "0xa81a2391ea4d0f65ab4325196559d67e2648b3f1e464509430b40d9948d5b0fc01c337d9b51048a93c4d62e6b73e1e8c", - "0x849a026e55ed77135138836c9df67883763e4602357d8566da2ee2505d135d44061de0c070cf333ffb9ac2e55a0894b2", - "0x8e272cc5734374c003c7b2e6ba833eb99b6be608da04e576df471c24705b6b2a790549c53e7971df2d9f0b88d0f570c6", - "0xb0f9e6d985064aa311d4a147f41007fdc576b7b9194aa4b8712bf59a76a71543fec2ee3db21bd3d30d4096f25babc543", - "0x96331837f0d74e2ba6cb1bfaddf4b1fb359bf46cb6c3c664938eb030e56bc85a5ce17bcd60b7fa7b72cb0ba1f3af0b5b", - "0xa0eaab6de4b5a551896e7d26153fb5df4bc22a37833ec864090b57b5115b0f8f1279e855cea456bb844802b294b0dbb7", - "0x955e87d3b966edff34f28137f871881c59bbbc6d69986b739867807680ca22b5e3272ced1d25854ed9700d87f133848b", - "0x9270a6db157a8ce78a1af6bfe2b5bbe7b621d56cc8f9940a03b5a5f600848b87b05d83595b2a3a315d4b7f4687c46085", - "0x9043328f2dd4dd85e14c91237a3478dc1eed239164924b53d1de9364d76c81315afa9639b58eedb1ab2122e2ae2e7cfb", - "0x857fe9f7d00b03bce367de7f789d755911a5f85d78044f18311ecd9b955e821b4a50228347260ba1205aef61219001fe", - "0xa0f878050367a7103fddf380908da66058ef4430eae1758335c46c24f5c22fefb0753991b3a47dba5c7eaafa4d598178", - "0xab5959296b1af14d2878816c7da9926484cbf8896b7eeac8a99dc255013319a67a0209025e1f8266ffd8cd7d960bdc87", - "0xabe53abc57ea46419dbe0ac1f39eee39a4feae265e58b50928eb0695e25938a16a8b00e65c1313837dc3367297e2c258", - "0x93e3e42ed6ba9c45d4e7a4bf21c1e469efafded1f3be9931a683dbb780db2494742fd76c9ad29fd7d12da2b778ede543", - "0xab3e64035c488a6e63496ddb2de9648cc63a670c5d4b610c187d8ceb144fcc50b016046f50b10e93b82937ebe932ac08", - "0xa3a8fa898f489b313d31838ad9f0c7ffe62ef7155de5da9ffe6ecd49a984fac3c6763e8cb64e675e1c4a0e45e7daf078", - "0x8356b26aa7c9fc9734b511480dad07b164cfec1324ad98eec9839a7943f2889d37c188d465515ad4e47c23df641c18c3", - "0x83c4476f829e0fe91da2353d5b58091e9335157941e89ca60ccab1d7fdd014bcf21bd55249805780ddc655c5c8c2536e", - "0x814f6e66505b2cb36de92c0de8004d6d094476522e66b9537787beff8f71a1381ed9f2b7d86778979ad016a7dae6cbac", - "0xb1cd7f6da4a625b82bea475442f65d1caa881b0f7ce0d37d4b12134d3f1beb3ad4c2f25f352811e618c446185486adb6", - "0xa71b918481b9bda667de0533292d81396853a3b7e2504edd63904400511f1a29891564d0091409f1de61276d2aebc12a", - "0xa2cd3d4104ec5fb6d75f5f34762d5e7d2ff0b261bea5f40a00deec08fbdab730721231a214e4df9b47685d5bacfe37c6", - "0x807f2d9de1399093bf284814bc4093f448f56a9bde0169407cdc0e7d2a34ff45052aef18bcb92f0ac7a0a5e54bd843e9", - "0xabeb03010c3ac38587be2547890a8476dd166ac7b2a92c50d442f031eaf273ad97114c38e57fe76d662c3e615334ac0b", - "0xb90a688da4b0bf65ff01bcf8699f0cba995b3397fcbe472e876ae1091a294463e4b94350ae8bd5c63b8441089e0884fd", - "0xad88db4afb177931788fb08eff187e15ad739edc7e1a14c8b777b6bf668aec69ca4749773f94250c1fdda3b59f705f7c", - "0x9886809f9ae952797c6527c6db297d2aa3d5209b360efe6a19970575a9f78aee3c21daadb8e8dfcbeeea5290238d16d9", - "0x930f486e95d7c053c9742e6f0b31e6d4fa2187e41229e46a074b469aafb87880aa8e972719b363049fc9fe2db8f03ce2", - "0x8d229af4fa08bd8aeb5fd9acfee47571eb03fcd2f19073b94cd27e2a6735029d31f123249d557f8d20c32ac881eae3aa", - "0x84576ed5aebe3a9c3449243a25247628993fdb2cc327072418ea2f1d11342756e56e9a82449bc3ea6e8eaecabc62e9b5", - "0xb775cb86cbec9c46a4a93d426379c62872c85dd08bccda39b21cb471222b85b93afd34a53337b6d258f4891c6458e502", - "0x8be1540e6b535b416b8d21e3ecf67dfb27a10fd4010f9f19426422edaeb0a4961d43ff3afd1db0994170056ce4d77aec", - "0xb9c7438e90a5501a4d05bbb8ab68d6db7e9baa8927231a5c58715ee2ab76ca1da0e94910a076958654869148d813d0e9", - "0xaa9bed1c4d2e7cbc2e1a884c8998773f7cc6fa9d6493c8abe8b425114a48305c3a43a1abda2292177ffd39ef02db4163", - "0x897b395356047cd86f576cfc050f7e4546ecd4df30b2c31ed8945797b81dd4ed9b9106cfbe6d7dd8bf91882e3cf1f42e", - "0x949a37e1037d9464b2ccd3ad23eda7089570d6b5ffa18025d2548a9df8829de8d62960f04a603f21eecbca5893d45284", - "0xb8a0642f68ff169ffbcd8cd684fae75d96f9bd76949472775bf155edc55a3d9c3e6f0299ee73a6cfb96289361fdbe9ee", - "0xa1273141510fcddd89b9b92c19a268dadd1528ad85744b8174684c9b56668e6b35dabb05f2b4cc6ef5611eaea6052f27", - "0x97c7415c82de83ecc066eb922268b8205ad7266c65b2b8f7e0aadac87f076c738cea72f9b0f069b8d28cf9d5438b8287", - "0xb32c7005380c848f71092a74297555dc6022369fc2a4f285e586ac8f53f6bd354fbe4b1f8a4cfb406a101103bf87bb64", - "0x91b48eeba52f02d04f536d32112038f8ba70bb34284fbb39e0f7bae2e08b3f45ad32e2f55d1beae94b949c15652d06a1", - "0x99e24f5ea378cb816a4436af2ee7891ac78a2e37c72590be0abd619244a190fee51fc701b6c1c073611b412cb76332c9", - "0x9465d1e73a1a0a5f7b1cd85f4fa4f5dee008b622b14d228d5cd5baeec174451e7ae93c5de688393d37cc24ce15df4139", - "0xa6ac3986ee01debdacb5ddc1e2550cb4f039156df15c7d5752b79f333175b840bdca89c4959a523e58cf97bbd6b2039e", - "0xb7f7a5cc1b1b6145988170d619c170c130231abbe0b5143a9bccaaebeef9ceb1c16e26749bc9dc5650fe91f92fa1b79b", - "0x854cb04f1557457383a401d79a655adfd0a4b706ea2bbc6262949c8d657efcfdc9c7960cbe1a50b5eebb361c5e378f80", - "0x8dd199dccbdc85aeca9ddcb5a78dd741a452f7a0d3ceb6546d76624bad2fce0e7e6c47ee30d60bf773f18d98503e7f9c", - "0x889e1ca9f0582be9bf5f1aede6a7312b30ea9bed45ab02d87182a013430f16007ae477ee6a823ae86c7fef7da016a0ec", - "0x892a60e63edfb3e7a6cf2d0be184413d214401fc1e6c004ca2902c3f1423728bf759a136e6e715d26d5bb229c75cc20a", - "0xa2287cd092261b39d22dcb1fa19512590b244771bb69fb62eda72f12be37d48e408b3e37a47608f68d743834edee7f15", - "0xb3b6afb950bbec0ff631bdf18af433e68adc63d02cb479704f24329ca6b6edd9a3d1d606563dbdce6038b676b85130b9", - "0x847da90f37b294509de51ab6521fdff12d5a1ec3cccaf730aa744da7e54b85fd9c70618787e87c0ba9947ce6c81387fb", - "0xad872153c00bccac75bdb30d1ab7044d814f4f8655ff26421d48fea04fb21d4dc82c1900620a57d13adc45c1062a1817", - "0x90fa5ee98fd7ec719f2a8543bbd0ff45ac69296c2416fc8666d05de3deea1017079a68aba55540a19585925803c8335d", - "0x962ba6d029e9176d0e8c80a21f2413f7322f22a9e9a32c933697a8b0e995ce25bea5264736a75718b3d330e215a58a05", - "0xa446f9530db30c5e9c1b3844d635e5c2cd311cc4537ff277fe83dd1a0382bcfa73beb07aaa0cf5a97d24c67e688086a4", - "0x8766b2053f16c72db387abe18b43d7b357a542916c9b8d530ee264e921c999494d6eb1e491352ecdf53758640c7a246d", - "0x83f32f511f7b0233662acfc14f30df345af99d2d6c777ce0b4bcdc4dd110533f30b45071df17230aaec392cc482355e1", - "0x82e3521bc9519b36f0cc020225586b263e4feb57b533b38d8e89ccf8d03f301d94da90efb4902002732fbf3876697f38", - "0xb5d1ea69c97ceaa34a720bb67af3fcf0c24293df37a5f6d06268b1eabe441531606954ac2598a1513f64231af722b3a3", - "0x956842696b411e6221c5064e6f16739e731497e074326ef9517b095671f52a19e792d93fe1b99b5a99a5dc29782a5deb", - "0xb19b5658e55c279eb4b0c19a0807865858cbec1255acd621f6d60c7e9c50e5d3ee57da76b133580899a97c09f1dd8dac", - "0x89e6a8b916d3fcc8607790e5da7e391f6bc9eae44cc7665eb326a230b02bc4eb4ef66e608ccc6031048fc682529833d0", - "0xb1a210bc8070ed68b79debd0ec8f24ec5241457b2d79fd651e5d12ceb7920e0136c3e0260bc75c7ff23a470da90d8de9", - "0x85b1954278e2c69007ad3ab9be663ad23ae37c8e7fa9bc8bd64143184d51aea913a25b954471b8badc9e49078146f5ac", - "0x98bf63c7a4b200f3ce6bf99e98543925bc02659dc76dfedebe91ec5c8877d1271973a6e75dad1d56c54d5844617313e1", - "0xb7404b6e0f320889e2a0a9c3c8238b918b5eb37bcdab6925c9c8865e22192ba9be2b7d408e1ea921a71af3f4d46806d0", - "0xb73cbbebf1d89801aa838475be27c15b901f27d1052072d8317dcae630ab2af0986e56e755431f1c93f96cd249f2c564", - "0x95b2027302f7f536e009f8a63018da6c91ec2b2733c07f526cc34cbcfa2f895ccfd3cc70be89f4e92c63c7ddc2a93370", - "0x9201d9ff5d0b1222bfa2345394f88ddf4fe9282acf51bee9b18b96bb724fdf8e736d7101acc2795a34e72f9e0545c9a8", - "0xacbff7eb160f427d8de6f29feeddfa8994674e033a0ccdc8e8c73f9243968f1a6379da670a7340f422892d50c97113c7", - "0x97ae8d03352c3729e1623e680dd9664f303b3bcfb844ef80d21e9c773a247967d27b86c9326af29db5eefd0bd3d4fac8", - "0x8e53ae5c22f5bfa5fe4c414dad6a10b28a3e5b82a22e24a94e50ce3b2bf41af31e7ba017d2968811c179017b78741ef0", - "0xb5ac7dd150247eb63dfb7dd28f64b1bf14426dc3c95c941e8e92750c206c4c7f4ad1a6b89e777cfe26ecb680dbf0acb6", - "0x99ae2e4652ea1c1c695e7ea2022fd35bd72b1a0d145c0b050da1be48ad781a413dc20fbda1b0b538881d4421e7609286", - "0xb8abe1fb3a7443f19cd8b687a45e68364842fc8c23d5af5ec85da41d73afb6840ef4b160d022b2dad1a75456d809e80b", - "0x842619c3547e44db805127c462f5964551f296a270ed2b922e271f9dc1074fdf1c5e45bb31686cec55cb816d77853c01", - "0x902dff769391de4e241a98c3ed759436e018e82b2c50b57147552bb94baddd1f66530915555e45404df9e7101b20e607", - "0x82e4f2ee7c7ca1ee8f38afa295d884e0629a509c909a5464eb9ea6b2d089205478120eed7b6049b077b2df685ec8ba48", - "0xaa21a68b0888e4a98b919002a7e71e6876b4eb42227858bf48c82daf664c3870df49e4d5f6363c05878a9a00a0bcf178", - "0xa8420cd71b1d8edd11ebc6a52ba7fc82da87dd0a1af386d5471b8b5362c4f42718338bcbc302d53794204a0a06b0671d", - "0x98c686bd3a994668fbbd80c472eed8aedd3ab5aa730c8d3ce72e63fb70742e58525437be1f260b7ecc6d9d18a43356a0", - "0xaca0b2df9ec8ede0b72f03b121cded5387d9f472b8c1f3a5f1badd5879fb2d5d0bbb6af1a2dd6bdebf758cfceadbe61d", - "0x93b1abd9cb41da1422d171b4dbf6fbcb5421189c48e85c9b8492d0597838f5845198494c13032e631c32456054598e1d", - "0xa246ab3a47f7dc5caedc26c6c2f0f3f303ed24188844ab67a3da1e793d64c7c7fe3e5cc46efafbd791b751e71de0614c", - "0xb9b52095ca98f1f07f3b0f568dd8462b4056c7350c449aa6ce10e5e8e313c2516ac4b303a4fc521fe51faf9bf7766ce9", - "0x8e2e9d26036e847c2a2e4ba25706a465ac9fbb27804a243e3f1da15dd4084f184e37808661ec929479d3c735555085ee", - "0x8b8c4f4ad5c8e57e6a7c55d70ef643083d4b8dac02716ea476d02dbbb16c702a2f2d5dd5efe3aec7704d2b8cdafe3959", - "0xa800afea30d0df333805d295bac25419b7049d70044be00c7c85a92a0503ca471001bc1e6552323f1a719eb96616fc20", - "0x868bced4560e1495b8527058ebc82a538b7cf806f8d8fe8eeed6981aba771de4d5e9f03cbfc7157d38b9f99cdea87b96", - "0x86b86258b0c1feb988cc79f6c4d4b458ff39428eda292f9608a5fc4c3765782c8c23c66f82d7538e78e092cd81d69a56", - "0x9370eac15de2555824c7d48520a678316a7bb672e66f8115ad7dbc7c7b1f35a7718e8fa0c35f37e3ef2df32dfa7ca8d1", - "0xae200bc5be0c1c8c6ec8e9fd28b4d256c6f806c0f270766099e191e256d67b9cceda2cc2fed46dfa2d410971a7408993", - "0xaf2428c77b2b9887ecde1ea835ed53c04891547fb79fe92e92f9c6009cdfffa0cb14de390532ad0ef81348b91798bd47", - "0xa9069eef0316a5d13d1aa4cef0cf9431518f99b916c8d734bd27b789828ae03e5870837163ea6ad0be67c69184b31e8d", - "0xb1b1ce6d529f5a8f80728173b2f873c8357f29644b00f619c15111224377ae31a2efb98f7e0c06f5f868030aab78ed52", - "0xb89c98beef19ee7f300e1c332a91569618ef8bf2c1d3de284fc393d45f036e2335d54917c762f7c2874a03fe4f0f6926", - "0x8264f993dceb202f8426339183157e9e0e026d4e935efe4cf957eb14cd53edcdc866305fb1334cdf0e819b69eafbaccf", - "0xaebd113f73210b11f5ac75b474f70a2005e5c349345003989175dffa19f168abd7f0e28125b18907502fff6fcc6f769b", - "0x9993ad061066ca6c2bb29fe258a645089184c5a5a2ef22c811352749a199be3a3af3a0d5ce963febf20b7d9e63508139", - "0x97952105000c6fc6c2dcae1ebdb2feae64f578d26a5523807d88e6caf1fe944b8185e49222d06a4553b3bdb48c3267a2", - "0x82dd955f208957d74693bed78d479c9663f7d911f68ff033929418eb4a5c5dc467589ca210c1ba3c2e37d18f04afe887", - "0xb816fc4763d4c8a1d64a549c4ef22918e045ea25fa394272c7e8a46dcb0c84d843d323a68cc3b2ef47a5bbb11b3913bc", - "0xa7a87ba4d12a60ee459aad306309b66b935d0c6115a5d62a8738482f89e4f80d533c7bba8503e0d53e9e11a7fd5fe72b", - "0x92b36d8fa2fdee71b7eea62a5cc739be518d0ecf5056f93e30b8169c3729a6a7ed3aa44c329aa1990809142e0e5e2b15", - "0x8835b6cf207b4499529a9034997d2d3bc2054e35937038deb9c3e2f729ebd97125f111c12816d30b716b397016133c52", - "0xacf14cd6d978ba905cf33b9839b386958b7a262b41cbd15e0d3a9d4ef191fcc598c5ab5681cf63bc722fe8acfda25ce6", - "0xb31302881969c5b283c6df90971f4fb2cc8b9a5da8073662da4029f7977fbb4aaa57dd95b003a9e509c817b739f964e7", - "0xb74669e1c3fa7f435e15b5e81f40de6cfb4ad252fcdfb29862724b0a540f373d6e26c3d600471c7421b60a1d43dbeb0f", - "0x861d01615cba6ca4e4ef86b8b90f37fa9a4cc65cef25d12370f7e3313b33bb75de0953c8e69972b3c2a54fe110f2a520", - "0xa58a56820efaf9572fd0f487542aaff37171d5db4a5d25bfb1a5c36ca975eb5df3cb3f427589e1101494abb96b5e4031", - "0xaf13d0a6869ef95cb8025367c0a12350800c6bc4ae5b5856dcb0a3ca495211d4139f30a8682d848cb7c05c14ae9f48cb", - "0x8c385767d49ba85b25a3a00026dd6a3052e09cd28809d5a1374edf4f02dc1beed367055b0dee09102c85985492b90333", - "0xb5129fc2fec76711449f0fcb057f9cf65add01b254900c425e89b593b8d395fc53bb0a83ddbd3166acc6d2c17f7fc2a4", - "0x86bd01b3417d192341518ad4abf1b59190d9c1829041e6f621068bce0bef77ec3b86875b7803cf84ff93c053c2e9aad1", - "0xa74fc276f6af05348b5fabccb03179540858e55594eb8d42417788438c574784919fb6297460f698bd0da31ce84cebfc", - "0x967ed3ec9f1fc51f76f07b956e1568d597f59840ef899472a3138f8af4b4c90861e23690c56b7db536f4dd477f23add6", - "0xb9e678206de4fc1437c62d63814d65f3496be25a7a452e53d719981d09c7e3cae75e6475f00474e7c8a589e2e0c6bfa3", - "0xb028eaffaa4ff2b1b508886ff13c522d0b6881998e60e06b83abe2ac1b69f036eece3ded0f95e9ae721aea02efff17b6", - "0x935f82de9be578c12de99707af6905c04c30a993a70e20c7e9dd2088c05660e361942fa3099db14f55a73097bfd32a44", - "0x96a1cc133997d6420a45555611af8bcd09a4c7dbddf11dbe65aab7688cc5a397485596c21d67d1c60aae9d840f2d8e48", - "0x80d117b25aa1a78e5d92ea50e8f1e932d632d8b37bebf444dcc76cc409322fb8eface74a5dddab101e793ff0a31f0a53", - "0x893229136d5ab555dc3217fb4e8c6d785b5e97a306cdaa62f98c95bad7b5558ed43e9a62a87af39630a1563abd56ec54", - "0xb7ec1973ec60bd61d34201a7f8f7d89d2bc468c8edc772a0ba4b886785f4dadc979e23d37b9f7ef3ff7d2101d3aa8947", - "0xb6080ca201d99205a90953b50fc0d1bd5efd5eadbfe5014db2aeb2e1874d645ab152fb4b0ff836f691b013b98ce7c010", - "0xb546e66ec0c39037bbaa66b2b3f4704a6a72cf1924a561550564b6fcf41fbc2930e708cd5cac1d05e12a4b8ec93ff7eb", - "0x8abeed90a01477260f4b09fff8fa00e93afe727e8eed6f111d225c872a67e6ab61d0472ab6add3fe987744e16f7c5268", - "0x8e02342d5cc1836ed21834b9ef81686172cc730f0412479db5f590b0ff7a729a0e986ffed16d6ecafd6b83d65922ca5e", - "0xb05660605cf8e8a10c8d3c77cccbe4e7179fa27cc829571f6b722a58e65e4e44d7fe977446118e9da2d2f40af146cc2d", - "0x942a00e006baba6d025cbd99297bdb0cbf3d84cddf849b1b5a9fe9ef1745352fad81313cce5d7622d6652096a8fa065c", - "0xaace8212b3d8dbe44ac97460a5938a3b803aca9bd00d8a643a859351daf391b22d1fd2a6b3e0ff83cc9ee272a1ad7686", - "0x965a9885a5259197a75a19707a2f040e0fd62505e00e35ebe5041d8467596752aedf0b7ec12111689eceb3e2e01ecfc8", - "0x81d58270a4e7ee0137cb2bf559c78c4fd5b3a613468a8157b6a9c5c0b6ca20a071b87c127d59cecc3d0359237a66d890", - "0xaf92b6354fbf35674abf005cb109edc5d95845e3d84b968e6001c4b83d548715dffc6723ac754c45a5ace8cd7dd30a24", - "0xb112caa707f9be48fdde27f1649149d9456857f928ea73e05b64bb62d597801daac0b89165fea76074f8b5770043f673", - "0xb6e7380746da358fc429f676b3d800341e7ab3f9072c271310626ae7f67b62562ff76c63bc9f5a1dbc0e0af87752408a", - "0xa45e9e8d0931207ebc75199aa0c983134aa97f771ff546a94a3367bcedf14486f761e7f572cf112e8c412018995fdaf4", - "0x854381128de5bfb79c67b3820f3005555f3ee6f1200046ebbfaee4b61b3b80a9cebf059c363a76b601ff574b8dbf0e6b", - "0xaa1b828a8b015d7c879669d5b729709f20a2614be6af6ff43b9c09b031f725f15b30cde63521edda6cd4cf9e4ab4b840", - "0x8f28f6b62c744084eeddcb756eced786c33725f0f255e5999af32b81d6c6506a3f83b99a46c68fc822643339fe1b91c5", - "0xac584e76a74cafe4298ca4954c5189ccc0cc92840c42f557c40e65a173ea2a5cd4ae9d9f9b4211c9e3dfd6471fc03a1b", - "0xa413365df01db91e6a9933d52ab3e5ed22d7f36a5585ad6054e96753b832e363484fb388c82d808d1e4dfb77f836eab9", - "0x8a68c51006d45bf1454a6c48a2923a6dbeb04bd78b720bb6921a3ca64c007043937498557f0a157262aac906f84f9bf8", - "0xb93ff8b6c8c569cc90ee00cfe2fc3c23cccea2d69cbca98a4007554878311635cb3b6582f91636006c47b97e989fe53d", - "0xb9a8a44d54592511d74c92f6a64d4a8c539a1d8949916ef3773e544f6f72c19a79577de9878433bd35bb5f14d92f411d", - "0x94f066a7e49ae88d497893e4ce6d34edc2dc0b42fe03934da5d4ed264d1620d506fcc0661faa90a6cf5083e1720beaaf", - "0xb42b102adef8f42c1059b5ca90fe3524dcd633cf49893b04b4a97a1b932ca4c7f305cebd89f466d5c79e246bad9c5ced", - "0x86b560d78d3c5fb24a81317c32912b92f6ea644e9bedfdea224a2f0e069f87d59e6680b36c18b3b955c43c52f0a9d040", - "0xa3829fa7e017c934fa999779c50618c6fb5eafb5e6dec0183f7254708a275c94ba6d2226c5ca0c0c357b2f2b053eea93", - "0x9337dda730076da88798fd50faed1efa062f7936a8879ea4658c41d4fcf18cee7120366100d574536e71f2f11271b574", - "0x853d09a30f4342f5a84c4758e4f55517a9c878b9b3f8f19e1362be9ae85ca0d79c2d4a1c0c14f5eff86010ad21476a7a", - "0xb0bc74cb69bdd8fdffca647979e693ad5cbf12a9f4ead139162fa3263bfebef3d085aab424ed8c6220b655228c63c6b1", - "0x88d8dc8faf3aab12ba7180550e6a047f00d63798775b038e4a43a3b40a421a3f5f152a7e09f28ccd7198bb8cefc40c07", - "0x88db2e3b8746415d0c3e9f5706eda69a29d0b9ee5135ad006060be7787f4f1f7069e2e2e693c5e10b7c3d5a949085ae0", - "0xb5bd830d2f1c722188dba2690d21b7b84b92cbdd873a55aaa966f1d08d217bfc8cffe8caea68868f3850b90b4ab68439", - "0xb5ad4be0c9626a33fce6c8501297bdde21b07b88531451912ed41971a4c48fdd1036d8a4994a99a7fbba4a5901a7095e", - "0xb0e1337a2a1772191faa91302f1e562e7cdc69ba5b25139e7728ce778a68a7fa9817f852ec8e04a159122cff62992ec6", - "0xb4fd4a4c1be8bc7e4e2bfd45404c35d65b75f45fb19ce55c213a8035b41f1ccbce9766f3df687c0d7cd6cdfc1abb00a5", - "0x814bf565ece6e9e2a094ffbd101f0b9fea7f315a2f4917abe2bf7d070ed8c64a2987bd288385a42fd336ed0a70a9d132", - "0xaf860af861dc80894ed69f29c8601d986917ec4add3d3f7c933a5e9d540bc8ff8e4e79d0bb01bbc08fa19ef062f2890c", - "0xb66d33fcf3cd28f15111960ffc6ed032c3b33d4bb53d035ab460cc5fa7ce78872f0476d0bb13f1d38f2672347d2d6c4d", - "0x89603ae1a5dd7c526936b86a3b69b7b1d0bdf79ba3cc9cc2e542ec801a6126d1514c075d6ad119fe6b6e95544ffe7fbe", - "0x8a1b097f46a62d85cff354d1e38df19a9619875aad055cc6313fdb17e2866d8f837a369a9ee56d4f57995e2b0a94310e", - "0x8dc165d86c7f80b0fcd4b6f90d96cd11dc62e61d4aae27594e661d5b08ef6c91156c749de8948adfaf3265b1d13e21cf", - "0x98e3173772c3b083b728040b8e0ee01dc717b74c48b79669dd9d2f7da207af64ccd7e9244bc21438a5d4ac79b88e9822", - "0x924d168099b6952d6fe615355851f2b474f6edfcd6a4bd3ad2972e6e45c31bf0a7fb6f7fca5879a0de3ea99830cfb5bc", - "0x95452f0b7efda93c9e7a99348e13f356bad4350f60fcd246a8f2aa5f595a9505d05ec9f88b1fe01b90ecd781027b9856", - "0xb95e8af516bb0941fc0767ecd651ada2bc64cc3e5c67a1f70048c634260c0f2c0e55ed22948e1870c54590b36683a977", - "0x82f7feb71e746d5ca24455e3f3e57e4eade92669ab043e877b836612efd3de82009f0555e5d8811bff9f2b75fc57a01d", - "0x87623c02caf590ea84cf4a84d1be501f89262e26eb463f2f94a2d3042889c051b058823c3367a989498e46ff25edab16", - "0xb88da847b1ef74c66f923773ce8c920ca89751335fde17b3a98c0603862069a2afbf35b1552b43ad64dccea69f040ff8", - "0x96b734758c823e5ce5b44625c252957e16fa09f87f869baac195956052dc92f933f377b288c7f63b8028751cbbdca609", - "0xa23cc5fbbe5cb7c1d33d433cec4e502f6548412e2374e285d307f75e98280b0c0af4f46bba18015be88cdf7db8b1239c", - "0x8bd5bbe04bc929ca8f546e673803ec79602f66ec24298d3e3b6bf6f2c25180fc0032ea6f86c38a6e0ec20ff4eaafc7a1", - "0xb95768ca113e5d57ad887a1cb5ef84ce89007ce34c3156cd80b9aa891f3ebaa52b74c0cb42919cfbcf0cb8bafa8085f9", - "0xa117f99045f65e88acc5a14fc944f8363f466e4a64057eb8fc64569da5dd022a01f2860c8e21b16aff98aebdf89461b7", - "0x895cda6503907c98c43477eaf71dfd26759032523691659f13662ca3a967d93bbc5be342d168223cef7e8a333987d6a0", - "0xa084d77d913d3ec0586ad5df2647610c7ed1f592e06a4993a5914f41994a29c4a8492d9dce2e14d8130c872d20722920", - "0x84a328b73c64137bb97a0a289b56b12060fa186ce178f46fe96648402f1b6a97d1c6c7b75321e4b546046c726add5a08", - "0xb7c35087b2c95127ce1470d97bceb8d873a7ad11a8034cc1cba7b60d56f7e882fc06796048435a9586eab25880787804", - "0xab05e3394375ee617c39c25c0ec76e8a7f2381954650c94fbcd11063ea6772c1823c693d2d9dd18bd540a130d7b92855", - "0x82ba5907051d84b37fd9d28f8b9abebc41fc4aaa334570516ca2e848846644016356d40fa9314543017d4f710d193901", - "0x9170517b6e23ee2b87ff7c930cb02b3e6bd8e2ae446107b5b19e269bf88f08de5ded3d81a2ff71b632ca8b8f933253a0", - "0x93dc0e3f6234b756cdbb3fe473b9214e970972e6bf70803f4e2bf25b195b60075177a1a16382f1dee612a4758aa076ee", - "0xb4b49fac49cdfccda33db991994a8e26ab97366545166cc7140aef3d965529f96a5dac14d038191af4fb9beb020ff6d5", - "0xb826537670acdf7a8a45ef4a422d5ae5a1b5416ad0b938307518d103cc7ba78e495ea200adc5941414a70158a366e8a2", - "0x8ae3588b1fbecbc769c761f0390d888e34773cf521d976ee335f6c813bf06dad38850871ac8a8e16528684f1e093d0c1", - "0xad9c00b8dccdb545315fbf26849135699c6aa3735f89581244281154c906aba80d20c1e7f18f41acc61e0565f8015a33", - "0x954ce68146c05fc1c9e536add3d4f702335d93c1650b8c1fad893722a81f915eee2d38275dad00ce87f3f5bc90ef7341", - "0x8243feaeff9a12f5aeb782e3dd68609ce04ecde897c90fd8a19c9c5dace3cf43bd5bc0f1624bf7fd2607ca0d71adbba8", - "0xa8a1be55259cd27898d9d60a61998d8da2bf2d439ba6eedb61d6d16dacc4a81ec706b9196dfa080ba20701d2cd9fa1f4", - "0xb0eac6212c7a62ef6062c30875fbe24b8e1a9d88854c035686f849a9eed4d17fbc9af27429eb7c3fd60b47a5e29f6783", - "0x878561a88412e95f19f1cb8894be9d0ea4a2cdd44f343387f87dd37445e5777bceb643cebc68c910acb5e588c509cd2e", - "0xa57b6c347955d8b0057a87494223148ff9ff12b88e79dbd9d0aae352fe55e15ea57fcfb9add3d5d269ee0001d8660f20", - "0xa07fa66340d4082585e4d72c77510c59b272e7a3345f4b1de6be7ff4a11ea95d712d035a7355fc8d2e571fa65fe8236f", - "0xb9d84a627462438e8ede6c453e3367bfaf81cff199d3e5157ef2bc582d358b28b5ccc3bc27bb73af98ef45179ea79caf", - "0xb14f26ea7ca558761cb19508e5940fbf5dcf2ad8555c5a03e8ff92481994072f523b1ab6b7176f698e2cfd83d4f8caad", - "0x800cca1cbb14e1fc230c7b420ff06864a934b082321bbf5b71f37340383923f23183d4fdc8fa2913928722b8892db28e", - "0x94790c950b92e971ec39e9396c3f32dee32a8275d78e6ea28a47130651bddc86a189ef404c5e8c210bd291186dee0df4", - "0xad7b3b3e377df64023b8726d43a7b6ec81e5a5e8c0943c5bebe5ab5ddd6597255f434a205c14ba90e9e5e3c462a1fe0c", - "0x86ff8156cc857a416e735009cf656b89da59b766b4c4e5a0c0165282b530c10657cc28cf5cb847696725c37ac48b69d7", - "0x89cb64cf9294f68f01533660a2af2aec0ec34cc0b4a0cc36a128f2e0efb3da244981f69aede962f50590faeeb9a5da01", - "0xa2ea5a94a524bb8e6f767017246cd1af9d87c9abb9894e91c4e90c34c5161be6179b49dafcab9cff877a522c76beb145", - "0xb5d9abf29ed6030a1e0f9dc19be416c45ba8cb5ed21aff5492233e114035715d77405d574cd62f2716285e49f79b9c99", - "0xac441cf6104473420babdfb74c76459cbea901f56938723de7ad3c2d3fadb0c47f19c8d9cb15a3ff374e01480b78a813", - "0xabea34bd2d36c5c15f6f1cdd906eb887f0dd89726279925dbe20546609178afd7c37676c1db9687bc7c7ea794516af03", - "0x8140abfd0ec5ca60ef21ad1f9aabbb41c4198bac0198cb4d220e8d26864eedb77af438349a89ca4c3ff0f732709d41a9", - "0xa5a25abf69f3acd7745facb275d85df23e0f1f4104e7a3d2d533c0b98af80477a26ac3cf5a73117db8954d08f9c67222", - "0xb45ac8d221a7e726ad2233ba66f46e83ed7d84dbe68182a00a0cf10020b6d4872f3707d90a6da85f6440c093914c4efa", - "0x80f586dfd0ceaa8844441c3337195ba5392c1c655403a1d6375f441e89d86ce678b207be5698c120166999576611b157", - "0xb8ce52089e687d77408d69f2d1e4f160a640778466489d93b0ec4281db68564b544ec1228b5ab03e518a12a365915e49", - "0x8990f80bae5f61542cc07cb625d988800954aa6d3b2af1997415f35bd12d3602071503b9483c27db4197f0f1f84a97ac", - "0x8329858a37285249d37225b44b68e4e70efeef45f889d2d62de4e60bd89dde32e98e40e2422f7908e244f5bd4ffc9fe2", - "0x8d70c66ea780c68735283ed8832dc10b99d3daeb18329c8a44a99611a3f49542e215bf4066ff4232d36ad72f1a17ccc3", - "0xa3b2676cc8cdf4cc9e38c6cb8482c088e5e422163357da3b7586a3768030f851ad2a138eeb31584845be9ffb8067fc00", - "0x95b1fa74e9f429c26d84a8e3c500c943c585ad8df3ce3aea1f6ab3d6c5d0ed8bb8fa5c2e50dd395fa8d4d40e30f26947", - "0xb1185f2ac7ada67b63a06d2aa42c4970ca8ef4233d4f87c8ffa14a712a211b1ffde0752916bfafdfa739be30e39af15d", - "0x8705a8f86db7c4ecd3fd8cc42dd8c9844eab06b27d66809dc1e893ece07186c57b615eab957a623a7cf3283ddc880107", - "0xaf6356b372f0280658744c355051f38ff086f5563491fc1b3b1c22cfec41d5c42b47762baeb9ee6c2d9be59efd21d2b7", - "0x86bdd4527b6fe79872740d399bc2ebf6c92c423f629cdfcd5ece58e8ed86e797378a2485ead87cbb5e2f91ba7b3fbda1", - "0xa900f0be1785b7f1fda90b8aedd17172d389c55907f01c2dfb9da07c4dc4743cb385e94f1b0fc907dd0fedb6c52e0979", - "0xa9f59f79829a9e3d9a591e4408eaec68782c30bc148d16eb6ae2efccb0e5478830bbdaa4ae6eac1f1088e7de2a60f542", - "0x99cf54a69ad5e8c8ec2c67880900e0202bcc90c9815531d66de8866c0a06489ea750745cc3e3aa1c4d5cb55dcd1e88f7", - "0x8676246a4710d6d73066f23078e09b3fa19411af067258e0b8790456525c02081727b585d6f428c8be285da4aa775a4b", - "0xb596c7014fe9214529c8e6b7602f501f796b545b8c70dbf3d47acc88e2f5afd65dccef2ef01010df31f03653566b16df", - "0xa12205c6c1780fc8aebdd98611e12180005b57750d40210b9eff0396d06023bd4ff7e45f36777123ff8bed7c5f52e7a3", - "0xae7dbd435bba81685d5eab9abc806e620253da83e56b4170952852d442648a5d8743f494a4b0fc9d606574f87895b0d6", - "0x9786257b1726b7cdc85219ca9eec415f98f5a11e78027c67c7b38f36f29fe7a56443570fdfedc1d9293a50e4c89d89f6", - "0xaaf0515070d1ca92aacdf5fac84193d98473d8eb2592381f391b8599bcd7503dbf23055324399d84f75b4278a601c8b2", - "0xb31654dbf62fbbe24db4055f750f43b47f199a2f03c4d5b7155645276b2e456a218ca133743fb29d6f1a711977323f6e", - "0x8f4d39106ecdca55c1122346bdaaac7f3589d0cf0897a6b4b69e14b4d60550fd017876399401ce7c5d35f27da95f50be", - "0x8a7bfdb48cd47afe94aff705fac65f260b3a3359223cff159b4135565c04b544dd889f6c9a6686f417e6081ad01e0685", - "0x967ba91111e5e08f9befcbaad031c4fb193776320989f8ede4018254be0e94586254432d3dbae1455014f3a2f2549d01", - "0xa9db52352feeb76715a35c8bed49fb3a8774c9c8e58838febf800285fd6c4938ec162eb8457029e6984d8397dc79ea19", - "0x811794e6bfe2539e8f6d5397c6058876e9e30763ad20dad942bb5dbcab2f16d51718ce52bfb4de17889ba91da1b85bcd", - "0xa6db0f65a6dc8b8cc2312a3e0146d8daf520255bb12f74874c05693914e64e92be0cd53d479c72cb2591e7725dfaf8b0", - "0x918d21bfa06d166e9eb5b7875c600663a0f19cc88c8e14412319d7aa982e3365f2dff79c09c915fc45013f6b3a21200d", - "0x9894852b7d5d7f8d335dd5f0f3d455b98f1525ad896fdd54c020eeaf52824cc0277ecbfa242001070dc83368e219b76d", - "0xad00acc47080c31fcc17566b29b9f1f19ccaae9e85a312a8dcc0340965c4db17e6c8bd085b327eaf867f72966bf61452", - "0x965e74649e35696744ecc8bed1589700bae9ca83978966f602cf4d9518074a9aa7c29bc81d36e868a0161293f5a96e95", - "0x961e29a239c2e0e0999b834e430b8edfe481eb024cc54ffaffd14edaf4b8522e6350dc32039465badfff90dcb2ba31cc", - "0x943dda8fa8237418a07e311efde8353c56dd8ec0bfa04889ccdd7faa3dee527e316fdc60d433a3b75a3e36ca2aa9d441", - "0xa0ed4c102e3f1d6ebf52e85a2bc863c1af2f55dc48eb94e40066f96964e4d37fff86db2cff55a8d43d517e47d49b5bd7", - "0x9045770ad4e81345bc6d9a10853ee131232bf5634ef4931b0e4ba56161585b4286876bc8a49b7b1f458d768718cb8ebf", - "0xb0dd430295ff28f81895fde7e96809630d1360009bbe555e3ac10962de217d93ead55a99fd4f84d8cadd1e8d86d7b7ef", - "0x95ced48419b870ea4d478a2c8db699b94292f03303f1bf4560b5b1e49ca9b47e7008514fe0a9cf785717f3824567e1b2", - "0xa7986e0e389e8aef6aac4a7a95e2440a9af877ae2bc5ad4c5f29d198ec66aa0db1d58c451e76ae70275a2e44c3d3fa68", - "0x85a8490faf32d15de12d6794c47cc48e02428af1e32205e0742f8299ea96b64bcd6d3b4655272afa595eec74ecbb047c", - "0xb790d7fb1307aacc2d303d9b6753a9773252b66c6b67763cf8841c690cbccc4866ffb5fec1c068b97601a7953fe0f7e8", - "0xafcc4011f8c53f10d63c29b74d9779cd75c861e01974c28a4ec2cbb909b67a1b2287ead175231343c936ad75dfa416ff", - "0x918058bffdecc1ae8779dccf1d874bb9e28edbe34c8b5954a8da64a848858d2f0776437b423baf4e731f3f5fa05a2841", - "0xab554db549aa36dfa9f966a5ed6be8267e3aa9ced348695f3dafc96333c6dbb48ef031693aafd59d1b746ecd11a89c51", - "0xac4ecf746b46b26a7af49cc9cc1d381e1e49b538dbd7fb773ce6b1df63ae31c916693cca8a90fb89f1e7ec5e0e8dd467", - "0xa8de66d48f16b016f780a25ba25bd6338fd8895a1909aabcfb6e70f04ff66f9866e6e2a339bcbfa4bfba4070a6a8db26", - "0xb4b49374eff6dac622e49b0e9c0e334ecbec513a96297f6369696ad39e5ec0de81a1417f6544be866c9f60957a9ba09a", - "0xb8023968549ebab6c1e7a8e82954a5b213bec50bbf35b36697a8d4fd75f9e12d510b365962aace4c9978c5b04da974a7", - "0x8d4bc016026dd19e4059d1c5784897cefa47f7ae2ed6dfa2b3c14a852fff2b64abc09549d106584e0daed861a2d6d6c2", - "0x85e26f433d0b657a53da4c1353485e0c2efa092484c5b8adb3f63dc72ee00be79197ebef7937b37a6a006571641cd6af", - "0xabb37a917301e68328032ff4715abc0fee32e5f5be68232ca8bf7ffb8732bc47504e75b40bcc0a7c7720b71496fa80af", - "0x9837c8d2660522c0357f5222777559d40321a1377f89ca1717215195bad4a348a14764bd87fa75f08e1f6263e9d08982", - "0x97e06f971b4c56408ed5f1de621d233e6a91c797f96ec912737be29352760a58831aaf1f64e377c3ed9f2f4dc8ad1adb", - "0xa12d211304da7b91101513d57a557b2504069b4383db8ecb88aa91e9e66e46e8139dadc1270620c0982103bc89666215", - "0xaab74ba48991c728ba65213e8c769e6824c594a31a9b73804e53d0fda9429403ff3d9f6ea5ef60884585d46356c87390", - "0x92f19be2b7adf031f73611282ad33e462852f778c5e072f689dd0e9458fa6ebccfae02f2b2dc021802c9225035862468", - "0x953bb843c48d722604576cef297123755cef8daa648c30c3a678eada8718dfdb16e71cc3e042a51fedc80577235c2563", - "0x86f509e3c1b9ee9a3b95e6da8516b47feb8c8a83403984228f4903c7ee1ee4f03addcb8fe86283af1196a54b36b9470c", - "0x903d793a377e98e2562c49de33e3fbf84bf99211925e7002a4f688470db655884e1efe92782bf970ffa55d9c418ef3b5", - "0xa41b65681ed7f10987a7bfdf9e56b010d53683819d845d880fc21b2d525540605c5823e75c434f17b5a0d08a091c1564", - "0x971be802de51cfc0d10a96be7977c037873f19334ed4ed4904b7675aec8bfa1f8956cd0150b07064caf18229ffd1ccd9", - "0xb253ebe4f82cdbefbc3ef816d40c497fe426a9f0f0f170e783fa4a05ae6dabdfa8c448817a24e723a314b43e76a7c422", - "0x86f397c95025489929ce9230b1466b5c330ec7c58a3c7e3153d6d05bcb8348a13398908e192590b8812f5c5ff09c133a", - "0xa0713983a3dc9f10b3833687cd2575de2fc63c4ad8d2f54ff85c6db23dd308daefef1bd1e51eec26732f77c1f37ba793", - "0x8249a1d53ec92f311f4fa77e777800d777f3e9d4d452df740fc767fa7b0f36c8dce603d6e6e25f464c0399b8d0b93c30", - "0xa73d0a206a62922f07b928501940d415e5a95716ee23bf6625b01ff2cd303f777adfa373d70279ba8a30fbb4c99a6f1f", - "0xb1106b407ecf234e73b95ff58ac9fdf6709ad2e763b58f0aacc5d41790226d441b5d41405ac03a0641f577848a4f5e8e", - "0xb009963ccc7b2d42792f09ab7cb0e929503dd1438f33b953104b4de43274ca3ce051554d10d7b37041b6f47d7a2dab6f", - "0xb744512a1b3c7ef9180b095c6a0c5bc16086a50020cf20dc2216bbff24d91ca99b95cb73070444dafc3ab45c3598960d", - "0xa0209669ffeddc074d35cc6aa2dac53acac8e870f8a8a5118e734482245b70c3175f760652e792118fdddac028642259", - "0x8ddd3e0d313da17292fdcc1bbc6e9d81189bb1d768411c6fe99801975eddb48dbf76699dcf785cac20ab2d48e392c8fd", - "0x8392aa285b8b734aa7a6e0f5a1850b631ddf6315922e39314916e627e7078065d705ff63adbc85e281d214ec7567863e", - "0xb655a1fff4dba544a068bf944e9de35eaaa6c9a0672d193c23926776c82bebed8aa6c07c074b352882136b17abdab04b", - "0xaf5095f40d1e345b3d37bebee3eb48c5d7b0547f12c030d5bfe8c0285943e0a7a53a186f33f791decba6a416cba0c5c9", - "0x8223527f9eb3c8ff52708613cd2ee47e64c0da039cea3a0189b211dc25e9bfa3d5367a137f024abe94f98722e5c14b67", - "0xafdb106d279273edc1ee43b4eead697f73cb0d291388f7e3fc70f0dd06513e20cc88b32056567dcc9d05364cb9ca8c58", - "0x9319eac79ff22a2d538dcd451d69bca8aa8e639979b0d1b60d494809dbd184a60e92ad03b889037a1ac29a5547423070", - "0xb79191ce22dbd356044e1777b6373b2d9d55d02b2cc23167642bc26d5f29fd9e2fb67dce5bd5cf81a602c3243bedd55c", - "0x988e0da1e96188ffd7c5460ecdf2321f07bc539d61c74a3292c34cb8c56dbafbca23eb4471a61e8e64e9a771a49fd967", - "0xb0792b6cf4b10f8af89d3401c91c9833736616bb9fe1367b5f561c09d8911fb5a43b7a4fd808927b33ab06e82dd37a28", - "0x862f68ea55206023ca470dbd08b69f0f785fcbabb575a1306ff3453c98ffcad5fd6ead42e8a1f9edf14c6fd165ffd63a", - "0x815ff0898b1330ac70610180c0f909561877888ff10def749a1e65edf9f4f7cea710a757c85241dfb13d0031efb5e54b", - "0xaa6e6ce21776ea4507d452ccdaf43a161a63687aae1cb009d340c9200e5646e9c2de4104dfd66b8e55dfa6de6ee83e4a", - "0x8e8f3d3403e0256ecc254b9b1464edca199cad3f3348002d744721c345a1a3c7f257c3587d2229774cd395e26693d1ba", - "0x90483e28985e4a0f7a3cb4bc5e865b9d408b94cd2146c04aed00b48a7ab80a28deb05efec320817d63578d4f953bd137", - "0x84fb2a762ba29193b07f1dd84b3f69153cedb679b66ad04f8a4adf01c14f115163a107e6db23aaf0f0c9687824ded197", - "0xb4a23922bf4302cc9a6583f252a1afa026c87c132b9ae44cc1f75a972cb6ae473447c500827906f9b677617ddd6fb473", - "0x809bb9edbbe3a2769165f029f2a48b6e10e833eb55d8f9107c4a09ca71f0986dc28f3bf4ead9cab498086eb54c626bbf", - "0xa0459dbb08db4155d16301933ec03df77c4f835db2aa3f9697eeb2bb6fcd03337fab45fa43372a469fecc9a8be2e3119", - "0xa638eaace7f21854de49f4db6e4ea83d2983751645e0fb200c5e56561f599fd37dac70bdbd36566fdd10d4114fbb9c2f", - "0xa3a27bc2728390643a524521bf8ef3b6437cfba6febfd8bb54f2b6ecbafafb96196d3dea279ce782efd97b212f364ef5", - "0xb86693b3ea23ea6b2c4d52554f61ef39c0ef57e514ff6da80c6e54395df8376e2e96b9d50e4ec301c59e022c5c5610db", - "0xaf4d7cd678d79e67ae19789d43331dff99346cd18efff7bab68f6170c111598d32837372e3afe3e881fd1e984648483e", - "0xb8735a555ba7fe294e7adc471145276b6525de31cda8c75aae39182915129025fb572ed10c51392e93c114f3a71bd0be", - "0xb1dfb6dbda4e0faaa90fe0154f4ddaf68ee7da19b03daad1356a8550fca78a7354a58e00adeecb364e2fd475f8242c24", - "0x9044b73c1bd19cd8bb46d778214d047f5dd89b99b42466431b661279220af5c50c0cffecebd2b64c3d0847a9c7a8b1ec", - "0x891f0d162651a0aa0d68fb1cc39fa8d77fd9f41ff98b5d6c056c969c4bac05ba8c52cbfa7fbb6ef9adfe44543a6ec416", - "0x8920ae1d5ac05bf4be6aba843e9fc1bc5b109817381cdd9aa13df53cabea319a34ee122dcb32086d880b20900ff28239", - "0xabb14023142876cbc9301336dced18c7878daa830070b5515ff4ac87b7bf358aa7ff129ebbf6fb78e827570a4142661f", - "0xa74b15e178cf91cde56eab0332e62d5ff84c05fcc849b86f45f94d7978bf9c0fc72a04f24d092a9d795ca3d976467f46", - "0x806829621a908ca9b6433f04557a305814a95d91c13152dca221e4c56bfaa3473d8bb1bacd66e5095a53070f85954278", - "0xb09a3c185e93869aa266a0593456a5d70587712bca81983dbc9eebbb0bd4b9108a38ae1643020ecf60c39c55bb3ac062", - "0xb2bbe8f5361a3ecdb19598dd02e85a4c4c87e009f66fee980b4819a75d61f0a5c5e0bdc882830606cb89554ef1f90ead", - "0x825e16cb54fc2e378187aedae84a037e32903467ac022deb302cf4142da3eda3ead5b9f3e188d44f004824a3b5d94fbe", - "0x8b39d4a11d9b8ba885d36bcdb6446b41da12cfd66cb22705be05ab86936464716954360cc403f8a0fd3db6d8b301cb59", - "0xac19d453106c9121b856c4b327ddb3e3112b3af04793df13f02d760842b93d1b1fbdff5734edc38e53103a6e429a1d1f", - "0xb1cacbb965ec563f9e07d669ffc5e84d4149f1fb9fcfbc505788c073578c8f67956fb8f603e0b9a9d65e2d41803038ce", - "0xb7612d9e7dc930bff29191d1503feb2d6451b368b69fa8ecb06353c959967daccdc262a963f01c7fb95496f1bd50d92e", - "0x93f8fceb65ea9ef2052fa8113fb6720c94f0fed3432d89014ee5ad16260aeb428aadea0d1f1e002d2f670612ba565da3", - "0xb3eb9213752156ed1fced3bca151fd0c630554215c808b9a0938b55fed42b6b89f9b76bc698f3e37c3c348d2395dbed1", - "0xb46ab3553ef172ae40fc21c51d1d7eab8599a67f2f89a32a971aa52c2f031664e268b976dd2f7dc2195458fcf4bf3860", - "0x8fb66f2c67ca5b6fb371c7d04592385a15df0c343857ba8037fe2aa9f2a5d4abc1058323ff9652653261b1c7db0edc24", - "0xa7dfdbbf0b14e4af70fdb017875cdc36ad2108f90deb30bfca49301c92cbf821645a00ade1d1ee59a1a55a346675c904", - "0x856199cad25ec80ee0327869077f272e33d59bf2af66c972e4a5839ec3b2a689e16f7fd0a03a3138bec458fcff8edbea", - "0xa2842ac5a715c2f48394988c6f84a6644c567673806feaa575838e906138c1b25d699e1b6ffdfc9be850b15da34077e4", - "0x814b448ada88f769de33054c3c19f988226317797acacdbe55ed2485b52cd259ac5bcbee13f9de057eee33930a7fa0c0", - "0xb49de8dd90da916ed374ca42665464b6abe89ff4453168921f5a7e5ddd3dcfa69422782e389e586e531fd78a1f236a8b", - "0x851f9d942b4c8ffc020c02c7fbee0f65ef42b1ab210ab4668a3db6aa0f8ab9eedb16f6fd739a542cc7e3cc03172b565b", - "0xa5128c155b8062d7fa0117412f43a6fdc2de98fa5628e1f5fc1175de0fa49fc52d015ec0aff228f060628268359e299c", - "0xb0765849127cc4ce1a1668011556367d22ce46027aa3056f741c7869287abcaccf0da726a5781a03964a9ded1febf67d", - "0x984562c64f3338ffe82f840c6a98a3dc958113f7ed28ee085af6890bbc0cd025723543a126df86f379e9c4771bb69c17", - "0x8087fe60a9a22a4333f6fbe7d070b372c428d8c5df3804bb874b6035e5602c0693757fb30a9cd5a86684b5bca6737106", - "0xa15e195b5850f7d45674cdc3bd74f972768b46fe9473182498263edc401745a8716fc532df8fc8c1375e39e391019226", - "0x858ec10208c14a67c4156ea9c147f36d36c4fa0a232195b647e976ba82c8e16262b2b68d31e3b4702070c3dc701bccb5", - "0x84bf3fb83c003380ee1158e2d6b1dca75cd14c7b2a32aec89d901f0d79e1475aa0827cb07cba1784a6bb0d37f6ca5cd4", - "0x91e69f5392648e7f7c698059a0fc4b8478ab8af166d3842fb382ec5c396daa082ee3b2cb0192da3c9d90f6523c4c039d", - "0x8f7299f451c5e641d6fd961946b7a6ba4755685b2a40164e6276c25aefc66715b92492097a191813d39bb4405dc5da36", - "0xade2cf04ff6c94c1019bfa1e0e8f580696230fa6ee9695c4772e5a44501b2fffdd765ec7cc71ba14b83559ad62cc0fc5", - "0x85fc98ecf469d6f98c8b3e441680816f764de39001a249bc7162f990c5a5354683e849164d4fc9287ee516780cdcd436", - "0x928d118188120d038c37abdbe66c05adaa87f1cf9957dee2783b09fa91c4c43a7b0d0b2b6c5f4dea57e3ec8af230e84f", - "0x8025f71cf8d3085d6ea5104dddea8fa66cdb8527e40db01472469be021632daf22721f4acf1a8698a53439fe2f82596c", - "0x83266fffb12b3c795a6b551ac2aa7d9a29c183f861e78768c11286a04e22bd423bba05a68775bd77273e3ca316a4318e", - "0x95fd0c69c2d9df4e795c7ba71ed71a9d9f2878cd7e3a64be7b671d9611649fd41d29f8bdab642ba19cbd3db660d6a7e7", - "0x92a912cb4d5ef4b639876daf4289500c4ebdbd80aff07fd93dc3eea645f084f910e5c02c10492a37f16acaa7e646d073", - "0xb3d2622c987189a0873932aaea8b92ebb6e9e67ff46e91a96bf733c3b84175fffe950f8f4622cc4fa50f116321c5537f", - "0xa98f9a40054b31023a8f7549a44cae853b379bbfe673c815b8726e43ecd11a96db40b20369d712cbf72ffab064ecfac5", - "0xb4e9a38e371fc21f4b8a3d7ad173c9ffad0554530dc053365d9555ddb60f5c9063c72ff4c65d78b091af631a9e1ee430", - "0x875a31aee4ba19e09f8c2754fab0b5530ec283c7861a4858b239a12432f09ef155a35fedb0bc33eac2117c7e62f1c7ee", - "0x95edd0d1a6e94af718590756b5c5f5492f1c3441ecc7fa22f4e37f4ec256b9fffd2fda4c11fc1a7c220daee096eb1ff8", - "0xb35fdc435adc73e15c5aaf4e2eea795f9e590d3e3ee4066cafa9c489ee5917466c2a4c897a186b2d27b848c8a65fa8a8", - "0x94a5ce56f8d72ec4d0f480cb8f03e52b22f7d43f949a4b50d4a688a928ffd2c9074ecbab37733c0c30759204a54f9a6a", - "0x987562d78ef42228c56074193f80de1b5a9ed625dd7c4c7df3bf5096e7d7b08e2ee864bd12d2ea563e24fa20ad4d30ef", - "0x95a8218405038c991ace2f45980dbb1efa9e4ad0d8153486b0213a89e4d7e3cac6d607100660784627c74f90a8e55482", - "0xb6a29d566f5a924355b7f7912f55140e1b5f99f983c614b8a92814ce261f2750e8db178866651ea3b461fb8f92890b14", - "0xafdacc0a13da0446a92455f57a42b3ba27ba707f24171727aa974d05143fae219de9e2eb7c857235dd9c7568f43be5a8", - "0x862a7dc25f7cfa4a09aeca0ed2c9c5ee66189e119e226720b19344e231981504e37bca179aa7cad238ee3ab1386aa722", - "0xa336364e76635f188e544613a47a85978073f1686e4ee7a8987f54da91c4193540ac448b91d07d1fc5c7a8538b1f1688", - "0x8f1ddca9638decd8247c1ce49c1e6cf494d03d91c4f33e48a84452d12b6736e8bd18c157068dfeff3a90977af19e5b1e", - "0x96ae91b9aaf00e437c18ddfc1aef2113ee278153ba090aedeb3f48f1e66feb8897bb1ac7f5ffeffc3be29376dd51e498", - "0x8230b5bd9067efb6089e50213f1cc84da892e6faf0b79d5e4768c29303a80b1b754cb09d17a21933aba4c5f32070878a", - "0xa79dfe217faec7b4d3cf97d8363949efbc6f3d2c6bbc25df2c7bb8b7fd2521e6d3fa76672bfc06de6f426290d0b3cc45", - "0x8290bd36552609d6b3ac9ccb57ff8668fc8290548eecdcee9a231f1125298c20bd8e60f033214dfbd42cd3c8642c699b", - "0x8945db9e8ec437c5145add028d25936ee8823ceb300a959402d262232ae0cbd9a64c1f0a1be6aed15ff152202ea9a70c", - "0x949e232b48adeaf57bd38eacb035267d3e78333c6b4524cab86651a428a730baf9c27ff42cb172526d925de863132e82", - "0x98917e7a5073a9c93a526399bb74af71c76958a74619caccf47949f8fd25962810a19e399b4efcba0c550c371bea3676", - "0xb5b144e0707aefc853ea5570bd78dedc4e690cf29edc9413080f28335ac78022139bfe7f7d6986eb1f76872bb91e82ad", - "0x949945072a08de6fd5838e9d2c3dc3200d048b5d21183020240fa13e71a1a8d30e6bfee4e6895e91d87b92f1444d0589", - "0xb351a03c7c98506ee92d7fb9476065839baa8ed8ac1dc250f5a095c0d4c8abcfab62690d29d001f0862672da29721f16", - "0xa82d81c136bc5e418d1fba614cb40a11f39dc526e66a8b1d7609f42fea4c02b63196315014400084f31f62c24b177cbd", - "0x87d51c907fdcdf528d01291b28adfee1e5b6221c6da68fd92ab66126247cd8086a6bcffff0ea17e7b57b0ba8d01bb95d", - "0xa2a9a1a91dfd918f36c1bfeeca705ea8e926ee012f8c18d633e50ec6e50f68f3380ef2ee839e5a43cf80fbb75bfb5304", - "0x86f22616caed13c9e9cd5568175b6b0a6a463f9a15c301b8766feca593efa6e5ee4c7066e1cd61b407c0be12b3d8236a", - "0xb57e0a2c42790d2fd0207ef6476a433fca0cf213d70840c4af1ad45833f23fca082d21a484f78af447a19a0b068ea55c", - "0x8ae9bda5d85e6e3600dde26379b7270abd088678098506b72196ac8f9ce5b0173bc9c7ff245c95cbab5b5b967bcb043b", - "0x95c7d11f6c874f59ba632b63ce07a7a9d917a74d0b89cefa043f52aa1a7fe2e81c38dea0b20378264b5b4f64039932bc", - "0xac7dee7479f50722526ea1c9d4e2f1a4578d1b5cce2092a07722069c96bb4da295de1c4f16e21005276e3b3f1624ac5a", - "0x89b8aaa49bd18b09f78fc5a1f3dd85d69b5dfcff28fc6d5a92b1520bc54107b8b71bb71fd6e0bde10e0a5809c633e5d2", - "0x8982cb43fe4d3488c55e8c08b935e6c8d31bb57e4f2aeb76d6319470cce99ebf7dc2f116ac15b9d845ab1bc16aa6a583", - "0xa12c63f48e27b1a1c83a32992642f37fb5b89851a35e80f6d1f9bc483cb25acd0e12b1dcf68781ae0cc861f002368bcb", - "0xaa6da92a4b4fa229afc8007abca257ce0ff5fad3b1ccfe5d836b9b52ff6b72575a0b915a759403b993733b16a47fdb15", - "0x8bf706a92fe54f15d633b9463926b874dd43e28aaeca3fe2353fb58ad7753c8a293c56b0e94176070e8a9ec7401073a1", - "0xb81e86de4bb5c1046e40cca79585c5b98c8673626fd3a28e563c5a3296256c2f7086522ae95cbabfaa8f1a8f7eae6272", - "0xad10f895b05d35cb251f78cc042d3f0969a8b6b3f289ddb4b016e0b8e06bfffc3a3e1afa9b0cc548f8c092832bb766bc", - "0xad993aceb68d5217cfb07f862956cde83d05dec5060fc7a8fbfd37c6bfd5429ba69bdaf478b6cd01c323a06793dcd9fa", - "0x83da9c9a8fcb2775df0777aceabe90642a2df1c6abc646566e954f42d6e43455b00b101ec5ef58850c8d4b3100222ca1", - "0xb55484f339fe7c7d107e70432601f4a34e1cc02ae4de5d18b99e5aa995f7b3710fc745769b85c1af803d457491dd8ce3", - "0x8009d80593e82f3e751cec9e7e495fd29ad6f45f8d3ae513bec998b43c49fed74c44229c6f27c421e80c65413b897644", - "0x9868081bbcc71192f7ff8dcf99a91dcd40f96556fbd6f285bdbfdfc785f604d8bf75c368c59db5ac8cdcc663087db53a", - "0xa04b1e91af025b4387ee0a2d790a1afb842e46f4c3717e355578efd1f84fea78782c6f7944b4961268de7f1ac71fb92b", - "0xa7b6301ddb9738b89b28a36d29d5323264a78d93d369f57ddab4cea399c36018a1fcc2cc1bfadf956a775124ae2925bd", - "0xa6cdb469014b33c590a07a728ce48f15f17c027eb92055e1858a1f9805c8deb58491a471aaa765de86a6bda62a18aef4", - "0x828a23280ec67384a8846376378896037bd0cb5a6927ff9422fca266ee10a6fde5b95d963a4acfa92efbb0309cdb17b4", - "0xb498ec16bcdb50091647ae02d199d70c783d7c91348a1354661b1c42bc1266e5a5309b542ef5fdf5281d426819a671cb", - "0x806533fb603e78b63598ff390375eebe5b68380640f5e020e89a5430037db2e519ab8ae5d0d0ad3fa041921c098448e1", - "0x9104ad119681c54cdee19f0db92ebfe1da2fa6bef4177f5a383df84512d1b0af5cbe7baf6a93ad4b89138cd51c7c5838", - "0xac695cde30d021d9f4f295109890c4013f7e213d2150c9d5c85a36d4abfdca4cdc88faee9891e927a82fc204b988dcd9", - "0xa311c244df546d5dc76eccb91fe4c47055fc9d222d310b974d4c067923a29e7a7f6d5a88bfef72fd6d317471f80d5c82", - "0x89e4518335240479ad041a0915fc4f1afaab660bd4033c5d09c6707f0cc963eb2e6872cabc4a02169893943be7f847d4", - "0xa8ad395b784c83aacf133de50d6b23bd63b4f245bb9e180c11f568faca4c897f8dbda73335ef0f80a8cb548a0c3c48fc" + "0xb5bfd7dd8cdeb128843bc287230af38926187075cbfbefa81009a2ce615ac53d2914e5870cb452d2afaaab24f3499f72185cbfee53492714734429b7b38608e23926c911cceceac9a36851477ba4c60b087041de621000edc98edada20c1def2", + "0xb5337ba0ce5d37224290916e268e2060e5c14f3f9fc9e1ec3af5a958e7a0303122500ce18f1a4640bf66525bd10e763501fe986d86649d8d45143c08c3209db3411802c226e9fe9a55716ac4a0c14f9dcef9e70b2bb309553880dc5025eab3cc", + "0xb3c1dcdc1f62046c786f0b82242ef283e7ed8f5626f72542aa2c7a40f14d9094dd1ebdbd7457ffdcdac45fd7da7e16c51200b06d791e5e43e257e45efdf0bd5b06cd2333beca2a3a84354eb48662d83aef5ecf4e67658c851c10b13d8d87c874", + "0x954d91c7688983382609fca9e211e461f488a5971fd4e40d7e2892037268eacdfd495cfa0a7ed6eb0eb11ac3ae6f651716757e7526abe1e06c64649d80996fd3105c20c4c94bc2b22d97045356fe9d791f21ea6428ac48db6f9e68e30d875280", + "0x88a6b6bb26c51cf9812260795523973bb90ce80f6820b6c9048ab366f0fb96e48437a7f7cb62aedf64b11eb4dfefebb0147608793133d32003cb1f2dc47b13b5ff45f1bb1b2408ea45770a08dbfaec60961acb8119c47b139a13b8641e2c9487", + "0x85cd7be9728bd925d12f47fb04b32d9fad7cab88788b559f053e69ca18e463113ecc8bbb6dbfb024835f901b3a957d3108d6770fb26d4c8be0a9a619f6e3a4bf15cbfd48e61593490885f6cee30e4300c5f9cf5e1c08e60a2d5b023ee94fcad0", + "0x80477dba360f04399821a48ca388c0fa81102dd15687fea792ee8c1114e00d1bc4839ad37ac58900a118d863723acfbe08126ea883be87f50e4eabe3b5e72f5d9e041db8d9b186409fd4df4a7dde38c0e0a3b1ae29b098e5697e7f110b6b27e4", + "0xb7a6aec08715a9f8672a2b8c367e407be37e59514ac19dd4f0942a68007bba3923df22da48702c63c0d6b3efd3c2d04e0fe042d8b5a54d562f9f33afc4865dcbcc16e99029e25925580e87920c399e710d438ac1ce3a6dc9b0d76c064a01f6f7", + "0xac1b001edcea02c8258aeffbf9203114c1c874ad88dae1184fadd7d94cd09053649efd0ca413400e6e9b5fa4eac33261000af88b6bd0d2abf877a4f0355d2fb4d6007adb181695201c5432e50b850b51b3969f893bddf82126c5a71b042b7686", + "0x90043fda4de53fb364fab2c04be5296c215599105ecff0c12e4917c549257125775c29f2507124d15f56e30447f367db0596c33237242c02d83dfd058735f1e3c1ff99069af55773b6d51d32a68bf75763f59ec4ee7267932ae426522b8aaab6", + "0xa8660ce853e9dc08271bf882e29cd53397d63b739584dda5263da4c7cc1878d0cf6f3e403557885f557e184700575fee016ee8542dec22c97befe1d10f414d22e84560741cdb3e74c30dda9b42eeaaf53e27822de2ee06e24e912bf764a9a533", + "0x8fe3921a96d0d065e8aa8fce9aa42c8e1461ca0470688c137be89396dd05103606dab6cdd2a4591efd6addf72026c12e065da7be276dee27a7e30afa2bd81c18f1516e7f068f324d0bad9570b95f6bd02c727cd2343e26db0887c3e4e26dceda", + "0x8ae1ad97dcb9c192c9a3933541b40447d1dc4eebf380151440bbaae1e120cc5cdf1bcea55180b128d8e180e3af623815191d063cc0d7a47d55fb7687b9d87040bf7bc1a7546b07c61db5ccf1841372d7c2fe4a5431ffff829f3c2eb590b0b710", + "0x8c2fa96870a88150f7876c931e2d3cc2adeaaaf5c73ef5fa1cf9dfa0991ae4819f9321af7e916e5057d87338e630a2f21242c29d76963cf26035b548d2a63d8ad7bd6efefa01c1df502cbdfdfe0334fb21ceb9f686887440f713bf17a89b8081", + "0xb9aa98e2f02bb616e22ee5dd74c7d1049321ac9214d093a738159850a1dbcc7138cb8d26ce09d8296368fd5b291d74fa17ac7cc1b80840fdd4ee35e111501e3fa8485b508baecda7c1ab7bd703872b7d64a2a40b3210b6a70e8a6ffe0e5127e3", + "0x9292db67f8771cdc86854a3f614a73805bf3012b48f1541e704ea4015d2b6b9c9aaed36419769c87c49f9e3165f03edb159c23b3a49c4390951f78e1d9b0ad997129b17cdb57ea1a6638794c0cca7d239f229e589c5ae4f9fe6979f7f8cba1d7", + "0x91cd9e86550f230d128664f7312591fee6a84c34f5fc7aed557bcf986a409a6de722c4330453a305f06911d2728626e611acfdf81284f77f60a3a1595053a9479964fd713117e27c0222cc679674b03bc8001501aaf9b506196c56de29429b46", + "0xa9516b73f605cc31b89c68b7675dc451e6364595243d235339437f556cf22d745d4250c1376182273be2d99e02c10eee047410a43eff634d051aeb784e76cb3605d8e079b9eb6ad1957dfdf77e1cd32ce4a573c9dfcc207ca65af6eb187f6c3d", + "0xa9667271f7d191935cc8ad59ef3ec50229945faea85bfdfb0d582090f524436b348aaa0183b16a6231c00332fdac2826125b8c857a2ed9ec66821cfe02b3a2279be2412441bc2e369b255eb98614e4be8490799c4df22f18d47d24ec70bba5f7", + "0xa4371144d2aa44d70d3cb9789096d3aa411149a6f800cb46f506461ee8363c8724667974252f28aea61b6030c05930ac039c1ee64bb4bd56532a685cae182bf2ab935eee34718cffcb46cae214c77aaca11dbb1320faf23c47247db1da04d8dc", + "0x89a7eb441892260b7e81168c386899cd84ffc4a2c5cad2eae0d1ab9e8b5524662e6f660fe3f8bfe4c92f60b060811bc605b14c5631d16709266886d7885a5eb5930097127ec6fb2ebbaf2df65909cf48f253b3d5e22ae48d3e9a2fd2b01f447e", + "0x9648c42ca97665b5eccb49580d8532df05eb5a68db07f391a2340769b55119eaf4c52fe4f650c09250fa78a76c3a1e271799b8333cc2628e3d4b4a6a3e03da1f771ecf6516dd63236574a7864ff07e319a6f11f153406280d63af9e2b5713283", + "0x9663bf6dd446ea7a90658ee458578d4196dc0b175ef7fcfa75f44d41670850774c2e46c5a6be132a2c072a3c0180a24f0305d1acac49d2d79878e5cda80c57feda3d01a6af12e78b5874e2a4b3717f11c97503b41a4474e2e95b179113726199", + "0xb212aeb4814e0915b432711b317923ed2b09e076aaf558c3ae8ef83f9e15a83f9ea3f47805b2750ab9e8106cb4dc6ad003522c84b03dc02829978a097899c773f6fb31f7fe6b8f2d836d96580f216fec20158f1590c3e0d7850622e15194db05", + "0x925f005059bf07e9ceccbe66c711b048e236ade775720d0fe479aebe6e23e8af281225ad18e62458dc1b03b42ad4ca290d4aa176260604a7aad0d9791337006fbdebe23746f8060d42876f45e4c83c3643931392fde1cd13ff8bddf8111ef974", + "0x9553edb22b4330c568e156a59ef03b26f5c326424f830fe3e8c0b602f08c124730ffc40bc745bec1a22417adb22a1a960243a10565c2be3066bfdb841d1cd14c624cd06e0008f4beb83f972ce6182a303bee3fcbcabc6cfe48ec5ae4b7941bfc", + "0x935f5a404f0a78bdcce709899eda0631169b366a669e9b58eacbbd86d7b5016d044b8dfc59ce7ed8de743ae16c2343b50e2f925e88ba6319e33c3fc76b314043abad7813677b4615c8a97eb83cc79de4fedf6ccbcfa4d4cbf759a5a84e4d9742", + "0xa5b014ab936eb4be113204490e8b61cd38d71da0dec7215125bcd131bf3ab22d0a32ce645bca93e7b3637cf0c2db3d6601a0ddd330dc46f9fae82abe864ffc12d656c88eb50c20782e5bb6f75d18760666f43943abb644b881639083e122f557", + "0x935b7298ae52862fa22bf03bfc1795b34c70b181679ae27de08a9f5b4b884f824ef1b276b7600efa0d2f1d79e4a470d51692fd565c5cf8343dd80e5d3336968fc21c09ba9348590f6206d4424eb229e767547daefa98bc3aa9f421158dee3f2a", + "0x9830f92446e708a8f6b091cc3c38b653505414f8b6507504010a96ffda3bcf763d5331eb749301e2a1437f00e2415efb01b799ad4c03f4b02de077569626255ac1165f96ea408915d4cf7955047620da573e5c439671d1fa5c833fb11de7afe6", + "0x840dcc44f673fff3e387af2bb41e89640f2a70bcd2b92544876daa92143f67c7512faf5f90a04b7191de01f3e2b1bde00622a20dc62ca23bbbfaa6ad220613deff43908382642d4d6a86999f662efd64b1df448b68c847cfa87630a3ffd2ec76", + "0x92950c895ed54f7f876b2fda17ecc9c41b7accfbdd42c210cc5b475e0737a7279f558148531b5c916e310604a1de25a80940c94fe5389ae5d6a5e9c371be67bceea1877f5401725a6595bcf77ece60905151b6dfcb68b75ed2e708c73632f4fd", + "0x8010246bf8e94c25fd029b346b5fbadb404ef6f44a58fd9dd75acf62433d8cc6db66974f139a76e0c26dddc1f329a88214dbb63276516cf325c7869e855d07e0852d622c332ac55609ba1ec9258c45746a2aeb1af0800141ee011da80af175d4", + "0xb0f1bad257ebd187bdc3f37b23f33c6a5d6a8e1f2de586080d6ada19087b0e2bf23b79c1b6da1ee82271323f5bdf3e1b018586b54a5b92ab6a1a16bb3315190a3584a05e6c37d5ca1e05d702b9869e27f513472bcdd00f4d0502a107773097da", + "0x9636d24f1ede773ce919f309448dd7ce023f424afd6b4b69cb98c2a988d849a283646dc3e469879daa1b1edae91ae41f009887518e7eb5578f88469321117303cd3ac2d7aee4d9cb5f82ab9ae3458e796dfe7c24284b05815acfcaa270ff22e2", + "0xb373feb5d7012fd60578d7d00834c5c81df2a23d42794fed91aa9535a4771fde0341c4da882261785e0caca40bf83405143085e7f17e55b64f6c5c809680c20b050409bf3702c574769127c854d27388b144b05624a0e24a1cbcc4d08467005b", + "0xb15680648949ce69f82526e9b67d9b55ce5c537dc6ab7f3089091a9a19a6b90df7656794f6edc87fb387d21573ffc847062623685931c2790a508cbc8c6b231dd2c34f4d37d4706237b1407673605a604bcf6a50cc0b1a2db20485e22b02c17e", + "0x8817e46672d40c8f748081567b038a3165f87994788ec77ee8daea8587f5540df3422f9e120e94339be67f186f50952504cb44f61e30a5241f1827e501b2de53c4c64473bcc79ab887dd277f282fbfe47997a930dd140ac08b03efac88d81075", + "0xa6e4ef6c1d1098f95aae119905f87eb49b909d17f9c41bcfe51127aa25fee20782ea884a7fdf7d5e9c245b5a5b32230b07e0dbf7c6743bf52ee20e2acc0b269422bd6cf3c07115df4aa85b11b2c16630a07c974492d9cdd0ec325a3fabd95044", + "0x8634aa7c3d00e7f17150009698ce440d8e1b0f13042b624a722ace68ead870c3d2212fbee549a2c190e384d7d6ac37ce14ab962c299ea1218ef1b1489c98906c91323b94c587f1d205a6edd5e9d05b42d591c26494a6f6a029a2aadb5f8b6f67", + "0x821a58092900bdb73decf48e13e7a5012a3f88b06288a97b855ef51306406e7d867d613d9ec738ebacfa6db344b677d21509d93f3b55c2ebf3a2f2a6356f875150554c6fff52e62e3e46f7859be971bf7dd9d5b3e1d799749c8a97c2e04325df", + "0x8dba356577a3a388f782e90edb1a7f3619759f4de314ad5d95c7cc6e197211446819c4955f99c5fc67f79450d2934e3c09adefc91b724887e005c5190362245eec48ce117d0a94d6fa6db12eda4ba8dde608fbbd0051f54dcf3bb057adfb2493", + "0xa32a690dc95c23ed9fb46443d9b7d4c2e27053a7fcc216d2b0020a8cf279729c46114d2cda5772fd60a97016a07d6c5a0a7eb085a18307d34194596f5b541cdf01b2ceb31d62d6b55515acfd2b9eec92b27d082fbc4dc59fc63b551eccdb8468", + "0xa040f7f4be67eaf0a1d658a3175d65df21a7dbde99bfa893469b9b43b9d150fc2e333148b1cb88cfd0447d88fa1a501d126987e9fdccb2852ecf1ba907c2ca3d6f97b055e354a9789854a64ecc8c2e928382cf09dda9abde42bbdf92280cdd96", + "0x864baff97fa60164f91f334e0c9be00a152a416556b462f96d7c43b59fe1ebaff42f0471d0bf264976f8aa6431176eb905bd875024cf4f76c13a70bede51dc3e47e10b9d5652d30d2663b3af3f08d5d11b9709a0321aba371d2ef13174dcfcaf", + "0x95a46f32c994133ecc22db49bad2c36a281d6b574c83cfee6680b8c8100466ca034b815cfaedfbf54f4e75188e661df901abd089524e1e0eb0bf48d48caa9dd97482d2e8c1253e7e8ac250a32fd066d5b5cb08a8641bdd64ecfa48289dca83a3", + "0xa2cce2be4d12144138cb91066e0cd0542c80b478bf467867ebef9ddaf3bd64e918294043500bf5a9f45ee089a8d6ace917108d9ce9e4f41e7e860cbce19ac52e791db3b6dde1c4b0367377b581f999f340e1d6814d724edc94cb07f9c4730774", + "0xb145f203eee1ac0a1a1731113ffa7a8b0b694ef2312dabc4d431660f5e0645ef5838e3e624cfe1228cfa248d48b5760501f93e6ab13d3159fc241427116c4b90359599a4cb0a86d0bb9190aa7fabff482c812db966fd2ce0a1b48cb8ac8b3bca", + "0xadabe5d215c608696e03861cbd5f7401869c756b3a5aadc55f41745ad9478145d44393fec8bb6dfc4ad9236dc62b9ada0f7ca57fe2bae1b71565dbf9536d33a68b8e2090b233422313cc96afc7f1f7e0907dc7787806671541d6de8ce47c4cd0", + "0xae7845fa6b06db53201c1080e01e629781817f421f28956589c6df3091ec33754f8a4bd4647a6bb1c141ac22731e3c1014865d13f3ed538dcb0f7b7576435133d9d03be655f8fbb4c9f7d83e06d1210aedd45128c2b0c9bab45a9ddde1c862a5", + "0x9159eaa826a24adfa7adf6e8d2832120ebb6eccbeb3d0459ffdc338548813a2d239d22b26451fda98cc0c204d8e1ac69150b5498e0be3045300e789bcb4e210d5cd431da4bdd915a21f407ea296c20c96608ded0b70d07188e96e6c1a7b9b86b", + "0xa9fc6281e2d54b46458ef564ffaed6944bff71e389d0acc11fa35d3fcd8e10c1066e0dde5b9b6516f691bb478e81c6b20865281104dcb640e29dc116daae2e884f1fe6730d639dbe0e19a532be4fb337bf52ae8408446deb393d224eee7cfa50", + "0x84291a42f991bfb36358eedead3699d9176a38f6f63757742fdbb7f631f2c70178b1aedef4912fed7b6cf27e88ddc7eb0e2a6aa4b999f3eb4b662b93f386c8d78e9ac9929e21f4c5e63b12991fcde93aa64a735b75b535e730ff8dd2abb16e04", + "0xa1b7fcacae181495d91765dfddf26581e8e39421579c9cbd0dd27a40ea4c54af3444a36bf85a11dda2114246eaddbdd619397424bb1eb41b5a15004b902a590ede5742cd850cf312555be24d2df8becf48f5afba5a8cd087cb7be0a521728386", + "0x92feaaf540dbd84719a4889a87cdd125b7e995a6782911931fef26da9afcfbe6f86aaf5328fe1f77631491ce6239c5470f44c7791506c6ef1626803a5794e76d2be0af92f7052c29ac6264b7b9b51f267ad820afc6f881460521428496c6a5f1", + "0xa525c925bfae1b89320a5054acc1fa11820f73d0cf28d273092b305467b2831fab53b6daf75fb926f332782d50e2522a19edcd85be5eb72f1497193c952d8cd0bcc5d43b39363b206eae4cb1e61668bde28a3fb2fc1e0d3d113f6dfadb799717", + "0x98752bb6f5a44213f40eda6aa4ff124057c1b13b6529ab42fe575b9afa66e59b9c0ed563fb20dff62130c436c3e905ee17dd8433ba02c445b1d67182ab6504a90bbe12c26a754bbf734665c622f76c62fe2e11dd43ce04fd2b91a8463679058b", + "0xa9aa9a84729f7c44219ff9e00e651e50ddea3735ef2a73fdf8ed8cd271961d8ed7af5cd724b713a89a097a3fe65a3c0202f69458a8b4c157c62a85668b12fc0d3957774bc9b35f86c184dd03bfefd5c325da717d74192cc9751c2073fe9d170e", + "0xb221c1fd335a4362eff504cd95145f122bf93ea02ae162a3fb39c75583fc13a932d26050e164da97cff3e91f9a7f6ff80302c19dd1916f24acf6b93b62f36e9665a8785413b0c7d930c7f1668549910f849bca319b00e59dd01e5dec8d2edacc", + "0xa71e2b1e0b16d754b848f05eda90f67bedab37709550171551050c94efba0bfc282f72aeaaa1f0330041461f5e6aa4d11537237e955e1609a469d38ed17f5c2a35a1752f546db89bfeff9eab78ec944266f1cb94c1db3334ab48df716ce408ef", + "0xb990ae72768779ba0b2e66df4dd29b3dbd00f901c23b2b4a53419226ef9232acedeb498b0d0687c463e3f1eead58b20b09efcefa566fbfdfe1c6e48d32367936142d0a734143e5e63cdf86be7457723535b787a9cfcfa32fe1d61ad5a2617220", + "0x8d27e7fbff77d5b9b9bbc864d5231fecf817238a6433db668d5a62a2c1ee1e5694fdd90c3293c06cc0cb15f7cbeab44d0d42be632cb9ff41fc3f6628b4b62897797d7b56126d65b694dcf3e298e3561ac8813fbd7296593ced33850426df42db", + "0xa92039a08b5502d5b211a7744099c9f93fa8c90cedcb1d05e92f01886219dd464eb5fb0337496ad96ed09c987da4e5f019035c5b01cc09b2a18b8a8dd419bc5895388a07e26958f6bd26751929c25f89b8eb4a299d822e2d26fec9ef350e0d3c", + "0x92dcc5a1c8c3e1b28b1524e3dd6dbecd63017c9201da9dbe077f1b82adc08c50169f56fc7b5a3b28ec6b89254de3e2fd12838a761053437883c3e01ba616670cea843754548ef84bcc397de2369adcca2ab54cd73c55dc68d87aec3fc2fe4f10" ] } \ No newline at end of file diff --git a/crypto/secp256k1/secp256.go b/crypto/secp256k1/secp256.go index c9c01b3209af..61abc1eaf04b 100644 --- a/crypto/secp256k1/secp256.go +++ b/crypto/secp256k1/secp256.go @@ -21,11 +21,14 @@ package secp256k1 # define USE_SCALAR_8X32 #endif +#ifndef NDEBUG +# define NDEBUG +#endif + #define USE_ENDOMORPHISM #define USE_NUM_NONE #define USE_FIELD_INV_BUILTIN #define USE_SCALAR_INV_BUILTIN -#define NDEBUG #include "./libsecp256k1/src/secp256k1.c" #include "./libsecp256k1/src/modules/recovery/main_impl.h" #include "ext.h" diff --git a/eth/api.go b/eth/api.go index b2da27814d20..44e934fd040b 100644 --- a/eth/api.go +++ b/eth/api.go @@ -17,27 +17,8 @@ package eth import ( - "compress/gzip" - "context" - "errors" - "fmt" - "io" - "math/big" - "os" - "strings" - "time" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/internal/ethapi" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/trie" ) // EthereumAPI provides an API to access Ethereum full node-related information. @@ -50,12 +31,12 @@ func NewEthereumAPI(e *Ethereum) *EthereumAPI { return &EthereumAPI{e} } -// Etherbase is the address that mining rewards will be send to. +// Etherbase is the address that mining rewards will be sent to. func (api *EthereumAPI) Etherbase() (common.Address, error) { return api.e.Etherbase() } -// Coinbase is the address that mining rewards will be send to (alias for Etherbase). +// Coinbase is the address that mining rewards will be sent to (alias for Etherbase). func (api *EthereumAPI) Coinbase() (common.Address, error) { return api.Etherbase() } @@ -69,552 +50,3 @@ func (api *EthereumAPI) Hashrate() hexutil.Uint64 { func (api *EthereumAPI) Mining() bool { return api.e.IsMining() } - -// MinerAPI provides an API to control the miner. -type MinerAPI struct { - e *Ethereum -} - -// NewMinerAPI create a new MinerAPI instance. -func NewMinerAPI(e *Ethereum) *MinerAPI { - return &MinerAPI{e} -} - -// Start starts the miner with the given number of threads. If threads is nil, -// the number of workers started is equal to the number of logical CPUs that are -// usable by this process. If mining is already running, this method adjust the -// number of threads allowed to use and updates the minimum price required by the -// transaction pool. -func (api *MinerAPI) Start() error { - return api.e.StartMining() -} - -// Stop terminates the miner, both at the consensus engine level as well as at -// the block creation level. -func (api *MinerAPI) Stop() { - api.e.StopMining() -} - -// SetExtra sets the extra data string that is included when this miner mines a block. -func (api *MinerAPI) SetExtra(extra string) (bool, error) { - if err := api.e.Miner().SetExtra([]byte(extra)); err != nil { - return false, err - } - return true, nil -} - -// SetGasPrice sets the minimum accepted gas price for the miner. -func (api *MinerAPI) SetGasPrice(gasPrice hexutil.Big) bool { - api.e.lock.Lock() - api.e.gasPrice = (*big.Int)(&gasPrice) - api.e.lock.Unlock() - - api.e.txPool.SetGasPrice((*big.Int)(&gasPrice)) - return true -} - -// SetGasLimit sets the gaslimit to target towards during mining. -func (api *MinerAPI) SetGasLimit(gasLimit hexutil.Uint64) bool { - api.e.Miner().SetGasCeil(uint64(gasLimit)) - return true -} - -// SetEtherbase sets the etherbase of the miner. -func (api *MinerAPI) SetEtherbase(etherbase common.Address) bool { - api.e.SetEtherbase(etherbase) - return true -} - -// SetRecommitInterval updates the interval for miner sealing work recommitting. -func (api *MinerAPI) SetRecommitInterval(interval int) { - api.e.Miner().SetRecommitInterval(time.Duration(interval) * time.Millisecond) -} - -// AdminAPI is the collection of Ethereum full node related APIs for node -// administration. -type AdminAPI struct { - eth *Ethereum -} - -// NewAdminAPI creates a new instance of AdminAPI. -func NewAdminAPI(eth *Ethereum) *AdminAPI { - return &AdminAPI{eth: eth} -} - -// ExportChain exports the current blockchain into a local file, -// or a range of blocks if first and last are non-nil. -func (api *AdminAPI) ExportChain(file string, first *uint64, last *uint64) (bool, error) { - if first == nil && last != nil { - return false, errors.New("last cannot be specified without first") - } - if first != nil && last == nil { - head := api.eth.BlockChain().CurrentHeader().Number.Uint64() - last = &head - } - if _, err := os.Stat(file); err == nil { - // File already exists. Allowing overwrite could be a DoS vector, - // since the 'file' may point to arbitrary paths on the drive. - return false, errors.New("location would overwrite an existing file") - } - // Make sure we can create the file to export into - out, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) - if err != nil { - return false, err - } - defer out.Close() - - var writer io.Writer = out - if strings.HasSuffix(file, ".gz") { - writer = gzip.NewWriter(writer) - defer writer.(*gzip.Writer).Close() - } - - // Export the blockchain - if first != nil { - if err := api.eth.BlockChain().ExportN(writer, *first, *last); err != nil { - return false, err - } - } else if err := api.eth.BlockChain().Export(writer); err != nil { - return false, err - } - return true, nil -} - -func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool { - for _, b := range bs { - if !chain.HasBlock(b.Hash(), b.NumberU64()) { - return false - } - } - - return true -} - -// ImportChain imports a blockchain from a local file. -func (api *AdminAPI) ImportChain(file string) (bool, error) { - // Make sure the can access the file to import - in, err := os.Open(file) - if err != nil { - return false, err - } - defer in.Close() - - var reader io.Reader = in - if strings.HasSuffix(file, ".gz") { - if reader, err = gzip.NewReader(reader); err != nil { - return false, err - } - } - - // Run actual the import in pre-configured batches - stream := rlp.NewStream(reader, 0) - - blocks, index := make([]*types.Block, 0, 2500), 0 - for batch := 0; ; batch++ { - // Load a batch of blocks from the input file - for len(blocks) < cap(blocks) { - block := new(types.Block) - if err := stream.Decode(block); err == io.EOF { - break - } else if err != nil { - return false, fmt.Errorf("block %d: failed to parse: %v", index, err) - } - blocks = append(blocks, block) - index++ - } - if len(blocks) == 0 { - break - } - - if hasAllBlocks(api.eth.BlockChain(), blocks) { - blocks = blocks[:0] - continue - } - // Import the batch and reset the buffer - if _, err := api.eth.BlockChain().InsertChain(blocks); err != nil { - return false, fmt.Errorf("batch %d: failed to insert: %v", batch, err) - } - blocks = blocks[:0] - } - return true, nil -} - -// DebugAPI is the collection of Ethereum full node APIs for debugging the -// protocol. -type DebugAPI struct { - eth *Ethereum -} - -// NewDebugAPI creates a new DebugAPI instance. -func NewDebugAPI(eth *Ethereum) *DebugAPI { - return &DebugAPI{eth: eth} -} - -// DumpBlock retrieves the entire state of the database at a given block. -func (api *DebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) { - opts := &state.DumpConfig{ - OnlyWithAddresses: true, - Max: AccountRangeMaxResults, // Sanity limit over RPC - } - // arbitrum: in case of ArbEthereum, miner in not available here - // use current block instead of pending - if blockNr == rpc.PendingBlockNumber && api.eth.miner == nil { - blockNr = rpc.LatestBlockNumber - } - if blockNr == rpc.PendingBlockNumber { - // If we're dumping the pending state, we need to request - // both the pending block as well as the pending state from - // the miner and operate on those - _, stateDb := api.eth.miner.Pending() - return stateDb.RawDump(opts), nil - } - var header *types.Header - if blockNr == rpc.LatestBlockNumber { - header = api.eth.blockchain.CurrentBlock() - } else if blockNr == rpc.FinalizedBlockNumber { - header = api.eth.blockchain.CurrentFinalBlock() - } else if blockNr == rpc.SafeBlockNumber { - header = api.eth.blockchain.CurrentSafeBlock() - } else { - block := api.eth.blockchain.GetBlockByNumber(uint64(blockNr)) - if block == nil { - return state.Dump{}, fmt.Errorf("block #%d not found", blockNr) - } - header = block.Header() - } - if header == nil { - return state.Dump{}, fmt.Errorf("block #%d not found", blockNr) - } - stateDb, err := api.eth.BlockChain().StateAt(header.Root) - if err != nil { - return state.Dump{}, err - } - return stateDb.RawDump(opts), nil -} - -// Preimage is a debug API function that returns the preimage for a sha3 hash, if known. -func (api *DebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { - if preimage := rawdb.ReadPreimage(api.eth.ChainDb(), hash); preimage != nil { - return preimage, nil - } - return nil, errors.New("unknown preimage") -} - -// BadBlockArgs represents the entries in the list returned when bad blocks are queried. -type BadBlockArgs struct { - Hash common.Hash `json:"hash"` - Block map[string]interface{} `json:"block"` - RLP string `json:"rlp"` -} - -// GetBadBlocks returns a list of the last 'bad blocks' that the client has seen on the network -// and returns them as a JSON list of block hashes. -func (api *DebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error) { - var ( - err error - blocks = rawdb.ReadAllBadBlocks(api.eth.chainDb) - results = make([]*BadBlockArgs, 0, len(blocks)) - ) - for _, block := range blocks { - var ( - blockRlp string - blockJSON map[string]interface{} - ) - if rlpBytes, err := rlp.EncodeToBytes(block); err != nil { - blockRlp = err.Error() // Hacky, but hey, it works - } else { - blockRlp = fmt.Sprintf("%#x", rlpBytes) - } - if blockJSON, err = ethapi.RPCMarshalBlock(block, true, true, api.eth.blockchain.Config()); err != nil { - blockJSON = map[string]interface{}{"error": err.Error()} - } - results = append(results, &BadBlockArgs{ - Hash: block.Hash(), - RLP: blockRlp, - Block: blockJSON, - }) - } - return results, nil -} - -// AccountRangeMaxResults is the maximum number of results to be returned per call -const AccountRangeMaxResults = 256 - -// AccountRange enumerates all accounts in the given block and start point in paging request -func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hexutil.Bytes, maxResults int, nocode, nostorage, incompletes bool) (state.IteratorDump, error) { - var stateDb *state.StateDB - var err error - - if number, ok := blockNrOrHash.Number(); ok { - // arbitrum: in case of ArbEthereum, miner in not available here - // use current block instead of pending - if number == rpc.PendingBlockNumber && api.eth.miner == nil { - number = rpc.LatestBlockNumber - } - if number == rpc.PendingBlockNumber { - // If we're dumping the pending state, we need to request - // both the pending block as well as the pending state from - // the miner and operate on those - _, stateDb = api.eth.miner.Pending() - } else { - var header *types.Header - if number == rpc.LatestBlockNumber { - header = api.eth.blockchain.CurrentBlock() - } else if number == rpc.FinalizedBlockNumber { - header = api.eth.blockchain.CurrentFinalBlock() - } else if number == rpc.SafeBlockNumber { - header = api.eth.blockchain.CurrentSafeBlock() - } else { - block := api.eth.blockchain.GetBlockByNumber(uint64(number)) - if block == nil { - return state.IteratorDump{}, fmt.Errorf("block #%d not found", number) - } - header = block.Header() - } - if header == nil { - return state.IteratorDump{}, fmt.Errorf("block #%d not found", number) - } - stateDb, err = api.eth.BlockChain().StateAt(header.Root) - if err != nil { - return state.IteratorDump{}, err - } - } - } else if hash, ok := blockNrOrHash.Hash(); ok { - block := api.eth.blockchain.GetBlockByHash(hash) - if block == nil { - return state.IteratorDump{}, fmt.Errorf("block %s not found", hash.Hex()) - } - stateDb, err = api.eth.BlockChain().StateAt(block.Root()) - if err != nil { - return state.IteratorDump{}, err - } - } else { - return state.IteratorDump{}, errors.New("either block number or block hash must be specified") - } - - opts := &state.DumpConfig{ - SkipCode: nocode, - SkipStorage: nostorage, - OnlyWithAddresses: !incompletes, - Start: start, - Max: uint64(maxResults), - } - if maxResults > AccountRangeMaxResults || maxResults <= 0 { - opts.Max = AccountRangeMaxResults - } - return stateDb.IteratorDump(opts), nil -} - -// StorageRangeResult is the result of a debug_storageRangeAt API call. -type StorageRangeResult struct { - Storage storageMap `json:"storage"` - NextKey *common.Hash `json:"nextKey"` // nil if Storage includes the last key in the trie. -} - -type storageMap map[common.Hash]storageEntry - -type storageEntry struct { - Key *common.Hash `json:"key"` - Value common.Hash `json:"value"` -} - -// StorageRangeAt returns the storage at the given block height and transaction index. -func (api *DebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) { - // Retrieve the block - block := api.eth.blockchain.GetBlockByHash(blockHash) - if block == nil { - return StorageRangeResult{}, fmt.Errorf("block %#x not found", blockHash) - } - _, _, statedb, release, err := api.eth.stateAtTransaction(ctx, block, txIndex, 0) - if err != nil { - return StorageRangeResult{}, err - } - defer release() - - st, err := statedb.StorageTrie(contractAddress) - if err != nil { - return StorageRangeResult{}, err - } - if st == nil { - return StorageRangeResult{}, fmt.Errorf("account %x doesn't exist", contractAddress) - } - return storageRangeAt(st, keyStart, maxResult) -} - -func storageRangeAt(st state.Trie, start []byte, maxResult int) (StorageRangeResult, error) { - it := trie.NewIterator(st.NodeIterator(start)) - result := StorageRangeResult{Storage: storageMap{}} - for i := 0; i < maxResult && it.Next(); i++ { - _, content, _, err := rlp.Split(it.Value) - if err != nil { - return StorageRangeResult{}, err - } - e := storageEntry{Value: common.BytesToHash(content)} - if preimage := st.GetKey(it.Key); preimage != nil { - preimage := common.BytesToHash(preimage) - e.Key = &preimage - } - result.Storage[common.BytesToHash(it.Key)] = e - } - // Add the 'next key' so clients can continue downloading. - if it.Next() { - next := common.BytesToHash(it.Key) - result.NextKey = &next - } - return result, nil -} - -// GetModifiedAccountsByNumber returns all accounts that have changed between the -// two blocks specified. A change is defined as a difference in nonce, balance, -// code hash, or storage hash. -// -// With one parameter, returns the list of accounts modified in the specified block. -func (api *DebugAPI) GetModifiedAccountsByNumber(startNum uint64, endNum *uint64) ([]common.Address, error) { - var startBlock, endBlock *types.Block - - startBlock = api.eth.blockchain.GetBlockByNumber(startNum) - if startBlock == nil { - return nil, fmt.Errorf("start block %x not found", startNum) - } - - if endNum == nil { - endBlock = startBlock - startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash()) - if startBlock == nil { - return nil, fmt.Errorf("block %x has no parent", endBlock.Number()) - } - } else { - endBlock = api.eth.blockchain.GetBlockByNumber(*endNum) - if endBlock == nil { - return nil, fmt.Errorf("end block %d not found", *endNum) - } - } - return api.getModifiedAccounts(startBlock, endBlock) -} - -// GetModifiedAccountsByHash returns all accounts that have changed between the -// two blocks specified. A change is defined as a difference in nonce, balance, -// code hash, or storage hash. -// -// With one parameter, returns the list of accounts modified in the specified block. -func (api *DebugAPI) GetModifiedAccountsByHash(startHash common.Hash, endHash *common.Hash) ([]common.Address, error) { - var startBlock, endBlock *types.Block - startBlock = api.eth.blockchain.GetBlockByHash(startHash) - if startBlock == nil { - return nil, fmt.Errorf("start block %x not found", startHash) - } - - if endHash == nil { - endBlock = startBlock - startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash()) - if startBlock == nil { - return nil, fmt.Errorf("block %x has no parent", endBlock.Number()) - } - } else { - endBlock = api.eth.blockchain.GetBlockByHash(*endHash) - if endBlock == nil { - return nil, fmt.Errorf("end block %x not found", *endHash) - } - } - return api.getModifiedAccounts(startBlock, endBlock) -} - -func (api *DebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]common.Address, error) { - if startBlock.Number().Uint64() >= endBlock.Number().Uint64() { - return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64()) - } - triedb := api.eth.BlockChain().StateCache().TrieDB() - - oldTrie, err := trie.NewStateTrie(trie.StateTrieID(startBlock.Root()), triedb) - if err != nil { - return nil, err - } - newTrie, err := trie.NewStateTrie(trie.StateTrieID(endBlock.Root()), triedb) - if err != nil { - return nil, err - } - diff, _ := trie.NewDifferenceIterator(oldTrie.NodeIterator([]byte{}), newTrie.NodeIterator([]byte{})) - iter := trie.NewIterator(diff) - - var dirty []common.Address - for iter.Next() { - key := newTrie.GetKey(iter.Key) - if key == nil { - return nil, fmt.Errorf("no preimage found for hash %x", iter.Key) - } - dirty = append(dirty, common.BytesToAddress(key)) - } - return dirty, nil -} - -// GetAccessibleState returns the first number where the node has accessible -// state on disk. Note this being the post-state of that block and the pre-state -// of the next block. -// The (from, to) parameters are the sequence of blocks to search, which can go -// either forwards or backwards -func (api *DebugAPI) GetAccessibleState(from, to rpc.BlockNumber) (uint64, error) { - db := api.eth.ChainDb() - var pivot uint64 - if p := rawdb.ReadLastPivotNumber(db); p != nil { - pivot = *p - log.Info("Found fast-sync pivot marker", "number", pivot) - } - var resolveNum = func(num rpc.BlockNumber) (uint64, error) { - // We don't have state for pending (-2), so treat it as latest - if num.Int64() < 0 { - block := api.eth.blockchain.CurrentBlock() - if block == nil { - return 0, errors.New("current block missing") - } - return block.Number.Uint64(), nil - } - return uint64(num.Int64()), nil - } - var ( - start uint64 - end uint64 - delta = int64(1) - lastLog time.Time - err error - ) - if start, err = resolveNum(from); err != nil { - return 0, err - } - if end, err = resolveNum(to); err != nil { - return 0, err - } - if start == end { - return 0, errors.New("from and to needs to be different") - } - if start > end { - delta = -1 - } - for i := int64(start); i != int64(end); i += delta { - if time.Since(lastLog) > 8*time.Second { - log.Info("Finding roots", "from", start, "to", end, "at", i) - lastLog = time.Now() - } - if i < int64(pivot) { - continue - } - h := api.eth.BlockChain().GetHeaderByNumber(uint64(i)) - if h == nil { - return 0, fmt.Errorf("missing header %d", i) - } - if ok, _ := api.eth.ChainDb().Has(h.Root[:]); ok { - return uint64(i), nil - } - } - return 0, errors.New("no state found") -} - -// SetTrieFlushInterval configures how often in-memory tries are persisted -// to disk. The value is in terms of block processing time, not wall clock. -func (api *DebugAPI) SetTrieFlushInterval(interval string) error { - t, err := time.ParseDuration(interval) - if err != nil { - return err - } - api.eth.blockchain.SetTrieFlushInterval(t) - return nil -} diff --git a/eth/api_admin.go b/eth/api_admin.go new file mode 100644 index 000000000000..9620cf1b0ab2 --- /dev/null +++ b/eth/api_admin.go @@ -0,0 +1,145 @@ +// Copyright 2023 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 eth + +import ( + "compress/gzip" + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// AdminAPI is the collection of Ethereum full node related APIs for node +// administration. +type AdminAPI struct { + eth *Ethereum +} + +// NewAdminAPI creates a new instance of AdminAPI. +func NewAdminAPI(eth *Ethereum) *AdminAPI { + return &AdminAPI{eth: eth} +} + +// ExportChain exports the current blockchain into a local file, +// or a range of blocks if first and last are non-nil. +func (api *AdminAPI) ExportChain(file string, first *uint64, last *uint64) (bool, error) { + if first == nil && last != nil { + return false, errors.New("last cannot be specified without first") + } + if first != nil && last == nil { + head := api.eth.BlockChain().CurrentHeader().Number.Uint64() + last = &head + } + if _, err := os.Stat(file); err == nil { + // File already exists. Allowing overwrite could be a DoS vector, + // since the 'file' may point to arbitrary paths on the drive. + return false, errors.New("location would overwrite an existing file") + } + // Make sure we can create the file to export into + out, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + return false, err + } + defer out.Close() + + var writer io.Writer = out + if strings.HasSuffix(file, ".gz") { + writer = gzip.NewWriter(writer) + defer writer.(*gzip.Writer).Close() + } + + // Export the blockchain + if first != nil { + if err := api.eth.BlockChain().ExportN(writer, *first, *last); err != nil { + return false, err + } + } else if err := api.eth.BlockChain().Export(writer); err != nil { + return false, err + } + return true, nil +} + +func hasAllBlocks(chain *core.BlockChain, bs []*types.Block) bool { + for _, b := range bs { + if !chain.HasBlock(b.Hash(), b.NumberU64()) { + return false + } + } + + return true +} + +// ImportChain imports a blockchain from a local file. +func (api *AdminAPI) ImportChain(file string) (bool, error) { + // Make sure the can access the file to import + in, err := os.Open(file) + if err != nil { + return false, err + } + defer in.Close() + + var reader io.Reader = in + if strings.HasSuffix(file, ".gz") { + if reader, err = gzip.NewReader(reader); err != nil { + return false, err + } + } + + // Run actual the import in pre-configured batches + stream := rlp.NewStream(reader, 0) + + chainConfig := api.eth.blockchain.Config() + + blocks, index := make([]*types.Block, 0, 2500), 0 + for batch := 0; ; batch++ { + // Load a batch of blocks from the input file + for len(blocks) < cap(blocks) { + block := new(types.Block) + if err := stream.Decode(block); err == io.EOF { + break + } else if err != nil { + return false, fmt.Errorf("block %d: failed to parse: %v", index, err) + } + // ignore the genesis block when importing blocks + if block.NumberU64() <= chainConfig.ArbitrumChainParams.GenesisBlockNum { + continue + } + blocks = append(blocks, block) + index++ + } + if len(blocks) == 0 { + break + } + + if hasAllBlocks(api.eth.BlockChain(), blocks) { + blocks = blocks[:0] + continue + } + // Import the batch and reset the buffer + if _, err := api.eth.BlockChain().InsertChain(blocks); err != nil { + return false, fmt.Errorf("batch %d: failed to insert: %v", batch, err) + } + blocks = blocks[:0] + } + return true, nil +} diff --git a/eth/api_backend.go b/eth/api_backend.go index 10d4259eac91..7f88147df5dd 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -42,7 +42,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -// EthAPIBackend implements ethapi.Backend for full nodes +// EthAPIBackend implements ethapi.Backend and tracers.Backend for full nodes type EthAPIBackend struct { extRPCEnabled bool allowUnprotectedTxs bool @@ -68,6 +68,9 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb // Pending block is only known by the miner if number == rpc.PendingBlockNumber { block := b.eth.miner.PendingBlock() + if block == nil { + return nil, errors.New("pending block is not available") + } return block.Header(), nil } // Otherwise resolve and return the block @@ -75,24 +78,18 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb return b.eth.blockchain.CurrentBlock(), nil } if number == rpc.FinalizedBlockNumber { - if !b.eth.Merger().TDDReached() { - return nil, errors.New("'finalized' tag not supported on pre-merge network") - } block := b.eth.blockchain.CurrentFinalBlock() - if block != nil { - return block, nil + if block == nil { + return nil, errors.New("finalized block not found") } - return nil, errors.New("finalized block not found") + return block, nil } if number == rpc.SafeBlockNumber { - if !b.eth.Merger().TDDReached() { - return nil, errors.New("'safe' tag not supported on pre-merge network") - } block := b.eth.blockchain.CurrentSafeBlock() - if block != nil { - return block, nil + if block == nil { + return nil, errors.New("safe block not found") } - return nil, errors.New("safe block not found") + return block, nil } return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil } @@ -122,6 +119,9 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe // Pending block is only known by the miner if number == rpc.PendingBlockNumber { block := b.eth.miner.PendingBlock() + if block == nil { + return nil, errors.New("pending block is not available") + } return block, nil } // Otherwise resolve and return the block @@ -130,9 +130,6 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil } if number == rpc.FinalizedBlockNumber { - if !b.eth.Merger().TDDReached() { - return nil, errors.New("'finalized' tag not supported on pre-merge network") - } header := b.eth.blockchain.CurrentFinalBlock() if header == nil { return nil, errors.New("finalized block not found") @@ -140,9 +137,6 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe return b.eth.blockchain.GetBlock(header.Hash(), header.Number.Uint64()), nil } if number == rpc.SafeBlockNumber { - if !b.eth.Merger().TDDReached() { - return nil, errors.New("'safe' tag not supported on pre-merge network") - } header := b.eth.blockchain.CurrentSafeBlock() if header == nil { return nil, errors.New("safe block not found") @@ -196,6 +190,9 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B // Pending state is only known by the miner if number == rpc.PendingBlockNumber { block, state := b.eth.miner.Pending() + if block == nil || state == nil { + return nil, nil, errors.New("pending state is not available") + } return state, block.Header(), nil } // Otherwise resolve the block number and return its state @@ -207,7 +204,10 @@ func (b *EthAPIBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.B return nil, nil, errors.New("header not found") } stateDb, err := b.eth.BlockChain().StateAt(header.Root) - return stateDb, header, err + if err != nil { + return nil, nil, err + } + return stateDb, header, nil } func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { @@ -226,7 +226,10 @@ func (b *EthAPIBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockN return nil, nil, errors.New("hash is not currently canonical") } stateDb, err := b.eth.BlockChain().StateAt(header.Root) - return stateDb, header, err + if err != nil { + return nil, nil, err + } + return stateDb, header, nil } return nil, nil, errors.New("invalid arguments; neither block nor hash specified") } @@ -236,7 +239,7 @@ func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (type } func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { - return rawdb.ReadLogs(b.eth.chainDb, hash, number, b.ChainConfig()), nil + return rawdb.ReadLogs(b.eth.chainDb, hash, number), nil } func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { @@ -285,14 +288,18 @@ func (b *EthAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscri } func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { - return b.eth.txPool.AddLocal(signedTx) + return b.eth.txPool.Add([]*types.Transaction{signedTx}, true, false)[0] } func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) { pending := b.eth.txPool.Pending(false) var txs types.Transactions for _, batch := range pending { - txs = append(txs, batch...) + for _, lazy := range batch { + if tx := lazy.Resolve(); tx != nil { + txs = append(txs, tx) + } + } } return txs, nil } @@ -310,24 +317,24 @@ func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) ( return b.eth.txPool.Nonce(addr), nil } -func (b *EthAPIBackend) Stats() (pending int, queued int) { +func (b *EthAPIBackend) Stats() (runnable int, blocked int) { return b.eth.txPool.Stats() } -func (b *EthAPIBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { - return b.eth.TxPool().Content() +func (b *EthAPIBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { + return b.eth.txPool.Content() } -func (b *EthAPIBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) { - return b.eth.TxPool().ContentFrom(addr) +func (b *EthAPIBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { + return b.eth.txPool.ContentFrom(addr) } func (b *EthAPIBackend) TxPool() *txpool.TxPool { - return b.eth.TxPool() + return b.eth.txPool } func (b *EthAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { - return b.eth.TxPool().SubscribeNewTxsEvent(ch) + return b.eth.txPool.SubscribeTransactions(ch, true) } func (b *EthAPIBackend) SyncProgressMap() map[string]interface{} { @@ -407,7 +414,7 @@ func (b *EthAPIBackend) StartMining() error { } func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) { - return b.eth.StateAtBlock(ctx, block, reexec, base, readOnly, preferDisk) + return b.eth.stateAtBlock(ctx, block, reexec, base, readOnly, preferDisk) } func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { diff --git a/eth/api_debug.go b/eth/api_debug.go new file mode 100644 index 000000000000..15c25e2fae3c --- /dev/null +++ b/eth/api_debug.go @@ -0,0 +1,455 @@ +// Copyright 2023 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 eth + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/ethapi" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" +) + +// DebugAPI is the collection of Ethereum full node APIs for debugging the +// protocol. +type DebugAPI struct { + eth *Ethereum +} + +// NewDebugAPI creates a new DebugAPI instance. +func NewDebugAPI(eth *Ethereum) *DebugAPI { + return &DebugAPI{eth: eth} +} + +// DumpBlock retrieves the entire state of the database at a given block. +func (api *DebugAPI) DumpBlock(blockNr rpc.BlockNumber) (state.Dump, error) { + opts := &state.DumpConfig{ + OnlyWithAddresses: true, + Max: AccountRangeMaxResults, // Sanity limit over RPC + } + // arbitrum: in case of ArbEthereum, miner in not available here + // use current block instead of pending + if blockNr == rpc.PendingBlockNumber && api.eth.miner == nil { + blockNr = rpc.LatestBlockNumber + } + if blockNr == rpc.PendingBlockNumber { + // If we're dumping the pending state, we need to request + // both the pending block as well as the pending state from + // the miner and operate on those + _, stateDb := api.eth.miner.Pending() + if stateDb == nil { + return state.Dump{}, errors.New("pending state is not available") + } + return stateDb.RawDump(opts), nil + } + var header *types.Header + switch blockNr { + case rpc.LatestBlockNumber: + header = api.eth.blockchain.CurrentBlock() + case rpc.FinalizedBlockNumber: + header = api.eth.blockchain.CurrentFinalBlock() + case rpc.SafeBlockNumber: + header = api.eth.blockchain.CurrentSafeBlock() + default: + block := api.eth.blockchain.GetBlockByNumber(uint64(blockNr)) + if block == nil { + return state.Dump{}, fmt.Errorf("block #%d not found", blockNr) + } + header = block.Header() + } + if header == nil { + return state.Dump{}, fmt.Errorf("block #%d not found", blockNr) + } + stateDb, err := api.eth.BlockChain().StateAt(header.Root) + if err != nil { + return state.Dump{}, err + } + return stateDb.RawDump(opts), nil +} + +// Preimage is a debug API function that returns the preimage for a sha3 hash, if known. +func (api *DebugAPI) Preimage(ctx context.Context, hash common.Hash) (hexutil.Bytes, error) { + if preimage := rawdb.ReadPreimage(api.eth.ChainDb(), hash); preimage != nil { + return preimage, nil + } + return nil, errors.New("unknown preimage") +} + +// BadBlockArgs represents the entries in the list returned when bad blocks are queried. +type BadBlockArgs struct { + Hash common.Hash `json:"hash"` + Block map[string]interface{} `json:"block"` + RLP string `json:"rlp"` +} + +// GetBadBlocks returns a list of the last 'bad blocks' that the client has seen on the network +// and returns them as a JSON list of block hashes. +func (api *DebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error) { + var ( + blocks = rawdb.ReadAllBadBlocks(api.eth.chainDb) + results = make([]*BadBlockArgs, 0, len(blocks)) + ) + for _, block := range blocks { + var ( + blockRlp string + blockJSON map[string]interface{} + ) + if rlpBytes, err := rlp.EncodeToBytes(block); err != nil { + blockRlp = err.Error() // Hacky, but hey, it works + } else { + blockRlp = fmt.Sprintf("%#x", rlpBytes) + } + blockJSON = ethapi.RPCMarshalBlock(block, true, true, api.eth.APIBackend.ChainConfig()) + results = append(results, &BadBlockArgs{ + Hash: block.Hash(), + RLP: blockRlp, + Block: blockJSON, + }) + } + return results, nil +} + +// AccountRangeMaxResults is the maximum number of results to be returned per call +const AccountRangeMaxResults = 256 + +// AccountRange enumerates all accounts in the given block and start point in paging request +func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hexutil.Bytes, maxResults int, nocode, nostorage, incompletes bool) (state.IteratorDump, error) { + var stateDb *state.StateDB + var err error + + if number, ok := blockNrOrHash.Number(); ok { + // arbitrum: in case of ArbEthereum, miner in not available here + // use current block instead of pending + if number == rpc.PendingBlockNumber && api.eth.miner == nil { + number = rpc.LatestBlockNumber + } + if number == rpc.PendingBlockNumber { + // If we're dumping the pending state, we need to request + // both the pending block as well as the pending state from + // the miner and operate on those + _, stateDb = api.eth.miner.Pending() + if stateDb == nil { + return state.IteratorDump{}, errors.New("pending state is not available") + } + } else { + var header *types.Header + switch number { + case rpc.LatestBlockNumber: + header = api.eth.blockchain.CurrentBlock() + case rpc.FinalizedBlockNumber: + header = api.eth.blockchain.CurrentFinalBlock() + case rpc.SafeBlockNumber: + header = api.eth.blockchain.CurrentSafeBlock() + default: + block := api.eth.blockchain.GetBlockByNumber(uint64(number)) + if block == nil { + return state.IteratorDump{}, fmt.Errorf("block #%d not found", number) + } + header = block.Header() + } + if header == nil { + return state.IteratorDump{}, fmt.Errorf("block #%d not found", number) + } + stateDb, err = api.eth.BlockChain().StateAt(header.Root) + if err != nil { + return state.IteratorDump{}, err + } + } + } else if hash, ok := blockNrOrHash.Hash(); ok { + block := api.eth.blockchain.GetBlockByHash(hash) + if block == nil { + return state.IteratorDump{}, fmt.Errorf("block %s not found", hash.Hex()) + } + stateDb, err = api.eth.BlockChain().StateAt(block.Root()) + if err != nil { + return state.IteratorDump{}, err + } + } else { + return state.IteratorDump{}, errors.New("either block number or block hash must be specified") + } + + opts := &state.DumpConfig{ + SkipCode: nocode, + SkipStorage: nostorage, + OnlyWithAddresses: !incompletes, + Start: start, + Max: uint64(maxResults), + } + if maxResults > AccountRangeMaxResults || maxResults <= 0 { + opts.Max = AccountRangeMaxResults + } + return stateDb.IteratorDump(opts), nil +} + +// StorageRangeResult is the result of a debug_storageRangeAt API call. +type StorageRangeResult struct { + Storage storageMap `json:"storage"` + NextKey *common.Hash `json:"nextKey"` // nil if Storage includes the last key in the trie. +} + +type storageMap map[common.Hash]storageEntry + +type storageEntry struct { + Key *common.Hash `json:"key"` + Value common.Hash `json:"value"` +} + +// StorageRangeAt returns the storage at the given block height and transaction index. +func (api *DebugAPI) StorageRangeAt(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) { + var block *types.Block + + block, err := api.eth.APIBackend.BlockByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + return StorageRangeResult{}, err + } + if block == nil { + return StorageRangeResult{}, fmt.Errorf("block %v not found", blockNrOrHash) + } + _, _, statedb, release, err := api.eth.stateAtTransaction(ctx, block, txIndex, 0) + if err != nil { + return StorageRangeResult{}, err + } + defer release() + + return storageRangeAt(statedb, block.Root(), contractAddress, keyStart, maxResult) +} + +func storageRangeAt(statedb *state.StateDB, root common.Hash, address common.Address, start []byte, maxResult int) (StorageRangeResult, error) { + storageRoot := statedb.GetStorageRoot(address) + if storageRoot == types.EmptyRootHash || storageRoot == (common.Hash{}) { + return StorageRangeResult{}, nil // empty storage + } + id := trie.StorageTrieID(root, crypto.Keccak256Hash(address.Bytes()), storageRoot) + tr, err := trie.NewStateTrie(id, statedb.Database().TrieDB()) + if err != nil { + return StorageRangeResult{}, err + } + trieIt, err := tr.NodeIterator(start) + if err != nil { + return StorageRangeResult{}, err + } + it := trie.NewIterator(trieIt) + result := StorageRangeResult{Storage: storageMap{}} + for i := 0; i < maxResult && it.Next(); i++ { + _, content, _, err := rlp.Split(it.Value) + if err != nil { + return StorageRangeResult{}, err + } + e := storageEntry{Value: common.BytesToHash(content)} + if preimage := tr.GetKey(it.Key); preimage != nil { + preimage := common.BytesToHash(preimage) + e.Key = &preimage + } + result.Storage[common.BytesToHash(it.Key)] = e + } + // Add the 'next key' so clients can continue downloading. + if it.Next() { + next := common.BytesToHash(it.Key) + result.NextKey = &next + } + return result, nil +} + +// GetModifiedAccountsByNumber returns all accounts that have changed between the +// two blocks specified. A change is defined as a difference in nonce, balance, +// code hash, or storage hash. +// +// With one parameter, returns the list of accounts modified in the specified block. +func (api *DebugAPI) GetModifiedAccountsByNumber(startNum uint64, endNum *uint64) ([]common.Address, error) { + var startBlock, endBlock *types.Block + + startBlock = api.eth.blockchain.GetBlockByNumber(startNum) + if startBlock == nil { + return nil, fmt.Errorf("start block %x not found", startNum) + } + + if endNum == nil { + endBlock = startBlock + startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash()) + if startBlock == nil { + return nil, fmt.Errorf("block %x has no parent", endBlock.Number()) + } + } else { + endBlock = api.eth.blockchain.GetBlockByNumber(*endNum) + if endBlock == nil { + return nil, fmt.Errorf("end block %d not found", *endNum) + } + } + return api.getModifiedAccounts(startBlock, endBlock) +} + +// GetModifiedAccountsByHash returns all accounts that have changed between the +// two blocks specified. A change is defined as a difference in nonce, balance, +// code hash, or storage hash. +// +// With one parameter, returns the list of accounts modified in the specified block. +func (api *DebugAPI) GetModifiedAccountsByHash(startHash common.Hash, endHash *common.Hash) ([]common.Address, error) { + var startBlock, endBlock *types.Block + startBlock = api.eth.blockchain.GetBlockByHash(startHash) + if startBlock == nil { + return nil, fmt.Errorf("start block %x not found", startHash) + } + + if endHash == nil { + endBlock = startBlock + startBlock = api.eth.blockchain.GetBlockByHash(startBlock.ParentHash()) + if startBlock == nil { + return nil, fmt.Errorf("block %x has no parent", endBlock.Number()) + } + } else { + endBlock = api.eth.blockchain.GetBlockByHash(*endHash) + if endBlock == nil { + return nil, fmt.Errorf("end block %x not found", *endHash) + } + } + return api.getModifiedAccounts(startBlock, endBlock) +} + +func (api *DebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]common.Address, error) { + if startBlock.Number().Uint64() >= endBlock.Number().Uint64() { + return nil, fmt.Errorf("start block height (%d) must be less than end block height (%d)", startBlock.Number().Uint64(), endBlock.Number().Uint64()) + } + triedb := api.eth.BlockChain().TrieDB() + + oldTrie, err := trie.NewStateTrie(trie.StateTrieID(startBlock.Root()), triedb) + if err != nil { + return nil, err + } + newTrie, err := trie.NewStateTrie(trie.StateTrieID(endBlock.Root()), triedb) + if err != nil { + return nil, err + } + oldIt, err := oldTrie.NodeIterator([]byte{}) + if err != nil { + return nil, err + } + newIt, err := newTrie.NodeIterator([]byte{}) + if err != nil { + return nil, err + } + diff, _ := trie.NewDifferenceIterator(oldIt, newIt) + iter := trie.NewIterator(diff) + + var dirty []common.Address + for iter.Next() { + key := newTrie.GetKey(iter.Key) + if key == nil { + return nil, fmt.Errorf("no preimage found for hash %x", iter.Key) + } + dirty = append(dirty, common.BytesToAddress(key)) + } + return dirty, nil +} + +// GetAccessibleState returns the first number where the node has accessible +// state on disk. Note this being the post-state of that block and the pre-state +// of the next block. +// The (from, to) parameters are the sequence of blocks to search, which can go +// either forwards or backwards +func (api *DebugAPI) GetAccessibleState(from, to rpc.BlockNumber) (uint64, error) { + if api.eth.blockchain.TrieDB().Scheme() == rawdb.PathScheme { + return 0, errors.New("state history is not yet available in path-based scheme") + } + db := api.eth.ChainDb() + var pivot uint64 + if p := rawdb.ReadLastPivotNumber(db); p != nil { + pivot = *p + log.Info("Found fast-sync pivot marker", "number", pivot) + } + var resolveNum = func(num rpc.BlockNumber) (uint64, error) { + // We don't have state for pending (-2), so treat it as latest + if num.Int64() < 0 { + block := api.eth.blockchain.CurrentBlock() + if block == nil { + return 0, errors.New("current block missing") + } + return block.Number.Uint64(), nil + } + return uint64(num.Int64()), nil + } + var ( + start uint64 + end uint64 + delta = int64(1) + lastLog time.Time + err error + ) + if start, err = resolveNum(from); err != nil { + return 0, err + } + if end, err = resolveNum(to); err != nil { + return 0, err + } + if start == end { + return 0, errors.New("from and to needs to be different") + } + if start > end { + delta = -1 + } + for i := int64(start); i != int64(end); i += delta { + if time.Since(lastLog) > 8*time.Second { + log.Info("Finding roots", "from", start, "to", end, "at", i) + lastLog = time.Now() + } + if i < int64(pivot) { + continue + } + h := api.eth.BlockChain().GetHeaderByNumber(uint64(i)) + if h == nil { + return 0, fmt.Errorf("missing header %d", i) + } + if ok, _ := api.eth.ChainDb().Has(h.Root[:]); ok { + return uint64(i), nil + } + } + return 0, errors.New("no state found") +} + +// SetTrieFlushInterval configures how often in-memory tries are persisted +// to disk. The value is in terms of block processing time, not wall clock. +// If the value is shorter than the block generation time, or even 0 or negative, +// the node will flush trie after processing each block (effectively archive mode). +func (api *DebugAPI) SetTrieFlushInterval(interval string) error { + if api.eth.blockchain.TrieDB().Scheme() == rawdb.PathScheme { + return errors.New("trie flush interval is undefined for path-based scheme") + } + t, err := time.ParseDuration(interval) + if err != nil { + return err + } + api.eth.blockchain.SetTrieFlushInterval(t) + return nil +} + +// GetTrieFlushInterval gets the current value of in-memory trie flush interval +func (api *DebugAPI) GetTrieFlushInterval() (string, error) { + if api.eth.blockchain.TrieDB().Scheme() == rawdb.PathScheme { + return "", errors.New("trie flush interval is undefined for path-based scheme") + } + return api.eth.blockchain.GetTrieFlushInterval().String(), nil +} diff --git a/eth/api_test.go b/eth/api_debug_test.go similarity index 79% rename from eth/api_test.go rename to eth/api_debug_test.go index dec1b34ddc1f..3d3444a87163 100644 --- a/eth/api_test.go +++ b/eth/api_debug_test.go @@ -21,7 +21,6 @@ import ( "fmt" "math/big" "reflect" - "sort" "testing" "github.com/davecgh/go-spew/spew" @@ -31,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/trie" + "golang.org/x/exp/slices" ) var dumper = spew.ConfigState{Indent: " "} @@ -58,46 +58,40 @@ func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, st return result } -type resultHash []common.Hash - -func (h resultHash) Len() int { return len(h) } -func (h resultHash) Swap(i, j int) { h[i], h[j] = h[j], h[i] } -func (h resultHash) Less(i, j int) bool { return bytes.Compare(h[i].Bytes(), h[j].Bytes()) < 0 } - func TestAccountRange(t *testing.T) { t.Parallel() var ( - statedb = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{Preimages: true}) - state, _ = state.New(types.EmptyRootHash, statedb, nil) - addrs = [AccountRangeMaxResults * 2]common.Address{} - m = map[common.Address]bool{} + statedb = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{Preimages: true}) + sdb, _ = state.New(types.EmptyRootHash, statedb, nil) + addrs = [AccountRangeMaxResults * 2]common.Address{} + m = map[common.Address]bool{} ) for i := range addrs { hash := common.HexToHash(fmt.Sprintf("%x", i)) addr := common.BytesToAddress(crypto.Keccak256Hash(hash.Bytes()).Bytes()) addrs[i] = addr - state.SetBalance(addrs[i], big.NewInt(1)) + sdb.SetBalance(addrs[i], big.NewInt(1)) if _, ok := m[addr]; ok { t.Fatalf("bad") } else { m[addr] = true } } - state.Commit(true) - root := state.IntermediateRoot(true) + root, _ := sdb.Commit(0, true) + sdb, _ = state.New(root, statedb, nil) trie, err := statedb.OpenTrie(root) if err != nil { t.Fatal(err) } - accountRangeTest(t, &trie, state, common.Hash{}, AccountRangeMaxResults/2, AccountRangeMaxResults/2) + accountRangeTest(t, &trie, sdb, common.Hash{}, AccountRangeMaxResults/2, AccountRangeMaxResults/2) // test pagination - firstResult := accountRangeTest(t, &trie, state, common.Hash{}, AccountRangeMaxResults, AccountRangeMaxResults) - secondResult := accountRangeTest(t, &trie, state, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults) + firstResult := accountRangeTest(t, &trie, sdb, common.Hash{}, AccountRangeMaxResults, AccountRangeMaxResults) + secondResult := accountRangeTest(t, &trie, sdb, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults) - hList := make(resultHash, 0) + hList := make([]common.Hash, 0) for addr1 := range firstResult.Accounts { // If address is empty, then it makes no sense to compare // them as they might be two different accounts. @@ -111,9 +105,9 @@ func TestAccountRange(t *testing.T) { } // Test to see if it's possible to recover from the middle of the previous // set and get an even split between the first and second sets. - sort.Sort(hList) + slices.SortFunc(hList, common.Hash.Cmp) middleH := hList[AccountRangeMaxResults/2] - middleResult := accountRangeTest(t, &trie, state, middleH, AccountRangeMaxResults, AccountRangeMaxResults) + middleResult := accountRangeTest(t, &trie, sdb, middleH, AccountRangeMaxResults, AccountRangeMaxResults) missing, infirst, insecond := 0, 0, 0 for h := range middleResult.Accounts { if _, ok := firstResult.Accounts[h]; ok { @@ -142,8 +136,10 @@ func TestEmptyAccountRange(t *testing.T) { statedb = state.NewDatabase(rawdb.NewMemoryDatabase()) st, _ = state.New(types.EmptyRootHash, statedb, nil) ) - st.Commit(true) - st.IntermediateRoot(true) + // Commit(although nothing to flush) and re-init the statedb + st.Commit(0, true) + st, _ = state.New(types.EmptyRootHash, statedb, nil) + results := st.IteratorDump(&state.DumpConfig{ SkipCode: true, SkipStorage: true, @@ -163,9 +159,10 @@ func TestStorageRangeAt(t *testing.T) { // Create a state where account 0x010000... has a few storage entries. var ( - state, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - addr = common.Address{0x01} - keys = []common.Hash{ // hashes of Keys of storage + db = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{Preimages: true}) + sdb, _ = state.New(types.EmptyRootHash, db, nil) + addr = common.Address{0x01} + keys = []common.Hash{ // hashes of Keys of storage common.HexToHash("340dd630ad21bf010b4e676dbfa9ba9a02175262d1fa356232cfde6cb5b47ef2"), common.HexToHash("426fcb404ab2d5d8e61a3d918108006bbb0a9be65e92235bb10eefbdb6dcd053"), common.HexToHash("48078cfed56339ea54962e72c37c7f588fc4f8e5bc173827ba75cb10a63a96a5"), @@ -179,8 +176,10 @@ func TestStorageRangeAt(t *testing.T) { } ) for _, entry := range storage { - state.SetState(addr, *entry.Key, entry.Value) + sdb.SetState(addr, *entry.Key, entry.Value) } + root, _ := sdb.Commit(0, false) + sdb, _ = state.New(root, db, nil) // Check a few combinations of limit and start/end. tests := []struct { @@ -210,11 +209,7 @@ func TestStorageRangeAt(t *testing.T) { }, } for _, test := range tests { - tr, err := state.StorageTrie(addr) - if err != nil { - t.Error(err) - } - result, err := storageRangeAt(tr, test.start, test.limit) + result, err := storageRangeAt(sdb, root, addr, test.start, test.limit) if err != nil { t.Error(err) } diff --git a/eth/api_miner.go b/eth/api_miner.go new file mode 100644 index 000000000000..477531d49496 --- /dev/null +++ b/eth/api_miner.go @@ -0,0 +1,85 @@ +// Copyright 2023 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 eth + +import ( + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// MinerAPI provides an API to control the miner. +type MinerAPI struct { + e *Ethereum +} + +// NewMinerAPI create a new MinerAPI instance. +func NewMinerAPI(e *Ethereum) *MinerAPI { + return &MinerAPI{e} +} + +// Start starts the miner with the given number of threads. If threads is nil, +// the number of workers started is equal to the number of logical CPUs that are +// usable by this process. If mining is already running, this method adjust the +// number of threads allowed to use and updates the minimum price required by the +// transaction pool. +func (api *MinerAPI) Start() error { + return api.e.StartMining() +} + +// Stop terminates the miner, both at the consensus engine level as well as at +// the block creation level. +func (api *MinerAPI) Stop() { + api.e.StopMining() +} + +// SetExtra sets the extra data string that is included when this miner mines a block. +func (api *MinerAPI) SetExtra(extra string) (bool, error) { + if err := api.e.Miner().SetExtra([]byte(extra)); err != nil { + return false, err + } + return true, nil +} + +// SetGasPrice sets the minimum accepted gas price for the miner. +func (api *MinerAPI) SetGasPrice(gasPrice hexutil.Big) bool { + api.e.lock.Lock() + api.e.gasPrice = (*big.Int)(&gasPrice) + api.e.lock.Unlock() + + api.e.txPool.SetGasTip((*big.Int)(&gasPrice)) + return true +} + +// SetGasLimit sets the gaslimit to target towards during mining. +func (api *MinerAPI) SetGasLimit(gasLimit hexutil.Uint64) bool { + api.e.Miner().SetGasCeil(uint64(gasLimit)) + return true +} + +// SetEtherbase sets the etherbase of the miner. +func (api *MinerAPI) SetEtherbase(etherbase common.Address) bool { + api.e.SetEtherbase(etherbase) + return true +} + +// SetRecommitInterval updates the interval for miner sealing work recommitting. +func (api *MinerAPI) SetRecommitInterval(interval int) { + api.e.Miner().SetRecommitInterval(time.Duration(interval) * time.Millisecond) +} diff --git a/eth/backend.go b/eth/backend.go index 2d8e6618f974..bf37d30880cd 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -36,6 +36,8 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state/pruner" "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/txpool/blobpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" @@ -67,7 +69,8 @@ type Ethereum struct { config *ethconfig.Config // Handlers - txPool *txpool.TxPool + txPool *txpool.TxPool + blockchain *core.BlockChain handler *handler ethDialCandidates enode.Iterator @@ -131,8 +134,15 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } - if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb, stack.ResolvePath(config.TrieCleanCacheJournal)); err != nil { - log.Error("Failed to recover state", "error", err) + scheme, err := rawdb.ParseStateScheme(config.StateScheme, chainDb) + if err != nil { + return nil, err + } + // Try to recover offline state pruning only in hash-based. + if scheme == rawdb.HashScheme { + if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb); err != nil { + log.Error("Failed to recover state", "error", err) + } } // Transfer mining-related config to the ethash config. chainConfig, err := core.LoadChainConfig(chainDb, config.Genesis) @@ -159,7 +169,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { p2pServer: stack.Server(), shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb), } - bcVersion := rawdb.ReadDatabaseVersion(chainDb) var dbVer = "" if bcVersion != nil { @@ -188,14 +197,14 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { TrieRetention: 30 * time.Minute, TrieCleanLimit: config.TrieCleanCache, - TrieCleanJournal: stack.ResolvePath(config.TrieCleanCacheJournal), - TrieCleanRejournal: config.TrieCleanCacheRejournal, TrieCleanNoPrefetch: config.NoPrefetch, TrieDirtyLimit: config.TrieDirtyCache, TrieDirtyDisabled: config.NoPruning, TrieTimeLimit: config.TrieTimeout, SnapshotLimit: config.SnapshotCache, Preimages: config.Preimages, + StateHistory: config.StateHistory, + StateScheme: scheme, } ) // Override the chain config with provided settings. @@ -203,17 +212,29 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if config.OverrideCancun != nil { overrides.OverrideCancun = config.OverrideCancun } - eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, nil, config.Genesis, &overrides, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit) + if config.OverrideVerkle != nil { + overrides.OverrideVerkle = config.OverrideVerkle + } + eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, nil, config.Genesis, &overrides, eth.engine, vmConfig, eth.shouldPreserve, &config.TransactionHistory) if err != nil { return nil, err } eth.bloomIndexer.Start(eth.blockchain) + if config.BlobPool.Datadir != "" { + config.BlobPool.Datadir = stack.ResolvePath(config.BlobPool.Datadir) + } + blobPool := blobpool.New(config.BlobPool, eth.blockchain) + if config.TxPool.Journal != "" { config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal) } - eth.txPool = txpool.NewTxPool(config.TxPool, eth.blockchain.Config(), eth.blockchain) + legacyPool := legacypool.New(config.TxPool, eth.blockchain) + eth.txPool, err = txpool.New(new(big.Int).SetUint64(config.TxPool.PriceLimit), eth.blockchain, []txpool.SubPool{legacyPool, blobPool}) + if err != nil { + return nil, err + } // Permit the downloader to use the trie cache allowance during fast sync cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit if eth.handler, err = newHandler(&handlerConfig{ @@ -376,7 +397,7 @@ func (s *Ethereum) shouldPreserve(header *types.Header) bool { // r5 A [X] F G // r6 [X] // - // In the round5, the inturn signer E is offline, so the worst case + // In the round5, the in-turn signer E is offline, so the worst case // is A, F and G sign the block of round5 and reject the block of opponents // and in the round6, the last available signer B is offline, the whole // network is stuck. @@ -405,7 +426,7 @@ func (s *Ethereum) StartMining() error { s.lock.RLock() price := s.gasPrice s.lock.RUnlock() - s.txPool.SetGasPrice(price) + s.txPool.SetGasTip(price) // Configure the local mining address eb, err := s.Etherbase() @@ -431,7 +452,7 @@ func (s *Ethereum) StartMining() error { } // If mining is started, we can disable the transaction rejection mechanism // introduced to speed sync times. - s.handler.acceptTxs.Store(true) + s.handler.enableSyncedFeatures() go s.miner.Start() } @@ -463,8 +484,8 @@ func (s *Ethereum) Engine() consensus.Engine { return s.engine } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } func (s *Ethereum) IsListening() bool { return true } // Always listening func (s *Ethereum) Downloader() *downloader.Downloader { return s.handler.downloader } -func (s *Ethereum) Synced() bool { return s.handler.acceptTxs.Load() } -func (s *Ethereum) SetSynced() { s.handler.acceptTxs.Store(true) } +func (s *Ethereum) Synced() bool { return s.handler.synced.Load() } +func (s *Ethereum) SetSynced() { s.handler.enableSyncedFeatures() } func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer } func (s *Ethereum) Merger() *consensus.Merger { return s.merger } @@ -518,7 +539,7 @@ func (s *Ethereum) Stop() error { // Then stop everything else. s.bloomIndexer.Close() close(s.closeBloomHandler) - s.txPool.Stop() + s.txPool.Close() s.miner.Close() s.blockchain.Stop() s.engine.Close() diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 0ba21b617ca5..41a66e017378 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -78,11 +78,14 @@ const ( var caps = []string{ "engine_forkchoiceUpdatedV1", "engine_forkchoiceUpdatedV2", + "engine_forkchoiceUpdatedV3", "engine_exchangeTransitionConfigurationV1", "engine_getPayloadV1", "engine_getPayloadV2", + "engine_getPayloadV3", "engine_newPayloadV1", "engine_newPayloadV2", + "engine_newPayloadV3", "engine_getPayloadBodiesByHashV1", "engine_getPayloadBodiesByRangeV1", } @@ -133,6 +136,13 @@ type ConsensusAPI struct { // NewConsensusAPI creates a new consensus api for the given backend. // The underlying blockchain needs to have a valid terminal total difficulty set. func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI { + api := newConsensusAPIWithoutHeartbeat(eth) + go api.heartbeat() + return api +} + +// newConsensusAPIWithoutHeartbeat creates a new consensus api for the SimulatedBeacon Node. +func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI { if eth.BlockChain().Config().TerminalTotalDifficulty == nil { log.Warn("Engine API started but chain not configured for merge yet") } @@ -144,8 +154,6 @@ func NewConsensusAPI(eth *eth.Ethereum) *ConsensusAPI { invalidTipsets: make(map[common.Hash]*types.Header), } eth.Downloader().SetBadBlockCallback(api.setInvalidAncestor) - go api.heartbeat() - return api } @@ -185,18 +193,37 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV2(update engine.ForkchoiceStateV1, pa return api.forkchoiceUpdated(update, payloadAttributes) } -func (api *ConsensusAPI) verifyPayloadAttributes(attr *engine.PayloadAttributes) error { - if !api.eth.BlockChain().Config().IsShanghai(api.eth.BlockChain().Config().LondonBlock, attr.Timestamp, types.DeserializeHeaderExtraInformation(api.eth.BlockChain().CurrentHeader()).ArbOSFormatVersion) { - // Reject payload attributes with withdrawals before shanghai - if attr.Withdrawals != nil { - return errors.New("withdrawals before shanghai") - } - } else { - // Reject payload attributes with nil withdrawals after shanghai - if attr.Withdrawals == nil { - return errors.New("missing withdrawals list") +// ForkchoiceUpdatedV3 is equivalent to V2 with the addition of parent beacon block root in the payload attributes. +func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { + if payloadAttributes != nil { + if err := api.verifyPayloadAttributes(payloadAttributes); err != nil { + return engine.STATUS_INVALID, engine.InvalidParams.With(err) } } + return api.forkchoiceUpdated(update, payloadAttributes) +} + +func (api *ConsensusAPI) verifyPayloadAttributes(attr *engine.PayloadAttributes) error { + c := api.eth.BlockChain().Config() + + // Verify withdrawals attribute for Shanghai. + if err := checkAttribute(c.IsShanghai, attr.Withdrawals != nil, c.LondonBlock, attr.Timestamp, types.DeserializeHeaderExtraInformation(api.eth.BlockChain().CurrentHeader()).ArbOSFormatVersion); err != nil { + return fmt.Errorf("invalid withdrawals: %w", err) + } + // Verify beacon root attribute for Cancun. + if err := checkAttribute(c.IsCancun, attr.BeaconRoot != nil, c.LondonBlock, attr.Timestamp, types.DeserializeHeaderExtraInformation(api.eth.BlockChain().CurrentHeader()).ArbOSFormatVersion); err != nil { + return fmt.Errorf("invalid parent beacon block root: %w", err) + } + return nil +} + +func checkAttribute(active func(*big.Int, uint64, uint64) bool, exists bool, block *big.Int, time uint64, currentArbosVersion uint64) error { + if active(block, time, currentArbosVersion) && !exists { + return errors.New("fork active, missing expected attribute") + } + if !active(block, time, currentArbosVersion) && exists { + return errors.New("fork inactive, unexpected attribute set") + } return nil } @@ -343,6 +370,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl FeeRecipient: payloadAttributes.SuggestedFeeRecipient, Random: payloadAttributes.Random, Withdrawals: payloadAttributes.Withdrawals, + BeaconRoot: payloadAttributes.BeaconRoot, } id := args.Id() // If we already are busy generating this work, then we do not need @@ -393,7 +421,7 @@ func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.Transit // GetPayloadV1 returns a cached payload by id. func (api *ConsensusAPI) GetPayloadV1(payloadID engine.PayloadID) (*engine.ExecutableData, error) { - data, err := api.getPayload(payloadID) + data, err := api.getPayload(payloadID, false) if err != nil { return nil, err } @@ -402,12 +430,17 @@ func (api *ConsensusAPI) GetPayloadV1(payloadID engine.PayloadID) (*engine.Execu // GetPayloadV2 returns a cached payload by id. func (api *ConsensusAPI) GetPayloadV2(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { - return api.getPayload(payloadID) + return api.getPayload(payloadID, false) +} + +// GetPayloadV3 returns a cached payload by id. +func (api *ConsensusAPI) GetPayloadV3(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { + return api.getPayload(payloadID, false) } -func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) { +func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool) (*engine.ExecutionPayloadEnvelope, error) { log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) - data := api.localBlocks.get(payloadID) + data := api.localBlocks.get(payloadID, full) if data == nil { return nil, engine.UnknownPayload } @@ -419,22 +452,49 @@ func (api *ConsensusAPI) NewPayloadV1(params engine.ExecutableData) (engine.Payl if params.Withdrawals != nil { return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("withdrawals not supported in V1")) } - return api.newPayload(params) + return api.newPayload(params, nil, nil) } // NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.PayloadStatusV1, error) { - if api.eth.BlockChain().Config().IsShanghai(new(big.Int).SetUint64(params.Number), params.Timestamp, types.DeserializeHeaderExtraInformation(api.eth.BlockChain().CurrentHeader()).ArbOSFormatVersion) { + arbosVersion := types.DeserializeHeaderExtraInformation(api.eth.BlockChain().CurrentHeader()).ArbOSFormatVersion + if api.eth.BlockChain().Config().IsShanghai(new(big.Int).SetUint64(params.Number), params.Timestamp, arbosVersion) { if params.Withdrawals == nil { return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil withdrawals post-shanghai")) } } else if params.Withdrawals != nil { return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("non-nil withdrawals pre-shanghai")) } - return api.newPayload(params) + if api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp, arbosVersion) { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("newPayloadV2 called post-cancun")) + } + return api.newPayload(params, nil, nil) +} + +// NewPayloadV3 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. +func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) { + arbosVersion := types.DeserializeHeaderExtraInformation(api.eth.BlockChain().CurrentHeader()).ArbOSFormatVersion + if params.ExcessBlobGas == nil { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun")) + } + if params.BlobGasUsed == nil { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil params.BlobGasUsed post-cancun")) + } + if versionedHashes == nil { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil versionedHashes post-cancun")) + } + if beaconRoot == nil { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil parentBeaconBlockRoot post-cancun")) + } + + if !api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp, arbosVersion) { + return engine.PayloadStatusV1{Status: engine.INVALID}, engine.UnsupportedFork.With(errors.New("newPayloadV3 called pre-cancun")) + } + + return api.newPayload(params, versionedHashes, beaconRoot) } -func (api *ConsensusAPI) newPayload(params engine.ExecutableData) (engine.PayloadStatusV1, error) { +func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) { // The locking here is, strictly, not required. Without these locks, this can happen: // // 1. NewPayload( execdata-N ) is invoked from the CL. It goes all the way down to @@ -452,10 +512,10 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData) (engine.Payloa defer api.newPayloadLock.Unlock() log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) - block, err := engine.ExecutableDataToBlock(params) + block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot) if err != nil { - log.Debug("Invalid NewPayload params", "params", params, "error", err) - return engine.PayloadStatusV1{Status: engine.INVALID}, nil + log.Warn("Invalid NewPayload params", "params", params, "error", err) + return api.invalid(err, nil), nil } // Stash away the last update to warn the user if the beacon client goes offline api.lastNewPayloadLock.Lock() @@ -502,7 +562,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData) (engine.Payloa log.Warn("Invalid timestamp", "parent", block.Time(), "block", block.Time()) return api.invalid(errors.New("invalid timestamp"), parent.Header()), nil } - // Another cornercase: if the node is in snap sync mode, but the CL client + // Another corner case: if the node is in snap sync mode, but the CL client // tries to make it import a block. That should be denied as pushing something // into the database directly will conflict with the assumptions of snap sync // that it has an empty db that it can fill itself. @@ -636,20 +696,21 @@ func (api *ConsensusAPI) checkInvalidAncestor(check common.Hash, head common.Has } } -// invalid returns a response "INVALID" with the latest valid hash supplied by latest or to the current head -// if no latestValid block was provided. +// invalid returns a response "INVALID" with the latest valid hash supplied by latest. func (api *ConsensusAPI) invalid(err error, latestValid *types.Header) engine.PayloadStatusV1 { - currentHash := api.eth.BlockChain().CurrentBlock().Hash() + var currentHash *common.Hash if latestValid != nil { - // Set latest valid hash to 0x0 if parent is PoW block - currentHash = common.Hash{} - if latestValid.Difficulty.BitLen() == 0 { + if latestValid.Difficulty.BitLen() != 0 { + // Set latest valid hash to 0x0 if parent is PoW block + currentHash = &common.Hash{} + } else { // Otherwise set latest valid hash to parent hash - currentHash = latestValid.Hash() + h := latestValid.Hash() + currentHash = &h } } errorMsg := err.Error() - return engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: ¤tHash, ValidationError: &errorMsg} + return engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: currentHash, ValidationError: &errorMsg} } // heartbeat loops indefinitely, and checks if there have been beacon client updates @@ -715,10 +776,10 @@ func (api *ConsensusAPI) ExchangeCapabilities([]string) []string { return caps } -// GetPayloadBodiesV1 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list +// GetPayloadBodiesByHashV1 implements engine_getPayloadBodiesByHashV1 which allows for retrieval of a list // of block bodies by the engine api. func (api *ConsensusAPI) GetPayloadBodiesByHashV1(hashes []common.Hash) []*engine.ExecutionPayloadBodyV1 { - var bodies = make([]*engine.ExecutionPayloadBodyV1, len(hashes)) + bodies := make([]*engine.ExecutionPayloadBodyV1, len(hashes)) for i, hash := range hashes { block := api.eth.BlockChain().GetBlockByHash(hash) bodies[i] = getBody(block) diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index 5bab7ba18643..59f44fafea4e 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -37,15 +37,18 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/trie" + "github.com/mattn/go-colorable" ) var ( @@ -67,8 +70,11 @@ func generateMergeChain(n int, merged bool) (*core.Genesis, []*types.Block) { engine = beaconConsensus.NewFaker() } genesis := &core.Genesis{ - Config: &config, - Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, + Config: &config, + Alloc: core.GenesisAlloc{ + testAddr: {Balance: testBalance}, + params.BeaconRootsStorageAddress: {Balance: common.Big0, Code: common.Hex2Bytes("3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500")}, + }, ExtraData: []byte("test genesis"), Timestamp: 9000, BaseFee: big.NewInt(params.InitialBaseFee), @@ -106,7 +112,7 @@ func TestEth2AssembleBlock(t *testing.T) { if err != nil { t.Fatalf("error signing transaction, err=%v", err) } - ethservice.TxPool().AddLocal(tx) + ethservice.TxPool().Add([]*types.Transaction{tx}, true, false) blockParams := engine.PayloadAttributes{ Timestamp: blocks[9].Time() + 5, } @@ -142,7 +148,8 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { api := NewConsensusAPI(ethservice) // Put the 10th block's tx in the pool and produce a new block - api.eth.TxPool().AddRemotesSync(blocks[9].Transactions()) + txs := blocks[9].Transactions() + api.eth.TxPool().Add(txs, false, true) blockParams := engine.PayloadAttributes{ Timestamp: blocks[8].Time() + 5, } @@ -181,7 +188,8 @@ func TestEth2PrepareAndGetPayload(t *testing.T) { api := NewConsensusAPI(ethservice) // Put the 10th block's tx in the pool and produce a new block - ethservice.TxPool().AddLocals(blocks[9].Transactions()) + txs := blocks[9].Transactions() + ethservice.TxPool().Add(txs, true, false) blockParams := engine.PayloadAttributes{ Timestamp: blocks[8].Time() + 5, } @@ -201,6 +209,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) { Timestamp: blockParams.Timestamp, FeeRecipient: blockParams.SuggestedFeeRecipient, Random: blockParams.Random, + BeaconRoot: blockParams.BeaconRoot, }).Id() execData, err := api.GetPayloadV1(payloadID) if err != nil { @@ -303,7 +312,7 @@ func TestEth2NewBlock(t *testing.T) { statedb, _ := ethservice.BlockChain().StateAt(parent.Root()) nonce := statedb.GetNonce(testAddr) tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey) - ethservice.TxPool().AddLocal(tx) + ethservice.TxPool().Add([]*types.Transaction{tx}, true, false) execData, err := assembleWithTransactions(api, parent.Hash(), &engine.PayloadAttributes{ Timestamp: parent.Time() + 5, @@ -311,7 +320,7 @@ func TestEth2NewBlock(t *testing.T) { if err != nil { t.Fatalf("Failed to create the executable data %v", err) } - block, err := engine.ExecutableDataToBlock(*execData) + block, err := engine.ExecutableDataToBlock(*execData, nil, nil) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } @@ -353,7 +362,7 @@ func TestEth2NewBlock(t *testing.T) { if err != nil { t.Fatalf("Failed to create the executable data %v", err) } - block, err := engine.ExecutableDataToBlock(*execData) + block, err := engine.ExecutableDataToBlock(*execData, nil, nil) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } @@ -472,7 +481,7 @@ func TestFullAPI(t *testing.T) { statedb, _ := ethservice.BlockChain().StateAt(parent.Root) nonce := statedb.GetNonce(testAddr) tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey) - ethservice.TxPool().AddLocal(tx) + ethservice.TxPool().Add([]*types.Transaction{tx}, true, false) } setupBlocks(t, ethservice, 10, parent, callback, nil) @@ -598,7 +607,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) { GasPrice: big.NewInt(2 * params.InitialBaseFee), Data: logCode, }) - ethservice.TxPool().AddRemotesSync([]*types.Transaction{tx}) + ethservice.TxPool().Add([]*types.Transaction{tx}, false, true) var ( params = engine.PayloadAttributes{ Timestamp: parent.Time + 1, @@ -664,6 +673,7 @@ func assembleBlock(api *ConsensusAPI, parentHash common.Hash, params *engine.Pay FeeRecipient: params.SuggestedFeeRecipient, Random: params.Random, Withdrawals: params.Withdrawals, + BeaconRoot: params.BeaconRoot, } payload, err := api.eth.Miner().BuildPayload(args) if err != nil { @@ -985,7 +995,7 @@ func TestSimultaneousNewBlock(t *testing.T) { t.Fatal(testErr) } } - block, err := engine.ExecutableDataToBlock(*execData) + block, err := engine.ExecutableDataToBlock(*execData, nil, nil) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } @@ -1065,6 +1075,7 @@ func TestWithdrawals(t *testing.T) { FeeRecipient: blockParams.SuggestedFeeRecipient, Random: blockParams.Random, Withdrawals: blockParams.Withdrawals, + BeaconRoot: blockParams.BeaconRoot, }).Id() execData, err := api.GetPayloadV2(payloadID) if err != nil { @@ -1112,6 +1123,7 @@ func TestWithdrawals(t *testing.T) { FeeRecipient: blockParams.SuggestedFeeRecipient, Random: blockParams.Random, Withdrawals: blockParams.Withdrawals, + BeaconRoot: blockParams.BeaconRoot, }).Id() execData, err = api.GetPayloadV2(payloadID) if err != nil { @@ -1242,6 +1254,7 @@ func TestNilWithdrawals(t *testing.T) { Timestamp: test.blockParams.Timestamp, FeeRecipient: test.blockParams.SuggestedFeeRecipient, Random: test.blockParams.Random, + BeaconRoot: test.blockParams.BeaconRoot, }).Id() execData, err := api.GetPayloadV2(payloadID) if err != nil { @@ -1272,7 +1285,7 @@ func setupBodies(t *testing.T) (*node.Node, *eth.Ethereum, []*types.Block) { statedb, _ := ethservice.BlockChain().StateAt(parent.Root) nonce := statedb.GetNonce(testAddr) tx, _ := types.SignTx(types.NewContractCreation(nonce, new(big.Int), 1000000, big.NewInt(2*params.InitialBaseFee), logCode), types.LatestSigner(ethservice.BlockChain().Config()), testKey) - ethservice.TxPool().AddLocal(tx) + ethservice.TxPool().Add([]*types.Transaction{tx}, false, false) } withdrawals := make([][]*types.Withdrawal, 10) @@ -1508,3 +1521,124 @@ func equalBody(a *types.Body, b *engine.ExecutionPayloadBodyV1) bool { } return reflect.DeepEqual(a.Withdrawals, b.Withdrawals) } + +func TestBlockToPayloadWithBlobs(t *testing.T) { + header := types.Header{} + var txs []*types.Transaction + + inner := types.BlobTx{ + BlobHashes: make([]common.Hash, 1), + } + + txs = append(txs, types.NewTx(&inner)) + sidecars := []*types.BlobTxSidecar{ + { + Blobs: make([]kzg4844.Blob, 1), + Commitments: make([]kzg4844.Commitment, 1), + Proofs: make([]kzg4844.Proof, 1), + }, + } + + block := types.NewBlock(&header, txs, nil, nil, trie.NewStackTrie(nil)) + envelope := engine.BlockToExecutableData(block, nil, sidecars) + var want int + for _, tx := range txs { + want += len(tx.BlobHashes()) + } + if got := len(envelope.BlobsBundle.Commitments); got != want { + t.Fatalf("invalid number of commitments: got %v, want %v", got, want) + } + if got := len(envelope.BlobsBundle.Proofs); got != want { + t.Fatalf("invalid number of proofs: got %v, want %v", got, want) + } + if got := len(envelope.BlobsBundle.Blobs); got != want { + t.Fatalf("invalid number of blobs: got %v, want %v", got, want) + } + _, err := engine.ExecutableDataToBlock(*envelope.ExecutionPayload, make([]common.Hash, 1), nil) + if err != nil { + t.Error(err) + } +} + +// This checks that beaconRoot is applied to the state from the engine API. +func TestParentBeaconBlockRoot(t *testing.T) { + log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) + + genesis, blocks := generateMergeChain(10, true) + + // Set cancun time to last block + 5 seconds + time := blocks[len(blocks)-1].Time() + 5 + genesis.Config.ShanghaiTime = &time + genesis.Config.CancunTime = &time + + n, ethservice := startEthService(t, genesis, blocks) + ethservice.Merger().ReachTTD() + defer n.Close() + + api := NewConsensusAPI(ethservice) + + // 11: Build Shanghai block with no withdrawals. + parent := ethservice.BlockChain().CurrentHeader() + blockParams := engine.PayloadAttributes{ + Timestamp: parent.Time + 5, + Withdrawals: make([]*types.Withdrawal, 0), + BeaconRoot: &common.Hash{42}, + } + fcState := engine.ForkchoiceStateV1{ + HeadBlockHash: parent.Hash(), + } + resp, err := api.ForkchoiceUpdatedV2(fcState, &blockParams) + if err != nil { + t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData()) + } + if resp.PayloadStatus.Status != engine.VALID { + t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID) + } + + // 11: verify state root is the same as parent + payloadID := (&miner.BuildPayloadArgs{ + Parent: fcState.HeadBlockHash, + Timestamp: blockParams.Timestamp, + FeeRecipient: blockParams.SuggestedFeeRecipient, + Random: blockParams.Random, + Withdrawals: blockParams.Withdrawals, + BeaconRoot: blockParams.BeaconRoot, + }).Id() + execData, err := api.GetPayloadV3(payloadID) + if err != nil { + t.Fatalf("error getting payload, err=%v", err) + } + + // 11: verify locally built block + if status, err := api.NewPayloadV3(*execData.ExecutionPayload, []common.Hash{}, &common.Hash{42}); err != nil { + t.Fatalf("error validating payload: %v", err) + } else if status.Status != engine.VALID { + t.Fatalf("invalid payload") + } + + fcState.HeadBlockHash = execData.ExecutionPayload.BlockHash + resp, err = api.ForkchoiceUpdatedV3(fcState, nil) + if err != nil { + t.Fatalf("error preparing payload, err=%v", err.(*engine.EngineAPIError).ErrorData()) + } + if resp.PayloadStatus.Status != engine.VALID { + t.Fatalf("unexpected status (got: %s, want: %s)", resp.PayloadStatus.Status, engine.VALID) + } + + // 11: verify beacon root was processed. + db, _, err := ethservice.APIBackend.StateAndHeaderByNumber(context.Background(), rpc.BlockNumber(execData.ExecutionPayload.Number)) + if err != nil { + t.Fatalf("unable to load db: %v", err) + } + var ( + timeIdx = common.BigToHash(big.NewInt(int64(execData.ExecutionPayload.Timestamp % 98304))) + rootIdx = common.BigToHash(big.NewInt(int64((execData.ExecutionPayload.Timestamp % 98304) + 98304))) + ) + + if num := db.GetState(params.BeaconRootsStorageAddress, timeIdx); num != timeIdx { + t.Fatalf("incorrect number stored: want %s, got %s", timeIdx, num) + } + if root := db.GetState(params.BeaconRootsStorageAddress, rootIdx); root != *blockParams.BeaconRoot { + t.Fatalf("incorrect root stored: want %s, got %s", *blockParams.BeaconRoot, root) + } +} diff --git a/eth/catalyst/queue.go b/eth/catalyst/queue.go index e8037aacad83..634dc1b2e6c2 100644 --- a/eth/catalyst/queue.go +++ b/eth/catalyst/queue.go @@ -73,7 +73,7 @@ func (q *payloadQueue) put(id engine.PayloadID, payload *miner.Payload) { } // get retrieves a previously stored payload item or nil if it does not exist. -func (q *payloadQueue) get(id engine.PayloadID) *engine.ExecutionPayloadEnvelope { +func (q *payloadQueue) get(id engine.PayloadID, full bool) *engine.ExecutionPayloadEnvelope { q.lock.RLock() defer q.lock.RUnlock() @@ -82,7 +82,10 @@ func (q *payloadQueue) get(id engine.PayloadID) *engine.ExecutionPayloadEnvelope return nil // no more items } if item.id == id { - return item.payload.Resolve() + if !full { + return item.payload.Resolve() + } + return item.payload.ResolveFull() } } return nil diff --git a/eth/catalyst/simulated_beacon.go b/eth/catalyst/simulated_beacon.go new file mode 100644 index 000000000000..a9a2bb4a9a7b --- /dev/null +++ b/eth/catalyst/simulated_beacon.go @@ -0,0 +1,276 @@ +// Copyright 2023 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 catalyst + +import ( + "crypto/rand" + "errors" + "sync" + "time" + + "github.com/ethereum/go-ethereum/beacon/engine" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" +) + +const devEpochLength = 32 + +// withdrawalQueue implements a FIFO queue which holds withdrawals that are +// pending inclusion. +type withdrawalQueue struct { + pending chan *types.Withdrawal +} + +// add queues a withdrawal for future inclusion. +func (w *withdrawalQueue) add(withdrawal *types.Withdrawal) error { + select { + case w.pending <- withdrawal: + break + default: + return errors.New("withdrawal queue full") + } + return nil +} + +// gatherPending returns a number of queued withdrawals up to a maximum count. +func (w *withdrawalQueue) gatherPending(maxCount int) []*types.Withdrawal { + withdrawals := []*types.Withdrawal{} + for { + select { + case withdrawal := <-w.pending: + withdrawals = append(withdrawals, withdrawal) + if len(withdrawals) == maxCount { + break + } + default: + return withdrawals + } + } +} + +type SimulatedBeacon struct { + shutdownCh chan struct{} + eth *eth.Ethereum + period uint64 + withdrawals withdrawalQueue + + feeRecipient common.Address + feeRecipientLock sync.Mutex // lock gates concurrent access to the feeRecipient + + engineAPI *ConsensusAPI + curForkchoiceState engine.ForkchoiceStateV1 + lastBlockTime uint64 +} + +func NewSimulatedBeacon(period uint64, eth *eth.Ethereum) (*SimulatedBeacon, error) { + chainConfig := eth.APIBackend.ChainConfig() + if !chainConfig.IsDevMode { + return nil, errors.New("incompatible pre-existing chain configuration") + } + block := eth.BlockChain().CurrentBlock() + current := engine.ForkchoiceStateV1{ + HeadBlockHash: block.Hash(), + SafeBlockHash: block.Hash(), + FinalizedBlockHash: block.Hash(), + } + engineAPI := newConsensusAPIWithoutHeartbeat(eth) + + // if genesis block, send forkchoiceUpdated to trigger transition to PoS + if block.Number.Sign() == 0 { + if _, err := engineAPI.ForkchoiceUpdatedV2(current, nil); err != nil { + return nil, err + } + } + return &SimulatedBeacon{ + eth: eth, + period: period, + shutdownCh: make(chan struct{}), + engineAPI: engineAPI, + lastBlockTime: block.Time, + curForkchoiceState: current, + withdrawals: withdrawalQueue{make(chan *types.Withdrawal, 20)}, + }, nil +} + +func (c *SimulatedBeacon) setFeeRecipient(feeRecipient common.Address) { + c.feeRecipientLock.Lock() + c.feeRecipient = feeRecipient + c.feeRecipientLock.Unlock() +} + +// Start invokes the SimulatedBeacon life-cycle function in a goroutine. +func (c *SimulatedBeacon) Start() error { + if c.period == 0 { + go c.loopOnDemand() + } else { + go c.loop() + } + return nil +} + +// Stop halts the SimulatedBeacon service. +func (c *SimulatedBeacon) Stop() error { + close(c.shutdownCh) + return nil +} + +// sealBlock initiates payload building for a new block and creates a new block +// with the completed payload. +func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error { + tstamp := uint64(time.Now().Unix()) + if tstamp <= c.lastBlockTime { + tstamp = c.lastBlockTime + 1 + } + c.feeRecipientLock.Lock() + feeRecipient := c.feeRecipient + c.feeRecipientLock.Unlock() + + // Reset to CurrentBlock in case of the chain was rewound + if header := c.eth.BlockChain().CurrentBlock(); c.curForkchoiceState.HeadBlockHash != header.Hash() { + finalizedHash := c.finalizedBlockHash(header.Number.Uint64()) + c.setCurrentState(header.Hash(), *finalizedHash) + } + + var random [32]byte + rand.Read(random[:]) + fcResponse, err := c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, &engine.PayloadAttributes{ + Timestamp: tstamp, + SuggestedFeeRecipient: feeRecipient, + Withdrawals: withdrawals, + Random: random, + }) + if err != nil { + return err + } + if fcResponse == engine.STATUS_SYNCING { + return errors.New("chain rewind prevented invocation of payload creation") + } + + envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true) + if err != nil { + return err + } + payload := envelope.ExecutionPayload + + var finalizedHash common.Hash + if payload.Number%devEpochLength == 0 { + finalizedHash = payload.BlockHash + } else { + if fh := c.finalizedBlockHash(payload.Number); fh == nil { + return errors.New("chain rewind interrupted calculation of finalized block hash") + } else { + finalizedHash = *fh + } + } + + // Mark the payload as canon + if _, err = c.engineAPI.NewPayloadV2(*payload); err != nil { + return err + } + c.setCurrentState(payload.BlockHash, finalizedHash) + // Mark the block containing the payload as canonical + if _, err = c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, nil); err != nil { + return err + } + c.lastBlockTime = payload.Timestamp + return nil +} + +// loopOnDemand runs the block production loop for "on-demand" configuration (period = 0) +func (c *SimulatedBeacon) loopOnDemand() { + var ( + newTxs = make(chan core.NewTxsEvent) + sub = c.eth.TxPool().SubscribeTransactions(newTxs, true) + ) + defer sub.Unsubscribe() + + for { + select { + case <-c.shutdownCh: + return + case w := <-c.withdrawals.pending: + withdrawals := append(c.withdrawals.gatherPending(9), w) + if err := c.sealBlock(withdrawals); err != nil { + log.Warn("Error performing sealing work", "err", err) + } + case <-newTxs: + withdrawals := c.withdrawals.gatherPending(10) + if err := c.sealBlock(withdrawals); err != nil { + log.Warn("Error performing sealing work", "err", err) + } + } + } +} + +// loop runs the block production loop for non-zero period configuration +func (c *SimulatedBeacon) loop() { + timer := time.NewTimer(0) + for { + select { + case <-c.shutdownCh: + return + case <-timer.C: + withdrawals := c.withdrawals.gatherPending(10) + if err := c.sealBlock(withdrawals); err != nil { + log.Warn("Error performing sealing work", "err", err) + } else { + timer.Reset(time.Second * time.Duration(c.period)) + } + } + } +} + +// finalizedBlockHash returns the block hash of the finalized block corresponding to the given number +// or nil if doesn't exist in the chain. +func (c *SimulatedBeacon) finalizedBlockHash(number uint64) *common.Hash { + var finalizedNumber uint64 + if number%devEpochLength == 0 { + finalizedNumber = number + } else { + finalizedNumber = (number - 1) / devEpochLength * devEpochLength + } + + if finalizedBlock := c.eth.BlockChain().GetBlockByNumber(finalizedNumber); finalizedBlock != nil { + fh := finalizedBlock.Hash() + return &fh + } + return nil +} + +// setCurrentState sets the current forkchoice state +func (c *SimulatedBeacon) setCurrentState(headHash, finalizedHash common.Hash) { + c.curForkchoiceState = engine.ForkchoiceStateV1{ + HeadBlockHash: headHash, + SafeBlockHash: headHash, + FinalizedBlockHash: finalizedHash, + } +} + +func RegisterSimulatedBeaconAPIs(stack *node.Node, sim *SimulatedBeacon) { + stack.RegisterAPIs([]rpc.API{ + { + Namespace: "dev", + Service: &api{sim}, + Version: "1.0", + }, + }) +} diff --git a/internal/debug/trace_fallback.go b/eth/catalyst/simulated_beacon_api.go similarity index 61% rename from internal/debug/trace_fallback.go rename to eth/catalyst/simulated_beacon_api.go index ec07d991efd6..93670257f6b4 100644 --- a/internal/debug/trace_fallback.go +++ b/eth/catalyst/simulated_beacon_api.go @@ -1,4 +1,4 @@ -// Copyright 2016 The go-ethereum Authors +// Copyright 2023 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 @@ -14,19 +14,23 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -//go:build !go1.5 -// +build !go1.5 +package catalyst -// no-op implementation of tracing methods for Go < 1.5. +import ( + "context" -package debug + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) -import "errors" +type api struct { + simBeacon *SimulatedBeacon +} -func (*HandlerT) StartGoTrace(string) error { - return errors.New("tracing is not supported on Go < 1.5") +func (a *api) AddWithdrawal(ctx context.Context, withdrawal *types.Withdrawal) error { + return a.simBeacon.withdrawals.add(withdrawal) } -func (*HandlerT) StopGoTrace() error { - return errors.New("tracing is not supported on Go < 1.5") +func (a *api) SetFeeRecipient(ctx context.Context, feeRecipient common.Address) { + a.simBeacon.setFeeRecipient(feeRecipient) } diff --git a/eth/catalyst/simulated_beacon_test.go b/eth/catalyst/simulated_beacon_test.go new file mode 100644 index 000000000000..0df195fb9da4 --- /dev/null +++ b/eth/catalyst/simulated_beacon_test.go @@ -0,0 +1,141 @@ +// Copyright 2023 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 catalyst + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/ethconfig" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/params" +) + +func startSimulatedBeaconEthService(t *testing.T, genesis *core.Genesis) (*node.Node, *eth.Ethereum, *SimulatedBeacon) { + t.Helper() + + n, err := node.New(&node.Config{ + P2P: p2p.Config{ + ListenAddr: "127.0.0.1:8545", + NoDiscovery: true, + MaxPeers: 0, + }, + }) + if err != nil { + t.Fatal("can't create node:", err) + } + + ethcfg := ðconfig.Config{Genesis: genesis, SyncMode: downloader.FullSync, TrieTimeout: time.Minute, TrieDirtyCache: 256, TrieCleanCache: 256} + ethservice, err := eth.New(n, ethcfg) + if err != nil { + t.Fatal("can't create eth service:", err) + } + + simBeacon, err := NewSimulatedBeacon(1, ethservice) + if err != nil { + t.Fatal("can't create simulated beacon:", err) + } + + n.RegisterLifecycle(simBeacon) + + if err := n.Start(); err != nil { + t.Fatal("can't start node:", err) + } + + ethservice.SetSynced() + return n, ethservice, simBeacon +} + +// send 20 transactions, >10 withdrawals and ensure they are included in order +// send enough transactions to fill multiple blocks +func TestSimulatedBeaconSendWithdrawals(t *testing.T) { + var withdrawals []types.Withdrawal + txs := make(map[common.Hash]types.Transaction) + + var ( + // testKey is a private key to use for funding a tester account. + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + + // testAddr is the Ethereum address of the tester account. + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) + ) + + // short period (1 second) for testing purposes + var gasLimit uint64 = 10_000_000 + genesis := core.DeveloperGenesisBlock(gasLimit, testAddr) + node, ethService, mock := startSimulatedBeaconEthService(t, genesis) + _ = mock + defer node.Close() + + chainHeadCh := make(chan core.ChainHeadEvent, 10) + subscription := ethService.BlockChain().SubscribeChainHeadEvent(chainHeadCh) + defer subscription.Unsubscribe() + + // generate some withdrawals + for i := 0; i < 20; i++ { + withdrawals = append(withdrawals, types.Withdrawal{Index: uint64(i)}) + if err := mock.withdrawals.add(&withdrawals[i]); err != nil { + t.Fatal("addWithdrawal failed", err) + } + } + + // generate a bunch of transactions + signer := types.NewEIP155Signer(ethService.BlockChain().Config().ChainID) + for i := 0; i < 20; i++ { + tx, err := types.SignTx(types.NewTransaction(uint64(i), common.Address{}, big.NewInt(1000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, testKey) + if err != nil { + t.Fatalf("error signing transaction, err=%v", err) + } + txs[tx.Hash()] = *tx + + if err := ethService.APIBackend.SendTx(context.Background(), tx); err != nil { + t.Fatal("SendTx failed", err) + } + } + + includedTxs := make(map[common.Hash]struct{}) + var includedWithdrawals []uint64 + + timer := time.NewTimer(12 * time.Second) + for { + select { + case evt := <-chainHeadCh: + for _, includedTx := range evt.Block.Transactions() { + includedTxs[includedTx.Hash()] = struct{}{} + } + for _, includedWithdrawal := range evt.Block.Withdrawals() { + includedWithdrawals = append(includedWithdrawals, includedWithdrawal.Index) + } + + // ensure all withdrawals/txs included. this will take two blocks b/c number of withdrawals > 10 + if len(includedTxs) == len(txs) && len(includedWithdrawals) == len(withdrawals) && evt.Block.Number().Cmp(big.NewInt(2)) == 0 { + return + } + case <-timer.C: + t.Fatal("timed out without including all withdrawals/txs") + } + } +} diff --git a/eth/catalyst/tester.go b/eth/catalyst/tester.go index c4eafd30d918..0922ac0ba654 100644 --- a/eth/catalyst/tester.go +++ b/eth/catalyst/tester.go @@ -20,7 +20,7 @@ import ( "sync" "time" - "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/log" @@ -28,23 +28,27 @@ import ( ) // FullSyncTester is an auxiliary service that allows Geth to perform full sync -// alone without consensus-layer attached. Users must specify a valid block as -// the sync target. This tester can be applied to different networks, no matter -// it's pre-merge or post-merge, but only for full-sync. +// alone without consensus-layer attached. Users must specify a valid block hash +// as the sync target. +// +// This tester can be applied to different networks, no matter it's pre-merge or +// post-merge, but only for full-sync. type FullSyncTester struct { - api *ConsensusAPI - block *types.Block - closed chan struct{} - wg sync.WaitGroup + stack *node.Node + backend *eth.Ethereum + target common.Hash + closed chan struct{} + wg sync.WaitGroup } // RegisterFullSyncTester registers the full-sync tester service into the node // stack for launching and stopping the service controlled by node. -func RegisterFullSyncTester(stack *node.Node, backend *eth.Ethereum, block *types.Block) (*FullSyncTester, error) { +func RegisterFullSyncTester(stack *node.Node, backend *eth.Ethereum, target common.Hash) (*FullSyncTester, error) { cl := &FullSyncTester{ - api: NewConsensusAPI(backend), - block: block, - closed: make(chan struct{}), + stack: stack, + backend: backend, + target: target, + closed: make(chan struct{}), } stack.RegisterLifecycle(cl) return cl, nil @@ -56,29 +60,25 @@ func (tester *FullSyncTester) Start() error { go func() { defer tester.wg.Done() + // Trigger beacon sync with the provided block hash as trusted + // chain head. + err := tester.backend.Downloader().BeaconDevSync(downloader.FullSync, tester.target, tester.closed) + if err != nil { + log.Info("Failed to trigger beacon sync", "err", err) + } + ticker := time.NewTicker(time.Second * 5) defer ticker.Stop() for { select { case <-ticker.C: - // Don't bother downloader in case it's already syncing. - if tester.api.eth.Downloader().Synchronising() { - continue - } - // Short circuit in case the target block is already stored - // locally. TODO(somehow terminate the node stack if target - // is reached). - if tester.api.eth.BlockChain().HasBlock(tester.block.Hash(), tester.block.NumberU64()) { - log.Info("Full-sync target reached", "number", tester.block.NumberU64(), "hash", tester.block.Hash()) + // Stop in case the target block is already stored locally. + if block := tester.backend.BlockChain().GetBlockByHash(tester.target); block != nil { + log.Info("Full-sync target reached", "number", block.NumberU64(), "hash", block.Hash()) + go tester.stack.Close() // async since we need to close ourselves return } - // Trigger beacon sync with the provided block header as - // trusted chain head. - err := tester.api.eth.Downloader().BeaconSync(downloader.FullSync, tester.block.Header(), nil) - if err != nil { - log.Info("Failed to beacon sync", "err", err) - } case <-tester.closed: return diff --git a/eth/downloader/beacondevsync.go b/eth/downloader/beacondevsync.go new file mode 100644 index 000000000000..9a38fedd463e --- /dev/null +++ b/eth/downloader/beacondevsync.go @@ -0,0 +1,81 @@ +// Copyright 2023 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 downloader + +import ( + "errors" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +// BeaconDevSync is a development helper to test synchronization by providing +// a block hash instead of header to run the beacon sync against. +// +// The method will reach out to the network to retrieve the header of the sync +// target instead of receiving it from the consensus node. +// +// Note, this must not be used in live code. If the forkchcoice endpoint where +// to use this instead of giving us the payload first, then essentially nobody +// in the network would have the block yet that we'd attempt to retrieve. +func (d *Downloader) BeaconDevSync(mode SyncMode, hash common.Hash, stop chan struct{}) error { + // Be very loud that this code should not be used in a live node + log.Warn("----------------------------------") + log.Warn("Beacon syncing with hash as target", "hash", hash) + log.Warn("This is unhealthy for a live node!") + log.Warn("----------------------------------") + + log.Info("Waiting for peers to retrieve sync target") + for { + // If the node is going down, unblock + select { + case <-stop: + return errors.New("stop requested") + default: + } + // Pick a random peer to sync from and keep retrying if none are yet + // available due to fresh startup + d.peers.lock.RLock() + var peer *peerConnection + for _, peer = range d.peers.peers { + break + } + d.peers.lock.RUnlock() + + if peer == nil { + time.Sleep(time.Second) + continue + } + // Found a peer, attempt to retrieve the header whilst blocking and + // retry if it fails for whatever reason + log.Info("Attempting to retrieve sync target", "peer", peer.id) + headers, metas, err := d.fetchHeadersByHash(peer, hash, 1, 0, false) + if err != nil || len(headers) != 1 { + log.Warn("Failed to fetch sync target", "headers", len(headers), "err", err) + time.Sleep(time.Second) + continue + } + // Head header retrieved, if the hash matches, start the actual sync + if metas[0] != hash { + log.Error("Received invalid sync target", "want", hash, "have", metas[0]) + time.Sleep(time.Second) + continue + } + return d.BeaconSync(mode, headers[0], headers[0]) + } +} diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 9a805396c4d3..2ca7e328c6c4 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -286,11 +286,6 @@ func (d *Downloader) Progress() ethereum.SyncProgress { } } -// Synchronising returns whether the downloader is currently retrieving blocks. -func (d *Downloader) Synchronising() bool { - return d.synchronising.Load() -} - // RegisterPeer injects a new download peer into the set of block source to be // used for fetching hashes and blocks from. func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error { @@ -309,11 +304,6 @@ func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error { return nil } -// RegisterLightPeer injects a light client peer, wrapping it so it appears as a regular peer. -func (d *Downloader) RegisterLightPeer(id string, version uint, peer LightPeer) error { - return d.RegisterPeer(id, version, &lightPeerWrapper{peer}) -} - // UnregisterPeer remove a peer from the known list, preventing any action from // the specified peer. An effort is also made to return any pending fetches into // the queue. @@ -398,7 +388,16 @@ func (d *Downloader) synchronise(id string, hash common.Hash, td, ttd *big.Int, log.Info("Block synchronisation started") } if mode == SnapSync { - // Snap sync uses the snapshot namespace to store potentially flakey data until + // Snap sync will directly modify the persistent state, making the entire + // trie database unusable until the state is fully synced. To prevent any + // subsequent state reads, explicitly disable the trie database and state + // syncer is responsible to address and correct any state missing. + if d.blockchain.TrieDB().Scheme() == rawdb.PathScheme { + if err := d.blockchain.TrieDB().Disable(); err != nil { + return err + } + } + // Snap sync uses the snapshot namespace to store potentially flaky data until // sync completely heals and finishes. Pause snapshot maintenance in the mean- // time to prevent access. if snapshots := d.blockchain.Snapshots(); snapshots != nil { // Only nil in tests @@ -1273,41 +1272,13 @@ func (d *Downloader) fetchReceipts(from uint64, beaconMode bool) error { // keeps processing and scheduling them into the header chain and downloader's // queue until the stream ends or a failure occurs. func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode bool) error { - // Keep a count of uncertain headers to roll back var ( - rollback uint64 // Zero means no rollback (fine as you can't unroll the genesis) - rollbackErr error - mode = d.getMode() + mode = d.getMode() + gotHeaders = false // Wait for batches of headers to process ) - defer func() { - if rollback > 0 { - lastHeader, lastFastBlock, lastBlock := d.lightchain.CurrentHeader().Number, common.Big0, common.Big0 - if mode != LightSync { - lastFastBlock = d.blockchain.CurrentSnapBlock().Number - lastBlock = d.blockchain.CurrentBlock().Number - } - if err := d.lightchain.SetHead(rollback - 1); err != nil { // -1 to target the parent of the first uncertain block - // We're already unwinding the stack, only print the error to make it more visible - log.Error("Failed to roll back chain segment", "head", rollback-1, "err", err) - } - curFastBlock, curBlock := common.Big0, common.Big0 - if mode != LightSync { - curFastBlock = d.blockchain.CurrentSnapBlock().Number - curBlock = d.blockchain.CurrentBlock().Number - } - log.Warn("Rolled back chain segment", - "header", fmt.Sprintf("%d->%d", lastHeader, d.lightchain.CurrentHeader().Number), - "snap", fmt.Sprintf("%d->%d", lastFastBlock, curFastBlock), - "block", fmt.Sprintf("%d->%d", lastBlock, curBlock), "reason", rollbackErr) - } - }() - // Wait for batches of headers to process - gotHeaders := false - for { select { case <-d.cancelCh: - rollbackErr = errCanceled return errCanceled case task := <-d.headerProcCh: @@ -1356,8 +1327,6 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode } } } - // Disable any rollback and return - rollback = 0 return nil } // Otherwise split the chunk of headers into batches and process them @@ -1368,7 +1337,6 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode // Terminate if something failed in between processing chunks select { case <-d.cancelCh: - rollbackErr = errCanceled return errCanceled default: } @@ -1415,29 +1383,11 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode } if len(chunkHeaders) > 0 { if n, err := d.lightchain.InsertHeaderChain(chunkHeaders); err != nil { - rollbackErr = err - - // If some headers were inserted, track them as uncertain - if mode == SnapSync && n > 0 && rollback == 0 { - rollback = chunkHeaders[0].Number.Uint64() - } log.Warn("Invalid header encountered", "number", chunkHeaders[n].Number, "hash", chunkHashes[n], "parent", chunkHeaders[n].ParentHash, "err", err) return fmt.Errorf("%w: %v", errInvalidChain, err) } - // All verifications passed, track all headers within the allowed limits - if mode == SnapSync { - head := chunkHeaders[len(chunkHeaders)-1].Number.Uint64() - if head-rollback > uint64(fsHeaderSafetyNet) { - rollback = head - uint64(fsHeaderSafetyNet) - } else { - rollback = 1 - } - } } if len(rejected) != 0 { - // Merge threshold reached, stop importing, but don't roll back - rollback = 0 - log.Info("Legacy sync reached merge threshold", "number", rejected[0].Number, "hash", rejected[0].Hash(), "td", td, "ttd", ttd) return ErrMergeTransition } @@ -1448,7 +1398,6 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode for d.queue.PendingBodies() >= maxQueuedHeaders || d.queue.PendingReceipts() >= maxQueuedHeaders { select { case <-d.cancelCh: - rollbackErr = errCanceled return errCanceled case <-time.After(time.Second): } @@ -1456,7 +1405,6 @@ func (d *Downloader) processHeaders(origin uint64, td, ttd *big.Int, beaconMode // Otherwise insert the headers for content retrieval inserts := d.queue.Schedule(chunkHeaders, chunkHashes, origin) if len(inserts) != len(chunkHeaders) { - rollbackErr = fmt.Errorf("stale headers: len inserts %v len(chunk) %v", len(inserts), len(chunkHeaders)) return fmt.Errorf("%w: stale headers", errBadPeer) } } @@ -1606,17 +1554,30 @@ func (d *Downloader) processSnapSyncContent() error { // To cater for moving pivot points, track the pivot block and subsequently // accumulated download results separately. + // + // These will be nil up to the point where we reach the pivot, and will only + // be set temporarily if the synced blocks are piling up, but the pivot is + // still busy downloading. In that case, we need to occasionally check for + // pivot moves, so need to unblock the loop. These fields will accumulate + // the results in the meantime. + // + // Note, there's no issue with memory piling up since after 64 blocks the + // pivot will forcefully move so these accumulators will be dropped. var ( oldPivot *fetchResult // Locked in pivot block, might change eventually oldTail []*fetchResult // Downloaded content after the pivot ) for { - // Wait for the next batch of downloaded data to be available, and if the pivot - // block became stale, move the goalpost - results := d.queue.Results(oldPivot == nil) // Block if we're not monitoring pivot staleness + // Wait for the next batch of downloaded data to be available. If we have + // not yet reached the pivot point, wait blockingly as there's no need to + // spin-loop check for pivot moves. If we reached the pivot but have not + // yet processed it, check for results async, so we might notice pivot + // moves while state syncing. If the pivot was passed fully, block again + // as there's no more reason to check for pivot moves at all. + results := d.queue.Results(oldPivot == nil) if len(results) == 0 { // If pivot sync is done, stop - if oldPivot == nil { + if d.committed.Load() { d.reportSnapSyncProgress(true) return sync.Cancel() } @@ -1639,21 +1600,23 @@ func (d *Downloader) processSnapSyncContent() error { pivot := d.pivotHeader d.pivotLock.RUnlock() - if oldPivot == nil { - if pivot.Root != sync.root { - sync.Cancel() - sync = d.syncState(pivot.Root) + if oldPivot == nil { // no results piling up, we can move the pivot + if !d.committed.Load() { // not yet passed the pivot, we can move the pivot + if pivot.Root != sync.root { // pivot position changed, we can move the pivot + sync.Cancel() + sync = d.syncState(pivot.Root) - go closeOnErr(sync) + go closeOnErr(sync) + } } - } else { + } else { // results already piled up, consume before handling pivot move results = append(append([]*fetchResult{oldPivot}, oldTail...), results...) } // Split around the pivot block and process the two sides via snap/full sync if !d.committed.Load() { latest := results[len(results)-1].Header // If the height is above the pivot block by 2 sets, it means the pivot - // become stale in the network and it was garbage collected, move to a + // become stale in the network, and it was garbage collected, move to a // new pivot. // // Note, we have `reorgProtHeaderDelay` number of blocks withheld, Those diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index 3fc2f7142a49..567fddf5d202 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -177,7 +177,7 @@ func unmarshalRlpHeaders(rlpdata []rlp.RawValue) []*types.Header { // function can be used to retrieve batches of headers from the particular peer. func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool, sink chan *eth.Response) (*eth.Request, error) { // Service the header query via the live handler code - rlpHeaders := eth.ServiceGetBlockHeadersQuery(dlp.chain, ð.GetBlockHeadersPacket{ + rlpHeaders := eth.ServiceGetBlockHeadersQuery(dlp.chain, ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{ Hash: origin, }, @@ -205,7 +205,7 @@ func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount i } res := ð.Response{ Req: req, - Res: (*eth.BlockHeadersPacket)(&headers), + Res: (*eth.BlockHeadersRequest)(&headers), Meta: hashes, Time: 1, Done: make(chan error, 1), // Ignore the returned status @@ -221,7 +221,7 @@ func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount i // function can be used to retrieve batches of headers from the particular peer. func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool, sink chan *eth.Response) (*eth.Request, error) { // Service the header query via the live handler code - rlpHeaders := eth.ServiceGetBlockHeadersQuery(dlp.chain, ð.GetBlockHeadersPacket{ + rlpHeaders := eth.ServiceGetBlockHeadersQuery(dlp.chain, ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{ Number: origin, }, @@ -249,7 +249,7 @@ func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int, } res := ð.Response{ Req: req, - Res: (*eth.BlockHeadersPacket)(&headers), + Res: (*eth.BlockHeadersRequest)(&headers), Meta: hashes, Time: 1, Done: make(chan error, 1), // Ignore the returned status @@ -286,7 +286,7 @@ func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash, sink chan *et } res := ð.Response{ Req: req, - Res: (*eth.BlockBodiesPacket)(&bodies), + Res: (*eth.BlockBodiesResponse)(&bodies), Meta: [][]common.Hash{txsHashes, uncleHashes, withdrawalHashes}, Time: 1, Done: make(chan error, 1), // Ignore the returned status @@ -317,7 +317,7 @@ func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash, sink chan * } res := ð.Response{ Req: req, - Res: (*eth.ReceiptsPacket)(&receipts), + Res: (*eth.ReceiptsResponse)(&receipts), Meta: hashes, Time: 1, Done: make(chan error, 1), // Ignore the returned status @@ -437,9 +437,9 @@ func assertOwnChain(t *testing.T, tester *downloadTester, length int) { } } -func TestCanonicalSynchronisation66Full(t *testing.T) { testCanonSync(t, eth.ETH66, FullSync) } -func TestCanonicalSynchronisation66Snap(t *testing.T) { testCanonSync(t, eth.ETH66, SnapSync) } -func TestCanonicalSynchronisation66Light(t *testing.T) { testCanonSync(t, eth.ETH66, LightSync) } +func TestCanonicalSynchronisation68Full(t *testing.T) { testCanonSync(t, eth.ETH68, FullSync) } +func TestCanonicalSynchronisation68Snap(t *testing.T) { testCanonSync(t, eth.ETH68, SnapSync) } +func TestCanonicalSynchronisation68Light(t *testing.T) { testCanonSync(t, eth.ETH68, LightSync) } func TestCanonicalSynchronisation67Full(t *testing.T) { testCanonSync(t, eth.ETH67, FullSync) } func TestCanonicalSynchronisation67Snap(t *testing.T) { testCanonSync(t, eth.ETH67, SnapSync) } func TestCanonicalSynchronisation67Light(t *testing.T) { testCanonSync(t, eth.ETH67, LightSync) } @@ -461,8 +461,8 @@ func testCanonSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that if a large batch of blocks are being downloaded, it is throttled // until the cached blocks are retrieved. -func TestThrottling66Full(t *testing.T) { testThrottling(t, eth.ETH66, FullSync) } -func TestThrottling66Snap(t *testing.T) { testThrottling(t, eth.ETH66, SnapSync) } +func TestThrottling68Full(t *testing.T) { testThrottling(t, eth.ETH68, FullSync) } +func TestThrottling68Snap(t *testing.T) { testThrottling(t, eth.ETH68, SnapSync) } func TestThrottling67Full(t *testing.T) { testThrottling(t, eth.ETH67, FullSync) } func TestThrottling67Snap(t *testing.T) { testThrottling(t, eth.ETH67, SnapSync) } @@ -543,9 +543,9 @@ func testThrottling(t *testing.T, protocol uint, mode SyncMode) { // Tests that simple synchronization against a forked chain works correctly. In // this test common ancestor lookup should *not* be short circuited, and a full // binary search should be executed. -func TestForkedSync66Full(t *testing.T) { testForkedSync(t, eth.ETH66, FullSync) } -func TestForkedSync66Snap(t *testing.T) { testForkedSync(t, eth.ETH66, SnapSync) } -func TestForkedSync66Light(t *testing.T) { testForkedSync(t, eth.ETH66, LightSync) } +func TestForkedSync68Full(t *testing.T) { testForkedSync(t, eth.ETH68, FullSync) } +func TestForkedSync68Snap(t *testing.T) { testForkedSync(t, eth.ETH68, SnapSync) } +func TestForkedSync68Light(t *testing.T) { testForkedSync(t, eth.ETH68, LightSync) } func TestForkedSync67Full(t *testing.T) { testForkedSync(t, eth.ETH67, FullSync) } func TestForkedSync67Snap(t *testing.T) { testForkedSync(t, eth.ETH67, SnapSync) } func TestForkedSync67Light(t *testing.T) { testForkedSync(t, eth.ETH67, LightSync) } @@ -573,9 +573,9 @@ func testForkedSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that synchronising against a much shorter but much heavier fork works // currently and is not dropped. -func TestHeavyForkedSync66Full(t *testing.T) { testHeavyForkedSync(t, eth.ETH66, FullSync) } -func TestHeavyForkedSync66Snap(t *testing.T) { testHeavyForkedSync(t, eth.ETH66, SnapSync) } -func TestHeavyForkedSync66Light(t *testing.T) { testHeavyForkedSync(t, eth.ETH66, LightSync) } +func TestHeavyForkedSync68Full(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, FullSync) } +func TestHeavyForkedSync68Snap(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, SnapSync) } +func TestHeavyForkedSync68Light(t *testing.T) { testHeavyForkedSync(t, eth.ETH68, LightSync) } func TestHeavyForkedSync67Full(t *testing.T) { testHeavyForkedSync(t, eth.ETH67, FullSync) } func TestHeavyForkedSync67Snap(t *testing.T) { testHeavyForkedSync(t, eth.ETH67, SnapSync) } func TestHeavyForkedSync67Light(t *testing.T) { testHeavyForkedSync(t, eth.ETH67, LightSync) } @@ -605,9 +605,9 @@ func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that chain forks are contained within a certain interval of the current // chain head, ensuring that malicious peers cannot waste resources by feeding // long dead chains. -func TestBoundedForkedSync66Full(t *testing.T) { testBoundedForkedSync(t, eth.ETH66, FullSync) } -func TestBoundedForkedSync66Snap(t *testing.T) { testBoundedForkedSync(t, eth.ETH66, SnapSync) } -func TestBoundedForkedSync66Light(t *testing.T) { testBoundedForkedSync(t, eth.ETH66, LightSync) } +func TestBoundedForkedSync68Full(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, FullSync) } +func TestBoundedForkedSync68Snap(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, SnapSync) } +func TestBoundedForkedSync68Light(t *testing.T) { testBoundedForkedSync(t, eth.ETH68, LightSync) } func TestBoundedForkedSync67Full(t *testing.T) { testBoundedForkedSync(t, eth.ETH67, FullSync) } func TestBoundedForkedSync67Snap(t *testing.T) { testBoundedForkedSync(t, eth.ETH67, SnapSync) } func TestBoundedForkedSync67Light(t *testing.T) { testBoundedForkedSync(t, eth.ETH67, LightSync) } @@ -636,14 +636,14 @@ func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that chain forks are contained within a certain interval of the current // chain head for short but heavy forks too. These are a bit special because they // take different ancestor lookup paths. -func TestBoundedHeavyForkedSync66Full(t *testing.T) { - testBoundedHeavyForkedSync(t, eth.ETH66, FullSync) +func TestBoundedHeavyForkedSync68Full(t *testing.T) { + testBoundedHeavyForkedSync(t, eth.ETH68, FullSync) } -func TestBoundedHeavyForkedSync66Snap(t *testing.T) { - testBoundedHeavyForkedSync(t, eth.ETH66, SnapSync) +func TestBoundedHeavyForkedSync68Snap(t *testing.T) { + testBoundedHeavyForkedSync(t, eth.ETH68, SnapSync) } -func TestBoundedHeavyForkedSync66Light(t *testing.T) { - testBoundedHeavyForkedSync(t, eth.ETH66, LightSync) +func TestBoundedHeavyForkedSync68Light(t *testing.T) { + testBoundedHeavyForkedSync(t, eth.ETH68, LightSync) } func TestBoundedHeavyForkedSync67Full(t *testing.T) { testBoundedHeavyForkedSync(t, eth.ETH67, FullSync) @@ -678,9 +678,9 @@ func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { } // Tests that a canceled download wipes all previously accumulated state. -func TestCancel66Full(t *testing.T) { testCancel(t, eth.ETH66, FullSync) } -func TestCancel66Snap(t *testing.T) { testCancel(t, eth.ETH66, SnapSync) } -func TestCancel66Light(t *testing.T) { testCancel(t, eth.ETH66, LightSync) } +func TestCancel68Full(t *testing.T) { testCancel(t, eth.ETH68, FullSync) } +func TestCancel68Snap(t *testing.T) { testCancel(t, eth.ETH68, SnapSync) } +func TestCancel68Light(t *testing.T) { testCancel(t, eth.ETH68, LightSync) } func TestCancel67Full(t *testing.T) { testCancel(t, eth.ETH67, FullSync) } func TestCancel67Snap(t *testing.T) { testCancel(t, eth.ETH67, SnapSync) } func TestCancel67Light(t *testing.T) { testCancel(t, eth.ETH67, LightSync) } @@ -708,9 +708,9 @@ func testCancel(t *testing.T, protocol uint, mode SyncMode) { } // Tests that synchronisation from multiple peers works as intended (multi thread sanity test). -func TestMultiSynchronisation66Full(t *testing.T) { testMultiSynchronisation(t, eth.ETH66, FullSync) } -func TestMultiSynchronisation66Snap(t *testing.T) { testMultiSynchronisation(t, eth.ETH66, SnapSync) } -func TestMultiSynchronisation66Light(t *testing.T) { testMultiSynchronisation(t, eth.ETH66, LightSync) } +func TestMultiSynchronisation68Full(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, FullSync) } +func TestMultiSynchronisation68Snap(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, SnapSync) } +func TestMultiSynchronisation68Light(t *testing.T) { testMultiSynchronisation(t, eth.ETH68, LightSync) } func TestMultiSynchronisation67Full(t *testing.T) { testMultiSynchronisation(t, eth.ETH67, FullSync) } func TestMultiSynchronisation67Snap(t *testing.T) { testMultiSynchronisation(t, eth.ETH67, SnapSync) } func TestMultiSynchronisation67Light(t *testing.T) { testMultiSynchronisation(t, eth.ETH67, LightSync) } @@ -735,9 +735,9 @@ func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) { // Tests that synchronisations behave well in multi-version protocol environments // and not wreak havoc on other nodes in the network. -func TestMultiProtoSynchronisation66Full(t *testing.T) { testMultiProtoSync(t, eth.ETH66, FullSync) } -func TestMultiProtoSynchronisation66Snap(t *testing.T) { testMultiProtoSync(t, eth.ETH66, SnapSync) } -func TestMultiProtoSynchronisation66Light(t *testing.T) { testMultiProtoSync(t, eth.ETH66, LightSync) } +func TestMultiProtoSynchronisation68Full(t *testing.T) { testMultiProtoSync(t, eth.ETH68, FullSync) } +func TestMultiProtoSynchronisation68Snap(t *testing.T) { testMultiProtoSync(t, eth.ETH68, SnapSync) } +func TestMultiProtoSynchronisation68Light(t *testing.T) { testMultiProtoSync(t, eth.ETH68, LightSync) } func TestMultiProtoSynchronisation67Full(t *testing.T) { testMultiProtoSync(t, eth.ETH67, FullSync) } func TestMultiProtoSynchronisation67Snap(t *testing.T) { testMultiProtoSync(t, eth.ETH67, SnapSync) } func TestMultiProtoSynchronisation67Light(t *testing.T) { testMultiProtoSync(t, eth.ETH67, LightSync) } @@ -750,7 +750,7 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { chain := testChainBase.shorten(blockCacheMaxItems - 15) // Create peers of every type - tester.newPeer("peer 66", eth.ETH66, chain.blocks[1:]) + tester.newPeer("peer 68", eth.ETH68, chain.blocks[1:]) tester.newPeer("peer 67", eth.ETH67, chain.blocks[1:]) // Synchronise with the requested peer and make sure all blocks were retrieved @@ -760,7 +760,7 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { assertOwnChain(t, tester, len(chain.blocks)) // Check that no peers have been dropped off - for _, version := range []int{66, 67} { + for _, version := range []int{68, 67} { peer := fmt.Sprintf("peer %d", version) if _, ok := tester.peers[peer]; !ok { t.Errorf("%s dropped", peer) @@ -770,9 +770,9 @@ func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { // Tests that if a block is empty (e.g. header only), no body request should be // made, and instead the header should be assembled into a whole block in itself. -func TestEmptyShortCircuit66Full(t *testing.T) { testEmptyShortCircuit(t, eth.ETH66, FullSync) } -func TestEmptyShortCircuit66Snap(t *testing.T) { testEmptyShortCircuit(t, eth.ETH66, SnapSync) } -func TestEmptyShortCircuit66Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH66, LightSync) } +func TestEmptyShortCircuit68Full(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, FullSync) } +func TestEmptyShortCircuit68Snap(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, SnapSync) } +func TestEmptyShortCircuit68Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH68, LightSync) } func TestEmptyShortCircuit67Full(t *testing.T) { testEmptyShortCircuit(t, eth.ETH67, FullSync) } func TestEmptyShortCircuit67Snap(t *testing.T) { testEmptyShortCircuit(t, eth.ETH67, SnapSync) } func TestEmptyShortCircuit67Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH67, LightSync) } @@ -821,9 +821,9 @@ func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { // Tests that headers are enqueued continuously, preventing malicious nodes from // stalling the downloader by feeding gapped header chains. -func TestMissingHeaderAttack66Full(t *testing.T) { testMissingHeaderAttack(t, eth.ETH66, FullSync) } -func TestMissingHeaderAttack66Snap(t *testing.T) { testMissingHeaderAttack(t, eth.ETH66, SnapSync) } -func TestMissingHeaderAttack66Light(t *testing.T) { testMissingHeaderAttack(t, eth.ETH66, LightSync) } +func TestMissingHeaderAttack68Full(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, FullSync) } +func TestMissingHeaderAttack68Snap(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, SnapSync) } +func TestMissingHeaderAttack68Light(t *testing.T) { testMissingHeaderAttack(t, eth.ETH68, LightSync) } func TestMissingHeaderAttack67Full(t *testing.T) { testMissingHeaderAttack(t, eth.ETH67, FullSync) } func TestMissingHeaderAttack67Snap(t *testing.T) { testMissingHeaderAttack(t, eth.ETH67, SnapSync) } func TestMissingHeaderAttack67Light(t *testing.T) { testMissingHeaderAttack(t, eth.ETH67, LightSync) } @@ -850,9 +850,9 @@ func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { // Tests that if requested headers are shifted (i.e. first is missing), the queue // detects the invalid numbering. -func TestShiftedHeaderAttack66Full(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH66, FullSync) } -func TestShiftedHeaderAttack66Snap(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH66, SnapSync) } -func TestShiftedHeaderAttack66Light(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH66, LightSync) } +func TestShiftedHeaderAttack68Full(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, FullSync) } +func TestShiftedHeaderAttack68Snap(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, SnapSync) } +func TestShiftedHeaderAttack68Light(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH68, LightSync) } func TestShiftedHeaderAttack67Full(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH67, FullSync) } func TestShiftedHeaderAttack67Snap(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH67, SnapSync) } func TestShiftedHeaderAttack67Light(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH67, LightSync) } @@ -878,96 +878,16 @@ func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { assertOwnChain(t, tester, len(chain.blocks)) } -// Tests that upon detecting an invalid header, the recent ones are rolled back -// for various failure scenarios. Afterwards a full sync is attempted to make -// sure no state was corrupted. -func TestInvalidHeaderRollback66Snap(t *testing.T) { testInvalidHeaderRollback(t, eth.ETH66, SnapSync) } -func TestInvalidHeaderRollback67Snap(t *testing.T) { testInvalidHeaderRollback(t, eth.ETH67, SnapSync) } - -func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { - tester := newTester(t) - defer tester.terminate() - - // Create a small enough block chain to download - targetBlocks := 3*fsHeaderSafetyNet + 256 + fsMinFullBlocks - chain := testChainBase.shorten(targetBlocks) - - // Attempt to sync with an attacker that feeds junk during the fast sync phase. - // This should result in the last fsHeaderSafetyNet headers being rolled back. - missing := fsHeaderSafetyNet + MaxHeaderFetch + 1 - - fastAttacker := tester.newPeer("fast-attack", protocol, chain.blocks[1:]) - fastAttacker.withholdHeaders[chain.blocks[missing].Hash()] = struct{}{} - - if err := tester.sync("fast-attack", nil, mode); err == nil { - t.Fatalf("succeeded fast attacker synchronisation") - } - if head := tester.chain.CurrentHeader().Number.Int64(); int(head) > MaxHeaderFetch { - t.Errorf("rollback head mismatch: have %v, want at most %v", head, MaxHeaderFetch) - } - // Attempt to sync with an attacker that feeds junk during the block import phase. - // This should result in both the last fsHeaderSafetyNet number of headers being - // rolled back, and also the pivot point being reverted to a non-block status. - missing = 3*fsHeaderSafetyNet + MaxHeaderFetch + 1 - - blockAttacker := tester.newPeer("block-attack", protocol, chain.blocks[1:]) - fastAttacker.withholdHeaders[chain.blocks[missing].Hash()] = struct{}{} // Make sure the fast-attacker doesn't fill in - blockAttacker.withholdHeaders[chain.blocks[missing].Hash()] = struct{}{} - - if err := tester.sync("block-attack", nil, mode); err == nil { - t.Fatalf("succeeded block attacker synchronisation") - } - if head := tester.chain.CurrentHeader().Number.Int64(); int(head) > 2*fsHeaderSafetyNet+MaxHeaderFetch { - t.Errorf("rollback head mismatch: have %v, want at most %v", head, 2*fsHeaderSafetyNet+MaxHeaderFetch) - } - if mode == SnapSync { - if head := tester.chain.CurrentBlock().Number.Uint64(); head != 0 { - t.Errorf("fast sync pivot block #%d not rolled back", head) - } - } - // Attempt to sync with an attacker that withholds promised blocks after the - // fast sync pivot point. This could be a trial to leave the node with a bad - // but already imported pivot block. - withholdAttacker := tester.newPeer("withhold-attack", protocol, chain.blocks[1:]) - - tester.downloader.syncInitHook = func(uint64, uint64) { - for i := missing; i < len(chain.blocks); i++ { - withholdAttacker.withholdHeaders[chain.blocks[i].Hash()] = struct{}{} - } - tester.downloader.syncInitHook = nil - } - if err := tester.sync("withhold-attack", nil, mode); err == nil { - t.Fatalf("succeeded withholding attacker synchronisation") - } - if head := tester.chain.CurrentHeader().Number.Int64(); int(head) > 2*fsHeaderSafetyNet+MaxHeaderFetch { - t.Errorf("rollback head mismatch: have %v, want at most %v", head, 2*fsHeaderSafetyNet+MaxHeaderFetch) - } - if mode == SnapSync { - if head := tester.chain.CurrentBlock().Number.Uint64(); head != 0 { - t.Errorf("fast sync pivot block #%d not rolled back", head) - } - } - // Synchronise with the valid peer and make sure sync succeeds. Since the last rollback - // should also disable fast syncing for this process, verify that we did a fresh full - // sync. Note, we can't assert anything about the receipts since we won't purge the - // database of them, hence we can't use assertOwnChain. - tester.newPeer("valid", protocol, chain.blocks[1:]) - if err := tester.sync("valid", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - assertOwnChain(t, tester, len(chain.blocks)) -} - // Tests that a peer advertising a high TD doesn't get to stall the downloader // afterwards by not sending any useful hashes. -func TestHighTDStarvationAttack66Full(t *testing.T) { - testHighTDStarvationAttack(t, eth.ETH66, FullSync) +func TestHighTDStarvationAttack68Full(t *testing.T) { + testHighTDStarvationAttack(t, eth.ETH68, FullSync) } -func TestHighTDStarvationAttack66Snap(t *testing.T) { - testHighTDStarvationAttack(t, eth.ETH66, SnapSync) +func TestHighTDStarvationAttack68Snap(t *testing.T) { + testHighTDStarvationAttack(t, eth.ETH68, SnapSync) } -func TestHighTDStarvationAttack66Light(t *testing.T) { - testHighTDStarvationAttack(t, eth.ETH66, LightSync) +func TestHighTDStarvationAttack68Light(t *testing.T) { + testHighTDStarvationAttack(t, eth.ETH68, LightSync) } func TestHighTDStarvationAttack67Full(t *testing.T) { testHighTDStarvationAttack(t, eth.ETH67, FullSync) @@ -991,7 +911,7 @@ func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) { } // Tests that misbehaving peers are disconnected, whilst behaving ones are not. -func TestBlockHeaderAttackerDropping66(t *testing.T) { testBlockHeaderAttackerDropping(t, eth.ETH66) } +func TestBlockHeaderAttackerDropping68(t *testing.T) { testBlockHeaderAttackerDropping(t, eth.ETH68) } func TestBlockHeaderAttackerDropping67(t *testing.T) { testBlockHeaderAttackerDropping(t, eth.ETH67) } func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { @@ -1040,9 +960,9 @@ func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { // Tests that synchronisation progress (origin block number, current block number // and highest block number) is tracked and updated correctly. -func TestSyncProgress66Full(t *testing.T) { testSyncProgress(t, eth.ETH66, FullSync) } -func TestSyncProgress66Snap(t *testing.T) { testSyncProgress(t, eth.ETH66, SnapSync) } -func TestSyncProgress66Light(t *testing.T) { testSyncProgress(t, eth.ETH66, LightSync) } +func TestSyncProgress68Full(t *testing.T) { testSyncProgress(t, eth.ETH68, FullSync) } +func TestSyncProgress68Snap(t *testing.T) { testSyncProgress(t, eth.ETH68, SnapSync) } +func TestSyncProgress68Light(t *testing.T) { testSyncProgress(t, eth.ETH68, LightSync) } func TestSyncProgress67Full(t *testing.T) { testSyncProgress(t, eth.ETH67, FullSync) } func TestSyncProgress67Snap(t *testing.T) { testSyncProgress(t, eth.ETH67, SnapSync) } func TestSyncProgress67Light(t *testing.T) { testSyncProgress(t, eth.ETH67, LightSync) } @@ -1120,9 +1040,9 @@ func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.Sync // Tests that synchronisation progress (origin block number and highest block // number) is tracked and updated correctly in case of a fork (or manual head // revertal). -func TestForkedSyncProgress66Full(t *testing.T) { testForkedSyncProgress(t, eth.ETH66, FullSync) } -func TestForkedSyncProgress66Snap(t *testing.T) { testForkedSyncProgress(t, eth.ETH66, SnapSync) } -func TestForkedSyncProgress66Light(t *testing.T) { testForkedSyncProgress(t, eth.ETH66, LightSync) } +func TestForkedSyncProgress68Full(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, FullSync) } +func TestForkedSyncProgress68Snap(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, SnapSync) } +func TestForkedSyncProgress68Light(t *testing.T) { testForkedSyncProgress(t, eth.ETH68, LightSync) } func TestForkedSyncProgress67Full(t *testing.T) { testForkedSyncProgress(t, eth.ETH67, FullSync) } func TestForkedSyncProgress67Snap(t *testing.T) { testForkedSyncProgress(t, eth.ETH67, SnapSync) } func TestForkedSyncProgress67Light(t *testing.T) { testForkedSyncProgress(t, eth.ETH67, LightSync) } @@ -1194,9 +1114,9 @@ func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Tests that if synchronisation is aborted due to some failure, then the progress // origin is not updated in the next sync cycle, as it should be considered the // continuation of the previous sync and not a new instance. -func TestFailedSyncProgress66Full(t *testing.T) { testFailedSyncProgress(t, eth.ETH66, FullSync) } -func TestFailedSyncProgress66Snap(t *testing.T) { testFailedSyncProgress(t, eth.ETH66, SnapSync) } -func TestFailedSyncProgress66Light(t *testing.T) { testFailedSyncProgress(t, eth.ETH66, LightSync) } +func TestFailedSyncProgress68Full(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, FullSync) } +func TestFailedSyncProgress68Snap(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, SnapSync) } +func TestFailedSyncProgress68Light(t *testing.T) { testFailedSyncProgress(t, eth.ETH68, LightSync) } func TestFailedSyncProgress67Full(t *testing.T) { testFailedSyncProgress(t, eth.ETH67, FullSync) } func TestFailedSyncProgress67Snap(t *testing.T) { testFailedSyncProgress(t, eth.ETH67, SnapSync) } func TestFailedSyncProgress67Light(t *testing.T) { testFailedSyncProgress(t, eth.ETH67, LightSync) } @@ -1263,9 +1183,9 @@ func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { // Tests that if an attacker fakes a chain height, after the attack is detected, // the progress height is successfully reduced at the next sync invocation. -func TestFakedSyncProgress66Full(t *testing.T) { testFakedSyncProgress(t, eth.ETH66, FullSync) } -func TestFakedSyncProgress66Snap(t *testing.T) { testFakedSyncProgress(t, eth.ETH66, SnapSync) } -func TestFakedSyncProgress66Light(t *testing.T) { testFakedSyncProgress(t, eth.ETH66, LightSync) } +func TestFakedSyncProgress68Full(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, FullSync) } +func TestFakedSyncProgress68Snap(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, SnapSync) } +func TestFakedSyncProgress68Light(t *testing.T) { testFakedSyncProgress(t, eth.ETH68, LightSync) } func TestFakedSyncProgress67Full(t *testing.T) { testFakedSyncProgress(t, eth.ETH67, FullSync) } func TestFakedSyncProgress67Snap(t *testing.T) { testFakedSyncProgress(t, eth.ETH67, SnapSync) } func TestFakedSyncProgress67Light(t *testing.T) { testFakedSyncProgress(t, eth.ETH67, LightSync) } @@ -1410,8 +1330,10 @@ func TestRemoteHeaderRequestSpan(t *testing.T) { // Tests that peers below a pre-configured checkpoint block are prevented from // being fast-synced from, avoiding potential cheap eclipse attacks. -func TestBeaconSync66Full(t *testing.T) { testBeaconSync(t, eth.ETH66, FullSync) } -func TestBeaconSync66Snap(t *testing.T) { testBeaconSync(t, eth.ETH66, SnapSync) } +func TestBeaconSync68Full(t *testing.T) { testBeaconSync(t, eth.ETH68, FullSync) } +func TestBeaconSync68Snap(t *testing.T) { testBeaconSync(t, eth.ETH68, SnapSync) } +func TestBeaconSync67Full(t *testing.T) { testBeaconSync(t, eth.ETH67, FullSync) } +func TestBeaconSync67Snap(t *testing.T) { testBeaconSync(t, eth.ETH67, SnapSync) } func testBeaconSync(t *testing.T, protocol uint, mode SyncMode) { //log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) diff --git a/eth/downloader/fetchers.go b/eth/downloader/fetchers.go index 021e8c4f9bf3..cc4279b0da7a 100644 --- a/eth/downloader/fetchers.go +++ b/eth/downloader/fetchers.go @@ -58,14 +58,14 @@ func (d *Downloader) fetchHeadersByHash(p *peerConnection, hash common.Hash, amo case res := <-resCh: // Headers successfully retrieved, update the metrics headerReqTimer.Update(time.Since(start)) - headerInMeter.Mark(int64(len(*res.Res.(*eth.BlockHeadersPacket)))) + headerInMeter.Mark(int64(len(*res.Res.(*eth.BlockHeadersRequest)))) // Don't reject the packet even if it turns out to be bad, downloader will // disconnect the peer on its own terms. Simply delivery the headers to // be processed by the caller res.Done <- nil - return *res.Res.(*eth.BlockHeadersPacket), res.Meta.([]common.Hash), nil + return *res.Res.(*eth.BlockHeadersRequest), res.Meta.([]common.Hash), nil } } @@ -103,13 +103,13 @@ func (d *Downloader) fetchHeadersByNumber(p *peerConnection, number uint64, amou case res := <-resCh: // Headers successfully retrieved, update the metrics headerReqTimer.Update(time.Since(start)) - headerInMeter.Mark(int64(len(*res.Res.(*eth.BlockHeadersPacket)))) + headerInMeter.Mark(int64(len(*res.Res.(*eth.BlockHeadersRequest)))) // Don't reject the packet even if it turns out to be bad, downloader will // disconnect the peer on its own terms. Simply delivery the headers to // be processed by the caller res.Done <- nil - return *res.Res.(*eth.BlockHeadersPacket), res.Meta.([]common.Hash), nil + return *res.Res.(*eth.BlockHeadersRequest), res.Meta.([]common.Hash), nil } } diff --git a/eth/downloader/fetchers_concurrent_bodies.go b/eth/downloader/fetchers_concurrent_bodies.go index 9440972c6d70..5105fda66b3a 100644 --- a/eth/downloader/fetchers_concurrent_bodies.go +++ b/eth/downloader/fetchers_concurrent_bodies.go @@ -89,7 +89,7 @@ func (q *bodyQueue) request(peer *peerConnection, req *fetchRequest, resCh chan // deliver is responsible for taking a generic response packet from the concurrent // fetcher, unpacking the body data and delivering it to the downloader's queue. func (q *bodyQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) { - txs, uncles, withdrawals := packet.Res.(*eth.BlockBodiesPacket).Unpack() + txs, uncles, withdrawals := packet.Res.(*eth.BlockBodiesResponse).Unpack() hashsets := packet.Meta.([][]common.Hash) // {txs hashes, uncle hashes, withdrawal hashes} accepted, err := q.queue.DeliverBodies(peer.id, txs, hashsets[0], uncles, hashsets[1], withdrawals, hashsets[2]) diff --git a/eth/downloader/fetchers_concurrent_headers.go b/eth/downloader/fetchers_concurrent_headers.go index 84c7f209865a..8201f4ca7423 100644 --- a/eth/downloader/fetchers_concurrent_headers.go +++ b/eth/downloader/fetchers_concurrent_headers.go @@ -81,7 +81,7 @@ func (q *headerQueue) request(peer *peerConnection, req *fetchRequest, resCh cha // deliver is responsible for taking a generic response packet from the concurrent // fetcher, unpacking the header data and delivering it to the downloader's queue. func (q *headerQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) { - headers := *packet.Res.(*eth.BlockHeadersPacket) + headers := *packet.Res.(*eth.BlockHeadersRequest) hashes := packet.Meta.([]common.Hash) accepted, err := q.queue.DeliverHeaders(peer.id, headers, hashes, q.headerProcCh) diff --git a/eth/downloader/fetchers_concurrent_receipts.go b/eth/downloader/fetchers_concurrent_receipts.go index 1c853c218443..3169f030ba10 100644 --- a/eth/downloader/fetchers_concurrent_receipts.go +++ b/eth/downloader/fetchers_concurrent_receipts.go @@ -88,7 +88,7 @@ func (q *receiptQueue) request(peer *peerConnection, req *fetchRequest, resCh ch // deliver is responsible for taking a generic response packet from the concurrent // fetcher, unpacking the receipt data and delivering it to the downloader's queue. func (q *receiptQueue) deliver(peer *peerConnection, packet *eth.Response) (int, error) { - receipts := *packet.Res.(*eth.ReceiptsPacket) + receipts := *packet.Res.(*eth.ReceiptsResponse) hashes := packet.Meta.([]common.Hash) // {receipt hashes} accepted, err := q.queue.DeliverReceipts(peer.id, receipts, hashes) diff --git a/eth/downloader/peer.go b/eth/downloader/peer.go index 6b8269495948..4c43af5270ca 100644 --- a/eth/downloader/peer.go +++ b/eth/downloader/peer.go @@ -55,39 +55,16 @@ type peerConnection struct { lock sync.RWMutex } -// LightPeer encapsulates the methods required to synchronise with a remote light peer. -type LightPeer interface { +// Peer encapsulates the methods required to synchronise with a remote full peer. +type Peer interface { Head() (common.Hash, *big.Int) RequestHeadersByHash(common.Hash, int, int, bool, chan *eth.Response) (*eth.Request, error) RequestHeadersByNumber(uint64, int, int, bool, chan *eth.Response) (*eth.Request, error) -} -// Peer encapsulates the methods required to synchronise with a remote full peer. -type Peer interface { - LightPeer RequestBodies([]common.Hash, chan *eth.Response) (*eth.Request, error) RequestReceipts([]common.Hash, chan *eth.Response) (*eth.Request, error) } -// lightPeerWrapper wraps a LightPeer struct, stubbing out the Peer-only methods. -type lightPeerWrapper struct { - peer LightPeer -} - -func (w *lightPeerWrapper) Head() (common.Hash, *big.Int) { return w.peer.Head() } -func (w *lightPeerWrapper) RequestHeadersByHash(h common.Hash, amount int, skip int, reverse bool, sink chan *eth.Response) (*eth.Request, error) { - return w.peer.RequestHeadersByHash(h, amount, skip, reverse, sink) -} -func (w *lightPeerWrapper) RequestHeadersByNumber(i uint64, amount int, skip int, reverse bool, sink chan *eth.Response) (*eth.Request, error) { - return w.peer.RequestHeadersByNumber(i, amount, skip, reverse, sink) -} -func (w *lightPeerWrapper) RequestBodies([]common.Hash, chan *eth.Response) (*eth.Request, error) { - panic("RequestBodies not supported in light client mode sync") -} -func (w *lightPeerWrapper) RequestReceipts([]common.Hash, chan *eth.Response) (*eth.Request, error) { - panic("RequestReceipts not supported in light client mode sync") -} - // newPeerConnection creates a new downloader peer. func newPeerConnection(id string, version uint, peer Peer, logger log.Logger) *peerConnection { return &peerConnection{ diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index e9907297a0b9..e55715879772 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/params" ) const ( @@ -796,6 +797,37 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH return errInvalidBody } } + // Blocks must have a number of blobs corresponding to the header gas usage, + // and zero before the Cancun hardfork. + var blobs int + for _, tx := range txLists[index] { + // Count the number of blobs to validate against the header's blobGasUsed + blobs += len(tx.BlobHashes()) + + // Validate the data blobs individually too + if tx.Type() == types.BlobTxType { + if len(tx.BlobHashes()) == 0 { + return errInvalidBody + } + for _, hash := range tx.BlobHashes() { + if hash[0] != params.BlobTxHashVersion { + return errInvalidBody + } + } + if tx.BlobTxSidecar() != nil { + return errInvalidBody + } + } + } + if header.BlobGasUsed != nil { + if want := *header.BlobGasUsed / params.BlobTxBlobGasPerBlob; uint64(blobs) != want { // div because the header is surely good vs the body might be bloated + return errInvalidBody + } + } else { + if blobs != 0 { + return errInvalidBody + } + } return nil } diff --git a/eth/downloader/skeleton.go b/eth/downloader/skeleton.go index 12eb5700f8d3..4f1f4620481d 100644 --- a/eth/downloader/skeleton.go +++ b/eth/downloader/skeleton.go @@ -367,13 +367,31 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) { s.filler.resume() } defer func() { - if filled := s.filler.suspend(); filled != nil { - // If something was filled, try to delete stale sync helpers. If - // unsuccessful, warn the user, but not much else we can do (it's - // a programming error, just let users report an issue and don't - // choke in the meantime). - if err := s.cleanStales(filled); err != nil { - log.Error("Failed to clean stale beacon headers", "err", err) + // The filler needs to be suspended, but since it can block for a while + // when there are many blocks queued up for full-sync importing, run it + // on a separate goroutine and consume head messages that need instant + // replies. + done := make(chan struct{}) + go func() { + defer close(done) + if filled := s.filler.suspend(); filled != nil { + // If something was filled, try to delete stale sync helpers. If + // unsuccessful, warn the user, but not much else we can do (it's + // a programming error, just let users report an issue and don't + // choke in the meantime). + if err := s.cleanStales(filled); err != nil { + log.Error("Failed to clean stale beacon headers", "err", err) + } + } + }() + // Wait for the suspend to finish, consuming head events in the meantime + // and dropping them on the floor. + for { + select { + case <-done: + return + case event := <-s.headEvents: + event.errc <- errors.New("beacon syncer reorging") } } }() @@ -405,7 +423,7 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) { for _, peer := range s.peers.AllPeers() { s.idles[peer.id] = peer } - // Nofity any tester listening for startup events + // Notify any tester listening for startup events if s.syncStarting != nil { s.syncStarting() } @@ -630,7 +648,7 @@ func (s *skeleton) processNewHead(head *types.Header, final *types.Header, force } if parent := rawdb.ReadSkeletonHeader(s.db, number-1); parent.Hash() != head.ParentHash { if force { - log.Warn("Beacon chain forked", "ancestor", parent.Number, "hash", parent.Hash(), "want", head.ParentHash) + log.Warn("Beacon chain forked", "ancestor", number-1, "hash", parent.Hash(), "want", head.ParentHash) } return true } @@ -776,7 +794,7 @@ func (s *skeleton) executeTask(peer *peerConnection, req *headerRequest) { case res := <-resCh: // Headers successfully retrieved, update the metrics - headers := *res.Res.(*eth.BlockHeadersPacket) + headers := *res.Res.(*eth.BlockHeadersRequest) headerReqTimer.Update(time.Since(start)) s.peers.rates.Update(peer.id, eth.BlockHeadersMsg, res.Time, len(headers)) diff --git a/eth/downloader/skeleton_test.go b/eth/downloader/skeleton_test.go index 6a76d78ac817..c31007765a8b 100644 --- a/eth/downloader/skeleton_test.go +++ b/eth/downloader/skeleton_test.go @@ -173,7 +173,7 @@ func (p *skeletonTestPeer) RequestHeadersByNumber(origin uint64, amount int, ski } res := ð.Response{ Req: req, - Res: (*eth.BlockHeadersPacket)(&headers), + Res: (*eth.BlockHeadersRequest)(&headers), Meta: hashes, Time: 1, Done: make(chan error), @@ -811,7 +811,7 @@ func TestSkeletonSyncRetrievals(t *testing.T) { // Create a peer set to feed headers through peerset := newPeerSet() for _, peer := range tt.peers { - peerset.Register(newPeerConnection(peer.id, eth.ETH66, peer, log.New("id", peer.id))) + peerset.Register(newPeerConnection(peer.id, eth.ETH67, peer, log.New("id", peer.id))) } // Create a peer dropper to track malicious peers dropped := make(map[string]int) @@ -913,7 +913,7 @@ func TestSkeletonSyncRetrievals(t *testing.T) { skeleton.Sync(tt.newHead, nil, true) } if tt.newPeer != nil { - if err := peerset.Register(newPeerConnection(tt.newPeer.id, eth.ETH66, tt.newPeer, log.New("id", tt.newPeer.id))); err != nil { + if err := peerset.Register(newPeerConnection(tt.newPeer.id, eth.ETH67, tt.newPeer, log.New("id", tt.newPeer.id))); err != nil { t.Errorf("test %d: failed to register new peer: %v", i, err) } } diff --git a/eth/downloader/testchain_test.go b/eth/downloader/testchain_test.go index d398ce8f8f2e..9da3c7b1b86f 100644 --- a/eth/downloader/testchain_test.go +++ b/eth/downloader/testchain_test.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" ) // Test chain parameters. @@ -43,7 +44,7 @@ var ( Alloc: core.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, BaseFee: big.NewInt(params.InitialBaseFee), } - testGenesis = testGspec.MustCommit(testDB) + testGenesis = testGspec.MustCommit(testDB, trie.NewDatabase(testDB, trie.HashDefaults)) ) // The common prefix of all test chains: diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index a98d9ee4aaff..bfb1df3fb15e 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -27,7 +27,8 @@ import ( "github.com/ethereum/go-ethereum/consensus/clique" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/txpool/blobpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/ethdb" @@ -57,30 +58,30 @@ var LightClientGPO = gasprice.Config{ // Defaults contains default settings for use on the Ethereum main net. var Defaults = Config{ - SyncMode: downloader.SnapSync, - NetworkId: 1, - TxLookupLimit: 2350000, - LightPeers: 100, - UltraLightFraction: 75, - DatabaseCache: 512, - TrieCleanCache: 154, - TrieCleanCacheJournal: "triecache", - TrieCleanCacheRejournal: 60 * time.Minute, - TrieDirtyCache: 256, - TrieTimeout: 60 * time.Minute, - SnapshotCache: 102, - FilterLogCacheSize: 32, - Miner: miner.DefaultConfig, - TxPool: txpool.DefaultConfig, - RPCGasCap: 50000000, - RPCEVMTimeout: 5 * time.Second, - GPO: FullNodeGPO, - RPCTxFeeCap: 1, // 1 ether + SyncMode: downloader.SnapSync, + NetworkId: 1, + TxLookupLimit: 2350000, + TransactionHistory: 2350000, + StateHistory: params.FullImmutabilityThreshold, + LightPeers: 100, + DatabaseCache: 512, + TrieCleanCache: 154, + TrieDirtyCache: 256, + TrieTimeout: 60 * time.Minute, + SnapshotCache: 102, + FilterLogCacheSize: 32, + Miner: miner.DefaultConfig, + TxPool: legacypool.DefaultConfig, + BlobPool: blobpool.DefaultConfig, + RPCGasCap: 50000000, + RPCEVMTimeout: 5 * time.Second, + GPO: FullNodeGPO, + RPCTxFeeCap: 1, // 1 ether } //go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go -// Config contains configuration options for of the ETH and LES protocols. +// Config contains configuration options for ETH and LES protocols. type Config struct { // The genesis block, which is inserted if the database is empty. // If nil, the Ethereum main net block is used. @@ -98,7 +99,15 @@ type Config struct { NoPruning bool // Whether to disable pruning and flush everything to disk NoPrefetch bool // Whether to disable prefetching and only load state on demand - TxLookupLimit uint64 `toml:",omitempty"` // The maximum number of blocks from head whose tx indices are reserved. + // Deprecated, use 'TransactionHistory' instead. + TxLookupLimit uint64 `toml:",omitempty"` // The maximum number of blocks from head whose tx indices are reserved. + TransactionHistory uint64 `toml:",omitempty"` // The maximum number of blocks from head whose tx indices are reserved. + StateHistory uint64 `toml:",omitempty"` // The maximum number of blocks from head whose state histories are reserved. + + // State scheme represents the scheme used to store ethereum states and trie + // nodes on top. It can be 'hash', 'path', or none which means use the scheme + // consistent with persistent state. + StateScheme string `toml:",omitempty"` // RequiredBlocks is a set of block number -> hash mappings which must be in the // canonical chain of all remote peers. Setting the option makes geth verify the @@ -113,24 +122,17 @@ type Config struct { LightNoPrune bool `toml:",omitempty"` // Whether to disable light chain pruning LightNoSyncServe bool `toml:",omitempty"` // Whether to serve light clients before syncing - // Ultra Light client options - UltraLightServers []string `toml:",omitempty"` // List of trusted ultra light servers - UltraLightFraction int `toml:",omitempty"` // Percentage of trusted servers to accept an announcement - UltraLightOnlyAnnounce bool `toml:",omitempty"` // Whether to only announce headers, or also serve them - // Database options SkipBcVersionCheck bool `toml:"-"` DatabaseHandles int `toml:"-"` DatabaseCache int DatabaseFreezer string - TrieCleanCache int - TrieCleanCacheJournal string `toml:",omitempty"` // Disk journal directory for trie cache to survive node restarts - TrieCleanCacheRejournal time.Duration `toml:",omitempty"` // Time interval to regenerate the journal for clean cache - TrieDirtyCache int - TrieTimeout time.Duration - SnapshotCache int - Preimages bool + TrieCleanCache int + TrieDirtyCache int + TrieTimeout time.Duration + SnapshotCache int + Preimages bool // This is the number of blocks for which logs will be cached in the filter system. FilterLogCacheSize int @@ -139,7 +141,8 @@ type Config struct { Miner miner.Config // Transaction pool options - TxPool txpool.Config + TxPool legacypool.Config + BlobPool blobpool.Config // Gas Price Oracle options GPO gasprice.Config @@ -162,6 +165,9 @@ type Config struct { // OverrideCancun (TODO: remove after the fork) OverrideCancun *uint64 `toml:",omitempty"` + + // OverrideVerkle (TODO: remove after the fork) + OverrideVerkle *uint64 `toml:",omitempty"` } // CreateConsensusEngine creates a consensus engine for the given chain config. @@ -173,7 +179,7 @@ func CreateConsensusEngine(config *params.ChainConfig, db ethdb.Database) (conse return beacon.New(clique.New(config.Clique, db)), nil } // If defaulting to proof-of-work, enforce an already merged network since - // we cannot run PoW algorithms and more, so we cannot even follow a chain + // we cannot run PoW algorithms anymore, so we cannot even follow a chain // not coordinated by a beacon node. if !config.TerminalTotalDifficultyPassed { return nil, errors.New("ethash is only supported as a historical component of already merged networks") diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index 0e2f037740ec..2abddc9e0d38 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -7,7 +7,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/txpool/blobpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" "github.com/ethereum/go-ethereum/miner" @@ -24,6 +25,9 @@ func (c Config) MarshalTOML() (interface{}, error) { NoPruning bool NoPrefetch bool TxLookupLimit uint64 `toml:",omitempty"` + TransactionHistory uint64 `toml:",omitempty"` + StateHistory uint64 `toml:",omitempty"` + StateScheme string `toml:",omitempty"` RequiredBlocks map[uint64]common.Hash `toml:"-"` LightServ int `toml:",omitempty"` LightIngress int `toml:",omitempty"` @@ -31,23 +35,19 @@ func (c Config) MarshalTOML() (interface{}, error) { LightPeers int `toml:",omitempty"` LightNoPrune bool `toml:",omitempty"` LightNoSyncServe bool `toml:",omitempty"` - UltraLightServers []string `toml:",omitempty"` - UltraLightFraction int `toml:",omitempty"` - UltraLightOnlyAnnounce bool `toml:",omitempty"` SkipBcVersionCheck bool `toml:"-"` DatabaseHandles int `toml:"-"` DatabaseCache int DatabaseFreezer string TrieCleanCache int - TrieCleanCacheJournal string `toml:",omitempty"` - TrieCleanCacheRejournal time.Duration `toml:",omitempty"` TrieDirtyCache int TrieTimeout time.Duration SnapshotCache int Preimages bool FilterLogCacheSize int Miner miner.Config - TxPool txpool.Config + TxPool legacypool.Config + BlobPool blobpool.Config GPO gasprice.Config EnablePreimageRecording bool DocRoot string `toml:"-"` @@ -55,6 +55,7 @@ func (c Config) MarshalTOML() (interface{}, error) { RPCEVMTimeout time.Duration RPCTxFeeCap float64 OverrideCancun *uint64 `toml:",omitempty"` + OverrideVerkle *uint64 `toml:",omitempty"` } var enc Config enc.Genesis = c.Genesis @@ -65,6 +66,9 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.NoPruning = c.NoPruning enc.NoPrefetch = c.NoPrefetch enc.TxLookupLimit = c.TxLookupLimit + enc.TransactionHistory = c.TransactionHistory + enc.StateHistory = c.StateHistory + enc.StateScheme = c.StateScheme enc.RequiredBlocks = c.RequiredBlocks enc.LightServ = c.LightServ enc.LightIngress = c.LightIngress @@ -72,16 +76,11 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.LightPeers = c.LightPeers enc.LightNoPrune = c.LightNoPrune enc.LightNoSyncServe = c.LightNoSyncServe - enc.UltraLightServers = c.UltraLightServers - enc.UltraLightFraction = c.UltraLightFraction - enc.UltraLightOnlyAnnounce = c.UltraLightOnlyAnnounce enc.SkipBcVersionCheck = c.SkipBcVersionCheck enc.DatabaseHandles = c.DatabaseHandles enc.DatabaseCache = c.DatabaseCache enc.DatabaseFreezer = c.DatabaseFreezer enc.TrieCleanCache = c.TrieCleanCache - enc.TrieCleanCacheJournal = c.TrieCleanCacheJournal - enc.TrieCleanCacheRejournal = c.TrieCleanCacheRejournal enc.TrieDirtyCache = c.TrieDirtyCache enc.TrieTimeout = c.TrieTimeout enc.SnapshotCache = c.SnapshotCache @@ -89,6 +88,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.FilterLogCacheSize = c.FilterLogCacheSize enc.Miner = c.Miner enc.TxPool = c.TxPool + enc.BlobPool = c.BlobPool enc.GPO = c.GPO enc.EnablePreimageRecording = c.EnablePreimageRecording enc.DocRoot = c.DocRoot @@ -96,6 +96,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.RPCEVMTimeout = c.RPCEVMTimeout enc.RPCTxFeeCap = c.RPCTxFeeCap enc.OverrideCancun = c.OverrideCancun + enc.OverrideVerkle = c.OverrideVerkle return &enc, nil } @@ -110,6 +111,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { NoPruning *bool NoPrefetch *bool TxLookupLimit *uint64 `toml:",omitempty"` + TransactionHistory *uint64 `toml:",omitempty"` + StateHistory *uint64 `toml:",omitempty"` + StateScheme *string `toml:",omitempty"` RequiredBlocks map[uint64]common.Hash `toml:"-"` LightServ *int `toml:",omitempty"` LightIngress *int `toml:",omitempty"` @@ -117,23 +121,19 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { LightPeers *int `toml:",omitempty"` LightNoPrune *bool `toml:",omitempty"` LightNoSyncServe *bool `toml:",omitempty"` - UltraLightServers []string `toml:",omitempty"` - UltraLightFraction *int `toml:",omitempty"` - UltraLightOnlyAnnounce *bool `toml:",omitempty"` SkipBcVersionCheck *bool `toml:"-"` DatabaseHandles *int `toml:"-"` DatabaseCache *int DatabaseFreezer *string TrieCleanCache *int - TrieCleanCacheJournal *string `toml:",omitempty"` - TrieCleanCacheRejournal *time.Duration `toml:",omitempty"` TrieDirtyCache *int TrieTimeout *time.Duration SnapshotCache *int Preimages *bool FilterLogCacheSize *int Miner *miner.Config - TxPool *txpool.Config + TxPool *legacypool.Config + BlobPool *blobpool.Config GPO *gasprice.Config EnablePreimageRecording *bool DocRoot *string `toml:"-"` @@ -141,6 +141,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { RPCEVMTimeout *time.Duration RPCTxFeeCap *float64 OverrideCancun *uint64 `toml:",omitempty"` + OverrideVerkle *uint64 `toml:",omitempty"` } var dec Config if err := unmarshal(&dec); err != nil { @@ -170,6 +171,15 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.TxLookupLimit != nil { c.TxLookupLimit = *dec.TxLookupLimit } + if dec.TransactionHistory != nil { + c.TransactionHistory = *dec.TransactionHistory + } + if dec.StateHistory != nil { + c.StateHistory = *dec.StateHistory + } + if dec.StateScheme != nil { + c.StateScheme = *dec.StateScheme + } if dec.RequiredBlocks != nil { c.RequiredBlocks = dec.RequiredBlocks } @@ -191,15 +201,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.LightNoSyncServe != nil { c.LightNoSyncServe = *dec.LightNoSyncServe } - if dec.UltraLightServers != nil { - c.UltraLightServers = dec.UltraLightServers - } - if dec.UltraLightFraction != nil { - c.UltraLightFraction = *dec.UltraLightFraction - } - if dec.UltraLightOnlyAnnounce != nil { - c.UltraLightOnlyAnnounce = *dec.UltraLightOnlyAnnounce - } if dec.SkipBcVersionCheck != nil { c.SkipBcVersionCheck = *dec.SkipBcVersionCheck } @@ -215,12 +216,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.TrieCleanCache != nil { c.TrieCleanCache = *dec.TrieCleanCache } - if dec.TrieCleanCacheJournal != nil { - c.TrieCleanCacheJournal = *dec.TrieCleanCacheJournal - } - if dec.TrieCleanCacheRejournal != nil { - c.TrieCleanCacheRejournal = *dec.TrieCleanCacheRejournal - } if dec.TrieDirtyCache != nil { c.TrieDirtyCache = *dec.TrieDirtyCache } @@ -242,6 +237,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.TxPool != nil { c.TxPool = *dec.TxPool } + if dec.BlobPool != nil { + c.BlobPool = *dec.BlobPool + } if dec.GPO != nil { c.GPO = *dec.GPO } @@ -263,5 +261,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.OverrideCancun != nil { c.OverrideCancun = dec.OverrideCancun } + if dec.OverrideVerkle != nil { + c.OverrideVerkle = dec.OverrideVerkle + } return nil } diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index 35608031d979..8751c4e3ea13 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -483,7 +483,7 @@ func (f *BlockFetcher) loop() { select { case res := <-resCh: res.Done <- nil - f.FilterHeaders(peer, *res.Res.(*eth.BlockHeadersPacket), time.Now().Add(res.Time)) + f.FilterHeaders(peer, *res.Res.(*eth.BlockHeadersRequest), time.Now().Add(res.Time)) case <-timeout.C: // The peer didn't respond in time. The request @@ -541,7 +541,7 @@ func (f *BlockFetcher) loop() { case res := <-resCh: res.Done <- nil // Ignoring withdrawals here, since the block fetcher is not used post-merge. - txs, uncles, _ := res.Res.(*eth.BlockBodiesPacket).Unpack() + txs, uncles, _ := res.Res.(*eth.BlockBodiesResponse).Unpack() f.FilterBodies(peer, txs, uncles, time.Now()) case <-timeout.C: diff --git a/eth/fetcher/block_fetcher_test.go b/eth/fetcher/block_fetcher_test.go index 6d8516fa6ac0..6927300b1d34 100644 --- a/eth/fetcher/block_fetcher_test.go +++ b/eth/fetcher/block_fetcher_test.go @@ -44,7 +44,7 @@ var ( Alloc: core.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, BaseFee: big.NewInt(params.InitialBaseFee), } - genesis = gspec.MustCommit(testdb) + genesis = gspec.MustCommit(testdb, trie.NewDatabase(testdb, trie.HashDefaults)) unknownBlock = types.NewBlock(&types.Header{Root: types.EmptyRootHash, GasLimit: params.GenesisGasLimit, BaseFee: big.NewInt(params.InitialBaseFee)}, nil, nil, nil, trie.NewStackTrie(nil)) ) @@ -213,7 +213,7 @@ func (f *fetcherTester) makeHeaderFetcher(peer string, blocks map[common.Hash]*t } res := ð.Response{ Req: req, - Res: (*eth.BlockHeadersPacket)(&headers), + Res: (*eth.BlockHeadersRequest)(&headers), Time: drift, Done: make(chan error, 1), // Ignore the returned status } @@ -255,7 +255,7 @@ func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*typ } res := ð.Response{ Req: req, - Res: (*eth.BlockBodiesPacket)(&bodies), + Res: (*eth.BlockBodiesResponse)(&bodies), Time: drift, Done: make(chan error, 1), // Ignore the returned status } diff --git a/eth/fetcher/tx_fetcher.go b/eth/fetcher/tx_fetcher.go index 39727e0079d7..574762696869 100644 --- a/eth/fetcher/tx_fetcher.go +++ b/eth/fetcher/tx_fetcher.go @@ -20,12 +20,13 @@ import ( "bytes" "errors" "fmt" + "math" mrand "math/rand" "sort" "time" - mapset "github.com/deckarep/golang-set/v2" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" @@ -38,21 +39,30 @@ const ( // can announce in a short time. maxTxAnnounces = 4096 - // maxTxRetrievals is the maximum transaction number can be fetched in one - // request. The rationale to pick 256 is: - // - In eth protocol, the softResponseLimit is 2MB. Nowadays according to - // Etherscan the average transaction size is around 200B, so in theory - // we can include lots of transaction in a single protocol packet. - // - However the maximum size of a single transaction is raised to 128KB, - // so pick a middle value here to ensure we can maximize the efficiency - // of the retrieval and response size overflow won't happen in most cases. + // maxTxRetrievals is the maximum number of transactions that can be fetched + // in one request. The rationale for picking 256 is to have a reasonabe lower + // bound for the transferred data (don't waste RTTs, transfer more meaningful + // batch sizes), but also have an upper bound on the sequentiality to allow + // using our entire peerset for deliveries. + // + // This number also acts as a failsafe against malicious announces which might + // cause us to request more data than we'd expect. maxTxRetrievals = 256 + // maxTxRetrievalSize is the max number of bytes that delivered transactions + // should weigh according to the announcements. The 128KB was chosen to limit + // retrieving a maximum of one blob transaction at a time to minimize hogging + // a connection between two peers. + maxTxRetrievalSize = 128 * 1024 + // maxTxUnderpricedSetSize is the size of the underpriced transaction set that // is used to track recent transactions that have been dropped so we don't // re-request them. maxTxUnderpricedSetSize = 32768 + // maxTxUnderpricedTimeout is the max time a transaction should be stuck in the underpriced set. + maxTxUnderpricedTimeout = int64(5 * time.Minute) + // txArriveTimeout is the time allowance before an announced transaction is // explicitly requested. txArriveTimeout = 500 * time.Millisecond @@ -102,6 +112,14 @@ var ( type txAnnounce struct { origin string // Identifier of the peer originating the notification hashes []common.Hash // Batch of transaction hashes being announced + metas []*txMetadata // Batch of metadatas associated with the hashes (nil before eth/68) +} + +// txMetadata is a set of extra data transmitted along the announcement for better +// fetch scheduling. +type txMetadata struct { + kind byte // Transaction consensus type + size uint32 // Transaction size in bytes } // txRequest represents an in-flight transaction retrieval request destined to @@ -117,6 +135,7 @@ type txRequest struct { type txDelivery struct { origin string // Identifier of the peer originating the notification hashes []common.Hash // Batch of transaction hashes having been delivered + metas []txMetadata // Batch of metadatas associated with the delivered hashes direct bool // Whether this is a direct reply or a broadcast } @@ -148,18 +167,18 @@ type TxFetcher struct { drop chan *txDrop quit chan struct{} - underpriced mapset.Set[common.Hash] // Transactions discarded as too cheap (don't re-fetch) + underpriced *lru.Cache[common.Hash, int64] // Transactions discarded as too cheap (don't re-fetch) // Stage 1: Waiting lists for newly discovered transactions that might be // broadcast without needing explicit request/reply round trips. - waitlist map[common.Hash]map[string]struct{} // Transactions waiting for an potential broadcast - waittime map[common.Hash]mclock.AbsTime // Timestamps when transactions were added to the waitlist - waitslots map[string]map[common.Hash]struct{} // Waiting announcements grouped by peer (DoS protection) + waitlist map[common.Hash]map[string]struct{} // Transactions waiting for an potential broadcast + waittime map[common.Hash]mclock.AbsTime // Timestamps when transactions were added to the waitlist + waitslots map[string]map[common.Hash]*txMetadata // Waiting announcements grouped by peer (DoS protection) // Stage 2: Queue of transactions that waiting to be allocated to some peer // to be retrieved directly. - announces map[string]map[common.Hash]struct{} // Set of announced transactions, grouped by origin peer - announced map[common.Hash]map[string]struct{} // Set of download locations, grouped by transaction hash + announces map[string]map[common.Hash]*txMetadata // Set of announced transactions, grouped by origin peer + announced map[common.Hash]map[string]struct{} // Set of download locations, grouped by transaction hash // Stage 3: Set of transactions currently being retrieved, some which may be // fulfilled and some rescheduled. Note, this step shares 'announces' from the @@ -172,6 +191,7 @@ type TxFetcher struct { hasTx func(common.Hash) bool // Retrieves a tx from the local txpool addTxs func([]*types.Transaction) []error // Insert a batch of transactions into local txpool fetchTxs func(string, []common.Hash) error // Retrieves a set of txs from a remote peer + dropPeer func(string) // Drops a peer in case of announcement violation step chan struct{} // Notification channel when the fetcher loop iterates clock mclock.Clock // Time wrapper to simulate in tests @@ -180,14 +200,14 @@ type TxFetcher struct { // NewTxFetcher creates a transaction fetcher to retrieve transaction // based on hash announcements. -func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error) *TxFetcher { - return NewTxFetcherForTests(hasTx, addTxs, fetchTxs, mclock.System{}, nil) +func NewTxFetcher(hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string)) *TxFetcher { + return NewTxFetcherForTests(hasTx, addTxs, fetchTxs, dropPeer, mclock.System{}, nil) } // NewTxFetcherForTests is a testing method to mock out the realtime clock with // a simulated version and the internal randomness with a deterministic one. func NewTxFetcherForTests( - hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, + hasTx func(common.Hash) bool, addTxs func([]*types.Transaction) []error, fetchTxs func(string, []common.Hash) error, dropPeer func(string), clock mclock.Clock, rand *mrand.Rand) *TxFetcher { return &TxFetcher{ notify: make(chan *txAnnounce), @@ -196,16 +216,17 @@ func NewTxFetcherForTests( quit: make(chan struct{}), waitlist: make(map[common.Hash]map[string]struct{}), waittime: make(map[common.Hash]mclock.AbsTime), - waitslots: make(map[string]map[common.Hash]struct{}), - announces: make(map[string]map[common.Hash]struct{}), + waitslots: make(map[string]map[common.Hash]*txMetadata), + announces: make(map[string]map[common.Hash]*txMetadata), announced: make(map[common.Hash]map[string]struct{}), fetching: make(map[common.Hash]string), requests: make(map[string]*txRequest), alternates: make(map[common.Hash]map[string]struct{}), - underpriced: mapset.NewSet[common.Hash](), + underpriced: lru.NewCache[common.Hash, int64](maxTxUnderpricedSetSize), hasTx: hasTx, addTxs: addTxs, fetchTxs: fetchTxs, + dropPeer: dropPeer, clock: clock, rand: rand, } @@ -213,7 +234,7 @@ func NewTxFetcherForTests( // Notify announces the fetcher of the potential availability of a new batch of // transactions in the network. -func (f *TxFetcher) Notify(peer string, hashes []common.Hash) error { +func (f *TxFetcher) Notify(peer string, types []byte, sizes []uint32, hashes []common.Hash) error { // Keep track of all the announced transactions txAnnounceInMeter.Mark(int64(len(hashes))) @@ -223,32 +244,35 @@ func (f *TxFetcher) Notify(peer string, hashes []common.Hash) error { // still valuable to check here because it runs concurrent to the internal // loop, so anything caught here is time saved internally. var ( - unknowns = make([]common.Hash, 0, len(hashes)) - duplicate, underpriced int64 + unknownHashes = make([]common.Hash, 0, len(hashes)) + unknownMetas = make([]*txMetadata, 0, len(hashes)) + + duplicate int64 + underpriced int64 ) - for _, hash := range hashes { + for i, hash := range hashes { switch { case f.hasTx(hash): duplicate++ - - case f.underpriced.Contains(hash): + case f.isKnownUnderpriced(hash): underpriced++ - default: - unknowns = append(unknowns, hash) + unknownHashes = append(unknownHashes, hash) + if types == nil { + unknownMetas = append(unknownMetas, nil) + } else { + unknownMetas = append(unknownMetas, &txMetadata{kind: types[i], size: sizes[i]}) + } } } txAnnounceKnownMeter.Mark(duplicate) txAnnounceUnderpricedMeter.Mark(underpriced) // If anything's left to announce, push it into the internal loop - if len(unknowns) == 0 { + if len(unknownHashes) == 0 { return nil } - announce := &txAnnounce{ - origin: peer, - hashes: unknowns, - } + announce := &txAnnounce{origin: peer, hashes: unknownHashes, metas: unknownMetas} select { case f.notify <- announce: return nil @@ -257,6 +281,16 @@ func (f *TxFetcher) Notify(peer string, hashes []common.Hash) error { } } +// isKnownUnderpriced reports whether a transaction hash was recently found to be underpriced. +func (f *TxFetcher) isKnownUnderpriced(hash common.Hash) bool { + prevTime, ok := f.underpriced.Peek(hash) + if ok && prevTime+maxTxUnderpricedTimeout < time.Now().Unix() { + f.underpriced.Remove(hash) + return false + } + return ok +} + // Enqueue imports a batch of received transaction into the transaction pool // and the fetcher. This method may be called by both transaction broadcasts and // direct request replies. The differentiation is important so the fetcher can @@ -281,6 +315,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) // re-requesting them and dropping the peer in case of malicious transfers. var ( added = make([]common.Hash, 0, len(txs)) + metas = make([]txMetadata, 0, len(txs)) ) // proceed in batches for i := 0; i < len(txs); i += 128 { @@ -294,15 +329,13 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) otherreject int64 ) batch := txs[i:end] + for j, err := range f.addTxs(batch) { // Track the transaction hash if the price is too low for us. // Avoid re-request this transaction when we receive another // announcement. if errors.Is(err, txpool.ErrUnderpriced) || errors.Is(err, txpool.ErrReplaceUnderpriced) { - for f.underpriced.Cardinality() >= maxTxUnderpricedSetSize { - f.underpriced.Pop() - } - f.underpriced.Add(batch[j].Hash()) + f.underpriced.Add(batch[j].Hash(), batch[j].Time().Unix()) } // Track a few interesting failure types switch { @@ -318,6 +351,10 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) otherreject++ } added = append(added, batch[j].Hash()) + metas = append(metas, txMetadata{ + kind: batch[j].Type(), + size: uint32(batch[j].Size()), + }) } knownMeter.Mark(duplicate) underpricedMeter.Mark(underpriced) @@ -330,7 +367,7 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) } } select { - case f.cleanup <- &txDelivery{origin: peer, hashes: added, direct: direct}: + case f.cleanup <- &txDelivery{origin: peer, hashes: added, metas: metas, direct: direct}: return nil case <-f.quit: return errTerminated @@ -387,13 +424,15 @@ func (f *TxFetcher) loop() { want := used + len(ann.hashes) if want > maxTxAnnounces { txAnnounceDOSMeter.Mark(int64(want - maxTxAnnounces)) + ann.hashes = ann.hashes[:want-maxTxAnnounces] + ann.metas = ann.metas[:want-maxTxAnnounces] } // All is well, schedule the remainder of the transactions idleWait := len(f.waittime) == 0 _, oldPeer := f.announces[ann.origin] - for _, hash := range ann.hashes { + for i, hash := range ann.hashes { // If the transaction is already downloading, add it to the list // of possible alternates (in case the current retrieval fails) and // also account it for the peer. @@ -402,9 +441,9 @@ func (f *TxFetcher) loop() { // Stage 2 and 3 share the set of origins per tx if announces := f.announces[ann.origin]; announces != nil { - announces[hash] = struct{}{} + announces[hash] = ann.metas[i] } else { - f.announces[ann.origin] = map[common.Hash]struct{}{hash: {}} + f.announces[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]} } continue } @@ -415,9 +454,9 @@ func (f *TxFetcher) loop() { // Stage 2 and 3 share the set of origins per tx if announces := f.announces[ann.origin]; announces != nil { - announces[hash] = struct{}{} + announces[hash] = ann.metas[i] } else { - f.announces[ann.origin] = map[common.Hash]struct{}{hash: {}} + f.announces[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]} } continue } @@ -425,12 +464,18 @@ func (f *TxFetcher) loop() { // yet downloading, add the peer as an alternate origin in the // waiting list. if f.waitlist[hash] != nil { + // Ignore double announcements from the same peer. This is + // especially important if metadata is also passed along to + // prevent malicious peers flip-flopping good/bad values. + if _, ok := f.waitlist[hash][ann.origin]; ok { + continue + } f.waitlist[hash][ann.origin] = struct{}{} if waitslots := f.waitslots[ann.origin]; waitslots != nil { - waitslots[hash] = struct{}{} + waitslots[hash] = ann.metas[i] } else { - f.waitslots[ann.origin] = map[common.Hash]struct{}{hash: {}} + f.waitslots[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]} } continue } @@ -439,9 +484,9 @@ func (f *TxFetcher) loop() { f.waittime[hash] = f.clock.Now() if waitslots := f.waitslots[ann.origin]; waitslots != nil { - waitslots[hash] = struct{}{} + waitslots[hash] = ann.metas[i] } else { - f.waitslots[ann.origin] = map[common.Hash]struct{}{hash: {}} + f.waitslots[ann.origin] = map[common.Hash]*txMetadata{hash: ann.metas[i]} } } // If a new item was added to the waitlist, schedule it into the fetcher @@ -467,9 +512,9 @@ func (f *TxFetcher) loop() { f.announced[hash] = f.waitlist[hash] for peer := range f.waitlist[hash] { if announces := f.announces[peer]; announces != nil { - announces[hash] = struct{}{} + announces[hash] = f.waitslots[peer][hash] } else { - f.announces[peer] = map[common.Hash]struct{}{hash: {}} + f.announces[peer] = map[common.Hash]*txMetadata{hash: f.waitslots[peer][hash]} } delete(f.waitslots[peer], hash) if len(f.waitslots[peer]) == 0 { @@ -538,10 +583,27 @@ func (f *TxFetcher) loop() { case delivery := <-f.cleanup: // Independent if the delivery was direct or broadcast, remove all - // traces of the hash from internal trackers - for _, hash := range delivery.hashes { + // traces of the hash from internal trackers. That said, compare any + // advertised metadata with the real ones and drop bad peers. + for i, hash := range delivery.hashes { if _, ok := f.waitlist[hash]; ok { for peer, txset := range f.waitslots { + if meta := txset[hash]; meta != nil { + if delivery.metas[i].kind != meta.kind { + log.Warn("Announced transaction type mismatch", "peer", peer, "tx", hash, "type", delivery.metas[i].kind, "ann", meta.kind) + f.dropPeer(peer) + } else if delivery.metas[i].size != meta.size { + log.Warn("Announced transaction size mismatch", "peer", peer, "tx", hash, "size", delivery.metas[i].size, "ann", meta.size) + if math.Abs(float64(delivery.metas[i].size)-float64(meta.size)) > 8 { + // Normally we should drop a peer considering this is a protocol violation. + // However, due to the RLP vs consensus format messyness, allow a few bytes + // wiggle-room where we only warn, but don't drop. + // + // TODO(karalabe): Get rid of this relaxation when clients are proven stable. + f.dropPeer(peer) + } + } + } delete(txset, hash) if len(txset) == 0 { delete(f.waitslots, peer) @@ -551,6 +613,22 @@ func (f *TxFetcher) loop() { delete(f.waittime, hash) } else { for peer, txset := range f.announces { + if meta := txset[hash]; meta != nil { + if delivery.metas[i].kind != meta.kind { + log.Warn("Announced transaction type mismatch", "peer", peer, "tx", hash, "type", delivery.metas[i].kind, "ann", meta.kind) + f.dropPeer(peer) + } else if delivery.metas[i].size != meta.size { + log.Warn("Announced transaction size mismatch", "peer", peer, "tx", hash, "size", delivery.metas[i].size, "ann", meta.size) + if math.Abs(float64(delivery.metas[i].size)-float64(meta.size)) > 8 { + // Normally we should drop a peer considering this is a protocol violation. + // However, due to the RLP vs consensus format messyness, allow a few bytes + // wiggle-room where we only warn, but don't drop. + // + // TODO(karalabe): Get rid of this relaxation when clients are proven stable. + f.dropPeer(peer) + } + } + } delete(txset, hash) if len(txset) == 0 { delete(f.announces, peer) @@ -787,25 +865,36 @@ func (f *TxFetcher) scheduleFetches(timer *mclock.Timer, timeout chan struct{}, if len(f.announces[peer]) == 0 { return // continue in the for-each } - hashes := make([]common.Hash, 0, maxTxRetrievals) - f.forEachHash(f.announces[peer], func(hash common.Hash) bool { - if _, ok := f.fetching[hash]; !ok { - // Mark the hash as fetching and stash away possible alternates - f.fetching[hash] = peer - - if _, ok := f.alternates[hash]; ok { - panic(fmt.Sprintf("alternate tracker already contains fetching item: %v", f.alternates[hash])) - } - f.alternates[hash] = f.announced[hash] - delete(f.announced, hash) + var ( + hashes = make([]common.Hash, 0, maxTxRetrievals) + bytes uint64 + ) + f.forEachAnnounce(f.announces[peer], func(hash common.Hash, meta *txMetadata) bool { + // If the transaction is already fetching, skip to the next one + if _, ok := f.fetching[hash]; ok { + return true + } + // Mark the hash as fetching and stash away possible alternates + f.fetching[hash] = peer - // Accumulate the hash and stop if the limit was reached - hashes = append(hashes, hash) - if len(hashes) >= maxTxRetrievals { - return false // break in the for-each + if _, ok := f.alternates[hash]; ok { + panic(fmt.Sprintf("alternate tracker already contains fetching item: %v", f.alternates[hash])) + } + f.alternates[hash] = f.announced[hash] + delete(f.announced, hash) + + // Accumulate the hash and stop if the limit was reached + hashes = append(hashes, hash) + if len(hashes) >= maxTxRetrievals { + return false // break in the for-each + } + if meta != nil { // Only set eth/68 and upwards + bytes += uint64(meta.size) + if bytes >= maxTxRetrievalSize { + return false } } - return true // continue in the for-each + return true // scheduled, try to add more }) // If any hashes were allocated, request them from the peer if len(hashes) > 0 { @@ -850,27 +939,28 @@ func (f *TxFetcher) forEachPeer(peers map[string]struct{}, do func(peer string)) } } -// forEachHash does a range loop over a map of hashes in production, but during -// testing it does a deterministic sorted random to allow reproducing issues. -func (f *TxFetcher) forEachHash(hashes map[common.Hash]struct{}, do func(hash common.Hash) bool) { +// forEachAnnounce does a range loop over a map of announcements in production, +// but during testing it does a deterministic sorted random to allow reproducing +// issues. +func (f *TxFetcher) forEachAnnounce(announces map[common.Hash]*txMetadata, do func(hash common.Hash, meta *txMetadata) bool) { // If we're running production, use whatever Go's map gives us if f.rand == nil { - for hash := range hashes { - if !do(hash) { + for hash, meta := range announces { + if !do(hash, meta) { return } } return } // We're running the test suite, make iteration deterministic - list := make([]common.Hash, 0, len(hashes)) - for hash := range hashes { + list := make([]common.Hash, 0, len(announces)) + for hash := range announces { list = append(list, hash) } sortHashes(list) rotateHashes(list, f.rand.Intn(len(list))) for _, hash := range list { - if !do(hash) { + if !do(hash, announces[hash]) { return } } diff --git a/eth/fetcher/tx_fetcher_test.go b/eth/fetcher/tx_fetcher_test.go index 1715def99c00..fbb9ff9dccc9 100644 --- a/eth/fetcher/tx_fetcher_test.go +++ b/eth/fetcher/tx_fetcher_test.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" ) var ( @@ -41,9 +42,20 @@ var ( testTxsHashes = []common.Hash{testTxs[0].Hash(), testTxs[1].Hash(), testTxs[2].Hash(), testTxs[3].Hash()} ) +type announce struct { + hash common.Hash + kind *byte + size *uint32 +} + +func typeptr(t byte) *byte { return &t } +func sizeptr(n uint32) *uint32 { return &n } + type doTxNotify struct { peer string hashes []common.Hash + types []byte + sizes []uint32 } type doTxEnqueue struct { peer string @@ -57,7 +69,14 @@ type doWait struct { type doDrop string type doFunc func() +type isWaitingWithMeta map[string][]announce type isWaiting map[string][]common.Hash + +type isScheduledWithMeta struct { + tracking map[string][]announce + fetching map[string][]common.Hash + dangling map[string][]common.Hash +} type isScheduled struct { tracking map[string][]common.Hash fetching map[string][]common.Hash @@ -81,6 +100,7 @@ func TestTransactionFetcherWaiting(t *testing.T) { func(common.Hash) bool { return false }, nil, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -162,6 +182,212 @@ func TestTransactionFetcherWaiting(t *testing.T) { }) } +// Tests that transaction announcements with associated metadata are added to a +// waitlist, and none of them are scheduled for retrieval until the wait expires. +// +// This test is an extended version of TestTransactionFetcherWaiting. It's mostly +// to cover the metadata checkes without bloating up the basic behavioral tests +// with all the useless extra fields. +func TestTransactionFetcherWaitingWithMeta(t *testing.T) { + testTransactionFetcherParallel(t, txFetcherTest{ + init: func() *TxFetcher { + return NewTxFetcher( + func(common.Hash) bool { return false }, + nil, + func(string, []common.Hash) error { return nil }, + nil, + ) + }, + steps: []interface{}{ + // Initial announcement to get something into the waitlist + doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 222}}, + isWaitingWithMeta(map[string][]announce{ + "A": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)}, + }, + }), + // Announce from a new peer to check that no overwrite happens + doTxNotify{peer: "B", hashes: []common.Hash{{0x03}, {0x04}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{333, 444}}, + isWaitingWithMeta(map[string][]announce{ + "A": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)}, + }, + "B": { + {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)}, + }, + }), + // Announce clashing hashes but unique new peer + doTxNotify{peer: "C", hashes: []common.Hash{{0x01}, {0x04}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{111, 444}}, + isWaitingWithMeta(map[string][]announce{ + "A": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)}, + }, + "B": { + {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)}, + }, + "C": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)}, + }, + }), + // Announce existing and clashing hashes from existing peer. Clashes + // should not overwrite previous announcements. + doTxNotify{peer: "A", hashes: []common.Hash{{0x01}, {0x03}, {0x05}}, types: []byte{types.LegacyTxType, types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{999, 333, 555}}, + isWaitingWithMeta(map[string][]announce{ + "A": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)}, + {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)}, + {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)}, + }, + "B": { + {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)}, + }, + "C": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)}, + }, + }), + // Announce clashing hashes with conflicting metadata. Somebody will + // be in the wrong, but we don't know yet who. + doTxNotify{peer: "D", hashes: []common.Hash{{0x01}, {0x02}}, types: []byte{types.LegacyTxType, types.BlobTxType}, sizes: []uint32{999, 222}}, + isWaitingWithMeta(map[string][]announce{ + "A": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)}, + {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)}, + {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)}, + }, + "B": { + {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)}, + }, + "C": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)}, + }, + "D": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(999)}, + {common.Hash{0x02}, typeptr(types.BlobTxType), sizeptr(222)}, + }, + }), + isScheduled{tracking: nil, fetching: nil}, + + // Wait for the arrival timeout which should move all expired items + // from the wait list to the scheduler + doWait{time: txArriveTimeout, step: true}, + isWaiting(nil), + isScheduledWithMeta{ + tracking: map[string][]announce{ + "A": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)}, + {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)}, + {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)}, + }, + "B": { + {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)}, + }, + "C": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)}, + }, + "D": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(999)}, + {common.Hash{0x02}, typeptr(types.BlobTxType), sizeptr(222)}, + }, + }, + fetching: map[string][]common.Hash{ // Depends on deterministic test randomizer + "A": {{0x03}, {0x05}}, + "C": {{0x01}, {0x04}}, + "D": {{0x02}}, + }, + }, + // Queue up a non-fetchable transaction and then trigger it with a new + // peer (weird case to test 1 line in the fetcher) + doTxNotify{peer: "C", hashes: []common.Hash{{0x06}, {0x07}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{666, 777}}, + isWaitingWithMeta(map[string][]announce{ + "C": { + {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(666)}, + {common.Hash{0x07}, typeptr(types.LegacyTxType), sizeptr(777)}, + }, + }), + doWait{time: txArriveTimeout, step: true}, + isScheduledWithMeta{ + tracking: map[string][]announce{ + "A": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)}, + {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)}, + {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)}, + }, + "B": { + {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)}, + }, + "C": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)}, + {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(666)}, + {common.Hash{0x07}, typeptr(types.LegacyTxType), sizeptr(777)}, + }, + "D": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(999)}, + {common.Hash{0x02}, typeptr(types.BlobTxType), sizeptr(222)}, + }, + }, + fetching: map[string][]common.Hash{ + "A": {{0x03}, {0x05}}, + "C": {{0x01}, {0x04}}, + "D": {{0x02}}, + }, + }, + doTxNotify{peer: "E", hashes: []common.Hash{{0x06}, {0x07}}, types: []byte{types.LegacyTxType, types.LegacyTxType}, sizes: []uint32{666, 777}}, + isScheduledWithMeta{ + tracking: map[string][]announce{ + "A": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(222)}, + {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)}, + {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(555)}, + }, + "B": { + {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(333)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)}, + }, + "C": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(111)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(444)}, + {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(666)}, + {common.Hash{0x07}, typeptr(types.LegacyTxType), sizeptr(777)}, + }, + "D": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(999)}, + {common.Hash{0x02}, typeptr(types.BlobTxType), sizeptr(222)}, + }, + "E": { + {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(666)}, + {common.Hash{0x07}, typeptr(types.LegacyTxType), sizeptr(777)}, + }, + }, + fetching: map[string][]common.Hash{ + "A": {{0x03}, {0x05}}, + "C": {{0x01}, {0x04}}, + "D": {{0x02}}, + "E": {{0x06}, {0x07}}, + }, + }, + }, + }) +} + // Tests that transaction announcements skip the waiting list if they are // already scheduled. func TestTransactionFetcherSkipWaiting(t *testing.T) { @@ -171,6 +397,7 @@ func TestTransactionFetcherSkipWaiting(t *testing.T) { func(common.Hash) bool { return false }, nil, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -234,6 +461,7 @@ func TestTransactionFetcherSingletonRequesting(t *testing.T) { func(common.Hash) bool { return false }, nil, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -313,6 +541,7 @@ func TestTransactionFetcherFailedRescheduling(t *testing.T) { <-proceed return errors.New("peer disconnected") }, + nil, ) }, steps: []interface{}{ @@ -382,6 +611,7 @@ func TestTransactionFetcherCleanup(t *testing.T) { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -421,6 +651,7 @@ func TestTransactionFetcherCleanupEmpty(t *testing.T) { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -459,6 +690,7 @@ func TestTransactionFetcherMissingRescheduling(t *testing.T) { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -505,6 +737,7 @@ func TestTransactionFetcherMissingCleanup(t *testing.T) { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -543,6 +776,7 @@ func TestTransactionFetcherBroadcasts(t *testing.T) { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -591,6 +825,7 @@ func TestTransactionFetcherWaitTimerResets(t *testing.T) { func(common.Hash) bool { return false }, nil, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -648,6 +883,7 @@ func TestTransactionFetcherTimeoutRescheduling(t *testing.T) { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -713,6 +949,7 @@ func TestTransactionFetcherTimeoutTimerResets(t *testing.T) { func(common.Hash) bool { return false }, nil, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -757,21 +994,21 @@ func TestTransactionFetcherTimeoutTimerResets(t *testing.T) { }) } -// Tests that if thousands of transactions are announces, only a small +// Tests that if thousands of transactions are announced, only a small // number of them will be requested at a time. func TestTransactionFetcherRateLimiting(t *testing.T) { - // Create a slew of transactions and to announce them + // Create a slew of transactions and announce them var hashes []common.Hash for i := 0; i < maxTxAnnounces; i++ { hashes = append(hashes, common.Hash{byte(i / 256), byte(i % 256)}) } - testTransactionFetcherParallel(t, txFetcherTest{ init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, nil, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -792,6 +1029,68 @@ func TestTransactionFetcherRateLimiting(t *testing.T) { }) } +// Tests that if huge transactions are announced, only a small number of them will +// be requested at a time, to keep the responses below a resonable level. +func TestTransactionFetcherBandwidthLimiting(t *testing.T) { + testTransactionFetcherParallel(t, txFetcherTest{ + init: func() *TxFetcher { + return NewTxFetcher( + func(common.Hash) bool { return false }, + nil, + func(string, []common.Hash) error { return nil }, + nil, + ) + }, + steps: []interface{}{ + // Announce mid size transactions from A to verify that multiple + // ones can be piled into a single request. + doTxNotify{peer: "A", + hashes: []common.Hash{{0x01}, {0x02}, {0x03}, {0x04}}, + types: []byte{types.LegacyTxType, types.LegacyTxType, types.LegacyTxType, types.LegacyTxType}, + sizes: []uint32{48 * 1024, 48 * 1024, 48 * 1024, 48 * 1024}, + }, + // Announce exactly on the limit transactions to see that only one + // gets requested + doTxNotify{peer: "B", + hashes: []common.Hash{{0x05}, {0x06}}, + types: []byte{types.LegacyTxType, types.LegacyTxType}, + sizes: []uint32{maxTxRetrievalSize, maxTxRetrievalSize}, + }, + // Announce oversized blob transactions to see that overflows are ok + doTxNotify{peer: "C", + hashes: []common.Hash{{0x07}, {0x08}}, + types: []byte{types.BlobTxType, types.BlobTxType}, + sizes: []uint32{params.MaxBlobGasPerBlock, params.MaxBlobGasPerBlock}, + }, + doWait{time: txArriveTimeout, step: true}, + isWaiting(nil), + isScheduledWithMeta{ + tracking: map[string][]announce{ + "A": { + {common.Hash{0x01}, typeptr(types.LegacyTxType), sizeptr(48 * 1024)}, + {common.Hash{0x02}, typeptr(types.LegacyTxType), sizeptr(48 * 1024)}, + {common.Hash{0x03}, typeptr(types.LegacyTxType), sizeptr(48 * 1024)}, + {common.Hash{0x04}, typeptr(types.LegacyTxType), sizeptr(48 * 1024)}, + }, + "B": { + {common.Hash{0x05}, typeptr(types.LegacyTxType), sizeptr(maxTxRetrievalSize)}, + {common.Hash{0x06}, typeptr(types.LegacyTxType), sizeptr(maxTxRetrievalSize)}, + }, + "C": { + {common.Hash{0x07}, typeptr(types.BlobTxType), sizeptr(params.MaxBlobGasPerBlock)}, + {common.Hash{0x08}, typeptr(types.BlobTxType), sizeptr(params.MaxBlobGasPerBlock)}, + }, + }, + fetching: map[string][]common.Hash{ + "A": {{0x02}, {0x03}, {0x04}}, + "B": {{0x06}}, + "C": {{0x08}}, + }, + }, + }, + }) +} + // Tests that then number of transactions a peer is allowed to announce and/or // request at the same time is hard capped. func TestTransactionFetcherDoSProtection(t *testing.T) { @@ -810,6 +1109,7 @@ func TestTransactionFetcherDoSProtection(t *testing.T) { func(common.Hash) bool { return false }, nil, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -877,6 +1177,7 @@ func TestTransactionFetcherUnderpricedDedup(t *testing.T) { return errs }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -946,6 +1247,7 @@ func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) { return errs }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: append(steps, []interface{}{ @@ -968,6 +1270,7 @@ func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -1021,6 +1324,7 @@ func TestTransactionFetcherDrop(t *testing.T) { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -1087,6 +1391,7 @@ func TestTransactionFetcherDropRescheduling(t *testing.T) { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -1120,6 +1425,74 @@ func TestTransactionFetcherDropRescheduling(t *testing.T) { }) } +// Tests that announced transactions with the wrong transaction type or size will +// result in a dropped peer. +func TestInvalidAnnounceMetadata(t *testing.T) { + drop := make(chan string, 2) + testTransactionFetcherParallel(t, txFetcherTest{ + init: func() *TxFetcher { + return NewTxFetcher( + func(common.Hash) bool { return false }, + func(txs []*types.Transaction) []error { + return make([]error, len(txs)) + }, + func(string, []common.Hash) error { return nil }, + func(peer string) { drop <- peer }, + ) + }, + steps: []interface{}{ + // Initial announcement to get something into the waitlist + doTxNotify{peer: "A", hashes: []common.Hash{testTxsHashes[0], testTxsHashes[1]}, types: []byte{testTxs[0].Type(), testTxs[1].Type()}, sizes: []uint32{uint32(testTxs[0].Size()), uint32(testTxs[1].Size())}}, + isWaitingWithMeta(map[string][]announce{ + "A": { + {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(uint32(testTxs[0].Size()))}, + {testTxsHashes[1], typeptr(testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))}, + }, + }), + // Announce from new peers conflicting transactions + doTxNotify{peer: "B", hashes: []common.Hash{testTxsHashes[0]}, types: []byte{testTxs[0].Type()}, sizes: []uint32{1024 + uint32(testTxs[0].Size())}}, + doTxNotify{peer: "C", hashes: []common.Hash{testTxsHashes[1]}, types: []byte{1 + testTxs[1].Type()}, sizes: []uint32{uint32(testTxs[1].Size())}}, + isWaitingWithMeta(map[string][]announce{ + "A": { + {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(uint32(testTxs[0].Size()))}, + {testTxsHashes[1], typeptr(testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))}, + }, + "B": { + {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(1024 + uint32(testTxs[0].Size()))}, + }, + "C": { + {testTxsHashes[1], typeptr(1 + testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))}, + }, + }), + // Schedule all the transactions for retrieval + doWait{time: txArriveTimeout, step: true}, + isWaitingWithMeta(nil), + isScheduledWithMeta{ + tracking: map[string][]announce{ + "A": { + {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(uint32(testTxs[0].Size()))}, + {testTxsHashes[1], typeptr(testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))}, + }, + "B": { + {testTxsHashes[0], typeptr(testTxs[0].Type()), sizeptr(1024 + uint32(testTxs[0].Size()))}, + }, + "C": { + {testTxsHashes[1], typeptr(1 + testTxs[1].Type()), sizeptr(uint32(testTxs[1].Size()))}, + }, + }, + fetching: map[string][]common.Hash{ + "A": {testTxsHashes[0]}, + "C": {testTxsHashes[1]}, + }, + }, + // Deliver the transactions and wait for B to be dropped + doTxEnqueue{peer: "A", txs: []*types.Transaction{testTxs[0], testTxs[1]}}, + doFunc(func() { <-drop }), + doFunc(func() { <-drop }), + }, + }) +} + // This test reproduces a crash caught by the fuzzer. The root cause was a // dangling transaction timing out and clashing on re-add with a concurrently // announced one. @@ -1132,6 +1505,7 @@ func TestTransactionFetcherFuzzCrash01(t *testing.T) { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -1159,6 +1533,7 @@ func TestTransactionFetcherFuzzCrash02(t *testing.T) { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -1188,6 +1563,7 @@ func TestTransactionFetcherFuzzCrash03(t *testing.T) { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -1224,6 +1600,7 @@ func TestTransactionFetcherFuzzCrash04(t *testing.T) { <-proceed return errors.New("peer disconnected") }, + nil, ) }, steps: []interface{}{ @@ -1274,9 +1651,34 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { // Crunch through all the test steps and execute them for i, step := range tt.steps { + // Auto-expand certain steps to ones with metadata + switch old := step.(type) { + case isWaiting: + new := make(isWaitingWithMeta) + for peer, hashes := range old { + for _, hash := range hashes { + new[peer] = append(new[peer], announce{hash, nil, nil}) + } + } + step = new + + case isScheduled: + new := isScheduledWithMeta{ + tracking: make(map[string][]announce), + fetching: old.fetching, + dangling: old.dangling, + } + for peer, hashes := range old.tracking { + for _, hash := range hashes { + new.tracking[peer] = append(new.tracking[peer], announce{hash, nil, nil}) + } + } + step = new + } + // Process the original or expanded steps switch step := step.(type) { case doTxNotify: - if err := fetcher.Notify(step.peer, step.hashes); err != nil { + if err := fetcher.Notify(step.peer, step.types, step.sizes, step.hashes); err != nil { t.Errorf("step %d: %v", i, err) } <-wait // Fetcher needs to process this, wait until it's done @@ -1307,24 +1709,34 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { case doFunc: step() - case isWaiting: + case isWaitingWithMeta: // We need to check that the waiting list (stage 1) internals // match with the expected set. Check the peer->hash mappings // first. - for peer, hashes := range step { + for peer, announces := range step { waiting := fetcher.waitslots[peer] if waiting == nil { t.Errorf("step %d: peer %s missing from waitslots", i, peer) continue } - for _, hash := range hashes { - if _, ok := waiting[hash]; !ok { - t.Errorf("step %d, peer %s: hash %x missing from waitslots", i, peer, hash) + for _, ann := range announces { + if meta, ok := waiting[ann.hash]; !ok { + t.Errorf("step %d, peer %s: hash %x missing from waitslots", i, peer, ann.hash) + } else { + if (meta == nil && (ann.kind != nil || ann.size != nil)) || + (meta != nil && (ann.kind == nil || ann.size == nil)) || + (meta != nil && (meta.kind != *ann.kind || meta.size != *ann.size)) { + t.Errorf("step %d, peer %s, hash %x: waitslot metadata mismatch: want %v, have %v/%v", i, peer, ann.hash, meta, *ann.kind, *ann.size) + } } } - for hash := range waiting { - if !containsHash(hashes, hash) { - t.Errorf("step %d, peer %s: hash %x extra in waitslots", i, peer, hash) + for hash, meta := range waiting { + ann := announce{hash: hash} + if meta != nil { + ann.kind, ann.size = &meta.kind, &meta.size + } + if !containsAnnounce(announces, ann) { + t.Errorf("step %d, peer %s: announce %v extra in waitslots", i, peer, ann) } } } @@ -1334,13 +1746,13 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { } } // Peer->hash sets correct, check the hash->peer and timeout sets - for peer, hashes := range step { - for _, hash := range hashes { - if _, ok := fetcher.waitlist[hash][peer]; !ok { - t.Errorf("step %d, hash %x: peer %s missing from waitlist", i, hash, peer) + for peer, announces := range step { + for _, ann := range announces { + if _, ok := fetcher.waitlist[ann.hash][peer]; !ok { + t.Errorf("step %d, hash %x: peer %s missing from waitlist", i, ann.hash, peer) } - if _, ok := fetcher.waittime[hash]; !ok { - t.Errorf("step %d: hash %x missing from waittime", i, hash) + if _, ok := fetcher.waittime[ann.hash]; !ok { + t.Errorf("step %d: hash %x missing from waittime", i, ann.hash) } } } @@ -1349,15 +1761,15 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { t.Errorf("step %d, hash %x: empty peerset in waitlist", i, hash) } for peer := range peers { - if !containsHash(step[peer], hash) { + if !containsHashInAnnounces(step[peer], hash) { t.Errorf("step %d, hash %x: peer %s extra in waitlist", i, hash, peer) } } } for hash := range fetcher.waittime { var found bool - for _, hashes := range step { - if containsHash(hashes, hash) { + for _, announces := range step { + if containsHashInAnnounces(announces, hash) { found = true break } @@ -1367,23 +1779,33 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { } } - case isScheduled: + case isScheduledWithMeta: // Check that all scheduled announces are accounted for and no // extra ones are present. - for peer, hashes := range step.tracking { + for peer, announces := range step.tracking { scheduled := fetcher.announces[peer] if scheduled == nil { t.Errorf("step %d: peer %s missing from announces", i, peer) continue } - for _, hash := range hashes { - if _, ok := scheduled[hash]; !ok { - t.Errorf("step %d, peer %s: hash %x missing from announces", i, peer, hash) + for _, ann := range announces { + if meta, ok := scheduled[ann.hash]; !ok { + t.Errorf("step %d, peer %s: hash %x missing from announces", i, peer, ann.hash) + } else { + if (meta == nil && (ann.kind != nil || ann.size != nil)) || + (meta != nil && (ann.kind == nil || ann.size == nil)) || + (meta != nil && (meta.kind != *ann.kind || meta.size != *ann.size)) { + t.Errorf("step %d, peer %s, hash %x: announce metadata mismatch: want %v, have %v/%v", i, peer, ann.hash, meta, *ann.kind, *ann.size) + } } } - for hash := range scheduled { - if !containsHash(hashes, hash) { - t.Errorf("step %d, peer %s: hash %x extra in announces", i, peer, hash) + for hash, meta := range scheduled { + ann := announce{hash: hash} + if meta != nil { + ann.kind, ann.size = &meta.kind, &meta.size + } + if !containsAnnounce(announces, ann) { + t.Errorf("step %d, peer %s: announce %x extra in announces", i, peer, hash) } } } @@ -1483,17 +1905,17 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { // retrieval but not actively being downloaded are tracked only // in the stage 2 `announced` map. var queued []common.Hash - for _, hashes := range step.tracking { - for _, hash := range hashes { + for _, announces := range step.tracking { + for _, ann := range announces { var found bool for _, hs := range step.fetching { - if containsHash(hs, hash) { + if containsHash(hs, ann.hash) { found = true break } } if !found { - queued = append(queued, hash) + queued = append(queued, ann.hash) } } } @@ -1509,8 +1931,8 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { } case isUnderpriced: - if fetcher.underpriced.Cardinality() != int(step) { - t.Errorf("step %d: underpriced set size mismatch: have %d, want %d", i, fetcher.underpriced.Cardinality(), step) + if fetcher.underpriced.Len() != int(step) { + t.Errorf("step %d: underpriced set size mismatch: have %d, want %d", i, fetcher.underpriced.Len(), step) } default: @@ -1526,6 +1948,42 @@ func testTransactionFetcher(t *testing.T, tt txFetcherTest) { } } +// containsAnnounce returns whether an announcement is contained within a slice +// of announcements. +func containsAnnounce(slice []announce, ann announce) bool { + for _, have := range slice { + if have.hash == ann.hash { + if have.kind == nil || ann.kind == nil { + if have.kind != ann.kind { + return false + } + } else if *have.kind != *ann.kind { + return false + } + if have.size == nil || ann.size == nil { + if have.size != ann.size { + return false + } + } else if *have.size != *ann.size { + return false + } + return true + } + } + return false +} + +// containsHashInAnnounces returns whether a hash is contained within a slice +// of announcements. +func containsHashInAnnounces(slice []announce, hash common.Hash) bool { + for _, have := range slice { + if have.hash == hash { + return true + } + } + return false +} + // containsHash returns whether a hash is contained within a hash slice. func containsHash(slice []common.Hash, hash common.Hash) bool { for _, have := range slice { diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 122676e96c81..a5750c193482 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -106,32 +106,32 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { } return f.blockLogs(ctx, header) } - // Short-cut if all we care about is pending logs - if f.begin == rpc.PendingBlockNumber.Int64() { - if f.end != rpc.PendingBlockNumber.Int64() { - return nil, errors.New("invalid block range") - } - return f.pendingLogs() - } - // Figure out the limits of the filter range - header, _ := f.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) - if header == nil { - return nil, nil - } + var ( - err error - head = header.Number.Int64() - pending = f.end == rpc.PendingBlockNumber.Int64() + beginPending = f.begin == rpc.PendingBlockNumber.Int64() + endPending = f.end == rpc.PendingBlockNumber.Int64() ) + + // special case for pending logs + if beginPending && !endPending { + return nil, errors.New("invalid block range") + } + + // Short-cut if all we care about is pending logs + if beginPending && endPending { + return f.pendingLogs(), nil + } + resolveSpecial := func(number int64) (int64, error) { var hdr *types.Header switch number { - case rpc.LatestBlockNumber.Int64(): - return head, nil - case rpc.PendingBlockNumber.Int64(): + case rpc.LatestBlockNumber.Int64(), rpc.PendingBlockNumber.Int64(): // we should return head here since we've already captured // that we need to get the pending logs in the pending boolean above - return head, nil + hdr, _ = f.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber) + if hdr == nil { + return 0, errors.New("latest header not found") + } case rpc.FinalizedBlockNumber.Int64(): hdr, _ = f.sys.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber) if hdr == nil { @@ -147,57 +147,92 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { } return hdr.Number.Int64(), nil } + + var err error + // range query need to resolve the special begin/end block number if f.begin, err = resolveSpecial(f.begin); err != nil { return nil, err } if f.end, err = resolveSpecial(f.end); err != nil { return nil, err } - // Gather all indexed logs, and finish with non indexed ones + + logChan, errChan := f.rangeLogsAsync(ctx) + var logs []*types.Log + for { + select { + case log := <-logChan: + logs = append(logs, log) + case err := <-errChan: + if err != nil { + // if an error occurs during extraction, we do return the extracted data + return logs, err + } + // Append the pending ones + if endPending { + pendingLogs := f.pendingLogs() + logs = append(logs, pendingLogs...) + } + return logs, nil + } + } +} + +// rangeLogsAsync retrieves block-range logs that match the filter criteria asynchronously, +// it creates and returns two channels: one for delivering log data, and one for reporting errors. +func (f *Filter) rangeLogsAsync(ctx context.Context) (chan *types.Log, chan error) { var ( - logs []*types.Log - end = uint64(f.end) - size, sections = f.sys.backend.BloomStatus() + logChan = make(chan *types.Log) + errChan = make(chan error) ) - if indexed := sections * size; indexed > uint64(f.begin) { - if indexed > end { - logs, err = f.indexedLogs(ctx, end) - } else { - logs, err = f.indexedLogs(ctx, indexed-1) - } - if err != nil { - return logs, err + + go func() { + defer func() { + close(errChan) + close(logChan) + }() + + // Gather all indexed logs, and finish with non indexed ones + var ( + end = uint64(f.end) + size, sections = f.sys.backend.BloomStatus() + err error + ) + if indexed := sections * size; indexed > uint64(f.begin) { + if indexed > end { + indexed = end + 1 + } + if err = f.indexedLogs(ctx, indexed-1, logChan); err != nil { + errChan <- err + return + } } - } - rest, err := f.unindexedLogs(ctx, end) - logs = append(logs, rest...) - if pending { - pendingLogs, err := f.pendingLogs() - if err != nil { - return nil, err + + if err := f.unindexedLogs(ctx, end, logChan); err != nil { + errChan <- err + return } - logs = append(logs, pendingLogs...) - } - return logs, err + + errChan <- nil + }() + + return logChan, errChan } // indexedLogs returns the logs matching the filter criteria based on the bloom // bits indexed available locally or via the network. -func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) { +func (f *Filter) indexedLogs(ctx context.Context, end uint64, logChan chan *types.Log) error { // Create a matcher session and request servicing from the backend matches := make(chan uint64, 64) session, err := f.matcher.Start(ctx, uint64(f.begin), end, matches) if err != nil { - return nil, err + return err } defer session.Close() f.sys.backend.ServiceFilter(ctx, session) - // Iterate over the matches until exhausted or context closed - var logs []*types.Log - for { select { case number, ok := <-matches: @@ -207,47 +242,50 @@ func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, err if err == nil { f.begin = int64(end) + 1 } - return logs, err + return err } f.begin = int64(number) + 1 // Retrieve the suggested block and pull any truly matching logs header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(number)) if header == nil || err != nil { - return logs, err + return err } found, err := f.checkMatches(ctx, header) if err != nil { - return logs, err + return err + } + for _, log := range found { + logChan <- log } - logs = append(logs, found...) case <-ctx.Done(): - return logs, ctx.Err() + return ctx.Err() } } } // unindexedLogs returns the logs matching the filter criteria based on raw block // iteration and bloom matching. -func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) { - var logs []*types.Log - +func (f *Filter) unindexedLogs(ctx context.Context, end uint64, logChan chan *types.Log) error { for ; f.begin <= int64(end); f.begin++ { - if f.begin%10 == 0 && ctx.Err() != nil { - return logs, ctx.Err() - } header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin)) if header == nil || err != nil { - return logs, err + return err } found, err := f.blockLogs(ctx, header) if err != nil { - return logs, err + return err + } + for _, log := range found { + select { + case logChan <- log: + case <-ctx.Done(): + return ctx.Err() + } } - logs = append(logs, found...) } - return logs, nil + return nil } // blockLogs returns the logs matching the filter criteria within a single block. @@ -294,63 +332,62 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*typ } // pendingLogs returns the logs matching the filter criteria within the pending block. -func (f *Filter) pendingLogs() ([]*types.Log, error) { +func (f *Filter) pendingLogs() []*types.Log { block, receipts := f.sys.backend.PendingBlockAndReceipts() - if block == nil { - return nil, errors.New("pending state not available") + if block == nil || receipts == nil { + return nil } if bloomFilter(block.Bloom(), f.addresses, f.topics) { var unfiltered []*types.Log for _, r := range receipts { unfiltered = append(unfiltered, r.Logs...) } - return filterLogs(unfiltered, nil, nil, f.addresses, f.topics), nil + return filterLogs(unfiltered, nil, nil, f.addresses, f.topics) } - return nil, nil + return nil } -func includes(addresses []common.Address, a common.Address) bool { - for _, addr := range addresses { - if addr == a { +// includes returns true if the element is present in the list. +func includes[T comparable](things []T, element T) bool { + for _, thing := range things { + if thing == element { return true } } - return false } // filterLogs creates a slice of logs matching the given criteria. func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log { - var ret []*types.Log -Logs: - for _, log := range logs { + var check = func(log *types.Log) bool { if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber { - continue + return false } if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber { - continue + return false } - if len(addresses) > 0 && !includes(addresses, log.Address) { - continue + return false } // If the to filtered topics is greater than the amount of topics in logs, skip. if len(topics) > len(log.Topics) { - continue + return false } for i, sub := range topics { - match := len(sub) == 0 // empty rule set == wildcard - for _, topic := range sub { - if log.Topics[i] == topic { - match = true - break - } + if len(sub) == 0 { + continue // empty rule set == wildcard } - if !match { - continue Logs + if !includes(sub, log.Topics[i]) { + return false } } - ret = append(ret, log) + return true + } + var ret []*types.Log + for _, log := range logs { + if check(log) { + ret = append(ret, log) + } } return ret } diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index 1768681c1721..35e396c23e75 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -444,15 +444,6 @@ func (es *EventSystem) handlePendingLogs(filters filterIndex, ev []*types.Log) { } } -func (es *EventSystem) handleRemovedLogs(filters filterIndex, ev core.RemovedLogsEvent) { - for _, f := range filters[LogsSubscription] { - matchedLogs := filterLogs(ev.Logs, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) - if len(matchedLogs) > 0 { - f.logs <- matchedLogs - } - } -} - func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent) { for _, f := range filters[PendingTransactionsSubscription] { f.txs <- ev.Txs @@ -573,7 +564,7 @@ func (es *EventSystem) eventLoop() { case ev := <-es.logsCh: es.handleLogs(index, ev) case ev := <-es.rmLogsCh: - es.handleRemovedLogs(index, ev) + es.handleLogs(index, ev.Logs) case ev := <-es.pendingLogsCh: es.handlePendingLogs(index, ev) case ev := <-es.chainCh: diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index ae0c83eb2924..b5d716ae594b 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -50,6 +50,8 @@ type testBackend struct { rmLogsFeed event.Feed pendingLogsFeed event.Feed chainFeed event.Feed + pendingBlock *types.Block + pendingReceipts types.Receipts } func (b *testBackend) ChainConfig() *params.ChainConfig { @@ -119,12 +121,12 @@ func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types. } func (b *testBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { - logs := rawdb.ReadLogs(b.db, hash, number, params.TestChainConfig) + logs := rawdb.ReadLogs(b.db, hash, number) return logs, nil } func (b *testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { - return nil, nil + return b.pendingBlock, b.pendingReceipts } func (b *testBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index 0e42c798ec9e..dd4de0034f1e 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -18,18 +18,23 @@ package filters import ( "context" + "encoding/json" "math/big" - "reflect" + "strings" "testing" + "time" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" ) func makeReceipt(addr common.Address) *types.Receipt { @@ -81,7 +86,7 @@ func BenchmarkFilters(b *testing.B) { // The test txs are not properly signed, can't simply create a chain // and then import blocks. TODO(rjl493456442) try to get rid of the // manual database writes. - gspec.MustCommit(db) + gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults)) for i, block := range chain { rawdb.WriteBlock(db, block) @@ -103,142 +108,264 @@ func BenchmarkFilters(b *testing.B) { func TestFilters(t *testing.T) { var ( - db, _ = rawdb.NewLevelDBDatabase(t.TempDir(), 0, 0, "", false) - _, sys = newTestFilterSystem(t, db, Config{}) + db = rawdb.NewMemoryDatabase() + _, sys = newTestFilterSystem(t, db, Config{}) + // Sender account key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr = crypto.PubkeyToAddress(key1.PublicKey) + signer = types.NewLondonSigner(big.NewInt(1)) + // Logging contract + contract = common.Address{0xfe} + contract2 = common.Address{0xff} + abiStr = `[{"inputs":[],"name":"log0","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"t1","type":"uint256"}],"name":"log1","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"t1","type":"uint256"},{"internalType":"uint256","name":"t2","type":"uint256"}],"name":"log2","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"t1","type":"uint256"},{"internalType":"uint256","name":"t2","type":"uint256"},{"internalType":"uint256","name":"t3","type":"uint256"}],"name":"log3","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"t1","type":"uint256"},{"internalType":"uint256","name":"t2","type":"uint256"},{"internalType":"uint256","name":"t3","type":"uint256"},{"internalType":"uint256","name":"t4","type":"uint256"}],"name":"log4","outputs":[],"stateMutability":"nonpayable","type":"function"}]` + /* + // SPDX-License-Identifier: GPL-3.0 + pragma solidity >=0.7.0 <0.9.0; + + contract Logger { + function log0() external { + assembly { + log0(0, 0) + } + } + + function log1(uint t1) external { + assembly { + log1(0, 0, t1) + } + } + + function log2(uint t1, uint t2) external { + assembly { + log2(0, 0, t1, t2) + } + } + + function log3(uint t1, uint t2, uint t3) external { + assembly { + log3(0, 0, t1, t2, t3) + } + } + + function log4(uint t1, uint t2, uint t3, uint t4) external { + assembly { + log4(0, 0, t1, t2, t3, t4) + } + } + } + */ + bytecode = common.FromHex("608060405234801561001057600080fd5b50600436106100575760003560e01c80630aa731851461005c5780632a4c08961461006657806378b9a1f314610082578063c670f8641461009e578063c683d6a3146100ba575b600080fd5b6100646100d6565b005b610080600480360381019061007b9190610143565b6100dc565b005b61009c60048036038101906100979190610196565b6100e8565b005b6100b860048036038101906100b391906101d6565b6100f2565b005b6100d460048036038101906100cf9190610203565b6100fa565b005b600080a0565b808284600080a3505050565b8082600080a25050565b80600080a150565b80828486600080a450505050565b600080fd5b6000819050919050565b6101208161010d565b811461012b57600080fd5b50565b60008135905061013d81610117565b92915050565b60008060006060848603121561015c5761015b610108565b5b600061016a8682870161012e565b935050602061017b8682870161012e565b925050604061018c8682870161012e565b9150509250925092565b600080604083850312156101ad576101ac610108565b5b60006101bb8582860161012e565b92505060206101cc8582860161012e565b9150509250929050565b6000602082840312156101ec576101eb610108565b5b60006101fa8482850161012e565b91505092915050565b6000806000806080858703121561021d5761021c610108565b5b600061022b8782880161012e565b945050602061023c8782880161012e565b935050604061024d8782880161012e565b925050606061025e8782880161012e565b9150509295919450925056fea264697066735822122073a4b156f487e59970dc1ef449cc0d51467268f676033a17188edafcee861f9864736f6c63430008110033") hash1 = common.BytesToHash([]byte("topic1")) hash2 = common.BytesToHash([]byte("topic2")) hash3 = common.BytesToHash([]byte("topic3")) hash4 = common.BytesToHash([]byte("topic4")) + hash5 = common.BytesToHash([]byte("topic5")) gspec = &core.Genesis{ - Config: params.TestChainConfig, - Alloc: core.GenesisAlloc{addr: {Balance: big.NewInt(1000000)}}, + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{ + addr: {Balance: big.NewInt(0).Mul(big.NewInt(100), big.NewInt(params.Ether))}, + contract: {Balance: big.NewInt(0), Code: bytecode}, + contract2: {Balance: big.NewInt(0), Code: bytecode}, + }, BaseFee: big.NewInt(params.InitialBaseFee), } ) - defer db.Close() - _, chain, receipts := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 1000, func(i int, gen *core.BlockGen) { + contractABI, err := abi.JSON(strings.NewReader(abiStr)) + if err != nil { + t.Fatal(err) + } + + // Hack: GenerateChainWithGenesis creates a new db. + // Commit the genesis manually and use GenerateChain. + _, err = gspec.Commit(db, trie.NewDatabase(db, nil)) + if err != nil { + t.Fatal(err) + } + chain, _ := core.GenerateChain(gspec.Config, gspec.ToBlock(), ethash.NewFaker(), db, 1000, func(i int, gen *core.BlockGen) { switch i { case 1: - receipt := types.NewReceipt(nil, false, 0) - receipt.Logs = []*types.Log{ - { - Address: addr, - Topics: []common.Hash{hash1}, - }, + data, err := contractABI.Pack("log1", hash1.Big()) + if err != nil { + t.Fatal(err) } - gen.AddUncheckedReceipt(receipt) - gen.AddUncheckedTx(types.NewTransaction(1, common.HexToAddress("0x1"), big.NewInt(1), 1, gen.BaseFee(), nil)) + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 0, + GasPrice: gen.BaseFee(), + Gas: 30000, + To: &contract, + Data: data, + }), signer, key1) + gen.AddTx(tx) + tx2, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 1, + GasPrice: gen.BaseFee(), + Gas: 30000, + To: &contract2, + Data: data, + }), signer, key1) + gen.AddTx(tx2) case 2: - receipt := types.NewReceipt(nil, false, 0) - receipt.Logs = []*types.Log{ - { - Address: addr, - Topics: []common.Hash{hash2}, - }, + data, err := contractABI.Pack("log2", hash2.Big(), hash1.Big()) + if err != nil { + t.Fatal(err) } - gen.AddUncheckedReceipt(receipt) - gen.AddUncheckedTx(types.NewTransaction(2, common.HexToAddress("0x2"), big.NewInt(2), 2, gen.BaseFee(), nil)) - + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 2, + GasPrice: gen.BaseFee(), + Gas: 30000, + To: &contract, + Data: data, + }), signer, key1) + gen.AddTx(tx) case 998: - receipt := types.NewReceipt(nil, false, 0) - receipt.Logs = []*types.Log{ - { - Address: addr, - Topics: []common.Hash{hash3}, - }, + data, err := contractABI.Pack("log1", hash3.Big()) + if err != nil { + t.Fatal(err) } - gen.AddUncheckedReceipt(receipt) - gen.AddUncheckedTx(types.NewTransaction(998, common.HexToAddress("0x998"), big.NewInt(998), 998, gen.BaseFee(), nil)) + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 3, + GasPrice: gen.BaseFee(), + Gas: 30000, + To: &contract2, + Data: data, + }), signer, key1) + gen.AddTx(tx) case 999: - receipt := types.NewReceipt(nil, false, 0) - receipt.Logs = []*types.Log{ - { - Address: addr, - Topics: []common.Hash{hash4}, - }, + data, err := contractABI.Pack("log1", hash4.Big()) + if err != nil { + t.Fatal(err) } - gen.AddUncheckedReceipt(receipt) - gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil)) + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 4, + GasPrice: gen.BaseFee(), + Gas: 30000, + To: &contract, + Data: data, + }), signer, key1) + gen.AddTx(tx) } }) - // The test txs are not properly signed, can't simply create a chain - // and then import blocks. TODO(rjl493456442) try to get rid of the - // manual database writes. - gspec.MustCommit(db) - for i, block := range chain { - rawdb.WriteBlock(db, block) - rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) - rawdb.WriteHeadBlockHash(db, block.Hash()) - rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), receipts[i]) + var l uint64 + bc, err := core.NewBlockChain(db, nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l) + if err != nil { + t.Fatal(err) + } + _, err = bc.InsertChain(chain) + if err != nil { + t.Fatal(err) } // Set block 998 as Finalized (-3) - rawdb.WriteFinalizedBlockHash(db, chain[998].Hash()) + bc.SetFinalized(chain[998].Header()) - filter := sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{addr}, [][]common.Hash{{hash1, hash2, hash3, hash4}}) - logs, _ := filter.Logs(context.Background()) - if len(logs) != 4 { - t.Error("expected 4 log, got", len(logs)) - } + // Generate pending block + pchain, preceipts := core.GenerateChain(gspec.Config, chain[len(chain)-1], ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) { + data, err := contractABI.Pack("log1", hash5.Big()) + if err != nil { + t.Fatal(err) + } + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: 5, + GasPrice: gen.BaseFee(), + Gas: 30000, + To: &contract, + Data: data, + }), signer, key1) + gen.AddTx(tx) + }) + sys.backend.(*testBackend).pendingBlock = pchain[0] + sys.backend.(*testBackend).pendingReceipts = preceipts[0] for i, tc := range []struct { - f *Filter - wantHashes []common.Hash + f *Filter + want string + err string }{ { - sys.NewRangeFilter(900, 999, []common.Address{addr}, [][]common.Hash{{hash3}}), - []common.Hash{hash3}, + f: sys.NewBlockFilter(chain[2].Hash(), []common.Address{contract}, nil), + want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","logIndex":"0x0","removed":false}]`, + }, { + f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{contract}, [][]common.Hash{{hash1, hash2, hash3, hash4}}), + want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","logIndex":"0x0","removed":false}]`, }, { - sys.NewRangeFilter(990, int64(rpc.LatestBlockNumber), []common.Address{addr}, [][]common.Hash{{hash3}}), - []common.Hash{hash3}, + f: sys.NewRangeFilter(900, 999, []common.Address{contract}, [][]common.Hash{{hash3}}), }, { - sys.NewRangeFilter(1, 10, nil, [][]common.Hash{{hash1, hash2}}), - []common.Hash{hash1, hash2}, + f: sys.NewRangeFilter(990, int64(rpc.LatestBlockNumber), []common.Address{contract2}, [][]common.Hash{{hash3}}), + want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","logIndex":"0x0","removed":false}]`, }, { - sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}}), - nil, + f: sys.NewRangeFilter(1, 10, []common.Address{contract}, [][]common.Hash{{hash2}, {hash1}}), + want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","logIndex":"0x0","removed":false}]`, }, { - sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{common.BytesToAddress([]byte("failmenow"))}, nil), - nil, + f: sys.NewRangeFilter(1, 10, nil, [][]common.Hash{{hash1, hash2}}), + want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","logIndex":"0x0","removed":false},{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xdba3e2ea9a7d690b722d70ee605fd67ba4c00d1d3aecd5cf187a7b92ad8eb3df","transactionIndex":"0x1","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","logIndex":"0x1","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","logIndex":"0x0","removed":false}]`, }, { - sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}, {hash1}}), - nil, + f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}}), }, { - sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), []common.Hash{hash4}, + f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{common.BytesToAddress([]byte("failmenow"))}, nil), }, { - sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), []common.Hash{hash3, hash4}, + f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}, {hash1}}), }, { - sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil), []common.Hash{hash3}, + f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), + want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","logIndex":"0x0","removed":false}]`, }, { - sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil), nil, + f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), + want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","logIndex":"0x0","removed":false}]`, }, { - sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), nil, + f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil), + want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","logIndex":"0x0","removed":false}]`, }, { - sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.SafeBlockNumber), nil, nil), nil, + f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil), }, { - sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.SafeBlockNumber), nil, nil), nil, + f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), + err: "safe header not found", }, { - sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.PendingBlockNumber), nil, nil), nil, + f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.SafeBlockNumber), nil, nil), + err: "safe header not found", + }, { + f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.SafeBlockNumber), nil, nil), + err: "safe header not found", + }, { + f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.PendingBlockNumber), nil, nil), + want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696335"],"data":"0x","blockNumber":"0x3e9","transactionHash":"0x4110587c1b8d86edc85dce929a34127f1cb8809515a9f177c91c866de3eb0638","transactionIndex":"0x0","blockHash":"0xc7245899e5817f16fa99cf5ad2d9c1e4b98443a565a673ec9c764640443ef037","logIndex":"0x0","removed":false}]`, + }, { + f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.PendingBlockNumber), nil, nil), + want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696335"],"data":"0x","blockNumber":"0x3e9","transactionHash":"0x4110587c1b8d86edc85dce929a34127f1cb8809515a9f177c91c866de3eb0638","transactionIndex":"0x0","blockHash":"0xc7245899e5817f16fa99cf5ad2d9c1e4b98443a565a673ec9c764640443ef037","logIndex":"0x0","removed":false}]`, + }, { + f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), + err: "invalid block range", }, } { - logs, _ := tc.f.Logs(context.Background()) - var haveHashes []common.Hash - for _, l := range logs { - haveHashes = append(haveHashes, l.Topics[0]) - } - if have, want := len(haveHashes), len(tc.wantHashes); have != want { - t.Fatalf("test %d, have %d logs, want %d", i, have, want) + logs, err := tc.f.Logs(context.Background()) + if err == nil && tc.err != "" { + t.Fatalf("test %d, expected error %q, got nil", i, tc.err) + } else if err != nil && err.Error() != tc.err { + t.Fatalf("test %d, expected error %q, got %q", i, tc.err, err.Error()) } - if len(haveHashes) == 0 { + if tc.want == "" && len(logs) == 0 { continue } - if !reflect.DeepEqual(tc.wantHashes, haveHashes) { - t.Fatalf("test %d, have %v want %v", i, haveHashes, tc.wantHashes) + have, err := json.Marshal(logs) + if err != nil { + t.Fatal(err) + } + if string(have) != tc.want { + t.Fatalf("test %d, have:\n%s\nwant:\n%s", i, have, tc.want) } } + + t.Run("timeout", func(t *testing.T) { + f := sys.NewRangeFilter(0, -1, nil, nil) + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Hour)) + defer cancel() + _, err := f.Logs(ctx) + if err == nil { + t.Fatal("expected error") + } + if err != context.DeadlineExceeded { + t.Fatalf("expected context.DeadlineExceeded, got %v", err) + } + }) } diff --git a/eth/gasprice/feehistory.go b/eth/gasprice/feehistory.go index ee0b0ebcca1c..226991b24b2c 100644 --- a/eth/gasprice/feehistory.go +++ b/eth/gasprice/feehistory.go @@ -23,14 +23,14 @@ import ( "fmt" "math" "math/big" - "sort" "sync/atomic" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" + "golang.org/x/exp/slices" ) var ( @@ -69,20 +69,9 @@ type processedFees struct { } // txGasAndReward is sorted in ascending order based on reward -type ( - txGasAndReward struct { - gasUsed uint64 - reward *big.Int - } - sortGasAndReward []txGasAndReward -) - -func (s sortGasAndReward) Len() int { return len(s) } -func (s sortGasAndReward) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} -func (s sortGasAndReward) Less(i, j int) bool { - return s[i].reward.Cmp(s[j].reward) < 0 +type txGasAndReward struct { + gasUsed uint64 + reward *big.Int } // processBlock takes a blockFees structure with the blockNumber, the header and optionally @@ -94,7 +83,7 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { bf.results.baseFee = new(big.Int) } if chainconfig.IsLondon(big.NewInt(int64(bf.blockNumber + 1))) { - bf.results.nextBaseFee = misc.CalcBaseFee(chainconfig, bf.header) + bf.results.nextBaseFee = eip1559.CalcBaseFee(chainconfig, bf.header) } else { bf.results.nextBaseFee = new(big.Int) } @@ -117,12 +106,14 @@ func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { return } - sorter := make(sortGasAndReward, len(bf.block.Transactions())) + sorter := make([]txGasAndReward, len(bf.block.Transactions())) for i, tx := range bf.block.Transactions() { reward, _ := tx.EffectiveGasTip(bf.block.BaseFee()) sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward} } - sort.Stable(sorter) + slices.SortStableFunc(sorter, func(a, b txGasAndReward) int { + return a.reward.Cmp(b.reward) + }) var txIndex int sumGasUsed := sorter[0].gasUsed diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index a3dd83d79fb2..b71964981145 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -19,7 +19,6 @@ package gasprice import ( "context" "math/big" - "sort" "sync" "github.com/ethereum/go-ethereum/common" @@ -30,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + "golang.org/x/exp/slices" ) const sampleNumber = 3 // Number of transactions sampled in a block @@ -208,7 +208,7 @@ func (oracle *Oracle) SuggestTipCap(ctx context.Context) (*big.Int, error) { } price := lastPrice if len(results) > 0 { - sort.Sort(bigIntArray(results)) + slices.SortFunc(results, func(a, b *big.Int) int { return a.Cmp(b) }) price = results[(len(results)-1)*oracle.percentile/100] } if price.Cmp(oracle.maxPrice) > 0 { @@ -227,30 +227,6 @@ type results struct { err error } -type txSorter struct { - txs []*types.Transaction - baseFee *big.Int -} - -func newSorter(txs []*types.Transaction, baseFee *big.Int) *txSorter { - return &txSorter{ - txs: txs, - baseFee: baseFee, - } -} - -func (s *txSorter) Len() int { return len(s.txs) } -func (s *txSorter) Swap(i, j int) { - s.txs[i], s.txs[j] = s.txs[j], s.txs[i] -} -func (s *txSorter) Less(i, j int) bool { - // It's okay to discard the error because a tx would never be - // accepted into a block with an invalid effective tip. - tip1, _ := s.txs[i].EffectiveGasTip(s.baseFee) - tip2, _ := s.txs[j].EffectiveGasTip(s.baseFee) - return tip1.Cmp(tip2) < 0 -} - // getBlockValues calculates the lowest transaction gas price in a given block // and sends it to the result channel. If the block is empty or all transactions // are sent by the miner itself(it doesn't make any sense to include this kind of @@ -267,14 +243,21 @@ func (oracle *Oracle) getBlockValues(ctx context.Context, blockNum uint64, limit signer := types.MakeSigner(oracle.backend.ChainConfig(), block.Number(), block.Time()) // Sort the transaction by effective tip in ascending sort. - txs := make([]*types.Transaction, len(block.Transactions())) - copy(txs, block.Transactions()) - sorter := newSorter(txs, block.BaseFee()) - sort.Sort(sorter) + txs := block.Transactions() + sortedTxs := make([]*types.Transaction, len(txs)) + copy(sortedTxs, txs) + baseFee := block.BaseFee() + slices.SortFunc(sortedTxs, func(a, b *types.Transaction) int { + // It's okay to discard the error because a tx would never be + // accepted into a block with an invalid effective tip. + tip1, _ := a.EffectiveGasTip(baseFee) + tip2, _ := b.EffectiveGasTip(baseFee) + return tip1.Cmp(tip2) + }) var prices []*big.Int - for _, tx := range sorter.txs { - tip, _ := tx.EffectiveGasTip(block.BaseFee()) + for _, tx := range sortedTxs { + tip, _ := tx.EffectiveGasTip(baseFee) if ignoreUnder != nil && tip.Cmp(ignoreUnder) == -1 { continue } @@ -291,9 +274,3 @@ func (oracle *Oracle) getBlockValues(ctx context.Context, blockNum uint64, limit case <-quit: } } - -type bigIntArray []*big.Int - -func (s bigIntArray) Len() int { return len(s) } -func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 } -func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] } diff --git a/eth/handler.go b/eth/handler.go index f0b043166efb..f0021e56446c 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -29,6 +29,8 @@ import ( "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/fetcher" @@ -37,19 +39,24 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" ) const ( // txChanSize is the size of channel listening to NewTxsEvent. // The number is referenced from the size of tx pool. txChanSize = 4096 -) -var ( - syncChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the sync progress challenge + // txMaxBroadcastSize is the max size of a transaction that will be broadcasted. + // All transactions with a higher size will be announced and need to be fetched + // by the peer. + txMaxBroadcastSize = 4096 ) +var syncChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the sync progress challenge + // txPool defines the methods needed from a transaction pool implementation to // support all the operations needed by the Ethereum chain protocols. type txPool interface { @@ -61,16 +68,17 @@ type txPool interface { // tx hash. Get(hash common.Hash) *types.Transaction - // AddRemotes should add the given transactions to the pool. - AddRemotes([]*types.Transaction) []error + // Add should add the given transactions to the pool. + Add(txs []*types.Transaction, local bool, sync bool) []error // Pending should return pending transactions. // The slice should be modifiable by the caller. - Pending(enforceTips bool) map[common.Address]types.Transactions + Pending(enforceTips bool) map[common.Address][]*txpool.LazyTransaction - // SubscribeNewTxsEvent should return an event subscription of - // NewTxsEvent and send events to the given channel. - SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription + // SubscribeTransactions subscribes to new transaction events. The subscriber + // can decide whether to receive notifications only for newly seen transactions + // or also for reorged out ones. + SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription } // handlerConfig is the collection of initialization parameters to create a full @@ -80,7 +88,7 @@ type handlerConfig struct { Chain *core.BlockChain // Blockchain to serve data from TxPool txPool // Transaction pool to propagate from Merger *consensus.Merger // The manager for eth1/2 transition - Network uint64 // Network identifier to adfvertise + Network uint64 // Network identifier to advertise Sync downloader.SyncMode // Whether to snap or full sync BloomCache uint64 // Megabytes to alloc for snap sync bloom EventMux *event.TypeMux // Legacy event mux, deprecate for `feed` @@ -91,8 +99,8 @@ type handler struct { networkID uint64 forkFilter forkid.Filter // Fork ID filter, constant across the lifetime of the node - snapSync atomic.Bool // Flag whether snap sync is enabled (gets disabled if we already have blocks) - acceptTxs atomic.Bool // Flag whether we're considered synchronised (enables transaction processing) + snapSync atomic.Bool // Flag whether snap sync is enabled (gets disabled if we already have blocks) + synced atomic.Bool // Flag whether we're considered synchronised (enables transaction processing) database ethdb.Database txpool txPool @@ -117,7 +125,9 @@ type handler struct { chainSync *chainSyncer wg sync.WaitGroup - peerWG sync.WaitGroup + + handlerStartCh chan struct{} + handlerDoneCh chan struct{} } // newHandler returns a handler for all Ethereum chain management protocol. @@ -137,6 +147,8 @@ func newHandler(config *handlerConfig) (*handler, error) { merger: config.Merger, requiredBlocks: config.RequiredBlocks, quitSync: make(chan struct{}), + handlerDoneCh: make(chan struct{}), + handlerStartCh: make(chan struct{}), } if config.Sync == downloader.FullSync { // The database seems empty as the current block is the genesis. Yet the snap @@ -150,32 +162,24 @@ func newHandler(config *handlerConfig) (*handler, error) { fullBlock, snapBlock := h.chain.CurrentBlock(), h.chain.CurrentSnapBlock() if fullBlock.Number.Uint64() == 0 && snapBlock.Number.Uint64() > 0 { h.snapSync.Store(true) - log.Warn("Switch sync mode from full sync to snap sync") + log.Warn("Switch sync mode from full sync to snap sync", "reason", "snap sync incomplete") + } else if !h.chain.HasState(fullBlock.Root) { + h.snapSync.Store(true) + log.Warn("Switch sync mode from full sync to snap sync", "reason", "head state missing") } } else { - if h.chain.CurrentBlock().Number.Uint64() > 0 { + head := h.chain.CurrentBlock() + if head.Number.Uint64() > 0 && h.chain.HasState(head.Root) { // Print warning log if database is not empty to run snap sync. - log.Warn("Switch sync mode from snap sync to full sync") + log.Warn("Switch sync mode from snap sync to full sync", "reason", "snap sync complete") } else { // If snap sync was requested and our database is empty, grant it h.snapSync.Store(true) + log.Info("Enabled snap sync", "head", head.Number, "hash", head.Hash()) } } - // If sync succeeds, pass a callback to potentially disable snap sync mode - // and enable transaction propagation. - success := func() { - // If we were running snap sync and it finished, disable doing another - // round on next sync cycle - if h.snapSync.Load() { - log.Info("Snap sync complete, auto disabling") - h.snapSync.Store(false) - } - // If we've successfully finished a sync cycle, accept transactions from - // the network - h.acceptTxs.Store(true) - } // Construct the downloader (long sync) - h.downloader = downloader.New(config.Database, h.eventMux, h.chain, nil, h.removePeer, success) + h.downloader = downloader.New(config.Database, h.eventMux, h.chain, nil, h.removePeer, h.enableSyncedFeatures) if ttd := h.chain.Config().TerminalTotalDifficulty; ttd != nil { if h.chain.Config().TerminalTotalDifficultyPassed { log.Info("Chain post-merge, sync via beacon client") @@ -232,8 +236,8 @@ func newHandler(config *handlerConfig) (*handler, error) { // accept each others' blocks until a restart. Unfortunately we haven't figured // out a way yet where nodes can decide unilaterally whether the network is new // or not. This should be fixed if we figure out a solution. - if h.snapSync.Load() { - log.Warn("Snap syncing, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash()) + if !h.synced.Load() { + log.Warn("Syncing, discarded propagated block", "number", blocks[0].Number(), "hash", blocks[0].Hash()) return 0, nil } if h.merger.TDDReached() { @@ -250,7 +254,7 @@ func newHandler(config *handlerConfig) (*handler, error) { } td := new(big.Int).Add(ptd, block.Difficulty()) if !h.chain.Config().IsTerminalPoWBlock(ptd, td) { - log.Info("Filtered out non-termimal pow block", "number", block.NumberU64(), "hash", block.Hash()) + log.Info("Filtered out non-terminal pow block", "number", block.NumberU64(), "hash", block.Hash()) return 0, nil } if err := h.chain.InsertBlockWithoutSetHead(block); err != nil { @@ -259,11 +263,7 @@ func newHandler(config *handlerConfig) (*handler, error) { } return 0, nil } - n, err := h.chain.InsertChain(blocks) - if err == nil { - h.acceptTxs.Store(true) // Mark initial sync done on any fetcher import - } - return n, err + return h.chain.InsertChain(blocks) } h.blockFetcher = fetcher.NewBlockFetcher(false, nil, h.chain.GetBlockByHash, validator, h.BroadcastBlock, heighter, nil, inserter, h.removePeer) @@ -274,14 +274,58 @@ func newHandler(config *handlerConfig) (*handler, error) { } return p.RequestTxs(hashes) } - h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, h.txpool.AddRemotes, fetchTx) + addTxs := func(txs []*types.Transaction) []error { + return h.txpool.Add(txs, false, false) + } + h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, addTxs, fetchTx, h.removePeer) h.chainSync = newChainSyncer(h) return h, nil } +// protoTracker tracks the number of active protocol handlers. +func (h *handler) protoTracker() { + defer h.wg.Done() + var active int + for { + select { + case <-h.handlerStartCh: + active++ + case <-h.handlerDoneCh: + active-- + case <-h.quitSync: + // Wait for all active handlers to finish. + for ; active > 0; active-- { + <-h.handlerDoneCh + } + return + } + } +} + +// incHandlers signals to increment the number of active handlers if not +// quitting. +func (h *handler) incHandlers() bool { + select { + case h.handlerStartCh <- struct{}{}: + return true + case <-h.quitSync: + return false + } +} + +// decHandlers signals to decrement the number of active handlers. +func (h *handler) decHandlers() { + h.handlerDoneCh <- struct{}{} +} + // runEthPeer registers an eth peer into the joint eth/snap peerset, adds it to // various subsystems and starts handling messages. func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { + if !h.incHandlers() { + return p2p.DiscQuitting + } + defer h.decHandlers() + // If the peer has a `snap` extension, wait for it to connect so we can have // a uniform initialization/teardown mechanism snap, err := h.peers.waitSnapExtension(peer) @@ -289,12 +333,6 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { peer.Log().Error("Snapshot extension barrier failed", "err", err) return err } - // TODO(karalabe): Not sure why this is needed - if !h.chainSync.handlePeerEvent(peer) { - return p2p.DiscQuitting - } - h.peerWG.Add(1) - defer h.peerWG.Done() // Execute the Ethereum handshake var ( @@ -304,7 +342,7 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { number = head.Number.Uint64() td = h.chain.GetTd(hash, number) ) - forkID := forkid.NewID(h.chain.Config(), genesis.Hash(), number, head.Time) + forkID := forkid.NewID(h.chain.Config(), genesis, number, head.Time) if err := peer.Handshake(h.networkID, td, hash, genesis.Hash(), forkID, h.forkFilter); err != nil { peer.Log().Debug("Ethereum handshake failed", "err", err) return err @@ -350,7 +388,7 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { return err } } - h.chainSync.handlePeerEvent(peer) + h.chainSync.handlePeerEvent() // Propagate existing transactions. new transactions appearing // after this will be sent via broadcasts. @@ -377,7 +415,7 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { select { case res := <-resCh: - headers := ([]*types.Header)(*res.Res.(*eth.BlockHeadersPacket)) + headers := ([]*types.Header)(*res.Res.(*eth.BlockHeadersRequest)) if len(headers) == 0 { // Required blocks are allowed to be missing if the remote // node is not yet synced @@ -411,11 +449,20 @@ func (h *handler) runEthPeer(peer *eth.Peer, handler eth.Handler) error { // `eth`, all subsystem registrations and lifecycle management will be done by // the main `eth` handler to prevent strange races. func (h *handler) runSnapExtension(peer *snap.Peer, handler snap.Handler) error { - h.peerWG.Add(1) - defer h.peerWG.Done() + if !h.incHandlers() { + return p2p.DiscQuitting + } + defer h.decHandlers() if err := h.peers.registerSnapExtension(peer); err != nil { - peer.Log().Warn("Snapshot extension registration failed", "err", err) + if metrics.Enabled { + if peer.Inbound() { + snap.IngressRegistrationErrorMeter.Mark(1) + } else { + snap.EgressRegistrationErrorMeter.Mark(1) + } + } + peer.Log().Debug("Snapshot extension registration failed", "err", err) return err } return handler(peer) @@ -463,10 +510,10 @@ func (h *handler) unregisterPeer(id string) { func (h *handler) Start(maxPeers int) { h.maxPeers = maxPeers - // broadcast transactions + // broadcast and announce transactions (only new ones, not resurrected ones) h.wg.Add(1) h.txsCh = make(chan core.NewTxsEvent, txChanSize) - h.txsSub = h.txpool.SubscribeNewTxsEvent(h.txsCh) + h.txsSub = h.txpool.SubscribeTransactions(h.txsCh, false) go h.txBroadcastLoop() // broadcast mined blocks @@ -477,6 +524,10 @@ func (h *handler) Start(maxPeers int) { // start sync handlers h.wg.Add(1) go h.chainSync.loop() + + // start peer handler tracker + h.wg.Add(1) + go h.protoTracker() } func (h *handler) Stop() { @@ -486,14 +537,13 @@ func (h *handler) Stop() { // Quit chainSync and txsync64. // After this is done, no new peers will be accepted. close(h.quitSync) - h.wg.Wait() // Disconnect existing sessions. // This also closes the gate for any new registrations on the peer set. // sessions which are already established but not added to h.peers yet // will exit when they try to register. h.peers.close() - h.peerWG.Wait() + h.wg.Wait() log.Info("Ethereum protocol stopped") } @@ -543,25 +593,36 @@ func (h *handler) BroadcastBlock(block *types.Block, propagate bool) { } // BroadcastTransactions will propagate a batch of transactions -// - To a square root of all peers +// - To a square root of all peers for non-blob transactions // - And, separately, as announcements to all peers which are not known to // already have the given transaction. func (h *handler) BroadcastTransactions(txs types.Transactions) { var ( - annoCount int // Count of announcements made - annoPeers int - directCount int // Count of the txs sent directly to peers - directPeers int // Count of the peers that were sent transactions directly + blobTxs int // Number of blob transactions to announce only + largeTxs int // Number of large transactions to announce only + + directCount int // Number of transactions sent directly to peers (duplicates included) + directPeers int // Number of peers that were sent transactions directly + annCount int // Number of transactions announced across all peers (duplicates included) + annPeers int // Number of peers announced about transactions txset = make(map[*ethPeer][]common.Hash) // Set peer->hash to transfer directly annos = make(map[*ethPeer][]common.Hash) // Set peer->hash to announce - ) // Broadcast transactions to a batch of peers not knowing about it for _, tx := range txs { peers := h.peers.peersWithoutTransaction(tx.Hash()) + + var numDirect int + switch { + case tx.Type() == types.BlobTxType: + blobTxs++ + case tx.Size() > txMaxBroadcastSize: + largeTxs++ + default: + numDirect = int(math.Sqrt(float64(len(peers)))) + } // Send the tx unconditionally to a subset of our peers - numDirect := int(math.Sqrt(float64(len(peers)))) for _, peer := range peers[:numDirect] { txset[peer] = append(txset[peer], tx.Hash()) } @@ -576,13 +637,12 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) { peer.AsyncSendTransactions(hashes) } for peer, hashes := range annos { - annoPeers++ - annoCount += len(hashes) + annPeers++ + annCount += len(hashes) peer.AsyncSendPooledTransactionHashes(hashes) } - log.Debug("Transaction broadcast", "txs", len(txs), - "announce packs", annoPeers, "announced hashes", annoCount, - "tx packs", directPeers, "broadcast txs", directCount) + log.Debug("Distributed transactions", "plaintxs", len(txs)-blobTxs-largeTxs, "blobtxs", blobTxs, "largetxs", largeTxs, + "bcastpeers", directPeers, "bcastcount", directCount, "annpeers", annPeers, "anncount", annCount) } // minedBroadcastLoop sends mined blocks to connected peers. @@ -609,3 +669,20 @@ func (h *handler) txBroadcastLoop() { } } } + +// enableSyncedFeatures enables the post-sync functionalities when the initial +// sync is finished. +func (h *handler) enableSyncedFeatures() { + // Mark the local node as synced. + h.synced.Store(true) + + // If we were running snap sync and it finished, disable doing another + // round on next sync cycle + if h.snapSync.Load() { + log.Info("Snap sync complete, auto disabling") + h.snapSync.Store(false) + } + if h.chain.TrieDB().Scheme() == rawdb.PathScheme { + h.chain.TrieDB().SetBufferSize(pathdb.DefaultBufferSize) + } +} diff --git a/eth/handler_eth.go b/eth/handler_eth.go index 00be022d9e9c..2a839f615f63 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -17,6 +17,7 @@ package eth import ( + "errors" "fmt" "math/big" "time" @@ -51,7 +52,7 @@ func (h *ethHandler) PeerInfo(id enode.ID) interface{} { // AcceptTxs retrieves whether transaction processing is enabled on the node // or if inbound transactions should simply be dropped. func (h *ethHandler) AcceptTxs() bool { - return h.acceptTxs.Load() + return h.synced.Load() } // Handle is invoked from a peer's message handler when it receives a new remote @@ -66,16 +67,21 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error { case *eth.NewBlockPacket: return h.handleBlockBroadcast(peer, packet.Block, packet.TD) - case *eth.NewPooledTransactionHashesPacket66: - return h.txFetcher.Notify(peer.ID(), *packet) + case *eth.NewPooledTransactionHashesPacket67: + return h.txFetcher.Notify(peer.ID(), nil, nil, *packet) case *eth.NewPooledTransactionHashesPacket68: - return h.txFetcher.Notify(peer.ID(), packet.Hashes) + return h.txFetcher.Notify(peer.ID(), packet.Types, packet.Sizes, packet.Hashes) case *eth.TransactionsPacket: + for _, tx := range *packet { + if tx.Type() == types.BlobTxType { + return errors.New("disallowed broadcast blob transaction") + } + } return h.txFetcher.Enqueue(peer.ID(), *packet, false) - case *eth.PooledTransactionsPacket: + case *eth.PooledTransactionsResponse: return h.txFetcher.Enqueue(peer.ID(), *packet, true) default: @@ -90,9 +96,7 @@ func (h *ethHandler) handleBlockAnnounces(peer *eth.Peer, hashes []common.Hash, // the chain already entered the pos stage and disconnect the // remote peer. if h.merger.PoSFinalized() { - // TODO (MariusVanDerWijden) drop non-updated peers after the merge - return nil - // return errors.New("unexpected block announces") + return errors.New("disallowed block announcement") } // Schedule all the unknown hashes for retrieval var ( @@ -118,9 +122,7 @@ func (h *ethHandler) handleBlockBroadcast(peer *eth.Peer, block *types.Block, td // the chain already entered the pos stage and disconnect the // remote peer. if h.merger.PoSFinalized() { - // TODO (MariusVanDerWijden) drop non-updated peers after the merge - return nil - // return errors.New("unexpected block announces") + return errors.New("disallowed block broadcast") } // Schedule the block for import h.blockFetcher.Enqueue(peer.ID(), block) @@ -134,7 +136,7 @@ func (h *ethHandler) handleBlockBroadcast(peer *eth.Peer, block *types.Block, td // Update the peer's total difficulty if better than the previous if _, td := peer.Head(); trueTD.Cmp(td) > 0 { peer.SetHead(trueHead, trueTD) - h.chainSync.handlePeerEvent(peer) + h.chainSync.handlePeerEvent() } return nil } diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index fe852f8fdc0d..b4fde6910c0b 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -58,7 +58,7 @@ func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error { h.blockBroadcasts.Send(packet.Block) return nil - case *eth.NewPooledTransactionHashesPacket66: + case *eth.NewPooledTransactionHashesPacket67: h.txAnnounces.Send(([]common.Hash)(*packet)) return nil @@ -70,7 +70,7 @@ func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error { h.txBroadcasts.Send(([]*types.Transaction)(*packet)) return nil - case *eth.PooledTransactionsPacket: + case *eth.PooledTransactionsResponse: h.txBroadcasts.Send(([]*types.Transaction)(*packet)) return nil @@ -81,7 +81,6 @@ func (h *testEthHandler) Handle(peer *eth.Peer, packet eth.Packet) error { // Tests that peers are correctly accepted (or rejected) based on the advertised // fork IDs in the protocol handshake. -func TestForkIDSplit66(t *testing.T) { testForkIDSplit(t, eth.ETH66) } func TestForkIDSplit67(t *testing.T) { testForkIDSplit(t, eth.ETH67) } func TestForkIDSplit68(t *testing.T) { testForkIDSplit(t, eth.ETH68) } @@ -237,7 +236,6 @@ func testForkIDSplit(t *testing.T, protocol uint) { } // Tests that received transactions are added to the local pool. -func TestRecvTransactions66(t *testing.T) { testRecvTransactions(t, eth.ETH66) } func TestRecvTransactions67(t *testing.T) { testRecvTransactions(t, eth.ETH67) } func TestRecvTransactions68(t *testing.T) { testRecvTransactions(t, eth.ETH68) } @@ -248,10 +246,10 @@ func testRecvTransactions(t *testing.T, protocol uint) { handler := newTestHandler() defer handler.close() - handler.handler.acceptTxs.Store(true) // mark synced to accept transactions + handler.handler.synced.Store(true) // mark synced to accept transactions txs := make(chan core.NewTxsEvent) - sub := handler.txpool.SubscribeNewTxsEvent(txs) + sub := handler.txpool.SubscribeTransactions(txs, false) defer sub.Unsubscribe() // Create a source peer to send messages through and a sink handler to receive them @@ -296,7 +294,6 @@ func testRecvTransactions(t *testing.T, protocol uint) { } // This test checks that pending transactions are sent. -func TestSendTransactions66(t *testing.T) { testSendTransactions(t, eth.ETH66) } func TestSendTransactions67(t *testing.T) { testSendTransactions(t, eth.ETH67) } func TestSendTransactions68(t *testing.T) { testSendTransactions(t, eth.ETH68) } @@ -311,11 +308,10 @@ func testSendTransactions(t *testing.T, protocol uint) { for nonce := range insert { tx := types.NewTransaction(uint64(nonce), common.Address{}, big.NewInt(0), 100000, big.NewInt(0), make([]byte, 10240)) tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) - insert[nonce] = tx } - go handler.txpool.AddRemotes(insert) // Need goroutine to not block on feed - time.Sleep(250 * time.Millisecond) // Wait until tx events get out of the system (can't use events, tx broadcaster races with peer join) + go handler.txpool.Add(insert, false, false) // Need goroutine to not block on feed + time.Sleep(250 * time.Millisecond) // Wait until tx events get out of the system (can't use events, tx broadcaster races with peer join) // Create a source handler to send messages through and a sink peer to receive them p2pSrc, p2pSink := p2p.MsgPipe() @@ -357,7 +353,7 @@ func testSendTransactions(t *testing.T, protocol uint) { seen := make(map[common.Hash]struct{}) for len(seen) < len(insert) { switch protocol { - case 66, 67, 68: + case 67, 68: select { case hashes := <-anns: for _, hash := range hashes { @@ -383,7 +379,6 @@ func testSendTransactions(t *testing.T, protocol uint) { // Tests that transactions get propagated to all attached peers, either via direct // broadcasts or via announcements/retrievals. -func TestTransactionPropagation66(t *testing.T) { testTransactionPropagation(t, eth.ETH66) } func TestTransactionPropagation67(t *testing.T) { testTransactionPropagation(t, eth.ETH67) } func TestTransactionPropagation68(t *testing.T) { testTransactionPropagation(t, eth.ETH68) } @@ -402,7 +397,7 @@ func testTransactionPropagation(t *testing.T, protocol uint) { sinks[i] = newTestHandler() defer sinks[i].close() - sinks[i].handler.acceptTxs.Store(true) // mark synced to accept transactions + sinks[i].handler.synced.Store(true) // mark synced to accept transactions } // Interconnect all the sink handlers with the source handler for i, sink := range sinks { @@ -429,7 +424,7 @@ func testTransactionPropagation(t *testing.T, protocol uint) { for i := 0; i < len(sinks); i++ { txChs[i] = make(chan core.NewTxsEvent, 1024) - sub := sinks[i].txpool.SubscribeNewTxsEvent(txChs[i]) + sub := sinks[i].txpool.SubscribeTransactions(txChs[i], false) defer sub.Unsubscribe() } // Fill the source pool with transactions and wait for them at the sinks @@ -437,10 +432,9 @@ func testTransactionPropagation(t *testing.T, protocol uint) { for nonce := range txs { tx := types.NewTransaction(uint64(nonce), common.Address{}, big.NewInt(0), 100000, big.NewInt(0), nil) tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) - txs[nonce] = tx } - source.txpool.AddRemotes(txs) + source.txpool.Add(txs, false, false) // Iterate through all the sinks and ensure they all got the transactions for i := range sinks { @@ -492,8 +486,8 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) { defer sourcePipe.Close() defer sinkPipe.Close() - sourcePeer := eth.NewPeer(eth.ETH66, p2p.NewPeerPipe(enode.ID{byte(i)}, "", nil, sourcePipe), sourcePipe, nil) - sinkPeer := eth.NewPeer(eth.ETH66, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, nil) + sourcePeer := eth.NewPeer(eth.ETH67, p2p.NewPeerPipe(enode.ID{byte(i)}, "", nil, sourcePipe), sourcePipe, nil) + sinkPeer := eth.NewPeer(eth.ETH67, p2p.NewPeerPipe(enode.ID{0}, "", nil, sinkPipe), sinkPipe, nil) defer sourcePeer.Close() defer sinkPeer.Close() @@ -545,7 +539,6 @@ func testBroadcastBlock(t *testing.T, peers, bcasts int) { // Tests that a propagated malformed block (uncles or transactions don't match // with the hashes in the header) gets discarded and not broadcast forward. -func TestBroadcastMalformedBlock66(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH66) } func TestBroadcastMalformedBlock67(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH67) } func TestBroadcastMalformedBlock68(t *testing.T) { testBroadcastMalformedBlock(t, eth.ETH68) } diff --git a/eth/handler_test.go b/eth/handler_test.go index d9443775618f..f907c81633dc 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -74,13 +75,12 @@ func (p *testTxPool) Has(hash common.Hash) bool { func (p *testTxPool) Get(hash common.Hash) *types.Transaction { p.lock.Lock() defer p.lock.Unlock() - return p.pool[hash] } -// AddRemotes appends a batch of transactions to the pool, and notifies any +// Add appends a batch of transactions to the pool, and notifies any // listeners if the addition channel is non nil -func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error { +func (p *testTxPool) Add(txs []*types.Transaction, local bool, sync bool) []error { p.lock.Lock() defer p.lock.Unlock() @@ -92,11 +92,11 @@ func (p *testTxPool) AddRemotes(txs []*types.Transaction) []error { } // Pending returns all the transactions known to the pool -func (p *testTxPool) Pending(enforceTips bool) map[common.Address]types.Transactions { +func (p *testTxPool) Pending(enforceTips bool) map[common.Address][]*txpool.LazyTransaction { p.lock.RLock() defer p.lock.RUnlock() - batches := make(map[common.Address]types.Transactions) + batches := make(map[common.Address][]*types.Transaction) for _, tx := range p.pool { from, _ := types.Sender(types.HomesteadSigner{}, tx) batches[from] = append(batches[from], tx) @@ -104,12 +104,26 @@ func (p *testTxPool) Pending(enforceTips bool) map[common.Address]types.Transact for _, batch := range batches { sort.Sort(types.TxByNonce(batch)) } - return batches + pending := make(map[common.Address][]*txpool.LazyTransaction) + for addr, batch := range batches { + for _, tx := range batch { + pending[addr] = append(pending[addr], &txpool.LazyTransaction{ + Hash: tx.Hash(), + Tx: tx, + Time: tx.Time(), + GasFeeCap: tx.GasFeeCap(), + GasTipCap: tx.GasTipCap(), + Gas: tx.Gas(), + BlobGas: tx.BlobGas(), + }) + } + } + return pending } -// SubscribeNewTxsEvent should return an event subscription of NewTxsEvent and +// SubscribeTransactions should return an event subscription of NewTxsEvent and // send events to the given channel. -func (p *testTxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { +func (p *testTxPool) SubscribeTransactions(ch chan<- core.NewTxsEvent, reorgs bool) event.Subscription { return p.txFeed.Subscribe(ch) } diff --git a/eth/peerset.go b/eth/peerset.go index b9cc1e03aca3..b27d3964a119 100644 --- a/eth/peerset.go +++ b/eth/peerset.go @@ -18,6 +18,7 @@ package eth import ( "errors" + "fmt" "math/big" "sync" @@ -74,7 +75,7 @@ func (ps *peerSet) registerSnapExtension(peer *snap.Peer) error { // Reject the peer if it advertises `snap` without `eth` as `snap` is only a // satellite protocol meaningful with the chain selection of `eth` if !peer.RunningCap(eth.ProtocolName, eth.ProtocolVersions) { - return errSnapWithoutEth + return fmt.Errorf("%w: have %v", errSnapWithoutEth, peer.Caps()) } // Ensure nobody can double connect ps.lock.Lock() diff --git a/eth/protocols/eth/discovery.go b/eth/protocols/eth/discovery.go index 87857244b589..a7bdd47daf07 100644 --- a/eth/protocols/eth/discovery.go +++ b/eth/protocols/eth/discovery.go @@ -61,6 +61,6 @@ func StartENRUpdater(chain *core.BlockChain, ln *enode.LocalNode) { func currentENREntry(chain *core.BlockChain) *enrEntry { head := chain.CurrentHeader() return &enrEntry{ - ForkID: forkid.NewID(chain.Config(), chain.Genesis().Hash(), head.Number.Uint64(), head.Time), + ForkID: forkid.NewID(chain.Config(), chain.Genesis(), head.Number.Uint64(), head.Time), } } diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index dc4564beadeb..8149db149568 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -44,10 +44,6 @@ const ( // nowadays, the practical limit will always be softResponseLimit. maxBodiesServe = 1024 - // maxNodeDataServe is the maximum number of state trie nodes to serve. This - // number is there to limit the number of disk lookups. - maxNodeDataServe = 1024 - // maxReceiptsServe is the maximum number of block receipts to serve. This // number is mostly there to limit the number of disk lookups. With block // containing 200+ transactions nowadays, the practical limit will always @@ -95,11 +91,15 @@ type TxPool interface { // MakeProtocols constructs the P2P protocol definitions for `eth`. func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2p.Protocol { - protocols := make([]p2p.Protocol, len(ProtocolVersions)) - for i, version := range ProtocolVersions { + protocols := make([]p2p.Protocol, 0, len(ProtocolVersions)) + for _, version := range ProtocolVersions { + // Blob transactions require eth/68 announcements, disable everything else + if version <= ETH67 && backend.Chain().Config().CancunTime != nil { + continue + } version := version // Closure - protocols[i] = p2p.Protocol{ + protocols = append(protocols, p2p.Protocol{ Name: ProtocolName, Version: version, Length: protocolLengths[version], @@ -119,7 +119,7 @@ func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2 }, Attributes: []enr.Entry{currentENREntry(backend.Chain())}, DialCandidates: dnsdisc, - } + }) } return protocols } @@ -127,7 +127,7 @@ func MakeProtocols(backend Backend, network uint64, dnsdisc enode.Iterator) []p2 // NodeInfo represents a short summary of the `eth` sub-protocol metadata // known about the host peer. type NodeInfo struct { - Network uint64 `json:"network"` // Ethereum network ID (1=Mainnet, Rinkeby=4, Goerli=5) + Network uint64 `json:"network"` // Ethereum network ID (1=Mainnet, Goerli=5) Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules @@ -166,36 +166,19 @@ type Decoder interface { Time() time.Time } -var eth66 = map[uint64]msgHandler{ - NewBlockHashesMsg: handleNewBlockhashes, - NewBlockMsg: handleNewBlock, - TransactionsMsg: handleTransactions, - NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes66, - GetBlockHeadersMsg: handleGetBlockHeaders66, - BlockHeadersMsg: handleBlockHeaders66, - GetBlockBodiesMsg: handleGetBlockBodies66, - BlockBodiesMsg: handleBlockBodies66, - GetNodeDataMsg: handleGetNodeData66, - NodeDataMsg: handleNodeData66, - GetReceiptsMsg: handleGetReceipts66, - ReceiptsMsg: handleReceipts66, - GetPooledTransactionsMsg: handleGetPooledTransactions66, - PooledTransactionsMsg: handlePooledTransactions66, -} - var eth67 = map[uint64]msgHandler{ NewBlockHashesMsg: handleNewBlockhashes, NewBlockMsg: handleNewBlock, TransactionsMsg: handleTransactions, - NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes66, - GetBlockHeadersMsg: handleGetBlockHeaders66, - BlockHeadersMsg: handleBlockHeaders66, - GetBlockBodiesMsg: handleGetBlockBodies66, - BlockBodiesMsg: handleBlockBodies66, - GetReceiptsMsg: handleGetReceipts66, - ReceiptsMsg: handleReceipts66, - GetPooledTransactionsMsg: handleGetPooledTransactions66, - PooledTransactionsMsg: handlePooledTransactions66, + NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes67, + GetBlockHeadersMsg: handleGetBlockHeaders, + BlockHeadersMsg: handleBlockHeaders, + GetBlockBodiesMsg: handleGetBlockBodies, + BlockBodiesMsg: handleBlockBodies, + GetReceiptsMsg: handleGetReceipts, + ReceiptsMsg: handleReceipts, + GetPooledTransactionsMsg: handleGetPooledTransactions, + PooledTransactionsMsg: handlePooledTransactions, } var eth68 = map[uint64]msgHandler{ @@ -203,14 +186,14 @@ var eth68 = map[uint64]msgHandler{ NewBlockMsg: handleNewBlock, TransactionsMsg: handleTransactions, NewPooledTransactionHashesMsg: handleNewPooledTransactionHashes68, - GetBlockHeadersMsg: handleGetBlockHeaders66, - BlockHeadersMsg: handleBlockHeaders66, - GetBlockBodiesMsg: handleGetBlockBodies66, - BlockBodiesMsg: handleBlockBodies66, - GetReceiptsMsg: handleGetReceipts66, - ReceiptsMsg: handleReceipts66, - GetPooledTransactionsMsg: handleGetPooledTransactions66, - PooledTransactionsMsg: handlePooledTransactions66, + GetBlockHeadersMsg: handleGetBlockHeaders, + BlockHeadersMsg: handleBlockHeaders, + GetBlockBodiesMsg: handleGetBlockBodies, + BlockBodiesMsg: handleBlockBodies, + GetReceiptsMsg: handleGetReceipts, + ReceiptsMsg: handleReceipts, + GetPooledTransactionsMsg: handleGetPooledTransactions, + PooledTransactionsMsg: handlePooledTransactions, } // handleMessage is invoked whenever an inbound message is received from a remote @@ -226,14 +209,10 @@ func handleMessage(backend Backend, peer *Peer) error { } defer msg.Discard() - var handlers = eth66 - if peer.Version() == ETH67 { - handlers = eth67 - } + var handlers = eth67 if peer.Version() >= ETH68 { handlers = eth68 } - // Track the amount of time it takes to serve the request and run the handler if metrics.Enabled { h := fmt.Sprintf("%s/%s/%d/%#02x", p2p.HandleHistName, ProtocolName, peer.Version(), msg.Code) diff --git a/eth/protocols/eth/handler_test.go b/eth/protocols/eth/handler_test.go index 3642db8c12dd..6cbe37e8c26c 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -28,8 +28,8 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -111,21 +111,24 @@ func newTestBackendWithGenerator(blocks int, shanghai bool, generator func(int, panic(err) } for _, block := range bs { - chain.StateCache().TrieDB().Commit(block.Root(), false) + chain.TrieDB().Commit(block.Root(), false) } - txconfig := txpool.DefaultConfig + txconfig := legacypool.DefaultConfig txconfig.Journal = "" // Don't litter the disk with test journals + pool := legacypool.New(txconfig, chain) + txpool, _ := txpool.New(new(big.Int).SetUint64(txconfig.PriceLimit), chain, []txpool.SubPool{pool}) + return &testBackend{ db: db, chain: chain, - txpool: txpool.NewTxPool(txconfig, params.TestChainConfig, chain), + txpool: txpool, } } // close tears down the transaction pool and chain behind the mock backend. func (b *testBackend) close() { - b.txpool.Stop() + b.txpool.Close() b.chain.Stop() } @@ -147,7 +150,6 @@ func (b *testBackend) Handle(*Peer, Packet) error { } // Tests that block headers can be retrieved from a remote chain based on user queries. -func TestGetBlockHeaders66(t *testing.T) { testGetBlockHeaders(t, ETH66) } func TestGetBlockHeaders67(t *testing.T) { testGetBlockHeaders(t, ETH67) } func TestGetBlockHeaders68(t *testing.T) { testGetBlockHeaders(t, ETH68) } @@ -174,29 +176,29 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { // Create a batch of tests for various scenarios limit := uint64(maxHeadersServe) tests := []struct { - query *GetBlockHeadersPacket // The query to execute for header retrieval - expect []common.Hash // The hashes of the block whose headers are expected + query *GetBlockHeadersRequest // The query to execute for header retrieval + expect []common.Hash // The hashes of the block whose headers are expected }{ // A single random block should be retrievable by hash { - &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(limit / 2).Hash()}, Amount: 1}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(limit / 2).Hash()}, Amount: 1}, []common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()}, }, // A single random block should be retrievable by number { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 1}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: limit / 2}, Amount: 1}, []common.Hash{backend.chain.GetBlockByNumber(limit / 2).Hash()}, }, // Multiple headers should be retrievable in both directions { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 3}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: limit / 2}, Amount: 3}, []common.Hash{ backend.chain.GetBlockByNumber(limit / 2).Hash(), backend.chain.GetBlockByNumber(limit/2 + 1).Hash(), backend.chain.GetBlockByNumber(limit/2 + 2).Hash(), }, }, { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true}, []common.Hash{ backend.chain.GetBlockByNumber(limit / 2).Hash(), backend.chain.GetBlockByNumber(limit/2 - 1).Hash(), @@ -205,14 +207,14 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { }, // Multiple headers with skip lists should be retrievable { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3}, []common.Hash{ backend.chain.GetBlockByNumber(limit / 2).Hash(), backend.chain.GetBlockByNumber(limit/2 + 4).Hash(), backend.chain.GetBlockByNumber(limit/2 + 8).Hash(), }, }, { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true}, []common.Hash{ backend.chain.GetBlockByNumber(limit / 2).Hash(), backend.chain.GetBlockByNumber(limit/2 - 4).Hash(), @@ -221,31 +223,31 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { }, // The chain endpoints should be retrievable { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 0}, Amount: 1}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 0}, Amount: 1}, []common.Hash{backend.chain.GetBlockByNumber(0).Hash()}, }, { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64()}, Amount: 1}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64()}, Amount: 1}, []common.Hash{backend.chain.CurrentBlock().Hash()}, }, { // If the peer requests a bit into the future, we deliver what we have - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64()}, Amount: 10}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64()}, Amount: 10}, []common.Hash{backend.chain.CurrentBlock().Hash()}, }, // Ensure protocol limits are honored { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64() - 1}, Amount: limit + 10, Reverse: true}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64() - 1}, Amount: limit + 10, Reverse: true}, getHashes(backend.chain.CurrentBlock().Number.Uint64(), limit), }, // Check that requesting more than available is handled gracefully { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64() - 4}, Skip: 3, Amount: 3}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64() - 4}, Skip: 3, Amount: 3}, []common.Hash{ backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64() - 4).Hash(), backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64()).Hash(), }, }, { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, []common.Hash{ backend.chain.GetBlockByNumber(4).Hash(), backend.chain.GetBlockByNumber(0).Hash(), @@ -253,13 +255,13 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { }, // Check that requesting more than available is handled gracefully, even if mid skip { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64() - 4}, Skip: 2, Amount: 3}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64() - 4}, Skip: 2, Amount: 3}, []common.Hash{ backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64() - 4).Hash(), backend.chain.GetBlockByNumber(backend.chain.CurrentBlock().Number.Uint64() - 1).Hash(), }, }, { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, []common.Hash{ backend.chain.GetBlockByNumber(4).Hash(), backend.chain.GetBlockByNumber(1).Hash(), @@ -267,7 +269,7 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { }, // Check a corner case where requesting more can iterate past the endpoints { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 2}, Amount: 5, Reverse: true}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 2}, Amount: 5, Reverse: true}, []common.Hash{ backend.chain.GetBlockByNumber(2).Hash(), backend.chain.GetBlockByNumber(1).Hash(), @@ -276,24 +278,24 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { }, // Check a corner case where skipping overflow loops back into the chain start { - &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(3).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64 - 1}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(3).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64 - 1}, []common.Hash{ backend.chain.GetBlockByNumber(3).Hash(), }, }, // Check a corner case where skipping overflow loops back to the same header { - &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(1).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: backend.chain.GetBlockByNumber(1).Hash()}, Amount: 2, Reverse: false, Skip: math.MaxUint64}, []common.Hash{ backend.chain.GetBlockByNumber(1).Hash(), }, }, // Check that non existing headers aren't returned { - &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: unknown}, Amount: 1}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: unknown}, Amount: 1}, []common.Hash{}, }, { - &GetBlockHeadersPacket{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64() + 1}, Amount: 1}, + &GetBlockHeadersRequest{Origin: HashOrNumber{Number: backend.chain.CurrentBlock().Number.Uint64() + 1}, Amount: 1}, []common.Hash{}, }, } @@ -305,13 +307,13 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { headers = append(headers, backend.chain.GetBlockByHash(hash).Header()) } // Send the hash request and verify the response - p2p.Send(peer.app, GetBlockHeadersMsg, &GetBlockHeadersPacket66{ - RequestId: 123, - GetBlockHeadersPacket: tt.query, + p2p.Send(peer.app, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + RequestId: 123, + GetBlockHeadersRequest: tt.query, }) - if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, &BlockHeadersPacket66{ - RequestId: 123, - BlockHeadersPacket: headers, + if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, &BlockHeadersPacket{ + RequestId: 123, + BlockHeadersRequest: headers, }); err != nil { t.Errorf("test %d: headers mismatch: %v", i, err) } @@ -320,11 +322,11 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { if origin := backend.chain.GetBlockByNumber(tt.query.Origin.Number); origin != nil { tt.query.Origin.Hash, tt.query.Origin.Number = origin.Hash(), 0 - p2p.Send(peer.app, GetBlockHeadersMsg, &GetBlockHeadersPacket66{ - RequestId: 456, - GetBlockHeadersPacket: tt.query, + p2p.Send(peer.app, GetBlockHeadersMsg, &GetBlockHeadersPacket{ + RequestId: 456, + GetBlockHeadersRequest: tt.query, }) - expected := &BlockHeadersPacket66{RequestId: 456, BlockHeadersPacket: headers} + expected := &BlockHeadersPacket{RequestId: 456, BlockHeadersRequest: headers} if err := p2p.ExpectMsg(peer.app, BlockHeadersMsg, expected); err != nil { t.Errorf("test %d by hash: headers mismatch: %v", i, err) } @@ -334,7 +336,6 @@ func testGetBlockHeaders(t *testing.T, protocol uint) { } // Tests that block contents can be retrieved from a remote chain based on their hashes. -func TestGetBlockBodies66(t *testing.T) { testGetBlockBodies(t, ETH66) } func TestGetBlockBodies67(t *testing.T) { testGetBlockBodies(t, ETH67) } func TestGetBlockBodies68(t *testing.T) { testGetBlockBodies(t, ETH68) } @@ -416,139 +417,20 @@ func testGetBlockBodies(t *testing.T, protocol uint) { } // Send the hash request and verify the response - p2p.Send(peer.app, GetBlockBodiesMsg, &GetBlockBodiesPacket66{ - RequestId: 123, - GetBlockBodiesPacket: hashes, + p2p.Send(peer.app, GetBlockBodiesMsg, &GetBlockBodiesPacket{ + RequestId: 123, + GetBlockBodiesRequest: hashes, }) - if err := p2p.ExpectMsg(peer.app, BlockBodiesMsg, &BlockBodiesPacket66{ - RequestId: 123, - BlockBodiesPacket: bodies, + if err := p2p.ExpectMsg(peer.app, BlockBodiesMsg, &BlockBodiesPacket{ + RequestId: 123, + BlockBodiesResponse: bodies, }); err != nil { t.Fatalf("test %d: bodies mismatch: %v", i, err) } } } -// Tests that the state trie nodes can be retrieved based on hashes. -func TestGetNodeData66(t *testing.T) { testGetNodeData(t, ETH66, false) } -func TestGetNodeData67(t *testing.T) { testGetNodeData(t, ETH67, true) } -func TestGetNodeData68(t *testing.T) { testGetNodeData(t, ETH68, true) } - -func testGetNodeData(t *testing.T, protocol uint, drop bool) { - t.Parallel() - - // Define three accounts to simulate transactions with - acc1Key, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") - acc2Key, _ := crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") - acc1Addr := crypto.PubkeyToAddress(acc1Key.PublicKey) - acc2Addr := crypto.PubkeyToAddress(acc2Key.PublicKey) - - signer := types.HomesteadSigner{} - // Create a chain generator with some simple transactions (blatantly stolen from @fjl/chain_makers_test) - generator := func(i int, block *core.BlockGen) { - switch i { - case 0: - // In block 1, the test bank sends account #1 some ether. - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(10_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, testKey) - block.AddTx(tx) - case 1: - // In block 2, the test bank sends some more ether to account #1. - // acc1Addr passes it on to account #2. - tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), acc1Addr, big.NewInt(1_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, testKey) - tx2, _ := types.SignTx(types.NewTransaction(block.TxNonce(acc1Addr), acc2Addr, big.NewInt(1_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, acc1Key) - block.AddTx(tx1) - block.AddTx(tx2) - case 2: - // Block 3 is empty but was mined by account #2. - block.SetCoinbase(acc2Addr) - block.SetExtra([]byte("yeehaw")) - case 3: - // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). - b2 := block.PrevBlock(1).Header() - b2.Extra = []byte("foo") - block.AddUncle(b2) - b3 := block.PrevBlock(2).Header() - b3.Extra = []byte("foo") - block.AddUncle(b3) - } - } - // Assemble the test environment - backend := newTestBackendWithGenerator(4, false, generator) - defer backend.close() - - peer, _ := newTestPeer("peer", protocol, backend) - defer peer.close() - - // Collect all state tree hashes. - var hashes []common.Hash - it := backend.db.NewIterator(nil, nil) - for it.Next() { - if key := it.Key(); len(key) == common.HashLength { - hashes = append(hashes, common.BytesToHash(key)) - } - } - it.Release() - - // Request all hashes. - p2p.Send(peer.app, GetNodeDataMsg, &GetNodeDataPacket66{ - RequestId: 123, - GetNodeDataPacket: hashes, - }) - msg, err := peer.app.ReadMsg() - if !drop { - if err != nil { - t.Fatalf("failed to read node data response: %v", err) - } - } else { - if err != nil { - return - } - t.Fatalf("succeeded to read node data response on non-supporting protocol: %v", msg) - } - if msg.Code != NodeDataMsg { - t.Fatalf("response packet code mismatch: have %x, want %x", msg.Code, NodeDataMsg) - } - var res NodeDataPacket66 - if err := msg.Decode(&res); err != nil { - t.Fatalf("failed to decode response node data: %v", err) - } - - // Verify that all hashes correspond to the requested data. - data := res.NodeDataPacket - for i, want := range hashes { - if hash := crypto.Keccak256Hash(data[i]); hash != want { - t.Errorf("data hash mismatch: have %x, want %x", hash, want) - } - } - - // Reconstruct state tree from the received data. - reconstructDB := rawdb.NewMemoryDatabase() - for i := 0; i < len(data); i++ { - rawdb.WriteLegacyTrieNode(reconstructDB, hashes[i], data[i]) - } - - // Sanity check whether all state matches. - accounts := []common.Address{testAddr, acc1Addr, acc2Addr} - for i := uint64(0); i <= backend.chain.CurrentBlock().Number.Uint64(); i++ { - root := backend.chain.GetBlockByNumber(i).Root() - reconstructed, _ := state.New(root, state.NewDatabase(reconstructDB), nil) - for j, acc := range accounts { - state, _ := backend.chain.StateAt(root) - bw := state.GetBalance(acc) - bh := reconstructed.GetBalance(acc) - - if (bw == nil) != (bh == nil) { - t.Errorf("block %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) - } - if bw != nil && bh != nil && bw.Cmp(bh) != 0 { - t.Errorf("block %d, account %d: balance mismatch: have %v, want %v", i, j, bh, bw) - } - } - } -} - // Tests that the transaction receipts can be retrieved based on hashes. -func TestGetBlockReceipts66(t *testing.T) { testGetBlockReceipts(t, ETH66) } func TestGetBlockReceipts67(t *testing.T) { testGetBlockReceipts(t, ETH67) } func TestGetBlockReceipts68(t *testing.T) { testGetBlockReceipts(t, ETH68) } @@ -609,13 +491,13 @@ func testGetBlockReceipts(t *testing.T, protocol uint) { receipts = append(receipts, backend.chain.GetReceiptsByHash(block.Hash())) } // Send the hash request and verify the response - p2p.Send(peer.app, GetReceiptsMsg, &GetReceiptsPacket66{ - RequestId: 123, - GetReceiptsPacket: hashes, + p2p.Send(peer.app, GetReceiptsMsg, &GetReceiptsPacket{ + RequestId: 123, + GetReceiptsRequest: hashes, }) - if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, &ReceiptsPacket66{ - RequestId: 123, - ReceiptsPacket: receipts, + if err := p2p.ExpectMsg(peer.app, ReceiptsMsg, &ReceiptsPacket{ + RequestId: 123, + ReceiptsResponse: receipts, }); err != nil { t.Errorf("receipts mismatch: %v", err) } diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index 74e514b863a3..069e92dadf90 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -28,20 +28,19 @@ import ( "github.com/ethereum/go-ethereum/trie" ) -// handleGetBlockHeaders66 is the eth/66 version of handleGetBlockHeaders -func handleGetBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error { +func handleGetBlockHeaders(backend Backend, msg Decoder, peer *Peer) error { // Decode the complex header query - var query GetBlockHeadersPacket66 + var query GetBlockHeadersPacket if err := msg.Decode(&query); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } - response := ServiceGetBlockHeadersQuery(backend.Chain(), query.GetBlockHeadersPacket, peer) + response := ServiceGetBlockHeadersQuery(backend.Chain(), query.GetBlockHeadersRequest, peer) return peer.ReplyBlockHeadersRLP(query.RequestId, response) } // ServiceGetBlockHeadersQuery assembles the response to a header query. It is // exposed to allow external packages to test protocol behavior. -func ServiceGetBlockHeadersQuery(chain *core.BlockChain, query *GetBlockHeadersPacket, peer *Peer) []rlp.RawValue { +func ServiceGetBlockHeadersQuery(chain *core.BlockChain, query *GetBlockHeadersRequest, peer *Peer) []rlp.RawValue { if query.Skip == 0 { // The fast path: when the request is for a contiguous segment of headers. return serviceContiguousBlockHeaderQuery(chain, query) @@ -50,7 +49,7 @@ func ServiceGetBlockHeadersQuery(chain *core.BlockChain, query *GetBlockHeadersP } } -func serviceNonContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHeadersPacket, peer *Peer) []rlp.RawValue { +func serviceNonContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHeadersRequest, peer *Peer) []rlp.RawValue { hashMode := query.Origin.Hash != (common.Hash{}) first := true maxNonCanonical := uint64(100) @@ -139,7 +138,7 @@ func serviceNonContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBloc return headers } -func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHeadersPacket) []rlp.RawValue { +func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHeadersRequest) []rlp.RawValue { count := query.Amount if count > maxHeadersServe { count = maxHeadersServe @@ -202,19 +201,19 @@ func serviceContiguousBlockHeaderQuery(chain *core.BlockChain, query *GetBlockHe } } -func handleGetBlockBodies66(backend Backend, msg Decoder, peer *Peer) error { +func handleGetBlockBodies(backend Backend, msg Decoder, peer *Peer) error { // Decode the block body retrieval message - var query GetBlockBodiesPacket66 + var query GetBlockBodiesPacket if err := msg.Decode(&query); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } - response := ServiceGetBlockBodiesQuery(backend.Chain(), query.GetBlockBodiesPacket) + response := ServiceGetBlockBodiesQuery(backend.Chain(), query.GetBlockBodiesRequest) return peer.ReplyBlockBodiesRLP(query.RequestId, response) } // ServiceGetBlockBodiesQuery assembles the response to a body query. It is // exposed to allow external packages to test protocol behavior. -func ServiceGetBlockBodiesQuery(chain *core.BlockChain, query GetBlockBodiesPacket) []rlp.RawValue { +func ServiceGetBlockBodiesQuery(chain *core.BlockChain, query GetBlockBodiesRequest) []rlp.RawValue { // Gather blocks until the fetch or network limits is reached var ( bytes int @@ -233,56 +232,19 @@ func ServiceGetBlockBodiesQuery(chain *core.BlockChain, query GetBlockBodiesPack return bodies } -func handleGetNodeData66(backend Backend, msg Decoder, peer *Peer) error { - // Decode the trie node data retrieval message - var query GetNodeDataPacket66 - if err := msg.Decode(&query); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - response := ServiceGetNodeDataQuery(backend.Chain(), query.GetNodeDataPacket) - return peer.ReplyNodeData(query.RequestId, response) -} - -// ServiceGetNodeDataQuery assembles the response to a node data query. It is -// exposed to allow external packages to test protocol behavior. -func ServiceGetNodeDataQuery(chain *core.BlockChain, query GetNodeDataPacket) [][]byte { - // Gather state data until the fetch or network limits is reached - var ( - bytes int - nodes [][]byte - ) - for lookups, hash := range query { - if bytes >= softResponseLimit || len(nodes) >= maxNodeDataServe || - lookups >= 2*maxNodeDataServe { - break - } - // Retrieve the requested state entry - entry, err := chain.TrieNode(hash) - if len(entry) == 0 || err != nil { - // Read the contract code with prefix only to save unnecessary lookups. - entry, err = chain.ContractCodeWithPrefix(hash) - } - if err == nil && len(entry) > 0 { - nodes = append(nodes, entry) - bytes += len(entry) - } - } - return nodes -} - -func handleGetReceipts66(backend Backend, msg Decoder, peer *Peer) error { +func handleGetReceipts(backend Backend, msg Decoder, peer *Peer) error { // Decode the block receipts retrieval message - var query GetReceiptsPacket66 + var query GetReceiptsPacket if err := msg.Decode(&query); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } - response := ServiceGetReceiptsQuery(backend.Chain(), query.GetReceiptsPacket) + response := ServiceGetReceiptsQuery(backend.Chain(), query.GetReceiptsRequest) return peer.ReplyReceiptsRLP(query.RequestId, response) } // ServiceGetReceiptsQuery assembles the response to a receipt query. It is // exposed to allow external packages to test protocol behavior. -func ServiceGetReceiptsQuery(chain *core.BlockChain, query GetReceiptsPacket) []rlp.RawValue { +func ServiceGetReceiptsQuery(chain *core.BlockChain, query GetReceiptsRequest) []rlp.RawValue { // Gather state data until the fetch or network limits is reached var ( bytes int @@ -351,15 +313,15 @@ func handleNewBlock(backend Backend, msg Decoder, peer *Peer) error { return backend.Handle(peer, ann) } -func handleBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error { +func handleBlockHeaders(backend Backend, msg Decoder, peer *Peer) error { // A batch of headers arrived to one of our previous requests - res := new(BlockHeadersPacket66) + res := new(BlockHeadersPacket) if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } metadata := func() interface{} { - hashes := make([]common.Hash, len(res.BlockHeadersPacket)) - for i, header := range res.BlockHeadersPacket { + hashes := make([]common.Hash, len(res.BlockHeadersRequest)) + for i, header := range res.BlockHeadersRequest { hashes[i] = header.Hash() } return hashes @@ -367,24 +329,24 @@ func handleBlockHeaders66(backend Backend, msg Decoder, peer *Peer) error { return peer.dispatchResponse(&Response{ id: res.RequestId, code: BlockHeadersMsg, - Res: &res.BlockHeadersPacket, + Res: &res.BlockHeadersRequest, }, metadata) } -func handleBlockBodies66(backend Backend, msg Decoder, peer *Peer) error { +func handleBlockBodies(backend Backend, msg Decoder, peer *Peer) error { // A batch of block bodies arrived to one of our previous requests - res := new(BlockBodiesPacket66) + res := new(BlockBodiesPacket) if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } metadata := func() interface{} { var ( - txsHashes = make([]common.Hash, len(res.BlockBodiesPacket)) - uncleHashes = make([]common.Hash, len(res.BlockBodiesPacket)) - withdrawalHashes = make([]common.Hash, len(res.BlockBodiesPacket)) + txsHashes = make([]common.Hash, len(res.BlockBodiesResponse)) + uncleHashes = make([]common.Hash, len(res.BlockBodiesResponse)) + withdrawalHashes = make([]common.Hash, len(res.BlockBodiesResponse)) ) hasher := trie.NewStackTrie(nil) - for i, body := range res.BlockBodiesPacket { + for i, body := range res.BlockBodiesResponse { txsHashes[i] = types.DeriveSha(types.Transactions(body.Transactions), hasher) uncleHashes[i] = types.CalcUncleHash(body.Uncles) if body.Withdrawals != nil { @@ -396,33 +358,20 @@ func handleBlockBodies66(backend Backend, msg Decoder, peer *Peer) error { return peer.dispatchResponse(&Response{ id: res.RequestId, code: BlockBodiesMsg, - Res: &res.BlockBodiesPacket, + Res: &res.BlockBodiesResponse, }, metadata) } -func handleNodeData66(backend Backend, msg Decoder, peer *Peer) error { - // A batch of node state data arrived to one of our previous requests - res := new(NodeDataPacket66) - if err := msg.Decode(res); err != nil { - return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) - } - return peer.dispatchResponse(&Response{ - id: res.RequestId, - code: NodeDataMsg, - Res: &res.NodeDataPacket, - }, nil) // No post-processing, we're not using this packet anymore -} - -func handleReceipts66(backend Backend, msg Decoder, peer *Peer) error { +func handleReceipts(backend Backend, msg Decoder, peer *Peer) error { // A batch of receipts arrived to one of our previous requests - res := new(ReceiptsPacket66) + res := new(ReceiptsPacket) if err := msg.Decode(res); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } metadata := func() interface{} { hasher := trie.NewStackTrie(nil) - hashes := make([]common.Hash, len(res.ReceiptsPacket)) - for i, receipt := range res.ReceiptsPacket { + hashes := make([]common.Hash, len(res.ReceiptsResponse)) + for i, receipt := range res.ReceiptsResponse { hashes[i] = types.DeriveSha(types.Receipts(receipt), hasher) } return hashes @@ -430,17 +379,17 @@ func handleReceipts66(backend Backend, msg Decoder, peer *Peer) error { return peer.dispatchResponse(&Response{ id: res.RequestId, code: ReceiptsMsg, - Res: &res.ReceiptsPacket, + Res: &res.ReceiptsResponse, }, metadata) } -func handleNewPooledTransactionHashes66(backend Backend, msg Decoder, peer *Peer) error { +func handleNewPooledTransactionHashes67(backend Backend, msg Decoder, peer *Peer) error { // New transaction announcement arrived, make sure we have // a valid and fresh chain to handle them if !backend.AcceptTxs() { return nil } - ann := new(NewPooledTransactionHashesPacket66) + ann := new(NewPooledTransactionHashesPacket67) if err := msg.Decode(ann); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } @@ -471,17 +420,17 @@ func handleNewPooledTransactionHashes68(backend Backend, msg Decoder, peer *Peer return backend.Handle(peer, ann) } -func handleGetPooledTransactions66(backend Backend, msg Decoder, peer *Peer) error { +func handleGetPooledTransactions(backend Backend, msg Decoder, peer *Peer) error { // Decode the pooled transactions retrieval message - var query GetPooledTransactionsPacket66 + var query GetPooledTransactionsPacket if err := msg.Decode(&query); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } - hashes, txs := answerGetPooledTransactions(backend, query.GetPooledTransactionsPacket, peer) + hashes, txs := answerGetPooledTransactions(backend, query.GetPooledTransactionsRequest) return peer.ReplyPooledTransactionsRLP(query.RequestId, hashes, txs) } -func answerGetPooledTransactions(backend Backend, query GetPooledTransactionsPacket, peer *Peer) ([]common.Hash, []rlp.RawValue) { +func answerGetPooledTransactions(backend Backend, query GetPooledTransactionsRequest) ([]common.Hash, []rlp.RawValue) { // Gather transactions until the fetch or network limits is reached var ( bytes int @@ -529,17 +478,17 @@ func handleTransactions(backend Backend, msg Decoder, peer *Peer) error { return backend.Handle(peer, &txs) } -func handlePooledTransactions66(backend Backend, msg Decoder, peer *Peer) error { +func handlePooledTransactions(backend Backend, msg Decoder, peer *Peer) error { // Transactions arrived, make sure we have a valid and fresh chain to handle them if !backend.AcceptTxs() { return nil } // Transactions can be processed, parse all of them and deliver to the pool - var txs PooledTransactionsPacket66 + var txs PooledTransactionsPacket if err := msg.Decode(&txs); err != nil { return fmt.Errorf("%w: message %v: %v", errDecode, msg, err) } - for i, tx := range txs.PooledTransactionsPacket { + for i, tx := range txs.PooledTransactionsResponse { // Validate and mark the remote transaction if tx == nil { return fmt.Errorf("%w: transaction %d is nil", errDecode, i) @@ -548,5 +497,5 @@ func handlePooledTransactions66(backend Backend, msg Decoder, peer *Peer) error } requestTracker.Fulfil(peer.id, peer.version, PooledTransactionsMsg, txs.RequestId) - return backend.Handle(peer, &txs.PooledTransactionsPacket) + return backend.Handle(peer, &txs.PooledTransactionsResponse) } diff --git a/eth/protocols/eth/handshake.go b/eth/protocols/eth/handshake.go index 9a2769fa0d12..ea16a85b1e1e 100644 --- a/eth/protocols/eth/handshake.go +++ b/eth/protocols/eth/handshake.go @@ -17,12 +17,14 @@ package eth import ( + "errors" "fmt" "math/big" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/forkid" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" ) @@ -59,9 +61,11 @@ func (p *Peer) Handshake(network uint64, td *big.Int, head common.Hash, genesis select { case err := <-errc: if err != nil { + markError(p, err) return err } case <-timeout.C: + markError(p, p2p.DiscReadTimeout) return p2p.DiscReadTimeout } } @@ -105,3 +109,25 @@ func (p *Peer) readStatus(network uint64, status *StatusPacket, genesis common.H } return nil } + +// markError registers the error with the corresponding metric. +func markError(p *Peer, err error) { + if !metrics.Enabled { + return + } + m := meters.get(p.Inbound()) + switch errors.Unwrap(err) { + case errNetworkIDMismatch: + m.networkIDMismatch.Mark(1) + case errProtocolVersionMismatch: + m.protocolVersionMismatch.Mark(1) + case errGenesisMismatch: + m.genesisMismatch.Mark(1) + case errForkIDRejected: + m.forkidRejected.Mark(1) + case p2p.DiscReadTimeout: + m.timeoutError.Mark(1) + default: + m.peerError.Mark(1) + } +} diff --git a/eth/protocols/eth/handshake_test.go b/eth/protocols/eth/handshake_test.go index 5c6727d91cc7..d96cfc8165b5 100644 --- a/eth/protocols/eth/handshake_test.go +++ b/eth/protocols/eth/handshake_test.go @@ -27,7 +27,8 @@ import ( ) // Tests that handshake failures are detected and reported correctly. -func TestHandshake66(t *testing.T) { testHandshake(t, ETH66) } +func TestHandshake67(t *testing.T) { testHandshake(t, ETH67) } +func TestHandshake68(t *testing.T) { testHandshake(t, ETH68) } func testHandshake(t *testing.T, protocol uint) { t.Parallel() @@ -40,7 +41,7 @@ func testHandshake(t *testing.T, protocol uint) { genesis = backend.chain.Genesis() head = backend.chain.CurrentBlock() td = backend.chain.GetTd(head.Hash(), head.Number.Uint64()) - forkID = forkid.NewID(backend.chain.Config(), backend.chain.Genesis().Hash(), backend.chain.CurrentHeader().Number.Uint64(), backend.chain.CurrentHeader().Time) + forkID = forkid.NewID(backend.chain.Config(), backend.chain.Genesis(), backend.chain.CurrentHeader().Number.Uint64(), backend.chain.CurrentHeader().Time) ) tests := []struct { code uint64 diff --git a/eth/protocols/eth/metrics.go b/eth/protocols/eth/metrics.go new file mode 100644 index 000000000000..5e0aee39f84b --- /dev/null +++ b/eth/protocols/eth/metrics.go @@ -0,0 +1,81 @@ +// Copyright 2023 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 eth + +import "github.com/ethereum/go-ethereum/metrics" + +// meters stores ingress and egress handshake meters. +var meters bidirectionalMeters + +// bidirectionalMeters stores ingress and egress handshake meters. +type bidirectionalMeters struct { + ingress *hsMeters + egress *hsMeters +} + +// get returns the corresponding meter depending if ingress or egress is +// desired. +func (h *bidirectionalMeters) get(ingress bool) *hsMeters { + if ingress { + return h.ingress + } + return h.egress +} + +// hsMeters is a collection of meters which track metrics related to the +// eth subprotocol handshake. +type hsMeters struct { + // peerError measures the number of errors related to incorrect peer + // behaviour, such as invalid message code, size, encoding, etc. + peerError metrics.Meter + + // timeoutError measures the number of timeouts. + timeoutError metrics.Meter + + // networkIDMismatch measures the number of network id mismatch errors. + networkIDMismatch metrics.Meter + + // protocolVersionMismatch measures the number of differing protocol + // versions. + protocolVersionMismatch metrics.Meter + + // genesisMismatch measures the number of differing genesises. + genesisMismatch metrics.Meter + + // forkidRejected measures the number of differing forkids. + forkidRejected metrics.Meter +} + +// newHandshakeMeters registers and returns handshake meters for the given +// base. +func newHandshakeMeters(base string) *hsMeters { + return &hsMeters{ + peerError: metrics.NewRegisteredMeter(base+"error/peer", nil), + timeoutError: metrics.NewRegisteredMeter(base+"error/timeout", nil), + networkIDMismatch: metrics.NewRegisteredMeter(base+"error/network", nil), + protocolVersionMismatch: metrics.NewRegisteredMeter(base+"error/version", nil), + genesisMismatch: metrics.NewRegisteredMeter(base+"error/genesis", nil), + forkidRejected: metrics.NewRegisteredMeter(base+"error/forkid", nil), + } +} + +func init() { + meters = bidirectionalMeters{ + ingress: newHandshakeMeters("eth/protocols/eth/ingress/handshake/"), + egress: newHandshakeMeters("eth/protocols/eth/egress/handshake/"), + } +} diff --git a/eth/protocols/eth/peer.go b/eth/protocols/eth/peer.go index 219f486c8e6f..938af0cab0df 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -219,7 +219,7 @@ func (p *Peer) AsyncSendTransactions(hashes []common.Hash) { func (p *Peer) sendPooledTransactionHashes66(hashes []common.Hash) error { // Mark all the transactions as known, but ensure we don't overflow our limits p.knownTxs.Add(hashes...) - return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket66(hashes)) + return p2p.Send(p.rw, NewPooledTransactionHashesMsg, NewPooledTransactionHashesPacket67(hashes)) } // sendPooledTransactionHashes68 sends transaction hashes (tagged with their type @@ -248,15 +248,15 @@ func (p *Peer) AsyncSendPooledTransactionHashes(hashes []common.Hash) { } } -// ReplyPooledTransactionsRLP is the eth/66 version of SendPooledTransactionsRLP. +// ReplyPooledTransactionsRLP is the response to RequestTxs. func (p *Peer) ReplyPooledTransactionsRLP(id uint64, hashes []common.Hash, txs []rlp.RawValue) error { // Mark all the transactions as known, but ensure we don't overflow our limits p.knownTxs.Add(hashes...) - // Not packed into PooledTransactionsPacket to avoid RLP decoding - return p2p.Send(p.rw, PooledTransactionsMsg, &PooledTransactionsRLPPacket66{ - RequestId: id, - PooledTransactionsRLPPacket: txs, + // Not packed into PooledTransactionsResponse to avoid RLP decoding + return p2p.Send(p.rw, PooledTransactionsMsg, &PooledTransactionsRLPPacket{ + RequestId: id, + PooledTransactionsRLPResponse: txs, }) } @@ -309,36 +309,28 @@ func (p *Peer) AsyncSendNewBlock(block *types.Block, td *big.Int) { } } -// ReplyBlockHeadersRLP is the eth/66 response to GetBlockHeaders. +// ReplyBlockHeadersRLP is the response to GetBlockHeaders. func (p *Peer) ReplyBlockHeadersRLP(id uint64, headers []rlp.RawValue) error { - return p2p.Send(p.rw, BlockHeadersMsg, &BlockHeadersRLPPacket66{ - RequestId: id, - BlockHeadersRLPPacket: headers, + return p2p.Send(p.rw, BlockHeadersMsg, &BlockHeadersRLPPacket{ + RequestId: id, + BlockHeadersRLPResponse: headers, }) } -// ReplyBlockBodiesRLP is the eth/66 response to GetBlockBodies. +// ReplyBlockBodiesRLP is the response to GetBlockBodies. func (p *Peer) ReplyBlockBodiesRLP(id uint64, bodies []rlp.RawValue) error { - // Not packed into BlockBodiesPacket to avoid RLP decoding - return p2p.Send(p.rw, BlockBodiesMsg, &BlockBodiesRLPPacket66{ - RequestId: id, - BlockBodiesRLPPacket: bodies, + // Not packed into BlockBodiesResponse to avoid RLP decoding + return p2p.Send(p.rw, BlockBodiesMsg, &BlockBodiesRLPPacket{ + RequestId: id, + BlockBodiesRLPResponse: bodies, }) } -// ReplyNodeData is the eth/66 response to GetNodeData. -func (p *Peer) ReplyNodeData(id uint64, data [][]byte) error { - return p2p.Send(p.rw, NodeDataMsg, &NodeDataPacket66{ - RequestId: id, - NodeDataPacket: data, - }) -} - -// ReplyReceiptsRLP is the eth/66 response to GetReceipts. +// ReplyReceiptsRLP is the response to GetReceipts. func (p *Peer) ReplyReceiptsRLP(id uint64, receipts []rlp.RawValue) error { - return p2p.Send(p.rw, ReceiptsMsg, &ReceiptsRLPPacket66{ - RequestId: id, - ReceiptsRLPPacket: receipts, + return p2p.Send(p.rw, ReceiptsMsg, &ReceiptsRLPPacket{ + RequestId: id, + ReceiptsRLPResponse: receipts, }) } @@ -353,9 +345,9 @@ func (p *Peer) RequestOneHeader(hash common.Hash, sink chan *Response) (*Request sink: sink, code: GetBlockHeadersMsg, want: BlockHeadersMsg, - data: &GetBlockHeadersPacket66{ + data: &GetBlockHeadersPacket{ RequestId: id, - GetBlockHeadersPacket: &GetBlockHeadersPacket{ + GetBlockHeadersRequest: &GetBlockHeadersRequest{ Origin: HashOrNumber{Hash: hash}, Amount: uint64(1), Skip: uint64(0), @@ -380,9 +372,9 @@ func (p *Peer) RequestHeadersByHash(origin common.Hash, amount int, skip int, re sink: sink, code: GetBlockHeadersMsg, want: BlockHeadersMsg, - data: &GetBlockHeadersPacket66{ + data: &GetBlockHeadersPacket{ RequestId: id, - GetBlockHeadersPacket: &GetBlockHeadersPacket{ + GetBlockHeadersRequest: &GetBlockHeadersRequest{ Origin: HashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), @@ -407,9 +399,9 @@ func (p *Peer) RequestHeadersByNumber(origin uint64, amount int, skip int, rever sink: sink, code: GetBlockHeadersMsg, want: BlockHeadersMsg, - data: &GetBlockHeadersPacket66{ + data: &GetBlockHeadersPacket{ RequestId: id, - GetBlockHeadersPacket: &GetBlockHeadersPacket{ + GetBlockHeadersRequest: &GetBlockHeadersRequest{ Origin: HashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), @@ -434,31 +426,9 @@ func (p *Peer) RequestBodies(hashes []common.Hash, sink chan *Response) (*Reques sink: sink, code: GetBlockBodiesMsg, want: BlockBodiesMsg, - data: &GetBlockBodiesPacket66{ - RequestId: id, - GetBlockBodiesPacket: hashes, - }, - } - if err := p.dispatchRequest(req); err != nil { - return nil, err - } - return req, nil -} - -// RequestNodeData fetches a batch of arbitrary data from a node's known state -// data, corresponding to the specified hashes. -func (p *Peer) RequestNodeData(hashes []common.Hash, sink chan *Response) (*Request, error) { - p.Log().Debug("Fetching batch of state data", "count", len(hashes)) - id := rand.Uint64() - - req := &Request{ - id: id, - sink: sink, - code: GetNodeDataMsg, - want: NodeDataMsg, - data: &GetNodeDataPacket66{ - RequestId: id, - GetNodeDataPacket: hashes, + data: &GetBlockBodiesPacket{ + RequestId: id, + GetBlockBodiesRequest: hashes, }, } if err := p.dispatchRequest(req); err != nil { @@ -477,9 +447,9 @@ func (p *Peer) RequestReceipts(hashes []common.Hash, sink chan *Response) (*Requ sink: sink, code: GetReceiptsMsg, want: ReceiptsMsg, - data: &GetReceiptsPacket66{ - RequestId: id, - GetReceiptsPacket: hashes, + data: &GetReceiptsPacket{ + RequestId: id, + GetReceiptsRequest: hashes, }, } if err := p.dispatchRequest(req); err != nil { @@ -494,9 +464,9 @@ func (p *Peer) RequestTxs(hashes []common.Hash) error { id := rand.Uint64() requestTracker.Track(p.id, p.version, GetPooledTransactionsMsg, PooledTransactionsMsg, id) - return p2p.Send(p.rw, GetPooledTransactionsMsg, &GetPooledTransactionsPacket66{ - RequestId: id, - GetPooledTransactionsPacket: hashes, + return p2p.Send(p.rw, GetPooledTransactionsMsg, &GetPooledTransactionsPacket{ + RequestId: id, + GetPooledTransactionsRequest: hashes, }) } diff --git a/eth/protocols/eth/protocol.go b/eth/protocols/eth/protocol.go index 0d4b368988f7..0f44f83de159 100644 --- a/eth/protocols/eth/protocol.go +++ b/eth/protocols/eth/protocol.go @@ -30,7 +30,6 @@ import ( // Constants to match up protocol versions and messages const ( - ETH66 = 66 ETH67 = 67 ETH68 = 68 ) @@ -41,11 +40,11 @@ const ProtocolName = "eth" // ProtocolVersions are the supported versions of the `eth` protocol (first // is primary). -var ProtocolVersions = []uint{ETH68, ETH67, ETH66} +var ProtocolVersions = []uint{ETH68, ETH67} // protocolLengths are the number of implemented message corresponding to // different protocol versions. -var protocolLengths = map[uint]uint64{ETH68: 17, ETH67: 17, ETH66: 17} +var protocolLengths = map[uint]uint64{ETH68: 17, ETH67: 17} // maxMessageSize is the maximum cap on the size of a protocol message. const maxMessageSize = 10 * 1024 * 1024 @@ -59,13 +58,11 @@ const ( GetBlockBodiesMsg = 0x05 BlockBodiesMsg = 0x06 NewBlockMsg = 0x07 - GetNodeDataMsg = 0x0d - NodeDataMsg = 0x0e - GetReceiptsMsg = 0x0f - ReceiptsMsg = 0x10 NewPooledTransactionHashesMsg = 0x08 GetPooledTransactionsMsg = 0x09 PooledTransactionsMsg = 0x0a + GetReceiptsMsg = 0x0f + ReceiptsMsg = 0x10 ) var ( @@ -85,7 +82,7 @@ type Packet interface { Kind() byte // Kind returns the message type. } -// StatusPacket is the network packet for the status message for eth/64 and later. +// StatusPacket is the network packet for the status message. type StatusPacket struct { ProtocolVersion uint32 NetworkID uint64 @@ -118,18 +115,18 @@ func (p *NewBlockHashesPacket) Unpack() ([]common.Hash, []uint64) { // TransactionsPacket is the network packet for broadcasting new transactions. type TransactionsPacket []*types.Transaction -// GetBlockHeadersPacket represents a block header query. -type GetBlockHeadersPacket struct { +// GetBlockHeadersRequest represents a block header query. +type GetBlockHeadersRequest struct { Origin HashOrNumber // Block from which to retrieve headers Amount uint64 // Maximum number of headers to retrieve Skip uint64 // Blocks to skip between consecutive headers Reverse bool // Query direction (false = rising towards latest, true = falling towards genesis) } -// GetBlockHeadersPacket66 represents a block header query over eth/66 -type GetBlockHeadersPacket66 struct { +// GetBlockHeadersPacket represents a block header query with request ID wrapping. +type GetBlockHeadersPacket struct { RequestId uint64 - *GetBlockHeadersPacket + *GetBlockHeadersRequest } // HashOrNumber is a combined field for specifying an origin block. @@ -168,23 +165,23 @@ func (hn *HashOrNumber) DecodeRLP(s *rlp.Stream) error { } } -// BlockHeadersPacket represents a block header response. -type BlockHeadersPacket []*types.Header +// BlockHeadersRequest represents a block header response. +type BlockHeadersRequest []*types.Header -// BlockHeadersPacket66 represents a block header response over eth/66. -type BlockHeadersPacket66 struct { +// BlockHeadersPacket represents a block header response over with request ID wrapping. +type BlockHeadersPacket struct { RequestId uint64 - BlockHeadersPacket + BlockHeadersRequest } -// BlockHeadersRLPPacket represents a block header response, to use when we already +// BlockHeadersRLPResponse represents a block header response, to use when we already // have the headers rlp encoded. -type BlockHeadersRLPPacket []rlp.RawValue +type BlockHeadersRLPResponse []rlp.RawValue -// BlockHeadersRLPPacket66 represents a block header response over eth/66. -type BlockHeadersRLPPacket66 struct { +// BlockHeadersRLPPacket represents a block header response with request ID wrapping. +type BlockHeadersRLPPacket struct { RequestId uint64 - BlockHeadersRLPPacket + BlockHeadersRLPResponse } // NewBlockPacket is the network packet for the block propagation message. @@ -206,33 +203,34 @@ func (request *NewBlockPacket) sanityCheck() error { return nil } -// GetBlockBodiesPacket represents a block body query. -type GetBlockBodiesPacket []common.Hash +// GetBlockBodiesRequest represents a block body query. +type GetBlockBodiesRequest []common.Hash -// GetBlockBodiesPacket66 represents a block body query over eth/66. -type GetBlockBodiesPacket66 struct { +// GetBlockBodiesPacket represents a block body query with request ID wrapping. +type GetBlockBodiesPacket struct { RequestId uint64 - GetBlockBodiesPacket + GetBlockBodiesRequest } -// BlockBodiesPacket is the network packet for block content distribution. -type BlockBodiesPacket []*BlockBody +// BlockBodiesResponse is the network packet for block content distribution. +type BlockBodiesResponse []*BlockBody -// BlockBodiesPacket66 is the network packet for block content distribution over eth/66. -type BlockBodiesPacket66 struct { +// BlockBodiesPacket is the network packet for block content distribution with +// request ID wrapping. +type BlockBodiesPacket struct { RequestId uint64 - BlockBodiesPacket + BlockBodiesResponse } -// BlockBodiesRLPPacket is used for replying to block body requests, in cases +// BlockBodiesRLPResponse is used for replying to block body requests, in cases // where we already have them RLP-encoded, and thus can avoid the decode-encode // roundtrip. -type BlockBodiesRLPPacket []rlp.RawValue +type BlockBodiesRLPResponse []rlp.RawValue -// BlockBodiesRLPPacket66 is the BlockBodiesRLPPacket over eth/66 -type BlockBodiesRLPPacket66 struct { +// BlockBodiesRLPPacket is the BlockBodiesRLPResponse with request ID wrapping. +type BlockBodiesRLPPacket struct { RequestId uint64 - BlockBodiesRLPPacket + BlockBodiesRLPResponse } // BlockBody represents the data content of a single block. @@ -244,7 +242,7 @@ type BlockBody struct { // Unpack retrieves the transactions and uncles from the range packet and returns // them in a split flat format that's more consistent with the internal data structures. -func (p *BlockBodiesPacket) Unpack() ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal) { +func (p *BlockBodiesResponse) Unpack() ([][]*types.Transaction, [][]*types.Header, [][]*types.Withdrawal) { // TODO(matt): add support for withdrawals to fetchers var ( txset = make([][]*types.Transaction, len(*p)) @@ -257,53 +255,36 @@ func (p *BlockBodiesPacket) Unpack() ([][]*types.Transaction, [][]*types.Header, return txset, uncleset, withdrawalset } -// GetNodeDataPacket represents a trie node data query. -type GetNodeDataPacket []common.Hash +// GetReceiptsRequest represents a block receipts query. +type GetReceiptsRequest []common.Hash -// GetNodeDataPacket66 represents a trie node data query over eth/66. -type GetNodeDataPacket66 struct { +// GetReceiptsPacket represents a block receipts query with request ID wrapping. +type GetReceiptsPacket struct { RequestId uint64 - GetNodeDataPacket + GetReceiptsRequest } -// NodeDataPacket is the network packet for trie node data distribution. -type NodeDataPacket [][]byte +// ReceiptsResponse is the network packet for block receipts distribution. +type ReceiptsResponse [][]*types.Receipt -// NodeDataPacket66 is the network packet for trie node data distribution over eth/66. -type NodeDataPacket66 struct { +// ReceiptsPacket is the network packet for block receipts distribution with +// request ID wrapping. +type ReceiptsPacket struct { RequestId uint64 - NodeDataPacket + ReceiptsResponse } -// GetReceiptsPacket represents a block receipts query. -type GetReceiptsPacket []common.Hash +// ReceiptsRLPResponse is used for receipts, when we already have it encoded +type ReceiptsRLPResponse []rlp.RawValue -// GetReceiptsPacket66 represents a block receipts query over eth/66. -type GetReceiptsPacket66 struct { +// ReceiptsRLPPacket is ReceiptsRLPResponse with request ID wrapping. +type ReceiptsRLPPacket struct { RequestId uint64 - GetReceiptsPacket + ReceiptsRLPResponse } -// ReceiptsPacket is the network packet for block receipts distribution. -type ReceiptsPacket [][]*types.Receipt - -// ReceiptsPacket66 is the network packet for block receipts distribution over eth/66. -type ReceiptsPacket66 struct { - RequestId uint64 - ReceiptsPacket -} - -// ReceiptsRLPPacket is used for receipts, when we already have it encoded -type ReceiptsRLPPacket []rlp.RawValue - -// ReceiptsRLPPacket66 is the eth-66 version of ReceiptsRLPPacket -type ReceiptsRLPPacket66 struct { - RequestId uint64 - ReceiptsRLPPacket -} - -// NewPooledTransactionHashesPacket66 represents a transaction announcement packet on eth/66 and eth/67. -type NewPooledTransactionHashesPacket66 []common.Hash +// NewPooledTransactionHashesPacket67 represents a transaction announcement packet on eth/67. +type NewPooledTransactionHashesPacket67 []common.Hash // NewPooledTransactionHashesPacket68 represents a transaction announcement packet on eth/68 and newer. type NewPooledTransactionHashesPacket68 struct { @@ -312,31 +293,33 @@ type NewPooledTransactionHashesPacket68 struct { Hashes []common.Hash } -// GetPooledTransactionsPacket represents a transaction query. -type GetPooledTransactionsPacket []common.Hash +// GetPooledTransactionsRequest represents a transaction query. +type GetPooledTransactionsRequest []common.Hash -type GetPooledTransactionsPacket66 struct { +// GetPooledTransactionsPacket represents a transaction query with request ID wrapping. +type GetPooledTransactionsPacket struct { RequestId uint64 - GetPooledTransactionsPacket + GetPooledTransactionsRequest } -// PooledTransactionsPacket is the network packet for transaction distribution. -type PooledTransactionsPacket []*types.Transaction +// PooledTransactionsResponse is the network packet for transaction distribution. +type PooledTransactionsResponse []*types.Transaction -// PooledTransactionsPacket66 is the network packet for transaction distribution over eth/66. -type PooledTransactionsPacket66 struct { +// PooledTransactionsPacket is the network packet for transaction distribution +// with request ID wrapping. +type PooledTransactionsPacket struct { RequestId uint64 - PooledTransactionsPacket + PooledTransactionsResponse } -// PooledTransactionsRLPPacket is the network packet for transaction distribution, used +// PooledTransactionsRLPResponse is the network packet for transaction distribution, used // in the cases we already have them in rlp-encoded form -type PooledTransactionsRLPPacket []rlp.RawValue +type PooledTransactionsRLPResponse []rlp.RawValue -// PooledTransactionsRLPPacket66 is the eth/66 form of PooledTransactionsRLPPacket -type PooledTransactionsRLPPacket66 struct { +// PooledTransactionsRLPPacket is PooledTransactionsRLPResponse with request ID wrapping. +type PooledTransactionsRLPPacket struct { RequestId uint64 - PooledTransactionsRLPPacket + PooledTransactionsRLPResponse } func (*StatusPacket) Name() string { return "Status" } @@ -348,41 +331,34 @@ func (*NewBlockHashesPacket) Kind() byte { return NewBlockHashesMsg } func (*TransactionsPacket) Name() string { return "Transactions" } func (*TransactionsPacket) Kind() byte { return TransactionsMsg } -func (*GetBlockHeadersPacket) Name() string { return "GetBlockHeaders" } -func (*GetBlockHeadersPacket) Kind() byte { return GetBlockHeadersMsg } +func (*GetBlockHeadersRequest) Name() string { return "GetBlockHeaders" } +func (*GetBlockHeadersRequest) Kind() byte { return GetBlockHeadersMsg } -func (*BlockHeadersPacket) Name() string { return "BlockHeaders" } -func (*BlockHeadersPacket) Kind() byte { return BlockHeadersMsg } +func (*BlockHeadersRequest) Name() string { return "BlockHeaders" } +func (*BlockHeadersRequest) Kind() byte { return BlockHeadersMsg } -func (*GetBlockBodiesPacket) Name() string { return "GetBlockBodies" } -func (*GetBlockBodiesPacket) Kind() byte { return GetBlockBodiesMsg } +func (*GetBlockBodiesRequest) Name() string { return "GetBlockBodies" } +func (*GetBlockBodiesRequest) Kind() byte { return GetBlockBodiesMsg } -func (*BlockBodiesPacket) Name() string { return "BlockBodies" } -func (*BlockBodiesPacket) Kind() byte { return BlockBodiesMsg } +func (*BlockBodiesResponse) Name() string { return "BlockBodies" } +func (*BlockBodiesResponse) Kind() byte { return BlockBodiesMsg } func (*NewBlockPacket) Name() string { return "NewBlock" } func (*NewBlockPacket) Kind() byte { return NewBlockMsg } -func (*GetNodeDataPacket) Name() string { return "GetNodeData" } -func (*GetNodeDataPacket) Kind() byte { return GetNodeDataMsg } - -func (*NodeDataPacket) Name() string { return "NodeData" } -func (*NodeDataPacket) Kind() byte { return NodeDataMsg } - -func (*GetReceiptsPacket) Name() string { return "GetReceipts" } -func (*GetReceiptsPacket) Kind() byte { return GetReceiptsMsg } - -func (*ReceiptsPacket) Name() string { return "Receipts" } -func (*ReceiptsPacket) Kind() byte { return ReceiptsMsg } - -func (*NewPooledTransactionHashesPacket66) Name() string { return "NewPooledTransactionHashes" } -func (*NewPooledTransactionHashesPacket66) Kind() byte { return NewPooledTransactionHashesMsg } - +func (*NewPooledTransactionHashesPacket67) Name() string { return "NewPooledTransactionHashes" } +func (*NewPooledTransactionHashesPacket67) Kind() byte { return NewPooledTransactionHashesMsg } func (*NewPooledTransactionHashesPacket68) Name() string { return "NewPooledTransactionHashes" } func (*NewPooledTransactionHashesPacket68) Kind() byte { return NewPooledTransactionHashesMsg } -func (*GetPooledTransactionsPacket) Name() string { return "GetPooledTransactions" } -func (*GetPooledTransactionsPacket) Kind() byte { return GetPooledTransactionsMsg } +func (*GetPooledTransactionsRequest) Name() string { return "GetPooledTransactions" } +func (*GetPooledTransactionsRequest) Kind() byte { return GetPooledTransactionsMsg } + +func (*PooledTransactionsResponse) Name() string { return "PooledTransactions" } +func (*PooledTransactionsResponse) Kind() byte { return PooledTransactionsMsg } + +func (*GetReceiptsRequest) Name() string { return "GetReceipts" } +func (*GetReceiptsRequest) Kind() byte { return GetReceiptsMsg } -func (*PooledTransactionsPacket) Name() string { return "PooledTransactions" } -func (*PooledTransactionsPacket) Kind() byte { return PooledTransactionsMsg } +func (*ReceiptsResponse) Name() string { return "Receipts" } +func (*ReceiptsResponse) Kind() byte { return ReceiptsMsg } diff --git a/eth/protocols/eth/protocol_test.go b/eth/protocols/eth/protocol_test.go index a86fbb0a6906..bc2545dea286 100644 --- a/eth/protocols/eth/protocol_test.go +++ b/eth/protocols/eth/protocol_test.go @@ -35,19 +35,19 @@ func TestGetBlockHeadersDataEncodeDecode(t *testing.T) { } // Assemble some table driven tests tests := []struct { - packet *GetBlockHeadersPacket + packet *GetBlockHeadersRequest fail bool }{ // Providing the origin as either a hash or a number should both work - {fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 314}}}, - {fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash}}}, + {fail: false, packet: &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 314}}}, + {fail: false, packet: &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: hash}}}, // Providing arbitrary query field should also work - {fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Number: 314}, Amount: 314, Skip: 1, Reverse: true}}, - {fail: false, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash}, Amount: 314, Skip: 1, Reverse: true}}, + {fail: false, packet: &GetBlockHeadersRequest{Origin: HashOrNumber{Number: 314}, Amount: 314, Skip: 1, Reverse: true}}, + {fail: false, packet: &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: hash}, Amount: 314, Skip: 1, Reverse: true}}, // Providing both the origin hash and origin number must fail - {fail: true, packet: &GetBlockHeadersPacket{Origin: HashOrNumber{Hash: hash, Number: 314}}}, + {fail: true, packet: &GetBlockHeadersRequest{Origin: HashOrNumber{Hash: hash, Number: 314}}}, } // Iterate over each of the tests and try to encode and then decode for i, tt := range tests { @@ -58,7 +58,7 @@ func TestGetBlockHeadersDataEncodeDecode(t *testing.T) { t.Fatalf("test %d: encode should have failed", i) } if !tt.fail { - packet := new(GetBlockHeadersPacket) + packet := new(GetBlockHeadersRequest) if err := rlp.DecodeBytes(bytes, packet); err != nil { t.Fatalf("test %d: failed to decode packet: %v", i, err) } @@ -70,46 +70,40 @@ func TestGetBlockHeadersDataEncodeDecode(t *testing.T) { } } -// TestEth66EmptyMessages tests encoding of empty eth66 messages -func TestEth66EmptyMessages(t *testing.T) { +// TestEmptyMessages tests encoding of empty messages. +func TestEmptyMessages(t *testing.T) { // All empty messages encodes to the same format want := common.FromHex("c4820457c0") for i, msg := range []interface{}{ // Headers - GetBlockHeadersPacket66{1111, nil}, - BlockHeadersPacket66{1111, nil}, + GetBlockHeadersPacket{1111, nil}, + BlockHeadersPacket{1111, nil}, // Bodies - GetBlockBodiesPacket66{1111, nil}, - BlockBodiesPacket66{1111, nil}, - BlockBodiesRLPPacket66{1111, nil}, - // Node data - GetNodeDataPacket66{1111, nil}, - NodeDataPacket66{1111, nil}, + GetBlockBodiesPacket{1111, nil}, + BlockBodiesPacket{1111, nil}, + BlockBodiesRLPPacket{1111, nil}, // Receipts - GetReceiptsPacket66{1111, nil}, - ReceiptsPacket66{1111, nil}, + GetReceiptsPacket{1111, nil}, + ReceiptsPacket{1111, nil}, // Transactions - GetPooledTransactionsPacket66{1111, nil}, - PooledTransactionsPacket66{1111, nil}, - PooledTransactionsRLPPacket66{1111, nil}, + GetPooledTransactionsPacket{1111, nil}, + PooledTransactionsPacket{1111, nil}, + PooledTransactionsRLPPacket{1111, nil}, // Headers - BlockHeadersPacket66{1111, BlockHeadersPacket([]*types.Header{})}, + BlockHeadersPacket{1111, BlockHeadersRequest([]*types.Header{})}, // Bodies - GetBlockBodiesPacket66{1111, GetBlockBodiesPacket([]common.Hash{})}, - BlockBodiesPacket66{1111, BlockBodiesPacket([]*BlockBody{})}, - BlockBodiesRLPPacket66{1111, BlockBodiesRLPPacket([]rlp.RawValue{})}, - // Node data - GetNodeDataPacket66{1111, GetNodeDataPacket([]common.Hash{})}, - NodeDataPacket66{1111, NodeDataPacket([][]byte{})}, + GetBlockBodiesPacket{1111, GetBlockBodiesRequest([]common.Hash{})}, + BlockBodiesPacket{1111, BlockBodiesResponse([]*BlockBody{})}, + BlockBodiesRLPPacket{1111, BlockBodiesRLPResponse([]rlp.RawValue{})}, // Receipts - GetReceiptsPacket66{1111, GetReceiptsPacket([]common.Hash{})}, - ReceiptsPacket66{1111, ReceiptsPacket([][]*types.Receipt{})}, + GetReceiptsPacket{1111, GetReceiptsRequest([]common.Hash{})}, + ReceiptsPacket{1111, ReceiptsResponse([][]*types.Receipt{})}, // Transactions - GetPooledTransactionsPacket66{1111, GetPooledTransactionsPacket([]common.Hash{})}, - PooledTransactionsPacket66{1111, PooledTransactionsPacket([]*types.Transaction{})}, - PooledTransactionsRLPPacket66{1111, PooledTransactionsRLPPacket([]rlp.RawValue{})}, + GetPooledTransactionsPacket{1111, GetPooledTransactionsRequest([]common.Hash{})}, + PooledTransactionsPacket{1111, PooledTransactionsResponse([]*types.Transaction{})}, + PooledTransactionsRLPPacket{1111, PooledTransactionsRLPResponse([]rlp.RawValue{})}, } { if have, _ := rlp.EncodeToBytes(msg); !bytes.Equal(have, want) { t.Errorf("test %d, type %T, have\n\t%x\nwant\n\t%x", i, msg, have, want) @@ -117,8 +111,8 @@ func TestEth66EmptyMessages(t *testing.T) { } } -// TestEth66Messages tests the encoding of all redefined eth66 messages -func TestEth66Messages(t *testing.T) { +// TestMessages tests the encoding of all messages. +func TestMessages(t *testing.T) { // Some basic structs used during testing var ( header *types.Header @@ -169,10 +163,6 @@ func TestEth66Messages(t *testing.T) { common.HexToHash("deadc0de"), common.HexToHash("feedbeef"), } - byteSlices := [][]byte{ - common.FromHex("deadc0de"), - common.FromHex("feedbeef"), - } // init the receipts { receipts = []*types.Receipt{ @@ -203,59 +193,51 @@ func TestEth66Messages(t *testing.T) { want []byte }{ { - GetBlockHeadersPacket66{1111, &GetBlockHeadersPacket{HashOrNumber{hashes[0], 0}, 5, 5, false}}, + GetBlockHeadersPacket{1111, &GetBlockHeadersRequest{HashOrNumber{hashes[0], 0}, 5, 5, false}}, common.FromHex("e8820457e4a000000000000000000000000000000000000000000000000000000000deadc0de050580"), }, { - GetBlockHeadersPacket66{1111, &GetBlockHeadersPacket{HashOrNumber{common.Hash{}, 9999}, 5, 5, false}}, + GetBlockHeadersPacket{1111, &GetBlockHeadersRequest{HashOrNumber{common.Hash{}, 9999}, 5, 5, false}}, common.FromHex("ca820457c682270f050580"), }, { - BlockHeadersPacket66{1111, BlockHeadersPacket{header}}, + BlockHeadersPacket{1111, BlockHeadersRequest{header}}, common.FromHex("f90202820457f901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"), }, { - GetBlockBodiesPacket66{1111, GetBlockBodiesPacket(hashes)}, + GetBlockBodiesPacket{1111, GetBlockBodiesRequest(hashes)}, common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), }, { - BlockBodiesPacket66{1111, BlockBodiesPacket([]*BlockBody{blockBody})}, + BlockBodiesPacket{1111, BlockBodiesResponse([]*BlockBody{blockBody})}, common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"), }, { // Identical to non-rlp-shortcut version - BlockBodiesRLPPacket66{1111, BlockBodiesRLPPacket([]rlp.RawValue{blockBodyRlp})}, + BlockBodiesRLPPacket{1111, BlockBodiesRLPResponse([]rlp.RawValue{blockBodyRlp})}, common.FromHex("f902dc820457f902d6f902d3f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afbf901fcf901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"), }, { - GetNodeDataPacket66{1111, GetNodeDataPacket(hashes)}, - common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), - }, - { - NodeDataPacket66{1111, NodeDataPacket(byteSlices)}, - common.FromHex("ce820457ca84deadc0de84feedbeef"), - }, - { - GetReceiptsPacket66{1111, GetReceiptsPacket(hashes)}, + GetReceiptsPacket{1111, GetReceiptsRequest(hashes)}, common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), }, { - ReceiptsPacket66{1111, ReceiptsPacket([][]*types.Receipt{receipts})}, + ReceiptsPacket{1111, ReceiptsResponse([][]*types.Receipt{receipts})}, common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"), }, { - ReceiptsRLPPacket66{1111, ReceiptsRLPPacket([]rlp.RawValue{receiptsRlp})}, + ReceiptsRLPPacket{1111, ReceiptsRLPResponse([]rlp.RawValue{receiptsRlp})}, common.FromHex("f90172820457f9016cf90169f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"), }, { - GetPooledTransactionsPacket66{1111, GetPooledTransactionsPacket(hashes)}, + GetPooledTransactionsPacket{1111, GetPooledTransactionsRequest(hashes)}, common.FromHex("f847820457f842a000000000000000000000000000000000000000000000000000000000deadc0dea000000000000000000000000000000000000000000000000000000000feedbeef"), }, { - PooledTransactionsPacket66{1111, PooledTransactionsPacket(txs)}, + PooledTransactionsPacket{1111, PooledTransactionsResponse(txs)}, common.FromHex("f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"), }, { - PooledTransactionsRLPPacket66{1111, PooledTransactionsRLPPacket(txRlps)}, + PooledTransactionsRLPPacket{1111, PooledTransactionsRLPResponse(txRlps)}, common.FromHex("f8d7820457f8d2f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"), }, } { diff --git a/eth/protocols/snap/handler.go b/eth/protocols/snap/handler.go index ec0c8afd12ff..8c416f4f4068 100644 --- a/eth/protocols/snap/handler.go +++ b/eth/protocols/snap/handler.go @@ -24,13 +24,13 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" ) const ( @@ -282,7 +282,7 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac req.Bytes = softResponseLimit } // Retrieve the requested state and bail out if non existent - tr, err := trie.New(trie.StateTrieID(req.Root), chain.StateCache().TrieDB()) + tr, err := trie.New(trie.StateTrieID(req.Root), chain.TrieDB()) if err != nil { return nil, nil } @@ -319,19 +319,19 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac it.Release() // Generate the Merkle proofs for the first and last account - proof := light.NewNodeSet() - if err := tr.Prove(req.Origin[:], 0, proof); err != nil { + proof := trienode.NewProofSet() + if err := tr.Prove(req.Origin[:], proof); err != nil { log.Warn("Failed to prove account range", "origin", req.Origin, "err", err) return nil, nil } if last != (common.Hash{}) { - if err := tr.Prove(last[:], 0, proof); err != nil { + if err := tr.Prove(last[:], proof); err != nil { log.Warn("Failed to prove account range", "last", last, "err", err) return nil, nil } } var proofs [][]byte - for _, blob := range proof.NodeList() { + for _, blob := range proof.List() { proofs = append(proofs, blob) } return accounts, proofs @@ -412,7 +412,7 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP if origin != (common.Hash{}) || (abort && len(storage) > 0) { // Request started at a non-zero hash or was capped prematurely, add // the endpoint Merkle proofs - accTrie, err := trie.NewStateTrie(trie.StateTrieID(req.Root), chain.StateCache().TrieDB()) + accTrie, err := trie.NewStateTrie(trie.StateTrieID(req.Root), chain.TrieDB()) if err != nil { return nil, nil } @@ -421,22 +421,22 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP return nil, nil } id := trie.StorageTrieID(req.Root, account, acc.Root) - stTrie, err := trie.NewStateTrie(id, chain.StateCache().TrieDB()) + stTrie, err := trie.NewStateTrie(id, chain.TrieDB()) if err != nil { return nil, nil } - proof := light.NewNodeSet() - if err := stTrie.Prove(origin[:], 0, proof); err != nil { + proof := trienode.NewProofSet() + if err := stTrie.Prove(origin[:], proof); err != nil { log.Warn("Failed to prove storage range", "origin", req.Origin, "err", err) return nil, nil } if last != (common.Hash{}) { - if err := stTrie.Prove(last[:], 0, proof); err != nil { + if err := stTrie.Prove(last[:], proof); err != nil { log.Warn("Failed to prove storage range", "last", last, "err", err) return nil, nil } } - for _, blob := range proof.NodeList() { + for _, blob := range proof.List() { proofs = append(proofs, blob) } // Proof terminates the reply as proofs are only added if a node @@ -485,7 +485,7 @@ func ServiceGetTrieNodesQuery(chain *core.BlockChain, req *GetTrieNodesPacket, s req.Bytes = softResponseLimit } // Make sure we have the state associated with the request - triedb := chain.StateCache().TrieDB() + triedb := chain.TrieDB() accTrie, err := trie.NewStateTrie(trie.StateTrieID(req.Root), triedb) if err != nil { diff --git a/rpc/constants_unix.go b/eth/protocols/snap/metrics.go similarity index 62% rename from rpc/constants_unix.go rename to eth/protocols/snap/metrics.go index 1f04d15d7fd9..a9f35ca4471b 100644 --- a/rpc/constants_unix.go +++ b/eth/protocols/snap/metrics.go @@ -1,4 +1,4 @@ -// Copyright 2019 The go-ethereum Authors +// Copyright 2023 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 @@ -14,21 +14,16 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -//go:build darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris +package snap -package rpc - -/* -#include - -int max_socket_path_size() { -struct sockaddr_un s; -return sizeof(s.sun_path); -} -*/ -import "C" +import ( + metrics "github.com/ethereum/go-ethereum/metrics" +) var ( - max_path_size = C.max_socket_path_size() + ingressRegistrationErrorName = "eth/protocols/snap/ingress/registration/error" + egressRegistrationErrorName = "eth/protocols/snap/egress/registration/error" + + IngressRegistrationErrorMeter = metrics.NewRegisteredMeter(ingressRegistrationErrorName, nil) + EgressRegistrationErrorMeter = metrics.NewRegisteredMeter(egressRegistrationErrorName, nil) ) diff --git a/eth/protocols/snap/protocol.go b/eth/protocols/snap/protocol.go index 60a254f396d5..0db206b0810e 100644 --- a/eth/protocols/snap/protocol.go +++ b/eth/protocols/snap/protocol.go @@ -21,7 +21,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" ) @@ -104,7 +104,7 @@ func (p *AccountRangePacket) Unpack() ([]common.Hash, [][]byte, error) { accounts = make([][]byte, len(p.Accounts)) ) for i, acc := range p.Accounts { - val, err := snapshot.FullAccountRLP(acc.Body) + val, err := types.FullAccountRLP(acc.Body) if err != nil { return nil, nil, fmt.Errorf("invalid account %x: %v", acc.Body, err) } diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index e99eb8843663..df1473e999af 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -33,16 +33,15 @@ import ( "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/msgrate" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" "golang.org/x/crypto/sha3" ) @@ -731,26 +730,31 @@ func (s *Syncer) loadSyncStatus() { } s.tasks = progress.Tasks for _, task := range s.tasks { + task := task // closure for task.genBatch in the stacktrie writer callback + task.genBatch = ethdb.HookedBatch{ Batch: s.db.NewBatch(), OnPut: func(key []byte, value []byte) { s.accountBytes += common.StorageSize(len(key) + len(value)) }, } - task.genTrie = trie.NewStackTrie(func(owner common.Hash, path []byte, hash common.Hash, val []byte) { - rawdb.WriteTrieNode(task.genBatch, owner, path, hash, val, s.scheme) + task.genTrie = trie.NewStackTrie(func(path []byte, hash common.Hash, val []byte) { + rawdb.WriteTrieNode(task.genBatch, common.Hash{}, path, hash, val, s.scheme) }) for accountHash, subtasks := range task.SubTasks { for _, subtask := range subtasks { + subtask := subtask // closure for subtask.genBatch in the stacktrie writer callback + subtask.genBatch = ethdb.HookedBatch{ Batch: s.db.NewBatch(), OnPut: func(key []byte, value []byte) { s.storageBytes += common.StorageSize(len(key) + len(value)) }, } - subtask.genTrie = trie.NewStackTrieWithOwner(func(owner common.Hash, path []byte, hash common.Hash, val []byte) { + owner := accountHash // local assignment for stacktrie writer closure + subtask.genTrie = trie.NewStackTrie(func(path []byte, hash common.Hash, val []byte) { rawdb.WriteTrieNode(subtask.genBatch, owner, path, hash, val, s.scheme) - }, accountHash) + }) } } } @@ -807,8 +811,8 @@ func (s *Syncer) loadSyncStatus() { Last: last, SubTasks: make(map[common.Hash][]*storageTask), genBatch: batch, - genTrie: trie.NewStackTrie(func(owner common.Hash, path []byte, hash common.Hash, val []byte) { - rawdb.WriteTrieNode(batch, owner, path, hash, val, s.scheme) + genTrie: trie.NewStackTrie(func(path []byte, hash common.Hash, val []byte) { + rawdb.WriteTrieNode(batch, common.Hash{}, path, hash, val, s.scheme) }), }) log.Debug("Created account sync task", "from", next, "last", last) @@ -2001,14 +2005,15 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { s.storageBytes += common.StorageSize(len(key) + len(value)) }, } + owner := account // local assignment for stacktrie writer closure tasks = append(tasks, &storageTask{ Next: common.Hash{}, Last: r.End(), root: acc.Root, genBatch: batch, - genTrie: trie.NewStackTrieWithOwner(func(owner common.Hash, path []byte, hash common.Hash, val []byte) { + genTrie: trie.NewStackTrie(func(path []byte, hash common.Hash, val []byte) { rawdb.WriteTrieNode(batch, owner, path, hash, val, s.scheme) - }, account), + }), }) for r.Next() { batch := ethdb.HookedBatch{ @@ -2022,9 +2027,9 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { Last: r.End(), root: acc.Root, genBatch: batch, - genTrie: trie.NewStackTrieWithOwner(func(owner common.Hash, path []byte, hash common.Hash, val []byte) { + genTrie: trie.NewStackTrie(func(path []byte, hash common.Hash, val []byte) { rawdb.WriteTrieNode(batch, owner, path, hash, val, s.scheme) - }, account), + }), }) } for _, task := range tasks { @@ -2069,9 +2074,10 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { slots += len(res.hashes[i]) if i < len(res.hashes)-1 || res.subTask == nil { - tr := trie.NewStackTrieWithOwner(func(owner common.Hash, path []byte, hash common.Hash, val []byte) { - rawdb.WriteTrieNode(batch, owner, path, hash, val, s.scheme) - }, account) + // no need to make local reassignment of account: this closure does not outlive the loop + tr := trie.NewStackTrie(func(path []byte, hash common.Hash, val []byte) { + rawdb.WriteTrieNode(batch, account, path, hash, val, s.scheme) + }) for j := 0; j < len(res.hashes[i]); j++ { tr.Update(res.hashes[i][j][:], res.slots[i][j]) } @@ -2277,13 +2283,13 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { if task.needCode[i] || task.needState[i] { break } - slim := snapshot.SlimAccountRLP(res.accounts[i].Nonce, res.accounts[i].Balance, res.accounts[i].Root, res.accounts[i].CodeHash) + slim := types.SlimAccountRLP(*res.accounts[i]) rawdb.WriteAccountSnapshot(batch, hash, slim) // If the task is complete, drop it into the stack trie to generate // account trie nodes for it if !task.needHeal[i] { - full, err := snapshot.FullAccountRLP(slim) // TODO(karalabe): Slim parsing can be omitted + full, err := types.FullAccountRLP(slim) // TODO(karalabe): Slim parsing can be omitted if err != nil { panic(err) // Really shouldn't ever happen } @@ -2391,11 +2397,11 @@ func (s *Syncer) OnAccounts(peer SyncPeer, id uint64, hashes []common.Hash, acco for i, key := range hashes { keys[i] = common.CopyBytes(key[:]) } - nodes := make(light.NodeList, len(proof)) + nodes := make(trienode.ProofList, len(proof)) for i, node := range proof { nodes[i] = node } - proofdb := nodes.NodeSet() + proofdb := nodes.Set() var end []byte if len(keys) > 0 { @@ -2636,7 +2642,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo for j, key := range hashes[i] { keys[j] = common.CopyBytes(key[:]) } - nodes := make(light.NodeList, 0, len(proof)) + nodes := make(trienode.ProofList, 0, len(proof)) if i == len(hashes)-1 { for _, node := range proof { nodes = append(nodes, node) @@ -2655,7 +2661,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo } else { // A proof was attached, the response is only partial, check that the // returned data is indeed part of the storage trie - proofdb := nodes.NodeSet() + proofdb := nodes.Set() var end []byte if len(keys) > 0 { @@ -2902,7 +2908,7 @@ func (s *Syncer) onHealState(paths [][]byte, value []byte) error { if err := rlp.DecodeBytes(value, &account); err != nil { return nil // Returning the error here would drop the remote peer } - blob := snapshot.SlimAccountRLP(account.Nonce, account.Balance, account.Root, account.CodeHash) + blob := types.SlimAccountRLP(account) rawdb.WriteAccountSnapshot(s.stateWriter, common.BytesToHash(paths[0]), blob) s.accountHealed += 1 s.accountHealedBytes += common.StorageSize(1 + common.HashLength + len(blob)) diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index 58cde8d1ebb9..1ee381a6619e 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -22,7 +22,6 @@ import ( "encoding/binary" "fmt" "math/big" - "sort" "sync" "testing" "time" @@ -32,12 +31,13 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" "github.com/ethereum/go-ethereum/trie/trienode" "golang.org/x/crypto/sha3" + "golang.org/x/exp/slices" ) func TestHashing(t *testing.T) { @@ -127,9 +127,9 @@ type testPeer struct { remote *Syncer logger log.Logger accountTrie *trie.Trie - accountValues entrySlice + accountValues []*kv storageTries map[common.Hash]*trie.Trie - storageValues map[common.Hash]entrySlice + storageValues map[common.Hash][]*kv accountRequestHandler accountHandlerFunc storageRequestHandler storageHandlerFunc @@ -272,17 +272,17 @@ func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.H // Unless we send the entire trie, we need to supply proofs // Actually, we need to supply proofs either way! This seems to be an implementation // quirk in go-ethereum - proof := light.NewNodeSet() - if err := t.accountTrie.Prove(origin[:], 0, proof); err != nil { + proof := trienode.NewProofSet() + if err := t.accountTrie.Prove(origin[:], proof); err != nil { t.logger.Error("Could not prove inexistence of origin", "origin", origin, "error", err) } if len(keys) > 0 { lastK := (keys[len(keys)-1])[:] - if err := t.accountTrie.Prove(lastK, 0, proof); err != nil { + if err := t.accountTrie.Prove(lastK, proof); err != nil { t.logger.Error("Could not prove last item", "error", err) } } - for _, blob := range proof.NodeList() { + for _, blob := range proof.List() { proofs = append(proofs, blob) } return keys, vals, proofs @@ -352,22 +352,22 @@ func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []comm if originHash != (common.Hash{}) || (abort && len(keys) > 0) { // If we're aborting, we need to prove the first and last item // This terminates the response (and thus the loop) - proof := light.NewNodeSet() + proof := trienode.NewProofSet() stTrie := t.storageTries[account] // Here's a potential gotcha: when constructing the proof, we cannot // use the 'origin' slice directly, but must use the full 32-byte // hash form. - if err := stTrie.Prove(originHash[:], 0, proof); err != nil { + if err := stTrie.Prove(originHash[:], proof); err != nil { t.logger.Error("Could not prove inexistence of origin", "origin", originHash, "error", err) } if len(keys) > 0 { lastK := (keys[len(keys)-1])[:] - if err := stTrie.Prove(lastK, 0, proof); err != nil { + if err := stTrie.Prove(lastK, proof); err != nil { t.logger.Error("Could not prove last item", "error", err) } } - for _, blob := range proof.NodeList() { + for _, blob := range proof.List() { proofs = append(proofs, blob) } break @@ -410,23 +410,23 @@ func createStorageRequestResponseAlwaysProve(t *testPeer, root common.Hash, acco if exit { // If we're aborting, we need to prove the first and last item // This terminates the response (and thus the loop) - proof := light.NewNodeSet() + proof := trienode.NewProofSet() stTrie := t.storageTries[account] // Here's a potential gotcha: when constructing the proof, we cannot // use the 'origin' slice directly, but must use the full 32-byte // hash form. - if err := stTrie.Prove(origin[:], 0, proof); err != nil { + if err := stTrie.Prove(origin[:], proof); err != nil { t.logger.Error("Could not prove inexistence of origin", "origin", origin, "error", err) } if len(keys) > 0 { lastK := (keys[len(keys)-1])[:] - if err := stTrie.Prove(lastK, 0, proof); err != nil { + if err := stTrie.Prove(lastK, proof); err != nil { t.logger.Error("Could not prove last item", "error", err) } } - for _, blob := range proof.NodeList() { + for _, blob := range proof.List() { proofs = append(proofs, blob) } break @@ -561,6 +561,11 @@ func noProofStorageRequestHandler(t *testPeer, requestId uint64, root common.Has func TestSyncBloatedProof(t *testing.T) { t.Parallel() + testSyncBloatedProof(t, rawdb.HashScheme) + testSyncBloatedProof(t, rawdb.PathScheme) +} + +func testSyncBloatedProof(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -570,7 +575,7 @@ func TestSyncBloatedProof(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme) source := newTestPeer("source", t, term) source.accountTrie = sourceAccountTrie.Copy() source.accountValues = elems @@ -593,13 +598,14 @@ func TestSyncBloatedProof(t *testing.T) { vals = append(vals, entry.v) } // The proofs - proof := light.NewNodeSet() - if err := t.accountTrie.Prove(origin[:], 0, proof); err != nil { + proof := trienode.NewProofSet() + if err := t.accountTrie.Prove(origin[:], proof); err != nil { + t.logger.Error("Could not prove origin", "origin", origin, "error", err) t.logger.Error("Could not prove origin", "origin", origin, "error", err) } // The bloat: add proof of every single element for _, entry := range t.accountValues { - if err := t.accountTrie.Prove(entry.k, 0, proof); err != nil { + if err := t.accountTrie.Prove(entry.k, proof); err != nil { t.logger.Error("Could not prove item", "error", err) } } @@ -608,7 +614,7 @@ func TestSyncBloatedProof(t *testing.T) { keys = append(keys[:1], keys[2:]...) vals = append(vals[:1], vals[2:]...) } - for _, blob := range proof.NodeList() { + for _, blob := range proof.List() { proofs = append(proofs, blob) } if err := t.remote.OnAccounts(t, requestId, keys, vals, proofs); err != nil { @@ -638,6 +644,11 @@ func setupSyncer(scheme string, peers ...*testPeer) *Syncer { func TestSync(t *testing.T) { t.Parallel() + testSync(t, rawdb.HashScheme) + testSync(t, rawdb.PathScheme) +} + +func testSync(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -647,7 +658,7 @@ func TestSync(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) @@ -659,7 +670,7 @@ func TestSync(t *testing.T) { if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncTinyTriePanic tests a basic sync with one peer, and a tiny trie. This caused a @@ -667,6 +678,11 @@ func TestSync(t *testing.T) { func TestSyncTinyTriePanic(t *testing.T) { t.Parallel() + testSyncTinyTriePanic(t, rawdb.HashScheme) + testSyncTinyTriePanic(t, rawdb.PathScheme) +} + +func testSyncTinyTriePanic(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -676,7 +692,7 @@ func TestSyncTinyTriePanic(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(1) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(1, scheme) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) @@ -690,13 +706,18 @@ func TestSyncTinyTriePanic(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } // TestMultiSync tests a basic sync with multiple peers func TestMultiSync(t *testing.T) { t.Parallel() + testMultiSync(t, rawdb.HashScheme) + testMultiSync(t, rawdb.PathScheme) +} + +func testMultiSync(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -706,7 +727,7 @@ func TestMultiSync(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) @@ -720,13 +741,18 @@ func TestMultiSync(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncWithStorage tests basic sync using accounts + storage + code func TestSyncWithStorage(t *testing.T) { t.Parallel() + testSyncWithStorage(t, rawdb.HashScheme) + testSyncWithStorage(t, rawdb.PathScheme) +} + +func testSyncWithStorage(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -736,7 +762,7 @@ func TestSyncWithStorage(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(3, 3000, true, false) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 3, 3000, true, false) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) @@ -752,13 +778,18 @@ func TestSyncWithStorage(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } // TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all func TestMultiSyncManyUseless(t *testing.T) { t.Parallel() + testMultiSyncManyUseless(t, rawdb.HashScheme) + testMultiSyncManyUseless(t, rawdb.PathScheme) +} + +func testMultiSyncManyUseless(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -768,7 +799,7 @@ func TestMultiSyncManyUseless(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false) mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer { source := newTestPeer(name, t, term) @@ -801,11 +832,18 @@ func TestMultiSyncManyUseless(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } // TestMultiSyncManyUseless contains one good peer, and many which doesn't return anything valuable at all func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { + t.Parallel() + + testMultiSyncManyUselessWithLowTimeout(t, rawdb.HashScheme) + testMultiSyncManyUselessWithLowTimeout(t, rawdb.PathScheme) +} + +func testMultiSyncManyUselessWithLowTimeout(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -815,7 +853,7 @@ func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false) mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer { source := newTestPeer(name, t, term) @@ -853,11 +891,18 @@ func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } // TestMultiSyncManyUnresponsive contains one good peer, and many which doesn't respond at all func TestMultiSyncManyUnresponsive(t *testing.T) { + t.Parallel() + + testMultiSyncManyUnresponsive(t, rawdb.HashScheme) + testMultiSyncManyUnresponsive(t, rawdb.PathScheme) +} + +func testMultiSyncManyUnresponsive(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -867,7 +912,7 @@ func TestMultiSyncManyUnresponsive(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false) mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer { source := newTestPeer(name, t, term) @@ -903,7 +948,7 @@ func TestMultiSyncManyUnresponsive(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } func checkStall(t *testing.T, term func()) chan struct{} { @@ -925,6 +970,11 @@ func checkStall(t *testing.T, term func()) chan struct{} { func TestSyncBoundaryAccountTrie(t *testing.T) { t.Parallel() + testSyncBoundaryAccountTrie(t, rawdb.HashScheme) + testSyncBoundaryAccountTrie(t, rawdb.PathScheme) +} + +func testSyncBoundaryAccountTrie(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -934,7 +984,7 @@ func TestSyncBoundaryAccountTrie(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems := makeBoundaryAccountTrie(3000) + nodeScheme, sourceAccountTrie, elems := makeBoundaryAccountTrie(scheme, 3000) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) @@ -952,7 +1002,7 @@ func TestSyncBoundaryAccountTrie(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncNoStorageAndOneCappedPeer tests sync using accounts and no storage, where one peer is @@ -960,6 +1010,11 @@ func TestSyncBoundaryAccountTrie(t *testing.T) { func TestSyncNoStorageAndOneCappedPeer(t *testing.T) { t.Parallel() + testSyncNoStorageAndOneCappedPeer(t, rawdb.HashScheme) + testSyncNoStorageAndOneCappedPeer(t, rawdb.PathScheme) +} + +func testSyncNoStorageAndOneCappedPeer(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -969,7 +1024,7 @@ func TestSyncNoStorageAndOneCappedPeer(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000, scheme) mkSource := func(name string, slow bool) *testPeer { source := newTestPeer(name, t, term) @@ -994,7 +1049,7 @@ func TestSyncNoStorageAndOneCappedPeer(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncNoStorageAndOneCodeCorruptPeer has one peer which doesn't deliver @@ -1002,6 +1057,11 @@ func TestSyncNoStorageAndOneCappedPeer(t *testing.T) { func TestSyncNoStorageAndOneCodeCorruptPeer(t *testing.T) { t.Parallel() + testSyncNoStorageAndOneCodeCorruptPeer(t, rawdb.HashScheme) + testSyncNoStorageAndOneCodeCorruptPeer(t, rawdb.PathScheme) +} + +func testSyncNoStorageAndOneCodeCorruptPeer(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -1011,7 +1071,7 @@ func TestSyncNoStorageAndOneCodeCorruptPeer(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000, scheme) mkSource := func(name string, codeFn codeHandlerFunc) *testPeer { source := newTestPeer(name, t, term) @@ -1034,12 +1094,17 @@ func TestSyncNoStorageAndOneCodeCorruptPeer(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) { t.Parallel() + testSyncNoStorageAndOneAccountCorruptPeer(t, rawdb.HashScheme) + testSyncNoStorageAndOneAccountCorruptPeer(t, rawdb.PathScheme) +} + +func testSyncNoStorageAndOneAccountCorruptPeer(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -1049,7 +1114,7 @@ func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000, scheme) mkSource := func(name string, accFn accountHandlerFunc) *testPeer { source := newTestPeer(name, t, term) @@ -1072,7 +1137,7 @@ func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncNoStorageAndOneCodeCappedPeer has one peer which delivers code hashes @@ -1080,6 +1145,11 @@ func TestSyncNoStorageAndOneAccountCorruptPeer(t *testing.T) { func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { t.Parallel() + testSyncNoStorageAndOneCodeCappedPeer(t, rawdb.HashScheme) + testSyncNoStorageAndOneCodeCappedPeer(t, rawdb.PathScheme) +} + +func testSyncNoStorageAndOneCodeCappedPeer(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -1089,7 +1159,7 @@ func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(3000, scheme) mkSource := func(name string, codeFn codeHandlerFunc) *testPeer { source := newTestPeer(name, t, term) @@ -1123,7 +1193,7 @@ func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { if threshold := 100; counter > threshold { t.Logf("Error, expected < %d invocations, got %d", threshold, counter) } - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncBoundaryStorageTrie tests sync against a few normal peers, but the @@ -1131,6 +1201,11 @@ func TestSyncNoStorageAndOneCodeCappedPeer(t *testing.T) { func TestSyncBoundaryStorageTrie(t *testing.T) { t.Parallel() + testSyncBoundaryStorageTrie(t, rawdb.HashScheme) + testSyncBoundaryStorageTrie(t, rawdb.PathScheme) +} + +func testSyncBoundaryStorageTrie(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -1140,7 +1215,7 @@ func TestSyncBoundaryStorageTrie(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(10, 1000, false, true) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 10, 1000, false, true) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) @@ -1160,7 +1235,7 @@ func TestSyncBoundaryStorageTrie(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncWithStorageAndOneCappedPeer tests sync using accounts + storage, where one peer is @@ -1168,6 +1243,11 @@ func TestSyncBoundaryStorageTrie(t *testing.T) { func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { t.Parallel() + testSyncWithStorageAndOneCappedPeer(t, rawdb.HashScheme) + testSyncWithStorageAndOneCappedPeer(t, rawdb.PathScheme) +} + +func testSyncWithStorageAndOneCappedPeer(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -1177,7 +1257,7 @@ func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(300, 1000, false, false) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 300, 1000, false, false) mkSource := func(name string, slow bool) *testPeer { source := newTestPeer(name, t, term) @@ -1202,7 +1282,7 @@ func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncWithStorageAndCorruptPeer tests sync using accounts + storage, where one peer is @@ -1210,6 +1290,11 @@ func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { func TestSyncWithStorageAndCorruptPeer(t *testing.T) { t.Parallel() + testSyncWithStorageAndCorruptPeer(t, rawdb.HashScheme) + testSyncWithStorageAndCorruptPeer(t, rawdb.PathScheme) +} + +func testSyncWithStorageAndCorruptPeer(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -1219,7 +1304,7 @@ func TestSyncWithStorageAndCorruptPeer(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false) mkSource := func(name string, handler storageHandlerFunc) *testPeer { source := newTestPeer(name, t, term) @@ -1243,12 +1328,17 @@ func TestSyncWithStorageAndCorruptPeer(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { t.Parallel() + testSyncWithStorageAndNonProvingPeer(t, rawdb.HashScheme) + testSyncWithStorageAndNonProvingPeer(t, rawdb.PathScheme) +} + +func testSyncWithStorageAndNonProvingPeer(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -1258,7 +1348,7 @@ func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false) mkSource := func(name string, handler storageHandlerFunc) *testPeer { source := newTestPeer(name, t, term) @@ -1281,7 +1371,7 @@ func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { t.Fatalf("sync failed: %v", err) } close(done) - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } // TestSyncWithStorage tests basic sync using accounts + storage + code, against @@ -1290,6 +1380,12 @@ func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { // did not mark the account for healing. func TestSyncWithStorageMisbehavingProve(t *testing.T) { t.Parallel() + + testSyncWithStorageMisbehavingProve(t, rawdb.HashScheme) + testSyncWithStorageMisbehavingProve(t, rawdb.PathScheme) +} + +func testSyncWithStorageMisbehavingProve(t *testing.T, scheme string) { var ( once sync.Once cancel = make(chan struct{}) @@ -1299,7 +1395,7 @@ func TestSyncWithStorageMisbehavingProve(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorageWithUniqueStorage(10, 30, false) + nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorageWithUniqueStorage(scheme, 10, 30, false) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) @@ -1314,19 +1410,16 @@ func TestSyncWithStorageMisbehavingProve(t *testing.T) { if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) } type kv struct { k, v []byte } -// Some helpers for sorting -type entrySlice []*kv - -func (p entrySlice) Len() int { return len(p) } -func (p entrySlice) Less(i, j int) bool { return bytes.Compare(p[i].k, p[j].k) < 0 } -func (p entrySlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +func (k *kv) cmp(other *kv) int { + return bytes.Compare(k.k, other.k) +} func key32(i uint64) []byte { key := make([]byte, 32) @@ -1367,11 +1460,11 @@ func getCodeByHash(hash common.Hash) []byte { } // makeAccountTrieNoStorage spits out a trie, along with the leafs -func makeAccountTrieNoStorage(n int) (string, *trie.Trie, entrySlice) { +func makeAccountTrieNoStorage(n int, scheme string) (string, *trie.Trie, []*kv) { var ( - db = trie.NewDatabase(rawdb.NewMemoryDatabase()) + db = trie.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme)) accTrie = trie.NewEmpty(db) - entries entrySlice + entries []*kv ) for i := uint64(1); i <= uint64(n); i++ { value, _ := rlp.EncodeToBytes(&types.StateAccount{ @@ -1385,12 +1478,12 @@ func makeAccountTrieNoStorage(n int) (string, *trie.Trie, entrySlice) { accTrie.MustUpdate(elem.k, elem.v) entries = append(entries, elem) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) // Commit the state changes into db and re-create the trie // for accessing later. - root, nodes := accTrie.Commit(false) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := accTrie.Commit(false) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) accTrie, _ = trie.New(trie.StateTrieID(root), db) return db.Scheme(), accTrie, entries @@ -1399,12 +1492,12 @@ func makeAccountTrieNoStorage(n int) (string, *trie.Trie, entrySlice) { // makeBoundaryAccountTrie constructs an account trie. Instead of filling // accounts normally, this function will fill a few accounts which have // boundary hash. -func makeBoundaryAccountTrie(n int) (string, *trie.Trie, entrySlice) { +func makeBoundaryAccountTrie(scheme string, n int) (string, *trie.Trie, []*kv) { var ( - entries entrySlice + entries []*kv boundaries []common.Hash - db = trie.NewDatabase(rawdb.NewMemoryDatabase()) + db = trie.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme)) accTrie = trie.NewEmpty(db) ) // Initialize boundaries @@ -1447,12 +1540,12 @@ func makeBoundaryAccountTrie(n int) (string, *trie.Trie, entrySlice) { accTrie.MustUpdate(elem.k, elem.v) entries = append(entries, elem) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) // Commit the state changes into db and re-create the trie // for accessing later. - root, nodes := accTrie.Commit(false) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := accTrie.Commit(false) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) accTrie, _ = trie.New(trie.StateTrieID(root), db) return db.Scheme(), accTrie, entries @@ -1460,14 +1553,14 @@ func makeBoundaryAccountTrie(n int) (string, *trie.Trie, entrySlice) { // makeAccountTrieWithStorageWithUniqueStorage creates an account trie where each accounts // has a unique storage set. -func makeAccountTrieWithStorageWithUniqueStorage(accounts, slots int, code bool) (string, *trie.Trie, entrySlice, map[common.Hash]*trie.Trie, map[common.Hash]entrySlice) { +func makeAccountTrieWithStorageWithUniqueStorage(scheme string, accounts, slots int, code bool) (string, *trie.Trie, []*kv, map[common.Hash]*trie.Trie, map[common.Hash][]*kv) { var ( - db = trie.NewDatabase(rawdb.NewMemoryDatabase()) + db = trie.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme)) accTrie = trie.NewEmpty(db) - entries entrySlice + entries []*kv storageRoots = make(map[common.Hash]common.Hash) storageTries = make(map[common.Hash]*trie.Trie) - storageEntries = make(map[common.Hash]entrySlice) + storageEntries = make(map[common.Hash][]*kv) nodes = trienode.NewMergedNodeSet() ) // Create n accounts in the trie @@ -1494,14 +1587,14 @@ func makeAccountTrieWithStorageWithUniqueStorage(accounts, slots int, code bool) storageRoots[common.BytesToHash(key)] = stRoot storageEntries[common.BytesToHash(key)] = stEntries } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) // Commit account trie - root, set := accTrie.Commit(true) + root, set, _ := accTrie.Commit(true) nodes.Merge(set) // Commit gathered dirty nodes into database - db.Update(root, types.EmptyRootHash, nodes) + db.Update(root, types.EmptyRootHash, 0, nodes, nil) // Re-create tries with new root accTrie, _ = trie.New(trie.StateTrieID(root), db) @@ -1515,14 +1608,14 @@ func makeAccountTrieWithStorageWithUniqueStorage(accounts, slots int, code bool) } // makeAccountTrieWithStorage spits out a trie, along with the leafs -func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (string, *trie.Trie, entrySlice, map[common.Hash]*trie.Trie, map[common.Hash]entrySlice) { +func makeAccountTrieWithStorage(scheme string, accounts, slots int, code, boundary bool) (string, *trie.Trie, []*kv, map[common.Hash]*trie.Trie, map[common.Hash][]*kv) { var ( - db = trie.NewDatabase(rawdb.NewMemoryDatabase()) + db = trie.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme)) accTrie = trie.NewEmpty(db) - entries entrySlice + entries []*kv storageRoots = make(map[common.Hash]common.Hash) storageTries = make(map[common.Hash]*trie.Trie) - storageEntries = make(map[common.Hash]entrySlice) + storageEntries = make(map[common.Hash][]*kv) nodes = trienode.NewMergedNodeSet() ) // Create n accounts in the trie @@ -1536,7 +1629,7 @@ func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (strin var ( stRoot common.Hash stNodes *trienode.NodeSet - stEntries entrySlice + stEntries []*kv ) if boundary { stRoot, stNodes, stEntries = makeBoundaryStorageTrie(common.BytesToHash(key), slots, db) @@ -1559,14 +1652,14 @@ func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (strin storageRoots[common.BytesToHash(key)] = stRoot storageEntries[common.BytesToHash(key)] = stEntries } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) // Commit account trie - root, set := accTrie.Commit(true) + root, set, _ := accTrie.Commit(true) nodes.Merge(set) // Commit gathered dirty nodes into database - db.Update(root, types.EmptyRootHash, nodes) + db.Update(root, types.EmptyRootHash, 0, nodes, nil) // Re-create tries with new root accTrie, err := trie.New(trie.StateTrieID(root), db) @@ -1588,9 +1681,9 @@ func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (strin // makeStorageTrieWithSeed fills a storage trie with n items, returning the // not-yet-committed trie and the sorted entries. The seeds can be used to ensure // that tries are unique. -func makeStorageTrieWithSeed(owner common.Hash, n, seed uint64, db *trie.Database) (common.Hash, *trienode.NodeSet, entrySlice) { +func makeStorageTrieWithSeed(owner common.Hash, n, seed uint64, db *trie.Database) (common.Hash, *trienode.NodeSet, []*kv) { trie, _ := trie.New(trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash), db) - var entries entrySlice + var entries []*kv for i := uint64(1); i <= n; i++ { // store 'x' at slot 'x' slotValue := key32(i + seed) @@ -1603,17 +1696,17 @@ func makeStorageTrieWithSeed(owner common.Hash, n, seed uint64, db *trie.Databas trie.MustUpdate(elem.k, elem.v) entries = append(entries, elem) } - sort.Sort(entries) - root, nodes := trie.Commit(false) + slices.SortFunc(entries, (*kv).cmp) + root, nodes, _ := trie.Commit(false) return root, nodes, entries } // makeBoundaryStorageTrie constructs a storage trie. Instead of filling // storage slots normally, this function will fill a few slots which have // boundary hash. -func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (common.Hash, *trienode.NodeSet, entrySlice) { +func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (common.Hash, *trienode.NodeSet, []*kv) { var ( - entries entrySlice + entries []*kv boundaries []common.Hash trie, _ = trie.New(trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash), db) ) @@ -1654,20 +1747,20 @@ func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (commo trie.MustUpdate(elem.k, elem.v) entries = append(entries, elem) } - sort.Sort(entries) - root, nodes := trie.Commit(false) + slices.SortFunc(entries, (*kv).cmp) + root, nodes, _ := trie.Commit(false) return root, nodes, entries } -func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) { +func verifyTrie(scheme string, db ethdb.KeyValueStore, root common.Hash, t *testing.T) { t.Helper() - triedb := trie.NewDatabase(rawdb.NewDatabase(db)) + triedb := trie.NewDatabase(rawdb.NewDatabase(db), newDbConfig(scheme)) accTrie, err := trie.New(trie.StateTrieID(root), triedb) if err != nil { t.Fatal(err) } accounts, slots := 0, 0 - accIt := trie.NewIterator(accTrie.NodeIterator(nil)) + accIt := trie.NewIterator(accTrie.MustNodeIterator(nil)) for accIt.Next() { var acc struct { Nonce uint64 @@ -1685,7 +1778,7 @@ func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) { if err != nil { t.Fatal(err) } - storeIt := trie.NewIterator(storeTrie.NodeIterator(nil)) + storeIt := trie.NewIterator(storeTrie.MustNodeIterator(nil)) for storeIt.Next() { slots++ } @@ -1703,6 +1796,13 @@ func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) { // TestSyncAccountPerformance tests how efficient the snap algo is at minimizing // state healing func TestSyncAccountPerformance(t *testing.T) { + t.Parallel() + + testSyncAccountPerformance(t, rawdb.HashScheme) + testSyncAccountPerformance(t, rawdb.PathScheme) +} + +func testSyncAccountPerformance(t *testing.T, scheme string) { // Set the account concurrency to 1. This _should_ result in the // range root to become correct, and there should be no healing needed defer func(old int) { accountConcurrency = old }(accountConcurrency) @@ -1717,7 +1817,7 @@ func TestSyncAccountPerformance(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100) + nodeScheme, sourceAccountTrie, elems := makeAccountTrieNoStorage(100, scheme) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) @@ -1730,7 +1830,7 @@ func TestSyncAccountPerformance(t *testing.T) { if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { t.Fatalf("sync failed: %v", err) } - verifyTrie(syncer.db, sourceAccountTrie.Hash(), t) + verifyTrie(scheme, syncer.db, sourceAccountTrie.Hash(), t) // The trie root will always be requested, since it is added when the snap // sync cycle starts. When popping the queue, we do not look it up again. // Doing so would bring this number down to zero in this artificial testcase, @@ -1790,3 +1890,10 @@ func TestSlotEstimation(t *testing.T) { } } } + +func newDbConfig(scheme string) *trie.Config { + if scheme == rawdb.HashScheme { + return &trie.Config{} + } + return &trie.Config{PathDB: pathdb.Defaults} +} diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 1b0ab0c54509..72061c7fe318 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -36,31 +37,11 @@ import ( // for releasing state. var noopReleaser = tracers.StateReleaseFunc(func() {}) -// StateAtBlock retrieves the state database associated with a certain block. -// If no state is locally available for the given block, a number of blocks -// are attempted to be reexecuted to generate the desired state. The optional -// base layer statedb can be provided which is regarded as the statedb of the -// parent block. -// -// An additional release function will be returned if the requested state is -// available. Release is expected to be invoked when the returned state is no longer needed. -// Its purpose is to prevent resource leaking. Though it can be noop in some cases. -// -// Parameters: -// - block: The block for which we want the state(state = block.Root) -// - reexec: The maximum number of blocks to reprocess trying to obtain the desired state -// - base: If the caller is tracing multiple blocks, the caller can provide the parent -// state continuously from the callsite. -// - readOnly: If true, then the live 'blockchain' state database is used. No mutation should -// be made from caller, e.g. perform Commit or other 'save-to-disk' changes. -// Otherwise, the trash generated by caller may be persisted permanently. -// - preferDisk: this arg can be used by the caller to signal that even though the 'base' is -// provided, it would be preferable to start from a fresh state, if we have it -// on disk. -func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) { +func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) { var ( current *types.Block database state.Database + triedb *trie.Database report = true origin = block.NumberU64() ) @@ -71,9 +52,9 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe // on top to prevent garbage collection and return a release // function to deref it. if statedb, err = eth.blockchain.StateAt(block.Root()); err == nil { - statedb.Database().TrieDB().Reference(block.Root(), common.Hash{}) + eth.blockchain.TrieDB().Reference(block.Root(), common.Hash{}) return statedb, func() { - statedb.Database().TrieDB().Dereference(block.Root()) + eth.blockchain.TrieDB().Dereference(block.Root()) }, nil } } @@ -84,14 +65,16 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe if preferDisk { // Create an ephemeral trie.Database for isolating the live one. Otherwise // the internal junks created by tracing will be persisted into the disk. - database = state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16}) + // TODO(rjl493456442), clean cache is disabled to prevent memory leak, + // please re-enable it for better performance. + database = state.NewDatabaseWithConfig(eth.chainDb, trie.HashDefaults) if statedb, err = state.New(block.Root(), database, nil); err == nil { log.Info("Found disk backend for state trie", "root", block.Root(), "number", block.Number()) - return statedb, noopReleaser, nil + return statedb, func() { database.TrieDB().Close() }, nil } } // The optional base statedb is given, mark the start point as parent block - statedb, database, report = base, base.Database(), false + statedb, database, triedb, report = base, base.Database(), base.Database().TrieDB(), false current = eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) } else { // Otherwise, try to reexec blocks until we find a state or reach our limit @@ -99,7 +82,10 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe // Create an ephemeral trie.Database for isolating the live one. Otherwise // the internal junks created by tracing will be persisted into the disk. - database = state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16}) + // TODO(rjl493456442), clean cache is disabled to prevent memory leak, + // please re-enable it for better performance. + triedb = trie.NewDatabase(eth.chainDb, trie.HashDefaults) + database = state.NewDatabaseWithNodeDB(eth.chainDb, triedb) // If we didn't check the live database, do check state over ephemeral database, // otherwise we would rewind past a persisted block (specific corner case is @@ -107,7 +93,7 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe if !readOnly { statedb, err = state.New(current.Root(), database, nil) if err == nil { - return statedb, noopReleaser, nil + return statedb, func() { database.TrieDB().Close() }, nil } } // Database does not have the state for the given block, try to regenerate @@ -164,7 +150,7 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err) } // Finalize the state so any modifications are written to the trie - root, err := statedb.Commit(eth.blockchain.Config().IsEIP158(current.Number())) + root, err := statedb.Commit(current.NumberU64(), eth.blockchain.Config().IsEIP158(current.Number())) if err != nil { return nil, nil, fmt.Errorf("stateAtBlock commit failed, number %d root %v: %w", current.NumberU64(), current.Root().Hex(), err) @@ -175,17 +161,63 @@ func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexe } // Hold the state reference and also drop the parent state // to prevent accumulating too many nodes in memory. - database.TrieDB().Reference(root, common.Hash{}) + triedb.Reference(root, common.Hash{}) if parent != (common.Hash{}) { - database.TrieDB().Dereference(parent) + triedb.Dereference(parent) } parent = root } if report { - nodes, imgs := database.TrieDB().Size() + _, nodes, imgs := triedb.Size() // all memory is contained within the nodes return in hashdb log.Info("Historical state regenerated", "block", current.NumberU64(), "elapsed", time.Since(start), "nodes", nodes, "preimages", imgs) } - return statedb, func() { database.TrieDB().Dereference(block.Root()) }, nil + return statedb, func() { triedb.Dereference(block.Root()) }, nil +} + +func (eth *Ethereum) pathState(block *types.Block) (*state.StateDB, func(), error) { + // Check if the requested state is available in the live chain. + statedb, err := eth.blockchain.StateAt(block.Root()) + if err == nil { + return statedb, noopReleaser, nil + } + // TODO historic state is not supported in path-based scheme. + // Fully archive node in pbss will be implemented by relying + // on state history, but needs more work on top. + return nil, nil, errors.New("historical state not available in path scheme yet") +} + +// stateAtBlock retrieves the state database associated with a certain block. +// If no state is locally available for the given block, a number of blocks +// are attempted to be reexecuted to generate the desired state. The optional +// base layer statedb can be provided which is regarded as the statedb of the +// parent block. +// +// An additional release function will be returned if the requested state is +// available. Release is expected to be invoked when the returned state is no +// longer needed. Its purpose is to prevent resource leaking. Though it can be +// noop in some cases. +// +// Parameters: +// - block: The block for which we want the state(state = block.Root) +// - reexec: The maximum number of blocks to reprocess trying to obtain the desired state +// - base: If the caller is tracing multiple blocks, the caller can provide the parent +// state continuously from the callsite. +// - readOnly: If true, then the live 'blockchain' state database is used. No mutation should +// be made from caller, e.g. perform Commit or other 'save-to-disk' changes. +// Otherwise, the trash generated by caller may be persisted permanently. +// - preferDisk: This arg can be used by the caller to signal that even though the 'base' is +// provided, it would be preferable to start from a fresh state, if we have it +// on disk. +func (eth *Ethereum) stateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) { + if eth.blockchain.TrieDB().Scheme() == rawdb.HashScheme { + return eth.hashState(ctx, block, reexec, base, readOnly, preferDisk) + } + return eth.pathState(block) +} + +// arbitrum: exposing stateAtBlock function +func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) { + return eth.stateAtBlock(ctx, block, reexec, base, readOnly, preferDisk) } // stateAtTransaction returns the execution environment of a certain transaction. @@ -201,7 +233,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, } // Lookup the statedb of parent block from the live database, // otherwise regenerate it on the flight. - statedb, release, err := eth.StateAtBlock(ctx, parent, reexec, nil, true, false) + statedb, release, err := eth.stateAtBlock(ctx, parent, reexec, nil, true, false) if err != nil { return nil, vm.BlockContext{}, nil, nil, err } diff --git a/eth/sync.go b/eth/sync.go index ace1071143b5..c7ba7c93d6e9 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/log" @@ -36,27 +35,15 @@ const ( // syncTransactions starts sending all currently pending transactions to the given peer. func (h *handler) syncTransactions(p *eth.Peer) { - // Assemble the set of transaction to broadcast or announce to the remote - // peer. Fun fact, this is quite an expensive operation as it needs to sort - // the transactions if the sorting is not cached yet. However, with a random - // order, insertions could overflow the non-executable queues and get dropped. - // - // TODO(karalabe): Figure out if we could get away with random order somehow - var txs types.Transactions - pending := h.txpool.Pending(false) - for _, batch := range pending { - txs = append(txs, batch...) + var hashes []common.Hash + for _, batch := range h.txpool.Pending(false) { + for _, tx := range batch { + hashes = append(hashes, tx.Hash) + } } - if len(txs) == 0 { + if len(hashes) == 0 { return } - // The eth/65 protocol introduces proper transaction announcements, so instead - // of dripping transactions across multiple peers, just send the entire list as - // an announcement and let the remote side decide what they need (likely nothing). - hashes := make([]common.Hash, len(txs)) - for i, tx := range txs { - hashes[i] = tx.Hash() - } p.AsyncSendPooledTransactionHashes(hashes) } @@ -89,7 +76,7 @@ func newChainSyncer(handler *handler) *chainSyncer { // handlePeerEvent notifies the syncer about a change in the peer set. // This is called for new peers and every time a peer announces a new // chain head. -func (cs *chainSyncer) handlePeerEvent(peer *eth.Peer) bool { +func (cs *chainSyncer) handlePeerEvent() bool { select { case cs.peerEventCh <- struct{}{}: return true @@ -210,16 +197,25 @@ func (cs *chainSyncer) modeAndLocalHead() (downloader.SyncMode, *big.Int) { return downloader.SnapSync, td } // We are probably in full sync, but we might have rewound to before the - // snap sync pivot, check if we should reenable + // snap sync pivot, check if we should re-enable snap sync. + head := cs.handler.chain.CurrentBlock() if pivot := rawdb.ReadLastPivotNumber(cs.handler.database); pivot != nil { - if head := cs.handler.chain.CurrentBlock(); head.Number.Uint64() < *pivot { + if head.Number.Uint64() < *pivot { block := cs.handler.chain.CurrentSnapBlock() td := cs.handler.chain.GetTd(block.Hash(), block.Number.Uint64()) return downloader.SnapSync, td } } + // We are in a full sync, but the associated head state is missing. To complete + // the head state, forcefully rerun the snap sync. Note it doesn't mean the + // persistent state is corrupted, just mismatch with the head block. + if !cs.handler.chain.HasState(head.Root) { + block := cs.handler.chain.CurrentSnapBlock() + td := cs.handler.chain.GetTd(block.Hash(), block.Number.Uint64()) + log.Info("Reenabled snap sync as chain is stateless") + return downloader.SnapSync, td + } // Nope, we're really full syncing - head := cs.handler.chain.CurrentBlock() td := cs.handler.chain.GetTd(head.Hash(), head.Number.Uint64()) return downloader.FullSync, td } @@ -255,13 +251,7 @@ func (h *handler) doSync(op *chainSyncOp) error { if err != nil { return err } - if h.snapSync.Load() { - log.Info("Snap sync complete, auto disabling") - h.snapSync.Store(false) - } - // If we've successfully finished a sync cycle, enable accepting transactions - // from the network. - h.acceptTxs.Store(true) + h.enableSyncedFeatures() head := h.chain.CurrentBlock() if head.Number.Uint64() > 0 { diff --git a/eth/sync_test.go b/eth/sync_test.go index b5e00298b9e9..d26cbb66ea61 100644 --- a/eth/sync_test.go +++ b/eth/sync_test.go @@ -28,8 +28,8 @@ import ( ) // Tests that snap sync is disabled after a successful sync cycle. -func TestSnapSyncDisabling66(t *testing.T) { testSnapSyncDisabling(t, eth.ETH66, snap.SNAP1) } func TestSnapSyncDisabling67(t *testing.T) { testSnapSyncDisabling(t, eth.ETH67, snap.SNAP1) } +func TestSnapSyncDisabling68(t *testing.T) { testSnapSyncDisabling(t, eth.ETH68, snap.SNAP1) } // Tests that snap sync gets disabled as soon as a real block is successfully // imported into the blockchain. diff --git a/eth/tracers/api.go b/eth/tracers/api.go index b3be9d91250f..cfbd683d894e 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -18,7 +18,6 @@ package tracers import ( "bufio" - "bytes" "context" "encoding/json" "errors" @@ -369,8 +368,8 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed // if the relevant state is available in disk. var preferDisk bool if statedb != nil { - s1, s2 := statedb.Database().TrieDB().Size() - preferDisk = s1+s2 > defaultTracechainMemLimit + s1, s2, s3 := statedb.Database().TrieDB().Size() + preferDisk = s1+s2+s3 > defaultTracechainMemLimit } statedb, release, err = api.backend.StateAtBlock(ctx, block, reexec, statedb, false, preferDisk) if err != nil { @@ -453,7 +452,7 @@ func (api *API) TraceBlockByHash(ctx context.Context, hash common.Hash, config * // and returns them as a JSON object. func (api *API) TraceBlock(ctx context.Context, blob hexutil.Bytes, config *TraceConfig) ([]*txTraceResult, error) { block := new(types.Block) - if err := rlp.Decode(bytes.NewReader(blob), block); err != nil { + if err := rlp.DecodeBytes(blob, block); err != nil { return nil, fmt.Errorf("could not decode block: %v", err) } return api.traceBlock(ctx, block, config) @@ -1020,6 +1019,10 @@ func overrideConfig(original *params.ChainConfig, override *params.ChainConfig) copy.PragueTime = timestamp canon = false } + if timestamp := override.VerkleTime; timestamp != nil { + copy.VerkleTime = timestamp + canon = false + } return copy, canon } diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 3ec53e675079..2cc6d10b90a8 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -17,7 +17,6 @@ package tracers import ( - "bytes" "context" "crypto/ecdsa" "encoding/json" @@ -25,7 +24,6 @@ import ( "fmt" "math/big" "reflect" - "sort" "sync/atomic" "testing" "time" @@ -45,6 +43,7 @@ import ( "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + "golang.org/x/exp/slices" ) var ( @@ -790,19 +789,13 @@ type Account struct { addr common.Address } -type Accounts []Account - -func (a Accounts) Len() int { return len(a) } -func (a Accounts) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a Accounts) Less(i, j int) bool { return bytes.Compare(a[i].addr.Bytes(), a[j].addr.Bytes()) < 0 } - -func newAccounts(n int) (accounts Accounts) { +func newAccounts(n int) (accounts []Account) { for i := 0; i < n; i++ { key, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(key.PublicKey) accounts = append(accounts, Account{key: key, addr: addr}) } - sort.Sort(accounts) + slices.SortFunc(accounts, func(a, b Account) int { return a.addr.Cmp(b.addr) }) return accounts } diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index c27dffbfaf3f..ce52aa1a99ab 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -148,8 +148,10 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { GasLimit: uint64(test.Context.GasLimit), BaseFee: test.Genesis.BaseFee, } - _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) + triedb, _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) ) + triedb.Close() + tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) if err != nil { t.Fatalf("failed to create call tracer: %v", err) @@ -248,7 +250,8 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { Difficulty: (*big.Int)(test.Context.Difficulty), GasLimit: uint64(test.Context.GasLimit), } - _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) + triedb, _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) + defer triedb.Close() b.ReportAllocs() b.ResetTimer() @@ -313,13 +316,13 @@ func TestInternals(t *testing.T) { byte(vm.CALL), }, tracer: mkTracer("callTracer", nil), - want: `{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0x000000000000000000000000000000000000FEeD","to":null,"value":"0x0"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0x000000000000000000000000000000000000FEeD","value":"0x0"},{"purpose":"tip","from":null,"to":"0x0000000000000000000000000000000000000000","value":"0x0"}],"from":"0x000000000000000000000000000000000000feed","gas":"0xc350","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0x6cbf","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`, + want: `{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0x000000000000000000000000000000000000FEeD","to":null,"value":"0x0"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0x000000000000000000000000000000000000FEeD","value":"0x0"},{"purpose":"tip","from":null,"to":"0x0000000000000000000000000000000000000000","value":"0x0"}],"from":"0x000000000000000000000000000000000000feed","gas":"0x13880","gasUsed":"0x54d8","to":"0x00000000000000000000000000000000deadbeef","input":"0x","calls":[{"from":"0x00000000000000000000000000000000deadbeef","gas":"0xe01a","gasUsed":"0x0","to":"0x00000000000000000000000000000000000000ff","input":"0x","value":"0x0","type":"CALL"}],"value":"0x0","type":"CALL"}`, }, { name: "Stack depletion in LOG0", code: []byte{byte(vm.LOG3)}, tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), - want: `{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0x000000000000000000000000000000000000FEeD","to":null,"value":"0x0"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0x000000000000000000000000000000000000FEeD","value":"0x0"},{"purpose":"tip","from":null,"to":"0x0000000000000000000000000000000000000000","value":"0x0"}],"from":"0x000000000000000000000000000000000000feed","gas":"0xc350","gasUsed":"0xc350","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`, + want: `{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0x000000000000000000000000000000000000FEeD","to":null,"value":"0x0"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0x000000000000000000000000000000000000FEeD","value":"0x0"},{"purpose":"tip","from":null,"to":"0x0000000000000000000000000000000000000000","value":"0x0"}],"from":"0x000000000000000000000000000000000000feed","gas":"0x13880","gasUsed":"0x13880","to":"0x00000000000000000000000000000000deadbeef","input":"0x","error":"stack underflow (0 \u003c=\u003e 5)","value":"0x0","type":"CALL"}`, }, { name: "Mem expansion in LOG0", @@ -332,11 +335,11 @@ func TestInternals(t *testing.T) { byte(vm.LOG0), }, tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), - want: `{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0x000000000000000000000000000000000000FEeD","to":null,"value":"0x0"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0x000000000000000000000000000000000000FEeD","value":"0x0"},{"purpose":"tip","from":null,"to":"0x0000000000000000000000000000000000000000","value":"0x0"}],"from":"0x000000000000000000000000000000000000feed","gas":"0xc350","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}],"value":"0x0","type":"CALL"}`, + want: `{"beforeEVMTransfers":[{"purpose":"feePayment","from":"0x000000000000000000000000000000000000FEeD","to":null,"value":"0x0"}],"afterEVMTransfers":[{"purpose":"gasRefund","from":null,"to":"0x000000000000000000000000000000000000FEeD","value":"0x0"},{"purpose":"tip","from":null,"to":"0x0000000000000000000000000000000000000000","value":"0x0"}],"from":"0x000000000000000000000000000000000000feed","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}],"value":"0x0","type":"CALL"}`, }, { // Leads to OOM on the prestate tracer - name: "Prestate-tracer - mem expansion in CREATE2", + name: "Prestate-tracer - CREATE2 OOM", code: []byte{ byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, @@ -350,41 +353,64 @@ func TestInternals(t *testing.T) { byte(vm.PUSH1), 0x0, byte(vm.LOG0), }, - tracer: mkTracer("prestateTracer", json.RawMessage(`{ "withLog": true }`)), - want: `{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x000000000000000000000000000000000000feed":{"balance":"0x1c6bf52640350"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600164ffffffffff60016000f560ff6000a0"}}`, + tracer: mkTracer("prestateTracer", nil), + want: `{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x000000000000000000000000000000000000feed":{"balance":"0x1c6bf52647880"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600164ffffffffff60016000f560ff6000a0"}}`, + }, + { + // CREATE2 which requires padding memory by prestate tracer + name: "Prestate-tracer - CREATE2 Memory padding", + code: []byte{ + byte(vm.PUSH1), 0x1, + byte(vm.PUSH1), 0x0, + byte(vm.MSTORE), + byte(vm.PUSH1), 0x1, + byte(vm.PUSH1), 0xff, + byte(vm.PUSH1), 0x1, + byte(vm.PUSH1), 0x0, + byte(vm.CREATE2), + byte(vm.PUSH1), 0xff, + byte(vm.PUSH1), 0x0, + byte(vm.LOG0), + }, + tracer: mkTracer("prestateTracer", nil), + want: `{"0x0000000000000000000000000000000000000000":{"balance":"0x0"},"0x000000000000000000000000000000000000feed":{"balance":"0x1c6bf52647880"},"0x00000000000000000000000000000000deadbeef":{"balance":"0x0","code":"0x6001600052600160ff60016000f560ff6000a0"},"0x91ff9a805d36f54e3e272e230f3e3f5c1b330804":{"balance":"0x0"}}`, }, } { - _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), - core.GenesisAlloc{ - to: core.GenesisAccount{ - Code: tc.code, - }, - origin: core.GenesisAccount{ - Balance: big.NewInt(500000000000000), - }, - }, false) - evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Tracer: tc.tracer}) - msg := &core.Message{ - To: &to, - From: origin, - Value: big.NewInt(0), - GasLimit: 50000, - GasPrice: big.NewInt(0), - GasFeeCap: big.NewInt(0), - GasTipCap: big.NewInt(0), - SkipAccountChecks: false, - } - st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)) - if _, err := st.TransitionDb(); err != nil { - t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err) - } - // Retrieve the trace result and compare against the expected - res, err := tc.tracer.GetResult() - if err != nil { - t.Fatalf("test %v: failed to retrieve trace result: %v", tc.name, err) - } - if string(res) != tc.want { - t.Fatalf("test %v: trace mismatch\n have: %v\n want: %v\n", tc.name, string(res), tc.want) - } + t.Run(tc.name, func(t *testing.T) { + triedb, _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), + core.GenesisAlloc{ + to: core.GenesisAccount{ + Code: tc.code, + }, + origin: core.GenesisAccount{ + Balance: big.NewInt(500000000000000), + }, + }, false, rawdb.HashScheme) + defer triedb.Close() + + evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Tracer: tc.tracer}) + msg := &core.Message{ + To: &to, + From: origin, + Value: big.NewInt(0), + GasLimit: 80000, + GasPrice: big.NewInt(0), + GasFeeCap: big.NewInt(0), + GasTipCap: big.NewInt(0), + SkipAccountChecks: false, + } + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(msg.GasLimit)) + if _, err := st.TransitionDb(); err != nil { + t.Fatalf("test %v: failed to execute transaction: %v", tc.name, err) + } + // Retrieve the trace result and compare against the expected + res, err := tc.tracer.GetResult() + if err != nil { + t.Fatalf("test %v: failed to retrieve trace result: %v", tc.name, err) + } + if string(res) != tc.want { + t.Errorf("test %v: trace mismatch\n have: %v\n want: %v\n", tc.name, string(res), tc.want) + } + }) } } diff --git a/eth/tracers/internal/tracetest/flat_calltrace_test.go b/eth/tracers/internal/tracetest/flat_calltrace_test.go index 85e95401a215..423167b13ccd 100644 --- a/eth/tracers/internal/tracetest/flat_calltrace_test.go +++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go @@ -100,7 +100,8 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string Difficulty: (*big.Int)(test.Context.Difficulty), GasLimit: uint64(test.Context.GasLimit), } - _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) + triedb, _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) + defer triedb.Close() // Create the tracer, the EVM environment and run it tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go index 991da10b3ab1..b4fa5b627269 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -108,8 +108,10 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { GasLimit: uint64(test.Context.GasLimit), BaseFee: test.Genesis.BaseFee, } - _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) + triedb, _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) ) + defer triedb.Close() + tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig) if err != nil { t.Fatalf("failed to create call tracer: %v", err) diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index fecc531b6d43..befec4542c07 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -86,7 +86,7 @@ func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString b if !obj.Get("constructor").SameAs(bufType) { break } - b := obj.Get("buffer").Export().(goja.ArrayBuffer).Bytes() + b := obj.Export().([]byte) return b, nil } return nil, errors.New("invalid buffer type") @@ -236,7 +236,12 @@ func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr t.ctx["to"] = t.vm.ToValue(to.Bytes()) t.ctx["input"] = t.vm.ToValue(input) t.ctx["gas"] = t.vm.ToValue(t.gasLimit) - t.ctx["gasPrice"] = t.vm.ToValue(env.TxContext.GasPrice) + gasPriceBig, err := t.toBig(t.vm, env.TxContext.GasPrice.String()) + if err != nil { + t.err = err + return + } + t.ctx["gasPrice"] = gasPriceBig valueBig, err := t.toBig(t.vm, value.String()) if err != nil { t.err = err @@ -469,7 +474,7 @@ func (t *jsTracer) setBuiltinFunctions() { addr := common.BytesToAddress(a) for _, p := range t.activePrecompiles { space := new(big.Int).SetUint64(0x65) - arbOS := p.Hash().Big().Cmp(space) >= 0 + arbOS := p.Big().Cmp(space) >= 0 if p == addr && arbOS { return true } diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index c7f171c5bdf9..4c9b910a27f1 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -418,6 +418,7 @@ type StructLogRes struct { Depth int `json:"depth"` Error string `json:"error,omitempty"` Stack *[]string `json:"stack,omitempty"` + ReturnData string `json:"returnData,omitempty"` Memory *[]string `json:"memory,omitempty"` Storage *map[string]string `json:"storage,omitempty"` RefundCounter uint64 `json:"refund,omitempty"` @@ -443,6 +444,9 @@ func formatLogs(logs []StructLog) []StructLogRes { } formatted[index].Stack = &stack } + if trace.ReturnData != nil && len(trace.ReturnData) > 0 { + formatted[index].ReturnData = hexutil.Bytes(trace.ReturnData).String() + } if trace.Memory != nil { memory := make([]string, 0, (len(trace.Memory)+31)/32) for i := 0; i+32 <= len(trace.Memory); i += 32 { diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 5456216853a7..dc9c8f538f13 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/log" ) //go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go @@ -197,6 +198,7 @@ func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco data, err := tracers.GetMemoryCopyPadded(scope.Memory, int64(mStart.Uint64()), int64(mSize.Uint64())) if err != nil { // mSize was unrealistically large + log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "callTracer", "offset", mStart, "size", mSize) return } diff --git a/eth/tracers/native/call_flat.go b/eth/tracers/native/call_flat.go index 5a9a194dc5d7..c5ed8e062446 100644 --- a/eth/tracers/native/call_flat.go +++ b/eth/tracers/native/call_flat.go @@ -267,8 +267,8 @@ func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx case vm.CREATE, vm.CREATE2: frame = newFlatCreate(input) case vm.SELFDESTRUCT: - frame = newFlatSuicide(input) - case vm.CALL, vm.STATICCALL, vm.CALLCODE, vm.DELEGATECALL: + frame = newFlatSelfdestruct(input) + case vm.CALL, vm.STATICCALL, vm.CALLCODE, vm.DELEGATECALL, vm.INVALID: frame = newFlatCall(input) default: return nil, fmt.Errorf("unrecognized call frame type: %s", input.Type) @@ -351,7 +351,7 @@ func newFlatCall(input *callFrame) *flatCallFrame { } } -func newFlatSuicide(input *callFrame) *flatCallFrame { +func newFlatSelfdestruct(input *callFrame) *flatCallFrame { return &flatCallFrame{ Type: "suicide", Action: flatCallAction{ diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index b71d5d62152d..82451c40a65f 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/log" ) //go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go @@ -165,7 +166,11 @@ func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, case stackLen >= 4 && op == vm.CREATE2: offset := stackData[stackLen-2] size := stackData[stackLen-3] - init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) + init, err := tracers.GetMemoryCopyPadded(scope.Memory, int64(offset.Uint64()), int64(size.Uint64())) + if err != nil { + log.Warn("failed to copy CREATE2 input", "err", err, "tracer", "prestateTracer", "offset", offset, "size", size) + return + } inithash := crypto.Keccak256(init) salt := stackData[stackLen-4] addr := crypto.CreateAddress2(caller, salt.Bytes32(), inithash) diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 759e3a4dd38e..b4989ec98445 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -79,7 +79,9 @@ func BenchmarkTransactionTrace(b *testing.B) { Code: []byte{}, Balance: big.NewInt(500000000000000), } - _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) + triedb, _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false, rawdb.HashScheme) + defer triedb.Close() + // Create the tracer, the EVM environment and run it tracer := logger.NewStructLogger(&logger.Config{ Debug: false, diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 1a571b47e070..93cd2744c141 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -108,6 +108,16 @@ func (ec *Client) PeerCount(ctx context.Context) (uint64, error) { return uint64(result), err } +// BlockReceipts returns the receipts of a given block number or hash. +func (ec *Client) BlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]*types.Receipt, error) { + var r []*types.Receipt + err := ec.c.CallContext(ctx, &r, "eth_getBlockReceipts", blockNrOrHash.String()) + if err == nil && r == nil { + return nil, ethereum.NotFound + } + return r, err +} + type rpcBlock struct { Hash common.Hash `json:"hash"` Transactions []rpcTransaction `json:"transactions"` @@ -609,7 +619,7 @@ func toCallArg(msg ethereum.CallMsg) interface{} { "to": msg.To, } if len(msg.Data) > 0 { - arg["data"] = hexutil.Bytes(msg.Data) + arg["input"] = hexutil.Bytes(msg.Data) } if msg.Value != nil { arg["value"] = (*hexutil.Big)(msg.Value) diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 385676075655..d2f371093661 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -250,7 +250,7 @@ func generateTestChain() []*types.Block { func TestEthClient(t *testing.T) { backend, chain := newTestBackend(t) - client, _ := backend.Attach() + client := backend.Attach() defer backend.Close() defer client.Close() diff --git a/ethclient/gethclient/gethclient.go b/ethclient/gethclient/gethclient.go index c029611678f0..e2c0ef3ed02e 100644 --- a/ethclient/gethclient/gethclient.go +++ b/ethclient/gethclient/gethclient.go @@ -225,7 +225,7 @@ func toCallArg(msg ethereum.CallMsg) interface{} { "to": msg.To, } if len(msg.Data) > 0 { - arg["data"] = hexutil.Bytes(msg.Data) + arg["input"] = hexutil.Bytes(msg.Data) } if msg.Value != nil { arg["value"] = (*hexutil.Big)(msg.Value) diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go index 296d32848451..5a0f4d2534e3 100644 --- a/ethclient/gethclient/gethclient_test.go +++ b/ethclient/gethclient/gethclient_test.go @@ -94,10 +94,7 @@ func generateTestChain() (*core.Genesis, []*types.Block) { func TestGethClient(t *testing.T) { backend, _ := newTestBackend(t) - client, err := backend.Attach() - if err != nil { - t.Fatal(err) - } + client := backend.Attach() defer backend.Close() defer client.Close() @@ -108,6 +105,9 @@ func TestGethClient(t *testing.T) { { "TestGetProof", func(t *testing.T) { testGetProof(t, client) }, + }, { + "TestGetProofCanonicalizeKeys", + func(t *testing.T) { testGetProofCanonicalizeKeys(t, client) }, }, { "TestGCStats", func(t *testing.T) { testGCStats(t, client) }, @@ -221,6 +221,7 @@ func testGetProof(t *testing.T, client *rpc.Client) { if result.Balance.Cmp(balance) != 0 { t.Fatalf("invalid balance, want: %v got: %v", balance, result.Balance) } + // test storage if len(result.StorageProof) != 1 { t.Fatalf("invalid storage proof, want 1 proof, got %v proof(s)", len(result.StorageProof)) @@ -231,7 +232,37 @@ func testGetProof(t *testing.T, client *rpc.Client) { t.Fatalf("invalid storage proof value, want: %v, got: %v", slotValue, proof.Value.Bytes()) } if proof.Key != testSlot.String() { - t.Fatalf("invalid storage proof key, want: %v, got: %v", testSlot.String(), proof.Key) + t.Fatalf("invalid storage proof key, want: %q, got: %q", testSlot.String(), proof.Key) + } +} + +func testGetProofCanonicalizeKeys(t *testing.T, client *rpc.Client) { + ec := New(client) + + // Tests with non-canon input for storage keys. + // Here we check that the storage key is canonicalized. + result, err := ec.GetProof(context.Background(), testAddr, []string{"0x0dEadbeef"}, nil) + if err != nil { + t.Fatal(err) + } + if result.StorageProof[0].Key != "0xdeadbeef" { + t.Fatalf("wrong storage key encoding in proof: %q", result.StorageProof[0].Key) + } + if result, err = ec.GetProof(context.Background(), testAddr, []string{"0x000deadbeef"}, nil); err != nil { + t.Fatal(err) + } + if result.StorageProof[0].Key != "0xdeadbeef" { + t.Fatalf("wrong storage key encoding in proof: %q", result.StorageProof[0].Key) + } + + // If the requested storage key is 32 bytes long, it will be returned as is. + hashSizedKey := "0x00000000000000000000000000000000000000000000000000000000deadbeef" + result, err = ec.GetProof(context.Background(), testAddr, []string{hashSizedKey}, nil) + if err != nil { + t.Fatal(err) + } + if result.StorageProof[0].Key != hashSizedKey { + t.Fatalf("wrong storage key encoding in proof: %q", result.StorageProof[0].Key) } } @@ -368,17 +399,17 @@ func testCallContract(t *testing.T, client *rpc.Client) { func TestOverrideAccountMarshal(t *testing.T) { om := map[common.Address]OverrideAccount{ - common.Address{0x11}: OverrideAccount{ + {0x11}: { // Zero-valued nonce is not overriddden, but simply dropped by the encoder. Nonce: 0, }, - common.Address{0xaa}: OverrideAccount{ + {0xaa}: { Nonce: 5, }, - common.Address{0xbb}: OverrideAccount{ + {0xbb}: { Code: []byte{1}, }, - common.Address{0xcc}: OverrideAccount{ + {0xcc}: { // 'code', 'balance', 'state' should be set when input is // a non-nil but empty value. Code: []byte{}, diff --git a/ethdb/database.go b/ethdb/database.go index 361218f24742..4d4817daf2e5 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -79,9 +79,10 @@ type AncientReaderOp interface { // AncientRange retrieves multiple items in sequence, starting from the index 'start'. // It will return - // - at most 'count' items, - // - at least 1 item (even if exceeding the maxBytes), but will otherwise - // return as many items as fit into maxBytes. + // - at most 'count' items, + // - if maxBytes is specified: at least 1 item (even if exceeding the maxByteSize), + // but will otherwise return as many items as fit into maxByteSize. + // - if maxBytes is not specified, 'count' items will be returned if they are present AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) // Ancients returns the ancient item numbers in the ancient store. @@ -113,14 +114,14 @@ type AncientWriter interface { // TruncateHead discards all but the first n ancient data from the ancient store. // After the truncation, the latest item can be accessed it item_n-1(start from 0). - TruncateHead(n uint64) error + TruncateHead(n uint64) (uint64, error) // TruncateTail discards the first n ancient data from the ancient store. The already // deleted items are ignored. After the truncation, the earliest item can be accessed // is item_n(start from 0). The deleted items may not be removed from the ancient store // immediately, but only when the accumulated deleted data reach the threshold then // will be removed all together. - TruncateTail(n uint64) error + TruncateTail(n uint64) (uint64, error) // Sync flushes all in-memory ancient store data to disk. Sync() error diff --git a/ethdb/dbtest/testsuite.go b/ethdb/dbtest/testsuite.go index d82dbd699282..0d3d5f5aa694 100644 --- a/ethdb/dbtest/testsuite.go +++ b/ethdb/dbtest/testsuite.go @@ -24,6 +24,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/ethdb" + "golang.org/x/exp/slices" ) // TestDatabaseSuite runs a suite of tests against a KeyValueStore database @@ -526,7 +527,7 @@ func makeDataset(size, ksize, vsize int, order bool) ([][]byte, [][]byte) { vals = append(vals, randBytes(vsize)) } if order { - sort.Slice(keys, func(i, j int) bool { return bytes.Compare(keys[i], keys[j]) < 0 }) + slices.SortFunc(keys, func(a, b []byte) int { return bytes.Compare(a, b) }) } return keys, vals } diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index aec72eb3c26e..a4270bfaf2d1 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -22,7 +22,6 @@ package leveldb import ( "fmt" - "strconv" "strings" "sync" "time" @@ -77,6 +76,8 @@ type Database struct { seekCompGauge metrics.Gauge // Gauge for tracking the number of table compaction caused by read opt manualMemAllocGauge metrics.Gauge // Gauge to track the amount of memory that has been manually allocated (not a part of runtime/GC) + levelsGauge []metrics.Gauge // Gauge for tracking the number of tables in levels + quitLock sync.Mutex // Mutex protecting the quit channel access quitChan chan chan error // Quit channel to stop the metrics collection before closing the database @@ -147,7 +148,7 @@ func NewCustom(file string, namespace string, customize func(options *opt.Option ldb.manualMemAllocGauge = metrics.NewRegisteredGauge(namespace+"memory/manualalloc", nil) // Start up the metrics gathering and return - go ldb.meter(metricsGatheringInterval) + go ldb.meter(metricsGatheringInterval, namespace) return ldb, nil } @@ -245,6 +246,11 @@ func (db *Database) NewSnapshot() (ethdb.Snapshot, error) { // Stat returns a particular internal stat of the database. func (db *Database) Stat(property string) (string, error) { + if property == "" { + property = "leveldb.stats" + } else if !strings.HasPrefix(property, "leveldb.") { + property = "leveldb." + property + } return db.db.GetProperty(property) } @@ -266,122 +272,63 @@ func (db *Database) Path() string { // meter periodically retrieves internal leveldb counters and reports them to // the metrics subsystem. -// -// This is how a LevelDB stats table looks like (currently): -// -// Compactions -// Level | Tables | Size(MB) | Time(sec) | Read(MB) | Write(MB) -// -------+------------+---------------+---------------+---------------+--------------- -// 0 | 0 | 0.00000 | 1.27969 | 0.00000 | 12.31098 -// 1 | 85 | 109.27913 | 28.09293 | 213.92493 | 214.26294 -// 2 | 523 | 1000.37159 | 7.26059 | 66.86342 | 66.77884 -// 3 | 570 | 1113.18458 | 0.00000 | 0.00000 | 0.00000 -// -// This is how the write delay look like (currently): -// DelayN:5 Delay:406.604657ms Paused: false -// -// This is how the iostats look like (currently): -// Read(MB):3895.04860 Write(MB):3654.64712 -func (db *Database) meter(refresh time.Duration) { +func (db *Database) meter(refresh time.Duration, namespace string) { // Create the counters to store current and previous compaction values - compactions := make([][]float64, 2) + compactions := make([][]int64, 2) for i := 0; i < 2; i++ { - compactions[i] = make([]float64, 4) + compactions[i] = make([]int64, 4) } - // Create storage for iostats. - var iostats [2]float64 - - // Create storage and warning log tracer for write delay. - var ( - delaystats [2]int64 - lastWritePaused time.Time - ) - + // Create storages for states and warning log tracer. var ( errc chan error merr error - ) + stats leveldb.DBStats + iostats [2]int64 + delaystats [2]int64 + lastWritePaused time.Time + ) timer := time.NewTimer(refresh) defer timer.Stop() // Iterate ad infinitum and collect the stats for i := 1; errc == nil && merr == nil; i++ { // Retrieve the database stats - stats, err := db.db.GetProperty("leveldb.stats") + // Stats method resets buffers inside therefore it's okay to just pass the struct. + err := db.db.Stats(&stats) if err != nil { db.log.Error("Failed to read database stats", "err", err) merr = err continue } - // Find the compaction table, skip the header - lines := strings.Split(stats, "\n") - for len(lines) > 0 && strings.TrimSpace(lines[0]) != "Compactions" { - lines = lines[1:] - } - if len(lines) <= 3 { - db.log.Error("Compaction leveldbTable not found") - merr = errors.New("compaction leveldbTable not found") - continue - } - lines = lines[3:] - // Iterate over all the leveldbTable rows, and accumulate the entries for j := 0; j < len(compactions[i%2]); j++ { compactions[i%2][j] = 0 } - for _, line := range lines { - parts := strings.Split(line, "|") - if len(parts) != 6 { - break - } - for idx, counter := range parts[2:] { - value, err := strconv.ParseFloat(strings.TrimSpace(counter), 64) - if err != nil { - db.log.Error("Compaction entry parsing failed", "err", err) - merr = err - continue - } - compactions[i%2][idx] += value - } + compactions[i%2][0] = stats.LevelSizes.Sum() + for _, t := range stats.LevelDurations { + compactions[i%2][1] += t.Nanoseconds() } + compactions[i%2][2] = stats.LevelRead.Sum() + compactions[i%2][3] = stats.LevelWrite.Sum() // Update all the requested meters if db.diskSizeGauge != nil { - db.diskSizeGauge.Update(int64(compactions[i%2][0] * 1024 * 1024)) + db.diskSizeGauge.Update(compactions[i%2][0]) } if db.compTimeMeter != nil { - db.compTimeMeter.Mark(int64((compactions[i%2][1] - compactions[(i-1)%2][1]) * 1000 * 1000 * 1000)) + db.compTimeMeter.Mark(compactions[i%2][1] - compactions[(i-1)%2][1]) } if db.compReadMeter != nil { - db.compReadMeter.Mark(int64((compactions[i%2][2] - compactions[(i-1)%2][2]) * 1024 * 1024)) + db.compReadMeter.Mark(compactions[i%2][2] - compactions[(i-1)%2][2]) } if db.compWriteMeter != nil { - db.compWriteMeter.Mark(int64((compactions[i%2][3] - compactions[(i-1)%2][3]) * 1024 * 1024)) - } - // Retrieve the write delay statistic - writedelay, err := db.db.GetProperty("leveldb.writedelay") - if err != nil { - db.log.Error("Failed to read database write delay statistic", "err", err) - merr = err - continue + db.compWriteMeter.Mark(compactions[i%2][3] - compactions[(i-1)%2][3]) } var ( - delayN int64 - delayDuration string - duration time.Duration - paused bool + delayN = int64(stats.WriteDelayCount) + duration = stats.WriteDelayDuration + paused = stats.WritePaused ) - if n, err := fmt.Sscanf(writedelay, "DelayN:%d Delay:%s Paused:%t", &delayN, &delayDuration, &paused); n != 3 || err != nil { - db.log.Error("Write delay statistic not found") - merr = err - continue - } - duration, err = time.ParseDuration(delayDuration) - if err != nil { - db.log.Error("Failed to parse delay duration", "err", err) - merr = err - continue - } if db.writeDelayNMeter != nil { db.writeDelayNMeter.Mark(delayN - delaystats[0]) } @@ -397,60 +344,30 @@ func (db *Database) meter(refresh time.Duration) { } delaystats[0], delaystats[1] = delayN, duration.Nanoseconds() - // Retrieve the database iostats. - ioStats, err := db.db.GetProperty("leveldb.iostats") - if err != nil { - db.log.Error("Failed to read database iostats", "err", err) - merr = err - continue - } - var nRead, nWrite float64 - parts := strings.Split(ioStats, " ") - if len(parts) < 2 { - db.log.Error("Bad syntax of ioStats", "ioStats", ioStats) - merr = fmt.Errorf("bad syntax of ioStats %s", ioStats) - continue - } - if n, err := fmt.Sscanf(parts[0], "Read(MB):%f", &nRead); n != 1 || err != nil { - db.log.Error("Bad syntax of read entry", "entry", parts[0]) - merr = err - continue - } - if n, err := fmt.Sscanf(parts[1], "Write(MB):%f", &nWrite); n != 1 || err != nil { - db.log.Error("Bad syntax of write entry", "entry", parts[1]) - merr = err - continue - } + var ( + nRead = int64(stats.IORead) + nWrite = int64(stats.IOWrite) + ) if db.diskReadMeter != nil { - db.diskReadMeter.Mark(int64((nRead - iostats[0]) * 1024 * 1024)) + db.diskReadMeter.Mark(nRead - iostats[0]) } if db.diskWriteMeter != nil { - db.diskWriteMeter.Mark(int64((nWrite - iostats[1]) * 1024 * 1024)) + db.diskWriteMeter.Mark(nWrite - iostats[1]) } iostats[0], iostats[1] = nRead, nWrite - compCount, err := db.db.GetProperty("leveldb.compcount") - if err != nil { - db.log.Error("Failed to read database iostats", "err", err) - merr = err - continue - } + db.memCompGauge.Update(int64(stats.MemComp)) + db.level0CompGauge.Update(int64(stats.Level0Comp)) + db.nonlevel0CompGauge.Update(int64(stats.NonLevel0Comp)) + db.seekCompGauge.Update(int64(stats.SeekComp)) - var ( - memComp uint32 - level0Comp uint32 - nonLevel0Comp uint32 - seekComp uint32 - ) - if n, err := fmt.Sscanf(compCount, "MemComp:%d Level0Comp:%d NonLevel0Comp:%d SeekComp:%d", &memComp, &level0Comp, &nonLevel0Comp, &seekComp); n != 4 || err != nil { - db.log.Error("Compaction count statistic not found") - merr = err - continue + for i, tables := range stats.LevelTablesCounts { + // Append metrics for additional layers + if i >= len(db.levelsGauge) { + db.levelsGauge = append(db.levelsGauge, metrics.NewRegisteredGauge(namespace+fmt.Sprintf("tables/level%v", i), nil)) + } + db.levelsGauge[i].Update(int64(tables)) } - db.memCompGauge.Update(int64(memComp)) - db.level0CompGauge.Update(int64(level0Comp)) - db.nonlevel0CompGauge.Update(int64(nonLevel0Comp)) - db.seekCompGauge.Update(int64(seekComp)) // Sleep a bit, then repeat the stats collection select { diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go index b43529eddbcc..00c1551989eb 100644 --- a/ethdb/pebble/pebble.go +++ b/ethdb/pebble/pebble.go @@ -27,6 +27,7 @@ import ( "sync/atomic" "time" + "github.com/cockroachdb/errors" "github.com/cockroachdb/pebble" "github.com/cockroachdb/pebble/bloom" "github.com/ethereum/go-ethereum/common" @@ -70,6 +71,8 @@ type Database struct { seekCompGauge metrics.Gauge // Gauge for tracking the number of table compaction caused by read opt manualMemAllocGauge metrics.Gauge // Gauge for tracking amount of non-managed memory currently allocated + levelsGauge []metrics.Gauge // Gauge for tracking the number of tables in levels + quitLock sync.RWMutex // Mutex protecting the quit channel and the closed flag quitChan chan chan error // Quit channel to stop the metrics collection before closing the database closed bool // keep track of whether we're Closed @@ -84,6 +87,8 @@ type Database struct { writeDelayStartTime time.Time // The start time of the latest write stall writeDelayCount atomic.Int64 // Total number of write stall counts writeDelayTime atomic.Int64 // Total time spent in write stalls + + writeOptions *pebble.WriteOptions } func (d *Database) onCompactionBegin(info pebble.CompactionInfo) { @@ -116,9 +121,21 @@ func (d *Database) onWriteStallEnd() { d.writeDelayTime.Add(int64(time.Since(d.writeDelayStartTime))) } +// panicLogger is just a noop logger to disable Pebble's internal logger. +// +// TODO(karalabe): Remove when Pebble sets this as the default. +type panicLogger struct{} + +func (l panicLogger) Infof(format string, args ...interface{}) { +} + +func (l panicLogger) Fatalf(format string, args ...interface{}) { + panic(errors.Errorf("fatal: "+format, args...)) +} + // New returns a wrapped pebble DB object. The namespace is the prefix that the // metrics reporting should use for surfacing internal stats. -func New(file string, cache int, handles int, namespace string, readonly bool) (*Database, error) { +func New(file string, cache int, handles int, namespace string, readonly bool, ephemeral bool) (*Database, error) { // Ensure we have some minimal caching and file guarantees if cache < minCache { cache = minCache @@ -142,9 +159,10 @@ func New(file string, cache int, handles int, namespace string, readonly bool) ( memTableSize = maxMemTableSize } db := &Database{ - fn: file, - log: logger, - quitChan: make(chan chan error), + fn: file, + log: logger, + quitChan: make(chan chan error), + writeOptions: &pebble.WriteOptions{Sync: !ephemeral}, } opt := &pebble.Options{ // Pebble has a single combined cache area and the write @@ -155,7 +173,7 @@ func New(file string, cache int, handles int, namespace string, readonly bool) ( // The size of memory table(as well as the write buffer). // Note, there may have more than two memory tables in the system. - MemTableSize: memTableSize, + MemTableSize: uint64(memTableSize), // MemTableStopWritesThreshold places a hard limit on the size // of the existent MemTables(including the frozen one). @@ -186,6 +204,7 @@ func New(file string, cache int, handles int, namespace string, readonly bool) ( WriteStallBegin: db.onWriteStallBegin, WriteStallEnd: db.onWriteStallEnd, }, + Logger: panicLogger{}, // TODO(karalabe): Delete when this is upstreamed in Pebble } // Disable seek compaction explicitly. Check https://github.com/ethereum/go-ethereum/pull/20130 // for more details. @@ -213,7 +232,7 @@ func New(file string, cache int, handles int, namespace string, readonly bool) ( db.manualMemAllocGauge = metrics.NewRegisteredGauge(namespace+"memory/manualalloc", nil) // Start up the metrics gathering and return - go db.meter(metricsGatheringInterval) + go db.meter(metricsGatheringInterval, namespace) return db, nil } @@ -279,7 +298,7 @@ func (d *Database) Put(key []byte, value []byte) error { if d.closed { return pebble.ErrClosed } - return d.db.Set(key, value, pebble.NoSync) + return d.db.Set(key, value, d.writeOptions) } // Delete removes the key from the key-value store. @@ -302,12 +321,9 @@ func (d *Database) NewBatch() ethdb.Batch { } // NewBatchWithSize creates a write-only database batch with pre-allocated buffer. -// It's not supported by pebble, but pebble has better memory allocation strategy -// which turns out a lot faster than leveldb. It's performant enough to construct -// batch object without any pre-allocated space. -func (d *Database) NewBatchWithSize(_ int) ethdb.Batch { +func (d *Database) NewBatchWithSize(size int) ethdb.Batch { return &batch{ - b: d.db.NewBatch(), + b: d.db.NewBatchWithSize(size), db: d, } } @@ -376,9 +392,12 @@ func upperBound(prefix []byte) (limit []byte) { return limit } -// Stat returns a particular internal stat of the database. +// Stat returns the internal metrics of Pebble in a text format. It's a developer +// method to read everything there is to read independent of Pebble version. +// +// The property is unused in Pebble as there's only one thing to retrieve. func (d *Database) Stat(property string) (string, error) { - return "", nil + return d.db.Metrics().String(), nil } // Compact flattens the underlying data store for the given key range. In essence, @@ -410,7 +429,7 @@ func (d *Database) Path() string { // meter periodically retrieves internal pebble counters and reports them to // the metrics subsystem. -func (d *Database) meter(refresh time.Duration) { +func (d *Database) meter(refresh time.Duration, namespace string) { var errc chan error timer := time.NewTimer(refresh) defer timer.Stop() @@ -433,7 +452,7 @@ func (d *Database) meter(refresh time.Duration) { compRead int64 nWrite int64 - metrics = d.db.Metrics() + stats = d.db.Metrics() compTime = d.compTime.Load() writeDelayCount = d.writeDelayCount.Load() writeDelayTime = d.writeDelayTime.Load() @@ -444,14 +463,14 @@ func (d *Database) meter(refresh time.Duration) { writeDelayCounts[i%2] = writeDelayCount compTimes[i%2] = compTime - for _, levelMetrics := range metrics.Levels { + for _, levelMetrics := range stats.Levels { nWrite += int64(levelMetrics.BytesCompacted) nWrite += int64(levelMetrics.BytesFlushed) compWrite += int64(levelMetrics.BytesCompacted) compRead += int64(levelMetrics.BytesRead) } - nWrite += int64(metrics.WAL.BytesWritten) + nWrite += int64(stats.WAL.BytesWritten) compWrites[i%2] = compWrite compReads[i%2] = compRead @@ -473,7 +492,7 @@ func (d *Database) meter(refresh time.Duration) { d.compWriteMeter.Mark(compWrites[i%2] - compWrites[(i-1)%2]) } if d.diskSizeGauge != nil { - d.diskSizeGauge.Update(int64(metrics.DiskSpaceUsage())) + d.diskSizeGauge.Update(int64(stats.DiskSpaceUsage())) } if d.diskReadMeter != nil { d.diskReadMeter.Mark(0) // pebble doesn't track non-compaction reads @@ -482,12 +501,20 @@ func (d *Database) meter(refresh time.Duration) { d.diskWriteMeter.Mark(nWrites[i%2] - nWrites[(i-1)%2]) } // See https://github.com/cockroachdb/pebble/pull/1628#pullrequestreview-1026664054 - manuallyAllocated := metrics.BlockCache.Size + int64(metrics.MemTable.Size) + int64(metrics.MemTable.ZombieSize) + manuallyAllocated := stats.BlockCache.Size + int64(stats.MemTable.Size) + int64(stats.MemTable.ZombieSize) d.manualMemAllocGauge.Update(manuallyAllocated) - d.memCompGauge.Update(metrics.Flush.Count) + d.memCompGauge.Update(stats.Flush.Count) d.nonlevel0CompGauge.Update(nonLevel0CompCount) d.level0CompGauge.Update(level0CompCount) - d.seekCompGauge.Update(metrics.Compact.ReadCount) + d.seekCompGauge.Update(stats.Compact.ReadCount) + + for i, level := range stats.Levels { + // Append metrics for additional layers + if i >= len(d.levelsGauge) { + d.levelsGauge = append(d.levelsGauge, metrics.NewRegisteredGauge(namespace+fmt.Sprintf("tables/level%v", i), nil)) + } + d.levelsGauge[i].Update(level.NumFiles) + } // Sleep a bit, then repeat the stats collection select { @@ -535,7 +562,7 @@ func (b *batch) Write() error { if b.db.closed { return pebble.ErrClosed } - return b.b.Commit(pebble.NoSync) + return b.b.Commit(b.db.writeOptions) } // Reset resets the batch for reuse. @@ -567,21 +594,24 @@ func (b *batch) Replay(w ethdb.KeyValueWriter) error { // pebbleIterator is a wrapper of underlying iterator in storage engine. // The purpose of this structure is to implement the missing APIs. +// +// The pebble iterator is not thread-safe. type pebbleIterator struct { - iter *pebble.Iterator - moved bool + iter *pebble.Iterator + moved bool + released bool } // NewIterator creates a binary-alphabetical iterator over a subset // of database content with a particular key prefix, starting at a particular // initial key (or after, if it does not exist). func (d *Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator { - iter := d.db.NewIter(&pebble.IterOptions{ + iter, _ := d.db.NewIter(&pebble.IterOptions{ LowerBound: append(prefix, start...), UpperBound: upperBound(prefix), }) iter.First() - return &pebbleIterator{iter: iter, moved: true} + return &pebbleIterator{iter: iter, moved: true, released: false} } // Next moves the iterator to the next key/value pair. It returns whether the @@ -616,4 +646,9 @@ func (iter *pebbleIterator) Value() []byte { // Release releases associated resources. Release should always succeed and can // be called multiple times without causing error. -func (iter *pebbleIterator) Release() { iter.iter.Close() } +func (iter *pebbleIterator) Release() { + if !iter.released { + iter.iter.Close() + iter.released = true + } +} diff --git a/ethdb/remotedb/remotedb.go b/ethdb/remotedb/remotedb.go index 9ce657d78026..c1c803caf2b9 100644 --- a/ethdb/remotedb/remotedb.go +++ b/ethdb/remotedb/remotedb.go @@ -98,11 +98,11 @@ func (db *Database) ModifyAncients(f func(ethdb.AncientWriteOp) error) (int64, e panic("not supported") } -func (db *Database) TruncateHead(n uint64) error { +func (db *Database) TruncateHead(n uint64) (uint64, error) { panic("not supported") } -func (db *Database) TruncateTail(n uint64) error { +func (db *Database) TruncateTail(n uint64) (uint64, error) { panic("not supported") } diff --git a/event/feed.go b/event/feed.go index 33dafe58867e..d94bd820f0c5 100644 --- a/event/feed.go +++ b/event/feed.go @@ -57,7 +57,8 @@ func (e feedTypeError) Error() string { return "event: wrong type in " + e.op + " got " + e.got.String() + ", want " + e.want.String() } -func (f *Feed) init() { +func (f *Feed) init(etype reflect.Type) { + f.etype = etype f.removeSub = make(chan interface{}) f.sendLock = make(chan struct{}, 1) f.sendLock <- struct{}{} @@ -70,8 +71,6 @@ func (f *Feed) init() { // The channel should have ample buffer space to avoid blocking other subscribers. // Slow subscribers are not dropped. func (f *Feed) Subscribe(channel interface{}) Subscription { - f.once.Do(f.init) - chanval := reflect.ValueOf(channel) chantyp := chanval.Type() if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.SendDir == 0 { @@ -79,11 +78,13 @@ func (f *Feed) Subscribe(channel interface{}) Subscription { } sub := &feedSub{feed: f, channel: chanval, err: make(chan error, 1)} - f.mu.Lock() - defer f.mu.Unlock() - if !f.typecheck(chantyp.Elem()) { + f.once.Do(func() { f.init(chantyp.Elem()) }) + if f.etype != chantyp.Elem() { panic(feedTypeError{op: "Subscribe", got: chantyp, want: reflect.ChanOf(reflect.SendDir, f.etype)}) } + + f.mu.Lock() + defer f.mu.Unlock() // Add the select case to the inbox. // The next Send will add it to f.sendCases. cas := reflect.SelectCase{Dir: reflect.SelectSend, Chan: chanval} @@ -91,15 +92,6 @@ func (f *Feed) Subscribe(channel interface{}) Subscription { return sub } -// note: callers must hold f.mu -func (f *Feed) typecheck(typ reflect.Type) bool { - if f.etype == nil { - f.etype = typ - return true - } - return f.etype == typ -} - func (f *Feed) remove(sub *feedSub) { // Delete from inbox first, which covers channels // that have not been added to f.sendCases yet. @@ -128,19 +120,17 @@ func (f *Feed) remove(sub *feedSub) { func (f *Feed) Send(value interface{}) (nsent int) { rvalue := reflect.ValueOf(value) - f.once.Do(f.init) + f.once.Do(func() { f.init(rvalue.Type()) }) + if f.etype != rvalue.Type() { + panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype}) + } + <-f.sendLock // Add new cases from the inbox after taking the send lock. f.mu.Lock() f.sendCases = append(f.sendCases, f.inbox...) f.inbox = nil - - if !f.typecheck(rvalue.Type()) { - f.sendLock <- struct{}{} - f.mu.Unlock() - panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype}) - } f.mu.Unlock() // Set the sent value on all channels. diff --git a/event/multisub.go b/event/multisub.go new file mode 100644 index 000000000000..5c8d2df48cc4 --- /dev/null +++ b/event/multisub.go @@ -0,0 +1,50 @@ +// Copyright 2023 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 event + +// JoinSubscriptions joins multiple subscriptions to be able to track them as +// one entity and collectively cancel them of consume any errors from them. +func JoinSubscriptions(subs ...Subscription) Subscription { + return NewSubscription(func(unsubbed <-chan struct{}) error { + // Unsubscribe all subscriptions before returning + defer func() { + for _, sub := range subs { + sub.Unsubscribe() + } + }() + // Wait for an error on any of the subscriptions and propagate up + errc := make(chan error, len(subs)) + for i := range subs { + go func(sub Subscription) { + select { + case err := <-sub.Err(): + if err != nil { + errc <- err + } + case <-unsubbed: + } + }(subs[i]) + } + + select { + case err := <-errc: + return err + case <-unsubbed: + return nil + } + }) +} diff --git a/event/multisub_test.go b/event/multisub_test.go new file mode 100644 index 000000000000..c92bcfae9bcc --- /dev/null +++ b/event/multisub_test.go @@ -0,0 +1,175 @@ +// Copyright 2023 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 event + +import ( + "testing" + "time" +) + +func TestMultisub(t *testing.T) { + // Create a double subscription and ensure events propagate through + var ( + feed1 Feed + feed2 Feed + ) + sink1 := make(chan int, 1) + sink2 := make(chan int, 1) + + sub1 := feed1.Subscribe(sink1) + sub2 := feed2.Subscribe(sink2) + + sub := JoinSubscriptions(sub1, sub2) + + feed1.Send(1) + select { + case n := <-sink1: + if n != 1 { + t.Errorf("sink 1 delivery mismatch: have %d, want %d", n, 1) + } + default: + t.Error("sink 1 missing delivery") + } + + feed2.Send(2) + select { + case n := <-sink2: + if n != 2 { + t.Errorf("sink 2 delivery mismatch: have %d, want %d", n, 2) + } + default: + t.Error("sink 2 missing delivery") + } + // Unsubscribe and ensure no more events are delivered + sub.Unsubscribe() + select { + case <-sub.Err(): + case <-time.After(50 * time.Millisecond): + t.Error("multisub didn't propagate closure") + } + + feed1.Send(11) + select { + case n := <-sink1: + t.Errorf("sink 1 unexpected delivery: %d", n) + default: + } + + feed2.Send(22) + select { + case n := <-sink2: + t.Errorf("sink 2 unexpected delivery: %d", n) + default: + } +} + +func TestMutisubPartialUnsubscribe(t *testing.T) { + // Create a double subscription but terminate one half, ensuring no error + // is propagated yet up to the outer subscription + var ( + feed1 Feed + feed2 Feed + ) + sink1 := make(chan int, 1) + sink2 := make(chan int, 1) + + sub1 := feed1.Subscribe(sink1) + sub2 := feed2.Subscribe(sink2) + + sub := JoinSubscriptions(sub1, sub2) + + sub1.Unsubscribe() + select { + case <-sub.Err(): + t.Error("multisub propagated closure") + case <-time.After(50 * time.Millisecond): + } + // Ensure that events cross only the second feed + feed1.Send(1) + select { + case n := <-sink1: + t.Errorf("sink 1 unexpected delivery: %d", n) + default: + } + + feed2.Send(2) + select { + case n := <-sink2: + if n != 2 { + t.Errorf("sink 2 delivery mismatch: have %d, want %d", n, 2) + } + default: + t.Error("sink 2 missing delivery") + } + // Unsubscribe and ensure no more events are delivered + sub.Unsubscribe() + select { + case <-sub.Err(): + case <-time.After(50 * time.Millisecond): + t.Error("multisub didn't propagate closure") + } + + feed1.Send(11) + select { + case n := <-sink1: + t.Errorf("sink 1 unexpected delivery: %d", n) + default: + } + + feed2.Send(22) + select { + case n := <-sink2: + t.Errorf("sink 2 unexpected delivery: %d", n) + default: + } +} + +func TestMultisubFullUnsubscribe(t *testing.T) { + // Create a double subscription and terminate the multi sub, ensuring an + // error is propagated up. + var ( + feed1 Feed + feed2 Feed + ) + sink1 := make(chan int, 1) + sink2 := make(chan int, 1) + + sub1 := feed1.Subscribe(sink1) + sub2 := feed2.Subscribe(sink2) + + sub := JoinSubscriptions(sub1, sub2) + sub.Unsubscribe() + select { + case <-sub.Err(): + case <-time.After(50 * time.Millisecond): + t.Error("multisub didn't propagate closure") + } + // Ensure no more events are delivered + feed1.Send(1) + select { + case n := <-sink1: + t.Errorf("sink 1 unexpected delivery: %d", n) + default: + } + + feed2.Send(2) + select { + case n := <-sink2: + t.Errorf("sink 2 unexpected delivery: %d", n) + default: + } +} diff --git a/go.mod b/go.mod index be0cbee2552e..47a3a5688378 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,10 @@ module github.com/ethereum/go-ethereum -go 1.19 +go 1.20 require ( github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0 + github.com/Microsoft/go-winio v0.6.1 github.com/VictoriaMetrics/fastcache v1.6.0 github.com/aws/aws-sdk-go-v2 v1.2.0 github.com/aws/aws-sdk-go-v2/config v1.1.1 @@ -12,43 +13,45 @@ require ( github.com/btcsuite/btcd/btcec/v2 v2.2.0 github.com/cespare/cp v0.1.0 github.com/cloudflare/cloudflare-go v0.14.0 - github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 + github.com/cockroachdb/errors v1.8.1 + github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 github.com/consensys/gnark-crypto v0.10.0 - github.com/crate-crypto/go-kzg-4844 v0.2.0 + github.com/crate-crypto/go-kzg-4844 v0.7.0 github.com/davecgh/go-spew v1.1.1 github.com/deckarep/golang-set/v2 v2.1.0 - github.com/docker/docker v1.6.2 - github.com/dop251/goja v0.0.0-20230122112309-96b1610dd4f7 - github.com/ethereum/c-kzg-4844 v0.2.0 + github.com/docker/docker v24.0.5+incompatible + github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 + github.com/ethereum/c-kzg-4844 v0.4.0 github.com/fatih/color v1.7.0 github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 github.com/fsnotify/fsnotify v1.6.0 github.com/gammazero/deque v0.2.1 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff - github.com/gballet/go-verkle v0.0.0-20220902153445-097bd83b7732 + github.com/gballet/go-verkle v0.0.0-20230607174250-df487255f46b github.com/go-stack/stack v1.8.1 github.com/gofrs/flock v0.8.1 github.com/golang-jwt/jwt/v4 v4.3.0 - github.com/golang/protobuf v1.5.2 + github.com/golang/protobuf v1.5.3 github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa github.com/google/uuid v1.3.0 github.com/gorilla/websocket v1.4.2 github.com/graph-gophers/graphql-go v1.3.0 github.com/hashicorp/go-bexpr v0.1.10 + github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 github.com/holiman/bloomfilter/v2 v2.0.3 - github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c - github.com/huin/goupnp v1.0.3 + github.com/holiman/uint256 v1.2.3 + github.com/huin/goupnp v1.3.0 github.com/influxdata/influxdb-client-go/v2 v2.4.0 github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c github.com/jackpal/go-nat-pmp v1.0.2 - github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e + github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 github.com/julienschmidt/httprouter v1.3.0 github.com/karalabe/usb v0.0.2 github.com/kylelemons/godebug v1.1.0 github.com/mattn/go-colorable v0.1.13 - github.com/mattn/go-isatty v0.0.16 + github.com/mattn/go-isatty v0.0.17 github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 github.com/olekukonko/tablewriter v0.0.5 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 @@ -58,76 +61,78 @@ require ( github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible github.com/spf13/pflag v1.0.5 github.com/status-im/keycard-go v0.2.0 - github.com/stretchr/testify v1.8.1 - github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b + github.com/stretchr/testify v1.8.2 + github.com/supranational/blst v0.3.11 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 github.com/tyler-smith/go-bip39 v1.1.0 - github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa - golang.org/x/crypto v0.1.0 - golang.org/x/exp v0.0.0-20230206171751-46f607a40771 - golang.org/x/sync v0.1.0 - golang.org/x/sys v0.7.0 - golang.org/x/text v0.8.0 - golang.org/x/time v0.0.0-20220922220347-f3bd1da661af - golang.org/x/tools v0.7.0 + github.com/urfave/cli/v2 v2.25.7 + go.uber.org/automaxprocs v1.5.2 + golang.org/x/crypto v0.13.0 + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 + golang.org/x/sync v0.3.0 + golang.org/x/sys v0.12.0 + golang.org/x/text v0.13.0 + golang.org/x/time v0.3.0 + golang.org/x/tools v0.13.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0 - gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3 // indirect - github.com/DataDog/zstd v1.5.2 // indirect - github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect + github.com/DataDog/zstd v1.4.5 // indirect + github.com/StackExchange/wmi v1.2.1 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.0.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.0.2 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.1.1 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.1.1 // indirect github.com/aws/smithy-go v1.1.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.7.0 // indirect + github.com/bits-and-blooms/bitset v1.5.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cockroachdb/errors v1.9.1 // indirect - github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/redact v1.1.3 // indirect + github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect + github.com/cockroachdb/redact v1.0.8 // indirect + github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect - github.com/crate-crypto/go-ipa v0.0.0-20220523130400-f11357ae11c7 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20230601170251-1830d0757c80 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect - github.com/deepmap/oapi-codegen v1.8.2 // indirect + github.com/deepmap/oapi-codegen v1.6.0 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect - github.com/getsentry/sentry-go v0.18.0 // indirect - github.com/go-ole/go-ole v1.2.1 // indirect + github.com/go-ole/go-ole v1.2.5 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect + github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect github.com/kilic/bls12-381 v0.1.0 // indirect github.com/klauspost/compress v1.15.15 // indirect - github.com/kr/pretty v0.3.1 // indirect + github.com/kr/pretty v0.3.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/naoina/go-stringutil v0.1.0 // indirect github.com/opentracing/opentracing-go v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.39.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect - github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/prometheus/client_golang v1.12.0 // indirect + github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/rogpeppe/go-internal v1.6.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/tklauser/go-sysconf v0.3.5 // indirect - github.com/tklauser/numcpus v0.2.2 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect - google.golang.org/protobuf v1.28.1 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.15.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gotest.tools/v3 v3.5.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index d5ba9ce86df3..fb141907d971 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,36 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1 h1:qoVeMsc9/fh/yhxVaA0obYjVH/oI/ihrOoMwsLS9KSA= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.21.1/go.mod h1:fBF9PQNqB8scdgpZ3ufzaLntG0AG7C1WjPMsiFOmfHM= @@ -7,18 +39,27 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSu github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0 h1:Px2UA+2RvSSvv+RvJNuUB6n7rs5Wsel4dXLe90Um2n4= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= -github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= -github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= -github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= -github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= +github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= +github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o= github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -41,10 +82,12 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.1.1/go.mod h1:Wi0EBZwiz/K44YliU0EKxq github.com/aws/smithy-go v1.1.0 h1:D6CSsM3gdxaGaqXnPgOBCeL6Mophqzu7KJOu7zW78sU= github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.7.0 h1:YjAGVd3XmtK9ktAbX8Zg2g2PwLIMjGREZJHlV4j7NEo= -github.com/bits-and-blooms/bitset v1.7.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8= +github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= @@ -52,23 +95,34 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.14.0 h1:gFqGlGl/5f9UGXAaKapCGUfaTCgRKKnzu2VvzMZlOFA= github.com/cloudflare/cloudflare-go v0.14.0/go.mod h1:EnwdgGMaFOruiPZRFSgn+TsQ3hQ7C/YWzIGLeu5c304= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= -github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= -github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= -github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= -github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= -github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811/go.mod h1:Nb5lgvnQ2+oGlE/EyZy4+2/CxRh9KfvCXnag1vtpxVM= -github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= -github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v1.0.0/go.mod h1:5Ib8Meh+jk1RlHIXej6Pzevx/NLlNvQB9pmSBZErGA4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/errors v1.6.1/go.mod h1:tm6FTP5G81vwJ5lC0SizQo374JNCOPrHyXGitRJoDqM= +github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= +github.com/cockroachdb/errors v1.8.1/go.mod h1:qGwQn6JmZ+oMjuLwjWzUNqblqk0xl4CVV3SQbGwK7Ac= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= +github.com/cockroachdb/redact v1.0.8 h1:8QG/764wK+vmEYoOlfobpe12EQcS81ukx/a4hdVMxNw= +github.com/cockroachdb/redact v1.0.8/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2 h1:IKgmqgMQlVJIZj19CdocBeSfSaiCbEBZGKODaixqtHM= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= @@ -81,10 +135,10 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crate-crypto/go-ipa v0.0.0-20220523130400-f11357ae11c7 h1:6IrxszG5G+O7zhtkWxq6+unVvnrm1fqV2Pe+T95DUzw= -github.com/crate-crypto/go-ipa v0.0.0-20220523130400-f11357ae11c7/go.mod h1:gFnFS95y8HstDP6P9pPwzrxOOC5TRDkwbM+ao15ChAI= -github.com/crate-crypto/go-kzg-4844 v0.2.0 h1:UVuHOE+5tIWrim4zf/Xaa43+MIsDCPyW76QhUpiMGj4= -github.com/crate-crypto/go-kzg-4844 v0.2.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4= +github.com/crate-crypto/go-ipa v0.0.0-20230601170251-1830d0757c80 h1:DuBDHVjgGMPki7bAyh91+3cF1Vh34sAEdH8JQgbc2R0= +github.com/crate-crypto/go-ipa v0.0.0-20230601170251-1830d0757c80/go.mod h1:gzbVz57IDJgQ9rLQwfSk696JGWof8ftznEL9GoAv3NI= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -96,9 +150,8 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= -github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= -github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= @@ -108,22 +161,22 @@ github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnm github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/docker v1.6.2 h1:HlFGsy+9/xrgMmhmN+NGhCc5SHGJ7I+kHosRR1xc/aI= -github.com/docker/docker v1.6.2/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY= +github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= -github.com/dop251/goja v0.0.0-20230122112309-96b1610dd4f7 h1:kgvzE5wLsLa7XKfV85VZl40QXaMCaeFtHpPwJ8fhotY= -github.com/dop251/goja v0.0.0-20230122112309-96b1610dd4f7/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs= +github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 h1:qwcF+vdFrvPSEUDSX5RVoRccG8a5DhOdWdQ4zN62zzo= +github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/ethereum/c-kzg-4844 v0.2.0 h1:+cUvymlnoDDQgMInp25Bo3OmLajmmY8mLJ/tLjqd77Q= -github.com/ethereum/c-kzg-4844 v0.2.0/go.mod h1:WI2Nd82DMZAAZI1wV2neKGost9EKjvbpQR9OqE5Qqa8= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -132,6 +185,7 @@ github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e h1:bBLctRc7kr01YGvaDf github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -143,27 +197,33 @@ github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILD github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= -github.com/gballet/go-verkle v0.0.0-20220902153445-097bd83b7732 h1:AB7YjNrzlVHsYz06zCULVV2zYCEft82P86dSmtwxKL0= -github.com/gballet/go-verkle v0.0.0-20220902153445-097bd83b7732/go.mod h1:o/XfIXWi4/GqbQirfRm5uTbXMG5NpqxkxblnbZ+QM9I= +github.com/gballet/go-verkle v0.0.0-20230607174250-df487255f46b h1:vMT47RYsrftsHSTQhqXwC3BYflo38OLC3Y4LtXtLyU0= +github.com/gballet/go-verkle v0.0.0-20230607174250-df487255f46b/go.mod h1:CDncRYVRSDqwakm282WEkjfaAj1hxU/v5RXxk5nXOiI= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= -github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= -github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= -github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= -github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= @@ -172,19 +232,30 @@ github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/E github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -193,35 +264,54 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa h1:Q75Upo5UN4JbPFURXZ8nLKYUvF85dyFRop/vQ0Rv+64= github.com/google/gofuzz v1.1.1-0.20200604201612-c04b05f3adfa/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= @@ -229,71 +319,85 @@ github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLt github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw= +github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c h1:DZfsyhDK1hnSS5lH8l+JggqzEleHteTYfutAiVlSUM8= -github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= +github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ= -github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y= -github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= -github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/hydrogen18/memlistener v0.0.0-20141126152155-54553eb933fb/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k= github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSHzRbhzK8RdXOsAdfDgO49TtqC1oZ+acxPrkfTxcCs= github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 h1:vilfsDSy7TDxedi9gyBkMvAirat/oRcL0lFdJBf6tdM= -github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= -github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= -github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= +github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= -github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+Uj2hWd8aOlwPmoZ+CITRFrdit+sDGfAg8U= -github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= +github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= +github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/karalabe/usb v0.0.2 h1:M6QQBNxF+CQ8OFvxrT90BA0qBOXymndZnk5q235mFc4= github.com/karalabe/usb v0.0.2/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= -github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= -github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= -github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= -github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= -github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= +github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk= +github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U= +github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw= +github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= -github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= @@ -304,23 +408,24 @@ github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIG github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mediocregopher/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg= +github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -335,23 +440,25 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM= +github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= @@ -364,27 +471,41 @@ github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQm github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= -github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.0 h1:C+UIj/QWtmqY13Arb8kwMt5j34/0Z2iKamrJ+ryC0Gg= +github.com/prometheus/client_golang v1.12.0/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= -github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a h1:CmF68hwI0XsOQ5UwlBopMi2Ow4Pbg32akc4KIVCOm+Y= +github.com/prometheus/client_model v0.2.1-0.20210607210712-147c58e9608a/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7 h1:cZC+usqsYgHtlBaGulVnZ1hfKAi8iWtujBnRLQE698c= github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7/go.mod h1:IToEjHuttnUzwZI5KBSM/LOOW3qLbbrHOEfp3SbECGY= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -392,11 +513,14 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -410,6 +534,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -419,25 +544,23 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b h1:u49mjRnygnB34h8OKbnNJFVUtWSKIKb1KukdV8bILUM= -github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= +github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= -github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= -github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q= -github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= @@ -454,49 +577,98 @@ github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmv github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME= +go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190909091759-094676da4a83/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -504,111 +676,238 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y= -golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -617,11 +916,13 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -631,28 +932,35 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= -gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/graphql/graphql.go b/graphql/graphql.go index 0b64f746101d..ae4e5314d480 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -31,7 +31,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -184,6 +184,31 @@ func (at *AccessTuple) StorageKeys(ctx context.Context) []common.Hash { return at.storageKeys } +// Withdrawal represents a withdrawal of value from the beacon chain +// by a validator. For details see EIP-4895. +type Withdrawal struct { + index uint64 + validator uint64 + address common.Address + amount uint64 +} + +func (w *Withdrawal) Index(ctx context.Context) hexutil.Uint64 { + return hexutil.Uint64(w.index) +} + +func (w *Withdrawal) Validator(ctx context.Context) hexutil.Uint64 { + return hexutil.Uint64(w.validator) +} + +func (w *Withdrawal) Address(ctx context.Context) common.Address { + return w.address +} + +func (w *Withdrawal) Amount(ctx context.Context) hexutil.Uint64 { + return hexutil.Uint64(w.amount) +} + // Transaction represents an Ethereum transaction. // backend and hash are mandatory; all others will be fetched when required. type Transaction struct { @@ -198,11 +223,11 @@ type Transaction struct { // resolve returns the internal transaction object, fetching it if needed. // It also returns the block the tx belongs to, unless it is a pending tx. -func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, *Block, error) { +func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, *Block) { t.mu.Lock() defer t.mu.Unlock() if t.tx != nil { - return t.tx, t.block, nil + return t.tx, t.block } // Try to return an already finalized transaction tx, blockHash, _, index, err := t.r.backend.GetTransaction(ctx, t.hash) @@ -215,58 +240,56 @@ func (t *Transaction) resolve(ctx context.Context) (*types.Transaction, *Block, hash: blockHash, } t.index = index - return t.tx, t.block, nil + return t.tx, t.block } // No finalized transaction, try to retrieve it from the pool t.tx = t.r.backend.GetPoolTransaction(t.hash) - return t.tx, nil, nil + return t.tx, nil } func (t *Transaction) Hash(ctx context.Context) common.Hash { return t.hash } -func (t *Transaction) InputData(ctx context.Context) (hexutil.Bytes, error) { - tx, _, err := t.resolve(ctx) - if err != nil || tx == nil { - return hexutil.Bytes{}, err +func (t *Transaction) InputData(ctx context.Context) hexutil.Bytes { + tx, _ := t.resolve(ctx) + if tx == nil { + return hexutil.Bytes{} } - return tx.Data(), nil + return tx.Data() } -func (t *Transaction) Gas(ctx context.Context) (hexutil.Uint64, error) { - tx, _, err := t.resolve(ctx) - if err != nil || tx == nil { - return 0, err +func (t *Transaction) Gas(ctx context.Context) hexutil.Uint64 { + tx, _ := t.resolve(ctx) + if tx == nil { + return 0 } - return hexutil.Uint64(tx.Gas()), nil + return hexutil.Uint64(tx.Gas()) } -func (t *Transaction) GasPrice(ctx context.Context) (hexutil.Big, error) { - tx, block, err := t.resolve(ctx) - if err != nil || tx == nil { - return hexutil.Big{}, err +func (t *Transaction) GasPrice(ctx context.Context) hexutil.Big { + tx, block := t.resolve(ctx) + if tx == nil { + return hexutil.Big{} } switch tx.Type() { - case types.AccessListTxType: - return hexutil.Big(*tx.GasPrice()), nil case types.DynamicFeeTxType: if block != nil { if baseFee, _ := block.BaseFeePerGas(ctx); baseFee != nil { - // price = min(tip, gasFeeCap - baseFee) + baseFee - return (hexutil.Big)(*math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee.ToInt()), tx.GasFeeCap())), nil + // price = min(gasTipCap + baseFee, gasFeeCap) + return (hexutil.Big)(*math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee.ToInt()), tx.GasFeeCap())) } } - return hexutil.Big(*tx.GasPrice()), nil + return hexutil.Big(*tx.GasPrice()) default: - return hexutil.Big(*tx.GasPrice()), nil + return hexutil.Big(*tx.GasPrice()) } } func (t *Transaction) EffectiveGasPrice(ctx context.Context) (*hexutil.Big, error) { - tx, block, err := t.resolve(ctx) - if err != nil || tx == nil { - return nil, err + tx, block := t.resolve(ctx) + if tx == nil { + return nil, nil } // Pending tx if block == nil { @@ -285,40 +308,56 @@ func (t *Transaction) EffectiveGasPrice(ctx context.Context) (*hexutil.Big, erro return (*hexutil.Big)(math.BigMin(new(big.Int).Add(tx.GasTipCap(), header.BaseFee), tx.GasFeeCap())), nil } -func (t *Transaction) MaxFeePerGas(ctx context.Context) (*hexutil.Big, error) { - tx, _, err := t.resolve(ctx) - if err != nil || tx == nil { - return nil, err +func (t *Transaction) MaxFeePerGas(ctx context.Context) *hexutil.Big { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil } switch tx.Type() { - case types.AccessListTxType: - return nil, nil - case types.DynamicFeeTxType: - return (*hexutil.Big)(tx.GasFeeCap()), nil + case types.DynamicFeeTxType, types.BlobTxType: + return (*hexutil.Big)(tx.GasFeeCap()) default: - return nil, nil + return nil } } -func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, error) { - tx, _, err := t.resolve(ctx) - if err != nil || tx == nil { - return nil, err +func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) *hexutil.Big { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil } switch tx.Type() { - case types.AccessListTxType: - return nil, nil - case types.DynamicFeeTxType: - return (*hexutil.Big)(tx.GasTipCap()), nil + case types.DynamicFeeTxType, types.BlobTxType: + return (*hexutil.Big)(tx.GasTipCap()) default: - return nil, nil + return nil } } +func (t *Transaction) MaxFeePerBlobGas(ctx context.Context) *hexutil.Big { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil + } + return (*hexutil.Big)(tx.BlobGasFeeCap()) +} + +func (t *Transaction) BlobVersionedHashes(ctx context.Context) *[]common.Hash { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil + } + if tx.Type() != types.BlobTxType { + return nil + } + blobHashes := tx.BlobHashes() + return &blobHashes +} + func (t *Transaction) EffectiveTip(ctx context.Context) (*hexutil.Big, error) { - tx, block, err := t.resolve(ctx) - if err != nil || tx == nil { - return nil, err + tx, block := t.resolve(ctx) + if tx == nil { + return nil, nil } // Pending tx if block == nil { @@ -340,9 +379,9 @@ func (t *Transaction) EffectiveTip(ctx context.Context) (*hexutil.Big, error) { } func (t *Transaction) Value(ctx context.Context) (hexutil.Big, error) { - tx, _, err := t.resolve(ctx) - if err != nil || tx == nil { - return hexutil.Big{}, err + tx, _ := t.resolve(ctx) + if tx == nil { + return hexutil.Big{}, nil } if tx.Value() == nil { return hexutil.Big{}, fmt.Errorf("invalid transaction value %x", t.hash) @@ -350,34 +389,34 @@ func (t *Transaction) Value(ctx context.Context) (hexutil.Big, error) { return hexutil.Big(*tx.Value()), nil } -func (t *Transaction) Nonce(ctx context.Context) (hexutil.Uint64, error) { - tx, _, err := t.resolve(ctx) - if err != nil || tx == nil { - return 0, err +func (t *Transaction) Nonce(ctx context.Context) hexutil.Uint64 { + tx, _ := t.resolve(ctx) + if tx == nil { + return 0 } - return hexutil.Uint64(tx.Nonce()), nil + return hexutil.Uint64(tx.Nonce()) } -func (t *Transaction) To(ctx context.Context, args BlockNumberArgs) (*Account, error) { - tx, _, err := t.resolve(ctx) - if err != nil || tx == nil { - return nil, err +func (t *Transaction) To(ctx context.Context, args BlockNumberArgs) *Account { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil } to := tx.To() if to == nil { - return nil, nil + return nil } return &Account{ r: t.r, address: *to, blockNrOrHash: args.NumberOrLatest(), - }, nil + } } -func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) (*Account, error) { - tx, _, err := t.resolve(ctx) - if err != nil || tx == nil { - return nil, err +func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) *Account { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil } signer := types.LatestSigner(t.r.backend.ChainConfig()) from, _ := types.Sender(signer, tx) @@ -385,36 +424,27 @@ func (t *Transaction) From(ctx context.Context, args BlockNumberArgs) (*Account, r: t.r, address: from, blockNrOrHash: args.NumberOrLatest(), - }, nil + } } -func (t *Transaction) Block(ctx context.Context) (*Block, error) { - _, block, err := t.resolve(ctx) - if err != nil { - return nil, err - } - return block, nil +func (t *Transaction) Block(ctx context.Context) *Block { + _, block := t.resolve(ctx) + return block } -func (t *Transaction) Index(ctx context.Context) (*hexutil.Uint64, error) { - _, block, err := t.resolve(ctx) - if err != nil { - return nil, err - } +func (t *Transaction) Index(ctx context.Context) *hexutil.Uint64 { + _, block := t.resolve(ctx) // Pending tx if block == nil { - return nil, nil + return nil } index := hexutil.Uint64(t.index) - return &index, nil + return &index } // getReceipt returns the receipt associated with this transaction, if any. func (t *Transaction) getReceipt(ctx context.Context) (*types.Receipt, error) { - _, block, err := t.resolve(ctx) - if err != nil { - return nil, err - } + _, block := t.resolve(ctx) // Pending tx if block == nil { return nil, nil @@ -456,6 +486,40 @@ func (t *Transaction) CumulativeGasUsed(ctx context.Context) (*hexutil.Uint64, e return &ret, nil } +func (t *Transaction) BlobGasUsed(ctx context.Context) (*hexutil.Uint64, error) { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil, nil + } + if tx.Type() != types.BlobTxType { + return nil, nil + } + + receipt, err := t.getReceipt(ctx) + if err != nil || receipt == nil { + return nil, err + } + ret := hexutil.Uint64(receipt.BlobGasUsed) + return &ret, nil +} + +func (t *Transaction) BlobGasPrice(ctx context.Context) (*hexutil.Big, error) { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil, nil + } + if tx.Type() != types.BlobTxType { + return nil, nil + } + + receipt, err := t.getReceipt(ctx) + if err != nil || receipt == nil { + return nil, err + } + ret := (*hexutil.Big)(receipt.BlobGasPrice) + return ret, nil +} + func (t *Transaction) CreatedContract(ctx context.Context, args BlockNumberArgs) (*Account, error) { receipt, err := t.getReceipt(ctx) if err != nil || receipt == nil || receipt.ContractAddress == (common.Address{}) { @@ -469,10 +533,7 @@ func (t *Transaction) CreatedContract(ctx context.Context, args BlockNumberArgs) } func (t *Transaction) Logs(ctx context.Context) (*[]*Log, error) { - _, block, err := t.resolve(ctx) - if err != nil { - return nil, err - } + _, block := t.resolve(ctx) // Pending tx if block == nil { return nil, nil @@ -508,19 +569,16 @@ func (t *Transaction) getLogs(ctx context.Context, hash common.Hash) (*[]*Log, e return &ret, nil } -func (t *Transaction) Type(ctx context.Context) (*hexutil.Uint64, error) { - tx, _, err := t.resolve(ctx) - if err != nil { - return nil, err - } +func (t *Transaction) Type(ctx context.Context) *hexutil.Uint64 { + tx, _ := t.resolve(ctx) txType := hexutil.Uint64(tx.Type()) - return &txType, nil + return &txType } -func (t *Transaction) AccessList(ctx context.Context) (*[]*AccessTuple, error) { - tx, _, err := t.resolve(ctx) - if err != nil || tx == nil { - return nil, err +func (t *Transaction) AccessList(ctx context.Context) *[]*AccessTuple { + tx, _ := t.resolve(ctx) + if tx == nil { + return nil } accessList := tx.AccessList() ret := make([]*AccessTuple, 0, len(accessList)) @@ -530,40 +588,50 @@ func (t *Transaction) AccessList(ctx context.Context) (*[]*AccessTuple, error) { storageKeys: al.StorageKeys, }) } - return &ret, nil + return &ret } -func (t *Transaction) R(ctx context.Context) (hexutil.Big, error) { - tx, _, err := t.resolve(ctx) - if err != nil || tx == nil { - return hexutil.Big{}, err +func (t *Transaction) R(ctx context.Context) hexutil.Big { + tx, _ := t.resolve(ctx) + if tx == nil { + return hexutil.Big{} } _, r, _ := tx.RawSignatureValues() - return hexutil.Big(*r), nil + return hexutil.Big(*r) } -func (t *Transaction) S(ctx context.Context) (hexutil.Big, error) { - tx, _, err := t.resolve(ctx) - if err != nil || tx == nil { - return hexutil.Big{}, err +func (t *Transaction) S(ctx context.Context) hexutil.Big { + tx, _ := t.resolve(ctx) + if tx == nil { + return hexutil.Big{} } _, _, s := tx.RawSignatureValues() - return hexutil.Big(*s), nil + return hexutil.Big(*s) } -func (t *Transaction) V(ctx context.Context) (hexutil.Big, error) { - tx, _, err := t.resolve(ctx) - if err != nil || tx == nil { - return hexutil.Big{}, err +func (t *Transaction) V(ctx context.Context) hexutil.Big { + tx, _ := t.resolve(ctx) + if tx == nil { + return hexutil.Big{} } v, _, _ := tx.RawSignatureValues() - return hexutil.Big(*v), nil + return hexutil.Big(*v) +} + +func (t *Transaction) YParity(ctx context.Context) (*hexutil.Uint64, error) { + tx, _ := t.resolve(ctx) + if tx == nil || tx.Type() == types.LegacyTxType { + return nil, nil + } + v, _, _ := tx.RawSignatureValues() + ret := hexutil.Uint64(v.Int64()) + return &ret, nil } func (t *Transaction) Raw(ctx context.Context) (hexutil.Bytes, error) { - tx, _, err := t.resolve(ctx) - if err != nil || tx == nil { - return hexutil.Bytes{}, err + tx, _ := t.resolve(ctx) + if tx == nil { + return hexutil.Bytes{}, nil } return tx.MarshalBinary() } @@ -708,7 +776,7 @@ func (b *Block) NextBaseFeePerGas(ctx context.Context) (*hexutil.Big, error) { return nil, nil } } - nextBaseFee := misc.CalcBaseFee(chaincfg, header) + nextBaseFee := eip1559.CalcBaseFee(chaincfg, header) return (*hexutil.Big)(nextBaseFee), nil } @@ -970,6 +1038,63 @@ func (b *Block) OmmerAt(ctx context.Context, args struct{ Index Long }) (*Block, }, nil } +func (b *Block) WithdrawalsRoot(ctx context.Context) (*common.Hash, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return nil, err + } + // Pre-shanghai blocks + if header.WithdrawalsHash == nil { + return nil, nil + } + return header.WithdrawalsHash, nil +} + +func (b *Block) Withdrawals(ctx context.Context) (*[]*Withdrawal, error) { + block, err := b.resolve(ctx) + if err != nil || block == nil { + return nil, err + } + // Pre-shanghai blocks + if block.Header().WithdrawalsHash == nil { + return nil, nil + } + ret := make([]*Withdrawal, 0, len(block.Withdrawals())) + for _, w := range block.Withdrawals() { + ret = append(ret, &Withdrawal{ + index: w.Index, + validator: w.Validator, + address: w.Address, + amount: w.Amount, + }) + } + return &ret, nil +} + +func (b *Block) BlobGasUsed(ctx context.Context) (*hexutil.Uint64, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return nil, err + } + if header.BlobGasUsed == nil { + return nil, nil + } + ret := hexutil.Uint64(*header.BlobGasUsed) + return &ret, nil +} + +func (b *Block) ExcessBlobGas(ctx context.Context) (*hexutil.Uint64, error) { + header, err := b.resolveHeader(ctx) + if err != nil { + return nil, err + } + if header.ExcessBlobGas == nil { + return nil, nil + } + ret := hexutil.Uint64(*header.ExcessBlobGas) + return &ret, nil +} + // BlockFilterCriteria encapsulates criteria passed to a `logs` accessor inside // a block. type BlockFilterCriteria struct { @@ -1091,7 +1216,7 @@ func (b *Block) Call(ctx context.Context, args struct { func (b *Block) EstimateGas(ctx context.Context, args struct { Data ethapi.TransactionArgs }) (hexutil.Uint64, error) { - return ethapi.DoEstimateGas(ctx, b.r.backend, args.Data, *b.numberOrHash, b.r.backend.RPCGasCap()) + return ethapi.DoEstimateGas(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, b.r.backend.RPCGasCap()) } type Pending struct { @@ -1155,7 +1280,7 @@ func (p *Pending) EstimateGas(ctx context.Context, args struct { Data ethapi.TransactionArgs }) (Long, error) { latestBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) - gas, err := ethapi.DoEstimateGas(ctx, p.r.backend, args.Data, latestBlockNr, p.r.backend.RPCGasCap()) + gas, err := ethapi.DoEstimateGas(ctx, p.r.backend, args.Data, latestBlockNr, nil, p.r.backend.RPCGasCap()) return Long(gas), err } @@ -1169,6 +1294,9 @@ func (r *Resolver) Block(ctx context.Context, args struct { Number *Long Hash *common.Hash }) (*Block, error) { + if args.Number != nil && args.Hash != nil { + return nil, errors.New("only one of number or hash must be specified") + } var numberOrHash rpc.BlockNumberOrHash if args.Number != nil { if *args.Number < 0 { @@ -1212,7 +1340,7 @@ func (r *Resolver) Blocks(ctx context.Context, args struct { if to < from { return []*Block{}, nil } - ret := make([]*Block, 0, to-from+1) + var ret []*Block for i := from; i <= to; i++ { numberOrHash := rpc.BlockNumberOrHashWithNumber(i) block := &Block{ @@ -1230,6 +1358,9 @@ func (r *Resolver) Blocks(ctx context.Context, args struct { break } ret = append(ret, block) + if err := ctx.Err(); err != nil { + return nil, err + } } return ret, nil } @@ -1238,19 +1369,17 @@ func (r *Resolver) Pending(ctx context.Context) *Pending { return &Pending{r} } -func (r *Resolver) Transaction(ctx context.Context, args struct{ Hash common.Hash }) (*Transaction, error) { +func (r *Resolver) Transaction(ctx context.Context, args struct{ Hash common.Hash }) *Transaction { tx := &Transaction{ r: r, hash: args.Hash, } // Resolve the transaction; if it doesn't exist, return nil. - t, _, err := tx.resolve(ctx) - if err != nil { - return nil, err - } else if t == nil { - return nil, nil + t, _ := tx.resolve(ctx) + if t == nil { + return nil } - return tx, nil + return tx } func (r *Resolver) SendRawTransaction(ctx context.Context, args struct{ Data hexutil.Bytes }) (common.Hash, error) { diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 0efee51a66ab..4bbfb7251d0c 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -28,6 +28,8 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -67,7 +69,7 @@ func TestGraphQLBlockSerialization(t *testing.T) { GasLimit: 11500000, Difficulty: big.NewInt(1048576), } - newGQLService(t, stack, genesis, 10, func(i int, gen *core.BlockGen) {}) + newGQLService(t, stack, false, genesis, 10, func(i int, gen *core.BlockGen) {}) // start node if err := stack.Start(); err != nil { t.Fatalf("could not start node: %v", err) @@ -191,7 +193,7 @@ func TestGraphQLBlockSerializationEIP2718(t *testing.T) { BaseFee: big.NewInt(params.InitialBaseFee), } signer := types.LatestSigner(genesis.Config) - newGQLService(t, stack, genesis, 1, func(i int, gen *core.BlockGen) { + newGQLService(t, stack, false, genesis, 1, func(i int, gen *core.BlockGen) { gen.SetCoinbase(common.Address{1}) tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{ Nonce: uint64(0), @@ -292,7 +294,7 @@ func TestGraphQLConcurrentResolvers(t *testing.T) { defer stack.Close() var tx *types.Transaction - handler, chain := newGQLService(t, stack, genesis, 1, func(i int, gen *core.BlockGen) { + handler, chain := newGQLService(t, stack, false, genesis, 1, func(i int, gen *core.BlockGen) { tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{To: &dad, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)}) gen.AddTx(tx) tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{To: &dad, Nonce: 1, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)}) @@ -360,6 +362,66 @@ func TestGraphQLConcurrentResolvers(t *testing.T) { } } +func TestWithdrawals(t *testing.T) { + var ( + key, _ = crypto.GenerateKey() + addr = crypto.PubkeyToAddress(key.PublicKey) + + genesis = &core.Genesis{ + Config: params.AllEthashProtocolChanges, + GasLimit: 11500000, + Difficulty: common.Big1, + Alloc: core.GenesisAlloc{ + addr: {Balance: big.NewInt(params.Ether)}, + }, + } + signer = types.LatestSigner(genesis.Config) + stack = createNode(t) + ) + defer stack.Close() + + handler, _ := newGQLService(t, stack, true, genesis, 1, func(i int, gen *core.BlockGen) { + tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{To: &common.Address{}, Gas: 100000, GasPrice: big.NewInt(params.InitialBaseFee)}) + gen.AddTx(tx) + gen.AddWithdrawal(&types.Withdrawal{ + Validator: 5, + Address: common.Address{}, + Amount: 10, + }) + }) + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + + for i, tt := range []struct { + body string + want string + }{ + // Genesis block has no withdrawals. + { + body: "{block(number: 0) { withdrawalsRoot withdrawals { index } } }", + want: `{"block":{"withdrawalsRoot":null,"withdrawals":null}}`, + }, + { + body: "{block(number: 1) { withdrawalsRoot withdrawals { validator amount } } }", + want: `{"block":{"withdrawalsRoot":"0x8418fc1a48818928f6692f148e9b10e99a88edc093b095cb8ca97950284b553d","withdrawals":[{"validator":"0x5","amount":"0xa"}]}}`, + }, + } { + res := handler.Schema.Exec(context.Background(), tt.body, "", map[string]interface{}{}) + if res.Errors != nil { + t.Fatalf("failed to execute query for testcase #%d: %v", i, res.Errors) + } + have, err := json.Marshal(res.Data) + if err != nil { + t.Fatalf("failed to encode graphql response for testcase #%d: %s", i, err) + } + if string(have) != tt.want { + t.Errorf("response unmatch for testcase #%d.\nhave:\n%s\nwant:\n%s", i, have, tt.want) + } + } +} + func createNode(t *testing.T) *node.Node { stack, err := node.New(&node.Config{ HTTPHost: "127.0.0.1", @@ -374,16 +436,25 @@ func createNode(t *testing.T) *node.Node { return stack } -func newGQLService(t *testing.T, stack *node.Node, gspec *core.Genesis, genBlocks int, genfunc func(i int, gen *core.BlockGen)) (*handler, []*types.Block) { +func newGQLService(t *testing.T, stack *node.Node, shanghai bool, gspec *core.Genesis, genBlocks int, genfunc func(i int, gen *core.BlockGen)) (*handler, []*types.Block) { ethConf := ðconfig.Config{ - Genesis: gspec, - NetworkId: 1337, - TrieCleanCache: 5, - TrieCleanCacheJournal: "triecache", - TrieCleanCacheRejournal: 60 * time.Minute, - TrieDirtyCache: 5, - TrieTimeout: 60 * time.Minute, - SnapshotCache: 5, + Genesis: gspec, + NetworkId: 1337, + TrieCleanCache: 5, + TrieDirtyCache: 5, + TrieTimeout: 60 * time.Minute, + SnapshotCache: 5, + } + var engine consensus.Engine = ethash.NewFaker() + if shanghai { + engine = beacon.NewFaker() + chainCfg := gspec.Config + chainCfg.TerminalTotalDifficultyPassed = true + chainCfg.TerminalTotalDifficulty = common.Big0 + // GenerateChain will increment timestamps by 10. + // Shanghai upgrade at block 1. + shanghaiTime := uint64(5) + chainCfg.ShanghaiTime = &shanghaiTime } ethBackend, err := eth.New(stack, ethConf) if err != nil { @@ -391,7 +462,7 @@ func newGQLService(t *testing.T, stack *node.Node, gspec *core.Genesis, genBlock } // Create some blocks and import them chain, _ := core.GenerateChain(params.AllEthashProtocolChanges, ethBackend.BlockChain().Genesis(), - ethash.NewFaker(), ethBackend.ChainDb(), genBlocks, genfunc) + engine, ethBackend.ChainDb(), genBlocks, genfunc) _, err = ethBackend.BlockChain().InsertChain(chain) if err != nil { t.Fatalf("could not create import blocks: %v", err) diff --git a/graphql/schema.go b/graphql/schema.go index e4ce7b9afc7d..5738923fc170 100644 --- a/graphql/schema.go +++ b/graphql/schema.go @@ -71,12 +71,24 @@ const schema string = ` transaction: Transaction! } - #EIP-2718 - type AccessTuple{ + # EIP-2718 + type AccessTuple { address: Address! storageKeys : [Bytes32!]! } + # EIP-4895 + type Withdrawal { + # Index is a monotonically increasing identifier issued by consensus layer. + index: Long! + # Validator is index of the validator associated with withdrawal. + validator: Long! + # Recipient address of the withdrawn amount. + address: Address! + # Amount is the withdrawal value in Gwei. + amount: Long! + } + # Transaction is an Ethereum transaction. type Transaction { # Hash is the hash of this transaction. @@ -100,6 +112,8 @@ const schema string = ` maxFeePerGas: BigInt # MaxPriorityFeePerGas is the maximum miner tip per gas offered to include a transaction, in wei. maxPriorityFeePerGas: BigInt + # MaxFeePerBlobGas is the maximum blob gas fee cap per blob the sender is willing to pay for blob transaction, in wei. + maxFeePerBlobGas: BigInt # EffectiveTip is the actual amount of reward going to miner after considering the max fee cap. effectiveTip: BigInt # Gas is the maximum amount of gas this transaction can consume. @@ -129,6 +143,10 @@ const schema string = ` # coerced into the EIP-1559 format by setting both maxFeePerGas and # maxPriorityFeePerGas as the transaction's gas price. effectiveGasPrice: BigInt + # BlobGasUsed is the amount of blob gas used by this transaction. + blobGasUsed: Long + # blobGasPrice is the actual value per blob gas deducted from the senders account. + blobGasPrice: BigInt # CreatedContract is the account that was created by a contract creation # transaction. If the transaction was not a contract creation transaction, # or it has not yet been mined, this field will be null. @@ -139,6 +157,7 @@ const schema string = ` r: BigInt! s: BigInt! v: BigInt! + yParity: Long # Envelope transaction support type: Long accessList: [AccessTuple!] @@ -149,6 +168,8 @@ const schema string = ` # RawReceipt is the canonical encoding of the receipt. For post EIP-2718 typed transactions # this is equivalent to TxType || ReceiptEncoding. rawReceipt: Bytes! + # BlobVersionedHashes is a set of hash outputs from the blobs in the transaction. + blobVersionedHashes: [Bytes32!] } # BlockFilterCriteria encapsulates log filter criteria for a filter applied @@ -158,16 +179,16 @@ const schema string = ` # empty, results will not be filtered by address. addresses: [Address!] # Topics list restricts matches to particular event topics. Each event has a list - # of topics. Topics matches a prefix of that list. An empty element array matches any - # topic. Non-empty elements represent an alternative that matches any of the - # contained topics. - # - # Examples: - # - [] or nil matches any topic list - # - [[A]] matches topic A in first position - # - [[], [B]] matches any topic in first position, B in second position - # - [[A], [B]] matches topic A in first position, B in second position - # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position + # of topics. Topics matches a prefix of that list. An empty element array matches any + # topic. Non-empty elements represent an alternative that matches any of the + # contained topics. + # + # Examples: + # - [] or nil matches any topic list + # - [[A]] matches topic A in first position + # - [[], [B]] matches any topic in first position, B in second position + # - [[A], [B]] matches topic A in first position, B in second position + # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position topics: [[Bytes32!]!] } @@ -248,6 +269,16 @@ const schema string = ` rawHeader: Bytes! # Raw is the RLP encoding of the block. raw: Bytes! + # WithdrawalsRoot is the withdrawals trie root in this block. + # If withdrawals are unavailable for this block, this field will be null. + withdrawalsRoot: Bytes32 + # Withdrawals is a list of withdrawals associated with this block. If + # withdrawals are unavailable for this block, this field will be null. + withdrawals: [Withdrawal!] + # BlobGasUsed is the total amount of gas used by the transactions. + blobGasUsed: Long + # ExcessBlobGas is a running total of blob gas consumed in excess of the target, prior to the block. + excessBlobGas: Long } # CallData represents the data associated with a local contract call. @@ -293,21 +324,21 @@ const schema string = ` # empty, results will not be filtered by address. addresses: [Address!] # Topics list restricts matches to particular event topics. Each event has a list - # of topics. Topics matches a prefix of that list. An empty element array matches any - # topic. Non-empty elements represent an alternative that matches any of the - # contained topics. - # - # Examples: - # - [] or nil matches any topic list - # - [[A]] matches topic A in first position - # - [[], [B]] matches any topic in first position, B in second position - # - [[A], [B]] matches topic A in first position, B in second position - # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position + # of topics. Topics matches a prefix of that list. An empty element array matches any + # topic. Non-empty elements represent an alternative that matches any of the + # contained topics. + # + # Examples: + # - [] or nil matches any topic list + # - [[A]] matches topic A in first position + # - [[], [B]] matches any topic in first position, B in second position + # - [[A], [B]] matches topic A in first position, B in second position + # - [[A, B]], [C, D]] matches topic (A OR B) in first position, (C OR D) in second position topics: [[Bytes32!]!] } # SyncState contains the current synchronisation state of the client. - type SyncState{ + type SyncState { # StartingBlock is the block number at which synchronisation started. startingBlock: Long! # CurrentBlock is the point at which synchronisation has presently reached. @@ -318,17 +349,17 @@ const schema string = ` # Pending represents the current pending state. type Pending { - # TransactionCount is the number of transactions in the pending state. - transactionCount: Long! - # Transactions is a list of transactions in the current pending state. - transactions: [Transaction!] - # Account fetches an Ethereum account for the pending state. - account(address: Address!): Account! - # Call executes a local call operation for the pending state. - call(data: CallData!): CallResult - # EstimateGas estimates the amount of gas that will be required for - # successful execution of a transaction for the pending state. - estimateGas(data: CallData!): Long! + # TransactionCount is the number of transactions in the pending state. + transactionCount: Long! + # Transactions is a list of transactions in the current pending state. + transactions: [Transaction!] + # Account fetches an Ethereum account for the pending state. + account(address: Address!): Account! + # Call executes a local call operation for the pending state. + call(data: CallData!): CallResult + # EstimateGas estimates the amount of gas that will be required for + # successful execution of a transaction for the pending state. + estimateGas(data: CallData!): Long! } type Query { diff --git a/graphql/service.go b/graphql/service.go index 4ca427658a94..f33e763058e3 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -88,7 +88,9 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } response := h.Schema.Exec(ctx, params.Query, params.OperationName, params.Variables) - timer.Stop() + if timer != nil { + timer.Stop() + } responded.Do(func() { responseJSON, err := json.Marshal(response) if err != nil { diff --git a/internal/blocktest/test_hash.go b/internal/blocktest/test_hash.go new file mode 100644 index 000000000000..4d2b077e89b5 --- /dev/null +++ b/internal/blocktest/test_hash.go @@ -0,0 +1,59 @@ +// Copyright 2023 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 utesting provides a standalone replacement for package testing. +// +// This package exists because package testing cannot easily be embedded into a +// standalone go program. It provides an API that mirrors the standard library +// testing API. + +package blocktest + +import ( + "hash" + + "github.com/ethereum/go-ethereum/common" + "golang.org/x/crypto/sha3" +) + +// testHasher is the helper tool for transaction/receipt list hashing. +// The original hasher is trie, in order to get rid of import cycle, +// use the testing hasher instead. +type testHasher struct { + hasher hash.Hash +} + +// NewHasher returns a new testHasher instance. +func NewHasher() *testHasher { + return &testHasher{hasher: sha3.NewLegacyKeccak256()} +} + +// Reset resets the hash state. +func (h *testHasher) Reset() { + h.hasher.Reset() +} + +// Update updates the hash state with the given key and value. +func (h *testHasher) Update(key, val []byte) error { + h.hasher.Write(key) + h.hasher.Write(val) + return nil +} + +// Hash returns the hash value. +func (h *testHasher) Hash() common.Hash { + return common.BytesToHash(h.hasher.Sum(nil)) +} diff --git a/internal/build/env.go b/internal/build/env.go index d70c0d50a499..0854fba2498d 100644 --- a/internal/build/env.go +++ b/internal/build/env.go @@ -28,12 +28,13 @@ import ( var ( // These flags override values in build env. - GitCommitFlag = flag.String("git-commit", "", `Overrides git commit hash embedded into executables`) - GitBranchFlag = flag.String("git-branch", "", `Overrides git branch being built`) - GitTagFlag = flag.String("git-tag", "", `Overrides git tag being built`) - BuildnumFlag = flag.String("buildnum", "", `Overrides CI build number`) - PullRequestFlag = flag.Bool("pull-request", false, `Overrides pull request status of the build`) - CronJobFlag = flag.Bool("cron-job", false, `Overrides cron job status of the build`) + GitCommitFlag = flag.String("git-commit", "", `Overrides git commit hash embedded into executables`) + GitBranchFlag = flag.String("git-branch", "", `Overrides git branch being built`) + GitTagFlag = flag.String("git-tag", "", `Overrides git tag being built`) + BuildnumFlag = flag.String("buildnum", "", `Overrides CI build number`) + PullRequestFlag = flag.Bool("pull-request", false, `Overrides pull request status of the build`) + CronJobFlag = flag.Bool("cron-job", false, `Overrides cron job status of the build`) + UbuntuVersionFlag = flag.String("ubuntu", "", `Sets the ubuntu version being built for`) ) // Environment contains metadata provided by the build environment. @@ -43,6 +44,7 @@ type Environment struct { Repo string // name of GitHub repo Commit, Date, Branch, Tag string // Git info Buildnum string + UbuntuVersion string // Ubuntu version being built for IsPullRequest bool IsCronJob bool } @@ -168,5 +170,8 @@ func applyEnvFlags(env Environment) Environment { if *CronJobFlag { env.IsCronJob = true } + if *UbuntuVersionFlag != "" { + env.UbuntuVersion = *UbuntuVersionFlag + } return env } diff --git a/internal/build/gotool.go b/internal/build/gotool.go index b5452f80cc3c..296ba8c36ecb 100644 --- a/internal/build/gotool.go +++ b/internal/build/gotool.go @@ -54,8 +54,8 @@ func (g *GoToolchain) Go(command string, args ...string) *exec.Cmd { tool.Env = append(tool.Env, "CC="+os.Getenv("CC")) } // CKZG by default is not portable, append the necessary build flags to make - // it not rely on modern CPU instructions and enable linking against - tool.Env = append(tool.Env, "CGO_CFLAGS=-D__BLST_PORTABLE__") + // it not rely on modern CPU instructions and enable linking against. + tool.Env = append(tool.Env, "CGO_CFLAGS=-O2 -g -D__BLST_PORTABLE__") return tool } diff --git a/internal/build/util.go b/internal/build/util.go index 3a0b384e5ee2..5c77b236dcf5 100644 --- a/internal/build/util.go +++ b/internal/build/util.go @@ -197,6 +197,9 @@ func FindMainPackages(dir string) []string { } for _, cmd := range cmds { pkgdir := filepath.Join(dir, cmd.Name()) + if !cmd.IsDir() { + continue + } pkgs, err := parser.ParseDir(token.NewFileSet(), pkgdir, nil, parser.PackageClauseOnly) if err != nil { log.Fatal(err) diff --git a/internal/cmdtest/test_cmd.go b/internal/cmdtest/test_cmd.go index df5107952e51..43137053c13e 100644 --- a/internal/cmdtest/test_cmd.go +++ b/internal/cmdtest/test_cmd.go @@ -55,13 +55,13 @@ type TestCmd struct { Err error } -var id int32 +var id atomic.Int32 // Run exec's the current binary using name as argv[0] which will trigger the // reexec init function for that name (e.g. "geth-test" in cmd/geth/run_test.go) func (tt *TestCmd) Run(name string, args ...string) { - id := atomic.AddInt32(&id, 1) - tt.stderr = &testlogger{t: tt.T, name: fmt.Sprintf("%d", id)} + id.Add(1) + tt.stderr = &testlogger{t: tt.T, name: fmt.Sprintf("%d", id.Load())} tt.cmd = &exec.Cmd{ Path: reexec.Self(), Args: append([]string{name}, args...), diff --git a/internal/debug/flags.go b/internal/debug/flags.go index d425a17db42b..736fede94334 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -19,6 +19,7 @@ package debug import ( "fmt" "io" + "net" "net/http" _ "net/http/pprof" "os" @@ -86,8 +87,9 @@ var ( Category: flags.LoggingCategory, } logRotateFlag = &cli.BoolFlag{ - Name: "log.rotate", - Usage: "Enables log file rotation", + Name: "log.rotate", + Usage: "Enables log file rotation", + Category: flags.LoggingCategory, } logMaxSizeMBsFlag = &cli.IntFlag{ Name: "log.maxsize", @@ -308,7 +310,7 @@ func Setup(ctx *cli.Context) error { port := ctx.Int(pprofPortFlag.Name) - address := fmt.Sprintf("%s:%d", listenHost, port) + address := net.JoinHostPort(listenHost, fmt.Sprintf("%d", port)) // This context value ("metrics.addr") represents the utils.MetricsHTTPFlag.Name. // It cannot be imported because it will cause a cyclical dependency. StartPProf(address, !ctx.IsSet("metrics.addr")) diff --git a/internal/debug/loudpanic.go b/internal/debug/loudpanic.go index 86e6bc88f83f..a7296e7b3f33 100644 --- a/internal/debug/loudpanic.go +++ b/internal/debug/loudpanic.go @@ -14,9 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -//go:build go1.6 -// +build go1.6 - package debug import "runtime/debug" diff --git a/internal/debug/loudpanic_fallback.go b/internal/debug/loudpanic_fallback.go deleted file mode 100644 index 377490e5bee5..000000000000 --- a/internal/debug/loudpanic_fallback.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -//go:build !go1.6 -// +build !go1.6 - -package debug - -// LoudPanic panics in a way that gets all goroutine stacks printed on stderr. -func LoudPanic(x interface{}) { - panic(x) -} diff --git a/internal/debug/trace.go b/internal/debug/trace.go index eea879823429..e291030b82e3 100644 --- a/internal/debug/trace.go +++ b/internal/debug/trace.go @@ -14,9 +14,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -//go:build go1.5 -// +build go1.5 - package debug import ( diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 0007451991af..2d3fe10d1172 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -34,7 +34,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -46,6 +46,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/trie" "github.com/tyler-smith/go-bip39" ) @@ -649,54 +650,93 @@ type StorageResult struct { Proof []string `json:"proof"` } +// proofList implements ethdb.KeyValueWriter and collects the proofs as +// hex-strings for delivery to rpc-caller. +type proofList []string + +func (n *proofList) Put(key []byte, value []byte) error { + *n = append(*n, hexutil.Encode(value)) + return nil +} + +func (n *proofList) Delete(key []byte) error { + panic("not supported") +} + // GetProof returns the Merkle-proof for a given account and optionally some storage keys. func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) { - state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + var ( + keys = make([]common.Hash, len(storageKeys)) + keyLengths = make([]int, len(storageKeys)) + storageProof = make([]StorageResult, len(storageKeys)) + + storageTrie state.Trie + storageHash = types.EmptyRootHash + codeHash = types.EmptyCodeHash + ) + // Deserialize all keys. This prevents state access on invalid input. + for i, hexKey := range storageKeys { + var err error + keys[i], keyLengths[i], err = decodeHash(hexKey) + if err != nil { + return nil, err + } + } + state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return nil, err } - storageTrie, err := state.StorageTrie(address) - if err != nil { - return nil, err + if storageRoot := state.GetStorageRoot(address); storageRoot != types.EmptyRootHash && storageRoot != (common.Hash{}) { + id := trie.StorageTrieID(header.Root, crypto.Keccak256Hash(address.Bytes()), storageRoot) + tr, err := trie.NewStateTrie(id, state.Database().TrieDB()) + if err != nil { + return nil, err + } + storageTrie = tr } - storageHash := types.EmptyRootHash - codeHash := state.GetCodeHash(address) - storageProof := make([]StorageResult, len(storageKeys)) - - // if we have a storageTrie, (which means the account exists), we can update the storagehash + // If we have a storageTrie, the account exists and we must update + // the storage root hash and the code hash. if storageTrie != nil { storageHash = storageTrie.Hash() - } else { - // no storageTrie means the account does not exist, so the codeHash is the hash of an empty bytearray. - codeHash = crypto.Keccak256Hash(nil) - } + codeHash = state.GetCodeHash(address) + } + // Create the proofs for the storageKeys. + for i, key := range keys { + // Output key encoding is a bit special: if the input was a 32-byte hash, it is + // returned as such. Otherwise, we apply the QUANTITY encoding mandated by the + // JSON-RPC spec for getProof. This behavior exists to preserve backwards + // compatibility with older client versions. + var outputKey string + if keyLengths[i] != 32 { + outputKey = hexutil.EncodeBig(key.Big()) + } else { + outputKey = hexutil.Encode(key[:]) + } - // create the proof for the storageKeys - for i, hexKey := range storageKeys { - key, err := decodeHash(hexKey) - if err != nil { - return nil, err + if storageTrie == nil { + storageProof[i] = StorageResult{outputKey, &hexutil.Big{}, []string{}} + continue } - if storageTrie != nil { - proof, storageError := state.GetStorageProof(address, key) - if storageError != nil { - return nil, storageError - } - storageProof[i] = StorageResult{hexKey, (*hexutil.Big)(state.GetState(address, key).Big()), toHexSlice(proof)} - } else { - storageProof[i] = StorageResult{hexKey, &hexutil.Big{}, []string{}} + var proof proofList + if err := storageTrie.Prove(crypto.Keccak256(key.Bytes()), &proof); err != nil { + return nil, err } + value := (*hexutil.Big)(state.GetState(address, key).Big()) + storageProof[i] = StorageResult{outputKey, value, proof} } - // create the accountProof - accountProof, proofErr := state.GetProof(address) - if proofErr != nil { - return nil, proofErr + // Create the accountProof. + tr, err := trie.NewStateTrie(trie.StateTrieID(header.Root), state.Database().TrieDB()) + if err != nil { + return nil, err + } + var accountProof proofList + if err := tr.Prove(crypto.Keccak256(address.Bytes()), &accountProof); err != nil { + return nil, err } - return &AccountResult{ Address: address, - AccountProof: toHexSlice(accountProof), + AccountProof: accountProof, Balance: (*hexutil.Big)(state.GetBalance(address)), CodeHash: codeHash, Nonce: hexutil.Uint64(state.GetNonce(address)), @@ -707,7 +747,7 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st // decodeHash parses a hex-encoded 32-byte hash. The input may optionally // be prefixed by 0x and can have a byte length up to 32. -func decodeHash(s string) (common.Hash, error) { +func decodeHash(s string) (h common.Hash, inputLength int, err error) { if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { s = s[2:] } @@ -716,17 +756,19 @@ func decodeHash(s string) (common.Hash, error) { } b, err := hex.DecodeString(s) if err != nil { - return common.Hash{}, errors.New("hex string invalid") + return common.Hash{}, 0, errors.New("hex string invalid") } if len(b) > 32 { - return common.Hash{}, errors.New("hex string too long, want at most 32 bytes") + return common.Hash{}, len(b), errors.New("hex string too long, want at most 32 bytes") } - return common.BytesToHash(b), nil + return common.BytesToHash(b), len(b), nil } // GetHeaderByNumber returns the requested canonical block header. -// * When blockNr is -1 the chain head is returned. -// * When blockNr is -2 the pending chain head is returned. +// - When blockNr is -1 the chain pending header is returned. +// - When blockNr is -2 the chain latest header is returned. +// - When blockNr is -3 the chain finalized header is returned. +// - When blockNr is -4 the chain safe header is returned. func (s *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) { header, err := s.b.HeaderByNumber(ctx, number) if header != nil && err == nil { @@ -752,8 +794,10 @@ func (s *BlockChainAPI) GetHeaderByHash(ctx context.Context, hash common.Hash) m } // GetBlockByNumber returns the requested canonical block. -// - When blockNr is -1 the chain head is returned. -// - When blockNr is -2 the pending chain head is returned. +// - When blockNr is -1 the chain pending block is returned. +// - When blockNr is -2 the chain latest block is returned. +// - When blockNr is -3 the chain finalized block is returned. +// - When blockNr is -4 the chain safe block is returned. // - When fullTx is true all transactions in the block are returned, otherwise // only the transaction hash is returned. func (s *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { @@ -848,7 +892,7 @@ func (s *BlockChainAPI) GetCode(ctx context.Context, address common.Address, blo // block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block // numbers are also allowed. func (s *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address, hexKey string, blockNrOrHash rpc.BlockNumberOrHash) (hexutil.Bytes, error) { - key, err := decodeHash(hexKey) + key, _, err := decodeHash(hexKey) if err != nil { return nil, fmt.Errorf("unable to decode storage key: %s", err) } @@ -865,6 +909,37 @@ func (s *BlockChainAPI) GetStorageAt(ctx context.Context, address common.Address return res[:], state.Error() } +// GetBlockReceipts returns the block receipts for the given block hash or number or tag. +func (s *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { + block, err := s.b.BlockByNumberOrHash(ctx, blockNrOrHash) + if block == nil || err != nil { + // When the block doesn't exist, the RPC method should return JSON null + // as per specification. + return nil, nil + } + receipts, err := s.b.GetReceipts(ctx, block.Hash()) + if err != nil { + return nil, err + } + txs := block.Transactions() + if len(txs) != len(receipts) { + return nil, fmt.Errorf("receipts length mismatch: %d vs %d", len(txs), len(receipts)) + } + + // Derive the sender. + signer := types.MakeSigner(s.b.ChainConfig(), block.Number(), block.Time()) + + result := make([]map[string]interface{}, len(receipts)) + for i, receipt := range receipts { + result[i], err = marshalReceipt(ctx, receipt, block.Hash(), block.NumberU64(), signer, txs[i], i, s.b) + if err != nil { + return nil, err + } + } + + return result, nil +} + // OverrideAccount indicates the overriding fields of account during the execution // of a message call. // Note, state and stateDiff can't be specified at the same time. If state is @@ -923,13 +998,14 @@ func (diff *StateOverride) Apply(state *state.StateDB) error { // BlockOverrides is a set of header fields to override. type BlockOverrides struct { - Number *hexutil.Big - Difficulty *hexutil.Big - Time *hexutil.Uint64 - GasLimit *hexutil.Uint64 - Coinbase *common.Address - Random *common.Hash - BaseFee *hexutil.Big + Number *hexutil.Big + Difficulty *hexutil.Big + Time *hexutil.Uint64 + GasLimit *hexutil.Uint64 + Coinbase *common.Address + Random *common.Hash + BaseFee *hexutil.Big + BlobBaseFee *hexutil.Big } // Apply overrides the given header fields into the given block context. @@ -962,6 +1038,9 @@ func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) { if diff.BaseFee != nil { blockCtx.BaseFee = diff.BaseFee.ToInt() } + if diff.BlobBaseFee != nil { + blockCtx.BlobBaseFee = diff.BlobBaseFee.ToInt() + } } // ChainContextBackend provides methods required to implement ChainContext. @@ -996,13 +1075,7 @@ func (context *ChainContext) GetHeader(hash common.Hash, number uint64) *types.H return header } -func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides, timeout time.Duration, globalGasCap uint64, runMode core.MessageRunMode) (*core.ExecutionResult, error) { - defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) - - state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) - if state == nil || err != nil { - return nil, err - } +func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, overrides *StateOverride, blockOverrides *BlockOverrides, timeout time.Duration, globalGasCap uint64, runMode core.MessageRunMode) (*core.ExecutionResult, error) { if err := overrides.Apply(state); err != nil { return nil, err } @@ -1068,6 +1141,15 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash if err != nil { return nil, err } + // The scheduling transaction will "use" all of the gas available to it, + // but it's really just passing it on to the scheduled tx, so we subtract it out here. + if result.UsedGas >= msg.GasLimit { + result.UsedGas -= msg.GasLimit + } else { + log.Warn("Scheduling tx used less gas than scheduled tx has available", "usedGas", result.UsedGas, "scheduledGas", msg.GasLimit) + result.UsedGas = 0 + } + msg.TxRunMode = runMode // make a new EVM for the scheduled Tx (an EVM must never be reused) evm, vmError := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx) go func() { @@ -1085,12 +1167,40 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash if scheduledTxResult.Failed() { return scheduledTxResult, nil } + // Add back in any gas used by the scheduled transaction. + result.UsedGas += scheduledTxResult.UsedGas scheduled = append(scheduled[1:], scheduledTxResult.ScheduledTxes...) } return result, nil } +func updateHeaderForPendingBlocks(blockNrOrHash rpc.BlockNumberOrHash, header *types.Header) *types.Header { + if blockNrOrHash.BlockNumber != nil && + *blockNrOrHash.BlockNumber == rpc.PendingBlockNumber { + headerCopy := *header + now := uint64(time.Now().Unix()) + if now > headerCopy.Time { + headerCopy.Time = now + } + headerCopy.Number = new(big.Int).Add(headerCopy.Number, common.Big1) + return &headerCopy + } + return header +} + +func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides, timeout time.Duration, globalGasCap uint64, runMode core.MessageRunMode) (*core.ExecutionResult, error) { + defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now()) + + state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if state == nil || err != nil { + return nil, err + } + header = updateHeaderForPendingBlocks(blockNrOrHash, header) + + return doCall(ctx, b, args, state, header, overrides, blockOverrides, timeout, globalGasCap, runMode) +} + func newRevertError(result *core.ExecutionResult) *revertError { reason, errUnpack := abi.UnpackRevert(result.Revert()) err := errors.New("execution reverted") @@ -1135,8 +1245,12 @@ func (e *revertError) ErrorData() interface{} { // // Note, this function doesn't make and changes in the state/blockchain and is // useful to execute and retrieve values. -func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Bytes, error) { - result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, blockOverrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap(), core.MessageEthcallMode) +func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Bytes, error) { + if blockNrOrHash == nil { + latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + blockNrOrHash = &latest + } + result, err := DoCall(ctx, s.b, args, *blockNrOrHash, overrides, blockOverrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap(), core.MessageEthcallMode) if err != nil { if client := fallbackClientFor(s.b, err); client != nil { var res hexutil.Bytes @@ -1152,12 +1266,30 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO return result.Return(), result.Err } -func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) { - // Binary search the gas requirement, as it may be higher than the amount used +// executeEstimate is a helper that executes the transaction under a given gas limit and returns +// true if the transaction fails for a reason that might be related to not enough gas. A non-nil +// error means execution failed due to reasons unrelated to the gas limit. +func executeEstimate(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, gasCap uint64, gasLimit uint64) (bool, *core.ExecutionResult, error) { + args.Gas = (*hexutil.Uint64)(&gasLimit) + result, err := doCall(ctx, b, args, state, header, nil, nil, 0, gasCap, core.MessageGasEstimationMode) + if err != nil { + if errors.Is(err, core.ErrIntrinsicGas) { + return true, nil, nil // Special case, raise gas limit + } + return true, nil, err // Bail out + } + return result.Failed(), result, nil +} + +// DoEstimateGas returns the lowest possible gas limit that allows the transaction to run +// successfully at block `blockNrOrHash`. It returns error if the transaction would revert, or if +// there are unexpected failures. The gas limit is capped by both `args.Gas` (if non-nil & +// non-zero) and `gasCap` (if non-zero). +func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, gasCap uint64) (hexutil.Uint64, error) { + // Binary search the gas limit, as it may need to be higher than the amount used var ( - lo uint64 = params.TxGas - 1 - hi uint64 - cap uint64 + lo uint64 // lowest-known gas limit where tx execution fails + hi uint64 // lowest-known gas limit where tx execution succeeds ) // Use zero address if sender unspecified. if args.From == nil { @@ -1188,12 +1320,18 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr } else { feeCap = common.Big0 } + + state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if state == nil || err != nil { + return 0, err + } + if err := overrides.Apply(state); err != nil { + return 0, err + } + header = updateHeaderForPendingBlocks(blockNrOrHash, header) + // Recap the highest gas limit with account's available balance. if feeCap.BitLen() != 0 { - state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) - if err != nil { - return 0, err - } balance := state.GetBalance(*args.From) // from can't be nil available := new(big.Int).Set(balance) if args.Value != nil { @@ -1234,30 +1372,42 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap) hi = gasCap } - cap = hi - - // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) (bool, *core.ExecutionResult, error) { - args.Gas = (*hexutil.Uint64)(&gas) - result, err := DoCall(ctx, b, args, blockNrOrHash, nil, nil, 0, vanillaGasCap, core.MessageGasEstimationMode) - if err != nil { - if errors.Is(err, core.ErrIntrinsicGas) { - return true, nil, nil // Special case, raise gas limit + // We first execute the transaction at the highest allowable gas limit, since if this fails we + // can return error immediately. + failed, result, err := executeEstimate(ctx, b, args, state.Copy(), header, vanillaGasCap, hi) + if err != nil { + return 0, err + } + if failed { + if result != nil && result.Err != vm.ErrOutOfGas { + if len(result.Revert()) > 0 { + return 0, newRevertError(result) } - return true, nil, err // Bail out + return 0, result.Err } - return result.Failed(), result, nil + return 0, fmt.Errorf("gas required exceeds allowance (%d)", hi) } - // Execute the binary search and hone in on an executable gas limit + // For almost any transaction, the gas consumed by the unconstrained execution above + // lower-bounds the gas limit required for it to succeed. One exception is those txs that + // explicitly check gas remaining in order to successfully execute within a given limit, but we + // probably don't want to return a lowest possible gas limit for these cases anyway. + lo = result.UsedGas - 1 + + // Binary search for the smallest gas limit that allows the tx to execute successfully. for lo+1 < hi { mid := (hi + lo) / 2 - failed, _, err := executable(mid) - - // If the error is not nil(consensus error), it means the provided message - // call or transaction will never be accepted no matter how much gas it is - // assigned. Return the error directly, don't struggle any more. + if mid > lo*2 { + // Most txs don't need much higher gas limit than their gas used, and most txs don't + // require near the full block limit of gas, so the selection of where to bisect the + // range here is skewed to favor the low side. + mid = lo * 2 + } + failed, _, err = executeEstimate(ctx, b, args, state.Copy(), header, gasCap, mid) if err != nil { + // This should not happen under normal conditions since if we make it this far the + // transaction had run without error at least once before. + log.Error("execution error in estimate gas", "err", err) return 0, err } if failed { @@ -1266,37 +1416,23 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr hi = mid } } - // Reject the transaction as invalid if it still fails at the highest allowance - if hi == cap { - failed, result, err := executable(hi) - if err != nil { - return 0, err - } - if failed { - if result != nil && result.Err != vm.ErrOutOfGas { - if len(result.Revert()) > 0 { - return 0, newRevertError(result) - } - return 0, result.Err - } - // Otherwise, the specified gas cap is too low - return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap) - } - } return hexutil.Uint64(hi), nil } -// EstimateGas returns an estimate of the amount of gas needed to execute the -// given transaction against the current pending block. -func (s *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash) (hexutil.Uint64, error) { - bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) +// EstimateGas returns the lowest possible gas limit that allows the transaction to run +// successfully at block `blockNrOrHash`, or the latest block if `blockNrOrHash` is unspecified. It +// returns error if the transaction would revert or if there are unexpected failures. The returned +// value is capped by both `args.Gas` (if non-nil & non-zero) and the backend's RPCGasCap +// configuration (if non-zero). +func (s *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Uint64, error) { + bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) if blockNrOrHash != nil { bNrOrHash = *blockNrOrHash } - res, err := DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap()) + res, err := DoEstimateGas(ctx, s.b, args, bNrOrHash, overrides, s.b.RPCGasCap()) if client := fallbackClientFor(s.b, err); client != nil { var res hexutil.Uint64 - err := client.CallContext(ctx, &res, "eth_estimateGas", args, blockNrOrHash) + err := client.CallContext(ctx, &res, "eth_estimateGas", args, blockNrOrHash, overrides) return res, err } return res, err @@ -1316,29 +1452,34 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} { "miner": head.Coinbase, "difficulty": (*hexutil.Big)(head.Difficulty), "extraData": hexutil.Bytes(head.Extra), - "size": hexutil.Uint64(head.Size()), "gasLimit": hexutil.Uint64(head.GasLimit), "gasUsed": hexutil.Uint64(head.GasUsed), "timestamp": hexutil.Uint64(head.Time), "transactionsRoot": head.TxHash, "receiptsRoot": head.ReceiptHash, } - if head.BaseFee != nil { result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee) } - if head.WithdrawalsHash != nil { result["withdrawalsRoot"] = head.WithdrawalsHash } - + if head.BlobGasUsed != nil { + result["blobGasUsed"] = hexutil.Uint64(*head.BlobGasUsed) + } + if head.ExcessBlobGas != nil { + result["excessBlobGas"] = hexutil.Uint64(*head.ExcessBlobGas) + } + if head.ParentBeaconRoot != nil { + result["parentBeaconBlockRoot"] = head.ParentBeaconRoot + } return result } // RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are // returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain // transaction hashes. -func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *params.ChainConfig) (map[string]interface{}, error) { +func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *params.ChainConfig) map[string]interface{} { fields := RPCMarshalHeader(block.Header()) fields["size"] = hexutil.Uint64(block.Size()) @@ -1373,7 +1514,7 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param fields["withdrawals"] = block.Withdrawals() } - return fields, nil + return fields } func fillArbitrumNitroHeaderInfo(header *types.Header, fields map[string]interface{}) { @@ -1412,7 +1553,7 @@ func (s *BlockChainAPI) arbClassicL1BlockNumber(ctx context.Context, block *type } i++ blockNum = startBlockNum - i - if i > 5 { + if i > 50 { return 0, fmt.Errorf("couldn't find block with transactions. Reached %d", blockNum) } var err error @@ -1427,10 +1568,7 @@ func (s *BlockChainAPI) arbClassicL1BlockNumber(ctx context.Context, block *type // a `BlockchainAPI`. func (s *BlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { chainConfig := s.b.ChainConfig() - fields, err := RPCMarshalBlock(b, inclTx, fullTx, chainConfig) - if err != nil { - return nil, err - } + fields := RPCMarshalBlock(b, inclTx, fullTx, chainConfig) if inclTx { fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(ctx, b.Hash())) } @@ -1438,34 +1576,38 @@ func (s *BlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, inc l1BlockNumber, err := s.arbClassicL1BlockNumber(ctx, b) if err != nil { log.Error("error trying to fill legacy l1BlockNumber", "err", err) + return fields, err } else { fields["l1BlockNumber"] = l1BlockNumber } } - return fields, err + return fields, nil } // RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction type RPCTransaction struct { - BlockHash *common.Hash `json:"blockHash"` - BlockNumber *hexutil.Big `json:"blockNumber"` - From common.Address `json:"from"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` - GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` - Hash common.Hash `json:"hash"` - Input hexutil.Bytes `json:"input"` - Nonce hexutil.Uint64 `json:"nonce"` - To *common.Address `json:"to"` - TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` - Value *hexutil.Big `json:"value"` - Type hexutil.Uint64 `json:"type"` - Accesses *types.AccessList `json:"accessList,omitempty"` - ChainID *hexutil.Big `json:"chainId,omitempty"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` + BlockHash *common.Hash `json:"blockHash"` + BlockNumber *hexutil.Big `json:"blockNumber"` + From common.Address `json:"from"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` + GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` + MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` + Hash common.Hash `json:"hash"` + Input hexutil.Bytes `json:"input"` + Nonce hexutil.Uint64 `json:"nonce"` + To *common.Address `json:"to"` + TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` + Value *hexutil.Big `json:"value"` + Type hexutil.Uint64 `json:"type"` + Accesses *types.AccessList `json:"accessList,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` + BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` + YParity *hexutil.Uint64 `json:"yParity,omitempty"` // Arbitrum fields: RequestId *common.Hash `json:"requestId,omitempty"` // Contract SubmitRetryable Deposit @@ -1507,30 +1649,53 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) result.TransactionIndex = (*hexutil.Uint64)(&index) } + switch tx.Type() { case types.LegacyTxType: // if a legacy transaction has an EIP-155 chain id, include it explicitly if id := tx.ChainId(); id.Sign() != 0 { result.ChainID = (*hexutil.Big)(id) } + case types.AccessListTxType: al := tx.AccessList() + yparity := hexutil.Uint64(v.Sign()) result.Accesses = &al result.ChainID = (*hexutil.Big)(tx.ChainId()) + result.YParity = &yparity + case types.DynamicFeeTxType: al := tx.AccessList() + yparity := hexutil.Uint64(v.Sign()) + result.Accesses = &al + result.ChainID = (*hexutil.Big)(tx.ChainId()) + result.YParity = &yparity + result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap()) + result.GasTipCap = (*hexutil.Big)(tx.GasTipCap()) + // if the transaction has been mined, compute the effective gas price + if baseFee != nil && blockHash != (common.Hash{}) { + // price = min(gasTipCap + baseFee, gasFeeCap) + result.GasPrice = (*hexutil.Big)(effectiveGasPrice(tx, baseFee)) + } else { + result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) + } + + case types.BlobTxType: + al := tx.AccessList() + yparity := hexutil.Uint64(v.Sign()) result.Accesses = &al result.ChainID = (*hexutil.Big)(tx.ChainId()) + result.YParity = &yparity result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap()) result.GasTipCap = (*hexutil.Big)(tx.GasTipCap()) // if the transaction has been mined, compute the effective gas price if baseFee != nil && blockHash != (common.Hash{}) { - // price = min(tip, gasFeeCap - baseFee) + baseFee - price := math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee), tx.GasFeeCap()) - result.GasPrice = (*hexutil.Big)(price) + result.GasPrice = (*hexutil.Big)(effectiveGasPrice(tx, baseFee)) } else { result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) } + result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap()) + result.BlobVersionedHashes = tx.BlobHashes() } // Arbitrum: support arbitrum-specific transaction types @@ -1563,10 +1728,24 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber result.MaxSubmissionFee = (*hexutil.Big)(inner.MaxSubmissionFee) result.GasFeeCap = (*hexutil.Big)(inner.GasFeeCap) result.ChainID = (*hexutil.Big)(inner.ChainId) + case *types.ArbitrumUnsignedTx: + result.ChainID = (*hexutil.Big)(inner.ChainId) } return result } +// effectiveGasPrice computes the transaction gas fee, based on the given basefee value. +// +// price = min(gasTipCap + baseFee, gasFeeCap) +func effectiveGasPrice(tx *types.Transaction, baseFee *big.Int) *big.Int { + fee := tx.GasTipCap() + fee = fee.Add(fee, baseFee) + if tx.GasFeeCapIntCmp(fee) < 0 { + return tx.GasFeeCap() + } + return fee +} + // NewRPCPendingTransaction returns a pending transaction that will serialize to the RPC representation func NewRPCPendingTransaction(tx *types.Transaction, current *types.Header, config *params.ChainConfig) *RPCTransaction { var ( @@ -1575,7 +1754,7 @@ func NewRPCPendingTransaction(tx *types.Transaction, current *types.Header, conf blockTime = uint64(0) ) if current != nil { - baseFee = misc.CalcBaseFee(config, current) + baseFee = eip1559.CalcBaseFee(config, current) blockNumber = current.Number.Uint64() blockTime = current.Time } @@ -1823,7 +2002,7 @@ func (s *TransactionAPI) GetRawTransactionByHash(ctx context.Context, hash commo // GetTransactionReceipt returns the transaction receipt for the given transaction hash. func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { tx, blockHash, blockNumber, index, err := s.b.GetTransaction(ctx, hash) - if err != nil { + if tx == nil || err != nil { // When the transaction doesn't exist, the RPC method should return JSON null // as per specification. return nil, nil @@ -1843,13 +2022,18 @@ func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common. // Derive the sender. signer := types.MakeSigner(s.b.ChainConfig(), header.Number, header.Time) + return marshalReceipt(ctx, receipt, blockHash, blockNumber, signer, tx, int(index), s.b) +} + +// marshalReceipt marshals a transaction receipt into a JSON object. +func marshalReceipt(ctx context.Context, receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int, backend Backend) (map[string]interface{}, error) { from, _ := types.Sender(signer, tx) fields := map[string]interface{}{ "blockHash": blockHash, "blockNumber": hexutil.Uint64(blockNumber), - "transactionHash": hash, - "transactionIndex": hexutil.Uint64(index), + "transactionHash": tx.Hash(), + "transactionIndex": hexutil.Uint64(txIndex), "from": from, "to": tx.To(), "gasUsed": hexutil.Uint64(receipt.GasUsed), @@ -1871,18 +2055,23 @@ func (s *TransactionAPI) GetTransactionReceipt(ctx context.Context, hash common. fields["logs"] = []*types.Log{} } + if tx.Type() == types.BlobTxType { + fields["blobGasUsed"] = hexutil.Uint64(receipt.BlobGasUsed) + fields["blobGasPrice"] = (*hexutil.Big)(receipt.BlobGasPrice) + } + // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation if receipt.ContractAddress != (common.Address{}) { fields["contractAddress"] = receipt.ContractAddress } - if s.b.ChainConfig().IsArbitrum() { + if backend.ChainConfig().IsArbitrum() { fields["gasUsedForL1"] = hexutil.Uint64(receipt.GasUsedForL1) - header, err := s.b.HeaderByHash(ctx, blockHash) + header, err := backend.HeaderByHash(ctx, blockHash) if err != nil { return nil, err } - if s.b.ChainConfig().IsArbitrumNitro(header.Number) { + if backend.ChainConfig().IsArbitrumNitro(header.Number) { fields["effectiveGasPrice"] = hexutil.Uint64(header.BaseFee.Uint64()) fields["l1BlockNumber"] = hexutil.Uint64(types.DeserializeHeaderExtraInformation(header).L1BlockNumber) } else { @@ -2244,20 +2433,23 @@ func (api *DebugAPI) PrintBlock(ctx context.Context, number uint64) (string, err // ChaindbProperty returns leveldb properties of the key-value database. func (api *DebugAPI) ChaindbProperty(property string) (string, error) { - if property == "" { - property = "leveldb.stats" - } else if !strings.HasPrefix(property, "leveldb.") { - property = "leveldb." + property - } return api.b.ChainDb().Stat(property) } // ChaindbCompact flattens the entire key-value database into a single level, // removing all unused slots and merging all keys. func (api *DebugAPI) ChaindbCompact() error { - for b := byte(0); b < 255; b++ { - log.Info("Compacting chain database", "range", fmt.Sprintf("0x%0.2X-0x%0.2X", b, b+1)) - if err := api.b.ChainDb().Compact([]byte{b}, []byte{b + 1}); err != nil { + cstart := time.Now() + for b := 0; b <= 255; b++ { + var ( + start = []byte{byte(b)} + end = []byte{byte(b + 1)} + ) + if b == 255 { + end = nil + } + log.Info("Compacting database", "range", fmt.Sprintf("%#X-%#X", start, end), "elapsed", common.PrettyDuration(time.Since(cstart))) + if err := api.b.ChainDb().Compact(start, end); err != nil { log.Error("Database compaction failed", "err", err) return err } @@ -2314,12 +2506,3 @@ func checkTxFee(gasPrice *big.Int, gas uint64, cap float64) error { } return nil } - -// toHexSlice creates a slice of hex-strings based on []byte. -func toHexSlice(b [][]byte) []string { - r := make([]string, len(b)) - for i := range b { - r[i] = hexutil.Encode(b[i]) - } - return r -} diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 6f4e5ee9b411..3f770ee8264f 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -17,15 +17,15 @@ package ethapi import ( - "bytes" "context" "crypto/ecdsa" "encoding/json" "errors" - "hash" + "fmt" "math/big" + "os" + "path/filepath" "reflect" - "sort" "testing" "time" @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" @@ -44,22 +45,24 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/internal/blocktest" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" - "golang.org/x/crypto/sha3" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" ) -func TestTransaction_RoundTripRpcJSON(t *testing.T) { +func testTransactionMarshal(t *testing.T, tests []txData, config *params.ChainConfig) { + t.Parallel() var ( - config = params.AllEthashProtocolChanges signer = types.LatestSigner(config) key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - tests = allTransactionTypes(common.Address{0xde, 0xad}, config) ) - t.Parallel() + for i, tt := range tests { var tx2 types.Transaction - tx, err := types.SignNewTx(key, signer, tt) + tx, err := types.SignNewTx(key, signer, tt.Tx) if err != nil { t.Fatalf("test %d: signing failed: %v", i, err) } @@ -72,7 +75,7 @@ func TestTransaction_RoundTripRpcJSON(t *testing.T) { t.Fatalf("test %d: stx changed, want %x have %x", i, want, have) } - // rpcTransaction + // rpcTransaction rpcTx := newRPCTransaction(tx, common.Hash{}, 0, 0, 0, nil, config) if data, err := json.Marshal(rpcTx); err != nil { t.Fatalf("test %d: marshalling failed; %v", i, err) @@ -80,117 +83,334 @@ func TestTransaction_RoundTripRpcJSON(t *testing.T) { t.Fatalf("test %d: unmarshal failed: %v", i, err) } else if want, have := tx.Hash(), tx2.Hash(); want != have { t.Fatalf("test %d: tx changed, want %x have %x", i, want, have) + } else { + want, have := tt.Want, string(data) + require.JSONEqf(t, want, have, "test %d: rpc json not match, want %s have %s", i, want, have) } } } -func allTransactionTypes(addr common.Address, config *params.ChainConfig) []types.TxData { - return []types.TxData{ - &types.LegacyTx{ - Nonce: 5, - GasPrice: big.NewInt(6), - Gas: 7, - To: &addr, - Value: big.NewInt(8), - Data: []byte{0, 1, 2, 3, 4}, - V: big.NewInt(9), - R: big.NewInt(10), - S: big.NewInt(11), - }, - &types.LegacyTx{ - Nonce: 5, - GasPrice: big.NewInt(6), - Gas: 7, - To: nil, - Value: big.NewInt(8), - Data: []byte{0, 1, 2, 3, 4}, - V: big.NewInt(32), - R: big.NewInt(10), - S: big.NewInt(11), - }, - &types.AccessListTx{ - ChainID: config.ChainID, - Nonce: 5, - GasPrice: big.NewInt(6), - Gas: 7, - To: &addr, - Value: big.NewInt(8), - Data: []byte{0, 1, 2, 3, 4}, - AccessList: types.AccessList{ - types.AccessTuple{ - Address: common.Address{0x2}, - StorageKeys: []common.Hash{types.EmptyRootHash}, +func TestTransaction_RoundTripRpcJSON(t *testing.T) { + var ( + config = params.AllEthashProtocolChanges + tests = allTransactionTypes(common.Address{0xde, 0xad}, config) + ) + testTransactionMarshal(t, tests, config) +} + +func TestTransactionBlobTx(t *testing.T) { + config := *params.TestChainConfig + config.ShanghaiTime = new(uint64) + config.CancunTime = new(uint64) + tests := allBlobTxs(common.Address{0xde, 0xad}, &config) + + testTransactionMarshal(t, tests, &config) +} + +type txData struct { + Tx types.TxData + Want string +} + +func allTransactionTypes(addr common.Address, config *params.ChainConfig) []txData { + return []txData{ + { + Tx: &types.LegacyTx{ + Nonce: 5, + GasPrice: big.NewInt(6), + Gas: 7, + To: &addr, + Value: big.NewInt(8), + Data: []byte{0, 1, 2, 3, 4}, + V: big.NewInt(9), + R: big.NewInt(10), + S: big.NewInt(11), + }, + Want: `{ + "blockHash": null, + "blockNumber": null, + "from": "0x71562b71999873db5b286df957af199ec94617f7", + "gas": "0x7", + "gasPrice": "0x6", + "hash": "0x5f3240454cd09a5d8b1c5d651eefae7a339262875bcd2d0e6676f3d989967008", + "input": "0x0001020304", + "nonce": "0x5", + "to": "0xdead000000000000000000000000000000000000", + "transactionIndex": null, + "value": "0x8", + "type": "0x0", + "chainId": "0x539", + "v": "0xa96", + "r": "0xbc85e96592b95f7160825d837abb407f009df9ebe8f1b9158a4b8dd093377f75", + "s": "0x1b55ea3af5574c536967b039ba6999ef6c89cf22fc04bcb296e0e8b0b9b576f5" + }`, + }, { + Tx: &types.LegacyTx{ + Nonce: 5, + GasPrice: big.NewInt(6), + Gas: 7, + To: nil, + Value: big.NewInt(8), + Data: []byte{0, 1, 2, 3, 4}, + V: big.NewInt(32), + R: big.NewInt(10), + S: big.NewInt(11), + }, + Want: `{ + "blockHash": null, + "blockNumber": null, + "from": "0x71562b71999873db5b286df957af199ec94617f7", + "gas": "0x7", + "gasPrice": "0x6", + "hash": "0x806e97f9d712b6cb7e781122001380a2837531b0fc1e5f5d78174ad4cb699873", + "input": "0x0001020304", + "nonce": "0x5", + "to": null, + "transactionIndex": null, + "value": "0x8", + "type": "0x0", + "chainId": "0x539", + "v": "0xa96", + "r": "0x9dc28b267b6ad4e4af6fe9289668f9305c2eb7a3241567860699e478af06835a", + "s": "0xa0b51a071aa9bed2cd70aedea859779dff039e3630ea38497d95202e9b1fec7" + }`, + }, + { + Tx: &types.AccessListTx{ + ChainID: config.ChainID, + Nonce: 5, + GasPrice: big.NewInt(6), + Gas: 7, + To: &addr, + Value: big.NewInt(8), + Data: []byte{0, 1, 2, 3, 4}, + AccessList: types.AccessList{ + types.AccessTuple{ + Address: common.Address{0x2}, + StorageKeys: []common.Hash{types.EmptyRootHash}, + }, }, + V: big.NewInt(32), + R: big.NewInt(10), + S: big.NewInt(11), }, - V: big.NewInt(32), - R: big.NewInt(10), - S: big.NewInt(11), - }, - &types.AccessListTx{ - ChainID: config.ChainID, - Nonce: 5, - GasPrice: big.NewInt(6), - Gas: 7, - To: nil, - Value: big.NewInt(8), - Data: []byte{0, 1, 2, 3, 4}, - AccessList: types.AccessList{ - types.AccessTuple{ - Address: common.Address{0x2}, - StorageKeys: []common.Hash{types.EmptyRootHash}, + Want: `{ + "blockHash": null, + "blockNumber": null, + "from": "0x71562b71999873db5b286df957af199ec94617f7", + "gas": "0x7", + "gasPrice": "0x6", + "hash": "0x121347468ee5fe0a29f02b49b4ffd1c8342bc4255146bb686cd07117f79e7129", + "input": "0x0001020304", + "nonce": "0x5", + "to": "0xdead000000000000000000000000000000000000", + "transactionIndex": null, + "value": "0x8", + "type": "0x1", + "accessList": [ + { + "address": "0x0200000000000000000000000000000000000000", + "storageKeys": [ + "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + ] + } + ], + "chainId": "0x539", + "v": "0x0", + "r": "0xf372ad499239ae11d91d34c559ffc5dab4daffc0069e03afcabdcdf231a0c16b", + "s": "0x28573161d1f9472fa0fd4752533609e72f06414f7ab5588699a7141f65d2abf", + "yParity": "0x0" + }`, + }, { + Tx: &types.AccessListTx{ + ChainID: config.ChainID, + Nonce: 5, + GasPrice: big.NewInt(6), + Gas: 7, + To: nil, + Value: big.NewInt(8), + Data: []byte{0, 1, 2, 3, 4}, + AccessList: types.AccessList{ + types.AccessTuple{ + Address: common.Address{0x2}, + StorageKeys: []common.Hash{types.EmptyRootHash}, + }, }, + V: big.NewInt(32), + R: big.NewInt(10), + S: big.NewInt(11), }, - V: big.NewInt(32), - R: big.NewInt(10), - S: big.NewInt(11), - }, - &types.DynamicFeeTx{ - ChainID: config.ChainID, - Nonce: 5, - GasTipCap: big.NewInt(6), - GasFeeCap: big.NewInt(9), - Gas: 7, - To: &addr, - Value: big.NewInt(8), - Data: []byte{0, 1, 2, 3, 4}, - AccessList: types.AccessList{ - types.AccessTuple{ - Address: common.Address{0x2}, - StorageKeys: []common.Hash{types.EmptyRootHash}, + Want: `{ + "blockHash": null, + "blockNumber": null, + "from": "0x71562b71999873db5b286df957af199ec94617f7", + "gas": "0x7", + "gasPrice": "0x6", + "hash": "0x067c3baebede8027b0f828a9d933be545f7caaec623b00684ac0659726e2055b", + "input": "0x0001020304", + "nonce": "0x5", + "to": null, + "transactionIndex": null, + "value": "0x8", + "type": "0x1", + "accessList": [ + { + "address": "0x0200000000000000000000000000000000000000", + "storageKeys": [ + "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + ] + } + ], + "chainId": "0x539", + "v": "0x1", + "r": "0x542981b5130d4613897fbab144796cb36d3cb3d7807d47d9c7f89ca7745b085c", + "s": "0x7425b9dd6c5deaa42e4ede35d0c4570c4624f68c28d812c10d806ffdf86ce63", + "yParity": "0x1" + }`, + }, { + Tx: &types.DynamicFeeTx{ + ChainID: config.ChainID, + Nonce: 5, + GasTipCap: big.NewInt(6), + GasFeeCap: big.NewInt(9), + Gas: 7, + To: &addr, + Value: big.NewInt(8), + Data: []byte{0, 1, 2, 3, 4}, + AccessList: types.AccessList{ + types.AccessTuple{ + Address: common.Address{0x2}, + StorageKeys: []common.Hash{types.EmptyRootHash}, + }, }, + V: big.NewInt(32), + R: big.NewInt(10), + S: big.NewInt(11), + }, + Want: `{ + "blockHash": null, + "blockNumber": null, + "from": "0x71562b71999873db5b286df957af199ec94617f7", + "gas": "0x7", + "gasPrice": "0x9", + "maxFeePerGas": "0x9", + "maxPriorityFeePerGas": "0x6", + "hash": "0xb63e0b146b34c3e9cb7fbabb5b3c081254a7ded6f1b65324b5898cc0545d79ff", + "input": "0x0001020304", + "nonce": "0x5", + "to": "0xdead000000000000000000000000000000000000", + "transactionIndex": null, + "value": "0x8", + "type": "0x2", + "accessList": [ + { + "address": "0x0200000000000000000000000000000000000000", + "storageKeys": [ + "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + ] + } + ], + "chainId": "0x539", + "v": "0x1", + "r": "0x3b167e05418a8932cd53d7578711fe1a76b9b96c48642402bb94978b7a107e80", + "s": "0x22f98a332d15ea2cc80386c1ebaa31b0afebfa79ebc7d039a1e0074418301fef", + "yParity": "0x1" + }`, + }, { + Tx: &types.DynamicFeeTx{ + ChainID: config.ChainID, + Nonce: 5, + GasTipCap: big.NewInt(6), + GasFeeCap: big.NewInt(9), + Gas: 7, + To: nil, + Value: big.NewInt(8), + Data: []byte{0, 1, 2, 3, 4}, + AccessList: types.AccessList{}, + V: big.NewInt(32), + R: big.NewInt(10), + S: big.NewInt(11), }, - V: big.NewInt(32), - R: big.NewInt(10), - S: big.NewInt(11), + Want: `{ + "blockHash": null, + "blockNumber": null, + "from": "0x71562b71999873db5b286df957af199ec94617f7", + "gas": "0x7", + "gasPrice": "0x9", + "maxFeePerGas": "0x9", + "maxPriorityFeePerGas": "0x6", + "hash": "0xcbab17ee031a9d5b5a09dff909f0a28aedb9b295ac0635d8710d11c7b806ec68", + "input": "0x0001020304", + "nonce": "0x5", + "to": null, + "transactionIndex": null, + "value": "0x8", + "type": "0x2", + "accessList": [], + "chainId": "0x539", + "v": "0x0", + "r": "0x6446b8a682db7e619fc6b4f6d1f708f6a17351a41c7fbd63665f469bc78b41b9", + "s": "0x7626abc15834f391a117c63450047309dbf84c5ce3e8e609b607062641e2de43", + "yParity": "0x0" + }`, }, - &types.DynamicFeeTx{ - ChainID: config.ChainID, - Nonce: 5, - GasTipCap: big.NewInt(6), - GasFeeCap: big.NewInt(9), - Gas: 7, - To: nil, - Value: big.NewInt(8), - Data: []byte{0, 1, 2, 3, 4}, - AccessList: types.AccessList{}, - V: big.NewInt(32), - R: big.NewInt(10), - S: big.NewInt(11), + } +} + +func allBlobTxs(addr common.Address, config *params.ChainConfig) []txData { + return []txData{ + { + Tx: &types.BlobTx{ + Nonce: 6, + GasTipCap: uint256.NewInt(1), + GasFeeCap: uint256.NewInt(5), + Gas: 6, + To: addr, + BlobFeeCap: uint256.NewInt(1), + BlobHashes: []common.Hash{{1}}, + Value: new(uint256.Int), + V: uint256.NewInt(32), + R: uint256.NewInt(10), + S: uint256.NewInt(11), + }, + Want: `{ + "blockHash": null, + "blockNumber": null, + "from": "0x71562b71999873db5b286df957af199ec94617f7", + "gas": "0x6", + "gasPrice": "0x5", + "maxFeePerGas": "0x5", + "maxPriorityFeePerGas": "0x1", + "maxFeePerBlobGas": "0x1", + "hash": "0x1f2b59a20e61efc615ad0cbe936379d6bbea6f938aafaf35eb1da05d8e7f46a3", + "input": "0x", + "nonce": "0x6", + "to": "0xdead000000000000000000000000000000000000", + "transactionIndex": null, + "value": "0x0", + "type": "0x3", + "accessList": [], + "chainId": "0x1", + "blobVersionedHashes": [ + "0x0100000000000000000000000000000000000000000000000000000000000000" + ], + "v": "0x0", + "r": "0x618be8908e0e5320f8f3b48042a079fe5a335ebd4ed1422a7d2207cd45d872bc", + "s": "0x27b2bc6c80e849a8e8b764d4549d8c2efac3441e73cf37054eb0a9b9f8e89b27", + "yParity": "0x0" + }`, }, } } type testBackend struct { - db ethdb.Database - chain *core.BlockChain + db ethdb.Database + chain *core.BlockChain + pending *types.Block } -func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { +func newTestBackend(t *testing.T, n int, gspec *core.Genesis, engine consensus.Engine, generator func(i int, b *core.BlockGen)) *testBackend { var ( - engine = ethash.NewFaker() - backend = &testBackend{ - db: rawdb.NewMemoryDatabase(), - } cacheConfig = &core.CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, @@ -200,18 +420,24 @@ func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i i } ) // Generate blocks for testing - _, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, generator) - chain, err := core.NewBlockChain(backend.db, cacheConfig, nil, gspec, nil, engine, vm.Config{}, nil, nil) + db, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, generator) + txlookupLimit := uint64(0) + chain, err := core.NewBlockChain(db, cacheConfig, nil, gspec, nil, engine, vm.Config{}, nil, &txlookupLimit) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } if n, err := chain.InsertChain(blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } - backend.chain = chain + + backend := &testBackend{db: db, chain: chain} return backend } +func (b *testBackend) setPendingBlock(block *types.Block) { + b.pending = block +} + func (b testBackend) SyncProgress() ethereum.SyncProgress { return ethereum.SyncProgress{} } func (b testBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { return big.NewInt(0), nil @@ -231,31 +457,46 @@ func (b testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) if number == rpc.LatestBlockNumber { return b.chain.CurrentBlock(), nil } + if number == rpc.PendingBlockNumber && b.pending != nil { + return b.pending.Header(), nil + } return b.chain.GetHeaderByNumber(uint64(number)), nil } func (b testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { - panic("implement me") + return b.chain.GetHeaderByHash(hash), nil } func (b testBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { - panic("implement me") + if blockNr, ok := blockNrOrHash.Number(); ok { + return b.HeaderByNumber(ctx, blockNr) + } + if blockHash, ok := blockNrOrHash.Hash(); ok { + return b.HeaderByHash(ctx, blockHash) + } + panic("unknown type rpc.BlockNumberOrHash") } -func (b testBackend) CurrentHeader() *types.Header { panic("implement me") } -func (b testBackend) CurrentBlock() *types.Header { panic("implement me") } +func (b testBackend) CurrentHeader() *types.Header { return b.chain.CurrentBlock() } +func (b testBackend) CurrentBlock() *types.Header { return b.chain.CurrentBlock() } func (b testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { if number == rpc.LatestBlockNumber { head := b.chain.CurrentBlock() return b.chain.GetBlock(head.Hash(), head.Number.Uint64()), nil } + if number == rpc.PendingBlockNumber { + return b.pending, nil + } return b.chain.GetBlockByNumber(uint64(number)), nil } func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - panic("implement me") + return b.chain.GetBlockByHash(hash), nil } func (b testBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { if blockNr, ok := blockNrOrHash.Number(); ok { return b.BlockByNumber(ctx, blockNr) } - panic("implement me") + if blockHash, ok := blockNrOrHash.Hash(); ok { + return b.BlockByHash(ctx, blockHash) + } + panic("unknown type rpc.BlockNumberOrHash") } func (b testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { return b.chain.GetBlock(hash, uint64(number.Int64())).Body(), nil @@ -282,9 +523,19 @@ func (b testBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOr } func (b testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { panic("implement me") } func (b testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { - panic("implement me") + header, err := b.HeaderByHash(ctx, hash) + if header == nil || err != nil { + return nil, err + } + receipts := rawdb.ReadReceipts(b.db, hash, header.Number.Uint64(), header.Time, b.chain.Config()) + return receipts, nil +} +func (b testBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { + if b.pending != nil && hash == b.pending.Hash() { + return nil + } + return big.NewInt(1) } -func (b testBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { panic("implement me") } func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext) (*vm.EVM, func() error) { vmError := func() error { return nil } if vmConfig == nil { @@ -310,7 +561,8 @@ func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) er panic("implement me") } func (b testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { - panic("implement me") + tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.db, txHash) + return tx, blockHash, blockNumber, index, nil } func (b testBackend) GetPoolTransactions() (types.Transactions, error) { panic("implement me") } func (b testBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { panic("implement me") } @@ -318,10 +570,10 @@ func (b testBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uin panic("implement me") } func (b testBackend) Stats() (pending int, queued int) { panic("implement me") } -func (b testBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { +func (b testBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { panic("implement me") } -func (b testBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) { +func (b testBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { panic("implement me") } func (b testBackend) SubscribeNewTxsEvent(events chan<- core.NewTxsEvent) event.Subscription { @@ -370,7 +622,7 @@ func TestEstimateGas(t *testing.T) { signer = types.HomesteadSigner{} randomAccounts = newAccounts(2) ) - api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) { // Transfer from account[0] to account[1] // value: 1000 wei // fee: 0 wei @@ -380,6 +632,7 @@ func TestEstimateGas(t *testing.T) { var testSuite = []struct { blockNumber rpc.BlockNumber call TransactionArgs + overrides StateOverride expectErr error want uint64 }{ @@ -412,9 +665,30 @@ func TestEstimateGas(t *testing.T) { expectErr: nil, want: 53000, }, + { + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{}, + overrides: StateOverride{ + randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))}, + }, + expectErr: nil, + want: 53000, + }, + { + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{ + From: &randomAccounts[0].addr, + To: &randomAccounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + overrides: StateOverride{ + randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(big.NewInt(0))}, + }, + expectErr: core.ErrInsufficientFunds, + }, } for i, tc := range testSuite { - result, err := api.EstimateGas(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}) + result, err := api.EstimateGas(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides) if tc.expectErr != nil { if err == nil { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) @@ -451,7 +725,7 @@ func TestCall(t *testing.T) { genBlocks = 10 signer = types.HomesteadSigner{} ) - api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) { // Transfer from account[0] to account[1] // value: 1000 wei // fee: 0 wei @@ -580,7 +854,7 @@ func TestCall(t *testing.T) { }, } for i, tc := range testSuite { - result, err := api.Call(context.Background(), tc.call, rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides) + result, err := api.Call(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides) if tc.expectErr != nil { if err == nil { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) @@ -609,19 +883,13 @@ type Account struct { addr common.Address } -type Accounts []Account - -func (a Accounts) Len() int { return len(a) } -func (a Accounts) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a Accounts) Less(i, j int) bool { return bytes.Compare(a[i].addr.Bytes(), a[j].addr.Bytes()) < 0 } - -func newAccounts(n int) (accounts Accounts) { +func newAccounts(n int) (accounts []Account) { for i := 0; i < n; i++ { key, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(key.PublicKey) accounts = append(accounts, Account{key: key, addr: addr}) } - sort.Sort(accounts) + slices.SortFunc(accounts, func(a, b Account) int { return a.addr.Cmp(b.addr) }) return accounts } @@ -635,32 +903,8 @@ func hex2Bytes(str string) *hexutil.Bytes { return &rpcBytes } -// testHasher is the helper tool for transaction/receipt list hashing. -// The original hasher is trie, in order to get rid of import cycle, -// use the testing hasher instead. -type testHasher struct { - hasher hash.Hash -} - -func newHasher() *testHasher { - return &testHasher{hasher: sha3.NewLegacyKeccak256()} -} - -func (h *testHasher) Reset() { - h.hasher.Reset() -} - -func (h *testHasher) Update(key, val []byte) error { - h.hasher.Write(key) - h.hasher.Write(val) - return nil -} - -func (h *testHasher) Hash() common.Hash { - return common.BytesToHash(h.hasher.Sum(nil)) -} - func TestRPCMarshalBlock(t *testing.T) { + t.Parallel() var ( txs []*types.Transaction to = common.BytesToAddress([]byte{0x11}) @@ -689,7 +933,7 @@ func TestRPCMarshalBlock(t *testing.T) { } txs = append(txs, tx) } - block := types.NewBlock(&types.Header{Number: big.NewInt(100)}, txs, nil, nil, newHasher()) + block := types.NewBlock(&types.Header{Number: big.NewInt(100)}, txs, nil, nil, blocktest.NewHasher()) var testSuite = []struct { inclTx bool @@ -700,36 +944,702 @@ func TestRPCMarshalBlock(t *testing.T) { { inclTx: false, fullTx: false, - want: `{"difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x64","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x296","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","transactionsRoot":"0x661a9febcfa8f1890af549b874faf9fa274aede26ef489d9db0b25daa569450e","uncles":[]}`, + want: `{ + "difficulty": "0x0", + "extraData": "0x", + "gasLimit": "0x0", + "gasUsed": "0x0", + "hash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x64", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x296", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x0", + "transactionsRoot": "0x661a9febcfa8f1890af549b874faf9fa274aede26ef489d9db0b25daa569450e", + "uncles": [] + }`, }, // only tx hashes { inclTx: true, fullTx: false, - want: `{"difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x64","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x296","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","transactions":["0x7d39df979e34172322c64983a9ad48302c2b889e55bda35324afecf043a77605","0x9bba4c34e57c875ff57ac8d172805a26ae912006985395dc1bdf8f44140a7bf4","0x98909ea1ff040da6be56bc4231d484de1414b3c1dac372d69293a4beb9032cb5","0x12e1f81207b40c3bdcc13c0ee18f5f86af6d31754d57a0ea1b0d4cfef21abef1"],"transactionsRoot":"0x661a9febcfa8f1890af549b874faf9fa274aede26ef489d9db0b25daa569450e","uncles":[]}`, + want: `{ + "difficulty": "0x0", + "extraData": "0x", + "gasLimit": "0x0", + "gasUsed": "0x0", + "hash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x64", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x296", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x0", + "transactions": [ + "0x7d39df979e34172322c64983a9ad48302c2b889e55bda35324afecf043a77605", + "0x9bba4c34e57c875ff57ac8d172805a26ae912006985395dc1bdf8f44140a7bf4", + "0x98909ea1ff040da6be56bc4231d484de1414b3c1dac372d69293a4beb9032cb5", + "0x12e1f81207b40c3bdcc13c0ee18f5f86af6d31754d57a0ea1b0d4cfef21abef1" + ], + "transactionsRoot": "0x661a9febcfa8f1890af549b874faf9fa274aede26ef489d9db0b25daa569450e", + "uncles": [] + }`, }, - // full tx details { inclTx: true, fullTx: true, - want: `{"difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x64","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x296","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","transactions":[{"blockHash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","blockNumber":"0x64","from":"0x0000000000000000000000000000000000000000","gas":"0x457","gasPrice":"0x2b67","hash":"0x7d39df979e34172322c64983a9ad48302c2b889e55bda35324afecf043a77605","input":"0x111111","nonce":"0x1","to":"0x0000000000000000000000000000000000000011","transactionIndex":"0x0","value":"0x6f","type":"0x1","accessList":[],"chainId":"0x539","v":"0x0","r":"0x0","s":"0x0"},{"blockHash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","blockNumber":"0x64","from":"0x0000000000000000000000000000000000000000","gas":"0x457","gasPrice":"0x2b67","hash":"0x9bba4c34e57c875ff57ac8d172805a26ae912006985395dc1bdf8f44140a7bf4","input":"0x111111","nonce":"0x2","to":"0x0000000000000000000000000000000000000011","transactionIndex":"0x1","value":"0x6f","type":"0x0","chainId":"0x7fffffffffffffee","v":"0x0","r":"0x0","s":"0x0"},{"blockHash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","blockNumber":"0x64","from":"0x0000000000000000000000000000000000000000","gas":"0x457","gasPrice":"0x2b67","hash":"0x98909ea1ff040da6be56bc4231d484de1414b3c1dac372d69293a4beb9032cb5","input":"0x111111","nonce":"0x3","to":"0x0000000000000000000000000000000000000011","transactionIndex":"0x2","value":"0x6f","type":"0x1","accessList":[],"chainId":"0x539","v":"0x0","r":"0x0","s":"0x0"},{"blockHash":"0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee","blockNumber":"0x64","from":"0x0000000000000000000000000000000000000000","gas":"0x457","gasPrice":"0x2b67","hash":"0x12e1f81207b40c3bdcc13c0ee18f5f86af6d31754d57a0ea1b0d4cfef21abef1","input":"0x111111","nonce":"0x4","to":"0x0000000000000000000000000000000000000011","transactionIndex":"0x3","value":"0x6f","type":"0x0","chainId":"0x7fffffffffffffee","v":"0x0","r":"0x0","s":"0x0"}],"transactionsRoot":"0x661a9febcfa8f1890af549b874faf9fa274aede26ef489d9db0b25daa569450e","uncles":[]}`, + want: `{ + "difficulty": "0x0", + "extraData": "0x", + "gasLimit": "0x0", + "gasUsed": "0x0", + "hash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x64", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x296", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x0", + "transactions": [ + { + "blockHash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "blockNumber": "0x64", + "from": "0x0000000000000000000000000000000000000000", + "gas": "0x457", + "gasPrice": "0x2b67", + "hash": "0x7d39df979e34172322c64983a9ad48302c2b889e55bda35324afecf043a77605", + "input": "0x111111", + "nonce": "0x1", + "to": "0x0000000000000000000000000000000000000011", + "transactionIndex": "0x0", + "value": "0x6f", + "type": "0x1", + "accessList": [], + "chainId": "0x539", + "v": "0x0", + "r": "0x0", + "s": "0x0", + "yParity": "0x0" + }, + { + "blockHash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "blockNumber": "0x64", + "from": "0x0000000000000000000000000000000000000000", + "gas": "0x457", + "gasPrice": "0x2b67", + "hash": "0x9bba4c34e57c875ff57ac8d172805a26ae912006985395dc1bdf8f44140a7bf4", + "input": "0x111111", + "nonce": "0x2", + "to": "0x0000000000000000000000000000000000000011", + "transactionIndex": "0x1", + "value": "0x6f", + "type": "0x0", + "chainId": "0x7fffffffffffffee", + "v": "0x0", + "r": "0x0", + "s": "0x0" + }, + { + "blockHash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "blockNumber": "0x64", + "from": "0x0000000000000000000000000000000000000000", + "gas": "0x457", + "gasPrice": "0x2b67", + "hash": "0x98909ea1ff040da6be56bc4231d484de1414b3c1dac372d69293a4beb9032cb5", + "input": "0x111111", + "nonce": "0x3", + "to": "0x0000000000000000000000000000000000000011", + "transactionIndex": "0x2", + "value": "0x6f", + "type": "0x1", + "accessList": [], + "chainId": "0x539", + "v": "0x0", + "r": "0x0", + "s": "0x0", + "yParity": "0x0" + }, + { + "blockHash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "blockNumber": "0x64", + "from": "0x0000000000000000000000000000000000000000", + "gas": "0x457", + "gasPrice": "0x2b67", + "hash": "0x12e1f81207b40c3bdcc13c0ee18f5f86af6d31754d57a0ea1b0d4cfef21abef1", + "input": "0x111111", + "nonce": "0x4", + "to": "0x0000000000000000000000000000000000000011", + "transactionIndex": "0x3", + "value": "0x6f", + "type": "0x0", + "chainId": "0x7fffffffffffffee", + "v": "0x0", + "r": "0x0", + "s": "0x0" + } + ], + "transactionsRoot": "0x661a9febcfa8f1890af549b874faf9fa274aede26ef489d9db0b25daa569450e", + "uncles": [] + }`, }, } for i, tc := range testSuite { - resp, err := RPCMarshalBlock(block, tc.inclTx, tc.fullTx, params.MainnetChainConfig) + resp := RPCMarshalBlock(block, tc.inclTx, tc.fullTx, params.MainnetChainConfig) + out, err := json.Marshal(resp) if err != nil { - t.Errorf("test %d: got error %v", i, err) + t.Errorf("test %d: json marshal error: %v", i, err) continue } - out, err := json.Marshal(resp) + require.JSONEqf(t, tc.want, string(out), "test %d", i) + } +} + +func TestRPCGetBlockOrHeader(t *testing.T) { + t.Parallel() + + // Initialize test accounts + var ( + acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey) + acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey) + genesis = &core.Genesis{ + Config: params.TestChainConfig, + Alloc: core.GenesisAlloc{ + acc1Addr: {Balance: big.NewInt(params.Ether)}, + acc2Addr: {Balance: big.NewInt(params.Ether)}, + }, + } + genBlocks = 10 + signer = types.HomesteadSigner{} + tx = types.NewTx(&types.LegacyTx{ + Nonce: 11, + GasPrice: big.NewInt(11111), + Gas: 1111, + To: &acc2Addr, + Value: big.NewInt(111), + Data: []byte{0x11, 0x11, 0x11}, + }) + withdrawal = &types.Withdrawal{ + Index: 0, + Validator: 1, + Address: common.Address{0x12, 0x34}, + Amount: 10, + } + pending = types.NewBlockWithWithdrawals(&types.Header{Number: big.NewInt(11), Time: 42}, []*types.Transaction{tx}, nil, nil, []*types.Withdrawal{withdrawal}, blocktest.NewHasher()) + ) + backend := newTestBackend(t, genBlocks, genesis, ethash.NewFaker(), func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: &acc2Addr, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), signer, acc1Key) + b.AddTx(tx) + }) + backend.setPendingBlock(pending) + api := NewBlockChainAPI(backend) + blockHashes := make([]common.Hash, genBlocks+1) + ctx := context.Background() + for i := 0; i <= genBlocks; i++ { + header, err := backend.HeaderByNumber(ctx, rpc.BlockNumber(i)) if err != nil { - t.Errorf("test %d: json marshal error: %v", i, err) + t.Errorf("failed to get block: %d err: %v", i, err) + } + blockHashes[i] = header.Hash() + } + pendingHash := pending.Hash() + + var testSuite = []struct { + blockNumber rpc.BlockNumber + blockHash *common.Hash + fullTx bool + reqHeader bool + file string + expectErr error + }{ + // 0. latest header + { + blockNumber: rpc.LatestBlockNumber, + reqHeader: true, + file: "tag-latest", + }, + // 1. genesis header + { + blockNumber: rpc.BlockNumber(0), + reqHeader: true, + file: "number-0", + }, + // 2. #1 header + { + blockNumber: rpc.BlockNumber(1), + reqHeader: true, + file: "number-1", + }, + // 3. latest-1 header + { + blockNumber: rpc.BlockNumber(9), + reqHeader: true, + file: "number-latest-1", + }, + // 4. latest+1 header + { + blockNumber: rpc.BlockNumber(11), + reqHeader: true, + file: "number-latest+1", + }, + // 5. pending header + { + blockNumber: rpc.PendingBlockNumber, + reqHeader: true, + file: "tag-pending", + }, + // 6. latest block + { + blockNumber: rpc.LatestBlockNumber, + file: "tag-latest", + }, + // 7. genesis block + { + blockNumber: rpc.BlockNumber(0), + file: "number-0", + }, + // 8. #1 block + { + blockNumber: rpc.BlockNumber(1), + file: "number-1", + }, + // 9. latest-1 block + { + blockNumber: rpc.BlockNumber(9), + fullTx: true, + file: "number-latest-1", + }, + // 10. latest+1 block + { + blockNumber: rpc.BlockNumber(11), + fullTx: true, + file: "number-latest+1", + }, + // 11. pending block + { + blockNumber: rpc.PendingBlockNumber, + file: "tag-pending", + }, + // 12. pending block + fullTx + { + blockNumber: rpc.PendingBlockNumber, + fullTx: true, + file: "tag-pending-fullTx", + }, + // 13. latest header by hash + { + blockHash: &blockHashes[len(blockHashes)-1], + reqHeader: true, + file: "hash-latest", + }, + // 14. genesis header by hash + { + blockHash: &blockHashes[0], + reqHeader: true, + file: "hash-0", + }, + // 15. #1 header + { + blockHash: &blockHashes[1], + reqHeader: true, + file: "hash-1", + }, + // 16. latest-1 header + { + blockHash: &blockHashes[len(blockHashes)-2], + reqHeader: true, + file: "hash-latest-1", + }, + // 17. empty hash + { + blockHash: &common.Hash{}, + reqHeader: true, + file: "hash-empty", + }, + // 18. pending hash + { + blockHash: &pendingHash, + reqHeader: true, + file: `hash-pending`, + }, + // 19. latest block + { + blockHash: &blockHashes[len(blockHashes)-1], + file: "hash-latest", + }, + // 20. genesis block + { + blockHash: &blockHashes[0], + file: "hash-genesis", + }, + // 21. #1 block + { + blockHash: &blockHashes[1], + file: "hash-1", + }, + // 22. latest-1 block + { + blockHash: &blockHashes[len(blockHashes)-2], + fullTx: true, + file: "hash-latest-1-fullTx", + }, + // 23. empty hash + body + { + blockHash: &common.Hash{}, + fullTx: true, + file: "hash-empty-fullTx", + }, + // 24. pending block + { + blockHash: &pendingHash, + file: `hash-pending`, + }, + // 25. pending block + fullTx + { + blockHash: &pendingHash, + fullTx: true, + file: "hash-pending-fullTx", + }, + } + + for i, tt := range testSuite { + var ( + result map[string]interface{} + err error + rpc string + ) + if tt.blockHash != nil { + if tt.reqHeader { + result = api.GetHeaderByHash(context.Background(), *tt.blockHash) + rpc = "eth_getHeaderByHash" + } else { + result, err = api.GetBlockByHash(context.Background(), *tt.blockHash, tt.fullTx) + rpc = "eth_getBlockByHash" + } + } else { + if tt.reqHeader { + result, err = api.GetHeaderByNumber(context.Background(), tt.blockNumber) + rpc = "eth_getHeaderByNumber" + } else { + result, err = api.GetBlockByNumber(context.Background(), tt.blockNumber, tt.fullTx) + rpc = "eth_getBlockByNumber" + } + } + if tt.expectErr != nil { + if err == nil { + t.Errorf("test %d: want error %v, have nothing", i, tt.expectErr) + continue + } + if !errors.Is(err, tt.expectErr) { + t.Errorf("test %d: error mismatch, want %v, have %v", i, tt.expectErr, err) + } + continue + } + if err != nil { + t.Errorf("test %d: want no error, have %v", i, err) continue } - if have := string(out); have != tc.want { - t.Errorf("test %d: want: %s have: %s", i, tc.want, have) + + testRPCResponseWithFile(t, i, result, rpc, tt.file) + } +} + +func setupReceiptBackend(t *testing.T, genBlocks int) (*testBackend, []common.Hash) { + config := *params.TestChainConfig + config.ShanghaiTime = new(uint64) + config.CancunTime = new(uint64) + var ( + acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey) + acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey) + contract = common.HexToAddress("0000000000000000000000000000000000031ec7") + genesis = &core.Genesis{ + Config: &config, + ExcessBlobGas: new(uint64), + BlobGasUsed: new(uint64), + Alloc: core.GenesisAlloc{ + acc1Addr: {Balance: big.NewInt(params.Ether)}, + acc2Addr: {Balance: big.NewInt(params.Ether)}, + // // SPDX-License-Identifier: GPL-3.0 + // pragma solidity >=0.7.0 <0.9.0; + // + // contract Token { + // event Transfer(address indexed from, address indexed to, uint256 value); + // function transfer(address to, uint256 value) public returns (bool) { + // emit Transfer(msg.sender, to, value); + // return true; + // } + // } + contract: {Balance: big.NewInt(params.Ether), Code: common.FromHex("0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063a9059cbb14610030575b600080fd5b61004a6004803603810190610045919061016a565b610060565b60405161005791906101c5565b60405180910390f35b60008273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516100bf91906101ef565b60405180910390a36001905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610101826100d6565b9050919050565b610111816100f6565b811461011c57600080fd5b50565b60008135905061012e81610108565b92915050565b6000819050919050565b61014781610134565b811461015257600080fd5b50565b6000813590506101648161013e565b92915050565b60008060408385031215610181576101806100d1565b5b600061018f8582860161011f565b92505060206101a085828601610155565b9150509250929050565b60008115159050919050565b6101bf816101aa565b82525050565b60006020820190506101da60008301846101b6565b92915050565b6101e981610134565b82525050565b600060208201905061020460008301846101e0565b9291505056fea2646970667358221220b469033f4b77b9565ee84e0a2f04d496b18160d26034d54f9487e57788fd36d564736f6c63430008120033")}, + }, } + signer = types.LatestSignerForChainID(params.TestChainConfig.ChainID) + txHashes = make([]common.Hash, genBlocks) + ) + + // Set the terminal total difficulty in the config + genesis.Config.TerminalTotalDifficulty = big.NewInt(0) + genesis.Config.TerminalTotalDifficultyPassed = true + backend := newTestBackend(t, genBlocks, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) { + var ( + tx *types.Transaction + err error + ) + switch i { + case 0: + // transfer 1000wei + tx, err = types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: &acc2Addr, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), types.HomesteadSigner{}, acc1Key) + case 1: + // create contract + tx, err = types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: nil, Gas: 53100, GasPrice: b.BaseFee(), Data: common.FromHex("0x60806040")}), signer, acc1Key) + case 2: + // with logs + // transfer(address to, uint256 value) + data := fmt.Sprintf("0xa9059cbb%s%s", common.HexToHash(common.BigToAddress(big.NewInt(int64(i + 1))).Hex()).String()[2:], common.BytesToHash([]byte{byte(i + 11)}).String()[2:]) + tx, err = types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: &contract, Gas: 60000, GasPrice: b.BaseFee(), Data: common.FromHex(data)}), signer, acc1Key) + case 3: + // dynamic fee with logs + // transfer(address to, uint256 value) + data := fmt.Sprintf("0xa9059cbb%s%s", common.HexToHash(common.BigToAddress(big.NewInt(int64(i + 1))).Hex()).String()[2:], common.BytesToHash([]byte{byte(i + 11)}).String()[2:]) + fee := big.NewInt(500) + fee.Add(fee, b.BaseFee()) + tx, err = types.SignTx(types.NewTx(&types.DynamicFeeTx{Nonce: uint64(i), To: &contract, Gas: 60000, Value: big.NewInt(1), GasTipCap: big.NewInt(500), GasFeeCap: fee, Data: common.FromHex(data)}), signer, acc1Key) + case 4: + // access list with contract create + accessList := types.AccessList{{ + Address: contract, + StorageKeys: []common.Hash{{0}}, + }} + tx, err = types.SignTx(types.NewTx(&types.AccessListTx{Nonce: uint64(i), To: nil, Gas: 58100, GasPrice: b.BaseFee(), Data: common.FromHex("0x60806040"), AccessList: accessList}), signer, acc1Key) + case 5: + // blob tx + fee := big.NewInt(500) + fee.Add(fee, b.BaseFee()) + tx, err = types.SignTx(types.NewTx(&types.BlobTx{ + Nonce: uint64(i), + GasTipCap: uint256.NewInt(1), + GasFeeCap: uint256.MustFromBig(fee), + Gas: params.TxGas, + To: acc2Addr, + BlobFeeCap: uint256.NewInt(1), + BlobHashes: []common.Hash{{1}}, + Value: new(uint256.Int), + }), signer, acc1Key) + } + if err != nil { + t.Errorf("failed to sign tx: %v", err) + } + if tx != nil { + b.AddTx(tx) + txHashes[i] = tx.Hash() + } + b.SetPoS() + }) + return backend, txHashes +} + +func TestRPCGetTransactionReceipt(t *testing.T) { + t.Parallel() + + var ( + backend, txHashes = setupReceiptBackend(t, 6) + api = NewTransactionAPI(backend, new(AddrLocker)) + ) + + var testSuite = []struct { + txHash common.Hash + file string + }{ + // 0. normal success + { + txHash: txHashes[0], + file: "normal-transfer-tx", + }, + // 1. create contract + { + txHash: txHashes[1], + file: "create-contract-tx", + }, + // 2. with logs success + { + txHash: txHashes[2], + file: "with-logs", + }, + // 3. dynamic tx with logs success + { + txHash: txHashes[3], + file: `dynamic-tx-with-logs`, + }, + // 4. access list tx with create contract + { + txHash: txHashes[4], + file: "create-contract-with-access-list", + }, + // 5. txhash empty + { + txHash: common.Hash{}, + file: "txhash-empty", + }, + // 6. txhash not found + { + txHash: common.HexToHash("deadbeef"), + file: "txhash-notfound", + }, + // 7. blob tx + { + txHash: txHashes[5], + file: "blob-tx", + }, + } + + for i, tt := range testSuite { + var ( + result interface{} + err error + ) + result, err = api.GetTransactionReceipt(context.Background(), tt.txHash) + if err != nil { + t.Errorf("test %d: want no error, have %v", i, err) + continue + } + testRPCResponseWithFile(t, i, result, "eth_getTransactionReceipt", tt.file) + } +} + +func TestRPCGetBlockReceipts(t *testing.T) { + t.Parallel() + + var ( + genBlocks = 6 + backend, _ = setupReceiptBackend(t, genBlocks) + api = NewBlockChainAPI(backend) + ) + blockHashes := make([]common.Hash, genBlocks+1) + ctx := context.Background() + for i := 0; i <= genBlocks; i++ { + header, err := backend.HeaderByNumber(ctx, rpc.BlockNumber(i)) + if err != nil { + t.Errorf("failed to get block: %d err: %v", i, err) + } + blockHashes[i] = header.Hash() + } + + var testSuite = []struct { + test rpc.BlockNumberOrHash + file string + }{ + // 0. block without any txs(hash) + { + test: rpc.BlockNumberOrHashWithHash(blockHashes[0], false), + file: "number-0", + }, + // 1. block without any txs(number) + { + test: rpc.BlockNumberOrHashWithNumber(0), + file: "number-1", + }, + // 2. earliest tag + { + test: rpc.BlockNumberOrHashWithNumber(rpc.EarliestBlockNumber), + file: "tag-earliest", + }, + // 3. latest tag + { + test: rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber), + file: "tag-latest", + }, + // 4. block with legacy transfer tx(hash) + { + test: rpc.BlockNumberOrHashWithHash(blockHashes[1], false), + file: "block-with-legacy-transfer-tx", + }, + // 5. block with contract create tx(number) + { + test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(2)), + file: "block-with-contract-create-tx", + }, + // 6. block with legacy contract call tx(hash) + { + test: rpc.BlockNumberOrHashWithHash(blockHashes[3], false), + file: "block-with-legacy-contract-call-tx", + }, + // 7. block with dynamic fee tx(number) + { + test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(4)), + file: "block-with-dynamic-fee-tx", + }, + // 8. block is empty + { + test: rpc.BlockNumberOrHashWithHash(common.Hash{}, false), + file: "hash-empty", + }, + // 9. block is not found + { + test: rpc.BlockNumberOrHashWithHash(common.HexToHash("deadbeef"), false), + file: "hash-notfound", + }, + // 10. block is not found + { + test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(genBlocks + 1)), + file: "block-notfound", + }, + // 11. block with blob tx + { + test: rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(6)), + file: "block-with-blob-tx", + }, + } + + for i, tt := range testSuite { + var ( + result interface{} + err error + ) + result, err = api.GetBlockReceipts(context.Background(), tt.test) + if err != nil { + t.Errorf("test %d: want no error, have %v", i, err) + continue + } + testRPCResponseWithFile(t, i, result, "eth_getBlockReceipts", tt.file) + } +} + +func testRPCResponseWithFile(t *testing.T, testid int, result interface{}, rpc string, file string) { + data, err := json.MarshalIndent(result, "", " ") + if err != nil { + t.Errorf("test %d: json marshal error", testid) + return + } + outputFile := filepath.Join("testdata", fmt.Sprintf("%s-%s.json", rpc, file)) + if os.Getenv("WRITE_TEST_FILES") != "" { + os.WriteFile(outputFile, data, 0644) + } + want, err := os.ReadFile(outputFile) + if err != nil { + t.Fatalf("error reading expected test file: %s output: %v", outputFile, err) } + require.JSONEqf(t, string(want), string(data), "test %d: json not match, want: %s, have: %s", testid, string(want), string(data)) } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index f93ba44a71e8..6ebd22ddafd5 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -83,8 +83,8 @@ type Backend interface { GetPoolTransaction(txHash common.Hash) *types.Transaction GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) Stats() (pending int, queued int) - TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) - TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) + TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) + TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription ChainConfig() *params.ChainConfig diff --git a/internal/ethapi/testdata/eth_getBlockByHash-hash-1.json b/internal/ethapi/testdata/eth_getBlockByHash-hash-1.json new file mode 100644 index 000000000000..379636d5f380 --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByHash-hash-1.json @@ -0,0 +1,25 @@ +{ + "baseFeePerGas": "0x342770c0", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x5208", + "hash": "0x0da274b315de8e4d5bf8717218ec43540464ef36378cb896469bb731e1d3f3cb", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x1", + "parentHash": "0xbdc7d83b8f876938810462fe8d053263a482e44201e3883d4ae204ff4de7eff5", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x26a", + "stateRoot": "0x92c5c55a698963f5b06e3aee415630f5c48b0760e537af94917ce9c4f42a2e22", + "timestamp": "0xa", + "totalDifficulty": "0x1", + "transactions": [ + "0x644a31c354391520d00e95b9affbbb010fc79ac268144ab8e28207f4cf51097e" + ], + "transactionsRoot": "0xca0ebcce920d2cdfbf9e1dbe90ed3441a1a576f344bd80e60508da814916f4e7", + "uncles": [] +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByHash-hash-empty-fullTx.json b/internal/ethapi/testdata/eth_getBlockByHash-hash-empty-fullTx.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByHash-hash-empty-fullTx.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByHash-hash-genesis.json b/internal/ethapi/testdata/eth_getBlockByHash-hash-genesis.json new file mode 100644 index 000000000000..759dbf69e949 --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByHash-hash-genesis.json @@ -0,0 +1,23 @@ +{ + "baseFeePerGas": "0x3b9aca00", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x0", + "hash": "0xbdc7d83b8f876938810462fe8d053263a482e44201e3883d4ae204ff4de7eff5", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x200", + "stateRoot": "0xfe168c5e9584a85927212e5bea5304bb7d0d8a893453b4b2c52176a72f585ae2", + "timestamp": "0x0", + "totalDifficulty": "0x1", + "transactions": [], + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "uncles": [] +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByHash-hash-latest-1-fullTx.json b/internal/ethapi/testdata/eth_getBlockByHash-hash-latest-1-fullTx.json new file mode 100644 index 000000000000..3526da1219a7 --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByHash-hash-latest-1-fullTx.json @@ -0,0 +1,41 @@ +{ + "baseFeePerGas": "0x121a9cca", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x5208", + "hash": "0xda97ed946e0d502fb898b0ac881bd44da3c7fee5eaf184431e1ec3d361dad17e", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x9", + "parentHash": "0x5abd19c39d9f1c6e52998e135ea14e1fbc5db3fa2a108f4538e238ca5c2e68d7", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x26a", + "stateRoot": "0xbd4aa2c2873df709151075250a8c01c9a14d2b0e2f715dbdd16e0ef8030c2cf0", + "timestamp": "0x5a", + "totalDifficulty": "0x1", + "transactions": [ + { + "blockHash": "0xda97ed946e0d502fb898b0ac881bd44da3c7fee5eaf184431e1ec3d361dad17e", + "blockNumber": "0x9", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gas": "0x5208", + "gasPrice": "0x121a9cca", + "hash": "0xecd155a61a5734b3efab75924e3ae34026c7c4133d8c2a46122bd03d7d199725", + "input": "0x", + "nonce": "0x8", + "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", + "transactionIndex": "0x0", + "value": "0x3e8", + "type": "0x0", + "v": "0x1b", + "r": "0xc6028b8e983d62fa8542f8a7633fb23cc941be2c897134352d95a7d9b19feafd", + "s": "0xeb6adcaaae3bed489c6cce4435f9db05d23a52820c78bd350e31eec65ed809d" + } + ], + "transactionsRoot": "0x0767ed8359337dc6a8fdc77fe52db611bed1be87aac73c4556b1bf1dd3d190a5", + "uncles": [] +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByHash-hash-latest.json b/internal/ethapi/testdata/eth_getBlockByHash-hash-latest.json new file mode 100644 index 000000000000..32fee83268fb --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByHash-hash-latest.json @@ -0,0 +1,25 @@ +{ + "baseFeePerGas": "0xfdc7303", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x5208", + "hash": "0x97f540a3577c0f645c5dada5da86f38350e8f847e71f21124f917835003e2607", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0xa", + "parentHash": "0xda97ed946e0d502fb898b0ac881bd44da3c7fee5eaf184431e1ec3d361dad17e", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x26a", + "stateRoot": "0xbb62872e4023fa8a8b17b9cc37031f4817d9595779748d01cba408b495707a91", + "timestamp": "0x64", + "totalDifficulty": "0x1", + "transactions": [ + "0x3ee4094ca1e0b07a66dd616a057e081e53144ca7e9685a126fd4dda9ca042644" + ], + "transactionsRoot": "0xb0893d21a4a44dc26a962a6e91abae66df87fb61ac9c60e936aee89c76331445", + "uncles": [] +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByHash-hash-pending-fullTx.json b/internal/ethapi/testdata/eth_getBlockByHash-hash-pending-fullTx.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByHash-hash-pending-fullTx.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByHash-hash-pending.json b/internal/ethapi/testdata/eth_getBlockByHash-hash-pending.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByHash-hash-pending.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByNumber-number-0.json b/internal/ethapi/testdata/eth_getBlockByNumber-number-0.json new file mode 100644 index 000000000000..759dbf69e949 --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByNumber-number-0.json @@ -0,0 +1,23 @@ +{ + "baseFeePerGas": "0x3b9aca00", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x0", + "hash": "0xbdc7d83b8f876938810462fe8d053263a482e44201e3883d4ae204ff4de7eff5", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x200", + "stateRoot": "0xfe168c5e9584a85927212e5bea5304bb7d0d8a893453b4b2c52176a72f585ae2", + "timestamp": "0x0", + "totalDifficulty": "0x1", + "transactions": [], + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "uncles": [] +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByNumber-number-1.json b/internal/ethapi/testdata/eth_getBlockByNumber-number-1.json new file mode 100644 index 000000000000..379636d5f380 --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByNumber-number-1.json @@ -0,0 +1,25 @@ +{ + "baseFeePerGas": "0x342770c0", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x5208", + "hash": "0x0da274b315de8e4d5bf8717218ec43540464ef36378cb896469bb731e1d3f3cb", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x1", + "parentHash": "0xbdc7d83b8f876938810462fe8d053263a482e44201e3883d4ae204ff4de7eff5", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x26a", + "stateRoot": "0x92c5c55a698963f5b06e3aee415630f5c48b0760e537af94917ce9c4f42a2e22", + "timestamp": "0xa", + "totalDifficulty": "0x1", + "transactions": [ + "0x644a31c354391520d00e95b9affbbb010fc79ac268144ab8e28207f4cf51097e" + ], + "transactionsRoot": "0xca0ebcce920d2cdfbf9e1dbe90ed3441a1a576f344bd80e60508da814916f4e7", + "uncles": [] +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByNumber-number-latest+1.json b/internal/ethapi/testdata/eth_getBlockByNumber-number-latest+1.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByNumber-number-latest+1.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByNumber-number-latest-1.json b/internal/ethapi/testdata/eth_getBlockByNumber-number-latest-1.json new file mode 100644 index 000000000000..3526da1219a7 --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByNumber-number-latest-1.json @@ -0,0 +1,41 @@ +{ + "baseFeePerGas": "0x121a9cca", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x5208", + "hash": "0xda97ed946e0d502fb898b0ac881bd44da3c7fee5eaf184431e1ec3d361dad17e", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x9", + "parentHash": "0x5abd19c39d9f1c6e52998e135ea14e1fbc5db3fa2a108f4538e238ca5c2e68d7", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x26a", + "stateRoot": "0xbd4aa2c2873df709151075250a8c01c9a14d2b0e2f715dbdd16e0ef8030c2cf0", + "timestamp": "0x5a", + "totalDifficulty": "0x1", + "transactions": [ + { + "blockHash": "0xda97ed946e0d502fb898b0ac881bd44da3c7fee5eaf184431e1ec3d361dad17e", + "blockNumber": "0x9", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gas": "0x5208", + "gasPrice": "0x121a9cca", + "hash": "0xecd155a61a5734b3efab75924e3ae34026c7c4133d8c2a46122bd03d7d199725", + "input": "0x", + "nonce": "0x8", + "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", + "transactionIndex": "0x0", + "value": "0x3e8", + "type": "0x0", + "v": "0x1b", + "r": "0xc6028b8e983d62fa8542f8a7633fb23cc941be2c897134352d95a7d9b19feafd", + "s": "0xeb6adcaaae3bed489c6cce4435f9db05d23a52820c78bd350e31eec65ed809d" + } + ], + "transactionsRoot": "0x0767ed8359337dc6a8fdc77fe52db611bed1be87aac73c4556b1bf1dd3d190a5", + "uncles": [] +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByNumber-tag-latest.json b/internal/ethapi/testdata/eth_getBlockByNumber-tag-latest.json new file mode 100644 index 000000000000..32fee83268fb --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByNumber-tag-latest.json @@ -0,0 +1,25 @@ +{ + "baseFeePerGas": "0xfdc7303", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x5208", + "hash": "0x97f540a3577c0f645c5dada5da86f38350e8f847e71f21124f917835003e2607", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0xa", + "parentHash": "0xda97ed946e0d502fb898b0ac881bd44da3c7fee5eaf184431e1ec3d361dad17e", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x26a", + "stateRoot": "0xbb62872e4023fa8a8b17b9cc37031f4817d9595779748d01cba408b495707a91", + "timestamp": "0x64", + "totalDifficulty": "0x1", + "transactions": [ + "0x3ee4094ca1e0b07a66dd616a057e081e53144ca7e9685a126fd4dda9ca042644" + ], + "transactionsRoot": "0xb0893d21a4a44dc26a962a6e91abae66df87fb61ac9c60e936aee89c76331445", + "uncles": [] +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByNumber-tag-pending-fullTx.json b/internal/ethapi/testdata/eth_getBlockByNumber-tag-pending-fullTx.json new file mode 100644 index 000000000000..1d524d6eced1 --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByNumber-tag-pending-fullTx.json @@ -0,0 +1,50 @@ +{ + "difficulty": "0x0", + "extraData": "0x", + "gasLimit": "0x0", + "gasUsed": "0x0", + "hash": null, + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": null, + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": null, + "number": "0xb", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x256", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x2a", + "totalDifficulty": null, + "transactions": [ + { + "blockHash": "0x6cebd9f966ea686f44b981685e3f0eacea28591a7a86d7fbbe521a86e9f81165", + "blockNumber": "0xb", + "from": "0x0000000000000000000000000000000000000000", + "gas": "0x457", + "gasPrice": "0x2b67", + "hash": "0x4afee081df5dff7a025964032871f7d4ba4d21baf5f6376a2f4a9f79fc506298", + "input": "0x111111", + "nonce": "0xb", + "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", + "transactionIndex": "0x0", + "value": "0x6f", + "type": "0x0", + "chainId": "0x7fffffffffffffee", + "v": "0x0", + "r": "0x0", + "s": "0x0" + } + ], + "transactionsRoot": "0x98d9f6dd0aa479c0fb448f2627e9f1964aca699fccab8f6e95861547a4699e37", + "uncles": [], + "withdrawals": [ + { + "index": "0x0", + "validatorIndex": "0x1", + "address": "0x1234000000000000000000000000000000000000", + "amount": "0xa" + } + ], + "withdrawalsRoot": "0x73d756269cdfc22e7e17a3548e36f42f750ca06d7e3cd98d1b6d0eb5add9dc84" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByNumber-tag-pending.json b/internal/ethapi/testdata/eth_getBlockByNumber-tag-pending.json new file mode 100644 index 000000000000..c0e2b07bb8aa --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockByNumber-tag-pending.json @@ -0,0 +1,33 @@ +{ + "difficulty": "0x0", + "extraData": "0x", + "gasLimit": "0x0", + "gasUsed": "0x0", + "hash": null, + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": null, + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": null, + "number": "0xb", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x256", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x2a", + "totalDifficulty": null, + "transactions": [ + "0x4afee081df5dff7a025964032871f7d4ba4d21baf5f6376a2f4a9f79fc506298" + ], + "transactionsRoot": "0x98d9f6dd0aa479c0fb448f2627e9f1964aca699fccab8f6e95861547a4699e37", + "uncles": [], + "withdrawals": [ + { + "index": "0x0", + "validatorIndex": "0x1", + "address": "0x1234000000000000000000000000000000000000", + "amount": "0xa" + } + ], + "withdrawalsRoot": "0x73d756269cdfc22e7e17a3548e36f42f750ca06d7e3cd98d1b6d0eb5add9dc84" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-notfound.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-notfound.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-notfound.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-blob-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-blob-tx.json new file mode 100644 index 000000000000..591fab673d34 --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-blob-tx.json @@ -0,0 +1,20 @@ +[ + { + "blobGasPrice": "0x1", + "blobGasUsed": "0x20000", + "blockHash": "0xe724dfd4349861f4dceef2bc4df086d0a3d88858214f6bee9fcf1bebd1edc2a6", + "blockNumber": "0x6", + "contractAddress": null, + "cumulativeGasUsed": "0x5208", + "effectiveGasPrice": "0x1b09d63b", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gasUsed": "0x5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", + "transactionHash": "0xb51ee3d2a89ba5d5623c73133c8d7a6ba9fb41194c17f4302c21b30994a1180f", + "transactionIndex": "0x0", + "type": "0x3" + } +] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-contract-create-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-contract-create-tx.json new file mode 100644 index 000000000000..f1e0db22c2dc --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-contract-create-tx.json @@ -0,0 +1,18 @@ +[ + { + "blockHash": "0x1e7dcf3abe8bf05d32367a5dc387caa32578b15871bf8b3cbeedf2d8d530f844", + "blockNumber": "0x2", + "contractAddress": "0xae9bea628c4ce503dcfd7e305cab4e29e7476592", + "cumulativeGasUsed": "0xcf50", + "effectiveGasPrice": "0x2db16291", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gasUsed": "0xcf50", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": null, + "transactionHash": "0x340e58cda5086495010b571fe25067fecc9954dc4ee3cedece00691fa3f5904a", + "transactionIndex": "0x0", + "type": "0x0" + } +] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-dynamic-fee-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-dynamic-fee-tx.json new file mode 100644 index 000000000000..520e30e4ea8f --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-dynamic-fee-tx.json @@ -0,0 +1,18 @@ +[ + { + "blockHash": "0xffa737e6ce9a9162ffd411dd06169114b3ed5ee9fc1474a2625c92548e4455e0", + "blockNumber": "0x4", + "contractAddress": null, + "cumulativeGasUsed": "0x538d", + "effectiveGasPrice": "0x2325c42f", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gasUsed": "0x538d", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x0", + "to": "0x0000000000000000000000000000000000031ec7", + "transactionHash": "0xdcde2574628c9d7dff22b9afa19f235959a924ceec65a9df903a517ae91f5c84", + "transactionIndex": "0x0", + "type": "0x2" + } +] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-contract-call-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-contract-call-tx.json new file mode 100644 index 000000000000..a71cf4b37f0e --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-contract-call-tx.json @@ -0,0 +1,34 @@ +[ + { + "blockHash": "0x173dcd9d22ce71929cd17e84ea88702a0f84d6244c6898d2a4f48722e494fe9c", + "blockNumber": "0x3", + "contractAddress": null, + "cumulativeGasUsed": "0x5e28", + "effectiveGasPrice": "0x281c2585", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gasUsed": "0x5e28", + "logs": [ + { + "address": "0x0000000000000000000000000000000000031ec7", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000703c4b2bd70c169f5717101caee543299fc946c7", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000000d", + "blockNumber": "0x3", + "transactionHash": "0xeaf3921cbf03ba45bad4e6ab807b196ce3b2a0b5bacc355b6272fa96b11b4287", + "transactionIndex": "0x0", + "blockHash": "0x173dcd9d22ce71929cd17e84ea88702a0f84d6244c6898d2a4f48722e494fe9c", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000800000000000000008000000000000000000000000000000000020000000080000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000400000000002000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000", + "status": "0x1", + "to": "0x0000000000000000000000000000000000031ec7", + "transactionHash": "0xeaf3921cbf03ba45bad4e6ab807b196ce3b2a0b5bacc355b6272fa96b11b4287", + "transactionIndex": "0x0", + "type": "0x0" + } +] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-transfer-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-transfer-tx.json new file mode 100644 index 000000000000..3e16c3062e96 --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-transfer-tx.json @@ -0,0 +1,18 @@ +[ + { + "blockHash": "0xa8a067b3cb3b9ddc6cfb8317bfd08b266fcf9994fc870c1f7ed394acecfadf39", + "blockNumber": "0x1", + "contractAddress": null, + "cumulativeGasUsed": "0x5208", + "effectiveGasPrice": "0x342770c0", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gasUsed": "0x5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", + "transactionHash": "0x644a31c354391520d00e95b9affbbb010fc79ac268144ab8e28207f4cf51097e", + "transactionIndex": "0x0", + "type": "0x0" + } +] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-hash-empty.json b/internal/ethapi/testdata/eth_getBlockReceipts-hash-empty.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockReceipts-hash-empty.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-hash-notfound.json b/internal/ethapi/testdata/eth_getBlockReceipts-hash-notfound.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockReceipts-hash-notfound.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-number-0.json b/internal/ethapi/testdata/eth_getBlockReceipts-number-0.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockReceipts-number-0.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-number-1.json b/internal/ethapi/testdata/eth_getBlockReceipts-number-1.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockReceipts-number-1.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-tag-earliest.json b/internal/ethapi/testdata/eth_getBlockReceipts-tag-earliest.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockReceipts-tag-earliest.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-tag-latest.json b/internal/ethapi/testdata/eth_getBlockReceipts-tag-latest.json new file mode 100644 index 000000000000..591fab673d34 --- /dev/null +++ b/internal/ethapi/testdata/eth_getBlockReceipts-tag-latest.json @@ -0,0 +1,20 @@ +[ + { + "blobGasPrice": "0x1", + "blobGasUsed": "0x20000", + "blockHash": "0xe724dfd4349861f4dceef2bc4df086d0a3d88858214f6bee9fcf1bebd1edc2a6", + "blockNumber": "0x6", + "contractAddress": null, + "cumulativeGasUsed": "0x5208", + "effectiveGasPrice": "0x1b09d63b", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gasUsed": "0x5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", + "transactionHash": "0xb51ee3d2a89ba5d5623c73133c8d7a6ba9fb41194c17f4302c21b30994a1180f", + "transactionIndex": "0x0", + "type": "0x3" + } +] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByHash-hash-0.json b/internal/ethapi/testdata/eth_getHeaderByHash-hash-0.json new file mode 100644 index 000000000000..dc61aa9a2e5d --- /dev/null +++ b/internal/ethapi/testdata/eth_getHeaderByHash-hash-0.json @@ -0,0 +1,20 @@ +{ + "baseFeePerGas": "0x3b9aca00", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x0", + "hash": "0xbdc7d83b8f876938810462fe8d053263a482e44201e3883d4ae204ff4de7eff5", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "stateRoot": "0xfe168c5e9584a85927212e5bea5304bb7d0d8a893453b4b2c52176a72f585ae2", + "timestamp": "0x0", + "totalDifficulty": "0x1", + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByHash-hash-1.json b/internal/ethapi/testdata/eth_getHeaderByHash-hash-1.json new file mode 100644 index 000000000000..c1dc70f64f13 --- /dev/null +++ b/internal/ethapi/testdata/eth_getHeaderByHash-hash-1.json @@ -0,0 +1,20 @@ +{ + "baseFeePerGas": "0x342770c0", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x5208", + "hash": "0x0da274b315de8e4d5bf8717218ec43540464ef36378cb896469bb731e1d3f3cb", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x1", + "parentHash": "0xbdc7d83b8f876938810462fe8d053263a482e44201e3883d4ae204ff4de7eff5", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "stateRoot": "0x92c5c55a698963f5b06e3aee415630f5c48b0760e537af94917ce9c4f42a2e22", + "timestamp": "0xa", + "totalDifficulty": "0x1", + "transactionsRoot": "0xca0ebcce920d2cdfbf9e1dbe90ed3441a1a576f344bd80e60508da814916f4e7" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByHash-hash-empty.json b/internal/ethapi/testdata/eth_getHeaderByHash-hash-empty.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/internal/ethapi/testdata/eth_getHeaderByHash-hash-empty.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest-1.json b/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest-1.json new file mode 100644 index 000000000000..a63ff86700f4 --- /dev/null +++ b/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest-1.json @@ -0,0 +1,20 @@ +{ + "baseFeePerGas": "0x121a9cca", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x5208", + "hash": "0xda97ed946e0d502fb898b0ac881bd44da3c7fee5eaf184431e1ec3d361dad17e", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x9", + "parentHash": "0x5abd19c39d9f1c6e52998e135ea14e1fbc5db3fa2a108f4538e238ca5c2e68d7", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "stateRoot": "0xbd4aa2c2873df709151075250a8c01c9a14d2b0e2f715dbdd16e0ef8030c2cf0", + "timestamp": "0x5a", + "totalDifficulty": "0x1", + "transactionsRoot": "0x0767ed8359337dc6a8fdc77fe52db611bed1be87aac73c4556b1bf1dd3d190a5" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest.json b/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest.json new file mode 100644 index 000000000000..f2affcc1c96b --- /dev/null +++ b/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest.json @@ -0,0 +1,20 @@ +{ + "baseFeePerGas": "0xfdc7303", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x5208", + "hash": "0x97f540a3577c0f645c5dada5da86f38350e8f847e71f21124f917835003e2607", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0xa", + "parentHash": "0xda97ed946e0d502fb898b0ac881bd44da3c7fee5eaf184431e1ec3d361dad17e", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "stateRoot": "0xbb62872e4023fa8a8b17b9cc37031f4817d9595779748d01cba408b495707a91", + "timestamp": "0x64", + "totalDifficulty": "0x1", + "transactionsRoot": "0xb0893d21a4a44dc26a962a6e91abae66df87fb61ac9c60e936aee89c76331445" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByHash-hash-pending.json b/internal/ethapi/testdata/eth_getHeaderByHash-hash-pending.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/internal/ethapi/testdata/eth_getHeaderByHash-hash-pending.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByNumber-number-0.json b/internal/ethapi/testdata/eth_getHeaderByNumber-number-0.json new file mode 100644 index 000000000000..dc61aa9a2e5d --- /dev/null +++ b/internal/ethapi/testdata/eth_getHeaderByNumber-number-0.json @@ -0,0 +1,20 @@ +{ + "baseFeePerGas": "0x3b9aca00", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x0", + "hash": "0xbdc7d83b8f876938810462fe8d053263a482e44201e3883d4ae204ff4de7eff5", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "stateRoot": "0xfe168c5e9584a85927212e5bea5304bb7d0d8a893453b4b2c52176a72f585ae2", + "timestamp": "0x0", + "totalDifficulty": "0x1", + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByNumber-number-1.json b/internal/ethapi/testdata/eth_getHeaderByNumber-number-1.json new file mode 100644 index 000000000000..c1dc70f64f13 --- /dev/null +++ b/internal/ethapi/testdata/eth_getHeaderByNumber-number-1.json @@ -0,0 +1,20 @@ +{ + "baseFeePerGas": "0x342770c0", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x5208", + "hash": "0x0da274b315de8e4d5bf8717218ec43540464ef36378cb896469bb731e1d3f3cb", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x1", + "parentHash": "0xbdc7d83b8f876938810462fe8d053263a482e44201e3883d4ae204ff4de7eff5", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "stateRoot": "0x92c5c55a698963f5b06e3aee415630f5c48b0760e537af94917ce9c4f42a2e22", + "timestamp": "0xa", + "totalDifficulty": "0x1", + "transactionsRoot": "0xca0ebcce920d2cdfbf9e1dbe90ed3441a1a576f344bd80e60508da814916f4e7" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByNumber-number-latest+1.json b/internal/ethapi/testdata/eth_getHeaderByNumber-number-latest+1.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/internal/ethapi/testdata/eth_getHeaderByNumber-number-latest+1.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByNumber-number-latest-1.json b/internal/ethapi/testdata/eth_getHeaderByNumber-number-latest-1.json new file mode 100644 index 000000000000..a63ff86700f4 --- /dev/null +++ b/internal/ethapi/testdata/eth_getHeaderByNumber-number-latest-1.json @@ -0,0 +1,20 @@ +{ + "baseFeePerGas": "0x121a9cca", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x5208", + "hash": "0xda97ed946e0d502fb898b0ac881bd44da3c7fee5eaf184431e1ec3d361dad17e", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0x9", + "parentHash": "0x5abd19c39d9f1c6e52998e135ea14e1fbc5db3fa2a108f4538e238ca5c2e68d7", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "stateRoot": "0xbd4aa2c2873df709151075250a8c01c9a14d2b0e2f715dbdd16e0ef8030c2cf0", + "timestamp": "0x5a", + "totalDifficulty": "0x1", + "transactionsRoot": "0x0767ed8359337dc6a8fdc77fe52db611bed1be87aac73c4556b1bf1dd3d190a5" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByNumber-tag-latest.json b/internal/ethapi/testdata/eth_getHeaderByNumber-tag-latest.json new file mode 100644 index 000000000000..f2affcc1c96b --- /dev/null +++ b/internal/ethapi/testdata/eth_getHeaderByNumber-tag-latest.json @@ -0,0 +1,20 @@ +{ + "baseFeePerGas": "0xfdc7303", + "difficulty": "0x20000", + "extraData": "0x", + "gasLimit": "0x47e7c4", + "gasUsed": "0x5208", + "hash": "0x97f540a3577c0f645c5dada5da86f38350e8f847e71f21124f917835003e2607", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "number": "0xa", + "parentHash": "0xda97ed946e0d502fb898b0ac881bd44da3c7fee5eaf184431e1ec3d361dad17e", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "stateRoot": "0xbb62872e4023fa8a8b17b9cc37031f4817d9595779748d01cba408b495707a91", + "timestamp": "0x64", + "totalDifficulty": "0x1", + "transactionsRoot": "0xb0893d21a4a44dc26a962a6e91abae66df87fb61ac9c60e936aee89c76331445" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByNumber-tag-pending.json b/internal/ethapi/testdata/eth_getHeaderByNumber-tag-pending.json new file mode 100644 index 000000000000..da177f218928 --- /dev/null +++ b/internal/ethapi/testdata/eth_getHeaderByNumber-tag-pending.json @@ -0,0 +1,20 @@ +{ + "difficulty": "0x0", + "extraData": "0x", + "gasLimit": "0x0", + "gasUsed": "0x0", + "hash": null, + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner": null, + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": null, + "number": "0xb", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x2a", + "totalDifficulty": null, + "transactionsRoot": "0x98d9f6dd0aa479c0fb448f2627e9f1964aca699fccab8f6e95861547a4699e37", + "withdrawalsRoot": "0x73d756269cdfc22e7e17a3548e36f42f750ca06d7e3cd98d1b6d0eb5add9dc84" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-blob-tx.json b/internal/ethapi/testdata/eth_getTransactionReceipt-blob-tx.json new file mode 100644 index 000000000000..c3a4a0deee8c --- /dev/null +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-blob-tx.json @@ -0,0 +1,18 @@ +{ + "blobGasPrice": "0x1", + "blobGasUsed": "0x20000", + "blockHash": "0xe724dfd4349861f4dceef2bc4df086d0a3d88858214f6bee9fcf1bebd1edc2a6", + "blockNumber": "0x6", + "contractAddress": null, + "cumulativeGasUsed": "0x5208", + "effectiveGasPrice": "0x1b09d63b", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gasUsed": "0x5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", + "transactionHash": "0xb51ee3d2a89ba5d5623c73133c8d7a6ba9fb41194c17f4302c21b30994a1180f", + "transactionIndex": "0x0", + "type": "0x3" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-tx.json b/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-tx.json new file mode 100644 index 000000000000..ad6d6152ec3f --- /dev/null +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-tx.json @@ -0,0 +1,16 @@ +{ + "blockHash": "0x1e7dcf3abe8bf05d32367a5dc387caa32578b15871bf8b3cbeedf2d8d530f844", + "blockNumber": "0x2", + "contractAddress": "0xae9bea628c4ce503dcfd7e305cab4e29e7476592", + "cumulativeGasUsed": "0xcf50", + "effectiveGasPrice": "0x2db16291", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gasUsed": "0xcf50", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": null, + "transactionHash": "0x340e58cda5086495010b571fe25067fecc9954dc4ee3cedece00691fa3f5904a", + "transactionIndex": "0x0", + "type": "0x0" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-with-access-list.json b/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-with-access-list.json new file mode 100644 index 000000000000..b3362260a0a9 --- /dev/null +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-with-access-list.json @@ -0,0 +1,16 @@ +{ + "blockHash": "0x3fadc5bc916018a326732be829a2565b3acb960a8406f0f151a5e1fa971ea7dd", + "blockNumber": "0x5", + "contractAddress": "0xfdaa97661a584d977b4d3abb5370766ff5b86a18", + "cumulativeGasUsed": "0xe01c", + "effectiveGasPrice": "0x1ecb3fb4", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gasUsed": "0xe01c", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": null, + "transactionHash": "0xb5a1148819cfdfff9bfe70035524fec940eb735d89b76960b97751d01ae2a9f2", + "transactionIndex": "0x0", + "type": "0x1" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-dynamic-tx-with-logs.json b/internal/ethapi/testdata/eth_getTransactionReceipt-dynamic-tx-with-logs.json new file mode 100644 index 000000000000..cc0be1809e7f --- /dev/null +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-dynamic-tx-with-logs.json @@ -0,0 +1,16 @@ +{ + "blockHash": "0xffa737e6ce9a9162ffd411dd06169114b3ed5ee9fc1474a2625c92548e4455e0", + "blockNumber": "0x4", + "contractAddress": null, + "cumulativeGasUsed": "0x538d", + "effectiveGasPrice": "0x2325c42f", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gasUsed": "0x538d", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x0", + "to": "0x0000000000000000000000000000000000031ec7", + "transactionHash": "0xdcde2574628c9d7dff22b9afa19f235959a924ceec65a9df903a517ae91f5c84", + "transactionIndex": "0x0", + "type": "0x2" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-normal-transfer-tx.json b/internal/ethapi/testdata/eth_getTransactionReceipt-normal-transfer-tx.json new file mode 100644 index 000000000000..d3b6ef1c912a --- /dev/null +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-normal-transfer-tx.json @@ -0,0 +1,16 @@ +{ + "blockHash": "0xa8a067b3cb3b9ddc6cfb8317bfd08b266fcf9994fc870c1f7ed394acecfadf39", + "blockNumber": "0x1", + "contractAddress": null, + "cumulativeGasUsed": "0x5208", + "effectiveGasPrice": "0x342770c0", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gasUsed": "0x5208", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", + "transactionHash": "0x644a31c354391520d00e95b9affbbb010fc79ac268144ab8e28207f4cf51097e", + "transactionIndex": "0x0", + "type": "0x0" +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-txhash-empty.json b/internal/ethapi/testdata/eth_getTransactionReceipt-txhash-empty.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-txhash-empty.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-txhash-notfound.json b/internal/ethapi/testdata/eth_getTransactionReceipt-txhash-notfound.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-txhash-notfound.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-with-logs.json b/internal/ethapi/testdata/eth_getTransactionReceipt-with-logs.json new file mode 100644 index 000000000000..45a4f6d67031 --- /dev/null +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-with-logs.json @@ -0,0 +1,32 @@ +{ + "blockHash": "0x173dcd9d22ce71929cd17e84ea88702a0f84d6244c6898d2a4f48722e494fe9c", + "blockNumber": "0x3", + "contractAddress": null, + "cumulativeGasUsed": "0x5e28", + "effectiveGasPrice": "0x281c2585", + "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", + "gasUsed": "0x5e28", + "logs": [ + { + "address": "0x0000000000000000000000000000000000031ec7", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000703c4b2bd70c169f5717101caee543299fc946c7", + "0x0000000000000000000000000000000000000000000000000000000000000003" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000000d", + "blockNumber": "0x3", + "transactionHash": "0xeaf3921cbf03ba45bad4e6ab807b196ce3b2a0b5bacc355b6272fa96b11b4287", + "transactionIndex": "0x0", + "blockHash": "0x173dcd9d22ce71929cd17e84ea88702a0f84d6244c6898d2a4f48722e494fe9c", + "logIndex": "0x0", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000800000000000000008000000000000000000000000000000000020000000080000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000400000000002000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000", + "status": "0x1", + "to": "0x0000000000000000000000000000000000031ec7", + "transactionHash": "0xeaf3921cbf03ba45bad4e6ab807b196ce3b2a0b5bacc355b6272fa96b11b4287", + "transactionIndex": "0x0", + "type": "0x0" +} \ No newline at end of file diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 2369d0a78be9..ccdc56b4e0e2 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -113,7 +113,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { AccessList: args.AccessList, } pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber) - estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap()) + estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, nil, b.RPCGasCap()) if err != nil { return err } diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 06cfe27b000f..ba5dd3977f80 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -325,10 +325,10 @@ func (b *backendMock) GetPoolNonce(ctx context.Context, addr common.Address) (ui return 0, nil } func (b *backendMock) Stats() (pending int, queued int) { return 0, 0 } -func (b *backendMock) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { +func (b *backendMock) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { return nil, nil } -func (b *backendMock) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) { +func (b *backendMock) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { return nil, nil } func (b *backendMock) SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription { return nil } diff --git a/internal/flags/categories.go b/internal/flags/categories.go index c2db6c6c1d25..487684d98b3e 100644 --- a/internal/flags/categories.go +++ b/internal/flags/categories.go @@ -22,8 +22,9 @@ const ( EthCategory = "ETHEREUM" LightCategory = "LIGHT CLIENT" DevCategory = "DEVELOPER CHAIN" - EthashCategory = "ETHASH" - TxPoolCategory = "TRANSACTION POOL" + StateCategory = "STATE HISTORY MANAGEMENT" + TxPoolCategory = "TRANSACTION POOL (EVM)" + BlobPoolCategory = "TRANSACTION POOL (BLOB)" PerfCategory = "PERFORMANCE TUNING" AccountCategory = "ACCOUNT" APICategory = "API AND CONSOLE" diff --git a/internal/flags/flags.go b/internal/flags/flags.go index b0756b4e0a1f..69e9743556b4 100644 --- a/internal/flags/flags.go +++ b/internal/flags/flags.go @@ -20,11 +20,13 @@ import ( "encoding" "errors" "flag" + "fmt" "math/big" "os" "os/user" "path/filepath" "strings" + "syscall" "github.com/ethereum/go-ethereum/common/math" "github.com/urfave/cli/v2" @@ -68,6 +70,7 @@ type DirectoryFlag struct { Value DirectoryString Aliases []string + EnvVars []string } // For cli.Flag: @@ -79,6 +82,14 @@ func (f *DirectoryFlag) String() string { return cli.FlagStringer(f) } // Apply called by cli library, grabs variable from environment (if in env) // and adds variable to flag set for parsing. func (f *DirectoryFlag) Apply(set *flag.FlagSet) error { + for _, envVar := range f.EnvVars { + envVar = strings.TrimSpace(envVar) + if value, found := syscall.Getenv(envVar); found { + f.Value.Set(value) + f.HasBeenSet = true + break + } + } eachName(f, func(name string) { set.Var(&f.Value, f.Name, f.Usage) }) @@ -102,7 +113,7 @@ func (f *DirectoryFlag) GetCategory() string { return f.Category } func (f *DirectoryFlag) TakesValue() bool { return true } func (f *DirectoryFlag) GetUsage() string { return f.Usage } func (f *DirectoryFlag) GetValue() string { return f.Value.String() } -func (f *DirectoryFlag) GetEnvVars() []string { return nil } // env not supported +func (f *DirectoryFlag) GetEnvVars() []string { return f.EnvVars } func (f *DirectoryFlag) GetDefaultText() string { if f.DefaultText != "" { @@ -156,6 +167,7 @@ type TextMarshalerFlag struct { Value TextMarshaler Aliases []string + EnvVars []string } // For cli.Flag: @@ -165,6 +177,16 @@ func (f *TextMarshalerFlag) IsSet() bool { return f.HasBeenSet } func (f *TextMarshalerFlag) String() string { return cli.FlagStringer(f) } func (f *TextMarshalerFlag) Apply(set *flag.FlagSet) error { + for _, envVar := range f.EnvVars { + envVar = strings.TrimSpace(envVar) + if value, found := syscall.Getenv(envVar); found { + if err := f.Value.UnmarshalText([]byte(value)); err != nil { + return fmt.Errorf("could not parse %q from environment variable %q for flag %s: %s", value, envVar, f.Name, err) + } + f.HasBeenSet = true + break + } + } eachName(f, func(name string) { set.Var(textMarshalerVal{f.Value}, f.Name, f.Usage) }) @@ -187,7 +209,7 @@ func (f *TextMarshalerFlag) GetCategory() string { return f.Category } func (f *TextMarshalerFlag) TakesValue() bool { return true } func (f *TextMarshalerFlag) GetUsage() string { return f.Usage } -func (f *TextMarshalerFlag) GetEnvVars() []string { return nil } // env not supported +func (f *TextMarshalerFlag) GetEnvVars() []string { return f.EnvVars } func (f *TextMarshalerFlag) GetValue() string { t, err := f.Value.MarshalText() @@ -237,6 +259,7 @@ type BigFlag struct { Value *big.Int Aliases []string + EnvVars []string } // For cli.Flag: @@ -246,6 +269,16 @@ func (f *BigFlag) IsSet() bool { return f.HasBeenSet } func (f *BigFlag) String() string { return cli.FlagStringer(f) } func (f *BigFlag) Apply(set *flag.FlagSet) error { + for _, envVar := range f.EnvVars { + envVar = strings.TrimSpace(envVar) + if value, found := syscall.Getenv(envVar); found { + if _, ok := f.Value.SetString(value, 10); !ok { + return fmt.Errorf("could not parse %q from environment variable %q for flag %s", value, envVar, f.Name) + } + f.HasBeenSet = true + break + } + } eachName(f, func(name string) { f.Value = new(big.Int) set.Var((*bigValue)(f.Value), f.Name, f.Usage) @@ -271,7 +304,7 @@ func (f *BigFlag) GetCategory() string { return f.Category } func (f *BigFlag) TakesValue() bool { return true } func (f *BigFlag) GetUsage() string { return f.Usage } func (f *BigFlag) GetValue() string { return f.Value.String() } -func (f *BigFlag) GetEnvVars() []string { return nil } // env not supported +func (f *BigFlag) GetEnvVars() []string { return f.EnvVars } func (f *BigFlag) GetDefaultText() string { if f.DefaultText != "" { diff --git a/internal/flags/helpers.go b/internal/flags/helpers.go index f210e729dd27..d4b8e373cc45 100644 --- a/internal/flags/helpers.go +++ b/internal/flags/helpers.go @@ -18,13 +18,22 @@ package flags import ( "fmt" + "os" + "regexp" + "sort" "strings" "github.com/ethereum/go-ethereum/internal/version" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/mattn/go-isatty" "github.com/urfave/cli/v2" ) +// usecolor defines whether the CLI help should use colored output or normal dumb +// colorless terminal formatting. +var usecolor = (isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())) && os.Getenv("TERM") != "dumb" + // NewApp creates an app with sane defaults. func NewApp(usage string) *cli.App { git, _ := version.VCS() @@ -129,6 +138,14 @@ func doMigrateFlags(ctx *cli.Context) { } func init() { + if usecolor { + // Annotate all help categories with colors + cli.AppHelpTemplate = regexp.MustCompile("[A-Z ]+:").ReplaceAllString(cli.AppHelpTemplate, "\u001B[33m$0\u001B[0m") + + // Annotate flag categories with colors (private template, so need to + // copy-paste the entire thing here...) + cli.AppHelpTemplate = strings.ReplaceAll(cli.AppHelpTemplate, "{{template \"visibleFlagCategoryTemplate\" .}}", "{{range .VisibleFlagCategories}}\n {{if .Name}}\u001B[33m{{.Name}}\u001B[0m\n\n {{end}}{{$flglen := len .Flags}}{{range $i, $e := .Flags}}{{if eq (subtract $flglen $i) 1}}{{$e}}\n{{else}}{{$e}}\n {{end}}{{end}}{{end}}") + } cli.FlagStringer = FlagString } @@ -138,37 +155,31 @@ func FlagString(f cli.Flag) string { if !ok { return "" } - needsPlaceholder := df.TakesValue() placeholder := "" if needsPlaceholder { placeholder = "value" } - namesText := pad(cli.FlagNamePrefixer(df.Names(), placeholder), 30) + namesText := cli.FlagNamePrefixer(df.Names(), placeholder) defaultValueString := "" if s := df.GetDefaultText(); s != "" { defaultValueString = " (default: " + s + ")" } - - usage := strings.TrimSpace(df.GetUsage()) envHint := strings.TrimSpace(cli.FlagEnvHinter(df.GetEnvVars(), "")) - if len(envHint) > 0 { - usage += " " + envHint + if envHint != "" { + envHint = " (" + envHint[1:len(envHint)-1] + ")" } - + usage := strings.TrimSpace(df.GetUsage()) usage = wordWrap(usage, 80) usage = indent(usage, 10) - return fmt.Sprintf("\n %s%s\n%s", namesText, defaultValueString, usage) -} - -func pad(s string, length int) string { - if len(s) < length { - s += strings.Repeat(" ", length-len(s)) + if usecolor { + return fmt.Sprintf("\n \u001B[32m%-35s%-35s\u001B[0m%s\n%s", namesText, defaultValueString, envHint, usage) + } else { + return fmt.Sprintf("\n %-35s%-35s%s\n%s", namesText, defaultValueString, envHint, usage) } - return s } func indent(s string, nspace int) string { @@ -213,3 +224,78 @@ func wordWrap(s string, width int) string { return output.String() } + +// AutoEnvVars extends all the specific CLI flags with automatically generated +// env vars by capitalizing the flag, replacing . with _ and prefixing it with +// the specified string. +// +// Note, the prefix should *not* contain the separator underscore, that will be +// added automatically. +func AutoEnvVars(flags []cli.Flag, prefix string) { + for _, flag := range flags { + envvar := strings.ToUpper(prefix + "_" + strings.ReplaceAll(strings.ReplaceAll(flag.Names()[0], ".", "_"), "-", "_")) + + switch flag := flag.(type) { + case *cli.StringFlag: + flag.EnvVars = append(flag.EnvVars, envvar) + + case *cli.BoolFlag: + flag.EnvVars = append(flag.EnvVars, envvar) + + case *cli.IntFlag: + flag.EnvVars = append(flag.EnvVars, envvar) + + case *cli.Uint64Flag: + flag.EnvVars = append(flag.EnvVars, envvar) + + case *cli.DurationFlag: + flag.EnvVars = append(flag.EnvVars, envvar) + + case *cli.PathFlag: + flag.EnvVars = append(flag.EnvVars, envvar) + + case *BigFlag: + flag.EnvVars = append(flag.EnvVars, envvar) + + case *TextMarshalerFlag: + flag.EnvVars = append(flag.EnvVars, envvar) + + case *DirectoryFlag: + flag.EnvVars = append(flag.EnvVars, envvar) + } + } +} + +// CheckEnvVars iterates over all the environment variables and checks if any of +// them look like a CLI flag but is not consumed. This can be used to detect old +// or mistyped names. +func CheckEnvVars(ctx *cli.Context, flags []cli.Flag, prefix string) { + known := make(map[string]string) + for _, flag := range flags { + docflag, ok := flag.(cli.DocGenerationFlag) + if !ok { + continue + } + for _, envvar := range docflag.GetEnvVars() { + known[envvar] = flag.Names()[0] + } + } + keyvals := os.Environ() + sort.Strings(keyvals) + + for _, keyval := range keyvals { + key := strings.Split(keyval, "=")[0] + if !strings.HasPrefix(key, prefix) { + continue + } + if flag, ok := known[key]; ok { + if ctx.Count(flag) > 0 { + log.Info("Config environment variable found", "envvar", key, "shadowedby", "--"+flag) + } else { + log.Info("Config environment variable found", "envvar", key) + } + } else { + log.Warn("Unknown config environment variable", "envvar", key) + } + } +} diff --git a/internal/jsre/deps/web3.js b/internal/jsre/deps/web3.js index a291218ec51f..7a09fddab033 100644 --- a/internal/jsre/deps/web3.js +++ b/internal/jsre/deps/web3.js @@ -518,7 +518,7 @@ var f = require('./formatters'); var SolidityType = require('./type'); /** - * SolidityTypeAddress is a prootype that represents address type + * SolidityTypeAddress is a prototype that represents address type * It matches: * address * address[] @@ -546,7 +546,7 @@ var f = require('./formatters'); var SolidityType = require('./type'); /** - * SolidityTypeBool is a prootype that represents bool type + * SolidityTypeBool is a prototype that represents bool type * It matches: * bool * bool[] @@ -1146,7 +1146,7 @@ var f = require('./formatters'); var SolidityType = require('./type'); /** - * SolidityTypeInt is a prootype that represents int type + * SolidityTypeInt is a prototype that represents int type * It matches: * int * int[] @@ -1334,7 +1334,7 @@ var f = require('./formatters'); var SolidityType = require('./type'); /** - * SolidityTypeReal is a prootype that represents real type + * SolidityTypeReal is a prototype that represents real type * It matches: * real * real[] @@ -1647,7 +1647,7 @@ var f = require('./formatters'); var SolidityType = require('./type'); /** - * SolidityTypeUInt is a prootype that represents uint type + * SolidityTypeUInt is a prototype that represents uint type * It matches: * uint * uint[] @@ -1681,7 +1681,7 @@ var f = require('./formatters'); var SolidityType = require('./type'); /** - * SolidityTypeUReal is a prootype that represents ureal type + * SolidityTypeUReal is a prototype that represents ureal type * It matches: * ureal * ureal[] @@ -2307,7 +2307,7 @@ var toChecksumAddress = function (address) { }; /** - * Transforms given string to valid 20 bytes-length addres with 0x prefix + * Transforms given string to valid 20 bytes-length address with 0x prefix * * @method toAddress * @param {String} address diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index a2fc3b5a9040..b86b5909d2cb 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -30,6 +30,7 @@ var Modules = map[string]string{ "txpool": TxpoolJs, "les": LESJs, "vflux": VfluxJs, + "dev": DevJs, } const CliqueJs = ` @@ -495,6 +496,11 @@ web3._extend({ call: 'debug_setTrieFlushInterval', params: 1 }), + new web3._extend.Method({ + name: 'getTrieFlushInterval', + call: 'debug_getTrieFlushInterval', + params: 0 + }), ], properties: [] }); @@ -530,8 +536,8 @@ web3._extend({ new web3._extend.Method({ name: 'estimateGas', call: 'eth_estimateGas', - params: 2, - inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputBlockNumberFormatter], + params: 3, + inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputBlockNumberFormatter, null], outputFormatter: web3._extend.utils.toDecimal }), new web3._extend.Method({ @@ -611,6 +617,11 @@ web3._extend({ params: 4, inputFormatter: [web3._extend.formatters.inputCallFormatter, web3._extend.formatters.inputDefaultBlockNumberFormatter, null, null], }), + new web3._extend.Method({ + name: 'getBlockReceipts', + call: 'eth_getBlockReceipts', + params: 1, + }), ], properties: [ new web3._extend.Property({ @@ -641,8 +652,6 @@ web3._extend({ new web3._extend.Method({ name: 'start', call: 'miner_start', - params: 1, - inputFormatter: [null] }), new web3._extend.Method({ name: 'stop', @@ -883,3 +892,22 @@ web3._extend({ ] }); ` + +const DevJs = ` +web3._extend({ + property: 'dev', + methods: + [ + new web3._extend.Method({ + name: 'addWithdrawal', + call: 'dev_addWithdrawal', + params: 1 + }), + new web3._extend.Method({ + name: 'setFeeRecipient', + call: 'dev_setFeeRecipient', + params: 1 + }), + ], +}); +` diff --git a/les/api_backend.go b/les/api_backend.go index 3aaf6fe79337..311db0b82824 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -57,7 +57,6 @@ func (b *LesApiBackend) CurrentBlock() *types.Header { } func (b *LesApiBackend) SetHead(number uint64) { - b.eth.handler.downloader.Cancel() b.eth.blockchain.SetHead(number) } @@ -224,11 +223,11 @@ func (b *LesApiBackend) Stats() (pending int, queued int) { return b.eth.txPool.Stats(), 0 } -func (b *LesApiBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { +func (b *LesApiBackend) TxPoolContent() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { return b.eth.txPool.Content() } -func (b *LesApiBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) { +func (b *LesApiBackend) TxPoolContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { return b.eth.txPool.ContentFrom(addr) } @@ -264,12 +263,12 @@ func (b *LesApiBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEven } func (b *LesApiBackend) SyncProgressMap() map[string]interface{} { - progress := b.eth.Downloader().Progress() + progress := b.SyncProgress() return progress.ToMap() } func (b *LesApiBackend) SyncProgress() ethereum.SyncProgress { - return b.eth.Downloader().Progress() + return ethereum.SyncProgress{} } func (b *LesApiBackend) ProtocolVersion() int { diff --git a/les/api_test.go b/les/api_test.go index ac895362a0e3..484c95504c0d 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -31,9 +31,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/eth" - ethdownloader "github.com/ethereum/go-ethereum/eth/downloader" + "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/les/downloader" "github.com/ethereum/go-ethereum/les/flowcontrol" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -148,7 +147,7 @@ func testCapacityAPI(t *testing.T, clientCount int) { var wg sync.WaitGroup stop := make(chan struct{}) - reqCount := make([]uint64, len(clientRpcClients)) + reqCount := make([]atomic.Uint64, len(clientRpcClients)) // Send light request like crazy. for i, c := range clientRpcClients { @@ -158,7 +157,7 @@ func testCapacityAPI(t *testing.T, clientCount int) { defer wg.Done() queue := make(chan struct{}, 100) - reqCount[i] = 0 + reqCount[i].Store(0) for { select { case queue <- struct{}{}: @@ -174,8 +173,7 @@ func testCapacityAPI(t *testing.T, clientCount int) { wg.Done() <-queue if ok { - count := atomic.AddUint64(&reqCount[i], 1) - if count%10000 == 0 { + if reqCount[i].Add(1)%10000 == 0 { freezeClient(ctx, t, serverRpcClient, clients[i].ID()) } } @@ -193,7 +191,7 @@ func testCapacityAPI(t *testing.T, clientCount int) { processedSince := func(start []uint64) []uint64 { res := make([]uint64, len(reqCount)) for i := range reqCount { - res[i] = atomic.LoadUint64(&reqCount[i]) + res[i] = reqCount[i].Load() if start != nil { res[i] -= start[i] } @@ -293,8 +291,8 @@ func testCapacityAPI(t *testing.T, clientCount int) { close(stop) wg.Wait() - for i, count := range reqCount { - t.Log("client", i, "processed", count) + for i := range reqCount { + t.Log("client", i, "processed", reqCount[i].Load()) } return true }) { @@ -493,13 +491,13 @@ func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir [] func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { config := ethconfig.Defaults - config.SyncMode = (ethdownloader.SyncMode)(downloader.LightSync) + config.SyncMode = downloader.LightSync return New(stack, &config) } func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { config := ethconfig.Defaults - config.SyncMode = (ethdownloader.SyncMode)(downloader.FullSync) + config.SyncMode = downloader.FullSync config.LightServ = testServerCapacity config.LightPeers = testMaxClients ethereum, err := eth.New(stack, &config) diff --git a/les/benchmark.go b/les/benchmark.go index c63a2564d5b6..ab935183492d 100644 --- a/les/benchmark.go +++ b/les/benchmark.go @@ -117,7 +117,7 @@ func (b *benchmarkProofsOrCode) request(peer *serverPeer, index int) error { key := make([]byte, 32) crand.Read(key) if b.code { - return peer.requestCode(0, []CodeReq{{BHash: b.headHash, AccKey: key}}) + return peer.requestCode(0, []CodeReq{{BHash: b.headHash, AccountAddress: key}}) } return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}}) } diff --git a/les/catalyst/api.go b/les/catalyst/api.go deleted file mode 100644 index 30b41101dbc1..000000000000 --- a/les/catalyst/api.go +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright 2022 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 catalyst implements the temporary eth1/eth2 RPC integration. -package catalyst - -import ( - "errors" - "fmt" - - "github.com/ethereum/go-ethereum/beacon/engine" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/les" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc" -) - -// Register adds catalyst APIs to the light client. -func Register(stack *node.Node, backend *les.LightEthereum) error { - log.Warn("Catalyst mode enabled", "protocol", "les") - stack.RegisterAPIs([]rpc.API{ - { - Namespace: "engine", - Service: NewConsensusAPI(backend), - Authenticated: true, - }, - }) - return nil -} - -type ConsensusAPI struct { - les *les.LightEthereum -} - -// NewConsensusAPI creates a new consensus api for the given backend. -// The underlying blockchain needs to have a valid terminal total difficulty set. -func NewConsensusAPI(les *les.LightEthereum) *ConsensusAPI { - if les.BlockChain().Config().TerminalTotalDifficulty == nil { - log.Warn("Catalyst started without valid total difficulty") - } - return &ConsensusAPI{les: les} -} - -// ForkchoiceUpdatedV1 has several responsibilities: -// -// We try to set our blockchain to the headBlock. -// -// If the method is called with an empty head block: we return success, which can be used -// to check if the catalyst mode is enabled. -// -// If the total difficulty was not reached: we return INVALID. -// -// If the finalizedBlockHash is set: we check if we have the finalizedBlockHash in our db, -// if not we start a sync. -// -// If there are payloadAttributes: we return an error since block creation is not -// supported in les mode. -func (api *ConsensusAPI) ForkchoiceUpdatedV1(heads engine.ForkchoiceStateV1, payloadAttributes *engine.PayloadAttributes) (engine.ForkChoiceResponse, error) { - if heads.HeadBlockHash == (common.Hash{}) { - log.Warn("Forkchoice requested update to zero hash") - return engine.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this? - } - if err := api.checkTerminalTotalDifficulty(heads.HeadBlockHash); err != nil { - if header := api.les.BlockChain().GetHeaderByHash(heads.HeadBlockHash); header == nil { - // TODO (MariusVanDerWijden) trigger sync - return engine.STATUS_SYNCING, nil - } - return engine.STATUS_INVALID, err - } - // If the finalized block is set, check if it is in our blockchain - if heads.FinalizedBlockHash != (common.Hash{}) { - if header := api.les.BlockChain().GetHeaderByHash(heads.FinalizedBlockHash); header == nil { - // TODO (MariusVanDerWijden) trigger sync - return engine.STATUS_SYNCING, nil - } - } - // SetHead - if err := api.setCanonical(heads.HeadBlockHash); err != nil { - return engine.STATUS_INVALID, err - } - if payloadAttributes != nil { - return engine.STATUS_INVALID, errors.New("not supported") - } - return api.validForkChoiceResponse(), nil -} - -// GetPayloadV1 returns a cached payload by id. It's not supported in les mode. -func (api *ConsensusAPI) GetPayloadV1(payloadID engine.PayloadID) (*engine.ExecutableData, error) { - return nil, engine.GenericServerError.With(errors.New("not supported in light client mode")) -} - -// ExecutePayloadV1 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. -func (api *ConsensusAPI) ExecutePayloadV1(params engine.ExecutableData) (engine.PayloadStatusV1, error) { - block, err := engine.ExecutableDataToBlock(params) - if err != nil { - return api.invalid(), err - } - if !api.les.BlockChain().HasHeader(block.ParentHash(), block.NumberU64()-1) { - /* - TODO (MariusVanDerWijden) reenable once sync is merged - if err := api.eth.Downloader().BeaconSync(api.eth.SyncMode(), block.Header()); err != nil { - return SYNCING, err - } - */ - // TODO (MariusVanDerWijden) we should return nil here not empty hash - return engine.PayloadStatusV1{Status: engine.SYNCING, LatestValidHash: nil}, nil - } - parent := api.les.BlockChain().GetHeaderByHash(params.ParentHash) - if parent == nil { - return api.invalid(), fmt.Errorf("could not find parent %x", params.ParentHash) - } - td := api.les.BlockChain().GetTd(parent.Hash(), block.NumberU64()-1) - ttd := api.les.BlockChain().Config().TerminalTotalDifficulty - if td.Cmp(ttd) < 0 { - return api.invalid(), fmt.Errorf("can not execute payload on top of block with low td got: %v threshold %v", td, ttd) - } - if err = api.les.BlockChain().InsertHeader(block.Header()); err != nil { - return api.invalid(), err - } - if merger := api.les.Merger(); !merger.TDDReached() { - merger.ReachTTD() - } - hash := block.Hash() - return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil -} - -func (api *ConsensusAPI) validForkChoiceResponse() engine.ForkChoiceResponse { - currentHash := api.les.BlockChain().CurrentHeader().Hash() - return engine.ForkChoiceResponse{ - PayloadStatus: engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: ¤tHash}, - } -} - -// invalid returns a response "INVALID" with the latest valid hash set to the current head. -func (api *ConsensusAPI) invalid() engine.PayloadStatusV1 { - currentHash := api.les.BlockChain().CurrentHeader().Hash() - return engine.PayloadStatusV1{Status: engine.INVALID, LatestValidHash: ¤tHash} -} - -func (api *ConsensusAPI) checkTerminalTotalDifficulty(head common.Hash) error { - // shortcut if we entered PoS already - if api.les.Merger().PoSFinalized() { - return nil - } - // make sure the parent has enough terminal total difficulty - header := api.les.BlockChain().GetHeaderByHash(head) - if header == nil { - return errors.New("unknown header") - } - td := api.les.BlockChain().GetTd(header.Hash(), header.Number.Uint64()) - if td != nil && td.Cmp(api.les.BlockChain().Config().TerminalTotalDifficulty) < 0 { - return errors.New("invalid ttd") - } - return nil -} - -// setCanonical is called to perform a force choice. -func (api *ConsensusAPI) setCanonical(newHead common.Hash) error { - log.Info("Setting head", "head", newHead) - - headHeader := api.les.BlockChain().CurrentHeader() - if headHeader.Hash() == newHead { - return nil - } - newHeadHeader := api.les.BlockChain().GetHeaderByHash(newHead) - if newHeadHeader == nil { - return errors.New("unknown header") - } - if err := api.les.BlockChain().SetCanonical(newHeadHeader); err != nil { - return err - } - // Trigger the transition if it's the first `NewHead` event. - if merger := api.les.Merger(); !merger.PoSFinalized() { - merger.FinalizePoS() - } - return nil -} - -// ExchangeTransitionConfigurationV1 checks the given configuration against -// the configuration of the node. -func (api *ConsensusAPI) ExchangeTransitionConfigurationV1(config engine.TransitionConfigurationV1) (*engine.TransitionConfigurationV1, error) { - log.Trace("Engine API request received", "method", "ExchangeTransitionConfiguration", "ttd", config.TerminalTotalDifficulty) - if config.TerminalTotalDifficulty == nil { - return nil, errors.New("invalid terminal total difficulty") - } - - ttd := api.les.BlockChain().Config().TerminalTotalDifficulty - if ttd == nil || ttd.Cmp(config.TerminalTotalDifficulty.ToInt()) != 0 { - log.Warn("Invalid TTD configured", "geth", ttd, "beacon", config.TerminalTotalDifficulty) - return nil, fmt.Errorf("invalid ttd: execution %v consensus %v", ttd, config.TerminalTotalDifficulty) - } - - if config.TerminalBlockHash != (common.Hash{}) { - if hash := api.les.BlockChain().GetCanonicalHash(uint64(config.TerminalBlockNumber)); hash == config.TerminalBlockHash { - return &engine.TransitionConfigurationV1{ - TerminalTotalDifficulty: (*hexutil.Big)(ttd), - TerminalBlockHash: config.TerminalBlockHash, - TerminalBlockNumber: config.TerminalBlockNumber, - }, nil - } - return nil, errors.New("invalid terminal block hash") - } - - return &engine.TransitionConfigurationV1{TerminalTotalDifficulty: (*hexutil.Big)(ttd)}, nil -} diff --git a/les/catalyst/api_test.go b/les/catalyst/api_test.go deleted file mode 100644 index 2405499d3275..000000000000 --- a/les/catalyst/api_test.go +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright 2022 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 catalyst - -import ( - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/beacon/engine" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/les" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/trie" -) - -var ( - // testKey is a private key to use for funding a tester account. - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - - // testAddr is the Ethereum address of the tester account. - testAddr = crypto.PubkeyToAddress(testKey.PublicKey) - - testBalance = big.NewInt(2e18) -) - -func generatePreMergeChain(pre, post int) (*core.Genesis, []*types.Header, []*types.Block, []*types.Header, []*types.Block) { - config := *params.AllEthashProtocolChanges - genesis := &core.Genesis{ - Config: &config, - Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, - ExtraData: []byte("test genesis"), - Timestamp: 9000, - BaseFee: big.NewInt(params.InitialBaseFee), - } - // Pre-merge blocks - db, preBLocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), pre, nil) - totalDifficulty := new(big.Int).Set(params.GenesisDifficulty) - - var preHeaders []*types.Header - for _, b := range preBLocks { - totalDifficulty.Add(totalDifficulty, b.Difficulty()) - preHeaders = append(preHeaders, b.Header()) - } - config.TerminalTotalDifficulty = totalDifficulty - // Post-merge blocks - postBlocks, _ := core.GenerateChain(genesis.Config, - preBLocks[len(preBLocks)-1], ethash.NewFaker(), db, post, - func(i int, b *core.BlockGen) { - b.SetPoS() - }) - - var postHeaders []*types.Header - for _, b := range postBlocks { - postHeaders = append(postHeaders, b.Header()) - } - - return genesis, preHeaders, preBLocks, postHeaders, postBlocks -} - -func TestSetHeadBeforeTotalDifficulty(t *testing.T) { - genesis, headers, blocks, _, _ := generatePreMergeChain(10, 0) - n, lesService := startLesService(t, genesis, headers) - defer n.Close() - - api := NewConsensusAPI(lesService) - fcState := engine.ForkchoiceStateV1{ - HeadBlockHash: blocks[5].Hash(), - SafeBlockHash: common.Hash{}, - FinalizedBlockHash: common.Hash{}, - } - if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err == nil { - t.Errorf("fork choice updated before total terminal difficulty should fail") - } -} - -func TestExecutePayloadV1(t *testing.T) { - genesis, headers, _, _, postBlocks := generatePreMergeChain(10, 2) - n, lesService := startLesService(t, genesis, headers) - lesService.Merger().ReachTTD() - defer n.Close() - - api := NewConsensusAPI(lesService) - fcState := engine.ForkchoiceStateV1{ - HeadBlockHash: postBlocks[0].Hash(), - SafeBlockHash: common.Hash{}, - FinalizedBlockHash: common.Hash{}, - } - if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { - t.Errorf("Failed to update head %v", err) - } - block := postBlocks[0] - - fakeBlock := types.NewBlock(&types.Header{ - ParentHash: block.ParentHash(), - UncleHash: crypto.Keccak256Hash(nil), - Coinbase: block.Coinbase(), - Root: block.Root(), - TxHash: crypto.Keccak256Hash(nil), - ReceiptHash: crypto.Keccak256Hash(nil), - Bloom: block.Bloom(), - Difficulty: big.NewInt(0), - Number: block.Number(), - GasLimit: block.GasLimit(), - GasUsed: block.GasUsed(), - Time: block.Time(), - Extra: block.Extra(), - MixDigest: block.MixDigest(), - Nonce: types.BlockNonce{}, - BaseFee: block.BaseFee(), - }, nil, nil, nil, trie.NewStackTrie(nil)) - - _, err := api.ExecutePayloadV1(engine.ExecutableData{ - ParentHash: fakeBlock.ParentHash(), - FeeRecipient: fakeBlock.Coinbase(), - StateRoot: fakeBlock.Root(), - ReceiptsRoot: fakeBlock.ReceiptHash(), - LogsBloom: fakeBlock.Bloom().Bytes(), - Random: fakeBlock.MixDigest(), - Number: fakeBlock.NumberU64(), - GasLimit: fakeBlock.GasLimit(), - GasUsed: fakeBlock.GasUsed(), - Timestamp: fakeBlock.Time(), - ExtraData: fakeBlock.Extra(), - BaseFeePerGas: fakeBlock.BaseFee(), - BlockHash: fakeBlock.Hash(), - Transactions: encodeTransactions(fakeBlock.Transactions()), - }) - if err != nil { - t.Errorf("Failed to execute payload %v", err) - } - headHeader := api.les.BlockChain().CurrentHeader() - if headHeader.Number.Uint64() != fakeBlock.NumberU64()-1 { - t.Fatal("Unexpected chain head update") - } - fcState = engine.ForkchoiceStateV1{ - HeadBlockHash: fakeBlock.Hash(), - SafeBlockHash: common.Hash{}, - FinalizedBlockHash: common.Hash{}, - } - if _, err := api.ForkchoiceUpdatedV1(fcState, nil); err != nil { - t.Fatal("Failed to update head") - } - headHeader = api.les.BlockChain().CurrentHeader() - if headHeader.Number.Uint64() != fakeBlock.NumberU64() { - t.Fatal("Failed to update chain head") - } -} - -func TestEth2DeepReorg(t *testing.T) { - // TODO (MariusVanDerWijden) TestEth2DeepReorg is currently broken, because it tries to reorg - // before the totalTerminalDifficulty threshold - /* - genesis, preMergeBlocks := generatePreMergeChain(core.TriesInMemory * 2) - n, ethservice := startEthService(t, genesis, preMergeBlocks) - defer n.Close() - - var ( - api = NewConsensusAPI(ethservice, nil) - parent = preMergeBlocks[len(preMergeBlocks)-core.TriesInMemory-1] - head = ethservice.BlockChain().CurrentBlock().NumberU64() - ) - if ethservice.BlockChain().HasBlockAndState(parent.Hash(), parent.NumberU64()) { - t.Errorf("Block %d not pruned", parent.NumberU64()) - } - for i := 0; i < 10; i++ { - execData, err := api.assembleBlock(AssembleBlockParams{ - ParentHash: parent.Hash(), - Timestamp: parent.Time() + 5, - }) - if err != nil { - t.Fatalf("Failed to create the executable data %v", err) - } - block, err := ExecutableDataToBlock(ethservice.BlockChain().Config(), parent.Header(), *execData) - if err != nil { - t.Fatalf("Failed to convert executable data to block %v", err) - } - newResp, err := api.ExecutePayload(*execData) - if err != nil || newResp.Status != "VALID" { - t.Fatalf("Failed to insert block: %v", err) - } - if ethservice.BlockChain().CurrentBlock().NumberU64() != head { - t.Fatalf("Chain head shouldn't be updated") - } - if err := api.setCanonical(block.Hash()); err != nil { - t.Fatalf("Failed to set head: %v", err) - } - if ethservice.BlockChain().CurrentBlock().NumberU64() != block.NumberU64() { - t.Fatalf("Chain head should be updated") - } - parent, head = block, block.NumberU64() - } - */ -} - -// startEthService creates a full node instance for testing. -func startLesService(t *testing.T, genesis *core.Genesis, headers []*types.Header) (*node.Node, *les.LightEthereum) { - t.Helper() - - n, err := node.New(&node.Config{}) - if err != nil { - t.Fatal("can't create node:", err) - } - ethcfg := ðconfig.Config{ - Genesis: genesis, - SyncMode: downloader.LightSync, - TrieDirtyCache: 256, - TrieCleanCache: 256, - LightPeers: 10, - } - lesService, err := les.New(n, ethcfg) - if err != nil { - t.Fatal("can't create eth service:", err) - } - if err := n.Start(); err != nil { - t.Fatal("can't start node:", err) - } - if _, err := lesService.BlockChain().InsertHeaderChain(headers); err != nil { - n.Close() - t.Fatal("can't import test headers:", err) - } - return n, lesService -} - -func encodeTransactions(txs []*types.Transaction) [][]byte { - var enc = make([][]byte, len(txs)) - for i, tx := range txs { - enc[i], _ = tx.MarshalBinary() - } - return enc -} diff --git a/les/client.go b/les/client.go index bd6667429e54..be5e9fd5641d 100644 --- a/les/client.go +++ b/les/client.go @@ -36,7 +36,6 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/shutdowncheck" - "github.com/ethereum/go-ethereum/les/downloader" "github.com/ethereum/go-ethereum/les/vflux" vfc "github.com/ethereum/go-ethereum/les/vflux/client" "github.com/ethereum/go-ethereum/light" @@ -64,7 +63,6 @@ type LightEthereum struct { blockchain *light.LightChain serverPool *vfc.ServerPool serverPoolIterator enode.Iterator - pruner *pruner merger *consensus.Merger bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests @@ -97,7 +95,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if config.OverrideCancun != nil { overrides.OverrideCancun = config.OverrideCancun } - chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, trie.NewDatabase(chainDb), config.Genesis, &overrides) + if config.OverrideVerkle != nil { + overrides.OverrideVerkle = config.OverrideVerkle + } + triedb := trie.NewDatabase(chainDb, trie.HashDefaults) + chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, triedb, config.Genesis, &overrides) if _, isCompat := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !isCompat { return nil, genesisErr } @@ -143,7 +145,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if leth.udpEnabled { prenegQuery = leth.prenegQuery } - leth.serverPool, leth.serverPoolIterator = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, prenegQuery, &mclock.System{}, config.UltraLightServers, requestList) + leth.serverPool, leth.serverPoolIterator = vfc.NewServerPool(lesDb, []byte("serverpool:"), time.Second, prenegQuery, &mclock.System{}, nil, requestList) leth.serverPool.AddMetrics(suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge, sessionValueMeter, serverDialedMeter) leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool.GetTimeout) @@ -167,9 +169,6 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { leth.chtIndexer.Start(leth.blockchain) leth.bloomIndexer.Start(leth.blockchain) - // Start a light chain pruner to delete useless historical data. - leth.pruner = newPruner(chainDb, leth.chtIndexer, leth.bloomTrieIndexer) - // Rewind the chain in case of an incompatible config upgrade. if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat) @@ -188,7 +187,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { } leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams) - leth.handler = newClientHandler(config.UltraLightServers, config.UltraLightFraction, leth) + leth.handler = newClientHandler(leth) leth.netRPCService = ethapi.NewNetAPI(leth.p2pServer, leth.config.NetworkId) // Register the backend on the node @@ -266,12 +265,12 @@ func (s *LightEthereum) prenegQuery(n *enode.Node) int { type LightDummyAPI struct{} -// Etherbase is the address that mining rewards will be send to +// Etherbase is the address that mining rewards will be sent to func (s *LightDummyAPI) Etherbase() (common.Address, error) { return common.Address{}, errors.New("mining is not supported in light mode") } -// Coinbase is the address that mining rewards will be send to (alias for Etherbase) +// Coinbase is the address that mining rewards will be sent to (alias for Etherbase) func (s *LightDummyAPI) Coinbase() (common.Address, error) { return common.Address{}, errors.New("mining is not supported in light mode") } @@ -295,9 +294,6 @@ func (s *LightEthereum) APIs() []rpc.API { { Namespace: "eth", Service: &LightDummyAPI{}, - }, { - Namespace: "eth", - Service: downloader.NewDownloaderAPI(s.handler.downloader, s.eventMux), }, { Namespace: "net", Service: s.netRPCService, @@ -312,13 +308,12 @@ func (s *LightEthereum) ResetWithGenesisBlock(gb *types.Block) { s.blockchain.ResetWithGenesisBlock(gb) } -func (s *LightEthereum) BlockChain() *light.LightChain { return s.blockchain } -func (s *LightEthereum) TxPool() *light.TxPool { return s.txPool } -func (s *LightEthereum) Engine() consensus.Engine { return s.engine } -func (s *LightEthereum) LesVersion() int { return int(ClientProtocolVersions[0]) } -func (s *LightEthereum) Downloader() *downloader.Downloader { return s.handler.downloader } -func (s *LightEthereum) EventMux() *event.TypeMux { return s.eventMux } -func (s *LightEthereum) Merger() *consensus.Merger { return s.merger } +func (s *LightEthereum) BlockChain() *light.LightChain { return s.blockchain } +func (s *LightEthereum) TxPool() *light.TxPool { return s.txPool } +func (s *LightEthereum) Engine() consensus.Engine { return s.engine } +func (s *LightEthereum) LesVersion() int { return int(ClientProtocolVersions[0]) } +func (s *LightEthereum) EventMux() *event.TypeMux { return s.eventMux } +func (s *LightEthereum) Merger() *consensus.Merger { return s.merger } // Protocols returns all the currently configured network protocols to start. func (s *LightEthereum) Protocols() []p2p.Protocol { @@ -351,7 +346,6 @@ func (s *LightEthereum) Start() error { // Start bloom request workers. s.wg.Add(bloomServiceThreads) s.startBloomHandlers(params.BloomBitsBlocksClient) - s.handler.start() return nil } @@ -371,7 +365,6 @@ func (s *LightEthereum) Stop() error { s.handler.stop() s.txPool.Stop() s.engine.Close() - s.pruner.close() s.eventMux.Stop() // Clean shutdown marker as the last thing before closing db s.shutdownTracker.Stop() diff --git a/les/client_handler.go b/les/client_handler.go index 6e7d88fc5ae0..50f6dce879ff 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -17,9 +17,6 @@ package les import ( - "context" - "math/big" - "math/rand" "sync" "time" @@ -27,68 +24,38 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth/protocols/eth" - "github.com/ethereum/go-ethereum/les/downloader" "github.com/ethereum/go-ethereum/light" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/trie/trienode" ) // clientHandler is responsible for receiving and processing all incoming server // responses. type clientHandler struct { - ulc *ulc forkFilter forkid.Filter - fetcher *lightFetcher - downloader *downloader.Downloader backend *LightEthereum closeCh chan struct{} wg sync.WaitGroup // WaitGroup used to track all connected peers. - - // Hooks used in the testing - syncStart func(header *types.Header) // Hook called when the syncing is started - syncEnd func(header *types.Header) // Hook called when the syncing is done } -func newClientHandler(ulcServers []string, ulcFraction int, backend *LightEthereum) *clientHandler { +func newClientHandler(backend *LightEthereum) *clientHandler { handler := &clientHandler{ forkFilter: forkid.NewFilter(backend.blockchain), backend: backend, closeCh: make(chan struct{}), } - if ulcServers != nil { - ulc, err := newULC(ulcServers, ulcFraction) - if err != nil { - log.Error("Failed to initialize ultra light client") - } - handler.ulc = ulc - log.Info("Enable ultra light client mode") - } - handler.fetcher = newLightFetcher(backend.blockchain, backend.engine, backend.peers, handler.ulc, backend.chainDb, backend.reqDist, handler.synchronise) - handler.downloader = downloader.New(0, backend.chainDb, backend.eventMux, nil, backend.blockchain, handler.removePeer) - handler.backend.peers.subscribe((*downloaderPeerNotify)(handler)) return handler } -func (h *clientHandler) start() { - h.fetcher.start() -} - func (h *clientHandler) stop() { close(h.closeCh) - h.downloader.Terminate() - h.fetcher.stop() h.wg.Wait() } // runPeer is the p2p protocol run function for the given version. func (h *clientHandler) runPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error { - trusted := false - if h.ulc != nil { - trusted = h.ulc.trusted(p.ID()) - } - peer := newServerPeer(int(version), h.backend.config.NetworkId, trusted, p, newMeteredMsgWriter(rw, int(version))) + peer := newServerPeer(int(version), h.backend.config.NetworkId, false, p, newMeteredMsgWriter(rw, int(version))) defer peer.close() h.wg.Add(1) defer h.wg.Done() @@ -103,7 +70,7 @@ func (h *clientHandler) handle(p *serverPeer, noInitAnnounce bool) error { p.Log().Debug("Light Ethereum peer connected", "name", p.Name()) // Execute the LES handshake - forkid := forkid.NewID(h.backend.blockchain.Config(), h.backend.genesis, h.backend.blockchain.CurrentHeader().Number.Uint64(), h.backend.blockchain.CurrentHeader().Time) + forkid := forkid.NewID(h.backend.blockchain.Config(), h.backend.BlockChain().Genesis(), h.backend.blockchain.CurrentHeader().Number.Uint64(), h.backend.blockchain.CurrentHeader().Time) if err := p.Handshake(h.backend.blockchain.Genesis().Hash(), forkid, h.forkFilter); err != nil { p.Log().Debug("Light Ethereum handshake failed", "err", err) return err @@ -136,12 +103,6 @@ func (h *clientHandler) handle(p *serverPeer, noInitAnnounce bool) error { serverConnectionGauge.Update(int64(h.backend.peers.len())) }() - // Discard all the announces after the transition - // Also discarding initial signal to prevent syncing during testing. - if !(noInitAnnounce || h.backend.merger.TDDReached()) { - h.fetcher.announce(p, &announceData{Hash: p.headInfo.Hash, Number: p.headInfo.Number, Td: p.headInfo.Td}) - } - // Mark the peer starts to be served. p.serving.Store(true) defer p.serving.Store(false) @@ -206,11 +167,6 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { // Update peer head information first and then notify the announcement p.updateHead(req.Hash, req.Number, req.Td) - - // Discard all the announces after the transition - if !h.backend.merger.TDDReached() { - h.fetcher.announce(p, &req) - } } case msg.Code == BlockHeadersMsg: p.Log().Trace("Received block header response message") @@ -221,28 +177,13 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { if err := msg.Decode(&resp); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) } - headers := resp.Headers p.fcServer.ReceivedReply(resp.ReqID, resp.BV) p.answeredRequest(resp.ReqID) - // Filter out the explicitly requested header by the retriever - if h.backend.retriever.requested(resp.ReqID) { - deliverMsg = &Msg{ - MsgType: MsgBlockHeaders, - ReqID: resp.ReqID, - Obj: resp.Headers, - } - } else { - // Filter out any explicitly requested headers, deliver the rest to the downloader - filter := len(headers) == 1 - if filter { - headers = h.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers) - } - if len(headers) != 0 || !filter { - if err := h.downloader.DeliverHeaders(p.id, headers); err != nil { - log.Debug("Failed to deliver headers", "err", err) - } - } + deliverMsg = &Msg{ + MsgType: MsgBlockHeaders, + ReqID: resp.ReqID, + Obj: resp.Headers, } case msg.Code == BlockBodiesMsg: p.Log().Trace("Received block bodies response") @@ -296,7 +237,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { p.Log().Trace("Received les/2 proofs response") var resp struct { ReqID, BV uint64 - Data light.NodeList + Data trienode.ProofList } if err := msg.Decode(&resp); err != nil { return errResp(ErrDecode, "msg %v: %v", msg, err) @@ -366,117 +307,3 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { } return nil } - -func (h *clientHandler) removePeer(id string) { - h.backend.peers.unregister(id) -} - -type peerConnection struct { - handler *clientHandler - peer *serverPeer -} - -func (pc *peerConnection) Head() (common.Hash, *big.Int) { - return pc.peer.HeadAndTd() -} - -func (pc *peerConnection) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error { - rq := &distReq{ - getCost: func(dp distPeer) uint64 { - peer := dp.(*serverPeer) - return peer.getRequestCost(GetBlockHeadersMsg, amount) - }, - canSend: func(dp distPeer) bool { - return dp.(*serverPeer) == pc.peer - }, - request: func(dp distPeer) func() { - reqID := rand.Uint64() - peer := dp.(*serverPeer) - cost := peer.getRequestCost(GetBlockHeadersMsg, amount) - peer.fcServer.QueuedRequest(reqID, cost) - return func() { peer.requestHeadersByHash(reqID, origin, amount, skip, reverse) } - }, - } - _, ok := <-pc.handler.backend.reqDist.queue(rq) - if !ok { - return light.ErrNoPeers - } - return nil -} - -func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { - rq := &distReq{ - getCost: func(dp distPeer) uint64 { - peer := dp.(*serverPeer) - return peer.getRequestCost(GetBlockHeadersMsg, amount) - }, - canSend: func(dp distPeer) bool { - return dp.(*serverPeer) == pc.peer - }, - request: func(dp distPeer) func() { - reqID := rand.Uint64() - peer := dp.(*serverPeer) - cost := peer.getRequestCost(GetBlockHeadersMsg, amount) - peer.fcServer.QueuedRequest(reqID, cost) - return func() { peer.requestHeadersByNumber(reqID, origin, amount, skip, reverse) } - }, - } - _, ok := <-pc.handler.backend.reqDist.queue(rq) - if !ok { - return light.ErrNoPeers - } - return nil -} - -// RetrieveSingleHeaderByNumber requests a single header by the specified block -// number. This function will wait the response until it's timeout or delivered. -func (pc *peerConnection) RetrieveSingleHeaderByNumber(context context.Context, number uint64) (*types.Header, error) { - reqID := rand.Uint64() - rq := &distReq{ - getCost: func(dp distPeer) uint64 { - peer := dp.(*serverPeer) - return peer.getRequestCost(GetBlockHeadersMsg, 1) - }, - canSend: func(dp distPeer) bool { - return dp.(*serverPeer) == pc.peer - }, - request: func(dp distPeer) func() { - peer := dp.(*serverPeer) - cost := peer.getRequestCost(GetBlockHeadersMsg, 1) - peer.fcServer.QueuedRequest(reqID, cost) - return func() { peer.requestHeadersByNumber(reqID, number, 1, 0, false) } - }, - } - var header *types.Header - if err := pc.handler.backend.retriever.retrieve(context, reqID, rq, func(peer distPeer, msg *Msg) error { - if msg.MsgType != MsgBlockHeaders { - return errInvalidMessageType - } - headers := msg.Obj.([]*types.Header) - if len(headers) != 1 { - return errInvalidEntryCount - } - header = headers[0] - return nil - }, nil); err != nil { - return nil, err - } - return header, nil -} - -// downloaderPeerNotify implements peerSetNotify -type downloaderPeerNotify clientHandler - -func (d *downloaderPeerNotify) registerPeer(p *serverPeer) { - h := (*clientHandler)(d) - pc := &peerConnection{ - handler: h, - peer: p, - } - h.downloader.RegisterLightPeer(p.id, eth.ETH66, pc) -} - -func (d *downloaderPeerNotify) unregisterPeer(p *serverPeer) { - h := (*clientHandler)(d) - h.downloader.UnregisterPeer(p.id) -} diff --git a/les/commons.go b/les/commons.go index b35d6b51d0e0..cb3fc430b767 100644 --- a/les/commons.go +++ b/les/commons.go @@ -58,7 +58,7 @@ type lesCommons struct { // NodeInfo represents a short summary of the Ethereum sub-protocol metadata // known about the host peer. type NodeInfo struct { - Network uint64 `json:"network"` // Ethereum network ID (1=Mainnet, Rinkeby=4, Goerli=5) + Network uint64 `json:"network"` // Ethereum network ID (1=Mainnet, Goerli=5) Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules diff --git a/les/costtracker.go b/les/costtracker.go index 43e32a5b2d00..695d54e14147 100644 --- a/les/costtracker.go +++ b/les/costtracker.go @@ -128,7 +128,7 @@ type costTracker struct { reqInfoCh chan reqInfo totalRechargeCh chan uint64 - stats map[uint64][]uint64 // Used for testing purpose. + stats map[uint64][]atomic.Uint64 // Used for testing purpose. // TestHooks testing bool // Disable real cost evaluation for testing purpose. @@ -152,9 +152,9 @@ func newCostTracker(db ethdb.Database, config *ethconfig.Config) (*costTracker, ct.outSizeFactor = utilTarget / float64(config.LightEgress) } if makeCostStats { - ct.stats = make(map[uint64][]uint64) + ct.stats = make(map[uint64][]atomic.Uint64) for code := range reqAvgTimeCost { - ct.stats[code] = make([]uint64, 10) + ct.stats[code] = make([]atomic.Uint64, 10) } } ct.gfLoop() @@ -423,7 +423,7 @@ func (ct *costTracker) updateStats(code, amount, servingTime, realCost uint64) { l++ realCost >>= 1 } - atomic.AddUint64(&ct.stats[code][l], 1) + ct.stats[code][l].Add(1) } } @@ -454,7 +454,7 @@ func (ct *costTracker) printStats() { return } for code, arr := range ct.stats { - log.Info("Request cost statistics", "code", code, "1/16", arr[0], "1/8", arr[1], "1/4", arr[2], "1/2", arr[3], "1", arr[4], "2", arr[5], "4", arr[6], "8", arr[7], "16", arr[8], ">16", arr[9]) + log.Info("Request cost statistics", "code", code, "1/16", arr[0].Load(), "1/8", arr[1].Load(), "1/4", arr[2].Load(), "1/2", arr[3].Load(), "1", arr[4].Load(), "2", arr[5].Load(), "4", arr[6].Load(), "8", arr[7].Load(), "16", arr[8].Load(), ">16", arr[9].Load()) } } diff --git a/les/downloader/api.go b/les/downloader/api.go deleted file mode 100644 index 21200b676c64..000000000000 --- a/les/downloader/api.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package downloader - -import ( - "context" - "sync" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/rpc" -) - -// DownloaderAPI provides an API which gives information about the current synchronisation status. -// It offers only methods that operates on data that can be available to anyone without security risks. -type DownloaderAPI struct { - d *Downloader - mux *event.TypeMux - installSyncSubscription chan chan interface{} - uninstallSyncSubscription chan *uninstallSyncSubscriptionRequest -} - -// NewDownloaderAPI create a new PublicDownloaderAPI. The API has an internal event loop that -// listens for events from the downloader through the global event mux. In case it receives one of -// these events it broadcasts it to all syncing subscriptions that are installed through the -// installSyncSubscription channel. -func NewDownloaderAPI(d *Downloader, m *event.TypeMux) *DownloaderAPI { - api := &DownloaderAPI{ - d: d, - mux: m, - installSyncSubscription: make(chan chan interface{}), - uninstallSyncSubscription: make(chan *uninstallSyncSubscriptionRequest), - } - - go api.eventLoop() - - return api -} - -// eventLoop runs a loop until the event mux closes. It will install and uninstall new -// sync subscriptions and broadcasts sync status updates to the installed sync subscriptions. -func (api *DownloaderAPI) eventLoop() { - var ( - sub = api.mux.Subscribe(StartEvent{}, DoneEvent{}, FailedEvent{}) - syncSubscriptions = make(map[chan interface{}]struct{}) - ) - - for { - select { - case i := <-api.installSyncSubscription: - syncSubscriptions[i] = struct{}{} - case u := <-api.uninstallSyncSubscription: - delete(syncSubscriptions, u.c) - close(u.uninstalled) - case event := <-sub.Chan(): - if event == nil { - return - } - - var notification interface{} - switch event.Data.(type) { - case StartEvent: - notification = &SyncingResult{ - Syncing: true, - Status: api.d.Progress(), - } - case DoneEvent, FailedEvent: - notification = false - } - // broadcast - for c := range syncSubscriptions { - c <- notification - } - } - } -} - -// Syncing provides information when this nodes starts synchronising with the Ethereum network and when it's finished. -func (api *DownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error) { - notifier, supported := rpc.NotifierFromContext(ctx) - if !supported { - return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported - } - - rpcSub := notifier.CreateSubscription() - - go func() { - statuses := make(chan interface{}) - sub := api.SubscribeSyncStatus(statuses) - - for { - select { - case status := <-statuses: - notifier.Notify(rpcSub.ID, status) - case <-rpcSub.Err(): - sub.Unsubscribe() - return - case <-notifier.Closed(): - sub.Unsubscribe() - return - } - } - }() - - return rpcSub, nil -} - -// SyncingResult provides information about the current synchronisation status for this node. -type SyncingResult struct { - Syncing bool `json:"syncing"` - Status ethereum.SyncProgress `json:"status"` -} - -// uninstallSyncSubscriptionRequest uninstalls a syncing subscription in the API event loop. -type uninstallSyncSubscriptionRequest struct { - c chan interface{} - uninstalled chan interface{} -} - -// SyncStatusSubscription represents a syncing subscription. -type SyncStatusSubscription struct { - api *DownloaderAPI // register subscription in event loop of this api instance - c chan interface{} // channel where events are broadcasted to - unsubOnce sync.Once // make sure unsubscribe logic is executed once -} - -// Unsubscribe uninstalls the subscription from the DownloadAPI event loop. -// The status channel that was passed to subscribeSyncStatus isn't used anymore -// after this method returns. -func (s *SyncStatusSubscription) Unsubscribe() { - s.unsubOnce.Do(func() { - req := uninstallSyncSubscriptionRequest{s.c, make(chan interface{})} - s.api.uninstallSyncSubscription <- &req - - for { - select { - case <-s.c: - // drop new status events until uninstall confirmation - continue - case <-req.uninstalled: - return - } - } - }) -} - -// SubscribeSyncStatus creates a subscription that will broadcast new synchronisation updates. -// The given channel must receive interface values, the result can either -func (api *DownloaderAPI) SubscribeSyncStatus(status chan interface{}) *SyncStatusSubscription { - api.installSyncSubscription <- status - return &SyncStatusSubscription{api: api, c: status} -} diff --git a/les/downloader/downloader.go b/les/downloader/downloader.go deleted file mode 100644 index 5ffcbfa42309..000000000000 --- a/les/downloader/downloader.go +++ /dev/null @@ -1,1979 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package downloader is a temporary package whilst working on the eth/66 blocking refactors. -// After that work is done, les needs to be refactored to use the new package, -// or alternatively use a stripped down version of it. Either way, we need to -// keep the changes scoped so duplicating temporarily seems the sanest. -package downloader - -import ( - "errors" - "fmt" - "math/big" - "sync" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state/snapshot" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth/protocols/eth" - "github.com/ethereum/go-ethereum/eth/protocols/snap" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/params" -) - -var ( - MaxBlockFetch = 128 // Amount of blocks to be fetched per retrieval request - MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request - MaxSkeletonSize = 128 // Number of header fetches to need for a skeleton assembly - MaxReceiptFetch = 256 // Amount of transaction receipts to allow fetching per request - MaxStateFetch = 384 // Amount of node state values to allow fetching per request - - maxQueuedHeaders = 32 * 1024 // [eth/62] Maximum number of headers to queue for import (DOS protection) - maxHeadersProcess = 2048 // Number of header download results to import at once into the chain - maxResultsProcess = 2048 // Number of content download results to import at once into the chain - fullMaxForkAncestry uint64 = params.FullImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) - lightMaxForkAncestry uint64 = params.LightImmutabilityThreshold // Maximum chain reorganisation (locally redeclared so tests can reduce it) - - reorgProtThreshold = 48 // Threshold number of recent blocks to disable mini reorg protection - reorgProtHeaderDelay = 2 // Number of headers to delay delivering to cover mini reorgs - - fsHeaderSafetyNet = 2048 // Number of headers to discard in case a chain violation is detected - fsHeaderContCheck = 3 * time.Second // Time interval to check for header continuations during state download - fsMinFullBlocks = 64 // Number of blocks to retrieve fully even in fast sync -) - -var ( - errBusy = errors.New("busy") - errUnknownPeer = errors.New("peer is unknown or unhealthy") - errBadPeer = errors.New("action from bad peer ignored") - errStallingPeer = errors.New("peer is stalling") - errUnsyncedPeer = errors.New("unsynced peer") - errNoPeers = errors.New("no peers to keep download active") - errTimeout = errors.New("timeout") - errEmptyHeaderSet = errors.New("empty header set by peer") - errPeersUnavailable = errors.New("no peers available or all tried for download") - errInvalidAncestor = errors.New("retrieved ancestor is invalid") - errInvalidChain = errors.New("retrieved hash chain is invalid") - errInvalidBody = errors.New("retrieved block body is invalid") - errInvalidReceipt = errors.New("retrieved receipt is invalid") - errCancelStateFetch = errors.New("state data download canceled (requested)") - errCancelContentProcessing = errors.New("content processing canceled (requested)") - errCanceled = errors.New("syncing canceled (requested)") - errNoSyncActive = errors.New("no sync active") - errTooOld = errors.New("peer's protocol version too old") - errNoAncestorFound = errors.New("no common ancestor found") -) - -type Downloader struct { - mode uint32 // Synchronisation mode defining the strategy used (per sync cycle), use d.getMode() to get the SyncMode - mux *event.TypeMux // Event multiplexer to announce sync operation events - - checkpoint uint64 // Checkpoint block number to enforce head against (e.g. fast sync) - genesis uint64 // Genesis block number to limit sync to (e.g. light client CHT) - queue *queue // Scheduler for selecting the hashes to download - peers *peerSet // Set of active peers from which download can proceed - - stateDB ethdb.Database // Database to state sync into (and deduplicate via) - - // Statistics - syncStatsChainOrigin uint64 // Origin block number where syncing started at - syncStatsChainHeight uint64 // Highest block number known when syncing started - syncStatsState stateSyncStats - syncStatsLock sync.RWMutex // Lock protecting the sync stats fields - - lightchain LightChain - blockchain BlockChain - - // Callbacks - dropPeer peerDropFn // Drops a peer for misbehaving - - // Status - synchroniseMock func(id string, hash common.Hash) error // Replacement for synchronise during testing - synchronising int32 - notified int32 - committed int32 - ancientLimit uint64 // The maximum block number which can be regarded as ancient data. - - // Channels - headerCh chan dataPack // Channel receiving inbound block headers - bodyCh chan dataPack // Channel receiving inbound block bodies - receiptCh chan dataPack // Channel receiving inbound receipts - bodyWakeCh chan bool // Channel to signal the block body fetcher of new tasks - receiptWakeCh chan bool // Channel to signal the receipt fetcher of new tasks - headerProcCh chan []*types.Header // Channel to feed the header processor new tasks - - // State sync - pivotHeader *types.Header // Pivot block header to dynamically push the syncing state root - pivotLock sync.RWMutex // Lock protecting pivot header reads from updates - - snapSync bool // Whether to run state sync over the snap protocol - SnapSyncer *snap.Syncer // TODO(karalabe): make private! hack for now - stateSyncStart chan *stateSync - trackStateReq chan *stateReq - stateCh chan dataPack // Channel receiving inbound node state data - - // Cancellation and termination - cancelPeer string // Identifier of the peer currently being used as the master (cancel on drop) - cancelCh chan struct{} // Channel to cancel mid-flight syncs - cancelLock sync.RWMutex // Lock to protect the cancel channel and peer in delivers - cancelWg sync.WaitGroup // Make sure all fetcher goroutines have exited. - - quitCh chan struct{} // Quit channel to signal termination - quitLock sync.Mutex // Lock to prevent double closes - - // Testing hooks - syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run - bodyFetchHook func([]*types.Header) // Method to call upon starting a block body fetch - receiptFetchHook func([]*types.Header) // Method to call upon starting a receipt fetch - chainInsertHook func([]*fetchResult) // Method to call upon inserting a chain of blocks (possibly in multiple invocations) -} - -// LightChain encapsulates functions required to synchronise a light chain. -type LightChain interface { - // HasHeader verifies a header's presence in the local chain. - HasHeader(common.Hash, uint64) bool - - // GetHeaderByHash retrieves a header from the local chain. - GetHeaderByHash(common.Hash) *types.Header - - // CurrentHeader retrieves the head header from the local chain. - CurrentHeader() *types.Header - - // GetTd returns the total difficulty of a local block. - GetTd(common.Hash, uint64) *big.Int - - // InsertHeaderChain inserts a batch of headers into the local chain. - InsertHeaderChain([]*types.Header) (int, error) - - // SetHead rewinds the local chain to a new head. - SetHead(uint64) error -} - -// BlockChain encapsulates functions required to sync a (full or fast) blockchain. -type BlockChain interface { - LightChain - - // HasBlock verifies a block's presence in the local chain. - HasBlock(common.Hash, uint64) bool - - // HasFastBlock verifies a fast block's presence in the local chain. - HasFastBlock(common.Hash, uint64) bool - - // GetBlockByHash retrieves a block from the local chain. - GetBlockByHash(common.Hash) *types.Block - - // CurrentBlock retrieves the head block from the local chain. - CurrentBlock() *types.Block - - // CurrentFastBlock retrieves the head fast block from the local chain. - CurrentFastBlock() *types.Block - - // FastSyncCommitHead directly commits the head block to a certain entity. - FastSyncCommitHead(common.Hash) error - - // InsertChain inserts a batch of blocks into the local chain. - InsertChain(types.Blocks) (int, error) - - // InsertReceiptChain inserts a batch of receipts into the local chain. - InsertReceiptChain(types.Blocks, []types.Receipts, uint64) (int, error) - - // Snapshots returns the blockchain snapshot tree to paused it during sync. - Snapshots() *snapshot.Tree -} - -// New creates a new downloader to fetch hashes and blocks from remote peers. -func New(checkpoint uint64, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn) *Downloader { - if lightchain == nil { - lightchain = chain - } - dl := &Downloader{ - stateDB: stateDb, - mux: mux, - checkpoint: checkpoint, - queue: newQueue(blockCacheMaxItems, blockCacheInitialItems), - peers: newPeerSet(), - blockchain: chain, - lightchain: lightchain, - dropPeer: dropPeer, - headerCh: make(chan dataPack, 1), - bodyCh: make(chan dataPack, 1), - receiptCh: make(chan dataPack, 1), - bodyWakeCh: make(chan bool, 1), - receiptWakeCh: make(chan bool, 1), - headerProcCh: make(chan []*types.Header, 1), - quitCh: make(chan struct{}), - stateCh: make(chan dataPack), - SnapSyncer: snap.NewSyncer(stateDb, rawdb.HashScheme), - stateSyncStart: make(chan *stateSync), - //syncStatsState: stateSyncStats{ - // processed: rawdb.ReadFastTrieProgress(stateDb), - //}, - trackStateReq: make(chan *stateReq), - } - go dl.stateFetcher() - return dl -} - -// Progress retrieves the synchronisation boundaries, specifically the origin -// block where synchronisation started at (may have failed/suspended); the block -// or header sync is currently at; and the latest known block which the sync targets. -// -// In addition, during the state download phase of fast synchronisation the number -// of processed and the total number of known states are also returned. Otherwise -// these are zero. -func (d *Downloader) Progress() ethereum.SyncProgress { - // Lock the current stats and return the progress - d.syncStatsLock.RLock() - defer d.syncStatsLock.RUnlock() - - current := uint64(0) - mode := d.getMode() - switch { - case d.blockchain != nil && mode == FullSync: - current = d.blockchain.CurrentBlock().NumberU64() - case d.blockchain != nil && mode == FastSync: - current = d.blockchain.CurrentFastBlock().NumberU64() - case d.lightchain != nil: - current = d.lightchain.CurrentHeader().Number.Uint64() - default: - log.Error("Unknown downloader chain/mode combo", "light", d.lightchain != nil, "full", d.blockchain != nil, "mode", mode) - } - return ethereum.SyncProgress{ - StartingBlock: d.syncStatsChainOrigin, - CurrentBlock: current, - HighestBlock: d.syncStatsChainHeight, - //PulledStates: d.syncStatsState.processed, - //KnownStates: d.syncStatsState.processed + d.syncStatsState.pending, - } -} - -// Synchronising returns whether the downloader is currently retrieving blocks. -func (d *Downloader) Synchronising() bool { - return atomic.LoadInt32(&d.synchronising) > 0 -} - -// RegisterPeer injects a new download peer into the set of block source to be -// used for fetching hashes and blocks from. -func (d *Downloader) RegisterPeer(id string, version uint, peer Peer) error { - var logger log.Logger - if len(id) < 16 { - // Tests use short IDs, don't choke on them - logger = log.New("peer", id) - } else { - logger = log.New("peer", id[:8]) - } - logger.Trace("Registering sync peer") - if err := d.peers.Register(newPeerConnection(id, version, peer, logger)); err != nil { - logger.Error("Failed to register sync peer", "err", err) - return err - } - return nil -} - -// RegisterLightPeer injects a light client peer, wrapping it so it appears as a regular peer. -func (d *Downloader) RegisterLightPeer(id string, version uint, peer LightPeer) error { - return d.RegisterPeer(id, version, &lightPeerWrapper{peer}) -} - -// UnregisterPeer remove a peer from the known list, preventing any action from -// the specified peer. An effort is also made to return any pending fetches into -// the queue. -func (d *Downloader) UnregisterPeer(id string) error { - // Unregister the peer from the active peer set and revoke any fetch tasks - var logger log.Logger - if len(id) < 16 { - // Tests use short IDs, don't choke on them - logger = log.New("peer", id) - } else { - logger = log.New("peer", id[:8]) - } - logger.Trace("Unregistering sync peer") - if err := d.peers.Unregister(id); err != nil { - logger.Error("Failed to unregister sync peer", "err", err) - return err - } - d.queue.Revoke(id) - - return nil -} - -// Synchronise tries to sync up our local block chain with a remote peer, both -// adding various sanity checks as well as wrapping it with various log entries. -func (d *Downloader) Synchronise(id string, head common.Hash, td *big.Int, mode SyncMode) error { - err := d.synchronise(id, head, td, mode) - - switch err { - case nil, errBusy, errCanceled: - return err - } - if errors.Is(err, errInvalidChain) || errors.Is(err, errBadPeer) || errors.Is(err, errTimeout) || - errors.Is(err, errStallingPeer) || errors.Is(err, errUnsyncedPeer) || errors.Is(err, errEmptyHeaderSet) || - errors.Is(err, errPeersUnavailable) || errors.Is(err, errTooOld) || errors.Is(err, errInvalidAncestor) { - log.Warn("Synchronisation failed, dropping peer", "peer", id, "err", err) - if d.dropPeer == nil { - // The dropPeer method is nil when `--copydb` is used for a local copy. - // Timeouts can occur if e.g. compaction hits at the wrong time, and can be ignored - log.Warn("Downloader wants to drop peer, but peerdrop-function is not set", "peer", id) - } else { - d.dropPeer(id) - } - return err - } - log.Warn("Synchronisation failed, retrying", "err", err) - return err -} - -// synchronise will select the peer and use it for synchronising. If an empty string is given -// it will use the best peer possible and synchronize if its TD is higher than our own. If any of the -// checks fail an error will be returned. This method is synchronous -func (d *Downloader) synchronise(id string, hash common.Hash, td *big.Int, mode SyncMode) error { - // Mock out the synchronisation if testing - if d.synchroniseMock != nil { - return d.synchroniseMock(id, hash) - } - // Make sure only one goroutine is ever allowed past this point at once - if !atomic.CompareAndSwapInt32(&d.synchronising, 0, 1) { - return errBusy - } - defer atomic.StoreInt32(&d.synchronising, 0) - - // Post a user notification of the sync (only once per session) - if atomic.CompareAndSwapInt32(&d.notified, 0, 1) { - log.Info("Block synchronisation started") - } - // If snap sync was requested, create the snap scheduler and switch to fast - // sync mode. Long term we could drop fast sync or merge the two together, - // but until snap becomes prevalent, we should support both. TODO(karalabe). - if mode == SnapSync { - if !d.snapSync { - // Snap sync uses the snapshot namespace to store potentially flakey data until - // sync completely heals and finishes. Pause snapshot maintenance in the mean - // time to prevent access. - if snapshots := d.blockchain.Snapshots(); snapshots != nil { // Only nil in tests - snapshots.Disable() - } - log.Warn("Enabling snapshot sync prototype") - d.snapSync = true - } - mode = FastSync - } - // Reset the queue, peer set and wake channels to clean any internal leftover state - d.queue.Reset(blockCacheMaxItems, blockCacheInitialItems) - d.peers.Reset() - - for _, ch := range []chan bool{d.bodyWakeCh, d.receiptWakeCh} { - select { - case <-ch: - default: - } - } - for _, ch := range []chan dataPack{d.headerCh, d.bodyCh, d.receiptCh} { - for empty := false; !empty; { - select { - case <-ch: - default: - empty = true - } - } - } - for empty := false; !empty; { - select { - case <-d.headerProcCh: - default: - empty = true - } - } - // Create cancel channel for aborting mid-flight and mark the master peer - d.cancelLock.Lock() - d.cancelCh = make(chan struct{}) - d.cancelPeer = id - d.cancelLock.Unlock() - - defer d.Cancel() // No matter what, we can't leave the cancel channel open - - // Atomically set the requested sync mode - atomic.StoreUint32(&d.mode, uint32(mode)) - - // Retrieve the origin peer and initiate the downloading process - p := d.peers.Peer(id) - if p == nil { - return errUnknownPeer - } - return d.syncWithPeer(p, hash, td) -} - -func (d *Downloader) getMode() SyncMode { - return SyncMode(atomic.LoadUint32(&d.mode)) -} - -// syncWithPeer starts a block synchronization based on the hash chain from the -// specified peer and head hash. -func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td *big.Int) (err error) { - d.mux.Post(StartEvent{}) - defer func() { - // reset on error - if err != nil { - d.mux.Post(FailedEvent{err}) - } else { - latest := d.lightchain.CurrentHeader() - d.mux.Post(DoneEvent{latest}) - } - }() - if p.version < eth.ETH66 { - return fmt.Errorf("%w: advertized %d < required %d", errTooOld, p.version, eth.ETH66) - } - mode := d.getMode() - - log.Debug("Synchronising with the network", "peer", p.id, "eth", p.version, "head", hash, "td", td, "mode", mode) - defer func(start time.Time) { - log.Debug("Synchronisation terminated", "elapsed", common.PrettyDuration(time.Since(start))) - }(time.Now()) - - // Look up the sync boundaries: the common ancestor and the target block - latest, pivot, err := d.fetchHead(p) - if err != nil { - return err - } - if mode == FastSync && pivot == nil { - // If no pivot block was returned, the head is below the min full block - // threshold (i.e. new chain). In that case we won't really fast sync - // anyway, but still need a valid pivot block to avoid some code hitting - // nil panics on an access. - pivot = d.blockchain.CurrentBlock().Header() - } - height := latest.Number.Uint64() - - origin, err := d.findAncestor(p, latest) - if err != nil { - return err - } - d.syncStatsLock.Lock() - if d.syncStatsChainHeight <= origin || d.syncStatsChainOrigin > origin { - d.syncStatsChainOrigin = origin - } - d.syncStatsChainHeight = height - d.syncStatsLock.Unlock() - - // Ensure our origin point is below any fast sync pivot point - if mode == FastSync { - if height <= uint64(fsMinFullBlocks) { - origin = 0 - } else { - pivotNumber := pivot.Number.Uint64() - if pivotNumber <= origin { - origin = pivotNumber - 1 - } - // Write out the pivot into the database so a rollback beyond it will - // reenable fast sync - rawdb.WriteLastPivotNumber(d.stateDB, pivotNumber) - } - } - d.committed = 1 - if mode == FastSync && pivot.Number.Uint64() != 0 { - d.committed = 0 - } - if mode == FastSync { - // Set the ancient data limitation. - // If we are running fast sync, all block data older than ancientLimit will be - // written to the ancient store. More recent data will be written to the active - // database and will wait for the freezer to migrate. - // - // If there is a checkpoint available, then calculate the ancientLimit through - // that. Otherwise calculate the ancient limit through the advertised height - // of the remote peer. - // - // The reason for picking checkpoint first is that a malicious peer can give us - // a fake (very high) height, forcing the ancient limit to also be very high. - // The peer would start to feed us valid blocks until head, resulting in all of - // the blocks might be written into the ancient store. A following mini-reorg - // could cause issues. - if d.checkpoint != 0 && d.checkpoint > fullMaxForkAncestry+1 { - d.ancientLimit = d.checkpoint - } else if height > fullMaxForkAncestry+1 { - d.ancientLimit = height - fullMaxForkAncestry - 1 - } else { - d.ancientLimit = 0 - } - frozen, _ := d.stateDB.Ancients() // Ignore the error here since light client can also hit here. - - // If a part of blockchain data has already been written into active store, - // disable the ancient style insertion explicitly. - if origin >= frozen && frozen != 0 { - d.ancientLimit = 0 - log.Info("Disabling direct-ancient mode", "origin", origin, "ancient", frozen-1) - } else if d.ancientLimit > 0 { - log.Debug("Enabling direct-ancient mode", "ancient", d.ancientLimit) - } - // Rewind the ancient store and blockchain if reorg happens. - if origin+1 < frozen { - if err := d.lightchain.SetHead(origin + 1); err != nil { - return err - } - } - } - // Initiate the sync using a concurrent header and content retrieval algorithm - d.queue.Prepare(origin+1, mode) - if d.syncInitHook != nil { - d.syncInitHook(origin, height) - } - fetchers := []func() error{ - func() error { return d.fetchHeaders(p, origin+1) }, // Headers are always retrieved - func() error { return d.fetchBodies(origin + 1) }, // Bodies are retrieved during normal and fast sync - func() error { return d.fetchReceipts(origin + 1) }, // Receipts are retrieved during fast sync - func() error { return d.processHeaders(origin+1, td) }, - } - if mode == FastSync { - d.pivotLock.Lock() - d.pivotHeader = pivot - d.pivotLock.Unlock() - - fetchers = append(fetchers, func() error { return d.processFastSyncContent() }) - } else if mode == FullSync { - fetchers = append(fetchers, d.processFullSyncContent) - } - return d.spawnSync(fetchers) -} - -// spawnSync runs d.process and all given fetcher functions to completion in -// separate goroutines, returning the first error that appears. -func (d *Downloader) spawnSync(fetchers []func() error) error { - errc := make(chan error, len(fetchers)) - d.cancelWg.Add(len(fetchers)) - for _, fn := range fetchers { - fn := fn - go func() { defer d.cancelWg.Done(); errc <- fn() }() - } - // Wait for the first error, then terminate the others. - var err error - for i := 0; i < len(fetchers); i++ { - if i == len(fetchers)-1 { - // Close the queue when all fetchers have exited. - // This will cause the block processor to end when - // it has processed the queue. - d.queue.Close() - } - if err = <-errc; err != nil && err != errCanceled { - break - } - } - d.queue.Close() - d.Cancel() - return err -} - -// cancel aborts all of the operations and resets the queue. However, cancel does -// not wait for the running download goroutines to finish. This method should be -// used when cancelling the downloads from inside the downloader. -func (d *Downloader) cancel() { - // Close the current cancel channel - d.cancelLock.Lock() - defer d.cancelLock.Unlock() - - if d.cancelCh != nil { - select { - case <-d.cancelCh: - // Channel was already closed - default: - close(d.cancelCh) - } - } -} - -// Cancel aborts all of the operations and waits for all download goroutines to -// finish before returning. -func (d *Downloader) Cancel() { - d.cancel() - d.cancelWg.Wait() -} - -// Terminate interrupts the downloader, canceling all pending operations. -// The downloader cannot be reused after calling Terminate. -func (d *Downloader) Terminate() { - // Close the termination channel (make sure double close is allowed) - d.quitLock.Lock() - select { - case <-d.quitCh: - default: - close(d.quitCh) - } - d.quitLock.Unlock() - - // Cancel any pending download requests - d.Cancel() -} - -// fetchHead retrieves the head header and prior pivot block (if available) from -// a remote peer. -func (d *Downloader) fetchHead(p *peerConnection) (head *types.Header, pivot *types.Header, err error) { - p.log.Debug("Retrieving remote chain head") - mode := d.getMode() - - // Request the advertised remote head block and wait for the response - latest, _ := p.peer.Head() - fetch := 1 - if mode == FastSync { - fetch = 2 // head + pivot headers - } - go p.peer.RequestHeadersByHash(latest, fetch, fsMinFullBlocks-1, true) - - ttl := d.peers.rates.TargetTimeout() - timeout := time.After(ttl) - for { - select { - case <-d.cancelCh: - return nil, nil, errCanceled - - case packet := <-d.headerCh: - // Discard anything not from the origin peer - if packet.PeerId() != p.id { - log.Debug("Received headers from incorrect peer", "peer", packet.PeerId()) - break - } - // Make sure the peer gave us at least one and at most the requested headers - headers := packet.(*headerPack).headers - if len(headers) == 0 || len(headers) > fetch { - return nil, nil, fmt.Errorf("%w: returned headers %d != requested %d", errBadPeer, len(headers), fetch) - } - // The first header needs to be the head, validate against the checkpoint - // and request. If only 1 header was returned, make sure there's no pivot - // or there was not one requested. - head := headers[0] - if (mode == FastSync || mode == LightSync) && head.Number.Uint64() < d.checkpoint { - return nil, nil, fmt.Errorf("%w: remote head %d below checkpoint %d", errUnsyncedPeer, head.Number, d.checkpoint) - } - if len(headers) == 1 { - if mode == FastSync && head.Number.Uint64() > uint64(fsMinFullBlocks) { - return nil, nil, fmt.Errorf("%w: no pivot included along head header", errBadPeer) - } - p.log.Debug("Remote head identified, no pivot", "number", head.Number, "hash", head.Hash()) - return head, nil, nil - } - // At this point we have 2 headers in total and the first is the - // validated head of the chain. Check the pivot number and return, - pivot := headers[1] - if pivot.Number.Uint64() != head.Number.Uint64()-uint64(fsMinFullBlocks) { - return nil, nil, fmt.Errorf("%w: remote pivot %d != requested %d", errInvalidChain, pivot.Number, head.Number.Uint64()-uint64(fsMinFullBlocks)) - } - return head, pivot, nil - - case <-timeout: - p.log.Debug("Waiting for head header timed out", "elapsed", ttl) - return nil, nil, errTimeout - - case <-d.bodyCh: - case <-d.receiptCh: - // Out of bounds delivery, ignore - } - } -} - -// calculateRequestSpan calculates what headers to request from a peer when trying to determine the -// common ancestor. -// It returns parameters to be used for peer.RequestHeadersByNumber: -// -// from - starting block number -// count - number of headers to request -// skip - number of headers to skip -// -// and also returns 'max', the last block which is expected to be returned by the remote peers, -// given the (from,count,skip) -func calculateRequestSpan(remoteHeight, localHeight uint64) (int64, int, int, uint64) { - var ( - from int - count int - MaxCount = MaxHeaderFetch / 16 - ) - // requestHead is the highest block that we will ask for. If requestHead is not offset, - // the highest block that we will get is 16 blocks back from head, which means we - // will fetch 14 or 15 blocks unnecessarily in the case the height difference - // between us and the peer is 1-2 blocks, which is most common - requestHead := int(remoteHeight) - 1 - if requestHead < 0 { - requestHead = 0 - } - // requestBottom is the lowest block we want included in the query - // Ideally, we want to include the one just below our own head - requestBottom := int(localHeight - 1) - if requestBottom < 0 { - requestBottom = 0 - } - totalSpan := requestHead - requestBottom - span := 1 + totalSpan/MaxCount - if span < 2 { - span = 2 - } - if span > 16 { - span = 16 - } - - count = 1 + totalSpan/span - if count > MaxCount { - count = MaxCount - } - if count < 2 { - count = 2 - } - from = requestHead - (count-1)*span - if from < 0 { - from = 0 - } - max := from + (count-1)*span - return int64(from), count, span - 1, uint64(max) -} - -// findAncestor tries to locate the common ancestor link of the local chain and -// a remote peers blockchain. In the general case when our node was in sync and -// on the correct chain, checking the top N links should already get us a match. -// In the rare scenario when we ended up on a long reorganisation (i.e. none of -// the head links match), we do a binary search to find the common ancestor. -func (d *Downloader) findAncestor(p *peerConnection, remoteHeader *types.Header) (uint64, error) { - // Figure out the valid ancestor range to prevent rewrite attacks - var ( - floor = int64(-1) - localHeight uint64 - remoteHeight = remoteHeader.Number.Uint64() - ) - mode := d.getMode() - switch mode { - case FullSync: - localHeight = d.blockchain.CurrentBlock().NumberU64() - case FastSync: - localHeight = d.blockchain.CurrentFastBlock().NumberU64() - default: - localHeight = d.lightchain.CurrentHeader().Number.Uint64() - } - p.log.Debug("Looking for common ancestor", "local", localHeight, "remote", remoteHeight) - - // Recap floor value for binary search - maxForkAncestry := fullMaxForkAncestry - if d.getMode() == LightSync { - maxForkAncestry = lightMaxForkAncestry - } - if localHeight >= maxForkAncestry { - // We're above the max reorg threshold, find the earliest fork point - floor = int64(localHeight - maxForkAncestry) - } - // If we're doing a light sync, ensure the floor doesn't go below the CHT, as - // all headers before that point will be missing. - if mode == LightSync { - // If we don't know the current CHT position, find it - if d.genesis == 0 { - header := d.lightchain.CurrentHeader() - for header != nil { - d.genesis = header.Number.Uint64() - if floor >= int64(d.genesis)-1 { - break - } - header = d.lightchain.GetHeaderByHash(header.ParentHash) - } - } - // We already know the "genesis" block number, cap floor to that - if floor < int64(d.genesis)-1 { - floor = int64(d.genesis) - 1 - } - } - - ancestor, err := d.findAncestorSpanSearch(p, mode, remoteHeight, localHeight, floor) - if err == nil { - return ancestor, nil - } - // The returned error was not nil. - // If the error returned does not reflect that a common ancestor was not found, return it. - // If the error reflects that a common ancestor was not found, continue to binary search, - // where the error value will be reassigned. - if !errors.Is(err, errNoAncestorFound) { - return 0, err - } - - ancestor, err = d.findAncestorBinarySearch(p, mode, remoteHeight, floor) - if err != nil { - return 0, err - } - return ancestor, nil -} - -func (d *Downloader) findAncestorSpanSearch(p *peerConnection, mode SyncMode, remoteHeight, localHeight uint64, floor int64) (commonAncestor uint64, err error) { - from, count, skip, max := calculateRequestSpan(remoteHeight, localHeight) - - p.log.Trace("Span searching for common ancestor", "count", count, "from", from, "skip", skip) - go p.peer.RequestHeadersByNumber(uint64(from), count, skip, false) - - // Wait for the remote response to the head fetch - number, hash := uint64(0), common.Hash{} - - ttl := d.peers.rates.TargetTimeout() - timeout := time.After(ttl) - - for finished := false; !finished; { - select { - case <-d.cancelCh: - return 0, errCanceled - - case packet := <-d.headerCh: - // Discard anything not from the origin peer - if packet.PeerId() != p.id { - log.Debug("Received headers from incorrect peer", "peer", packet.PeerId()) - break - } - // Make sure the peer actually gave something valid - headers := packet.(*headerPack).headers - if len(headers) == 0 { - p.log.Warn("Empty head header set") - return 0, errEmptyHeaderSet - } - // Make sure the peer's reply conforms to the request - for i, header := range headers { - expectNumber := from + int64(i)*int64(skip+1) - if number := header.Number.Int64(); number != expectNumber { - p.log.Warn("Head headers broke chain ordering", "index", i, "requested", expectNumber, "received", number) - return 0, fmt.Errorf("%w: %v", errInvalidChain, errors.New("head headers broke chain ordering")) - } - } - // Check if a common ancestor was found - finished = true - for i := len(headers) - 1; i >= 0; i-- { - // Skip any headers that underflow/overflow our requested set - if headers[i].Number.Int64() < from || headers[i].Number.Uint64() > max { - continue - } - // Otherwise check if we already know the header or not - h := headers[i].Hash() - n := headers[i].Number.Uint64() - - var known bool - switch mode { - case FullSync: - known = d.blockchain.HasBlock(h, n) - case FastSync: - known = d.blockchain.HasFastBlock(h, n) - default: - known = d.lightchain.HasHeader(h, n) - } - if known { - number, hash = n, h - break - } - } - - case <-timeout: - p.log.Debug("Waiting for head header timed out", "elapsed", ttl) - return 0, errTimeout - - case <-d.bodyCh: - case <-d.receiptCh: - // Out of bounds delivery, ignore - } - } - // If the head fetch already found an ancestor, return - if hash != (common.Hash{}) { - if int64(number) <= floor { - p.log.Warn("Ancestor below allowance", "number", number, "hash", hash, "allowance", floor) - return 0, errInvalidAncestor - } - p.log.Debug("Found common ancestor", "number", number, "hash", hash) - return number, nil - } - return 0, errNoAncestorFound -} - -func (d *Downloader) findAncestorBinarySearch(p *peerConnection, mode SyncMode, remoteHeight uint64, floor int64) (commonAncestor uint64, err error) { - hash := common.Hash{} - - // Ancestor not found, we need to binary search over our chain - start, end := uint64(0), remoteHeight - if floor > 0 { - start = uint64(floor) - } - p.log.Trace("Binary searching for common ancestor", "start", start, "end", end) - - for start+1 < end { - // Split our chain interval in two, and request the hash to cross check - check := (start + end) / 2 - - ttl := d.peers.rates.TargetTimeout() - timeout := time.After(ttl) - - go p.peer.RequestHeadersByNumber(check, 1, 0, false) - - // Wait until a reply arrives to this request - for arrived := false; !arrived; { - select { - case <-d.cancelCh: - return 0, errCanceled - - case packet := <-d.headerCh: - // Discard anything not from the origin peer - if packet.PeerId() != p.id { - log.Debug("Received headers from incorrect peer", "peer", packet.PeerId()) - break - } - // Make sure the peer actually gave something valid - headers := packet.(*headerPack).headers - if len(headers) != 1 { - p.log.Warn("Multiple headers for single request", "headers", len(headers)) - return 0, fmt.Errorf("%w: multiple headers (%d) for single request", errBadPeer, len(headers)) - } - arrived = true - - // Modify the search interval based on the response - h := headers[0].Hash() - n := headers[0].Number.Uint64() - - var known bool - switch mode { - case FullSync: - known = d.blockchain.HasBlock(h, n) - case FastSync: - known = d.blockchain.HasFastBlock(h, n) - default: - known = d.lightchain.HasHeader(h, n) - } - if !known { - end = check - break - } - header := d.lightchain.GetHeaderByHash(h) // Independent of sync mode, header surely exists - if header.Number.Uint64() != check { - p.log.Warn("Received non requested header", "number", header.Number, "hash", header.Hash(), "request", check) - return 0, fmt.Errorf("%w: non-requested header (%d)", errBadPeer, header.Number) - } - start = check - hash = h - - case <-timeout: - p.log.Debug("Waiting for search header timed out", "elapsed", ttl) - return 0, errTimeout - - case <-d.bodyCh: - case <-d.receiptCh: - // Out of bounds delivery, ignore - } - } - } - // Ensure valid ancestry and return - if int64(start) <= floor { - p.log.Warn("Ancestor below allowance", "number", start, "hash", hash, "allowance", floor) - return 0, errInvalidAncestor - } - p.log.Debug("Found common ancestor", "number", start, "hash", hash) - return start, nil -} - -// fetchHeaders keeps retrieving headers concurrently from the number -// requested, until no more are returned, potentially throttling on the way. To -// facilitate concurrency but still protect against malicious nodes sending bad -// headers, we construct a header chain skeleton using the "origin" peer we are -// syncing with, and fill in the missing headers using anyone else. Headers from -// other peers are only accepted if they map cleanly to the skeleton. If no one -// can fill in the skeleton - not even the origin peer - it's assumed invalid and -// the origin is dropped. -func (d *Downloader) fetchHeaders(p *peerConnection, from uint64) error { - p.log.Debug("Directing header downloads", "origin", from) - defer p.log.Debug("Header download terminated") - - // Create a timeout timer, and the associated header fetcher - skeleton := true // Skeleton assembly phase or finishing up - pivoting := false // Whether the next request is pivot verification - request := time.Now() // time of the last skeleton fetch request - timeout := time.NewTimer(0) // timer to dump a non-responsive active peer - <-timeout.C // timeout channel should be initially empty - defer timeout.Stop() - - var ttl time.Duration - getHeaders := func(from uint64) { - request = time.Now() - - ttl = d.peers.rates.TargetTimeout() - timeout.Reset(ttl) - - if skeleton { - p.log.Trace("Fetching skeleton headers", "count", MaxHeaderFetch, "from", from) - go p.peer.RequestHeadersByNumber(from+uint64(MaxHeaderFetch)-1, MaxSkeletonSize, MaxHeaderFetch-1, false) - } else { - p.log.Trace("Fetching full headers", "count", MaxHeaderFetch, "from", from) - go p.peer.RequestHeadersByNumber(from, MaxHeaderFetch, 0, false) - } - } - getNextPivot := func() { - pivoting = true - request = time.Now() - - ttl = d.peers.rates.TargetTimeout() - timeout.Reset(ttl) - - d.pivotLock.RLock() - pivot := d.pivotHeader.Number.Uint64() - d.pivotLock.RUnlock() - - p.log.Trace("Fetching next pivot header", "number", pivot+uint64(fsMinFullBlocks)) - go p.peer.RequestHeadersByNumber(pivot+uint64(fsMinFullBlocks), 2, fsMinFullBlocks-9, false) // move +64 when it's 2x64-8 deep - } - // Start pulling the header chain skeleton until all is done - ancestor := from - getHeaders(from) - - mode := d.getMode() - for { - select { - case <-d.cancelCh: - return errCanceled - - case packet := <-d.headerCh: - // Make sure the active peer is giving us the skeleton headers - if packet.PeerId() != p.id { - log.Debug("Received skeleton from incorrect peer", "peer", packet.PeerId()) - break - } - headerReqTimer.UpdateSince(request) - timeout.Stop() - - // If the pivot is being checked, move if it became stale and run the real retrieval - var pivot uint64 - - d.pivotLock.RLock() - if d.pivotHeader != nil { - pivot = d.pivotHeader.Number.Uint64() - } - d.pivotLock.RUnlock() - - if pivoting { - if packet.Items() == 2 { - // Retrieve the headers and do some sanity checks, just in case - headers := packet.(*headerPack).headers - - if have, want := headers[0].Number.Uint64(), pivot+uint64(fsMinFullBlocks); have != want { - log.Warn("Peer sent invalid next pivot", "have", have, "want", want) - return fmt.Errorf("%w: next pivot number %d != requested %d", errInvalidChain, have, want) - } - if have, want := headers[1].Number.Uint64(), pivot+2*uint64(fsMinFullBlocks)-8; have != want { - log.Warn("Peer sent invalid pivot confirmer", "have", have, "want", want) - return fmt.Errorf("%w: next pivot confirmer number %d != requested %d", errInvalidChain, have, want) - } - log.Warn("Pivot seemingly stale, moving", "old", pivot, "new", headers[0].Number) - pivot = headers[0].Number.Uint64() - - d.pivotLock.Lock() - d.pivotHeader = headers[0] - d.pivotLock.Unlock() - - // Write out the pivot into the database so a rollback beyond - // it will reenable fast sync and update the state root that - // the state syncer will be downloading. - rawdb.WriteLastPivotNumber(d.stateDB, pivot) - } - pivoting = false - getHeaders(from) - continue - } - // If the skeleton's finished, pull any remaining head headers directly from the origin - if skeleton && packet.Items() == 0 { - skeleton = false - getHeaders(from) - continue - } - // If no more headers are inbound, notify the content fetchers and return - if packet.Items() == 0 { - // Don't abort header fetches while the pivot is downloading - if atomic.LoadInt32(&d.committed) == 0 && pivot <= from { - p.log.Debug("No headers, waiting for pivot commit") - select { - case <-time.After(fsHeaderContCheck): - getHeaders(from) - continue - case <-d.cancelCh: - return errCanceled - } - } - // Pivot done (or not in fast sync) and no more headers, terminate the process - p.log.Debug("No more headers available") - select { - case d.headerProcCh <- nil: - return nil - case <-d.cancelCh: - return errCanceled - } - } - headers := packet.(*headerPack).headers - - // If we received a skeleton batch, resolve internals concurrently - if skeleton { - filled, proced, err := d.fillHeaderSkeleton(from, headers) - if err != nil { - p.log.Debug("Skeleton chain invalid", "err", err) - return fmt.Errorf("%w: %v", errInvalidChain, err) - } - headers = filled[proced:] - from += uint64(proced) - } else { - // If we're closing in on the chain head, but haven't yet reached it, delay - // the last few headers so mini reorgs on the head don't cause invalid hash - // chain errors. - if n := len(headers); n > 0 { - // Retrieve the current head we're at - var head uint64 - if mode == LightSync { - head = d.lightchain.CurrentHeader().Number.Uint64() - } else { - head = d.blockchain.CurrentFastBlock().NumberU64() - if full := d.blockchain.CurrentBlock().NumberU64(); head < full { - head = full - } - } - // If the head is below the common ancestor, we're actually deduplicating - // already existing chain segments, so use the ancestor as the fake head. - // Otherwise we might end up delaying header deliveries pointlessly. - if head < ancestor { - head = ancestor - } - // If the head is way older than this batch, delay the last few headers - if head+uint64(reorgProtThreshold) < headers[n-1].Number.Uint64() { - delay := reorgProtHeaderDelay - if delay > n { - delay = n - } - headers = headers[:n-delay] - } - } - } - // Insert all the new headers and fetch the next batch - if len(headers) > 0 { - p.log.Trace("Scheduling new headers", "count", len(headers), "from", from) - select { - case d.headerProcCh <- headers: - case <-d.cancelCh: - return errCanceled - } - from += uint64(len(headers)) - - // If we're still skeleton filling fast sync, check pivot staleness - // before continuing to the next skeleton filling - if skeleton && pivot > 0 { - getNextPivot() - } else { - getHeaders(from) - } - } else { - // No headers delivered, or all of them being delayed, sleep a bit and retry - p.log.Trace("All headers delayed, waiting") - select { - case <-time.After(fsHeaderContCheck): - getHeaders(from) - continue - case <-d.cancelCh: - return errCanceled - } - } - - case <-timeout.C: - if d.dropPeer == nil { - // The dropPeer method is nil when `--copydb` is used for a local copy. - // Timeouts can occur if e.g. compaction hits at the wrong time, and can be ignored - p.log.Warn("Downloader wants to drop peer, but peerdrop-function is not set", "peer", p.id) - break - } - // Header retrieval timed out, consider the peer bad and drop - p.log.Debug("Header request timed out", "elapsed", ttl) - headerTimeoutMeter.Mark(1) - d.dropPeer(p.id) - - // Finish the sync gracefully instead of dumping the gathered data though - for _, ch := range []chan bool{d.bodyWakeCh, d.receiptWakeCh} { - select { - case ch <- false: - case <-d.cancelCh: - } - } - select { - case d.headerProcCh <- nil: - case <-d.cancelCh: - } - return fmt.Errorf("%w: header request timed out", errBadPeer) - } - } -} - -// fillHeaderSkeleton concurrently retrieves headers from all our available peers -// and maps them to the provided skeleton header chain. -// -// Any partial results from the beginning of the skeleton is (if possible) forwarded -// immediately to the header processor to keep the rest of the pipeline full even -// in the case of header stalls. -// -// The method returns the entire filled skeleton and also the number of headers -// already forwarded for processing. -func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) ([]*types.Header, int, error) { - log.Debug("Filling up skeleton", "from", from) - d.queue.ScheduleSkeleton(from, skeleton) - - var ( - deliver = func(packet dataPack) (int, error) { - pack := packet.(*headerPack) - return d.queue.DeliverHeaders(pack.peerID, pack.headers, d.headerProcCh) - } - expire = func() map[string]int { return d.queue.ExpireHeaders(d.peers.rates.TargetTimeout()) } - reserve = func(p *peerConnection, count int) (*fetchRequest, bool, bool) { - return d.queue.ReserveHeaders(p, count), false, false - } - fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchHeaders(req.From, MaxHeaderFetch) } - capacity = func(p *peerConnection) int { return p.HeaderCapacity(d.peers.rates.TargetRoundTrip()) } - setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) { - p.SetHeadersIdle(accepted, deliveryTime) - } - ) - err := d.fetchParts(d.headerCh, deliver, d.queue.headerContCh, expire, - d.queue.PendingHeaders, d.queue.InFlightHeaders, reserve, - nil, fetch, d.queue.CancelHeaders, capacity, d.peers.HeaderIdlePeers, setIdle, "headers") - - log.Debug("Skeleton fill terminated", "err", err) - - filled, proced := d.queue.RetrieveHeaders() - return filled, proced, err -} - -// fetchBodies iteratively downloads the scheduled block bodies, taking any -// available peers, reserving a chunk of blocks for each, waiting for delivery -// and also periodically checking for timeouts. -func (d *Downloader) fetchBodies(from uint64) error { - log.Debug("Downloading block bodies", "origin", from) - - var ( - deliver = func(packet dataPack) (int, error) { - pack := packet.(*bodyPack) - return d.queue.DeliverBodies(pack.peerID, pack.transactions, pack.uncles) - } - expire = func() map[string]int { return d.queue.ExpireBodies(d.peers.rates.TargetTimeout()) } - fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchBodies(req) } - capacity = func(p *peerConnection) int { return p.BlockCapacity(d.peers.rates.TargetRoundTrip()) } - setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) { p.SetBodiesIdle(accepted, deliveryTime) } - ) - err := d.fetchParts(d.bodyCh, deliver, d.bodyWakeCh, expire, - d.queue.PendingBlocks, d.queue.InFlightBlocks, d.queue.ReserveBodies, - d.bodyFetchHook, fetch, d.queue.CancelBodies, capacity, d.peers.BodyIdlePeers, setIdle, "bodies") - - log.Debug("Block body download terminated", "err", err) - return err -} - -// fetchReceipts iteratively downloads the scheduled block receipts, taking any -// available peers, reserving a chunk of receipts for each, waiting for delivery -// and also periodically checking for timeouts. -func (d *Downloader) fetchReceipts(from uint64) error { - log.Debug("Downloading transaction receipts", "origin", from) - - var ( - deliver = func(packet dataPack) (int, error) { - pack := packet.(*receiptPack) - return d.queue.DeliverReceipts(pack.peerID, pack.receipts) - } - expire = func() map[string]int { return d.queue.ExpireReceipts(d.peers.rates.TargetTimeout()) } - fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchReceipts(req) } - capacity = func(p *peerConnection) int { return p.ReceiptCapacity(d.peers.rates.TargetRoundTrip()) } - setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) { - p.SetReceiptsIdle(accepted, deliveryTime) - } - ) - err := d.fetchParts(d.receiptCh, deliver, d.receiptWakeCh, expire, - d.queue.PendingReceipts, d.queue.InFlightReceipts, d.queue.ReserveReceipts, - d.receiptFetchHook, fetch, d.queue.CancelReceipts, capacity, d.peers.ReceiptIdlePeers, setIdle, "receipts") - - log.Debug("Transaction receipt download terminated", "err", err) - return err -} - -// fetchParts iteratively downloads scheduled block parts, taking any available -// peers, reserving a chunk of fetch requests for each, waiting for delivery and -// also periodically checking for timeouts. -// -// As the scheduling/timeout logic mostly is the same for all downloaded data -// types, this method is used by each for data gathering and is instrumented with -// various callbacks to handle the slight differences between processing them. -// -// The instrumentation parameters: -// - errCancel: error type to return if the fetch operation is cancelled (mostly makes logging nicer) -// - deliveryCh: channel from which to retrieve downloaded data packets (merged from all concurrent peers) -// - deliver: processing callback to deliver data packets into type specific download queues (usually within `queue`) -// - wakeCh: notification channel for waking the fetcher when new tasks are available (or sync completed) -// - expire: task callback method to abort requests that took too long and return the faulty peers (traffic shaping) -// - pending: task callback for the number of requests still needing download (detect completion/non-completability) -// - inFlight: task callback for the number of in-progress requests (wait for all active downloads to finish) -// - throttle: task callback to check if the processing queue is full and activate throttling (bound memory use) -// - reserve: task callback to reserve new download tasks to a particular peer (also signals partial completions) -// - fetchHook: tester callback to notify of new tasks being initiated (allows testing the scheduling logic) -// - fetch: network callback to actually send a particular download request to a physical remote peer -// - cancel: task callback to abort an in-flight download request and allow rescheduling it (in case of lost peer) -// - capacity: network callback to retrieve the estimated type-specific bandwidth capacity of a peer (traffic shaping) -// - idle: network callback to retrieve the currently (type specific) idle peers that can be assigned tasks -// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping) -// - kind: textual label of the type being downloaded to display in log messages -func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) (int, error), wakeCh chan bool, - expire func() map[string]int, pending func() int, inFlight func() bool, reserve func(*peerConnection, int) (*fetchRequest, bool, bool), - fetchHook func([]*types.Header), fetch func(*peerConnection, *fetchRequest) error, cancel func(*fetchRequest), capacity func(*peerConnection) int, - idle func() ([]*peerConnection, int), setIdle func(*peerConnection, int, time.Time), kind string) error { - // Create a ticker to detect expired retrieval tasks - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() - - update := make(chan struct{}, 1) - - // Prepare the queue and fetch block parts until the block header fetcher's done - finished := false - for { - select { - case <-d.cancelCh: - return errCanceled - - case packet := <-deliveryCh: - deliveryTime := time.Now() - // If the peer was previously banned and failed to deliver its pack - // in a reasonable time frame, ignore its message. - if peer := d.peers.Peer(packet.PeerId()); peer != nil { - // Deliver the received chunk of data and check chain validity - accepted, err := deliver(packet) - if errors.Is(err, errInvalidChain) { - return err - } - // Unless a peer delivered something completely else than requested (usually - // caused by a timed out request which came through in the end), set it to - // idle. If the delivery's stale, the peer should have already been idled. - if !errors.Is(err, errStaleDelivery) { - setIdle(peer, accepted, deliveryTime) - } - // Issue a log to the user to see what's going on - switch { - case err == nil && packet.Items() == 0: - peer.log.Trace("Requested data not delivered", "type", kind) - case err == nil: - peer.log.Trace("Delivered new batch of data", "type", kind, "count", packet.Stats()) - default: - peer.log.Debug("Failed to deliver retrieved data", "type", kind, "err", err) - } - } - // Blocks assembled, try to update the progress - select { - case update <- struct{}{}: - default: - } - - case cont := <-wakeCh: - // The header fetcher sent a continuation flag, check if it's done - if !cont { - finished = true - } - // Headers arrive, try to update the progress - select { - case update <- struct{}{}: - default: - } - - case <-ticker.C: - // Sanity check update the progress - select { - case update <- struct{}{}: - default: - } - - case <-update: - // Short circuit if we lost all our peers - if d.peers.Len() == 0 { - return errNoPeers - } - // Check for fetch request timeouts and demote the responsible peers - for pid, fails := range expire() { - if peer := d.peers.Peer(pid); peer != nil { - // If a lot of retrieval elements expired, we might have overestimated the remote peer or perhaps - // ourselves. Only reset to minimal throughput but don't drop just yet. If even the minimal times - // out that sync wise we need to get rid of the peer. - // - // The reason the minimum threshold is 2 is because the downloader tries to estimate the bandwidth - // and latency of a peer separately, which requires pushing the measures capacity a bit and seeing - // how response times reacts, to it always requests one more than the minimum (i.e. min 2). - if fails > 2 { - peer.log.Trace("Data delivery timed out", "type", kind) - setIdle(peer, 0, time.Now()) - } else { - peer.log.Debug("Stalling delivery, dropping", "type", kind) - - if d.dropPeer == nil { - // The dropPeer method is nil when `--copydb` is used for a local copy. - // Timeouts can occur if e.g. compaction hits at the wrong time, and can be ignored - peer.log.Warn("Downloader wants to drop peer, but peerdrop-function is not set", "peer", pid) - } else { - d.dropPeer(pid) - - // If this peer was the master peer, abort sync immediately - d.cancelLock.RLock() - master := pid == d.cancelPeer - d.cancelLock.RUnlock() - - if master { - d.cancel() - return errTimeout - } - } - } - } - } - // If there's nothing more to fetch, wait or terminate - if pending() == 0 { - if !inFlight() && finished { - log.Debug("Data fetching completed", "type", kind) - return nil - } - break - } - // Send a download request to all idle peers, until throttled - progressed, throttled, running := false, false, inFlight() - idles, total := idle() - pendCount := pending() - for _, peer := range idles { - // Short circuit if throttling activated - if throttled { - break - } - // Short circuit if there is no more available task. - if pendCount = pending(); pendCount == 0 { - break - } - // Reserve a chunk of fetches for a peer. A nil can mean either that - // no more headers are available, or that the peer is known not to - // have them. - request, progress, throttle := reserve(peer, capacity(peer)) - if progress { - progressed = true - } - if throttle { - throttled = true - throttleCounter.Inc(1) - } - if request == nil { - continue - } - if request.From > 0 { - peer.log.Trace("Requesting new batch of data", "type", kind, "from", request.From) - } else { - peer.log.Trace("Requesting new batch of data", "type", kind, "count", len(request.Headers), "from", request.Headers[0].Number) - } - // Fetch the chunk and make sure any errors return the hashes to the queue - if fetchHook != nil { - fetchHook(request.Headers) - } - if err := fetch(peer, request); err != nil { - // Although we could try and make an attempt to fix this, this error really - // means that we've double allocated a fetch task to a peer. If that is the - // case, the internal state of the downloader and the queue is very wrong so - // better hard crash and note the error instead of silently accumulating into - // a much bigger issue. - panic(fmt.Sprintf("%v: %s fetch assignment failed", peer, kind)) - } - running = true - } - // Make sure that we have peers available for fetching. If all peers have been tried - // and all failed throw an error - if !progressed && !throttled && !running && len(idles) == total && pendCount > 0 { - return errPeersUnavailable - } - } - } -} - -// processHeaders takes batches of retrieved headers from an input channel and -// keeps processing and scheduling them into the header chain and downloader's -// queue until the stream ends or a failure occurs. -func (d *Downloader) processHeaders(origin uint64, td *big.Int) error { - // Keep a count of uncertain headers to roll back - var ( - rollback uint64 // Zero means no rollback (fine as you can't unroll the genesis) - rollbackErr error - mode = d.getMode() - ) - defer func() { - if rollback > 0 { - lastHeader, lastFastBlock, lastBlock := d.lightchain.CurrentHeader().Number, common.Big0, common.Big0 - if mode != LightSync { - lastFastBlock = d.blockchain.CurrentFastBlock().Number() - lastBlock = d.blockchain.CurrentBlock().Number() - } - if err := d.lightchain.SetHead(rollback - 1); err != nil { // -1 to target the parent of the first uncertain block - // We're already unwinding the stack, only print the error to make it more visible - log.Error("Failed to roll back chain segment", "head", rollback-1, "err", err) - } - curFastBlock, curBlock := common.Big0, common.Big0 - if mode != LightSync { - curFastBlock = d.blockchain.CurrentFastBlock().Number() - curBlock = d.blockchain.CurrentBlock().Number() - } - log.Warn("Rolled back chain segment", - "header", fmt.Sprintf("%d->%d", lastHeader, d.lightchain.CurrentHeader().Number), - "fast", fmt.Sprintf("%d->%d", lastFastBlock, curFastBlock), - "block", fmt.Sprintf("%d->%d", lastBlock, curBlock), "reason", rollbackErr) - } - }() - // Wait for batches of headers to process - gotHeaders := false - - for { - select { - case <-d.cancelCh: - rollbackErr = errCanceled - return errCanceled - - case headers := <-d.headerProcCh: - // Terminate header processing if we synced up - if len(headers) == 0 { - // Notify everyone that headers are fully processed - for _, ch := range []chan bool{d.bodyWakeCh, d.receiptWakeCh} { - select { - case ch <- false: - case <-d.cancelCh: - } - } - // If no headers were retrieved at all, the peer violated its TD promise that it had a - // better chain compared to ours. The only exception is if its promised blocks were - // already imported by other means (e.g. fetcher): - // - // R , L : Both at block 10 - // R: Mine block 11, and propagate it to L - // L: Queue block 11 for import - // L: Notice that R's head and TD increased compared to ours, start sync - // L: Import of block 11 finishes - // L: Sync begins, and finds common ancestor at 11 - // L: Request new headers up from 11 (R's TD was higher, it must have something) - // R: Nothing to give - if mode != LightSync { - head := d.blockchain.CurrentBlock() - if !gotHeaders && td.Cmp(d.blockchain.GetTd(head.Hash(), head.NumberU64())) > 0 { - return errStallingPeer - } - } - // If fast or light syncing, ensure promised headers are indeed delivered. This is - // needed to detect scenarios where an attacker feeds a bad pivot and then bails out - // of delivering the post-pivot blocks that would flag the invalid content. - // - // This check cannot be executed "as is" for full imports, since blocks may still be - // queued for processing when the header download completes. However, as long as the - // peer gave us something useful, we're already happy/progressed (above check). - if mode == FastSync || mode == LightSync { - head := d.lightchain.CurrentHeader() - if td.Cmp(d.lightchain.GetTd(head.Hash(), head.Number.Uint64())) > 0 { - return errStallingPeer - } - } - // Disable any rollback and return - rollback = 0 - return nil - } - // Otherwise split the chunk of headers into batches and process them - gotHeaders = true - for len(headers) > 0 { - // Terminate if something failed in between processing chunks - select { - case <-d.cancelCh: - rollbackErr = errCanceled - return errCanceled - default: - } - // Select the next chunk of headers to import - limit := maxHeadersProcess - if limit > len(headers) { - limit = len(headers) - } - chunk := headers[:limit] - - // In case of header only syncing, validate the chunk immediately - if mode == FastSync || mode == LightSync { - if n, err := d.lightchain.InsertHeaderChain(chunk); err != nil { - rollbackErr = err - - // If some headers were inserted, track them as uncertain - if mode == FastSync && n > 0 && rollback == 0 { - rollback = chunk[0].Number.Uint64() - } - log.Warn("Invalid header encountered", "number", chunk[n].Number, "hash", chunk[n].Hash(), "parent", chunk[n].ParentHash, "err", err) - return fmt.Errorf("%w: %v", errInvalidChain, err) - } - // All verifications passed, track all headers within the allotted limits - if mode == FastSync { - head := chunk[len(chunk)-1].Number.Uint64() - if head-rollback > uint64(fsHeaderSafetyNet) { - rollback = head - uint64(fsHeaderSafetyNet) - } else { - rollback = 1 - } - } - } - // Unless we're doing light chains, schedule the headers for associated content retrieval - if mode == FullSync || mode == FastSync { - // If we've reached the allowed number of pending headers, stall a bit - for d.queue.PendingBlocks() >= maxQueuedHeaders || d.queue.PendingReceipts() >= maxQueuedHeaders { - select { - case <-d.cancelCh: - rollbackErr = errCanceled - return errCanceled - case <-time.After(time.Second): - } - } - // Otherwise insert the headers for content retrieval - inserts := d.queue.Schedule(chunk, origin) - if len(inserts) != len(chunk) { - rollbackErr = fmt.Errorf("stale headers: len inserts %v len(chunk) %v", len(inserts), len(chunk)) - return fmt.Errorf("%w: stale headers", errBadPeer) - } - } - headers = headers[limit:] - origin += uint64(limit) - } - // Update the highest block number we know if a higher one is found. - d.syncStatsLock.Lock() - if d.syncStatsChainHeight < origin { - d.syncStatsChainHeight = origin - 1 - } - d.syncStatsLock.Unlock() - - // Signal the content downloaders of the availability of new tasks - for _, ch := range []chan bool{d.bodyWakeCh, d.receiptWakeCh} { - select { - case ch <- true: - default: - } - } - } - } -} - -// processFullSyncContent takes fetch results from the queue and imports them into the chain. -func (d *Downloader) processFullSyncContent() error { - for { - results := d.queue.Results(true) - if len(results) == 0 { - return nil - } - if d.chainInsertHook != nil { - d.chainInsertHook(results) - } - if err := d.importBlockResults(results); err != nil { - return err - } - } -} - -func (d *Downloader) importBlockResults(results []*fetchResult) error { - // Check for any early termination requests - if len(results) == 0 { - return nil - } - select { - case <-d.quitCh: - return errCancelContentProcessing - default: - } - // Retrieve the a batch of results to import - first, last := results[0].Header, results[len(results)-1].Header - log.Debug("Inserting downloaded chain", "items", len(results), - "firstnum", first.Number, "firsthash", first.Hash(), - "lastnum", last.Number, "lasthash", last.Hash(), - ) - blocks := make([]*types.Block, len(results)) - for i, result := range results { - blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) - } - if index, err := d.blockchain.InsertChain(blocks); err != nil { - if index < len(results) { - log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err) - } else { - // The InsertChain method in blockchain.go will sometimes return an out-of-bounds index, - // when it needs to preprocess blocks to import a sidechain. - // The importer will put together a new list of blocks to import, which is a superset - // of the blocks delivered from the downloader, and the indexing will be off. - log.Debug("Downloaded item processing failed on sidechain import", "index", index, "err", err) - } - return fmt.Errorf("%w: %v", errInvalidChain, err) - } - return nil -} - -// processFastSyncContent takes fetch results from the queue and writes them to the -// database. It also controls the synchronisation of state nodes of the pivot block. -func (d *Downloader) processFastSyncContent() error { - // Start syncing state of the reported head block. This should get us most of - // the state of the pivot block. - d.pivotLock.RLock() - sync := d.syncState(d.pivotHeader.Root) - d.pivotLock.RUnlock() - - defer func() { - // The `sync` object is replaced every time the pivot moves. We need to - // defer close the very last active one, hence the lazy evaluation vs. - // calling defer sync.Cancel() !!! - sync.Cancel() - }() - - closeOnErr := func(s *stateSync) { - if err := s.Wait(); err != nil && err != errCancelStateFetch && err != errCanceled && err != snap.ErrCancelled { - d.queue.Close() // wake up Results - } - } - go closeOnErr(sync) - - // To cater for moving pivot points, track the pivot block and subsequently - // accumulated download results separately. - var ( - oldPivot *fetchResult // Locked in pivot block, might change eventually - oldTail []*fetchResult // Downloaded content after the pivot - ) - for { - // Wait for the next batch of downloaded data to be available, and if the pivot - // block became stale, move the goalpost - results := d.queue.Results(oldPivot == nil) // Block if we're not monitoring pivot staleness - if len(results) == 0 { - // If pivot sync is done, stop - if oldPivot == nil { - return sync.Cancel() - } - // If sync failed, stop - select { - case <-d.cancelCh: - sync.Cancel() - return errCanceled - default: - } - } - if d.chainInsertHook != nil { - d.chainInsertHook(results) - } - // If we haven't downloaded the pivot block yet, check pivot staleness - // notifications from the header downloader - d.pivotLock.RLock() - pivot := d.pivotHeader - d.pivotLock.RUnlock() - - if oldPivot == nil { - if pivot.Root != sync.root { - sync.Cancel() - sync = d.syncState(pivot.Root) - - go closeOnErr(sync) - } - } else { - results = append(append([]*fetchResult{oldPivot}, oldTail...), results...) - } - // Split around the pivot block and process the two sides via fast/full sync - if atomic.LoadInt32(&d.committed) == 0 { - latest := results[len(results)-1].Header - // If the height is above the pivot block by 2 sets, it means the pivot - // become stale in the network and it was garbage collected, move to a - // new pivot. - // - // Note, we have `reorgProtHeaderDelay` number of blocks withheld, Those - // need to be taken into account, otherwise we're detecting the pivot move - // late and will drop peers due to unavailable state!!! - if height := latest.Number.Uint64(); height >= pivot.Number.Uint64()+2*uint64(fsMinFullBlocks)-uint64(reorgProtHeaderDelay) { - log.Warn("Pivot became stale, moving", "old", pivot.Number.Uint64(), "new", height-uint64(fsMinFullBlocks)+uint64(reorgProtHeaderDelay)) - pivot = results[len(results)-1-fsMinFullBlocks+reorgProtHeaderDelay].Header // must exist as lower old pivot is uncommitted - - d.pivotLock.Lock() - d.pivotHeader = pivot - d.pivotLock.Unlock() - - // Write out the pivot into the database so a rollback beyond it will - // reenable fast sync - rawdb.WriteLastPivotNumber(d.stateDB, pivot.Number.Uint64()) - } - } - P, beforeP, afterP := splitAroundPivot(pivot.Number.Uint64(), results) - if err := d.commitFastSyncData(beforeP, sync); err != nil { - return err - } - if P != nil { - // If new pivot block found, cancel old state retrieval and restart - if oldPivot != P { - sync.Cancel() - sync = d.syncState(P.Header.Root) - - go closeOnErr(sync) - oldPivot = P - } - // Wait for completion, occasionally checking for pivot staleness - select { - case <-sync.done: - if sync.err != nil { - return sync.err - } - if err := d.commitPivotBlock(P); err != nil { - return err - } - oldPivot = nil - - case <-time.After(time.Second): - oldTail = afterP - continue - } - } - // Fast sync done, pivot commit done, full import - if err := d.importBlockResults(afterP); err != nil { - return err - } - } -} - -func splitAroundPivot(pivot uint64, results []*fetchResult) (p *fetchResult, before, after []*fetchResult) { - if len(results) == 0 { - return nil, nil, nil - } - if lastNum := results[len(results)-1].Header.Number.Uint64(); lastNum < pivot { - // the pivot is somewhere in the future - return nil, results, nil - } - // This can also be optimized, but only happens very seldom - for _, result := range results { - num := result.Header.Number.Uint64() - switch { - case num < pivot: - before = append(before, result) - case num == pivot: - p = result - default: - after = append(after, result) - } - } - return p, before, after -} - -func (d *Downloader) commitFastSyncData(results []*fetchResult, stateSync *stateSync) error { - // Check for any early termination requests - if len(results) == 0 { - return nil - } - select { - case <-d.quitCh: - return errCancelContentProcessing - case <-stateSync.done: - if err := stateSync.Wait(); err != nil { - return err - } - default: - } - // Retrieve the a batch of results to import - first, last := results[0].Header, results[len(results)-1].Header - log.Debug("Inserting fast-sync blocks", "items", len(results), - "firstnum", first.Number, "firsthash", first.Hash(), - "lastnumn", last.Number, "lasthash", last.Hash(), - ) - blocks := make([]*types.Block, len(results)) - receipts := make([]types.Receipts, len(results)) - for i, result := range results { - blocks[i] = types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) - receipts[i] = result.Receipts - } - if index, err := d.blockchain.InsertReceiptChain(blocks, receipts, d.ancientLimit); err != nil { - log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err) - return fmt.Errorf("%w: %v", errInvalidChain, err) - } - return nil -} - -func (d *Downloader) commitPivotBlock(result *fetchResult) error { - block := types.NewBlockWithHeader(result.Header).WithBody(result.Transactions, result.Uncles) - log.Debug("Committing fast sync pivot as new head", "number", block.Number(), "hash", block.Hash()) - - // Commit the pivot block as the new head, will require full sync from here on - if _, err := d.blockchain.InsertReceiptChain([]*types.Block{block}, []types.Receipts{result.Receipts}, d.ancientLimit); err != nil { - return err - } - if err := d.blockchain.FastSyncCommitHead(block.Hash()); err != nil { - return err - } - atomic.StoreInt32(&d.committed, 1) - return nil -} - -// DeliverHeaders injects a new batch of block headers received from a remote -// node into the download schedule. -func (d *Downloader) DeliverHeaders(id string, headers []*types.Header) error { - return d.deliver(d.headerCh, &headerPack{id, headers}, headerInMeter, headerDropMeter) -} - -// DeliverBodies injects a new batch of block bodies received from a remote node. -func (d *Downloader) DeliverBodies(id string, transactions [][]*types.Transaction, uncles [][]*types.Header) error { - return d.deliver(d.bodyCh, &bodyPack{id, transactions, uncles}, bodyInMeter, bodyDropMeter) -} - -// DeliverReceipts injects a new batch of receipts received from a remote node. -func (d *Downloader) DeliverReceipts(id string, receipts [][]*types.Receipt) error { - return d.deliver(d.receiptCh, &receiptPack{id, receipts}, receiptInMeter, receiptDropMeter) -} - -// DeliverNodeData injects a new batch of node state data received from a remote node. -func (d *Downloader) DeliverNodeData(id string, data [][]byte) error { - return d.deliver(d.stateCh, &statePack{id, data}, stateInMeter, stateDropMeter) -} - -// DeliverSnapPacket is invoked from a peer's message handler when it transmits a -// data packet for the local node to consume. -func (d *Downloader) DeliverSnapPacket(peer *snap.Peer, packet snap.Packet) error { - switch packet := packet.(type) { - case *snap.AccountRangePacket: - hashes, accounts, err := packet.Unpack() - if err != nil { - return err - } - return d.SnapSyncer.OnAccounts(peer, packet.ID, hashes, accounts, packet.Proof) - - case *snap.StorageRangesPacket: - hashset, slotset := packet.Unpack() - return d.SnapSyncer.OnStorage(peer, packet.ID, hashset, slotset, packet.Proof) - - case *snap.ByteCodesPacket: - return d.SnapSyncer.OnByteCodes(peer, packet.ID, packet.Codes) - - case *snap.TrieNodesPacket: - return d.SnapSyncer.OnTrieNodes(peer, packet.ID, packet.Nodes) - - default: - return fmt.Errorf("unexpected snap packet type: %T", packet) - } -} - -// deliver injects a new batch of data received from a remote node. -func (d *Downloader) deliver(destCh chan dataPack, packet dataPack, inMeter, dropMeter metrics.Meter) (err error) { - // Update the delivery metrics for both good and failed deliveries - inMeter.Mark(int64(packet.Items())) - defer func() { - if err != nil { - dropMeter.Mark(int64(packet.Items())) - } - }() - // Deliver or abort if the sync is canceled while queuing - d.cancelLock.RLock() - cancel := d.cancelCh - d.cancelLock.RUnlock() - if cancel == nil { - return errNoSyncActive - } - select { - case destCh <- packet: - return nil - case <-cancel: - return errNoSyncActive - } -} diff --git a/les/downloader/downloader_test.go b/les/downloader/downloader_test.go deleted file mode 100644 index ae0d5ccda3a6..000000000000 --- a/les/downloader/downloader_test.go +++ /dev/null @@ -1,1621 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package downloader - -import ( - "errors" - "fmt" - "math/big" - "strings" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state/snapshot" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth/protocols/eth" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/trie" -) - -// Reduce some of the parameters to make the tester faster. -func init() { - fullMaxForkAncestry = 10000 - lightMaxForkAncestry = 10000 - blockCacheMaxItems = 1024 - fsHeaderContCheck = 500 * time.Millisecond -} - -// downloadTester is a test simulator for mocking out local block chain. -type downloadTester struct { - downloader *Downloader - - genesis *types.Block // Genesis blocks used by the tester and peers - stateDb ethdb.Database // Database used by the tester for syncing from peers - peerDb ethdb.Database // Database of the peers containing all data - peers map[string]*downloadTesterPeer - - ownHashes []common.Hash // Hash chain belonging to the tester - ownHeaders map[common.Hash]*types.Header // Headers belonging to the tester - ownBlocks map[common.Hash]*types.Block // Blocks belonging to the tester - ownReceipts map[common.Hash]types.Receipts // Receipts belonging to the tester - ownChainTd map[common.Hash]*big.Int // Total difficulties of the blocks in the local chain - - ancientHeaders map[common.Hash]*types.Header // Ancient headers belonging to the tester - ancientBlocks map[common.Hash]*types.Block // Ancient blocks belonging to the tester - ancientReceipts map[common.Hash]types.Receipts // Ancient receipts belonging to the tester - ancientChainTd map[common.Hash]*big.Int // Ancient total difficulties of the blocks in the local chain - - lock sync.RWMutex -} - -// newTester creates a new downloader test mocker. -func newTester() *downloadTester { - tester := &downloadTester{ - genesis: testGenesis, - peerDb: testDB, - peers: make(map[string]*downloadTesterPeer), - ownHashes: []common.Hash{testGenesis.Hash()}, - ownHeaders: map[common.Hash]*types.Header{testGenesis.Hash(): testGenesis.Header()}, - ownBlocks: map[common.Hash]*types.Block{testGenesis.Hash(): testGenesis}, - ownReceipts: map[common.Hash]types.Receipts{testGenesis.Hash(): nil}, - ownChainTd: map[common.Hash]*big.Int{testGenesis.Hash(): testGenesis.Difficulty()}, - - // Initialize ancient store with test genesis block - ancientHeaders: map[common.Hash]*types.Header{testGenesis.Hash(): testGenesis.Header()}, - ancientBlocks: map[common.Hash]*types.Block{testGenesis.Hash(): testGenesis}, - ancientReceipts: map[common.Hash]types.Receipts{testGenesis.Hash(): nil}, - ancientChainTd: map[common.Hash]*big.Int{testGenesis.Hash(): testGenesis.Difficulty()}, - } - tester.stateDb = rawdb.NewMemoryDatabase() - tester.stateDb.Put(testGenesis.Root().Bytes(), []byte{0x00}) - - tester.downloader = New(0, tester.stateDb, new(event.TypeMux), tester, nil, tester.dropPeer) - return tester -} - -// terminate aborts any operations on the embedded downloader and releases all -// held resources. -func (dl *downloadTester) terminate() { - dl.downloader.Terminate() -} - -// sync starts synchronizing with a remote peer, blocking until it completes. -func (dl *downloadTester) sync(id string, td *big.Int, mode SyncMode) error { - dl.lock.RLock() - hash := dl.peers[id].chain.headBlock().Hash() - // If no particular TD was requested, load from the peer's blockchain - if td == nil { - td = dl.peers[id].chain.td(hash) - } - dl.lock.RUnlock() - - // Synchronise with the chosen peer and ensure proper cleanup afterwards - err := dl.downloader.synchronise(id, hash, td, mode) - select { - case <-dl.downloader.cancelCh: - // Ok, downloader fully cancelled after sync cycle - default: - // Downloader is still accepting packets, can block a peer up - panic("downloader active post sync cycle") // panic will be caught by tester - } - return err -} - -// HasHeader checks if a header is present in the testers canonical chain. -func (dl *downloadTester) HasHeader(hash common.Hash, number uint64) bool { - return dl.GetHeaderByHash(hash) != nil -} - -// HasBlock checks if a block is present in the testers canonical chain. -func (dl *downloadTester) HasBlock(hash common.Hash, number uint64) bool { - return dl.GetBlockByHash(hash) != nil -} - -// HasFastBlock checks if a block is present in the testers canonical chain. -func (dl *downloadTester) HasFastBlock(hash common.Hash, number uint64) bool { - dl.lock.RLock() - defer dl.lock.RUnlock() - - if _, ok := dl.ancientReceipts[hash]; ok { - return true - } - _, ok := dl.ownReceipts[hash] - return ok -} - -// GetHeader retrieves a header from the testers canonical chain. -func (dl *downloadTester) GetHeaderByHash(hash common.Hash) *types.Header { - dl.lock.RLock() - defer dl.lock.RUnlock() - return dl.getHeaderByHash(hash) -} - -// getHeaderByHash returns the header if found either within ancients or own blocks) -// This method assumes that the caller holds at least the read-lock (dl.lock) -func (dl *downloadTester) getHeaderByHash(hash common.Hash) *types.Header { - header := dl.ancientHeaders[hash] - if header != nil { - return header - } - return dl.ownHeaders[hash] -} - -// GetBlock retrieves a block from the testers canonical chain. -func (dl *downloadTester) GetBlockByHash(hash common.Hash) *types.Block { - dl.lock.RLock() - defer dl.lock.RUnlock() - - block := dl.ancientBlocks[hash] - if block != nil { - return block - } - return dl.ownBlocks[hash] -} - -// CurrentHeader retrieves the current head header from the canonical chain. -func (dl *downloadTester) CurrentHeader() *types.Header { - dl.lock.RLock() - defer dl.lock.RUnlock() - - for i := len(dl.ownHashes) - 1; i >= 0; i-- { - if header := dl.ancientHeaders[dl.ownHashes[i]]; header != nil { - return header - } - if header := dl.ownHeaders[dl.ownHashes[i]]; header != nil { - return header - } - } - return dl.genesis.Header() -} - -// CurrentBlock retrieves the current head block from the canonical chain. -func (dl *downloadTester) CurrentBlock() *types.Block { - dl.lock.RLock() - defer dl.lock.RUnlock() - - for i := len(dl.ownHashes) - 1; i >= 0; i-- { - if block := dl.ancientBlocks[dl.ownHashes[i]]; block != nil { - if _, err := dl.stateDb.Get(block.Root().Bytes()); err == nil { - return block - } - return block - } - if block := dl.ownBlocks[dl.ownHashes[i]]; block != nil { - if _, err := dl.stateDb.Get(block.Root().Bytes()); err == nil { - return block - } - } - } - return dl.genesis -} - -// CurrentFastBlock retrieves the current head fast-sync block from the canonical chain. -func (dl *downloadTester) CurrentFastBlock() *types.Block { - dl.lock.RLock() - defer dl.lock.RUnlock() - - for i := len(dl.ownHashes) - 1; i >= 0; i-- { - if block := dl.ancientBlocks[dl.ownHashes[i]]; block != nil { - return block - } - if block := dl.ownBlocks[dl.ownHashes[i]]; block != nil { - return block - } - } - return dl.genesis -} - -// FastSyncCommitHead manually sets the head block to a given hash. -func (dl *downloadTester) FastSyncCommitHead(hash common.Hash) error { - // For now only check that the state trie is correct - if block := dl.GetBlockByHash(hash); block != nil { - _, err := trie.NewStateTrie(trie.StateTrieID(block.Root()), trie.NewDatabase(dl.stateDb)) - return err - } - return fmt.Errorf("non existent block: %x", hash[:4]) -} - -// GetTd retrieves the block's total difficulty from the canonical chain. -func (dl *downloadTester) GetTd(hash common.Hash, number uint64) *big.Int { - dl.lock.RLock() - defer dl.lock.RUnlock() - - return dl.getTd(hash) -} - -// getTd retrieves the block's total difficulty if found either within -// ancients or own blocks). -// This method assumes that the caller holds at least the read-lock (dl.lock) -func (dl *downloadTester) getTd(hash common.Hash) *big.Int { - if td := dl.ancientChainTd[hash]; td != nil { - return td - } - return dl.ownChainTd[hash] -} - -// InsertHeaderChain injects a new batch of headers into the simulated chain. -func (dl *downloadTester) InsertHeaderChain(headers []*types.Header) (i int, err error) { - dl.lock.Lock() - defer dl.lock.Unlock() - // Do a quick check, as the blockchain.InsertHeaderChain doesn't insert anything in case of errors - if dl.getHeaderByHash(headers[0].ParentHash) == nil { - return 0, fmt.Errorf("InsertHeaderChain: unknown parent at first position, parent of number %d", headers[0].Number) - } - var hashes []common.Hash - for i := 1; i < len(headers); i++ { - hash := headers[i-1].Hash() - if headers[i].ParentHash != headers[i-1].Hash() { - return i, fmt.Errorf("non-contiguous import at position %d", i) - } - hashes = append(hashes, hash) - } - hashes = append(hashes, headers[len(headers)-1].Hash()) - // Do a full insert if pre-checks passed - for i, header := range headers { - hash := hashes[i] - if dl.getHeaderByHash(hash) != nil { - continue - } - if dl.getHeaderByHash(header.ParentHash) == nil { - // This _should_ be impossible, due to precheck and induction - return i, fmt.Errorf("InsertHeaderChain: unknown parent at position %d", i) - } - dl.ownHashes = append(dl.ownHashes, hash) - dl.ownHeaders[hash] = header - - td := dl.getTd(header.ParentHash) - dl.ownChainTd[hash] = new(big.Int).Add(td, header.Difficulty) - } - return len(headers), nil -} - -// InsertChain injects a new batch of blocks into the simulated chain. -func (dl *downloadTester) InsertChain(blocks types.Blocks) (i int, err error) { - dl.lock.Lock() - defer dl.lock.Unlock() - for i, block := range blocks { - if parent, ok := dl.ownBlocks[block.ParentHash()]; !ok { - return i, fmt.Errorf("InsertChain: unknown parent at position %d / %d", i, len(blocks)) - } else if _, err := dl.stateDb.Get(parent.Root().Bytes()); err != nil { - return i, fmt.Errorf("InsertChain: unknown parent state %x: %v", parent.Root(), err) - } - if hdr := dl.getHeaderByHash(block.Hash()); hdr == nil { - dl.ownHashes = append(dl.ownHashes, block.Hash()) - dl.ownHeaders[block.Hash()] = block.Header() - } - dl.ownBlocks[block.Hash()] = block - dl.ownReceipts[block.Hash()] = make(types.Receipts, 0) - dl.stateDb.Put(block.Root().Bytes(), []byte{0x00}) - td := dl.getTd(block.ParentHash()) - dl.ownChainTd[block.Hash()] = new(big.Int).Add(td, block.Difficulty()) - } - return len(blocks), nil -} - -// InsertReceiptChain injects a new batch of receipts into the simulated chain. -func (dl *downloadTester) InsertReceiptChain(blocks types.Blocks, receipts []types.Receipts, ancientLimit uint64) (i int, err error) { - dl.lock.Lock() - defer dl.lock.Unlock() - - for i := 0; i < len(blocks) && i < len(receipts); i++ { - if _, ok := dl.ownHeaders[blocks[i].Hash()]; !ok { - return i, errors.New("unknown owner") - } - if _, ok := dl.ancientBlocks[blocks[i].ParentHash()]; !ok { - if _, ok := dl.ownBlocks[blocks[i].ParentHash()]; !ok { - return i, errors.New("InsertReceiptChain: unknown parent") - } - } - if blocks[i].NumberU64() <= ancientLimit { - dl.ancientBlocks[blocks[i].Hash()] = blocks[i] - dl.ancientReceipts[blocks[i].Hash()] = receipts[i] - - // Migrate from active db to ancient db - dl.ancientHeaders[blocks[i].Hash()] = blocks[i].Header() - dl.ancientChainTd[blocks[i].Hash()] = new(big.Int).Add(dl.ancientChainTd[blocks[i].ParentHash()], blocks[i].Difficulty()) - delete(dl.ownHeaders, blocks[i].Hash()) - delete(dl.ownChainTd, blocks[i].Hash()) - } else { - dl.ownBlocks[blocks[i].Hash()] = blocks[i] - dl.ownReceipts[blocks[i].Hash()] = receipts[i] - } - } - return len(blocks), nil -} - -// SetHead rewinds the local chain to a new head. -func (dl *downloadTester) SetHead(head uint64) error { - dl.lock.Lock() - defer dl.lock.Unlock() - - // Find the hash of the head to reset to - var hash common.Hash - for h, header := range dl.ownHeaders { - if header.Number.Uint64() == head { - hash = h - } - } - for h, header := range dl.ancientHeaders { - if header.Number.Uint64() == head { - hash = h - } - } - if hash == (common.Hash{}) { - return fmt.Errorf("unknown head to set: %d", head) - } - // Find the offset in the header chain - var offset int - for o, h := range dl.ownHashes { - if h == hash { - offset = o - break - } - } - // Remove all the hashes and associated data afterwards - for i := offset + 1; i < len(dl.ownHashes); i++ { - delete(dl.ownChainTd, dl.ownHashes[i]) - delete(dl.ownHeaders, dl.ownHashes[i]) - delete(dl.ownReceipts, dl.ownHashes[i]) - delete(dl.ownBlocks, dl.ownHashes[i]) - - delete(dl.ancientChainTd, dl.ownHashes[i]) - delete(dl.ancientHeaders, dl.ownHashes[i]) - delete(dl.ancientReceipts, dl.ownHashes[i]) - delete(dl.ancientBlocks, dl.ownHashes[i]) - } - dl.ownHashes = dl.ownHashes[:offset+1] - return nil -} - -// Rollback removes some recently added elements from the chain. -func (dl *downloadTester) Rollback(hashes []common.Hash) { -} - -// newPeer registers a new block download source into the downloader. -func (dl *downloadTester) newPeer(id string, version uint, chain *testChain) error { - dl.lock.Lock() - defer dl.lock.Unlock() - - peer := &downloadTesterPeer{dl: dl, id: id, chain: chain} - dl.peers[id] = peer - return dl.downloader.RegisterPeer(id, version, peer) -} - -// dropPeer simulates a hard peer removal from the connection pool. -func (dl *downloadTester) dropPeer(id string) { - dl.lock.Lock() - defer dl.lock.Unlock() - - delete(dl.peers, id) - dl.downloader.UnregisterPeer(id) -} - -// Snapshots implements the BlockChain interface for the downloader, but is a noop. -func (dl *downloadTester) Snapshots() *snapshot.Tree { - return nil -} - -type downloadTesterPeer struct { - dl *downloadTester - id string - chain *testChain - missingStates map[common.Hash]bool // State entries that fast sync should not return -} - -// Head constructs a function to retrieve a peer's current head hash -// and total difficulty. -func (dlp *downloadTesterPeer) Head() (common.Hash, *big.Int) { - b := dlp.chain.headBlock() - return b.Hash(), dlp.chain.td(b.Hash()) -} - -// RequestHeadersByHash constructs a GetBlockHeaders function based on a hashed -// origin; associated with a particular peer in the download tester. The returned -// function can be used to retrieve batches of headers from the particular peer. -func (dlp *downloadTesterPeer) RequestHeadersByHash(origin common.Hash, amount int, skip int, reverse bool) error { - result := dlp.chain.headersByHash(origin, amount, skip, reverse) - go dlp.dl.downloader.DeliverHeaders(dlp.id, result) - return nil -} - -// RequestHeadersByNumber constructs a GetBlockHeaders function based on a numbered -// origin; associated with a particular peer in the download tester. The returned -// function can be used to retrieve batches of headers from the particular peer. -func (dlp *downloadTesterPeer) RequestHeadersByNumber(origin uint64, amount int, skip int, reverse bool) error { - result := dlp.chain.headersByNumber(origin, amount, skip, reverse) - go dlp.dl.downloader.DeliverHeaders(dlp.id, result) - return nil -} - -// RequestBodies constructs a getBlockBodies method associated with a particular -// peer in the download tester. The returned function can be used to retrieve -// batches of block bodies from the particularly requested peer. -func (dlp *downloadTesterPeer) RequestBodies(hashes []common.Hash) error { - txs, uncles := dlp.chain.bodies(hashes) - go dlp.dl.downloader.DeliverBodies(dlp.id, txs, uncles) - return nil -} - -// RequestReceipts constructs a getReceipts method associated with a particular -// peer in the download tester. The returned function can be used to retrieve -// batches of block receipts from the particularly requested peer. -func (dlp *downloadTesterPeer) RequestReceipts(hashes []common.Hash) error { - receipts := dlp.chain.receipts(hashes) - go dlp.dl.downloader.DeliverReceipts(dlp.id, receipts) - return nil -} - -// RequestNodeData constructs a getNodeData method associated with a particular -// peer in the download tester. The returned function can be used to retrieve -// batches of node state data from the particularly requested peer. -func (dlp *downloadTesterPeer) RequestNodeData(hashes []common.Hash) error { - dlp.dl.lock.RLock() - defer dlp.dl.lock.RUnlock() - - results := make([][]byte, 0, len(hashes)) - for _, hash := range hashes { - if data, err := dlp.dl.peerDb.Get(hash.Bytes()); err == nil { - if !dlp.missingStates[hash] { - results = append(results, data) - } - } - } - go dlp.dl.downloader.DeliverNodeData(dlp.id, results) - return nil -} - -// assertOwnChain checks if the local chain contains the correct number of items -// of the various chain components. -func assertOwnChain(t *testing.T, tester *downloadTester, length int) { - // Mark this method as a helper to report errors at callsite, not in here - t.Helper() - - assertOwnForkedChain(t, tester, 1, []int{length}) -} - -// assertOwnForkedChain checks if the local forked chain contains the correct -// number of items of the various chain components. -func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, lengths []int) { - // Mark this method as a helper to report errors at callsite, not in here - t.Helper() - - // Initialize the counters for the first fork - headers, blocks, receipts := lengths[0], lengths[0], lengths[0] - - // Update the counters for each subsequent fork - for _, length := range lengths[1:] { - headers += length - common - blocks += length - common - receipts += length - common - } - if tester.downloader.getMode() == LightSync { - blocks, receipts = 1, 1 - } - if hs := len(tester.ownHeaders) + len(tester.ancientHeaders) - 1; hs != headers { - t.Fatalf("synchronised headers mismatch: have %v, want %v", hs, headers) - } - if bs := len(tester.ownBlocks) + len(tester.ancientBlocks) - 1; bs != blocks { - t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, blocks) - } - if rs := len(tester.ownReceipts) + len(tester.ancientReceipts) - 1; rs != receipts { - t.Fatalf("synchronised receipts mismatch: have %v, want %v", rs, receipts) - } -} - -func TestCanonicalSynchronisation66Full(t *testing.T) { testCanonSync(t, eth.ETH66, FullSync) } -func TestCanonicalSynchronisation66Fast(t *testing.T) { testCanonSync(t, eth.ETH66, FastSync) } -func TestCanonicalSynchronisation66Light(t *testing.T) { testCanonSync(t, eth.ETH66, LightSync) } - -func testCanonSync(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - - // Create a small enough block chain to download - chain := testChainBase.shorten(blockCacheMaxItems - 15) - tester.newPeer("peer", protocol, chain) - - // Synchronise with the peer and make sure all relevant data was retrieved - if err := tester.sync("peer", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - assertOwnChain(t, tester, chain.len()) -} - -// Tests that if a large batch of blocks are being downloaded, it is throttled -// until the cached blocks are retrieved. -func TestThrottling66Full(t *testing.T) { testThrottling(t, eth.ETH66, FullSync) } -func TestThrottling66Fast(t *testing.T) { testThrottling(t, eth.ETH66, FastSync) } - -func testThrottling(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - tester := newTester() - - // Create a long block chain to download and the tester - targetBlocks := testChainBase.len() - 1 - tester.newPeer("peer", protocol, testChainBase) - - // Wrap the importer to allow stepping - blocked, proceed := uint32(0), make(chan struct{}) - tester.downloader.chainInsertHook = func(results []*fetchResult) { - atomic.StoreUint32(&blocked, uint32(len(results))) - <-proceed - } - // Start a synchronisation concurrently - errc := make(chan error, 1) - go func() { - errc <- tester.sync("peer", nil, mode) - }() - // Iteratively take some blocks, always checking the retrieval count - for { - // Check the retrieval count synchronously (! reason for this ugly block) - tester.lock.RLock() - retrieved := len(tester.ownBlocks) - tester.lock.RUnlock() - if retrieved >= targetBlocks+1 { - break - } - // Wait a bit for sync to throttle itself - var cached, frozen int - for start := time.Now(); time.Since(start) < 3*time.Second; { - time.Sleep(25 * time.Millisecond) - - tester.lock.Lock() - tester.downloader.queue.lock.Lock() - tester.downloader.queue.resultCache.lock.Lock() - { - cached = tester.downloader.queue.resultCache.countCompleted() - frozen = int(atomic.LoadUint32(&blocked)) - retrieved = len(tester.ownBlocks) - } - tester.downloader.queue.resultCache.lock.Unlock() - tester.downloader.queue.lock.Unlock() - tester.lock.Unlock() - - if cached == blockCacheMaxItems || - cached == blockCacheMaxItems-reorgProtHeaderDelay || - retrieved+cached+frozen == targetBlocks+1 || - retrieved+cached+frozen == targetBlocks+1-reorgProtHeaderDelay { - break - } - } - // Make sure we filled up the cache, then exhaust it - time.Sleep(25 * time.Millisecond) // give it a chance to screw up - tester.lock.RLock() - retrieved = len(tester.ownBlocks) - tester.lock.RUnlock() - if cached != blockCacheMaxItems && cached != blockCacheMaxItems-reorgProtHeaderDelay && retrieved+cached+frozen != targetBlocks+1 && retrieved+cached+frozen != targetBlocks+1-reorgProtHeaderDelay { - t.Fatalf("block count mismatch: have %v, want %v (owned %v, blocked %v, target %v)", cached, blockCacheMaxItems, retrieved, frozen, targetBlocks+1) - } - - // Permit the blocked blocks to import - if atomic.LoadUint32(&blocked) > 0 { - atomic.StoreUint32(&blocked, uint32(0)) - proceed <- struct{}{} - } - } - // Check that we haven't pulled more blocks than available - assertOwnChain(t, tester, targetBlocks+1) - if err := <-errc; err != nil { - t.Fatalf("block synchronization failed: %v", err) - } - tester.terminate() -} - -// Tests that simple synchronization against a forked chain works correctly. In -// this test common ancestor lookup should *not* be short circuited, and a full -// binary search should be executed. -func TestForkedSync66Full(t *testing.T) { testForkedSync(t, eth.ETH66, FullSync) } -func TestForkedSync66Fast(t *testing.T) { testForkedSync(t, eth.ETH66, FastSync) } -func TestForkedSync66Light(t *testing.T) { testForkedSync(t, eth.ETH66, LightSync) } - -func testForkedSync(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - - chainA := testChainForkLightA.shorten(testChainBase.len() + 80) - chainB := testChainForkLightB.shorten(testChainBase.len() + 80) - tester.newPeer("fork A", protocol, chainA) - tester.newPeer("fork B", protocol, chainB) - // Synchronise with the peer and make sure all blocks were retrieved - if err := tester.sync("fork A", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - assertOwnChain(t, tester, chainA.len()) - - // Synchronise with the second peer and make sure that fork is pulled too - if err := tester.sync("fork B", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - assertOwnForkedChain(t, tester, testChainBase.len(), []int{chainA.len(), chainB.len()}) -} - -// Tests that synchronising against a much shorter but much heavier fork works -// correctly and is not dropped. -func TestHeavyForkedSync66Full(t *testing.T) { testHeavyForkedSync(t, eth.ETH66, FullSync) } -func TestHeavyForkedSync66Fast(t *testing.T) { testHeavyForkedSync(t, eth.ETH66, FastSync) } -func TestHeavyForkedSync66Light(t *testing.T) { testHeavyForkedSync(t, eth.ETH66, LightSync) } - -func testHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - - chainA := testChainForkLightA.shorten(testChainBase.len() + 80) - chainB := testChainForkHeavy.shorten(testChainBase.len() + 80) - tester.newPeer("light", protocol, chainA) - tester.newPeer("heavy", protocol, chainB) - - // Synchronise with the peer and make sure all blocks were retrieved - if err := tester.sync("light", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - assertOwnChain(t, tester, chainA.len()) - - // Synchronise with the second peer and make sure that fork is pulled too - if err := tester.sync("heavy", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - assertOwnForkedChain(t, tester, testChainBase.len(), []int{chainA.len(), chainB.len()}) -} - -// Tests that chain forks are contained within a certain interval of the current -// chain head, ensuring that malicious peers cannot waste resources by feeding -// long dead chains. -func TestBoundedForkedSync66Full(t *testing.T) { testBoundedForkedSync(t, eth.ETH66, FullSync) } -func TestBoundedForkedSync66Fast(t *testing.T) { testBoundedForkedSync(t, eth.ETH66, FastSync) } -func TestBoundedForkedSync66Light(t *testing.T) { testBoundedForkedSync(t, eth.ETH66, LightSync) } - -func testBoundedForkedSync(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - - chainA := testChainForkLightA - chainB := testChainForkLightB - tester.newPeer("original", protocol, chainA) - tester.newPeer("rewriter", protocol, chainB) - - // Synchronise with the peer and make sure all blocks were retrieved - if err := tester.sync("original", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - assertOwnChain(t, tester, chainA.len()) - - // Synchronise with the second peer and ensure that the fork is rejected to being too old - if err := tester.sync("rewriter", nil, mode); err != errInvalidAncestor { - t.Fatalf("sync failure mismatch: have %v, want %v", err, errInvalidAncestor) - } -} - -// Tests that chain forks are contained within a certain interval of the current -// chain head for short but heavy forks too. These are a bit special because they -// take different ancestor lookup paths. -func TestBoundedHeavyForkedSync66Full(t *testing.T) { - testBoundedHeavyForkedSync(t, eth.ETH66, FullSync) -} -func TestBoundedHeavyForkedSync66Fast(t *testing.T) { - testBoundedHeavyForkedSync(t, eth.ETH66, FastSync) -} -func TestBoundedHeavyForkedSync66Light(t *testing.T) { - testBoundedHeavyForkedSync(t, eth.ETH66, LightSync) -} - -func testBoundedHeavyForkedSync(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - tester := newTester() - - // Create a long enough forked chain - chainA := testChainForkLightA - chainB := testChainForkHeavy - tester.newPeer("original", protocol, chainA) - - // Synchronise with the peer and make sure all blocks were retrieved - if err := tester.sync("original", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - assertOwnChain(t, tester, chainA.len()) - - tester.newPeer("heavy-rewriter", protocol, chainB) - // Synchronise with the second peer and ensure that the fork is rejected to being too old - if err := tester.sync("heavy-rewriter", nil, mode); err != errInvalidAncestor { - t.Fatalf("sync failure mismatch: have %v, want %v", err, errInvalidAncestor) - } - tester.terminate() -} - -// Tests that an inactive downloader will not accept incoming block headers, -// bodies and receipts. -func TestInactiveDownloader63(t *testing.T) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - - // Check that neither block headers nor bodies are accepted - if err := tester.downloader.DeliverHeaders("bad peer", []*types.Header{}); err != errNoSyncActive { - t.Errorf("error mismatch: have %v, want %v", err, errNoSyncActive) - } - if err := tester.downloader.DeliverBodies("bad peer", [][]*types.Transaction{}, [][]*types.Header{}); err != errNoSyncActive { - t.Errorf("error mismatch: have %v, want %v", err, errNoSyncActive) - } - if err := tester.downloader.DeliverReceipts("bad peer", [][]*types.Receipt{}); err != errNoSyncActive { - t.Errorf("error mismatch: have %v, want %v", err, errNoSyncActive) - } -} - -// Tests that a canceled download wipes all previously accumulated state. -func TestCancel66Full(t *testing.T) { testCancel(t, eth.ETH66, FullSync) } -func TestCancel66Fast(t *testing.T) { testCancel(t, eth.ETH66, FastSync) } -func TestCancel66Light(t *testing.T) { testCancel(t, eth.ETH66, LightSync) } - -func testCancel(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - - chain := testChainBase.shorten(MaxHeaderFetch) - tester.newPeer("peer", protocol, chain) - - // Make sure canceling works with a pristine downloader - tester.downloader.Cancel() - if !tester.downloader.queue.Idle() { - t.Errorf("download queue not idle") - } - // Synchronise with the peer, but cancel afterwards - if err := tester.sync("peer", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - tester.downloader.Cancel() - if !tester.downloader.queue.Idle() { - t.Errorf("download queue not idle") - } -} - -// Tests that synchronisation from multiple peers works as intended (multi thread sanity test). -func TestMultiSynchronisation66Full(t *testing.T) { testMultiSynchronisation(t, eth.ETH66, FullSync) } -func TestMultiSynchronisation66Fast(t *testing.T) { testMultiSynchronisation(t, eth.ETH66, FastSync) } -func TestMultiSynchronisation66Light(t *testing.T) { testMultiSynchronisation(t, eth.ETH66, LightSync) } - -func testMultiSynchronisation(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - - // Create various peers with various parts of the chain - targetPeers := 8 - chain := testChainBase.shorten(targetPeers * 100) - - for i := 0; i < targetPeers; i++ { - id := fmt.Sprintf("peer #%d", i) - tester.newPeer(id, protocol, chain.shorten(chain.len()/(i+1))) - } - if err := tester.sync("peer #0", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - assertOwnChain(t, tester, chain.len()) -} - -// Tests that synchronisations behave well in multi-version protocol environments -// and not wreak havoc on other nodes in the network. -func TestMultiProtoSynchronisation66Full(t *testing.T) { testMultiProtoSync(t, eth.ETH66, FullSync) } -func TestMultiProtoSynchronisation66Fast(t *testing.T) { testMultiProtoSync(t, eth.ETH66, FastSync) } -func TestMultiProtoSynchronisation66Light(t *testing.T) { testMultiProtoSync(t, eth.ETH66, LightSync) } - -func testMultiProtoSync(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - - // Create a small enough block chain to download - chain := testChainBase.shorten(blockCacheMaxItems - 15) - - // Create peers of every type - tester.newPeer("peer 66", eth.ETH66, chain) - //tester.newPeer("peer 65", eth.ETH67, chain) - - // Synchronise with the requested peer and make sure all blocks were retrieved - if err := tester.sync(fmt.Sprintf("peer %d", protocol), nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - assertOwnChain(t, tester, chain.len()) - - // Check that no peers have been dropped off - for _, version := range []int{66} { - peer := fmt.Sprintf("peer %d", version) - if _, ok := tester.peers[peer]; !ok { - t.Errorf("%s dropped", peer) - } - } -} - -// Tests that if a block is empty (e.g. header only), no body request should be -// made, and instead the header should be assembled into a whole block in itself. -func TestEmptyShortCircuit66Full(t *testing.T) { testEmptyShortCircuit(t, eth.ETH66, FullSync) } -func TestEmptyShortCircuit66Fast(t *testing.T) { testEmptyShortCircuit(t, eth.ETH66, FastSync) } -func TestEmptyShortCircuit66Light(t *testing.T) { testEmptyShortCircuit(t, eth.ETH66, LightSync) } - -func testEmptyShortCircuit(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - - // Create a block chain to download - chain := testChainBase - tester.newPeer("peer", protocol, chain) - - // Instrument the downloader to signal body requests - bodiesHave, receiptsHave := int32(0), int32(0) - tester.downloader.bodyFetchHook = func(headers []*types.Header) { - atomic.AddInt32(&bodiesHave, int32(len(headers))) - } - tester.downloader.receiptFetchHook = func(headers []*types.Header) { - atomic.AddInt32(&receiptsHave, int32(len(headers))) - } - // Synchronise with the peer and make sure all blocks were retrieved - if err := tester.sync("peer", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - assertOwnChain(t, tester, chain.len()) - - // Validate the number of block bodies that should have been requested - bodiesNeeded, receiptsNeeded := 0, 0 - for _, block := range chain.blockm { - if mode != LightSync && block != tester.genesis && (len(block.Transactions()) > 0 || len(block.Uncles()) > 0) { - bodiesNeeded++ - } - } - for _, receipt := range chain.receiptm { - if mode == FastSync && len(receipt) > 0 { - receiptsNeeded++ - } - } - if int(bodiesHave) != bodiesNeeded { - t.Errorf("body retrieval count mismatch: have %v, want %v", bodiesHave, bodiesNeeded) - } - if int(receiptsHave) != receiptsNeeded { - t.Errorf("receipt retrieval count mismatch: have %v, want %v", receiptsHave, receiptsNeeded) - } -} - -// Tests that headers are enqueued continuously, preventing malicious nodes from -// stalling the downloader by feeding gapped header chains. -func TestMissingHeaderAttack66Full(t *testing.T) { testMissingHeaderAttack(t, eth.ETH66, FullSync) } -func TestMissingHeaderAttack66Fast(t *testing.T) { testMissingHeaderAttack(t, eth.ETH66, FastSync) } -func TestMissingHeaderAttack66Light(t *testing.T) { testMissingHeaderAttack(t, eth.ETH66, LightSync) } - -func testMissingHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - - chain := testChainBase.shorten(blockCacheMaxItems - 15) - brokenChain := chain.shorten(chain.len()) - delete(brokenChain.headerm, brokenChain.chain[brokenChain.len()/2]) - tester.newPeer("attack", protocol, brokenChain) - - if err := tester.sync("attack", nil, mode); err == nil { - t.Fatalf("succeeded attacker synchronisation") - } - // Synchronise with the valid peer and make sure sync succeeds - tester.newPeer("valid", protocol, chain) - if err := tester.sync("valid", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - assertOwnChain(t, tester, chain.len()) -} - -// Tests that if requested headers are shifted (i.e. first is missing), the queue -// detects the invalid numbering. -func TestShiftedHeaderAttack66Full(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH66, FullSync) } -func TestShiftedHeaderAttack66Fast(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH66, FastSync) } -func TestShiftedHeaderAttack66Light(t *testing.T) { testShiftedHeaderAttack(t, eth.ETH66, LightSync) } - -func testShiftedHeaderAttack(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - - chain := testChainBase.shorten(blockCacheMaxItems - 15) - - // Attempt a full sync with an attacker feeding shifted headers - brokenChain := chain.shorten(chain.len()) - delete(brokenChain.headerm, brokenChain.chain[1]) - delete(brokenChain.blockm, brokenChain.chain[1]) - delete(brokenChain.receiptm, brokenChain.chain[1]) - tester.newPeer("attack", protocol, brokenChain) - if err := tester.sync("attack", nil, mode); err == nil { - t.Fatalf("succeeded attacker synchronisation") - } - - // Synchronise with the valid peer and make sure sync succeeds - tester.newPeer("valid", protocol, chain) - if err := tester.sync("valid", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - assertOwnChain(t, tester, chain.len()) -} - -// Tests that upon detecting an invalid header, the recent ones are rolled back -// for various failure scenarios. Afterwards a full sync is attempted to make -// sure no state was corrupted. -func TestInvalidHeaderRollback66Fast(t *testing.T) { testInvalidHeaderRollback(t, eth.ETH66, FastSync) } - -func testInvalidHeaderRollback(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - - // Create a small enough block chain to download - targetBlocks := 3*fsHeaderSafetyNet + 256 + fsMinFullBlocks - chain := testChainBase.shorten(targetBlocks) - - // Attempt to sync with an attacker that feeds junk during the fast sync phase. - // This should result in the last fsHeaderSafetyNet headers being rolled back. - missing := fsHeaderSafetyNet + MaxHeaderFetch + 1 - fastAttackChain := chain.shorten(chain.len()) - delete(fastAttackChain.headerm, fastAttackChain.chain[missing]) - tester.newPeer("fast-attack", protocol, fastAttackChain) - - if err := tester.sync("fast-attack", nil, mode); err == nil { - t.Fatalf("succeeded fast attacker synchronisation") - } - if head := tester.CurrentHeader().Number.Int64(); int(head) > MaxHeaderFetch { - t.Errorf("rollback head mismatch: have %v, want at most %v", head, MaxHeaderFetch) - } - - // Attempt to sync with an attacker that feeds junk during the block import phase. - // This should result in both the last fsHeaderSafetyNet number of headers being - // rolled back, and also the pivot point being reverted to a non-block status. - missing = 3*fsHeaderSafetyNet + MaxHeaderFetch + 1 - blockAttackChain := chain.shorten(chain.len()) - delete(fastAttackChain.headerm, fastAttackChain.chain[missing]) // Make sure the fast-attacker doesn't fill in - delete(blockAttackChain.headerm, blockAttackChain.chain[missing]) - tester.newPeer("block-attack", protocol, blockAttackChain) - - if err := tester.sync("block-attack", nil, mode); err == nil { - t.Fatalf("succeeded block attacker synchronisation") - } - if head := tester.CurrentHeader().Number.Int64(); int(head) > 2*fsHeaderSafetyNet+MaxHeaderFetch { - t.Errorf("rollback head mismatch: have %v, want at most %v", head, 2*fsHeaderSafetyNet+MaxHeaderFetch) - } - if mode == FastSync { - if head := tester.CurrentBlock().NumberU64(); head != 0 { - t.Errorf("fast sync pivot block #%d not rolled back", head) - } - } - - // Attempt to sync with an attacker that withholds promised blocks after the - // fast sync pivot point. This could be a trial to leave the node with a bad - // but already imported pivot block. - withholdAttackChain := chain.shorten(chain.len()) - tester.newPeer("withhold-attack", protocol, withholdAttackChain) - tester.downloader.syncInitHook = func(uint64, uint64) { - for i := missing; i < withholdAttackChain.len(); i++ { - delete(withholdAttackChain.headerm, withholdAttackChain.chain[i]) - } - tester.downloader.syncInitHook = nil - } - if err := tester.sync("withhold-attack", nil, mode); err == nil { - t.Fatalf("succeeded withholding attacker synchronisation") - } - if head := tester.CurrentHeader().Number.Int64(); int(head) > 2*fsHeaderSafetyNet+MaxHeaderFetch { - t.Errorf("rollback head mismatch: have %v, want at most %v", head, 2*fsHeaderSafetyNet+MaxHeaderFetch) - } - if mode == FastSync { - if head := tester.CurrentBlock().NumberU64(); head != 0 { - t.Errorf("fast sync pivot block #%d not rolled back", head) - } - } - - // synchronise with the valid peer and make sure sync succeeds. Since the last rollback - // should also disable fast syncing for this process, verify that we did a fresh full - // sync. Note, we can't assert anything about the receipts since we won't purge the - // database of them, hence we can't use assertOwnChain. - tester.newPeer("valid", protocol, chain) - if err := tester.sync("valid", nil, mode); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } - if hs := len(tester.ownHeaders); hs != chain.len() { - t.Fatalf("synchronised headers mismatch: have %v, want %v", hs, chain.len()) - } - if mode != LightSync { - if bs := len(tester.ownBlocks); bs != chain.len() { - t.Fatalf("synchronised blocks mismatch: have %v, want %v", bs, chain.len()) - } - } - tester.terminate() -} - -// Tests that a peer advertising a high TD doesn't get to stall the downloader -// afterwards by not sending any useful hashes. -func TestHighTDStarvationAttack66Full(t *testing.T) { - testHighTDStarvationAttack(t, eth.ETH66, FullSync) -} -func TestHighTDStarvationAttack66Fast(t *testing.T) { - testHighTDStarvationAttack(t, eth.ETH66, FastSync) -} -func TestHighTDStarvationAttack66Light(t *testing.T) { - testHighTDStarvationAttack(t, eth.ETH66, LightSync) -} - -func testHighTDStarvationAttack(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - - chain := testChainBase.shorten(1) - tester.newPeer("attack", protocol, chain) - if err := tester.sync("attack", big.NewInt(1000000), mode); err != errStallingPeer { - t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errStallingPeer) - } - tester.terminate() -} - -// Tests that misbehaving peers are disconnected, whilst behaving ones are not. -func TestBlockHeaderAttackerDropping66(t *testing.T) { testBlockHeaderAttackerDropping(t, eth.ETH66) } - -func testBlockHeaderAttackerDropping(t *testing.T, protocol uint) { - t.Parallel() - - // Define the disconnection requirement for individual hash fetch errors - tests := []struct { - result error - drop bool - }{ - {nil, false}, // Sync succeeded, all is well - {errBusy, false}, // Sync is already in progress, no problem - {errUnknownPeer, false}, // Peer is unknown, was already dropped, don't double drop - {errBadPeer, true}, // Peer was deemed bad for some reason, drop it - {errStallingPeer, true}, // Peer was detected to be stalling, drop it - {errUnsyncedPeer, true}, // Peer was detected to be unsynced, drop it - {errNoPeers, false}, // No peers to download from, soft race, no issue - {errTimeout, true}, // No hashes received in due time, drop the peer - {errEmptyHeaderSet, true}, // No headers were returned as a response, drop as it's a dead end - {errPeersUnavailable, true}, // Nobody had the advertised blocks, drop the advertiser - {errInvalidAncestor, true}, // Agreed upon ancestor is not acceptable, drop the chain rewriter - {errInvalidChain, true}, // Hash chain was detected as invalid, definitely drop - {errInvalidBody, false}, // A bad peer was detected, but not the sync origin - {errInvalidReceipt, false}, // A bad peer was detected, but not the sync origin - {errCancelContentProcessing, false}, // Synchronisation was canceled, origin may be innocent, don't drop - } - // Run the tests and check disconnection status - tester := newTester() - defer tester.terminate() - chain := testChainBase.shorten(1) - - for i, tt := range tests { - // Register a new peer and ensure its presence - id := fmt.Sprintf("test %d", i) - if err := tester.newPeer(id, protocol, chain); err != nil { - t.Fatalf("test %d: failed to register new peer: %v", i, err) - } - if _, ok := tester.peers[id]; !ok { - t.Fatalf("test %d: registered peer not found", i) - } - // Simulate a synchronisation and check the required result - tester.downloader.synchroniseMock = func(string, common.Hash) error { return tt.result } - - tester.downloader.Synchronise(id, tester.genesis.Hash(), big.NewInt(1000), FullSync) - if _, ok := tester.peers[id]; !ok != tt.drop { - t.Errorf("test %d: peer drop mismatch for %v: have %v, want %v", i, tt.result, !ok, tt.drop) - } - } -} - -// Tests that synchronisation progress (origin block number, current block number -// and highest block number) is tracked and updated correctly. -func TestSyncProgress66Full(t *testing.T) { testSyncProgress(t, eth.ETH66, FullSync) } -func TestSyncProgress66Fast(t *testing.T) { testSyncProgress(t, eth.ETH66, FastSync) } -func TestSyncProgress66Light(t *testing.T) { testSyncProgress(t, eth.ETH66, LightSync) } - -func testSyncProgress(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - chain := testChainBase.shorten(blockCacheMaxItems - 15) - - // Set a sync init hook to catch progress changes - starting := make(chan struct{}) - progress := make(chan struct{}) - - tester.downloader.syncInitHook = func(origin, latest uint64) { - starting <- struct{}{} - <-progress - } - checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) - - // Synchronise half the blocks and check initial progress - tester.newPeer("peer-half", protocol, chain.shorten(chain.len()/2)) - pending := new(sync.WaitGroup) - pending.Add(1) - - go func() { - defer pending.Done() - if err := tester.sync("peer-half", nil, mode); err != nil { - panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) - } - }() - <-starting - checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{ - HighestBlock: uint64(chain.len()/2 - 1), - }) - progress <- struct{}{} - pending.Wait() - - // Synchronise all the blocks and check continuation progress - tester.newPeer("peer-full", protocol, chain) - pending.Add(1) - go func() { - defer pending.Done() - if err := tester.sync("peer-full", nil, mode); err != nil { - panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) - } - }() - <-starting - checkProgress(t, tester.downloader, "completing", ethereum.SyncProgress{ - StartingBlock: uint64(chain.len()/2 - 1), - CurrentBlock: uint64(chain.len()/2 - 1), - HighestBlock: uint64(chain.len() - 1), - }) - - // Check final progress after successful sync - progress <- struct{}{} - pending.Wait() - checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{ - StartingBlock: uint64(chain.len()/2 - 1), - CurrentBlock: uint64(chain.len() - 1), - HighestBlock: uint64(chain.len() - 1), - }) -} - -func checkProgress(t *testing.T, d *Downloader, stage string, want ethereum.SyncProgress) { - // Mark this method as a helper to report errors at callsite, not in here - t.Helper() - - p := d.Progress() - //p.KnownStates, p.PulledStates = 0, 0 - //want.KnownStates, want.PulledStates = 0, 0 - if p != want { - t.Fatalf("%s progress mismatch:\nhave %+v\nwant %+v", stage, p, want) - } -} - -// Tests that synchronisation progress (origin block number and highest block -// number) is tracked and updated correctly in case of a fork (or manual head -// revertal). -func TestForkedSyncProgress66Full(t *testing.T) { testForkedSyncProgress(t, eth.ETH66, FullSync) } -func TestForkedSyncProgress66Fast(t *testing.T) { testForkedSyncProgress(t, eth.ETH66, FastSync) } -func TestForkedSyncProgress66Light(t *testing.T) { testForkedSyncProgress(t, eth.ETH66, LightSync) } - -func testForkedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - chainA := testChainForkLightA.shorten(testChainBase.len() + MaxHeaderFetch) - chainB := testChainForkLightB.shorten(testChainBase.len() + MaxHeaderFetch) - - // Set a sync init hook to catch progress changes - starting := make(chan struct{}) - progress := make(chan struct{}) - - tester.downloader.syncInitHook = func(origin, latest uint64) { - starting <- struct{}{} - <-progress - } - checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) - - // Synchronise with one of the forks and check progress - tester.newPeer("fork A", protocol, chainA) - pending := new(sync.WaitGroup) - pending.Add(1) - go func() { - defer pending.Done() - if err := tester.sync("fork A", nil, mode); err != nil { - panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) - } - }() - <-starting - - checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{ - HighestBlock: uint64(chainA.len() - 1), - }) - progress <- struct{}{} - pending.Wait() - - // Simulate a successful sync above the fork - tester.downloader.syncStatsChainOrigin = tester.downloader.syncStatsChainHeight - - // Synchronise with the second fork and check progress resets - tester.newPeer("fork B", protocol, chainB) - pending.Add(1) - go func() { - defer pending.Done() - if err := tester.sync("fork B", nil, mode); err != nil { - panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) - } - }() - <-starting - checkProgress(t, tester.downloader, "forking", ethereum.SyncProgress{ - StartingBlock: uint64(testChainBase.len()) - 1, - CurrentBlock: uint64(chainA.len() - 1), - HighestBlock: uint64(chainB.len() - 1), - }) - - // Check final progress after successful sync - progress <- struct{}{} - pending.Wait() - checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{ - StartingBlock: uint64(testChainBase.len()) - 1, - CurrentBlock: uint64(chainB.len() - 1), - HighestBlock: uint64(chainB.len() - 1), - }) -} - -// Tests that if synchronisation is aborted due to some failure, then the progress -// origin is not updated in the next sync cycle, as it should be considered the -// continuation of the previous sync and not a new instance. -func TestFailedSyncProgress66Full(t *testing.T) { testFailedSyncProgress(t, eth.ETH66, FullSync) } -func TestFailedSyncProgress66Fast(t *testing.T) { testFailedSyncProgress(t, eth.ETH66, FastSync) } -func TestFailedSyncProgress66Light(t *testing.T) { testFailedSyncProgress(t, eth.ETH66, LightSync) } - -func testFailedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - chain := testChainBase.shorten(blockCacheMaxItems - 15) - - // Set a sync init hook to catch progress changes - starting := make(chan struct{}) - progress := make(chan struct{}) - - tester.downloader.syncInitHook = func(origin, latest uint64) { - starting <- struct{}{} - <-progress - } - checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) - - // Attempt a full sync with a faulty peer - brokenChain := chain.shorten(chain.len()) - missing := brokenChain.len() / 2 - delete(brokenChain.headerm, brokenChain.chain[missing]) - delete(brokenChain.blockm, brokenChain.chain[missing]) - delete(brokenChain.receiptm, brokenChain.chain[missing]) - tester.newPeer("faulty", protocol, brokenChain) - - pending := new(sync.WaitGroup) - pending.Add(1) - go func() { - defer pending.Done() - if err := tester.sync("faulty", nil, mode); err == nil { - panic("succeeded faulty synchronisation") - } - }() - <-starting - checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{ - HighestBlock: uint64(brokenChain.len() - 1), - }) - progress <- struct{}{} - pending.Wait() - afterFailedSync := tester.downloader.Progress() - - // Synchronise with a good peer and check that the progress origin remind the same - // after a failure - tester.newPeer("valid", protocol, chain) - pending.Add(1) - go func() { - defer pending.Done() - if err := tester.sync("valid", nil, mode); err != nil { - panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) - } - }() - <-starting - checkProgress(t, tester.downloader, "completing", afterFailedSync) - - // Check final progress after successful sync - progress <- struct{}{} - pending.Wait() - checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{ - CurrentBlock: uint64(chain.len() - 1), - HighestBlock: uint64(chain.len() - 1), - }) -} - -// Tests that if an attacker fakes a chain height, after the attack is detected, -// the progress height is successfully reduced at the next sync invocation. -func TestFakedSyncProgress66Full(t *testing.T) { testFakedSyncProgress(t, eth.ETH66, FullSync) } -func TestFakedSyncProgress66Fast(t *testing.T) { testFakedSyncProgress(t, eth.ETH66, FastSync) } -func TestFakedSyncProgress66Light(t *testing.T) { testFakedSyncProgress(t, eth.ETH66, LightSync) } - -func testFakedSyncProgress(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - tester := newTester() - defer tester.terminate() - chain := testChainBase.shorten(blockCacheMaxItems - 15) - - // Set a sync init hook to catch progress changes - starting := make(chan struct{}) - progress := make(chan struct{}) - tester.downloader.syncInitHook = func(origin, latest uint64) { - starting <- struct{}{} - <-progress - } - checkProgress(t, tester.downloader, "pristine", ethereum.SyncProgress{}) - - // Create and sync with an attacker that promises a higher chain than available. - brokenChain := chain.shorten(chain.len()) - numMissing := 5 - for i := brokenChain.len() - 2; i > brokenChain.len()-numMissing; i-- { - delete(brokenChain.headerm, brokenChain.chain[i]) - } - tester.newPeer("attack", protocol, brokenChain) - - pending := new(sync.WaitGroup) - pending.Add(1) - go func() { - defer pending.Done() - if err := tester.sync("attack", nil, mode); err == nil { - panic("succeeded attacker synchronisation") - } - }() - <-starting - checkProgress(t, tester.downloader, "initial", ethereum.SyncProgress{ - HighestBlock: uint64(brokenChain.len() - 1), - }) - progress <- struct{}{} - pending.Wait() - afterFailedSync := tester.downloader.Progress() - - // Synchronise with a good peer and check that the progress height has been reduced to - // the true value. - validChain := chain.shorten(chain.len() - numMissing) - tester.newPeer("valid", protocol, validChain) - pending.Add(1) - - go func() { - defer pending.Done() - if err := tester.sync("valid", nil, mode); err != nil { - panic(fmt.Sprintf("failed to synchronise blocks: %v", err)) - } - }() - <-starting - checkProgress(t, tester.downloader, "completing", ethereum.SyncProgress{ - CurrentBlock: afterFailedSync.CurrentBlock, - HighestBlock: uint64(validChain.len() - 1), - }) - - // Check final progress after successful sync. - progress <- struct{}{} - pending.Wait() - checkProgress(t, tester.downloader, "final", ethereum.SyncProgress{ - CurrentBlock: uint64(validChain.len() - 1), - HighestBlock: uint64(validChain.len() - 1), - }) -} - -// This test reproduces an issue where unexpected deliveries would -// block indefinitely if they arrived at the right time. -func TestDeliverHeadersHang66Full(t *testing.T) { testDeliverHeadersHang(t, eth.ETH66, FullSync) } -func TestDeliverHeadersHang66Fast(t *testing.T) { testDeliverHeadersHang(t, eth.ETH66, FastSync) } -func TestDeliverHeadersHang66Light(t *testing.T) { testDeliverHeadersHang(t, eth.ETH66, LightSync) } - -func testDeliverHeadersHang(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - master := newTester() - defer master.terminate() - chain := testChainBase.shorten(15) - - for i := 0; i < 200; i++ { - tester := newTester() - tester.peerDb = master.peerDb - tester.newPeer("peer", protocol, chain) - - // Whenever the downloader requests headers, flood it with - // a lot of unrequested header deliveries. - tester.downloader.peers.peers["peer"].peer = &floodingTestPeer{ - peer: tester.downloader.peers.peers["peer"].peer, - tester: tester, - } - if err := tester.sync("peer", nil, mode); err != nil { - t.Errorf("test %d: sync failed: %v", i, err) - } - tester.terminate() - } -} - -type floodingTestPeer struct { - peer Peer - tester *downloadTester -} - -func (ftp *floodingTestPeer) Head() (common.Hash, *big.Int) { return ftp.peer.Head() } -func (ftp *floodingTestPeer) RequestHeadersByHash(hash common.Hash, count int, skip int, reverse bool) error { - return ftp.peer.RequestHeadersByHash(hash, count, skip, reverse) -} -func (ftp *floodingTestPeer) RequestBodies(hashes []common.Hash) error { - return ftp.peer.RequestBodies(hashes) -} -func (ftp *floodingTestPeer) RequestReceipts(hashes []common.Hash) error { - return ftp.peer.RequestReceipts(hashes) -} -func (ftp *floodingTestPeer) RequestNodeData(hashes []common.Hash) error { - return ftp.peer.RequestNodeData(hashes) -} - -func (ftp *floodingTestPeer) RequestHeadersByNumber(from uint64, count, skip int, reverse bool) error { - deliveriesDone := make(chan struct{}, 500) - for i := 0; i < cap(deliveriesDone)-1; i++ { - peer := fmt.Sprintf("fake-peer%d", i) - go func() { - ftp.tester.downloader.DeliverHeaders(peer, []*types.Header{{}, {}, {}, {}}) - deliveriesDone <- struct{}{} - }() - } - - // None of the extra deliveries should block. - timeout := time.After(60 * time.Second) - launched := false - for i := 0; i < cap(deliveriesDone); i++ { - select { - case <-deliveriesDone: - if !launched { - // Start delivering the requested headers - // after one of the flooding responses has arrived. - go func() { - ftp.peer.RequestHeadersByNumber(from, count, skip, reverse) - deliveriesDone <- struct{}{} - }() - launched = true - } - case <-timeout: - panic("blocked") - } - } - return nil -} - -func TestRemoteHeaderRequestSpan(t *testing.T) { - testCases := []struct { - remoteHeight uint64 - localHeight uint64 - expected []int - }{ - // Remote is way higher. We should ask for the remote head and go backwards - {1500, 1000, - []int{1323, 1339, 1355, 1371, 1387, 1403, 1419, 1435, 1451, 1467, 1483, 1499}, - }, - {15000, 13006, - []int{14823, 14839, 14855, 14871, 14887, 14903, 14919, 14935, 14951, 14967, 14983, 14999}, - }, - // Remote is pretty close to us. We don't have to fetch as many - {1200, 1150, - []int{1149, 1154, 1159, 1164, 1169, 1174, 1179, 1184, 1189, 1194, 1199}, - }, - // Remote is equal to us (so on a fork with higher td) - // We should get the closest couple of ancestors - {1500, 1500, - []int{1497, 1499}, - }, - // We're higher than the remote! Odd - {1000, 1500, - []int{997, 999}, - }, - // Check some weird edgecases that it behaves somewhat rationally - {0, 1500, - []int{0, 2}, - }, - {6000000, 0, - []int{5999823, 5999839, 5999855, 5999871, 5999887, 5999903, 5999919, 5999935, 5999951, 5999967, 5999983, 5999999}, - }, - {0, 0, - []int{0, 2}, - }, - } - reqs := func(from, count, span int) []int { - var r []int - num := from - for len(r) < count { - r = append(r, num) - num += span + 1 - } - return r - } - for i, tt := range testCases { - from, count, span, max := calculateRequestSpan(tt.remoteHeight, tt.localHeight) - data := reqs(int(from), count, span) - - if max != uint64(data[len(data)-1]) { - t.Errorf("test %d: wrong last value %d != %d", i, data[len(data)-1], max) - } - failed := false - if len(data) != len(tt.expected) { - failed = true - t.Errorf("test %d: length wrong, expected %d got %d", i, len(tt.expected), len(data)) - } else { - for j, n := range data { - if n != tt.expected[j] { - failed = true - break - } - } - } - if failed { - res := strings.ReplaceAll(fmt.Sprint(data), " ", ",") - exp := strings.ReplaceAll(fmt.Sprint(tt.expected), " ", ",") - t.Logf("got: %v\n", res) - t.Logf("exp: %v\n", exp) - t.Errorf("test %d: wrong values", i) - } - } -} - -// Tests that peers below a pre-configured checkpoint block are prevented from -// being fast-synced from, avoiding potential cheap eclipse attacks. -func TestCheckpointEnforcement66Full(t *testing.T) { testCheckpointEnforcement(t, eth.ETH66, FullSync) } -func TestCheckpointEnforcement66Fast(t *testing.T) { testCheckpointEnforcement(t, eth.ETH66, FastSync) } -func TestCheckpointEnforcement66Light(t *testing.T) { - testCheckpointEnforcement(t, eth.ETH66, LightSync) -} - -func testCheckpointEnforcement(t *testing.T, protocol uint, mode SyncMode) { - t.Parallel() - - // Create a new tester with a particular hard coded checkpoint block - tester := newTester() - defer tester.terminate() - - tester.downloader.checkpoint = uint64(fsMinFullBlocks) + 256 - chain := testChainBase.shorten(int(tester.downloader.checkpoint) - 1) - - // Attempt to sync with the peer and validate the result - tester.newPeer("peer", protocol, chain) - - var expect error - if mode == FastSync || mode == LightSync { - expect = errUnsyncedPeer - } - if err := tester.sync("peer", nil, mode); !errors.Is(err, expect) { - t.Fatalf("block sync error mismatch: have %v, want %v", err, expect) - } - if mode == FastSync || mode == LightSync { - assertOwnChain(t, tester, 1) - } else { - assertOwnChain(t, tester, chain.len()) - } -} diff --git a/les/downloader/events.go b/les/downloader/events.go deleted file mode 100644 index 25255a3a72e5..000000000000 --- a/les/downloader/events.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package downloader - -import "github.com/ethereum/go-ethereum/core/types" - -type DoneEvent struct { - Latest *types.Header -} -type StartEvent struct{} -type FailedEvent struct{ Err error } diff --git a/les/downloader/metrics.go b/les/downloader/metrics.go deleted file mode 100644 index c38732043aa2..000000000000 --- a/les/downloader/metrics.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Contains the metrics collected by the downloader. - -package downloader - -import ( - "github.com/ethereum/go-ethereum/metrics" -) - -var ( - headerInMeter = metrics.NewRegisteredMeter("eth/downloader/headers/in", nil) - headerReqTimer = metrics.NewRegisteredTimer("eth/downloader/headers/req", nil) - headerDropMeter = metrics.NewRegisteredMeter("eth/downloader/headers/drop", nil) - headerTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/headers/timeout", nil) - - bodyInMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/in", nil) - bodyReqTimer = metrics.NewRegisteredTimer("eth/downloader/bodies/req", nil) - bodyDropMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/drop", nil) - bodyTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/bodies/timeout", nil) - - receiptInMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/in", nil) - receiptReqTimer = metrics.NewRegisteredTimer("eth/downloader/receipts/req", nil) - receiptDropMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/drop", nil) - receiptTimeoutMeter = metrics.NewRegisteredMeter("eth/downloader/receipts/timeout", nil) - - stateInMeter = metrics.NewRegisteredMeter("eth/downloader/states/in", nil) - stateDropMeter = metrics.NewRegisteredMeter("eth/downloader/states/drop", nil) - - throttleCounter = metrics.NewRegisteredCounter("eth/downloader/throttle", nil) -) diff --git a/les/downloader/modes.go b/les/downloader/modes.go deleted file mode 100644 index 3ea14d22d7e0..000000000000 --- a/les/downloader/modes.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package downloader - -import "fmt" - -// SyncMode represents the synchronisation mode of the downloader. -// It is a uint32 as it is used with atomic operations. -type SyncMode uint32 - -const ( - FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks - FastSync // Quickly download the headers, full sync only at the chain - SnapSync // Download the chain and the state via compact snapshots - LightSync // Download only the headers and terminate afterwards -) - -func (mode SyncMode) IsValid() bool { - return mode >= FullSync && mode <= LightSync -} - -// String implements the stringer interface. -func (mode SyncMode) String() string { - switch mode { - case FullSync: - return "full" - case FastSync: - return "fast" - case SnapSync: - return "snap" - case LightSync: - return "light" - default: - return "unknown" - } -} - -func (mode SyncMode) MarshalText() ([]byte, error) { - switch mode { - case FullSync: - return []byte("full"), nil - case FastSync: - return []byte("fast"), nil - case SnapSync: - return []byte("snap"), nil - case LightSync: - return []byte("light"), nil - default: - return nil, fmt.Errorf("unknown sync mode %d", mode) - } -} - -func (mode *SyncMode) UnmarshalText(text []byte) error { - switch string(text) { - case "full": - *mode = FullSync - case "fast": - *mode = FastSync - case "snap": - *mode = SnapSync - case "light": - *mode = LightSync - default: - return fmt.Errorf(`unknown sync mode %q, want "full", "fast" or "light"`, text) - } - return nil -} diff --git a/les/downloader/peer.go b/les/downloader/peer.go deleted file mode 100644 index c2161e2dae42..000000000000 --- a/les/downloader/peer.go +++ /dev/null @@ -1,502 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Contains the active peer-set of the downloader, maintaining both failures -// as well as reputation metrics to prioritize the block retrievals. - -package downloader - -import ( - "errors" - "math/big" - "sort" - "sync" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/eth/protocols/eth" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/msgrate" -) - -const ( - maxLackingHashes = 4096 // Maximum number of entries allowed on the list or lacking items -) - -var ( - errAlreadyFetching = errors.New("already fetching blocks from peer") - errAlreadyRegistered = errors.New("peer is already registered") - errNotRegistered = errors.New("peer is not registered") -) - -// peerConnection represents an active peer from which hashes and blocks are retrieved. -type peerConnection struct { - id string // Unique identifier of the peer - - headerIdle int32 // Current header activity state of the peer (idle = 0, active = 1) - blockIdle int32 // Current block activity state of the peer (idle = 0, active = 1) - receiptIdle int32 // Current receipt activity state of the peer (idle = 0, active = 1) - stateIdle int32 // Current node data activity state of the peer (idle = 0, active = 1) - - headerStarted time.Time // Time instance when the last header fetch was started - blockStarted time.Time // Time instance when the last block (body) fetch was started - receiptStarted time.Time // Time instance when the last receipt fetch was started - stateStarted time.Time // Time instance when the last node data fetch was started - - rates *msgrate.Tracker // Tracker to hone in on the number of items retrievable per second - lacking map[common.Hash]struct{} // Set of hashes not to request (didn't have previously) - - peer Peer - - version uint // Eth protocol version number to switch strategies - log log.Logger // Contextual logger to add extra infos to peer logs - lock sync.RWMutex -} - -// LightPeer encapsulates the methods required to synchronise with a remote light peer. -type LightPeer interface { - Head() (common.Hash, *big.Int) - RequestHeadersByHash(common.Hash, int, int, bool) error - RequestHeadersByNumber(uint64, int, int, bool) error -} - -// Peer encapsulates the methods required to synchronise with a remote full peer. -type Peer interface { - LightPeer - RequestBodies([]common.Hash) error - RequestReceipts([]common.Hash) error - RequestNodeData([]common.Hash) error -} - -// lightPeerWrapper wraps a LightPeer struct, stubbing out the Peer-only methods. -type lightPeerWrapper struct { - peer LightPeer -} - -func (w *lightPeerWrapper) Head() (common.Hash, *big.Int) { return w.peer.Head() } -func (w *lightPeerWrapper) RequestHeadersByHash(h common.Hash, amount int, skip int, reverse bool) error { - return w.peer.RequestHeadersByHash(h, amount, skip, reverse) -} -func (w *lightPeerWrapper) RequestHeadersByNumber(i uint64, amount int, skip int, reverse bool) error { - return w.peer.RequestHeadersByNumber(i, amount, skip, reverse) -} -func (w *lightPeerWrapper) RequestBodies([]common.Hash) error { - panic("RequestBodies not supported in light client mode sync") -} -func (w *lightPeerWrapper) RequestReceipts([]common.Hash) error { - panic("RequestReceipts not supported in light client mode sync") -} -func (w *lightPeerWrapper) RequestNodeData([]common.Hash) error { - panic("RequestNodeData not supported in light client mode sync") -} - -// newPeerConnection creates a new downloader peer. -func newPeerConnection(id string, version uint, peer Peer, logger log.Logger) *peerConnection { - return &peerConnection{ - id: id, - lacking: make(map[common.Hash]struct{}), - peer: peer, - version: version, - log: logger, - } -} - -// Reset clears the internal state of a peer entity. -func (p *peerConnection) Reset() { - p.lock.Lock() - defer p.lock.Unlock() - - atomic.StoreInt32(&p.headerIdle, 0) - atomic.StoreInt32(&p.blockIdle, 0) - atomic.StoreInt32(&p.receiptIdle, 0) - atomic.StoreInt32(&p.stateIdle, 0) - - p.lacking = make(map[common.Hash]struct{}) -} - -// FetchHeaders sends a header retrieval request to the remote peer. -func (p *peerConnection) FetchHeaders(from uint64, count int) error { - // Short circuit if the peer is already fetching - if !atomic.CompareAndSwapInt32(&p.headerIdle, 0, 1) { - return errAlreadyFetching - } - p.headerStarted = time.Now() - - // Issue the header retrieval request (absolute upwards without gaps) - go p.peer.RequestHeadersByNumber(from, count, 0, false) - - return nil -} - -// FetchBodies sends a block body retrieval request to the remote peer. -func (p *peerConnection) FetchBodies(request *fetchRequest) error { - // Short circuit if the peer is already fetching - if !atomic.CompareAndSwapInt32(&p.blockIdle, 0, 1) { - return errAlreadyFetching - } - p.blockStarted = time.Now() - - go func() { - // Convert the header set to a retrievable slice - hashes := make([]common.Hash, 0, len(request.Headers)) - for _, header := range request.Headers { - hashes = append(hashes, header.Hash()) - } - p.peer.RequestBodies(hashes) - }() - - return nil -} - -// FetchReceipts sends a receipt retrieval request to the remote peer. -func (p *peerConnection) FetchReceipts(request *fetchRequest) error { - // Short circuit if the peer is already fetching - if !atomic.CompareAndSwapInt32(&p.receiptIdle, 0, 1) { - return errAlreadyFetching - } - p.receiptStarted = time.Now() - - go func() { - // Convert the header set to a retrievable slice - hashes := make([]common.Hash, 0, len(request.Headers)) - for _, header := range request.Headers { - hashes = append(hashes, header.Hash()) - } - p.peer.RequestReceipts(hashes) - }() - - return nil -} - -// FetchNodeData sends a node state data retrieval request to the remote peer. -func (p *peerConnection) FetchNodeData(hashes []common.Hash) error { - // Short circuit if the peer is already fetching - if !atomic.CompareAndSwapInt32(&p.stateIdle, 0, 1) { - return errAlreadyFetching - } - p.stateStarted = time.Now() - - go p.peer.RequestNodeData(hashes) - - return nil -} - -// SetHeadersIdle sets the peer to idle, allowing it to execute new header retrieval -// requests. Its estimated header retrieval throughput is updated with that measured -// just now. -func (p *peerConnection) SetHeadersIdle(delivered int, deliveryTime time.Time) { - p.rates.Update(eth.BlockHeadersMsg, deliveryTime.Sub(p.headerStarted), delivered) - atomic.StoreInt32(&p.headerIdle, 0) -} - -// SetBodiesIdle sets the peer to idle, allowing it to execute block body retrieval -// requests. Its estimated body retrieval throughput is updated with that measured -// just now. -func (p *peerConnection) SetBodiesIdle(delivered int, deliveryTime time.Time) { - p.rates.Update(eth.BlockBodiesMsg, deliveryTime.Sub(p.blockStarted), delivered) - atomic.StoreInt32(&p.blockIdle, 0) -} - -// SetReceiptsIdle sets the peer to idle, allowing it to execute new receipt -// retrieval requests. Its estimated receipt retrieval throughput is updated -// with that measured just now. -func (p *peerConnection) SetReceiptsIdle(delivered int, deliveryTime time.Time) { - p.rates.Update(eth.ReceiptsMsg, deliveryTime.Sub(p.receiptStarted), delivered) - atomic.StoreInt32(&p.receiptIdle, 0) -} - -// SetNodeDataIdle sets the peer to idle, allowing it to execute new state trie -// data retrieval requests. Its estimated state retrieval throughput is updated -// with that measured just now. -func (p *peerConnection) SetNodeDataIdle(delivered int, deliveryTime time.Time) { - p.rates.Update(eth.NodeDataMsg, deliveryTime.Sub(p.stateStarted), delivered) - atomic.StoreInt32(&p.stateIdle, 0) -} - -// HeaderCapacity retrieves the peers header download allowance based on its -// previously discovered throughput. -func (p *peerConnection) HeaderCapacity(targetRTT time.Duration) int { - cap := p.rates.Capacity(eth.BlockHeadersMsg, targetRTT) - if cap > MaxHeaderFetch { - cap = MaxHeaderFetch - } - return cap -} - -// BlockCapacity retrieves the peers block download allowance based on its -// previously discovered throughput. -func (p *peerConnection) BlockCapacity(targetRTT time.Duration) int { - cap := p.rates.Capacity(eth.BlockBodiesMsg, targetRTT) - if cap > MaxBlockFetch { - cap = MaxBlockFetch - } - return cap -} - -// ReceiptCapacity retrieves the peers receipt download allowance based on its -// previously discovered throughput. -func (p *peerConnection) ReceiptCapacity(targetRTT time.Duration) int { - cap := p.rates.Capacity(eth.ReceiptsMsg, targetRTT) - if cap > MaxReceiptFetch { - cap = MaxReceiptFetch - } - return cap -} - -// NodeDataCapacity retrieves the peers state download allowance based on its -// previously discovered throughput. -func (p *peerConnection) NodeDataCapacity(targetRTT time.Duration) int { - cap := p.rates.Capacity(eth.NodeDataMsg, targetRTT) - if cap > MaxStateFetch { - cap = MaxStateFetch - } - return cap -} - -// MarkLacking appends a new entity to the set of items (blocks, receipts, states) -// that a peer is known not to have (i.e. have been requested before). If the -// set reaches its maximum allowed capacity, items are randomly dropped off. -func (p *peerConnection) MarkLacking(hash common.Hash) { - p.lock.Lock() - defer p.lock.Unlock() - - for len(p.lacking) >= maxLackingHashes { - for drop := range p.lacking { - delete(p.lacking, drop) - break - } - } - p.lacking[hash] = struct{}{} -} - -// Lacks retrieves whether the hash of a blockchain item is on the peers lacking -// list (i.e. whether we know that the peer does not have it). -func (p *peerConnection) Lacks(hash common.Hash) bool { - p.lock.RLock() - defer p.lock.RUnlock() - - _, ok := p.lacking[hash] - return ok -} - -// peerSet represents the collection of active peer participating in the chain -// download procedure. -type peerSet struct { - peers map[string]*peerConnection - rates *msgrate.Trackers // Set of rate trackers to give the sync a common beat - - newPeerFeed event.Feed - peerDropFeed event.Feed - - lock sync.RWMutex -} - -// newPeerSet creates a new peer set top track the active download sources. -func newPeerSet() *peerSet { - return &peerSet{ - peers: make(map[string]*peerConnection), - rates: msgrate.NewTrackers(log.New("proto", "eth")), - } -} - -// SubscribeNewPeers subscribes to peer arrival events. -func (ps *peerSet) SubscribeNewPeers(ch chan<- *peerConnection) event.Subscription { - return ps.newPeerFeed.Subscribe(ch) -} - -// SubscribePeerDrops subscribes to peer departure events. -func (ps *peerSet) SubscribePeerDrops(ch chan<- *peerConnection) event.Subscription { - return ps.peerDropFeed.Subscribe(ch) -} - -// Reset iterates over the current peer set, and resets each of the known peers -// to prepare for a next batch of block retrieval. -func (ps *peerSet) Reset() { - ps.lock.RLock() - defer ps.lock.RUnlock() - - for _, peer := range ps.peers { - peer.Reset() - } -} - -// Register injects a new peer into the working set, or returns an error if the -// peer is already known. -// -// The method also sets the starting throughput values of the new peer to the -// average of all existing peers, to give it a realistic chance of being used -// for data retrievals. -func (ps *peerSet) Register(p *peerConnection) error { - // Register the new peer with some meaningful defaults - ps.lock.Lock() - if _, ok := ps.peers[p.id]; ok { - ps.lock.Unlock() - return errAlreadyRegistered - } - p.rates = msgrate.NewTracker(ps.rates.MeanCapacities(), ps.rates.MedianRoundTrip()) - if err := ps.rates.Track(p.id, p.rates); err != nil { - ps.lock.Unlock() - return err - } - ps.peers[p.id] = p - ps.lock.Unlock() - - ps.newPeerFeed.Send(p) - return nil -} - -// Unregister removes a remote peer from the active set, disabling any further -// actions to/from that particular entity. -func (ps *peerSet) Unregister(id string) error { - ps.lock.Lock() - p, ok := ps.peers[id] - if !ok { - ps.lock.Unlock() - return errNotRegistered - } - delete(ps.peers, id) - ps.rates.Untrack(id) - ps.lock.Unlock() - - ps.peerDropFeed.Send(p) - return nil -} - -// Peer retrieves the registered peer with the given id. -func (ps *peerSet) Peer(id string) *peerConnection { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return ps.peers[id] -} - -// Len returns if the current number of peers in the set. -func (ps *peerSet) Len() int { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return len(ps.peers) -} - -// AllPeers retrieves a flat list of all the peers within the set. -func (ps *peerSet) AllPeers() []*peerConnection { - ps.lock.RLock() - defer ps.lock.RUnlock() - - list := make([]*peerConnection, 0, len(ps.peers)) - for _, p := range ps.peers { - list = append(list, p) - } - return list -} - -// HeaderIdlePeers retrieves a flat list of all the currently header-idle peers -// within the active peer set, ordered by their reputation. -func (ps *peerSet) HeaderIdlePeers() ([]*peerConnection, int) { - idle := func(p *peerConnection) bool { - return atomic.LoadInt32(&p.headerIdle) == 0 - } - throughput := func(p *peerConnection) int { - return p.rates.Capacity(eth.BlockHeadersMsg, time.Second) - } - return ps.idlePeers(eth.ETH66, eth.ETH67, idle, throughput) -} - -// BodyIdlePeers retrieves a flat list of all the currently body-idle peers within -// the active peer set, ordered by their reputation. -func (ps *peerSet) BodyIdlePeers() ([]*peerConnection, int) { - idle := func(p *peerConnection) bool { - return atomic.LoadInt32(&p.blockIdle) == 0 - } - throughput := func(p *peerConnection) int { - return p.rates.Capacity(eth.BlockBodiesMsg, time.Second) - } - return ps.idlePeers(eth.ETH66, eth.ETH67, idle, throughput) -} - -// ReceiptIdlePeers retrieves a flat list of all the currently receipt-idle peers -// within the active peer set, ordered by their reputation. -func (ps *peerSet) ReceiptIdlePeers() ([]*peerConnection, int) { - idle := func(p *peerConnection) bool { - return atomic.LoadInt32(&p.receiptIdle) == 0 - } - throughput := func(p *peerConnection) int { - return p.rates.Capacity(eth.ReceiptsMsg, time.Second) - } - return ps.idlePeers(eth.ETH66, eth.ETH67, idle, throughput) -} - -// NodeDataIdlePeers retrieves a flat list of all the currently node-data-idle -// peers within the active peer set, ordered by their reputation. -func (ps *peerSet) NodeDataIdlePeers() ([]*peerConnection, int) { - idle := func(p *peerConnection) bool { - return atomic.LoadInt32(&p.stateIdle) == 0 - } - throughput := func(p *peerConnection) int { - return p.rates.Capacity(eth.NodeDataMsg, time.Second) - } - return ps.idlePeers(eth.ETH66, eth.ETH67, idle, throughput) -} - -// idlePeers retrieves a flat list of all currently idle peers satisfying the -// protocol version constraints, using the provided function to check idleness. -// The resulting set of peers are sorted by their capacity. -func (ps *peerSet) idlePeers(minProtocol, maxProtocol uint, idleCheck func(*peerConnection) bool, capacity func(*peerConnection) int) ([]*peerConnection, int) { - ps.lock.RLock() - defer ps.lock.RUnlock() - - var ( - total = 0 - idle = make([]*peerConnection, 0, len(ps.peers)) - tps = make([]int, 0, len(ps.peers)) - ) - for _, p := range ps.peers { - if p.version >= minProtocol && p.version <= maxProtocol { - if idleCheck(p) { - idle = append(idle, p) - tps = append(tps, capacity(p)) - } - total++ - } - } - - // And sort them - sortPeers := &peerCapacitySort{idle, tps} - sort.Sort(sortPeers) - return sortPeers.p, total -} - -// peerCapacitySort implements sort.Interface. -// It sorts peer connections by capacity (descending). -type peerCapacitySort struct { - p []*peerConnection - tp []int -} - -func (ps *peerCapacitySort) Len() int { - return len(ps.p) -} - -func (ps *peerCapacitySort) Less(i, j int) bool { - return ps.tp[i] > ps.tp[j] -} - -func (ps *peerCapacitySort) Swap(i, j int) { - ps.p[i], ps.p[j] = ps.p[j], ps.p[i] - ps.tp[i], ps.tp[j] = ps.tp[j], ps.tp[i] -} diff --git a/les/downloader/queue.go b/les/downloader/queue.go deleted file mode 100644 index 6896b09b388a..000000000000 --- a/les/downloader/queue.go +++ /dev/null @@ -1,913 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Contains the block download scheduler to collect download tasks and schedule -// them in an ordered, and throttled way. - -package downloader - -import ( - "errors" - "fmt" - "sync" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/prque" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/trie" -) - -const ( - bodyType = uint(0) - receiptType = uint(1) -) - -var ( - blockCacheMaxItems = 8192 // Maximum number of blocks to cache before throttling the download - blockCacheInitialItems = 2048 // Initial number of blocks to start fetching, before we know the sizes of the blocks - blockCacheMemory = 256 * 1024 * 1024 // Maximum amount of memory to use for block caching - blockCacheSizeWeight = 0.1 // Multiplier to approximate the average block size based on past ones -) - -var ( - errNoFetchesPending = errors.New("no fetches pending") - errStaleDelivery = errors.New("stale delivery") -) - -// fetchRequest is a currently running data retrieval operation. -type fetchRequest struct { - Peer *peerConnection // Peer to which the request was sent - From uint64 // [eth/62] Requested chain element index (used for skeleton fills only) - Headers []*types.Header // [eth/62] Requested headers, sorted by request order - Time time.Time // Time when the request was made -} - -// fetchResult is a struct collecting partial results from data fetchers until -// all outstanding pieces complete and the result as a whole can be processed. -type fetchResult struct { - pending int32 // Flag telling what deliveries are outstanding - - Header *types.Header - Uncles []*types.Header - Transactions types.Transactions - Receipts types.Receipts -} - -func newFetchResult(header *types.Header, fastSync bool) *fetchResult { - item := &fetchResult{ - Header: header, - } - if !header.EmptyBody() { - item.pending |= (1 << bodyType) - } - if fastSync && !header.EmptyReceipts() { - item.pending |= (1 << receiptType) - } - return item -} - -// SetBodyDone flags the body as finished. -func (f *fetchResult) SetBodyDone() { - if v := atomic.LoadInt32(&f.pending); (v & (1 << bodyType)) != 0 { - atomic.AddInt32(&f.pending, -1) - } -} - -// AllDone checks if item is done. -func (f *fetchResult) AllDone() bool { - return atomic.LoadInt32(&f.pending) == 0 -} - -// SetReceiptsDone flags the receipts as finished. -func (f *fetchResult) SetReceiptsDone() { - if v := atomic.LoadInt32(&f.pending); (v & (1 << receiptType)) != 0 { - atomic.AddInt32(&f.pending, -2) - } -} - -// Done checks if the given type is done already -func (f *fetchResult) Done(kind uint) bool { - v := atomic.LoadInt32(&f.pending) - return v&(1< 0 -} - -// InFlightBlocks retrieves whether there are block fetch requests currently in -// flight. -func (q *queue) InFlightBlocks() bool { - q.lock.Lock() - defer q.lock.Unlock() - - return len(q.blockPendPool) > 0 -} - -// InFlightReceipts retrieves whether there are receipt fetch requests currently -// in flight. -func (q *queue) InFlightReceipts() bool { - q.lock.Lock() - defer q.lock.Unlock() - - return len(q.receiptPendPool) > 0 -} - -// Idle returns if the queue is fully idle or has some data still inside. -func (q *queue) Idle() bool { - q.lock.Lock() - defer q.lock.Unlock() - - queued := q.blockTaskQueue.Size() + q.receiptTaskQueue.Size() - pending := len(q.blockPendPool) + len(q.receiptPendPool) - - return (queued + pending) == 0 -} - -// ScheduleSkeleton adds a batch of header retrieval tasks to the queue to fill -// up an already retrieved header skeleton. -func (q *queue) ScheduleSkeleton(from uint64, skeleton []*types.Header) { - q.lock.Lock() - defer q.lock.Unlock() - - // No skeleton retrieval can be in progress, fail hard if so (huge implementation bug) - if q.headerResults != nil { - panic("skeleton assembly already in progress") - } - // Schedule all the header retrieval tasks for the skeleton assembly - q.headerTaskPool = make(map[uint64]*types.Header) - q.headerTaskQueue = prque.New[int64, uint64](nil) - q.headerPeerMiss = make(map[string]map[uint64]struct{}) // Reset availability to correct invalid chains - q.headerResults = make([]*types.Header, len(skeleton)*MaxHeaderFetch) - q.headerProced = 0 - q.headerOffset = from - q.headerContCh = make(chan bool, 1) - - for i, header := range skeleton { - index := from + uint64(i*MaxHeaderFetch) - - q.headerTaskPool[index] = header - q.headerTaskQueue.Push(index, -int64(index)) - } -} - -// RetrieveHeaders retrieves the header chain assemble based on the scheduled -// skeleton. -func (q *queue) RetrieveHeaders() ([]*types.Header, int) { - q.lock.Lock() - defer q.lock.Unlock() - - headers, proced := q.headerResults, q.headerProced - q.headerResults, q.headerProced = nil, 0 - - return headers, proced -} - -// Schedule adds a set of headers for the download queue for scheduling, returning -// the new headers encountered. -func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header { - q.lock.Lock() - defer q.lock.Unlock() - - // Insert all the headers prioritised by the contained block number - inserts := make([]*types.Header, 0, len(headers)) - for _, header := range headers { - // Make sure chain order is honoured and preserved throughout - hash := header.Hash() - if header.Number == nil || header.Number.Uint64() != from { - log.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", from) - break - } - if q.headerHead != (common.Hash{}) && q.headerHead != header.ParentHash { - log.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash) - break - } - // Make sure no duplicate requests are executed - // We cannot skip this, even if the block is empty, since this is - // what triggers the fetchResult creation. - if _, ok := q.blockTaskPool[hash]; ok { - log.Warn("Header already scheduled for block fetch", "number", header.Number, "hash", hash) - } else { - q.blockTaskPool[hash] = header - q.blockTaskQueue.Push(header, -int64(header.Number.Uint64())) - } - // Queue for receipt retrieval - if q.mode == FastSync && !header.EmptyReceipts() { - if _, ok := q.receiptTaskPool[hash]; ok { - log.Warn("Header already scheduled for receipt fetch", "number", header.Number, "hash", hash) - } else { - q.receiptTaskPool[hash] = header - q.receiptTaskQueue.Push(header, -int64(header.Number.Uint64())) - } - } - inserts = append(inserts, header) - q.headerHead = hash - from++ - } - return inserts -} - -// Results retrieves and permanently removes a batch of fetch results from -// the cache. the result slice will be empty if the queue has been closed. -// Results can be called concurrently with Deliver and Schedule, -// but assumes that there are not two simultaneous callers to Results -func (q *queue) Results(block bool) []*fetchResult { - // Abort early if there are no items and non-blocking requested - if !block && !q.resultCache.HasCompletedItems() { - return nil - } - closed := false - for !closed && !q.resultCache.HasCompletedItems() { - // In order to wait on 'active', we need to obtain the lock. - // That may take a while, if someone is delivering at the same - // time, so after obtaining the lock, we check again if there - // are any results to fetch. - // Also, in-between we ask for the lock and the lock is obtained, - // someone can have closed the queue. In that case, we should - // return the available results and stop blocking - q.lock.Lock() - if q.resultCache.HasCompletedItems() || q.closed { - q.lock.Unlock() - break - } - // No items available, and not closed - q.active.Wait() - closed = q.closed - q.lock.Unlock() - } - // Regardless if closed or not, we can still deliver whatever we have - results := q.resultCache.GetCompleted(maxResultsProcess) - for _, result := range results { - // Recalculate the result item weights to prevent memory exhaustion - size := result.Header.Size() - for _, uncle := range result.Uncles { - size += uncle.Size() - } - for _, receipt := range result.Receipts { - size += receipt.Size() - } - for _, tx := range result.Transactions { - size += common.StorageSize(tx.Size()) - } - q.resultSize = common.StorageSize(blockCacheSizeWeight)*size + - (1-common.StorageSize(blockCacheSizeWeight))*q.resultSize - } - // Using the newly calibrated resultsize, figure out the new throttle limit - // on the result cache - throttleThreshold := uint64((common.StorageSize(blockCacheMemory) + q.resultSize - 1) / q.resultSize) - throttleThreshold = q.resultCache.SetThrottleThreshold(throttleThreshold) - - // Log some info at certain times - if time.Since(q.lastStatLog) > 60*time.Second { - q.lastStatLog = time.Now() - info := q.Stats() - info = append(info, "throttle", throttleThreshold) - log.Info("Downloader queue stats", info...) - } - return results -} - -func (q *queue) Stats() []interface{} { - q.lock.RLock() - defer q.lock.RUnlock() - - return q.stats() -} - -func (q *queue) stats() []interface{} { - return []interface{}{ - "receiptTasks", q.receiptTaskQueue.Size(), - "blockTasks", q.blockTaskQueue.Size(), - "itemSize", q.resultSize, - } -} - -// ReserveHeaders reserves a set of headers for the given peer, skipping any -// previously failed batches. -func (q *queue) ReserveHeaders(p *peerConnection, count int) *fetchRequest { - q.lock.Lock() - defer q.lock.Unlock() - - // Short circuit if the peer's already downloading something (sanity check to - // not corrupt state) - if _, ok := q.headerPendPool[p.id]; ok { - return nil - } - // Retrieve a batch of hashes, skipping previously failed ones - send, skip := uint64(0), []uint64{} - for send == 0 && !q.headerTaskQueue.Empty() { - from, _ := q.headerTaskQueue.Pop() - if q.headerPeerMiss[p.id] != nil { - if _, ok := q.headerPeerMiss[p.id][from]; ok { - skip = append(skip, from) - continue - } - } - send = from - } - // Merge all the skipped batches back - for _, from := range skip { - q.headerTaskQueue.Push(from, -int64(from)) - } - // Assemble and return the block download request - if send == 0 { - return nil - } - request := &fetchRequest{ - Peer: p, - From: send, - Time: time.Now(), - } - q.headerPendPool[p.id] = request - return request -} - -// ReserveBodies reserves a set of body fetches for the given peer, skipping any -// previously failed downloads. Beside the next batch of needed fetches, it also -// returns a flag whether empty blocks were queued requiring processing. -func (q *queue) ReserveBodies(p *peerConnection, count int) (*fetchRequest, bool, bool) { - q.lock.Lock() - defer q.lock.Unlock() - - return q.reserveHeaders(p, count, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, bodyType) -} - -// ReserveReceipts reserves a set of receipt fetches for the given peer, skipping -// any previously failed downloads. Beside the next batch of needed fetches, it -// also returns a flag whether empty receipts were queued requiring importing. -func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bool, bool) { - q.lock.Lock() - defer q.lock.Unlock() - - return q.reserveHeaders(p, count, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool, receiptType) -} - -// reserveHeaders reserves a set of data download operations for a given peer, -// skipping any previously failed ones. This method is a generic version used -// by the individual special reservation functions. -// -// Note, this method expects the queue lock to be already held for writing. The -// reason the lock is not obtained in here is because the parameters already need -// to access the queue, so they already need a lock anyway. -// -// Returns: -// -// item - the fetchRequest -// progress - whether any progress was made -// throttle - if the caller should throttle for a while -func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common.Hash]*types.Header, taskQueue *prque.Prque[int64, *types.Header], - pendPool map[string]*fetchRequest, kind uint) (*fetchRequest, bool, bool) { - // Short circuit if the pool has been depleted, or if the peer's already - // downloading something (sanity check not to corrupt state) - if taskQueue.Empty() { - return nil, false, true - } - if _, ok := pendPool[p.id]; ok { - return nil, false, false - } - // Retrieve a batch of tasks, skipping previously failed ones - send := make([]*types.Header, 0, count) - skip := make([]*types.Header, 0) - progress := false - throttled := false - for proc := 0; len(send) < count && !taskQueue.Empty(); proc++ { - // the task queue will pop items in order, so the highest prio block - // is also the lowest block number. - header, _ := taskQueue.Peek() - - // we can ask the resultcache if this header is within the - // "prioritized" segment of blocks. If it is not, we need to throttle - - stale, throttle, item, err := q.resultCache.AddFetch(header, q.mode == FastSync) - if stale { - // Don't put back in the task queue, this item has already been - // delivered upstream - taskQueue.PopItem() - progress = true - delete(taskPool, header.Hash()) - proc = proc - 1 - log.Error("Fetch reservation already delivered", "number", header.Number.Uint64()) - continue - } - if throttle { - // There are no resultslots available. Leave it in the task queue - // However, if there are any left as 'skipped', we should not tell - // the caller to throttle, since we still want some other - // peer to fetch those for us - throttled = len(skip) == 0 - break - } - if err != nil { - // this most definitely should _not_ happen - log.Warn("Failed to reserve headers", "err", err) - // There are no resultslots available. Leave it in the task queue - break - } - if item.Done(kind) { - // If it's a noop, we can skip this task - delete(taskPool, header.Hash()) - taskQueue.PopItem() - proc = proc - 1 - progress = true - continue - } - // Remove it from the task queue - taskQueue.PopItem() - // Otherwise unless the peer is known not to have the data, add to the retrieve list - if p.Lacks(header.Hash()) { - skip = append(skip, header) - } else { - send = append(send, header) - } - } - // Merge all the skipped headers back - for _, header := range skip { - taskQueue.Push(header, -int64(header.Number.Uint64())) - } - if q.resultCache.HasCompletedItems() { - // Wake Results, resultCache was modified - q.active.Signal() - } - // Assemble and return the block download request - if len(send) == 0 { - return nil, progress, throttled - } - request := &fetchRequest{ - Peer: p, - Headers: send, - Time: time.Now(), - } - pendPool[p.id] = request - return request, progress, throttled -} - -// CancelHeaders aborts a fetch request, returning all pending skeleton indexes to the queue. -func (q *queue) CancelHeaders(request *fetchRequest) { - q.lock.Lock() - defer q.lock.Unlock() - q.cancel(request, q.headerTaskQueue, q.headerPendPool) -} - -// CancelBodies aborts a body fetch request, returning all pending headers to the -// task queue. -func (q *queue) CancelBodies(request *fetchRequest) { - q.lock.Lock() - defer q.lock.Unlock() - q.cancel(request, q.blockTaskQueue, q.blockPendPool) -} - -// CancelReceipts aborts a body fetch request, returning all pending headers to -// the task queue. -func (q *queue) CancelReceipts(request *fetchRequest) { - q.lock.Lock() - defer q.lock.Unlock() - q.cancel(request, q.receiptTaskQueue, q.receiptPendPool) -} - -// Cancel aborts a fetch request, returning all pending hashes to the task queue. -func (q *queue) cancel(request *fetchRequest, taskQueue interface{}, pendPool map[string]*fetchRequest) { - if request.From > 0 { - taskQueue.(*prque.Prque[int64, uint64]).Push(request.From, -int64(request.From)) - } - for _, header := range request.Headers { - taskQueue.(*prque.Prque[int64, *types.Header]).Push(header, -int64(header.Number.Uint64())) - } - delete(pendPool, request.Peer.id) -} - -// Revoke cancels all pending requests belonging to a given peer. This method is -// meant to be called during a peer drop to quickly reassign owned data fetches -// to remaining nodes. -func (q *queue) Revoke(peerID string) { - q.lock.Lock() - defer q.lock.Unlock() - - if request, ok := q.blockPendPool[peerID]; ok { - for _, header := range request.Headers { - q.blockTaskQueue.Push(header, -int64(header.Number.Uint64())) - } - delete(q.blockPendPool, peerID) - } - if request, ok := q.receiptPendPool[peerID]; ok { - for _, header := range request.Headers { - q.receiptTaskQueue.Push(header, -int64(header.Number.Uint64())) - } - delete(q.receiptPendPool, peerID) - } -} - -// ExpireHeaders checks for in flight requests that exceeded a timeout allowance, -// canceling them and returning the responsible peers for penalisation. -func (q *queue) ExpireHeaders(timeout time.Duration) map[string]int { - q.lock.Lock() - defer q.lock.Unlock() - - return q.expire(timeout, q.headerPendPool, q.headerTaskQueue, headerTimeoutMeter) -} - -// ExpireBodies checks for in flight block body requests that exceeded a timeout -// allowance, canceling them and returning the responsible peers for penalisation. -func (q *queue) ExpireBodies(timeout time.Duration) map[string]int { - q.lock.Lock() - defer q.lock.Unlock() - - return q.expire(timeout, q.blockPendPool, q.blockTaskQueue, bodyTimeoutMeter) -} - -// ExpireReceipts checks for in flight receipt requests that exceeded a timeout -// allowance, canceling them and returning the responsible peers for penalisation. -func (q *queue) ExpireReceipts(timeout time.Duration) map[string]int { - q.lock.Lock() - defer q.lock.Unlock() - - return q.expire(timeout, q.receiptPendPool, q.receiptTaskQueue, receiptTimeoutMeter) -} - -// expire is the generic check that move expired tasks from a pending pool back -// into a task pool, returning all entities caught with expired tasks. -// -// Note, this method expects the queue lock to be already held. The -// reason the lock is not obtained in here is because the parameters already need -// to access the queue, so they already need a lock anyway. -func (q *queue) expire(timeout time.Duration, pendPool map[string]*fetchRequest, taskQueue interface{}, timeoutMeter metrics.Meter) map[string]int { - // Iterate over the expired requests and return each to the queue - expiries := make(map[string]int) - for id, request := range pendPool { - if time.Since(request.Time) > timeout { - // Update the metrics with the timeout - timeoutMeter.Mark(1) - - // Return any non satisfied requests to the pool - if request.From > 0 { - taskQueue.(*prque.Prque[int64, uint64]).Push(request.From, -int64(request.From)) - } - for _, header := range request.Headers { - taskQueue.(*prque.Prque[int64, *types.Header]).Push(header, -int64(header.Number.Uint64())) - } - // Add the peer to the expiry report along the number of failed requests - expiries[id] = len(request.Headers) - - // Remove the expired requests from the pending pool directly - delete(pendPool, id) - } - } - return expiries -} - -// DeliverHeaders injects a header retrieval response into the header results -// cache. This method either accepts all headers it received, or none of them -// if they do not map correctly to the skeleton. -// -// If the headers are accepted, the method makes an attempt to deliver the set -// of ready headers to the processor to keep the pipeline full. However it will -// not block to prevent stalling other pending deliveries. -func (q *queue) DeliverHeaders(id string, headers []*types.Header, headerProcCh chan []*types.Header) (int, error) { - q.lock.Lock() - defer q.lock.Unlock() - - var logger log.Logger - if len(id) < 16 { - // Tests use short IDs, don't choke on them - logger = log.New("peer", id) - } else { - logger = log.New("peer", id[:16]) - } - // Short circuit if the data was never requested - request := q.headerPendPool[id] - if request == nil { - return 0, errNoFetchesPending - } - headerReqTimer.UpdateSince(request.Time) - delete(q.headerPendPool, id) - - // Ensure headers can be mapped onto the skeleton chain - target := q.headerTaskPool[request.From].Hash() - - accepted := len(headers) == MaxHeaderFetch - if accepted { - if headers[0].Number.Uint64() != request.From { - logger.Trace("First header broke chain ordering", "number", headers[0].Number, "hash", headers[0].Hash(), "expected", request.From) - accepted = false - } else if headers[len(headers)-1].Hash() != target { - logger.Trace("Last header broke skeleton structure ", "number", headers[len(headers)-1].Number, "hash", headers[len(headers)-1].Hash(), "expected", target) - accepted = false - } - } - if accepted { - parentHash := headers[0].Hash() - for i, header := range headers[1:] { - hash := header.Hash() - if want := request.From + 1 + uint64(i); header.Number.Uint64() != want { - logger.Warn("Header broke chain ordering", "number", header.Number, "hash", hash, "expected", want) - accepted = false - break - } - if parentHash != header.ParentHash { - logger.Warn("Header broke chain ancestry", "number", header.Number, "hash", hash) - accepted = false - break - } - // Set-up parent hash for next round - parentHash = hash - } - } - // If the batch of headers wasn't accepted, mark as unavailable - if !accepted { - logger.Trace("Skeleton filling not accepted", "from", request.From) - - miss := q.headerPeerMiss[id] - if miss == nil { - q.headerPeerMiss[id] = make(map[uint64]struct{}) - miss = q.headerPeerMiss[id] - } - miss[request.From] = struct{}{} - - q.headerTaskQueue.Push(request.From, -int64(request.From)) - return 0, errors.New("delivery not accepted") - } - // Clean up a successful fetch and try to deliver any sub-results - copy(q.headerResults[request.From-q.headerOffset:], headers) - delete(q.headerTaskPool, request.From) - - ready := 0 - for q.headerProced+ready < len(q.headerResults) && q.headerResults[q.headerProced+ready] != nil { - ready += MaxHeaderFetch - } - if ready > 0 { - // Headers are ready for delivery, gather them and push forward (non blocking) - process := make([]*types.Header, ready) - copy(process, q.headerResults[q.headerProced:q.headerProced+ready]) - - select { - case headerProcCh <- process: - logger.Trace("Pre-scheduled new headers", "count", len(process), "from", process[0].Number) - q.headerProced += len(process) - default: - } - } - // Check for termination and return - if len(q.headerTaskPool) == 0 { - q.headerContCh <- false - } - return len(headers), nil -} - -// DeliverBodies injects a block body retrieval response into the results queue. -// The method returns the number of blocks bodies accepted from the delivery and -// also wakes any threads waiting for data delivery. -func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, uncleLists [][]*types.Header) (int, error) { - q.lock.Lock() - defer q.lock.Unlock() - trieHasher := trie.NewStackTrie(nil) - validate := func(index int, header *types.Header) error { - if types.DeriveSha(types.Transactions(txLists[index]), trieHasher) != header.TxHash { - return errInvalidBody - } - if types.CalcUncleHash(uncleLists[index]) != header.UncleHash { - return errInvalidBody - } - return nil - } - - reconstruct := func(index int, result *fetchResult) { - result.Transactions = txLists[index] - result.Uncles = uncleLists[index] - result.SetBodyDone() - } - return q.deliver(id, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, - bodyReqTimer, len(txLists), validate, reconstruct) -} - -// DeliverReceipts injects a receipt retrieval response into the results queue. -// The method returns the number of transaction receipts accepted from the delivery -// and also wakes any threads waiting for data delivery. -func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt) (int, error) { - q.lock.Lock() - defer q.lock.Unlock() - trieHasher := trie.NewStackTrie(nil) - validate := func(index int, header *types.Header) error { - if types.DeriveSha(types.Receipts(receiptList[index]), trieHasher) != header.ReceiptHash { - return errInvalidReceipt - } - return nil - } - reconstruct := func(index int, result *fetchResult) { - result.Receipts = receiptList[index] - result.SetReceiptsDone() - } - return q.deliver(id, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool, - receiptReqTimer, len(receiptList), validate, reconstruct) -} - -// deliver injects a data retrieval response into the results queue. -// -// Note, this method expects the queue lock to be already held for writing. The -// reason this lock is not obtained in here is because the parameters already need -// to access the queue, so they already need a lock anyway. -func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, - taskQueue *prque.Prque[int64, *types.Header], pendPool map[string]*fetchRequest, reqTimer metrics.Timer, - results int, validate func(index int, header *types.Header) error, - reconstruct func(index int, result *fetchResult)) (int, error) { - // Short circuit if the data was never requested - request := pendPool[id] - if request == nil { - return 0, errNoFetchesPending - } - reqTimer.UpdateSince(request.Time) - delete(pendPool, id) - - // If no data items were retrieved, mark them as unavailable for the origin peer - if results == 0 { - for _, header := range request.Headers { - request.Peer.MarkLacking(header.Hash()) - } - } - // Assemble each of the results with their headers and retrieved data parts - var ( - accepted int - failure error - i int - hashes []common.Hash - ) - for _, header := range request.Headers { - // Short circuit assembly if no more fetch results are found - if i >= results { - break - } - // Validate the fields - if err := validate(i, header); err != nil { - failure = err - break - } - hashes = append(hashes, header.Hash()) - i++ - } - - for _, header := range request.Headers[:i] { - if res, stale, err := q.resultCache.GetDeliverySlot(header.Number.Uint64()); err == nil && !stale { - reconstruct(accepted, res) - } else { - // else: between here and above, some other peer filled this result, - // or it was indeed a no-op. This should not happen, but if it does it's - // not something to panic about - log.Error("Delivery stale", "stale", stale, "number", header.Number.Uint64(), "err", err) - failure = errStaleDelivery - } - // Clean up a successful fetch - delete(taskPool, hashes[accepted]) - accepted++ - } - // Return all failed or missing fetches to the queue - for _, header := range request.Headers[accepted:] { - taskQueue.Push(header, -int64(header.Number.Uint64())) - } - // Wake up Results - if accepted > 0 { - q.active.Signal() - } - if failure == nil { - return accepted, nil - } - // If none of the data was good, it's a stale delivery - if accepted > 0 { - return accepted, fmt.Errorf("partial failure: %v", failure) - } - return accepted, fmt.Errorf("%w: %v", failure, errStaleDelivery) -} - -// Prepare configures the result cache to allow accepting and caching inbound -// fetch results. -func (q *queue) Prepare(offset uint64, mode SyncMode) { - q.lock.Lock() - defer q.lock.Unlock() - - // Prepare the queue for sync results - q.resultCache.Prepare(offset) - q.mode = mode -} diff --git a/les/downloader/queue_test.go b/les/downloader/queue_test.go deleted file mode 100644 index 6bffa90219ad..000000000000 --- a/les/downloader/queue_test.go +++ /dev/null @@ -1,438 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package downloader - -import ( - "fmt" - "math/big" - "math/rand" - "sync" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" -) - -// makeChain creates a chain of n blocks starting at and including parent. -// the returned hash chain is ordered head->parent. In addition, every 3rd block -// contains a transaction and every 5th an uncle to allow testing correct block -// reassembly. -func makeChain(n int, seed byte, parent *types.Block, empty bool) ([]*types.Block, []types.Receipts) { - blocks, receipts := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), testDB, n, func(i int, block *core.BlockGen) { - block.SetCoinbase(common.Address{seed}) - // Add one tx to every secondblock - if !empty && i%2 == 0 { - signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Timestamp()) - tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) - if err != nil { - panic(err) - } - block.AddTx(tx) - } - }) - return blocks, receipts -} - -type chainData struct { - blocks []*types.Block - offset int -} - -var chain *chainData -var emptyChain *chainData - -func init() { - // Create a chain of blocks to import - targetBlocks := 128 - blocks, _ := makeChain(targetBlocks, 0, testGenesis, false) - chain = &chainData{blocks, 0} - - blocks, _ = makeChain(targetBlocks, 0, testGenesis, true) - emptyChain = &chainData{blocks, 0} -} - -func (chain *chainData) headers() []*types.Header { - hdrs := make([]*types.Header, len(chain.blocks)) - for i, b := range chain.blocks { - hdrs[i] = b.Header() - } - return hdrs -} - -func (chain *chainData) Len() int { - return len(chain.blocks) -} - -func dummyPeer(id string) *peerConnection { - p := &peerConnection{ - id: id, - lacking: make(map[common.Hash]struct{}), - } - return p -} - -func TestBasics(t *testing.T) { - numOfBlocks := len(emptyChain.blocks) - numOfReceipts := len(emptyChain.blocks) / 2 - - q := newQueue(10, 10) - if !q.Idle() { - t.Errorf("new queue should be idle") - } - q.Prepare(1, FastSync) - if res := q.Results(false); len(res) != 0 { - t.Fatal("new queue should have 0 results") - } - - // Schedule a batch of headers - q.Schedule(chain.headers(), 1) - if q.Idle() { - t.Errorf("queue should not be idle") - } - if got, exp := q.PendingBlocks(), chain.Len(); got != exp { - t.Errorf("wrong pending block count, got %d, exp %d", got, exp) - } - // Only non-empty receipts get added to task-queue - if got, exp := q.PendingReceipts(), 64; got != exp { - t.Errorf("wrong pending receipt count, got %d, exp %d", got, exp) - } - // Items are now queued for downloading, next step is that we tell the - // queue that a certain peer will deliver them for us - { - peer := dummyPeer("peer-1") - fetchReq, _, throttle := q.ReserveBodies(peer, 50) - if !throttle { - // queue size is only 10, so throttling should occur - t.Fatal("should throttle") - } - // But we should still get the first things to fetch - if got, exp := len(fetchReq.Headers), 5; got != exp { - t.Fatalf("expected %d requests, got %d", exp, got) - } - if got, exp := fetchReq.Headers[0].Number.Uint64(), uint64(1); got != exp { - t.Fatalf("expected header %d, got %d", exp, got) - } - } - if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got { - t.Errorf("expected block task queue to be %d, got %d", exp, got) - } - if exp, got := q.receiptTaskQueue.Size(), numOfReceipts; exp != got { - t.Errorf("expected receipt task queue to be %d, got %d", exp, got) - } - { - peer := dummyPeer("peer-2") - fetchReq, _, throttle := q.ReserveBodies(peer, 50) - - // The second peer should hit throttling - if !throttle { - t.Fatalf("should throttle") - } - // And not get any fetches at all, since it was throttled to begin with - if fetchReq != nil { - t.Fatalf("should have no fetches, got %d", len(fetchReq.Headers)) - } - } - if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got { - t.Errorf("expected block task queue to be %d, got %d", exp, got) - } - if exp, got := q.receiptTaskQueue.Size(), numOfReceipts; exp != got { - t.Errorf("expected receipt task queue to be %d, got %d", exp, got) - } - { - // The receipt delivering peer should not be affected - // by the throttling of body deliveries - peer := dummyPeer("peer-3") - fetchReq, _, throttle := q.ReserveReceipts(peer, 50) - if !throttle { - // queue size is only 10, so throttling should occur - t.Fatal("should throttle") - } - // But we should still get the first things to fetch - if got, exp := len(fetchReq.Headers), 5; got != exp { - t.Fatalf("expected %d requests, got %d", exp, got) - } - if got, exp := fetchReq.Headers[0].Number.Uint64(), uint64(1); got != exp { - t.Fatalf("expected header %d, got %d", exp, got) - } - } - if exp, got := q.blockTaskQueue.Size(), numOfBlocks-10; exp != got { - t.Errorf("expected block task queue to be %d, got %d", exp, got) - } - if exp, got := q.receiptTaskQueue.Size(), numOfReceipts-5; exp != got { - t.Errorf("expected receipt task queue to be %d, got %d", exp, got) - } - if got, exp := q.resultCache.countCompleted(), 0; got != exp { - t.Errorf("wrong processable count, got %d, exp %d", got, exp) - } -} - -func TestEmptyBlocks(t *testing.T) { - numOfBlocks := len(emptyChain.blocks) - - q := newQueue(10, 10) - - q.Prepare(1, FastSync) - // Schedule a batch of headers - q.Schedule(emptyChain.headers(), 1) - if q.Idle() { - t.Errorf("queue should not be idle") - } - if got, exp := q.PendingBlocks(), len(emptyChain.blocks); got != exp { - t.Errorf("wrong pending block count, got %d, exp %d", got, exp) - } - if got, exp := q.PendingReceipts(), 0; got != exp { - t.Errorf("wrong pending receipt count, got %d, exp %d", got, exp) - } - // They won't be processable, because the fetchresults haven't been - // created yet - if got, exp := q.resultCache.countCompleted(), 0; got != exp { - t.Errorf("wrong processable count, got %d, exp %d", got, exp) - } - - // Items are now queued for downloading, next step is that we tell the - // queue that a certain peer will deliver them for us - // That should trigger all of them to suddenly become 'done' - { - // Reserve blocks - peer := dummyPeer("peer-1") - fetchReq, _, _ := q.ReserveBodies(peer, 50) - - // there should be nothing to fetch, blocks are empty - if fetchReq != nil { - t.Fatal("there should be no body fetch tasks remaining") - } - } - if q.blockTaskQueue.Size() != numOfBlocks-10 { - t.Errorf("expected block task queue to be %d, got %d", numOfBlocks-10, q.blockTaskQueue.Size()) - } - if q.receiptTaskQueue.Size() != 0 { - t.Errorf("expected receipt task queue to be %d, got %d", 0, q.receiptTaskQueue.Size()) - } - { - peer := dummyPeer("peer-3") - fetchReq, _, _ := q.ReserveReceipts(peer, 50) - - // there should be nothing to fetch, blocks are empty - if fetchReq != nil { - t.Fatal("there should be no receipt fetch tasks remaining") - } - } - if q.blockTaskQueue.Size() != numOfBlocks-10 { - t.Errorf("expected block task queue to be %d, got %d", numOfBlocks-10, q.blockTaskQueue.Size()) - } - if q.receiptTaskQueue.Size() != 0 { - t.Errorf("expected receipt task queue to be %d, got %d", 0, q.receiptTaskQueue.Size()) - } - if got, exp := q.resultCache.countCompleted(), 10; got != exp { - t.Errorf("wrong processable count, got %d, exp %d", got, exp) - } -} - -// XTestDelivery does some more extensive testing of events that happen, -// blocks that become known and peers that make reservations and deliveries. -// disabled since it's not really a unit-test, but can be executed to test -// some more advanced scenarios -func XTestDelivery(t *testing.T) { - // the outside network, holding blocks - blo, rec := makeChain(128, 0, testGenesis, false) - world := newNetwork() - world.receipts = rec - world.chain = blo - world.progress(10) - if false { - log.Root().SetHandler(log.StdoutHandler) - } - q := newQueue(10, 10) - var wg sync.WaitGroup - q.Prepare(1, FastSync) - wg.Add(1) - go func() { - // deliver headers - defer wg.Done() - c := 1 - for { - //fmt.Printf("getting headers from %d\n", c) - hdrs := world.headers(c) - l := len(hdrs) - //fmt.Printf("scheduling %d headers, first %d last %d\n", - // l, hdrs[0].Number.Uint64(), hdrs[len(hdrs)-1].Number.Uint64()) - q.Schedule(hdrs, uint64(c)) - c += l - } - }() - wg.Add(1) - go func() { - // collect results - defer wg.Done() - tot := 0 - for { - res := q.Results(true) - tot += len(res) - fmt.Printf("got %d results, %d tot\n", len(res), tot) - // Now we can forget about these - world.forget(res[len(res)-1].Header.Number.Uint64()) - } - }() - wg.Add(1) - go func() { - defer wg.Done() - // reserve body fetch - i := 4 - for { - peer := dummyPeer(fmt.Sprintf("peer-%d", i)) - f, _, _ := q.ReserveBodies(peer, rand.Intn(30)) - if f != nil { - var emptyList []*types.Header - var txs [][]*types.Transaction - var uncles [][]*types.Header - numToSkip := rand.Intn(len(f.Headers)) - for _, hdr := range f.Headers[0 : len(f.Headers)-numToSkip] { - txs = append(txs, world.getTransactions(hdr.Number.Uint64())) - uncles = append(uncles, emptyList) - } - time.Sleep(100 * time.Millisecond) - _, err := q.DeliverBodies(peer.id, txs, uncles) - if err != nil { - fmt.Printf("delivered %d bodies %v\n", len(txs), err) - } - } else { - i++ - time.Sleep(200 * time.Millisecond) - } - } - }() - go func() { - defer wg.Done() - // reserve receiptfetch - peer := dummyPeer("peer-3") - for { - f, _, _ := q.ReserveReceipts(peer, rand.Intn(50)) - if f != nil { - var rcs [][]*types.Receipt - for _, hdr := range f.Headers { - rcs = append(rcs, world.getReceipts(hdr.Number.Uint64())) - } - _, err := q.DeliverReceipts(peer.id, rcs) - if err != nil { - fmt.Printf("delivered %d receipts %v\n", len(rcs), err) - } - time.Sleep(100 * time.Millisecond) - } else { - time.Sleep(200 * time.Millisecond) - } - } - }() - wg.Add(1) - go func() { - defer wg.Done() - for i := 0; i < 50; i++ { - time.Sleep(300 * time.Millisecond) - //world.tick() - //fmt.Printf("trying to progress\n") - world.progress(rand.Intn(100)) - } - for i := 0; i < 50; i++ { - time.Sleep(2990 * time.Millisecond) - } - }() - wg.Add(1) - go func() { - defer wg.Done() - for { - time.Sleep(990 * time.Millisecond) - fmt.Printf("world block tip is %d\n", - world.chain[len(world.chain)-1].Header().Number.Uint64()) - fmt.Println(q.Stats()) - } - }() - wg.Wait() -} - -func newNetwork() *network { - var l sync.RWMutex - return &network{ - cond: sync.NewCond(&l), - offset: 1, // block 1 is at blocks[0] - } -} - -// represents the network -type network struct { - offset int - chain []*types.Block - receipts []types.Receipts - lock sync.RWMutex - cond *sync.Cond -} - -func (n *network) getTransactions(blocknum uint64) types.Transactions { - index := blocknum - uint64(n.offset) - return n.chain[index].Transactions() -} -func (n *network) getReceipts(blocknum uint64) types.Receipts { - index := blocknum - uint64(n.offset) - if got := n.chain[index].Header().Number.Uint64(); got != blocknum { - fmt.Printf("Err, got %d exp %d\n", got, blocknum) - panic("sd") - } - return n.receipts[index] -} - -func (n *network) forget(blocknum uint64) { - index := blocknum - uint64(n.offset) - n.chain = n.chain[index:] - n.receipts = n.receipts[index:] - n.offset = int(blocknum) -} -func (n *network) progress(numBlocks int) { - n.lock.Lock() - defer n.lock.Unlock() - //fmt.Printf("progressing...\n") - newBlocks, newR := makeChain(numBlocks, 0, n.chain[len(n.chain)-1], false) - n.chain = append(n.chain, newBlocks...) - n.receipts = append(n.receipts, newR...) - n.cond.Broadcast() -} - -func (n *network) headers(from int) []*types.Header { - numHeaders := 128 - var hdrs []*types.Header - index := from - n.offset - - for index >= len(n.chain) { - // wait for progress - n.cond.L.Lock() - //fmt.Printf("header going into wait\n") - n.cond.Wait() - index = from - n.offset - n.cond.L.Unlock() - } - n.lock.RLock() - defer n.lock.RUnlock() - for i, b := range n.chain[index:] { - hdrs = append(hdrs, b.Header()) - if i >= numHeaders { - break - } - } - return hdrs -} diff --git a/les/downloader/resultstore.go b/les/downloader/resultstore.go deleted file mode 100644 index 7fcade294660..000000000000 --- a/les/downloader/resultstore.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2020 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package downloader - -import ( - "fmt" - "sync" - "sync/atomic" - - "github.com/ethereum/go-ethereum/core/types" -) - -// resultStore implements a structure for maintaining fetchResults, tracking their -// download-progress and delivering (finished) results. -type resultStore struct { - items []*fetchResult // Downloaded but not yet delivered fetch results - resultOffset uint64 // Offset of the first cached fetch result in the block chain - - // Internal index of first non-completed entry, updated atomically when needed. - // If all items are complete, this will equal length(items), so - // *important* : is not safe to use for indexing without checking against length - indexIncomplete int32 // atomic access - - // throttleThreshold is the limit up to which we _want_ to fill the - // results. If blocks are large, we want to limit the results to less - // than the number of available slots, and maybe only fill 1024 out of - // 8192 possible places. The queue will, at certain times, recalibrate - // this index. - throttleThreshold uint64 - - lock sync.RWMutex -} - -func newResultStore(size int) *resultStore { - return &resultStore{ - resultOffset: 0, - items: make([]*fetchResult, size), - throttleThreshold: uint64(size), - } -} - -// SetThrottleThreshold updates the throttling threshold based on the requested -// limit and the total queue capacity. It returns the (possibly capped) threshold -func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 { - r.lock.Lock() - defer r.lock.Unlock() - - limit := uint64(len(r.items)) - if threshold >= limit { - threshold = limit - } - r.throttleThreshold = threshold - return r.throttleThreshold -} - -// AddFetch adds a header for body/receipt fetching. This is used when the queue -// wants to reserve headers for fetching. -// -// It returns the following: -// -// stale - if true, this item is already passed, and should not be requested again -// throttled - if true, the store is at capacity, this particular header is not prio now -// item - the result to store data into -// err - any error that occurred -func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale, throttled bool, item *fetchResult, err error) { - r.lock.Lock() - defer r.lock.Unlock() - - var index int - item, index, stale, throttled, err = r.getFetchResult(header.Number.Uint64()) - if err != nil || stale || throttled { - return stale, throttled, item, err - } - if item == nil { - item = newFetchResult(header, fastSync) - r.items[index] = item - } - return stale, throttled, item, err -} - -// GetDeliverySlot returns the fetchResult for the given header. If the 'stale' flag -// is true, that means the header has already been delivered 'upstream'. This method -// does not bubble up the 'throttle' flag, since it's moot at the point in time when -// the item is downloaded and ready for delivery -func (r *resultStore) GetDeliverySlot(headerNumber uint64) (*fetchResult, bool, error) { - r.lock.RLock() - defer r.lock.RUnlock() - - res, _, stale, _, err := r.getFetchResult(headerNumber) - return res, stale, err -} - -// getFetchResult returns the fetchResult corresponding to the given item, and -// the index where the result is stored. -func (r *resultStore) getFetchResult(headerNumber uint64) (item *fetchResult, index int, stale, throttle bool, err error) { - index = int(int64(headerNumber) - int64(r.resultOffset)) - throttle = index >= int(r.throttleThreshold) - stale = index < 0 - - if index >= len(r.items) { - err = fmt.Errorf("%w: index allocation went beyond available resultStore space "+ - "(index [%d] = header [%d] - resultOffset [%d], len(resultStore) = %d", errInvalidChain, - index, headerNumber, r.resultOffset, len(r.items)) - return nil, index, stale, throttle, err - } - if stale { - return nil, index, stale, throttle, nil - } - item = r.items[index] - return item, index, stale, throttle, nil -} - -// HasCompletedItems returns true if there are processable items available -// this method is cheaper than countCompleted -func (r *resultStore) HasCompletedItems() bool { - r.lock.RLock() - defer r.lock.RUnlock() - - if len(r.items) == 0 { - return false - } - if item := r.items[0]; item != nil && item.AllDone() { - return true - } - return false -} - -// countCompleted returns the number of items ready for delivery, stopping at -// the first non-complete item. -// -// The method assumes (at least) rlock is held. -func (r *resultStore) countCompleted() int { - // We iterate from the already known complete point, and see - // if any more has completed since last count - index := atomic.LoadInt32(&r.indexIncomplete) - for ; ; index++ { - if index >= int32(len(r.items)) { - break - } - result := r.items[index] - if result == nil || !result.AllDone() { - break - } - } - atomic.StoreInt32(&r.indexIncomplete, index) - return int(index) -} - -// GetCompleted returns the next batch of completed fetchResults -func (r *resultStore) GetCompleted(limit int) []*fetchResult { - r.lock.Lock() - defer r.lock.Unlock() - - completed := r.countCompleted() - if limit > completed { - limit = completed - } - results := make([]*fetchResult, limit) - copy(results, r.items[:limit]) - - // Delete the results from the cache and clear the tail. - copy(r.items, r.items[limit:]) - for i := len(r.items) - limit; i < len(r.items); i++ { - r.items[i] = nil - } - // Advance the expected block number of the first cache entry - r.resultOffset += uint64(limit) - atomic.AddInt32(&r.indexIncomplete, int32(-limit)) - - return results -} - -// Prepare initialises the offset with the given block number -func (r *resultStore) Prepare(offset uint64) { - r.lock.Lock() - defer r.lock.Unlock() - - if r.resultOffset < offset { - r.resultOffset = offset - } -} diff --git a/les/downloader/statesync.go b/les/downloader/statesync.go deleted file mode 100644 index 4dacade3fae9..000000000000 --- a/les/downloader/statesync.go +++ /dev/null @@ -1,638 +0,0 @@ -// Copyright 2017 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 downloader - -import ( - "fmt" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/trie" - "golang.org/x/crypto/sha3" -) - -// stateReq represents a batch of state fetch requests grouped together into -// a single data retrieval network packet. -type stateReq struct { - nItems uint16 // Number of items requested for download (max is 384, so uint16 is sufficient) - trieTasks map[string]*trieTask // Trie node download tasks to track previous attempts - codeTasks map[common.Hash]*codeTask // Byte code download tasks to track previous attempts - timeout time.Duration // Maximum round trip time for this to complete - timer *time.Timer // Timer to fire when the RTT timeout expires - peer *peerConnection // Peer that we're requesting from - delivered time.Time // Time when the packet was delivered (independent when we process it) - response [][]byte // Response data of the peer (nil for timeouts) - dropped bool // Flag whether the peer dropped off early -} - -// timedOut returns if this request timed out. -func (req *stateReq) timedOut() bool { - return req.response == nil -} - -// stateSyncStats is a collection of progress stats to report during a state trie -// sync to RPC requests as well as to display in user logs. -type stateSyncStats struct { - processed uint64 // Number of state entries processed - duplicate uint64 // Number of state entries downloaded twice - unexpected uint64 // Number of non-requested state entries received - pending uint64 // Number of still pending state entries -} - -// syncState starts downloading state with the given root hash. -func (d *Downloader) syncState(root common.Hash) *stateSync { - // Create the state sync - s := newStateSync(d, root) - select { - case d.stateSyncStart <- s: - // If we tell the statesync to restart with a new root, we also need - // to wait for it to actually also start -- when old requests have timed - // out or been delivered - <-s.started - case <-d.quitCh: - s.err = errCancelStateFetch - close(s.done) - } - return s -} - -// stateFetcher manages the active state sync and accepts requests -// on its behalf. -func (d *Downloader) stateFetcher() { - for { - select { - case s := <-d.stateSyncStart: - for next := s; next != nil; { - next = d.runStateSync(next) - } - case <-d.stateCh: - // Ignore state responses while no sync is running. - case <-d.quitCh: - return - } - } -} - -// runStateSync runs a state synchronisation until it completes or another root -// hash is requested to be switched over to. -func (d *Downloader) runStateSync(s *stateSync) *stateSync { - var ( - active = make(map[string]*stateReq) // Currently in-flight requests - finished []*stateReq // Completed or failed requests - timeout = make(chan *stateReq) // Timed out active requests - ) - log.Trace("State sync starting", "root", s.root) - - defer func() { - // Cancel active request timers on exit. Also set peers to idle so they're - // available for the next sync. - for _, req := range active { - req.timer.Stop() - req.peer.SetNodeDataIdle(int(req.nItems), time.Now()) - } - }() - go s.run() - defer s.Cancel() - - // Listen for peer departure events to cancel assigned tasks - peerDrop := make(chan *peerConnection, 1024) - peerSub := s.d.peers.SubscribePeerDrops(peerDrop) - defer peerSub.Unsubscribe() - - for { - // Enable sending of the first buffered element if there is one. - var ( - deliverReq *stateReq - deliverReqCh chan *stateReq - ) - if len(finished) > 0 { - deliverReq = finished[0] - deliverReqCh = s.deliver - } - - select { - // The stateSync lifecycle: - case next := <-d.stateSyncStart: - d.spindownStateSync(active, finished, timeout, peerDrop) - return next - - case <-s.done: - d.spindownStateSync(active, finished, timeout, peerDrop) - return nil - - // Send the next finished request to the current sync: - case deliverReqCh <- deliverReq: - // Shift out the first request, but also set the emptied slot to nil for GC - copy(finished, finished[1:]) - finished[len(finished)-1] = nil - finished = finished[:len(finished)-1] - - // Handle incoming state packs: - case pack := <-d.stateCh: - // Discard any data not requested (or previously timed out) - req := active[pack.PeerId()] - if req == nil { - log.Debug("Unrequested node data", "peer", pack.PeerId(), "len", pack.Items()) - continue - } - // Finalize the request and queue up for processing - req.timer.Stop() - req.response = pack.(*statePack).states - req.delivered = time.Now() - - finished = append(finished, req) - delete(active, pack.PeerId()) - - // Handle dropped peer connections: - case p := <-peerDrop: - // Skip if no request is currently pending - req := active[p.id] - if req == nil { - continue - } - // Finalize the request and queue up for processing - req.timer.Stop() - req.dropped = true - req.delivered = time.Now() - - finished = append(finished, req) - delete(active, p.id) - - // Handle timed-out requests: - case req := <-timeout: - // If the peer is already requesting something else, ignore the stale timeout. - // This can happen when the timeout and the delivery happens simultaneously, - // causing both pathways to trigger. - if active[req.peer.id] != req { - continue - } - req.delivered = time.Now() - // Move the timed out data back into the download queue - finished = append(finished, req) - delete(active, req.peer.id) - - // Track outgoing state requests: - case req := <-d.trackStateReq: - // If an active request already exists for this peer, we have a problem. In - // theory the trie node schedule must never assign two requests to the same - // peer. In practice however, a peer might receive a request, disconnect and - // immediately reconnect before the previous times out. In this case the first - // request is never honored, alas we must not silently overwrite it, as that - // causes valid requests to go missing and sync to get stuck. - if old := active[req.peer.id]; old != nil { - log.Warn("Busy peer assigned new state fetch", "peer", old.peer.id) - // Move the previous request to the finished set - old.timer.Stop() - old.dropped = true - old.delivered = time.Now() - finished = append(finished, old) - } - // Start a timer to notify the sync loop if the peer stalled. - req.timer = time.AfterFunc(req.timeout, func() { - timeout <- req - }) - active[req.peer.id] = req - } - } -} - -// spindownStateSync 'drains' the outstanding requests; some will be delivered and other -// will time out. This is to ensure that when the next stateSync starts working, all peers -// are marked as idle and de facto _are_ idle. -func (d *Downloader) spindownStateSync(active map[string]*stateReq, finished []*stateReq, timeout chan *stateReq, peerDrop chan *peerConnection) { - log.Trace("State sync spinning down", "active", len(active), "finished", len(finished)) - for len(active) > 0 { - var ( - req *stateReq - reason string - ) - select { - // Handle (drop) incoming state packs: - case pack := <-d.stateCh: - req = active[pack.PeerId()] - reason = "delivered" - // Handle dropped peer connections: - case p := <-peerDrop: - req = active[p.id] - reason = "peerdrop" - // Handle timed-out requests: - case req = <-timeout: - reason = "timeout" - } - if req == nil { - continue - } - req.peer.log.Trace("State peer marked idle (spindown)", "req.items", int(req.nItems), "reason", reason) - req.timer.Stop() - delete(active, req.peer.id) - req.peer.SetNodeDataIdle(int(req.nItems), time.Now()) - } - // The 'finished' set contains deliveries that we were going to pass to processing. - // Those are now moot, but we still need to set those peers as idle, which would - // otherwise have been done after processing - for _, req := range finished { - req.peer.SetNodeDataIdle(int(req.nItems), time.Now()) - } -} - -// stateSync schedules requests for downloading a particular state trie defined -// by a given state root. -type stateSync struct { - d *Downloader // Downloader instance to access and manage current peerset - - root common.Hash // State root currently being synced - sched *trie.Sync // State trie sync scheduler defining the tasks - keccak crypto.KeccakState // Keccak256 hasher to verify deliveries with - - trieTasks map[string]*trieTask // Set of trie node tasks currently queued for retrieval, indexed by path - codeTasks map[common.Hash]*codeTask // Set of byte code tasks currently queued for retrieval, indexed by hash - - numUncommitted int - bytesUncommitted int - - started chan struct{} // Started is signalled once the sync loop starts - - deliver chan *stateReq // Delivery channel multiplexing peer responses - cancel chan struct{} // Channel to signal a termination request - cancelOnce sync.Once // Ensures cancel only ever gets called once - done chan struct{} // Channel to signal termination completion - err error // Any error hit during sync (set before completion) -} - -// trieTask represents a single trie node download task, containing a set of -// peers already attempted retrieval from to detect stalled syncs and abort. -type trieTask struct { - hash common.Hash - path [][]byte - attempts map[string]struct{} -} - -// codeTask represents a single byte code download task, containing a set of -// peers already attempted retrieval from to detect stalled syncs and abort. -type codeTask struct { - attempts map[string]struct{} -} - -// newStateSync creates a new state trie download scheduler. This method does not -// yet start the sync. The user needs to call run to initiate. -func newStateSync(d *Downloader, root common.Hash) *stateSync { - // Hack the node scheme here. It's a dead code is not used - // by light client at all. Just aim for passing tests. - return &stateSync{ - d: d, - root: root, - sched: state.NewStateSync(root, d.stateDB, nil, rawdb.HashScheme), - keccak: sha3.NewLegacyKeccak256().(crypto.KeccakState), - trieTasks: make(map[string]*trieTask), - codeTasks: make(map[common.Hash]*codeTask), - deliver: make(chan *stateReq), - cancel: make(chan struct{}), - done: make(chan struct{}), - started: make(chan struct{}), - } -} - -// run starts the task assignment and response processing loop, blocking until -// it finishes, and finally notifying any goroutines waiting for the loop to -// finish. -func (s *stateSync) run() { - close(s.started) - if s.d.snapSync { - s.err = s.d.SnapSyncer.Sync(s.root, s.cancel) - } else { - s.err = s.loop() - } - close(s.done) -} - -// Wait blocks until the sync is done or canceled. -func (s *stateSync) Wait() error { - <-s.done - return s.err -} - -// Cancel cancels the sync and waits until it has shut down. -func (s *stateSync) Cancel() error { - s.cancelOnce.Do(func() { - close(s.cancel) - }) - return s.Wait() -} - -// loop is the main event loop of a state trie sync. It it responsible for the -// assignment of new tasks to peers (including sending it to them) as well as -// for the processing of inbound data. Note, that the loop does not directly -// receive data from peers, rather those are buffered up in the downloader and -// pushed here async. The reason is to decouple processing from data receipt -// and timeouts. -func (s *stateSync) loop() (err error) { - // Listen for new peer events to assign tasks to them - newPeer := make(chan *peerConnection, 1024) - peerSub := s.d.peers.SubscribeNewPeers(newPeer) - defer peerSub.Unsubscribe() - defer func() { - cerr := s.commit(true) - if err == nil { - err = cerr - } - }() - - // Keep assigning new tasks until the sync completes or aborts - for s.sched.Pending() > 0 { - if err = s.commit(false); err != nil { - return err - } - s.assignTasks() - // Tasks assigned, wait for something to happen - select { - case <-newPeer: - // New peer arrived, try to assign it download tasks - - case <-s.cancel: - return errCancelStateFetch - - case <-s.d.cancelCh: - return errCanceled - - case req := <-s.deliver: - // Response, disconnect or timeout triggered, drop the peer if stalling - log.Trace("Received node data response", "peer", req.peer.id, "count", len(req.response), "dropped", req.dropped, "timeout", !req.dropped && req.timedOut()) - if req.nItems <= 2 && !req.dropped && req.timedOut() { - // 2 items are the minimum requested, if even that times out, we've no use of - // this peer at the moment. - log.Warn("Stalling state sync, dropping peer", "peer", req.peer.id) - if s.d.dropPeer == nil { - // The dropPeer method is nil when `--copydb` is used for a local copy. - // Timeouts can occur if e.g. compaction hits at the wrong time, and can be ignored - req.peer.log.Warn("Downloader wants to drop peer, but peerdrop-function is not set", "peer", req.peer.id) - } else { - s.d.dropPeer(req.peer.id) - - // If this peer was the master peer, abort sync immediately - s.d.cancelLock.RLock() - master := req.peer.id == s.d.cancelPeer - s.d.cancelLock.RUnlock() - - if master { - s.d.cancel() - return errTimeout - } - } - } - // Process all the received blobs and check for stale delivery - delivered, err := s.process(req) - req.peer.SetNodeDataIdle(delivered, req.delivered) - if err != nil { - log.Warn("Node data write error", "err", err) - return err - } - } - } - return nil -} - -func (s *stateSync) commit(force bool) error { - if !force && s.bytesUncommitted < ethdb.IdealBatchSize { - return nil - } - start := time.Now() - b := s.d.stateDB.NewBatch() - if err := s.sched.Commit(b); err != nil { - return err - } - if err := b.Write(); err != nil { - return fmt.Errorf("DB write error: %v", err) - } - s.updateStats(s.numUncommitted, 0, 0, time.Since(start)) - s.numUncommitted = 0 - s.bytesUncommitted = 0 - return nil -} - -// assignTasks attempts to assign new tasks to all idle peers, either from the -// batch currently being retried, or fetching new data from the trie sync itself. -func (s *stateSync) assignTasks() { - // Iterate over all idle peers and try to assign them state fetches - peers, _ := s.d.peers.NodeDataIdlePeers() - for _, p := range peers { - // Assign a batch of fetches proportional to the estimated latency/bandwidth - cap := p.NodeDataCapacity(s.d.peers.rates.TargetRoundTrip()) - req := &stateReq{peer: p, timeout: s.d.peers.rates.TargetTimeout()} - - nodes, _, codes := s.fillTasks(cap, req) - - // If the peer was assigned tasks to fetch, send the network request - if len(nodes)+len(codes) > 0 { - req.peer.log.Trace("Requesting batch of state data", "nodes", len(nodes), "codes", len(codes), "root", s.root) - select { - case s.d.trackStateReq <- req: - req.peer.FetchNodeData(append(nodes, codes...)) // Unified retrieval under eth/6x - case <-s.cancel: - case <-s.d.cancelCh: - } - } - } -} - -// fillTasks fills the given request object with a maximum of n state download -// tasks to send to the remote peer. -func (s *stateSync) fillTasks(n int, req *stateReq) (nodes []common.Hash, paths []trie.SyncPath, codes []common.Hash) { - // Refill available tasks from the scheduler. - if fill := n - (len(s.trieTasks) + len(s.codeTasks)); fill > 0 { - paths, hashes, codes := s.sched.Missing(fill) - for i, path := range paths { - s.trieTasks[path] = &trieTask{ - hash: hashes[i], - path: trie.NewSyncPath([]byte(path)), - attempts: make(map[string]struct{}), - } - } - for _, hash := range codes { - s.codeTasks[hash] = &codeTask{ - attempts: make(map[string]struct{}), - } - } - } - // Find tasks that haven't been tried with the request's peer. Prefer code - // over trie nodes as those can be written to disk and forgotten about. - nodes = make([]common.Hash, 0, n) - paths = make([]trie.SyncPath, 0, n) - codes = make([]common.Hash, 0, n) - - req.trieTasks = make(map[string]*trieTask, n) - req.codeTasks = make(map[common.Hash]*codeTask, n) - - for hash, t := range s.codeTasks { - // Stop when we've gathered enough requests - if len(nodes)+len(codes) == n { - break - } - // Skip any requests we've already tried from this peer - if _, ok := t.attempts[req.peer.id]; ok { - continue - } - // Assign the request to this peer - t.attempts[req.peer.id] = struct{}{} - codes = append(codes, hash) - req.codeTasks[hash] = t - delete(s.codeTasks, hash) - } - for path, t := range s.trieTasks { - // Stop when we've gathered enough requests - if len(nodes)+len(codes) == n { - break - } - // Skip any requests we've already tried from this peer - if _, ok := t.attempts[req.peer.id]; ok { - continue - } - // Assign the request to this peer - t.attempts[req.peer.id] = struct{}{} - - nodes = append(nodes, t.hash) - paths = append(paths, t.path) - - req.trieTasks[path] = t - delete(s.trieTasks, path) - } - req.nItems = uint16(len(nodes) + len(codes)) - return nodes, paths, codes -} - -// process iterates over a batch of delivered state data, injecting each item -// into a running state sync, re-queuing any items that were requested but not -// delivered. Returns whether the peer actually managed to deliver anything of -// value, and any error that occurred. -func (s *stateSync) process(req *stateReq) (int, error) { - // Collect processing stats and update progress if valid data was received - duplicate, unexpected, successful := 0, 0, 0 - - defer func(start time.Time) { - if duplicate > 0 || unexpected > 0 { - s.updateStats(0, duplicate, unexpected, time.Since(start)) - } - }(time.Now()) - - // Iterate over all the delivered data and inject one-by-one into the trie - for _, blob := range req.response { - hash, err := s.processNodeData(req.trieTasks, req.codeTasks, blob) - switch err { - case nil: - s.numUncommitted++ - s.bytesUncommitted += len(blob) - successful++ - case trie.ErrNotRequested: - unexpected++ - case trie.ErrAlreadyProcessed: - duplicate++ - default: - return successful, fmt.Errorf("invalid state node %s: %v", hash.TerminalString(), err) - } - } - // Put unfulfilled tasks back into the retry queue - npeers := s.d.peers.Len() - for path, task := range req.trieTasks { - // If the node did deliver something, missing items may be due to a protocol - // limit or a previous timeout + delayed delivery. Both cases should permit - // the node to retry the missing items (to avoid single-peer stalls). - if len(req.response) > 0 || req.timedOut() { - delete(task.attempts, req.peer.id) - } - // If we've requested the node too many times already, it may be a malicious - // sync where nobody has the right data. Abort. - if len(task.attempts) >= npeers { - return successful, fmt.Errorf("trie node %s failed with all peers (%d tries, %d peers)", task.hash.TerminalString(), len(task.attempts), npeers) - } - // Missing item, place into the retry queue. - s.trieTasks[path] = task - } - for hash, task := range req.codeTasks { - // If the node did deliver something, missing items may be due to a protocol - // limit or a previous timeout + delayed delivery. Both cases should permit - // the node to retry the missing items (to avoid single-peer stalls). - if len(req.response) > 0 || req.timedOut() { - delete(task.attempts, req.peer.id) - } - // If we've requested the node too many times already, it may be a malicious - // sync where nobody has the right data. Abort. - if len(task.attempts) >= npeers { - return successful, fmt.Errorf("byte code %s failed with all peers (%d tries, %d peers)", hash.TerminalString(), len(task.attempts), npeers) - } - // Missing item, place into the retry queue. - s.codeTasks[hash] = task - } - return successful, nil -} - -// processNodeData tries to inject a trie node data blob delivered from a remote -// peer into the state trie, returning whether anything useful was written or any -// error occurred. -// -// If multiple requests correspond to the same hash, this method will inject the -// blob as a result for the first one only, leaving the remaining duplicates to -// be fetched again. -func (s *stateSync) processNodeData(nodeTasks map[string]*trieTask, codeTasks map[common.Hash]*codeTask, blob []byte) (common.Hash, error) { - var hash common.Hash - s.keccak.Reset() - s.keccak.Write(blob) - s.keccak.Read(hash[:]) - - if _, present := codeTasks[hash]; present { - err := s.sched.ProcessCode(trie.CodeSyncResult{ - Hash: hash, - Data: blob, - }) - delete(codeTasks, hash) - return hash, err - } - for path, task := range nodeTasks { - if task.hash == hash { - err := s.sched.ProcessNode(trie.NodeSyncResult{ - Path: path, - Data: blob, - }) - delete(nodeTasks, path) - return hash, err - } - } - return common.Hash{}, trie.ErrNotRequested -} - -// updateStats bumps the various state sync progress counters and displays a log -// message for the user to see. -func (s *stateSync) updateStats(written, duplicate, unexpected int, duration time.Duration) { - s.d.syncStatsLock.Lock() - defer s.d.syncStatsLock.Unlock() - - s.d.syncStatsState.pending = uint64(s.sched.Pending()) - s.d.syncStatsState.processed += uint64(written) - s.d.syncStatsState.duplicate += uint64(duplicate) - s.d.syncStatsState.unexpected += uint64(unexpected) - - if written > 0 || duplicate > 0 || unexpected > 0 { - log.Info("Imported new state entries", "count", written, "elapsed", common.PrettyDuration(duration), "processed", s.d.syncStatsState.processed, "pending", s.d.syncStatsState.pending, "trieretry", len(s.trieTasks), "coderetry", len(s.codeTasks), "duplicate", s.d.syncStatsState.duplicate, "unexpected", s.d.syncStatsState.unexpected) - } - //if written > 0 { - //rawdb.WriteFastTrieProgress(s.d.stateDB, s.d.syncStatsState.processed) - //} -} diff --git a/les/downloader/testchain_test.go b/les/downloader/testchain_test.go deleted file mode 100644 index dff0844cb893..000000000000 --- a/les/downloader/testchain_test.go +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package downloader - -import ( - "fmt" - "math/big" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" -) - -// Test chain parameters. -var ( - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testAddress = crypto.PubkeyToAddress(testKey.PublicKey) - testDB = rawdb.NewMemoryDatabase() - - gspec = core.Genesis{ - Alloc: core.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, - BaseFee: big.NewInt(params.InitialBaseFee), - } - testGenesis = gspec.MustCommit(testDB) -) - -// The common prefix of all test chains: -var testChainBase = newTestChain(blockCacheMaxItems+200, testGenesis) - -// Different forks on top of the base chain: -var testChainForkLightA, testChainForkLightB, testChainForkHeavy *testChain - -func init() { - var forkLen = int(fullMaxForkAncestry + 50) - var wg sync.WaitGroup - wg.Add(3) - go func() { testChainForkLightA = testChainBase.makeFork(forkLen, false, 1); wg.Done() }() - go func() { testChainForkLightB = testChainBase.makeFork(forkLen, false, 2); wg.Done() }() - go func() { testChainForkHeavy = testChainBase.makeFork(forkLen, true, 3); wg.Done() }() - wg.Wait() -} - -type testChain struct { - genesis *types.Block - chain []common.Hash - headerm map[common.Hash]*types.Header - blockm map[common.Hash]*types.Block - receiptm map[common.Hash][]*types.Receipt - tdm map[common.Hash]*big.Int -} - -// newTestChain creates a blockchain of the given length. -func newTestChain(length int, genesis *types.Block) *testChain { - tc := new(testChain).copy(length) - tc.genesis = genesis - tc.chain = append(tc.chain, genesis.Hash()) - tc.headerm[tc.genesis.Hash()] = tc.genesis.Header() - tc.tdm[tc.genesis.Hash()] = tc.genesis.Difficulty() - tc.blockm[tc.genesis.Hash()] = tc.genesis - tc.generate(length-1, 0, genesis, false) - return tc -} - -// makeFork creates a fork on top of the test chain. -func (tc *testChain) makeFork(length int, heavy bool, seed byte) *testChain { - fork := tc.copy(tc.len() + length) - fork.generate(length, seed, tc.headBlock(), heavy) - return fork -} - -// shorten creates a copy of the chain with the given length. It panics if the -// length is longer than the number of available blocks. -func (tc *testChain) shorten(length int) *testChain { - if length > tc.len() { - panic(fmt.Errorf("can't shorten test chain to %d blocks, it's only %d blocks long", length, tc.len())) - } - return tc.copy(length) -} - -func (tc *testChain) copy(newlen int) *testChain { - cpy := &testChain{ - genesis: tc.genesis, - headerm: make(map[common.Hash]*types.Header, newlen), - blockm: make(map[common.Hash]*types.Block, newlen), - receiptm: make(map[common.Hash][]*types.Receipt, newlen), - tdm: make(map[common.Hash]*big.Int, newlen), - } - for i := 0; i < len(tc.chain) && i < newlen; i++ { - hash := tc.chain[i] - cpy.chain = append(cpy.chain, tc.chain[i]) - cpy.tdm[hash] = tc.tdm[hash] - cpy.blockm[hash] = tc.blockm[hash] - cpy.headerm[hash] = tc.headerm[hash] - cpy.receiptm[hash] = tc.receiptm[hash] - } - return cpy -} - -// generate creates a chain of n blocks starting at and including parent. -// the returned hash chain is ordered head->parent. In addition, every 22th block -// contains a transaction and every 5th an uncle to allow testing correct block -// reassembly. -func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool) { - // start := time.Now() - // defer func() { fmt.Printf("test chain generated in %v\n", time.Since(start)) }() - - blocks, receipts := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), testDB, n, func(i int, block *core.BlockGen) { - block.SetCoinbase(common.Address{seed}) - // If a heavy chain is requested, delay blocks to raise difficulty - if heavy { - block.OffsetTime(-1) - } - // Include transactions to the miner to make blocks more interesting. - if parent == tc.genesis && i%22 == 0 { - signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Timestamp()) - tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) - if err != nil { - panic(err) - } - block.AddTx(tx) - } - // if the block number is a multiple of 5, add a bonus uncle to the block - if i > 0 && i%5 == 0 { - block.AddUncle(&types.Header{ - ParentHash: block.PrevBlock(i - 1).Hash(), - Number: big.NewInt(block.Number().Int64() - 1), - }) - } - }) - - // Convert the block-chain into a hash-chain and header/block maps - td := new(big.Int).Set(tc.td(parent.Hash())) - for i, b := range blocks { - td := td.Add(td, b.Difficulty()) - hash := b.Hash() - tc.chain = append(tc.chain, hash) - tc.blockm[hash] = b - tc.headerm[hash] = b.Header() - tc.receiptm[hash] = receipts[i] - tc.tdm[hash] = new(big.Int).Set(td) - } -} - -// len returns the total number of blocks in the chain. -func (tc *testChain) len() int { - return len(tc.chain) -} - -// headBlock returns the head of the chain. -func (tc *testChain) headBlock() *types.Block { - return tc.blockm[tc.chain[len(tc.chain)-1]] -} - -// td returns the total difficulty of the given block. -func (tc *testChain) td(hash common.Hash) *big.Int { - return tc.tdm[hash] -} - -// headersByHash returns headers in order from the given hash. -func (tc *testChain) headersByHash(origin common.Hash, amount int, skip int, reverse bool) []*types.Header { - num, _ := tc.hashToNumber(origin) - return tc.headersByNumber(num, amount, skip, reverse) -} - -// headersByNumber returns headers from the given number. -func (tc *testChain) headersByNumber(origin uint64, amount int, skip int, reverse bool) []*types.Header { - result := make([]*types.Header, 0, amount) - - if !reverse { - for num := origin; num < uint64(len(tc.chain)) && len(result) < amount; num += uint64(skip) + 1 { - if header, ok := tc.headerm[tc.chain[int(num)]]; ok { - result = append(result, header) - } - } - } else { - for num := int64(origin); num >= 0 && len(result) < amount; num -= int64(skip) + 1 { - if header, ok := tc.headerm[tc.chain[int(num)]]; ok { - result = append(result, header) - } - } - } - return result -} - -// receipts returns the receipts of the given block hashes. -func (tc *testChain) receipts(hashes []common.Hash) [][]*types.Receipt { - results := make([][]*types.Receipt, 0, len(hashes)) - for _, hash := range hashes { - if receipt, ok := tc.receiptm[hash]; ok { - results = append(results, receipt) - } - } - return results -} - -// bodies returns the block bodies of the given block hashes. -func (tc *testChain) bodies(hashes []common.Hash) ([][]*types.Transaction, [][]*types.Header) { - transactions := make([][]*types.Transaction, 0, len(hashes)) - uncles := make([][]*types.Header, 0, len(hashes)) - for _, hash := range hashes { - if block, ok := tc.blockm[hash]; ok { - transactions = append(transactions, block.Transactions()) - uncles = append(uncles, block.Uncles()) - } - } - return transactions, uncles -} - -func (tc *testChain) hashToNumber(target common.Hash) (uint64, bool) { - for num, hash := range tc.chain { - if hash == target { - return uint64(num), true - } - } - return 0, false -} diff --git a/les/downloader/types.go b/les/downloader/types.go deleted file mode 100644 index ff70bfa0e3cc..000000000000 --- a/les/downloader/types.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package downloader - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/core/types" -) - -// peerDropFn is a callback type for dropping a peer detected as malicious. -type peerDropFn func(id string) - -// dataPack is a data message returned by a peer for some query. -type dataPack interface { - PeerId() string - Items() int - Stats() string -} - -// headerPack is a batch of block headers returned by a peer. -type headerPack struct { - peerID string - headers []*types.Header -} - -func (p *headerPack) PeerId() string { return p.peerID } -func (p *headerPack) Items() int { return len(p.headers) } -func (p *headerPack) Stats() string { return fmt.Sprintf("%d", len(p.headers)) } - -// bodyPack is a batch of block bodies returned by a peer. -type bodyPack struct { - peerID string - transactions [][]*types.Transaction - uncles [][]*types.Header -} - -func (p *bodyPack) PeerId() string { return p.peerID } -func (p *bodyPack) Items() int { - if len(p.transactions) <= len(p.uncles) { - return len(p.transactions) - } - return len(p.uncles) -} -func (p *bodyPack) Stats() string { return fmt.Sprintf("%d:%d", len(p.transactions), len(p.uncles)) } - -// receiptPack is a batch of receipts returned by a peer. -type receiptPack struct { - peerID string - receipts [][]*types.Receipt -} - -func (p *receiptPack) PeerId() string { return p.peerID } -func (p *receiptPack) Items() int { return len(p.receipts) } -func (p *receiptPack) Stats() string { return fmt.Sprintf("%d", len(p.receipts)) } - -// statePack is a batch of states returned by a peer. -type statePack struct { - peerID string - states [][]byte -} - -func (p *statePack) PeerId() string { return p.peerID } -func (p *statePack) Items() int { return len(p.states) } -func (p *statePack) Stats() string { return fmt.Sprintf("%d", len(p.states)) } diff --git a/les/fetcher.go b/les/fetcher.go deleted file mode 100644 index df81ca4d9bd1..000000000000 --- a/les/fetcher.go +++ /dev/null @@ -1,563 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package les - -import ( - "math/big" - "math/rand" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/les/fetcher" - "github.com/ethereum/go-ethereum/light" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/enode" -) - -const ( - blockDelayTimeout = 10 * time.Second // Timeout for retrieving the headers from the peer - gatherSlack = 100 * time.Millisecond // Interval used to collate almost-expired requests - cachedAnnosThreshold = 64 // The maximum queued announcements -) - -// announce represents an new block announcement from the les server. -type announce struct { - data *announceData - trust bool - peerid enode.ID -} - -// request represents a record when the header request is sent. -type request struct { - reqid uint64 - peerid enode.ID - sendAt time.Time - hash common.Hash -} - -// response represents a response packet from network as well as a channel -// to return all un-requested data. -type response struct { - reqid uint64 - headers []*types.Header - peerid enode.ID - remain chan []*types.Header -} - -// fetcherPeer holds the fetcher-specific information for each active peer -type fetcherPeer struct { - latest *announceData // The latest announcement sent from the peer - - // These following two fields can track the latest announces - // from the peer with limited size for caching. We hold the - // assumption that all enqueued announces are td-monotonic. - announces map[common.Hash]*announce // Announcement map - fifo []common.Hash // FIFO announces list -} - -// addAnno enqueues an new trusted announcement. If the queued announces overflow, -// evict from the oldest. -func (fp *fetcherPeer) addAnno(anno *announce) { - // Short circuit if the anno already exists. In normal case it should - // never happen since only monotonic anno is accepted. But the adversary - // may feed us fake announces with higher td but same hash. In this case, - // ignore the anno anyway. - hash := anno.data.Hash - if _, exist := fp.announces[hash]; exist { - return - } - fp.announces[hash] = anno - fp.fifo = append(fp.fifo, hash) - - // Evict oldest if the announces are oversized. - if len(fp.fifo)-cachedAnnosThreshold > 0 { - for i := 0; i < len(fp.fifo)-cachedAnnosThreshold; i++ { - delete(fp.announces, fp.fifo[i]) - } - copy(fp.fifo, fp.fifo[len(fp.fifo)-cachedAnnosThreshold:]) - fp.fifo = fp.fifo[:cachedAnnosThreshold] - } -} - -// forwardAnno removes all announces from the map with a number lower than -// the provided threshold. -func (fp *fetcherPeer) forwardAnno(td *big.Int) []*announce { - var ( - cutset int - evicted []*announce - ) - for ; cutset < len(fp.fifo); cutset++ { - anno := fp.announces[fp.fifo[cutset]] - if anno == nil { - continue // In theory it should never ever happen - } - if anno.data.Td.Cmp(td) > 0 { - break - } - evicted = append(evicted, anno) - delete(fp.announces, anno.data.Hash) - } - if cutset > 0 { - copy(fp.fifo, fp.fifo[cutset:]) - fp.fifo = fp.fifo[:len(fp.fifo)-cutset] - } - return evicted -} - -// lightFetcher implements retrieval of newly announced headers. It reuses -// the eth.BlockFetcher as the underlying fetcher but adding more additional -// rules: e.g. evict "timeout" peers. -type lightFetcher struct { - // Various handlers - ulc *ulc - chaindb ethdb.Database - reqDist *requestDistributor - peerset *serverPeerSet // The global peerset of light client which shared by all components - chain *light.LightChain // The local light chain which maintains the canonical header chain. - fetcher *fetcher.BlockFetcher // The underlying fetcher which takes care block header retrieval. - - // Peerset maintained by fetcher - plock sync.RWMutex - peers map[enode.ID]*fetcherPeer - - // Various channels - announceCh chan *announce - requestCh chan *request - deliverCh chan *response - syncDone chan *types.Header - - closeCh chan struct{} - wg sync.WaitGroup - - // Callback - synchronise func(peer *serverPeer) - - // Test fields or hooks - newHeadHook func(*types.Header) -} - -// newLightFetcher creates a light fetcher instance. -func newLightFetcher(chain *light.LightChain, engine consensus.Engine, peers *serverPeerSet, ulc *ulc, chaindb ethdb.Database, reqDist *requestDistributor, syncFn func(p *serverPeer)) *lightFetcher { - // Construct the fetcher by offering all necessary APIs - validator := func(header *types.Header) error { - // Disable seal verification explicitly if we are running in ulc mode. - return engine.VerifyHeader(chain, header) - } - heighter := func() uint64 { return chain.CurrentHeader().Number.Uint64() } - dropper := func(id string) { peers.unregister(id) } - inserter := func(headers []*types.Header) (int, error) { return chain.InsertHeaderChain(headers) } - f := &lightFetcher{ - ulc: ulc, - peerset: peers, - chaindb: chaindb, - chain: chain, - reqDist: reqDist, - fetcher: fetcher.NewBlockFetcher(true, chain.GetHeaderByHash, nil, validator, nil, heighter, inserter, nil, dropper), - peers: make(map[enode.ID]*fetcherPeer), - synchronise: syncFn, - announceCh: make(chan *announce), - requestCh: make(chan *request), - deliverCh: make(chan *response), - syncDone: make(chan *types.Header), - closeCh: make(chan struct{}), - } - peers.subscribe(f) - return f -} - -func (f *lightFetcher) start() { - f.wg.Add(1) - f.fetcher.Start() - go f.mainloop() -} - -func (f *lightFetcher) stop() { - close(f.closeCh) - f.fetcher.Stop() - f.wg.Wait() -} - -// registerPeer adds an new peer to the fetcher's peer set -func (f *lightFetcher) registerPeer(p *serverPeer) { - f.plock.Lock() - defer f.plock.Unlock() - - f.peers[p.ID()] = &fetcherPeer{announces: make(map[common.Hash]*announce)} -} - -// unregisterPeer removes the specified peer from the fetcher's peer set -func (f *lightFetcher) unregisterPeer(p *serverPeer) { - f.plock.Lock() - defer f.plock.Unlock() - - delete(f.peers, p.ID()) -} - -// peer returns the peer from the fetcher peerset. -func (f *lightFetcher) peer(id enode.ID) *fetcherPeer { - f.plock.RLock() - defer f.plock.RUnlock() - - return f.peers[id] -} - -// forEachPeer iterates the fetcher peerset, abort the iteration if the -// callback returns false. -func (f *lightFetcher) forEachPeer(check func(id enode.ID, p *fetcherPeer) bool) { - f.plock.RLock() - defer f.plock.RUnlock() - - for id, peer := range f.peers { - if !check(id, peer) { - return - } - } -} - -// mainloop is the main event loop of the light fetcher, which is responsible for -// -// - announcement maintenance(ulc) -// -// If we are running in ultra light client mode, then all announcements from -// the trusted servers are maintained. If the same announcements from trusted -// servers reach the threshold, then the relevant header is requested for retrieval. -// -// - block header retrieval -// Whenever we receive announce with higher td compared with local chain, the -// request will be made for header retrieval. -// -// - re-sync trigger -// If the local chain lags too much, then the fetcher will enter "synchronise" -// mode to retrieve missing headers in batch. -func (f *lightFetcher) mainloop() { - defer f.wg.Done() - - var ( - syncInterval = uint64(1) // Interval used to trigger a light resync. - syncing bool // Indicator whether the client is syncing - - ulc = f.ulc != nil - headCh = make(chan core.ChainHeadEvent, 100) - fetching = make(map[uint64]*request) - requestTimer = time.NewTimer(0) - - // Local status - localHead = f.chain.CurrentHeader() - localTd = f.chain.GetTd(localHead.Hash(), localHead.Number.Uint64()) - ) - defer requestTimer.Stop() - sub := f.chain.SubscribeChainHeadEvent(headCh) - defer sub.Unsubscribe() - - // reset updates the local status with given header. - reset := func(header *types.Header) { - localHead = header - localTd = f.chain.GetTd(header.Hash(), header.Number.Uint64()) - } - // trustedHeader returns an indicator whether the header is regarded as - // trusted. If we are running in the ulc mode, only when we receive enough - // same announcement from trusted server, the header will be trusted. - trustedHeader := func(hash common.Hash, number uint64) (bool, []enode.ID) { - var ( - agreed []enode.ID - trusted bool - ) - f.forEachPeer(func(id enode.ID, p *fetcherPeer) bool { - if anno := p.announces[hash]; anno != nil && anno.trust && anno.data.Number == number { - agreed = append(agreed, id) - if 100*len(agreed)/len(f.ulc.keys) >= f.ulc.fraction { - trusted = true - return false // abort iteration - } - } - return true - }) - return trusted, agreed - } - for { - select { - case anno := <-f.announceCh: - peerid, data := anno.peerid, anno.data - log.Debug("Received new announce", "peer", peerid, "number", data.Number, "hash", data.Hash, "reorg", data.ReorgDepth) - - peer := f.peer(peerid) - if peer == nil { - log.Debug("Receive announce from unknown peer", "peer", peerid) - continue - } - // Announced tds should be strictly monotonic, drop the peer if - // the announce is out-of-order. - if peer.latest != nil && data.Td.Cmp(peer.latest.Td) <= 0 { - f.peerset.unregister(peerid.String()) - log.Debug("Non-monotonic td", "peer", peerid, "current", data.Td, "previous", peer.latest.Td) - continue - } - peer.latest = data - - // Filter out any stale announce, the local chain is ahead of announce - if localTd != nil && data.Td.Cmp(localTd) <= 0 { - continue - } - peer.addAnno(anno) - - // If we are not syncing, try to trigger a single retrieval or re-sync - if !ulc && !syncing { - // Two scenarios lead to re-sync: - // - reorg happens - // - local chain lags - // We can't retrieve the parent of the announce by single retrieval - // in both cases, so resync is necessary. - if data.Number > localHead.Number.Uint64()+syncInterval || data.ReorgDepth > 0 { - syncing = true - go f.startSync(peerid) - log.Debug("Trigger light sync", "peer", peerid, "local", localHead.Number, "localhash", localHead.Hash(), "remote", data.Number, "remotehash", data.Hash) - continue - } - f.fetcher.Notify(peerid.String(), data.Hash, data.Number, time.Now(), f.requestHeaderByHash(peerid), nil) - log.Debug("Trigger header retrieval", "peer", peerid, "number", data.Number, "hash", data.Hash) - } - // Keep collecting announces from trusted server even we are syncing. - if ulc && anno.trust { - // Notify underlying fetcher to retrieve header or trigger a resync if - // we have receive enough announcements from trusted server. - trusted, agreed := trustedHeader(data.Hash, data.Number) - if trusted && !syncing { - if data.Number > localHead.Number.Uint64()+syncInterval || data.ReorgDepth > 0 { - syncing = true - go f.startSync(peerid) - log.Debug("Trigger trusted light sync", "local", localHead.Number, "localhash", localHead.Hash(), "remote", data.Number, "remotehash", data.Hash) - continue - } - p := agreed[rand.Intn(len(agreed))] - f.fetcher.Notify(p.String(), data.Hash, data.Number, time.Now(), f.requestHeaderByHash(p), nil) - log.Debug("Trigger trusted header retrieval", "number", data.Number, "hash", data.Hash) - } - } - - case req := <-f.requestCh: - fetching[req.reqid] = req // Tracking all in-flight requests for response latency statistic. - if len(fetching) == 1 { - f.rescheduleTimer(fetching, requestTimer) - } - - case <-requestTimer.C: - for reqid, request := range fetching { - if time.Since(request.sendAt) > blockDelayTimeout-gatherSlack { - delete(fetching, reqid) - f.peerset.unregister(request.peerid.String()) - log.Debug("Request timeout", "peer", request.peerid, "reqid", reqid) - } - } - f.rescheduleTimer(fetching, requestTimer) - - case resp := <-f.deliverCh: - if req := fetching[resp.reqid]; req != nil { - delete(fetching, resp.reqid) - f.rescheduleTimer(fetching, requestTimer) - - // The underlying fetcher does not check the consistency of request and response. - // The adversary can send the fake announces with invalid hash and number but always - // delivery some mismatched header. So it can't be punished by the underlying fetcher. - // We have to add two more rules here to detect. - if len(resp.headers) != 1 { - f.peerset.unregister(req.peerid.String()) - log.Debug("Deliver more than requested", "peer", req.peerid, "reqid", req.reqid) - continue - } - if resp.headers[0].Hash() != req.hash { - f.peerset.unregister(req.peerid.String()) - log.Debug("Deliver invalid header", "peer", req.peerid, "reqid", req.reqid) - continue - } - resp.remain <- f.fetcher.FilterHeaders(resp.peerid.String(), resp.headers, time.Now()) - } else { - // Discard the entire packet no matter it's a timeout response or unexpected one. - resp.remain <- resp.headers - } - - case ev := <-headCh: - // Short circuit if we are still syncing. - if syncing { - continue - } - reset(ev.Block.Header()) - - // Clean stale announcements from les-servers. - var droplist []enode.ID - f.forEachPeer(func(id enode.ID, p *fetcherPeer) bool { - removed := p.forwardAnno(localTd) - for _, anno := range removed { - if header := f.chain.GetHeaderByHash(anno.data.Hash); header != nil { - if header.Number.Uint64() != anno.data.Number { - droplist = append(droplist, id) - break - } - // In theory td should exists. - td := f.chain.GetTd(anno.data.Hash, anno.data.Number) - if td != nil && td.Cmp(anno.data.Td) != 0 { - droplist = append(droplist, id) - break - } - } - } - return true - }) - for _, id := range droplist { - f.peerset.unregister(id.String()) - log.Debug("Kicked out peer for invalid announcement") - } - if f.newHeadHook != nil { - f.newHeadHook(localHead) - } - - case origin := <-f.syncDone: - syncing = false // Reset the status - - // Rewind all untrusted headers for ulc mode. - if ulc { - head := f.chain.CurrentHeader() - ancestor := rawdb.FindCommonAncestor(f.chaindb, origin, head) - - // Recap the ancestor with genesis header in case the ancestor - // is not found. It can happen the original head is before the - // checkpoint while the synced headers are after it. In this - // case there is no ancestor between them. - if ancestor == nil { - ancestor = f.chain.Genesis().Header() - } - var untrusted []common.Hash - for head.Number.Cmp(ancestor.Number) > 0 { - hash, number := head.Hash(), head.Number.Uint64() - if trusted, _ := trustedHeader(hash, number); trusted { - break - } - untrusted = append(untrusted, hash) - head = f.chain.GetHeader(head.ParentHash, number-1) - if head == nil { - break // all the synced headers will be dropped - } - } - if len(untrusted) > 0 { - for i, j := 0, len(untrusted)-1; i < j; i, j = i+1, j-1 { - untrusted[i], untrusted[j] = untrusted[j], untrusted[i] - } - f.chain.Rollback(untrusted) - } - } - // Reset local status. - reset(f.chain.CurrentHeader()) - if f.newHeadHook != nil { - f.newHeadHook(localHead) - } - log.Debug("light sync finished", "number", localHead.Number, "hash", localHead.Hash()) - - case <-f.closeCh: - return - } - } -} - -// announce processes a new announcement message received from a peer. -func (f *lightFetcher) announce(p *serverPeer, head *announceData) { - select { - case f.announceCh <- &announce{peerid: p.ID(), trust: p.trusted, data: head}: - case <-f.closeCh: - return - } -} - -// trackRequest sends a reqID to main loop for in-flight request tracking. -func (f *lightFetcher) trackRequest(peerid enode.ID, reqid uint64, hash common.Hash) { - select { - case f.requestCh <- &request{reqid: reqid, peerid: peerid, sendAt: time.Now(), hash: hash}: - case <-f.closeCh: - } -} - -// requestHeaderByHash constructs a header retrieval request and sends it to -// local request distributor. -// -// Note, we rely on the underlying eth/fetcher to retrieve and validate the -// response, so that we have to obey the rule of eth/fetcher which only accepts -// the response from given peer. -func (f *lightFetcher) requestHeaderByHash(peerid enode.ID) func(common.Hash) error { - return func(hash common.Hash) error { - req := &distReq{ - getCost: func(dp distPeer) uint64 { return dp.(*serverPeer).getRequestCost(GetBlockHeadersMsg, 1) }, - canSend: func(dp distPeer) bool { return dp.(*serverPeer).ID() == peerid }, - request: func(dp distPeer) func() { - peer, id := dp.(*serverPeer), rand.Uint64() - cost := peer.getRequestCost(GetBlockHeadersMsg, 1) - peer.fcServer.QueuedRequest(id, cost) - - return func() { - f.trackRequest(peer.ID(), id, hash) - peer.requestHeadersByHash(id, hash, 1, 0, false) - } - }, - } - f.reqDist.queue(req) - return nil - } -} - -// startSync invokes synchronisation callback to start syncing. -func (f *lightFetcher) startSync(id enode.ID) { - defer func(header *types.Header) { - f.syncDone <- header - }(f.chain.CurrentHeader()) - - peer := f.peerset.peer(id.String()) - if peer == nil || peer.onlyAnnounce { - return - } - f.synchronise(peer) -} - -// deliverHeaders delivers header download request responses for processing -func (f *lightFetcher) deliverHeaders(peer *serverPeer, reqid uint64, headers []*types.Header) []*types.Header { - remain := make(chan []*types.Header, 1) - select { - case f.deliverCh <- &response{reqid: reqid, headers: headers, peerid: peer.ID(), remain: remain}: - case <-f.closeCh: - return nil - } - return <-remain -} - -// rescheduleTimer resets the specified timeout timer to the next request timeout. -func (f *lightFetcher) rescheduleTimer(requests map[uint64]*request, timer *time.Timer) { - // Short circuit if no inflight requests - if len(requests) == 0 { - timer.Stop() - return - } - // Otherwise find the earliest expiring request - earliest := time.Now() - for _, req := range requests { - if earliest.After(req.sendAt) { - earliest = req.sendAt - } - } - timer.Reset(blockDelayTimeout - time.Since(earliest)) -} diff --git a/les/fetcher/block_fetcher.go b/les/fetcher/block_fetcher.go deleted file mode 100644 index 085ecb2d665b..000000000000 --- a/les/fetcher/block_fetcher.go +++ /dev/null @@ -1,888 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package fetcher is a temporary package whilst working on the eth/66 blocking refactors. -// After that work is done, les needs to be refactored to use the new package, -// or alternatively use a stripped down version of it. Either way, we need to -// keep the changes scoped so duplicating temporarily seems the sanest. -package fetcher - -import ( - "errors" - "math/rand" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/prque" - "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/trie" -) - -const ( - lightTimeout = time.Millisecond // Time allowance before an announced header is explicitly requested - arriveTimeout = 500 * time.Millisecond // Time allowance before an announced block/transaction is explicitly requested - gatherSlack = 100 * time.Millisecond // Interval used to collate almost-expired announces with fetches - fetchTimeout = 5 * time.Second // Maximum allotted time to return an explicitly requested block/transaction -) - -const ( - maxUncleDist = 7 // Maximum allowed backward distance from the chain head - maxQueueDist = 32 // Maximum allowed distance from the chain head to queue - hashLimit = 256 // Maximum number of unique blocks or headers a peer may have announced - blockLimit = 64 // Maximum number of unique blocks a peer may have delivered -) - -var ( - blockAnnounceInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/in", nil) - blockAnnounceOutTimer = metrics.NewRegisteredTimer("eth/fetcher/block/announces/out", nil) - blockAnnounceDropMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/drop", nil) - blockAnnounceDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/block/announces/dos", nil) - - blockBroadcastInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/in", nil) - blockBroadcastOutTimer = metrics.NewRegisteredTimer("eth/fetcher/block/broadcasts/out", nil) - blockBroadcastDropMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/drop", nil) - blockBroadcastDOSMeter = metrics.NewRegisteredMeter("eth/fetcher/block/broadcasts/dos", nil) - - headerFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/block/headers", nil) - bodyFetchMeter = metrics.NewRegisteredMeter("eth/fetcher/block/bodies", nil) - - headerFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/headers/in", nil) - headerFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/headers/out", nil) - bodyFilterInMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/bodies/in", nil) - bodyFilterOutMeter = metrics.NewRegisteredMeter("eth/fetcher/block/filter/bodies/out", nil) -) - -var errTerminated = errors.New("terminated") - -// HeaderRetrievalFn is a callback type for retrieving a header from the local chain. -type HeaderRetrievalFn func(common.Hash) *types.Header - -// blockRetrievalFn is a callback type for retrieving a block from the local chain. -type blockRetrievalFn func(common.Hash) *types.Block - -// headerRequesterFn is a callback type for sending a header retrieval request. -type headerRequesterFn func(common.Hash) error - -// bodyRequesterFn is a callback type for sending a body retrieval request. -type bodyRequesterFn func([]common.Hash) error - -// headerVerifierFn is a callback type to verify a block's header for fast propagation. -type headerVerifierFn func(header *types.Header) error - -// blockBroadcasterFn is a callback type for broadcasting a block to connected peers. -type blockBroadcasterFn func(block *types.Block, propagate bool) - -// chainHeightFn is a callback type to retrieve the current chain height. -type chainHeightFn func() uint64 - -// headersInsertFn is a callback type to insert a batch of headers into the local chain. -type headersInsertFn func(headers []*types.Header) (int, error) - -// chainInsertFn is a callback type to insert a batch of blocks into the local chain. -type chainInsertFn func(types.Blocks) (int, error) - -// peerDropFn is a callback type for dropping a peer detected as malicious. -type peerDropFn func(id string) - -// blockAnnounce is the hash notification of the availability of a new block in the -// network. -type blockAnnounce struct { - hash common.Hash // Hash of the block being announced - number uint64 // Number of the block being announced (0 = unknown | old protocol) - header *types.Header // Header of the block partially reassembled (new protocol) - time time.Time // Timestamp of the announcement - - origin string // Identifier of the peer originating the notification - - fetchHeader headerRequesterFn // Fetcher function to retrieve the header of an announced block - fetchBodies bodyRequesterFn // Fetcher function to retrieve the body of an announced block -} - -// headerFilterTask represents a batch of headers needing fetcher filtering. -type headerFilterTask struct { - peer string // The source peer of block headers - headers []*types.Header // Collection of headers to filter - time time.Time // Arrival time of the headers -} - -// bodyFilterTask represents a batch of block bodies (transactions and uncles) -// needing fetcher filtering. -type bodyFilterTask struct { - peer string // The source peer of block bodies - transactions [][]*types.Transaction // Collection of transactions per block bodies - uncles [][]*types.Header // Collection of uncles per block bodies - time time.Time // Arrival time of the blocks' contents -} - -// blockOrHeaderInject represents a schedules import operation. -type blockOrHeaderInject struct { - origin string - - header *types.Header // Used for light mode fetcher which only cares about header. - block *types.Block // Used for normal mode fetcher which imports full block. -} - -// number returns the block number of the injected object. -func (inject *blockOrHeaderInject) number() uint64 { - if inject.header != nil { - return inject.header.Number.Uint64() - } - return inject.block.NumberU64() -} - -// number returns the block hash of the injected object. -func (inject *blockOrHeaderInject) hash() common.Hash { - if inject.header != nil { - return inject.header.Hash() - } - return inject.block.Hash() -} - -// BlockFetcher is responsible for accumulating block announcements from various peers -// and scheduling them for retrieval. -type BlockFetcher struct { - light bool // The indicator whether it's a light fetcher or normal one. - - // Various event channels - notify chan *blockAnnounce - inject chan *blockOrHeaderInject - - headerFilter chan chan *headerFilterTask - bodyFilter chan chan *bodyFilterTask - - done chan common.Hash - quit chan struct{} - - // Announce states - announces map[string]int // Per peer blockAnnounce counts to prevent memory exhaustion - announced map[common.Hash][]*blockAnnounce // Announced blocks, scheduled for fetching - fetching map[common.Hash]*blockAnnounce // Announced blocks, currently fetching - fetched map[common.Hash][]*blockAnnounce // Blocks with headers fetched, scheduled for body retrieval - completing map[common.Hash]*blockAnnounce // Blocks with headers, currently body-completing - - // Block cache - queue *prque.Prque[int64, *blockOrHeaderInject] // Queue containing the import operations (block number sorted) - queues map[string]int // Per peer block counts to prevent memory exhaustion - queued map[common.Hash]*blockOrHeaderInject // Set of already queued blocks (to dedup imports) - - // Callbacks - getHeader HeaderRetrievalFn // Retrieves a header from the local chain - getBlock blockRetrievalFn // Retrieves a block from the local chain - verifyHeader headerVerifierFn // Checks if a block's headers have a valid proof of work - broadcastBlock blockBroadcasterFn // Broadcasts a block to connected peers - chainHeight chainHeightFn // Retrieves the current chain's height - insertHeaders headersInsertFn // Injects a batch of headers into the chain - insertChain chainInsertFn // Injects a batch of blocks into the chain - dropPeer peerDropFn // Drops a peer for misbehaving - - // Testing hooks - announceChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a hash from the blockAnnounce list - queueChangeHook func(common.Hash, bool) // Method to call upon adding or deleting a block from the import queue - fetchingHook func([]common.Hash) // Method to call upon starting a block (eth/61) or header (eth/62) fetch - completingHook func([]common.Hash) // Method to call upon starting a block body fetch (eth/62) - importedHook func(*types.Header, *types.Block) // Method to call upon successful header or block import (both eth/61 and eth/62) -} - -// NewBlockFetcher creates a block fetcher to retrieve blocks based on hash announcements. -func NewBlockFetcher(light bool, getHeader HeaderRetrievalFn, getBlock blockRetrievalFn, verifyHeader headerVerifierFn, broadcastBlock blockBroadcasterFn, chainHeight chainHeightFn, insertHeaders headersInsertFn, insertChain chainInsertFn, dropPeer peerDropFn) *BlockFetcher { - return &BlockFetcher{ - light: light, - notify: make(chan *blockAnnounce), - inject: make(chan *blockOrHeaderInject), - headerFilter: make(chan chan *headerFilterTask), - bodyFilter: make(chan chan *bodyFilterTask), - done: make(chan common.Hash), - quit: make(chan struct{}), - announces: make(map[string]int), - announced: make(map[common.Hash][]*blockAnnounce), - fetching: make(map[common.Hash]*blockAnnounce), - fetched: make(map[common.Hash][]*blockAnnounce), - completing: make(map[common.Hash]*blockAnnounce), - queue: prque.New[int64, *blockOrHeaderInject](nil), - queues: make(map[string]int), - queued: make(map[common.Hash]*blockOrHeaderInject), - getHeader: getHeader, - getBlock: getBlock, - verifyHeader: verifyHeader, - broadcastBlock: broadcastBlock, - chainHeight: chainHeight, - insertHeaders: insertHeaders, - insertChain: insertChain, - dropPeer: dropPeer, - } -} - -// Start boots up the announcement based synchroniser, accepting and processing -// hash notifications and block fetches until termination requested. -func (f *BlockFetcher) Start() { - go f.loop() -} - -// Stop terminates the announcement based synchroniser, canceling all pending -// operations. -func (f *BlockFetcher) Stop() { - close(f.quit) -} - -// Notify announces the fetcher of the potential availability of a new block in -// the network. -func (f *BlockFetcher) Notify(peer string, hash common.Hash, number uint64, time time.Time, - headerFetcher headerRequesterFn, bodyFetcher bodyRequesterFn) error { - block := &blockAnnounce{ - hash: hash, - number: number, - time: time, - origin: peer, - fetchHeader: headerFetcher, - fetchBodies: bodyFetcher, - } - select { - case f.notify <- block: - return nil - case <-f.quit: - return errTerminated - } -} - -// Enqueue tries to fill gaps the fetcher's future import queue. -func (f *BlockFetcher) Enqueue(peer string, block *types.Block) error { - op := &blockOrHeaderInject{ - origin: peer, - block: block, - } - select { - case f.inject <- op: - return nil - case <-f.quit: - return errTerminated - } -} - -// FilterHeaders extracts all the headers that were explicitly requested by the fetcher, -// returning those that should be handled differently. -func (f *BlockFetcher) FilterHeaders(peer string, headers []*types.Header, time time.Time) []*types.Header { - log.Trace("Filtering headers", "peer", peer, "headers", len(headers)) - - // Send the filter channel to the fetcher - filter := make(chan *headerFilterTask) - - select { - case f.headerFilter <- filter: - case <-f.quit: - return nil - } - // Request the filtering of the header list - select { - case filter <- &headerFilterTask{peer: peer, headers: headers, time: time}: - case <-f.quit: - return nil - } - // Retrieve the headers remaining after filtering - select { - case task := <-filter: - return task.headers - case <-f.quit: - return nil - } -} - -// FilterBodies extracts all the block bodies that were explicitly requested by -// the fetcher, returning those that should be handled differently. -func (f *BlockFetcher) FilterBodies(peer string, transactions [][]*types.Transaction, uncles [][]*types.Header, time time.Time) ([][]*types.Transaction, [][]*types.Header) { - log.Trace("Filtering bodies", "peer", peer, "txs", len(transactions), "uncles", len(uncles)) - - // Send the filter channel to the fetcher - filter := make(chan *bodyFilterTask) - - select { - case f.bodyFilter <- filter: - case <-f.quit: - return nil, nil - } - // Request the filtering of the body list - select { - case filter <- &bodyFilterTask{peer: peer, transactions: transactions, uncles: uncles, time: time}: - case <-f.quit: - return nil, nil - } - // Retrieve the bodies remaining after filtering - select { - case task := <-filter: - return task.transactions, task.uncles - case <-f.quit: - return nil, nil - } -} - -// Loop is the main fetcher loop, checking and processing various notification -// events. -func (f *BlockFetcher) loop() { - // Iterate the block fetching until a quit is requested - var ( - fetchTimer = time.NewTimer(0) - completeTimer = time.NewTimer(0) - ) - <-fetchTimer.C // clear out the channel - <-completeTimer.C - defer fetchTimer.Stop() - defer completeTimer.Stop() - - for { - // Clean up any expired block fetches - for hash, announce := range f.fetching { - if time.Since(announce.time) > fetchTimeout { - f.forgetHash(hash) - } - } - // Import any queued blocks that could potentially fit - height := f.chainHeight() - for !f.queue.Empty() { - op := f.queue.PopItem() - hash := op.hash() - if f.queueChangeHook != nil { - f.queueChangeHook(hash, false) - } - // If too high up the chain or phase, continue later - number := op.number() - if number > height+1 { - f.queue.Push(op, -int64(number)) - if f.queueChangeHook != nil { - f.queueChangeHook(hash, true) - } - break - } - // Otherwise if fresh and still unknown, try and import - if (number+maxUncleDist < height) || (f.light && f.getHeader(hash) != nil) || (!f.light && f.getBlock(hash) != nil) { - f.forgetBlock(hash) - continue - } - if f.light { - f.importHeaders(op.origin, op.header) - } else { - f.importBlocks(op.origin, op.block) - } - } - // Wait for an outside event to occur - select { - case <-f.quit: - // BlockFetcher terminating, abort all operations - return - - case notification := <-f.notify: - // A block was announced, make sure the peer isn't DOSing us - blockAnnounceInMeter.Mark(1) - - count := f.announces[notification.origin] + 1 - if count > hashLimit { - log.Debug("Peer exceeded outstanding announces", "peer", notification.origin, "limit", hashLimit) - blockAnnounceDOSMeter.Mark(1) - break - } - // If we have a valid block number, check that it's potentially useful - if notification.number > 0 { - if dist := int64(notification.number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist { - log.Debug("Peer discarded announcement", "peer", notification.origin, "number", notification.number, "hash", notification.hash, "distance", dist) - blockAnnounceDropMeter.Mark(1) - break - } - } - // All is well, schedule the announce if block's not yet downloading - if _, ok := f.fetching[notification.hash]; ok { - break - } - if _, ok := f.completing[notification.hash]; ok { - break - } - f.announces[notification.origin] = count - f.announced[notification.hash] = append(f.announced[notification.hash], notification) - if f.announceChangeHook != nil && len(f.announced[notification.hash]) == 1 { - f.announceChangeHook(notification.hash, true) - } - if len(f.announced) == 1 { - f.rescheduleFetch(fetchTimer) - } - - case op := <-f.inject: - // A direct block insertion was requested, try and fill any pending gaps - blockBroadcastInMeter.Mark(1) - - // Now only direct block injection is allowed, drop the header injection - // here silently if we receive. - if f.light { - continue - } - f.enqueue(op.origin, nil, op.block) - - case hash := <-f.done: - // A pending import finished, remove all traces of the notification - f.forgetHash(hash) - f.forgetBlock(hash) - - case <-fetchTimer.C: - // At least one block's timer ran out, check for needing retrieval - request := make(map[string][]common.Hash) - - for hash, announces := range f.announced { - // In current LES protocol(les2/les3), only header announce is - // available, no need to wait too much time for header broadcast. - timeout := arriveTimeout - gatherSlack - if f.light { - timeout = 0 - } - if time.Since(announces[0].time) > timeout { - // Pick a random peer to retrieve from, reset all others - announce := announces[rand.Intn(len(announces))] - f.forgetHash(hash) - - // If the block still didn't arrive, queue for fetching - if (f.light && f.getHeader(hash) == nil) || (!f.light && f.getBlock(hash) == nil) { - request[announce.origin] = append(request[announce.origin], hash) - f.fetching[hash] = announce - } - } - } - // Send out all block header requests - for peer, hashes := range request { - log.Trace("Fetching scheduled headers", "peer", peer, "list", hashes) - - // Create a closure of the fetch and schedule in on a new thread - fetchHeader, hashes := f.fetching[hashes[0]].fetchHeader, hashes - go func() { - if f.fetchingHook != nil { - f.fetchingHook(hashes) - } - for _, hash := range hashes { - headerFetchMeter.Mark(1) - fetchHeader(hash) // Suboptimal, but protocol doesn't allow batch header retrievals - } - }() - } - // Schedule the next fetch if blocks are still pending - f.rescheduleFetch(fetchTimer) - - case <-completeTimer.C: - // At least one header's timer ran out, retrieve everything - request := make(map[string][]common.Hash) - - for hash, announces := range f.fetched { - // Pick a random peer to retrieve from, reset all others - announce := announces[rand.Intn(len(announces))] - f.forgetHash(hash) - - // If the block still didn't arrive, queue for completion - if f.getBlock(hash) == nil { - request[announce.origin] = append(request[announce.origin], hash) - f.completing[hash] = announce - } - } - // Send out all block body requests - for peer, hashes := range request { - log.Trace("Fetching scheduled bodies", "peer", peer, "list", hashes) - - // Create a closure of the fetch and schedule in on a new thread - if f.completingHook != nil { - f.completingHook(hashes) - } - bodyFetchMeter.Mark(int64(len(hashes))) - go f.completing[hashes[0]].fetchBodies(hashes) - } - // Schedule the next fetch if blocks are still pending - f.rescheduleComplete(completeTimer) - - case filter := <-f.headerFilter: - // Headers arrived from a remote peer. Extract those that were explicitly - // requested by the fetcher, and return everything else so it's delivered - // to other parts of the system. - var task *headerFilterTask - select { - case task = <-filter: - case <-f.quit: - return - } - headerFilterInMeter.Mark(int64(len(task.headers))) - - // Split the batch of headers into unknown ones (to return to the caller), - // known incomplete ones (requiring body retrievals) and completed blocks. - unknown, incomplete, complete, lightHeaders := []*types.Header{}, []*blockAnnounce{}, []*types.Block{}, []*blockAnnounce{} - for _, header := range task.headers { - hash := header.Hash() - - // Filter fetcher-requested headers from other synchronisation algorithms - if announce := f.fetching[hash]; announce != nil && announce.origin == task.peer && f.fetched[hash] == nil && f.completing[hash] == nil && f.queued[hash] == nil { - // If the delivered header does not match the promised number, drop the announcer - if header.Number.Uint64() != announce.number { - log.Trace("Invalid block number fetched", "peer", announce.origin, "hash", header.Hash(), "announced", announce.number, "provided", header.Number) - f.dropPeer(announce.origin) - f.forgetHash(hash) - continue - } - // Collect all headers only if we are running in light - // mode and the headers are not imported by other means. - if f.light { - if f.getHeader(hash) == nil { - announce.header = header - lightHeaders = append(lightHeaders, announce) - } - f.forgetHash(hash) - continue - } - // Only keep if not imported by other means - if f.getBlock(hash) == nil { - announce.header = header - announce.time = task.time - - // If the block is empty (header only), short circuit into the final import queue - if header.TxHash == types.EmptyTxsHash && header.UncleHash == types.EmptyUncleHash { - log.Trace("Block empty, skipping body retrieval", "peer", announce.origin, "number", header.Number, "hash", header.Hash()) - - block := types.NewBlockWithHeader(header) - block.ReceivedAt = task.time - - complete = append(complete, block) - f.completing[hash] = announce - continue - } - // Otherwise add to the list of blocks needing completion - incomplete = append(incomplete, announce) - } else { - log.Trace("Block already imported, discarding header", "peer", announce.origin, "number", header.Number, "hash", header.Hash()) - f.forgetHash(hash) - } - } else { - // BlockFetcher doesn't know about it, add to the return list - unknown = append(unknown, header) - } - } - headerFilterOutMeter.Mark(int64(len(unknown))) - select { - case filter <- &headerFilterTask{headers: unknown, time: task.time}: - case <-f.quit: - return - } - // Schedule the retrieved headers for body completion - for _, announce := range incomplete { - hash := announce.header.Hash() - if _, ok := f.completing[hash]; ok { - continue - } - f.fetched[hash] = append(f.fetched[hash], announce) - if len(f.fetched) == 1 { - f.rescheduleComplete(completeTimer) - } - } - // Schedule the header for light fetcher import - for _, announce := range lightHeaders { - f.enqueue(announce.origin, announce.header, nil) - } - // Schedule the header-only blocks for import - for _, block := range complete { - if announce := f.completing[block.Hash()]; announce != nil { - f.enqueue(announce.origin, nil, block) - } - } - - case filter := <-f.bodyFilter: - // Block bodies arrived, extract any explicitly requested blocks, return the rest - var task *bodyFilterTask - select { - case task = <-filter: - case <-f.quit: - return - } - bodyFilterInMeter.Mark(int64(len(task.transactions))) - blocks := []*types.Block{} - // abort early if there's nothing explicitly requested - if len(f.completing) > 0 { - for i := 0; i < len(task.transactions) && i < len(task.uncles); i++ { - // Match up a body to any possible completion request - var ( - matched = false - uncleHash common.Hash // calculated lazily and reused - txnHash common.Hash // calculated lazily and reused - ) - for hash, announce := range f.completing { - if f.queued[hash] != nil || announce.origin != task.peer { - continue - } - if uncleHash == (common.Hash{}) { - uncleHash = types.CalcUncleHash(task.uncles[i]) - } - if uncleHash != announce.header.UncleHash { - continue - } - if txnHash == (common.Hash{}) { - txnHash = types.DeriveSha(types.Transactions(task.transactions[i]), trie.NewStackTrie(nil)) - } - if txnHash != announce.header.TxHash { - continue - } - // Mark the body matched, reassemble if still unknown - matched = true - if f.getBlock(hash) == nil { - block := types.NewBlockWithHeader(announce.header).WithBody(task.transactions[i], task.uncles[i]) - block.ReceivedAt = task.time - blocks = append(blocks, block) - } else { - f.forgetHash(hash) - } - } - if matched { - task.transactions = append(task.transactions[:i], task.transactions[i+1:]...) - task.uncles = append(task.uncles[:i], task.uncles[i+1:]...) - i-- - continue - } - } - } - bodyFilterOutMeter.Mark(int64(len(task.transactions))) - select { - case filter <- task: - case <-f.quit: - return - } - // Schedule the retrieved blocks for ordered import - for _, block := range blocks { - if announce := f.completing[block.Hash()]; announce != nil { - f.enqueue(announce.origin, nil, block) - } - } - } - } -} - -// rescheduleFetch resets the specified fetch timer to the next blockAnnounce timeout. -func (f *BlockFetcher) rescheduleFetch(fetch *time.Timer) { - // Short circuit if no blocks are announced - if len(f.announced) == 0 { - return - } - // Schedule announcement retrieval quickly for light mode - // since server won't send any headers to client. - if f.light { - fetch.Reset(lightTimeout) - return - } - // Otherwise find the earliest expiring announcement - earliest := time.Now() - for _, announces := range f.announced { - if earliest.After(announces[0].time) { - earliest = announces[0].time - } - } - fetch.Reset(arriveTimeout - time.Since(earliest)) -} - -// rescheduleComplete resets the specified completion timer to the next fetch timeout. -func (f *BlockFetcher) rescheduleComplete(complete *time.Timer) { - // Short circuit if no headers are fetched - if len(f.fetched) == 0 { - return - } - // Otherwise find the earliest expiring announcement - earliest := time.Now() - for _, announces := range f.fetched { - if earliest.After(announces[0].time) { - earliest = announces[0].time - } - } - complete.Reset(gatherSlack - time.Since(earliest)) -} - -// enqueue schedules a new header or block import operation, if the component -// to be imported has not yet been seen. -func (f *BlockFetcher) enqueue(peer string, header *types.Header, block *types.Block) { - var ( - hash common.Hash - number uint64 - ) - if header != nil { - hash, number = header.Hash(), header.Number.Uint64() - } else { - hash, number = block.Hash(), block.NumberU64() - } - // Ensure the peer isn't DOSing us - count := f.queues[peer] + 1 - if count > blockLimit { - log.Debug("Discarded delivered header or block, exceeded allowance", "peer", peer, "number", number, "hash", hash, "limit", blockLimit) - blockBroadcastDOSMeter.Mark(1) - f.forgetHash(hash) - return - } - // Discard any past or too distant blocks - if dist := int64(number) - int64(f.chainHeight()); dist < -maxUncleDist || dist > maxQueueDist { - log.Debug("Discarded delivered header or block, too far away", "peer", peer, "number", number, "hash", hash, "distance", dist) - blockBroadcastDropMeter.Mark(1) - f.forgetHash(hash) - return - } - // Schedule the block for future importing - if _, ok := f.queued[hash]; !ok { - op := &blockOrHeaderInject{origin: peer} - if header != nil { - op.header = header - } else { - op.block = block - } - f.queues[peer] = count - f.queued[hash] = op - f.queue.Push(op, -int64(number)) - if f.queueChangeHook != nil { - f.queueChangeHook(hash, true) - } - log.Debug("Queued delivered header or block", "peer", peer, "number", number, "hash", hash, "queued", f.queue.Size()) - } -} - -// importHeaders spawns a new goroutine to run a header insertion into the chain. -// If the header's number is at the same height as the current import phase, it -// updates the phase states accordingly. -func (f *BlockFetcher) importHeaders(peer string, header *types.Header) { - hash := header.Hash() - log.Debug("Importing propagated header", "peer", peer, "number", header.Number, "hash", hash) - - go func() { - defer func() { f.done <- hash }() - // If the parent's unknown, abort insertion - parent := f.getHeader(header.ParentHash) - if parent == nil { - log.Debug("Unknown parent of propagated header", "peer", peer, "number", header.Number, "hash", hash, "parent", header.ParentHash) - return - } - // Validate the header and if something went wrong, drop the peer - if err := f.verifyHeader(header); err != nil && err != consensus.ErrFutureBlock { - log.Debug("Propagated header verification failed", "peer", peer, "number", header.Number, "hash", hash, "err", err) - f.dropPeer(peer) - return - } - // Run the actual import and log any issues - if _, err := f.insertHeaders([]*types.Header{header}); err != nil { - log.Debug("Propagated header import failed", "peer", peer, "number", header.Number, "hash", hash, "err", err) - return - } - // Invoke the testing hook if needed - if f.importedHook != nil { - f.importedHook(header, nil) - } - }() -} - -// importBlocks spawns a new goroutine to run a block insertion into the chain. If the -// block's number is at the same height as the current import phase, it updates -// the phase states accordingly. -func (f *BlockFetcher) importBlocks(peer string, block *types.Block) { - hash := block.Hash() - - // Run the import on a new thread - log.Debug("Importing propagated block", "peer", peer, "number", block.Number(), "hash", hash) - go func() { - defer func() { f.done <- hash }() - - // If the parent's unknown, abort insertion - parent := f.getBlock(block.ParentHash()) - if parent == nil { - log.Debug("Unknown parent of propagated block", "peer", peer, "number", block.Number(), "hash", hash, "parent", block.ParentHash()) - return - } - // Quickly validate the header and propagate the block if it passes - switch err := f.verifyHeader(block.Header()); err { - case nil: - // All ok, quickly propagate to our peers - blockBroadcastOutTimer.UpdateSince(block.ReceivedAt) - go f.broadcastBlock(block, true) - - case consensus.ErrFutureBlock: - // Weird future block, don't fail, but neither propagate - - default: - // Something went very wrong, drop the peer - log.Debug("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err) - f.dropPeer(peer) - return - } - // Run the actual import and log any issues - if _, err := f.insertChain(types.Blocks{block}); err != nil { - log.Debug("Propagated block import failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err) - return - } - // If import succeeded, broadcast the block - blockAnnounceOutTimer.UpdateSince(block.ReceivedAt) - go f.broadcastBlock(block, false) - - // Invoke the testing hook if needed - if f.importedHook != nil { - f.importedHook(nil, block) - } - }() -} - -// forgetHash removes all traces of a block announcement from the fetcher's -// internal state. -func (f *BlockFetcher) forgetHash(hash common.Hash) { - // Remove all pending announces and decrement DOS counters - if announceMap, ok := f.announced[hash]; ok { - for _, announce := range announceMap { - f.announces[announce.origin]-- - if f.announces[announce.origin] <= 0 { - delete(f.announces, announce.origin) - } - } - delete(f.announced, hash) - if f.announceChangeHook != nil { - f.announceChangeHook(hash, false) - } - } - // Remove any pending fetches and decrement the DOS counters - if announce := f.fetching[hash]; announce != nil { - f.announces[announce.origin]-- - if f.announces[announce.origin] <= 0 { - delete(f.announces, announce.origin) - } - delete(f.fetching, hash) - } - - // Remove any pending completion requests and decrement the DOS counters - for _, announce := range f.fetched[hash] { - f.announces[announce.origin]-- - if f.announces[announce.origin] <= 0 { - delete(f.announces, announce.origin) - } - } - delete(f.fetched, hash) - - // Remove any pending completions and decrement the DOS counters - if announce := f.completing[hash]; announce != nil { - f.announces[announce.origin]-- - if f.announces[announce.origin] <= 0 { - delete(f.announces, announce.origin) - } - delete(f.completing, hash) - } -} - -// forgetBlock removes all traces of a queued block from the fetcher's internal -// state. -func (f *BlockFetcher) forgetBlock(hash common.Hash) { - if insert := f.queued[hash]; insert != nil { - f.queues[insert.origin]-- - if f.queues[insert.origin] == 0 { - delete(f.queues, insert.origin) - } - delete(f.queued, hash) - } -} diff --git a/les/fetcher/block_fetcher_test.go b/les/fetcher/block_fetcher_test.go deleted file mode 100644 index f29cf588056d..000000000000 --- a/les/fetcher/block_fetcher_test.go +++ /dev/null @@ -1,901 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package fetcher - -import ( - "errors" - "math/big" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/trie" -) - -var ( - testdb = rawdb.NewMemoryDatabase() - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testAddress = crypto.PubkeyToAddress(testKey.PublicKey) - - gspec = core.Genesis{ - Alloc: core.GenesisAlloc{testAddress: {Balance: big.NewInt(1000000000000000)}}, - BaseFee: big.NewInt(params.InitialBaseFee), - } - genesis = gspec.MustCommit(testdb) - unknownBlock = types.NewBlock(&types.Header{Root: types.EmptyRootHash, GasLimit: params.GenesisGasLimit, BaseFee: big.NewInt(params.InitialBaseFee)}, nil, nil, nil, trie.NewStackTrie(nil)) -) - -// makeChain creates a chain of n blocks starting at and including parent. -// the returned hash chain is ordered head->parent. In addition, every 3rd block -// contains a transaction and every 5th an uncle to allow testing correct block -// reassembly. -func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) { - blocks, _ := core.GenerateChain(params.TestChainConfig, parent, ethash.NewFaker(), testdb, n, func(i int, block *core.BlockGen) { - block.SetCoinbase(common.Address{seed}) - - // If the block number is multiple of 3, send a bonus transaction to the miner - if parent == genesis && i%3 == 0 { - signer := types.MakeSigner(params.TestChainConfig, block.Number(), block.Timestamp()) - tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddress), common.Address{seed}, big.NewInt(1000), params.TxGas, block.BaseFee(), nil), signer, testKey) - if err != nil { - panic(err) - } - block.AddTx(tx) - } - // If the block number is a multiple of 5, add a bonus uncle to the block - if i > 0 && i%5 == 0 { - block.AddUncle(&types.Header{ParentHash: block.PrevBlock(i - 2).Hash(), Number: big.NewInt(int64(i - 1))}) - } - }) - hashes := make([]common.Hash, n+1) - hashes[len(hashes)-1] = parent.Hash() - blockm := make(map[common.Hash]*types.Block, n+1) - blockm[parent.Hash()] = parent - for i, b := range blocks { - hashes[len(hashes)-i-2] = b.Hash() - blockm[b.Hash()] = b - } - return hashes, blockm -} - -// fetcherTester is a test simulator for mocking out local block chain. -type fetcherTester struct { - fetcher *BlockFetcher - - hashes []common.Hash // Hash chain belonging to the tester - headers map[common.Hash]*types.Header // Headers belonging to the tester - blocks map[common.Hash]*types.Block // Blocks belonging to the tester - drops map[string]bool // Map of peers dropped by the fetcher - - lock sync.RWMutex -} - -// newTester creates a new fetcher test mocker. -func newTester(light bool) *fetcherTester { - tester := &fetcherTester{ - hashes: []common.Hash{genesis.Hash()}, - headers: map[common.Hash]*types.Header{genesis.Hash(): genesis.Header()}, - blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis}, - drops: make(map[string]bool), - } - tester.fetcher = NewBlockFetcher(light, tester.getHeader, tester.getBlock, tester.verifyHeader, tester.broadcastBlock, tester.chainHeight, tester.insertHeaders, tester.insertChain, tester.dropPeer) - tester.fetcher.Start() - - return tester -} - -// getHeader retrieves a header from the tester's block chain. -func (f *fetcherTester) getHeader(hash common.Hash) *types.Header { - f.lock.RLock() - defer f.lock.RUnlock() - - return f.headers[hash] -} - -// getBlock retrieves a block from the tester's block chain. -func (f *fetcherTester) getBlock(hash common.Hash) *types.Block { - f.lock.RLock() - defer f.lock.RUnlock() - - return f.blocks[hash] -} - -// verifyHeader is a nop placeholder for the block header verification. -func (f *fetcherTester) verifyHeader(header *types.Header) error { - return nil -} - -// broadcastBlock is a nop placeholder for the block broadcasting. -func (f *fetcherTester) broadcastBlock(block *types.Block, propagate bool) { -} - -// chainHeight retrieves the current height (block number) of the chain. -func (f *fetcherTester) chainHeight() uint64 { - f.lock.RLock() - defer f.lock.RUnlock() - - if f.fetcher.light { - return f.headers[f.hashes[len(f.hashes)-1]].Number.Uint64() - } - return f.blocks[f.hashes[len(f.hashes)-1]].NumberU64() -} - -// insertChain injects a new headers into the simulated chain. -func (f *fetcherTester) insertHeaders(headers []*types.Header) (int, error) { - f.lock.Lock() - defer f.lock.Unlock() - - for i, header := range headers { - // Make sure the parent in known - if _, ok := f.headers[header.ParentHash]; !ok { - return i, errors.New("unknown parent") - } - // Discard any new blocks if the same height already exists - if header.Number.Uint64() <= f.headers[f.hashes[len(f.hashes)-1]].Number.Uint64() { - return i, nil - } - // Otherwise build our current chain - f.hashes = append(f.hashes, header.Hash()) - f.headers[header.Hash()] = header - } - return 0, nil -} - -// insertChain injects a new blocks into the simulated chain. -func (f *fetcherTester) insertChain(blocks types.Blocks) (int, error) { - f.lock.Lock() - defer f.lock.Unlock() - - for i, block := range blocks { - // Make sure the parent in known - if _, ok := f.blocks[block.ParentHash()]; !ok { - return i, errors.New("unknown parent") - } - // Discard any new blocks if the same height already exists - if block.NumberU64() <= f.blocks[f.hashes[len(f.hashes)-1]].NumberU64() { - return i, nil - } - // Otherwise build our current chain - f.hashes = append(f.hashes, block.Hash()) - f.blocks[block.Hash()] = block - } - return 0, nil -} - -// dropPeer is an emulator for the peer removal, simply accumulating the various -// peers dropped by the fetcher. -func (f *fetcherTester) dropPeer(peer string) { - f.lock.Lock() - defer f.lock.Unlock() - - f.drops[peer] = true -} - -// makeHeaderFetcher retrieves a block header fetcher associated with a simulated peer. -func (f *fetcherTester) makeHeaderFetcher(peer string, blocks map[common.Hash]*types.Block, drift time.Duration) headerRequesterFn { - closure := make(map[common.Hash]*types.Block) - for hash, block := range blocks { - closure[hash] = block - } - // Create a function that return a header from the closure - return func(hash common.Hash) error { - // Gather the blocks to return - headers := make([]*types.Header, 0, 1) - if block, ok := closure[hash]; ok { - headers = append(headers, block.Header()) - } - // Return on a new thread - go f.fetcher.FilterHeaders(peer, headers, time.Now().Add(drift)) - - return nil - } -} - -// makeBodyFetcher retrieves a block body fetcher associated with a simulated peer. -func (f *fetcherTester) makeBodyFetcher(peer string, blocks map[common.Hash]*types.Block, drift time.Duration) bodyRequesterFn { - closure := make(map[common.Hash]*types.Block) - for hash, block := range blocks { - closure[hash] = block - } - // Create a function that returns blocks from the closure - return func(hashes []common.Hash) error { - // Gather the block bodies to return - transactions := make([][]*types.Transaction, 0, len(hashes)) - uncles := make([][]*types.Header, 0, len(hashes)) - - for _, hash := range hashes { - if block, ok := closure[hash]; ok { - transactions = append(transactions, block.Transactions()) - uncles = append(uncles, block.Uncles()) - } - } - // Return on a new thread - go f.fetcher.FilterBodies(peer, transactions, uncles, time.Now().Add(drift)) - - return nil - } -} - -// verifyFetchingEvent verifies that one single event arrive on a fetching channel. -func verifyFetchingEvent(t *testing.T, fetching chan []common.Hash, arrive bool) { - if arrive { - select { - case <-fetching: - case <-time.After(time.Second): - t.Fatalf("fetching timeout") - } - } else { - select { - case <-fetching: - t.Fatalf("fetching invoked") - case <-time.After(10 * time.Millisecond): - } - } -} - -// verifyCompletingEvent verifies that one single event arrive on an completing channel. -func verifyCompletingEvent(t *testing.T, completing chan []common.Hash, arrive bool) { - if arrive { - select { - case <-completing: - case <-time.After(time.Second): - t.Fatalf("completing timeout") - } - } else { - select { - case <-completing: - t.Fatalf("completing invoked") - case <-time.After(10 * time.Millisecond): - } - } -} - -// verifyImportEvent verifies that one single event arrive on an import channel. -func verifyImportEvent(t *testing.T, imported chan interface{}, arrive bool) { - if arrive { - select { - case <-imported: - case <-time.After(time.Second): - t.Fatalf("import timeout") - } - } else { - select { - case <-imported: - t.Fatalf("import invoked") - case <-time.After(20 * time.Millisecond): - } - } -} - -// verifyImportCount verifies that exactly count number of events arrive on an -// import hook channel. -func verifyImportCount(t *testing.T, imported chan interface{}, count int) { - for i := 0; i < count; i++ { - select { - case <-imported: - case <-time.After(time.Second): - t.Fatalf("block %d: import timeout", i+1) - } - } - verifyImportDone(t, imported) -} - -// verifyImportDone verifies that no more events are arriving on an import channel. -func verifyImportDone(t *testing.T, imported chan interface{}) { - select { - case <-imported: - t.Fatalf("extra block imported") - case <-time.After(50 * time.Millisecond): - } -} - -// verifyChainHeight verifies the chain height is as expected. -func verifyChainHeight(t *testing.T, fetcher *fetcherTester, height uint64) { - if fetcher.chainHeight() != height { - t.Fatalf("chain height mismatch, got %d, want %d", fetcher.chainHeight(), height) - } -} - -// Tests that a fetcher accepts block/header announcements and initiates retrievals -// for them, successfully importing into the local chain. -func TestFullSequentialAnnouncements(t *testing.T) { testSequentialAnnouncements(t, false) } -func TestLightSequentialAnnouncements(t *testing.T) { testSequentialAnnouncements(t, true) } - -func testSequentialAnnouncements(t *testing.T, light bool) { - // Create a chain of blocks to import - targetBlocks := 4 * hashLimit - hashes, blocks := makeChain(targetBlocks, 0, genesis) - - tester := newTester(light) - headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) - bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) - - // Iteratively announce blocks until all are imported - imported := make(chan interface{}) - tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { - if light { - if header == nil { - t.Fatalf("Fetcher try to import empty header") - } - imported <- header - } else { - if block == nil { - t.Fatalf("Fetcher try to import empty block") - } - imported <- block - } - } - for i := len(hashes) - 2; i >= 0; i-- { - tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) - verifyImportEvent(t, imported, true) - } - verifyImportDone(t, imported) - verifyChainHeight(t, tester, uint64(len(hashes)-1)) -} - -// Tests that if blocks are announced by multiple peers (or even the same buggy -// peer), they will only get downloaded at most once. -func TestFullConcurrentAnnouncements(t *testing.T) { testConcurrentAnnouncements(t, false) } -func TestLightConcurrentAnnouncements(t *testing.T) { testConcurrentAnnouncements(t, true) } - -func testConcurrentAnnouncements(t *testing.T, light bool) { - // Create a chain of blocks to import - targetBlocks := 4 * hashLimit - hashes, blocks := makeChain(targetBlocks, 0, genesis) - - // Assemble a tester with a built in counter for the requests - tester := newTester(light) - firstHeaderFetcher := tester.makeHeaderFetcher("first", blocks, -gatherSlack) - firstBodyFetcher := tester.makeBodyFetcher("first", blocks, 0) - secondHeaderFetcher := tester.makeHeaderFetcher("second", blocks, -gatherSlack) - secondBodyFetcher := tester.makeBodyFetcher("second", blocks, 0) - - counter := uint32(0) - firstHeaderWrapper := func(hash common.Hash) error { - atomic.AddUint32(&counter, 1) - return firstHeaderFetcher(hash) - } - secondHeaderWrapper := func(hash common.Hash) error { - atomic.AddUint32(&counter, 1) - return secondHeaderFetcher(hash) - } - // Iteratively announce blocks until all are imported - imported := make(chan interface{}) - tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { - if light { - if header == nil { - t.Fatalf("Fetcher try to import empty header") - } - imported <- header - } else { - if block == nil { - t.Fatalf("Fetcher try to import empty block") - } - imported <- block - } - } - for i := len(hashes) - 2; i >= 0; i-- { - tester.fetcher.Notify("first", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), firstHeaderWrapper, firstBodyFetcher) - tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout+time.Millisecond), secondHeaderWrapper, secondBodyFetcher) - tester.fetcher.Notify("second", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout-time.Millisecond), secondHeaderWrapper, secondBodyFetcher) - verifyImportEvent(t, imported, true) - } - verifyImportDone(t, imported) - - // Make sure no blocks were retrieved twice - if int(counter) != targetBlocks { - t.Fatalf("retrieval count mismatch: have %v, want %v", counter, targetBlocks) - } - verifyChainHeight(t, tester, uint64(len(hashes)-1)) -} - -// Tests that announcements arriving while a previous is being fetched still -// results in a valid import. -func TestFullOverlappingAnnouncements(t *testing.T) { testOverlappingAnnouncements(t, false) } -func TestLightOverlappingAnnouncements(t *testing.T) { testOverlappingAnnouncements(t, true) } - -func testOverlappingAnnouncements(t *testing.T, light bool) { - // Create a chain of blocks to import - targetBlocks := 4 * hashLimit - hashes, blocks := makeChain(targetBlocks, 0, genesis) - - tester := newTester(light) - headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) - bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) - - // Iteratively announce blocks, but overlap them continuously - overlap := 16 - imported := make(chan interface{}, len(hashes)-1) - for i := 0; i < overlap; i++ { - imported <- nil - } - tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { - if light { - if header == nil { - t.Fatalf("Fetcher try to import empty header") - } - imported <- header - } else { - if block == nil { - t.Fatalf("Fetcher try to import empty block") - } - imported <- block - } - } - - for i := len(hashes) - 2; i >= 0; i-- { - tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) - select { - case <-imported: - case <-time.After(time.Second): - t.Fatalf("block %d: import timeout", len(hashes)-i) - } - } - // Wait for all the imports to complete and check count - verifyImportCount(t, imported, overlap) - verifyChainHeight(t, tester, uint64(len(hashes)-1)) -} - -// Tests that announces already being retrieved will not be duplicated. -func TestFullPendingDeduplication(t *testing.T) { testPendingDeduplication(t, false) } -func TestLightPendingDeduplication(t *testing.T) { testPendingDeduplication(t, true) } - -func testPendingDeduplication(t *testing.T, light bool) { - // Create a hash and corresponding block - hashes, blocks := makeChain(1, 0, genesis) - - // Assemble a tester with a built in counter and delayed fetcher - tester := newTester(light) - headerFetcher := tester.makeHeaderFetcher("repeater", blocks, -gatherSlack) - bodyFetcher := tester.makeBodyFetcher("repeater", blocks, 0) - - delay := 50 * time.Millisecond - counter := uint32(0) - headerWrapper := func(hash common.Hash) error { - atomic.AddUint32(&counter, 1) - - // Simulate a long running fetch - go func() { - time.Sleep(delay) - headerFetcher(hash) - }() - return nil - } - checkNonExist := func() bool { - return tester.getBlock(hashes[0]) == nil - } - if light { - checkNonExist = func() bool { - return tester.getHeader(hashes[0]) == nil - } - } - // Announce the same block many times until it's fetched (wait for any pending ops) - for checkNonExist() { - tester.fetcher.Notify("repeater", hashes[0], 1, time.Now().Add(-arriveTimeout), headerWrapper, bodyFetcher) - time.Sleep(time.Millisecond) - } - time.Sleep(delay) - - // Check that all blocks were imported and none fetched twice - if int(counter) != 1 { - t.Fatalf("retrieval count mismatch: have %v, want %v", counter, 1) - } - verifyChainHeight(t, tester, 1) -} - -// Tests that announcements retrieved in a random order are cached and eventually -// imported when all the gaps are filled in. -func TestFullRandomArrivalImport(t *testing.T) { testRandomArrivalImport(t, false) } -func TestLightRandomArrivalImport(t *testing.T) { testRandomArrivalImport(t, true) } - -func testRandomArrivalImport(t *testing.T, light bool) { - // Create a chain of blocks to import, and choose one to delay - targetBlocks := maxQueueDist - hashes, blocks := makeChain(targetBlocks, 0, genesis) - skip := targetBlocks / 2 - - tester := newTester(light) - headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) - bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) - - // Iteratively announce blocks, skipping one entry - imported := make(chan interface{}, len(hashes)-1) - tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { - if light { - if header == nil { - t.Fatalf("Fetcher try to import empty header") - } - imported <- header - } else { - if block == nil { - t.Fatalf("Fetcher try to import empty block") - } - imported <- block - } - } - for i := len(hashes) - 1; i >= 0; i-- { - if i != skip { - tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) - time.Sleep(time.Millisecond) - } - } - // Finally announce the skipped entry and check full import - tester.fetcher.Notify("valid", hashes[skip], uint64(len(hashes)-skip-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) - verifyImportCount(t, imported, len(hashes)-1) - verifyChainHeight(t, tester, uint64(len(hashes)-1)) -} - -// Tests that direct block enqueues (due to block propagation vs. hash announce) -// are correctly schedule, filling and import queue gaps. -func TestQueueGapFill(t *testing.T) { - // Create a chain of blocks to import, and choose one to not announce at all - targetBlocks := maxQueueDist - hashes, blocks := makeChain(targetBlocks, 0, genesis) - skip := targetBlocks / 2 - - tester := newTester(false) - headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) - bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) - - // Iteratively announce blocks, skipping one entry - imported := make(chan interface{}, len(hashes)-1) - tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block } - - for i := len(hashes) - 1; i >= 0; i-- { - if i != skip { - tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) - time.Sleep(time.Millisecond) - } - } - // Fill the missing block directly as if propagated - tester.fetcher.Enqueue("valid", blocks[hashes[skip]]) - verifyImportCount(t, imported, len(hashes)-1) - verifyChainHeight(t, tester, uint64(len(hashes)-1)) -} - -// Tests that blocks arriving from various sources (multiple propagations, hash -// announces, etc) do not get scheduled for import multiple times. -func TestImportDeduplication(t *testing.T) { - // Create two blocks to import (one for duplication, the other for stalling) - hashes, blocks := makeChain(2, 0, genesis) - - // Create the tester and wrap the importer with a counter - tester := newTester(false) - headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) - bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) - - counter := uint32(0) - tester.fetcher.insertChain = func(blocks types.Blocks) (int, error) { - atomic.AddUint32(&counter, uint32(len(blocks))) - return tester.insertChain(blocks) - } - // Instrument the fetching and imported events - fetching := make(chan []common.Hash) - imported := make(chan interface{}, len(hashes)-1) - tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- hashes } - tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block } - - // Announce the duplicating block, wait for retrieval, and also propagate directly - tester.fetcher.Notify("valid", hashes[0], 1, time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) - <-fetching - - tester.fetcher.Enqueue("valid", blocks[hashes[0]]) - tester.fetcher.Enqueue("valid", blocks[hashes[0]]) - tester.fetcher.Enqueue("valid", blocks[hashes[0]]) - - // Fill the missing block directly as if propagated, and check import uniqueness - tester.fetcher.Enqueue("valid", blocks[hashes[1]]) - verifyImportCount(t, imported, 2) - - if counter != 2 { - t.Fatalf("import invocation count mismatch: have %v, want %v", counter, 2) - } -} - -// Tests that blocks with numbers much lower or higher than out current head get -// discarded to prevent wasting resources on useless blocks from faulty peers. -func TestDistantPropagationDiscarding(t *testing.T) { - // Create a long chain to import and define the discard boundaries - hashes, blocks := makeChain(3*maxQueueDist, 0, genesis) - head := hashes[len(hashes)/2] - - low, high := len(hashes)/2+maxUncleDist+1, len(hashes)/2-maxQueueDist-1 - - // Create a tester and simulate a head block being the middle of the above chain - tester := newTester(false) - - tester.lock.Lock() - tester.hashes = []common.Hash{head} - tester.blocks = map[common.Hash]*types.Block{head: blocks[head]} - tester.lock.Unlock() - - // Ensure that a block with a lower number than the threshold is discarded - tester.fetcher.Enqueue("lower", blocks[hashes[low]]) - time.Sleep(10 * time.Millisecond) - if !tester.fetcher.queue.Empty() { - t.Fatalf("fetcher queued stale block") - } - // Ensure that a block with a higher number than the threshold is discarded - tester.fetcher.Enqueue("higher", blocks[hashes[high]]) - time.Sleep(10 * time.Millisecond) - if !tester.fetcher.queue.Empty() { - t.Fatalf("fetcher queued future block") - } -} - -// Tests that announcements with numbers much lower or higher than out current -// head get discarded to prevent wasting resources on useless blocks from faulty -// peers. -func TestFullDistantAnnouncementDiscarding(t *testing.T) { testDistantAnnouncementDiscarding(t, false) } -func TestLightDistantAnnouncementDiscarding(t *testing.T) { testDistantAnnouncementDiscarding(t, true) } - -func testDistantAnnouncementDiscarding(t *testing.T, light bool) { - // Create a long chain to import and define the discard boundaries - hashes, blocks := makeChain(3*maxQueueDist, 0, genesis) - head := hashes[len(hashes)/2] - - low, high := len(hashes)/2+maxUncleDist+1, len(hashes)/2-maxQueueDist-1 - - // Create a tester and simulate a head block being the middle of the above chain - tester := newTester(light) - - tester.lock.Lock() - tester.hashes = []common.Hash{head} - tester.headers = map[common.Hash]*types.Header{head: blocks[head].Header()} - tester.blocks = map[common.Hash]*types.Block{head: blocks[head]} - tester.lock.Unlock() - - headerFetcher := tester.makeHeaderFetcher("lower", blocks, -gatherSlack) - bodyFetcher := tester.makeBodyFetcher("lower", blocks, 0) - - fetching := make(chan struct{}, 2) - tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- struct{}{} } - - // Ensure that a block with a lower number than the threshold is discarded - tester.fetcher.Notify("lower", hashes[low], blocks[hashes[low]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) - select { - case <-time.After(50 * time.Millisecond): - case <-fetching: - t.Fatalf("fetcher requested stale header") - } - // Ensure that a block with a higher number than the threshold is discarded - tester.fetcher.Notify("higher", hashes[high], blocks[hashes[high]].NumberU64(), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) - select { - case <-time.After(50 * time.Millisecond): - case <-fetching: - t.Fatalf("fetcher requested future header") - } -} - -// Tests that peers announcing blocks with invalid numbers (i.e. not matching -// the headers provided afterwards) get dropped as malicious. -func TestFullInvalidNumberAnnouncement(t *testing.T) { testInvalidNumberAnnouncement(t, false) } -func TestLightInvalidNumberAnnouncement(t *testing.T) { testInvalidNumberAnnouncement(t, true) } - -func testInvalidNumberAnnouncement(t *testing.T, light bool) { - // Create a single block to import and check numbers against - hashes, blocks := makeChain(1, 0, genesis) - - tester := newTester(light) - badHeaderFetcher := tester.makeHeaderFetcher("bad", blocks, -gatherSlack) - badBodyFetcher := tester.makeBodyFetcher("bad", blocks, 0) - - imported := make(chan interface{}) - announced := make(chan interface{}) - tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { - if light { - if header == nil { - t.Fatalf("Fetcher try to import empty header") - } - imported <- header - } else { - if block == nil { - t.Fatalf("Fetcher try to import empty block") - } - imported <- block - } - } - // Announce a block with a bad number, check for immediate drop - tester.fetcher.announceChangeHook = func(hash common.Hash, b bool) { - announced <- nil - } - tester.fetcher.Notify("bad", hashes[0], 2, time.Now().Add(-arriveTimeout), badHeaderFetcher, badBodyFetcher) - verifyAnnounce := func() { - for i := 0; i < 2; i++ { - select { - case <-announced: - continue - case <-time.After(1 * time.Second): - t.Fatal("announce timeout") - return - } - } - } - verifyAnnounce() - verifyImportEvent(t, imported, false) - tester.lock.RLock() - dropped := tester.drops["bad"] - tester.lock.RUnlock() - - if !dropped { - t.Fatalf("peer with invalid numbered announcement not dropped") - } - goodHeaderFetcher := tester.makeHeaderFetcher("good", blocks, -gatherSlack) - goodBodyFetcher := tester.makeBodyFetcher("good", blocks, 0) - // Make sure a good announcement passes without a drop - tester.fetcher.Notify("good", hashes[0], 1, time.Now().Add(-arriveTimeout), goodHeaderFetcher, goodBodyFetcher) - verifyAnnounce() - verifyImportEvent(t, imported, true) - - tester.lock.RLock() - dropped = tester.drops["good"] - tester.lock.RUnlock() - - if dropped { - t.Fatalf("peer with valid numbered announcement dropped") - } - verifyImportDone(t, imported) -} - -// Tests that if a block is empty (i.e. header only), no body request should be -// made, and instead the header should be assembled into a whole block in itself. -func TestEmptyBlockShortCircuit(t *testing.T) { - // Create a chain of blocks to import - hashes, blocks := makeChain(32, 0, genesis) - - tester := newTester(false) - headerFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) - bodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) - - // Add a monitoring hook for all internal events - fetching := make(chan []common.Hash) - tester.fetcher.fetchingHook = func(hashes []common.Hash) { fetching <- hashes } - - completing := make(chan []common.Hash) - tester.fetcher.completingHook = func(hashes []common.Hash) { completing <- hashes } - - imported := make(chan interface{}) - tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { - if block == nil { - t.Fatalf("Fetcher try to import empty block") - } - imported <- block - } - // Iteratively announce blocks until all are imported - for i := len(hashes) - 2; i >= 0; i-- { - tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), headerFetcher, bodyFetcher) - - // All announces should fetch the header - verifyFetchingEvent(t, fetching, true) - - // Only blocks with data contents should request bodies - verifyCompletingEvent(t, completing, len(blocks[hashes[i]].Transactions()) > 0 || len(blocks[hashes[i]].Uncles()) > 0) - - // Irrelevant of the construct, import should succeed - verifyImportEvent(t, imported, true) - } - verifyImportDone(t, imported) -} - -// Tests that a peer is unable to use unbounded memory with sending infinite -// block announcements to a node, but that even in the face of such an attack, -// the fetcher remains operational. -func TestHashMemoryExhaustionAttack(t *testing.T) { - // Create a tester with instrumented import hooks - tester := newTester(false) - - imported, announces := make(chan interface{}), int32(0) - tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block } - tester.fetcher.announceChangeHook = func(hash common.Hash, added bool) { - if added { - atomic.AddInt32(&announces, 1) - } else { - atomic.AddInt32(&announces, -1) - } - } - // Create a valid chain and an infinite junk chain - targetBlocks := hashLimit + 2*maxQueueDist - hashes, blocks := makeChain(targetBlocks, 0, genesis) - validHeaderFetcher := tester.makeHeaderFetcher("valid", blocks, -gatherSlack) - validBodyFetcher := tester.makeBodyFetcher("valid", blocks, 0) - - attack, _ := makeChain(targetBlocks, 0, unknownBlock) - attackerHeaderFetcher := tester.makeHeaderFetcher("attacker", nil, -gatherSlack) - attackerBodyFetcher := tester.makeBodyFetcher("attacker", nil, 0) - - // Feed the tester a huge hashset from the attacker, and a limited from the valid peer - for i := 0; i < len(attack); i++ { - if i < maxQueueDist { - tester.fetcher.Notify("valid", hashes[len(hashes)-2-i], uint64(i+1), time.Now(), validHeaderFetcher, validBodyFetcher) - } - tester.fetcher.Notify("attacker", attack[i], 1 /* don't distance drop */, time.Now(), attackerHeaderFetcher, attackerBodyFetcher) - } - if count := atomic.LoadInt32(&announces); count != hashLimit+maxQueueDist { - t.Fatalf("queued announce count mismatch: have %d, want %d", count, hashLimit+maxQueueDist) - } - // Wait for fetches to complete - verifyImportCount(t, imported, maxQueueDist) - - // Feed the remaining valid hashes to ensure DOS protection state remains clean - for i := len(hashes) - maxQueueDist - 2; i >= 0; i-- { - tester.fetcher.Notify("valid", hashes[i], uint64(len(hashes)-i-1), time.Now().Add(-arriveTimeout), validHeaderFetcher, validBodyFetcher) - verifyImportEvent(t, imported, true) - } - verifyImportDone(t, imported) -} - -// Tests that blocks sent to the fetcher (either through propagation or via hash -// announces and retrievals) don't pile up indefinitely, exhausting available -// system memory. -func TestBlockMemoryExhaustionAttack(t *testing.T) { - // Create a tester with instrumented import hooks - tester := newTester(false) - - imported, enqueued := make(chan interface{}), int32(0) - tester.fetcher.importedHook = func(header *types.Header, block *types.Block) { imported <- block } - tester.fetcher.queueChangeHook = func(hash common.Hash, added bool) { - if added { - atomic.AddInt32(&enqueued, 1) - } else { - atomic.AddInt32(&enqueued, -1) - } - } - // Create a valid chain and a batch of dangling (but in range) blocks - targetBlocks := hashLimit + 2*maxQueueDist - hashes, blocks := makeChain(targetBlocks, 0, genesis) - attack := make(map[common.Hash]*types.Block) - for i := byte(0); len(attack) < blockLimit+2*maxQueueDist; i++ { - hashes, blocks := makeChain(maxQueueDist-1, i, unknownBlock) - for _, hash := range hashes[:maxQueueDist-2] { - attack[hash] = blocks[hash] - } - } - // Try to feed all the attacker blocks make sure only a limited batch is accepted - for _, block := range attack { - tester.fetcher.Enqueue("attacker", block) - } - time.Sleep(200 * time.Millisecond) - if queued := atomic.LoadInt32(&enqueued); queued != blockLimit { - t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit) - } - // Queue up a batch of valid blocks, and check that a new peer is allowed to do so - for i := 0; i < maxQueueDist-1; i++ { - tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-3-i]]) - } - time.Sleep(100 * time.Millisecond) - if queued := atomic.LoadInt32(&enqueued); queued != blockLimit+maxQueueDist-1 { - t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit+maxQueueDist-1) - } - // Insert the missing piece (and sanity check the import) - tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-2]]) - verifyImportCount(t, imported, maxQueueDist) - - // Insert the remaining blocks in chunks to ensure clean DOS protection - for i := maxQueueDist; i < len(hashes)-1; i++ { - tester.fetcher.Enqueue("valid", blocks[hashes[len(hashes)-2-i]]) - verifyImportEvent(t, imported, true) - } - verifyImportDone(t, imported) -} diff --git a/les/fetcher_test.go b/les/fetcher_test.go deleted file mode 100644 index 7c523f797ec9..000000000000 --- a/les/fetcher_test.go +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package les - -import ( - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/params" -) - -// verifyImportEvent verifies that one single event arrive on an import channel. -func verifyImportEvent(t *testing.T, imported chan interface{}, arrive bool) { - if arrive { - select { - case <-imported: - case <-time.After(time.Second): - t.Fatalf("import timeout") - } - } else { - select { - case <-imported: - t.Fatalf("import invoked") - case <-time.After(20 * time.Millisecond): - } - } -} - -// verifyImportDone verifies that no more events are arriving on an import channel. -func verifyImportDone(t *testing.T, imported chan interface{}) { - select { - case <-imported: - t.Fatalf("extra block imported") - case <-time.After(50 * time.Millisecond): - } -} - -// verifyChainHeight verifies the chain height is as expected. -func verifyChainHeight(t *testing.T, fetcher *lightFetcher, height uint64) { - local := fetcher.chain.CurrentHeader().Number.Uint64() - if local != height { - t.Fatalf("chain height mismatch, got %d, want %d", local, height) - } -} - -func TestSequentialAnnouncementsLes2(t *testing.T) { testSequentialAnnouncements(t, 2) } -func TestSequentialAnnouncementsLes3(t *testing.T) { testSequentialAnnouncements(t, 3) } - -func testSequentialAnnouncements(t *testing.T, protocol int) { - netconfig := testnetConfig{ - blocks: 4, - protocol: protocol, - nopruning: true, - } - s, c, teardown := newClientServerEnv(t, netconfig) - defer teardown() - - // Create connected peer pair, the initial signal from LES server - // is discarded to prevent syncing. - p1, _, err := newTestPeerPair("peer", protocol, s.handler, c.handler, true) - if err != nil { - t.Fatalf("Failed to create peer pair %v", err) - } - importCh := make(chan interface{}) - c.handler.fetcher.newHeadHook = func(header *types.Header) { - importCh <- header - } - for i := uint64(1); i <= s.backend.Blockchain().CurrentHeader().Number.Uint64(); i++ { - header := s.backend.Blockchain().GetHeaderByNumber(i) - hash, number := header.Hash(), header.Number.Uint64() - td := rawdb.ReadTd(s.db, hash, number) - - announce := announceData{hash, number, td, 0, nil} - if p1.cpeer.announceType == announceTypeSigned { - announce.sign(s.handler.server.privateKey) - } - p1.cpeer.sendAnnounce(announce) - verifyImportEvent(t, importCh, true) - } - verifyImportDone(t, importCh) - verifyChainHeight(t, c.handler.fetcher, 4) -} - -func TestGappedAnnouncementsLes2(t *testing.T) { testGappedAnnouncements(t, 2) } -func TestGappedAnnouncementsLes3(t *testing.T) { testGappedAnnouncements(t, 3) } - -func testGappedAnnouncements(t *testing.T, protocol int) { - netconfig := testnetConfig{ - blocks: 4, - protocol: protocol, - nopruning: true, - } - s, c, teardown := newClientServerEnv(t, netconfig) - defer teardown() - - // Create connected peer pair, the initial signal from LES server - // is discarded to prevent syncing. - peer, _, err := newTestPeerPair("peer", protocol, s.handler, c.handler, true) - if err != nil { - t.Fatalf("Failed to create peer pair %v", err) - } - done := make(chan *types.Header, 1) - c.handler.fetcher.newHeadHook = func(header *types.Header) { done <- header } - - // Prepare announcement by latest header. - latest := s.backend.Blockchain().CurrentHeader() - hash, number := latest.Hash(), latest.Number.Uint64() - td := rawdb.ReadTd(s.db, hash, number) - - // Sign the announcement if necessary. - announce := announceData{hash, number, td, 0, nil} - if peer.cpeer.announceType == announceTypeSigned { - announce.sign(s.handler.server.privateKey) - } - peer.cpeer.sendAnnounce(announce) - - <-done // Wait syncing - verifyChainHeight(t, c.handler.fetcher, 4) - - // Send a reorged announcement - blocks, _ := core.GenerateChain(rawdb.ReadChainConfig(s.db, s.backend.Blockchain().Genesis().Hash()), s.backend.Blockchain().GetBlockByNumber(3), - ethash.NewFaker(), s.db, 2, func(i int, gen *core.BlockGen) { - gen.OffsetTime(-9) // higher block difficulty - }) - s.backend.Blockchain().InsertChain(blocks) - - <-done // Wait syncing - verifyChainHeight(t, c.handler.fetcher, 5) -} - -func TestInvalidAnnouncesLES2(t *testing.T) { testInvalidAnnounces(t, lpv2) } -func TestInvalidAnnouncesLES3(t *testing.T) { testInvalidAnnounces(t, lpv3) } -func TestInvalidAnnouncesLES4(t *testing.T) { testInvalidAnnounces(t, lpv4) } - -func testInvalidAnnounces(t *testing.T, protocol int) { - netconfig := testnetConfig{ - blocks: 4, - protocol: protocol, - nopruning: true, - } - s, c, teardown := newClientServerEnv(t, netconfig) - defer teardown() - - // Create connected peer pair, the initial signal from LES server - // is discarded to prevent syncing. - peer, _, err := newTestPeerPair("peer", lpv3, s.handler, c.handler, true) - if err != nil { - t.Fatalf("Failed to create peer pair %v", err) - } - done := make(chan *types.Header, 1) - c.handler.fetcher.newHeadHook = func(header *types.Header) { done <- header } - - // Prepare announcement by latest header. - headerOne := s.backend.Blockchain().GetHeaderByNumber(1) - hash, number := headerOne.Hash(), headerOne.Number.Uint64() - td := big.NewInt(params.GenesisDifficulty.Int64() + 200) // bad td - - // Sign the announcement if necessary. - announce := announceData{hash, number, td, 0, nil} - if peer.cpeer.announceType == announceTypeSigned { - announce.sign(s.handler.server.privateKey) - } - peer.cpeer.sendAnnounce(announce) - <-done // Wait syncing - - // Ensure the bad peer is evicted - if c.handler.backend.peers.len() != 0 { - t.Fatalf("Failed to evict invalid peer") - } -} diff --git a/les/handler_test.go b/les/handler_test.go index 0a47150d9274..765ce6a0ae9c 100644 --- a/les/handler_test.go +++ b/les/handler_test.go @@ -31,12 +31,13 @@ import ( "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/les/downloader" + "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" ) func expectResponse(r p2p.MsgReader, msgcode, reqID, bv uint64, data interface{}) error { @@ -295,8 +296,8 @@ func testGetCode(t *testing.T, protocol int) { for i := uint64(0); i <= bc.CurrentBlock().Number.Uint64(); i++ { header := bc.GetHeaderByNumber(i) req := &CodeReq{ - BHash: header.Hash(), - AccKey: crypto.Keccak256(testContractAddr[:]), + BHash: header.Hash(), + AccountAddress: testContractAddr[:], } codereqs = append(codereqs, req) if i >= testContractDeployed { @@ -331,8 +332,8 @@ func testGetStaleCode(t *testing.T, protocol int) { check := func(number uint64, expected [][]byte) { req := &CodeReq{ - BHash: bc.GetHeaderByNumber(number).Hash(), - AccKey: crypto.Keccak256(testContractAddr[:]), + BHash: bc.GetHeaderByNumber(number).Hash(), + AccountAddress: testContractAddr[:], } sendRequest(rawPeer.app, GetCodeMsg, 42, []*CodeReq{req}) if err := expectResponse(rawPeer.app, CodeMsg, 42, testBufLimit, expected); err != nil { @@ -401,12 +402,12 @@ func testGetProofs(t *testing.T, protocol int) { bc := server.handler.blockchain var proofreqs []ProofReq - proofsV2 := light.NewNodeSet() + proofsV2 := trienode.NewProofSet() accounts := []common.Address{bankAddr, userAddr1, userAddr2, signerAddr, {}} for i := uint64(0); i <= bc.CurrentBlock().Number.Uint64(); i++ { header := bc.GetHeaderByNumber(i) - trie, _ := trie.New(trie.StateTrieID(header.Root), trie.NewDatabase(server.db)) + trie, _ := trie.New(trie.StateTrieID(header.Root), server.backend.Blockchain().TrieDB()) for _, acc := range accounts { req := ProofReq{ @@ -414,12 +415,12 @@ func testGetProofs(t *testing.T, protocol int) { Key: crypto.Keccak256(acc[:]), } proofreqs = append(proofreqs, req) - trie.Prove(crypto.Keccak256(acc[:]), 0, proofsV2) + trie.Prove(crypto.Keccak256(acc[:]), proofsV2) } } // Send the proof request and verify the response sendRequest(rawPeer.app, GetProofsV2Msg, 42, proofreqs) - if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.NodeList()); err != nil { + if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, proofsV2.List()); err != nil { t.Errorf("proofs mismatch: %v", err) } } @@ -456,10 +457,10 @@ func testGetStaleProof(t *testing.T, protocol int) { var expected []rlp.RawValue if wantOK { - proofsV2 := light.NewNodeSet() - t, _ := trie.New(trie.StateTrieID(header.Root), trie.NewDatabase(server.db)) - t.Prove(account, 0, proofsV2) - expected = proofsV2.NodeList() + proofsV2 := trienode.NewProofSet() + t, _ := trie.New(trie.StateTrieID(header.Root), server.backend.Blockchain().TrieDB()) + t.Prove(account, proofsV2) + expected = proofsV2.List() } if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, expected); err != nil { t.Errorf("codes mismatch: %v", err) @@ -513,8 +514,8 @@ func testGetCHTProofs(t *testing.T, protocol int) { AuxData: [][]byte{rlp}, } root := light.GetChtRoot(server.db, 0, bc.GetHeaderByNumber(config.ChtSize-1).Hash()) - trie, _ := trie.New(trie.TrieID(root), trie.NewDatabase(rawdb.NewTable(server.db, string(rawdb.ChtTablePrefix)))) - trie.Prove(key, 0, &proofsV2.Proofs) + trie, _ := trie.New(trie.TrieID(root), trie.NewDatabase(rawdb.NewTable(server.db, string(rawdb.ChtTablePrefix)), trie.HashDefaults)) + trie.Prove(key, &proofsV2.Proofs) // Assemble the requests for the different protocols requestsV2 := []HelperTrieReq{{ Type: htCanonical, @@ -578,8 +579,8 @@ func testGetBloombitsProofs(t *testing.T, protocol int) { var proofs HelperTrieResps root := light.GetBloomTrieRoot(server.db, 0, bc.GetHeaderByNumber(config.BloomTrieSize-1).Hash()) - trie, _ := trie.New(trie.TrieID(root), trie.NewDatabase(rawdb.NewTable(server.db, string(rawdb.BloomTrieTablePrefix)))) - trie.Prove(key, 0, &proofs.Proofs) + trie, _ := trie.New(trie.TrieID(root), trie.NewDatabase(rawdb.NewTable(server.db, string(rawdb.BloomTrieTablePrefix)), trie.HashDefaults)) + trie.Prove(key, &proofs.Proofs) // Send the proof request and verify the response sendRequest(rawPeer.app, GetHelperTrieProofsMsg, 42, requests) @@ -611,6 +612,8 @@ func testTransactionStatus(t *testing.T, protocol int) { var reqID uint64 test := func(tx *types.Transaction, send bool, expStatus light.TxStatus) { + t.Helper() + reqID++ if send { sendRequest(rawPeer.app, SendTxV2Msg, reqID, types.Transactions{tx}) @@ -618,14 +621,14 @@ func testTransactionStatus(t *testing.T, protocol int) { sendRequest(rawPeer.app, GetTxStatusMsg, reqID, []common.Hash{tx.Hash()}) } if err := expectResponse(rawPeer.app, TxStatusMsg, reqID, testBufLimit, []light.TxStatus{expStatus}); err != nil { - t.Errorf("transaction status mismatch") + t.Errorf("transaction status mismatch: %v", err) } } signer := types.HomesteadSigner{} // test error status by sending an underpriced transaction tx0, _ := types.SignTx(types.NewTransaction(0, userAddr1, big.NewInt(10000), params.TxGas, nil, nil), signer, bankKey) - test(tx0, true, light.TxStatus{Status: txpool.TxStatusUnknown, Error: txpool.ErrUnderpriced.Error()}) + test(tx0, true, light.TxStatus{Status: txpool.TxStatusUnknown, Error: "transaction underpriced: tip needed 1, tip permitted 0"}) tx1, _ := types.SignTx(types.NewTransaction(0, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, bankKey) test(tx1, false, light.TxStatus{Status: txpool.TxStatusUnknown}) // query before sending, should be unknown diff --git a/les/odr_requests.go b/les/odr_requests.go index d8b094b72781..c907018590fa 100644 --- a/les/odr_requests.go +++ b/les/odr_requests.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" ) var ( @@ -183,9 +184,9 @@ func (r *ReceiptsRequest) Validate(db ethdb.Database, msg *Msg) error { } type ProofReq struct { - BHash common.Hash - AccKey, Key []byte - FromLevel uint + BHash common.Hash + AccountAddress, Key []byte + FromLevel uint } // ODR request type for state/storage trie entries, see LesOdrRequest interface @@ -206,9 +207,9 @@ func (r *TrieRequest) CanSend(peer *serverPeer) bool { func (r *TrieRequest) Request(reqID uint64, peer *serverPeer) error { peer.Log().Debug("Requesting trie proof", "root", r.Id.Root, "key", r.Key) req := ProofReq{ - BHash: r.Id.BlockHash, - AccKey: r.Id.AccKey, - Key: r.Key, + BHash: r.Id.BlockHash, + AccountAddress: r.Id.AccountAddress, + Key: r.Key, } return peer.requestProofs(reqID, []ProofReq{req}) } @@ -222,9 +223,9 @@ func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error { if msg.MsgType != MsgProofsV2 { return errInvalidMessageType } - proofs := msg.Obj.(light.NodeList) + proofs := msg.Obj.(trienode.ProofList) // Verify the proof and store if checks out - nodeSet := proofs.NodeSet() + nodeSet := proofs.Set() reads := &readTraceDB{db: nodeSet} if _, err := trie.VerifyProof(r.Id.Root, r.Key, reads); err != nil { return fmt.Errorf("merkle proof verification failed: %v", err) @@ -238,8 +239,8 @@ func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error { } type CodeReq struct { - BHash common.Hash - AccKey []byte + BHash common.Hash + AccountAddress []byte } // CodeRequest is the ODR request type for node data (used for retrieving contract code), see LesOdrRequest interface @@ -260,8 +261,8 @@ func (r *CodeRequest) CanSend(peer *serverPeer) bool { func (r *CodeRequest) Request(reqID uint64, peer *serverPeer) error { peer.Log().Debug("Requesting code data", "hash", r.Hash) req := CodeReq{ - BHash: r.Id.BlockHash, - AccKey: r.Id.AccKey, + BHash: r.Id.BlockHash, + AccountAddress: r.Id.AccountAddress, } return peer.requestCode(reqID, []CodeReq{req}) } @@ -308,7 +309,7 @@ type HelperTrieReq struct { } type HelperTrieResps struct { // describes all responses, not just a single one - Proofs light.NodeList + Proofs trienode.ProofList AuxData [][]byte } @@ -356,7 +357,7 @@ func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error { if len(resp.AuxData) != 1 { return errInvalidEntryCount } - nodeSet := resp.Proofs.NodeSet() + nodeSet := resp.Proofs.Set() headerEnc := resp.AuxData[0] if len(headerEnc) == 0 { return errHeaderUnavailable @@ -451,7 +452,7 @@ func (r *BloomRequest) Validate(db ethdb.Database, msg *Msg) error { } resps := msg.Obj.(HelperTrieResps) proofs := resps.Proofs - nodeSet := proofs.NodeSet() + nodeSet := proofs.Set() reads := &readTraceDB{db: nodeSet} r.BloomBits = make([][]byte, len(r.SectionIndexList)) diff --git a/les/odr_test.go b/les/odr_test.go index 2db9acd4335e..69824a92dd08 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -16,6 +16,10 @@ package les +// Note: these tests are disabled now because they cannot work with the old sync +// mechanism removed but will be useful again once the PoS ultralight mode is implemented + +/* import ( "bytes" "context" @@ -100,7 +104,7 @@ func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainCon for _, addr := range acc { if bc != nil { header := bc.GetHeaderByHash(bhash) - st, err = state.New(header.Root, state.NewDatabase(db), nil) + st, err = state.New(header.Root, bc.StateCache(), nil) } else { header := lc.GetHeaderByHash(bhash) st = light.NewState(ctx, header, lc.Odr()) @@ -451,3 +455,4 @@ func randomHash() common.Hash { } return hash } +*/ diff --git a/les/peer.go b/les/peer.go index b138a02c500f..f96fa222310f 100644 --- a/les/peer.go +++ b/les/peer.go @@ -40,6 +40,7 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/trienode" ) var ( @@ -899,7 +900,7 @@ func (p *clientPeer) replyReceiptsRLP(reqID uint64, receipts []rlp.RawValue) *re } // replyProofsV2 creates a reply with a batch of merkle proofs, corresponding to the ones requested. -func (p *clientPeer) replyProofsV2(reqID uint64, proofs light.NodeList) *reply { +func (p *clientPeer) replyProofsV2(reqID uint64, proofs trienode.ProofList) *reply { data, _ := rlp.EncodeToBytes(proofs) return &reply{p.rw, ProofsV2Msg, reqID, data} } @@ -998,9 +999,6 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge recentTx -= blockSafetyMargin - txIndexRecentOffset } } - if server.config.UltraLightOnlyAnnounce { - recentTx = txIndexDisabled - } if recentTx != txIndexUnlimited && p.version < lpv4 { return errors.New("Cannot serve old clients without a complete tx index") } @@ -1009,20 +1007,18 @@ func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge p.headInfo = blockInfo{Hash: head, Number: headNum, Td: td} return p.handshake(td, head, headNum, genesis, forkID, forkFilter, func(lists *keyValueList) { // Add some information which services server can offer. - if !server.config.UltraLightOnlyAnnounce { - *lists = (*lists).add("serveHeaders", nil) - *lists = (*lists).add("serveChainSince", uint64(0)) - *lists = (*lists).add("serveStateSince", uint64(0)) - - // If local ethereum node is running in archive mode, advertise ourselves we have - // all version state data. Otherwise only recent state is available. - stateRecent := uint64(core.DefaultTriesInMemory - blockSafetyMargin) - if server.archiveMode { - stateRecent = 0 - } - *lists = (*lists).add("serveRecentState", stateRecent) - *lists = (*lists).add("txRelay", nil) + *lists = (*lists).add("serveHeaders", nil) + *lists = (*lists).add("serveChainSince", uint64(0)) + *lists = (*lists).add("serveStateSince", uint64(0)) + + // If local ethereum node is running in archive mode, advertise ourselves we have + // all version state data. Otherwise only recent state is available. + stateRecent := uint64(core.DefaultTriesInMemory - blockSafetyMargin) + if server.archiveMode { + stateRecent = 0 } + *lists = (*lists).add("serveRecentState", stateRecent) + *lists = (*lists).add("txRelay", nil) if p.version >= lpv4 { *lists = (*lists).add("recentTxLookup", recentTx) } diff --git a/les/peer_test.go b/les/peer_test.go index 021d5cb594c4..0881dd292b15 100644 --- a/les/peer_test.go +++ b/les/peer_test.go @@ -124,8 +124,8 @@ func TestHandshake(t *testing.T) { genesis = common.HexToHash("cafebabe") chain1, chain2 = &fakeChain{}, &fakeChain{} - forkID1 = forkid.NewID(chain1.Config(), chain1.Genesis().Hash(), chain1.CurrentHeader().Number.Uint64(), chain1.CurrentHeader().Time) - forkID2 = forkid.NewID(chain2.Config(), chain2.Genesis().Hash(), chain2.CurrentHeader().Number.Uint64(), chain2.CurrentHeader().Time) + forkID1 = forkid.NewID(chain1.Config(), chain1.Genesis(), chain1.CurrentHeader().Number.Uint64(), chain1.CurrentHeader().Time) + forkID2 = forkid.NewID(chain2.Config(), chain2.Genesis(), chain2.CurrentHeader().Number.Uint64(), chain2.CurrentHeader().Time) filter1, filter2 = forkid.NewFilter(chain1), forkid.NewFilter(chain2) ) diff --git a/les/protocol.go b/les/protocol.go index dced7039e402..cfebdbfb9a5a 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -40,9 +40,8 @@ const ( // Supported versions of the les protocol (first is primary) var ( - ClientProtocolVersions = []uint{lpv2, lpv3, lpv4} - ServerProtocolVersions = []uint{lpv2, lpv3, lpv4} - AdvertiseProtocolVersions = []uint{lpv2} // clients are searching for the first advertised protocol in the list + ClientProtocolVersions = []uint{lpv2, lpv3, lpv4} + ServerProtocolVersions = []uint{lpv2, lpv3, lpv4} ) // ProtocolLengths is the number of implemented message corresponding to different protocol versions. diff --git a/les/pruner.go b/les/pruner.go deleted file mode 100644 index d115a61a7019..000000000000 --- a/les/pruner.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2020 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package les - -import ( - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" -) - -// pruner is responsible for pruning historical light chain data. -type pruner struct { - db ethdb.Database - indexers []*core.ChainIndexer - closeCh chan struct{} - wg sync.WaitGroup -} - -// newPruner returns a light chain pruner instance. -func newPruner(db ethdb.Database, indexers ...*core.ChainIndexer) *pruner { - pruner := &pruner{ - db: db, - indexers: indexers, - closeCh: make(chan struct{}), - } - pruner.wg.Add(1) - go pruner.loop() - return pruner -} - -// close notifies all background goroutines belonging to pruner to exit. -func (p *pruner) close() { - close(p.closeCh) - p.wg.Wait() -} - -// loop periodically queries the status of chain indexers and prunes useless -// historical chain data. Notably, whenever Geth restarts, it will iterate -// all historical sections even they don't exist at all(below checkpoint) so -// that light client can prune cached chain data that was ODRed after pruning -// that section. -func (p *pruner) loop() { - defer p.wg.Done() - - // cleanTicker is the ticker used to trigger a history clean 2 times a day. - var cleanTicker = time.NewTicker(12 * time.Hour) - defer cleanTicker.Stop() - - // pruning finds the sections that have been processed by all indexers - // and deletes all historical chain data. - // Note, if some indexers don't support pruning(e.g. eth.BloomIndexer), - // pruning operations can be silently ignored. - pruning := func() { - min := uint64(math.MaxUint64) - for _, indexer := range p.indexers { - sections, _, _ := indexer.Sections() - if sections < min { - min = sections - } - } - // Always keep the latest section data in database. - if min < 2 || len(p.indexers) == 0 { - return - } - for _, indexer := range p.indexers { - if err := indexer.Prune(min - 2); err != nil { - log.Debug("Failed to prune historical data", "err", err) - return - } - } - p.db.Compact(nil, nil) // Compact entire database, ensure all removed data are deleted. - } - for { - pruning() - select { - case <-cleanTicker.C: - case <-p.closeCh: - return - } - } -} diff --git a/les/pruner_test.go b/les/pruner_test.go deleted file mode 100644 index 1672414937c1..000000000000 --- a/les/pruner_test.go +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2020 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package les - -import ( - "bytes" - "context" - "encoding/binary" - "testing" - "time" - - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/light" -) - -func TestLightPruner(t *testing.T) { - var ( - waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { - for { - cs, _, _ := cIndexer.Sections() - bts, _, _ := btIndexer.Sections() - if cs >= 3 && bts >= 3 { - break - } - time.Sleep(10 * time.Millisecond) - } - } - config = light.TestClientIndexerConfig - netconfig = testnetConfig{ - blocks: int(3*config.ChtSize + config.ChtConfirms), - protocol: 3, - indexFn: waitIndexers, - connect: true, - } - ) - server, client, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - // checkDB iterates the chain with given prefix, resolves the block number - // with given callback and ensures this entry should exist or not. - checkDB := func(from, to uint64, prefix []byte, resolve func(key, value []byte) *uint64, exist bool) bool { - it := client.db.NewIterator(prefix, nil) - defer it.Release() - - var next = from - for it.Next() { - number := resolve(it.Key(), it.Value()) - if number == nil || *number < from { - continue - } else if *number > to { - return true - } - if exist { - if *number != next { - return false - } - next++ - } else { - return false - } - } - return true - } - // checkPruned checks and ensures the stale chain data has been pruned. - checkPruned := func(from, to uint64) { - // Iterate canonical hash - if !checkDB(from, to, []byte("h"), func(key, value []byte) *uint64 { - if len(key) == 1+8+1 && bytes.Equal(key[9:10], []byte("n")) { - n := binary.BigEndian.Uint64(key[1:9]) - return &n - } - return nil - }, false) { - t.Fatalf("canonical hash mappings are not properly pruned") - } - // Iterate header - if !checkDB(from, to, []byte("h"), func(key, value []byte) *uint64 { - if len(key) == 1+8+32 { - n := binary.BigEndian.Uint64(key[1:9]) - return &n - } - return nil - }, false) { - t.Fatalf("headers are not properly pruned") - } - // Iterate body - if !checkDB(from, to, []byte("b"), func(key, value []byte) *uint64 { - if len(key) == 1+8+32 { - n := binary.BigEndian.Uint64(key[1:9]) - return &n - } - return nil - }, false) { - t.Fatalf("block bodies are not properly pruned") - } - // Iterate receipts - if !checkDB(from, to, []byte("r"), func(key, value []byte) *uint64 { - if len(key) == 1+8+32 { - n := binary.BigEndian.Uint64(key[1:9]) - return &n - } - return nil - }, false) { - t.Fatalf("receipts are not properly pruned") - } - // Iterate td - if !checkDB(from, to, []byte("h"), func(key, value []byte) *uint64 { - if len(key) == 1+8+32+1 && bytes.Equal(key[41:42], []byte("t")) { - n := binary.BigEndian.Uint64(key[1:9]) - return &n - } - return nil - }, false) { - t.Fatalf("tds are not properly pruned") - } - } - // Start light pruner. - time.Sleep(1500 * time.Millisecond) // Ensure light client has finished the syncing and indexing - newPruner(client.db, client.chtIndexer, client.bloomTrieIndexer) - - time.Sleep(1500 * time.Millisecond) // Ensure pruner have enough time to prune data. - checkPruned(1, config.ChtSize-1) - - // Ensure all APIs still work after pruning. - var cases = []struct { - from, to uint64 - methodName string - method func(uint64) bool - }{ - { - 1, 10, "GetHeaderByNumber", - func(n uint64) bool { - _, err := light.GetHeaderByNumber(context.Background(), client.handler.backend.odr, n) - return err == nil - }, - }, - { - 11, 20, "GetCanonicalHash", - func(n uint64) bool { - _, err := light.GetCanonicalHash(context.Background(), client.handler.backend.odr, n) - return err == nil - }, - }, - { - 21, 30, "GetTd", - func(n uint64) bool { - _, err := light.GetTd(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n) - return err == nil - }, - }, - { - 31, 40, "GetBodyRLP", - func(n uint64) bool { - _, err := light.GetBodyRLP(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n) - return err == nil - }, - }, - { - 41, 50, "GetBlock", - func(n uint64) bool { - _, err := light.GetBlock(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n) - return err == nil - }, - }, - { - 51, 60, "GetBlockReceipts", - func(n uint64) bool { - _, err := light.GetBlockReceipts(context.Background(), client.handler.backend.odr, server.handler.blockchain.GetHeaderByNumber(n).Hash(), n) - return err == nil - }, - }, - } - for _, c := range cases { - for i := c.from; i <= c.to; i++ { - if !c.method(i) { - t.Fatalf("rpc method %s failed, number %d", c.methodName, i) - } - } - } - // Check GetBloombits - _, err := light.GetBloomBits(context.Background(), client.handler.backend.odr, 0, []uint64{0}) - if err != nil { - t.Fatalf("Failed to retrieve bloombits of pruned section: %v", err) - } - - // Ensure the ODR cached data can be cleaned by pruner. - newPruner(client.db, client.chtIndexer, client.bloomTrieIndexer) - time.Sleep(50 * time.Millisecond) // Ensure pruner have enough time to prune data. - checkPruned(1, config.ChtSize-1) // Ensure all cached data(by odr) is cleaned. -} diff --git a/les/request_test.go b/les/request_test.go index 9b52e6bd86ad..5e354b7efd55 100644 --- a/les/request_test.go +++ b/les/request_test.go @@ -16,6 +16,10 @@ package les +// Note: these tests are disabled now because they cannot work with the old sync +// mechanism removed but will be useful again once the PoS ultralight mode is implemented + +/* import ( "context" "testing" @@ -23,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/light" @@ -77,7 +82,7 @@ func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrReq return nil } sti := light.StateTrieID(header) - ci := light.StorageTrieID(sti, crypto.Keccak256Hash(testContractAddr[:]), common.Hash{}) + ci := light.StorageTrieID(sti, testContractAddr, types.EmptyRootHash) return &light.CodeRequest{Id: ci, Hash: crypto.Keccak256Hash(testContractCodeDeployed)} } @@ -121,3 +126,4 @@ func testAccess(t *testing.T, protocol int, fn accessTestFn) { } test(5) } +*/ diff --git a/les/retrieve.go b/les/retrieve.go index 2b9e239e9a22..728f960a547e 100644 --- a/les/retrieve.go +++ b/les/retrieve.go @@ -153,15 +153,6 @@ func (rm *retrieveManager) sendReq(reqID uint64, req *distReq, val validatorFunc return r } -// requested reports whether the request with given reqid is sent by the retriever. -func (rm *retrieveManager) requested(reqId uint64) bool { - rm.lock.RLock() - defer rm.lock.RUnlock() - - _, ok := rm.sentReqs[reqId] - return ok -} - // deliver is called by the LES protocol manager to deliver reply messages to waiting requests func (rm *retrieveManager) deliver(peer distPeer, msg *Msg) error { rm.lock.RLock() diff --git a/les/server_handler.go b/les/server_handler.go index 418974c37c62..5b3505064b31 100644 --- a/les/server_handler.go +++ b/les/server_handler.go @@ -18,6 +18,7 @@ package les import ( "errors" + "fmt" "sync" "time" @@ -34,7 +35,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" ) @@ -116,7 +116,7 @@ func (h *serverHandler) handle(p *clientPeer) error { hash = head.Hash() number = head.Number.Uint64() td = h.blockchain.GetTd(hash, number) - forkID = forkid.NewID(h.blockchain.Config(), h.blockchain.Genesis().Hash(), number, head.Time) + forkID = forkid.NewID(h.blockchain.Config(), h.blockchain.Genesis(), number, head.Time) ) if err := p.Handshake(td, hash, number, h.blockchain.Genesis().Hash(), forkID, h.forkFilter, h.server); err != nil { p.Log().Debug("Light Ethereum handshake failed", "err", err) @@ -358,20 +358,19 @@ func (h *serverHandler) AddTxsSync() bool { } // getAccount retrieves an account from the state based on root. -func getAccount(triedb *trie.Database, root, hash common.Hash) (types.StateAccount, error) { - trie, err := trie.New(trie.StateTrieID(root), triedb) +func getAccount(triedb *trie.Database, root common.Hash, addr common.Address) (types.StateAccount, error) { + trie, err := trie.NewStateTrie(trie.StateTrieID(root), triedb) if err != nil { return types.StateAccount{}, err } - blob, err := trie.Get(hash[:]) + acc, err := trie.GetAccount(addr) if err != nil { return types.StateAccount{}, err } - var acc types.StateAccount - if err = rlp.DecodeBytes(blob, &acc); err != nil { - return types.StateAccount{}, err + if acc == nil { + return types.StateAccount{}, fmt.Errorf("account %#x is not present", addr) } - return acc, nil + return *acc, nil } // GetHelperTrie returns the post-processed trie root for the given trie ID and section index @@ -391,7 +390,8 @@ func (h *serverHandler) GetHelperTrie(typ uint, index uint64) *trie.Trie { if root == (common.Hash{}) { return nil } - trie, _ := trie.New(trie.TrieID(root), trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix))) + triedb := trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix), trie.HashDefaults) + trie, _ := trie.New(trie.TrieID(root), triedb) return trie } diff --git a/les/server_requests.go b/les/server_requests.go index 39762f5e42e9..eef5daf5022e 100644 --- a/les/server_requests.go +++ b/les/server_requests.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" ) // serverBackend defines the backend functions needed for serving LES requests @@ -303,17 +304,16 @@ func handleGetCode(msg Decoder) (serveRequestFn, uint64, uint64, error) { p.bumpInvalid() continue } - triedb := bc.StateCache().TrieDB() - - account, err := getAccount(triedb, header.Root, common.BytesToHash(request.AccKey)) + address := common.BytesToAddress(request.AccountAddress) + account, err := getAccount(bc.TrieDB(), header.Root, address) if err != nil { - p.Log().Warn("Failed to retrieve account for code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err) + p.Log().Warn("Failed to retrieve account for code", "block", header.Number, "hash", header.Hash(), "account", address, "err", err) p.bumpInvalid() continue } - code, err := bc.StateCache().ContractCode(common.BytesToHash(request.AccKey), common.BytesToHash(account.CodeHash)) + code, err := bc.StateCache().ContractCode(address, common.BytesToHash(account.CodeHash)) if err != nil { - p.Log().Warn("Failed to retrieve account code", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "codehash", common.BytesToHash(account.CodeHash), "err", err) + p.Log().Warn("Failed to retrieve account code", "block", header.Number, "hash", header.Hash(), "account", address, "codehash", common.BytesToHash(account.CodeHash), "err", err) continue } // Accumulate the code and abort if enough data was retrieved @@ -379,7 +379,7 @@ func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { err error ) bc := backend.BlockChain() - nodes := light.NewNodeSet() + nodes := trienode.NewProofSet() for i, request := range r.Reqs { if i != 0 && !waitOrStop() { @@ -413,7 +413,7 @@ func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { statedb := bc.StateCache() var trie state.Trie - switch len(request.AccKey) { + switch len(request.AccountAddress) { case 0: // No account key specified, open an account trie trie, err = statedb.OpenTrie(root) @@ -423,20 +423,21 @@ func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { } default: // Account key specified, open a storage trie - account, err := getAccount(statedb.TrieDB(), root, common.BytesToHash(request.AccKey)) + address := common.BytesToAddress(request.AccountAddress) + account, err := getAccount(bc.TrieDB(), root, address) if err != nil { - p.Log().Warn("Failed to retrieve account for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "err", err) + p.Log().Warn("Failed to retrieve account for proof", "block", header.Number, "hash", header.Hash(), "account", address, "err", err) p.bumpInvalid() continue } - trie, err = statedb.OpenStorageTrie(root, common.BytesToHash(request.AccKey), account.Root) + trie, err = statedb.OpenStorageTrie(root, address, account.Root) if trie == nil || err != nil { - p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "account", common.BytesToHash(request.AccKey), "root", account.Root, "err", err) + p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "account", address, "root", account.Root, "err", err) continue } } // Prove the user's request from the account or storage trie - if err := trie.Prove(request.Key, request.FromLevel, nodes); err != nil { + if err := trie.Prove(request.Key, nodes); err != nil { p.Log().Warn("Failed to prove state request", "block", header.Number, "hash", header.Hash(), "err", err) continue } @@ -444,7 +445,7 @@ func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { break } } - return p.replyProofsV2(r.ReqID, nodes.NodeList()) + return p.replyProofsV2(r.ReqID, nodes.List()) }, r.ReqID, uint64(len(r.Reqs)), nil } @@ -463,7 +464,7 @@ func handleGetHelperTrieProofs(msg Decoder) (serveRequestFn, uint64, uint64, err auxData [][]byte ) bc := backend.BlockChain() - nodes := light.NewNodeSet() + nodes := trienode.NewProofSet() for i, request := range r.Reqs { if i != 0 && !waitOrStop() { return nil @@ -480,7 +481,7 @@ func handleGetHelperTrieProofs(msg Decoder) (serveRequestFn, uint64, uint64, err // the headers with no valid proof. Keep the compatibility for // legacy les protocol and drop this hack when the les2/3 are // not supported. - err := auxTrie.Prove(request.Key, request.FromLevel, nodes) + err := auxTrie.Prove(request.Key, nodes) if p.version >= lpv4 && err != nil { return nil } @@ -498,7 +499,7 @@ func handleGetHelperTrieProofs(msg Decoder) (serveRequestFn, uint64, uint64, err break } } - return p.replyHelperTrieProofs(r.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData}) + return p.replyHelperTrieProofs(r.ReqID, HelperTrieResps{Proofs: nodes.List(), AuxData: auxData}) }, r.ReqID, uint64(len(r.Reqs)), nil } @@ -518,12 +519,7 @@ func handleSendTx(msg Decoder) (serveRequestFn, uint64, uint64, error) { hash := tx.Hash() stats[i] = txStatus(backend, hash) if stats[i].Status == txpool.TxStatusUnknown { - addFn := backend.TxPool().AddRemotes - // Add txs synchronously for testing purpose - if backend.AddTxsSync() { - addFn = backend.TxPool().AddRemotesSync - } - if errs := addFn([]*types.Transaction{tx}); errs[0] != nil { + if errs := backend.TxPool().Add([]*types.Transaction{tx}, false, backend.AddTxsSync()); errs[0] != nil { stats[i].Error = errs[0].Error() continue } @@ -556,7 +552,7 @@ func handleGetTxStatus(msg Decoder) (serveRequestFn, uint64, uint64, error) { func txStatus(b serverBackend, hash common.Hash) light.TxStatus { var stat light.TxStatus // Looking the transaction in txpool first. - stat.Status = b.TxPool().Status([]common.Hash{hash})[0] + stat.Status = b.TxPool().Status(hash) // If the transaction is unknown to the pool, try looking it up locally. if stat.Status == txpool.TxStatusUnknown { diff --git a/les/servingqueue.go b/les/servingqueue.go index b4b53d8df548..b258fc3cafdc 100644 --- a/les/servingqueue.go +++ b/les/servingqueue.go @@ -17,21 +17,22 @@ package les import ( - "sort" "sync" "sync/atomic" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/common/prque" + "golang.org/x/exp/slices" ) // servingQueue allows running tasks in a limited number of threads and puts the // waiting tasks in a priority queue type servingQueue struct { - recentTime, queuedTime, servingTimeDiff uint64 - burstLimit, burstDropLimit uint64 - burstDecRate float64 - lastUpdate mclock.AbsTime + recentTime, queuedTime uint64 + servingTimeDiff atomic.Uint64 + burstLimit, burstDropLimit uint64 + burstDecRate float64 + lastUpdate mclock.AbsTime queueAddCh, queueBestCh chan *servingTask stopThreadCh, quit chan struct{} @@ -100,7 +101,7 @@ func (t *servingTask) done() uint64 { t.timeAdded = t.servingTime if t.expTime > diff { t.expTime -= diff - atomic.AddUint64(&t.sq.servingTimeDiff, t.expTime) + t.sq.servingTimeDiff.Add(t.expTime) } else { t.expTime = 0 } @@ -180,35 +181,19 @@ func (sq *servingQueue) threadController() { } } -type ( - // peerTasks lists the tasks received from a given peer when selecting peers to freeze - peerTasks struct { - peer *clientPeer - list []*servingTask - sumTime uint64 - priority float64 - } - // peerList is a sortable list of peerTasks - peerList []*peerTasks -) - -func (l peerList) Len() int { - return len(l) -} - -func (l peerList) Less(i, j int) bool { - return l[i].priority < l[j].priority -} - -func (l peerList) Swap(i, j int) { - l[i], l[j] = l[j], l[i] +// peerTasks lists the tasks received from a given peer when selecting peers to freeze +type peerTasks struct { + peer *clientPeer + list []*servingTask + sumTime uint64 + priority float64 } // freezePeers selects the peers with the worst priority queued tasks and freezes // them until burstTime goes under burstDropLimit or all peers are frozen func (sq *servingQueue) freezePeers() { peerMap := make(map[*clientPeer]*peerTasks) - var peerList peerList + var peerList []*peerTasks if sq.best != nil { sq.queue.Push(sq.best, sq.best.priority) } @@ -231,7 +216,15 @@ func (sq *servingQueue) freezePeers() { tasks.list = append(tasks.list, task) tasks.sumTime += task.expTime } - sort.Sort(peerList) + slices.SortFunc(peerList, func(a, b *peerTasks) int { + if a.priority < b.priority { + return -1 + } + if a.priority > b.priority { + return 1 + } + return 0 + }) drop := true for _, tasks := range peerList { if drop { @@ -257,7 +250,7 @@ func (sq *servingQueue) freezePeers() { // updateRecentTime recalculates the recent serving time value func (sq *servingQueue) updateRecentTime() { - subTime := atomic.SwapUint64(&sq.servingTimeDiff, 0) + subTime := sq.servingTimeDiff.Swap(0) now := mclock.Now() dt := now - sq.lastUpdate sq.lastUpdate = now diff --git a/les/sync.go b/les/sync.go deleted file mode 100644 index bd06077aaefc..000000000000 --- a/les/sync.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package les - -import ( - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/les/downloader" - "github.com/ethereum/go-ethereum/log" -) - -// synchronise tries to sync up our local chain with a remote peer. -func (h *clientHandler) synchronise(peer *serverPeer) { - // Short circuit if the peer is nil. - if peer == nil { - return - } - // Make sure the peer's TD is higher than our own. - latest := h.backend.blockchain.CurrentHeader() - currentTd := rawdb.ReadTd(h.backend.chainDb, latest.Hash(), latest.Number.Uint64()) - if currentTd != nil && peer.Td().Cmp(currentTd) < 0 { - return - } - // Notify testing framework if syncing has completed (for testing purpose). - defer func() { - if h.syncEnd != nil { - h.syncEnd(h.backend.blockchain.CurrentHeader()) - } - }() - start := time.Now() - if h.syncStart != nil { - h.syncStart(h.backend.blockchain.CurrentHeader()) - } - // Fetch the remaining block headers based on the current chain header. - if err := h.downloader.Synchronise(peer.id, peer.Head(), peer.Td(), downloader.LightSync); err != nil { - log.Debug("Synchronise failed", "reason", err) - return - } - log.Debug("Synchronise finished", "elapsed", common.PrettyDuration(time.Since(start))) -} diff --git a/les/sync_test.go b/les/sync_test.go deleted file mode 100644 index 356a88ffbe88..000000000000 --- a/les/sync_test.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package les - -import ( - "fmt" - "testing" - "time" - - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/light" -) - -// Test light syncing which will download all headers from genesis. -func TestLightSyncingLes3(t *testing.T) { testSyncing(t, lpv3) } - -func testSyncing(t *testing.T, protocol int) { - config := light.TestServerIndexerConfig - - waitIndexers := func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { - for { - cs, _, _ := cIndexer.Sections() - bts, _, _ := btIndexer.Sections() - if cs >= 1 && bts >= 1 { - break - } - time.Sleep(10 * time.Millisecond) - } - } - // Generate 128+1 blocks (totally 1 CHT section) - netconfig := testnetConfig{ - blocks: int(config.ChtSize + config.ChtConfirms), - protocol: protocol, - indexFn: waitIndexers, - nopruning: true, - } - server, client, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - expected := config.ChtSize + config.ChtConfirms - - done := make(chan error) - client.handler.syncEnd = func(header *types.Header) { - if header.Number.Uint64() == expected { - done <- nil - } else { - done <- fmt.Errorf("blockchain length mismatch, want %d, got %d", expected, header.Number) - } - } - - // Create connected peer pair. - peer1, peer2, err := newTestPeerPair("peer", protocol, server.handler, client.handler, false) - if err != nil { - t.Fatalf("Failed to connect testing peers %v", err) - } - defer peer1.close() - defer peer2.close() - - select { - case err := <-done: - if err != nil { - t.Error("sync failed", err) - } - return - case <-time.NewTimer(10 * time.Second).C: - t.Error("checkpoint syncing timeout") - } -} diff --git a/les/test_helper.go b/les/test_helper.go index b5165cd3405c..9269a641e256 100644 --- a/les/test_helper.go +++ b/les/test_helper.go @@ -37,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/core/forkid" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/ethconfig" @@ -48,6 +49,7 @@ import ( "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" ) var ( @@ -176,7 +178,7 @@ func testIndexers(db ethdb.Database, odr light.OdrBackend, config *light.Indexer return indexers[:] } -func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, indexers []*core.ChainIndexer, db ethdb.Database, peers *serverPeerSet, ulcServers []string, ulcFraction int) (*clientHandler, func()) { +func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, indexers []*core.ChainIndexer, db ethdb.Database, peers *serverPeerSet) (*clientHandler, func()) { var ( evmux = new(event.TypeMux) engine = ethash.NewFaker() @@ -187,7 +189,7 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index BaseFee: big.NewInt(params.InitialBaseFee), } ) - genesis := gspec.MustCommit(db) + genesis := gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults)) chain, _ := light.NewLightChain(odr, gspec.Config, engine) client := &LightEthereum{ @@ -209,9 +211,8 @@ func newTestClientHandler(backend *backends.SimulatedBackend, odr *LesOdr, index eventMux: evmux, merger: consensus.NewMerger(rawdb.NewMemoryDatabase()), } - client.handler = newClientHandler(ulcServers, ulcFraction, client) + client.handler = newClientHandler(client) - client.handler.start() return client.handler, func() { client.handler.stop() } @@ -226,15 +227,17 @@ func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Da BaseFee: big.NewInt(params.InitialBaseFee), } ) - genesis := gspec.MustCommit(db) + genesis := gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults)) // create a simulation backend and pre-commit several customized block to the database. simulation := backends.NewSimulatedBackendWithDatabase(db, gspec.Alloc, 100000000) prepare(blocks, simulation) - txpoolConfig := txpool.DefaultConfig + txpoolConfig := legacypool.DefaultConfig txpoolConfig.Journal = "" - txpool := txpool.NewTxPool(txpoolConfig, gspec.Config, simulation.Blockchain()) + + pool := legacypool.New(txpoolConfig, simulation.Blockchain()) + txpool, _ := txpool.New(new(big.Int).SetUint64(txpoolConfig.PriceLimit), simulation.Blockchain(), []txpool.SubPool{pool}) server := &LesServer{ lesCommons: lesCommons{ @@ -304,7 +307,8 @@ func (p *testPeer) handshakeWithServer(t *testing.T, td *big.Int, head common.Ha } // handshakeWithClient executes the handshake with the remote client peer. -func (p *testPeer) handshakeWithClient(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, costList RequestCostList, recentTxLookup uint64) { +// (used by temporarily disabled tests) +/*func (p *testPeer) handshakeWithClient(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, costList RequestCostList, recentTxLookup uint64) { // It only works for the simulated client peer if p.speer == nil { t.Fatal("handshake for server peer only") @@ -334,7 +338,7 @@ func (p *testPeer) handshakeWithClient(t *testing.T, td *big.Int, head common.Ha if err := p2p.Send(p.app, StatusMsg, &sendList); err != nil { t.Fatalf("status send: %v", err) } -} +}*/ // close terminates the local side of the peer, notifying the remote protocol // manager of termination. @@ -402,7 +406,8 @@ type testClient struct { } // newRawPeer creates a new server peer connects to the server and do the handshake. -func (client *testClient) newRawPeer(t *testing.T, name string, version int, recentTxLookup uint64) (*testPeer, func(), <-chan error) { +// (used by temporarily disabled tests) +/*func (client *testClient) newRawPeer(t *testing.T, name string, version int, recentTxLookup uint64) (*testPeer, func(), <-chan error) { // Create a message pipe to communicate through app, net := p2p.MsgPipe() @@ -450,7 +455,7 @@ func (client *testClient) newRawPeer(t *testing.T, name string, version int, rec tp.close() } return tp, closePeer, errCh -} +}*/ // testServer represents a server object for testing with necessary auxiliary fields. type testServer struct { @@ -494,7 +499,7 @@ func (server *testServer) newRawPeer(t *testing.T, name string, version int) (*t head = server.handler.blockchain.CurrentHeader() td = server.handler.blockchain.GetTd(head.Hash(), head.Number.Uint64()) ) - forkID := forkid.NewID(server.handler.blockchain.Config(), genesis.Hash(), head.Number.Uint64(), head.Time) + forkID := forkid.NewID(server.handler.blockchain.Config(), genesis, head.Number.Uint64(), head.Time) tp.handshakeWithServer(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID) // Ensure the connection is established or exits when any error occurs @@ -518,14 +523,12 @@ func (server *testServer) newRawPeer(t *testing.T, name string, version int) (*t // testnetConfig wraps all the configurations for testing network. type testnetConfig struct { - blocks int - protocol int - indexFn indexerCallback - ulcServers []string - ulcFraction int - simClock bool - connect bool - nopruning bool + blocks int + protocol int + indexFn indexerCallback + simClock bool + connect bool + nopruning bool } func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testClient, func()) { @@ -550,7 +553,7 @@ func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testC odr.SetIndexers(ccIndexer, cbIndexer, cbtIndexer) server, b, serverClose := newTestServerHandler(config.blocks, sindexers, sdb, clock) - client, clientClose := newTestClientHandler(b, odr, cIndexers, cdb, speers, config.ulcServers, config.ulcFraction) + client, clientClose := newTestClientHandler(b, odr, cIndexers, cdb, speers) scIndexer.Start(server.blockchain) sbIndexer.Start(server.blockchain) @@ -566,7 +569,6 @@ func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testC ) if config.connect { done := make(chan struct{}) - client.syncEnd = func(_ *types.Header) { close(done) } cpeer, speer, err = newTestPeerPair("peer", config.protocol, server, client, false) if err != nil { t.Fatalf("Failed to connect testing peers %v", err) diff --git a/les/ulc.go b/les/ulc.go deleted file mode 100644 index b97217e79663..000000000000 --- a/les/ulc.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package les - -import ( - "errors" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/enode" -) - -type ulc struct { - keys map[string]bool - fraction int -} - -// newULC creates and returns an ultra light client instance. -func newULC(servers []string, fraction int) (*ulc, error) { - keys := make(map[string]bool) - for _, id := range servers { - node, err := enode.Parse(enode.ValidSchemes, id) - if err != nil { - log.Warn("Failed to parse trusted server", "id", id, "err", err) - continue - } - keys[node.ID().String()] = true - } - if len(keys) == 0 { - return nil, errors.New("no trusted servers") - } - return &ulc{ - keys: keys, - fraction: fraction, - }, nil -} - -// trusted return an indicator that whether the specified peer is trusted. -func (u *ulc) trusted(p enode.ID) bool { - return u.keys[p.String()] -} diff --git a/les/ulc_test.go b/les/ulc_test.go deleted file mode 100644 index 791bc2885385..000000000000 --- a/les/ulc_test.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package les - -import ( - "crypto/rand" - "fmt" - "net" - "testing" - "time" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" -) - -func TestULCAnnounceThresholdLes2(t *testing.T) { testULCAnnounceThreshold(t, 2) } -func TestULCAnnounceThresholdLes3(t *testing.T) { testULCAnnounceThreshold(t, 3) } - -func testULCAnnounceThreshold(t *testing.T, protocol int) { - // todo figure out why it takes fetcher so longer to fetcher the announced header. - t.Skip("Sometimes it can failed") - - // newTestLightPeer creates node with light sync mode - newTestLightPeer := func(t *testing.T, protocol int, ulcServers []string, ulcFraction int) (*testClient, func()) { - netconfig := testnetConfig{ - protocol: protocol, - ulcServers: ulcServers, - ulcFraction: ulcFraction, - nopruning: true, - } - _, c, teardown := newClientServerEnv(t, netconfig) - return c, teardown - } - - var cases = []struct { - height []int - threshold int - expect uint64 - }{ - {[]int{1}, 100, 1}, - {[]int{0, 0, 0}, 100, 0}, - {[]int{1, 2, 3}, 30, 3}, - {[]int{1, 2, 3}, 60, 2}, - {[]int{3, 2, 1}, 67, 1}, - {[]int{3, 2, 1}, 100, 1}, - } - for _, testcase := range cases { - var ( - servers []*testServer - teardowns []func() - nodes []*enode.Node - ids []string - ) - for i := 0; i < len(testcase.height); i++ { - s, n, teardown := newTestServerPeer(t, 0, protocol, nil) - - servers = append(servers, s) - nodes = append(nodes, n) - teardowns = append(teardowns, teardown) - ids = append(ids, n.String()) - } - c, teardown := newTestLightPeer(t, protocol, ids, testcase.threshold) - - // Connect all servers. - for i := 0; i < len(servers); i++ { - connect(servers[i].handler, nodes[i].ID(), c.handler, protocol, false) - } - for i := 0; i < len(servers); i++ { - for j := 0; j < testcase.height[i]; j++ { - servers[i].backend.Commit() - } - } - time.Sleep(1500 * time.Millisecond) // Ensure the fetcher has done its work. - head := c.handler.backend.blockchain.CurrentHeader().Number.Uint64() - if head != testcase.expect { - t.Fatalf("chain height mismatch, want %d, got %d", testcase.expect, head) - } - - // Release all servers and client resources. - teardown() - for i := 0; i < len(teardowns); i++ { - teardowns[i]() - } - } -} - -func connect(server *serverHandler, serverId enode.ID, client *clientHandler, protocol int, noInitAnnounce bool) (*serverPeer, *clientPeer, error) { - // Create a message pipe to communicate through - app, net := p2p.MsgPipe() - - var id enode.ID - rand.Read(id[:]) - - peer1 := newServerPeer(protocol, NetworkId, true, p2p.NewPeer(serverId, "", nil), net) // Mark server as trusted - peer2 := newClientPeer(protocol, NetworkId, p2p.NewPeer(id, "", nil), app) - - // Start the peerLight on a new thread - errc1 := make(chan error, 1) - errc2 := make(chan error, 1) - go func() { - select { - case <-server.closeCh: - errc1 <- p2p.DiscQuitting - case errc1 <- server.handle(peer2): - } - }() - go func() { - select { - case <-client.closeCh: - errc1 <- p2p.DiscQuitting - case errc1 <- client.handle(peer1, noInitAnnounce): - } - }() - // Ensure the connection is established or exits when any error occurs - for { - select { - case err := <-errc1: - return nil, nil, fmt.Errorf("failed to establish protocol connection %v", err) - case err := <-errc2: - return nil, nil, fmt.Errorf("failed to establish protocol connection %v", err) - default: - } - if peer1.serving.Load() && peer2.serving.Load() { - break - } - time.Sleep(50 * time.Millisecond) - } - return peer1, peer2, nil -} - -// newTestServerPeer creates server peer. -func newTestServerPeer(t *testing.T, blocks int, protocol int, indexFn indexerCallback) (*testServer, *enode.Node, func()) { - netconfig := testnetConfig{ - blocks: blocks, - protocol: protocol, - indexFn: indexFn, - nopruning: true, - } - s, _, teardown := newClientServerEnv(t, netconfig) - key, err := crypto.GenerateKey() - if err != nil { - t.Fatal("generate key err:", err) - } - s.handler.server.privateKey = key - n := enode.NewV4(&key.PublicKey, net.ParseIP("127.0.0.1"), 35000, 35000) - return s, n, teardown -} diff --git a/les/utils/limiter.go b/les/utils/limiter.go index 84d186efd63e..70b7ff64f721 100644 --- a/les/utils/limiter.go +++ b/les/utils/limiter.go @@ -17,10 +17,10 @@ package utils import ( - "sort" "sync" "github.com/ethereum/go-ethereum/p2p/enode" + "golang.org/x/exp/slices" ) const maxSelectionWeight = 1000000000 // maximum selection weight of each individual node/address group @@ -340,24 +340,9 @@ func (l *Limiter) Stop() { l.cond.Signal() } -type ( - dropList []dropListItem - dropListItem struct { - nq *nodeQueue - priority float64 - } -) - -func (l dropList) Len() int { - return len(l) -} - -func (l dropList) Less(i, j int) bool { - return l[i].priority < l[j].priority -} - -func (l dropList) Swap(i, j int) { - l[i], l[j] = l[j], l[i] +type dropListItem struct { + nq *nodeQueue + priority float64 } // dropRequests selects the nodes with the highest queued request cost to selection @@ -366,7 +351,7 @@ func (l dropList) Swap(i, j int) { func (l *Limiter) dropRequests() { var ( sumValue float64 - list dropList + list []dropListItem ) for _, nq := range l.nodes { sumValue += nq.value @@ -384,7 +369,15 @@ func (l *Limiter) dropRequests() { priority: w / float64(nq.sumCost), }) } - sort.Sort(list) + slices.SortFunc(list, func(a, b dropListItem) int { + if a.priority < b.priority { + return -1 + } + if a.priority < b.priority { + return 1 + } + return 0 + }) for _, item := range list { for _, request := range item.nq.queue { close(request.process) diff --git a/light/lightchain_test.go b/light/lightchain_test.go index e3d756f80111..5694ca72c266 100644 --- a/light/lightchain_test.go +++ b/light/lightchain_test.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" ) // So we can deterministically seed different blockchains @@ -55,7 +56,7 @@ func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) [ func newCanonical(n int) (ethdb.Database, *LightChain, error) { db := rawdb.NewMemoryDatabase() gspec := core.Genesis{Config: params.TestChainConfig} - genesis := gspec.MustCommit(db) + genesis := gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults)) blockchain, _ := NewLightChain(&dummyOdr{db: db, indexerConfig: TestClientIndexerConfig}, gspec.Config, ethash.NewFaker()) // Create and inject the requested chain @@ -75,7 +76,7 @@ func newTestLightChain() *LightChain { Difficulty: big.NewInt(1), Config: params.TestChainConfig, } - gspec.MustCommit(db) + gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults)) lc, err := NewLightChain(&dummyOdr{db: db}, gspec.Config, ethash.NewFullFaker()) if err != nil { panic(err) diff --git a/light/odr.go b/light/odr.go index d331d3c9b522..39f626ee2c53 100644 --- a/light/odr.go +++ b/light/odr.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/trie/trienode" ) // NoOdr is the default context passed to an ODR capable function when the ODR @@ -54,35 +55,35 @@ type OdrRequest interface { // TrieID identifies a state or account storage trie type TrieID struct { - BlockHash common.Hash - BlockNumber uint64 - StateRoot common.Hash - Root common.Hash - AccKey []byte + BlockHash common.Hash + BlockNumber uint64 + StateRoot common.Hash + Root common.Hash + AccountAddress []byte } // StateTrieID returns a TrieID for a state trie belonging to a certain block // header. func StateTrieID(header *types.Header) *TrieID { return &TrieID{ - BlockHash: header.Hash(), - BlockNumber: header.Number.Uint64(), - StateRoot: header.Root, - Root: header.Root, - AccKey: nil, + BlockHash: header.Hash(), + BlockNumber: header.Number.Uint64(), + StateRoot: header.Root, + Root: header.Root, + AccountAddress: nil, } } // StorageTrieID returns a TrieID for a contract storage trie at a given account // of a given state trie. It also requires the root hash of the trie for // checking Merkle proofs. -func StorageTrieID(state *TrieID, addrHash, root common.Hash) *TrieID { +func StorageTrieID(state *TrieID, address common.Address, root common.Hash) *TrieID { return &TrieID{ - BlockHash: state.BlockHash, - BlockNumber: state.BlockNumber, - StateRoot: state.StateRoot, - AccKey: addrHash[:], - Root: root, + BlockHash: state.BlockHash, + BlockNumber: state.BlockNumber, + StateRoot: state.StateRoot, + AccountAddress: address[:], + Root: root, } } @@ -90,7 +91,7 @@ func StorageTrieID(state *TrieID, addrHash, root common.Hash) *TrieID { type TrieRequest struct { Id *TrieID Key []byte - Proof *NodeSet + Proof *trienode.ProofSet } // StoreResult stores the retrieved data in local database @@ -143,7 +144,7 @@ type ChtRequest struct { ChtRoot common.Hash Header *types.Header Td *big.Int - Proof *NodeSet + Proof *trienode.ProofSet } // StoreResult stores the retrieved data in local database @@ -163,7 +164,7 @@ type BloomRequest struct { SectionIndexList []uint64 BloomTrieRoot common.Hash BloomBits [][]byte - Proofs *NodeSet + Proofs *trienode.ProofSet } // StoreResult stores the retrieved data in local database diff --git a/light/odr_test.go b/light/odr_test.go index f9627171a180..27b90efcb674 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -36,6 +36,8 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/trienode" ) var ( @@ -86,16 +88,16 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error { err error t state.Trie ) - if len(req.Id.AccKey) > 0 { - t, err = odr.serverState.OpenStorageTrie(req.Id.StateRoot, common.BytesToHash(req.Id.AccKey), req.Id.Root) + if len(req.Id.AccountAddress) > 0 { + t, err = odr.serverState.OpenStorageTrie(req.Id.StateRoot, common.BytesToAddress(req.Id.AccountAddress), req.Id.Root) } else { t, err = odr.serverState.OpenTrie(req.Id.Root) } if err != nil { panic(err) } - nodes := NewNodeSet() - t.Prove(req.Key, 0, nodes) + nodes := trienode.NewProofSet() + t.Prove(req.Key, nodes) req.Proof = nodes case *CodeRequest: req.Data = rawdb.ReadCode(odr.sdb, req.Hash) @@ -282,7 +284,7 @@ func testChainOdr(t *testing.T, protocol int, fn odrTestFn) { t.Fatal(err) } - gspec.MustCommit(ldb) + gspec.MustCommit(ldb, trie.NewDatabase(ldb, trie.HashDefaults)) odr := &testOdr{sdb: sdb, ldb: ldb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig} lightchain, err := NewLightChain(odr, gspec.Config, ethash.NewFullFaker()) if err != nil { diff --git a/light/odr_util.go b/light/odr_util.go index 8e2047f00688..9cac7df4fa98 100644 --- a/light/odr_util.go +++ b/light/odr_util.go @@ -17,12 +17,12 @@ package light import ( - "bytes" "context" "errors" "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" @@ -125,7 +125,7 @@ func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash, number uint6 return nil, err } body := new(types.Body) - if err := rlp.Decode(bytes.NewReader(data), body); err != nil { + if err := rlp.DecodeBytes(data, body); err != nil { return nil, err } return body, nil @@ -175,7 +175,13 @@ func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, num genesis := rawdb.ReadCanonicalHash(odr.Database(), 0) config := rawdb.ReadChainConfig(odr.Database(), genesis) - if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), block.Transactions()); err != nil { + var blobGasPrice *big.Int + excessBlobGas := block.ExcessBlobGas() + if excessBlobGas != nil { + blobGasPrice = eip4844.CalcBlobFee(*excessBlobGas) + } + + if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), blobGasPrice, block.Transactions()); err != nil { return nil, err } rawdb.WriteReceipts(odr.Database(), hash, number, receipts) diff --git a/light/postprocess.go b/light/postprocess.go index 001df209b7a3..a317e30b90a4 100644 --- a/light/postprocess.go +++ b/light/postprocess.go @@ -145,7 +145,7 @@ func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64, dis diskdb: db, odr: odr, trieTable: trieTable, - triedb: trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down + triedb: trie.NewDatabase(trieTable, trie.HashDefaults), sectionSize: size, disablePruning: disablePruning, } @@ -214,10 +214,13 @@ func (c *ChtIndexerBackend) Process(ctx context.Context, header *types.Header) e // Commit implements core.ChainIndexerBackend func (c *ChtIndexerBackend) Commit() error { - root, nodes := c.trie.Commit(false) + root, nodes, err := c.trie.Commit(false) + if err != nil { + return err + } // Commit trie changes into trie database in case it's not nil. if nodes != nil { - if err := c.triedb.Update(root, c.originRoot, trienode.NewWithNodeSet(nodes)); err != nil { + if err := c.triedb.Update(root, c.originRoot, 0, trienode.NewWithNodeSet(nodes), nil); err != nil { return err } if err := c.triedb.Commit(root, false); err != nil { @@ -225,7 +228,6 @@ func (c *ChtIndexerBackend) Commit() error { } } // Re-create trie with newly generated root and updated database. - var err error c.trie, err = trie.New(trie.TrieID(root), c.triedb) if err != nil { return err @@ -346,7 +348,7 @@ func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uin diskdb: db, odr: odr, trieTable: trieTable, - triedb: trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down + triedb: trie.NewDatabase(trieTable, trie.HashDefaults), parentSize: parentSize, size: size, disablePruning: disablePruning, @@ -361,7 +363,7 @@ func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uin func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error { indexCh := make(chan uint, types.BloomBitLength) type res struct { - nodes *NodeSet + nodes *trienode.ProofSet err error } resCh := make(chan res, types.BloomBitLength) @@ -465,10 +467,13 @@ func (b *BloomTrieIndexerBackend) Commit() error { return terr } } - root, nodes := b.trie.Commit(false) + root, nodes, err := b.trie.Commit(false) + if err != nil { + return err + } // Commit trie changes into trie database in case it's not nil. if nodes != nil { - if err := b.triedb.Update(root, b.originRoot, trienode.NewWithNodeSet(nodes)); err != nil { + if err := b.triedb.Update(root, b.originRoot, 0, trienode.NewWithNodeSet(nodes), nil); err != nil { return err } if err := b.triedb.Commit(root, false); err != nil { @@ -476,7 +481,6 @@ func (b *BloomTrieIndexerBackend) Commit() error { } } // Re-create trie with newly generated root and updated database. - var err error b.trie, err = trie.New(trie.TrieID(root), b.triedb) if err != nil { return err diff --git a/light/trie.go b/light/trie.go index 6d802a5fbf98..1847f1e71b3c 100644 --- a/light/trie.go +++ b/light/trie.go @@ -55,8 +55,8 @@ func (db *odrDatabase) OpenTrie(root common.Hash) (state.Trie, error) { return &odrTrie{db: db, id: db.id}, nil } -func (db *odrDatabase) OpenStorageTrie(state, addrHash, root common.Hash) (state.Trie, error) { - return &odrTrie{db: db, id: StorageTrieID(db.id, addrHash, root)}, nil +func (db *odrDatabase) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash) (state.Trie, error) { + return &odrTrie{db: db, id: StorageTrieID(db.id, address, root)}, nil } func (db *odrDatabase) CopyTrie(t state.Trie) state.Trie { @@ -72,7 +72,7 @@ func (db *odrDatabase) CopyTrie(t state.Trie) state.Trie { } } -func (db *odrDatabase) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) { +func (db *odrDatabase) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) { if codeHash == sha3Nil { return nil, nil } @@ -81,14 +81,14 @@ func (db *odrDatabase) ContractCode(addrHash, codeHash common.Hash) ([]byte, err return code, nil } id := *db.id - id.AccKey = addrHash[:] + id.AccountAddress = addr[:] req := &CodeRequest{Id: &id, Hash: codeHash} err := db.backend.Retrieve(db.ctx, req) return req.Data, err } -func (db *odrDatabase) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) { - code, err := db.ContractCode(addrHash, codeHash) +func (db *odrDatabase) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) { + code, err := db.ContractCode(addr, codeHash) return len(code), err } @@ -108,28 +108,35 @@ type odrTrie struct { func (t *odrTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) { key = crypto.Keccak256(key) - var res []byte + var enc []byte err := t.do(key, func() (err error) { - res, err = t.trie.Get(key) + enc, err = t.trie.Get(key) return err }) - return res, err + if err != nil || len(enc) == 0 { + return nil, err + } + _, content, _, err := rlp.Split(enc) + return content, err } func (t *odrTrie) GetAccount(address common.Address) (*types.StateAccount, error) { - var res types.StateAccount - key := crypto.Keccak256(address.Bytes()) + var ( + enc []byte + key = crypto.Keccak256(address.Bytes()) + ) err := t.do(key, func() (err error) { - value, err := t.trie.Get(key) - if err != nil { - return err - } - if value == nil { - return nil - } - return rlp.DecodeBytes(value, &res) + enc, err = t.trie.Get(key) + return err }) - return &res, err + if err != nil || len(enc) == 0 { + return nil, err + } + acct := new(types.StateAccount) + if err := rlp.DecodeBytes(enc, acct); err != nil { + return nil, err + } + return acct, nil } func (t *odrTrie) UpdateAccount(address common.Address, acc *types.StateAccount) error { @@ -143,10 +150,15 @@ func (t *odrTrie) UpdateAccount(address common.Address, acc *types.StateAccount) }) } +func (t *odrTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error { + return nil +} + func (t *odrTrie) UpdateStorage(_ common.Address, key, value []byte) error { key = crypto.Keccak256(key) + v, _ := rlp.EncodeToBytes(value) return t.do(key, func() error { - return t.trie.Update(key, value) + return t.trie.Update(key, v) }) } @@ -165,9 +177,9 @@ func (t *odrTrie) DeleteAccount(address common.Address) error { }) } -func (t *odrTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) { +func (t *odrTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) { if t.trie == nil { - return t.id.Root, nil + return t.id.Root, nil, nil } return t.trie.Commit(collectLeaf) } @@ -179,15 +191,15 @@ func (t *odrTrie) Hash() common.Hash { return t.trie.Hash() } -func (t *odrTrie) NodeIterator(startkey []byte) trie.NodeIterator { - return newNodeIterator(t, startkey) +func (t *odrTrie) NodeIterator(startkey []byte) (trie.NodeIterator, error) { + return newNodeIterator(t, startkey), nil } func (t *odrTrie) GetKey(sha []byte) []byte { return nil } -func (t *odrTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error { +func (t *odrTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { return errors.New("not implemented, needs client/server interface split") } @@ -198,12 +210,13 @@ func (t *odrTrie) do(key []byte, fn func() error) error { var err error if t.trie == nil { var id *trie.ID - if len(t.id.AccKey) > 0 { - id = trie.StorageTrieID(t.id.StateRoot, common.BytesToHash(t.id.AccKey), t.id.Root) + if len(t.id.AccountAddress) > 0 { + id = trie.StorageTrieID(t.id.StateRoot, crypto.Keccak256Hash(t.id.AccountAddress), t.id.Root) } else { id = trie.StateTrieID(t.id.StateRoot) } - t.trie, err = trie.New(id, trie.NewDatabase(t.db.backend.Database())) + triedb := trie.NewDatabase(t.db.backend.Database(), trie.HashDefaults) + t.trie, err = trie.New(id, triedb) } if err == nil { err = fn() @@ -230,12 +243,13 @@ func newNodeIterator(t *odrTrie, startkey []byte) trie.NodeIterator { if t.trie == nil { it.do(func() error { var id *trie.ID - if len(t.id.AccKey) > 0 { - id = trie.StorageTrieID(t.id.StateRoot, common.BytesToHash(t.id.AccKey), t.id.Root) + if len(t.id.AccountAddress) > 0 { + id = trie.StorageTrieID(t.id.StateRoot, crypto.Keccak256Hash(t.id.AccountAddress), t.id.Root) } else { id = trie.StateTrieID(t.id.StateRoot) } - t, err := trie.New(id, trie.NewDatabase(t.db.backend.Database())) + triedb := trie.NewDatabase(t.db.backend.Database(), trie.HashDefaults) + t, err := trie.New(id, triedb) if err == nil { it.t.trie = t } @@ -243,7 +257,11 @@ func newNodeIterator(t *odrTrie, startkey []byte) trie.NodeIterator { }) } it.do(func() error { - it.NodeIterator = it.t.trie.NodeIterator(startkey) + var err error + it.NodeIterator, err = it.t.trie.NodeIterator(startkey) + if err != nil { + return err + } return it.NodeIterator.Error() }) return it diff --git a/light/trie_test.go b/light/trie_test.go index 0b15278b11af..b5344460690b 100644 --- a/light/trie_test.go +++ b/light/trie_test.go @@ -50,7 +50,7 @@ func TestNodeIterator(t *testing.T) { panic(err) } - gspec.MustCommit(lightdb) + gspec.MustCommit(lightdb, trie.NewDatabase(lightdb, trie.HashDefaults)) ctx := context.Background() odr := &testOdr{sdb: fulldb, ldb: lightdb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig} head := blockchain.CurrentHeader() @@ -62,8 +62,16 @@ func TestNodeIterator(t *testing.T) { } func diffTries(t1, t2 state.Trie) error { - i1 := trie.NewIterator(t1.NodeIterator(nil)) - i2 := trie.NewIterator(t2.NodeIterator(nil)) + trieIt1, err := t1.NodeIterator(nil) + if err != nil { + return err + } + trieIt2, err := t2.NodeIterator(nil) + if err != nil { + return err + } + i1 := trie.NewIterator(trieIt1) + i2 := trie.NewIterator(trieIt2) for i1.Next() && i2.Next() { if !bytes.Equal(i1.Key, i2.Key) { spew.Dump(i2) diff --git a/light/txpool.go b/light/txpool.go index c3f71a7b972a..93b3c388b4b6 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -494,29 +494,29 @@ func (pool *TxPool) GetTransactions() (txs types.Transactions, err error) { // Content retrieves the data content of the transaction pool, returning all the // pending as well as queued transactions, grouped by account and nonce. -func (pool *TxPool) Content() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { +func (pool *TxPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) { pool.mu.RLock() defer pool.mu.RUnlock() // Retrieve all the pending transactions and sort by account and by nonce - pending := make(map[common.Address]types.Transactions) + pending := make(map[common.Address][]*types.Transaction) for _, tx := range pool.pending { account, _ := types.Sender(pool.signer, tx) pending[account] = append(pending[account], tx) } // There are no queued transactions in a light pool, just return an empty map - queued := make(map[common.Address]types.Transactions) + queued := make(map[common.Address][]*types.Transaction) return pending, queued } // ContentFrom retrieves the data content of the transaction pool, returning the // pending as well as queued transactions of this address, grouped by nonce. -func (pool *TxPool) ContentFrom(addr common.Address) (types.Transactions, types.Transactions) { +func (pool *TxPool) ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) { pool.mu.RLock() defer pool.mu.RUnlock() // Retrieve the pending transactions and sort by nonce - var pending types.Transactions + var pending []*types.Transaction for _, tx := range pool.pending { account, _ := types.Sender(pool.signer, tx) if account != addr { @@ -525,7 +525,7 @@ func (pool *TxPool) ContentFrom(addr common.Address) (types.Transactions, types. pending = append(pending, tx) } // There are no queued transactions in a light pool, just return an empty map - return pending, types.Transactions{} + return pending, []*types.Transaction{} } // RemoveTransactions removes all given transactions from the pool. diff --git a/light/txpool_test.go b/light/txpool_test.go index ee00e591e191..047f86c48017 100644 --- a/light/txpool_test.go +++ b/light/txpool_test.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" ) type testTxRelay struct { @@ -96,7 +97,7 @@ func TestTxPool(t *testing.T) { panic(err) } - gspec.MustCommit(ldb) + gspec.MustCommit(ldb, trie.NewDatabase(ldb, trie.HashDefaults)) odr := &testOdr{sdb: sdb, ldb: ldb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig} relay := &testTxRelay{ send: make(chan int, 1), diff --git a/log/format.go b/log/format.go index 315c6dcc4a8b..42a6d5d68971 100644 --- a/log/format.go +++ b/log/format.go @@ -32,20 +32,24 @@ var locationTrims = []string{ // PrintOrigins sets or unsets log location (file:line) printing for terminal // format output. func PrintOrigins(print bool) { + locationEnabled.Store(print) if print { - atomic.StoreUint32(&locationEnabled, 1) - } else { - atomic.StoreUint32(&locationEnabled, 0) + stackEnabled.Store(true) } } +// stackEnabled is an atomic flag controlling whether the log handler needs +// to store the callsite stack. This is needed in case any handler wants to +// print locations (locationEnabled), use vmodule, or print full stacks (BacktraceAt). +var stackEnabled atomic.Bool + // locationEnabled is an atomic flag controlling whether the terminal formatter // should append the log locations too when printing entries. -var locationEnabled uint32 +var locationEnabled atomic.Bool // locationLength is the maxmimum path length encountered, which all logs are // padded to to aid in alignment. -var locationLength uint32 +var locationLength atomic.Uint32 // fieldPadding is a global map with maximum field value lengths seen until now // to allow padding log contexts in a bit smarter way. @@ -109,17 +113,17 @@ func TerminalFormat(usecolor bool) Format { b := &bytes.Buffer{} lvl := r.Lvl.AlignedString() - if atomic.LoadUint32(&locationEnabled) != 0 { + if locationEnabled.Load() { // Log origin printing was requested, format the location path and line number location := fmt.Sprintf("%+v", r.Call) for _, prefix := range locationTrims { location = strings.TrimPrefix(location, prefix) } // Maintain the maximum location length for fancyer alignment - align := int(atomic.LoadUint32(&locationLength)) + align := int(locationLength.Load()) if align < len(location) { align = len(location) - atomic.StoreUint32(&locationLength, uint32(align)) + locationLength.Store(uint32(align)) } padding := strings.Repeat(" ", align-len(location)) diff --git a/log/handler.go b/log/handler.go index 892cfcc3e1ac..4a0cf578f6cd 100644 --- a/log/handler.go +++ b/log/handler.go @@ -7,6 +7,7 @@ import ( "os" "reflect" "sync" + "sync/atomic" "github.com/go-stack/stack" ) @@ -354,3 +355,21 @@ func (m muster) FileHandler(path string, fmtr Format) Handler { func (m muster) NetHandler(network, addr string, fmtr Format) Handler { return must(NetHandler(network, addr, fmtr)) } + +// swapHandler wraps another handler that may be swapped out +// dynamically at runtime in a thread-safe fashion. +type swapHandler struct { + handler atomic.Value +} + +func (h *swapHandler) Log(r *Record) error { + return (*h.handler.Load().(*Handler)).Log(r) +} + +func (h *swapHandler) Swap(newHandler Handler) { + h.handler.Store(&newHandler) +} + +func (h *swapHandler) Get() Handler { + return *h.handler.Load().(*Handler) +} diff --git a/log/handler_glog.go b/log/handler_glog.go index b5186d4b27ec..afca0808b38a 100644 --- a/log/handler_glog.go +++ b/log/handler_glog.go @@ -39,9 +39,9 @@ var errTraceSyntax = errors.New("expect file.go:234") type GlogHandler struct { origin Handler // The origin handler this wraps - level uint32 // Current log level, atomically accessible - override uint32 // Flag whether overrides are used, atomically accessible - backtrace uint32 // Flag whether backtrace location is set + level atomic.Uint32 // Current log level, atomically accessible + override atomic.Bool // Flag whether overrides are used, atomically accessible + backtrace atomic.Bool // Flag whether backtrace location is set patterns []pattern // Current list of patterns to override with siteCache map[uintptr]Lvl // Cache of callsite pattern evaluations @@ -72,7 +72,7 @@ type pattern struct { // Verbosity sets the glog verbosity ceiling. The verbosity of individual packages // and source files can be raised using Vmodule. func (h *GlogHandler) Verbosity(level Lvl) { - atomic.StoreUint32(&h.level, uint32(level)) + h.level.Store(uint32(level)) } // Vmodule sets the glog verbosity pattern. @@ -138,8 +138,11 @@ func (h *GlogHandler) Vmodule(ruleset string) error { h.patterns = filter h.siteCache = make(map[uintptr]Lvl) - atomic.StoreUint32(&h.override, uint32(len(filter))) - + h.override.Store(len(filter) != 0) + // Enable location storage (globally) + if len(h.patterns) > 0 { + stackEnabled.Store(true) + } return nil } @@ -171,8 +174,9 @@ func (h *GlogHandler) BacktraceAt(location string) error { defer h.lock.Unlock() h.location = location - atomic.StoreUint32(&h.backtrace, uint32(len(location))) - + h.backtrace.Store(len(location) > 0) + // Enable location storage (globally) + stackEnabled.Store(true) return nil } @@ -180,7 +184,7 @@ func (h *GlogHandler) BacktraceAt(location string) error { // and backtrace filters, finally emitting it if either allow it through. func (h *GlogHandler) Log(r *Record) error { // If backtracing is requested, check whether this is the callsite - if atomic.LoadUint32(&h.backtrace) > 0 { + if h.backtrace.Load() { // Everything below here is slow. Although we could cache the call sites the // same way as for vmodule, backtracing is so rare it's not worth the extra // complexity. @@ -198,11 +202,11 @@ func (h *GlogHandler) Log(r *Record) error { } } // If the global log level allows, fast track logging - if atomic.LoadUint32(&h.level) >= uint32(r.Lvl) { + if h.level.Load() >= uint32(r.Lvl) { return h.origin.Log(r) } // If no local overrides are present, fast track skipping - if atomic.LoadUint32(&h.override) == 0 { + if !h.override.Load() { return nil } // Check callsite cache for previously calculated log levels diff --git a/log/handler_go13.go b/log/handler_go13.go deleted file mode 100644 index 4df694debed8..000000000000 --- a/log/handler_go13.go +++ /dev/null @@ -1,27 +0,0 @@ -//go:build !go1.4 -// +build !go1.4 - -package log - -import ( - "sync/atomic" - "unsafe" -) - -// swapHandler wraps another handler that may be swapped out -// dynamically at runtime in a thread-safe fashion. -type swapHandler struct { - handler unsafe.Pointer -} - -func (h *swapHandler) Log(r *Record) error { - return h.Get().Log(r) -} - -func (h *swapHandler) Get() Handler { - return *(*Handler)(atomic.LoadPointer(&h.handler)) -} - -func (h *swapHandler) Swap(newHandler Handler) { - atomic.StorePointer(&h.handler, unsafe.Pointer(&newHandler)) -} diff --git a/log/handler_go14.go b/log/handler_go14.go deleted file mode 100644 index d0cb14aa063b..000000000000 --- a/log/handler_go14.go +++ /dev/null @@ -1,24 +0,0 @@ -//go:build go1.4 -// +build go1.4 - -package log - -import "sync/atomic" - -// swapHandler wraps another handler that may be swapped out -// dynamically at runtime in a thread-safe fashion. -type swapHandler struct { - handler atomic.Value -} - -func (h *swapHandler) Log(r *Record) error { - return (*h.handler.Load().(*Handler)).Log(r) -} - -func (h *swapHandler) Swap(newHandler Handler) { - h.handler.Store(&newHandler) -} - -func (h *swapHandler) Get() Handler { - return *h.handler.Load().(*Handler) -} diff --git a/log/logger.go b/log/logger.go index 4e471a22da9a..42e7e375d002 100644 --- a/log/logger.go +++ b/log/logger.go @@ -177,19 +177,22 @@ type logger struct { } func (l *logger) write(msg string, lvl Lvl, ctx []interface{}, skip int) { - l.h.Log(&Record{ + record := &Record{ Time: time.Now(), Lvl: lvl, Msg: msg, Ctx: newContext(l.ctx, ctx), - Call: stack.Caller(skip), KeyNames: RecordKeyNames{ Time: timeKey, Msg: msgKey, Lvl: lvlKey, Ctx: ctxKey, }, - }) + } + if stackEnabled.Load() { + record.Call = stack.Caller(skip) + } + l.h.Log(record) } func (l *logger) New(ctx ...interface{}) Logger { diff --git a/log/logger_test.go b/log/logger_test.go new file mode 100644 index 000000000000..2e59b3fdf0b1 --- /dev/null +++ b/log/logger_test.go @@ -0,0 +1,67 @@ +package log + +import ( + "bytes" + "os" + "strings" + "testing" +) + +// TestLoggingWithTrace checks that if BackTraceAt is set, then the +// gloghandler is capable of spitting out a stacktrace +func TestLoggingWithTrace(t *testing.T) { + defer stackEnabled.Store(stackEnabled.Load()) + out := new(bytes.Buffer) + logger := New() + { + glog := NewGlogHandler(StreamHandler(out, TerminalFormat(false))) + glog.Verbosity(LvlTrace) + if err := glog.BacktraceAt("logger_test.go:24"); err != nil { + t.Fatal(err) + } + logger.SetHandler(glog) + } + logger.Trace("a message", "foo", "bar") // Will be bumped to INFO + have := out.String() + if !strings.HasPrefix(have, "INFO") { + t.Fatalf("backtraceat should bump level to info: %s", have) + } + // The timestamp is locale-dependent, so we want to trim that off + // "INFO [01-01|00:00:00.000] a messag ..." -> "a messag..." + have = strings.Split(have, "]")[1] + wantPrefix := " a message\n\ngoroutine" + if !strings.HasPrefix(have, wantPrefix) { + t.Errorf("\nhave: %q\nwant: %q\n", have, wantPrefix) + } +} + +// TestLoggingWithVmodule checks that vmodule works. +func TestLoggingWithVmodule(t *testing.T) { + defer stackEnabled.Store(stackEnabled.Load()) + out := new(bytes.Buffer) + logger := New() + { + glog := NewGlogHandler(StreamHandler(out, TerminalFormat(false))) + glog.Verbosity(LvlCrit) + logger.SetHandler(glog) + logger.Warn("This should not be seen", "ignored", "true") + glog.Vmodule("logger_test.go=5") + } + logger.Trace("a message", "foo", "bar") + have := out.String() + // The timestamp is locale-dependent, so we want to trim that off + // "INFO [01-01|00:00:00.000] a messag ..." -> "a messag..." + have = strings.Split(have, "]")[1] + want := " a message foo=bar\n" + if have != want { + t.Errorf("\nhave: %q\nwant: %q\n", have, want) + } +} + +func BenchmarkTraceLogging(b *testing.B) { + Root().SetHandler(LvlFilterHandler(LvlInfo, StreamHandler(os.Stderr, TerminalFormat(true)))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Trace("a message", "v", i) + } +} diff --git a/metrics/counter.go b/metrics/counter.go index 55e1c59540f6..cb81599c215a 100644 --- a/metrics/counter.go +++ b/metrics/counter.go @@ -4,13 +4,16 @@ import ( "sync/atomic" ) +type CounterSnapshot interface { + Count() int64 +} + // Counters hold an int64 value that can be incremented and decremented. type Counter interface { Clear() - Count() int64 Dec(int64) Inc(int64) - Snapshot() Counter + Snapshot() CounterSnapshot } // GetOrRegisterCounter returns an existing Counter or constructs and registers @@ -38,13 +41,13 @@ func NewCounter() Counter { if !Enabled { return NilCounter{} } - return &StandardCounter{} + return new(StandardCounter) } // NewCounterForced constructs a new StandardCounter and returns it no matter if // the global switch is enabled or not. func NewCounterForced() Counter { - return &StandardCounter{} + return new(StandardCounter) } // NewRegisteredCounter constructs and registers a new StandardCounter. @@ -70,75 +73,40 @@ func NewRegisteredCounterForced(name string, r Registry) Counter { return c } -// CounterSnapshot is a read-only copy of another Counter. -type CounterSnapshot int64 - -// Clear panics. -func (CounterSnapshot) Clear() { - panic("Clear called on a CounterSnapshot") -} +// counterSnapshot is a read-only copy of another Counter. +type counterSnapshot int64 // Count returns the count at the time the snapshot was taken. -func (c CounterSnapshot) Count() int64 { return int64(c) } - -// Dec panics. -func (CounterSnapshot) Dec(int64) { - panic("Dec called on a CounterSnapshot") -} - -// Inc panics. -func (CounterSnapshot) Inc(int64) { - panic("Inc called on a CounterSnapshot") -} - -// Snapshot returns the snapshot. -func (c CounterSnapshot) Snapshot() Counter { return c } +func (c counterSnapshot) Count() int64 { return int64(c) } // NilCounter is a no-op Counter. type NilCounter struct{} -// Clear is a no-op. -func (NilCounter) Clear() {} - -// Count is a no-op. -func (NilCounter) Count() int64 { return 0 } - -// Dec is a no-op. -func (NilCounter) Dec(i int64) {} - -// Inc is a no-op. -func (NilCounter) Inc(i int64) {} - -// Snapshot is a no-op. -func (NilCounter) Snapshot() Counter { return NilCounter{} } +func (NilCounter) Clear() {} +func (NilCounter) Dec(i int64) {} +func (NilCounter) Inc(i int64) {} +func (NilCounter) Snapshot() CounterSnapshot { return (*emptySnapshot)(nil) } // StandardCounter is the standard implementation of a Counter and uses the // sync/atomic package to manage a single int64 value. -type StandardCounter struct { - count atomic.Int64 -} +type StandardCounter atomic.Int64 // Clear sets the counter to zero. func (c *StandardCounter) Clear() { - c.count.Store(0) -} - -// Count returns the current count. -func (c *StandardCounter) Count() int64 { - return c.count.Load() + (*atomic.Int64)(c).Store(0) } // Dec decrements the counter by the given amount. func (c *StandardCounter) Dec(i int64) { - c.count.Add(-i) + (*atomic.Int64)(c).Add(-i) } // Inc increments the counter by the given amount. func (c *StandardCounter) Inc(i int64) { - c.count.Add(i) + (*atomic.Int64)(c).Add(i) } // Snapshot returns a read-only copy of the counter. -func (c *StandardCounter) Snapshot() Counter { - return CounterSnapshot(c.Count()) +func (c *StandardCounter) Snapshot() CounterSnapshot { + return counterSnapshot((*atomic.Int64)(c).Load()) } diff --git a/metrics/counter_float64.go b/metrics/counter_float64.go index d1197bb8e0ae..15c81494efb8 100644 --- a/metrics/counter_float64.go +++ b/metrics/counter_float64.go @@ -5,13 +5,16 @@ import ( "sync/atomic" ) +type CounterFloat64Snapshot interface { + Count() float64 +} + // CounterFloat64 holds a float64 value that can be incremented and decremented. type CounterFloat64 interface { Clear() - Count() float64 Dec(float64) Inc(float64) - Snapshot() CounterFloat64 + Snapshot() CounterFloat64Snapshot } // GetOrRegisterCounterFloat64 returns an existing CounterFloat64 or constructs and registers @@ -71,47 +74,19 @@ func NewRegisteredCounterFloat64Forced(name string, r Registry) CounterFloat64 { return c } -// CounterFloat64Snapshot is a read-only copy of another CounterFloat64. -type CounterFloat64Snapshot float64 - -// Clear panics. -func (CounterFloat64Snapshot) Clear() { - panic("Clear called on a CounterFloat64Snapshot") -} +// counterFloat64Snapshot is a read-only copy of another CounterFloat64. +type counterFloat64Snapshot float64 // Count returns the value at the time the snapshot was taken. -func (c CounterFloat64Snapshot) Count() float64 { return float64(c) } - -// Dec panics. -func (CounterFloat64Snapshot) Dec(float64) { - panic("Dec called on a CounterFloat64Snapshot") -} +func (c counterFloat64Snapshot) Count() float64 { return float64(c) } -// Inc panics. -func (CounterFloat64Snapshot) Inc(float64) { - panic("Inc called on a CounterFloat64Snapshot") -} - -// Snapshot returns the snapshot. -func (c CounterFloat64Snapshot) Snapshot() CounterFloat64 { return c } - -// NilCounterFloat64 is a no-op CounterFloat64. type NilCounterFloat64 struct{} -// Clear is a no-op. -func (NilCounterFloat64) Clear() {} - -// Count is a no-op. -func (NilCounterFloat64) Count() float64 { return 0.0 } - -// Dec is a no-op. -func (NilCounterFloat64) Dec(i float64) {} - -// Inc is a no-op. -func (NilCounterFloat64) Inc(i float64) {} - -// Snapshot is a no-op. -func (NilCounterFloat64) Snapshot() CounterFloat64 { return NilCounterFloat64{} } +func (NilCounterFloat64) Clear() {} +func (NilCounterFloat64) Count() float64 { return 0.0 } +func (NilCounterFloat64) Dec(i float64) {} +func (NilCounterFloat64) Inc(i float64) {} +func (NilCounterFloat64) Snapshot() CounterFloat64Snapshot { return NilCounterFloat64{} } // StandardCounterFloat64 is the standard implementation of a CounterFloat64 and uses the // atomic to manage a single float64 value. @@ -124,11 +99,6 @@ func (c *StandardCounterFloat64) Clear() { c.floatBits.Store(0) } -// Count returns the current value. -func (c *StandardCounterFloat64) Count() float64 { - return math.Float64frombits(c.floatBits.Load()) -} - // Dec decrements the counter by the given amount. func (c *StandardCounterFloat64) Dec(v float64) { atomicAddFloat(&c.floatBits, -v) @@ -140,8 +110,9 @@ func (c *StandardCounterFloat64) Inc(v float64) { } // Snapshot returns a read-only copy of the counter. -func (c *StandardCounterFloat64) Snapshot() CounterFloat64 { - return CounterFloat64Snapshot(c.Count()) +func (c *StandardCounterFloat64) Snapshot() CounterFloat64Snapshot { + v := math.Float64frombits(c.floatBits.Load()) + return counterFloat64Snapshot(v) } func atomicAddFloat(fbits *atomic.Uint64, v float64) { diff --git a/metrics/counter_float_64_test.go b/metrics/counter_float_64_test.go index f17aca330cbe..c21bd3307fa1 100644 --- a/metrics/counter_float_64_test.go +++ b/metrics/counter_float_64_test.go @@ -27,7 +27,7 @@ func BenchmarkCounterFloat64Parallel(b *testing.B) { }() } wg.Wait() - if have, want := c.Count(), 10.0*float64(b.N); have != want { + if have, want := c.Snapshot().Count(), 10.0*float64(b.N); have != want { b.Fatalf("have %f want %f", have, want) } } @@ -36,7 +36,7 @@ func TestCounterFloat64Clear(t *testing.T) { c := NewCounterFloat64() c.Inc(1.0) c.Clear() - if count := c.Count(); count != 0 { + if count := c.Snapshot().Count(); count != 0 { t.Errorf("c.Count(): 0 != %v\n", count) } } @@ -44,7 +44,7 @@ func TestCounterFloat64Clear(t *testing.T) { func TestCounterFloat64Dec1(t *testing.T) { c := NewCounterFloat64() c.Dec(1.0) - if count := c.Count(); count != -1.0 { + if count := c.Snapshot().Count(); count != -1.0 { t.Errorf("c.Count(): -1.0 != %v\n", count) } } @@ -52,7 +52,7 @@ func TestCounterFloat64Dec1(t *testing.T) { func TestCounterFloat64Dec2(t *testing.T) { c := NewCounterFloat64() c.Dec(2.0) - if count := c.Count(); count != -2.0 { + if count := c.Snapshot().Count(); count != -2.0 { t.Errorf("c.Count(): -2.0 != %v\n", count) } } @@ -60,7 +60,7 @@ func TestCounterFloat64Dec2(t *testing.T) { func TestCounterFloat64Inc1(t *testing.T) { c := NewCounterFloat64() c.Inc(1.0) - if count := c.Count(); count != 1.0 { + if count := c.Snapshot().Count(); count != 1.0 { t.Errorf("c.Count(): 1.0 != %v\n", count) } } @@ -68,7 +68,7 @@ func TestCounterFloat64Inc1(t *testing.T) { func TestCounterFloat64Inc2(t *testing.T) { c := NewCounterFloat64() c.Inc(2.0) - if count := c.Count(); count != 2.0 { + if count := c.Snapshot().Count(); count != 2.0 { t.Errorf("c.Count(): 2.0 != %v\n", count) } } @@ -85,7 +85,7 @@ func TestCounterFloat64Snapshot(t *testing.T) { func TestCounterFloat64Zero(t *testing.T) { c := NewCounterFloat64() - if count := c.Count(); count != 0 { + if count := c.Snapshot().Count(); count != 0 { t.Errorf("c.Count(): 0 != %v\n", count) } } @@ -93,7 +93,7 @@ func TestCounterFloat64Zero(t *testing.T) { func TestGetOrRegisterCounterFloat64(t *testing.T) { r := NewRegistry() NewRegisteredCounterFloat64("foo", r).Inc(47.0) - if c := GetOrRegisterCounterFloat64("foo", r); c.Count() != 47.0 { + if c := GetOrRegisterCounterFloat64("foo", r).Snapshot(); c.Count() != 47.0 { t.Fatal(c) } } diff --git a/metrics/counter_test.go b/metrics/counter_test.go index af26ef1548fe..1b15b23f215f 100644 --- a/metrics/counter_test.go +++ b/metrics/counter_test.go @@ -14,7 +14,7 @@ func TestCounterClear(t *testing.T) { c := NewCounter() c.Inc(1) c.Clear() - if count := c.Count(); count != 0 { + if count := c.Snapshot().Count(); count != 0 { t.Errorf("c.Count(): 0 != %v\n", count) } } @@ -22,7 +22,7 @@ func TestCounterClear(t *testing.T) { func TestCounterDec1(t *testing.T) { c := NewCounter() c.Dec(1) - if count := c.Count(); count != -1 { + if count := c.Snapshot().Count(); count != -1 { t.Errorf("c.Count(): -1 != %v\n", count) } } @@ -30,7 +30,7 @@ func TestCounterDec1(t *testing.T) { func TestCounterDec2(t *testing.T) { c := NewCounter() c.Dec(2) - if count := c.Count(); count != -2 { + if count := c.Snapshot().Count(); count != -2 { t.Errorf("c.Count(): -2 != %v\n", count) } } @@ -38,7 +38,7 @@ func TestCounterDec2(t *testing.T) { func TestCounterInc1(t *testing.T) { c := NewCounter() c.Inc(1) - if count := c.Count(); count != 1 { + if count := c.Snapshot().Count(); count != 1 { t.Errorf("c.Count(): 1 != %v\n", count) } } @@ -46,7 +46,7 @@ func TestCounterInc1(t *testing.T) { func TestCounterInc2(t *testing.T) { c := NewCounter() c.Inc(2) - if count := c.Count(); count != 2 { + if count := c.Snapshot().Count(); count != 2 { t.Errorf("c.Count(): 2 != %v\n", count) } } @@ -63,7 +63,7 @@ func TestCounterSnapshot(t *testing.T) { func TestCounterZero(t *testing.T) { c := NewCounter() - if count := c.Count(); count != 0 { + if count := c.Snapshot().Count(); count != 0 { t.Errorf("c.Count(): 0 != %v\n", count) } } @@ -71,7 +71,7 @@ func TestCounterZero(t *testing.T) { func TestGetOrRegisterCounter(t *testing.T) { r := NewRegistry() NewRegisteredCounter("foo", r).Inc(47) - if c := GetOrRegisterCounter("foo", r); c.Count() != 47 { + if c := GetOrRegisterCounter("foo", r).Snapshot(); c.Count() != 47 { t.Fatal(c) } } diff --git a/metrics/doc.go b/metrics/doc.go deleted file mode 100644 index 13f429c1689d..000000000000 --- a/metrics/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -package metrics - -const epsilon = 0.0000000000000001 -const epsilonPercentile = .00000000001 diff --git a/metrics/ewma.go b/metrics/ewma.go index ed95cba19b4f..1d7a4f00cf45 100644 --- a/metrics/ewma.go +++ b/metrics/ewma.go @@ -7,11 +7,14 @@ import ( "time" ) +type EWMASnapshot interface { + Rate() float64 +} + // EWMAs continuously calculate an exponentially-weighted moving average // based on an outside source of clock ticks. type EWMA interface { - Rate() float64 - Snapshot() EWMA + Snapshot() EWMASnapshot Tick() Update(int64) } @@ -36,40 +39,19 @@ func NewEWMA15() EWMA { return NewEWMA(1 - math.Exp(-5.0/60.0/15)) } -// EWMASnapshot is a read-only copy of another EWMA. -type EWMASnapshot float64 +// ewmaSnapshot is a read-only copy of another EWMA. +type ewmaSnapshot float64 // Rate returns the rate of events per second at the time the snapshot was // taken. -func (a EWMASnapshot) Rate() float64 { return float64(a) } - -// Snapshot returns the snapshot. -func (a EWMASnapshot) Snapshot() EWMA { return a } - -// Tick panics. -func (EWMASnapshot) Tick() { - panic("Tick called on an EWMASnapshot") -} - -// Update panics. -func (EWMASnapshot) Update(int64) { - panic("Update called on an EWMASnapshot") -} +func (a ewmaSnapshot) Rate() float64 { return float64(a) } // NilEWMA is a no-op EWMA. type NilEWMA struct{} -// Rate is a no-op. -func (NilEWMA) Rate() float64 { return 0.0 } - -// Snapshot is a no-op. -func (NilEWMA) Snapshot() EWMA { return NilEWMA{} } - -// Tick is a no-op. -func (NilEWMA) Tick() {} - -// Update is a no-op. -func (NilEWMA) Update(n int64) {} +func (NilEWMA) Snapshot() EWMASnapshot { return (*emptySnapshot)(nil) } +func (NilEWMA) Tick() {} +func (NilEWMA) Update(n int64) {} // StandardEWMA is the standard implementation of an EWMA and tracks the number // of uncounted events and processes them on each tick. It uses the @@ -77,37 +59,50 @@ func (NilEWMA) Update(n int64) {} type StandardEWMA struct { uncounted atomic.Int64 alpha float64 - rate float64 - init bool + rate atomic.Uint64 + init atomic.Bool mutex sync.Mutex } -// Rate returns the moving average rate of events per second. -func (a *StandardEWMA) Rate() float64 { - a.mutex.Lock() - defer a.mutex.Unlock() - return a.rate * float64(time.Second) -} - // Snapshot returns a read-only copy of the EWMA. -func (a *StandardEWMA) Snapshot() EWMA { - return EWMASnapshot(a.Rate()) +func (a *StandardEWMA) Snapshot() EWMASnapshot { + r := math.Float64frombits(a.rate.Load()) * float64(time.Second) + return ewmaSnapshot(r) } // Tick ticks the clock to update the moving average. It assumes it is called // every five seconds. func (a *StandardEWMA) Tick() { - count := a.uncounted.Load() - a.uncounted.Add(-count) - instantRate := float64(count) / float64(5*time.Second) + // Optimization to avoid mutex locking in the hot-path. + if a.init.Load() { + a.updateRate(a.fetchInstantRate()) + return + } + // Slow-path: this is only needed on the first Tick() and preserves transactional updating + // of init and rate in the else block. The first conditional is needed below because + // a different thread could have set a.init = 1 between the time of the first atomic load and when + // the lock was acquired. a.mutex.Lock() - defer a.mutex.Unlock() - if a.init { - a.rate += a.alpha * (instantRate - a.rate) + if a.init.Load() { + // The fetchInstantRate() uses atomic loading, which is unnecessary in this critical section + // but again, this section is only invoked on the first successful Tick() operation. + a.updateRate(a.fetchInstantRate()) } else { - a.init = true - a.rate = instantRate + a.init.Store(true) + a.rate.Store(math.Float64bits(a.fetchInstantRate())) } + a.mutex.Unlock() +} + +func (a *StandardEWMA) fetchInstantRate() float64 { + count := a.uncounted.Swap(0) + return float64(count) / float64(5*time.Second) +} + +func (a *StandardEWMA) updateRate(instantRate float64) { + currentRate := math.Float64frombits(a.rate.Load()) + currentRate += a.alpha * (instantRate - currentRate) + a.rate.Store(math.Float64bits(currentRate)) } // Update adds n uncounted events. diff --git a/metrics/ewma_test.go b/metrics/ewma_test.go index 5b244191616e..9a91b43db81a 100644 --- a/metrics/ewma_test.go +++ b/metrics/ewma_test.go @@ -5,6 +5,8 @@ import ( "testing" ) +const epsilon = 0.0000000000000001 + func BenchmarkEWMA(b *testing.B) { a := NewEWMA1() b.ResetTimer() @@ -14,72 +16,33 @@ func BenchmarkEWMA(b *testing.B) { } } +func BenchmarkEWMAParallel(b *testing.B) { + a := NewEWMA1() + b.ResetTimer() + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + a.Update(1) + a.Tick() + } + }) +} + func TestEWMA1(t *testing.T) { a := NewEWMA1() a.Update(3) a.Tick() - if rate := a.Rate(); math.Abs(0.6-rate) > epsilon { - t.Errorf("initial a.Rate(): 0.6 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.22072766470286553-rate) > epsilon { - t.Errorf("1 minute a.Rate(): 0.22072766470286553 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.08120116994196772-rate) > epsilon { - t.Errorf("2 minute a.Rate(): 0.08120116994196772 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.029872241020718428-rate) > epsilon { - t.Errorf("3 minute a.Rate(): 0.029872241020718428 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.01098938333324054-rate) > epsilon { - t.Errorf("4 minute a.Rate(): 0.01098938333324054 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.004042768199451294-rate) > epsilon { - t.Errorf("5 minute a.Rate(): 0.004042768199451294 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.0014872513059998212-rate) > epsilon { - t.Errorf("6 minute a.Rate(): 0.0014872513059998212 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.0005471291793327122-rate) > epsilon { - t.Errorf("7 minute a.Rate(): 0.0005471291793327122 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.00020127757674150815-rate) > epsilon { - t.Errorf("8 minute a.Rate(): 0.00020127757674150815 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(7.404588245200814e-05-rate) > epsilon { - t.Errorf("9 minute a.Rate(): 7.404588245200814e-05 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(2.7239957857491083e-05-rate) > epsilon { - t.Errorf("10 minute a.Rate(): 2.7239957857491083e-05 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(1.0021020474147462e-05-rate) > epsilon { - t.Errorf("11 minute a.Rate(): 1.0021020474147462e-05 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(3.6865274119969525e-06-rate) > epsilon { - t.Errorf("12 minute a.Rate(): 3.6865274119969525e-06 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(1.3561976441886433e-06-rate) > epsilon { - t.Errorf("13 minute a.Rate(): 1.3561976441886433e-06 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(4.989172314621449e-07-rate) > epsilon { - t.Errorf("14 minute a.Rate(): 4.989172314621449e-07 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(1.8354139230109722e-07-rate) > epsilon { - t.Errorf("15 minute a.Rate(): 1.8354139230109722e-07 != %v\n", rate) + for i, want := range []float64{0.6, + 0.22072766470286553, 0.08120116994196772, 0.029872241020718428, + 0.01098938333324054, 0.004042768199451294, 0.0014872513059998212, + 0.0005471291793327122, 0.00020127757674150815, 7.404588245200814e-05, + 2.7239957857491083e-05, 1.0021020474147462e-05, 3.6865274119969525e-06, + 1.3561976441886433e-06, 4.989172314621449e-07, 1.8354139230109722e-07, + } { + if rate := a.Snapshot().Rate(); math.Abs(want-rate) > epsilon { + t.Errorf("%d minute a.Snapshot().Rate(): %f != %v\n", i, want, rate) + } + elapseMinute(a) } } @@ -87,68 +50,17 @@ func TestEWMA5(t *testing.T) { a := NewEWMA5() a.Update(3) a.Tick() - if rate := a.Rate(); math.Abs(0.6-rate) > epsilon { - t.Errorf("initial a.Rate(): 0.6 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.49123845184678905-rate) > epsilon { - t.Errorf("1 minute a.Rate(): 0.49123845184678905 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.4021920276213837-rate) > epsilon { - t.Errorf("2 minute a.Rate(): 0.4021920276213837 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.32928698165641596-rate) > epsilon { - t.Errorf("3 minute a.Rate(): 0.32928698165641596 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.269597378470333-rate) > epsilon { - t.Errorf("4 minute a.Rate(): 0.269597378470333 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.2207276647028654-rate) > epsilon { - t.Errorf("5 minute a.Rate(): 0.2207276647028654 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.18071652714732128-rate) > epsilon { - t.Errorf("6 minute a.Rate(): 0.18071652714732128 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.14795817836496392-rate) > epsilon { - t.Errorf("7 minute a.Rate(): 0.14795817836496392 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.12113791079679326-rate) > epsilon { - t.Errorf("8 minute a.Rate(): 0.12113791079679326 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.09917933293295193-rate) > epsilon { - t.Errorf("9 minute a.Rate(): 0.09917933293295193 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.08120116994196763-rate) > epsilon { - t.Errorf("10 minute a.Rate(): 0.08120116994196763 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.06648189501740036-rate) > epsilon { - t.Errorf("11 minute a.Rate(): 0.06648189501740036 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.05443077197364752-rate) > epsilon { - t.Errorf("12 minute a.Rate(): 0.05443077197364752 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.04456414692860035-rate) > epsilon { - t.Errorf("13 minute a.Rate(): 0.04456414692860035 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.03648603757513079-rate) > epsilon { - t.Errorf("14 minute a.Rate(): 0.03648603757513079 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.0298722410207183831020718428-rate) > epsilon { - t.Errorf("15 minute a.Rate(): 0.0298722410207183831020718428 != %v\n", rate) + for i, want := range []float64{ + 0.6, 0.49123845184678905, 0.4021920276213837, 0.32928698165641596, + 0.269597378470333, 0.2207276647028654, 0.18071652714732128, + 0.14795817836496392, 0.12113791079679326, 0.09917933293295193, + 0.08120116994196763, 0.06648189501740036, 0.05443077197364752, + 0.04456414692860035, 0.03648603757513079, 0.0298722410207183831020718428, + } { + if rate := a.Snapshot().Rate(); math.Abs(want-rate) > epsilon { + t.Errorf("%d minute a.Snapshot().Rate(): %f != %v\n", i, want, rate) + } + elapseMinute(a) } } @@ -156,68 +68,17 @@ func TestEWMA15(t *testing.T) { a := NewEWMA15() a.Update(3) a.Tick() - if rate := a.Rate(); math.Abs(0.6-rate) > epsilon { - t.Errorf("initial a.Rate(): 0.6 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.5613041910189706-rate) > epsilon { - t.Errorf("1 minute a.Rate(): 0.5613041910189706 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.5251039914257684-rate) > epsilon { - t.Errorf("2 minute a.Rate(): 0.5251039914257684 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.4912384518467888184678905-rate) > epsilon { - t.Errorf("3 minute a.Rate(): 0.4912384518467888184678905 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.459557003018789-rate) > epsilon { - t.Errorf("4 minute a.Rate(): 0.459557003018789 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.4299187863442732-rate) > epsilon { - t.Errorf("5 minute a.Rate(): 0.4299187863442732 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.4021920276213831-rate) > epsilon { - t.Errorf("6 minute a.Rate(): 0.4021920276213831 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.37625345116383313-rate) > epsilon { - t.Errorf("7 minute a.Rate(): 0.37625345116383313 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.3519877317060185-rate) > epsilon { - t.Errorf("8 minute a.Rate(): 0.3519877317060185 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.3292869816564153165641596-rate) > epsilon { - t.Errorf("9 minute a.Rate(): 0.3292869816564153165641596 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.3080502714195546-rate) > epsilon { - t.Errorf("10 minute a.Rate(): 0.3080502714195546 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.2881831806538789-rate) > epsilon { - t.Errorf("11 minute a.Rate(): 0.2881831806538789 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.26959737847033216-rate) > epsilon { - t.Errorf("12 minute a.Rate(): 0.26959737847033216 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.2522102307052083-rate) > epsilon { - t.Errorf("13 minute a.Rate(): 0.2522102307052083 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.23594443252115815-rate) > epsilon { - t.Errorf("14 minute a.Rate(): 0.23594443252115815 != %v\n", rate) - } - elapseMinute(a) - if rate := a.Rate(); math.Abs(0.2207276647028646247028654470286553-rate) > epsilon { - t.Errorf("15 minute a.Rate(): 0.2207276647028646247028654470286553 != %v\n", rate) + for i, want := range []float64{ + 0.6, 0.5613041910189706, 0.5251039914257684, 0.4912384518467888184678905, + 0.459557003018789, 0.4299187863442732, 0.4021920276213831, + 0.37625345116383313, 0.3519877317060185, 0.3292869816564153165641596, + 0.3080502714195546, 0.2881831806538789, 0.26959737847033216, + 0.2522102307052083, 0.23594443252115815, 0.2207276647028646247028654470286553, + } { + if rate := a.Snapshot().Rate(); math.Abs(want-rate) > epsilon { + t.Errorf("%d minute a.Snapshot().Rate(): %f != %v\n", i, want, rate) + } + elapseMinute(a) } } diff --git a/metrics/exp/exp.go b/metrics/exp/exp.go index 2b04eeab271f..7e3f82a075fc 100644 --- a/metrics/exp/exp.go +++ b/metrics/exp/exp.go @@ -95,24 +95,42 @@ func (exp *exp) getFloat(name string) *expvar.Float { return v } -func (exp *exp) publishCounter(name string, metric metrics.Counter) { +func (exp *exp) getInfo(name string) *expvar.String { + var v *expvar.String + exp.expvarLock.Lock() + p := expvar.Get(name) + if p != nil { + v = p.(*expvar.String) + } else { + v = new(expvar.String) + expvar.Publish(name, v) + } + exp.expvarLock.Unlock() + return v +} + +func (exp *exp) publishCounter(name string, metric metrics.CounterSnapshot) { v := exp.getInt(name) v.Set(metric.Count()) } -func (exp *exp) publishCounterFloat64(name string, metric metrics.CounterFloat64) { +func (exp *exp) publishCounterFloat64(name string, metric metrics.CounterFloat64Snapshot) { v := exp.getFloat(name) v.Set(metric.Count()) } -func (exp *exp) publishGauge(name string, metric metrics.Gauge) { +func (exp *exp) publishGauge(name string, metric metrics.GaugeSnapshot) { v := exp.getInt(name) v.Set(metric.Value()) } -func (exp *exp) publishGaugeFloat64(name string, metric metrics.GaugeFloat64) { +func (exp *exp) publishGaugeFloat64(name string, metric metrics.GaugeFloat64Snapshot) { exp.getFloat(name).Set(metric.Value()) } +func (exp *exp) publishGaugeInfo(name string, metric metrics.GaugeInfoSnapshot) { + exp.getInfo(name).Set(metric.Value().String()) +} + func (exp *exp) publishHistogram(name string, metric metrics.Histogram) { h := metric.Snapshot() ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) @@ -158,26 +176,28 @@ func (exp *exp) publishTimer(name string, metric metrics.Timer) { func (exp *exp) publishResettingTimer(name string, metric metrics.ResettingTimer) { t := metric.Snapshot() - ps := t.Percentiles([]float64{50, 75, 95, 99}) - exp.getInt(name + ".count").Set(int64(len(t.Values()))) + ps := t.Percentiles([]float64{0.50, 0.75, 0.95, 0.99}) + exp.getInt(name + ".count").Set(int64(t.Count())) exp.getFloat(name + ".mean").Set(t.Mean()) - exp.getInt(name + ".50-percentile").Set(ps[0]) - exp.getInt(name + ".75-percentile").Set(ps[1]) - exp.getInt(name + ".95-percentile").Set(ps[2]) - exp.getInt(name + ".99-percentile").Set(ps[3]) + exp.getFloat(name + ".50-percentile").Set(ps[0]) + exp.getFloat(name + ".75-percentile").Set(ps[1]) + exp.getFloat(name + ".95-percentile").Set(ps[2]) + exp.getFloat(name + ".99-percentile").Set(ps[3]) } func (exp *exp) syncToExpvar() { exp.registry.Each(func(name string, i interface{}) { switch i := i.(type) { case metrics.Counter: - exp.publishCounter(name, i) + exp.publishCounter(name, i.Snapshot()) case metrics.CounterFloat64: - exp.publishCounterFloat64(name, i) + exp.publishCounterFloat64(name, i.Snapshot()) case metrics.Gauge: - exp.publishGauge(name, i) + exp.publishGauge(name, i.Snapshot()) case metrics.GaugeFloat64: - exp.publishGaugeFloat64(name, i) + exp.publishGaugeFloat64(name, i.Snapshot()) + case metrics.GaugeInfo: + exp.publishGaugeInfo(name, i.Snapshot()) case metrics.Histogram: exp.publishHistogram(name, i) case metrics.Meter: diff --git a/metrics/gauge.go b/metrics/gauge.go index 81137d7f7c5e..68f8f11abcd7 100644 --- a/metrics/gauge.go +++ b/metrics/gauge.go @@ -2,13 +2,18 @@ package metrics import "sync/atomic" +// gaugeSnapshot contains a readonly int64. +type GaugeSnapshot interface { + Value() int64 +} + // Gauges hold an int64 value that can be set arbitrarily. type Gauge interface { - Snapshot() Gauge + Snapshot() GaugeSnapshot Update(int64) + UpdateIfGt(int64) Dec(int64) Inc(int64) - Value() int64 } // GetOrRegisterGauge returns an existing Gauge or constructs and registers a @@ -38,65 +43,20 @@ func NewRegisteredGauge(name string, r Registry) Gauge { return c } -// NewFunctionalGauge constructs a new FunctionalGauge. -func NewFunctionalGauge(f func() int64) Gauge { - if !Enabled { - return NilGauge{} - } - return &FunctionalGauge{value: f} -} - -// NewRegisteredFunctionalGauge constructs and registers a new StandardGauge. -func NewRegisteredFunctionalGauge(name string, r Registry, f func() int64) Gauge { - c := NewFunctionalGauge(f) - if nil == r { - r = DefaultRegistry - } - r.Register(name, c) - return c -} - -// GaugeSnapshot is a read-only copy of another Gauge. -type GaugeSnapshot int64 - -// Snapshot returns the snapshot. -func (g GaugeSnapshot) Snapshot() Gauge { return g } - -// Update panics. -func (GaugeSnapshot) Update(int64) { - panic("Update called on a GaugeSnapshot") -} - -// Dec panics. -func (GaugeSnapshot) Dec(int64) { - panic("Dec called on a GaugeSnapshot") -} - -// Inc panics. -func (GaugeSnapshot) Inc(int64) { - panic("Inc called on a GaugeSnapshot") -} +// gaugeSnapshot is a read-only copy of another Gauge. +type gaugeSnapshot int64 // Value returns the value at the time the snapshot was taken. -func (g GaugeSnapshot) Value() int64 { return int64(g) } +func (g gaugeSnapshot) Value() int64 { return int64(g) } // NilGauge is a no-op Gauge. type NilGauge struct{} -// Snapshot is a no-op. -func (NilGauge) Snapshot() Gauge { return NilGauge{} } - -// Update is a no-op. -func (NilGauge) Update(v int64) {} - -// Dec is a no-op. -func (NilGauge) Dec(i int64) {} - -// Inc is a no-op. -func (NilGauge) Inc(i int64) {} - -// Value is a no-op. -func (NilGauge) Value() int64 { return 0 } +func (NilGauge) Snapshot() GaugeSnapshot { return (*emptySnapshot)(nil) } +func (NilGauge) Update(v int64) {} +func (NilGauge) UpdateIfGt(v int64) {} +func (NilGauge) Dec(i int64) {} +func (NilGauge) Inc(i int64) {} // StandardGauge is the standard implementation of a Gauge and uses the // sync/atomic package to manage a single int64 value. @@ -105,8 +65,8 @@ type StandardGauge struct { } // Snapshot returns a read-only copy of the gauge. -func (g *StandardGauge) Snapshot() Gauge { - return GaugeSnapshot(g.Value()) +func (g *StandardGauge) Snapshot() GaugeSnapshot { + return gaugeSnapshot(g.value.Load()) } // Update updates the gauge's value. @@ -114,9 +74,17 @@ func (g *StandardGauge) Update(v int64) { g.value.Store(v) } -// Value returns the gauge's current value. -func (g *StandardGauge) Value() int64 { - return g.value.Load() +// Update updates the gauge's value if v is larger then the current valie. +func (g *StandardGauge) UpdateIfGt(v int64) { + for { + exist := g.value.Load() + if exist >= v { + break + } + if g.value.CompareAndSwap(exist, v) { + break + } + } } // Dec decrements the gauge's current value by the given amount. @@ -128,31 +96,3 @@ func (g *StandardGauge) Dec(i int64) { func (g *StandardGauge) Inc(i int64) { g.value.Add(i) } - -// FunctionalGauge returns value from given function -type FunctionalGauge struct { - value func() int64 -} - -// Value returns the gauge's current value. -func (g FunctionalGauge) Value() int64 { - return g.value() -} - -// Snapshot returns the snapshot. -func (g FunctionalGauge) Snapshot() Gauge { return GaugeSnapshot(g.Value()) } - -// Update panics. -func (FunctionalGauge) Update(int64) { - panic("Update called on a FunctionalGauge") -} - -// Dec panics. -func (FunctionalGauge) Dec(int64) { - panic("Dec called on a FunctionalGauge") -} - -// Inc panics. -func (FunctionalGauge) Inc(int64) { - panic("Inc called on a FunctionalGauge") -} diff --git a/metrics/gauge_float64.go b/metrics/gauge_float64.go index 237ff8036e01..967f2bc60e5c 100644 --- a/metrics/gauge_float64.go +++ b/metrics/gauge_float64.go @@ -5,11 +5,14 @@ import ( "sync/atomic" ) -// GaugeFloat64s hold a float64 value that can be set arbitrarily. +type GaugeFloat64Snapshot interface { + Value() float64 +} + +// GaugeFloat64 hold a float64 value that can be set arbitrarily. type GaugeFloat64 interface { - Snapshot() GaugeFloat64 + Snapshot() GaugeFloat64Snapshot Update(float64) - Value() float64 } // GetOrRegisterGaugeFloat64 returns an existing GaugeFloat64 or constructs and registers a @@ -39,49 +42,18 @@ func NewRegisteredGaugeFloat64(name string, r Registry) GaugeFloat64 { return c } -// NewFunctionalGauge constructs a new FunctionalGauge. -func NewFunctionalGaugeFloat64(f func() float64) GaugeFloat64 { - if !Enabled { - return NilGaugeFloat64{} - } - return &FunctionalGaugeFloat64{value: f} -} - -// NewRegisteredFunctionalGauge constructs and registers a new StandardGauge. -func NewRegisteredFunctionalGaugeFloat64(name string, r Registry, f func() float64) GaugeFloat64 { - c := NewFunctionalGaugeFloat64(f) - if nil == r { - r = DefaultRegistry - } - r.Register(name, c) - return c -} - -// GaugeFloat64Snapshot is a read-only copy of another GaugeFloat64. -type GaugeFloat64Snapshot float64 - -// Snapshot returns the snapshot. -func (g GaugeFloat64Snapshot) Snapshot() GaugeFloat64 { return g } - -// Update panics. -func (GaugeFloat64Snapshot) Update(float64) { - panic("Update called on a GaugeFloat64Snapshot") -} +// gaugeFloat64Snapshot is a read-only copy of another GaugeFloat64. +type gaugeFloat64Snapshot float64 // Value returns the value at the time the snapshot was taken. -func (g GaugeFloat64Snapshot) Value() float64 { return float64(g) } +func (g gaugeFloat64Snapshot) Value() float64 { return float64(g) } // NilGauge is a no-op Gauge. type NilGaugeFloat64 struct{} -// Snapshot is a no-op. -func (NilGaugeFloat64) Snapshot() GaugeFloat64 { return NilGaugeFloat64{} } - -// Update is a no-op. -func (NilGaugeFloat64) Update(v float64) {} - -// Value is a no-op. -func (NilGaugeFloat64) Value() float64 { return 0.0 } +func (NilGaugeFloat64) Snapshot() GaugeFloat64Snapshot { return NilGaugeFloat64{} } +func (NilGaugeFloat64) Update(v float64) {} +func (NilGaugeFloat64) Value() float64 { return 0.0 } // StandardGaugeFloat64 is the standard implementation of a GaugeFloat64 and uses // atomic to manage a single float64 value. @@ -90,34 +62,12 @@ type StandardGaugeFloat64 struct { } // Snapshot returns a read-only copy of the gauge. -func (g *StandardGaugeFloat64) Snapshot() GaugeFloat64 { - return GaugeFloat64Snapshot(g.Value()) +func (g *StandardGaugeFloat64) Snapshot() GaugeFloat64Snapshot { + v := math.Float64frombits(g.floatBits.Load()) + return gaugeFloat64Snapshot(v) } // Update updates the gauge's value. func (g *StandardGaugeFloat64) Update(v float64) { g.floatBits.Store(math.Float64bits(v)) } - -// Value returns the gauge's current value. -func (g *StandardGaugeFloat64) Value() float64 { - return math.Float64frombits(g.floatBits.Load()) -} - -// FunctionalGaugeFloat64 returns value from given function -type FunctionalGaugeFloat64 struct { - value func() float64 -} - -// Value returns the gauge's current value. -func (g FunctionalGaugeFloat64) Value() float64 { - return g.value() -} - -// Snapshot returns the snapshot. -func (g FunctionalGaugeFloat64) Snapshot() GaugeFloat64 { return GaugeFloat64Snapshot(g.Value()) } - -// Update panics. -func (FunctionalGaugeFloat64) Update(float64) { - panic("Update called on a FunctionalGaugeFloat64") -} diff --git a/metrics/gauge_float64_test.go b/metrics/gauge_float64_test.go index 647d09000935..f0ac7ea5e7be 100644 --- a/metrics/gauge_float64_test.go +++ b/metrics/gauge_float64_test.go @@ -26,19 +26,11 @@ func BenchmarkGaugeFloat64Parallel(b *testing.B) { }() } wg.Wait() - if have, want := c.Value(), float64(b.N-1); have != want { + if have, want := c.Snapshot().Value(), float64(b.N-1); have != want { b.Fatalf("have %f want %f", have, want) } } -func TestGaugeFloat64(t *testing.T) { - g := NewGaugeFloat64() - g.Update(47.0) - if v := g.Value(); 47.0 != v { - t.Errorf("g.Value(): 47.0 != %v\n", v) - } -} - func TestGaugeFloat64Snapshot(t *testing.T) { g := NewGaugeFloat64() g.Update(47.0) @@ -53,28 +45,7 @@ func TestGetOrRegisterGaugeFloat64(t *testing.T) { r := NewRegistry() NewRegisteredGaugeFloat64("foo", r).Update(47.0) t.Logf("registry: %v", r) - if g := GetOrRegisterGaugeFloat64("foo", r); 47.0 != g.Value() { - t.Fatal(g) - } -} - -func TestFunctionalGaugeFloat64(t *testing.T) { - var counter float64 - fg := NewFunctionalGaugeFloat64(func() float64 { - counter++ - return counter - }) - fg.Value() - fg.Value() - if counter != 2 { - t.Error("counter != 2") - } -} - -func TestGetOrRegisterFunctionalGaugeFloat64(t *testing.T) { - r := NewRegistry() - NewRegisteredFunctionalGaugeFloat64("foo", r, func() float64 { return 47 }) - if g := GetOrRegisterGaugeFloat64("foo", r); g.Value() != 47 { + if g := GetOrRegisterGaugeFloat64("foo", r).Snapshot(); 47.0 != g.Value() { t.Fatal(g) } } diff --git a/metrics/gauge_info.go b/metrics/gauge_info.go new file mode 100644 index 000000000000..c44b2d85f3ad --- /dev/null +++ b/metrics/gauge_info.go @@ -0,0 +1,84 @@ +package metrics + +import ( + "encoding/json" + "sync" +) + +type GaugeInfoSnapshot interface { + Value() GaugeInfoValue +} + +// GaugeInfos hold a GaugeInfoValue value that can be set arbitrarily. +type GaugeInfo interface { + Update(GaugeInfoValue) + Snapshot() GaugeInfoSnapshot +} + +// GaugeInfoValue is a mapping of keys to values +type GaugeInfoValue map[string]string + +func (val GaugeInfoValue) String() string { + data, _ := json.Marshal(val) + return string(data) +} + +// GetOrRegisterGaugeInfo returns an existing GaugeInfo or constructs and registers a +// new StandardGaugeInfo. +func GetOrRegisterGaugeInfo(name string, r Registry) GaugeInfo { + if nil == r { + r = DefaultRegistry + } + return r.GetOrRegister(name, NewGaugeInfo()).(GaugeInfo) +} + +// NewGaugeInfo constructs a new StandardGaugeInfo. +func NewGaugeInfo() GaugeInfo { + if !Enabled { + return NilGaugeInfo{} + } + return &StandardGaugeInfo{ + value: GaugeInfoValue{}, + } +} + +// NewRegisteredGaugeInfo constructs and registers a new StandardGaugeInfo. +func NewRegisteredGaugeInfo(name string, r Registry) GaugeInfo { + c := NewGaugeInfo() + if nil == r { + r = DefaultRegistry + } + r.Register(name, c) + return c +} + +// gaugeInfoSnapshot is a read-only copy of another GaugeInfo. +type gaugeInfoSnapshot GaugeInfoValue + +// Value returns the value at the time the snapshot was taken. +func (g gaugeInfoSnapshot) Value() GaugeInfoValue { return GaugeInfoValue(g) } + +type NilGaugeInfo struct{} + +func (NilGaugeInfo) Snapshot() GaugeInfoSnapshot { return NilGaugeInfo{} } +func (NilGaugeInfo) Update(v GaugeInfoValue) {} +func (NilGaugeInfo) Value() GaugeInfoValue { return GaugeInfoValue{} } + +// StandardGaugeInfo is the standard implementation of a GaugeInfo and uses +// sync.Mutex to manage a single string value. +type StandardGaugeInfo struct { + mutex sync.Mutex + value GaugeInfoValue +} + +// Snapshot returns a read-only copy of the gauge. +func (g *StandardGaugeInfo) Snapshot() GaugeInfoSnapshot { + return gaugeInfoSnapshot(g.value) +} + +// Update updates the gauge's value. +func (g *StandardGaugeInfo) Update(v GaugeInfoValue) { + g.mutex.Lock() + defer g.mutex.Unlock() + g.value = v +} diff --git a/metrics/gauge_info_test.go b/metrics/gauge_info_test.go new file mode 100644 index 000000000000..319afbf92e8f --- /dev/null +++ b/metrics/gauge_info_test.go @@ -0,0 +1,36 @@ +package metrics + +import ( + "testing" +) + +func TestGaugeInfoJsonString(t *testing.T) { + g := NewGaugeInfo() + g.Update(GaugeInfoValue{ + "chain_id": "5", + "anotherKey": "any_string_value", + "third_key": "anything", + }, + ) + want := `{"anotherKey":"any_string_value","chain_id":"5","third_key":"anything"}` + + original := g.Snapshot() + g.Update(GaugeInfoValue{"value": "updated"}) + + if have := original.Value().String(); have != want { + t.Errorf("\nhave: %v\nwant: %v\n", have, want) + } + if have, want := g.Snapshot().Value().String(), `{"value":"updated"}`; have != want { + t.Errorf("\nhave: %v\nwant: %v\n", have, want) + } +} + +func TestGetOrRegisterGaugeInfo(t *testing.T) { + r := NewRegistry() + NewRegisteredGaugeInfo("foo", r).Update( + GaugeInfoValue{"chain_id": "5"}) + g := GetOrRegisterGaugeInfo("foo", r).Snapshot() + if have, want := g.Value().String(), `{"chain_id":"5"}`; have != want { + t.Errorf("have\n%v\nwant\n%v\n", have, want) + } +} diff --git a/metrics/gauge_test.go b/metrics/gauge_test.go index a98fe985d8c2..f2ba930bc465 100644 --- a/metrics/gauge_test.go +++ b/metrics/gauge_test.go @@ -1,7 +1,6 @@ package metrics import ( - "fmt" "testing" ) @@ -13,14 +12,6 @@ func BenchmarkGauge(b *testing.B) { } } -func TestGauge(t *testing.T) { - g := NewGauge() - g.Update(int64(47)) - if v := g.Value(); v != 47 { - t.Errorf("g.Value(): 47 != %v\n", v) - } -} - func TestGaugeSnapshot(t *testing.T) { g := NewGauge() g.Update(int64(47)) @@ -34,35 +25,7 @@ func TestGaugeSnapshot(t *testing.T) { func TestGetOrRegisterGauge(t *testing.T) { r := NewRegistry() NewRegisteredGauge("foo", r).Update(47) - if g := GetOrRegisterGauge("foo", r); g.Value() != 47 { - t.Fatal(g) - } -} - -func TestFunctionalGauge(t *testing.T) { - var counter int64 - fg := NewFunctionalGauge(func() int64 { - counter++ - return counter - }) - fg.Value() - fg.Value() - if counter != 2 { - t.Error("counter != 2") - } -} - -func TestGetOrRegisterFunctionalGauge(t *testing.T) { - r := NewRegistry() - NewRegisteredFunctionalGauge("foo", r, func() int64 { return 47 }) - if g := GetOrRegisterGauge("foo", r); g.Value() != 47 { + if g := GetOrRegisterGauge("foo", r); g.Snapshot().Value() != 47 { t.Fatal(g) } } - -func ExampleGetOrRegisterGauge() { - m := "server.bytes_sent" - g := GetOrRegisterGauge(m, nil) - g.Update(47) - fmt.Println(g.Value()) // Output: 47 -} diff --git a/metrics/graphite.go b/metrics/graphite.go index 29f72b0c4181..aba752e0ed5e 100644 --- a/metrics/graphite.go +++ b/metrics/graphite.go @@ -66,13 +66,15 @@ func graphite(c *GraphiteConfig) error { c.Registry.Each(func(name string, i interface{}) { switch metric := i.(type) { case Counter: - fmt.Fprintf(w, "%s.%s.count %d %d\n", c.Prefix, name, metric.Count(), now) + fmt.Fprintf(w, "%s.%s.count %d %d\n", c.Prefix, name, metric.Snapshot().Count(), now) case CounterFloat64: - fmt.Fprintf(w, "%s.%s.count %f %d\n", c.Prefix, name, metric.Count(), now) + fmt.Fprintf(w, "%s.%s.count %f %d\n", c.Prefix, name, metric.Snapshot().Count(), now) case Gauge: - fmt.Fprintf(w, "%s.%s.value %d %d\n", c.Prefix, name, metric.Value(), now) + fmt.Fprintf(w, "%s.%s.value %d %d\n", c.Prefix, name, metric.Snapshot().Value(), now) case GaugeFloat64: - fmt.Fprintf(w, "%s.%s.value %f %d\n", c.Prefix, name, metric.Value(), now) + fmt.Fprintf(w, "%s.%s.value %f %d\n", c.Prefix, name, metric.Snapshot().Value(), now) + case GaugeInfo: + fmt.Fprintf(w, "%s.%s.value %s %d\n", c.Prefix, name, metric.Snapshot().Value().String(), now) case Histogram: h := metric.Snapshot() ps := h.Percentiles(c.Percentiles) diff --git a/metrics/histogram.go b/metrics/histogram.go index 2c54ce8b4063..44de588bc1dc 100644 --- a/metrics/histogram.go +++ b/metrics/histogram.go @@ -1,20 +1,14 @@ package metrics +type HistogramSnapshot interface { + SampleSnapshot +} + // Histograms calculate distribution statistics from a series of int64 values. type Histogram interface { Clear() - Count() int64 - Max() int64 - Mean() float64 - Min() int64 - Percentile(float64) float64 - Percentiles([]float64) []float64 - Sample() Sample - Snapshot() Histogram - StdDev() float64 - Sum() int64 Update(int64) - Variance() float64 + Snapshot() HistogramSnapshot } // GetOrRegisterHistogram returns an existing Histogram or constructs and @@ -54,108 +48,12 @@ func NewRegisteredHistogram(name string, r Registry, s Sample) Histogram { return c } -// HistogramSnapshot is a read-only copy of another Histogram. -type HistogramSnapshot struct { - sample *SampleSnapshot -} - -// Clear panics. -func (*HistogramSnapshot) Clear() { - panic("Clear called on a HistogramSnapshot") -} - -// Count returns the number of samples recorded at the time the snapshot was -// taken. -func (h *HistogramSnapshot) Count() int64 { return h.sample.Count() } - -// Max returns the maximum value in the sample at the time the snapshot was -// taken. -func (h *HistogramSnapshot) Max() int64 { return h.sample.Max() } - -// Mean returns the mean of the values in the sample at the time the snapshot -// was taken. -func (h *HistogramSnapshot) Mean() float64 { return h.sample.Mean() } - -// Min returns the minimum value in the sample at the time the snapshot was -// taken. -func (h *HistogramSnapshot) Min() int64 { return h.sample.Min() } - -// Percentile returns an arbitrary percentile of values in the sample at the -// time the snapshot was taken. -func (h *HistogramSnapshot) Percentile(p float64) float64 { - return h.sample.Percentile(p) -} - -// Percentiles returns a slice of arbitrary percentiles of values in the sample -// at the time the snapshot was taken. -func (h *HistogramSnapshot) Percentiles(ps []float64) []float64 { - return h.sample.Percentiles(ps) -} - -// Sample returns the Sample underlying the histogram. -func (h *HistogramSnapshot) Sample() Sample { return h.sample } - -// Snapshot returns the snapshot. -func (h *HistogramSnapshot) Snapshot() Histogram { return h } - -// StdDev returns the standard deviation of the values in the sample at the -// time the snapshot was taken. -func (h *HistogramSnapshot) StdDev() float64 { return h.sample.StdDev() } - -// Sum returns the sum in the sample at the time the snapshot was taken. -func (h *HistogramSnapshot) Sum() int64 { return h.sample.Sum() } - -// Update panics. -func (*HistogramSnapshot) Update(int64) { - panic("Update called on a HistogramSnapshot") -} - -// Variance returns the variance of inputs at the time the snapshot was taken. -func (h *HistogramSnapshot) Variance() float64 { return h.sample.Variance() } - // NilHistogram is a no-op Histogram. type NilHistogram struct{} -// Clear is a no-op. -func (NilHistogram) Clear() {} - -// Count is a no-op. -func (NilHistogram) Count() int64 { return 0 } - -// Max is a no-op. -func (NilHistogram) Max() int64 { return 0 } - -// Mean is a no-op. -func (NilHistogram) Mean() float64 { return 0.0 } - -// Min is a no-op. -func (NilHistogram) Min() int64 { return 0 } - -// Percentile is a no-op. -func (NilHistogram) Percentile(p float64) float64 { return 0.0 } - -// Percentiles is a no-op. -func (NilHistogram) Percentiles(ps []float64) []float64 { - return make([]float64, len(ps)) -} - -// Sample is a no-op. -func (NilHistogram) Sample() Sample { return NilSample{} } - -// Snapshot is a no-op. -func (NilHistogram) Snapshot() Histogram { return NilHistogram{} } - -// StdDev is a no-op. -func (NilHistogram) StdDev() float64 { return 0.0 } - -// Sum is a no-op. -func (NilHistogram) Sum() int64 { return 0 } - -// Update is a no-op. -func (NilHistogram) Update(v int64) {} - -// Variance is a no-op. -func (NilHistogram) Variance() float64 { return 0.0 } +func (NilHistogram) Clear() {} +func (NilHistogram) Snapshot() HistogramSnapshot { return (*emptySnapshot)(nil) } +func (NilHistogram) Update(v int64) {} // StandardHistogram is the standard implementation of a Histogram and uses a // Sample to bound its memory use. @@ -166,46 +64,10 @@ type StandardHistogram struct { // Clear clears the histogram and its sample. func (h *StandardHistogram) Clear() { h.sample.Clear() } -// Count returns the number of samples recorded since the histogram was last -// cleared. -func (h *StandardHistogram) Count() int64 { return h.sample.Count() } - -// Max returns the maximum value in the sample. -func (h *StandardHistogram) Max() int64 { return h.sample.Max() } - -// Mean returns the mean of the values in the sample. -func (h *StandardHistogram) Mean() float64 { return h.sample.Mean() } - -// Min returns the minimum value in the sample. -func (h *StandardHistogram) Min() int64 { return h.sample.Min() } - -// Percentile returns an arbitrary percentile of the values in the sample. -func (h *StandardHistogram) Percentile(p float64) float64 { - return h.sample.Percentile(p) -} - -// Percentiles returns a slice of arbitrary percentiles of the values in the -// sample. -func (h *StandardHistogram) Percentiles(ps []float64) []float64 { - return h.sample.Percentiles(ps) -} - -// Sample returns the Sample underlying the histogram. -func (h *StandardHistogram) Sample() Sample { return h.sample } - // Snapshot returns a read-only copy of the histogram. -func (h *StandardHistogram) Snapshot() Histogram { - return &HistogramSnapshot{sample: h.sample.Snapshot().(*SampleSnapshot)} +func (h *StandardHistogram) Snapshot() HistogramSnapshot { + return h.sample.Snapshot() } -// StdDev returns the standard deviation of the values in the sample. -func (h *StandardHistogram) StdDev() float64 { return h.sample.StdDev() } - -// Sum returns the sum in the sample. -func (h *StandardHistogram) Sum() int64 { return h.sample.Sum() } - // Update samples a new value. func (h *StandardHistogram) Update(v int64) { h.sample.Update(v) } - -// Variance returns the variance of the values in the sample. -func (h *StandardHistogram) Variance() float64 { return h.sample.Variance() } diff --git a/metrics/histogram_test.go b/metrics/histogram_test.go index 7c9f42fcec96..22fc5468b0b5 100644 --- a/metrics/histogram_test.go +++ b/metrics/histogram_test.go @@ -14,7 +14,7 @@ func TestGetOrRegisterHistogram(t *testing.T) { r := NewRegistry() s := NewUniformSample(100) NewRegisteredHistogram("foo", r, s).Update(47) - if h := GetOrRegisterHistogram("foo", r, s); h.Count() != 1 { + if h := GetOrRegisterHistogram("foo", r, s).Snapshot(); h.Count() != 1 { t.Fatal(h) } } @@ -24,11 +24,11 @@ func TestHistogram10000(t *testing.T) { for i := 1; i <= 10000; i++ { h.Update(int64(i)) } - testHistogram10000(t, h) + testHistogram10000(t, h.Snapshot()) } func TestHistogramEmpty(t *testing.T) { - h := NewHistogram(NewUniformSample(100)) + h := NewHistogram(NewUniformSample(100)).Snapshot() if count := h.Count(); count != 0 { t.Errorf("h.Count(): 0 != %v\n", count) } @@ -66,7 +66,7 @@ func TestHistogramSnapshot(t *testing.T) { testHistogram10000(t, snapshot) } -func testHistogram10000(t *testing.T, h Histogram) { +func testHistogram10000(t *testing.T, h HistogramSnapshot) { if count := h.Count(); count != 10000 { t.Errorf("h.Count(): 10000 != %v\n", count) } diff --git a/metrics/inactive.go b/metrics/inactive.go new file mode 100644 index 000000000000..1f47f0210af3 --- /dev/null +++ b/metrics/inactive.go @@ -0,0 +1,48 @@ +// Copyright 2023 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 metrics + +// compile-time checks that interfaces are implemented. +var ( + _ SampleSnapshot = (*emptySnapshot)(nil) + _ HistogramSnapshot = (*emptySnapshot)(nil) + _ CounterSnapshot = (*emptySnapshot)(nil) + _ GaugeSnapshot = (*emptySnapshot)(nil) + _ MeterSnapshot = (*emptySnapshot)(nil) + _ EWMASnapshot = (*emptySnapshot)(nil) + _ TimerSnapshot = (*emptySnapshot)(nil) +) + +type emptySnapshot struct{} + +func (*emptySnapshot) Count() int64 { return 0 } +func (*emptySnapshot) Max() int64 { return 0 } +func (*emptySnapshot) Mean() float64 { return 0.0 } +func (*emptySnapshot) Min() int64 { return 0 } +func (*emptySnapshot) Percentile(p float64) float64 { return 0.0 } +func (*emptySnapshot) Percentiles(ps []float64) []float64 { return make([]float64, len(ps)) } +func (*emptySnapshot) Size() int { return 0 } +func (*emptySnapshot) StdDev() float64 { return 0.0 } +func (*emptySnapshot) Sum() int64 { return 0 } +func (*emptySnapshot) Values() []int64 { return []int64{} } +func (*emptySnapshot) Variance() float64 { return 0.0 } +func (*emptySnapshot) Value() int64 { return 0 } +func (*emptySnapshot) Rate() float64 { return 0.0 } +func (*emptySnapshot) Rate1() float64 { return 0.0 } +func (*emptySnapshot) Rate5() float64 { return 0.0 } +func (*emptySnapshot) Rate15() float64 { return 0.0 } +func (*emptySnapshot) RateMean() float64 { return 0.0 } diff --git a/metrics/influxdb/influxdb.go b/metrics/influxdb/influxdb.go index 5dfbbab3edeb..bbc4fc024b34 100644 --- a/metrics/influxdb/influxdb.go +++ b/metrics/influxdb/influxdb.go @@ -11,13 +11,13 @@ func readMeter(namespace, name string, i interface{}) (string, map[string]interf case metrics.Counter: measurement := fmt.Sprintf("%s%s.count", namespace, name) fields := map[string]interface{}{ - "value": metric.Count(), + "value": metric.Snapshot().Count(), } return measurement, fields case metrics.CounterFloat64: measurement := fmt.Sprintf("%s%s.count", namespace, name) fields := map[string]interface{}{ - "value": metric.Count(), + "value": metric.Snapshot().Count(), } return measurement, fields case metrics.Gauge: @@ -32,6 +32,13 @@ func readMeter(namespace, name string, i interface{}) (string, map[string]interf "value": metric.Snapshot().Value(), } return measurement, fields + case metrics.GaugeInfo: + ms := metric.Snapshot() + measurement := fmt.Sprintf("%s%s.gauge", namespace, name) + fields := map[string]interface{}{ + "value": ms.Value().String(), + } + return measurement, fields case metrics.Histogram: ms := metric.Snapshot() if ms.Count() <= 0 { @@ -92,20 +99,19 @@ func readMeter(namespace, name string, i interface{}) (string, map[string]interf return measurement, fields case metrics.ResettingTimer: t := metric.Snapshot() - if len(t.Values()) == 0 { + if t.Count() == 0 { break } - ps := t.Percentiles([]float64{50, 95, 99}) - val := t.Values() + ps := t.Percentiles([]float64{0.50, 0.95, 0.99}) measurement := fmt.Sprintf("%s%s.span", namespace, name) fields := map[string]interface{}{ - "count": len(val), - "max": val[len(val)-1], + "count": t.Count(), + "max": t.Max(), "mean": t.Mean(), - "min": val[0], - "p50": ps[0], - "p95": ps[1], - "p99": ps[2], + "min": t.Min(), + "p50": int(ps[0]), + "p95": int(ps[1]), + "p99": int(ps[2]), } return measurement, fields } diff --git a/metrics/influxdb/influxdb_test.go b/metrics/influxdb/influxdb_test.go new file mode 100644 index 000000000000..c6f2eeac6277 --- /dev/null +++ b/metrics/influxdb/influxdb_test.go @@ -0,0 +1,114 @@ +// Copyright 2023 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 influxdb + +import ( + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/metrics/internal" + influxdb2 "github.com/influxdata/influxdb-client-go/v2" +) + +func TestMain(m *testing.M) { + metrics.Enabled = true + os.Exit(m.Run()) +} + +func TestExampleV1(t *testing.T) { + r := internal.ExampleMetrics() + var have, want string + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + haveB, _ := io.ReadAll(r.Body) + have = string(haveB) + r.Body.Close() + })) + defer ts.Close() + u, _ := url.Parse(ts.URL) + rep := &reporter{ + reg: r, + url: *u, + namespace: "goth.", + } + if err := rep.makeClient(); err != nil { + t.Fatal(err) + } + if err := rep.send(978307200); err != nil { + t.Fatal(err) + } + if wantB, err := os.ReadFile("./testdata/influxdbv1.want"); err != nil { + t.Fatal(err) + } else { + want = string(wantB) + } + if have != want { + t.Errorf("\nhave:\n%v\nwant:\n%v\n", have, want) + t.Logf("have vs want:\n%v", findFirstDiffPos(have, want)) + } +} + +func TestExampleV2(t *testing.T) { + r := internal.ExampleMetrics() + var have, want string + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + haveB, _ := io.ReadAll(r.Body) + have = string(haveB) + r.Body.Close() + })) + defer ts.Close() + + rep := &v2Reporter{ + reg: r, + endpoint: ts.URL, + namespace: "goth.", + } + rep.client = influxdb2.NewClient(rep.endpoint, rep.token) + defer rep.client.Close() + rep.write = rep.client.WriteAPI(rep.organization, rep.bucket) + + rep.send(978307200) + + if wantB, err := os.ReadFile("./testdata/influxdbv2.want"); err != nil { + t.Fatal(err) + } else { + want = string(wantB) + } + if have != want { + t.Errorf("\nhave:\n%v\nwant:\n%v\n", have, want) + t.Logf("have vs want:\n%v", findFirstDiffPos(have, want)) + } +} + +func findFirstDiffPos(a, b string) string { + yy := strings.Split(b, "\n") + for i, x := range strings.Split(a, "\n") { + if i >= len(yy) { + return fmt.Sprintf("have:%d: %s\nwant:%d: ", i, x, i) + } + if y := yy[i]; x != y { + return fmt.Sprintf("have:%d: %s\nwant:%d: %s", i, x, i, y) + } + } + return "" +} diff --git a/metrics/influxdb/influxdbv1.go b/metrics/influxdb/influxdbv1.go index f65d30ef9590..ac58280803bb 100644 --- a/metrics/influxdb/influxdbv1.go +++ b/metrics/influxdb/influxdbv1.go @@ -79,7 +79,7 @@ func InfluxDBWithTagsOnce(r metrics.Registry, url, database, username, password, return fmt.Errorf("unable to make InfluxDB client. err: %v", err) } - if err := rep.send(); err != nil { + if err := rep.send(0); err != nil { return fmt.Errorf("unable to send to InfluxDB. err: %v", err) } @@ -107,7 +107,7 @@ func (r *reporter) run() { for { select { case <-intervalTicker.C: - if err := r.send(); err != nil { + if err := r.send(0); err != nil { log.Warn("Unable to send to InfluxDB", "err", err) } case <-pingTicker.C: @@ -123,7 +123,9 @@ func (r *reporter) run() { } } -func (r *reporter) send() error { +// send sends the measurements. If provided tstamp is >0, it is used. Otherwise, +// a 'fresh' timestamp is used. +func (r *reporter) send(tstamp int64) error { bps, err := client.NewBatchPoints( client.BatchPointsConfig{ Database: r.database, @@ -132,7 +134,12 @@ func (r *reporter) send() error { return err } r.reg.Each(func(name string, i interface{}) { - now := time.Now() + var now time.Time + if tstamp <= 0 { + now = time.Now() + } else { + now = time.Unix(tstamp, 0) + } measurement, fields := readMeter(r.namespace, name, i) if fields == nil { return diff --git a/metrics/influxdb/influxdbv2.go b/metrics/influxdb/influxdbv2.go index 7984898f3259..0be5137d5ee1 100644 --- a/metrics/influxdb/influxdbv2.go +++ b/metrics/influxdb/influxdbv2.go @@ -64,7 +64,7 @@ func (r *v2Reporter) run() { for { select { case <-intervalTicker.C: - r.send() + r.send(0) case <-pingTicker.C: _, err := r.client.Health(context.Background()) if err != nil { @@ -74,9 +74,16 @@ func (r *v2Reporter) run() { } } -func (r *v2Reporter) send() { +// send sends the measurements. If provided tstamp is >0, it is used. Otherwise, +// a 'fresh' timestamp is used. +func (r *v2Reporter) send(tstamp int64) { r.reg.Each(func(name string, i interface{}) { - now := time.Now() + var now time.Time + if tstamp <= 0 { + now = time.Now() + } else { + now = time.Unix(tstamp, 0) + } measurement, fields := readMeter(r.namespace, name, i) if fields == nil { return diff --git a/metrics/influxdb/testdata/influxdbv1.want b/metrics/influxdb/testdata/influxdbv1.want new file mode 100644 index 000000000000..9443faedc5a2 --- /dev/null +++ b/metrics/influxdb/testdata/influxdbv1.want @@ -0,0 +1,11 @@ +goth.system/cpu/schedlatency.histogram count=5645i,max=41943040i,mean=1819544.0410983171,min=0i,p25=0,p50=0,p75=7168,p95=16777216,p99=29360128,p999=33554432,p9999=33554432,stddev=6393570.217198883,variance=40877740122252.57 978307200000000000 +goth.system/memory/pauses.histogram count=14i,max=229376i,mean=50066.28571428572,min=5120i,p25=10240,p50=32768,p75=57344,p95=196608,p99=196608,p999=196608,p9999=196608,stddev=54726.062410783874,variance=2994941906.9890113 978307200000000000 +goth.test/counter.count value=12345 978307200000000000 +goth.test/counter_float64.count value=54321.98 978307200000000000 +goth.test/gauge.gauge value=23456i 978307200000000000 +goth.test/gauge_float64.gauge value=34567.89 978307200000000000 +goth.test/gauge_info.gauge value="{\"arch\":\"amd64\",\"commit\":\"7caa2d8163ae3132c1c2d6978c76610caee2d949\",\"os\":\"linux\",\"protocol_versions\":\"64 65 66\",\"version\":\"1.10.18-unstable\"}" 978307200000000000 +goth.test/histogram.histogram count=3i,max=3i,mean=2,min=1i,p25=1,p50=2,p75=3,p95=3,p99=3,p999=3,p9999=3,stddev=0.816496580927726,variance=0.6666666666666666 978307200000000000 +goth.test/meter.meter count=0i,m1=0,m15=0,m5=0,mean=0 978307200000000000 +goth.test/resetting_timer.span count=6i,max=120000000i,mean=30000000,min=10000000i,p50=12500000i,p95=120000000i,p99=120000000i 978307200000000000 +goth.test/timer.timer count=6i,m1=0,m15=0,m5=0,max=120000000i,mean=38333333.333333336,meanrate=0,min=20000000i,p50=22500000,p75=48000000,p95=120000000,p99=120000000,p999=120000000,p9999=120000000,stddev=36545253.529775314,variance=1335555555555555.2 978307200000000000 diff --git a/metrics/influxdb/testdata/influxdbv2.want b/metrics/influxdb/testdata/influxdbv2.want new file mode 100644 index 000000000000..9443faedc5a2 --- /dev/null +++ b/metrics/influxdb/testdata/influxdbv2.want @@ -0,0 +1,11 @@ +goth.system/cpu/schedlatency.histogram count=5645i,max=41943040i,mean=1819544.0410983171,min=0i,p25=0,p50=0,p75=7168,p95=16777216,p99=29360128,p999=33554432,p9999=33554432,stddev=6393570.217198883,variance=40877740122252.57 978307200000000000 +goth.system/memory/pauses.histogram count=14i,max=229376i,mean=50066.28571428572,min=5120i,p25=10240,p50=32768,p75=57344,p95=196608,p99=196608,p999=196608,p9999=196608,stddev=54726.062410783874,variance=2994941906.9890113 978307200000000000 +goth.test/counter.count value=12345 978307200000000000 +goth.test/counter_float64.count value=54321.98 978307200000000000 +goth.test/gauge.gauge value=23456i 978307200000000000 +goth.test/gauge_float64.gauge value=34567.89 978307200000000000 +goth.test/gauge_info.gauge value="{\"arch\":\"amd64\",\"commit\":\"7caa2d8163ae3132c1c2d6978c76610caee2d949\",\"os\":\"linux\",\"protocol_versions\":\"64 65 66\",\"version\":\"1.10.18-unstable\"}" 978307200000000000 +goth.test/histogram.histogram count=3i,max=3i,mean=2,min=1i,p25=1,p50=2,p75=3,p95=3,p99=3,p999=3,p9999=3,stddev=0.816496580927726,variance=0.6666666666666666 978307200000000000 +goth.test/meter.meter count=0i,m1=0,m15=0,m5=0,mean=0 978307200000000000 +goth.test/resetting_timer.span count=6i,max=120000000i,mean=30000000,min=10000000i,p50=12500000i,p95=120000000i,p99=120000000i 978307200000000000 +goth.test/timer.timer count=6i,m1=0,m15=0,m5=0,max=120000000i,mean=38333333.333333336,meanrate=0,min=20000000i,p50=22500000,p75=48000000,p95=120000000,p99=120000000,p999=120000000,p9999=120000000,stddev=36545253.529775314,variance=1335555555555555.2 978307200000000000 diff --git a/metrics/internal/sampledata.go b/metrics/internal/sampledata.go new file mode 100644 index 000000000000..de9b207b6d4a --- /dev/null +++ b/metrics/internal/sampledata.go @@ -0,0 +1,95 @@ +// Copyright 2023 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 internal + +import ( + "bytes" + "encoding/gob" + metrics2 "runtime/metrics" + "time" + + "github.com/ethereum/go-ethereum/metrics" +) + +// ExampleMetrics returns an ordered registry populated with a sample of metrics. +func ExampleMetrics() metrics.Registry { + var registry = metrics.NewOrderedRegistry() + + metrics.NewRegisteredCounterFloat64("test/counter", registry).Inc(12345) + metrics.NewRegisteredCounterFloat64("test/counter_float64", registry).Inc(54321.98) + metrics.NewRegisteredGauge("test/gauge", registry).Update(23456) + metrics.NewRegisteredGaugeFloat64("test/gauge_float64", registry).Update(34567.89) + metrics.NewRegisteredGaugeInfo("test/gauge_info", registry).Update( + metrics.GaugeInfoValue{ + "version": "1.10.18-unstable", + "arch": "amd64", + "os": "linux", + "commit": "7caa2d8163ae3132c1c2d6978c76610caee2d949", + "protocol_versions": "64 65 66", + }) + + { + s := metrics.NewUniformSample(3) + s.Update(1) + s.Update(2) + s.Update(3) + //metrics.NewRegisteredHistogram("test/histogram", registry, metrics.NewSampleSnapshot(3, []int64{1, 2, 3})) + metrics.NewRegisteredHistogram("test/histogram", registry, s) + } + registry.Register("test/meter", metrics.NewInactiveMeter()) + { + timer := metrics.NewRegisteredResettingTimer("test/resetting_timer", registry) + timer.Update(10 * time.Millisecond) + timer.Update(11 * time.Millisecond) + timer.Update(12 * time.Millisecond) + timer.Update(120 * time.Millisecond) + timer.Update(13 * time.Millisecond) + timer.Update(14 * time.Millisecond) + } + { + timer := metrics.NewRegisteredTimer("test/timer", registry) + timer.Update(20 * time.Millisecond) + timer.Update(21 * time.Millisecond) + timer.Update(22 * time.Millisecond) + timer.Update(120 * time.Millisecond) + timer.Update(23 * time.Millisecond) + timer.Update(24 * time.Millisecond) + timer.Stop() + } + registry.Register("test/empty_resetting_timer", metrics.NewResettingTimer().Snapshot()) + + { // go runtime metrics + var sLatency = "7\xff\x81\x03\x01\x01\x10Float64Histogram\x01\xff\x82\x00\x01\x02\x01\x06Counts\x01\xff\x84\x00\x01\aBuckets\x01\xff\x86\x00\x00\x00\x16\xff\x83\x02\x01\x01\b[]uint64\x01\xff\x84\x00\x01\x06\x00\x00\x17\xff\x85\x02\x01\x01\t[]float64\x01\xff\x86\x00\x01\b\x00\x00\xfe\x06T\xff\x82\x01\xff\xa2\x00\xfe\r\xef\x00\x01\x02\x02\x04\x05\x04\b\x15\x17 B?6.L;$!2) \x1a? \x190aH7FY6#\x190\x1d\x14\x10\x1b\r\t\x04\x03\x01\x01\x00\x03\x02\x00\x03\x05\x05\x02\x02\x06\x04\v\x06\n\x15\x18\x13'&.\x12=H/L&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\xa3\xfe\xf0\xff\x00\xf8\x95\xd6&\xe8\v.q>\xf8\x95\xd6&\xe8\v.\x81>\xf8\xdfA:\xdc\x11ʼn>\xf8\x95\xd6&\xe8\v.\x91>\xf8:\x8c0\xe2\x8ey\x95>\xf8\xdfA:\xdc\x11Å™>\xf8\x84\xf7CÖ”\x10\x9e>\xf8\x95\xd6&\xe8\v.\xa1>\xf8:\x8c0\xe2\x8ey\xa5>\xf8\xdfA:\xdc\x11Å©>\xf8\x84\xf7CÖ”\x10\xae>\xf8\x95\xd6&\xe8\v.\xb1>\xf8:\x8c0\xe2\x8ey\xb5>\xf8\xdfA:\xdc\x11Ź>\xf8\x84\xf7CÖ”\x10\xbe>\xf8\x95\xd6&\xe8\v.\xc1>\xf8:\x8c0\xe2\x8ey\xc5>\xf8\xdfA:\xdc\x11\xc5\xc9>\xf8\x84\xf7CÖ”\x10\xce>\xf8\x95\xd6&\xe8\v.\xd1>\xf8:\x8c0\xe2\x8ey\xd5>\xf8\xdfA:\xdc\x11\xc5\xd9>\xf8\x84\xf7CÖ”\x10\xde>\xf8\x95\xd6&\xe8\v.\xe1>\xf8:\x8c0\xe2\x8ey\xe5>\xf8\xdfA:\xdc\x11\xc5\xe9>\xf8\x84\xf7CÖ”\x10\xee>\xf8\x95\xd6&\xe8\v.\xf1>\xf8:\x8c0\xe2\x8ey\xf5>\xf8\xdfA:\xdc\x11\xc5\xf9>\xf8\x84\xf7CÖ”\x10\xfe>\xf8\x95\xd6&\xe8\v.\x01?\xf8:\x8c0\xe2\x8ey\x05?\xf8\xdfA:\xdc\x11\xc5\t?\xf8\x84\xf7CÖ”\x10\x0e?\xf8\x95\xd6&\xe8\v.\x11?\xf8:\x8c0\xe2\x8ey\x15?\xf8\xdfA:\xdc\x11\xc5\x19?\xf8\x84\xf7CÖ”\x10\x1e?\xf8\x95\xd6&\xe8\v.!?\xf8:\x8c0\xe2\x8ey%?\xf8\xdfA:\xdc\x11\xc5)?\xf8\x84\xf7CÖ”\x10.?\xf8\x95\xd6&\xe8\v.1?\xf8:\x8c0\xe2\x8ey5?\xf8\xdfA:\xdc\x11\xc59?\xf8\x84\xf7CÖ”\x10>?\xf8\x95\xd6&\xe8\v.A?\xf8:\x8c0\xe2\x8eyE?\xf8\xdfA:\xdc\x11\xc5I?\xf8\x84\xf7CÖ”\x10N?\xf8\x95\xd6&\xe8\v.Q?\xf8:\x8c0\xe2\x8eyU?\xf8\xdfA:\xdc\x11\xc5Y?\xf8\x84\xf7CÖ”\x10^?\xf8\x95\xd6&\xe8\v.a?\xf8:\x8c0\xe2\x8eye?\xf8\xdfA:\xdc\x11\xc5i?\xf8\x84\xf7CÖ”\x10n?\xf8\x95\xd6&\xe8\v.q?\xf8:\x8c0\xe2\x8eyu?\xf8\xdfA:\xdc\x11\xc5y?\xf8\x84\xf7CÖ”\x10~?\xf8\x95\xd6&\xe8\v.\x81?\xf8:\x8c0\xe2\x8ey\x85?\xf8\xdfA:\xdc\x11ʼn?\xf8\x84\xf7CÖ”\x10\x8e?\xf8\x95\xd6&\xe8\v.\x91?\xf8:\x8c0\xe2\x8ey\x95?\xf8\xdfA:\xdc\x11Å™?\xf8\x84\xf7CÖ”\x10\x9e?\xf8\x95\xd6&\xe8\v.\xa1?\xf8:\x8c0\xe2\x8ey\xa5?\xf8\xdfA:\xdc\x11Å©?\xf8\x84\xf7CÖ”\x10\xae?\xf8\x95\xd6&\xe8\v.\xb1?\xf8:\x8c0\xe2\x8ey\xb5?\xf8\xdfA:\xdc\x11Ź?\xf8\x84\xf7CÖ”\x10\xbe?\xf8\x95\xd6&\xe8\v.\xc1?\xf8:\x8c0\xe2\x8ey\xc5?\xf8\xdfA:\xdc\x11\xc5\xc9?\xf8\x84\xf7CÖ”\x10\xce?\xf8\x95\xd6&\xe8\v.\xd1?\xf8:\x8c0\xe2\x8ey\xd5?\xf8\xdfA:\xdc\x11\xc5\xd9?\xf8\x84\xf7CÖ”\x10\xde?\xf8\x95\xd6&\xe8\v.\xe1?\xf8:\x8c0\xe2\x8ey\xe5?\xf8\xdfA:\xdc\x11\xc5\xe9?\xf8\x84\xf7CÖ”\x10\xee?\xf8\x95\xd6&\xe8\v.\xf1?\xf8:\x8c0\xe2\x8ey\xf5?\xf8\xdfA:\xdc\x11\xc5\xf9?\xf8\x84\xf7CÖ”\x10\xfe?\xf8\x95\xd6&\xe8\v.\x01@\xf8:\x8c0\xe2\x8ey\x05@\xf8\xdfA:\xdc\x11\xc5\t@\xf8\x84\xf7CÖ”\x10\x0e@\xf8\x95\xd6&\xe8\v.\x11@\xf8:\x8c0\xe2\x8ey\x15@\xf8\xdfA:\xdc\x11\xc5\x19@\xf8\x84\xf7CÖ”\x10\x1e@\xf8\x95\xd6&\xe8\v.!@\xf8:\x8c0\xe2\x8ey%@\xf8\xdfA:\xdc\x11\xc5)@\xf8\x84\xf7CÖ”\x10.@\xf8\x95\xd6&\xe8\v.1@\xf8:\x8c0\xe2\x8ey5@\xf8\xdfA:\xdc\x11\xc59@\xf8\x84\xf7CÖ”\x10>@\xf8\x95\xd6&\xe8\v.A@\xf8:\x8c0\xe2\x8eyE@\xf8\xdfA:\xdc\x11\xc5I@\xf8\x84\xf7CÖ”\x10N@\xf8\x95\xd6&\xe8\v.Q@\xf8:\x8c0\xe2\x8eyU@\xf8\xdfA:\xdc\x11\xc5Y@\xf8\x84\xf7CÖ”\x10^@\xf8\x95\xd6&\xe8\v.a@\xf8:\x8c0\xe2\x8eye@\xf8\xdfA:\xdc\x11\xc5i@\xf8\x84\xf7CÖ”\x10n@\xf8\x95\xd6&\xe8\v.q@\xf8:\x8c0\xe2\x8eyu@\xf8\xdfA:\xdc\x11\xc5y@\xf8\x84\xf7CÖ”\x10~@\xf8\x95\xd6&\xe8\v.\x81@\xf8:\x8c0\xe2\x8ey\x85@\xf8\xdfA:\xdc\x11ʼn@\xf8\x84\xf7CÖ”\x10\x8e@\xf8\x95\xd6&\xe8\v.\x91@\xf8:\x8c0\xe2\x8ey\x95@\xf8\xdfA:\xdc\x11Å™@\xf8\x84\xf7CÖ”\x10\x9e@\xf8\x95\xd6&\xe8\v.\xa1@\xf8:\x8c0\xe2\x8ey\xa5@\xf8\xdfA:\xdc\x11Å©@\xf8\x84\xf7CÖ”\x10\xae@\xf8\x95\xd6&\xe8\v.\xb1@\xf8:\x8c0\xe2\x8ey\xb5@\xf8\xdfA:\xdc\x11Ź@\xf8\x84\xf7CÖ”\x10\xbe@\xf8\x95\xd6&\xe8\v.\xc1@\xf8:\x8c0\xe2\x8ey\xc5@\xf8\xdfA:\xdc\x11\xc5\xc9@\xf8\x84\xf7CÖ”\x10\xce@\xf8\x95\xd6&\xe8\v.\xd1@\xf8:\x8c0\xe2\x8ey\xd5@\xf8\xdfA:\xdc\x11\xc5\xd9@\xf8\x84\xf7CÖ”\x10\xde@\xf8\x95\xd6&\xe8\v.\xe1@\xf8:\x8c0\xe2\x8ey\xe5@\xf8\xdfA:\xdc\x11\xc5\xe9@\xf8\x84\xf7CÖ”\x10\xee@\xf8\x95\xd6&\xe8\v.\xf1@\xf8:\x8c0\xe2\x8ey\xf5@\xf8\xdfA:\xdc\x11\xc5\xf9@\xf8\x84\xf7CÖ”\x10\xfe@\xf8\x95\xd6&\xe8\v.\x01A\xfe\xf0\x7f\x00" + var gcPauses = "7\xff\x81\x03\x01\x01\x10Float64Histogram\x01\xff\x82\x00\x01\x02\x01\x06Counts\x01\xff\x84\x00\x01\aBuckets\x01\xff\x86\x00\x00\x00\x16\xff\x83\x02\x01\x01\b[]uint64\x01\xff\x84\x00\x01\x06\x00\x00\x17\xff\x85\x02\x01\x01\t[]float64\x01\xff\x86\x00\x01\b\x00\x00\xfe\x06R\xff\x82\x01\xff\xa2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x00\x01\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x00\x02\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\xa3\xfe\xf0\xff\x00\xf8\x95\xd6&\xe8\v.q>\xf8\x95\xd6&\xe8\v.\x81>\xf8\xdfA:\xdc\x11ʼn>\xf8\x95\xd6&\xe8\v.\x91>\xf8:\x8c0\xe2\x8ey\x95>\xf8\xdfA:\xdc\x11Å™>\xf8\x84\xf7CÖ”\x10\x9e>\xf8\x95\xd6&\xe8\v.\xa1>\xf8:\x8c0\xe2\x8ey\xa5>\xf8\xdfA:\xdc\x11Å©>\xf8\x84\xf7CÖ”\x10\xae>\xf8\x95\xd6&\xe8\v.\xb1>\xf8:\x8c0\xe2\x8ey\xb5>\xf8\xdfA:\xdc\x11Ź>\xf8\x84\xf7CÖ”\x10\xbe>\xf8\x95\xd6&\xe8\v.\xc1>\xf8:\x8c0\xe2\x8ey\xc5>\xf8\xdfA:\xdc\x11\xc5\xc9>\xf8\x84\xf7CÖ”\x10\xce>\xf8\x95\xd6&\xe8\v.\xd1>\xf8:\x8c0\xe2\x8ey\xd5>\xf8\xdfA:\xdc\x11\xc5\xd9>\xf8\x84\xf7CÖ”\x10\xde>\xf8\x95\xd6&\xe8\v.\xe1>\xf8:\x8c0\xe2\x8ey\xe5>\xf8\xdfA:\xdc\x11\xc5\xe9>\xf8\x84\xf7CÖ”\x10\xee>\xf8\x95\xd6&\xe8\v.\xf1>\xf8:\x8c0\xe2\x8ey\xf5>\xf8\xdfA:\xdc\x11\xc5\xf9>\xf8\x84\xf7CÖ”\x10\xfe>\xf8\x95\xd6&\xe8\v.\x01?\xf8:\x8c0\xe2\x8ey\x05?\xf8\xdfA:\xdc\x11\xc5\t?\xf8\x84\xf7CÖ”\x10\x0e?\xf8\x95\xd6&\xe8\v.\x11?\xf8:\x8c0\xe2\x8ey\x15?\xf8\xdfA:\xdc\x11\xc5\x19?\xf8\x84\xf7CÖ”\x10\x1e?\xf8\x95\xd6&\xe8\v.!?\xf8:\x8c0\xe2\x8ey%?\xf8\xdfA:\xdc\x11\xc5)?\xf8\x84\xf7CÖ”\x10.?\xf8\x95\xd6&\xe8\v.1?\xf8:\x8c0\xe2\x8ey5?\xf8\xdfA:\xdc\x11\xc59?\xf8\x84\xf7CÖ”\x10>?\xf8\x95\xd6&\xe8\v.A?\xf8:\x8c0\xe2\x8eyE?\xf8\xdfA:\xdc\x11\xc5I?\xf8\x84\xf7CÖ”\x10N?\xf8\x95\xd6&\xe8\v.Q?\xf8:\x8c0\xe2\x8eyU?\xf8\xdfA:\xdc\x11\xc5Y?\xf8\x84\xf7CÖ”\x10^?\xf8\x95\xd6&\xe8\v.a?\xf8:\x8c0\xe2\x8eye?\xf8\xdfA:\xdc\x11\xc5i?\xf8\x84\xf7CÖ”\x10n?\xf8\x95\xd6&\xe8\v.q?\xf8:\x8c0\xe2\x8eyu?\xf8\xdfA:\xdc\x11\xc5y?\xf8\x84\xf7CÖ”\x10~?\xf8\x95\xd6&\xe8\v.\x81?\xf8:\x8c0\xe2\x8ey\x85?\xf8\xdfA:\xdc\x11ʼn?\xf8\x84\xf7CÖ”\x10\x8e?\xf8\x95\xd6&\xe8\v.\x91?\xf8:\x8c0\xe2\x8ey\x95?\xf8\xdfA:\xdc\x11Å™?\xf8\x84\xf7CÖ”\x10\x9e?\xf8\x95\xd6&\xe8\v.\xa1?\xf8:\x8c0\xe2\x8ey\xa5?\xf8\xdfA:\xdc\x11Å©?\xf8\x84\xf7CÖ”\x10\xae?\xf8\x95\xd6&\xe8\v.\xb1?\xf8:\x8c0\xe2\x8ey\xb5?\xf8\xdfA:\xdc\x11Ź?\xf8\x84\xf7CÖ”\x10\xbe?\xf8\x95\xd6&\xe8\v.\xc1?\xf8:\x8c0\xe2\x8ey\xc5?\xf8\xdfA:\xdc\x11\xc5\xc9?\xf8\x84\xf7CÖ”\x10\xce?\xf8\x95\xd6&\xe8\v.\xd1?\xf8:\x8c0\xe2\x8ey\xd5?\xf8\xdfA:\xdc\x11\xc5\xd9?\xf8\x84\xf7CÖ”\x10\xde?\xf8\x95\xd6&\xe8\v.\xe1?\xf8:\x8c0\xe2\x8ey\xe5?\xf8\xdfA:\xdc\x11\xc5\xe9?\xf8\x84\xf7CÖ”\x10\xee?\xf8\x95\xd6&\xe8\v.\xf1?\xf8:\x8c0\xe2\x8ey\xf5?\xf8\xdfA:\xdc\x11\xc5\xf9?\xf8\x84\xf7CÖ”\x10\xfe?\xf8\x95\xd6&\xe8\v.\x01@\xf8:\x8c0\xe2\x8ey\x05@\xf8\xdfA:\xdc\x11\xc5\t@\xf8\x84\xf7CÖ”\x10\x0e@\xf8\x95\xd6&\xe8\v.\x11@\xf8:\x8c0\xe2\x8ey\x15@\xf8\xdfA:\xdc\x11\xc5\x19@\xf8\x84\xf7CÖ”\x10\x1e@\xf8\x95\xd6&\xe8\v.!@\xf8:\x8c0\xe2\x8ey%@\xf8\xdfA:\xdc\x11\xc5)@\xf8\x84\xf7CÖ”\x10.@\xf8\x95\xd6&\xe8\v.1@\xf8:\x8c0\xe2\x8ey5@\xf8\xdfA:\xdc\x11\xc59@\xf8\x84\xf7CÖ”\x10>@\xf8\x95\xd6&\xe8\v.A@\xf8:\x8c0\xe2\x8eyE@\xf8\xdfA:\xdc\x11\xc5I@\xf8\x84\xf7CÖ”\x10N@\xf8\x95\xd6&\xe8\v.Q@\xf8:\x8c0\xe2\x8eyU@\xf8\xdfA:\xdc\x11\xc5Y@\xf8\x84\xf7CÖ”\x10^@\xf8\x95\xd6&\xe8\v.a@\xf8:\x8c0\xe2\x8eye@\xf8\xdfA:\xdc\x11\xc5i@\xf8\x84\xf7CÖ”\x10n@\xf8\x95\xd6&\xe8\v.q@\xf8:\x8c0\xe2\x8eyu@\xf8\xdfA:\xdc\x11\xc5y@\xf8\x84\xf7CÖ”\x10~@\xf8\x95\xd6&\xe8\v.\x81@\xf8:\x8c0\xe2\x8ey\x85@\xf8\xdfA:\xdc\x11ʼn@\xf8\x84\xf7CÖ”\x10\x8e@\xf8\x95\xd6&\xe8\v.\x91@\xf8:\x8c0\xe2\x8ey\x95@\xf8\xdfA:\xdc\x11Å™@\xf8\x84\xf7CÖ”\x10\x9e@\xf8\x95\xd6&\xe8\v.\xa1@\xf8:\x8c0\xe2\x8ey\xa5@\xf8\xdfA:\xdc\x11Å©@\xf8\x84\xf7CÖ”\x10\xae@\xf8\x95\xd6&\xe8\v.\xb1@\xf8:\x8c0\xe2\x8ey\xb5@\xf8\xdfA:\xdc\x11Ź@\xf8\x84\xf7CÖ”\x10\xbe@\xf8\x95\xd6&\xe8\v.\xc1@\xf8:\x8c0\xe2\x8ey\xc5@\xf8\xdfA:\xdc\x11\xc5\xc9@\xf8\x84\xf7CÖ”\x10\xce@\xf8\x95\xd6&\xe8\v.\xd1@\xf8:\x8c0\xe2\x8ey\xd5@\xf8\xdfA:\xdc\x11\xc5\xd9@\xf8\x84\xf7CÖ”\x10\xde@\xf8\x95\xd6&\xe8\v.\xe1@\xf8:\x8c0\xe2\x8ey\xe5@\xf8\xdfA:\xdc\x11\xc5\xe9@\xf8\x84\xf7CÖ”\x10\xee@\xf8\x95\xd6&\xe8\v.\xf1@\xf8:\x8c0\xe2\x8ey\xf5@\xf8\xdfA:\xdc\x11\xc5\xf9@\xf8\x84\xf7CÖ”\x10\xfe@\xf8\x95\xd6&\xe8\v.\x01A\xfe\xf0\x7f\x00" + + var secondsToNs = float64(time.Second) + + dserialize := func(data string) *metrics2.Float64Histogram { + var res metrics2.Float64Histogram + if err := gob.NewDecoder(bytes.NewReader([]byte(data))).Decode(&res); err != nil { + panic(err) + } + return &res + } + cpuSchedLatency := metrics.RuntimeHistogramFromData(secondsToNs, dserialize(sLatency)) + registry.Register("system/cpu/schedlatency", cpuSchedLatency) + + memPauses := metrics.RuntimeHistogramFromData(secondsToNs, dserialize(gcPauses)) + registry.Register("system/memory/pauses", memPauses) + } + return registry +} diff --git a/metrics/internal/sampledata_test.go b/metrics/internal/sampledata_test.go new file mode 100644 index 000000000000..00132994064e --- /dev/null +++ b/metrics/internal/sampledata_test.go @@ -0,0 +1,27 @@ +package internal + +import ( + "bytes" + "encoding/gob" + "fmt" + metrics2 "runtime/metrics" + "testing" + "time" + + "github.com/ethereum/go-ethereum/metrics" +) + +func TestCollectRuntimeMetrics(t *testing.T) { + t.Skip("Only used for generating testdata") + serialize := func(path string, histogram *metrics2.Float64Histogram) { + var f = new(bytes.Buffer) + if err := gob.NewEncoder(f).Encode(histogram); err != nil { + panic(err) + } + fmt.Printf("var %v = %q\n", path, f.Bytes()) + } + time.Sleep(2 * time.Second) + stats := metrics.ReadRuntimeStats() + serialize("schedlatency", stats.SchedLatency) + serialize("gcpauses", stats.GCPauses) +} diff --git a/metrics/librato/librato.go b/metrics/librato/librato.go index 3d45f4c7be1b..a86f75863786 100644 --- a/metrics/librato/librato.go +++ b/metrics/librato/librato.go @@ -61,16 +61,16 @@ func (rep *Reporter) Run() { // calculate sum of squares from data provided by metrics.Histogram // see http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods -func sumSquares(s metrics.Sample) float64 { - count := float64(s.Count()) - sumSquared := math.Pow(count*s.Mean(), 2) - sumSquares := math.Pow(count*s.StdDev(), 2) + sumSquared/count +func sumSquares(icount int64, mean, stDev float64) float64 { + count := float64(icount) + sumSquared := math.Pow(count*mean, 2) + sumSquares := math.Pow(count*stDev, 2) + sumSquared/count if math.IsNaN(sumSquares) { return 0.0 } return sumSquares } -func sumSquaresTimer(t metrics.Timer) float64 { +func sumSquaresTimer(t metrics.TimerSnapshot) float64 { count := float64(t.Count()) sumSquared := math.Pow(count*t.Mean(), 2) sumSquares := math.Pow(count*t.StdDev(), 2) + sumSquared/count @@ -97,9 +97,10 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B measurement[Period] = rep.Interval.Seconds() switch m := metric.(type) { case metrics.Counter: - if m.Count() > 0 { + ms := m.Snapshot() + if ms.Count() > 0 { measurement[Name] = fmt.Sprintf("%s.%s", name, "count") - measurement[Value] = float64(m.Count()) + measurement[Value] = float64(ms.Count()) measurement[Attributes] = map[string]interface{}{ DisplayUnitsLong: Operations, DisplayUnitsShort: OperationsShort, @@ -108,9 +109,9 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B snapshot.Counters = append(snapshot.Counters, measurement) } case metrics.CounterFloat64: - if m.Count() > 0 { + if count := m.Snapshot().Count(); count > 0 { measurement[Name] = fmt.Sprintf("%s.%s", name, "count") - measurement[Value] = m.Count() + measurement[Value] = count measurement[Attributes] = map[string]interface{}{ DisplayUnitsLong: Operations, DisplayUnitsShort: OperationsShort, @@ -120,40 +121,45 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B } case metrics.Gauge: measurement[Name] = name - measurement[Value] = float64(m.Value()) + measurement[Value] = float64(m.Snapshot().Value()) snapshot.Gauges = append(snapshot.Gauges, measurement) case metrics.GaugeFloat64: measurement[Name] = name - measurement[Value] = m.Value() + measurement[Value] = m.Snapshot().Value() + snapshot.Gauges = append(snapshot.Gauges, measurement) + case metrics.GaugeInfo: + measurement[Name] = name + measurement[Value] = m.Snapshot().Value() snapshot.Gauges = append(snapshot.Gauges, measurement) case metrics.Histogram: - if m.Count() > 0 { + ms := m.Snapshot() + if ms.Count() > 0 { gauges := make([]Measurement, histogramGaugeCount) - s := m.Sample() measurement[Name] = fmt.Sprintf("%s.%s", name, "hist") - measurement[Count] = uint64(s.Count()) - measurement[Max] = float64(s.Max()) - measurement[Min] = float64(s.Min()) - measurement[Sum] = float64(s.Sum()) - measurement[SumSquares] = sumSquares(s) + measurement[Count] = uint64(ms.Count()) + measurement[Max] = float64(ms.Max()) + measurement[Min] = float64(ms.Min()) + measurement[Sum] = float64(ms.Sum()) + measurement[SumSquares] = sumSquares(ms.Count(), ms.Mean(), ms.StdDev()) gauges[0] = measurement for i, p := range rep.Percentiles { gauges[i+1] = Measurement{ Name: fmt.Sprintf("%s.%.2f", measurement[Name], p), - Value: s.Percentile(p), + Value: ms.Percentile(p), Period: measurement[Period], } } snapshot.Gauges = append(snapshot.Gauges, gauges...) } case metrics.Meter: + ms := m.Snapshot() measurement[Name] = name - measurement[Value] = float64(m.Count()) + measurement[Value] = float64(ms.Count()) snapshot.Counters = append(snapshot.Counters, measurement) snapshot.Gauges = append(snapshot.Gauges, Measurement{ Name: fmt.Sprintf("%s.%s", name, "1min"), - Value: m.Rate1(), + Value: ms.Rate1(), Period: int64(rep.Interval.Seconds()), Attributes: map[string]interface{}{ DisplayUnitsLong: Operations, @@ -163,7 +169,7 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B }, Measurement{ Name: fmt.Sprintf("%s.%s", name, "5min"), - Value: m.Rate5(), + Value: ms.Rate5(), Period: int64(rep.Interval.Seconds()), Attributes: map[string]interface{}{ DisplayUnitsLong: Operations, @@ -173,7 +179,7 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B }, Measurement{ Name: fmt.Sprintf("%s.%s", name, "15min"), - Value: m.Rate15(), + Value: ms.Rate15(), Period: int64(rep.Interval.Seconds()), Attributes: map[string]interface{}{ DisplayUnitsLong: Operations, @@ -183,26 +189,27 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B }, ) case metrics.Timer: + ms := m.Snapshot() measurement[Name] = name - measurement[Value] = float64(m.Count()) + measurement[Value] = float64(ms.Count()) snapshot.Counters = append(snapshot.Counters, measurement) - if m.Count() > 0 { + if ms.Count() > 0 { libratoName := fmt.Sprintf("%s.%s", name, "timer.mean") gauges := make([]Measurement, histogramGaugeCount) gauges[0] = Measurement{ Name: libratoName, - Count: uint64(m.Count()), - Sum: m.Mean() * float64(m.Count()), - Max: float64(m.Max()), - Min: float64(m.Min()), - SumSquares: sumSquaresTimer(m), + Count: uint64(ms.Count()), + Sum: ms.Mean() * float64(ms.Count()), + Max: float64(ms.Max()), + Min: float64(ms.Min()), + SumSquares: sumSquaresTimer(ms), Period: int64(rep.Interval.Seconds()), Attributes: rep.TimerAttributes, } for i, p := range rep.Percentiles { gauges[i+1] = Measurement{ Name: fmt.Sprintf("%s.timer.%2.0f", name, p*100), - Value: m.Percentile(p), + Value: ms.Percentile(p), Period: int64(rep.Interval.Seconds()), Attributes: rep.TimerAttributes, } @@ -211,7 +218,7 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B snapshot.Gauges = append(snapshot.Gauges, Measurement{ Name: fmt.Sprintf("%s.%s", name, "rate.1min"), - Value: m.Rate1(), + Value: ms.Rate1(), Period: int64(rep.Interval.Seconds()), Attributes: map[string]interface{}{ DisplayUnitsLong: Operations, @@ -221,7 +228,7 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B }, Measurement{ Name: fmt.Sprintf("%s.%s", name, "rate.5min"), - Value: m.Rate5(), + Value: ms.Rate5(), Period: int64(rep.Interval.Seconds()), Attributes: map[string]interface{}{ DisplayUnitsLong: Operations, @@ -231,7 +238,7 @@ func (rep *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot B }, Measurement{ Name: fmt.Sprintf("%s.%s", name, "rate.15min"), - Value: m.Rate15(), + Value: ms.Rate15(), Period: int64(rep.Interval.Seconds()), Attributes: map[string]interface{}{ DisplayUnitsLong: Operations, diff --git a/metrics/log.go b/metrics/log.go index d1ce627a8378..3b9773faa728 100644 --- a/metrics/log.go +++ b/metrics/log.go @@ -23,16 +23,19 @@ func LogScaled(r Registry, freq time.Duration, scale time.Duration, l Logger) { switch metric := i.(type) { case Counter: l.Printf("counter %s\n", name) - l.Printf(" count: %9d\n", metric.Count()) + l.Printf(" count: %9d\n", metric.Snapshot().Count()) case CounterFloat64: l.Printf("counter %s\n", name) - l.Printf(" count: %f\n", metric.Count()) + l.Printf(" count: %f\n", metric.Snapshot().Count()) case Gauge: l.Printf("gauge %s\n", name) - l.Printf(" value: %9d\n", metric.Value()) + l.Printf(" value: %9d\n", metric.Snapshot().Value()) case GaugeFloat64: l.Printf("gauge %s\n", name) - l.Printf(" value: %f\n", metric.Value()) + l.Printf(" value: %f\n", metric.Snapshot().Value()) + case GaugeInfo: + l.Printf("gauge %s\n", name) + l.Printf(" value: %s\n", metric.Snapshot().Value()) case Healthcheck: metric.Check() l.Printf("healthcheck %s\n", name) diff --git a/metrics/meter.go b/metrics/meter.go index e8564d6a5e76..22475ef6ebee 100644 --- a/metrics/meter.go +++ b/metrics/meter.go @@ -1,21 +1,25 @@ package metrics import ( + "math" "sync" "sync/atomic" "time" ) -// Meters count events to produce exponentially-weighted moving average rates -// at one-, five-, and fifteen-minutes and a mean rate. -type Meter interface { +type MeterSnapshot interface { Count() int64 - Mark(int64) Rate1() float64 Rate5() float64 Rate15() float64 RateMean() float64 - Snapshot() Meter +} + +// Meters count events to produce exponentially-weighted moving average rates +// at one-, five-, and fifteen-minutes and a mean rate. +type Meter interface { + Mark(int64) + Snapshot() MeterSnapshot Stop() } @@ -30,17 +34,6 @@ func GetOrRegisterMeter(name string, r Registry) Meter { return r.GetOrRegister(name, NewMeter).(Meter) } -// GetOrRegisterMeterForced returns an existing Meter or constructs and registers a -// new StandardMeter no matter the global switch is enabled or not. -// Be sure to unregister the meter from the registry once it is of no use to -// allow for garbage collection. -func GetOrRegisterMeterForced(name string, r Registry) Meter { - if nil == r { - r = DefaultRegistry - } - return r.GetOrRegister(name, NewMeterForced).(Meter) -} - // NewMeter constructs a new StandardMeter and launches a goroutine. // Be sure to call Stop() once the meter is of no use to allow for garbage collection. func NewMeter() Meter { @@ -58,18 +51,13 @@ func NewMeter() Meter { return m } -// NewMeterForced constructs a new StandardMeter and launches a goroutine no matter -// the global switch is enabled or not. -// Be sure to call Stop() once the meter is of no use to allow for garbage collection. -func NewMeterForced() Meter { - m := newStandardMeter() - arbiter.Lock() - defer arbiter.Unlock() - arbiter.meters[m] = struct{}{} - if !arbiter.started { - arbiter.started = true - go arbiter.tick() +// NewInactiveMeter returns a meter but does not start any goroutines. This +// method is mainly intended for testing. +func NewInactiveMeter() Meter { + if !Enabled { + return NilMeter{} } + m := newStandardMeter() return m } @@ -78,95 +66,48 @@ func NewMeterForced() Meter { // Be sure to unregister the meter from the registry once it is of no use to // allow for garbage collection. func NewRegisteredMeter(name string, r Registry) Meter { - c := NewMeter() - if nil == r { - r = DefaultRegistry - } - r.Register(name, c) - return c -} - -// NewRegisteredMeterForced constructs and registers a new StandardMeter -// and launches a goroutine no matter the global switch is enabled or not. -// Be sure to unregister the meter from the registry once it is of no use to -// allow for garbage collection. -func NewRegisteredMeterForced(name string, r Registry) Meter { - c := NewMeterForced() - if nil == r { - r = DefaultRegistry - } - r.Register(name, c) - return c + return GetOrRegisterMeter(name, r) } -// MeterSnapshot is a read-only copy of another Meter. -type MeterSnapshot struct { - temp atomic.Int64 +// meterSnapshot is a read-only copy of the meter's internal values. +type meterSnapshot struct { count int64 rate1, rate5, rate15, rateMean float64 } // Count returns the count of events at the time the snapshot was taken. -func (m *MeterSnapshot) Count() int64 { return m.count } - -// Mark panics. -func (*MeterSnapshot) Mark(n int64) { - panic("Mark called on a MeterSnapshot") -} +func (m *meterSnapshot) Count() int64 { return m.count } // Rate1 returns the one-minute moving average rate of events per second at the // time the snapshot was taken. -func (m *MeterSnapshot) Rate1() float64 { return m.rate1 } +func (m *meterSnapshot) Rate1() float64 { return m.rate1 } // Rate5 returns the five-minute moving average rate of events per second at // the time the snapshot was taken. -func (m *MeterSnapshot) Rate5() float64 { return m.rate5 } +func (m *meterSnapshot) Rate5() float64 { return m.rate5 } // Rate15 returns the fifteen-minute moving average rate of events per second // at the time the snapshot was taken. -func (m *MeterSnapshot) Rate15() float64 { return m.rate15 } +func (m *meterSnapshot) Rate15() float64 { return m.rate15 } // RateMean returns the meter's mean rate of events per second at the time the // snapshot was taken. -func (m *MeterSnapshot) RateMean() float64 { return m.rateMean } - -// Snapshot returns the snapshot. -func (m *MeterSnapshot) Snapshot() Meter { return m } - -// Stop is a no-op. -func (m *MeterSnapshot) Stop() {} +func (m *meterSnapshot) RateMean() float64 { return m.rateMean } // NilMeter is a no-op Meter. type NilMeter struct{} -// Count is a no-op. -func (NilMeter) Count() int64 { return 0 } - -// Mark is a no-op. -func (NilMeter) Mark(n int64) {} - -// Rate1 is a no-op. -func (NilMeter) Rate1() float64 { return 0.0 } - -// Rate5 is a no-op. -func (NilMeter) Rate5() float64 { return 0.0 } - -// Rate15 is a no-op. -func (NilMeter) Rate15() float64 { return 0.0 } - -// RateMean is a no-op. -func (NilMeter) RateMean() float64 { return 0.0 } - -// Snapshot is a no-op. -func (NilMeter) Snapshot() Meter { return NilMeter{} } - -// Stop is a no-op. -func (NilMeter) Stop() {} +func (NilMeter) Count() int64 { return 0 } +func (NilMeter) Mark(n int64) {} +func (NilMeter) Snapshot() MeterSnapshot { return (*emptySnapshot)(nil) } +func (NilMeter) Stop() {} // StandardMeter is the standard implementation of a Meter. type StandardMeter struct { - lock sync.RWMutex - snapshot *MeterSnapshot + count atomic.Int64 + uncounted atomic.Int64 // not yet added to the EWMAs + rateMean atomic.Uint64 + a1, a5, a15 EWMA startTime time.Time stopped atomic.Bool @@ -174,7 +115,6 @@ type StandardMeter struct { func newStandardMeter() *StandardMeter { return &StandardMeter{ - snapshot: &MeterSnapshot{}, a1: NewEWMA1(), a5: NewEWMA5(), a15: NewEWMA15(), @@ -184,97 +124,42 @@ func newStandardMeter() *StandardMeter { // Stop stops the meter, Mark() will be a no-op if you use it after being stopped. func (m *StandardMeter) Stop() { - stopped := m.stopped.Swap(true) - if !stopped { + if stopped := m.stopped.Swap(true); !stopped { arbiter.Lock() delete(arbiter.meters, m) arbiter.Unlock() } } -// Count returns the number of events recorded. -// It updates the meter to be as accurate as possible -func (m *StandardMeter) Count() int64 { - m.lock.Lock() - defer m.lock.Unlock() - m.updateMeter() - return m.snapshot.count -} - // Mark records the occurrence of n events. func (m *StandardMeter) Mark(n int64) { - m.snapshot.temp.Add(n) -} - -// Rate1 returns the one-minute moving average rate of events per second. -func (m *StandardMeter) Rate1() float64 { - m.lock.RLock() - defer m.lock.RUnlock() - return m.snapshot.rate1 -} - -// Rate5 returns the five-minute moving average rate of events per second. -func (m *StandardMeter) Rate5() float64 { - m.lock.RLock() - defer m.lock.RUnlock() - return m.snapshot.rate5 -} - -// Rate15 returns the fifteen-minute moving average rate of events per second. -func (m *StandardMeter) Rate15() float64 { - m.lock.RLock() - defer m.lock.RUnlock() - return m.snapshot.rate15 -} - -// RateMean returns the meter's mean rate of events per second. -func (m *StandardMeter) RateMean() float64 { - m.lock.RLock() - defer m.lock.RUnlock() - return m.snapshot.rateMean + m.uncounted.Add(n) } // Snapshot returns a read-only copy of the meter. -func (m *StandardMeter) Snapshot() Meter { - m.lock.RLock() - snapshot := MeterSnapshot{ - count: m.snapshot.count, - rate1: m.snapshot.rate1, - rate5: m.snapshot.rate5, - rate15: m.snapshot.rate15, - rateMean: m.snapshot.rateMean, +func (m *StandardMeter) Snapshot() MeterSnapshot { + return &meterSnapshot{ + count: m.count.Load() + m.uncounted.Load(), + rate1: m.a1.Snapshot().Rate(), + rate5: m.a5.Snapshot().Rate(), + rate15: m.a15.Snapshot().Rate(), + rateMean: math.Float64frombits(m.rateMean.Load()), } - snapshot.temp.Store(m.snapshot.temp.Load()) - m.lock.RUnlock() - return &snapshot -} - -func (m *StandardMeter) updateSnapshot() { - // should run with write lock held on m.lock - snapshot := m.snapshot - snapshot.rate1 = m.a1.Rate() - snapshot.rate5 = m.a5.Rate() - snapshot.rate15 = m.a15.Rate() - snapshot.rateMean = float64(snapshot.count) / time.Since(m.startTime).Seconds() } -func (m *StandardMeter) updateMeter() { - // should only run with write lock held on m.lock - n := m.snapshot.temp.Swap(0) - m.snapshot.count += n +func (m *StandardMeter) tick() { + // Take the uncounted values, add to count + n := m.uncounted.Swap(0) + count := m.count.Add(n) + m.rateMean.Store(math.Float64bits(float64(count) / time.Since(m.startTime).Seconds())) + // Update the EWMA's internal state m.a1.Update(n) m.a5.Update(n) m.a15.Update(n) -} - -func (m *StandardMeter) tick() { - m.lock.Lock() - defer m.lock.Unlock() - m.updateMeter() + // And trigger them to calculate the rates m.a1.Tick() m.a5.Tick() m.a15.Tick() - m.updateSnapshot() } // meterArbiter ticks meters every 5s from a single goroutine. diff --git a/metrics/meter_test.go b/metrics/meter_test.go index b3f6cb8c0c97..019c4d765b52 100644 --- a/metrics/meter_test.go +++ b/metrics/meter_test.go @@ -12,11 +12,17 @@ func BenchmarkMeter(b *testing.B) { m.Mark(1) } } - +func TestMeter(t *testing.T) { + m := NewMeter() + m.Mark(47) + if v := m.Snapshot().Count(); v != 47 { + t.Fatalf("have %d want %d", v, 47) + } +} func TestGetOrRegisterMeter(t *testing.T) { r := NewRegistry() NewRegisteredMeter("foo", r).Mark(47) - if m := GetOrRegisterMeter("foo", r); m.Count() != 47 { + if m := GetOrRegisterMeter("foo", r).Snapshot(); m.Count() != 47 { t.Fatal(m.Count()) } } @@ -31,10 +37,10 @@ func TestMeterDecay(t *testing.T) { ma.meters[m] = struct{}{} m.Mark(1) ma.tickMeters() - rateMean := m.RateMean() + rateMean := m.Snapshot().RateMean() time.Sleep(100 * time.Millisecond) ma.tickMeters() - if m.RateMean() >= rateMean { + if m.Snapshot().RateMean() >= rateMean { t.Error("m.RateMean() didn't decrease") } } @@ -42,7 +48,7 @@ func TestMeterDecay(t *testing.T) { func TestMeterNonzero(t *testing.T) { m := NewMeter() m.Mark(3) - if count := m.Count(); count != 3 { + if count := m.Snapshot().Count(); count != 3 { t.Errorf("m.Count(): 3 != %v\n", count) } } @@ -59,16 +65,8 @@ func TestMeterStop(t *testing.T) { } } -func TestMeterSnapshot(t *testing.T) { - m := NewMeter() - m.Mark(1) - if snapshot := m.Snapshot(); m.RateMean() != snapshot.RateMean() { - t.Fatal(snapshot) - } -} - func TestMeterZero(t *testing.T) { - m := NewMeter() + m := NewMeter().Snapshot() if count := m.Count(); count != 0 { t.Errorf("m.Count(): 0 != %v\n", count) } @@ -79,13 +77,13 @@ func TestMeterRepeat(t *testing.T) { for i := 0; i < 101; i++ { m.Mark(int64(i)) } - if count := m.Count(); count != 5050 { + if count := m.Snapshot().Count(); count != 5050 { t.Errorf("m.Count(): 5050 != %v\n", count) } for i := 0; i < 101; i++ { m.Mark(int64(i)) } - if count := m.Count(); count != 10100 { + if count := m.Snapshot().Count(); count != 10100 { t.Errorf("m.Count(): 10100 != %v\n", count) } } diff --git a/metrics/metrics.go b/metrics/metrics.go index c206f1692407..9ca8f115c0f7 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -9,7 +9,9 @@ import ( "os" "runtime/metrics" "runtime/pprof" + "strconv" "strings" + "syscall" "time" "github.com/ethereum/go-ethereum/log" @@ -30,13 +32,35 @@ var EnabledExpensive = false // enablerFlags is the CLI flag names to use to enable metrics collections. var enablerFlags = []string{"metrics"} +// enablerEnvVars is the env var names to use to enable metrics collections. +var enablerEnvVars = []string{"GETH_METRICS"} + // expensiveEnablerFlags is the CLI flag names to use to enable metrics collections. var expensiveEnablerFlags = []string{"metrics.expensive"} +// expensiveEnablerEnvVars is the env var names to use to enable metrics collections. +var expensiveEnablerEnvVars = []string{"GETH_METRICS_EXPENSIVE"} + // Init enables or disables the metrics system. Since we need this to run before // any other code gets to create meters and timers, we'll actually do an ugly hack // and peek into the command line args for the metrics flag. func init() { + for _, enabler := range enablerEnvVars { + if val, found := syscall.Getenv(enabler); found && !Enabled { + if enable, _ := strconv.ParseBool(val); enable { // ignore error, flag parser will choke on it later + log.Info("Enabling metrics collection") + Enabled = true + } + } + } + for _, enabler := range expensiveEnablerEnvVars { + if val, found := syscall.Getenv(enabler); found && !EnabledExpensive { + if enable, _ := strconv.ParseBool(val); enable { // ignore error, flag parser will choke on it later + log.Info("Enabling expensive metrics collection") + EnabledExpensive = true + } + } + } for _, arg := range os.Args { flag := strings.TrimLeft(arg, "-") @@ -85,6 +109,12 @@ var runtimeSamples = []metrics.Sample{ {Name: "/sched/latencies:seconds"}, // histogram } +func ReadRuntimeStats() *runtimeStats { + r := new(runtimeStats) + readRuntimeStats(r) + return r +} + func readRuntimeStats(v *runtimeStats) { metrics.Read(runtimeSamples) for _, s := range runtimeSamples { diff --git a/metrics/metrics_test.go b/metrics/metrics_test.go index 534c44139b36..2861d5f2caf6 100644 --- a/metrics/metrics_test.go +++ b/metrics/metrics_test.go @@ -98,8 +98,8 @@ func Example() { t.Time(func() { time.Sleep(10 * time.Millisecond) }) t.Update(1) - fmt.Println(c.Count()) - fmt.Println(t.Min()) + fmt.Println(c.Snapshot().Count()) + fmt.Println(t.Snapshot().Min()) // Output: 17 // 1 } diff --git a/metrics/opentsdb.go b/metrics/opentsdb.go index c9fd2e75d5e5..e81690f94340 100644 --- a/metrics/opentsdb.go +++ b/metrics/opentsdb.go @@ -3,6 +3,7 @@ package metrics import ( "bufio" "fmt" + "io" "log" "net" "os" @@ -57,26 +58,22 @@ func getShortHostname() string { return shortHostName } -func openTSDB(c *OpenTSDBConfig) error { - shortHostname := getShortHostname() - now := time.Now().Unix() +// writeRegistry writes the registry-metrics on the opentsb format. +func (c *OpenTSDBConfig) writeRegistry(w io.Writer, now int64, shortHostname string) { du := float64(c.DurationUnit) - conn, err := net.DialTCP("tcp", nil, c.Addr) - if nil != err { - return err - } - defer conn.Close() - w := bufio.NewWriter(conn) + c.Registry.Each(func(name string, i interface{}) { switch metric := i.(type) { case Counter: - fmt.Fprintf(w, "put %s.%s.count %d %d host=%s\n", c.Prefix, name, now, metric.Count(), shortHostname) + fmt.Fprintf(w, "put %s.%s.count %d %d host=%s\n", c.Prefix, name, now, metric.Snapshot().Count(), shortHostname) case CounterFloat64: - fmt.Fprintf(w, "put %s.%s.count %d %f host=%s\n", c.Prefix, name, now, metric.Count(), shortHostname) + fmt.Fprintf(w, "put %s.%s.count %d %f host=%s\n", c.Prefix, name, now, metric.Snapshot().Count(), shortHostname) case Gauge: - fmt.Fprintf(w, "put %s.%s.value %d %d host=%s\n", c.Prefix, name, now, metric.Value(), shortHostname) + fmt.Fprintf(w, "put %s.%s.value %d %d host=%s\n", c.Prefix, name, now, metric.Snapshot().Value(), shortHostname) case GaugeFloat64: - fmt.Fprintf(w, "put %s.%s.value %d %f host=%s\n", c.Prefix, name, now, metric.Value(), shortHostname) + fmt.Fprintf(w, "put %s.%s.value %d %f host=%s\n", c.Prefix, name, now, metric.Snapshot().Value(), shortHostname) + case GaugeInfo: + fmt.Fprintf(w, "put %s.%s.value %d %s host=%s\n", c.Prefix, name, now, metric.Snapshot().Value().String(), shortHostname) case Histogram: h := metric.Snapshot() ps := h.Percentiles([]float64{0.5, 0.75, 0.95, 0.99, 0.999}) @@ -115,7 +112,17 @@ func openTSDB(c *OpenTSDBConfig) error { fmt.Fprintf(w, "put %s.%s.fifteen-minute %d %.2f host=%s\n", c.Prefix, name, now, t.Rate15(), shortHostname) fmt.Fprintf(w, "put %s.%s.mean-rate %d %.2f host=%s\n", c.Prefix, name, now, t.RateMean(), shortHostname) } - w.Flush() }) +} + +func openTSDB(c *OpenTSDBConfig) error { + conn, err := net.DialTCP("tcp", nil, c.Addr) + if nil != err { + return err + } + defer conn.Close() + w := bufio.NewWriter(conn) + c.writeRegistry(w, time.Now().Unix(), getShortHostname()) + w.Flush() return nil } diff --git a/metrics/opentsdb_test.go b/metrics/opentsdb_test.go index c43728960ed5..4548309f9c23 100644 --- a/metrics/opentsdb_test.go +++ b/metrics/opentsdb_test.go @@ -1,7 +1,11 @@ package metrics import ( + "fmt" "net" + "os" + "strings" + "testing" "time" ) @@ -19,3 +23,44 @@ func ExampleOpenTSDBWithConfig() { DurationUnit: time.Millisecond, }) } + +func TestExampleOpenTSB(t *testing.T) { + r := NewOrderedRegistry() + NewRegisteredGaugeInfo("foo", r).Update(GaugeInfoValue{"chain_id": "5"}) + NewRegisteredGaugeFloat64("pi", r).Update(3.14) + NewRegisteredCounter("months", r).Inc(12) + NewRegisteredCounterFloat64("tau", r).Inc(1.57) + NewRegisteredMeter("elite", r).Mark(1337) + NewRegisteredTimer("second", r).Update(time.Second) + NewRegisteredCounterFloat64("tau", r).Inc(1.57) + NewRegisteredCounterFloat64("tau", r).Inc(1.57) + + w := new(strings.Builder) + (&OpenTSDBConfig{ + Registry: r, + DurationUnit: time.Millisecond, + Prefix: "pre", + }).writeRegistry(w, 978307200, "hal9000") + + wantB, err := os.ReadFile("./testdata/opentsb.want") + if err != nil { + t.Fatal(err) + } + if have, want := w.String(), string(wantB); have != want { + t.Errorf("\nhave:\n%v\nwant:\n%v\n", have, want) + t.Logf("have vs want:\n%v", findFirstDiffPos(have, want)) + } +} + +func findFirstDiffPos(a, b string) string { + yy := strings.Split(b, "\n") + for i, x := range strings.Split(a, "\n") { + if i >= len(yy) { + return fmt.Sprintf("have:%d: %s\nwant:%d: ", i, x, i) + } + if y := yy[i]; x != y { + return fmt.Sprintf("have:%d: %s\nwant:%d: %s", i, x, i, y) + } + } + return "" +} diff --git a/metrics/prometheus/collector.go b/metrics/prometheus/collector.go index 2bd9bf22ccae..25b258d56ab1 100644 --- a/metrics/prometheus/collector.go +++ b/metrics/prometheus/collector.go @@ -19,6 +19,7 @@ package prometheus import ( "bytes" "fmt" + "sort" "strconv" "strings" @@ -46,23 +47,55 @@ func newCollector() *collector { } } -func (c *collector) addCounter(name string, m metrics.Counter) { +// Add adds the metric i to the collector. This method returns an error if the +// metric type is not supported/known. +func (c *collector) Add(name string, i any) error { + switch m := i.(type) { + case metrics.Counter: + c.addCounter(name, m.Snapshot()) + case metrics.CounterFloat64: + c.addCounterFloat64(name, m.Snapshot()) + case metrics.Gauge: + c.addGauge(name, m.Snapshot()) + case metrics.GaugeFloat64: + c.addGaugeFloat64(name, m.Snapshot()) + case metrics.GaugeInfo: + c.addGaugeInfo(name, m.Snapshot()) + case metrics.Histogram: + c.addHistogram(name, m.Snapshot()) + case metrics.Meter: + c.addMeter(name, m.Snapshot()) + case metrics.Timer: + c.addTimer(name, m.Snapshot()) + case metrics.ResettingTimer: + c.addResettingTimer(name, m.Snapshot()) + default: + return fmt.Errorf("unknown prometheus metric type %T", i) + } + return nil +} + +func (c *collector) addCounter(name string, m metrics.CounterSnapshot) { c.writeGaugeCounter(name, m.Count()) } -func (c *collector) addCounterFloat64(name string, m metrics.CounterFloat64) { +func (c *collector) addCounterFloat64(name string, m metrics.CounterFloat64Snapshot) { c.writeGaugeCounter(name, m.Count()) } -func (c *collector) addGauge(name string, m metrics.Gauge) { +func (c *collector) addGauge(name string, m metrics.GaugeSnapshot) { c.writeGaugeCounter(name, m.Value()) } -func (c *collector) addGaugeFloat64(name string, m metrics.GaugeFloat64) { +func (c *collector) addGaugeFloat64(name string, m metrics.GaugeFloat64Snapshot) { c.writeGaugeCounter(name, m.Value()) } -func (c *collector) addHistogram(name string, m metrics.Histogram) { +func (c *collector) addGaugeInfo(name string, m metrics.GaugeInfoSnapshot) { + c.writeGaugeInfo(name, m.Value()) +} + +func (c *collector) addHistogram(name string, m metrics.HistogramSnapshot) { pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999} ps := m.Percentiles(pv) c.writeSummaryCounter(name, m.Count()) @@ -73,11 +106,11 @@ func (c *collector) addHistogram(name string, m metrics.Histogram) { c.buff.WriteRune('\n') } -func (c *collector) addMeter(name string, m metrics.Meter) { +func (c *collector) addMeter(name string, m metrics.MeterSnapshot) { c.writeGaugeCounter(name, m.Count()) } -func (c *collector) addTimer(name string, m metrics.Timer) { +func (c *collector) addTimer(name string, m metrics.TimerSnapshot) { pv := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999} ps := m.Percentiles(pv) c.writeSummaryCounter(name, m.Count()) @@ -88,13 +121,12 @@ func (c *collector) addTimer(name string, m metrics.Timer) { c.buff.WriteRune('\n') } -func (c *collector) addResettingTimer(name string, m metrics.ResettingTimer) { - if len(m.Values()) <= 0 { +func (c *collector) addResettingTimer(name string, m metrics.ResettingTimerSnapshot) { + if m.Count() <= 0 { return } - ps := m.Percentiles([]float64{50, 95, 99}) - val := m.Values() - c.writeSummaryCounter(name, len(val)) + ps := m.Percentiles([]float64{0.50, 0.95, 0.99}) + c.writeSummaryCounter(name, m.Count()) c.buff.WriteString(fmt.Sprintf(typeSummaryTpl, mutateKey(name))) c.writeSummaryPercentile(name, "0.50", ps[0]) c.writeSummaryPercentile(name, "0.95", ps[1]) @@ -102,6 +134,19 @@ func (c *collector) addResettingTimer(name string, m metrics.ResettingTimer) { c.buff.WriteRune('\n') } +func (c *collector) writeGaugeInfo(name string, value metrics.GaugeInfoValue) { + name = mutateKey(name) + c.buff.WriteString(fmt.Sprintf(typeGaugeTpl, name)) + c.buff.WriteString(name) + c.buff.WriteString(" ") + var kvs []string + for k, v := range value { + kvs = append(kvs, fmt.Sprintf("%v=%q", k, v)) + } + sort.Strings(kvs) + c.buff.WriteString(fmt.Sprintf("{%v} 1\n\n", strings.Join(kvs, ", "))) +} + func (c *collector) writeGaugeCounter(name string, value interface{}) { name = mutateKey(name) c.buff.WriteString(fmt.Sprintf(typeGaugeTpl, name)) diff --git a/metrics/prometheus/collector_test.go b/metrics/prometheus/collector_test.go index ff87c8e765e1..ea17aac4585f 100644 --- a/metrics/prometheus/collector_test.go +++ b/metrics/prometheus/collector_test.go @@ -1,11 +1,29 @@ +// Copyright 2023 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 prometheus import ( + "fmt" "os" + "strings" "testing" - "time" "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/metrics/internal" ) func TestMain(m *testing.M) { @@ -14,104 +32,34 @@ func TestMain(m *testing.M) { } func TestCollector(t *testing.T) { - c := newCollector() - - counter := metrics.NewCounter() - counter.Inc(12345) - c.addCounter("test/counter", counter) - - counterfloat64 := metrics.NewCounterFloat64() - counterfloat64.Inc(54321.98) - c.addCounterFloat64("test/counter_float64", counterfloat64) - - gauge := metrics.NewGauge() - gauge.Update(23456) - c.addGauge("test/gauge", gauge) - - gaugeFloat64 := metrics.NewGaugeFloat64() - gaugeFloat64.Update(34567.89) - c.addGaugeFloat64("test/gauge_float64", gaugeFloat64) - - histogram := metrics.NewHistogram(&metrics.NilSample{}) - c.addHistogram("test/histogram", histogram) - - meter := metrics.NewMeter() - defer meter.Stop() - meter.Mark(9999999) - c.addMeter("test/meter", meter) - - timer := metrics.NewTimer() - defer timer.Stop() - timer.Update(20 * time.Millisecond) - timer.Update(21 * time.Millisecond) - timer.Update(22 * time.Millisecond) - timer.Update(120 * time.Millisecond) - timer.Update(23 * time.Millisecond) - timer.Update(24 * time.Millisecond) - c.addTimer("test/timer", timer) - - resettingTimer := metrics.NewResettingTimer() - resettingTimer.Update(10 * time.Millisecond) - resettingTimer.Update(11 * time.Millisecond) - resettingTimer.Update(12 * time.Millisecond) - resettingTimer.Update(120 * time.Millisecond) - resettingTimer.Update(13 * time.Millisecond) - resettingTimer.Update(14 * time.Millisecond) - c.addResettingTimer("test/resetting_timer", resettingTimer.Snapshot()) - - emptyResettingTimer := metrics.NewResettingTimer().Snapshot() - c.addResettingTimer("test/empty_resetting_timer", emptyResettingTimer) - - const expectedOutput = `# TYPE test_counter gauge -test_counter 12345 - -# TYPE test_counter_float64 gauge -test_counter_float64 54321.98 - -# TYPE test_gauge gauge -test_gauge 23456 - -# TYPE test_gauge_float64 gauge -test_gauge_float64 34567.89 - -# TYPE test_histogram_count counter -test_histogram_count 0 - -# TYPE test_histogram summary -test_histogram {quantile="0.5"} 0 -test_histogram {quantile="0.75"} 0 -test_histogram {quantile="0.95"} 0 -test_histogram {quantile="0.99"} 0 -test_histogram {quantile="0.999"} 0 -test_histogram {quantile="0.9999"} 0 - -# TYPE test_meter gauge -test_meter 9999999 - -# TYPE test_timer_count counter -test_timer_count 6 - -# TYPE test_timer summary -test_timer {quantile="0.5"} 2.25e+07 -test_timer {quantile="0.75"} 4.8e+07 -test_timer {quantile="0.95"} 1.2e+08 -test_timer {quantile="0.99"} 1.2e+08 -test_timer {quantile="0.999"} 1.2e+08 -test_timer {quantile="0.9999"} 1.2e+08 - -# TYPE test_resetting_timer_count counter -test_resetting_timer_count 6 - -# TYPE test_resetting_timer summary -test_resetting_timer {quantile="0.50"} 12000000 -test_resetting_timer {quantile="0.95"} 120000000 -test_resetting_timer {quantile="0.99"} 120000000 + var ( + c = newCollector() + want string + ) + internal.ExampleMetrics().Each(func(name string, i interface{}) { + c.Add(name, i) + }) + if wantB, err := os.ReadFile("./testdata/prometheus.want"); err != nil { + t.Fatal(err) + } else { + want = string(wantB) + } + if have := c.buff.String(); have != want { + t.Logf("have\n%v", have) + t.Logf("have vs want:\n%v", findFirstDiffPos(have, want)) + t.Fatalf("unexpected collector output") + } +} -` - exp := c.buff.String() - if exp != expectedOutput { - t.Log("Expected Output:\n", expectedOutput) - t.Log("Actual Output:\n", exp) - t.Fatal("unexpected collector output") +func findFirstDiffPos(a, b string) string { + yy := strings.Split(b, "\n") + for i, x := range strings.Split(a, "\n") { + if i >= len(yy) { + return fmt.Sprintf("have:%d: %s\nwant:%d: ", i, x, i) + } + if y := yy[i]; x != y { + return fmt.Sprintf("have:%d: %s\nwant:%d: %s", i, x, i, y) + } } + return "" } diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index d966fa9a8666..dbdeae6c7f7d 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -41,25 +41,7 @@ func Handler(reg metrics.Registry) http.Handler { for _, name := range names { i := reg.Get(name) - - switch m := i.(type) { - case metrics.Counter: - c.addCounter(name, m.Snapshot()) - case metrics.CounterFloat64: - c.addCounterFloat64(name, m.Snapshot()) - case metrics.Gauge: - c.addGauge(name, m.Snapshot()) - case metrics.GaugeFloat64: - c.addGaugeFloat64(name, m.Snapshot()) - case metrics.Histogram: - c.addHistogram(name, m.Snapshot()) - case metrics.Meter: - c.addMeter(name, m.Snapshot()) - case metrics.Timer: - c.addTimer(name, m.Snapshot()) - case metrics.ResettingTimer: - c.addResettingTimer(name, m.Snapshot()) - default: + if err := c.Add(name, i); err != nil { log.Warn("Unknown Prometheus metric type", "type", fmt.Sprintf("%T", i)) } } diff --git a/metrics/prometheus/testdata/prometheus.want b/metrics/prometheus/testdata/prometheus.want new file mode 100644 index 000000000000..861c5f5cf087 --- /dev/null +++ b/metrics/prometheus/testdata/prometheus.want @@ -0,0 +1,70 @@ +# TYPE system_cpu_schedlatency_count counter +system_cpu_schedlatency_count 5645 + +# TYPE system_cpu_schedlatency summary +system_cpu_schedlatency {quantile="0.5"} 0 +system_cpu_schedlatency {quantile="0.75"} 7168 +system_cpu_schedlatency {quantile="0.95"} 1.6777216e+07 +system_cpu_schedlatency {quantile="0.99"} 2.9360128e+07 +system_cpu_schedlatency {quantile="0.999"} 3.3554432e+07 +system_cpu_schedlatency {quantile="0.9999"} 3.3554432e+07 + +# TYPE system_memory_pauses_count counter +system_memory_pauses_count 14 + +# TYPE system_memory_pauses summary +system_memory_pauses {quantile="0.5"} 32768 +system_memory_pauses {quantile="0.75"} 57344 +system_memory_pauses {quantile="0.95"} 196608 +system_memory_pauses {quantile="0.99"} 196608 +system_memory_pauses {quantile="0.999"} 196608 +system_memory_pauses {quantile="0.9999"} 196608 + +# TYPE test_counter gauge +test_counter 12345 + +# TYPE test_counter_float64 gauge +test_counter_float64 54321.98 + +# TYPE test_gauge gauge +test_gauge 23456 + +# TYPE test_gauge_float64 gauge +test_gauge_float64 34567.89 + +# TYPE test_gauge_info gauge +test_gauge_info {arch="amd64", commit="7caa2d8163ae3132c1c2d6978c76610caee2d949", os="linux", protocol_versions="64 65 66", version="1.10.18-unstable"} 1 + +# TYPE test_histogram_count counter +test_histogram_count 3 + +# TYPE test_histogram summary +test_histogram {quantile="0.5"} 2 +test_histogram {quantile="0.75"} 3 +test_histogram {quantile="0.95"} 3 +test_histogram {quantile="0.99"} 3 +test_histogram {quantile="0.999"} 3 +test_histogram {quantile="0.9999"} 3 + +# TYPE test_meter gauge +test_meter 0 + +# TYPE test_resetting_timer_count counter +test_resetting_timer_count 6 + +# TYPE test_resetting_timer summary +test_resetting_timer {quantile="0.50"} 1.25e+07 +test_resetting_timer {quantile="0.95"} 1.2e+08 +test_resetting_timer {quantile="0.99"} 1.2e+08 + +# TYPE test_timer_count counter +test_timer_count 6 + +# TYPE test_timer summary +test_timer {quantile="0.5"} 2.25e+07 +test_timer {quantile="0.75"} 4.8e+07 +test_timer {quantile="0.95"} 1.2e+08 +test_timer {quantile="0.99"} 1.2e+08 +test_timer {quantile="0.999"} 1.2e+08 +test_timer {quantile="0.9999"} 1.2e+08 + diff --git a/metrics/registry.go b/metrics/registry.go index ec6e37c54ffe..8bfbc080420f 100644 --- a/metrics/registry.go +++ b/metrics/registry.go @@ -3,6 +3,7 @@ package metrics import ( "fmt" "reflect" + "sort" "strings" "sync" ) @@ -47,17 +48,39 @@ type Registry interface { Unregister(string) } +type orderedRegistry struct { + StandardRegistry +} + +// Call the given function for each registered metric. +func (r *orderedRegistry) Each(f func(string, interface{})) { + var names []string + reg := r.registered() + for name := range reg { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + f(name, reg[name]) + } +} + +// NewRegistry creates a new registry. +func NewRegistry() Registry { + return new(StandardRegistry) +} + +// NewOrderedRegistry creates a new ordered registry (for testing). +func NewOrderedRegistry() Registry { + return new(orderedRegistry) +} + // The standard implementation of a Registry uses sync.map // of names to metrics. type StandardRegistry struct { metrics sync.Map } -// Create a new registry. -func NewRegistry() Registry { - return &StandardRegistry{} -} - // Call the given function for each registered metric. func (r *StandardRegistry) Each(f func(string, interface{})) { for name, i := range r.registered() { @@ -127,13 +150,13 @@ func (r *StandardRegistry) GetAll() map[string]map[string]interface{} { values := make(map[string]interface{}) switch metric := i.(type) { case Counter: - values["count"] = metric.Count() + values["count"] = metric.Snapshot().Count() case CounterFloat64: - values["count"] = metric.Count() + values["count"] = metric.Snapshot().Count() case Gauge: - values["value"] = metric.Value() + values["value"] = metric.Snapshot().Value() case GaugeFloat64: - values["value"] = metric.Value() + values["value"] = metric.Snapshot().Value() case Healthcheck: values["error"] = nil metric.Check() @@ -191,7 +214,7 @@ func (r *StandardRegistry) Unregister(name string) { func (r *StandardRegistry) loadOrRegister(name string, i interface{}) (interface{}, bool, bool) { switch i.(type) { - case Counter, CounterFloat64, Gauge, GaugeFloat64, Healthcheck, Histogram, Meter, Timer, ResettingTimer: + case Counter, CounterFloat64, Gauge, GaugeFloat64, GaugeInfo, Healthcheck, Histogram, Meter, Timer, ResettingTimer: default: return nil, false, false } diff --git a/metrics/registry_test.go b/metrics/registry_test.go index 7cc5cf14fe55..75012dd4ac00 100644 --- a/metrics/registry_test.go +++ b/metrics/registry_test.go @@ -85,11 +85,11 @@ func TestRegistryDuplicate(t *testing.T) { func TestRegistryGet(t *testing.T) { r := NewRegistry() r.Register("foo", NewCounter()) - if count := r.Get("foo").(Counter).Count(); count != 0 { + if count := r.Get("foo").(Counter).Snapshot().Count(); count != 0 { t.Fatal(count) } r.Get("foo").(Counter).Inc(1) - if count := r.Get("foo").(Counter).Count(); count != 1 { + if count := r.Get("foo").(Counter).Snapshot().Count(); count != 1 { t.Fatal(count) } } diff --git a/metrics/resetting_sample.go b/metrics/resetting_sample.go index 43c1129cd0bc..c38ffcd3ec32 100644 --- a/metrics/resetting_sample.go +++ b/metrics/resetting_sample.go @@ -17,7 +17,7 @@ type resettingSample struct { } // Snapshot returns a read-only copy of the sample with the original reset. -func (rs *resettingSample) Snapshot() Sample { +func (rs *resettingSample) Snapshot() SampleSnapshot { s := rs.Sample.Snapshot() rs.Sample.Clear() return s diff --git a/metrics/resetting_timer.go b/metrics/resetting_timer.go index e5327d3bd339..6802e3fcea98 100644 --- a/metrics/resetting_timer.go +++ b/metrics/resetting_timer.go @@ -1,8 +1,6 @@ package metrics import ( - "math" - "sort" "sync" "time" ) @@ -10,12 +8,17 @@ import ( // Initial slice capacity for the values stored in a ResettingTimer const InitialResettingTimerSliceCap = 10 +type ResettingTimerSnapshot interface { + Count() int + Mean() float64 + Max() int64 + Min() int64 + Percentiles([]float64) []float64 +} + // ResettingTimer is used for storing aggregated values for timers, which are reset on every flush interval. type ResettingTimer interface { - Values() []int64 - Snapshot() ResettingTimer - Percentiles([]float64) []int64 - Mean() float64 + Snapshot() ResettingTimerSnapshot Time(func()) Update(time.Duration) UpdateSince(time.Time) @@ -51,70 +54,40 @@ func NewResettingTimer() ResettingTimer { } // NilResettingTimer is a no-op ResettingTimer. -type NilResettingTimer struct { -} - -// Values is a no-op. -func (NilResettingTimer) Values() []int64 { return nil } - -// Snapshot is a no-op. -func (NilResettingTimer) Snapshot() ResettingTimer { - return &ResettingTimerSnapshot{ - values: []int64{}, - } -} - -// Time is a no-op. -func (NilResettingTimer) Time(func()) {} - -// Update is a no-op. -func (NilResettingTimer) Update(time.Duration) {} - -// Percentiles panics. -func (NilResettingTimer) Percentiles([]float64) []int64 { - panic("Percentiles called on a NilResettingTimer") -} - -// Mean panics. -func (NilResettingTimer) Mean() float64 { - panic("Mean called on a NilResettingTimer") -} - -// UpdateSince is a no-op. -func (NilResettingTimer) UpdateSince(time.Time) {} +type NilResettingTimer struct{} + +func (NilResettingTimer) Values() []int64 { return nil } +func (n NilResettingTimer) Snapshot() ResettingTimerSnapshot { return n } +func (NilResettingTimer) Time(f func()) { f() } +func (NilResettingTimer) Update(time.Duration) {} +func (NilResettingTimer) Percentiles([]float64) []float64 { return nil } +func (NilResettingTimer) Mean() float64 { return 0.0 } +func (NilResettingTimer) Max() int64 { return 0 } +func (NilResettingTimer) Min() int64 { return 0 } +func (NilResettingTimer) UpdateSince(time.Time) {} +func (NilResettingTimer) Count() int { return 0 } // StandardResettingTimer is the standard implementation of a ResettingTimer. // and Meter. type StandardResettingTimer struct { values []int64 - mutex sync.Mutex -} + sum int64 // sum is a running count of the total sum, used later to calculate mean -// Values returns a slice with all measurements. -func (t *StandardResettingTimer) Values() []int64 { - return t.values + mutex sync.Mutex } // Snapshot resets the timer and returns a read-only copy of its contents. -func (t *StandardResettingTimer) Snapshot() ResettingTimer { +func (t *StandardResettingTimer) Snapshot() ResettingTimerSnapshot { t.mutex.Lock() defer t.mutex.Unlock() - currentValues := t.values - t.values = make([]int64, 0, InitialResettingTimerSliceCap) - - return &ResettingTimerSnapshot{ - values: currentValues, + snapshot := &resettingTimerSnapshot{} + if len(t.values) > 0 { + snapshot.mean = float64(t.sum) / float64(len(t.values)) + snapshot.values = t.values + t.values = make([]int64, 0, InitialResettingTimerSliceCap) } -} - -// Percentiles panics. -func (t *StandardResettingTimer) Percentiles([]float64) []int64 { - panic("Percentiles called on a StandardResettingTimer") -} - -// Mean panics. -func (t *StandardResettingTimer) Mean() float64 { - panic("Mean called on a StandardResettingTimer") + t.sum = 0 + return snapshot } // Record the duration of the execution of the given function. @@ -129,113 +102,70 @@ func (t *StandardResettingTimer) Update(d time.Duration) { t.mutex.Lock() defer t.mutex.Unlock() t.values = append(t.values, int64(d)) + t.sum += int64(d) } // Record the duration of an event that started at a time and ends now. func (t *StandardResettingTimer) UpdateSince(ts time.Time) { - t.mutex.Lock() - defer t.mutex.Unlock() - t.values = append(t.values, int64(time.Since(ts))) + t.Update(time.Since(ts)) } -// ResettingTimerSnapshot is a point-in-time copy of another ResettingTimer. -type ResettingTimerSnapshot struct { +// resettingTimerSnapshot is a point-in-time copy of another ResettingTimer. +type resettingTimerSnapshot struct { values []int64 mean float64 - thresholdBoundaries []int64 + max int64 + min int64 + thresholdBoundaries []float64 calculated bool } -// Snapshot returns the snapshot. -func (t *ResettingTimerSnapshot) Snapshot() ResettingTimer { return t } - -// Time panics. -func (*ResettingTimerSnapshot) Time(func()) { - panic("Time called on a ResettingTimerSnapshot") -} - -// Update panics. -func (*ResettingTimerSnapshot) Update(time.Duration) { - panic("Update called on a ResettingTimerSnapshot") -} - -// UpdateSince panics. -func (*ResettingTimerSnapshot) UpdateSince(time.Time) { - panic("UpdateSince called on a ResettingTimerSnapshot") -} - -// Values returns all values from snapshot. -func (t *ResettingTimerSnapshot) Values() []int64 { - return t.values +// Count return the length of the values from snapshot. +func (t *resettingTimerSnapshot) Count() int { + return len(t.values) } // Percentiles returns the boundaries for the input percentiles. -func (t *ResettingTimerSnapshot) Percentiles(percentiles []float64) []int64 { +// note: this method is not thread safe +func (t *resettingTimerSnapshot) Percentiles(percentiles []float64) []float64 { t.calc(percentiles) - return t.thresholdBoundaries } // Mean returns the mean of the snapshotted values -func (t *ResettingTimerSnapshot) Mean() float64 { +// note: this method is not thread safe +func (t *resettingTimerSnapshot) Mean() float64 { if !t.calculated { - t.calc([]float64{}) + t.calc(nil) } return t.mean } -func (t *ResettingTimerSnapshot) calc(percentiles []float64) { - sort.Sort(Int64Slice(t.values)) - - count := len(t.values) - if count > 0 { - min := t.values[0] - max := t.values[count-1] - - cumulativeValues := make([]int64, count) - cumulativeValues[0] = min - for i := 1; i < count; i++ { - cumulativeValues[i] = t.values[i] + cumulativeValues[i-1] - } - - t.thresholdBoundaries = make([]int64, len(percentiles)) - - thresholdBoundary := max - - for i, pct := range percentiles { - if count > 1 { - var abs float64 - if pct >= 0 { - abs = pct - } else { - abs = 100 + pct - } - // poor man's math.Round(x): - // math.Floor(x + 0.5) - indexOfPerc := int(math.Floor(((abs / 100.0) * float64(count)) + 0.5)) - if pct >= 0 && indexOfPerc > 0 { - indexOfPerc -= 1 // index offset=0 - } - thresholdBoundary = t.values[indexOfPerc] - } - - t.thresholdBoundaries[i] = thresholdBoundary - } - - sum := cumulativeValues[count-1] - t.mean = float64(sum) / float64(count) - } else { - t.thresholdBoundaries = make([]int64, len(percentiles)) - t.mean = 0 +// Max returns the max of the snapshotted values +// note: this method is not thread safe +func (t *resettingTimerSnapshot) Max() int64 { + if !t.calculated { + t.calc(nil) } - - t.calculated = true + return t.max } -// Int64Slice attaches the methods of sort.Interface to []int64, sorting in increasing order. -type Int64Slice []int64 +// Min returns the min of the snapshotted values +// note: this method is not thread safe +func (t *resettingTimerSnapshot) Min() int64 { + if !t.calculated { + t.calc(nil) + } + return t.min +} -func (s Int64Slice) Len() int { return len(s) } -func (s Int64Slice) Less(i, j int) bool { return s[i] < s[j] } -func (s Int64Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (t *resettingTimerSnapshot) calc(percentiles []float64) { + scores := CalculatePercentiles(t.values, percentiles) + t.thresholdBoundaries = scores + if len(t.values) == 0 { + return + } + t.min = t.values[0] + t.max = t.values[len(t.values)-1] +} diff --git a/metrics/resetting_timer_test.go b/metrics/resetting_timer_test.go index 77c49dc3866a..4571fc8eb052 100644 --- a/metrics/resetting_timer_test.go +++ b/metrics/resetting_timer_test.go @@ -10,9 +10,9 @@ func TestResettingTimer(t *testing.T) { values []int64 start int end int - wantP50 int64 - wantP95 int64 - wantP99 int64 + wantP50 float64 + wantP95 float64 + wantP99 float64 wantMean float64 wantMin int64 wantMax int64 @@ -21,14 +21,14 @@ func TestResettingTimer(t *testing.T) { values: []int64{}, start: 1, end: 11, - wantP50: 5, wantP95: 10, wantP99: 10, + wantP50: 5.5, wantP95: 10, wantP99: 10, wantMin: 1, wantMax: 10, wantMean: 5.5, }, { values: []int64{}, start: 1, end: 101, - wantP50: 50, wantP95: 95, wantP99: 99, + wantP50: 50.5, wantP95: 95.94999999999999, wantP99: 99.99, wantMin: 1, wantMax: 100, wantMean: 50.5, }, { @@ -56,11 +56,11 @@ func TestResettingTimer(t *testing.T) { values: []int64{1, 10}, start: 0, end: 0, - wantP50: 1, wantP95: 10, wantP99: 10, + wantP50: 5.5, wantP95: 10, wantP99: 10, wantMin: 1, wantMax: 10, wantMean: 5.5, }, } - for ind, tt := range tests { + for i, tt := range tests { timer := NewResettingTimer() for i := tt.start; i < tt.end; i++ { @@ -70,37 +70,27 @@ func TestResettingTimer(t *testing.T) { for _, v := range tt.values { timer.Update(time.Duration(v)) } - snap := timer.Snapshot() - ps := snap.Percentiles([]float64{50, 95, 99}) - - val := snap.Values() + ps := snap.Percentiles([]float64{0.50, 0.95, 0.99}) - if len(val) > 0 { - if tt.wantMin != val[0] { - t.Fatalf("%d: min: got %d, want %d", ind, val[0], tt.wantMin) - } - - if tt.wantMax != val[len(val)-1] { - t.Fatalf("%d: max: got %d, want %d", ind, val[len(val)-1], tt.wantMax) - } + if have, want := snap.Min(), tt.wantMin; have != want { + t.Fatalf("%d: min: have %d, want %d", i, have, want) } - - if tt.wantMean != snap.Mean() { - t.Fatalf("%d: mean: got %.2f, want %.2f", ind, snap.Mean(), tt.wantMean) + if have, want := snap.Max(), tt.wantMax; have != want { + t.Fatalf("%d: max: have %d, want %d", i, have, want) } - - if tt.wantP50 != ps[0] { - t.Fatalf("%d: p50: got %d, want %d", ind, ps[0], tt.wantP50) + if have, want := snap.Mean(), tt.wantMean; have != want { + t.Fatalf("%d: mean: have %v, want %v", i, have, want) } - - if tt.wantP95 != ps[1] { - t.Fatalf("%d: p95: got %d, want %d", ind, ps[1], tt.wantP95) + if have, want := ps[0], tt.wantP50; have != want { + t.Errorf("%d: p50: have %v, want %v", i, have, want) } - - if tt.wantP99 != ps[2] { - t.Fatalf("%d: p99: got %d, want %d", ind, ps[2], tt.wantP99) + if have, want := ps[1], tt.wantP95; have != want { + t.Errorf("%d: p95: have %v, want %v", i, have, want) + } + if have, want := ps[2], tt.wantP99; have != want { + t.Errorf("%d: p99: have %v, want %v", i, have, want) } } } @@ -110,11 +100,11 @@ func TestResettingTimerWithFivePercentiles(t *testing.T) { values []int64 start int end int - wantP05 int64 - wantP20 int64 - wantP50 int64 - wantP95 int64 - wantP99 int64 + wantP05 float64 + wantP20 float64 + wantP50 float64 + wantP95 float64 + wantP99 float64 wantMean float64 wantMin int64 wantMax int64 @@ -123,14 +113,14 @@ func TestResettingTimerWithFivePercentiles(t *testing.T) { values: []int64{}, start: 1, end: 11, - wantP05: 1, wantP20: 2, wantP50: 5, wantP95: 10, wantP99: 10, + wantP05: 1, wantP20: 2.2, wantP50: 5.5, wantP95: 10, wantP99: 10, wantMin: 1, wantMax: 10, wantMean: 5.5, }, { values: []int64{}, start: 1, end: 101, - wantP05: 5, wantP20: 20, wantP50: 50, wantP95: 95, wantP99: 99, + wantP05: 5.050000000000001, wantP20: 20.200000000000003, wantP50: 50.5, wantP95: 95.94999999999999, wantP99: 99.99, wantMin: 1, wantMax: 100, wantMean: 50.5, }, { @@ -158,7 +148,7 @@ func TestResettingTimerWithFivePercentiles(t *testing.T) { values: []int64{1, 10}, start: 0, end: 0, - wantP05: 1, wantP20: 1, wantP50: 1, wantP95: 10, wantP99: 10, + wantP05: 1, wantP20: 1, wantP50: 5.5, wantP95: 10, wantP99: 10, wantMin: 1, wantMax: 10, wantMean: 5.5, }, } @@ -175,42 +165,33 @@ func TestResettingTimerWithFivePercentiles(t *testing.T) { snap := timer.Snapshot() - ps := snap.Percentiles([]float64{5, 20, 50, 95, 99}) - - val := snap.Values() + ps := snap.Percentiles([]float64{0.05, 0.20, 0.50, 0.95, 0.99}) - if len(val) > 0 { - if tt.wantMin != val[0] { - t.Fatalf("%d: min: got %d, want %d", ind, val[0], tt.wantMin) - } + if tt.wantMin != snap.Min() { + t.Errorf("%d: min: got %d, want %d", ind, snap.Min(), tt.wantMin) + } - if tt.wantMax != val[len(val)-1] { - t.Fatalf("%d: max: got %d, want %d", ind, val[len(val)-1], tt.wantMax) - } + if tt.wantMax != snap.Max() { + t.Errorf("%d: max: got %d, want %d", ind, snap.Max(), tt.wantMax) } if tt.wantMean != snap.Mean() { - t.Fatalf("%d: mean: got %.2f, want %.2f", ind, snap.Mean(), tt.wantMean) + t.Errorf("%d: mean: got %.2f, want %.2f", ind, snap.Mean(), tt.wantMean) } - if tt.wantP05 != ps[0] { - t.Fatalf("%d: p05: got %d, want %d", ind, ps[0], tt.wantP05) + t.Errorf("%d: p05: got %v, want %v", ind, ps[0], tt.wantP05) } - if tt.wantP20 != ps[1] { - t.Fatalf("%d: p20: got %d, want %d", ind, ps[1], tt.wantP20) + t.Errorf("%d: p20: got %v, want %v", ind, ps[1], tt.wantP20) } - if tt.wantP50 != ps[2] { - t.Fatalf("%d: p50: got %d, want %d", ind, ps[2], tt.wantP50) + t.Errorf("%d: p50: got %v, want %v", ind, ps[2], tt.wantP50) } - if tt.wantP95 != ps[3] { - t.Fatalf("%d: p95: got %d, want %d", ind, ps[3], tt.wantP95) + t.Errorf("%d: p95: got %v, want %v", ind, ps[3], tt.wantP95) } - if tt.wantP99 != ps[4] { - t.Fatalf("%d: p99: got %d, want %d", ind, ps[4], tt.wantP99) + t.Errorf("%d: p99: got %v, want %v", ind, ps[4], tt.wantP99) } } } diff --git a/metrics/runtimehistogram.go b/metrics/runtimehistogram.go index c68939af1ef7..92fcbcc2814c 100644 --- a/metrics/runtimehistogram.go +++ b/metrics/runtimehistogram.go @@ -17,13 +17,19 @@ func getOrRegisterRuntimeHistogram(name string, scale float64, r Registry) *runt // runtimeHistogram wraps a runtime/metrics histogram. type runtimeHistogram struct { - v atomic.Value + v atomic.Value // v is a pointer to a metrics.Float64Histogram scaleFactor float64 } func newRuntimeHistogram(scale float64) *runtimeHistogram { h := &runtimeHistogram{scaleFactor: scale} - h.update(&metrics.Float64Histogram{}) + h.update(new(metrics.Float64Histogram)) + return h +} + +func RuntimeHistogramFromData(scale float64, hist *metrics.Float64Histogram) *runtimeHistogram { + h := &runtimeHistogram{scaleFactor: scale} + h.update(hist) return h } @@ -35,130 +41,107 @@ func (h *runtimeHistogram) update(mh *metrics.Float64Histogram) { return } - s := runtimeHistogramSnapshot{ + s := metrics.Float64Histogram{ Counts: make([]uint64, len(mh.Counts)), Buckets: make([]float64, len(mh.Buckets)), } copy(s.Counts, mh.Counts) - copy(s.Buckets, mh.Buckets) - for i, b := range s.Buckets { + for i, b := range mh.Buckets { s.Buckets[i] = b * h.scaleFactor } h.v.Store(&s) } -func (h *runtimeHistogram) load() *runtimeHistogramSnapshot { - return h.v.Load().(*runtimeHistogramSnapshot) -} - func (h *runtimeHistogram) Clear() { panic("runtimeHistogram does not support Clear") } func (h *runtimeHistogram) Update(int64) { panic("runtimeHistogram does not support Update") } -func (h *runtimeHistogram) Sample() Sample { - return NilSample{} -} - -// Snapshot returns a non-changing cop of the histogram. -func (h *runtimeHistogram) Snapshot() Histogram { - return h.load() -} - -// Count returns the sample count. -func (h *runtimeHistogram) Count() int64 { - return h.load().Count() -} - -// Mean returns an approximation of the mean. -func (h *runtimeHistogram) Mean() float64 { - return h.load().Mean() -} - -// StdDev approximates the standard deviation of the histogram. -func (h *runtimeHistogram) StdDev() float64 { - return h.load().StdDev() -} - -// Variance approximates the variance of the histogram. -func (h *runtimeHistogram) Variance() float64 { - return h.load().Variance() -} - -// Percentile computes the p'th percentile value. -func (h *runtimeHistogram) Percentile(p float64) float64 { - return h.load().Percentile(p) -} -// Percentiles computes all requested percentile values. -func (h *runtimeHistogram) Percentiles(ps []float64) []float64 { - return h.load().Percentiles(ps) -} - -// Max returns the highest sample value. -func (h *runtimeHistogram) Max() int64 { - return h.load().Max() +// Snapshot returns a non-changing copy of the histogram. +func (h *runtimeHistogram) Snapshot() HistogramSnapshot { + hist := h.v.Load().(*metrics.Float64Histogram) + return newRuntimeHistogramSnapshot(hist) } -// Min returns the lowest sample value. -func (h *runtimeHistogram) Min() int64 { - return h.load().Min() -} - -// Sum returns the sum of all sample values. -func (h *runtimeHistogram) Sum() int64 { - return h.load().Sum() +type runtimeHistogramSnapshot struct { + internal *metrics.Float64Histogram + calculated bool + // The following fields are (lazily) calculated based on 'internal' + mean float64 + count int64 + min int64 // min is the lowest sample value. + max int64 // max is the highest sample value. + variance float64 } -type runtimeHistogramSnapshot metrics.Float64Histogram - -func (h *runtimeHistogramSnapshot) Clear() { - panic("runtimeHistogram does not support Clear") -} -func (h *runtimeHistogramSnapshot) Update(int64) { - panic("runtimeHistogram does not support Update") -} -func (h *runtimeHistogramSnapshot) Sample() Sample { - return NilSample{} +func newRuntimeHistogramSnapshot(h *metrics.Float64Histogram) *runtimeHistogramSnapshot { + return &runtimeHistogramSnapshot{ + internal: h, + } } -func (h *runtimeHistogramSnapshot) Snapshot() Histogram { - return h +// calc calculates the values for the snapshot. This method is not threadsafe. +func (h *runtimeHistogramSnapshot) calc() { + h.calculated = true + var ( + count int64 // number of samples + sum float64 // approx sum of all sample values + min int64 + max float64 + ) + if len(h.internal.Counts) == 0 { + return + } + for i, c := range h.internal.Counts { + if c == 0 { + continue + } + if count == 0 { // Set min only first loop iteration + min = int64(math.Floor(h.internal.Buckets[i])) + } + count += int64(c) + sum += h.midpoint(i) * float64(c) + // Set max on every iteration + edge := h.internal.Buckets[i+1] + if math.IsInf(edge, 1) { + edge = h.internal.Buckets[i] + } + if edge > max { + max = edge + } + } + h.min = min + h.max = int64(max) + h.mean = sum / float64(count) + h.count = count } // Count returns the sample count. func (h *runtimeHistogramSnapshot) Count() int64 { - var count int64 - for _, c := range h.Counts { - count += int64(c) + if !h.calculated { + h.calc() } - return count + return h.count } -// Mean returns an approximation of the mean. -func (h *runtimeHistogramSnapshot) Mean() float64 { - if len(h.Counts) == 0 { - return 0 - } - mean, _ := h.mean() - return mean +// Size returns the size of the sample at the time the snapshot was taken. +func (h *runtimeHistogramSnapshot) Size() int { + return len(h.internal.Counts) } -// mean computes the mean and also the total sample count. -func (h *runtimeHistogramSnapshot) mean() (mean, totalCount float64) { - var sum float64 - for i, c := range h.Counts { - midpoint := h.midpoint(i) - sum += midpoint * float64(c) - totalCount += float64(c) +// Mean returns an approximation of the mean. +func (h *runtimeHistogramSnapshot) Mean() float64 { + if !h.calculated { + h.calc() } - return sum / totalCount, totalCount + return h.mean } func (h *runtimeHistogramSnapshot) midpoint(bucket int) float64 { - high := h.Buckets[bucket+1] - low := h.Buckets[bucket] + high := h.internal.Buckets[bucket+1] + low := h.internal.Buckets[bucket] if math.IsInf(high, 1) { // The edge of the highest bucket can be +Inf, and it's supposed to mean that this // bucket contains all remaining samples > low. We can't get the middle of an @@ -180,23 +163,31 @@ func (h *runtimeHistogramSnapshot) StdDev() float64 { // Variance approximates the variance of the histogram. func (h *runtimeHistogramSnapshot) Variance() float64 { - if len(h.Counts) == 0 { + if len(h.internal.Counts) == 0 { return 0 } - - mean, totalCount := h.mean() - if totalCount <= 1 { + if !h.calculated { + h.calc() + } + if h.count <= 1 { // There is no variance when there are zero or one items. return 0 } - + // Variance is not calculated in 'calc', because it requires a second iteration. + // Therefore we calculate it lazily in this method, triggered either by + // a direct call to Variance or via StdDev. + if h.variance != 0.0 { + return h.variance + } var sum float64 - for i, c := range h.Counts { + + for i, c := range h.internal.Counts { midpoint := h.midpoint(i) - d := midpoint - mean + d := midpoint - h.mean sum += float64(c) * (d * d) } - return sum / (totalCount - 1) + h.variance = sum / float64(h.count-1) + return h.variance } // Percentile computes the p'th percentile value. @@ -231,11 +222,11 @@ func (h *runtimeHistogramSnapshot) Percentiles(ps []float64) []float64 { func (h *runtimeHistogramSnapshot) computePercentiles(thresh []float64) { var totalCount float64 - for i, count := range h.Counts { + for i, count := range h.internal.Counts { totalCount += float64(count) for len(thresh) > 0 && thresh[0] < totalCount { - thresh[0] = h.Buckets[i] + thresh[0] = h.internal.Buckets[i] thresh = thresh[1:] } if len(thresh) == 0 { @@ -250,34 +241,25 @@ func (h *runtimeHistogramSnapshot) computePercentiles(thresh []float64) { // Max returns the highest sample value. func (h *runtimeHistogramSnapshot) Max() int64 { - for i := len(h.Counts) - 1; i >= 0; i-- { - count := h.Counts[i] - if count > 0 { - edge := h.Buckets[i+1] - if math.IsInf(edge, 1) { - edge = h.Buckets[i] - } - return int64(math.Ceil(edge)) - } + if !h.calculated { + h.calc() } - return 0 + return h.max } // Min returns the lowest sample value. func (h *runtimeHistogramSnapshot) Min() int64 { - for i, count := range h.Counts { - if count > 0 { - return int64(math.Floor(h.Buckets[i])) - } + if !h.calculated { + h.calc() } - return 0 + return h.min } // Sum returns the sum of all sample values. func (h *runtimeHistogramSnapshot) Sum() int64 { var sum float64 - for i := range h.Counts { - sum += h.Buckets[i] * float64(h.Counts[i]) + for i := range h.internal.Counts { + sum += h.internal.Buckets[i] * float64(h.internal.Counts[i]) } return int64(math.Ceil(sum)) } diff --git a/metrics/runtimehistogram_test.go b/metrics/runtimehistogram_test.go index d53a01438311..cf7e36420ae9 100644 --- a/metrics/runtimehistogram_test.go +++ b/metrics/runtimehistogram_test.go @@ -1,11 +1,14 @@ package metrics import ( + "bytes" + "encoding/gob" "fmt" "math" "reflect" "runtime/metrics" "testing" + "time" ) var _ Histogram = (*runtimeHistogram)(nil) @@ -74,7 +77,7 @@ func TestRuntimeHistogramStats(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprint(i), func(t *testing.T) { - s := runtimeHistogramSnapshot(test.h) + s := RuntimeHistogramFromData(1.0, &test.h).Snapshot() if v := s.Count(); v != test.Count { t.Errorf("Count() = %v, want %v", v, test.Count) @@ -121,13 +124,39 @@ func approxEqual(x, y, ε float64) bool { // This test verifies that requesting Percentiles in unsorted order // returns them in the requested order. func TestRuntimeHistogramStatsPercentileOrder(t *testing.T) { - p := runtimeHistogramSnapshot{ + s := RuntimeHistogramFromData(1.0, &metrics.Float64Histogram{ Counts: []uint64{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, Buckets: []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - } - result := p.Percentiles([]float64{1, 0.2, 0.5, 0.1, 0.2}) + }).Snapshot() + result := s.Percentiles([]float64{1, 0.2, 0.5, 0.1, 0.2}) expected := []float64{10, 2, 5, 1, 2} if !reflect.DeepEqual(result, expected) { t.Fatal("wrong result:", result) } } + +func BenchmarkRuntimeHistogramSnapshotRead(b *testing.B) { + var sLatency = "7\xff\x81\x03\x01\x01\x10Float64Histogram\x01\xff\x82\x00\x01\x02\x01\x06Counts\x01\xff\x84\x00\x01\aBuckets\x01\xff\x86\x00\x00\x00\x16\xff\x83\x02\x01\x01\b[]uint64\x01\xff\x84\x00\x01\x06\x00\x00\x17\xff\x85\x02\x01\x01\t[]float64\x01\xff\x86\x00\x01\b\x00\x00\xfe\x06T\xff\x82\x01\xff\xa2\x00\xfe\r\xef\x00\x01\x02\x02\x04\x05\x04\b\x15\x17 B?6.L;$!2) \x1a? \x190aH7FY6#\x190\x1d\x14\x10\x1b\r\t\x04\x03\x01\x01\x00\x03\x02\x00\x03\x05\x05\x02\x02\x06\x04\v\x06\n\x15\x18\x13'&.\x12=H/L&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff\xa3\xfe\xf0\xff\x00\xf8\x95\xd6&\xe8\v.q>\xf8\x95\xd6&\xe8\v.\x81>\xf8\xdfA:\xdc\x11ʼn>\xf8\x95\xd6&\xe8\v.\x91>\xf8:\x8c0\xe2\x8ey\x95>\xf8\xdfA:\xdc\x11Å™>\xf8\x84\xf7CÖ”\x10\x9e>\xf8\x95\xd6&\xe8\v.\xa1>\xf8:\x8c0\xe2\x8ey\xa5>\xf8\xdfA:\xdc\x11Å©>\xf8\x84\xf7CÖ”\x10\xae>\xf8\x95\xd6&\xe8\v.\xb1>\xf8:\x8c0\xe2\x8ey\xb5>\xf8\xdfA:\xdc\x11Ź>\xf8\x84\xf7CÖ”\x10\xbe>\xf8\x95\xd6&\xe8\v.\xc1>\xf8:\x8c0\xe2\x8ey\xc5>\xf8\xdfA:\xdc\x11\xc5\xc9>\xf8\x84\xf7CÖ”\x10\xce>\xf8\x95\xd6&\xe8\v.\xd1>\xf8:\x8c0\xe2\x8ey\xd5>\xf8\xdfA:\xdc\x11\xc5\xd9>\xf8\x84\xf7CÖ”\x10\xde>\xf8\x95\xd6&\xe8\v.\xe1>\xf8:\x8c0\xe2\x8ey\xe5>\xf8\xdfA:\xdc\x11\xc5\xe9>\xf8\x84\xf7CÖ”\x10\xee>\xf8\x95\xd6&\xe8\v.\xf1>\xf8:\x8c0\xe2\x8ey\xf5>\xf8\xdfA:\xdc\x11\xc5\xf9>\xf8\x84\xf7CÖ”\x10\xfe>\xf8\x95\xd6&\xe8\v.\x01?\xf8:\x8c0\xe2\x8ey\x05?\xf8\xdfA:\xdc\x11\xc5\t?\xf8\x84\xf7CÖ”\x10\x0e?\xf8\x95\xd6&\xe8\v.\x11?\xf8:\x8c0\xe2\x8ey\x15?\xf8\xdfA:\xdc\x11\xc5\x19?\xf8\x84\xf7CÖ”\x10\x1e?\xf8\x95\xd6&\xe8\v.!?\xf8:\x8c0\xe2\x8ey%?\xf8\xdfA:\xdc\x11\xc5)?\xf8\x84\xf7CÖ”\x10.?\xf8\x95\xd6&\xe8\v.1?\xf8:\x8c0\xe2\x8ey5?\xf8\xdfA:\xdc\x11\xc59?\xf8\x84\xf7CÖ”\x10>?\xf8\x95\xd6&\xe8\v.A?\xf8:\x8c0\xe2\x8eyE?\xf8\xdfA:\xdc\x11\xc5I?\xf8\x84\xf7CÖ”\x10N?\xf8\x95\xd6&\xe8\v.Q?\xf8:\x8c0\xe2\x8eyU?\xf8\xdfA:\xdc\x11\xc5Y?\xf8\x84\xf7CÖ”\x10^?\xf8\x95\xd6&\xe8\v.a?\xf8:\x8c0\xe2\x8eye?\xf8\xdfA:\xdc\x11\xc5i?\xf8\x84\xf7CÖ”\x10n?\xf8\x95\xd6&\xe8\v.q?\xf8:\x8c0\xe2\x8eyu?\xf8\xdfA:\xdc\x11\xc5y?\xf8\x84\xf7CÖ”\x10~?\xf8\x95\xd6&\xe8\v.\x81?\xf8:\x8c0\xe2\x8ey\x85?\xf8\xdfA:\xdc\x11ʼn?\xf8\x84\xf7CÖ”\x10\x8e?\xf8\x95\xd6&\xe8\v.\x91?\xf8:\x8c0\xe2\x8ey\x95?\xf8\xdfA:\xdc\x11Å™?\xf8\x84\xf7CÖ”\x10\x9e?\xf8\x95\xd6&\xe8\v.\xa1?\xf8:\x8c0\xe2\x8ey\xa5?\xf8\xdfA:\xdc\x11Å©?\xf8\x84\xf7CÖ”\x10\xae?\xf8\x95\xd6&\xe8\v.\xb1?\xf8:\x8c0\xe2\x8ey\xb5?\xf8\xdfA:\xdc\x11Ź?\xf8\x84\xf7CÖ”\x10\xbe?\xf8\x95\xd6&\xe8\v.\xc1?\xf8:\x8c0\xe2\x8ey\xc5?\xf8\xdfA:\xdc\x11\xc5\xc9?\xf8\x84\xf7CÖ”\x10\xce?\xf8\x95\xd6&\xe8\v.\xd1?\xf8:\x8c0\xe2\x8ey\xd5?\xf8\xdfA:\xdc\x11\xc5\xd9?\xf8\x84\xf7CÖ”\x10\xde?\xf8\x95\xd6&\xe8\v.\xe1?\xf8:\x8c0\xe2\x8ey\xe5?\xf8\xdfA:\xdc\x11\xc5\xe9?\xf8\x84\xf7CÖ”\x10\xee?\xf8\x95\xd6&\xe8\v.\xf1?\xf8:\x8c0\xe2\x8ey\xf5?\xf8\xdfA:\xdc\x11\xc5\xf9?\xf8\x84\xf7CÖ”\x10\xfe?\xf8\x95\xd6&\xe8\v.\x01@\xf8:\x8c0\xe2\x8ey\x05@\xf8\xdfA:\xdc\x11\xc5\t@\xf8\x84\xf7CÖ”\x10\x0e@\xf8\x95\xd6&\xe8\v.\x11@\xf8:\x8c0\xe2\x8ey\x15@\xf8\xdfA:\xdc\x11\xc5\x19@\xf8\x84\xf7CÖ”\x10\x1e@\xf8\x95\xd6&\xe8\v.!@\xf8:\x8c0\xe2\x8ey%@\xf8\xdfA:\xdc\x11\xc5)@\xf8\x84\xf7CÖ”\x10.@\xf8\x95\xd6&\xe8\v.1@\xf8:\x8c0\xe2\x8ey5@\xf8\xdfA:\xdc\x11\xc59@\xf8\x84\xf7CÖ”\x10>@\xf8\x95\xd6&\xe8\v.A@\xf8:\x8c0\xe2\x8eyE@\xf8\xdfA:\xdc\x11\xc5I@\xf8\x84\xf7CÖ”\x10N@\xf8\x95\xd6&\xe8\v.Q@\xf8:\x8c0\xe2\x8eyU@\xf8\xdfA:\xdc\x11\xc5Y@\xf8\x84\xf7CÖ”\x10^@\xf8\x95\xd6&\xe8\v.a@\xf8:\x8c0\xe2\x8eye@\xf8\xdfA:\xdc\x11\xc5i@\xf8\x84\xf7CÖ”\x10n@\xf8\x95\xd6&\xe8\v.q@\xf8:\x8c0\xe2\x8eyu@\xf8\xdfA:\xdc\x11\xc5y@\xf8\x84\xf7CÖ”\x10~@\xf8\x95\xd6&\xe8\v.\x81@\xf8:\x8c0\xe2\x8ey\x85@\xf8\xdfA:\xdc\x11ʼn@\xf8\x84\xf7CÖ”\x10\x8e@\xf8\x95\xd6&\xe8\v.\x91@\xf8:\x8c0\xe2\x8ey\x95@\xf8\xdfA:\xdc\x11Å™@\xf8\x84\xf7CÖ”\x10\x9e@\xf8\x95\xd6&\xe8\v.\xa1@\xf8:\x8c0\xe2\x8ey\xa5@\xf8\xdfA:\xdc\x11Å©@\xf8\x84\xf7CÖ”\x10\xae@\xf8\x95\xd6&\xe8\v.\xb1@\xf8:\x8c0\xe2\x8ey\xb5@\xf8\xdfA:\xdc\x11Ź@\xf8\x84\xf7CÖ”\x10\xbe@\xf8\x95\xd6&\xe8\v.\xc1@\xf8:\x8c0\xe2\x8ey\xc5@\xf8\xdfA:\xdc\x11\xc5\xc9@\xf8\x84\xf7CÖ”\x10\xce@\xf8\x95\xd6&\xe8\v.\xd1@\xf8:\x8c0\xe2\x8ey\xd5@\xf8\xdfA:\xdc\x11\xc5\xd9@\xf8\x84\xf7CÖ”\x10\xde@\xf8\x95\xd6&\xe8\v.\xe1@\xf8:\x8c0\xe2\x8ey\xe5@\xf8\xdfA:\xdc\x11\xc5\xe9@\xf8\x84\xf7CÖ”\x10\xee@\xf8\x95\xd6&\xe8\v.\xf1@\xf8:\x8c0\xe2\x8ey\xf5@\xf8\xdfA:\xdc\x11\xc5\xf9@\xf8\x84\xf7CÖ”\x10\xfe@\xf8\x95\xd6&\xe8\v.\x01A\xfe\xf0\x7f\x00" + + dserialize := func(data string) *metrics.Float64Histogram { + var res metrics.Float64Histogram + if err := gob.NewDecoder(bytes.NewReader([]byte(data))).Decode(&res); err != nil { + panic(err) + } + return &res + } + latency := RuntimeHistogramFromData(float64(time.Second), dserialize(sLatency)) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + snap := latency.Snapshot() + // These are the fields that influxdb accesses + _ = snap.Count() + _ = snap.Max() + _ = snap.Mean() + _ = snap.Min() + _ = snap.StdDev() + _ = snap.Variance() + _ = snap.Percentiles([]float64{0.25, 0.5, 0.75, 0.95, 0.99, 0.999, 0.9999}) + } +} diff --git a/metrics/sample.go b/metrics/sample.go index 838a92d0d5e3..fd1e537c19bc 100644 --- a/metrics/sample.go +++ b/metrics/sample.go @@ -3,17 +3,15 @@ package metrics import ( "math" "math/rand" - "sort" "sync" "time" + + "golang.org/x/exp/slices" ) const rescaleThreshold = time.Hour -// Samples maintain a statistically-significant selection of values from -// a stream. -type Sample interface { - Clear() +type SampleSnapshot interface { Count() int64 Max() int64 Mean() float64 @@ -21,14 +19,19 @@ type Sample interface { Percentile(float64) float64 Percentiles([]float64) []float64 Size() int - Snapshot() Sample StdDev() float64 Sum() int64 - Update(int64) - Values() []int64 Variance() float64 } +// Samples maintain a statistically-significant selection of values from +// a stream. +type Sample interface { + Snapshot() SampleSnapshot + Clear() + Update(int64) +} + func NewBoundedHistogramSample() Sample { return NewSlidingTimeWindowArraySample(time.Minute * 1) } @@ -80,72 +83,29 @@ func (s *ExpDecaySample) Clear() { s.values.Clear() } -// Count returns the number of samples recorded, which may exceed the -// reservoir size. -func (s *ExpDecaySample) Count() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return s.count -} - -// Max returns the maximum value in the sample, which may not be the maximum -// value ever to be part of the sample. -func (s *ExpDecaySample) Max() int64 { - return SampleMax(s.Values()) -} - -// Mean returns the mean of the values in the sample. -func (s *ExpDecaySample) Mean() float64 { - return SampleMean(s.Values()) -} - -// Min returns the minimum value in the sample, which may not be the minimum -// value ever to be part of the sample. -func (s *ExpDecaySample) Min() int64 { - return SampleMin(s.Values()) -} - -// Percentile returns an arbitrary percentile of values in the sample. -func (s *ExpDecaySample) Percentile(p float64) float64 { - return SamplePercentile(s.Values(), p) -} - -// Percentiles returns a slice of arbitrary percentiles of values in the -// sample. -func (s *ExpDecaySample) Percentiles(ps []float64) []float64 { - return SamplePercentiles(s.Values(), ps) -} - -// Size returns the size of the sample, which is at most the reservoir size. -func (s *ExpDecaySample) Size() int { - s.mutex.Lock() - defer s.mutex.Unlock() - return s.values.Size() -} - // Snapshot returns a read-only copy of the sample. -func (s *ExpDecaySample) Snapshot() Sample { - s.mutex.Lock() - defer s.mutex.Unlock() - vals := s.values.Values() - values := make([]int64, len(vals)) - for i, v := range vals { - values[i] = v.v - } - return &SampleSnapshot{ - count: s.count, - values: values, +func (s *ExpDecaySample) Snapshot() SampleSnapshot { + s.mutex.Lock() + defer s.mutex.Unlock() + var ( + samples = s.values.Values() + values = make([]int64, len(samples)) + max int64 = math.MinInt64 + min int64 = math.MaxInt64 + sum int64 + ) + for i, item := range samples { + v := item.v + values[i] = v + sum += v + if v > max { + max = v + } + if v < min { + min = v + } } -} - -// StdDev returns the standard deviation of the values in the sample. -func (s *ExpDecaySample) StdDev() float64 { - return SampleStdDev(s.Values()) -} - -// Sum returns the sum of the values in the sample. -func (s *ExpDecaySample) Sum() int64 { - return SampleSum(s.Values()) + return newSampleSnapshotPrecalculated(s.count, values, min, max, sum) } // Update samples a new value. @@ -153,23 +113,6 @@ func (s *ExpDecaySample) Update(v int64) { s.update(time.Now(), v) } -// Values returns a copy of the values in the sample. -func (s *ExpDecaySample) Values() []int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - vals := s.values.Values() - values := make([]int64, len(vals)) - for i, v := range vals { - values[i] = v.v - } - return values -} - -// Variance returns the variance of the values in the sample. -func (s *ExpDecaySample) Variance() float64 { - return SampleVariance(s.Values()) -} - // update samples a new value at a particular timestamp. This is a method all // its own to facilitate testing. func (s *ExpDecaySample) update(t time.Time, v int64) { @@ -205,207 +148,160 @@ func (s *ExpDecaySample) update(t time.Time, v int64) { // NilSample is a no-op Sample. type NilSample struct{} -// Clear is a no-op. -func (NilSample) Clear() {} - -// Count is a no-op. -func (NilSample) Count() int64 { return 0 } - -// Max is a no-op. -func (NilSample) Max() int64 { return 0 } - -// Mean is a no-op. -func (NilSample) Mean() float64 { return 0.0 } - -// Min is a no-op. -func (NilSample) Min() int64 { return 0 } - -// Percentile is a no-op. -func (NilSample) Percentile(p float64) float64 { return 0.0 } - -// Percentiles is a no-op. -func (NilSample) Percentiles(ps []float64) []float64 { - return make([]float64, len(ps)) -} - -// Size is a no-op. -func (NilSample) Size() int { return 0 } - -// Sample is a no-op. -func (NilSample) Snapshot() Sample { return NilSample{} } - -// StdDev is a no-op. -func (NilSample) StdDev() float64 { return 0.0 } - -// Sum is a no-op. -func (NilSample) Sum() int64 { return 0 } - -// Update is a no-op. -func (NilSample) Update(v int64) {} - -// Values is a no-op. -func (NilSample) Values() []int64 { return []int64{} } - -// Variance is a no-op. -func (NilSample) Variance() float64 { return 0.0 } - -// SampleMax returns the maximum value of the slice of int64. -func SampleMax(values []int64) int64 { - if len(values) == 0 { - return 0 - } - var max int64 = math.MinInt64 - for _, v := range values { - if max < v { - max = v - } - } - return max -} - -// SampleMean returns the mean value of the slice of int64. -func SampleMean(values []int64) float64 { - if len(values) == 0 { - return 0.0 - } - return float64(SampleSum(values)) / float64(len(values)) -} - -// SampleMin returns the minimum value of the slice of int64. -func SampleMin(values []int64) int64 { - if len(values) == 0 { - return 0 - } - var min int64 = math.MaxInt64 - for _, v := range values { - if min > v { - min = v - } - } - return min -} +func (NilSample) Clear() {} +func (NilSample) Snapshot() SampleSnapshot { return (*emptySnapshot)(nil) } +func (NilSample) Update(v int64) {} // SamplePercentiles returns an arbitrary percentile of the slice of int64. -func SamplePercentile(values int64Slice, p float64) float64 { - return SamplePercentiles(values, []float64{p})[0] +func SamplePercentile(values []int64, p float64) float64 { + return CalculatePercentiles(values, []float64{p})[0] } -// SamplePercentiles returns a slice of arbitrary percentiles of the slice of -// int64. -func SamplePercentiles(values int64Slice, ps []float64) []float64 { +// CalculatePercentiles returns a slice of arbitrary percentiles of the slice of +// int64. This method returns interpolated results, so e.g if there are only two +// values, [0, 10], a 50% percentile will land between them. +// +// Note: As a side-effect, this method will also sort the slice of values. +// Note2: The input format for percentiles is NOT percent! To express 50%, use 0.5, not 50. +func CalculatePercentiles(values []int64, ps []float64) []float64 { scores := make([]float64, len(ps)) size := len(values) - if size > 0 { - sort.Sort(values) - for i, p := range ps { - pos := p * float64(size+1) - if pos < 1.0 { - scores[i] = float64(values[0]) - } else if pos >= float64(size) { - scores[i] = float64(values[size-1]) - } else { - lower := float64(values[int(pos)-1]) - upper := float64(values[int(pos)]) - scores[i] = lower + (pos-math.Floor(pos))*(upper-lower) - } + if size == 0 { + return scores + } + slices.Sort(values) + for i, p := range ps { + pos := p * float64(size+1) + + if pos < 1.0 { + scores[i] = float64(values[0]) + } else if pos >= float64(size) { + scores[i] = float64(values[size-1]) + } else { + lower := float64(values[int(pos)-1]) + upper := float64(values[int(pos)]) + scores[i] = lower + (pos-math.Floor(pos))*(upper-lower) } } return scores } -// SampleSnapshot is a read-only copy of another Sample. -type SampleSnapshot struct { +// sampleSnapshot is a read-only copy of another Sample. +type sampleSnapshot struct { count int64 values []int64 + + max int64 + min int64 + mean float64 + sum int64 + variance float64 } -func NewSampleSnapshot(count int64, values []int64) *SampleSnapshot { - return &SampleSnapshot{ +// newSampleSnapshotPrecalculated creates a read-only sampleSnapShot, using +// precalculated sums to avoid iterating the values +func newSampleSnapshotPrecalculated(count int64, values []int64, min, max, sum int64) *sampleSnapshot { + if len(values) == 0 { + return &sampleSnapshot{ + count: count, + values: values, + } + } + return &sampleSnapshot{ count: count, values: values, + max: max, + min: min, + mean: float64(sum) / float64(len(values)), + sum: sum, } } -// Clear panics. -func (*SampleSnapshot) Clear() { - panic("Clear called on a SampleSnapshot") +// newSampleSnapshot creates a read-only sampleSnapShot, and calculates some +// numbers. +func newSampleSnapshot(count int64, values []int64) *sampleSnapshot { + var ( + max int64 = math.MinInt64 + min int64 = math.MaxInt64 + sum int64 + ) + for _, v := range values { + sum += v + if v > max { + max = v + } + if v < min { + min = v + } + } + return newSampleSnapshotPrecalculated(count, values, min, max, sum) } // Count returns the count of inputs at the time the snapshot was taken. -func (s *SampleSnapshot) Count() int64 { return s.count } +func (s *sampleSnapshot) Count() int64 { return s.count } // Max returns the maximal value at the time the snapshot was taken. -func (s *SampleSnapshot) Max() int64 { return SampleMax(s.values) } +func (s *sampleSnapshot) Max() int64 { return s.max } // Mean returns the mean value at the time the snapshot was taken. -func (s *SampleSnapshot) Mean() float64 { return SampleMean(s.values) } +func (s *sampleSnapshot) Mean() float64 { return s.mean } // Min returns the minimal value at the time the snapshot was taken. -func (s *SampleSnapshot) Min() int64 { return SampleMin(s.values) } +func (s *sampleSnapshot) Min() int64 { return s.min } // Percentile returns an arbitrary percentile of values at the time the // snapshot was taken. -func (s *SampleSnapshot) Percentile(p float64) float64 { +func (s *sampleSnapshot) Percentile(p float64) float64 { return SamplePercentile(s.values, p) } // Percentiles returns a slice of arbitrary percentiles of values at the time // the snapshot was taken. -func (s *SampleSnapshot) Percentiles(ps []float64) []float64 { - return SamplePercentiles(s.values, ps) +func (s *sampleSnapshot) Percentiles(ps []float64) []float64 { + return CalculatePercentiles(s.values, ps) } // Size returns the size of the sample at the time the snapshot was taken. -func (s *SampleSnapshot) Size() int { return len(s.values) } +func (s *sampleSnapshot) Size() int { return len(s.values) } // Snapshot returns the snapshot. -func (s *SampleSnapshot) Snapshot() Sample { return s } +func (s *sampleSnapshot) Snapshot() SampleSnapshot { return s } // StdDev returns the standard deviation of values at the time the snapshot was // taken. -func (s *SampleSnapshot) StdDev() float64 { return SampleStdDev(s.values) } +func (s *sampleSnapshot) StdDev() float64 { + if s.variance == 0.0 { + s.variance = SampleVariance(s.mean, s.values) + } + return math.Sqrt(s.variance) +} // Sum returns the sum of values at the time the snapshot was taken. -func (s *SampleSnapshot) Sum() int64 { return SampleSum(s.values) } - -// Update panics. -func (*SampleSnapshot) Update(int64) { - panic("Update called on a SampleSnapshot") -} +func (s *sampleSnapshot) Sum() int64 { return s.sum } // Values returns a copy of the values in the sample. -func (s *SampleSnapshot) Values() []int64 { +func (s *sampleSnapshot) Values() []int64 { values := make([]int64, len(s.values)) copy(values, s.values) return values } // Variance returns the variance of values at the time the snapshot was taken. -func (s *SampleSnapshot) Variance() float64 { return SampleVariance(s.values) } - -// SampleStdDev returns the standard deviation of the slice of int64. -func SampleStdDev(values []int64) float64 { - return math.Sqrt(SampleVariance(values)) -} - -// SampleSum returns the sum of the slice of int64. -func SampleSum(values []int64) int64 { - var sum int64 - for _, v := range values { - sum += v +func (s *sampleSnapshot) Variance() float64 { + if s.variance == 0.0 { + s.variance = SampleVariance(s.mean, s.values) } - return sum + return s.variance } // SampleVariance returns the variance of the slice of int64. -func SampleVariance(values []int64) float64 { +func SampleVariance(mean float64, values []int64) float64 { if len(values) == 0 { return 0.0 } - m := SampleMean(values) var sum float64 for _, v := range values { - d := float64(v) - m + d := float64(v) - mean sum += d * d } return sum / float64(len(values)) @@ -448,83 +344,14 @@ func (s *UniformSample) Clear() { s.values = make([]int64, 0, s.reservoirSize) } -// Count returns the number of samples recorded, which may exceed the -// reservoir size. -func (s *UniformSample) Count() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return s.count -} - -// Max returns the maximum value in the sample, which may not be the maximum -// value ever to be part of the sample. -func (s *UniformSample) Max() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SampleMax(s.values) -} - -// Mean returns the mean of the values in the sample. -func (s *UniformSample) Mean() float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SampleMean(s.values) -} - -// Min returns the minimum value in the sample, which may not be the minimum -// value ever to be part of the sample. -func (s *UniformSample) Min() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SampleMin(s.values) -} - -// Percentile returns an arbitrary percentile of values in the sample. -func (s *UniformSample) Percentile(p float64) float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SamplePercentile(s.values, p) -} - -// Percentiles returns a slice of arbitrary percentiles of values in the -// sample. -func (s *UniformSample) Percentiles(ps []float64) []float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SamplePercentiles(s.values, ps) -} - -// Size returns the size of the sample, which is at most the reservoir size. -func (s *UniformSample) Size() int { - s.mutex.Lock() - defer s.mutex.Unlock() - return len(s.values) -} - // Snapshot returns a read-only copy of the sample. -func (s *UniformSample) Snapshot() Sample { +func (s *UniformSample) Snapshot() SampleSnapshot { s.mutex.Lock() - defer s.mutex.Unlock() values := make([]int64, len(s.values)) copy(values, s.values) - return &SampleSnapshot{ - count: s.count, - values: values, - } -} - -// StdDev returns the standard deviation of the values in the sample. -func (s *UniformSample) StdDev() float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SampleStdDev(s.values) -} - -// Sum returns the sum of the values in the sample. -func (s *UniformSample) Sum() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SampleSum(s.values) + count := s.count + s.mutex.Unlock() + return newSampleSnapshot(count, values) } // Update samples a new value. @@ -547,22 +374,6 @@ func (s *UniformSample) Update(v int64) { } } -// Values returns a copy of the values in the sample. -func (s *UniformSample) Values() []int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - values := make([]int64, len(s.values)) - copy(values, s.values) - return values -} - -// Variance returns the variance of the values in the sample. -func (s *UniformSample) Variance() float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return SampleVariance(s.values) -} - // expDecaySample represents an individual sample in a heap. type expDecaySample struct { k float64 @@ -637,208 +448,3 @@ func (h *expDecaySampleHeap) down(i, n int) { i = j } } - -// SlidingTimeWindowArraySample is ported from Coda Hale's dropwizard library -// -// A reservoir implementation backed by a sliding window that stores only the -// measurements made in the last given window of time -type SlidingTimeWindowArraySample struct { - startTick int64 - measurements *ChunkedAssociativeArray - window int64 - count int64 - lastTick int64 - mutex sync.Mutex -} - -const ( - // SlidingTimeWindowCollisionBuffer allow this many duplicate ticks - // before overwriting measurements - SlidingTimeWindowCollisionBuffer = 256 - - // SlidingTimeWindowTrimThreshold is number of updates between trimming data - SlidingTimeWindowTrimThreshold = 256 - - // SlidingTimeWindowClearBufferTicks is the number of ticks to keep past the - // requested trim - SlidingTimeWindowClearBufferTicks = int64(time.Hour/time.Nanosecond) * - SlidingTimeWindowCollisionBuffer -) - -// NewSlidingTimeWindowArraySample creates new object with given window of time -func NewSlidingTimeWindowArraySample(window time.Duration) Sample { - if !Enabled { - return NilSample{} - } - return &SlidingTimeWindowArraySample{ - startTick: time.Now().UnixNano(), - measurements: NewChunkedAssociativeArray(ChunkedAssociativeArrayDefaultChunkSize), - window: window.Nanoseconds() * SlidingTimeWindowCollisionBuffer, - } -} - -// Clear clears all samples. -func (s *SlidingTimeWindowArraySample) Clear() { - s.mutex.Lock() - defer s.mutex.Unlock() - s.count = 0 - s.measurements.Clear() -} - -// Count returns the number of samples recorded, which may exceed the -// reservoir size. -func (s *SlidingTimeWindowArraySample) Count() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - return s.count -} - -// Max returns the maximum value in the sample, which may not be the maximum -// value ever to be part of the sample. -func (s *SlidingTimeWindowArraySample) Max() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - s.trim() - return SampleMax(s.measurements.Values()) -} - -// Mean returns the mean of the values in the sample. -func (s *SlidingTimeWindowArraySample) Mean() float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - s.trim() - return SampleMean(s.measurements.Values()) -} - -// Min returns the minimum value in the sample, which may not be the minimum -// value ever to be part of the sample. -func (s *SlidingTimeWindowArraySample) Min() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - s.trim() - return SampleMin(s.measurements.Values()) -} - -// Percentile returns an arbitrary percentile of values in the sample. -func (s *SlidingTimeWindowArraySample) Percentile(p float64) float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - s.trim() - return SamplePercentile(s.measurements.Values(), p) -} - -// Percentiles returns a slice of arbitrary percentiles of values in the -// sample. -func (s *SlidingTimeWindowArraySample) Percentiles(ps []float64) []float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - s.trim() - return SamplePercentiles(s.measurements.Values(), ps) -} - -// Size returns the size of the sample, which is at most the reservoir size. -func (s *SlidingTimeWindowArraySample) Size() int { - s.mutex.Lock() - defer s.mutex.Unlock() - s.trim() - return s.measurements.Size() -} - -// trim requires s.mutex to already be acquired -func (s *SlidingTimeWindowArraySample) trim() { - now := s.getTick() - windowStart := now - s.window - windowEnd := now + SlidingTimeWindowClearBufferTicks - if windowStart < windowEnd { - s.measurements.Trim(windowStart, windowEnd) - } else { - // long overflow handling that can only happen 1 year after class loading - s.measurements.Clear() - } -} - -// getTick requires s.mutex to already be acquired -func (s *SlidingTimeWindowArraySample) getTick() int64 { - oldTick := s.lastTick - tick := (time.Now().UnixNano() - s.startTick) * SlidingTimeWindowCollisionBuffer - var newTick int64 - if tick-oldTick > 0 { - newTick = tick - } else { - newTick = oldTick + 1 - } - s.lastTick = newTick - return newTick -} - -// Snapshot returns a read-only copy of the sample. -func (s *SlidingTimeWindowArraySample) Snapshot() Sample { - s.mutex.Lock() - defer s.mutex.Unlock() - s.trim() - origValues := s.measurements.Values() - values := make([]int64, len(origValues)) - copy(values, origValues) - return &SampleSnapshot{ - count: s.count, - values: values, - } -} - -// StdDev returns the standard deviation of the values in the sample. -func (s *SlidingTimeWindowArraySample) StdDev() float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - s.trim() - return SampleStdDev(s.measurements.Values()) -} - -// Sum returns the sum of the values in the sample. -func (s *SlidingTimeWindowArraySample) Sum() int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - s.trim() - return SampleSum(s.measurements.Values()) -} - -// Update samples a new value. -func (s *SlidingTimeWindowArraySample) Update(v int64) { - s.mutex.Lock() - defer s.mutex.Unlock() - var newTick int64 - s.count += 1 - if s.count%SlidingTimeWindowTrimThreshold == 0 { - s.trim() - } - newTick = s.getTick() - longOverflow := newTick < s.lastTick - if longOverflow { - s.measurements.Clear() - } - s.measurements.Put(newTick, v) -} - -// Values returns a copy of the values in the sample. -func (s *SlidingTimeWindowArraySample) Values() []int64 { - s.mutex.Lock() - defer s.mutex.Unlock() - s.trim() - origValues := s.measurements.Values() - values := make([]int64, len(origValues)) - copy(values, origValues) - return values -} - -// Variance returns the variance of the values in the sample. -func (s *SlidingTimeWindowArraySample) Variance() float64 { - s.mutex.Lock() - defer s.mutex.Unlock() - s.trim() - return SampleVariance(s.measurements.Values()) -} - -type int64Slice []int64 - -func (p int64Slice) Len() int { return len(p) } -func (p int64Slice) Less(i, j int) bool { return p[i] < p[j] } -func (p int64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } diff --git a/metrics/sample_test.go b/metrics/sample_test.go index 56706ee285ba..38bf50f51f13 100644 --- a/metrics/sample_test.go +++ b/metrics/sample_test.go @@ -8,28 +8,36 @@ import ( "time" ) +const epsilonPercentile = .00000000001 + // Benchmark{Compute,Copy}{1000,1000000} demonstrate that, even for relatively // expensive computations like Variance, the cost of copying the Sample, as // approximated by a make and copy, is much greater than the cost of the // computation for small samples and only slightly less for large samples. func BenchmarkCompute1000(b *testing.B) { s := make([]int64, 1000) + var sum int64 for i := 0; i < len(s); i++ { s[i] = int64(i) + sum += int64(i) } + mean := float64(sum) / float64(len(s)) b.ResetTimer() for i := 0; i < b.N; i++ { - SampleVariance(s) + SampleVariance(mean, s) } } func BenchmarkCompute1000000(b *testing.B) { s := make([]int64, 1000000) + var sum int64 for i := 0; i < len(s); i++ { s[i] = int64(i) + sum += int64(i) } + mean := float64(sum) / float64(len(s)) b.ResetTimer() for i := 0; i < b.N; i++ { - SampleVariance(s) + SampleVariance(mean, s) } } func BenchmarkCopy1000(b *testing.B) { @@ -91,65 +99,42 @@ func BenchmarkSlidingWindowArraySample1028(b *testing.B) { benchmarkSample(b, NewSlidingTimeWindowArraySample(1028)) } -func TestExpDecaySample10(t *testing.T) { - s := NewExpDecaySample(100, 0.99) - for i := 0; i < 10; i++ { - s.Update(int64(i)) - } - if size := s.Count(); size != 10 { - t.Errorf("s.Count(): 10 != %v\n", size) - } - if size := s.Size(); size != 10 { - t.Errorf("s.Size(): 10 != %v\n", size) - } - if l := len(s.Values()); l != 10 { - t.Errorf("len(s.Values()): 10 != %v\n", l) - } - for _, v := range s.Values() { - if v > 10 || v < 0 { - t.Errorf("out of range [0, 10): %v\n", v) - } +func min(a, b int) int { + if a < b { + return a } + return b } -func TestExpDecaySample100(t *testing.T) { - s := NewExpDecaySample(1000, 0.01) - for i := 0; i < 100; i++ { - s.Update(int64(i)) - } - if size := s.Count(); size != 100 { - t.Errorf("s.Count(): 100 != %v\n", size) - } - if size := s.Size(); size != 100 { - t.Errorf("s.Size(): 100 != %v\n", size) - } - if l := len(s.Values()); l != 100 { - t.Errorf("len(s.Values()): 100 != %v\n", l) - } - for _, v := range s.Values() { - if v > 100 || v < 0 { - t.Errorf("out of range [0, 100): %v\n", v) +func TestExpDecaySample(t *testing.T) { + for _, tc := range []struct { + reservoirSize int + alpha float64 + updates int + }{ + {100, 0.99, 10}, + {1000, 0.01, 100}, + {100, 0.99, 1000}, + } { + sample := NewExpDecaySample(tc.reservoirSize, tc.alpha) + for i := 0; i < tc.updates; i++ { + sample.Update(int64(i)) } - } -} - -func TestExpDecaySample1000(t *testing.T) { - s := NewExpDecaySample(100, 0.99) - for i := 0; i < 1000; i++ { - s.Update(int64(i)) - } - if size := s.Count(); size != 1000 { - t.Errorf("s.Count(): 1000 != %v\n", size) - } - if size := s.Size(); size != 100 { - t.Errorf("s.Size(): 100 != %v\n", size) - } - if l := len(s.Values()); l != 100 { - t.Errorf("len(s.Values()): 100 != %v\n", l) - } - for _, v := range s.Values() { - if v > 1000 || v < 0 { - t.Errorf("out of range [0, 1000): %v\n", v) + snap := sample.Snapshot() + if have, want := int(snap.Count()), tc.updates; have != want { + t.Errorf("have %d want %d", have, want) + } + if have, want := snap.Size(), min(tc.updates, tc.reservoirSize); have != want { + t.Errorf("have %d want %d", have, want) + } + values := snap.(*sampleSnapshot).values + if have, want := len(values), min(tc.updates, tc.reservoirSize); have != want { + t.Errorf("have %d want %d", have, want) + } + for _, v := range values { + if v > int64(tc.updates) || v < 0 { + t.Errorf("out of range [0, %d): %v", tc.updates, v) + } } } } @@ -159,15 +144,16 @@ func TestExpDecaySample1000(t *testing.T) { // The priority becomes +Inf quickly after starting if this is done, // effectively freezing the set of samples until a rescale step happens. func TestExpDecaySampleNanosecondRegression(t *testing.T) { - s := NewExpDecaySample(100, 0.99) + sw := NewExpDecaySample(100, 0.99) for i := 0; i < 100; i++ { - s.Update(10) + sw.Update(10) } time.Sleep(1 * time.Millisecond) for i := 0; i < 100; i++ { - s.Update(20) + sw.Update(20) } - v := s.Values() + s := sw.Snapshot() + v := s.(*sampleSnapshot).values avg := float64(0) for i := 0; i < len(v); i++ { avg += float64(v[i]) @@ -206,24 +192,27 @@ func TestExpDecaySampleStatistics(t *testing.T) { for i := 1; i <= 10000; i++ { s.(*ExpDecaySample).update(now.Add(time.Duration(i)), int64(i)) } - testExpDecaySampleStatistics(t, s) + testExpDecaySampleStatistics(t, s.Snapshot()) } func TestUniformSample(t *testing.T) { - s := NewUniformSample(100) + sw := NewUniformSample(100) for i := 0; i < 1000; i++ { - s.Update(int64(i)) + sw.Update(int64(i)) } + s := sw.Snapshot() if size := s.Count(); size != 1000 { t.Errorf("s.Count(): 1000 != %v\n", size) } if size := s.Size(); size != 100 { t.Errorf("s.Size(): 100 != %v\n", size) } - if l := len(s.Values()); l != 100 { + values := s.(*sampleSnapshot).values + + if l := len(values); l != 100 { t.Errorf("len(s.Values()): 100 != %v\n", l) } - for _, v := range s.Values() { + for _, v := range values { if v > 1000 || v < 0 { t.Errorf("out of range [0, 100): %v\n", v) } @@ -231,12 +220,13 @@ func TestUniformSample(t *testing.T) { } func TestUniformSampleIncludesTail(t *testing.T) { - s := NewUniformSample(100) + sw := NewUniformSample(100) max := 100 for i := 0; i < max; i++ { - s.Update(int64(i)) + sw.Update(int64(i)) } - v := s.Values() + s := sw.Snapshot() + v := s.(*sampleSnapshot).values sum := 0 exp := (max - 1) * max / 2 for i := 0; i < len(v); i++ { @@ -262,7 +252,7 @@ func TestUniformSampleStatistics(t *testing.T) { for i := 1; i <= 10000; i++ { s.Update(int64(i)) } - testUniformSampleStatistics(t, s) + testUniformSampleStatistics(t, s.Snapshot()) } func benchmarkSample(b *testing.B, s Sample) { @@ -279,7 +269,7 @@ func benchmarkSample(b *testing.B, s Sample) { b.Logf("GC cost: %d ns/op", int(memStats.PauseTotalNs-pauseTotalNs)/b.N) } -func testExpDecaySampleStatistics(t *testing.T, s Sample) { +func testExpDecaySampleStatistics(t *testing.T, s SampleSnapshot) { if count := s.Count(); count != 10000 { t.Errorf("s.Count(): 10000 != %v\n", count) } @@ -307,7 +297,7 @@ func testExpDecaySampleStatistics(t *testing.T, s Sample) { } } -func testUniformSampleStatistics(t *testing.T, s Sample) { +func testUniformSampleStatistics(t *testing.T, s SampleSnapshot) { if count := s.Count(); count != 10000 { t.Errorf("s.Count(): 10000 != %v\n", count) } @@ -361,8 +351,22 @@ func TestUniformSampleConcurrentUpdateCount(t *testing.T) { } }() for i := 0; i < 1000; i++ { - s.Count() + s.Snapshot().Count() time.Sleep(5 * time.Millisecond) } quit <- struct{}{} } + +func BenchmarkCalculatePercentiles(b *testing.B) { + pss := []float64{0.5, 0.75, 0.95, 0.99, 0.999, 0.9999} + var vals []int64 + for i := 0; i < 1000; i++ { + vals = append(vals, int64(rand.Int31())) + } + v := make([]int64, len(vals)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + copy(v, vals) + _ = CalculatePercentiles(v, pss) + } +} diff --git a/metrics/sliding_time_window_array_sample.go b/metrics/sliding_time_window_array_sample.go new file mode 100644 index 000000000000..f50bf5206c03 --- /dev/null +++ b/metrics/sliding_time_window_array_sample.go @@ -0,0 +1,123 @@ +package metrics + +import ( + "math" + "sync" + "time" +) + +// SlidingTimeWindowArraySample is ported from Coda Hale's dropwizard library +// +// A reservoir implementation backed by a sliding window that stores only the +// measurements made in the last given window of time +type SlidingTimeWindowArraySample struct { + startTick int64 + measurements *ChunkedAssociativeArray + window int64 + count int64 + lastTick int64 + mutex sync.Mutex +} + +const ( + // SlidingTimeWindowCollisionBuffer allow this many duplicate ticks + // before overwriting measurements + SlidingTimeWindowCollisionBuffer = 256 + + // SlidingTimeWindowTrimThreshold is number of updates between trimming data + SlidingTimeWindowTrimThreshold = 256 + + // SlidingTimeWindowClearBufferTicks is the number of ticks to keep past the + // requested trim + SlidingTimeWindowClearBufferTicks = int64(time.Hour/time.Nanosecond) * + SlidingTimeWindowCollisionBuffer +) + +// NewSlidingTimeWindowArraySample creates new object with given window of time +func NewSlidingTimeWindowArraySample(window time.Duration) Sample { + if !Enabled { + return NilSample{} + } + return &SlidingTimeWindowArraySample{ + startTick: time.Now().UnixNano(), + measurements: NewChunkedAssociativeArray(ChunkedAssociativeArrayDefaultChunkSize), + window: window.Nanoseconds() * SlidingTimeWindowCollisionBuffer, + } +} + +// Clear clears all samples. +func (s *SlidingTimeWindowArraySample) Clear() { + s.mutex.Lock() + defer s.mutex.Unlock() + s.count = 0 + s.measurements.Clear() +} + +// trim requires s.mutex to already be acquired +func (s *SlidingTimeWindowArraySample) trim() { + now := s.getTick() + windowStart := now - s.window + windowEnd := now + SlidingTimeWindowClearBufferTicks + if windowStart < windowEnd { + s.measurements.Trim(windowStart, windowEnd) + } else { + // long overflow handling that can only happen 1 year after class loading + s.measurements.Clear() + } +} + +// getTick requires s.mutex to already be acquired +func (s *SlidingTimeWindowArraySample) getTick() int64 { + oldTick := s.lastTick + tick := (time.Now().UnixNano() - s.startTick) * SlidingTimeWindowCollisionBuffer + var newTick int64 + if tick-oldTick > 0 { + newTick = tick + } else { + newTick = oldTick + 1 + } + s.lastTick = newTick + return newTick +} + +// Snapshot returns a read-only copy of the sample. +func (s *SlidingTimeWindowArraySample) Snapshot() SampleSnapshot { + s.mutex.Lock() + defer s.mutex.Unlock() + s.trim() + var ( + samples = s.measurements.Values() + values = make([]int64, len(samples)) + max int64 = math.MinInt64 + min int64 = math.MaxInt64 + sum int64 + ) + for i, v := range samples { + values[i] = v + sum += v + if v > max { + max = v + } + if v < min { + min = v + } + } + return newSampleSnapshotPrecalculated(s.count, values, min, max, sum) +} + +// Update samples a new value. +func (s *SlidingTimeWindowArraySample) Update(v int64) { + s.mutex.Lock() + defer s.mutex.Unlock() + var newTick int64 + s.count += 1 + if s.count%SlidingTimeWindowTrimThreshold == 0 { + s.trim() + } + newTick = s.getTick() + longOverflow := newTick < s.lastTick + if longOverflow { + s.measurements.Clear() + } + s.measurements.Put(newTick, v) +} diff --git a/metrics/syslog.go b/metrics/syslog.go index f23b07e199f3..fd856d697316 100644 --- a/metrics/syslog.go +++ b/metrics/syslog.go @@ -16,13 +16,15 @@ func Syslog(r Registry, d time.Duration, w *syslog.Writer) { r.Each(func(name string, i interface{}) { switch metric := i.(type) { case Counter: - w.Info(fmt.Sprintf("counter %s: count: %d", name, metric.Count())) + w.Info(fmt.Sprintf("counter %s: count: %d", name, metric.Snapshot().Count())) case CounterFloat64: - w.Info(fmt.Sprintf("counter %s: count: %f", name, metric.Count())) + w.Info(fmt.Sprintf("counter %s: count: %f", name, metric.Snapshot().Count())) case Gauge: - w.Info(fmt.Sprintf("gauge %s: value: %d", name, metric.Value())) + w.Info(fmt.Sprintf("gauge %s: value: %d", name, metric.Snapshot().Value())) case GaugeFloat64: - w.Info(fmt.Sprintf("gauge %s: value: %f", name, metric.Value())) + w.Info(fmt.Sprintf("gauge %s: value: %f", name, metric.Snapshot().Value())) + case GaugeInfo: + w.Info(fmt.Sprintf("gauge %s: value: %s", name, metric.Snapshot().Value())) case Healthcheck: metric.Check() w.Info(fmt.Sprintf("healthcheck %s: error: %v", name, metric.Error())) diff --git a/metrics/testdata/opentsb.want b/metrics/testdata/opentsb.want new file mode 100644 index 000000000000..43fe1b2ac27a --- /dev/null +++ b/metrics/testdata/opentsb.want @@ -0,0 +1,23 @@ +put pre.elite.count 978307200 1337 host=hal9000 +put pre.elite.one-minute 978307200 0.00 host=hal9000 +put pre.elite.five-minute 978307200 0.00 host=hal9000 +put pre.elite.fifteen-minute 978307200 0.00 host=hal9000 +put pre.elite.mean 978307200 0.00 host=hal9000 +put pre.foo.value 978307200 {"chain_id":"5"} host=hal9000 +put pre.months.count 978307200 12 host=hal9000 +put pre.pi.value 978307200 3.140000 host=hal9000 +put pre.second.count 978307200 1 host=hal9000 +put pre.second.min 978307200 1000 host=hal9000 +put pre.second.max 978307200 1000 host=hal9000 +put pre.second.mean 978307200 1000.00 host=hal9000 +put pre.second.std-dev 978307200 0.00 host=hal9000 +put pre.second.50-percentile 978307200 1000.00 host=hal9000 +put pre.second.75-percentile 978307200 1000.00 host=hal9000 +put pre.second.95-percentile 978307200 1000.00 host=hal9000 +put pre.second.99-percentile 978307200 1000.00 host=hal9000 +put pre.second.999-percentile 978307200 1000.00 host=hal9000 +put pre.second.one-minute 978307200 0.00 host=hal9000 +put pre.second.five-minute 978307200 0.00 host=hal9000 +put pre.second.fifteen-minute 978307200 0.00 host=hal9000 +put pre.second.mean-rate 978307200 0.00 host=hal9000 +put pre.tau.count 978307200 1.570000 host=hal9000 diff --git a/metrics/timer.go b/metrics/timer.go index a63c9dfb6c2d..576ad8aa3e63 100644 --- a/metrics/timer.go +++ b/metrics/timer.go @@ -5,26 +5,18 @@ import ( "time" ) +type TimerSnapshot interface { + HistogramSnapshot + MeterSnapshot +} + // Timers capture the duration and rate of events. type Timer interface { - Count() int64 - Max() int64 - Mean() float64 - Min() int64 - Percentile(float64) float64 - Percentiles([]float64) []float64 - Rate1() float64 - Rate5() float64 - Rate15() float64 - RateMean() float64 - Snapshot() Timer - StdDev() float64 + Snapshot() TimerSnapshot Stop() - Sum() int64 Time(func()) - Update(time.Duration) UpdateSince(time.Time) - Variance() float64 + Update(time.Duration) } // GetOrRegisterTimer returns an existing Timer or constructs and registers a @@ -78,61 +70,11 @@ func NewTimer() Timer { // NilTimer is a no-op Timer. type NilTimer struct{} -// Count is a no-op. -func (NilTimer) Count() int64 { return 0 } - -// Max is a no-op. -func (NilTimer) Max() int64 { return 0 } - -// Mean is a no-op. -func (NilTimer) Mean() float64 { return 0.0 } - -// Min is a no-op. -func (NilTimer) Min() int64 { return 0 } - -// Percentile is a no-op. -func (NilTimer) Percentile(p float64) float64 { return 0.0 } - -// Percentiles is a no-op. -func (NilTimer) Percentiles(ps []float64) []float64 { - return make([]float64, len(ps)) -} - -// Rate1 is a no-op. -func (NilTimer) Rate1() float64 { return 0.0 } - -// Rate5 is a no-op. -func (NilTimer) Rate5() float64 { return 0.0 } - -// Rate15 is a no-op. -func (NilTimer) Rate15() float64 { return 0.0 } - -// RateMean is a no-op. -func (NilTimer) RateMean() float64 { return 0.0 } - -// Snapshot is a no-op. -func (NilTimer) Snapshot() Timer { return NilTimer{} } - -// StdDev is a no-op. -func (NilTimer) StdDev() float64 { return 0.0 } - -// Stop is a no-op. -func (NilTimer) Stop() {} - -// Sum is a no-op. -func (NilTimer) Sum() int64 { return 0 } - -// Time is a no-op. -func (NilTimer) Time(func()) {} - -// Update is a no-op. -func (NilTimer) Update(time.Duration) {} - -// UpdateSince is a no-op. -func (NilTimer) UpdateSince(time.Time) {} - -// Variance is a no-op. -func (NilTimer) Variance() float64 { return 0.0 } +func (NilTimer) Snapshot() TimerSnapshot { return (*emptySnapshot)(nil) } +func (NilTimer) Stop() {} +func (NilTimer) Time(f func()) { f() } +func (NilTimer) Update(time.Duration) {} +func (NilTimer) UpdateSince(time.Time) {} // StandardTimer is the standard implementation of a Timer and uses a Histogram // and Meter. @@ -142,82 +84,21 @@ type StandardTimer struct { mutex sync.Mutex } -// Count returns the number of events recorded. -func (t *StandardTimer) Count() int64 { - return t.histogram.Count() -} - -// Max returns the maximum value in the sample. -func (t *StandardTimer) Max() int64 { - return t.histogram.Max() -} - -// Mean returns the mean of the values in the sample. -func (t *StandardTimer) Mean() float64 { - return t.histogram.Mean() -} - -// Min returns the minimum value in the sample. -func (t *StandardTimer) Min() int64 { - return t.histogram.Min() -} - -// Percentile returns an arbitrary percentile of the values in the sample. -func (t *StandardTimer) Percentile(p float64) float64 { - return t.histogram.Percentile(p) -} - -// Percentiles returns a slice of arbitrary percentiles of the values in the -// sample. -func (t *StandardTimer) Percentiles(ps []float64) []float64 { - return t.histogram.Percentiles(ps) -} - -// Rate1 returns the one-minute moving average rate of events per second. -func (t *StandardTimer) Rate1() float64 { - return t.meter.Rate1() -} - -// Rate5 returns the five-minute moving average rate of events per second. -func (t *StandardTimer) Rate5() float64 { - return t.meter.Rate5() -} - -// Rate15 returns the fifteen-minute moving average rate of events per second. -func (t *StandardTimer) Rate15() float64 { - return t.meter.Rate15() -} - -// RateMean returns the meter's mean rate of events per second. -func (t *StandardTimer) RateMean() float64 { - return t.meter.RateMean() -} - // Snapshot returns a read-only copy of the timer. -func (t *StandardTimer) Snapshot() Timer { +func (t *StandardTimer) Snapshot() TimerSnapshot { t.mutex.Lock() defer t.mutex.Unlock() - return &TimerSnapshot{ - histogram: t.histogram.Snapshot().(*HistogramSnapshot), - meter: t.meter.Snapshot().(*MeterSnapshot), + return &timerSnapshot{ + histogram: t.histogram.Snapshot(), + meter: t.meter.Snapshot(), } } -// StdDev returns the standard deviation of the values in the sample. -func (t *StandardTimer) StdDev() float64 { - return t.histogram.StdDev() -} - // Stop stops the meter. func (t *StandardTimer) Stop() { t.meter.Stop() } -// Sum returns the sum in the sample. -func (t *StandardTimer) Sum() int64 { - return t.histogram.Sum() -} - // Record the duration of the execution of the given function. func (t *StandardTimer) Time(f func()) { ts := time.Now() @@ -241,86 +122,63 @@ func (t *StandardTimer) UpdateSince(ts time.Time) { t.meter.Mark(1) } -// Variance returns the variance of the values in the sample. -func (t *StandardTimer) Variance() float64 { - return t.histogram.Variance() -} - -// TimerSnapshot is a read-only copy of another Timer. -type TimerSnapshot struct { - histogram *HistogramSnapshot - meter *MeterSnapshot +// timerSnapshot is a read-only copy of another Timer. +type timerSnapshot struct { + histogram HistogramSnapshot + meter MeterSnapshot } // Count returns the number of events recorded at the time the snapshot was // taken. -func (t *TimerSnapshot) Count() int64 { return t.histogram.Count() } +func (t *timerSnapshot) Count() int64 { return t.histogram.Count() } // Max returns the maximum value at the time the snapshot was taken. -func (t *TimerSnapshot) Max() int64 { return t.histogram.Max() } +func (t *timerSnapshot) Max() int64 { return t.histogram.Max() } + +// Size returns the size of the sample at the time the snapshot was taken. +func (t *timerSnapshot) Size() int { return t.histogram.Size() } // Mean returns the mean value at the time the snapshot was taken. -func (t *TimerSnapshot) Mean() float64 { return t.histogram.Mean() } +func (t *timerSnapshot) Mean() float64 { return t.histogram.Mean() } // Min returns the minimum value at the time the snapshot was taken. -func (t *TimerSnapshot) Min() int64 { return t.histogram.Min() } +func (t *timerSnapshot) Min() int64 { return t.histogram.Min() } // Percentile returns an arbitrary percentile of sampled values at the time the // snapshot was taken. -func (t *TimerSnapshot) Percentile(p float64) float64 { +func (t *timerSnapshot) Percentile(p float64) float64 { return t.histogram.Percentile(p) } // Percentiles returns a slice of arbitrary percentiles of sampled values at // the time the snapshot was taken. -func (t *TimerSnapshot) Percentiles(ps []float64) []float64 { +func (t *timerSnapshot) Percentiles(ps []float64) []float64 { return t.histogram.Percentiles(ps) } // Rate1 returns the one-minute moving average rate of events per second at the // time the snapshot was taken. -func (t *TimerSnapshot) Rate1() float64 { return t.meter.Rate1() } +func (t *timerSnapshot) Rate1() float64 { return t.meter.Rate1() } // Rate5 returns the five-minute moving average rate of events per second at // the time the snapshot was taken. -func (t *TimerSnapshot) Rate5() float64 { return t.meter.Rate5() } +func (t *timerSnapshot) Rate5() float64 { return t.meter.Rate5() } // Rate15 returns the fifteen-minute moving average rate of events per second // at the time the snapshot was taken. -func (t *TimerSnapshot) Rate15() float64 { return t.meter.Rate15() } +func (t *timerSnapshot) Rate15() float64 { return t.meter.Rate15() } // RateMean returns the meter's mean rate of events per second at the time the // snapshot was taken. -func (t *TimerSnapshot) RateMean() float64 { return t.meter.RateMean() } - -// Snapshot returns the snapshot. -func (t *TimerSnapshot) Snapshot() Timer { return t } +func (t *timerSnapshot) RateMean() float64 { return t.meter.RateMean() } // StdDev returns the standard deviation of the values at the time the snapshot // was taken. -func (t *TimerSnapshot) StdDev() float64 { return t.histogram.StdDev() } - -// Stop is a no-op. -func (t *TimerSnapshot) Stop() {} +func (t *timerSnapshot) StdDev() float64 { return t.histogram.StdDev() } // Sum returns the sum at the time the snapshot was taken. -func (t *TimerSnapshot) Sum() int64 { return t.histogram.Sum() } - -// Time panics. -func (*TimerSnapshot) Time(func()) { - panic("Time called on a TimerSnapshot") -} - -// Update panics. -func (*TimerSnapshot) Update(time.Duration) { - panic("Update called on a TimerSnapshot") -} - -// UpdateSince panics. -func (*TimerSnapshot) UpdateSince(time.Time) { - panic("UpdateSince called on a TimerSnapshot") -} +func (t *timerSnapshot) Sum() int64 { return t.histogram.Sum() } // Variance returns the variance of the values at the time the snapshot was // taken. -func (t *TimerSnapshot) Variance() float64 { return t.histogram.Variance() } +func (t *timerSnapshot) Variance() float64 { return t.histogram.Variance() } diff --git a/metrics/timer_test.go b/metrics/timer_test.go index 903e8e8d496e..f10de16c9c23 100644 --- a/metrics/timer_test.go +++ b/metrics/timer_test.go @@ -18,7 +18,7 @@ func BenchmarkTimer(b *testing.B) { func TestGetOrRegisterTimer(t *testing.T) { r := NewRegistry() NewRegisteredTimer("foo", r).Update(47) - if tm := GetOrRegisterTimer("foo", r); tm.Count() != 1 { + if tm := GetOrRegisterTimer("foo", r).Snapshot(); tm.Count() != 1 { t.Fatal(tm) } } @@ -27,7 +27,7 @@ func TestTimerExtremes(t *testing.T) { tm := NewTimer() tm.Update(math.MaxInt64) tm.Update(0) - if stdDev := tm.StdDev(); stdDev != 4.611686018427388e+18 { + if stdDev := tm.Snapshot().StdDev(); stdDev != 4.611686018427388e+18 { t.Errorf("tm.StdDev(): 4.611686018427388e+18 != %v\n", stdDev) } } @@ -56,7 +56,7 @@ func TestTimerFunc(t *testing.T) { }) var ( drift = time.Millisecond * 2 - measured = time.Duration(tm.Max()) + measured = time.Duration(tm.Snapshot().Max()) ceil = actualTime + drift floor = actualTime - drift ) @@ -66,7 +66,7 @@ func TestTimerFunc(t *testing.T) { } func TestTimerZero(t *testing.T) { - tm := NewTimer() + tm := NewTimer().Snapshot() if count := tm.Count(); count != 0 { t.Errorf("tm.Count(): 0 != %v\n", count) } @@ -110,5 +110,5 @@ func ExampleGetOrRegisterTimer() { m := "account.create.latency" t := GetOrRegisterTimer(m, nil) t.Update(47) - fmt.Println(t.Max()) // Output: 47 + fmt.Println(t.Snapshot().Max()) // Output: 47 } diff --git a/metrics/writer.go b/metrics/writer.go index 256fbd14c9b9..098da45c27b2 100644 --- a/metrics/writer.go +++ b/metrics/writer.go @@ -3,8 +3,10 @@ package metrics import ( "fmt" "io" - "sort" + "strings" "time" + + "golang.org/x/exp/slices" ) // Write sorts writes each metric in the given registry periodically to the @@ -18,26 +20,28 @@ func Write(r Registry, d time.Duration, w io.Writer) { // WriteOnce sorts and writes metrics in the given registry to the given // io.Writer. func WriteOnce(r Registry, w io.Writer) { - var namedMetrics namedMetricSlice + var namedMetrics []namedMetric r.Each(func(name string, i interface{}) { namedMetrics = append(namedMetrics, namedMetric{name, i}) }) - - sort.Sort(namedMetrics) + slices.SortFunc(namedMetrics, namedMetric.cmp) for _, namedMetric := range namedMetrics { switch metric := namedMetric.m.(type) { case Counter: fmt.Fprintf(w, "counter %s\n", namedMetric.name) - fmt.Fprintf(w, " count: %9d\n", metric.Count()) + fmt.Fprintf(w, " count: %9d\n", metric.Snapshot().Count()) case CounterFloat64: fmt.Fprintf(w, "counter %s\n", namedMetric.name) - fmt.Fprintf(w, " count: %f\n", metric.Count()) + fmt.Fprintf(w, " count: %f\n", metric.Snapshot().Count()) case Gauge: fmt.Fprintf(w, "gauge %s\n", namedMetric.name) - fmt.Fprintf(w, " value: %9d\n", metric.Value()) + fmt.Fprintf(w, " value: %9d\n", metric.Snapshot().Value()) case GaugeFloat64: fmt.Fprintf(w, "gauge %s\n", namedMetric.name) - fmt.Fprintf(w, " value: %f\n", metric.Value()) + fmt.Fprintf(w, " value: %f\n", metric.Snapshot().Value()) + case GaugeInfo: + fmt.Fprintf(w, "gauge %s\n", namedMetric.name) + fmt.Fprintf(w, " value: %s\n", metric.Snapshot().Value().String()) case Healthcheck: metric.Check() fmt.Fprintf(w, "healthcheck %s\n", namedMetric.name) @@ -91,13 +95,6 @@ type namedMetric struct { m interface{} } -// namedMetricSlice is a slice of namedMetrics that implements sort.Interface. -type namedMetricSlice []namedMetric - -func (nms namedMetricSlice) Len() int { return len(nms) } - -func (nms namedMetricSlice) Swap(i, j int) { nms[i], nms[j] = nms[j], nms[i] } - -func (nms namedMetricSlice) Less(i, j int) bool { - return nms[i].name < nms[j].name +func (m namedMetric) cmp(other namedMetric) int { + return strings.Compare(m.name, other.name) } diff --git a/metrics/writer_test.go b/metrics/writer_test.go index 1aacc287121b..8376bf8975c8 100644 --- a/metrics/writer_test.go +++ b/metrics/writer_test.go @@ -1,19 +1,20 @@ package metrics import ( - "sort" "testing" + + "golang.org/x/exp/slices" ) func TestMetricsSorting(t *testing.T) { - var namedMetrics = namedMetricSlice{ + var namedMetrics = []namedMetric{ {name: "zzz"}, {name: "bbb"}, {name: "fff"}, {name: "ggg"}, } - sort.Sort(namedMetrics) + slices.SortFunc(namedMetrics, namedMetric.cmp) for i, name := range []string{"bbb", "fff", "ggg", "zzz"} { if namedMetrics[i].name != name { t.Fail() diff --git a/miner/miner.go b/miner/miner.go index b1d1f7c4cbfe..b7273948f5e7 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -131,16 +131,22 @@ func (miner *Miner) update() { shouldStart = true log.Info("Mining aborted due to sync") } + miner.worker.syncing.Store(true) + case downloader.FailedEvent: canStart = true if shouldStart { miner.worker.start() } + miner.worker.syncing.Store(false) + case downloader.DoneEvent: canStart = true if shouldStart { miner.worker.start() } + miner.worker.syncing.Store(false) + // Stop reacting to downloader events events.Unsubscribe() } @@ -196,12 +202,14 @@ func (miner *Miner) SetRecommitInterval(interval time.Duration) { miner.worker.setRecommitInterval(interval) } -// Pending returns the currently pending block and associated state. +// Pending returns the currently pending block and associated state. The returned +// values can be nil in case the pending block is not initialized func (miner *Miner) Pending() (*types.Block, *state.StateDB) { return miner.worker.pending() } -// PendingBlock returns the currently pending block. +// PendingBlock returns the currently pending block. The returned block can be +// nil in case the pending block is not initialized. // // Note, to access both the pending block and the pending state // simultaneously, please use Pending(), as the pending state can @@ -211,6 +219,7 @@ func (miner *Miner) PendingBlock() *types.Block { } // PendingBlockAndReceipts returns the currently pending block and corresponding receipts. +// The returned values can be nil in case the pending block is not initialized. func (miner *Miner) PendingBlockAndReceipts() (*types.Block, types.Receipts) { return miner.worker.pendingBlockAndReceipts() } @@ -225,23 +234,6 @@ func (miner *Miner) SetGasCeil(ceil uint64) { miner.worker.setGasCeil(ceil) } -// EnablePreseal turns on the preseal mining feature. It's enabled by default. -// Note this function shouldn't be exposed to API, it's unnecessary for users -// (miners) to actually know the underlying detail. It's only for outside project -// which uses this library. -func (miner *Miner) EnablePreseal() { - miner.worker.enablePreseal() -} - -// DisablePreseal turns off the preseal mining feature. It's necessary for some -// fake consensus engine which can seal blocks instantaneously. -// Note this function shouldn't be exposed to API, it's unnecessary for users -// (miners) to actually know the underlying detail. It's only for outside project -// which uses this library. -func (miner *Miner) DisablePreseal() { - miner.worker.disablePreseal() -} - // SubscribePendingLogs starts delivering logs from pending transactions // to the given channel. func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscription { diff --git a/miner/miner_test.go b/miner/miner_test.go index 1fc178bd0196..e6de99a366b6 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -29,10 +29,13 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" ) @@ -61,11 +64,17 @@ func (m *mockBackend) StateAtBlock(block *types.Block, reexec uint64, base *stat } type testBlockChain struct { + root common.Hash + config *params.ChainConfig statedb *state.StateDB gasLimit uint64 chainHeadFeed *event.Feed } +func (bc *testBlockChain) Config() *params.ChainConfig { + return bc.config +} + func (bc *testBlockChain) CurrentBlock() *types.Header { return &types.Header{ Number: new(big.Int), @@ -81,6 +90,10 @@ func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { return bc.statedb, nil } +func (bc *testBlockChain) HasState(root common.Hash) bool { + return bc.root == root +} + func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { return bc.chainHeadFeed.Subscribe(ch) } @@ -245,6 +258,34 @@ func waitForMiningState(t *testing.T, m *Miner, mining bool) { t.Fatalf("Mining() == %t, want %t", state, mining) } +func minerTestGenesisBlock(period uint64, gasLimit uint64, faucet common.Address) *core.Genesis { + config := *params.AllCliqueProtocolChanges + config.Clique = ¶ms.CliqueConfig{ + Period: period, + Epoch: config.Clique.Epoch, + } + + // Assemble and return the genesis with the precompiles and faucet pre-funded + return &core.Genesis{ + Config: &config, + ExtraData: append(append(make([]byte, 32), faucet[:]...), make([]byte, crypto.SignatureLength)...), + GasLimit: gasLimit, + BaseFee: big.NewInt(params.InitialBaseFee), + Difficulty: big.NewInt(1), + Alloc: map[common.Address]core.GenesisAccount{ + common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover + common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256 + common.BytesToAddress([]byte{3}): {Balance: big.NewInt(1)}, // RIPEMD + common.BytesToAddress([]byte{4}): {Balance: big.NewInt(1)}, // Identity + common.BytesToAddress([]byte{5}): {Balance: big.NewInt(1)}, // ModExp + common.BytesToAddress([]byte{6}): {Balance: big.NewInt(1)}, // ECAdd + common.BytesToAddress([]byte{7}): {Balance: big.NewInt(1)}, // ECScalarMul + common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing + common.BytesToAddress([]byte{9}): {Balance: big.NewInt(1)}, // BLAKE2b + faucet: {Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))}, + }, + } +} func createMiner(t *testing.T) (*Miner, *event.TypeMux, func(skipMiner bool)) { // Create Ethash config config := Config{ @@ -252,8 +293,9 @@ func createMiner(t *testing.T) (*Miner, *event.TypeMux, func(skipMiner bool)) { } // Create chainConfig chainDB := rawdb.NewMemoryDatabase() - genesis := core.DeveloperGenesisBlock(15, 11_500_000, common.HexToAddress("12345")) - chainConfig, _, err := core.SetupGenesisBlock(chainDB, trie.NewDatabase(chainDB), genesis) + triedb := trie.NewDatabase(chainDB, nil) + genesis := minerTestGenesisBlock(15, 11_500_000, common.HexToAddress("12345")) + chainConfig, _, err := core.SetupGenesisBlock(chainDB, triedb, genesis) if err != nil { t.Fatalf("can't create new chain config: %v", err) } @@ -264,11 +306,13 @@ func createMiner(t *testing.T) (*Miner, *event.TypeMux, func(skipMiner bool)) { if err != nil { t.Fatalf("can't create new chain %v", err) } - statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(chainDB), nil) - blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)} + statedb, _ := state.New(bc.Genesis().Root(), bc.StateCache(), nil) + blockchain := &testBlockChain{bc.Genesis().Root(), chainConfig, statedb, 10000000, new(event.Feed)} + + pool := legacypool.New(testTxPoolConfig, blockchain) + txpool, _ := txpool.New(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain, []txpool.SubPool{pool}) - pool := txpool.NewTxPool(testTxPoolConfig, chainConfig, blockchain) - backend := NewMockBackend(bc, pool) + backend := NewMockBackend(bc, txpool) // Create event Mux mux := new(event.TypeMux) // Create Miner @@ -276,7 +320,7 @@ func createMiner(t *testing.T) (*Miner, *event.TypeMux, func(skipMiner bool)) { cleanup := func(skipMiner bool) { bc.Stop() engine.Close() - pool.Stop() + txpool.Close() if !skipMiner { miner.Close() } diff --git a/miner/ordering.go b/miner/ordering.go new file mode 100644 index 000000000000..4c3055f0d317 --- /dev/null +++ b/miner/ordering.go @@ -0,0 +1,147 @@ +// Copyright 2014 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 miner + +import ( + "container/heap" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" +) + +// txWithMinerFee wraps a transaction with its gas price or effective miner gasTipCap +type txWithMinerFee struct { + tx *txpool.LazyTransaction + from common.Address + fees *big.Int +} + +// newTxWithMinerFee creates a wrapped transaction, calculating the effective +// miner gasTipCap if a base fee is provided. +// Returns error in case of a negative effective miner gasTipCap. +func newTxWithMinerFee(tx *txpool.LazyTransaction, from common.Address, baseFee *big.Int) (*txWithMinerFee, error) { + tip := new(big.Int).Set(tx.GasTipCap) + if baseFee != nil { + if tx.GasFeeCap.Cmp(baseFee) < 0 { + return nil, types.ErrGasFeeCapTooLow + } + tip = math.BigMin(tx.GasTipCap, new(big.Int).Sub(tx.GasFeeCap, baseFee)) + } + return &txWithMinerFee{ + tx: tx, + from: from, + fees: tip, + }, nil +} + +// txByPriceAndTime implements both the sort and the heap interface, making it useful +// for all at once sorting as well as individually adding and removing elements. +type txByPriceAndTime []*txWithMinerFee + +func (s txByPriceAndTime) Len() int { return len(s) } +func (s txByPriceAndTime) Less(i, j int) bool { + // If the prices are equal, use the time the transaction was first seen for + // deterministic sorting + cmp := s[i].fees.Cmp(s[j].fees) + if cmp == 0 { + return s[i].tx.Time.Before(s[j].tx.Time) + } + return cmp > 0 +} +func (s txByPriceAndTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s *txByPriceAndTime) Push(x interface{}) { + *s = append(*s, x.(*txWithMinerFee)) +} + +func (s *txByPriceAndTime) Pop() interface{} { + old := *s + n := len(old) + x := old[n-1] + old[n-1] = nil + *s = old[0 : n-1] + return x +} + +// transactionsByPriceAndNonce represents a set of transactions that can return +// transactions in a profit-maximizing sorted order, while supporting removing +// entire batches of transactions for non-executable accounts. +type transactionsByPriceAndNonce struct { + txs map[common.Address][]*txpool.LazyTransaction // Per account nonce-sorted list of transactions + heads txByPriceAndTime // Next transaction for each unique account (price heap) + signer types.Signer // Signer for the set of transactions + baseFee *big.Int // Current base fee +} + +// newTransactionsByPriceAndNonce creates a transaction set that can retrieve +// price sorted transactions in a nonce-honouring way. +// +// Note, the input map is reowned so the caller should not interact any more with +// if after providing it to the constructor. +func newTransactionsByPriceAndNonce(signer types.Signer, txs map[common.Address][]*txpool.LazyTransaction, baseFee *big.Int) *transactionsByPriceAndNonce { + // Initialize a price and received time based heap with the head transactions + heads := make(txByPriceAndTime, 0, len(txs)) + for from, accTxs := range txs { + wrapped, err := newTxWithMinerFee(accTxs[0], from, baseFee) + if err != nil { + delete(txs, from) + continue + } + heads = append(heads, wrapped) + txs[from] = accTxs[1:] + } + heap.Init(&heads) + + // Assemble and return the transaction set + return &transactionsByPriceAndNonce{ + txs: txs, + heads: heads, + signer: signer, + baseFee: baseFee, + } +} + +// Peek returns the next transaction by price. +func (t *transactionsByPriceAndNonce) Peek() *txpool.LazyTransaction { + if len(t.heads) == 0 { + return nil + } + return t.heads[0].tx +} + +// Shift replaces the current best head with the next one from the same account. +func (t *transactionsByPriceAndNonce) Shift() { + acc := t.heads[0].from + if txs, ok := t.txs[acc]; ok && len(txs) > 0 { + if wrapped, err := newTxWithMinerFee(txs[0], acc, t.baseFee); err == nil { + t.heads[0], t.txs[acc] = wrapped, txs[1:] + heap.Fix(&t.heads, 0) + return + } + } + heap.Pop(&t.heads) +} + +// Pop removes the best transaction, *not* replacing it with the next one from +// the same account. This should be used when a transaction cannot be executed +// and hence all subsequent ones should be discarded from the same account. +func (t *transactionsByPriceAndNonce) Pop() { + heap.Pop(&t.heads) +} diff --git a/miner/ordering_test.go b/miner/ordering_test.go new file mode 100644 index 000000000000..59d478274d74 --- /dev/null +++ b/miner/ordering_test.go @@ -0,0 +1,192 @@ +// Copyright 2014 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 miner + +import ( + "crypto/ecdsa" + "math/big" + "math/rand" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestTransactionPriceNonceSortLegacy(t *testing.T) { + testTransactionPriceNonceSort(t, nil) +} + +func TestTransactionPriceNonceSort1559(t *testing.T) { + testTransactionPriceNonceSort(t, big.NewInt(0)) + testTransactionPriceNonceSort(t, big.NewInt(5)) + testTransactionPriceNonceSort(t, big.NewInt(50)) +} + +// Tests that transactions can be correctly sorted according to their price in +// decreasing order, but at the same time with increasing nonces when issued by +// the same account. +func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) { + // Generate a batch of accounts to start with + keys := make([]*ecdsa.PrivateKey, 25) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + } + signer := types.LatestSignerForChainID(common.Big1) + + // Generate a batch of transactions with overlapping values, but shifted nonces + groups := map[common.Address][]*txpool.LazyTransaction{} + expectedCount := 0 + for start, key := range keys { + addr := crypto.PubkeyToAddress(key.PublicKey) + count := 25 + for i := 0; i < 25; i++ { + var tx *types.Transaction + gasFeeCap := rand.Intn(50) + if baseFee == nil { + tx = types.NewTx(&types.LegacyTx{ + Nonce: uint64(start + i), + To: &common.Address{}, + Value: big.NewInt(100), + Gas: 100, + GasPrice: big.NewInt(int64(gasFeeCap)), + Data: nil, + }) + } else { + tx = types.NewTx(&types.DynamicFeeTx{ + Nonce: uint64(start + i), + To: &common.Address{}, + Value: big.NewInt(100), + Gas: 100, + GasFeeCap: big.NewInt(int64(gasFeeCap)), + GasTipCap: big.NewInt(int64(rand.Intn(gasFeeCap + 1))), + Data: nil, + }) + if count == 25 && int64(gasFeeCap) < baseFee.Int64() { + count = i + } + } + tx, err := types.SignTx(tx, signer, key) + if err != nil { + t.Fatalf("failed to sign tx: %s", err) + } + groups[addr] = append(groups[addr], &txpool.LazyTransaction{ + Hash: tx.Hash(), + Tx: tx, + Time: tx.Time(), + GasFeeCap: tx.GasFeeCap(), + GasTipCap: tx.GasTipCap(), + Gas: tx.Gas(), + BlobGas: tx.BlobGas(), + }) + } + expectedCount += count + } + // Sort the transactions and cross check the nonce ordering + txset := newTransactionsByPriceAndNonce(signer, groups, baseFee) + + txs := types.Transactions{} + for tx := txset.Peek(); tx != nil; tx = txset.Peek() { + txs = append(txs, tx.Tx) + txset.Shift() + } + if len(txs) != expectedCount { + t.Errorf("expected %d transactions, found %d", expectedCount, len(txs)) + } + for i, txi := range txs { + fromi, _ := types.Sender(signer, txi) + + // Make sure the nonce order is valid + for j, txj := range txs[i+1:] { + fromj, _ := types.Sender(signer, txj) + if fromi == fromj && txi.Nonce() > txj.Nonce() { + t.Errorf("invalid nonce ordering: tx #%d (A=%x N=%v) < tx #%d (A=%x N=%v)", i, fromi[:4], txi.Nonce(), i+j, fromj[:4], txj.Nonce()) + } + } + // If the next tx has different from account, the price must be lower than the current one + if i+1 < len(txs) { + next := txs[i+1] + fromNext, _ := types.Sender(signer, next) + tip, err := txi.EffectiveGasTip(baseFee) + nextTip, nextErr := next.EffectiveGasTip(baseFee) + if err != nil || nextErr != nil { + t.Errorf("error calculating effective tip: %v, %v", err, nextErr) + } + if fromi != fromNext && tip.Cmp(nextTip) < 0 { + t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice()) + } + } + } +} + +// Tests that if multiple transactions have the same price, the ones seen earlier +// are prioritized to avoid network spam attacks aiming for a specific ordering. +func TestTransactionTimeSort(t *testing.T) { + // Generate a batch of accounts to start with + keys := make([]*ecdsa.PrivateKey, 5) + for i := 0; i < len(keys); i++ { + keys[i], _ = crypto.GenerateKey() + } + signer := types.HomesteadSigner{} + + // Generate a batch of transactions with overlapping prices, but different creation times + groups := map[common.Address][]*txpool.LazyTransaction{} + for start, key := range keys { + addr := crypto.PubkeyToAddress(key.PublicKey) + + tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 100, big.NewInt(1), nil), signer, key) + tx.SetTime(time.Unix(0, int64(len(keys)-start))) + + groups[addr] = append(groups[addr], &txpool.LazyTransaction{ + Hash: tx.Hash(), + Tx: tx, + Time: tx.Time(), + GasFeeCap: tx.GasFeeCap(), + GasTipCap: tx.GasTipCap(), + Gas: tx.Gas(), + BlobGas: tx.BlobGas(), + }) + } + // Sort the transactions and cross check the nonce ordering + txset := newTransactionsByPriceAndNonce(signer, groups, nil) + + txs := types.Transactions{} + for tx := txset.Peek(); tx != nil; tx = txset.Peek() { + txs = append(txs, tx.Tx) + txset.Shift() + } + if len(txs) != len(keys) { + t.Errorf("expected %d transactions, found %d", len(keys), len(txs)) + } + for i, txi := range txs { + fromi, _ := types.Sender(signer, txi) + if i+1 < len(txs) { + next := txs[i+1] + fromNext, _ := types.Sender(signer, next) + + if txi.GasPrice().Cmp(next.GasPrice()) < 0 { + t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice()) + } + // Make sure time order is ascending if the txs have the same gas price + if txi.GasPrice().Cmp(next.GasPrice()) == 0 && txi.Time().After(next.Time()) { + t.Errorf("invalid received time ordering: tx #%d (A=%x T=%v) > tx #%d (A=%x T=%v)", i, fromi[:4], txi.Time(), i+1, fromNext[:4], next.Time()) + } + } + } +} diff --git a/miner/payload_building.go b/miner/payload_building.go index f84d908e86d6..7d8c4368bfc4 100644 --- a/miner/payload_building.go +++ b/miner/payload_building.go @@ -40,6 +40,7 @@ type BuildPayloadArgs struct { FeeRecipient common.Address // The provided recipient address for collecting transaction fee Random common.Hash // The provided randomness value Withdrawals types.Withdrawals // The provided withdrawals + BeaconRoot *common.Hash // The provided beaconRoot (Cancun) } // Id computes an 8-byte identifier by hashing the components of the payload arguments. @@ -51,6 +52,9 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID { hasher.Write(args.Random[:]) hasher.Write(args.FeeRecipient[:]) rlp.Encode(hasher, args.Withdrawals) + if args.BeaconRoot != nil { + hasher.Write(args.BeaconRoot[:]) + } var out engine.PayloadID copy(out[:], hasher.Sum(nil)[:8]) return out @@ -65,6 +69,7 @@ type Payload struct { id engine.PayloadID empty *types.Block full *types.Block + sidecars []*types.BlobTxSidecar fullFees *big.Int stop chan struct{} lock sync.Mutex @@ -84,7 +89,7 @@ func newPayload(empty *types.Block, id engine.PayloadID) *Payload { } // update updates the full-block with latest built version. -func (payload *Payload) update(block *types.Block, fees *big.Int, elapsed time.Duration) { +func (payload *Payload) update(r *newPayloadResult, elapsed time.Duration) { payload.lock.Lock() defer payload.lock.Unlock() @@ -96,14 +101,23 @@ func (payload *Payload) update(block *types.Block, fees *big.Int, elapsed time.D // Ensure the newly provided full block has a higher transaction fee. // In post-merge stage, there is no uncle reward anymore and transaction // fee(apart from the mev revenue) is the only indicator for comparison. - if payload.full == nil || fees.Cmp(payload.fullFees) > 0 { - payload.full = block - payload.fullFees = fees - - feesInEther := new(big.Float).Quo(new(big.Float).SetInt(fees), big.NewFloat(params.Ether)) - log.Info("Updated payload", "id", payload.id, "number", block.NumberU64(), "hash", block.Hash(), - "txs", len(block.Transactions()), "gas", block.GasUsed(), "fees", feesInEther, - "root", block.Root(), "elapsed", common.PrettyDuration(elapsed)) + if payload.full == nil || r.fees.Cmp(payload.fullFees) > 0 { + payload.full = r.block + payload.fullFees = r.fees + payload.sidecars = r.sidecars + + feesInEther := new(big.Float).Quo(new(big.Float).SetInt(r.fees), big.NewFloat(params.Ether)) + log.Info("Updated payload", + "id", payload.id, + "number", r.block.NumberU64(), + "hash", r.block.Hash(), + "txs", len(r.block.Transactions()), + "withdrawals", len(r.block.Withdrawals()), + "gas", r.block.GasUsed(), + "fees", feesInEther, + "root", r.block.Root(), + "elapsed", common.PrettyDuration(elapsed), + ) } payload.cond.Broadcast() // fire signal for notifying full block } @@ -120,9 +134,9 @@ func (payload *Payload) Resolve() *engine.ExecutionPayloadEnvelope { close(payload.stop) } if payload.full != nil { - return engine.BlockToExecutableData(payload.full, payload.fullFees) + return engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars) } - return engine.BlockToExecutableData(payload.empty, big.NewInt(0)) + return engine.BlockToExecutableData(payload.empty, big.NewInt(0), nil) } // ResolveEmpty is basically identical to Resolve, but it expects empty block only. @@ -131,11 +145,11 @@ func (payload *Payload) ResolveEmpty() *engine.ExecutionPayloadEnvelope { payload.lock.Lock() defer payload.lock.Unlock() - return engine.BlockToExecutableData(payload.empty, big.NewInt(0)) + return engine.BlockToExecutableData(payload.empty, big.NewInt(0), nil) } // ResolveFull is basically identical to Resolve, but it expects full block only. -// It's only used in tests. +// Don't call Resolve until ResolveFull returns, otherwise it might block forever. func (payload *Payload) ResolveFull() *engine.ExecutionPayloadEnvelope { payload.lock.Lock() defer payload.lock.Unlock() @@ -146,9 +160,18 @@ func (payload *Payload) ResolveFull() *engine.ExecutionPayloadEnvelope { return nil default: } + // Wait the full payload construction. Note it might block + // forever if Resolve is called in the meantime which + // terminates the background construction process. payload.cond.Wait() } - return engine.BlockToExecutableData(payload.full, payload.fullFees) + // Terminate the background payload construction + select { + case <-payload.stop: + default: + close(payload.stop) + } + return engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars) } // buildPayload builds the payload according to the provided parameters. @@ -156,12 +179,23 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) { // Build the initial version with no transaction included. It should be fast // enough to run. The empty payload can at least make sure there is something // to deliver for not missing slot. - empty, _, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, args.Withdrawals, true) - if err != nil { - return nil, err + emptyParams := &generateParams{ + timestamp: args.Timestamp, + forceTime: true, + parentHash: args.Parent, + coinbase: args.FeeRecipient, + random: args.Random, + withdrawals: args.Withdrawals, + beaconRoot: args.BeaconRoot, + noTxs: true, + } + empty := w.getSealingBlock(emptyParams) + if empty.err != nil { + return nil, empty.err } + // Construct a payload object for return. - payload := newPayload(empty, args.Id()) + payload := newPayload(empty.block, args.Id()) // Spin up a routine for updating the payload in background. This strategy // can maximum the revenue for including transactions with highest fee. @@ -176,13 +210,24 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) { // by the timestamp parameter. endTimer := time.NewTimer(time.Second * 12) + fullParams := &generateParams{ + timestamp: args.Timestamp, + forceTime: true, + parentHash: args.Parent, + coinbase: args.FeeRecipient, + random: args.Random, + withdrawals: args.Withdrawals, + beaconRoot: args.BeaconRoot, + noTxs: false, + } + for { select { case <-timer.C: start := time.Now() - block, fees, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.Random, args.Withdrawals, false) - if err == nil { - payload.update(block, fees, time.Since(start)) + r := w.getSealingBlock(fullParams) + if r.err == nil { + payload.update(r, time.Since(start)) } timer.Reset(w.recommit) case <-payload.stop: diff --git a/miner/payload_building_test.go b/miner/payload_building_test.go index b2f1d68f3c61..6f57363441c8 100644 --- a/miner/payload_building_test.go +++ b/miner/payload_building_test.go @@ -84,48 +84,48 @@ func TestBuildPayload(t *testing.T) { func TestPayloadId(t *testing.T) { ids := make(map[string]int) for i, tt := range []*BuildPayloadArgs{ - &BuildPayloadArgs{ + { Parent: common.Hash{1}, Timestamp: 1, Random: common.Hash{0x1}, FeeRecipient: common.Address{0x1}, }, // Different parent - &BuildPayloadArgs{ + { Parent: common.Hash{2}, Timestamp: 1, Random: common.Hash{0x1}, FeeRecipient: common.Address{0x1}, }, // Different timestamp - &BuildPayloadArgs{ + { Parent: common.Hash{2}, Timestamp: 2, Random: common.Hash{0x1}, FeeRecipient: common.Address{0x1}, }, // Different Random - &BuildPayloadArgs{ + { Parent: common.Hash{2}, Timestamp: 2, Random: common.Hash{0x2}, FeeRecipient: common.Address{0x1}, }, // Different fee-recipient - &BuildPayloadArgs{ + { Parent: common.Hash{2}, Timestamp: 2, Random: common.Hash{0x2}, FeeRecipient: common.Address{0x2}, }, // Different withdrawals (non-empty) - &BuildPayloadArgs{ + { Parent: common.Hash{2}, Timestamp: 2, Random: common.Hash{0x2}, FeeRecipient: common.Address{0x2}, Withdrawals: []*types.Withdrawal{ - &types.Withdrawal{ + { Index: 0, Validator: 0, Address: common.Address{}, @@ -134,13 +134,13 @@ func TestPayloadId(t *testing.T) { }, }, // Different withdrawals (non-empty) - &BuildPayloadArgs{ + { Parent: common.Hash{2}, Timestamp: 2, Random: common.Hash{0x2}, FeeRecipient: common.Address{0x2}, Withdrawals: []*types.Withdrawal{ - &types.Withdrawal{ + { Index: 2, Validator: 0, Address: common.Address{}, diff --git a/miner/stress/clique/main.go b/miner/stress/clique/main.go index 16a8687da31a..7b29e63dfc69 100644 --- a/miner/stress/clique/main.go +++ b/miner/stress/clique/main.go @@ -30,7 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/fdlimit" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" @@ -57,7 +57,7 @@ func main() { for i := 0; i < len(sealers); i++ { sealers[i], _ = crypto.GenerateKey() } - // Create a Clique network based off of the Rinkeby config + // Create a Clique network based off of the Sepolia config genesis := makeGenesis(faucets, sealers) // Handle interrupts. @@ -132,7 +132,7 @@ func main() { if err != nil { panic(err) } - if err := backend.TxPool().AddLocal(tx); err != nil { + if err := backend.TxPool().Add([]*types.Transaction{tx}, true, false); err != nil { panic(err) } nonces[index]++ @@ -147,8 +147,8 @@ func main() { // makeGenesis creates a custom Clique genesis block based on some pre-defined // signer and faucet accounts. func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core.Genesis { - // Create a Clique network based off of the Rinkeby config - genesis := core.DefaultRinkebyGenesisBlock() + // Create a Clique network based off of the Sepolia config + genesis := core.DefaultSepoliaGenesisBlock() genesis.GasLimit = 25000000 genesis.Config.ChainID = big.NewInt(18) @@ -206,7 +206,7 @@ func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { SyncMode: downloader.FullSync, DatabaseCache: 256, DatabaseHandles: 256, - TxPool: txpool.DefaultConfig, + TxPool: legacypool.DefaultConfig, GPO: ethconfig.Defaults.GPO, Miner: miner.Config{ GasCeil: genesis.GasLimit * 11 / 10, diff --git a/miner/unconfirmed.go b/miner/unconfirmed.go deleted file mode 100644 index 0489f1ea4adb..000000000000 --- a/miner/unconfirmed.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package miner - -import ( - "container/ring" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/log" -) - -// chainRetriever is used by the unconfirmed block set to verify whether a previously -// mined block is part of the canonical chain or not. -type chainRetriever interface { - // GetHeaderByNumber retrieves the canonical header associated with a block number. - GetHeaderByNumber(number uint64) *types.Header - - // GetBlockByNumber retrieves the canonical block associated with a block number. - GetBlockByNumber(number uint64) *types.Block -} - -// unconfirmedBlock is a small collection of metadata about a locally mined block -// that is placed into a unconfirmed set for canonical chain inclusion tracking. -type unconfirmedBlock struct { - index uint64 - hash common.Hash -} - -// unconfirmedBlocks implements a data structure to maintain locally mined blocks -// have not yet reached enough maturity to guarantee chain inclusion. It is -// used by the miner to provide logs to the user when a previously mined block -// has a high enough guarantee to not be reorged out of the canonical chain. -type unconfirmedBlocks struct { - chain chainRetriever // Blockchain to verify canonical status through - depth uint // Depth after which to discard previous blocks - blocks *ring.Ring // Block infos to allow canonical chain cross checks - lock sync.Mutex // Protects the fields from concurrent access -} - -// newUnconfirmedBlocks returns new data structure to track currently unconfirmed blocks. -func newUnconfirmedBlocks(chain chainRetriever, depth uint) *unconfirmedBlocks { - return &unconfirmedBlocks{ - chain: chain, - depth: depth, - } -} - -// Insert adds a new block to the set of unconfirmed ones. -func (set *unconfirmedBlocks) Insert(index uint64, hash common.Hash) { - // If a new block was mined locally, shift out any old enough blocks - set.Shift(index) - - // Create the new item as its own ring - item := ring.New(1) - item.Value = &unconfirmedBlock{ - index: index, - hash: hash, - } - // Set as the initial ring or append to the end - set.lock.Lock() - defer set.lock.Unlock() - - if set.blocks == nil { - set.blocks = item - } else { - set.blocks.Move(-1).Link(item) - } - // Display a log for the user to notify of a new mined block unconfirmed - log.Info("🔨 mined potential block", "number", index, "hash", hash) -} - -// Shift drops all unconfirmed blocks from the set which exceed the unconfirmed sets depth -// allowance, checking them against the canonical chain for inclusion or staleness -// report. -func (set *unconfirmedBlocks) Shift(height uint64) { - set.lock.Lock() - defer set.lock.Unlock() - - for set.blocks != nil { - // Retrieve the next unconfirmed block and abort if too fresh - next := set.blocks.Value.(*unconfirmedBlock) - if next.index+uint64(set.depth) > height { - break - } - // Block seems to exceed depth allowance, check for canonical status - header := set.chain.GetHeaderByNumber(next.index) - switch { - case header == nil: - log.Warn("Failed to retrieve header of mined block", "number", next.index, "hash", next.hash) - case header.Hash() == next.hash: - log.Info("🔗 block reached canonical chain", "number", next.index, "hash", next.hash) - default: - // Block is not canonical, check whether we have an uncle or a lost block - included := false - for number := next.index; !included && number < next.index+uint64(set.depth) && number <= height; number++ { - if block := set.chain.GetBlockByNumber(number); block != nil { - for _, uncle := range block.Uncles() { - if uncle.Hash() == next.hash { - included = true - break - } - } - } - } - if included { - log.Info("â‘‚ block became an uncle", "number", next.index, "hash", next.hash) - } else { - log.Info("😱 block lost", "number", next.index, "hash", next.hash) - } - } - // Drop the block out of the ring - if set.blocks.Value == set.blocks.Next().Value { - set.blocks = nil - } else { - set.blocks = set.blocks.Move(-1) - set.blocks.Unlink(1) - set.blocks = set.blocks.Move(1) - } - } -} diff --git a/miner/unconfirmed_test.go b/miner/unconfirmed_test.go deleted file mode 100644 index 60958f658abc..000000000000 --- a/miner/unconfirmed_test.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package miner - -import ( - "testing" - - "github.com/ethereum/go-ethereum/core/types" -) - -// noopChainRetriever is an implementation of headerRetriever that always -// returns nil for any requested headers. -type noopChainRetriever struct{} - -func (r *noopChainRetriever) GetHeaderByNumber(number uint64) *types.Header { - return nil -} -func (r *noopChainRetriever) GetBlockByNumber(number uint64) *types.Block { - return nil -} - -// Tests that inserting blocks into the unconfirmed set accumulates them until -// the desired depth is reached, after which they begin to be dropped. -func TestUnconfirmedInsertBounds(t *testing.T) { - limit := uint(10) - - pool := newUnconfirmedBlocks(new(noopChainRetriever), limit) - for depth := uint64(0); depth < 2*uint64(limit); depth++ { - // Insert multiple blocks for the same level just to stress it - for i := 0; i < int(depth); i++ { - pool.Insert(depth, [32]byte{byte(depth), byte(i)}) - } - // Validate that no blocks below the depth allowance are left in - pool.blocks.Do(func(block interface{}) { - if block := block.(*unconfirmedBlock); block.index+uint64(limit) <= depth { - t.Errorf("depth %d: block %x not dropped", depth, block.hash) - } - }) - } -} - -// Tests that shifting blocks out of the unconfirmed set works both for normal -// cases as well as for corner cases such as empty sets, empty shifts or full -// shifts. -func TestUnconfirmedShifts(t *testing.T) { - // Create a pool with a few blocks on various depths - limit, start := uint(10), uint64(25) - - pool := newUnconfirmedBlocks(new(noopChainRetriever), limit) - for depth := start; depth < start+uint64(limit); depth++ { - pool.Insert(depth, [32]byte{byte(depth)}) - } - // Try to shift below the limit and ensure no blocks are dropped - pool.Shift(start + uint64(limit) - 1) - if n := pool.blocks.Len(); n != int(limit) { - t.Errorf("unconfirmed count mismatch: have %d, want %d", n, limit) - } - // Try to shift half the blocks out and verify remainder - pool.Shift(start + uint64(limit) - 1 + uint64(limit/2)) - if n := pool.blocks.Len(); n != int(limit)/2 { - t.Errorf("unconfirmed count mismatch: have %d, want %d", n, limit/2) - } - // Try to shift all the remaining blocks out and verify emptiness - pool.Shift(start + 2*uint64(limit)) - if n := pool.blocks.Len(); n != 0 { - t.Errorf("unconfirmed count mismatch: have %d, want %d", n, 0) - } - // Try to shift out from the empty set and make sure it doesn't break - pool.Shift(start + 3*uint64(limit)) - if n := pool.blocks.Len(); n != 0 { - t.Errorf("unconfirmed count mismatch: have %d, want %d", n, 0) - } -} diff --git a/miner/worker.go b/miner/worker.go index 8f91ceb5c61b..45743850acd4 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -24,13 +24,15 @@ import ( "sync/atomic" "time" - mapset "github.com/deckarep/golang-set/v2" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -48,15 +50,9 @@ const ( // chainHeadChanSize is the size of channel listening to ChainHeadEvent. chainHeadChanSize = 10 - // chainSideChanSize is the size of channel listening to ChainSideEvent. - chainSideChanSize = 10 - // resubmitAdjustChanSize is the size of resubmitting interval adjustment channel. resubmitAdjustChanSize = 10 - // sealingLogAtDepth is the number of confirmations before logging successful sealing. - sealingLogAtDepth = 7 - // minRecommitInterval is the minimal time interval to recreate the sealing block with // any newly arrived transactions. minRecommitInterval = 1 * time.Second @@ -86,55 +82,40 @@ var ( // environment is the worker's current environment and holds all // information of the sealing block generation. type environment struct { - signer types.Signer - - state *state.StateDB // apply state changes here - ancestors mapset.Set[common.Hash] // ancestor set (used for checking uncle parent validity) - family mapset.Set[common.Hash] // family set (used for checking uncle invalidity) - tcount int // tx count in cycle - gasPool *core.GasPool // available gas used to pack transactions - coinbase common.Address + signer types.Signer + state *state.StateDB // apply state changes here + tcount int // tx count in cycle + gasPool *core.GasPool // available gas used to pack transactions + coinbase common.Address header *types.Header txs []*types.Transaction receipts []*types.Receipt - uncles map[common.Hash]*types.Header + sidecars []*types.BlobTxSidecar + blobs int } // copy creates a deep copy of environment. func (env *environment) copy() *environment { cpy := &environment{ - signer: env.signer, - state: env.state.Copy(), - ancestors: env.ancestors.Clone(), - family: env.family.Clone(), - tcount: env.tcount, - coinbase: env.coinbase, - header: types.CopyHeader(env.header), - receipts: copyReceipts(env.receipts), + signer: env.signer, + state: env.state.Copy(), + tcount: env.tcount, + coinbase: env.coinbase, + header: types.CopyHeader(env.header), + receipts: copyReceipts(env.receipts), } if env.gasPool != nil { gasPool := *env.gasPool cpy.gasPool = &gasPool } - // The content of txs and uncles are immutable, unnecessary - // to do the expensive deep copy for them. cpy.txs = make([]*types.Transaction, len(env.txs)) copy(cpy.txs, env.txs) - cpy.uncles = make(map[common.Hash]*types.Header) - for hash, uncle := range env.uncles { - cpy.uncles[hash] = uncle - } - return cpy -} -// unclelist returns the contained uncles as the list format. -func (env *environment) unclelist() []*types.Header { - var uncles []*types.Header - for _, uncle := range env.uncles { - uncles = append(uncles, uncle) - } - return uncles + cpy.sidecars = make([]*types.BlobTxSidecar, len(env.sidecars)) + copy(cpy.sidecars, env.sidecars) + + return cpy } // discard terminates the background prefetcher go-routine. It should @@ -165,15 +146,15 @@ const ( // newWorkReq represents a request for new sealing work submitting with relative interrupt notifier. type newWorkReq struct { interrupt *atomic.Int32 - noempty bool timestamp int64 } -// newPayloadResult represents a result struct corresponds to payload generation. +// newPayloadResult is the result of payload generation. type newPayloadResult struct { - err error - block *types.Block - fees *big.Int + err error + block *types.Block + fees *big.Int // total block fees + sidecars []*types.BlobTxSidecar // collected blobs of blob transactions } // getWorkReq represents a request for getting a new sealing work with provided parameters. @@ -206,8 +187,6 @@ type worker struct { txsSub event.Subscription chainHeadCh chan core.ChainHeadEvent chainHeadSub event.Subscription - chainSideCh chan core.ChainSideEvent - chainSideSub event.Subscription // Channels newWorkCh chan *newWorkReq @@ -221,10 +200,7 @@ type worker struct { wg sync.WaitGroup - current *environment // An environment for current running cycle. - localUncles map[common.Hash]*types.Block // A set of side blocks generated locally as the possible uncle blocks. - remoteUncles map[common.Hash]*types.Block // A set of side blocks as the possible uncle blocks. - unconfirmed *unconfirmedBlocks // A set of locally mined blocks pending canonicalness confirmations. + current *environment // An environment for current running cycle. mu sync.RWMutex // The lock used to protect the coinbase and extra fields coinbase common.Address @@ -241,13 +217,7 @@ type worker struct { // atomic status counters running atomic.Bool // The indicator whether the consensus engine is running or not. newTxs atomic.Int32 // New arrival transaction count since last sealing work submitting. - - // noempty is the flag used to control whether the feature of pre-seal empty - // block is enabled. The default value is false(pre-seal is enabled by default). - // But in some special scenario the consensus engine will seal blocks instantaneously, - // in this case this feature will add all empty blocks into canonical chain - // non-stop and no real transaction will be included. - noempty atomic.Bool + syncing atomic.Bool // The indicator whether the node is still syncing. // newpayloadTimeout is the maximum timeout allowance for creating payload. // The default value is 2 seconds but node operator can set it to arbitrary @@ -279,15 +249,11 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus chain: eth.BlockChain(), mux: mux, isLocalBlock: isLocalBlock, - localUncles: make(map[common.Hash]*types.Block), - remoteUncles: make(map[common.Hash]*types.Block), - unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), sealingLogAtDepth), coinbase: config.Etherbase, extra: config.ExtraData, pendingTasks: make(map[common.Hash]*task), txsCh: make(chan core.NewTxsEvent, txChanSize), chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), - chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize), newWorkCh: make(chan *newWorkReq), getWorkCh: make(chan *getWorkReq), taskCh: make(chan *task), @@ -297,11 +263,10 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus resubmitIntervalCh: make(chan time.Duration), resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize), } - // Subscribe NewTxsEvent for tx pool - worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh) + // Subscribe for transaction insertion events (whether from network or resurrects) + worker.txsSub = eth.TxPool().SubscribeTransactions(worker.txsCh, true) // Subscribe events for blockchain worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh) - worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh) // Sanitize recommit interval if the user-specified one is too short. recommit := worker.config.Recommit @@ -370,19 +335,9 @@ func (w *worker) setRecommitInterval(interval time.Duration) { } } -// disablePreseal disables pre-sealing feature -func (w *worker) disablePreseal() { - w.noempty.Store(true) -} - -// enablePreseal enables pre-sealing feature -func (w *worker) enablePreseal() { - w.noempty.Store(false) -} - -// pending returns the pending state and corresponding block. +// pending returns the pending state and corresponding block. The returned +// values can be nil in case the pending block is not initialized. func (w *worker) pending() (*types.Block, *state.StateDB) { - // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() if w.snapshotState == nil { @@ -391,17 +346,17 @@ func (w *worker) pending() (*types.Block, *state.StateDB) { return w.snapshotBlock, w.snapshotState.Copy() } -// pendingBlock returns pending block. +// pendingBlock returns pending block. The returned block can be nil in case the +// pending block is not initialized. func (w *worker) pendingBlock() *types.Block { - // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() return w.snapshotBlock } // pendingBlockAndReceipts returns pending block and corresponding receipts. +// The returned values can be nil in case the pending block is not initialized. func (w *worker) pendingBlockAndReceipts() (*types.Block, types.Receipts) { - // return a snapshot to avoid contention on currentMu mutex w.snapshotMu.RLock() defer w.snapshotMu.RUnlock() return w.snapshotBlock, w.snapshotReceipts @@ -467,13 +422,13 @@ func (w *worker) newWorkLoop(recommit time.Duration) { <-timer.C // discard the initial tick // commit aborts in-flight transaction execution with given signal and resubmits a new one. - commit := func(noempty bool, s int32) { + commit := func(s int32) { if interrupt != nil { interrupt.Store(s) } interrupt = new(atomic.Int32) select { - case w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty, timestamp: timestamp}: + case w.newWorkCh <- &newWorkReq{interrupt: interrupt, timestamp: timestamp}: case <-w.exitCh: return } @@ -496,12 +451,12 @@ func (w *worker) newWorkLoop(recommit time.Duration) { case <-w.startCh: clearPending(w.chain.CurrentBlock().Number.Uint64()) timestamp = time.Now().Unix() - commit(false, commitInterruptNewHead) + commit(commitInterruptNewHead) case head := <-w.chainHeadCh: clearPending(head.Block.NumberU64()) timestamp = time.Now().Unix() - commit(false, commitInterruptNewHead) + commit(commitInterruptNewHead) case <-timer.C: // If sealing is running resubmit a new work cycle periodically to pull in @@ -512,7 +467,7 @@ func (w *worker) newWorkLoop(recommit time.Duration) { timer.Reset(recommit) continue } - commit(true, commitInterruptResubmit) + commit(commitInterruptResubmit) } case interval := <-w.resubmitIntervalCh: @@ -558,64 +513,19 @@ func (w *worker) mainLoop() { defer w.wg.Done() defer w.txsSub.Unsubscribe() defer w.chainHeadSub.Unsubscribe() - defer w.chainSideSub.Unsubscribe() defer func() { if w.current != nil { w.current.discard() } }() - cleanTicker := time.NewTicker(time.Second * 10) - defer cleanTicker.Stop() - for { select { case req := <-w.newWorkCh: - w.commitWork(req.interrupt, req.noempty, req.timestamp) + w.commitWork(req.interrupt, req.timestamp) case req := <-w.getWorkCh: - block, fees, err := w.generateWork(req.params) - req.result <- &newPayloadResult{ - err: err, - block: block, - fees: fees, - } - case ev := <-w.chainSideCh: - // Short circuit for duplicate side blocks - if _, exist := w.localUncles[ev.Block.Hash()]; exist { - continue - } - if _, exist := w.remoteUncles[ev.Block.Hash()]; exist { - continue - } - // Add side block to possible uncle block set depending on the author. - if w.isLocalBlock != nil && w.isLocalBlock(ev.Block.Header()) { - w.localUncles[ev.Block.Hash()] = ev.Block - } else { - w.remoteUncles[ev.Block.Hash()] = ev.Block - } - // If our sealing block contains less than 2 uncle blocks, - // add the new uncle block if valid and regenerate a new - // sealing block for higher profit. - if w.isRunning() && w.current != nil && len(w.current.uncles) < 2 { - start := time.Now() - if err := w.commitUncle(w.current, ev.Block.Header()); err == nil { - w.commit(w.current.copy(), nil, true, start) - } - } - - case <-cleanTicker.C: - chainHead := w.chain.CurrentBlock() - for hash, uncle := range w.localUncles { - if uncle.NumberU64()+staleThreshold <= chainHead.Number.Uint64() { - delete(w.localUncles, hash) - } - } - for hash, uncle := range w.remoteUncles { - if uncle.NumberU64()+staleThreshold <= chainHead.Number.Uint64() { - delete(w.remoteUncles, hash) - } - } + req.result <- w.generateWork(req.params) case ev := <-w.txsCh: // Apply transactions to the pending state if we're not sealing @@ -628,12 +538,21 @@ func (w *worker) mainLoop() { if gp := w.current.gasPool; gp != nil && gp.Gas() < params.TxGas { continue } - txs := make(map[common.Address]types.Transactions, len(ev.Txs)) + txs := make(map[common.Address][]*txpool.LazyTransaction, len(ev.Txs)) for _, tx := range ev.Txs { acc, _ := types.Sender(w.current.signer, tx) - txs[acc] = append(txs[acc], tx) + txs[acc] = append(txs[acc], &txpool.LazyTransaction{ + Pool: w.eth.TxPool(), // We don't know where this came from, yolo resolve from everywhere + Hash: tx.Hash(), + Tx: nil, // Do *not* set this! We need to resolve it later to pull blobs in + Time: tx.Time(), + GasFeeCap: tx.GasFeeCap(), + GasTipCap: tx.GasTipCap(), + Gas: tx.Gas(), + BlobGas: tx.BlobGas(), + }) } - txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, w.current.header.BaseFee) + txset := newTransactionsByPriceAndNonce(w.current.signer, txs, w.current.header.BaseFee) tcount := w.current.tcount w.commitTransactions(w.current, txset, nil) @@ -647,7 +566,7 @@ func (w *worker) mainLoop() { // submit sealing work here since all empty submission will be rejected // by clique. Of course the advance sealing(empty submission) is disabled. if w.chainConfig.Clique != nil && w.chainConfig.Clique.Period == 0 { - w.commitWork(nil, true, time.Now().Unix()) + w.commitWork(nil, time.Now().Unix()) } } w.newTxs.Add(int32(len(ev.Txs))) @@ -659,8 +578,6 @@ func (w *worker) mainLoop() { return case <-w.chainHeadSub.Err(): return - case <-w.chainSideSub.Err(): - return } } } @@ -780,9 +697,6 @@ func (w *worker) resultLoop() { // Broadcast the block and announce chain insertion event w.mux.Post(core.NewMinedBlockEvent{Block: block}) - // Insert the block into the set of pending ones to resultLoop for confirmations - w.unconfirmed.Insert(block.NumberU64(), block.Hash()) - case <-w.exitCh: return } @@ -801,49 +715,16 @@ func (w *worker) makeEnv(parent *types.Header, header *types.Header, coinbase co // Note the passed coinbase may be different with header.Coinbase. env := &environment{ - signer: types.MakeSigner(w.chainConfig, header.Number, header.Time), - state: state, - coinbase: coinbase, - ancestors: mapset.NewSet[common.Hash](), - family: mapset.NewSet[common.Hash](), - header: header, - uncles: make(map[common.Hash]*types.Header), - } - // when 08 is processed ancestors contain 07 (quick block) - for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) { - for _, uncle := range ancestor.Uncles() { - env.family.Add(uncle.Hash()) - } - env.family.Add(ancestor.Hash()) - env.ancestors.Add(ancestor.Hash()) + signer: types.MakeSigner(w.chainConfig, header.Number, header.Time), + state: state, + coinbase: coinbase, + header: header, } // Keep track of transactions which return errors so they can be removed env.tcount = 0 return env, nil } -// commitUncle adds the given block to uncle block set, returns error if failed to add. -func (w *worker) commitUncle(env *environment, uncle *types.Header) error { - if w.isTTDReached(env.header) { - return errors.New("ignore uncle for beacon block") - } - hash := uncle.Hash() - if _, exist := env.uncles[hash]; exist { - return errors.New("uncle not unique") - } - if env.header.ParentHash == uncle.ParentHash { - return errors.New("uncle is sibling") - } - if !env.ancestors.Contains(uncle.ParentHash) { - return errors.New("uncle's parent unknown") - } - if env.family.Contains(hash) { - return errors.New("uncle already included") - } - env.uncles[hash] = uncle - return nil -} - // updateSnapshot updates pending snapshot block, receipts and state. func (w *worker) updateSnapshot(env *environment) { w.snapshotMu.Lock() @@ -852,7 +733,7 @@ func (w *worker) updateSnapshot(env *environment) { w.snapshotBlock = types.NewBlock( env.header, env.txs, - env.unclelist(), + nil, env.receipts, trie.NewStackTrie(nil), ) @@ -861,6 +742,44 @@ func (w *worker) updateSnapshot(env *environment) { } func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*types.Log, error) { + if tx.Type() == types.BlobTxType { + return w.commitBlobTransaction(env, tx) + } + receipt, err := w.applyTransaction(env, tx) + if err != nil { + return nil, err + } + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) + return receipt.Logs, nil +} + +func (w *worker) commitBlobTransaction(env *environment, tx *types.Transaction) ([]*types.Log, error) { + sc := tx.BlobTxSidecar() + if sc == nil { + panic("blob transaction without blobs in miner") + } + // Checking against blob gas limit: It's kind of ugly to perform this check here, but there + // isn't really a better place right now. The blob gas limit is checked at block validation time + // and not during execution. This means core.ApplyTransaction will not return an error if the + // tx has too many blobs. So we have to explicitly check it here. + if (env.blobs+len(sc.Blobs))*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock { + return nil, errors.New("max data blobs reached") + } + receipt, err := w.applyTransaction(env, tx) + if err != nil { + return nil, err + } + env.txs = append(env.txs, tx.WithoutBlobTxSidecar()) + env.receipts = append(env.receipts, receipt) + env.sidecars = append(env.sidecars, sc) + env.blobs += len(sc.Blobs) + *env.header.BlobGasUsed += receipt.BlobGasUsed + return receipt.Logs, nil +} + +// applyTransaction runs the transaction. If execution fails, state and gas pool are reverted. +func (w *worker) applyTransaction(env *environment, tx *types.Transaction) (*types.Receipt, error) { var ( snap = env.state.Snapshot() gp = env.gasPool.Gas() @@ -869,15 +788,11 @@ func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]* if err != nil { env.state.RevertToSnapshot(snap) env.gasPool.SetGas(gp) - return nil, err } - env.txs = append(env.txs, tx) - env.receipts = append(env.receipts, receipt) - - return receipt.Logs, nil + return receipt, err } -func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByPriceAndNonce, interrupt *atomic.Int32) error { +func (w *worker) commitTransactions(env *environment, txs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error { gasLimit := env.header.GasLimit if env.gasPool == nil { env.gasPool = new(core.GasPool).AddGas(gasLimit) @@ -897,10 +812,28 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP break } // Retrieve the next transaction and abort if all done. - tx := txs.Peek() - if tx == nil { + ltx := txs.Peek() + if ltx == nil { break } + // If we don't have enough space for the next transaction, skip the account. + if env.gasPool.Gas() < ltx.Gas { + log.Trace("Not enough gas left for transaction", "hash", ltx.Hash, "left", env.gasPool.Gas(), "needed", ltx.Gas) + txs.Pop() + continue + } + if left := uint64(params.MaxBlobGasPerBlock - env.blobs*params.BlobTxBlobGasPerBlob); left < ltx.BlobGas { + log.Trace("Not enough blob gas left for transaction", "hash", ltx.Hash, "left", left, "needed", ltx.BlobGas) + txs.Pop() + continue + } + // Transaction seems to fit, pull it up from the pool + tx := ltx.Resolve() + if tx == nil { + log.Trace("Ignoring evicted transaction", "hash", ltx.Hash) + txs.Pop() + continue + } // Error may be ignored here. The error has already been checked // during transaction acceptance is the transaction pool. from, _ := types.Sender(env.signer, tx) @@ -908,8 +841,7 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP // Check whether the tx is replay protected. If we're not in the EIP155 hf // phase, start ignoring the sender until we do. if tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) { - log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block) - + log.Trace("Ignoring replay protected transaction", "hash", ltx.Hash, "eip155", w.chainConfig.EIP155Block) txs.Pop() continue } @@ -920,7 +852,7 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP switch { case errors.Is(err, core.ErrNonceTooLow): // New head notification data race between the transaction pool and miner, shift - log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce()) + log.Trace("Skipping transaction with low nonce", "hash", ltx.Hash, "sender", from, "nonce", tx.Nonce()) txs.Shift() case errors.Is(err, nil): @@ -932,7 +864,7 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP default: // Transaction is regarded as invalid, drop all consecutive transactions from // the same sender because of `nonce-too-high` clause. - log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err) + log.Debug("Transaction failed, account skipped", "hash", ltx.Hash, "err", err) txs.Pop() } } @@ -962,7 +894,7 @@ type generateParams struct { coinbase common.Address // The fee recipient address for including transaction random common.Hash // The randomness generated by beacon chain, empty before the merge withdrawals types.Withdrawals // List of withdrawals to include in block. - noUncle bool // Flag whether the uncle block inclusion is allowed + beaconRoot *common.Hash // The beacon root (cancun field). noTxs bool // Flag whether an empty block without any transaction is expected } @@ -1009,12 +941,26 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { } // Set baseFee and GasLimit if we are on an EIP-1559 chain if w.chainConfig.IsLondon(header.Number) { - header.BaseFee = misc.CalcBaseFee(w.chainConfig, parent) + header.BaseFee = eip1559.CalcBaseFee(w.chainConfig, parent) if !w.chainConfig.IsLondon(parent.Number) { parentGasLimit := parent.GasLimit * w.chainConfig.ElasticityMultiplier() header.GasLimit = core.CalcGasLimit(parentGasLimit, w.config.GasCeil) } } + prevArbosVersion := types.DeserializeHeaderExtraInformation(parent).ArbOSFormatVersion + // Apply EIP-4844, EIP-4788. + if w.chainConfig.IsCancun(header.Number, header.Time, prevArbosVersion) { + var excessBlobGas uint64 + if w.chainConfig.IsCancun(parent.Number, parent.Time, prevArbosVersion) { + excessBlobGas = eip4844.CalcExcessBlobGas(*parent.ExcessBlobGas, *parent.BlobGasUsed) + } else { + // For the first post-fork block, both parent.data_gas_used and parent.excess_data_gas are evaluated as 0 + excessBlobGas = eip4844.CalcExcessBlobGas(0, 0) + } + header.BlobGasUsed = new(uint64) + header.ExcessBlobGas = &excessBlobGas + header.ParentBeaconRoot = genParams.beaconRoot + } // Run the consensus preparation with the default or customized consensus engine. if err := w.engine.Prepare(w.chain, header); err != nil { log.Error("Failed to prepare header for sealing", "err", err) @@ -1028,23 +974,10 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { log.Error("Failed to create sealing context", "err", err) return nil, err } - // Accumulate the uncles for the sealing work only if it's allowed. - if !genParams.noUncle { - commitUncles := func(blocks map[common.Hash]*types.Block) { - for hash, uncle := range blocks { - if len(env.uncles) == 2 { - break - } - if err := w.commitUncle(env, uncle.Header()); err != nil { - log.Trace("Possible uncle rejected", "hash", hash, "reason", err) - } else { - log.Debug("Committing new uncle to block", "hash", hash) - } - } - } - // Prefer to locally generated uncle - commitUncles(w.localUncles) - commitUncles(w.remoteUncles) + if header.ParentBeaconRoot != nil { + context := core.NewEVMBlockContext(header, w.chain, nil) + vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, w.chainConfig, vm.Config{}) + core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state) } return env, nil } @@ -1053,24 +986,26 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { // into the given sealing block. The transaction selection and ordering strategy can // be customized with the plugin in the future. func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) error { - // Split the pending transactions into locals and remotes - // Fill the block with all available pending transactions. pending := w.eth.TxPool().Pending(true) - localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending + + // Split the pending transactions into locals and remotes. + localTxs, remoteTxs := make(map[common.Address][]*txpool.LazyTransaction), pending for _, account := range w.eth.TxPool().Locals() { if txs := remoteTxs[account]; len(txs) > 0 { delete(remoteTxs, account) localTxs[account] = txs } } + + // Fill the block with all available pending transactions. if len(localTxs) > 0 { - txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee) + txs := newTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee) if err := w.commitTransactions(env, txs, interrupt); err != nil { return err } } if len(remoteTxs) > 0 { - txs := types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, env.header.BaseFee) + txs := newTransactionsByPriceAndNonce(env.signer, remoteTxs, env.header.BaseFee) if err := w.commitTransactions(env, txs, interrupt); err != nil { return err } @@ -1079,10 +1014,10 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err } // generateWork generates a sealing block based on the given parameters. -func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, error) { +func (w *worker) generateWork(params *generateParams) *newPayloadResult { work, err := w.prepareWork(params) if err != nil { - return nil, nil, err + return &newPayloadResult{err: err} } defer work.discard() @@ -1098,16 +1033,24 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, e log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(w.newpayloadTimeout)) } } - block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts, params.withdrawals) + block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, nil, work.receipts, params.withdrawals) if err != nil { - return nil, nil, err + return &newPayloadResult{err: err} + } + return &newPayloadResult{ + block: block, + fees: totalFees(block, work.receipts), + sidecars: work.sidecars, } - return block, totalFees(block, work.receipts), nil } // commitWork generates several new sealing tasks based on the parent block // and submit them to the sealer. -func (w *worker) commitWork(interrupt *atomic.Int32, noempty bool, timestamp int64) { +func (w *worker) commitWork(interrupt *atomic.Int32, timestamp int64) { + // Abort committing if node is still syncing + if w.syncing.Load() { + return + } start := time.Now() // Set the coinbase if the worker is running or it's required @@ -1126,11 +1069,6 @@ func (w *worker) commitWork(interrupt *atomic.Int32, noempty bool, timestamp int if err != nil { return } - // Create an empty block based on temporary copied state for - // sealing in advance without waiting block execution finished. - if !noempty && !w.noempty.Load() { - w.commit(work.copy(), nil, false, start) - } // Fill pending transactions from the txpool into the block. err = w.fillTransactions(interrupt, work) switch { @@ -1184,7 +1122,7 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti // https://github.com/ethereum/go-ethereum/issues/24299 env := env.copy() // Withdrawals are set to nil here, because this is only called in PoW. - block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, env.unclelist(), env.receipts, nil) + block, err := w.engine.FinalizeAndAssemble(w.chain, env.header, env.state, env.txs, nil, env.receipts, nil) if err != nil { return err } @@ -1192,13 +1130,10 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti if !w.isTTDReached(block.Header()) { select { case w.taskCh <- &task{receipts: env.receipts, state: env.state, block: block, createdAt: time.Now()}: - w.unconfirmed.Shift(block.NumberU64() - 1) - fees := totalFees(block, env.receipts) feesInEther := new(big.Float).Quo(new(big.Float).SetInt(fees), big.NewFloat(params.Ether)) log.Info("Commit new sealing work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), - "uncles", len(env.uncles), "txs", env.tcount, - "gas", block.GasUsed(), "fees", feesInEther, + "txs", env.tcount, "gas", block.GasUsed(), "fees", feesInEther, "elapsed", common.PrettyDuration(time.Since(start))) case <-w.exitCh: @@ -1215,29 +1150,16 @@ func (w *worker) commit(env *environment, interval func(), update bool, start ti // getSealingBlock generates the sealing block based on the given parameters. // The generation result will be passed back via the given channel no matter // the generation itself succeeds or not. -func (w *worker) getSealingBlock(parent common.Hash, timestamp uint64, coinbase common.Address, random common.Hash, withdrawals types.Withdrawals, noTxs bool) (*types.Block, *big.Int, error) { +func (w *worker) getSealingBlock(params *generateParams) *newPayloadResult { req := &getWorkReq{ - params: &generateParams{ - timestamp: timestamp, - forceTime: true, - parentHash: parent, - coinbase: coinbase, - random: random, - withdrawals: withdrawals, - noUncle: true, - noTxs: noTxs, - }, + params: params, result: make(chan *newPayloadResult, 1), } select { case w.getWorkCh <- req: - result := <-req.result - if result.err != nil { - return nil, nil, result.err - } - return result.block, result.fees, nil + return <-req.result case <-w.exitCh: - return nil, nil, errors.New("miner closed") + return &newPayloadResult{err: errors.New("miner closed")} } } @@ -1258,14 +1180,6 @@ func copyReceipts(receipts []*types.Receipt) []*types.Receipt { return result } -// postSideBlock fires a side chain event, only use it for testing. -func (w *worker) postSideBlock(event core.ChainSideEvent) { - select { - case w.chainSideCh <- event: - case <-w.exitCh: - } -} - // totalFees computes total consumed miner fees in Wei. Block transactions and receipts have to have the same order. func totalFees(block *types.Block, receipts []*types.Receipt) *big.Int { feesWei := new(big.Int) diff --git a/miner/worker_test.go b/miner/worker_test.go index 6db32d3eb0c3..33076cc174df 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -17,8 +17,6 @@ package miner import ( - "crypto/rand" - "errors" "math/big" "sync/atomic" "testing" @@ -31,8 +29,8 @@ import ( "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -52,7 +50,7 @@ const ( var ( // Test chain configurations - testTxPoolConfig txpool.Config + testTxPoolConfig legacypool.Config ethashChainConfig *params.ChainConfig cliqueChainConfig *params.ChainConfig @@ -75,7 +73,7 @@ var ( ) func init() { - testTxPoolConfig = txpool.DefaultConfig + testTxPoolConfig = legacypool.DefaultConfig testTxPoolConfig.Journal = "" ethashChainConfig = new(params.ChainConfig) *ethashChainConfig = *params.TestChainConfig @@ -109,11 +107,10 @@ func init() { // testWorkerBackend implements worker.Backend interfaces and wraps all information needed during the testing. type testWorkerBackend struct { - db ethdb.Database - txPool *txpool.TxPool - chain *core.BlockChain - genesis *core.Genesis - uncleBlock *types.Block + db ethdb.Database + txPool *txpool.TxPool + chain *core.BlockChain + genesis *core.Genesis } func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, n int) *testWorkerBackend { @@ -136,58 +133,19 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine if err != nil { t.Fatalf("core.NewBlockChain failed: %v", err) } - txpool := txpool.NewTxPool(testTxPoolConfig, chainConfig, chain) + pool := legacypool.New(testTxPoolConfig, chain) + txpool, _ := txpool.New(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), chain, []txpool.SubPool{pool}) - // Generate a small n-block chain and an uncle block for it - var uncle *types.Block - if n > 0 { - genDb, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, func(i int, gen *core.BlockGen) { - gen.SetCoinbase(testBankAddress) - }) - if _, err := chain.InsertChain(blocks); err != nil { - t.Fatalf("failed to insert origin chain: %v", err) - } - parent := chain.GetBlockByHash(chain.CurrentBlock().ParentHash) - blocks, _ = core.GenerateChain(chainConfig, parent, engine, genDb, 1, func(i int, gen *core.BlockGen) { - gen.SetCoinbase(testUserAddress) - }) - uncle = blocks[0] - } else { - _, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, 1, func(i int, gen *core.BlockGen) { - gen.SetCoinbase(testUserAddress) - }) - uncle = blocks[0] - } return &testWorkerBackend{ - db: db, - chain: chain, - txPool: txpool, - genesis: gspec, - uncleBlock: uncle, + db: db, + chain: chain, + txPool: txpool, + genesis: gspec, } } func (b *testWorkerBackend) BlockChain() *core.BlockChain { return b.chain } func (b *testWorkerBackend) TxPool() *txpool.TxPool { return b.txPool } -func (b *testWorkerBackend) StateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) { - return nil, errors.New("not supported") -} - -func (b *testWorkerBackend) newRandomUncle() *types.Block { - var parent *types.Block - cur := b.chain.CurrentBlock() - if cur.Number.Uint64() == 0 { - parent = b.chain.Genesis() - } else { - parent = b.chain.GetBlockByHash(b.chain.CurrentBlock().ParentHash) - } - blocks, _ := core.GenerateChain(b.chain.Config(), parent, b.chain.Engine(), b.db, 1, func(i int, gen *core.BlockGen) { - var addr = make([]byte, common.AddressLength) - rand.Read(addr) - gen.SetCoinbase(common.BytesToAddress(addr)) - }) - return blocks[0] -} func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { var tx *types.Transaction @@ -202,31 +160,21 @@ func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) { backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks) - backend.txPool.AddLocals(pendingTxs) + backend.txPool.Add(pendingTxs, true, false) w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false) w.setEtherbase(testBankAddress) return w, backend } -func TestGenerateBlockAndImportClique(t *testing.T) { - testGenerateBlockAndImport(t, true) -} - -func testGenerateBlockAndImport(t *testing.T, isClique bool) { +func TestGenerateAndImportBlock(t *testing.T) { var ( - engine consensus.Engine - chainConfig params.ChainConfig - db = rawdb.NewMemoryDatabase() + db = rawdb.NewMemoryDatabase() + config = *params.AllCliqueProtocolChanges ) - if isClique { - chainConfig = *params.AllCliqueProtocolChanges - chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} - engine = clique.New(chainConfig.Clique, db) - } else { - chainConfig = *params.AllEthashProtocolChanges - engine = ethash.NewFaker() - } - w, b := newTestWorker(t, &chainConfig, engine, db, 0) + config.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + engine := clique.New(config.Clique, db) + + w, b := newTestWorker(t, &config, engine, db, 0) defer w.close() // This test chain imports the mined blocks. @@ -246,10 +194,8 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) { w.start() for i := 0; i < 5; i++ { - b.txPool.AddLocal(b.newRandomTx(true)) - b.txPool.AddLocal(b.newRandomTx(false)) - w.postSideBlock(core.ChainSideEvent{Block: b.newRandomUncle()}) - w.postSideBlock(core.ChainSideEvent{Block: b.newRandomUncle()}) + b.txPool.Add([]*types.Transaction{b.newRandomTx(true)}, true, false) + b.txPool.Add([]*types.Transaction{b.newRandomTx(false)}, true, false) select { case ev := <-sub.Chan(): @@ -276,17 +222,10 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens w, _ := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) defer w.close() - var ( - taskIndex int - taskCh = make(chan struct{}, 2) - ) - checkEqual := func(t *testing.T, task *task, index int) { - // The first empty work without any txs included - receiptLen, balance := 0, big.NewInt(0) - if index == 1 { - // The second full work with 1 tx included - receiptLen, balance = 1, big.NewInt(1000) - } + taskCh := make(chan struct{}, 2) + checkEqual := func(t *testing.T, task *task) { + // The work should contain 1 tx + receiptLen, balance := 1, big.NewInt(1000) if len(task.receipts) != receiptLen { t.Fatalf("receipt number mismatch: have %d, want %d", len(task.receipts), receiptLen) } @@ -296,8 +235,7 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens } w.newTaskHook = func(task *task) { if task.block.NumberU64() == 1 { - checkEqual(t, task, taskIndex) - taskIndex += 1 + checkEqual(t, task) taskCh <- struct{}{} } } @@ -306,122 +244,9 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens time.Sleep(100 * time.Millisecond) } w.start() // Start mining! - for i := 0; i < 2; i += 1 { - select { - case <-taskCh: - case <-time.NewTimer(3 * time.Second).C: - t.Error("new task timeout") - } - } -} - -func TestStreamUncleBlock(t *testing.T) { - ethash := ethash.NewFaker() - defer ethash.Close() - - w, b := newTestWorker(t, ethashChainConfig, ethash, rawdb.NewMemoryDatabase(), 1) - defer w.close() - - var taskCh = make(chan struct{}, 3) - - taskIndex := 0 - w.newTaskHook = func(task *task) { - if task.block.NumberU64() == 2 { - // The first task is an empty task, the second - // one has 1 pending tx, the third one has 1 tx - // and 1 uncle. - if taskIndex == 2 { - have := task.block.Header().UncleHash - want := types.CalcUncleHash([]*types.Header{b.uncleBlock.Header()}) - if have != want { - t.Errorf("uncle hash mismatch: have %s, want %s", have.Hex(), want.Hex()) - } - } - taskCh <- struct{}{} - taskIndex += 1 - } - } - w.skipSealHook = func(task *task) bool { - return true - } - w.fullTaskHook = func() { - time.Sleep(100 * time.Millisecond) - } - w.start() - - for i := 0; i < 2; i += 1 { - select { - case <-taskCh: - case <-time.NewTimer(time.Second).C: - t.Error("new task timeout") - } - } - - w.postSideBlock(core.ChainSideEvent{Block: b.uncleBlock}) - select { case <-taskCh: - case <-time.NewTimer(time.Second).C: - t.Error("new task timeout") - } -} - -func TestRegenerateMiningBlockEthash(t *testing.T) { - testRegenerateMiningBlock(t, ethashChainConfig, ethash.NewFaker()) -} - -func TestRegenerateMiningBlockClique(t *testing.T) { - testRegenerateMiningBlock(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase())) -} - -func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) { - defer engine.Close() - - w, b := newTestWorker(t, chainConfig, engine, rawdb.NewMemoryDatabase(), 0) - defer w.close() - - var taskCh = make(chan struct{}, 3) - - taskIndex := 0 - w.newTaskHook = func(task *task) { - if task.block.NumberU64() == 1 { - // The first task is an empty task, the second - // one has 1 pending tx, the third one has 2 txs - if taskIndex == 2 { - receiptLen, balance := 2, big.NewInt(2000) - if len(task.receipts) != receiptLen { - t.Errorf("receipt number mismatch: have %d, want %d", len(task.receipts), receiptLen) - } - if task.state.GetBalance(testUserAddress).Cmp(balance) != 0 { - t.Errorf("account balance mismatch: have %d, want %d", task.state.GetBalance(testUserAddress), balance) - } - } - taskCh <- struct{}{} - taskIndex += 1 - } - } - w.skipSealHook = func(task *task) bool { - return true - } - w.fullTaskHook = func() { - time.Sleep(100 * time.Millisecond) - } - - w.start() - // Ignore the first two works - for i := 0; i < 2; i += 1 { - select { - case <-taskCh: - case <-time.NewTimer(time.Second).C: - t.Error("new task timeout") - } - } - b.txPool.AddLocals(newTxs) - time.Sleep(time.Second) - - select { - case <-taskCh: - case <-time.NewTimer(time.Second).C: + case <-time.NewTimer(3 * time.Second).C: t.Error("new task timeout") } } @@ -542,7 +367,6 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co defer w.close() w.setExtra([]byte{0x01, 0x02}) - w.postSideBlock(core.ChainSideEvent{Block: b.uncleBlock}) w.skipSealHook = func(task *task) bool { return true @@ -557,9 +381,6 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co // is even smaller than parent block's. It's OK. t.Logf("Invalid timestamp, want %d, get %d", timestamp, block.Time()) } - if len(block.Uncles()) != 0 { - t.Error("Unexpected uncle block") - } _, isClique := engine.(*clique.Clique) if !isClique { if len(block.Extra()) != 2 { @@ -631,32 +452,50 @@ func testGetSealingWork(t *testing.T, chainConfig *params.ChainConfig, engine co // This API should work even when the automatic sealing is not enabled for _, c := range cases { - block, _, err := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random, nil, false) + r := w.getSealingBlock(&generateParams{ + parentHash: c.parent, + timestamp: timestamp, + coinbase: c.coinbase, + random: c.random, + withdrawals: nil, + beaconRoot: nil, + noTxs: false, + forceTime: true, + }) if c.expectErr { - if err == nil { + if r.err == nil { t.Error("Expect error but get nil") } } else { - if err != nil { - t.Errorf("Unexpected error %v", err) + if r.err != nil { + t.Errorf("Unexpected error %v", r.err) } - assertBlock(block, c.expectNumber, c.coinbase, c.random) + assertBlock(r.block, c.expectNumber, c.coinbase, c.random) } } // This API should work even when the automatic sealing is enabled w.start() for _, c := range cases { - block, _, err := w.getSealingBlock(c.parent, timestamp, c.coinbase, c.random, nil, false) + r := w.getSealingBlock(&generateParams{ + parentHash: c.parent, + timestamp: timestamp, + coinbase: c.coinbase, + random: c.random, + withdrawals: nil, + beaconRoot: nil, + noTxs: false, + forceTime: true, + }) if c.expectErr { - if err == nil { + if r.err == nil { t.Error("Expect error but get nil") } } else { - if err != nil { - t.Errorf("Unexpected error %v", err) + if r.err != nil { + t.Errorf("Unexpected error %v", r.err) } - assertBlock(block, c.expectNumber, c.coinbase, c.random) + assertBlock(r.block, c.expectNumber, c.coinbase, c.random) } } } diff --git a/node/api.go b/node/api.go index 15892a270b66..f81f394beb24 100644 --- a/node/api.go +++ b/node/api.go @@ -176,6 +176,10 @@ func (api *adminAPI) StartHTTP(host *string, port *int, cors *string, apis *stri CorsAllowedOrigins: api.node.config.HTTPCors, Vhosts: api.node.config.HTTPVirtualHosts, Modules: api.node.config.HTTPModules, + rpcEndpointConfig: rpcEndpointConfig{ + batchItemLimit: api.node.config.BatchRequestLimit, + batchResponseSizeLimit: api.node.config.BatchResponseMaxSize, + }, } if cors != nil { config.CorsAllowedOrigins = nil @@ -250,6 +254,10 @@ func (api *adminAPI) StartWS(host *string, port *int, allowedOrigins *string, ap Modules: api.node.config.WSModules, Origins: api.node.config.WSOrigins, // ExposeAll: api.node.config.WSExposeAll, + rpcEndpointConfig: rpcEndpointConfig{ + batchItemLimit: api.node.config.BatchRequestLimit, + batchResponseSizeLimit: api.node.config.BatchResponseMaxSize, + }, } if apis != nil { config.Modules = nil diff --git a/node/config.go b/node/config.go index 472836e6d4e9..0aef1f18b26b 100644 --- a/node/config.go +++ b/node/config.go @@ -19,6 +19,7 @@ package node import ( "crypto/ecdsa" "fmt" + "net" "os" "path/filepath" "runtime" @@ -207,6 +208,12 @@ type Config struct { // AllowUnprotectedTxs allows non EIP-155 protected transactions to be send over RPC. AllowUnprotectedTxs bool `toml:",omitempty"` + // BatchRequestLimit is the maximum number of requests in a batch. + BatchRequestLimit int `toml:",omitempty"` + + // BatchResponseMaxSize is the maximum number of bytes returned from a batched rpc call. + BatchResponseMaxSize int `toml:",omitempty"` + // JWTSecret is the path to the hex-encoded jwt secret. JWTSecret string `toml:",omitempty"` @@ -267,7 +274,7 @@ func (c *Config) HTTPEndpoint() string { if c.HTTPHost == "" { return "" } - return fmt.Sprintf("%s:%d", c.HTTPHost, c.HTTPPort) + return net.JoinHostPort(c.HTTPHost, fmt.Sprintf("%d", c.HTTPPort)) } // DefaultHTTPEndpoint returns the HTTP endpoint used by default. @@ -282,7 +289,7 @@ func (c *Config) WSEndpoint() string { if c.WSHost == "" { return "" } - return fmt.Sprintf("%s:%d", c.WSHost, c.WSPort) + return net.JoinHostPort(c.WSHost, fmt.Sprintf("%d", c.WSPort)) } // DefaultWSEndpoint returns the websocket endpoint used by default. diff --git a/node/defaults.go b/node/defaults.go index 6624ba57a504..2aade210c89d 100644 --- a/node/defaults.go +++ b/node/defaults.go @@ -36,6 +36,13 @@ const ( DefaultAuthPort = 8551 // Default port for the authenticated apis ) +const ( + // Engine API batch limits: these are not configurable by users, and should cover the + // needs of all CLs. + engineAPIBatchItemLimit = 2000 + engineAPIBatchResponseSizeLimit = 250 * 1000 * 1000 +) + var ( DefaultAuthCors = []string{"localhost"} // Default cors domain for the authenticated apis DefaultAuthVhosts = []string{"localhost"} // Default virtual hosts for the authenticated apis @@ -46,19 +53,21 @@ var ( // DefaultConfig contains reasonable default settings. var DefaultConfig = Config{ - DataDir: DefaultDataDir(), - HTTPPort: DefaultHTTPPort, - AuthAddr: DefaultAuthHost, - AuthPort: DefaultAuthPort, - AuthVirtualHosts: DefaultAuthVhosts, - AuthModules: DefaultAuthModules, - AuthOrigins: DefaultAuthOrigins, - HTTPModules: []string{"net", "web3"}, - HTTPVirtualHosts: []string{"localhost"}, - HTTPTimeouts: rpc.DefaultHTTPTimeouts, - WSPort: DefaultWSPort, - WSModules: []string{"net", "web3"}, - GraphQLVirtualHosts: []string{"localhost"}, + DataDir: DefaultDataDir(), + HTTPPort: DefaultHTTPPort, + AuthAddr: DefaultAuthHost, + AuthPort: DefaultAuthPort, + AuthVirtualHosts: DefaultAuthVhosts, + AuthModules: DefaultAuthModules, + AuthOrigins: DefaultAuthOrigins, + HTTPModules: []string{"net", "web3"}, + HTTPVirtualHosts: []string{"localhost"}, + HTTPTimeouts: rpc.DefaultHTTPTimeouts, + WSPort: DefaultWSPort, + WSModules: []string{"net", "web3"}, + BatchRequestLimit: 1000, + BatchResponseMaxSize: 25 * 1000 * 1000, + GraphQLVirtualHosts: []string{"localhost"}, P2P: p2p.Config{ ListenAddr: ":30303", MaxPeers: 50, diff --git a/node/node.go b/node/node.go index c1530b6371ff..8b6d28e7690a 100644 --- a/node/node.go +++ b/node/node.go @@ -69,6 +69,8 @@ type Node struct { inprocHandler *rpc.Server // In-process RPC request handler to process the API requests databases map[*closeTrackingDB]struct{} // All open databases + + apiFilter map[string]bool // Whitelisting API methods } const ( @@ -105,10 +107,11 @@ func New(conf *Config) (*Node, error) { if strings.HasSuffix(conf.Name, ".ipc") { return nil, errors.New(`Config.Name cannot end in ".ipc"`) } - + server := rpc.NewServer() + server.SetBatchLimits(conf.BatchRequestLimit, conf.BatchResponseMaxSize) node := &Node{ config: conf, - inprocHandler: rpc.NewServer(), + inprocHandler: server, eventmux: new(event.TypeMux), log: conf.Logger, stop: make(chan struct{}), @@ -378,6 +381,11 @@ func (n *Node) obtainJWTSecret(cliParam string) ([]byte, error) { return jwtSecret, nil } +// ApplyAPIFilter is the first step in whitelisting given rpc methods inside apiFilter +func (n *Node) ApplyAPIFilter(apiFilter map[string]bool) { + n.apiFilter = apiFilter +} + // startRPC is a helper method to configure all the various RPC endpoints during node // startup. It's not meant to be called at any time afterwards as it makes certain // assumptions about the state of the node. @@ -409,6 +417,12 @@ func (n *Node) startRPC() error { openAPIs, allAPIs = n.getAPIs() ) + rpcConfig := rpcEndpointConfig{ + batchItemLimit: n.config.BatchRequestLimit, + batchResponseSizeLimit: n.config.BatchResponseMaxSize, + apiFilter: n.apiFilter, + } + initHttp := func(server *httpServer, port int) error { if err := server.setListenAddr(n.config.HTTPHost, port); err != nil { return err @@ -418,6 +432,7 @@ func (n *Node) startRPC() error { Vhosts: n.config.HTTPVirtualHosts, Modules: n.config.HTTPModules, prefix: n.config.HTTPPathPrefix, + rpcEndpointConfig: rpcConfig, }); err != nil { return err } @@ -431,9 +446,10 @@ func (n *Node) startRPC() error { return err } if err := server.enableWS(openAPIs, wsConfig{ - Modules: n.config.WSModules, - Origins: n.config.WSOrigins, - prefix: n.config.WSPathPrefix, + Modules: n.config.WSModules, + Origins: n.config.WSOrigins, + prefix: n.config.WSPathPrefix, + rpcEndpointConfig: rpcConfig, }); err != nil { return err } @@ -447,26 +463,32 @@ func (n *Node) startRPC() error { if err := server.setListenAddr(n.config.AuthAddr, port); err != nil { return err } + sharedConfig := rpcEndpointConfig{ + jwtSecret: secret, + batchItemLimit: engineAPIBatchItemLimit, + batchResponseSizeLimit: engineAPIBatchResponseSizeLimit, + } if err := server.enableRPC(allAPIs, httpConfig{ CorsAllowedOrigins: DefaultAuthCors, Vhosts: n.config.AuthVirtualHosts, Modules: n.config.AuthModules, prefix: DefaultAuthPrefix, - jwtSecret: secret, + rpcEndpointConfig: sharedConfig, }); err != nil { return err } servers = append(servers, server) + // Enable auth via WS server = n.wsServerForPort(port, true) if err := server.setListenAddr(n.config.AuthAddr, port); err != nil { return err } if err := server.enableWS(allAPIs, wsConfig{ - Modules: n.config.AuthModules, - Origins: n.config.AuthOrigins, - prefix: DefaultAuthPrefix, - jwtSecret: secret, + Modules: n.config.AuthModules, + Origins: n.config.AuthOrigins, + prefix: DefaultAuthPrefix, + rpcEndpointConfig: sharedConfig, }); err != nil { return err } @@ -621,8 +643,8 @@ func (n *Node) RegisterHandler(name, path string, handler http.Handler) { } // Attach creates an RPC client attached to an in-process API handler. -func (n *Node) Attach() (*rpc.Client, error) { - return rpc.DialInProc(n.inprocHandler), nil +func (n *Node) Attach() *rpc.Client { + return rpc.DialInProc(n.inprocHandler) } // RPCHandler returns the in-process RPC request handler. diff --git a/node/rpcstack.go b/node/rpcstack.go index 16d51b337181..b88025de4ac9 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -41,15 +41,22 @@ type httpConfig struct { CorsAllowedOrigins []string Vhosts []string prefix string // path prefix on which to mount http handler - jwtSecret []byte // optional JWT secret + rpcEndpointConfig } // wsConfig is the JSON-RPC/Websocket configuration type wsConfig struct { - Origins []string - Modules []string - prefix string // path prefix on which to mount ws handler - jwtSecret []byte // optional JWT secret + Origins []string + Modules []string + prefix string // path prefix on which to mount ws handler + rpcEndpointConfig +} + +type rpcEndpointConfig struct { + jwtSecret []byte // optional JWT secret + batchItemLimit int + batchResponseSizeLimit int + apiFilter map[string]bool } type rpcHandler struct { @@ -106,7 +113,7 @@ func (h *httpServer) setListenAddr(host string, port int) error { } h.host, h.port = host, port - h.endpoint = fmt.Sprintf("%s:%d", host, port) + h.endpoint = net.JoinHostPort(host, fmt.Sprintf("%d", port)) return nil } @@ -302,6 +309,8 @@ func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error { // Create RPC server and handler. srv := rpc.NewServer() + srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit) + srv.ApplyAPIFilter(config.apiFilter) if err := RegisterApis(apis, config.Modules, srv); err != nil { return err } @@ -339,6 +348,8 @@ func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error { } // Create RPC server and handler. srv := rpc.NewServer() + srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit) + srv.ApplyAPIFilter(config.apiFilter) if err := RegisterApis(apis, config.Modules, srv); err != nil { return err } diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go index 0790dddec411..e41cc51ad318 100644 --- a/node/rpcstack_test.go +++ b/node/rpcstack_test.go @@ -339,8 +339,10 @@ func TestJWT(t *testing.T) { ss, _ := jwt.NewWithClaims(method, testClaim(input)).SignedString(secret) return ss } - srv := createAndStartServer(t, &httpConfig{jwtSecret: []byte("secret")}, - true, &wsConfig{Origins: []string{"*"}, jwtSecret: []byte("secret")}, nil) + cfg := rpcEndpointConfig{jwtSecret: []byte("secret")} + httpcfg := &httpConfig{rpcEndpointConfig: cfg} + wscfg := &wsConfig{Origins: []string{"*"}, rpcEndpointConfig: cfg} + srv := createAndStartServer(t, httpcfg, true, wscfg, nil) wsUrl := fmt.Sprintf("ws://%v", srv.listenAddr()) htUrl := fmt.Sprintf("http://%v", srv.listenAddr()) diff --git a/p2p/dial.go b/p2p/dial.go index 134e6e2eae1a..5e4ab1d50dcc 100644 --- a/p2p/dial.go +++ b/p2p/dial.go @@ -521,13 +521,14 @@ func (t *dialTask) resolve(d *dialScheduler) bool { // dial performs the actual connection attempt. func (t *dialTask) dial(d *dialScheduler, dest *enode.Node) error { + dialMeter.Mark(1) fd, err := d.dialer.Dial(d.ctx, t.dest) if err != nil { d.log.Trace("Dial error", "id", t.dest.ID(), "addr", nodeAddr(t.dest), "conn", t.flags, "err", cleanupDialErr(err)) + dialConnectionError.Mark(1) return &dialError{err} } - mfd := newMeteredConn(fd, false, &net.TCPAddr{IP: dest.IP(), Port: dest.TCP()}) - return d.setupFunc(mfd, t.flags, dest) + return d.setupFunc(newMeteredConn(fd), t.flags, dest) } func (t *dialTask) String() string { diff --git a/p2p/discover/common.go b/p2p/discover/common.go index c36e8dcc3a71..c9f0477defeb 100644 --- a/p2p/discover/common.go +++ b/p2p/discover/common.go @@ -19,6 +19,7 @@ package discover import ( "crypto/ecdsa" "net" + "time" "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/log" @@ -35,29 +36,39 @@ type UDPConn interface { LocalAddr() net.Addr } -type V5Config struct { - ProtocolID *[6]byte -} - // Config holds settings for the discovery listener. type Config struct { // These settings are required and configure the UDP listener: PrivateKey *ecdsa.PrivateKey - // These settings are optional: + // All remaining settings are optional. + + // Packet handling configuration: NetRestrict *netutil.Netlist // list of allowed IP networks - Bootnodes []*enode.Node // list of bootstrap nodes Unhandled chan<- ReadPacket // unhandled packets are sent on this channel - Log log.Logger // if set, log messages go here - // V5ProtocolID configures the discv5 protocol identifier. - V5ProtocolID *[6]byte + // Node table configuration: + Bootnodes []*enode.Node // list of bootstrap nodes + PingInterval time.Duration // speed of node liveness check + RefreshInterval time.Duration // used in bucket refresh + // The options below are useful in very specific cases, like in unit tests. + V5ProtocolID *[6]byte + Log log.Logger // if set, log messages go here ValidSchemes enr.IdentityScheme // allowed identity schemes Clock mclock.Clock } func (cfg Config) withDefaults() Config { + // Node table configuration: + if cfg.PingInterval == 0 { + cfg.PingInterval = 10 * time.Second + } + if cfg.RefreshInterval == 0 { + cfg.RefreshInterval = 30 * time.Minute + } + + // Debug/test settings: if cfg.Log == nil { cfg.Log = log.Root() } diff --git a/p2p/discover/metrics.go b/p2p/discover/metrics.go index bf1a2fa2b8c9..da8e9cb81726 100644 --- a/p2p/discover/metrics.go +++ b/p2p/discover/metrics.go @@ -17,6 +17,7 @@ package discover import ( + "fmt" "net" "github.com/ethereum/go-ethereum/metrics" @@ -32,10 +33,17 @@ const ( ) var ( + bucketsCounter []metrics.Counter ingressTrafficMeter = metrics.NewRegisteredMeter(ingressMeterName, nil) egressTrafficMeter = metrics.NewRegisteredMeter(egressMeterName, nil) ) +func init() { + for i := 0; i < nBuckets; i++ { + bucketsCounter = append(bucketsCounter, metrics.NewRegisteredCounter(fmt.Sprintf("%s/bucket/%d/count", moduleName, i), nil)) + } +} + // meteredConn is a wrapper around a net.UDPConn that meters both the // inbound and outbound network traffic. type meteredUdpConn struct { diff --git a/p2p/discover/ntp.go b/p2p/discover/ntp.go index 48ceffe95b8d..3f9157808f12 100644 --- a/p2p/discover/ntp.go +++ b/p2p/discover/ntp.go @@ -22,10 +22,10 @@ package discover import ( "fmt" "net" - "sort" "time" "github.com/ethereum/go-ethereum/log" + "golang.org/x/exp/slices" ) const ( @@ -33,14 +33,6 @@ const ( ntpChecks = 3 // Number of measurements to do against the NTP server ) -// durationSlice attaches the methods of sort.Interface to []time.Duration, -// sorting in increasing order. -type durationSlice []time.Duration - -func (s durationSlice) Len() int { return len(s) } -func (s durationSlice) Less(i, j int) bool { return s[i] < s[j] } -func (s durationSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - // checkClockDrift queries an NTP server for clock drifts and warns the user if // one large enough is detected. func checkClockDrift() { @@ -109,7 +101,7 @@ func sntpDrift(measurements int) (time.Duration, error) { drifts = append(drifts, sent.Sub(t)+elapsed/2) } // Calculate average drift (drop two extremities to avoid outliers) - sort.Sort(durationSlice(drifts)) + slices.Sort(drifts) drift := time.Duration(0) for i := 1; i < len(drifts)-1; i++ { diff --git a/p2p/discover/table.go b/p2p/discover/table.go index 41d5ac6e34e7..f476d2079f8c 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/netutil" ) @@ -53,12 +54,10 @@ const ( bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24 tableIPLimit, tableSubnet = 10, 24 - refreshInterval = 30 * time.Minute - revalidateInterval = 10 * time.Second - copyNodesInterval = 30 * time.Second - seedMinTableTime = 5 * time.Minute - seedCount = 30 - seedMaxAge = 5 * 24 * time.Hour + copyNodesInterval = 30 * time.Second + seedMinTableTime = 5 * time.Minute + seedCount = 30 + seedMaxAge = 5 * 24 * time.Hour ) // Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps @@ -71,15 +70,19 @@ type Table struct { rand *mrand.Rand // source of randomness, periodically reseeded ips netutil.DistinctNetSet - log log.Logger - db *enode.DB // database of known nodes - net transport + db *enode.DB // database of known nodes + net transport + cfg Config + log log.Logger + + // loop channels refreshReq chan chan struct{} initDone chan struct{} closeReq chan struct{} closed chan struct{} - nodeAddedHook func(*node) // for testing + nodeAddedHook func(*bucket, *node) + nodeRemovedHook func(*bucket, *node) } // transport is implemented by the UDP transports. @@ -97,26 +100,30 @@ type bucket struct { entries []*node // live entries, sorted by time of last contact replacements []*node // recently seen nodes to be used if revalidation fails ips netutil.DistinctNetSet + index int } -func newTable(t transport, db *enode.DB, bootnodes []*enode.Node, log log.Logger) (*Table, error) { +func newTable(t transport, db *enode.DB, cfg Config) (*Table, error) { + cfg = cfg.withDefaults() tab := &Table{ net: t, db: db, + cfg: cfg, + log: cfg.Log, refreshReq: make(chan chan struct{}), initDone: make(chan struct{}), closeReq: make(chan struct{}), closed: make(chan struct{}), rand: mrand.New(mrand.NewSource(0)), ips: netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit}, - log: log, } - if err := tab.setFallbackNodes(bootnodes); err != nil { + if err := tab.setFallbackNodes(cfg.Bootnodes); err != nil { return nil, err } for i := range tab.buckets { tab.buckets[i] = &bucket{ - ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit}, + index: i, + ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit}, } } tab.seedRand() @@ -125,25 +132,28 @@ func newTable(t transport, db *enode.DB, bootnodes []*enode.Node, log log.Logger return tab, nil } -func (tab *Table) self() *enode.Node { - return tab.net.Self() -} - -func (tab *Table) seedRand() { - var b [8]byte - crand.Read(b[:]) - - tab.mutex.Lock() - tab.rand.Seed(int64(binary.BigEndian.Uint64(b[:]))) - tab.mutex.Unlock() +func newMeteredTable(t transport, db *enode.DB, cfg Config) (*Table, error) { + tab, err := newTable(t, db, cfg) + if err != nil { + return nil, err + } + if metrics.Enabled { + tab.nodeAddedHook = func(b *bucket, n *node) { + bucketsCounter[b.index].Inc(1) + } + tab.nodeRemovedHook = func(b *bucket, n *node) { + bucketsCounter[b.index].Dec(1) + } + } + return tab, nil } -// ReadRandomNodes fills the given slice with random nodes from the table. The results -// are guaranteed to be unique for a single invocation, no node will appear twice. -func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { +// Nodes returns all nodes contained in the table. +func (tab *Table) Nodes() []*enode.Node { if !tab.isInitDone() { - return 0 + return nil } + tab.mutex.Lock() defer tab.mutex.Unlock() @@ -153,12 +163,20 @@ func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) { nodes = append(nodes, unwrapNode(n)) } } - // Shuffle. - for i := 0; i < len(nodes); i++ { - j := tab.rand.Intn(len(nodes)) - nodes[i], nodes[j] = nodes[j], nodes[i] - } - return copy(buf, nodes) + return nodes +} + +func (tab *Table) self() *enode.Node { + return tab.net.Self() +} + +func (tab *Table) seedRand() { + var b [8]byte + crand.Read(b[:]) + + tab.mutex.Lock() + tab.rand.Seed(int64(binary.BigEndian.Uint64(b[:]))) + tab.mutex.Unlock() } // getNode returns the node with the given ID or nil if it isn't in the table. @@ -185,12 +203,18 @@ func (tab *Table) close() { // are used to connect to the network if the table is empty and there // are no known nodes in the database. func (tab *Table) setFallbackNodes(nodes []*enode.Node) error { + nursery := make([]*node, 0, len(nodes)) for _, n := range nodes { if err := n.ValidateComplete(); err != nil { return fmt.Errorf("bad bootstrap node %q: %v", n, err) } + if tab.cfg.NetRestrict != nil && !tab.cfg.NetRestrict.Contains(n.IP()) { + tab.log.Error("Bootstrap node filtered by netrestrict", "id", n.ID(), "ip", n.IP()) + continue + } + nursery = append(nursery, wrapNode(n)) } - tab.nursery = wrapNodes(nodes) + tab.nursery = nursery return nil } @@ -218,7 +242,7 @@ func (tab *Table) refresh() <-chan struct{} { func (tab *Table) loop() { var ( revalidate = time.NewTimer(tab.nextRevalidateTime()) - refresh = time.NewTicker(refreshInterval) + refresh = time.NewTimer(tab.nextRefreshTime()) copyNodes = time.NewTicker(copyNodesInterval) refreshDone = make(chan struct{}) // where doRefresh reports completion revalidateDone chan struct{} // where doRevalidate reports completion @@ -251,6 +275,7 @@ loop: close(ch) } waiting, refreshDone = nil, nil + refresh.Reset(tab.nextRefreshTime()) case <-revalidate.C: revalidateDone = make(chan struct{}) go tab.doRevalidate(revalidateDone) @@ -373,7 +398,15 @@ func (tab *Table) nextRevalidateTime() time.Duration { tab.mutex.Lock() defer tab.mutex.Unlock() - return time.Duration(tab.rand.Int63n(int64(revalidateInterval))) + return time.Duration(tab.rand.Int63n(int64(tab.cfg.PingInterval))) +} + +func (tab *Table) nextRefreshTime() time.Duration { + tab.mutex.Lock() + defer tab.mutex.Unlock() + + half := tab.cfg.RefreshInterval / 2 + return half + time.Duration(tab.rand.Int63n(int64(half))) } // copyLiveNodes adds nodes from the table to the database if they have been in the table @@ -481,12 +514,14 @@ func (tab *Table) addSeenNode(n *node) { // Can't add: IP limit reached. return } + // Add to end of bucket: b.entries = append(b.entries, n) b.replacements = deleteNode(b.replacements, n) n.addedAt = time.Now() + if tab.nodeAddedHook != nil { - tab.nodeAddedHook(n) + tab.nodeAddedHook(b, n) } } @@ -523,12 +558,14 @@ func (tab *Table) addVerifiedNode(n *node) { // Can't add: IP limit reached. return } + // Add to front of bucket. b.entries, _ = pushNode(b.entries, n, bucketSize) b.replacements = deleteNode(b.replacements, n) n.addedAt = time.Now() + if tab.nodeAddedHook != nil { - tab.nodeAddedHook(n) + tab.nodeAddedHook(b, n) } } @@ -627,8 +664,16 @@ func (tab *Table) bumpInBucket(b *bucket, n *node) bool { } func (tab *Table) deleteInBucket(b *bucket, n *node) { + // Check if the node is actually in the bucket so the removed hook + // isn't called multiple times for the same node. + if !contains(b.entries, n.ID()) { + return + } b.entries = deleteNode(b.entries, n) tab.removeIP(b, n.IP()) + if tab.nodeRemovedHook != nil { + tab.nodeRemovedHook(b, n) + } } func contains(ns []*node, id enode.ID) bool { diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 1ef63fe01019..2781dd4225b6 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -247,41 +247,6 @@ func TestTable_findnodeByID(t *testing.T) { } } -func TestTable_ReadRandomNodesGetAll(t *testing.T) { - cfg := &quick.Config{ - MaxCount: 200, - Rand: rand.New(rand.NewSource(time.Now().Unix())), - Values: func(args []reflect.Value, rand *rand.Rand) { - args[0] = reflect.ValueOf(make([]*enode.Node, rand.Intn(1000))) - }, - } - test := func(buf []*enode.Node) bool { - transport := newPingRecorder() - tab, db := newTestTable(transport) - defer db.Close() - defer tab.close() - <-tab.initDone - - for i := 0; i < len(buf); i++ { - ld := cfg.Rand.Intn(len(tab.buckets)) - fillTable(tab, []*node{nodeAtDistance(tab.self().ID(), ld, intIP(ld))}) - } - gotN := tab.ReadRandomNodes(buf) - if gotN != tab.len() { - t.Errorf("wrong number of nodes, got %d, want %d", gotN, tab.len()) - return false - } - if hasDuplicates(wrapNodes(buf[:gotN])) { - t.Errorf("result contains duplicates") - return false - } - return true - } - if err := quick.Check(test, cfg); err != nil { - t.Error(err) - } -} - type closeTest struct { Self enode.ID Target enode.ID diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go index 527afbb77e29..8f3813bdcf0f 100644 --- a/p2p/discover/table_util_test.go +++ b/p2p/discover/table_util_test.go @@ -24,13 +24,12 @@ import ( "fmt" "math/rand" "net" - "sort" "sync" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" + "golang.org/x/exp/slices" ) var nullNode *enode.Node @@ -42,8 +41,9 @@ func init() { } func newTestTable(t transport) (*Table, *enode.DB) { + cfg := Config{} db, _ := enode.OpenDB("") - tab, _ := newTable(t, db, nil, log.Root()) + tab, _ := newTable(t, db, cfg) go tab.loop() return tab, db } @@ -217,14 +217,14 @@ func nodeEqual(n1 *enode.Node, n2 *enode.Node) bool { } func sortByID(nodes []*enode.Node) { - sort.Slice(nodes, func(i, j int) bool { - return string(nodes[i].ID().Bytes()) < string(nodes[j].ID().Bytes()) + slices.SortFunc(nodes, func(a, b *enode.Node) int { + return bytes.Compare(a.ID().Bytes(), b.ID().Bytes()) }) } func sortedByDistanceTo(distbase enode.ID, slice []*node) bool { - return sort.SliceIsSorted(slice, func(i, j int) bool { - return enode.DistCmp(distbase, slice[i].ID(), slice[j].ID()) < 0 + return slices.IsSortedFunc(slice, func(a, b *node) int { + return enode.DistCmp(distbase, a.ID(), b.ID()) }) } diff --git a/p2p/discover/v4_lookup_test.go b/p2p/discover/v4_lookup_test.go index a00de9ca18c7..1f9ad69d0a23 100644 --- a/p2p/discover/v4_lookup_test.go +++ b/p2p/discover/v4_lookup_test.go @@ -20,13 +20,13 @@ import ( "crypto/ecdsa" "fmt" "net" - "sort" "testing" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p/discover/v4wire" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" + "golang.org/x/exp/slices" ) func TestUDPv4_Lookup(t *testing.T) { @@ -302,8 +302,8 @@ func (tn *preminedTestnet) closest(n int) (nodes []*enode.Node) { nodes = append(nodes, tn.node(d, i)) } } - sort.Slice(nodes, func(i, j int) bool { - return enode.DistCmp(tn.target.id(), nodes[i].ID(), nodes[j].ID()) < 0 + slices.SortFunc(nodes, func(a, b *enode.Node) int { + return enode.DistCmp(tn.target.id(), a.ID(), b.ID()) }) return nodes[:n] } diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go index 21fee9c7934b..988f16b01df2 100644 --- a/p2p/discover/v4_udp.go +++ b/p2p/discover/v4_udp.go @@ -142,7 +142,7 @@ func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) { log: cfg.Log, } - tab, err := newTable(t, ln.Database(), cfg.Bootnodes, t.log) + tab, err := newMeteredTable(t, ln.Database(), cfg) if err != nil { return nil, err } @@ -643,13 +643,13 @@ type packetHandlerV4 struct { func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error { req := h.Packet.(*v4wire.Ping) + if v4wire.Expired(req.Expiration) { + return errExpired + } senderKey, err := v4wire.DecodePubkey(crypto.S256(), fromKey) if err != nil { return err } - if v4wire.Expired(req.Expiration) { - return errExpired - } h.senderKey = senderKey return nil } diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index 21f0d75172c0..5add9cefa120 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -394,7 +394,7 @@ func TestUDPv4_pingMatchIP(t *testing.T) { func TestUDPv4_successfulPing(t *testing.T) { test := newUDPTest(t) added := make(chan *node, 1) - test.table.nodeAddedHook = func(n *node) { added <- n } + test.table.nodeAddedHook = func(b *bucket, n *node) { added <- n } defer test.close() // The remote side sends a ping packet to initiate the exchange. diff --git a/p2p/discover/v4wire/v4wire.go b/p2p/discover/v4wire/v4wire.go index 3935068cd9db..9c59359fb2c2 100644 --- a/p2p/discover/v4wire/v4wire.go +++ b/p2p/discover/v4wire/v4wire.go @@ -238,6 +238,8 @@ func Decode(input []byte) (Packet, Pubkey, []byte, error) { default: return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype) } + // Here we use NewStream to allow for additional data after the first + // RLP object (forward-compatibility). s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0) err = s.Decode(req) return req, fromKey, hash, err diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go index 6a86e72a5089..6ba7a9061882 100644 --- a/p2p/discover/v5_udp.go +++ b/p2p/discover/v5_udp.go @@ -174,7 +174,7 @@ func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { cancelCloseCtx: cancelCloseCtx, } t.talk = newTalkSystem(t) - tab, err := newTable(t, t.db, cfg.Bootnodes, cfg.Log) + tab, err := newMeteredTable(t, t.db, cfg) if err != nil { return nil, err } diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go index 392a95b61e3b..880b71a991ce 100644 --- a/p2p/discover/v5_udp_test.go +++ b/p2p/discover/v5_udp_test.go @@ -24,7 +24,6 @@ import ( "math/rand" "net" "reflect" - "sort" "testing" "time" @@ -35,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" ) // Real sockets, real crypto: this test checks end-to-end connectivity for UDPv5. @@ -61,8 +61,8 @@ func TestUDPv5_lookupE2E(t *testing.T) { for i := range nodes { expectedResult[i] = nodes[i].Self() } - sort.Slice(expectedResult, func(i, j int) bool { - return enode.DistCmp(target.ID(), expectedResult[i].ID(), expectedResult[j].ID()) < 0 + slices.SortFunc(expectedResult, func(a, b *enode.Node) int { + return enode.DistCmp(target.ID(), a.ID(), b.ID()) }) // Do the lookup. diff --git a/p2p/dnsdisc/tree.go b/p2p/dnsdisc/tree.go index a3f426e42806..06b7681f18ae 100644 --- a/p2p/dnsdisc/tree.go +++ b/p2p/dnsdisc/tree.go @@ -23,7 +23,6 @@ import ( "encoding/base64" "fmt" "io" - "sort" "strings" "github.com/ethereum/go-ethereum/crypto" @@ -31,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/rlp" "golang.org/x/crypto/sha3" + "golang.org/x/exp/slices" ) // Tree is a merkle tree of node records. @@ -214,8 +214,8 @@ func (t *Tree) build(entries []entry) entry { } func sortByID(nodes []*enode.Node) []*enode.Node { - sort.Slice(nodes, func(i, j int) bool { - return bytes.Compare(nodes[i].ID().Bytes(), nodes[j].ID().Bytes()) < 0 + slices.SortFunc(nodes, func(a, b *enode.Node) int { + return bytes.Compare(a.ID().Bytes(), b.ID().Bytes()) }) return nodes } diff --git a/p2p/message.go b/p2p/message.go index 24f21456d8e5..3ab56ee35018 100644 --- a/p2p/message.go +++ b/p2p/message.go @@ -156,7 +156,7 @@ func MsgPipe() (*MsgPipeRW, *MsgPipeRW) { var ( c1, c2 = make(chan Msg), make(chan Msg) closing = make(chan struct{}) - closed = new(int32) + closed = new(atomic.Bool) rw1 = &MsgPipeRW{c1, c2, closing, closed} rw2 = &MsgPipeRW{c2, c1, closing, closed} ) @@ -172,13 +172,13 @@ type MsgPipeRW struct { w chan<- Msg r <-chan Msg closing chan struct{} - closed *int32 + closed *atomic.Bool } // WriteMsg sends a message on the pipe. // It blocks until the receiver has consumed the message payload. func (p *MsgPipeRW) WriteMsg(msg Msg) error { - if atomic.LoadInt32(p.closed) == 0 { + if !p.closed.Load() { consumed := make(chan struct{}, 1) msg.Payload = &eofSignal{msg.Payload, msg.Size, consumed} select { @@ -199,7 +199,7 @@ func (p *MsgPipeRW) WriteMsg(msg Msg) error { // ReadMsg returns a message sent on the other end of the pipe. func (p *MsgPipeRW) ReadMsg() (Msg, error) { - if atomic.LoadInt32(p.closed) == 0 { + if !p.closed.Load() { select { case msg := <-p.r: return msg, nil @@ -213,9 +213,8 @@ func (p *MsgPipeRW) ReadMsg() (Msg, error) { // of the pipe. They will return ErrPipeClosed. Close also // interrupts any reads from a message payload. func (p *MsgPipeRW) Close() error { - if atomic.AddInt32(p.closed, 1) != 1 { + if p.closed.Swap(true) { // someone else is already closing - atomic.StoreInt32(p.closed, 1) // avoid overflow return nil } close(p.closing) diff --git a/p2p/metrics.go b/p2p/metrics.go index 1bb505cdfb35..a6e36b91a8d7 100644 --- a/p2p/metrics.go +++ b/p2p/metrics.go @@ -19,30 +19,86 @@ package p2p import ( + "errors" "net" "github.com/ethereum/go-ethereum/metrics" ) const ( + // HandleHistName is the prefix of the per-packet serving time histograms. + HandleHistName = "p2p/handle" + // ingressMeterName is the prefix of the per-packet inbound metrics. ingressMeterName = "p2p/ingress" // egressMeterName is the prefix of the per-packet outbound metrics. egressMeterName = "p2p/egress" - - // HandleHistName is the prefix of the per-packet serving time histograms. - HandleHistName = "p2p/handle" ) var ( - ingressConnectMeter = metrics.NewRegisteredMeter("p2p/serves", nil) - ingressTrafficMeter = metrics.NewRegisteredMeter(ingressMeterName, nil) - egressConnectMeter = metrics.NewRegisteredMeter("p2p/dials", nil) - egressTrafficMeter = metrics.NewRegisteredMeter(egressMeterName, nil) - activePeerGauge = metrics.NewRegisteredGauge("p2p/peers", nil) + activePeerGauge metrics.Gauge = metrics.NilGauge{} + + ingressTrafficMeter = metrics.NewRegisteredMeter("p2p/ingress", nil) + egressTrafficMeter = metrics.NewRegisteredMeter("p2p/egress", nil) + + // general ingress/egress connection meters + serveMeter metrics.Meter = metrics.NilMeter{} + serveSuccessMeter metrics.Meter = metrics.NilMeter{} + dialMeter metrics.Meter = metrics.NilMeter{} + dialSuccessMeter metrics.Meter = metrics.NilMeter{} + dialConnectionError metrics.Meter = metrics.NilMeter{} + + // handshake error meters + dialTooManyPeers = metrics.NewRegisteredMeter("p2p/dials/error/saturated", nil) + dialAlreadyConnected = metrics.NewRegisteredMeter("p2p/dials/error/known", nil) + dialSelf = metrics.NewRegisteredMeter("p2p/dials/error/self", nil) + dialUselessPeer = metrics.NewRegisteredMeter("p2p/dials/error/useless", nil) + dialUnexpectedIdentity = metrics.NewRegisteredMeter("p2p/dials/error/id/unexpected", nil) + dialEncHandshakeError = metrics.NewRegisteredMeter("p2p/dials/error/rlpx/enc", nil) + dialProtoHandshakeError = metrics.NewRegisteredMeter("p2p/dials/error/rlpx/proto", nil) ) +func init() { + if !metrics.Enabled { + return + } + + activePeerGauge = metrics.NewRegisteredGauge("p2p/peers", nil) + serveMeter = metrics.NewRegisteredMeter("p2p/serves", nil) + serveSuccessMeter = metrics.NewRegisteredMeter("p2p/serves/success", nil) + dialMeter = metrics.NewRegisteredMeter("p2p/dials", nil) + dialSuccessMeter = metrics.NewRegisteredMeter("p2p/dials/success", nil) + dialConnectionError = metrics.NewRegisteredMeter("p2p/dials/error/connection", nil) +} + +// markDialError matches errors that occur while setting up a dial connection +// to the corresponding meter. +func markDialError(err error) { + if !metrics.Enabled { + return + } + if err2 := errors.Unwrap(err); err2 != nil { + err = err2 + } + switch err { + case DiscTooManyPeers: + dialTooManyPeers.Mark(1) + case DiscAlreadyConnected: + dialAlreadyConnected.Mark(1) + case DiscSelf: + dialSelf.Mark(1) + case DiscUselessPeer: + dialUselessPeer.Mark(1) + case DiscUnexpectedIdentity: + dialUnexpectedIdentity.Mark(1) + case errEncHandshakeError: + dialEncHandshakeError.Mark(1) + case errProtoHandshakeError: + dialProtoHandshakeError.Mark(1) + } +} + // meteredConn is a wrapper around a net.Conn that meters both the // inbound and outbound network traffic. type meteredConn struct { @@ -52,18 +108,10 @@ type meteredConn struct { // newMeteredConn creates a new metered connection, bumps the ingress or egress // connection meter and also increases the metered peer count. If the metrics // system is disabled, function returns the original connection. -func newMeteredConn(conn net.Conn, ingress bool, addr *net.TCPAddr) net.Conn { - // Short circuit if metrics are disabled +func newMeteredConn(conn net.Conn) net.Conn { if !metrics.Enabled { return conn } - // Bump the connection counters and wrap the connection - if ingress { - ingressConnectMeter.Mark(1) - } else { - egressConnectMeter.Mark(1) - } - activePeerGauge.Inc(1) return &meteredConn{Conn: conn} } @@ -82,13 +130,3 @@ func (c *meteredConn) Write(b []byte) (n int, err error) { egressTrafficMeter.Mark(int64(n)) return n, err } - -// Close delegates a close operation to the underlying connection, unregisters -// the peer from the traffic registries and emits close event. -func (c *meteredConn) Close() error { - err := c.Conn.Close() - if err == nil { - activePeerGauge.Dec(1) - } - return err -} diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go index ad4c36582ae7..61b6922988fd 100644 --- a/p2p/nat/nat.go +++ b/p2p/nat/nat.go @@ -38,7 +38,7 @@ type Interface interface { // protocol is "UDP" or "TCP". Some implementations allow setting // a display name for the mapping. The mapping may be removed by // the gateway when its lifetime ends. - AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error + AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) DeleteMapping(protocol string, extport, intport int) error // ExternalIP should return the external (Internet-facing) @@ -91,20 +91,23 @@ func Parse(spec string) (Interface, error) { } const ( - mapTimeout = 10 * time.Minute + DefaultMapTimeout = 10 * time.Minute ) // Map adds a port mapping on m and keeps it alive until c is closed. // This function is typically invoked in its own goroutine. +// +// Note that Map does not handle the situation where the NAT interface assigns a different +// external port than the requested one. func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, name string) { log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m) - refresh := time.NewTimer(mapTimeout) + refresh := time.NewTimer(DefaultMapTimeout) defer func() { refresh.Stop() log.Debug("Deleting port mapping") m.DeleteMapping(protocol, extport, intport) }() - if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil { + if _, err := m.AddMapping(protocol, extport, intport, name, DefaultMapTimeout); err != nil { log.Debug("Couldn't add port mapping", "err", err) } else { log.Info("Mapped network port") @@ -117,10 +120,10 @@ func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, } case <-refresh.C: log.Trace("Refreshing port mapping") - if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil { + if _, err := m.AddMapping(protocol, extport, intport, name, DefaultMapTimeout); err != nil { log.Debug("Couldn't add port mapping", "err", err) } - refresh.Reset(mapTimeout) + refresh.Reset(DefaultMapTimeout) } } } @@ -135,8 +138,8 @@ func (n ExtIP) String() string { return fmt.Sprintf("ExtIP(%v)", ne // These do nothing. -func (ExtIP) AddMapping(string, int, int, string, time.Duration) error { return nil } -func (ExtIP) DeleteMapping(string, int, int) error { return nil } +func (ExtIP) AddMapping(string, int, int, string, time.Duration) (uint16, error) { return 0, nil } +func (ExtIP) DeleteMapping(string, int, int) error { return nil } // Any returns a port mapper that tries to discover any supported // mechanism on the local network. @@ -193,9 +196,9 @@ func startautodisc(what string, doit func() Interface) Interface { return &autodisc{what: what, doit: doit} } -func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error { +func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) { if err := n.wait(); err != nil { - return err + return 0, err } return n.found.AddMapping(protocol, extport, intport, name, lifetime) } diff --git a/p2p/nat/natpmp.go b/p2p/nat/natpmp.go index 40f2aff44e7a..97601c99dcbb 100644 --- a/p2p/nat/natpmp.go +++ b/p2p/nat/natpmp.go @@ -44,28 +44,21 @@ func (n *pmp) ExternalIP() (net.IP, error) { return response.ExternalIPAddress[:], nil } -func (n *pmp) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error { +func (n *pmp) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) { if lifetime <= 0 { - return fmt.Errorf("lifetime must not be <= 0") + return 0, fmt.Errorf("lifetime must not be <= 0") } // Note order of port arguments is switched between our // AddMapping and the client's AddPortMapping. res, err := n.c.AddPortMapping(strings.ToLower(protocol), intport, extport, int(lifetime/time.Second)) if err != nil { - return err + return 0, err } - // NAT-PMP maps an alternative available port number if the requested - // port is already mapped to another address and returns success. In this - // case, we return an error because there is no way to return the new port - // to the caller. - if uint16(extport) != res.MappedExternalPort { - // Destroy the mapping in NAT device. - n.c.AddPortMapping(strings.ToLower(protocol), intport, 0, 0) - return fmt.Errorf("port %d already mapped to another address (%s)", extport, protocol) - } - - return nil + // NAT-PMP maps an alternative available port number if the requested port + // is already mapped to another address and returns success. Handling of + // alternate port numbers is done by the caller. + return res.MappedExternalPort, nil } func (n *pmp) DeleteMapping(protocol string, extport, intport int) (err error) { diff --git a/p2p/nat/natupnp.go b/p2p/nat/natupnp.go index a8de00e978b9..c90c4f3de8b7 100644 --- a/p2p/nat/natupnp.go +++ b/p2p/nat/natupnp.go @@ -19,6 +19,8 @@ package nat import ( "errors" "fmt" + "math" + "math/rand" "net" "strings" "sync" @@ -40,6 +42,7 @@ type upnp struct { client upnpClient mu sync.Mutex lastReqTime time.Time + rand *rand.Rand } type upnpClient interface { @@ -76,18 +79,50 @@ func (n *upnp) ExternalIP() (addr net.IP, err error) { return ip, nil } -func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) error { +func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) (uint16, error) { ip, err := n.internalAddress() if err != nil { - return nil // TODO: Shouldn't we return the error? + return 0, nil // TODO: Shouldn't we return the error? } protocol = strings.ToUpper(protocol) lifetimeS := uint32(lifetime / time.Second) n.DeleteMapping(protocol, extport, intport) - return n.withRateLimit(func() error { + err = n.withRateLimit(func() error { return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) }) + if err == nil { + return uint16(extport), nil + } + + return uint16(extport), n.withRateLimit(func() error { + p, err := n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS) + if err == nil { + extport = int(p) + } + return err + }) +} + +func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) { + if client, ok := n.client.(*internetgateway2.WANIPConnection2); ok { + return client.AddAnyPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) + } + // It will retry with a random port number if the client does + // not support AddAnyPortMapping. + extport = n.randomPort() + err := n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS) + if err != nil { + return 0, err + } + return uint16(extport), nil +} + +func (n *upnp) randomPort() int { + if n.rand == nil { + n.rand = rand.New(rand.NewSource(time.Now().UnixNano())) + } + return n.rand.Intn(math.MaxUint16-10000) + 10000 } func (n *upnp) internalAddress() (net.IP, error) { diff --git a/p2p/peer.go b/p2p/peer.go index a4b22fb6d463..65a7903f5863 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -21,7 +21,6 @@ import ( "fmt" "io" "net" - "sort" "sync" "time" @@ -32,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/exp/slices" ) var ( @@ -112,6 +112,7 @@ type Peer struct { wg sync.WaitGroup protoErr chan error closed chan struct{} + pingRecv chan struct{} disc chan DiscReason // events receives message send / receive events if set @@ -233,6 +234,7 @@ func newPeer(log log.Logger, conn *conn, protocols []Protocol) *Peer { disc: make(chan DiscReason), protoErr: make(chan error, len(protomap)+1), // protocols + pingLoop closed: make(chan struct{}), + pingRecv: make(chan struct{}, 16), log: log.New("id", conn.node.ID(), "conn", conn.flags), } return p @@ -293,9 +295,11 @@ loop: } func (p *Peer) pingLoop() { - ping := time.NewTimer(pingInterval) defer p.wg.Done() + + ping := time.NewTimer(pingInterval) defer ping.Stop() + for { select { case <-ping.C: @@ -304,6 +308,10 @@ func (p *Peer) pingLoop() { return } ping.Reset(pingInterval) + + case <-p.pingRecv: + SendItems(p.rw, pongMsg) + case <-p.closed: return } @@ -330,7 +338,10 @@ func (p *Peer) handle(msg Msg) error { switch { case msg.Code == pingMsg: msg.Discard() - go SendItems(p.rw, pongMsg) + select { + case p.pingRecv <- struct{}{}: + case <-p.closed: + } case msg.Code == discMsg: // This is the last message. We don't need to discard or // check errors because, the connection will be closed after it. @@ -375,7 +386,7 @@ func countMatchingProtocols(protocols []Protocol, caps []Cap) int { // matchProtocols creates structures for matching named subprotocols. func matchProtocols(protocols []Protocol, caps []Cap, rw MsgReadWriter) map[string]*protoRW { - sort.Sort(capsByNameAndVersion(caps)) + slices.SortFunc(caps, Cap.Cmp) offset := baseProtocolLength result := make(map[string]*protoRW) diff --git a/p2p/protocol.go b/p2p/protocol.go index fa23a087c281..9bb6785a2250 100644 --- a/p2p/protocol.go +++ b/p2p/protocol.go @@ -18,6 +18,7 @@ package p2p import ( "fmt" + "strings" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/enr" @@ -77,10 +78,16 @@ func (cap Cap) String() string { return fmt.Sprintf("%s/%d", cap.Name, cap.Version) } -type capsByNameAndVersion []Cap - -func (cs capsByNameAndVersion) Len() int { return len(cs) } -func (cs capsByNameAndVersion) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] } -func (cs capsByNameAndVersion) Less(i, j int) bool { - return cs[i].Name < cs[j].Name || (cs[i].Name == cs[j].Name && cs[i].Version < cs[j].Version) +// Cmp defines the canonical sorting order of capabilities. +func (cap Cap) Cmp(other Cap) int { + if cap.Name == other.Name { + if cap.Version < other.Version { + return -1 + } + if cap.Version > other.Version { + return 1 + } + return 0 + } + return strings.Compare(cap.Name, other.Name) } diff --git a/p2p/server.go b/p2p/server.go index f7bf948b6901..8f42765a8c26 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -24,7 +24,6 @@ import ( "errors" "fmt" "net" - "sort" "sync" "sync/atomic" "time" @@ -39,6 +38,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/p2p/netutil" + "golang.org/x/exp/slices" ) const ( @@ -64,7 +64,11 @@ const ( frameWriteTimeout = 20 * time.Second ) -var errServerStopped = errors.New("server stopped") +var ( + errServerStopped = errors.New("server stopped") + errEncHandshakeError = errors.New("rlpx enc error") + errProtoHandshakeError = errors.New("rlpx proto error") +) // Config holds Server options. type Config struct { @@ -89,6 +93,9 @@ type Config struct { // Disabling is useful for protocol debugging (manual topology). NoDiscovery bool + // DiscoveryV4 specifies whether V4 discovery should be started. + DiscoveryV4 bool `toml:",omitempty"` + // DiscoveryV5 specifies whether the new topic-discovery based V5 discovery // protocol should be started or not. DiscoveryV5 bool `toml:",omitempty"` @@ -188,6 +195,9 @@ type Server struct { discmix *enode.FairMix dialsched *dialScheduler + // This is read by the NAT port mapping loop. + portMappingRegister chan *portMapping + // Channels into the run loop. quit chan struct{} addtrusted chan *enode.Node @@ -476,6 +486,8 @@ func (srv *Server) Start() (err error) { if err := srv.setupLocalNode(); err != nil { return err } + srv.setupPortMapping() + if srv.ListenAddr != "" { if err := srv.setupListening(); err != nil { return err @@ -498,7 +510,7 @@ func (srv *Server) setupLocalNode() error { for _, p := range srv.Protocols { srv.ourHandshake.Caps = append(srv.ourHandshake.Caps, p.cap()) } - sort.Sort(capsByNameAndVersion(srv.ourHandshake.Caps)) + slices.SortFunc(srv.ourHandshake.Caps, Cap.Cmp) // Create the local node. db, err := enode.OpenDB(srv.NodeDatabase) @@ -514,81 +526,34 @@ func (srv *Server) setupLocalNode() error { srv.localnode.Set(e) } } - switch srv.NAT.(type) { - case nil: - // No NAT interface, do nothing. - case nat.ExtIP: - // ExtIP doesn't block, set the IP right away. - ip, _ := srv.NAT.ExternalIP() - srv.localnode.SetStaticIP(ip) - default: - // Ask the router about the IP. This takes a while and blocks startup, - // do it in the background. - srv.loopWG.Add(1) - go func() { - defer srv.loopWG.Done() - if ip, err := srv.NAT.ExternalIP(); err == nil { - srv.localnode.SetStaticIP(ip) - } - }() - } return nil } func (srv *Server) setupDiscovery() error { srv.discmix = enode.NewFairMix(discmixTimeout) - // Add protocol-specific discovery sources. - added := make(map[string]bool) - for _, proto := range srv.Protocols { - if proto.DialCandidates != nil && !added[proto.Name] { - srv.discmix.AddSource(proto.DialCandidates) - added[proto.Name] = true - } - } - // Don't listen on UDP endpoint if DHT is disabled. - if srv.NoDiscovery && !srv.DiscoveryV5 { + if srv.NoDiscovery { return nil } - - listenAddr := srv.ListenAddr - - // Use an alternate listening address for UDP if - // a custom discovery address is configured. - if srv.DiscAddr != "" { - listenAddr = srv.DiscAddr - } - - addr, err := net.ResolveUDPAddr("udp", listenAddr) - if err != nil { - return err - } - conn, err := net.ListenUDP("udp", addr) + conn, err := srv.setupUDPListening() if err != nil { return err } - realaddr := conn.LocalAddr().(*net.UDPAddr) - srv.log.Debug("UDP listener up", "addr", realaddr) - if srv.NAT != nil { - if !realaddr.IP.IsLoopback() { - srv.loopWG.Add(1) - go func() { - nat.Map(srv.NAT, srv.quit, "udp", realaddr.Port, realaddr.Port, "ethereum discovery") - srv.loopWG.Done() - }() - } + + var ( + sconn discover.UDPConn = conn + unhandled chan discover.ReadPacket + ) + // If both versions of discovery are running, setup a shared + // connection, so v5 can read unhandled messages from v4. + if srv.DiscoveryV4 && srv.DiscoveryV5 { + unhandled = make(chan discover.ReadPacket, 100) + sconn = &sharedUDPConn{conn, unhandled} } - srv.localnode.SetFallbackUDP(realaddr.Port) - // Discovery V4 - var unhandled chan discover.ReadPacket - var sconn *sharedUDPConn - if !srv.NoDiscovery { - if srv.DiscoveryV5 { - unhandled = make(chan discover.ReadPacket, 100) - sconn = &sharedUDPConn{conn, unhandled} - } + // Start discovery services. + if srv.DiscoveryV4 { cfg := discover.Config{ PrivateKey: srv.PrivateKey, NetRestrict: srv.NetRestrict, @@ -603,8 +568,6 @@ func (srv *Server) setupDiscovery() error { srv.ntab = ntab srv.discmix.AddSource(ntab.RandomNodes()) } - - // Discovery V5 if srv.DiscoveryV5 { cfg := discover.Config{ PrivateKey: srv.PrivateKey, @@ -612,16 +575,20 @@ func (srv *Server) setupDiscovery() error { Bootnodes: srv.BootstrapNodesV5, Log: srv.log, } - var err error - if sconn != nil { - srv.DiscV5, err = discover.ListenV5(sconn, srv.localnode, cfg) - } else { - srv.DiscV5, err = discover.ListenV5(conn, srv.localnode, cfg) - } + srv.DiscV5, err = discover.ListenV5(sconn, srv.localnode, cfg) if err != nil { return err } } + + // Add protocol-specific discovery sources. + added := make(map[string]bool) + for _, proto := range srv.Protocols { + if proto.DialCandidates != nil && !added[proto.Name] { + srv.discmix.AddSource(proto.DialCandidates) + added[proto.Name] = true + } + } return nil } @@ -676,14 +643,15 @@ func (srv *Server) setupListening() error { srv.ListenAddr = listener.Addr().String() // Update the local node record and map the TCP listening port if NAT is configured. - if tcp, ok := listener.Addr().(*net.TCPAddr); ok { + tcp, isTCP := listener.Addr().(*net.TCPAddr) + if isTCP { srv.localnode.Set(enr.TCP(tcp.Port)) - if !tcp.IP.IsLoopback() && srv.NAT != nil { - srv.loopWG.Add(1) - go func() { - nat.Map(srv.NAT, srv.quit, "tcp", tcp.Port, tcp.Port, "ethereum p2p") - srv.loopWG.Done() - }() + if !tcp.IP.IsLoopback() && !tcp.IP.IsPrivate() { + srv.portMappingRegister <- &portMapping{ + protocol: "TCP", + name: "ethereum p2p", + port: tcp.Port, + } } } @@ -692,6 +660,36 @@ func (srv *Server) setupListening() error { return nil } +func (srv *Server) setupUDPListening() (*net.UDPConn, error) { + listenAddr := srv.ListenAddr + + // Use an alternate listening address for UDP if + // a custom discovery address is configured. + if srv.DiscAddr != "" { + listenAddr = srv.DiscAddr + } + addr, err := net.ResolveUDPAddr("udp", listenAddr) + if err != nil { + return nil, err + } + conn, err := net.ListenUDP("udp", addr) + if err != nil { + return nil, err + } + laddr := conn.LocalAddr().(*net.UDPAddr) + srv.localnode.SetFallbackUDP(laddr.Port) + srv.log.Debug("UDP listener up", "addr", laddr) + if !laddr.IP.IsLoopback() && !laddr.IP.IsPrivate() { + srv.portMappingRegister <- &portMapping{ + protocol: "UDP", + name: "ethereum peer discovery", + port: laddr.Port, + } + } + + return conn, nil +} + // doPeerOp runs fn on the main loop. func (srv *Server) doPeerOp(fn peerOpFunc) { select { @@ -772,7 +770,11 @@ running: srv.dialsched.peerAdded(c) if p.Inbound() { inboundCount++ + serveSuccessMeter.Mark(1) + } else { + dialSuccessMeter.Mark(1) } + activePeerGauge.Inc(1) } c.cont <- err @@ -785,6 +787,7 @@ running: if pd.Inbound() { inboundCount-- } + activePeerGauge.Dec(1) } } @@ -894,11 +897,8 @@ func (srv *Server) listenLoop() { continue } if remoteIP != nil { - var addr *net.TCPAddr - if tcp, ok := fd.RemoteAddr().(*net.TCPAddr); ok { - addr = tcp - } - fd = newMeteredConn(fd, true, addr) + fd = newMeteredConn(fd) + serveMeter.Mark(1) srv.log.Trace("Accepted connection", "addr", fd.RemoteAddr()) } go func() { @@ -939,6 +939,9 @@ func (srv *Server) SetupConn(fd net.Conn, flags connFlag, dialDest *enode.Node) err := srv.setupConn(c, flags, dialDest) if err != nil { + if !c.is(inboundConn) { + markDialError(err) + } c.close(err) } return err @@ -957,7 +960,7 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro if dialDest != nil { dialPubkey := new(ecdsa.PublicKey) if err := dialDest.Load((*enode.Secp256k1)(dialPubkey)); err != nil { - err = errors.New("dial destination doesn't have a secp256k1 public key") + err = fmt.Errorf("%w: dial destination doesn't have a secp256k1 public key", errEncHandshakeError) srv.log.Trace("Setting up connection failed", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err) return err } @@ -967,7 +970,7 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro remotePubkey, err := c.doEncHandshake(srv.PrivateKey) if err != nil { srv.log.Trace("Failed RLPx handshake", "addr", c.fd.RemoteAddr(), "conn", c.flags, "err", err) - return err + return fmt.Errorf("%w: %v", errEncHandshakeError, err) } if dialDest != nil { c.node = dialDest @@ -985,7 +988,7 @@ func (srv *Server) setupConn(c *conn, flags connFlag, dialDest *enode.Node) erro phs, err := c.doProtoHandshake(srv.ourHandshake) if err != nil { clog.Trace("Failed p2p handshake", "err", err) - return err + return fmt.Errorf("%w: %v", errProtoHandshakeError, err) } if id := c.node.ID(); !bytes.Equal(crypto.Keccak256(phs.ID), id[:]) { clog.Trace("Wrong devp2p handshake identity", "phsid", hex.EncodeToString(phs.ID)) diff --git a/p2p/server_nat.go b/p2p/server_nat.go new file mode 100644 index 000000000000..354597cc7a44 --- /dev/null +++ b/p2p/server_nat.go @@ -0,0 +1,187 @@ +// Copyright 2023 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 p2p + +import ( + "net" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/p2p/nat" +) + +const ( + portMapDuration = 10 * time.Minute + portMapRefreshInterval = 8 * time.Minute + portMapRetryInterval = 5 * time.Minute + extipRetryInterval = 2 * time.Minute +) + +type portMapping struct { + protocol string + name string + port int + + // for use by the portMappingLoop goroutine: + extPort int // the mapped port returned by the NAT interface + nextTime mclock.AbsTime +} + +// setupPortMapping starts the port mapping loop if necessary. +// Note: this needs to be called after the LocalNode instance has been set on the server. +func (srv *Server) setupPortMapping() { + // portMappingRegister will receive up to two values: one for the TCP port if + // listening is enabled, and one more for enabling UDP port mapping if discovery is + // enabled. We make it buffered to avoid blocking setup while a mapping request is in + // progress. + srv.portMappingRegister = make(chan *portMapping, 2) + + switch srv.NAT.(type) { + case nil: + // No NAT interface configured. + srv.loopWG.Add(1) + go srv.consumePortMappingRequests() + + case nat.ExtIP: + // ExtIP doesn't block, set the IP right away. + ip, _ := srv.NAT.ExternalIP() + srv.localnode.SetStaticIP(ip) + srv.loopWG.Add(1) + go srv.consumePortMappingRequests() + + default: + srv.loopWG.Add(1) + go srv.portMappingLoop() + } +} + +func (srv *Server) consumePortMappingRequests() { + defer srv.loopWG.Done() + for { + select { + case <-srv.quit: + return + case <-srv.portMappingRegister: + } + } +} + +// portMappingLoop manages port mappings for UDP and TCP. +func (srv *Server) portMappingLoop() { + defer srv.loopWG.Done() + + newLogger := func(p string, e int, i int) log.Logger { + return log.New("proto", p, "extport", e, "intport", i, "interface", srv.NAT) + } + + var ( + mappings = make(map[string]*portMapping, 2) + refresh = mclock.NewAlarm(srv.clock) + extip = mclock.NewAlarm(srv.clock) + lastExtIP net.IP + ) + extip.Schedule(srv.clock.Now()) + defer func() { + refresh.Stop() + extip.Stop() + for _, m := range mappings { + if m.extPort != 0 { + log := newLogger(m.protocol, m.extPort, m.port) + log.Debug("Deleting port mapping") + srv.NAT.DeleteMapping(m.protocol, m.extPort, m.port) + } + } + }() + + for { + // Schedule refresh of existing mappings. + for _, m := range mappings { + refresh.Schedule(m.nextTime) + } + + select { + case <-srv.quit: + return + + case <-extip.C(): + extip.Schedule(srv.clock.Now().Add(extipRetryInterval)) + ip, err := srv.NAT.ExternalIP() + if err != nil { + log.Debug("Couldn't get external IP", "err", err, "interface", srv.NAT) + } else if !ip.Equal(lastExtIP) { + log.Debug("External IP changed", "ip", extip, "interface", srv.NAT) + } else { + return + } + // Here, we either failed to get the external IP, or it has changed. + lastExtIP = ip + srv.localnode.SetStaticIP(ip) + // Ensure port mappings are refreshed in case we have moved to a new network. + for _, m := range mappings { + m.nextTime = srv.clock.Now() + } + + case m := <-srv.portMappingRegister: + if m.protocol != "TCP" && m.protocol != "UDP" { + panic("unknown NAT protocol name: " + m.protocol) + } + mappings[m.protocol] = m + m.nextTime = srv.clock.Now() + + case <-refresh.C(): + for _, m := range mappings { + if srv.clock.Now() < m.nextTime { + continue + } + + external := m.port + if m.extPort != 0 { + external = m.extPort + } + log := newLogger(m.protocol, external, m.port) + + log.Trace("Attempting port mapping") + p, err := srv.NAT.AddMapping(m.protocol, external, m.port, m.name, portMapDuration) + if err != nil { + log.Debug("Couldn't add port mapping", "err", err) + m.extPort = 0 + m.nextTime = srv.clock.Now().Add(portMapRetryInterval) + continue + } + // It was mapped! + m.extPort = int(p) + m.nextTime = srv.clock.Now().Add(portMapRefreshInterval) + if external != m.extPort { + log = newLogger(m.protocol, m.extPort, m.port) + log.Info("NAT mapped alternative port") + } else { + log.Info("NAT mapped port") + } + + // Update port in local ENR. + switch m.protocol { + case "TCP": + srv.localnode.Set(enr.TCP(m.extPort)) + case "UDP": + srv.localnode.SetFallbackUDP(m.extPort) + } + } + } + } +} diff --git a/p2p/server_nat_test.go b/p2p/server_nat_test.go new file mode 100644 index 000000000000..de935fcfc56d --- /dev/null +++ b/p2p/server_nat_test.go @@ -0,0 +1,102 @@ +// Copyright 2023 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 p2p + +import ( + "net" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/internal/testlog" + "github.com/ethereum/go-ethereum/log" +) + +func TestServerPortMapping(t *testing.T) { + clock := new(mclock.Simulated) + mockNAT := &mockNAT{mappedPort: 30000} + srv := Server{ + Config: Config{ + PrivateKey: newkey(), + NoDial: true, + ListenAddr: ":0", + NAT: mockNAT, + Logger: testlog.Logger(t, log.LvlTrace), + clock: clock, + }, + } + err := srv.Start() + if err != nil { + t.Fatal(err) + } + defer srv.Stop() + + // Wait for the port mapping to be registered. Synchronization with the port mapping + // goroutine works like this: For each iteration, we allow other goroutines to run and + // also advance the virtual clock by 1 second. Waiting stops when the NAT interface + // has received some requests, or when the clock reaches a timeout. + deadline := clock.Now().Add(portMapRefreshInterval) + for clock.Now() < deadline && mockNAT.mapRequests.Load() < 2 { + time.Sleep(10 * time.Millisecond) + clock.Run(1 * time.Second) + } + + if mockNAT.ipRequests.Load() == 0 { + t.Fatal("external IP was never requested") + } + reqCount := mockNAT.mapRequests.Load() + if reqCount != 2 { + t.Error("wrong request count:", reqCount) + } + enr := srv.LocalNode().Node() + if enr.IP().String() != "192.0.2.0" { + t.Error("wrong IP in ENR:", enr.IP()) + } + if enr.TCP() != 30000 { + t.Error("wrong TCP port in ENR:", enr.TCP()) + } + if enr.UDP() != 30000 { + t.Error("wrong UDP port in ENR:", enr.UDP()) + } +} + +type mockNAT struct { + mappedPort uint16 + mapRequests atomic.Int32 + unmapRequests atomic.Int32 + ipRequests atomic.Int32 +} + +func (m *mockNAT) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) { + m.mapRequests.Add(1) + return m.mappedPort, nil +} + +func (m *mockNAT) DeleteMapping(protocol string, extport, intport int) error { + m.unmapRequests.Add(1) + return nil +} + +func (m *mockNAT) ExternalIP() (net.IP, error) { + m.ipRequests.Add(1) + return net.ParseIP("192.0.2.0"), nil +} + +func (m *mockNAT) String() string { + return "mockNAT" +} diff --git a/p2p/server_test.go b/p2p/server_test.go index f6f5700c5efc..a0491e984a79 100644 --- a/p2p/server_test.go +++ b/p2p/server_test.go @@ -24,6 +24,8 @@ import ( "math/rand" "net" "reflect" + "strconv" + "strings" "testing" "time" @@ -224,6 +226,14 @@ func TestServerRemovePeerDisconnect(t *testing.T) { srv2.Start() defer srv2.Stop() + s := strings.Split(srv2.ListenAddr, ":") + if len(s) != 2 { + t.Fatal("invalid ListenAddr") + } + if port, err := strconv.Atoi(s[1]); err == nil { + srv2.localnode.Set(enr.TCP(uint16(port))) + } + if !syncAddPeer(srv1, srv2.Self()) { t.Fatal("peer not connected") } @@ -370,8 +380,6 @@ func TestServerSetupConn(t *testing.T) { clientkey, srvkey = newkey(), newkey() clientpub = &clientkey.PublicKey srvpub = &srvkey.PublicKey - fooErr = errors.New("foo") - readErr = errors.New("read error") ) tests := []struct { dontstart bool @@ -389,10 +397,10 @@ func TestServerSetupConn(t *testing.T) { wantCloseErr: errServerStopped, }, { - tt: &setupTransport{pubkey: clientpub, encHandshakeErr: readErr}, + tt: &setupTransport{pubkey: clientpub, encHandshakeErr: errEncHandshakeError}, flags: inboundConn, wantCalls: "doEncHandshake,close,", - wantCloseErr: readErr, + wantCloseErr: errEncHandshakeError, }, { tt: &setupTransport{pubkey: clientpub, phs: protoHandshake{ID: randomID().Bytes()}}, @@ -402,11 +410,11 @@ func TestServerSetupConn(t *testing.T) { wantCloseErr: DiscUnexpectedIdentity, }, { - tt: &setupTransport{pubkey: clientpub, protoHandshakeErr: fooErr}, + tt: &setupTransport{pubkey: clientpub, protoHandshakeErr: errProtoHandshakeError}, dialDest: enode.NewV4(clientpub, nil, 0, 0), flags: dynDialedConn, wantCalls: "doEncHandshake,doProtoHandshake,close,", - wantCloseErr: fooErr, + wantCloseErr: errProtoHandshakeError, }, { tt: &setupTransport{pubkey: srvpub, phs: protoHandshake{ID: crypto.FromECDSAPub(srvpub)[1:]}}, diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 36b5286517ae..c52917fd0ae5 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -147,7 +147,7 @@ func (s *SimAdapter) DialRPC(id enode.ID) (*rpc.Client, error) { if !ok { return nil, fmt.Errorf("unknown node: %s", id) } - return node.node.Attach() + return node.node.Attach(), nil } // GetNode returns the node with the given ID if it exists @@ -274,10 +274,7 @@ func (sn *SimNode) Start(snapshots map[string][]byte) error { } // create an in-process RPC client - client, err := sn.node.Attach() - if err != nil { - return err - } + client := sn.node.Attach() sn.lock.Lock() sn.client = client sn.lock.Unlock() diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go index d9b51dc09bb0..f6cf5113a6ee 100644 --- a/p2p/simulations/examples/ping-pong.go +++ b/p2p/simulations/examples/ping-pong.go @@ -91,7 +91,7 @@ func main() { type pingPongService struct { id enode.ID log log.Logger - received int64 + received atomic.Int64 } func newPingPongService(id enode.ID) *pingPongService { @@ -125,7 +125,7 @@ func (p *pingPongService) Info() interface{} { return struct { Received int64 `json:"received"` }{ - atomic.LoadInt64(&p.received), + p.received.Load(), } } @@ -162,7 +162,7 @@ func (p *pingPongService) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error { return } log.Info("received message", "msg.code", msg.Code, "msg.payload", string(payload)) - atomic.AddInt64(&p.received, 1) + p.received.Add(1) if msg.Code == pingMsgCode { log.Info("sending pong") go p2p.Send(rw, pongMsgCode, "PONG") diff --git a/params/bootnodes.go b/params/bootnodes.go index 4ae94cfbd322..a843896914a6 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -28,6 +28,14 @@ var MainnetBootnodes = []string{ "enode://4aeb4ab6c14b23e2c4cfdce879c04b0748a20d8e9b59e25ded2a08143e265c6c25936e74cbc8e641e3312ca288673d91f2f93f8e277de3cfa444ecdaaf982052@157.90.35.166:30303", // bootnode-hetzner-fsn } +// HoleskyBootnodes are the enode URLs of the P2P bootstrap nodes running on the +// Holesky test network. +var HoleskyBootnodes = []string{ + // EF DevOps + "enode://ac906289e4b7f12df423d654c5a962b6ebe5b3a74cc9e06292a85221f9a64a6f1cfdd6b714ed6dacef51578f92b34c60ee91e9ede9c7f8fadc4d347326d95e2b@146.190.13.128:30303", + "enode://a3435a0155a3e837c02f5e7f5662a2f1fbc25b48e4dc232016e1c51b544cb5b4510ef633ea3278c0e970fa8ad8141e2d4d0f9f95456c537ff05fdf9b31c15072@178.128.136.233:30303", +} + // SepoliaBootnodes are the enode URLs of the P2P bootstrap nodes running on the // Sepolia test network. var SepoliaBootnodes = []string{ @@ -39,13 +47,6 @@ var SepoliaBootnodes = []string{ "enode://9e9492e2e8836114cc75f5b929784f4f46c324ad01daf87d956f98b3b6c5fcba95524d6e5cf9861dc96a2c8a171ea7105bb554a197455058de185fa870970c7c@138.68.123.152:30303", // sepolia-bootnode-1-ams3 } -// RinkebyBootnodes are the enode URLs of the P2P bootstrap nodes running on the -// Rinkeby test network. -var RinkebyBootnodes = []string{ - "enode://a24ac7c5484ef4ed0c5eb2d36620ba4e4aa13b8c84684e1b4aab0cebea2ae45cb4d375b77eab56516d34bfbd3c1a833fc51296ff084b770b94fb9028c4d25ccf@52.169.42.101:30303", // IE - "enode://b6b28890b006743680c52e64e0d16db57f28124885595fa03a562be1d2bf0f3a1da297d56b13da25fb992888fd556d4c1a27b1f39d531bde7de1921c90061cc6@159.89.28.211:30303", // AKASHA -} - // GoerliBootnodes are the enode URLs of the P2P bootstrap nodes running on the // Görli test network. var GoerliBootnodes = []string{ @@ -91,12 +92,12 @@ func KnownDNSNetwork(genesis common.Hash, protocol string) string { switch genesis { case MainnetGenesisHash: net = "mainnet" - case RinkebyGenesisHash: - net = "rinkeby" case GoerliGenesisHash: net = "goerli" case SepoliaGenesisHash: net = "sepolia" + case HoleskyGenesisHash: + net = "holesky" default: return "" } diff --git a/params/config.go b/params/config.go index a5c518aa8d30..79fc9622396a 100644 --- a/params/config.go +++ b/params/config.go @@ -26,8 +26,8 @@ import ( // Genesis hashes to enforce below configs on. var ( MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") + HoleskyGenesisHash = common.HexToHash("0xb5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4") SepoliaGenesisHash = common.HexToHash("0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9") - RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a") ) @@ -59,6 +59,30 @@ var ( ShanghaiTime: newUint64(1681338455), Ethash: new(EthashConfig), } + // HoleskyChainConfig contains the chain parameters to run a node on the Holesky test network. + HoleskyChainConfig = &ChainConfig{ + ChainID: big.NewInt(17000), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: true, + 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: nil, + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: nil, + GrayGlacierBlock: nil, + TerminalTotalDifficulty: big.NewInt(0), + TerminalTotalDifficultyPassed: true, + MergeNetsplitBlock: nil, + ShanghaiTime: newUint64(1696000704), + Ethash: new(EthashConfig), + } // SepoliaChainConfig contains the chain parameters to run a node on the Sepolia test network. SepoliaChainConfig = &ChainConfig{ ChainID: big.NewInt(11155111), @@ -75,34 +99,14 @@ var ( MuirGlacierBlock: big.NewInt(0), BerlinBlock: big.NewInt(0), LondonBlock: big.NewInt(0), + ArrowGlacierBlock: nil, + GrayGlacierBlock: nil, TerminalTotalDifficulty: big.NewInt(17_000_000_000_000_000), TerminalTotalDifficultyPassed: true, MergeNetsplitBlock: big.NewInt(1735371), ShanghaiTime: newUint64(1677557088), Ethash: new(EthashConfig), } - // RinkebyChainConfig contains the chain parameters to run a node on the Rinkeby test network. - RinkebyChainConfig = &ChainConfig{ - ChainID: big.NewInt(4), - HomesteadBlock: big.NewInt(1), - DAOForkBlock: nil, - DAOForkSupport: true, - EIP150Block: big.NewInt(2), - EIP155Block: big.NewInt(3), - EIP158Block: big.NewInt(3), - ByzantiumBlock: big.NewInt(1_035_301), - ConstantinopleBlock: big.NewInt(3_660_663), - PetersburgBlock: big.NewInt(4_321_234), - IstanbulBlock: big.NewInt(5_435_345), - MuirGlacierBlock: nil, - BerlinBlock: big.NewInt(8_290_928), - LondonBlock: big.NewInt(8_897_988), - ArrowGlacierBlock: nil, - Clique: &CliqueConfig{ - Period: 15, - Epoch: 30000, - }, - } // GoerliChainConfig contains the chain parameters to run a node on the Görli test network. GoerliChainConfig = &ChainConfig{ ChainID: big.NewInt(5), @@ -151,6 +155,7 @@ var ( ShanghaiTime: nil, CancunTime: nil, PragueTime: nil, + VerkleTime: nil, TerminalTotalDifficulty: nil, TerminalTotalDifficultyPassed: true, Ethash: new(EthashConfig), @@ -158,6 +163,27 @@ var ( ArbitrumChainParams: DisableArbitrumParams(), } + AllDevChainProtocolChanges = &ChainConfig{ + ChainID: big.NewInt(1337), + 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), + GrayGlacierBlock: big.NewInt(0), + ShanghaiTime: newUint64(0), + TerminalTotalDifficulty: big.NewInt(0), + TerminalTotalDifficultyPassed: true, + IsDevMode: true, + } + // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. AllCliqueProtocolChanges = &ChainConfig{ @@ -181,6 +207,7 @@ var ( ShanghaiTime: nil, CancunTime: nil, PragueTime: nil, + VerkleTime: nil, TerminalTotalDifficulty: nil, TerminalTotalDifficultyPassed: false, Ethash: nil, @@ -189,7 +216,7 @@ var ( } // TestChainConfig contains every protocol change (EIPs) introduced - // and accepted by the Ethereum core developers for testing proposes. + // and accepted by the Ethereum core developers for testing purposes. TestChainConfig = &ChainConfig{ ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), @@ -211,6 +238,7 @@ var ( ShanghaiTime: nil, CancunTime: nil, PragueTime: nil, + VerkleTime: nil, TerminalTotalDifficulty: nil, TerminalTotalDifficultyPassed: false, Ethash: new(EthashConfig), @@ -241,6 +269,7 @@ var ( ShanghaiTime: nil, CancunTime: nil, PragueTime: nil, + VerkleTime: nil, TerminalTotalDifficulty: nil, TerminalTotalDifficultyPassed: false, Ethash: new(EthashConfig), @@ -253,9 +282,9 @@ var ( // NetworkNames are user friendly names to use in the chain spec banner. var NetworkNames = map[string]string{ MainnetChainConfig.ChainID.String(): "mainnet", - RinkebyChainConfig.ChainID.String(): "rinkeby", GoerliChainConfig.ChainID.String(): "goerli", SepoliaChainConfig.ChainID.String(): "sepolia", + HoleskyChainConfig.ChainID.String(): "holesky", } // ChainConfig is the core config which determines the blockchain settings. @@ -292,6 +321,7 @@ type ChainConfig struct { ShanghaiTime *uint64 `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai) CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun) PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague) + VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle) // TerminalTotalDifficulty is the amount of total difficulty reached by // the network that triggers the consensus upgrade. @@ -303,8 +333,9 @@ type ChainConfig struct { TerminalTotalDifficultyPassed bool `json:"terminalTotalDifficultyPassed,omitempty"` // Various consensus engines - Ethash *EthashConfig `json:"ethash,omitempty"` - Clique *CliqueConfig `json:"clique,omitempty"` + Ethash *EthashConfig `json:"ethash,omitempty"` + Clique *CliqueConfig `json:"clique,omitempty"` + IsDevMode bool `json:"isDev,omitempty"` ArbitrumChainParams ArbitrumChainParams `json:"arbitrum,omitempty"` } @@ -332,7 +363,7 @@ func (c *CliqueConfig) String() string { func (c *ChainConfig) Description() string { var banner string - // Create some basinc network config output + // Create some basic network config output network := NetworkNames[c.ChainID.String()] if network == "" { network = "unknown" @@ -414,6 +445,9 @@ func (c *ChainConfig) Description() string { if c.PragueTime != nil { banner += fmt.Sprintf(" - Prague: @%-10v\n", *c.PragueTime) } + if c.VerkleTime != nil { + banner += fmt.Sprintf(" - Verkle: @%-10v\n", *c.VerkleTime) + } return banner } @@ -510,7 +544,10 @@ func (c *ChainConfig) IsShanghai(num *big.Int, time uint64, currentArbosVersion } // IsCancun returns whether num is either equal to the Cancun fork time or greater. -func (c *ChainConfig) IsCancun(num *big.Int, time uint64) bool { +func (c *ChainConfig) IsCancun(num *big.Int, time uint64, currentArbosVersion uint64) bool { + if c.IsArbitrum() { + return currentArbosVersion >= 20 + } return c.IsLondon(num) && isTimestampForked(c.CancunTime, time) } @@ -519,6 +556,11 @@ func (c *ChainConfig) IsPrague(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.PragueTime, time) } +// IsVerkle returns whether num is either equal to the Verkle fork time or greater. +func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool { + return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time) +} + // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time uint64) *ConfigCompatError { @@ -573,6 +615,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "shanghaiTime", timestamp: c.ShanghaiTime}, {name: "cancunTime", timestamp: c.CancunTime, optional: true}, {name: "pragueTime", timestamp: c.PragueTime, optional: true}, + {name: "verkleTime", timestamp: c.VerkleTime, optional: true}, } { if lastFork.name != "" { switch { @@ -680,6 +723,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int, if isForkTimestampIncompatible(c.PragueTime, newcfg.PragueTime, headTimestamp) { return newTimestampCompatError("Prague fork timestamp", c.PragueTime, newcfg.PragueTime) } + if isForkTimestampIncompatible(c.VerkleTime, newcfg.VerkleTime, headTimestamp) { + return newTimestampCompatError("Verkle fork timestamp", c.VerkleTime, newcfg.VerkleTime) + } return nil } @@ -826,6 +872,7 @@ type Rules struct { IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsBerlin, IsLondon bool IsMerge, IsShanghai, IsCancun, IsPrague bool + IsVerkle bool } // Rules ensures c's ChainID is not nil. @@ -849,7 +896,8 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64, curren IsLondon: c.IsLondon(num), IsMerge: isMerge, IsShanghai: c.IsShanghai(num, timestamp, currentArbosVersion), - IsCancun: c.IsCancun(num, timestamp), + IsCancun: c.IsCancun(num, timestamp, currentArbosVersion), IsPrague: c.IsPrague(num, timestamp), + IsVerkle: c.IsVerkle(num, timestamp), } } diff --git a/params/config_arbitrum.go b/params/config_arbitrum.go index 516fb085af22..a22052a028c9 100644 --- a/params/config_arbitrum.go +++ b/params/config_arbitrum.go @@ -29,6 +29,8 @@ type ArbitrumChainParams struct { InitialArbOSVersion uint64 InitialChainOwner common.Address GenesisBlockNum uint64 + MaxCodeSize uint64 `json:"MaxCodeSize,omitempty"` // Maximum bytecode to permit for a contract. 0 value implies params.DefaultMaxCodeSize + MaxInitCodeSize uint64 `json:"MaxInitCodeSize,omitempty"` // Maximum initcode to permit in a creation transaction and create instructions. 0 value implies params.DefaultMaxInitCodeSize } func (c *ChainConfig) IsArbitrum() bool { @@ -39,6 +41,20 @@ func (c *ChainConfig) IsArbitrumNitro(num *big.Int) bool { return c.IsArbitrum() && isBlockForked(new(big.Int).SetUint64(c.ArbitrumChainParams.GenesisBlockNum), num) } +func (c *ChainConfig) MaxCodeSize() uint64 { + if c.ArbitrumChainParams.MaxCodeSize == 0 { + return DefaultMaxCodeSize + } + return c.ArbitrumChainParams.MaxCodeSize +} + +func (c *ChainConfig) MaxInitCodeSize() uint64 { + if c.ArbitrumChainParams.MaxInitCodeSize == 0 { + return c.MaxCodeSize() * 2 + } + return c.ArbitrumChainParams.MaxInitCodeSize +} + func (c *ChainConfig) DebugMode() bool { return c.ArbitrumChainParams.AllowDebugPrecompiles } @@ -89,22 +105,12 @@ func ArbitrumRollupGoerliTestnetParams() ArbitrumChainParams { } } -func ArbitrumRinkebyTestParams() ArbitrumChainParams { - return ArbitrumChainParams{ - EnableArbOS: true, - AllowDebugPrecompiles: false, - DataAvailabilityCommittee: false, - InitialArbOSVersion: 3, - InitialChainOwner: common.HexToAddress("0x06C7DBC804D7BcD881D7b86b667893736b8e0Be2"), - } -} - func ArbitrumDevTestParams() ArbitrumChainParams { return ArbitrumChainParams{ EnableArbOS: true, AllowDebugPrecompiles: true, DataAvailabilityCommittee: false, - InitialArbOSVersion: 11, + InitialArbOSVersion: 20, InitialChainOwner: common.Address{}, } } @@ -114,7 +120,7 @@ func ArbitrumDevTestDASParams() ArbitrumChainParams { EnableArbOS: true, AllowDebugPrecompiles: true, DataAvailabilityCommittee: true, - InitialArbOSVersion: 11, + InitialArbOSVersion: 20, InitialChainOwner: common.Address{}, } } @@ -283,30 +289,6 @@ func ArbitrumAnytrustGoerliTestnetChainConfig() *ChainConfig { } } -func ArbitrumRinkebyTestnetChainConfig() *ChainConfig { - return &ChainConfig{ - ChainID: big.NewInt(421611), - HomesteadBlock: big.NewInt(0), - DAOForkBlock: nil, - DAOForkSupport: true, - 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), - ArbitrumChainParams: ArbitrumRinkebyTestParams(), - Clique: &CliqueConfig{ - Period: 0, - Epoch: 0, - }, - } -} - var ArbitrumSupportedChainConfigs = []*ChainConfig{ ArbitrumOneChainConfig(), ArbitrumNovaChainConfig(), @@ -314,5 +296,4 @@ var ArbitrumSupportedChainConfigs = []*ChainConfig{ ArbitrumDevTestChainConfig(), ArbitrumDevTestDASChainConfig(), ArbitrumAnytrustGoerliTestnetChainConfig(), - ArbitrumRinkebyTestnetChainConfig(), } diff --git a/params/protocol_params.go b/params/protocol_params.go index 1fb258c1fc1e..0bf176c93d63 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -16,7 +16,11 @@ package params -import "math/big" +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" +) const ( GasLimitBoundDivisor uint64 = 1024 // The bound divisor of the gas limit, used in update calculations. @@ -124,8 +128,8 @@ const ( DefaultElasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have. InitialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks. - MaxCodeSize = 24576 // Maximum bytecode to permit for a contract - MaxInitCodeSize = 2 * MaxCodeSize // Maximum initcode to permit in a creation transaction and create instructions + DefaultMaxCodeSize = 24576 // Maximum bytecode to permit for a contract + DefaultMaxInitCodeSize = 2 * DefaultMaxCodeSize // Maximum initcode to permit in a creation transaction and create instructions // Precompiled contract gas prices @@ -160,9 +164,16 @@ const ( RefundQuotient uint64 = 2 RefundQuotientEIP3529 uint64 = 5 - BlobTxDataGasPerBlob = 1 << 17 // Gas consumption of a single data blob (== blob byte size) - BlobTxMinDataGasprice = 1 // Minimum gas price for data blobs - BlobTxDataGaspriceUpdateFraction = 2225652 // Controls the maximum rate of change for data gas price + BlobTxBytesPerFieldElement = 32 // Size in bytes of a field element + BlobTxFieldElementsPerBlob = 4096 // Number of field elements stored in a single data blob + BlobTxHashVersion = 0x01 // Version byte of the commitment hash + BlobTxBlobGasPerBlob = 1 << 17 // Gas consumption of a single data blob (== blob byte size) + BlobTxMinBlobGasprice = 1 // Minimum gas price for data blobs + BlobTxBlobGaspriceUpdateFraction = 3338477 // Controls the maximum rate of change for blob gas price + BlobTxPointEvaluationPrecompileGas = 50000 // Gas price for the point evaluation precompile. + + BlobTxTargetBlobGasPerBlock = 3 * BlobTxBlobGasPerBlob // Target consumable blob gas for data blobs per block (for 1559-like pricing) + MaxBlobGasPerBlock = 6 * BlobTxBlobGasPerBlob // Maximum consumable blob gas for data blobs per block ) // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations @@ -173,4 +184,9 @@ var ( GenesisDifficulty = big.NewInt(131072) // Difficulty of the Genesis block. MinimumDifficulty = big.NewInt(131072) // The minimum that the difficulty may ever be. DurationLimit = big.NewInt(13) // The decision boundary on the blocktime duration used to determine whether difficulty should go up or not. + + // BeaconRootsStorageAddress is the address where historical beacon roots are stored as per EIP-4788 + BeaconRootsStorageAddress = common.HexToAddress("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") + // SystemAddress is where the system-transaction is sent from as per EIP-4788 + SystemAddress common.Address = common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffffe") ) diff --git a/params/version.go b/params/version.go index 33c5399d11ed..130acceef7a3 100644 --- a/params/version.go +++ b/params/version.go @@ -22,8 +22,8 @@ import ( const ( VersionMajor = 1 // Major version component of the current release - VersionMinor = 12 // Minor version component of the current release - VersionPatch = 0 // Patch version component of the current release + VersionMinor = 13 // Minor version component of the current release + VersionPatch = 3 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string ) diff --git a/rlp/decode.go b/rlp/decode.go index c9b50e8c1879..9b17d2d81084 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -90,7 +90,7 @@ func Decode(r io.Reader, val interface{}) error { // DecodeBytes parses RLP data from b into val. Please see package-level documentation for // the decoding rules. The input must contain exactly one value and no trailing data. func DecodeBytes(b []byte, val interface{}) error { - r := bytes.NewReader(b) + r := (*sliceReader)(&b) stream := streamPool.Get().(*Stream) defer streamPool.Put(stream) @@ -99,7 +99,7 @@ func DecodeBytes(b []byte, val interface{}) error { if err := stream.Decode(val); err != nil { return err } - if r.Len() > 0 { + if len(b) > 0 { return ErrMoreThanOneValue } return nil @@ -1182,3 +1182,23 @@ func (s *Stream) listLimit() (inList bool, limit uint64) { } return true, s.stack[len(s.stack)-1] } + +type sliceReader []byte + +func (sr *sliceReader) Read(b []byte) (int, error) { + if len(*sr) == 0 { + return 0, io.EOF + } + n := copy(b, *sr) + *sr = (*sr)[n:] + return n, nil +} + +func (sr *sliceReader) ReadByte() (byte, error) { + if len(*sr) == 0 { + return 0, io.EOF + } + b := (*sr)[0] + *sr = (*sr)[1:] + return b, nil +} diff --git a/rlp/encbuffer.go b/rlp/encbuffer.go index 9ac4fcb2cabc..8d3a3b2293a5 100644 --- a/rlp/encbuffer.go +++ b/rlp/encbuffer.go @@ -56,18 +56,18 @@ func (buf *encBuffer) size() int { } // makeBytes creates the encoder output. -func (w *encBuffer) makeBytes() []byte { - out := make([]byte, w.size()) - w.copyTo(out) +func (buf *encBuffer) makeBytes() []byte { + out := make([]byte, buf.size()) + buf.copyTo(out) return out } -func (w *encBuffer) copyTo(dst []byte) { +func (buf *encBuffer) copyTo(dst []byte) { strpos := 0 pos := 0 - for _, head := range w.lheads { + for _, head := range buf.lheads { // write string data before header - n := copy(dst[pos:], w.str[strpos:head.offset]) + n := copy(dst[pos:], buf.str[strpos:head.offset]) pos += n strpos += n // write the header @@ -75,7 +75,7 @@ func (w *encBuffer) copyTo(dst []byte) { pos += len(enc) } // copy string data after the last list header - copy(dst[pos:], w.str[strpos:]) + copy(dst[pos:], buf.str[strpos:]) } // writeTo writes the encoder output to w. @@ -149,34 +149,34 @@ func (buf *encBuffer) writeString(s string) { const wordBytes = (32 << (uint64(^big.Word(0)) >> 63)) / 8 // writeBigInt writes i as an integer. -func (w *encBuffer) writeBigInt(i *big.Int) { +func (buf *encBuffer) writeBigInt(i *big.Int) { bitlen := i.BitLen() if bitlen <= 64 { - w.writeUint64(i.Uint64()) + buf.writeUint64(i.Uint64()) return } // Integer is larger than 64 bits, encode from i.Bits(). // The minimal byte length is bitlen rounded up to the next // multiple of 8, divided by 8. length := ((bitlen + 7) & -8) >> 3 - w.encodeStringHeader(length) - w.str = append(w.str, make([]byte, length)...) + buf.encodeStringHeader(length) + buf.str = append(buf.str, make([]byte, length)...) index := length - buf := w.str[len(w.str)-length:] + bytesBuf := buf.str[len(buf.str)-length:] for _, d := range i.Bits() { for j := 0; j < wordBytes && index > 0; j++ { index-- - buf[index] = byte(d) + bytesBuf[index] = byte(d) d >>= 8 } } } // writeUint256 writes z as an integer. -func (w *encBuffer) writeUint256(z *uint256.Int) { +func (buf *encBuffer) writeUint256(z *uint256.Int) { bitlen := z.BitLen() if bitlen <= 64 { - w.writeUint64(z.Uint64()) + buf.writeUint64(z.Uint64()) return } nBytes := byte((bitlen + 7) / 8) @@ -186,7 +186,7 @@ func (w *encBuffer) writeUint256(z *uint256.Int) { binary.BigEndian.PutUint64(b[17:25], z[1]) binary.BigEndian.PutUint64(b[25:33], z[0]) b[32-nBytes] = 0x80 + nBytes - w.str = append(w.str, b[32-nBytes:]...) + buf.str = append(buf.str, b[32-nBytes:]...) } // list adds a new list header to the header stack. It returns the index of the header. diff --git a/rlp/encode.go b/rlp/encode.go index 3fac0bd2d3c2..ffb42b29977c 100644 --- a/rlp/encode.go +++ b/rlp/encode.go @@ -419,7 +419,7 @@ func makeEncoderWriter(typ reflect.Type) writer { // package json simply doesn't call MarshalJSON for this case, but encodes the // value as if it didn't implement the interface. We don't want to handle it that // way. - return fmt.Errorf("rlp: unadressable value of type %v, EncodeRLP is pointer method", val.Type()) + return fmt.Errorf("rlp: unaddressable value of type %v, EncodeRLP is pointer method", val.Type()) } return val.Addr().Interface().(Encoder).EncodeRLP(w) } diff --git a/rlp/encode_test.go b/rlp/encode_test.go index 02be47d0ef1b..314958eb5604 100644 --- a/rlp/encode_test.go +++ b/rlp/encode_test.go @@ -396,7 +396,7 @@ var encTests = []encTest{ {val: &struct{ TE testEncoder }{testEncoder{errors.New("test error")}}, error: "test error"}, // Verify the error for non-addressable non-pointer Encoder. - {val: testEncoder{}, error: "rlp: unadressable value of type rlp.testEncoder, EncodeRLP is pointer method"}, + {val: testEncoder{}, error: "rlp: unaddressable value of type rlp.testEncoder, EncodeRLP is pointer method"}, // Verify Encoder takes precedence over []byte. {val: []byteEncoder{0, 1, 2, 3, 4}, output: "C5C0C0C0C0C0"}, diff --git a/rlp/rlpgen/main.go b/rlp/rlpgen/main.go index 25d4393cc656..b3a74b9df13f 100644 --- a/rlp/rlpgen/main.go +++ b/rlp/rlpgen/main.go @@ -73,9 +73,8 @@ type Config struct { func (cfg *Config) process() (code []byte, err error) { // Load packages. pcfg := &packages.Config{ - Mode: packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps, - Dir: cfg.Dir, - BuildFlags: []string{"-tags", "norlpgen"}, + Mode: packages.NeedName | packages.NeedTypes, + Dir: cfg.Dir, } ps, err := packages.Load(pcfg, pathOfPackageRLP, ".") if err != nil { @@ -117,8 +116,6 @@ func (cfg *Config) process() (code []byte, err error) { // This is done here to avoid processing these lines with gofmt. var header bytes.Buffer fmt.Fprint(&header, "// Code generated by rlpgen. DO NOT EDIT.\n\n") - fmt.Fprint(&header, "//go:build !norlpgen\n") - fmt.Fprint(&header, "// +build !norlpgen\n\n") return append(header.Bytes(), code...), nil } diff --git a/rpc/client.go b/rpc/client.go index 6d0e109943aa..f6a1d908222b 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -34,14 +34,15 @@ import ( var ( ErrBadResult = errors.New("bad result in JSON-RPC response") ErrClientQuit = errors.New("client is closed") - ErrNoResult = errors.New("no result in JSON-RPC response") + ErrNoResult = errors.New("JSON-RPC response has no result") + ErrMissingBatchResponse = errors.New("response batch did not contain a response to this call") ErrSubscriptionQueueOverflow = errors.New("subscription queue overflow") errClientReconnected = errors.New("client reconnected") errDead = errors.New("connection lost") ) +// Timeouts const ( - // Timeouts defaultDialTimeout = 10 * time.Second // used if context has no deadline subscribeTimeout = 10 * time.Second // overall timeout eth_subscribe, rpc_modules calls ) @@ -91,6 +92,10 @@ type Client struct { // This function, if non-nil, is called when the connection is lost. reconnectFunc reconnectFunc + // config fields + batchItemLimit int + batchResponseMaxSize int + // writeConn is used for writing to the connection on the caller's goroutine. It should // only be accessed outside of dispatch, with the write lock held. The write lock is // taken by sending on reqInit and released by sending on reqSent. @@ -121,7 +126,7 @@ func (c *Client) newClientConn(conn ServerCodec) *clientConn { ctx := context.Background() ctx = context.WithValue(ctx, clientContextKey{}, c) ctx = context.WithValue(ctx, peerInfoContextKey{}, conn.peerInfo()) - handler := newHandler(ctx, conn, c.idgen, c.services) + handler := newHandler(ctx, conn, c.idgen, c.services, c.batchItemLimit, c.batchResponseMaxSize) return &clientConn{conn, handler} } @@ -135,14 +140,17 @@ type readOp struct { batch bool } +// requestOp represents a pending request. This is used for both batch and non-batch +// requests. type requestOp struct { - ids []json.RawMessage - err error - resp chan *jsonrpcMessage // receives up to len(ids) responses - sub *ClientSubscription // only set for EthSubscribe requests + ids []json.RawMessage + err error + resp chan []*jsonrpcMessage // the response goes here + sub *ClientSubscription // set for Subscribe requests. + hadResponse bool // true when the request was responded to } -func (op *requestOp) wait(ctx context.Context, c *Client) (*jsonrpcMessage, error) { +func (op *requestOp) wait(ctx context.Context, c *Client) ([]*jsonrpcMessage, error) { select { case <-ctx.Done(): // Send the timeout to dispatch so it can remove the request IDs. @@ -218,7 +226,7 @@ func DialOptions(ctx context.Context, rawurl string, options ...ClientOption) (* return nil, fmt.Errorf("no known transport for URL scheme %q", u.Scheme) } - return newClient(ctx, reconnect) + return newClient(ctx, cfg, reconnect) } // ClientFromContext retrieves the client from the context, if any. This can be used to perform @@ -228,33 +236,42 @@ func ClientFromContext(ctx context.Context) (*Client, bool) { return client, ok } -func newClient(initctx context.Context, connect reconnectFunc) (*Client, error) { +func newClient(initctx context.Context, cfg *clientConfig, connect reconnectFunc) (*Client, error) { conn, err := connect(initctx) if err != nil { return nil, err } - c := initClient(conn, randomIDGenerator(), new(serviceRegistry)) + c := initClient(conn, new(serviceRegistry), cfg) c.reconnectFunc = connect return c, nil } -func initClient(conn ServerCodec, idgen func() ID, services *serviceRegistry) *Client { +func initClient(conn ServerCodec, services *serviceRegistry, cfg *clientConfig) *Client { _, isHTTP := conn.(*httpConn) c := &Client{ - isHTTP: isHTTP, - idgen: idgen, - services: services, - writeConn: conn, - close: make(chan struct{}), - closing: make(chan struct{}), - didClose: make(chan struct{}), - reconnected: make(chan ServerCodec), - readOp: make(chan readOp), - readErr: make(chan error), - reqInit: make(chan *requestOp), - reqSent: make(chan error, 1), - reqTimeout: make(chan *requestOp), - } + isHTTP: isHTTP, + services: services, + idgen: cfg.idgen, + batchItemLimit: cfg.batchItemLimit, + batchResponseMaxSize: cfg.batchResponseLimit, + writeConn: conn, + close: make(chan struct{}), + closing: make(chan struct{}), + didClose: make(chan struct{}), + reconnected: make(chan ServerCodec), + readOp: make(chan readOp), + readErr: make(chan error), + reqInit: make(chan *requestOp), + reqSent: make(chan error, 1), + reqTimeout: make(chan *requestOp), + } + + // Set defaults. + if c.idgen == nil { + c.idgen = randomIDGenerator() + } + + // Launch the main loop. if !isHTTP { go c.dispatch(conn) } @@ -332,7 +349,10 @@ func (c *Client) CallContext(ctx context.Context, result interface{}, method str if err != nil { return err } - op := &requestOp{ids: []json.RawMessage{msg.ID}, resp: make(chan *jsonrpcMessage, 1)} + op := &requestOp{ + ids: []json.RawMessage{msg.ID}, + resp: make(chan []*jsonrpcMessage, 1), + } if c.isHTTP { err = c.sendHTTP(ctx, op, msg) @@ -344,9 +364,12 @@ func (c *Client) CallContext(ctx context.Context, result interface{}, method str } // dispatch has accepted the request and will close the channel when it quits. - switch resp, err := op.wait(ctx, c); { - case err != nil: + batchresp, err := op.wait(ctx, c) + if err != nil { return err + } + resp := batchresp[0] + switch { case resp.Error != nil: return resp.Error case len(resp.Result) == 0: @@ -388,7 +411,7 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error { ) op := &requestOp{ ids: make([]json.RawMessage, len(b)), - resp: make(chan *jsonrpcMessage, len(b)), + resp: make(chan []*jsonrpcMessage, 1), } for i, elem := range b { msg, err := c.newMessage(elem.Method, elem.Args...) @@ -406,28 +429,48 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error { } else { err = c.send(ctx, op, msgs) } + if err != nil { + return err + } + + batchresp, err := op.wait(ctx, c) + if err != nil { + return err + } // Wait for all responses to come back. - for n := 0; n < len(b) && err == nil; n++ { - var resp *jsonrpcMessage - resp, err = op.wait(ctx, c) - if err != nil { - break + for n := 0; n < len(batchresp) && err == nil; n++ { + resp := batchresp[n] + if resp == nil { + // Ignore null responses. These can happen for batches sent via HTTP. + continue } + // Find the element corresponding to this response. - // The element is guaranteed to be present because dispatch - // only sends valid IDs to our channel. - elem := &b[byID[string(resp.ID)]] - if resp.Error != nil { - elem.Error = resp.Error + index, ok := byID[string(resp.ID)] + if !ok { continue } - if len(resp.Result) == 0 { + delete(byID, string(resp.ID)) + + // Assign result and error. + elem := &b[index] + switch { + case resp.Error != nil: + elem.Error = resp.Error + case resp.Result == nil: elem.Error = ErrNoResult - continue + default: + elem.Error = json.Unmarshal(resp.Result, elem.Result) } - elem.Error = json.Unmarshal(resp.Result, elem.Result) } + + // Check that all expected responses have been received. + for _, index := range byID { + elem := &b[index] + elem.Error = ErrMissingBatchResponse + } + return err } @@ -488,7 +531,7 @@ func (c *Client) Subscribe(ctx context.Context, namespace string, channel interf } op := &requestOp{ ids: []json.RawMessage{msg.ID}, - resp: make(chan *jsonrpcMessage), + resp: make(chan []*jsonrpcMessage, 1), sub: newClientSubscription(c, namespace, chanVal), } @@ -503,6 +546,13 @@ func (c *Client) Subscribe(ctx context.Context, namespace string, channel interf return op.sub, nil } +// SupportsSubscriptions reports whether subscriptions are supported by the client +// transport. When this returns false, Subscribe and related methods will return +// ErrNotificationsUnsupported. +func (c *Client) SupportsSubscriptions() bool { + return !c.isHTTP +} + func (c *Client) newMessage(method string, paramsIn ...interface{}) (*jsonrpcMessage, error) { msg := &jsonrpcMessage{Version: vsn, ID: c.nextID(), Method: method} if paramsIn != nil { // prevent sending "params":null diff --git a/rpc/client_opt.go b/rpc/client_opt.go index 5ad7c22b3ce7..3fa045a9b9f3 100644 --- a/rpc/client_opt.go +++ b/rpc/client_opt.go @@ -28,11 +28,19 @@ type ClientOption interface { } type clientConfig struct { + // HTTP settings httpClient *http.Client httpHeaders http.Header httpAuth HTTPAuth - wsDialer *websocket.Dialer + // WebSocket options + wsDialer *websocket.Dialer + wsMessageSizeLimit *int64 // wsMessageSizeLimit nil = default, 0 = no limit + + // RPC handler options + idgen func() ID + batchItemLimit int + batchResponseLimit int } func (cfg *clientConfig) initHeaders() { @@ -59,6 +67,14 @@ func WithWebsocketDialer(dialer websocket.Dialer) ClientOption { }) } +// WithWebsocketMessageSizeLimit configures the websocket message size limit used by the RPC +// client. Passing a limit of 0 means no limit. +func WithWebsocketMessageSizeLimit(messageSizeLimit int64) ClientOption { + return optionFunc(func(cfg *clientConfig) { + cfg.wsMessageSizeLimit = &messageSizeLimit + }) +} + // WithHeader configures HTTP headers set by the RPC client. Headers set using this option // will be used for both HTTP and WebSocket connections. func WithHeader(key, value string) ClientOption { @@ -104,3 +120,25 @@ func WithHTTPAuth(a HTTPAuth) ClientOption { // Usually, HTTPAuth functions will call h.Set("authorization", "...") to add // auth information to the request. type HTTPAuth func(h http.Header) error + +// WithBatchItemLimit changes the maximum number of items allowed in batch requests. +// +// Note: this option applies when processing incoming batch requests. It does not affect +// batch requests sent by the client. +func WithBatchItemLimit(limit int) ClientOption { + return optionFunc(func(cfg *clientConfig) { + cfg.batchItemLimit = limit + }) +} + +// WithBatchResponseSizeLimit changes the maximum number of response bytes that can be +// generated for batch requests. When this limit is reached, further calls in the batch +// will not be processed. +// +// Note: this option applies when processing incoming batch requests. It does not affect +// batch requests sent by the client. +func WithBatchResponseSizeLimit(sizeLimit int) ClientOption { + return optionFunc(func(cfg *clientConfig) { + cfg.batchResponseLimit = sizeLimit + }) +} diff --git a/rpc/client_test.go b/rpc/client_test.go index a94a54929b31..7c96b2d6667b 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -169,10 +169,12 @@ func TestClientBatchRequest(t *testing.T) { } } +// This checks that, for HTTP connections, the length of batch responses is validated to +// match the request exactly. func TestClientBatchRequest_len(t *testing.T) { b, err := json.Marshal([]jsonrpcMessage{ - {Version: "2.0", ID: json.RawMessage("1"), Method: "foo", Result: json.RawMessage(`"0x1"`)}, - {Version: "2.0", ID: json.RawMessage("2"), Method: "bar", Result: json.RawMessage(`"0x2"`)}, + {Version: "2.0", ID: json.RawMessage("1"), Result: json.RawMessage(`"0x1"`)}, + {Version: "2.0", ID: json.RawMessage("2"), Result: json.RawMessage(`"0x2"`)}, }) if err != nil { t.Fatal("failed to encode jsonrpc message:", err) @@ -185,37 +187,102 @@ func TestClientBatchRequest_len(t *testing.T) { })) t.Cleanup(s.Close) - client, err := Dial(s.URL) - if err != nil { - t.Fatal("failed to dial test server:", err) - } - defer client.Close() - t.Run("too-few", func(t *testing.T) { + client, err := Dial(s.URL) + if err != nil { + t.Fatal("failed to dial test server:", err) + } + defer client.Close() + batch := []BatchElem{ - {Method: "foo"}, - {Method: "bar"}, - {Method: "baz"}, + {Method: "foo", Result: new(string)}, + {Method: "bar", Result: new(string)}, + {Method: "baz", Result: new(string)}, } ctx, cancelFn := context.WithTimeout(context.Background(), time.Second) defer cancelFn() - if err := client.BatchCallContext(ctx, batch); !errors.Is(err, ErrBadResult) { - t.Errorf("expected %q but got: %v", ErrBadResult, err) + + if err := client.BatchCallContext(ctx, batch); err != nil { + t.Fatal("error:", err) + } + for i, elem := range batch[:2] { + if elem.Error != nil { + t.Errorf("expected no error for batch element %d, got %q", i, elem.Error) + } + } + for i, elem := range batch[2:] { + if elem.Error != ErrMissingBatchResponse { + t.Errorf("wrong error %q for batch element %d", elem.Error, i+2) + } } }) t.Run("too-many", func(t *testing.T) { + client, err := Dial(s.URL) + if err != nil { + t.Fatal("failed to dial test server:", err) + } + defer client.Close() + batch := []BatchElem{ - {Method: "foo"}, + {Method: "foo", Result: new(string)}, } ctx, cancelFn := context.WithTimeout(context.Background(), time.Second) defer cancelFn() - if err := client.BatchCallContext(ctx, batch); !errors.Is(err, ErrBadResult) { - t.Errorf("expected %q but got: %v", ErrBadResult, err) + + if err := client.BatchCallContext(ctx, batch); err != nil { + t.Fatal("error:", err) + } + for i, elem := range batch[:1] { + if elem.Error != nil { + t.Errorf("expected no error for batch element %d, got %q", i, elem.Error) + } + } + for i, elem := range batch[1:] { + if elem.Error != ErrMissingBatchResponse { + t.Errorf("wrong error %q for batch element %d", elem.Error, i+2) + } } }) } +// This checks that the client can handle the case where the server doesn't +// respond to all requests in a batch. +func TestClientBatchRequestLimit(t *testing.T) { + server := newTestServer() + defer server.Stop() + server.SetBatchLimits(2, 100000) + client := DialInProc(server) + + batch := []BatchElem{ + {Method: "foo"}, + {Method: "bar"}, + {Method: "baz"}, + } + err := client.BatchCall(batch) + if err != nil { + t.Fatal("unexpected error:", err) + } + + // Check that the first response indicates an error with batch size. + var err0 Error + if !errors.As(batch[0].Error, &err0) { + t.Log("error zero:", batch[0].Error) + t.Fatalf("batch elem 0 has wrong error type: %T", batch[0].Error) + } else { + if err0.ErrorCode() != -32600 || err0.Error() != errMsgBatchTooLarge { + t.Fatalf("wrong error on batch elem zero: %v", err0) + } + } + + // Check that remaining response batch elements are reported as absent. + for i, elem := range batch[1:] { + if elem.Error != ErrMissingBatchResponse { + t.Fatalf("batch elem %d has unexpected error: %v", i+1, elem.Error) + } + } +} + func TestClientNotify(t *testing.T) { server := newTestServer() defer server.Stop() @@ -310,7 +377,7 @@ func testClientCancel(transport string, t *testing.T) { _, hasDeadline := ctx.Deadline() t.Errorf("no error for call with %v wait time (deadline: %v)", timeout, hasDeadline) // default: - // t.Logf("got expected error with %v wait time: %v", timeout, err) + // t.Logf("got expected error with %v wait time: %v", timeout, err) } cancel() } @@ -487,7 +554,8 @@ func TestClientSubscriptionUnsubscribeServer(t *testing.T) { defer srv.Stop() // Create the client on the other end of the pipe. - client, _ := newClient(context.Background(), func(context.Context) (ServerCodec, error) { + cfg := new(clientConfig) + client, _ := newClient(context.Background(), cfg, func(context.Context) (ServerCodec, error) { return NewCodec(p2), nil }) defer client.Close() diff --git a/rpc/constants_unix_nocgo.go b/rpc/constants_unix_nocgo.go deleted file mode 100644 index a62e4ee529df..000000000000 --- a/rpc/constants_unix_nocgo.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -//go:build !cgo && !windows -// +build !cgo,!windows - -package rpc - -var ( - // On Linux, sun_path is 108 bytes in size - // see http://man7.org/linux/man-pages/man7/unix.7.html - max_path_size = 108 -) diff --git a/rpc/errors.go b/rpc/errors.go index 7188332d551e..438aff218c2e 100644 --- a/rpc/errors.go +++ b/rpc/errors.go @@ -58,15 +58,19 @@ var ( ) const ( - errcodeDefault = -32000 - errcodeNotificationsUnsupported = -32001 - errcodeTimeout = -32002 - errcodePanic = -32603 - errcodeMarshalError = -32603 + errcodeDefault = -32000 + errcodeTimeout = -32002 + errcodeResponseTooLarge = -32003 + errcodePanic = -32603 + errcodeMarshalError = -32603 + + legacyErrcodeNotificationsUnsupported = -32001 ) const ( - errMsgTimeout = "request timed out" + errMsgTimeout = "request timed out" + errMsgResponseTooLarge = "response too large" + errMsgBatchTooLarge = "batch too large" ) type methodNotFoundError struct{ method string } @@ -77,6 +81,34 @@ func (e *methodNotFoundError) Error() string { return fmt.Sprintf("the method %s does not exist/is not available", e.method) } +type notificationsUnsupportedError struct{} + +func (e notificationsUnsupportedError) Error() string { + return "notifications not supported" +} + +func (e notificationsUnsupportedError) ErrorCode() int { return -32601 } + +// Is checks for equivalence to another error. Here we define that all errors with code +// -32601 (method not found) are equivalent to notificationsUnsupportedError. This is +// done to enable the following pattern: +// +// sub, err := client.Subscribe(...) +// if errors.Is(err, rpc.ErrNotificationsUnsupported) { +// // server doesn't support subscriptions +// } +func (e notificationsUnsupportedError) Is(other error) bool { + if other == (notificationsUnsupportedError{}) { + return true + } + rpcErr, ok := other.(Error) + if ok { + code := rpcErr.ErrorCode() + return code == -32601 || code == legacyErrcodeNotificationsUnsupported + } + return false +} + type subscriptionNotFoundError struct{ namespace, subscription string } func (e *subscriptionNotFoundError) ErrorCode() int { return -32601 } diff --git a/rpc/handler.go b/rpc/handler.go index 965344d8beaf..f44e4d7b01d8 100644 --- a/rpc/handler.go +++ b/rpc/handler.go @@ -19,7 +19,6 @@ package rpc import ( "context" "encoding/json" - "fmt" "reflect" "strconv" "strings" @@ -50,17 +49,19 @@ import ( // h.removeRequestOp(op) // timeout, etc. // } type handler struct { - reg *serviceRegistry - unsubscribeCb *callback - idgen func() ID // subscription ID generator - respWait map[string]*requestOp // active client requests - clientSubs map[string]*ClientSubscription // active client subscriptions - callWG sync.WaitGroup // pending call goroutines - rootCtx context.Context // canceled by close() - cancelRoot func() // cancel function for rootCtx - conn jsonWriter // where responses will be sent - log log.Logger - allowSubscribe bool + reg *serviceRegistry + unsubscribeCb *callback + idgen func() ID // subscription ID generator + respWait map[string]*requestOp // active client requests + clientSubs map[string]*ClientSubscription // active client subscriptions + callWG sync.WaitGroup // pending call goroutines + rootCtx context.Context // canceled by close() + cancelRoot func() // cancel function for rootCtx + conn jsonWriter // where responses will be sent + log log.Logger + allowSubscribe bool + batchRequestLimit int + batchResponseMaxSize int subLock sync.Mutex serverSubs map[ID]*Subscription @@ -71,19 +72,21 @@ type callProc struct { notifiers []*Notifier } -func newHandler(connCtx context.Context, conn jsonWriter, idgen func() ID, reg *serviceRegistry) *handler { +func newHandler(connCtx context.Context, conn jsonWriter, idgen func() ID, reg *serviceRegistry, batchRequestLimit, batchResponseMaxSize int) *handler { rootCtx, cancelRoot := context.WithCancel(connCtx) h := &handler{ - reg: reg, - idgen: idgen, - conn: conn, - respWait: make(map[string]*requestOp), - clientSubs: make(map[string]*ClientSubscription), - rootCtx: rootCtx, - cancelRoot: cancelRoot, - allowSubscribe: true, - serverSubs: make(map[ID]*Subscription), - log: log.Root(), + reg: reg, + idgen: idgen, + conn: conn, + respWait: make(map[string]*requestOp), + clientSubs: make(map[string]*ClientSubscription), + rootCtx: rootCtx, + cancelRoot: cancelRoot, + allowSubscribe: true, + serverSubs: make(map[ID]*Subscription), + log: log.Root(), + batchRequestLimit: batchRequestLimit, + batchResponseMaxSize: batchResponseMaxSize, } if conn.remoteAddr() != "" { h.log = h.log.New("conn", conn.remoteAddr()) @@ -92,19 +95,14 @@ func newHandler(connCtx context.Context, conn jsonWriter, idgen func() ID, reg * return h } -var MaxBatchResponseSize int = 10_000_000 // 10MB - // batchCallBuffer manages in progress call messages and their responses during a batch // call. Calls need to be synchronized between the processing and timeout-triggering // goroutines. type batchCallBuffer struct { mutex sync.Mutex calls []*jsonrpcMessage - resp []json.RawMessage + resp []*jsonrpcMessage wrote bool - - // Arbitrum: response size limit - totalSize int } // nextCall returns the next unprocessed message. @@ -122,23 +120,14 @@ func (b *batchCallBuffer) nextCall() *jsonrpcMessage { } // pushResponse adds the response to last call returned by nextCall. -func (b *batchCallBuffer) pushResponse(answer *jsonrpcMessage) error { +func (b *batchCallBuffer) pushResponse(answer *jsonrpcMessage) { b.mutex.Lock() defer b.mutex.Unlock() if answer != nil { - serialized, err := json.Marshal(answer) - if err != nil { - return &parseError{"error serializing response: " + err.Error()} - } - b.totalSize += len(serialized) - if MaxBatchResponseSize > 0 && b.totalSize > MaxBatchResponseSize { - return &invalidRequestError{fmt.Sprintf("batch response exceeded limit of %v bytes", MaxBatchResponseSize)} - } - b.resp = append(b.resp, serialized) + b.resp = append(b.resp, answer) } b.calls = b.calls[1:] - return nil } // write sends the responses. @@ -149,23 +138,15 @@ func (b *batchCallBuffer) write(ctx context.Context, conn jsonWriter) { b.doWrite(ctx, conn, false) } -// timeout sends the responses added so far. For the remaining unanswered call -// messages, it sends a timeout error response. -func (b *batchCallBuffer) timeout(ctx context.Context, conn jsonWriter) { +// respondWithError sends the responses added so far. For the remaining unanswered call +// messages, it responds with the given error. +func (b *batchCallBuffer) respondWithError(ctx context.Context, conn jsonWriter, err error) { b.mutex.Lock() defer b.mutex.Unlock() for _, msg := range b.calls { if !msg.isNotification() { - resp := msg.errorResponse(&internalServerError{errcodeTimeout, errMsgTimeout}) - serialized, err := json.Marshal(resp) - if err != nil { - conn.writeJSON(ctx, errorMessage(&parseError{"error serializing timeout error: " + err.Error()}), true) - b.wrote = true - return - } - - b.resp = append(b.resp, serialized) + b.resp = append(b.resp, msg.errorResponse(err)) } } b.doWrite(ctx, conn, true) @@ -193,23 +174,30 @@ func (h *handler) handleBatch(msgs []*jsonrpcMessage) { }) return } + // Apply limit on total number of requests. + if h.batchRequestLimit != 0 && len(msgs) > h.batchRequestLimit { + h.startCallProc(func(cp *callProc) { + h.respondWithBatchTooLarge(cp, msgs) + }) + return + } - // Handle non-call messages first: + // Handle non-call messages first. + // Here we need to find the requestOp that sent the request batch. calls := make([]*jsonrpcMessage, 0, len(msgs)) - for _, msg := range msgs { - if handled := h.handleImmediate(msg); !handled { - calls = append(calls, msg) - } - } + h.handleResponses(msgs, func(msg *jsonrpcMessage) { + calls = append(calls, msg) + }) if len(calls) == 0 { return } + // Process calls on a goroutine because they may block indefinitely: h.startCallProc(func(cp *callProc) { var ( timer *time.Timer cancel context.CancelFunc - callBuffer = &batchCallBuffer{calls: calls, resp: make([]json.RawMessage, 0, len(calls))} + callBuffer = &batchCallBuffer{calls: calls, resp: make([]*jsonrpcMessage, 0, len(calls))} ) cp.ctx, cancel = context.WithCancel(cp.ctx) @@ -221,10 +209,12 @@ func (h *handler) handleBatch(msgs []*jsonrpcMessage) { if timeout, ok := ContextRequestTimeout(cp.ctx); ok { timer = time.AfterFunc(timeout, func() { cancel() - callBuffer.timeout(cp.ctx, h.conn) + err := &internalServerError{errcodeTimeout, errMsgTimeout} + callBuffer.respondWithError(cp.ctx, h.conn, err) }) } + responseBytes := 0 for { // No need to handle rest of calls if timed out. if cp.ctx.Err() != nil { @@ -235,64 +225,87 @@ func (h *handler) handleBatch(msgs []*jsonrpcMessage) { break } resp := h.handleCallMsg(cp, msg) - err := callBuffer.pushResponse(resp) - if err != nil { - h.conn.writeJSON(cp.ctx, errorMessage(err), true) - return + callBuffer.pushResponse(resp) + if resp != nil && h.batchResponseMaxSize != 0 { + responseBytes += len(resp.Result) + if responseBytes > h.batchResponseMaxSize { + err := &internalServerError{errcodeResponseTooLarge, errMsgResponseTooLarge} + callBuffer.respondWithError(cp.ctx, h.conn, err) + break + } } } if timer != nil { timer.Stop() } - callBuffer.write(cp.ctx, h.conn) + h.addSubscriptions(cp.notifiers) + callBuffer.write(cp.ctx, h.conn) for _, n := range cp.notifiers { n.activate() } }) } -// handleMsg handles a single message. -func (h *handler) handleMsg(msg *jsonrpcMessage) { - if ok := h.handleImmediate(msg); ok { - return +func (h *handler) respondWithBatchTooLarge(cp *callProc, batch []*jsonrpcMessage) { + resp := errorMessage(&invalidRequestError{errMsgBatchTooLarge}) + // Find the first call and add its "id" field to the error. + // This is the best we can do, given that the protocol doesn't have a way + // of reporting an error for the entire batch. + for _, msg := range batch { + if msg.isCall() { + resp.ID = msg.ID + break + } } - h.startCallProc(func(cp *callProc) { - var ( - responded sync.Once - timer *time.Timer - cancel context.CancelFunc - ) - cp.ctx, cancel = context.WithCancel(cp.ctx) - defer cancel() + h.conn.writeJSON(cp.ctx, []*jsonrpcMessage{resp}, true) +} - // Cancel the request context after timeout and send an error response. Since the - // running method might not return immediately on timeout, we must wait for the - // timeout concurrently with processing the request. - if timeout, ok := ContextRequestTimeout(cp.ctx); ok { - timer = time.AfterFunc(timeout, func() { - cancel() - responded.Do(func() { - resp := msg.errorResponse(&internalServerError{errcodeTimeout, errMsgTimeout}) - h.conn.writeJSON(cp.ctx, resp, true) - }) - }) - } +// handleMsg handles a single non-batch message. +func (h *handler) handleMsg(msg *jsonrpcMessage) { + msgs := []*jsonrpcMessage{msg} + h.handleResponses(msgs, func(msg *jsonrpcMessage) { + h.startCallProc(func(cp *callProc) { + h.handleNonBatchCall(cp, msg) + }) + }) +} - answer := h.handleCallMsg(cp, msg) - if timer != nil { - timer.Stop() - } - h.addSubscriptions(cp.notifiers) - if answer != nil { +func (h *handler) handleNonBatchCall(cp *callProc, msg *jsonrpcMessage) { + var ( + responded sync.Once + timer *time.Timer + cancel context.CancelFunc + ) + cp.ctx, cancel = context.WithCancel(cp.ctx) + defer cancel() + + // Cancel the request context after timeout and send an error response. Since the + // running method might not return immediately on timeout, we must wait for the + // timeout concurrently with processing the request. + if timeout, ok := ContextRequestTimeout(cp.ctx); ok { + timer = time.AfterFunc(timeout, func() { + cancel() responded.Do(func() { - h.conn.writeJSON(cp.ctx, answer, false) + resp := msg.errorResponse(&internalServerError{errcodeTimeout, errMsgTimeout}) + h.conn.writeJSON(cp.ctx, resp, true) }) - } - for _, n := range cp.notifiers { - n.activate() - } - }) + }) + } + + answer := h.handleCallMsg(cp, msg) + if timer != nil { + timer.Stop() + } + h.addSubscriptions(cp.notifiers) + if answer != nil { + responded.Do(func() { + h.conn.writeJSON(cp.ctx, answer, false) + }) + } + for _, n := range cp.notifiers { + n.activate() + } } // close cancels all requests except for inflightReq and waits for @@ -375,23 +388,60 @@ func (h *handler) startCallProc(fn func(*callProc)) { }() } -// handleImmediate executes non-call messages. It returns false if the message is a -// call or requires a reply. -func (h *handler) handleImmediate(msg *jsonrpcMessage) bool { - start := time.Now() - switch { - case msg.isNotification(): - if strings.HasSuffix(msg.Method, notificationMethodSuffix) { - h.handleSubscriptionResult(msg) - return true +// handleResponse processes method call responses. +func (h *handler) handleResponses(batch []*jsonrpcMessage, handleCall func(*jsonrpcMessage)) { + var resolvedops []*requestOp + handleResp := func(msg *jsonrpcMessage) { + op := h.respWait[string(msg.ID)] + if op == nil { + h.log.Debug("Unsolicited RPC response", "reqid", idForLog{msg.ID}) + return + } + resolvedops = append(resolvedops, op) + delete(h.respWait, string(msg.ID)) + + // For subscription responses, start the subscription if the server + // indicates success. EthSubscribe gets unblocked in either case through + // the op.resp channel. + if op.sub != nil { + if msg.Error != nil { + op.err = msg.Error + } else { + op.err = json.Unmarshal(msg.Result, &op.sub.subid) + if op.err == nil { + go op.sub.run() + h.clientSubs[op.sub.subid] = op.sub + } + } + } + + if !op.hadResponse { + op.hadResponse = true + op.resp <- batch + } + } + + for _, msg := range batch { + start := time.Now() + switch { + case msg.isResponse(): + handleResp(msg) + h.log.Trace("Handled RPC response", "reqid", idForLog{msg.ID}, "duration", time.Since(start)) + + case msg.isNotification(): + if strings.HasSuffix(msg.Method, notificationMethodSuffix) { + h.handleSubscriptionResult(msg) + continue + } + handleCall(msg) + + default: + handleCall(msg) } - return false - case msg.isResponse(): - h.handleResponse(msg) - h.log.Trace("Handled RPC response", "reqid", idForLog{msg.ID}, "duration", time.Since(start)) - return true - default: - return false + } + + for _, op := range resolvedops { + h.removeRequestOp(op) } } @@ -407,33 +457,6 @@ func (h *handler) handleSubscriptionResult(msg *jsonrpcMessage) { } } -// handleResponse processes method call responses. -func (h *handler) handleResponse(msg *jsonrpcMessage) { - op := h.respWait[string(msg.ID)] - if op == nil { - h.log.Debug("Unsolicited RPC response", "reqid", idForLog{msg.ID}) - return - } - delete(h.respWait, string(msg.ID)) - // For normal responses, just forward the reply to Call/BatchCall. - if op.sub == nil { - op.resp <- msg - return - } - // For subscription responses, start the subscription if the server - // indicates success. EthSubscribe gets unblocked in either case through - // the op.resp channel. - defer close(op.resp) - if msg.Error != nil { - op.err = msg.Error - return - } - if op.err = json.Unmarshal(msg.Result, &op.sub.subid); op.err == nil { - go op.sub.run() - h.clientSubs[op.sub.subid] = op.sub - } -} - // handleCallMsg executes a call message and returns the answer. func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMessage { start := time.Now() @@ -442,6 +465,7 @@ func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMess h.handleCall(ctx, msg) h.log.Debug("Served "+msg.Method, "duration", time.Since(start)) return nil + case msg.isCall(): resp := h.handleCall(ctx, msg) var ctx []interface{} @@ -456,8 +480,10 @@ func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMess h.log.Debug("Served "+msg.Method, ctx...) } return resp + case msg.hasValidID(): return msg.errorResponse(&invalidRequestError{"invalid request"}) + default: return errorMessage(&invalidRequestError{"invalid request"}) } @@ -477,12 +503,14 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage if callb == nil { return msg.errorResponse(&methodNotFoundError{method: msg.Method}) } + args, err := parsePositionalArguments(msg.Params, callb.argTypes) if err != nil { return msg.errorResponse(&invalidParamsError{err.Error()}) } start := time.Now() answer := h.runMethod(cp.ctx, msg, callb, args) + // Collect the statistics for RPC calls if metrics is enabled. // We only care about pure rpc call. Filter out subscription. if callb != h.unsubscribeCb { @@ -495,16 +523,14 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage rpcServingTimer.UpdateSince(start) updateServeTimeHistogram(msg.Method, answer.Error == nil, time.Since(start)) } + return answer } // handleSubscribe processes *_subscribe method calls. func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage { if !h.allowSubscribe { - return msg.errorResponse(&internalServerError{ - code: errcodeNotificationsUnsupported, - message: ErrNotificationsUnsupported.Error(), - }) + return msg.errorResponse(ErrNotificationsUnsupported) } // Subscription method name is first argument. diff --git a/rpc/http.go b/rpc/http.go index 8712f99610b5..741fa1c0eb4f 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -139,7 +139,7 @@ func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) { var cfg clientConfig cfg.httpClient = client fn := newClientTransportHTTP(endpoint, &cfg) - return newClient(context.Background(), fn) + return newClient(context.Background(), &cfg, fn) } func newClientTransportHTTP(endpoint string, cfg *clientConfig) reconnectFunc { @@ -176,11 +176,12 @@ func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) e } defer respBody.Close() - var respmsg jsonrpcMessage - if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil { + var resp jsonrpcMessage + batch := [1]*jsonrpcMessage{&resp} + if err := json.NewDecoder(respBody).Decode(&resp); err != nil { return err } - op.resp <- &respmsg + op.resp <- batch[:] return nil } @@ -191,16 +192,12 @@ func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonr return err } defer respBody.Close() - var respmsgs []jsonrpcMessage + + var respmsgs []*jsonrpcMessage if err := json.NewDecoder(respBody).Decode(&respmsgs); err != nil { return err } - if len(respmsgs) != len(msgs) { - return fmt.Errorf("batch has %d requests but response has %d: %w", len(msgs), len(respmsgs), ErrBadResult) - } - for i := 0; i < len(respmsgs); i++ { - op.resp <- &respmsgs[i] - } + op.resp <- respmsgs return nil } diff --git a/rpc/inproc.go b/rpc/inproc.go index fbe9a40ceca9..306974e04b81 100644 --- a/rpc/inproc.go +++ b/rpc/inproc.go @@ -24,7 +24,8 @@ import ( // DialInProc attaches an in-process connection to the given RPC server. func DialInProc(handler *Server) *Client { initctx := context.Background() - c, _ := newClient(initctx, func(context.Context) (ServerCodec, error) { + cfg := new(clientConfig) + c, _ := newClient(initctx, cfg, func(context.Context) (ServerCodec, error) { p1, p2 := net.Pipe() go handler.ServeCodec(NewCodec(p1), 0) return NewCodec(p2), nil diff --git a/rpc/ipc.go b/rpc/ipc.go index d9e0de62e877..a08245b27089 100644 --- a/rpc/ipc.go +++ b/rpc/ipc.go @@ -46,7 +46,8 @@ func (s *Server) ServeListener(l net.Listener) error { // The context is used for the initial connection establishment. It does not // affect subsequent interactions with the client. func DialIPC(ctx context.Context, endpoint string) (*Client, error) { - return newClient(ctx, newClientTransportIPC(endpoint)) + cfg := new(clientConfig) + return newClient(ctx, cfg, newClientTransportIPC(endpoint)) } func newClientTransportIPC(endpoint string) reconnectFunc { diff --git a/rpc/ipc_unix.go b/rpc/ipc_unix.go index 9876347708a6..33c1cad5494a 100644 --- a/rpc/ipc_unix.go +++ b/rpc/ipc_unix.go @@ -29,11 +29,17 @@ import ( "github.com/ethereum/go-ethereum/log" ) +const ( + // On Linux, sun_path is 108 bytes in size + // see http://man7.org/linux/man-pages/man7/unix.7.html + maxPathSize = int(108) +) + // ipcListen will create a Unix socket on the given endpoint. func ipcListen(endpoint string) (net.Listener, error) { // account for null-terminator too - if len(endpoint)+1 > int(max_path_size) { - log.Warn(fmt.Sprintf("The ipc endpoint is longer than %d characters. ", max_path_size-1), + if len(endpoint)+1 > maxPathSize { + log.Warn(fmt.Sprintf("The ipc endpoint is longer than %d characters. ", maxPathSize-1), "endpoint", endpoint) } diff --git a/rpc/ipc_windows.go b/rpc/ipc_windows.go index adb1826f0c80..efec38cf3729 100644 --- a/rpc/ipc_windows.go +++ b/rpc/ipc_windows.go @@ -24,7 +24,7 @@ import ( "net" "time" - "gopkg.in/natefinch/npipe.v2" + "github.com/Microsoft/go-winio" ) // This is used if the dialing context has no deadline. It is much smaller than the @@ -33,17 +33,12 @@ const defaultPipeDialTimeout = 2 * time.Second // ipcListen will create a named pipe on the given endpoint. func ipcListen(endpoint string) (net.Listener, error) { - return npipe.Listen(endpoint) + return winio.ListenPipe(endpoint, nil) } // newIPCConnection will connect to a named pipe with the given endpoint as name. func newIPCConnection(ctx context.Context, endpoint string) (net.Conn, error) { - timeout := defaultPipeDialTimeout - if deadline, ok := ctx.Deadline(); ok { - timeout = deadline.Sub(time.Now()) - if timeout < 0 { - timeout = 0 - } - } - return npipe.DialTimeout(endpoint, timeout) + ctx, cancel := context.WithTimeout(ctx, defaultPipeDialTimeout) + defer cancel() + return winio.DialPipeContext(ctx, endpoint) } diff --git a/rpc/server.go b/rpc/server.go index 089bbb1fd5d2..27820c408fbd 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -46,9 +46,11 @@ type Server struct { services serviceRegistry idgen func() ID - mutex sync.Mutex - codecs map[ServerCodec]struct{} - run atomic.Bool + mutex sync.Mutex + codecs map[ServerCodec]struct{} + run atomic.Bool + batchItemLimit int + batchResponseLimit int } // NewServer creates a new server instance with no registered handlers. @@ -65,6 +67,21 @@ func NewServer() *Server { return server } +// SetBatchLimits sets limits applied to batch requests. There are two limits: 'itemLimit' +// is the maximum number of items in a batch. 'maxResponseSize' is the maximum number of +// response bytes across all requests in a batch. +// +// This method should be called before processing any requests via ServeCodec, ServeHTTP, +// ServeListener etc. +func (s *Server) SetBatchLimits(itemLimit, maxResponseSize int) { + s.batchItemLimit = itemLimit + s.batchResponseLimit = maxResponseSize +} + +func (s *Server) ApplyAPIFilter(apiFilter map[string]bool) { + s.services.apiFilter = apiFilter +} + // RegisterName creates a service for the given receiver type under the given name. When no // methods on the given receiver match the criteria to be either a RPC method or a // subscription an error is returned. Otherwise a new service is created and added to the @@ -86,7 +103,12 @@ func (s *Server) ServeCodec(codec ServerCodec, options CodecOption) { } defer s.untrackCodec(codec) - c := initClient(codec, s.idgen, &s.services) + cfg := &clientConfig{ + idgen: s.idgen, + batchItemLimit: s.batchItemLimit, + batchResponseLimit: s.batchResponseLimit, + } + c := initClient(codec, &s.services, cfg) <-codec.closed() c.Close() } @@ -118,7 +140,7 @@ func (s *Server) serveSingleRequest(ctx context.Context, codec ServerCodec) { return } - h := newHandler(ctx, codec, s.idgen, &s.services) + h := newHandler(ctx, codec, s.idgen, &s.services, s.batchItemLimit, s.batchResponseLimit) h.allowSubscribe = false defer h.close(io.EOF, nil) diff --git a/rpc/server_test.go b/rpc/server_test.go index f1a9b3d5cd4a..9d1c7fb5f0fe 100644 --- a/rpc/server_test.go +++ b/rpc/server_test.go @@ -32,7 +32,8 @@ func TestServerRegisterName(t *testing.T) { server := NewServer() service := new(testService) - if err := server.RegisterName("test", service); err != nil { + svcName := "test" + if err := server.RegisterName(svcName, service); err != nil { t.Fatalf("%v", err) } @@ -40,12 +41,12 @@ func TestServerRegisterName(t *testing.T) { t.Fatalf("Expected 2 service entries, got %d", len(server.services.services)) } - svc, ok := server.services.services["test"] + svc, ok := server.services.services[svcName] if !ok { - t.Fatalf("Expected service calc to be registered") + t.Fatalf("Expected service %s to be registered", svcName) } - wantCallbacks := 13 + wantCallbacks := 14 if len(svc.callbacks) != wantCallbacks { t.Errorf("Expected %d callbacks for service 'service', got %d", wantCallbacks, len(svc.callbacks)) } @@ -70,6 +71,7 @@ func TestServer(t *testing.T) { func runTestScript(t *testing.T, file string) { server := newTestServer() + server.SetBatchLimits(4, 100000) content, err := os.ReadFile(file) if err != nil { t.Fatal(err) @@ -152,3 +154,41 @@ func TestServerShortLivedConn(t *testing.T) { } } } + +func TestServerBatchResponseSizeLimit(t *testing.T) { + server := newTestServer() + defer server.Stop() + server.SetBatchLimits(100, 60) + var ( + batch []BatchElem + client = DialInProc(server) + ) + for i := 0; i < 5; i++ { + batch = append(batch, BatchElem{ + Method: "test_echo", + Args: []any{"x", 1}, + Result: new(echoResult), + }) + } + if err := client.BatchCall(batch); err != nil { + t.Fatal("error sending batch:", err) + } + for i := range batch { + // We expect the first two queries to be ok, but after that the size limit takes effect. + if i < 2 { + if batch[i].Error != nil { + t.Fatalf("batch elem %d has unexpected error: %v", i, batch[i].Error) + } + continue + } + // After two, we expect an error. + re, ok := batch[i].Error.(Error) + if !ok { + t.Fatalf("batch elem %d has wrong error: %v", i, batch[i].Error) + } + wantedCode := errcodeResponseTooLarge + if re.ErrorCode() != wantedCode { + t.Errorf("batch elem %d wrong error code, have %d want %d", i, re.ErrorCode(), wantedCode) + } + } +} diff --git a/rpc/service.go b/rpc/service.go index 8485cab3aa42..7821d5fc2ab9 100644 --- a/rpc/service.go +++ b/rpc/service.go @@ -38,6 +38,8 @@ var ( type serviceRegistry struct { mu sync.Mutex services map[string]service + + apiFilter map[string]bool } // service represents a registered object. @@ -81,11 +83,17 @@ func (r *serviceRegistry) registerName(name string, rcvr interface{}) error { } r.services[name] = svc } - for name, cb := range callbacks { + for methodName, cb := range callbacks { + if r.apiFilter != nil { + key := name + "_" + methodName + if _, ok := r.apiFilter[key]; !ok { + continue + } + } if cb.isSubscribe { - svc.subscriptions[name] = cb + svc.subscriptions[methodName] = cb } else { - svc.callbacks[name] = cb + svc.callbacks[methodName] = cb } } return nil diff --git a/rpc/stdio.go b/rpc/stdio.go index ae32db26ef1c..084e5f0700ce 100644 --- a/rpc/stdio.go +++ b/rpc/stdio.go @@ -32,7 +32,8 @@ func DialStdIO(ctx context.Context) (*Client, error) { // DialIO creates a client which uses the given IO channels func DialIO(ctx context.Context, in io.Reader, out io.Writer) (*Client, error) { - return newClient(ctx, newClientTransportIO(in, out)) + cfg := new(clientConfig) + return newClient(ctx, cfg, newClientTransportIO(in, out)) } func newClientTransportIO(in io.Reader, out io.Writer) reconnectFunc { diff --git a/rpc/subscription.go b/rpc/subscription.go index 334ead3ace4d..3231c2ceec9b 100644 --- a/rpc/subscription.go +++ b/rpc/subscription.go @@ -32,8 +32,17 @@ import ( ) var ( - // ErrNotificationsUnsupported is returned when the connection doesn't support notifications - ErrNotificationsUnsupported = errors.New("notifications not supported") + // ErrNotificationsUnsupported is returned by the client when the connection doesn't + // support notifications. You can use this error value to check for subscription + // support like this: + // + // sub, err := client.EthSubscribe(ctx, channel, "newHeads", true) + // if errors.Is(err, rpc.ErrNotificationsUnsupported) { + // // Server does not support subscriptions, fall back to polling. + // } + // + ErrNotificationsUnsupported = notificationsUnsupportedError{} + // ErrSubscriptionNotFound is returned when the notification for the given id is not found ErrSubscriptionNotFound = errors.New("subscription not found") ) diff --git a/rpc/testdata/invalid-batch-toolarge.js b/rpc/testdata/invalid-batch-toolarge.js new file mode 100644 index 000000000000..218fea58aaac --- /dev/null +++ b/rpc/testdata/invalid-batch-toolarge.js @@ -0,0 +1,13 @@ +// This file checks the behavior of the batch item limit code. +// In tests, the batch item limit is set to 4. So to trigger the error, +// all batches in this file have 5 elements. + +// For batches that do not contain any calls, a response message with "id" == null +// is returned. + +--> [{"jsonrpc":"2.0","method":"test_echo","params":["x",99]},{"jsonrpc":"2.0","method":"test_echo","params":["x",99]},{"jsonrpc":"2.0","method":"test_echo","params":["x",99]},{"jsonrpc":"2.0","method":"test_echo","params":["x",99]},{"jsonrpc":"2.0","method":"test_echo","params":["x",99]}] +<-- [{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"batch too large"}}] + +// For batches with at least one call, the call's "id" is used. +--> [{"jsonrpc":"2.0","method":"test_echo","params":["x",99]},{"jsonrpc":"2.0","id":3,"method":"test_echo","params":["x",99]},{"jsonrpc":"2.0","method":"test_echo","params":["x",99]},{"jsonrpc":"2.0","method":"test_echo","params":["x",99]},{"jsonrpc":"2.0","method":"test_echo","params":["x",99]}] +<-- [{"jsonrpc":"2.0","id":3,"error":{"code":-32600,"message":"batch too large"}}] diff --git a/rpc/testservice_test.go b/rpc/testservice_test.go index eab67f1dd5d8..7d873af6670e 100644 --- a/rpc/testservice_test.go +++ b/rpc/testservice_test.go @@ -90,6 +90,10 @@ func (s *testService) EchoWithCtx(ctx context.Context, str string, i int, args * return echoResult{str, i, args} } +func (s *testService) Repeat(msg string, i int) string { + return strings.Repeat(msg, i) +} + func (s *testService) PeerInfo(ctx context.Context) PeerInfo { return PeerInfoFromContext(ctx) } diff --git a/rpc/websocket.go b/rpc/websocket.go index 889562d1ab55..538e53a31b7c 100644 --- a/rpc/websocket.go +++ b/rpc/websocket.go @@ -38,7 +38,7 @@ const ( wsPingInterval = 30 * time.Second wsPingWriteTimeout = 5 * time.Second wsPongTimeout = 30 * time.Second - wsMessageSizeLimit = 32 * 1024 * 1024 + wsDefaultReadLimit = 32 * 1024 * 1024 ) var wsBufferPool = new(sync.Pool) @@ -60,7 +60,7 @@ func (s *Server) WebsocketHandler(allowedOrigins []string) http.Handler { log.Debug("WebSocket upgrade failed", "err", err) return } - codec := newWebsocketCodec(conn, r.Host, r.Header) + codec := newWebsocketCodec(conn, r.Host, r.Header, wsDefaultReadLimit) s.ServeCodec(codec, 0) }) } @@ -197,7 +197,7 @@ func DialWebsocketWithDialer(ctx context.Context, endpoint, origin string, diale if err != nil { return nil, err } - return newClient(ctx, connect) + return newClient(ctx, cfg, connect) } // DialWebsocket creates a new RPC client that communicates with a JSON-RPC server @@ -214,7 +214,7 @@ func DialWebsocket(ctx context.Context, endpoint, origin string) (*Client, error if err != nil { return nil, err } - return newClient(ctx, connect) + return newClient(ctx, cfg, connect) } func newClientTransportWS(endpoint string, cfg *clientConfig) (reconnectFunc, error) { @@ -251,7 +251,11 @@ func newClientTransportWS(endpoint string, cfg *clientConfig) (reconnectFunc, er } return nil, hErr } - return newWebsocketCodec(conn, dialURL, header), nil + messageSizeLimit := int64(wsDefaultReadLimit) + if cfg.wsMessageSizeLimit != nil && *cfg.wsMessageSizeLimit >= 0 { + messageSizeLimit = *cfg.wsMessageSizeLimit + } + return newWebsocketCodec(conn, dialURL, header, messageSizeLimit), nil } return connect, nil } @@ -278,24 +282,21 @@ type websocketCodec struct { conn *websocket.Conn info PeerInfo - wg sync.WaitGroup - pingReset chan struct{} + wg sync.WaitGroup + pingReset chan struct{} + pongReceived chan struct{} } -func newWebsocketCodec(conn *websocket.Conn, host string, req http.Header) ServerCodec { - conn.SetReadLimit(wsMessageSizeLimit) - conn.SetPongHandler(func(appData string) error { - conn.SetReadDeadline(time.Time{}) - return nil - }) - +func newWebsocketCodec(conn *websocket.Conn, host string, req http.Header, readLimit int64) ServerCodec { + conn.SetReadLimit(readLimit) encode := func(v interface{}, isErrorResponse bool) error { return conn.WriteJSON(v) } wc := &websocketCodec{ - jsonCodec: NewFuncCodec(conn, encode, conn.ReadJSON).(*jsonCodec), - conn: conn, - pingReset: make(chan struct{}, 1), + jsonCodec: NewFuncCodec(conn, encode, conn.ReadJSON).(*jsonCodec), + conn: conn, + pingReset: make(chan struct{}, 1), + pongReceived: make(chan struct{}), info: PeerInfo{ Transport: "ws", RemoteAddr: conn.RemoteAddr().String(), @@ -306,6 +307,13 @@ func newWebsocketCodec(conn *websocket.Conn, host string, req http.Header) Serve wc.info.HTTP.Origin = req.Get("Origin") wc.info.HTTP.UserAgent = req.Get("User-Agent") // Start pinger. + conn.SetPongHandler(func(appData string) error { + select { + case wc.pongReceived <- struct{}{}: + case <-wc.closed(): + } + return nil + }) wc.wg.Add(1) go wc.pingLoop() return wc @@ -334,26 +342,31 @@ func (wc *websocketCodec) writeJSON(ctx context.Context, v interface{}, isError // pingLoop sends periodic ping frames when the connection is idle. func (wc *websocketCodec) pingLoop() { - var timer = time.NewTimer(wsPingInterval) + var pingTimer = time.NewTimer(wsPingInterval) defer wc.wg.Done() - defer timer.Stop() + defer pingTimer.Stop() for { select { case <-wc.closed(): return + case <-wc.pingReset: - if !timer.Stop() { - <-timer.C + if !pingTimer.Stop() { + <-pingTimer.C } - timer.Reset(wsPingInterval) - case <-timer.C: + pingTimer.Reset(wsPingInterval) + + case <-pingTimer.C: wc.jsonCodec.encMu.Lock() wc.conn.SetWriteDeadline(time.Now().Add(wsPingWriteTimeout)) wc.conn.WriteMessage(websocket.PingMessage, nil) wc.conn.SetReadDeadline(time.Now().Add(wsPongTimeout)) wc.jsonCodec.encMu.Unlock() - timer.Reset(wsPingInterval) + pingTimer.Reset(wsPingInterval) + + case <-wc.pongReceived: + wc.conn.SetReadDeadline(time.Time{}) } } } diff --git a/rpc/websocket_test.go b/rpc/websocket_test.go index fb9357605b8b..e4ac5c3fad3f 100644 --- a/rpc/websocket_test.go +++ b/rpc/websocket_test.go @@ -113,6 +113,66 @@ func TestWebsocketLargeCall(t *testing.T) { } } +// This test checks whether the wsMessageSizeLimit option is obeyed. +func TestWebsocketLargeRead(t *testing.T) { + t.Parallel() + + var ( + srv = newTestServer() + httpsrv = httptest.NewServer(srv.WebsocketHandler([]string{"*"})) + wsURL = "ws:" + strings.TrimPrefix(httpsrv.URL, "http:") + ) + defer srv.Stop() + defer httpsrv.Close() + + testLimit := func(limit *int64) { + opts := []ClientOption{} + expLimit := int64(wsDefaultReadLimit) + if limit != nil && *limit >= 0 { + opts = append(opts, WithWebsocketMessageSizeLimit(*limit)) + if *limit > 0 { + expLimit = *limit // 0 means infinite + } + } + client, err := DialOptions(context.Background(), wsURL, opts...) + if err != nil { + t.Fatalf("can't dial: %v", err) + } + defer client.Close() + // Remove some bytes for json encoding overhead. + underLimit := int(expLimit - 128) + overLimit := expLimit + 1 + if expLimit == wsDefaultReadLimit { + // No point trying the full 32MB in tests. Just sanity-check that + // it's not obviously limited. + underLimit = 1024 + overLimit = -1 + } + var res string + // Check under limit + if err = client.Call(&res, "test_repeat", "A", underLimit); err != nil { + t.Fatalf("unexpected error with limit %d: %v", expLimit, err) + } + if len(res) != underLimit || strings.Count(res, "A") != underLimit { + t.Fatal("incorrect data") + } + // Check over limit + if overLimit > 0 { + err = client.Call(&res, "test_repeat", "A", expLimit+1) + if err == nil || err != websocket.ErrReadLimit { + t.Fatalf("wrong error with limit %d: %v expecting %v", expLimit, err, websocket.ErrReadLimit) + } + } + } + ptr := func(v int64) *int64 { return &v } + + testLimit(ptr(-1)) // Should be ignored (use default) + testLimit(ptr(0)) // Should be ignored (use default) + testLimit(nil) // Should be ignored (use default) + testLimit(ptr(200)) + testLimit(ptr(wsDefaultReadLimit * 2)) +} + func TestWebsocketPeerInfo(t *testing.T) { var ( s = newTestServer() @@ -206,7 +266,7 @@ func TestClientWebsocketLargeMessage(t *testing.T) { defer srv.Stop() defer httpsrv.Close() - respLength := wsMessageSizeLimit - 50 + respLength := wsDefaultReadLimit - 50 srv.RegisterName("test", largeRespService{respLength}) c, err := DialWebsocket(context.Background(), wsURL, "") diff --git a/signer/core/api.go b/signer/core/api.go index 3c1c94801b11..43eb89ee0077 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -553,6 +553,7 @@ func (api *SignerAPI) SignTransaction(ctx context.Context, args apitypes.SendTxA // If we are in 'rejectMode', then reject rather than show the user warnings if api.rejectMode { if err := msgs.GetWarnings(); err != nil { + log.Info("Signing aborted due to warnings. In order to continue despite warnings, please use the flag '--advanced'.") return nil, err } } @@ -625,6 +626,7 @@ func (api *SignerAPI) SignGnosisSafeTx(ctx context.Context, signerAddress common // If we are in 'rejectMode', then reject rather than show the user warnings if api.rejectMode { if err := msgs.GetWarnings(); err != nil { + log.Info("Signing aborted due to warnings. In order to continue despite warnings, please use the flag '--advanced'.") return nil, err } } diff --git a/signer/core/api_test.go b/signer/core/api_test.go index 9bb55bddca31..5a9de161b3ac 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -282,7 +282,7 @@ func TestSignTx(t *testing.T) { t.Fatal(err) } parsedTx := &types.Transaction{} - rlp.Decode(bytes.NewReader(res.Raw), parsedTx) + rlp.DecodeBytes(res.Raw, parsedTx) //The tx should NOT be modified by the UI if parsedTx.Value().Cmp(tx.Value.ToInt()) != 0 { @@ -308,7 +308,7 @@ func TestSignTx(t *testing.T) { t.Fatal(err) } parsedTx2 := &types.Transaction{} - rlp.Decode(bytes.NewReader(res.Raw), parsedTx2) + rlp.DecodeBytes(res.Raw, parsedTx2) //The tx should be modified by the UI if parsedTx2.Value().Cmp(tx.Value.ToInt()) != 0 { diff --git a/tests/block_test.go b/tests/block_test.go index 9e72f7f9710a..645b4b702df2 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -18,11 +18,12 @@ package tests import ( "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" ) func TestBlockchain(t *testing.T) { - t.Parallel() - bt := new(testMatcher) // General state tests are 'exported' as blockchain tests, but we can run them natively. // For speedier CI-runs, the line below can be uncommented, so those are skipped. @@ -48,14 +49,40 @@ func TestBlockchain(t *testing.T) { bt.skipLoad(`.*randomStatetest94.json.*`) bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { - if err := bt.checkFailure(t, test.Run(false)); err != nil { - t.Errorf("test without snapshotter failed: %v", err) - } - if err := bt.checkFailure(t, test.Run(true)); err != nil { - t.Errorf("test with snapshotter failed: %v", err) - } + execBlockTest(t, bt, test) }) // 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. } + +// TestExecutionSpec runs the test fixtures from execution-spec-tests. +func TestExecutionSpec(t *testing.T) { + if !common.FileExist(executionSpecDir) { + t.Skipf("directory %s does not exist", executionSpecDir) + } + bt := new(testMatcher) + + // cancun tests are not complete yet + bt.skipLoad(`^cancun/`) + bt.skipLoad(`-fork=Cancun`) + + bt.walk(t, executionSpecDir, func(t *testing.T, name string, test *BlockTest) { + execBlockTest(t, bt, test) + }) +} + +func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) { + if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil)); err != nil { + t.Errorf("test in hash mode without snapshotter failed: %v", err) + } + if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil)); err != nil { + t.Errorf("test in hash mode with snapshotter failed: %v", err) + } + if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil)); err != nil { + t.Errorf("test in path mode without snapshotter failed: %v", err) + } + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil)); err != nil { + t.Errorf("test in path mode with snapshotter failed: %v", err) + } +} diff --git a/tests/block_test_util.go b/tests/block_test_util.go index e3afa99a86e1..fe5c43202f0c 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -38,6 +38,9 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/hashdb" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" ) // A BlockTest checks handling of entire blocks. @@ -70,24 +73,27 @@ type btBlock struct { //go:generate go run github.com/fjl/gencodec -type btHeader -field-override btHeaderMarshaling -out gen_btheader.go type btHeader struct { - Bloom types.Bloom - Coinbase common.Address - MixHash common.Hash - Nonce types.BlockNonce - Number *big.Int - Hash common.Hash - ParentHash common.Hash - ReceiptTrie common.Hash - StateRoot common.Hash - TransactionsTrie common.Hash - UncleHash common.Hash - ExtraData []byte - Difficulty *big.Int - GasLimit uint64 - GasUsed uint64 - Timestamp uint64 - BaseFeePerGas *big.Int - WithdrawalsRoot *common.Hash + Bloom types.Bloom + Coinbase common.Address + MixHash common.Hash + Nonce types.BlockNonce + Number *big.Int + Hash common.Hash + ParentHash common.Hash + ReceiptTrie common.Hash + StateRoot common.Hash + TransactionsTrie common.Hash + UncleHash common.Hash + ExtraData []byte + Difficulty *big.Int + GasLimit uint64 + GasUsed uint64 + Timestamp uint64 + BaseFeePerGas *big.Int + WithdrawalsRoot *common.Hash + BlobGasUsed *uint64 + ExcessBlobGas *uint64 + ParentBeaconBlockRoot *common.Hash } type btHeaderMarshaling struct { @@ -98,18 +104,34 @@ type btHeaderMarshaling struct { GasUsed math.HexOrDecimal64 Timestamp math.HexOrDecimal64 BaseFeePerGas *math.HexOrDecimal256 + BlobGasUsed *math.HexOrDecimal64 + ExcessBlobGas *math.HexOrDecimal64 } -func (t *BlockTest) Run(snapshotter bool) error { +func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger) error { config, ok := Forks[t.json.Network] if !ok { return UnsupportedForkError{t.json.Network} } - // import pre accounts & construct test genesis block & state root - db := rawdb.NewMemoryDatabase() + var ( + db = rawdb.NewMemoryDatabase() + tconf = &trie.Config{} + ) + if scheme == rawdb.PathScheme { + tconf.PathDB = pathdb.Defaults + } else { + tconf.HashDB = hashdb.Defaults + } + // Commit genesis state gspec := t.genesis(config) - gblock := gspec.MustCommit(db) + triedb := trie.NewDatabase(db, tconf) + gblock, err := gspec.Commit(db, triedb) + if err != nil { + return 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]) } @@ -119,12 +141,14 @@ func (t *BlockTest) Run(snapshotter bool) error { // Wrap the original engine within the beacon-engine engine := beacon.New(ethash.NewFaker()) - cache := &core.CacheConfig{TrieCleanLimit: 0, TriesInMemory: 128} + cache := &core.CacheConfig{TrieCleanLimit: 0, StateScheme: scheme, TriesInMemory: 128} if snapshotter { cache.SnapshotLimit = 1 cache.SnapshotWait = true } - chain, err := core.NewBlockChain(db, cache, nil, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := core.NewBlockChain(db, cache, nil, gspec, nil, engine, vm.Config{ + Tracer: tracer, + }, nil, nil) if err != nil { return err } @@ -156,18 +180,20 @@ func (t *BlockTest) Run(snapshotter bool) error { 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, + 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, } } @@ -276,6 +302,15 @@ func validateHeader(h *btHeader, h2 *types.Header) error { if !reflect.DeepEqual(h.WithdrawalsRoot, h2.WithdrawalsHash) { return fmt.Errorf("withdrawalsRoot: want: %v have: %v", h.WithdrawalsRoot, h2.WithdrawalsHash) } + if !reflect.DeepEqual(h.BlobGasUsed, h2.BlobGasUsed) { + return fmt.Errorf("blobGasUsed: want: %v have: %v", h.BlobGasUsed, h2.BlobGasUsed) + } + if !reflect.DeepEqual(h.ExcessBlobGas, h2.ExcessBlobGas) { + return fmt.Errorf("excessBlobGas: want: %v have: %v", h.ExcessBlobGas, h2.ExcessBlobGas) + } + if !reflect.DeepEqual(h.ParentBeaconBlockRoot, h2.ParentBeaconRoot) { + return fmt.Errorf("parentBeaconBlockRoot: want: %v have: %v", h.ParentBeaconBlockRoot, h2.ParentBeaconRoot) + } return nil } diff --git a/tests/fuzzers/les/les-fuzzer.go b/tests/fuzzers/les/les-fuzzer.go index 51a33f154abc..0585e73ece18 100644 --- a/tests/fuzzers/les/les-fuzzer.go +++ b/tests/fuzzers/les/les-fuzzer.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/txpool" + "github.com/ethereum/go-ethereum/core/txpool/legacypool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -44,9 +45,9 @@ var ( testChainLen = 256 testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056") - chain *core.BlockChain - addrHashes []common.Hash - txHashes []common.Hash + chain *core.BlockChain + addresses []common.Address + txHashes []common.Hash chtTrie *trie.Trie bloomTrie *trie.Trie @@ -54,7 +55,7 @@ var ( bloomKeys [][]byte ) -func makechain() (bc *core.BlockChain, addrHashes, txHashes []common.Hash) { +func makechain() (bc *core.BlockChain, addresses []common.Address, txHashes []common.Hash) { gspec := &core.Genesis{ Config: params.TestChainConfig, Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}}, @@ -76,7 +77,7 @@ func makechain() (bc *core.BlockChain, addrHashes, txHashes []common.Hash) { tx, _ = types.SignTx(types.NewTransaction(nonce, addr, big.NewInt(10000), params.TxGas, big.NewInt(params.GWei), nil), signer, bankKey) } gen.AddTx(tx) - addrHashes = append(addrHashes, crypto.Keccak256Hash(addr[:])) + addresses = append(addresses, addr) txHashes = append(txHashes, tx.Hash()) }) bc, _ = core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) @@ -87,8 +88,8 @@ func makechain() (bc *core.BlockChain, addrHashes, txHashes []common.Hash) { } func makeTries() (chtTrie *trie.Trie, bloomTrie *trie.Trie, chtKeys, bloomKeys [][]byte) { - chtTrie = trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase())) - bloomTrie = trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase())) + chtTrie = trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase(), trie.HashDefaults)) + bloomTrie = trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase(), trie.HashDefaults)) for i := 0; i < testChainLen; i++ { // The element in CHT is -> key := make([]byte, 8) @@ -106,7 +107,7 @@ func makeTries() (chtTrie *trie.Trie, bloomTrie *trie.Trie, chtKeys, bloomKeys [ } func init() { - chain, addrHashes, txHashes = makechain() + chain, addresses, txHashes = makechain() chtTrie, bloomTrie, chtKeys, bloomKeys = makeTries() } @@ -115,7 +116,8 @@ type fuzzer struct { pool *txpool.TxPool chainLen int - addr, txs []common.Hash + addresses []common.Address + txs []common.Hash nonce uint64 chtKeys [][]byte @@ -128,17 +130,20 @@ type fuzzer struct { } func newFuzzer(input []byte) *fuzzer { + pool := legacypool.New(legacypool.DefaultConfig, chain) + txpool, _ := txpool.New(new(big.Int).SetUint64(legacypool.DefaultConfig.PriceLimit), chain, []txpool.SubPool{pool}) + return &fuzzer{ chain: chain, chainLen: testChainLen, - addr: addrHashes, + addresses: addresses, txs: txHashes, chtTrie: chtTrie, bloomTrie: bloomTrie, chtKeys: chtKeys, bloomKeys: bloomKeys, nonce: uint64(len(txHashes)), - pool: txpool.NewTxPool(txpool.DefaultConfig, params.TestChainConfig, chain), + pool: txpool, input: bytes.NewReader(input), } } @@ -194,12 +199,12 @@ func (f *fuzzer) randomBlockHash() common.Hash { return common.BytesToHash(f.read(common.HashLength)) } -func (f *fuzzer) randomAddrHash() []byte { - i := f.randomInt(3 * len(f.addr)) - if i < len(f.addr) { - return f.addr[i].Bytes() +func (f *fuzzer) randomAddress() []byte { + i := f.randomInt(3 * len(f.addresses)) + if i < len(f.addresses) { + return f.addresses[i].Bytes() } - return f.read(common.HashLength) + return f.read(common.AddressLength) } func (f *fuzzer) randomCHTTrieKey() []byte { @@ -311,8 +316,8 @@ func Fuzz(input []byte) int { req := &l.GetCodePacket{Reqs: make([]l.CodeReq, f.randomInt(l.MaxCodeFetch+1))} for i := range req.Reqs { req.Reqs[i] = l.CodeReq{ - BHash: f.randomBlockHash(), - AccKey: f.randomAddrHash(), + BHash: f.randomBlockHash(), + AccountAddress: f.randomAddress(), } } f.doFuzz(l.GetCodeMsg, req) @@ -329,15 +334,15 @@ func Fuzz(input []byte) int { for i := range req.Reqs { if f.randomBool() { req.Reqs[i] = l.ProofReq{ - BHash: f.randomBlockHash(), - AccKey: f.randomAddrHash(), - Key: f.randomAddrHash(), - FromLevel: uint(f.randomX(3)), + BHash: f.randomBlockHash(), + AccountAddress: f.randomAddress(), + Key: f.randomAddress(), + FromLevel: uint(f.randomX(3)), } } else { req.Reqs[i] = l.ProofReq{ BHash: f.randomBlockHash(), - Key: f.randomAddrHash(), + Key: f.randomAddress(), FromLevel: uint(f.randomX(3)), } } diff --git a/tests/fuzzers/rangeproof/rangeproof-fuzzer.go b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go index 2881c7a7c211..ba490b761ff9 100644 --- a/tests/fuzzers/rangeproof/rangeproof-fuzzer.go +++ b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go @@ -21,12 +21,12 @@ import ( "encoding/binary" "fmt" "io" - "sort" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/trie" + "golang.org/x/exp/slices" ) type kv struct { @@ -34,12 +34,6 @@ type kv struct { t bool } -type entrySlice []*kv - -func (p entrySlice) Len() int { return len(p) } -func (p entrySlice) Less(i, j int) bool { return bytes.Compare(p[i].k, p[j].k) < 0 } -func (p entrySlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - type fuzzer struct { input io.Reader exhausted bool @@ -62,7 +56,7 @@ func (f *fuzzer) readInt() uint64 { } func (f *fuzzer) randomTrie(n int) (*trie.Trie, map[string]*kv) { - trie := trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase())) + trie := trie.NewEmpty(trie.NewDatabase(rawdb.NewMemoryDatabase(), nil)) vals := make(map[string]*kv) size := f.readInt() // Fill it with some fluff @@ -97,14 +91,16 @@ func (f *fuzzer) fuzz() int { if f.exhausted { return 0 // input too short } - var entries entrySlice + var entries []*kv for _, kv := range vals { entries = append(entries, kv) } if len(entries) <= 1 { return 0 } - sort.Sort(entries) + slices.SortFunc(entries, func(a, b *kv) int { + return bytes.Compare(a.k, b.k) + }) var ok = 0 for { @@ -117,10 +113,10 @@ func (f *fuzzer) fuzz() int { break } proof := memorydb.New() - if err := tr.Prove(entries[start].k, 0, proof); err != nil { + if err := tr.Prove(entries[start].k, proof); err != nil { panic(fmt.Sprintf("Failed to prove the first node %v", err)) } - if err := tr.Prove(entries[end-1].k, 0, proof); err != nil { + if err := tr.Prove(entries[end-1].k, proof); err != nil { panic(fmt.Sprintf("Failed to prove the last node %v", err)) } var keys [][]byte diff --git a/tests/fuzzers/snap/fuzz_handler.go b/tests/fuzzers/snap/fuzz_handler.go index 060c67188bd4..bef839206467 100644 --- a/tests/fuzzers/snap/fuzz_handler.go +++ b/tests/fuzzers/snap/fuzz_handler.go @@ -71,7 +71,6 @@ func getChain() *core.BlockChain { TrieDirtyLimit: 0, TrieTimeLimit: 5 * time.Minute, TrieCleanNoPrefetch: true, - TrieCleanRejournal: 0, SnapshotLimit: 100, SnapshotWait: true, } diff --git a/tests/fuzzers/stacktrie/trie_fuzzer.go b/tests/fuzzers/stacktrie/trie_fuzzer.go index 0099e9e16986..20b8ca24b3d2 100644 --- a/tests/fuzzers/stacktrie/trie_fuzzer.go +++ b/tests/fuzzers/stacktrie/trie_fuzzer.go @@ -23,7 +23,6 @@ import ( "fmt" "hash" "io" - "sort" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" @@ -33,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" "golang.org/x/crypto/sha3" + "golang.org/x/exp/slices" ) type fuzzer struct { @@ -104,19 +104,6 @@ func (b *spongeBatch) Replay(w ethdb.KeyValueWriter) error { return nil } type kv struct { k, v []byte } -type kvs []kv - -func (k kvs) Len() int { - return len(k) -} - -func (k kvs) Less(i, j int) bool { - return bytes.Compare(k[i].k, k[j].k) < 0 -} - -func (k kvs) Swap(i, j int) { - k[j], k[i] = k[i], k[j] -} // Fuzz is the fuzzing entry-point. // The function must return @@ -149,14 +136,14 @@ func (f *fuzzer) fuzz() int { // This spongeDb is used to check the sequence of disk-db-writes var ( spongeA = &spongeDb{sponge: sha3.NewLegacyKeccak256()} - dbA = trie.NewDatabase(rawdb.NewDatabase(spongeA)) + dbA = trie.NewDatabase(rawdb.NewDatabase(spongeA), nil) trieA = trie.NewEmpty(dbA) spongeB = &spongeDb{sponge: sha3.NewLegacyKeccak256()} - dbB = trie.NewDatabase(rawdb.NewDatabase(spongeB)) - trieB = trie.NewStackTrie(func(owner common.Hash, path []byte, hash common.Hash, blob []byte) { - rawdb.WriteTrieNode(spongeB, owner, path, hash, blob, dbB.Scheme()) + dbB = trie.NewDatabase(rawdb.NewDatabase(spongeB), nil) + trieB = trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme()) }) - vals kvs + vals []kv useful bool maxElements = 10000 // operate on unique keys only @@ -184,15 +171,20 @@ func (f *fuzzer) fuzz() int { return 0 } // Flush trie -> database - rootA, nodes := trieA.Commit(false) + rootA, nodes, err := trieA.Commit(false) + if err != nil { + panic(err) + } if nodes != nil { - dbA.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + dbA.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) } // Flush memdb -> disk (sponge) dbA.Commit(rootA, false) // Stacktrie requires sorted insertion - sort.Sort(vals) + slices.SortFunc(vals, func(a, b kv) int { + return bytes.Compare(a.k, b.k) + }) for _, kv := range vals { if f.debugging { fmt.Printf("{\"%#x\" , \"%#x\"} // stacktrie.Update\n", kv.k, kv.v) @@ -213,13 +205,10 @@ func (f *fuzzer) fuzz() int { // Ensure all the nodes are persisted correctly var ( nodeset = make(map[string][]byte) // path -> blob - trieC = trie.NewStackTrie(func(owner common.Hash, path []byte, hash common.Hash, blob []byte) { + trieC = trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { if crypto.Keccak256Hash(blob) != hash { panic("invalid node blob") } - if owner != (common.Hash{}) { - panic("invalid node owner") - } nodeset[string(path)] = common.CopyBytes(blob) }) checked int @@ -232,7 +221,7 @@ func (f *fuzzer) fuzz() int { panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootC)) } trieA, _ = trie.New(trie.TrieID(rootA), dbA) - iterA := trieA.NodeIterator(nil) + iterA := trieA.MustNodeIterator(nil) for iterA.Next(true) { if iterA.Hash() == (common.Hash{}) { if _, present := nodeset[string(iterA.Path())]; present { diff --git a/tests/fuzzers/trie/trie-fuzzer.go b/tests/fuzzers/trie/trie-fuzzer.go index 7be41b57cb62..687f5efb1cea 100644 --- a/tests/fuzzers/trie/trie-fuzzer.go +++ b/tests/fuzzers/trie/trie-fuzzer.go @@ -143,7 +143,7 @@ func Fuzz(input []byte) int { func runRandTest(rt randTest) error { var ( - triedb = trie.NewDatabase(rawdb.NewMemoryDatabase()) + triedb = trie.NewDatabase(rawdb.NewMemoryDatabase(), nil) tr = trie.NewEmpty(triedb) origin = types.EmptyRootHash values = make(map[string]string) // tracks content of the trie @@ -165,9 +165,12 @@ func runRandTest(rt randTest) error { case opHash: tr.Hash() case opCommit: - hash, nodes := tr.Commit(false) + hash, nodes, err := tr.Commit(false) + if err != nil { + return err + } if nodes != nil { - if err := triedb.Update(hash, origin, trienode.NewWithNodeSet(nodes)); err != nil { + if err := triedb.Update(hash, origin, 0, trienode.NewWithNodeSet(nodes), nil); err != nil { return err } } @@ -179,7 +182,7 @@ func runRandTest(rt randTest) error { origin = hash case opItercheckhash: checktr := trie.NewEmpty(triedb) - it := trie.NewIterator(tr.NodeIterator(nil)) + it := trie.NewIterator(tr.MustNodeIterator(nil)) for it.Next() { checktr.MustUpdate(it.Key, it.Value) } @@ -187,7 +190,7 @@ func runRandTest(rt randTest) error { return errors.New("hash mismatch in opItercheckhash") } case opProve: - rt[i].err = tr.Prove(step.key, 0, proofDb{}) + rt[i].err = tr.Prove(step.key, proofDb{}) } // Abort the test on error. if rt[i].err != nil { diff --git a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go index d1d6fdc66592..8b501645b663 100644 --- a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go +++ b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go @@ -83,6 +83,7 @@ func Fuzz(input []byte) int { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, clock, rand, ) f.Start() @@ -116,6 +117,8 @@ func Fuzz(input []byte) int { var ( announceIdxs = make([]int, announce) announces = make([]common.Hash, announce) + types = make([]byte, announce) + sizes = make([]uint32, announce) ) for i := 0; i < len(announces); i++ { annBuf := make([]byte, 2) @@ -124,11 +127,13 @@ func Fuzz(input []byte) int { } announceIdxs[i] = (int(annBuf[0])*256 + int(annBuf[1])) % len(txs) announces[i] = txs[announceIdxs[i]].Hash() + types[i] = txs[announceIdxs[i]].Type() + sizes[i] = uint32(txs[announceIdxs[i]].Size()) } if verbose { fmt.Println("Notify", peer, announceIdxs) } - if err := f.Notify(peer, announces); err != nil { + if err := f.Notify(peer, types, sizes, announces); err != nil { panic(err) } diff --git a/tests/gen_btheader.go b/tests/gen_btheader.go index 985ea692d751..80ad89e03bf5 100644 --- a/tests/gen_btheader.go +++ b/tests/gen_btheader.go @@ -17,24 +17,27 @@ var _ = (*btHeaderMarshaling)(nil) // MarshalJSON marshals as JSON. func (b btHeader) MarshalJSON() ([]byte, error) { type btHeader struct { - Bloom types.Bloom - Coinbase common.Address - MixHash common.Hash - Nonce types.BlockNonce - Number *math.HexOrDecimal256 - Hash common.Hash - ParentHash common.Hash - ReceiptTrie common.Hash - StateRoot common.Hash - TransactionsTrie common.Hash - UncleHash common.Hash - ExtraData hexutil.Bytes - Difficulty *math.HexOrDecimal256 - GasLimit math.HexOrDecimal64 - GasUsed math.HexOrDecimal64 - Timestamp math.HexOrDecimal64 - BaseFeePerGas *math.HexOrDecimal256 - WithdrawalsRoot *common.Hash + Bloom types.Bloom + Coinbase common.Address + MixHash common.Hash + Nonce types.BlockNonce + Number *math.HexOrDecimal256 + Hash common.Hash + ParentHash common.Hash + ReceiptTrie common.Hash + StateRoot common.Hash + TransactionsTrie common.Hash + UncleHash common.Hash + ExtraData hexutil.Bytes + Difficulty *math.HexOrDecimal256 + GasLimit math.HexOrDecimal64 + GasUsed math.HexOrDecimal64 + Timestamp math.HexOrDecimal64 + BaseFeePerGas *math.HexOrDecimal256 + WithdrawalsRoot *common.Hash + BlobGasUsed *math.HexOrDecimal64 + ExcessBlobGas *math.HexOrDecimal64 + ParentBeaconBlockRoot *common.Hash } var enc btHeader enc.Bloom = b.Bloom @@ -55,30 +58,36 @@ func (b btHeader) MarshalJSON() ([]byte, error) { enc.Timestamp = math.HexOrDecimal64(b.Timestamp) enc.BaseFeePerGas = (*math.HexOrDecimal256)(b.BaseFeePerGas) enc.WithdrawalsRoot = b.WithdrawalsRoot + enc.BlobGasUsed = (*math.HexOrDecimal64)(b.BlobGasUsed) + enc.ExcessBlobGas = (*math.HexOrDecimal64)(b.ExcessBlobGas) + enc.ParentBeaconBlockRoot = b.ParentBeaconBlockRoot return json.Marshal(&enc) } // UnmarshalJSON unmarshals from JSON. func (b *btHeader) UnmarshalJSON(input []byte) error { type btHeader struct { - Bloom *types.Bloom - Coinbase *common.Address - MixHash *common.Hash - Nonce *types.BlockNonce - Number *math.HexOrDecimal256 - Hash *common.Hash - ParentHash *common.Hash - ReceiptTrie *common.Hash - StateRoot *common.Hash - TransactionsTrie *common.Hash - UncleHash *common.Hash - ExtraData *hexutil.Bytes - Difficulty *math.HexOrDecimal256 - GasLimit *math.HexOrDecimal64 - GasUsed *math.HexOrDecimal64 - Timestamp *math.HexOrDecimal64 - BaseFeePerGas *math.HexOrDecimal256 - WithdrawalsRoot *common.Hash + Bloom *types.Bloom + Coinbase *common.Address + MixHash *common.Hash + Nonce *types.BlockNonce + Number *math.HexOrDecimal256 + Hash *common.Hash + ParentHash *common.Hash + ReceiptTrie *common.Hash + StateRoot *common.Hash + TransactionsTrie *common.Hash + UncleHash *common.Hash + ExtraData *hexutil.Bytes + Difficulty *math.HexOrDecimal256 + GasLimit *math.HexOrDecimal64 + GasUsed *math.HexOrDecimal64 + Timestamp *math.HexOrDecimal64 + BaseFeePerGas *math.HexOrDecimal256 + WithdrawalsRoot *common.Hash + BlobGasUsed *math.HexOrDecimal64 + ExcessBlobGas *math.HexOrDecimal64 + ParentBeaconBlockRoot *common.Hash } var dec btHeader if err := json.Unmarshal(input, &dec); err != nil { @@ -138,5 +147,14 @@ func (b *btHeader) UnmarshalJSON(input []byte) error { if dec.WithdrawalsRoot != nil { b.WithdrawalsRoot = dec.WithdrawalsRoot } + if dec.BlobGasUsed != nil { + b.BlobGasUsed = (*uint64)(dec.BlobGasUsed) + } + if dec.ExcessBlobGas != nil { + b.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) + } + if dec.ParentBeaconBlockRoot != nil { + b.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot + } return nil } diff --git a/tests/gen_sttransaction.go b/tests/gen_sttransaction.go index 7693a207a56c..9b5aecbfe6b8 100644 --- a/tests/gen_sttransaction.go +++ b/tests/gen_sttransaction.go @@ -6,6 +6,7 @@ import ( "encoding/json" "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" @@ -26,6 +27,9 @@ func (s stTransaction) MarshalJSON() ([]byte, error) { GasLimit []math.HexOrDecimal64 `json:"gasLimit"` Value []string `json:"value"` PrivateKey hexutil.Bytes `json:"secretKey"` + Sender *common.Address `json:"sender"` + BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` + BlobGasFeeCap *math.HexOrDecimal256 `json:"maxFeePerBlobGas,omitempty"` } var enc stTransaction enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice) @@ -43,6 +47,9 @@ func (s stTransaction) MarshalJSON() ([]byte, error) { } enc.Value = s.Value enc.PrivateKey = s.PrivateKey + enc.Sender = s.Sender + enc.BlobVersionedHashes = s.BlobVersionedHashes + enc.BlobGasFeeCap = (*math.HexOrDecimal256)(s.BlobGasFeeCap) return json.Marshal(&enc) } @@ -59,6 +66,9 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error { GasLimit []math.HexOrDecimal64 `json:"gasLimit"` Value []string `json:"value"` PrivateKey *hexutil.Bytes `json:"secretKey"` + Sender *common.Address `json:"sender"` + BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` + BlobGasFeeCap *math.HexOrDecimal256 `json:"maxFeePerBlobGas,omitempty"` } var dec stTransaction if err := json.Unmarshal(input, &dec); err != nil { @@ -97,5 +107,14 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error { if dec.PrivateKey != nil { s.PrivateKey = *dec.PrivateKey } + if dec.Sender != nil { + s.Sender = dec.Sender + } + if dec.BlobVersionedHashes != nil { + s.BlobVersionedHashes = dec.BlobVersionedHashes + } + if dec.BlobGasFeeCap != nil { + s.BlobGasFeeCap = (*big.Int)(dec.BlobGasFeeCap) + } return nil } diff --git a/tests/init.go b/tests/init.go index 869f1bfcdd67..99b7e4d33310 100644 --- a/tests/init.go +++ b/tests/init.go @@ -299,6 +299,44 @@ var Forks = map[string]*params.ChainConfig{ TerminalTotalDifficulty: big.NewInt(0), ShanghaiTime: u64(15_000), }, + "Cancun": { + 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), + }, + "ShanghaiToCancunAtTime15k": { + 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(15_000), + }, } // AvailableForks returns the set of defined fork names diff --git a/tests/init_test.go b/tests/init_test.go index 7d8743efcc71..3ab15e765832 100644 --- a/tests/init_test.go +++ b/tests/init_test.go @@ -41,6 +41,7 @@ var ( transactionTestDir = filepath.Join(baseDir, "TransactionTests") rlpTestDir = filepath.Join(baseDir, "RLPTests") difficultyTestDir = filepath.Join(baseDir, "BasicTests") + executionSpecDir = filepath.Join(".", "spec-tests", "fixtures") benchmarksDir = filepath.Join(".", "evm-benchmarks", "benchmarks") ) diff --git a/tests/state_test.go b/tests/state_test.go index a46b046e289c..772c7e02f169 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -30,6 +30,8 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/tracers/logger" @@ -48,23 +50,27 @@ func TestState(t *testing.T) { st.slow(`^stStaticCall/static_Return50000`) st.slow(`^stSystemOperationsTest/CallRecursiveBomb`) st.slow(`^stTransactionTest/Opcodes_TransactionInit`) - // Very time consuming st.skipLoad(`^stTimeConsuming/`) st.skipLoad(`.*vmPerformance/loop.*`) - // Uses 1GB RAM per tested fork st.skipLoad(`^stStaticCall/static_Call1MB`) // Broken tests: - // - // The stEOF tests are generated with EOF as part of Shanghai, which - // is erroneous. Therefore, these tests are skipped. - st.skipLoad(`^EIPTests/stEOF/`) + // EOF is not part of cancun + st.skipLoad(`^stEOF/`) + + // EIP-4844 tests need to be regenerated due to the data-to-blob rename + st.skipLoad(`^stEIP4844-blobtransactions/`) + // Expected failures: + // These EIP-4844 tests need to be regenerated. + st.fails(`stEIP4844-blobtransactions/opcodeBlobhashOutOfRange.json`, "test has incorrect state root") + st.fails(`stEIP4844-blobtransactions/opcodeBlobhBounds.json`, "test has incorrect state root") // For Istanbul, older tests were moved into LegacyTests for _, dir := range []string{ + filepath.Join(baseDir, "EIPTests", "StateTests"), stateTestDir, legacyStateTestDir, benchmarksDir, @@ -74,21 +80,52 @@ func TestState(t *testing.T) { subtest := subtest key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index) - t.Run(key+"/trie", func(t *testing.T) { + t.Run(key+"/hash/trie", func(t *testing.T) { + withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { + var result error + test.Run(subtest, vmconfig, false, rawdb.HashScheme, func(err error, snaps *snapshot.Tree, state *state.StateDB) { + result = st.checkFailure(t, err) + }) + return result + }) + }) + t.Run(key+"/hash/snap", func(t *testing.T) { + withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { + var result error + test.Run(subtest, vmconfig, true, rawdb.HashScheme, func(err error, snaps *snapshot.Tree, state *state.StateDB) { + if snaps != nil && state != nil { + if _, err := snaps.Journal(state.IntermediateRoot(false)); err != nil { + result = err + return + } + } + result = st.checkFailure(t, err) + }) + return result + }) + }) + t.Run(key+"/path/trie", func(t *testing.T) { withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { - _, _, err := test.Run(subtest, vmconfig, false) - return st.checkFailure(t, err) + var result error + test.Run(subtest, vmconfig, false, rawdb.PathScheme, func(err error, snaps *snapshot.Tree, state *state.StateDB) { + result = st.checkFailure(t, err) + }) + return result }) }) - t.Run(key+"/snap", func(t *testing.T) { + t.Run(key+"/path/snap", func(t *testing.T) { withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { - snaps, statedb, err := test.Run(subtest, vmconfig, true) - if snaps != nil && statedb != nil { - if _, err := snaps.Journal(statedb.IntermediateRoot(false)); err != nil { - return err + var result error + test.Run(subtest, vmconfig, true, rawdb.PathScheme, func(err error, snaps *snapshot.Tree, state *state.StateDB) { + if snaps != nil && state != nil { + if _, err := snaps.Journal(state.IntermediateRoot(false)); err != nil { + result = err + return + } } - } - return st.checkFailure(t, err) + result = st.checkFailure(t, err) + }) + return result }) }) } @@ -186,7 +223,8 @@ func runBenchmark(b *testing.B, t *StateTest) { vmconfig.ExtraEips = eips block := t.genesis(config).ToBlock() - _, statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, false) + triedb, _, statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, false, rawdb.HashScheme) + defer triedb.Close() var baseFee *big.Int if rules.IsLondon { diff --git a/tests/state_test_util.go b/tests/state_test_util.go index eec91b67a7d4..8c255c1b5bd2 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -39,6 +39,8 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/hashdb" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" "golang.org/x/crypto/sha3" ) @@ -113,6 +115,9 @@ type stTransaction struct { GasLimit []uint64 `json:"gasLimit"` Value []string `json:"value"` PrivateKey []byte `json:"secretKey"` + Sender *common.Address `json:"sender"` + BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` + BlobGasFeeCap *big.Int `json:"maxFeePerBlobGas,omitempty"` } type stTransactionMarshaling struct { @@ -122,6 +127,7 @@ type stTransactionMarshaling struct { Nonce math.HexOrDecimal64 GasLimit []math.HexOrDecimal64 PrivateKey hexutil.Bytes + BlobGasFeeCap *math.HexOrDecimal256 } // GetChainConfig takes a fork definition and returns a chain config. @@ -184,38 +190,50 @@ func (t *StateTest) checkError(subtest StateSubtest, err error) error { } // Run executes a specific subtest and verifies the post-state and logs -func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*snapshot.Tree, *state.StateDB, error) { - snaps, statedb, root, err := t.RunNoVerify(subtest, vmconfig, snapshotter) - if checkedErr := t.checkError(subtest, err); checkedErr != nil { - return snaps, statedb, checkedErr +func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bool, scheme string, postCheck func(err error, snaps *snapshot.Tree, state *state.StateDB)) (result error) { + triedb, snaps, statedb, root, err := t.RunNoVerify(subtest, vmconfig, snapshotter, scheme) + + // Invoke the callback at the end of function for further analysis. + defer func() { + postCheck(result, snaps, statedb) + + if triedb != nil { + triedb.Close() + } + }() + checkedErr := t.checkError(subtest, err) + if checkedErr != nil { + return checkedErr } // The error has been checked; if it was unexpected, it's already returned. if err != nil { // Here, an error exists but it was expected. // We do not check the post state or logs. - return snaps, statedb, nil + return nil } post := t.json.Post[subtest.Fork][subtest.Index] // N.B: We need to do this in a two-step process, because the first Commit takes care - // of suicides, and we need to touch the coinbase _after_ it has potentially suicided. + // of self-destructs, and we need to touch the coinbase _after_ it has potentially self-destructed. if root != common.Hash(post.Root) { - return snaps, statedb, fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root) + return fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root) } if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) { - return snaps, statedb, fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs) + return fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs) } - return snaps, statedb, nil + statedb, _ = state.New(root, statedb.Database(), snaps) + return nil } // RunNoVerify runs a specific subtest and returns the statedb and post-state root -func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*snapshot.Tree, *state.StateDB, common.Hash, error) { +func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool, scheme string) (*trie.Database, *snapshot.Tree, *state.StateDB, common.Hash, error) { config, eips, err := GetChainConfig(subtest.Fork) if err != nil { - return nil, nil, common.Hash{}, UnsupportedForkError{subtest.Fork} + return nil, nil, nil, common.Hash{}, UnsupportedForkError{subtest.Fork} } vmconfig.ExtraEips = eips + block := t.genesis(config).ToBlock() - snaps, statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter) + triedb, snaps, statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter, scheme) var baseFee *big.Int if config.IsLondon(new(big.Int)) { @@ -229,7 +247,8 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh post := t.json.Post[subtest.Fork][subtest.Index] msg, err := t.json.Tx.toMessage(post, baseFee) if err != nil { - return nil, nil, common.Hash{}, err + triedb.Close() + return nil, nil, nil, common.Hash{}, err } // Try to recover tx with current signer @@ -237,11 +256,13 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh var ttx types.Transaction err := ttx.UnmarshalBinary(post.TxBytes) if err != nil { - return nil, nil, common.Hash{}, err + triedb.Close() + return nil, nil, nil, common.Hash{}, err } if _, err := types.Sender(types.LatestSigner(config), &ttx); err != nil { - return nil, nil, common.Hash{}, err + triedb.Close() + return nil, nil, nil, common.Hash{}, err } } @@ -260,6 +281,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh context.Difficulty = big.NewInt(0) } evm := vm.NewEVM(context, txContext, statedb, config, vmconfig) + // Execute the message. snapshot := statedb.Snapshot() gaspool := new(core.GasPool) @@ -270,23 +292,29 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh } // Add 0-value mining reward. This only makes a difference in the cases // where - // - the coinbase suicided, or + // - the coinbase self-destructed, or // - there are only 'bad' transactions, which aren't executed. In those cases, // the coinbase gets no txfee, so isn't created, and thus needs to be touched statedb.AddBalance(block.Coinbase(), new(big.Int)) - // Commit block - statedb.Commit(config.IsEIP158(block.Number())) - // And _now_ get the state root - root := statedb.IntermediateRoot(config.IsEIP158(block.Number())) - return snaps, statedb, root, err + + // Commit state mutations into database. + root, _ := statedb.Commit(block.NumberU64(), config.IsEIP158(block.Number())) + return triedb, snaps, statedb, root, err } func (t *StateTest) gasLimit(subtest StateSubtest) uint64 { return t.json.Tx.GasLimit[t.json.Post[subtest.Fork][subtest.Index].Indexes.Gas] } -func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter bool) (*snapshot.Tree, *state.StateDB) { - sdb := state.NewDatabaseWithConfig(db, &trie.Config{Preimages: true}) +func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter bool, scheme string) (*trie.Database, *snapshot.Tree, *state.StateDB) { + tconf := &trie.Config{Preimages: true} + if scheme == rawdb.HashScheme { + tconf.HashDB = hashdb.Defaults + } else { + tconf.PathDB = pathdb.Defaults + } + triedb := trie.NewDatabase(db, tconf) + sdb := state.NewDatabaseWithNodeDB(db, triedb) statedb, _ := state.New(types.EmptyRootHash, sdb, nil) for addr, a := range accounts { statedb.SetCode(addr, a.Code) @@ -297,7 +325,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo } } // Commit and re-open to start with a clean state. - root, _ := statedb.Commit(false) + root, _ := statedb.Commit(0, false) var snaps *snapshot.Tree if snapshotter { @@ -307,10 +335,10 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo NoBuild: false, AsyncBuild: false, } - snaps, _ = snapshot.New(snapconfig, db, sdb.TrieDB(), root) + snaps, _ = snapshot.New(snapconfig, db, triedb, root) } statedb, _ = state.New(root, sdb, snaps) - return snaps, statedb + return triedb, snaps, statedb } func (t *StateTest) genesis(config *params.ChainConfig) *core.Genesis { @@ -332,9 +360,12 @@ func (t *StateTest) genesis(config *params.ChainConfig) *core.Genesis { } func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Message, error) { - // Derive sender from private key if present. var from common.Address - if len(tx.PrivateKey) > 0 { + // If 'sender' field is present, use that + if tx.Sender != nil { + from = *tx.Sender + } else if len(tx.PrivateKey) > 0 { + // Derive sender from private key if needed. key, err := crypto.ToECDSA(tx.PrivateKey) if err != nil { return nil, fmt.Errorf("invalid private key: %v", err) @@ -400,16 +431,18 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess } msg := &core.Message{ - From: from, - To: to, - Nonce: tx.Nonce, - Value: value, - GasLimit: gasLimit, - GasPrice: gasPrice, - GasFeeCap: tx.MaxFeePerGas, - GasTipCap: tx.MaxPriorityFeePerGas, - Data: data, - AccessList: accessList, + From: from, + To: to, + Nonce: tx.Nonce, + Value: value, + GasLimit: gasLimit, + GasPrice: gasPrice, + GasFeeCap: tx.MaxFeePerGas, + GasTipCap: tx.MaxPriorityFeePerGas, + Data: data, + AccessList: accessList, + BlobHashes: tx.BlobVersionedHashes, + BlobGasFeeCap: tx.BlobGasFeeCap, } return msg, nil } diff --git a/trie/committer.go b/trie/committer.go index e825287fd220..92163cdb3b64 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -131,22 +131,15 @@ func (c *committer) store(path []byte, n node) node { // The node is embedded in its parent, in other words, this node // will not be stored in the database independently, mark it as // deleted only if the node was existent in database before. - prev, ok := c.tracer.accessList[string(path)] + _, ok := c.tracer.accessList[string(path)] if ok { - c.nodes.AddNode(path, trienode.NewWithPrev(common.Hash{}, nil, prev)) + c.nodes.AddNode(path, trienode.NewDeleted()) } return n } // Collect the dirty node to nodeset for return. - var ( - nhash = common.BytesToHash(hash) - node = trienode.NewWithPrev( - nhash, - nodeToBytes(n), - c.tracer.accessList[string(path)], - ) - ) - c.nodes.AddNode(path, node) + nhash := common.BytesToHash(hash) + c.nodes.AddNode(path, trienode.New(nhash, nodeToBytes(n))) // Collect the corresponding leaf node if it's required. We don't check // full node since it's impossible to store value in fullNode. The key diff --git a/trie/database_wrap.go b/trie/database.go similarity index 51% rename from trie/database_wrap.go rename to trie/database.go index b05f56847a90..1e59f0908f38 100644 --- a/trie/database_wrap.go +++ b/trie/database.go @@ -18,22 +18,28 @@ package trie import ( "errors" - "runtime" - "time" - "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/trie/triedb/hashdb" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" ) // Config defines all necessary options for database. type Config struct { - Cache int // Memory allowance (MB) to use for caching trie nodes in memory - Journal string // Journal of clean cache to survive node restarts - Preimages bool // Flag whether the preimage of trie key is recorded + Preimages bool // Flag whether the preimage of node key is recorded + HashDB *hashdb.Config // Configs for hash-based scheme + PathDB *pathdb.Config // Configs for experimental path-based scheme +} + +// HashDefaults represents a config for using hash-based scheme with +// default settings. +var HashDefaults = &Config{ + Preimages: false, + HashDB: hashdb.Defaults, } // backend defines the methods needed to access/update trie nodes in different @@ -46,14 +52,20 @@ type backend interface { // according to the state scheme. Initialized(genesisRoot common.Hash) bool - // Size returns the current storage size of the memory cache in front of the - // persistent database layer. - Size() common.StorageSize + // Size returns the current storage size of the diff layers on top of the + // disk layer and the storage size of the nodes cached in the disk layer. + // + // For hash scheme, there is no differentiation between diff layer nodes + // and dirty disk layer nodes, so both are merged into the second return. + Size() (common.StorageSize, common.StorageSize) // Update performs a state transition by committing dirty nodes contained // in the given set in order to update state from the specified parent to // the specified root. - Update(root common.Hash, parent common.Hash, nodes *trienode.MergedNodeSet) error + // + // The passed in maps(nodes, states) will be retained to avoid copying + // everything. Therefore, these maps must not be changed afterwards. + Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error // Commit writes all relevant trie nodes belonging to the specified state // to disk. Report specifies whether logs will be displayed in info level. @@ -67,66 +79,63 @@ type backend interface { // types of node backend as an entrypoint. It's responsible for all interactions // relevant with trie nodes and node preimages. type Database struct { - config *Config // Configuration for trie database - diskdb ethdb.Database // Persistent database to store the snapshot - cleans *fastcache.Cache // Megabytes permitted using for read caches - preimages *preimageStore // The store for caching preimages - backend backend // The backend for managing trie nodes + config *Config // Configuration for trie database + diskdb ethdb.Database // Persistent database to store the snapshot + preimages *preimageStore // The store for caching preimages + backend backend // The backend for managing trie nodes } -// prepare initializes the database with provided configs, but the -// database backend is still left as nil. -func prepare(diskdb ethdb.Database, config *Config) *Database { - var cleans *fastcache.Cache - if config != nil && config.Cache > 0 { - if config.Journal == "" { - cleans = fastcache.New(config.Cache * 1024 * 1024) - } else { - cleans = fastcache.LoadFromFileOrNew(config.Journal, config.Cache*1024*1024) - } +// NewDatabase initializes the trie database with default settings, note +// the legacy hash-based scheme is used by default. +func NewDatabase(diskdb ethdb.Database, config *Config) *Database { + // Sanitize the config and use the default one if it's not specified. + if config == nil { + config = HashDefaults } var preimages *preimageStore - if config != nil && config.Preimages { + if config.Preimages { preimages = newPreimageStore(diskdb) } - return &Database{ + db := &Database{ config: config, diskdb: diskdb, - cleans: cleans, preimages: preimages, } -} - -// NewDatabase initializes the trie database with default settings, namely -// the legacy hash-based scheme is used by default. -func NewDatabase(diskdb ethdb.Database) *Database { - return NewDatabaseWithConfig(diskdb, nil) -} - -// NewDatabaseWithConfig initializes the trie database with provided configs. -// The path-based scheme is not activated yet, always initialized with legacy -// hash-based scheme by default. -func NewDatabaseWithConfig(diskdb ethdb.Database, config *Config) *Database { - db := prepare(diskdb, config) - db.backend = hashdb.New(diskdb, db.cleans, mptResolver{}) + if config.HashDB != nil && config.PathDB != nil { + log.Crit("Both 'hash' and 'path' mode are configured") + } + if config.PathDB != nil { + db.backend = pathdb.New(diskdb, config.PathDB) + } else { + db.backend = hashdb.New(diskdb, config.HashDB, mptResolver{}) + } return db } // Reader returns a reader for accessing all trie nodes with provided state root. -// Nil is returned in case the state is not available. -func (db *Database) Reader(blockRoot common.Hash) Reader { - return db.backend.(*hashdb.Database).Reader(blockRoot) +// An error will be returned if the requested state is not available. +func (db *Database) Reader(blockRoot common.Hash) (Reader, error) { + switch b := db.backend.(type) { + case *hashdb.Database: + return b.Reader(blockRoot) + case *pathdb.Database: + return b.Reader(blockRoot) + } + return nil, errors.New("unknown backend") } // Update performs a state transition by committing dirty nodes contained in the // given set in order to update state from the specified parent to the specified // root. The held pre-images accumulated up to this point will be flushed in case // the size exceeds the threshold. -func (db *Database) Update(root common.Hash, parent common.Hash, nodes *trienode.MergedNodeSet) error { +// +// The passed in maps(nodes, states) will be retained to avoid copying everything. +// Therefore, these maps must not be changed afterwards. +func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { if db.preimages != nil { db.preimages.commit(false) } - return db.backend.Update(root, parent, nodes) + return db.backend.Update(root, parent, block, nodes, states) } // Commit iterates over all the children of a particular node, writes them out @@ -139,18 +148,19 @@ func (db *Database) Commit(root common.Hash, report bool) error { return db.backend.Commit(root, report) } -// Size returns the storage size of dirty trie nodes in front of the persistent -// database and the size of cached preimages. -func (db *Database) Size() (common.StorageSize, common.StorageSize) { +// Size returns the storage size of diff layer nodes above the persistent disk +// layer, the dirty nodes buffered within the disk layer, and the size of cached +// preimages. +func (db *Database) Size() (common.StorageSize, common.StorageSize, common.StorageSize) { var ( - storages common.StorageSize - preimages common.StorageSize + diffs, nodes common.StorageSize + preimages common.StorageSize ) - storages = db.backend.Size() + diffs, nodes = db.backend.Size() if db.preimages != nil { preimages = db.preimages.size() } - return storages, preimages + return diffs, nodes, preimages } // Initialized returns an indicator if the state data is already initialized @@ -168,50 +178,24 @@ func (db *Database) Scheme() string { // It is meant to be called when closing the blockchain object, so that all // resources held can be released correctly. func (db *Database) Close() error { - if db.preimages != nil { - db.preimages.commit(true) - } + db.WritePreimages() return db.backend.Close() } -// saveCache saves clean state cache to given directory path -// using specified CPU cores. -func (db *Database) saveCache(dir string, threads int) error { - if db.cleans == nil { - return nil - } - log.Info("Writing clean trie cache to disk", "path", dir, "threads", threads) - - start := time.Now() - err := db.cleans.SaveToFileConcurrent(dir, threads) - if err != nil { - log.Error("Failed to persist clean trie cache", "error", err) - return err +// WritePreimages flushes all accumulated preimages to disk forcibly. +func (db *Database) WritePreimages() { + if db.preimages != nil { + db.preimages.commit(true) } - log.Info("Persisted the clean trie cache", "path", dir, "elapsed", common.PrettyDuration(time.Since(start))) - return nil } -// SaveCache atomically saves fast cache data to the given dir using all -// available CPU cores. -func (db *Database) SaveCache(dir string) error { - return db.saveCache(dir, runtime.GOMAXPROCS(0)) -} - -// SaveCachePeriodically atomically saves fast cache data to the given dir with -// the specified interval. All dump operation will only use a single CPU core. -func (db *Database) SaveCachePeriodically(dir string, interval time.Duration, stopCh <-chan struct{}) { - ticker := time.NewTicker(interval) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - db.saveCache(dir, 1) - case <-stopCh: - return - } +// Preimage retrieves a cached trie node pre-image from memory. If it cannot be +// found cached, the method queries the persistent database for the content. +func (db *Database) Preimage(hash common.Hash) []byte { + if db.preimages == nil { + return nil } + return db.preimages.preimage(hash) } // Cap iteratively flushes old but still referenced trie nodes until the total @@ -265,3 +249,72 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) { } return hdb.Node(hash) } + +// Recover rollbacks the database to a specified historical point. The state is +// supported as the rollback destination only if it's canonical state and the +// corresponding trie histories are existent. It's only supported by path-based +// database and will return an error for others. +func (db *Database) Recover(target common.Hash) error { + pdb, ok := db.backend.(*pathdb.Database) + if !ok { + return errors.New("not supported") + } + return pdb.Recover(target, &trieLoader{db: db}) +} + +// Recoverable returns the indicator if the specified state is enabled to be +// recovered. It's only supported by path-based database and will return an +// error for others. +func (db *Database) Recoverable(root common.Hash) (bool, error) { + pdb, ok := db.backend.(*pathdb.Database) + if !ok { + return false, errors.New("not supported") + } + return pdb.Recoverable(root), nil +} + +// Disable deactivates the database and invalidates all available state layers +// as stale to prevent access to the persistent state, which is in the syncing +// stage. +// +// It's only supported by path-based database and will return an error for others. +func (db *Database) Disable() error { + pdb, ok := db.backend.(*pathdb.Database) + if !ok { + return errors.New("not supported") + } + return pdb.Disable() +} + +// Enable activates database and resets the state tree with the provided persistent +// state root once the state sync is finished. +func (db *Database) Enable(root common.Hash) error { + pdb, ok := db.backend.(*pathdb.Database) + if !ok { + return errors.New("not supported") + } + return pdb.Enable(root) +} + +// Journal commits an entire diff hierarchy to disk into a single journal entry. +// This is meant to be used during shutdown to persist the snapshot without +// flattening everything down (bad for reorgs). It's only supported by path-based +// database and will return an error for others. +func (db *Database) Journal(root common.Hash) error { + pdb, ok := db.backend.(*pathdb.Database) + if !ok { + return errors.New("not supported") + } + return pdb.Journal(root) +} + +// SetBufferSize sets the node buffer size to the provided value(in bytes). +// It's only supported by path-based database and will return an error for +// others. +func (db *Database) SetBufferSize(size int) error { + pdb, ok := db.backend.(*pathdb.Database) + if !ok { + return errors.New("not supported") + } + return pdb.SetBufferSize(size) +} diff --git a/trie/database_test.go b/trie/database_test.go index cad462f737a7..d508c6553319 100644 --- a/trie/database_test.go +++ b/trie/database_test.go @@ -20,16 +20,21 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie/triedb/hashdb" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" ) // newTestDatabase initializes the trie database with specified scheme. func newTestDatabase(diskdb ethdb.Database, scheme string) *Database { - db := prepare(diskdb, nil) + config := &Config{Preimages: false} if scheme == rawdb.HashScheme { - db.backend = hashdb.New(diskdb, db.cleans, mptResolver{}) + config.HashDB = &hashdb.Config{ + CleanCacheSize: 0, + } // disable clean cache + } else { + config.PathDB = &pathdb.Config{ + CleanCacheSize: 0, + DirtyCacheSize: 0, + } // disable clean/dirty cache } - //} else { - // db.backend = snap.New(diskdb, db.cleans, nil) - //} - return db + return NewDatabase(diskdb, config) } diff --git a/trie/encoding.go b/trie/encoding.go index 8ee0022ef3a0..3284d3f8f021 100644 --- a/trie/encoding.go +++ b/trie/encoding.go @@ -51,9 +51,8 @@ func hexToCompact(hex []byte) []byte { return buf } -// hexToCompactInPlace places the compact key in input buffer, returning the length -// needed for the representation -func hexToCompactInPlace(hex []byte) int { +// hexToCompactInPlace places the compact key in input buffer, returning the compacted key. +func hexToCompactInPlace(hex []byte) []byte { var ( hexLen = len(hex) // length of the hex input firstByte = byte(0) @@ -77,7 +76,7 @@ func hexToCompactInPlace(hex []byte) int { hex[bi] = hex[ni]<<4 | hex[ni+1] } hex[0] = firstByte - return binLen + return hex[:binLen] } func compactToHex(compact []byte) []byte { diff --git a/trie/encoding_test.go b/trie/encoding_test.go index d16d25c359c7..ac50b5d025e5 100644 --- a/trie/encoding_test.go +++ b/trie/encoding_test.go @@ -86,8 +86,7 @@ func TestHexToCompactInPlace(t *testing.T) { } { hexBytes, _ := hex.DecodeString(key) exp := hexToCompact(hexBytes) - sz := hexToCompactInPlace(hexBytes) - got := hexBytes[:sz] + got := hexToCompactInPlace(hexBytes) if !bytes.Equal(exp, got) { t.Fatalf("test %d: encoding err\ninp %v\ngot %x\nexp %x\n", i, key, got, exp) } @@ -102,8 +101,7 @@ func TestHexToCompactInPlaceRandom(t *testing.T) { hexBytes := keybytesToHex(key) hexOrig := []byte(string(hexBytes)) exp := hexToCompact(hexBytes) - sz := hexToCompactInPlace(hexBytes) - got := hexBytes[:sz] + got := hexToCompactInPlace(hexBytes) if !bytes.Equal(exp, got) { t.Fatalf("encoding err \ncpt %x\nhex %x\ngot %x\nexp %x\n", @@ -119,6 +117,13 @@ func BenchmarkHexToCompact(b *testing.B) { } } +func BenchmarkHexToCompactInPlace(b *testing.B) { + testBytes := []byte{0, 15, 1, 12, 11, 8, 16 /*term*/} + for i := 0; i < b.N; i++ { + hexToCompactInPlace(testBytes) + } +} + func BenchmarkCompactToHex(b *testing.B) { testBytes := []byte{0, 15, 1, 12, 11, 8, 16 /*term*/} for i := 0; i < b.N; i++ { diff --git a/trie/errors.go b/trie/errors.go index bd82b950aad2..7be7041c7f09 100644 --- a/trie/errors.go +++ b/trie/errors.go @@ -17,11 +17,17 @@ package trie import ( + "errors" "fmt" "github.com/ethereum/go-ethereum/common" ) +// ErrCommitted is returned when a already committed trie is requested for usage. +// The potential usages can be `Get`, `Update`, `Delete`, `NodeIterator`, `Prove` +// and so on. +var ErrCommitted = errors.New("trie is already committed") + // MissingNodeError is returned by the trie functions (Get, Update, Delete) // in the case where a trie node is not present in the local database. It contains // information necessary for retrieving the missing node. diff --git a/trie/iterator_test.go b/trie/iterator_test.go index 8003858e5beb..57d1f06a160a 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -18,7 +18,6 @@ package trie import ( "bytes" - "encoding/binary" "fmt" "math/rand" "testing" @@ -27,14 +26,12 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/trie/trienode" ) func TestEmptyIterator(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) - iter := trie.NodeIterator(nil) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) + iter := trie.MustNodeIterator(nil) seen := make(map[string]struct{}) for iter.Next(true) { @@ -46,7 +43,7 @@ func TestEmptyIterator(t *testing.T) { } func TestIterator(t *testing.T) { - db := NewDatabase(rawdb.NewMemoryDatabase()) + db := NewDatabase(rawdb.NewMemoryDatabase(), nil) trie := NewEmpty(db) vals := []struct{ k, v string }{ {"do", "verb"}, @@ -62,12 +59,12 @@ func TestIterator(t *testing.T) { all[val.k] = val.v trie.MustUpdate([]byte(val.k), []byte(val.v)) } - root, nodes := trie.Commit(false) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := trie.Commit(false) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) trie, _ = New(TrieID(root), db) found := make(map[string]string) - it := NewIterator(trie.NodeIterator(nil)) + it := NewIterator(trie.MustNodeIterator(nil)) for it.Next() { found[string(it.Key)] = string(it.Value) } @@ -84,8 +81,12 @@ type kv struct { t bool } +func (k *kv) cmp(other *kv) int { + return bytes.Compare(k.k, other.k) +} + func TestIteratorLargeData(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) vals := make(map[string]*kv) for i := byte(0); i < 255; i++ { @@ -97,7 +98,7 @@ func TestIteratorLargeData(t *testing.T) { vals[string(value2.k)] = value2 } - it := NewIterator(trie.NodeIterator(nil)) + it := NewIterator(trie.MustNodeIterator(nil)) for it.Next() { vals[string(it.Key)].t = true } @@ -126,7 +127,7 @@ type iterationElement struct { // Tests that the node iterator indeed walks over the entire database contents. func TestNodeIteratorCoverage(t *testing.T) { testNodeIteratorCoverage(t, rawdb.HashScheme) - //testNodeIteratorCoverage(t, rawdb.PathScheme) + testNodeIteratorCoverage(t, rawdb.PathScheme) } func testNodeIteratorCoverage(t *testing.T, scheme string) { @@ -135,7 +136,7 @@ func testNodeIteratorCoverage(t *testing.T, scheme string) { // Gather all the node hashes found by the iterator var elements = make(map[common.Hash]iterationElement) - for it := trie.NodeIterator(nil); it.Next(true); { + for it := trie.MustNodeIterator(nil); it.Next(true); { if it.Hash() != (common.Hash{}) { elements[it.Hash()] = iterationElement{ hash: it.Hash(), @@ -145,8 +146,12 @@ func testNodeIteratorCoverage(t *testing.T, scheme string) { } } // Cross check the hashes and the database itself + reader, err := nodeDb.Reader(trie.Hash()) + if err != nil { + t.Fatalf("state is not available %x", trie.Hash()) + } for _, element := range elements { - if blob, err := nodeDb.Reader(trie.Hash()).Node(common.Hash{}, element.path, element.hash); err != nil { + if blob, err := reader.Node(common.Hash{}, element.path, element.hash); err != nil { t.Errorf("failed to retrieve reported node %x: %v", element.hash, err) } else if !bytes.Equal(blob, element.blob) { t.Errorf("node blob is different, want %v got %v", element.blob, blob) @@ -200,25 +205,25 @@ var testdata2 = []kvs{ } func TestIteratorSeek(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) for _, val := range testdata1 { trie.MustUpdate([]byte(val.k), []byte(val.v)) } // Seek to the middle. - it := NewIterator(trie.NodeIterator([]byte("fab"))) + it := NewIterator(trie.MustNodeIterator([]byte("fab"))) if err := checkIteratorOrder(testdata1[4:], it); err != nil { t.Fatal(err) } // Seek to a non-existent key. - it = NewIterator(trie.NodeIterator([]byte("barc"))) + it = NewIterator(trie.MustNodeIterator([]byte("barc"))) if err := checkIteratorOrder(testdata1[1:], it); err != nil { t.Fatal(err) } // Seek beyond the end. - it = NewIterator(trie.NodeIterator([]byte("z"))) + it = NewIterator(trie.MustNodeIterator([]byte("z"))) if err := checkIteratorOrder(nil, it); err != nil { t.Fatal(err) } @@ -241,26 +246,26 @@ func checkIteratorOrder(want []kvs, it *Iterator) error { } func TestDifferenceIterator(t *testing.T) { - dba := NewDatabase(rawdb.NewMemoryDatabase()) + dba := NewDatabase(rawdb.NewMemoryDatabase(), nil) triea := NewEmpty(dba) for _, val := range testdata1 { triea.MustUpdate([]byte(val.k), []byte(val.v)) } - rootA, nodesA := triea.Commit(false) - dba.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodesA)) + rootA, nodesA, _ := triea.Commit(false) + dba.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesA), nil) triea, _ = New(TrieID(rootA), dba) - dbb := NewDatabase(rawdb.NewMemoryDatabase()) + dbb := NewDatabase(rawdb.NewMemoryDatabase(), nil) trieb := NewEmpty(dbb) for _, val := range testdata2 { trieb.MustUpdate([]byte(val.k), []byte(val.v)) } - rootB, nodesB := trieb.Commit(false) - dbb.Update(rootB, types.EmptyRootHash, trienode.NewWithNodeSet(nodesB)) + rootB, nodesB, _ := trieb.Commit(false) + dbb.Update(rootB, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesB), nil) trieb, _ = New(TrieID(rootB), dbb) found := make(map[string]string) - di, _ := NewDifferenceIterator(triea.NodeIterator(nil), trieb.NodeIterator(nil)) + di, _ := NewDifferenceIterator(triea.MustNodeIterator(nil), trieb.MustNodeIterator(nil)) it := NewIterator(di) for it.Next() { found[string(it.Key)] = string(it.Value) @@ -283,25 +288,25 @@ func TestDifferenceIterator(t *testing.T) { } func TestUnionIterator(t *testing.T) { - dba := NewDatabase(rawdb.NewMemoryDatabase()) + dba := NewDatabase(rawdb.NewMemoryDatabase(), nil) triea := NewEmpty(dba) for _, val := range testdata1 { triea.MustUpdate([]byte(val.k), []byte(val.v)) } - rootA, nodesA := triea.Commit(false) - dba.Update(rootA, types.EmptyRootHash, trienode.NewWithNodeSet(nodesA)) + rootA, nodesA, _ := triea.Commit(false) + dba.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesA), nil) triea, _ = New(TrieID(rootA), dba) - dbb := NewDatabase(rawdb.NewMemoryDatabase()) + dbb := NewDatabase(rawdb.NewMemoryDatabase(), nil) trieb := NewEmpty(dbb) for _, val := range testdata2 { trieb.MustUpdate([]byte(val.k), []byte(val.v)) } - rootB, nodesB := trieb.Commit(false) - dbb.Update(rootB, types.EmptyRootHash, trienode.NewWithNodeSet(nodesB)) + rootB, nodesB, _ := trieb.Commit(false) + dbb.Update(rootB, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesB), nil) trieb, _ = New(TrieID(rootB), dbb) - di, _ := NewUnionIterator([]NodeIterator{triea.NodeIterator(nil), trieb.NodeIterator(nil)}) + di, _ := NewUnionIterator([]NodeIterator{triea.MustNodeIterator(nil), trieb.MustNodeIterator(nil)}) it := NewIterator(di) all := []struct{ k, v string }{ @@ -336,19 +341,19 @@ func TestUnionIterator(t *testing.T) { } func TestIteratorNoDups(t *testing.T) { - tr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + tr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) for _, val := range testdata1 { tr.MustUpdate([]byte(val.k), []byte(val.v)) } - checkIteratorNoDups(t, tr.NodeIterator(nil), nil) + checkIteratorNoDups(t, tr.MustNodeIterator(nil), nil) } // This test checks that nodeIterator.Next can be retried after inserting missing trie nodes. func TestIteratorContinueAfterError(t *testing.T) { testIteratorContinueAfterError(t, false, rawdb.HashScheme) testIteratorContinueAfterError(t, true, rawdb.HashScheme) - // testIteratorContinueAfterError(t, false, rawdb.PathScheme) - // testIteratorContinueAfterError(t, true, rawdb.PathScheme) + testIteratorContinueAfterError(t, false, rawdb.PathScheme) + testIteratorContinueAfterError(t, true, rawdb.PathScheme) } func testIteratorContinueAfterError(t *testing.T, memonly bool, scheme string) { @@ -359,13 +364,13 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool, scheme string) { for _, val := range testdata1 { tr.MustUpdate([]byte(val.k), []byte(val.v)) } - root, nodes := tr.Commit(false) - tdb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := tr.Commit(false) + tdb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) if !memonly { tdb.Commit(root, false) } tr, _ = New(TrieID(root), tdb) - wantNodeCount := checkIteratorNoDups(t, tr.NodeIterator(nil), nil) + wantNodeCount := checkIteratorNoDups(t, tr.MustNodeIterator(nil), nil) var ( paths [][]byte @@ -424,7 +429,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool, scheme string) { } // Iterate until the error is hit. seen := make(map[string]bool) - it := tr.NodeIterator(nil) + it := tr.MustNodeIterator(nil) checkIteratorNoDups(t, it, seen) missing, ok := it.Error().(*MissingNodeError) if !ok || missing.NodeHash != rhash { @@ -453,8 +458,8 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool, scheme string) { func TestIteratorContinueAfterSeekError(t *testing.T) { testIteratorContinueAfterSeekError(t, false, rawdb.HashScheme) testIteratorContinueAfterSeekError(t, true, rawdb.HashScheme) - // testIteratorContinueAfterSeekError(t, false, rawdb.PathScheme) - // testIteratorContinueAfterSeekError(t, true, rawdb.PathScheme) + testIteratorContinueAfterSeekError(t, false, rawdb.PathScheme) + testIteratorContinueAfterSeekError(t, true, rawdb.PathScheme) } func testIteratorContinueAfterSeekError(t *testing.T, memonly bool, scheme string) { @@ -469,14 +474,14 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool, scheme strin for _, val := range testdata1 { ctr.MustUpdate([]byte(val.k), []byte(val.v)) } - root, nodes := ctr.Commit(false) + root, nodes, _ := ctr.Commit(false) for path, n := range nodes.Nodes { if n.Hash == barNodeHash { barNodePath = []byte(path) break } } - triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + triedb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) if !memonly { triedb.Commit(root, false) } @@ -492,7 +497,7 @@ func testIteratorContinueAfterSeekError(t *testing.T, memonly bool, scheme strin } // Create a new iterator that seeks to "bars". Seeking can't proceed because // the node is missing. - it := tr.NodeIterator([]byte("bars")) + it := tr.MustNodeIterator([]byte("bars")) missing, ok := it.Error().(*MissingNodeError) if !ok { t.Fatal("want MissingNodeError, got", it.Error()) @@ -526,94 +531,7 @@ func checkIteratorNoDups(t *testing.T, it NodeIterator, seen map[string]bool) in func TestIteratorNodeBlob(t *testing.T) { testIteratorNodeBlob(t, rawdb.HashScheme) - //testIteratorNodeBlob(t, rawdb.PathScheme) -} - -type loggingDb struct { - getCount uint64 - backend ethdb.KeyValueStore -} - -func (l *loggingDb) Has(key []byte) (bool, error) { - return l.backend.Has(key) -} - -func (l *loggingDb) Get(key []byte) ([]byte, error) { - l.getCount++ - return l.backend.Get(key) -} - -func (l *loggingDb) Put(key []byte, value []byte) error { - return l.backend.Put(key, value) -} - -func (l *loggingDb) Delete(key []byte) error { - return l.backend.Delete(key) -} - -func (l *loggingDb) NewBatch() ethdb.Batch { - return l.backend.NewBatch() -} - -func (l *loggingDb) NewBatchWithSize(size int) ethdb.Batch { - return l.backend.NewBatchWithSize(size) -} - -func (l *loggingDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { - return l.backend.NewIterator(prefix, start) -} - -func (l *loggingDb) NewSnapshot() (ethdb.Snapshot, error) { - return l.backend.NewSnapshot() -} - -func (l *loggingDb) Stat(property string) (string, error) { - return l.backend.Stat(property) -} - -func (l *loggingDb) Compact(start []byte, limit []byte) error { - return l.backend.Compact(start, limit) -} - -func (l *loggingDb) Close() error { - return l.backend.Close() -} - -// makeLargeTestTrie create a sample test trie -func makeLargeTestTrie() (*Database, *StateTrie, *loggingDb) { - // Create an empty trie - logDb := &loggingDb{0, memorydb.New()} - triedb := NewDatabase(rawdb.NewDatabase(logDb)) - trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), triedb) - - // Fill it with some arbitrary data - for i := 0; i < 10000; i++ { - key := make([]byte, 32) - val := make([]byte, 32) - binary.BigEndian.PutUint64(key, uint64(i)) - binary.BigEndian.PutUint64(val, uint64(i)) - key = crypto.Keccak256(key) - val = crypto.Keccak256(val) - trie.MustUpdate(key, val) - } - root, nodes := trie.Commit(false) - triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) - // Return the generated trie - return triedb, trie, logDb -} - -// Tests that the node iterator indeed walks over the entire database contents. -func TestNodeIteratorLargeTrie(t *testing.T) { - // Create some arbitrary test trie to iterate - db, trie, logDb := makeLargeTestTrie() - db.Cap(0) // flush everything - // Do a seek operation - trie.NodeIterator(common.FromHex("0x77667766776677766778855885885885")) - // master: 24 get operations - // this pr: 5 get operations - if have, want := logDb.getCount, uint64(5); have != want { - t.Fatalf("Too many lookups during seek, have %d want %d", have, want) - } + testIteratorNodeBlob(t, rawdb.PathScheme) } func testIteratorNodeBlob(t *testing.T, scheme string) { @@ -636,13 +554,13 @@ func testIteratorNodeBlob(t *testing.T, scheme string) { all[val.k] = val.v trie.MustUpdate([]byte(val.k), []byte(val.v)) } - root, nodes := trie.Commit(false) - triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := trie.Commit(false) + triedb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) triedb.Commit(root, false) var found = make(map[common.Hash][]byte) trie, _ = New(TrieID(root), triedb) - it := trie.NodeIterator(nil) + it := trie.MustNodeIterator(nil) for it.Next(true) { if it.Hash() == (common.Hash{}) { continue @@ -689,7 +607,7 @@ func isTrieNode(scheme string, key, val []byte) (bool, []byte, common.Hash) { } hash = common.BytesToHash(key) } else { - ok, remain := rawdb.IsAccountTrieNode(key) + ok, remain := rawdb.ResolveAccountTrieNodeKey(key) if !ok { return false, nil, common.Hash{} } diff --git a/trie/proof.go b/trie/proof.go index a25263d82e75..a463c80b4889 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -33,7 +33,11 @@ import ( // If the trie does not contain a value for key, the returned proof contains all // nodes of the longest existing prefix of the key (at least the root node), ending // with the node that proves the absence of the key. -func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error { +func (t *Trie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return ErrCommitted + } // Collect all nodes on the path to key. var ( prefix []byte @@ -81,10 +85,6 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e defer returnHasherToPool(hasher) for i, n := range nodes { - if fromLevel > 0 { - fromLevel-- - continue - } var hn node n, hn = hasher.proofHash(n) if hash, ok := hn.(hashNode); ok || i == 0 { @@ -107,8 +107,8 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e // If the trie does not contain a value for key, the returned proof contains all // nodes of the longest existing prefix of the key (at least the root node), ending // with the node that proves the absence of the key. -func (t *StateTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error { - return t.trie.Prove(key, fromLevel, proofDb) +func (t *StateTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { + return t.trie.Prove(key, proofDb) } // VerifyProof checks merkle proofs. The given proof must contain the value for diff --git a/trie/proof_test.go b/trie/proof_test.go index 69e3f8e9ceea..fc2de626496e 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -22,13 +22,13 @@ import ( "encoding/binary" "fmt" mrand "math/rand" - "sort" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb/memorydb" + "golang.org/x/exp/slices" ) // Prng is a pseudo random number generator seeded by strong randomness. @@ -57,13 +57,13 @@ func makeProvers(trie *Trie) []func(key []byte) *memorydb.Database { // Create a direct trie based Merkle prover provers = append(provers, func(key []byte) *memorydb.Database { proof := memorydb.New() - trie.Prove(key, 0, proof) + trie.Prove(key, proof) return proof }) // Create a leaf iterator based Merkle prover provers = append(provers, func(key []byte) *memorydb.Database { proof := memorydb.New() - if it := NewIterator(trie.NodeIterator(key)); it.Next() && bytes.Equal(key, it.Key) { + if it := NewIterator(trie.MustNodeIterator(key)); it.Next() && bytes.Equal(key, it.Key) { for _, p := range it.Prove() { proof.Put(crypto.Keccak256(p), p) } @@ -94,7 +94,7 @@ func TestProof(t *testing.T) { } func TestOneElementProof(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) updateString(trie, "k", "v") for i, prover := range makeProvers(trie) { proof := prover([]byte("k")) @@ -145,12 +145,12 @@ func TestBadProof(t *testing.T) { // Tests that missing keys can also be proven. The test explicitly uses a single // entry trie and checks for missing keys both before and after the single entry. func TestMissingKeyProof(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) updateString(trie, "k", "v") for i, key := range []string{"a", "j", "l", "z"} { proof := memorydb.New() - trie.Prove([]byte(key), 0, proof) + trie.Prove([]byte(key), proof) if proof.Len() != 1 { t.Errorf("test %d: proof should have one element", i) @@ -165,30 +165,24 @@ func TestMissingKeyProof(t *testing.T) { } } -type entrySlice []*kv - -func (p entrySlice) Len() int { return len(p) } -func (p entrySlice) Less(i, j int) bool { return bytes.Compare(p[i].k, p[j].k) < 0 } -func (p entrySlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - // TestRangeProof tests normal range proof with both edge proofs // as the existent proof. The test cases are generated randomly. func TestRangeProof(t *testing.T) { trie, vals := randomTrie(4096) - var entries entrySlice + var entries []*kv for _, kv := range vals { entries = append(entries, kv) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) for i := 0; i < 500; i++ { start := mrand.Intn(len(entries)) end := mrand.Intn(len(entries)-start) + start + 1 proof := memorydb.New() - if err := trie.Prove(entries[start].k, 0, proof); err != nil { + if err := trie.Prove(entries[start].k, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { + if err := trie.Prove(entries[end-1].k, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } var keys [][]byte @@ -208,11 +202,11 @@ func TestRangeProof(t *testing.T) { // The test cases are generated randomly. func TestRangeProofWithNonExistentProof(t *testing.T) { trie, vals := randomTrie(4096) - var entries entrySlice + var entries []*kv for _, kv := range vals { entries = append(entries, kv) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) for i := 0; i < 500; i++ { start := mrand.Intn(len(entries)) end := mrand.Intn(len(entries)-start) + start + 1 @@ -236,10 +230,10 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { if bytes.Compare(last, entries[end-1].k) < 0 { continue } - if err := trie.Prove(first, 0, proof); err != nil { + if err := trie.Prove(first, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(last, 0, proof); err != nil { + if err := trie.Prove(last, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } var keys [][]byte @@ -257,10 +251,10 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { proof := memorydb.New() first := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes() last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes() - if err := trie.Prove(first, 0, proof); err != nil { + if err := trie.Prove(first, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(last, 0, proof); err != nil { + if err := trie.Prove(last, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } var k [][]byte @@ -280,21 +274,21 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { // - There exists a gap between the last element and the right edge proof func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { trie, vals := randomTrie(4096) - var entries entrySlice + var entries []*kv for _, kv := range vals { entries = append(entries, kv) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) // Case 1 start, end := 100, 200 first := decreaseKey(common.CopyBytes(entries[start].k)) proof := memorydb.New() - if err := trie.Prove(first, 0, proof); err != nil { + if err := trie.Prove(first, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { + if err := trie.Prove(entries[end-1].k, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } start = 105 // Gap created @@ -313,10 +307,10 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { start, end = 100, 200 last := increaseKey(common.CopyBytes(entries[end-1].k)) proof = memorydb.New() - if err := trie.Prove(entries[start].k, 0, proof); err != nil { + if err := trie.Prove(entries[start].k, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(last, 0, proof); err != nil { + if err := trie.Prove(last, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } end = 195 // Capped slice @@ -337,17 +331,17 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { // non-existent one. func TestOneElementRangeProof(t *testing.T) { trie, vals := randomTrie(4096) - var entries entrySlice + var entries []*kv for _, kv := range vals { entries = append(entries, kv) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) // One element with existent edge proof, both edge proofs // point to the SAME key. start := 1000 proof := memorydb.New() - if err := trie.Prove(entries[start].k, 0, proof); err != nil { + if err := trie.Prove(entries[start].k, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } _, err := VerifyRangeProof(trie.Hash(), entries[start].k, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) @@ -359,10 +353,10 @@ func TestOneElementRangeProof(t *testing.T) { start = 1000 first := decreaseKey(common.CopyBytes(entries[start].k)) proof = memorydb.New() - if err := trie.Prove(first, 0, proof); err != nil { + if err := trie.Prove(first, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[start].k, 0, proof); err != nil { + if err := trie.Prove(entries[start].k, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } _, err = VerifyRangeProof(trie.Hash(), first, entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) @@ -374,10 +368,10 @@ func TestOneElementRangeProof(t *testing.T) { start = 1000 last := increaseKey(common.CopyBytes(entries[start].k)) proof = memorydb.New() - if err := trie.Prove(entries[start].k, 0, proof); err != nil { + if err := trie.Prove(entries[start].k, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(last, 0, proof); err != nil { + if err := trie.Prove(last, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } _, err = VerifyRangeProof(trie.Hash(), entries[start].k, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) @@ -389,10 +383,10 @@ func TestOneElementRangeProof(t *testing.T) { start = 1000 first, last = decreaseKey(common.CopyBytes(entries[start].k)), increaseKey(common.CopyBytes(entries[start].k)) proof = memorydb.New() - if err := trie.Prove(first, 0, proof); err != nil { + if err := trie.Prove(first, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(last, 0, proof); err != nil { + if err := trie.Prove(last, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) @@ -401,17 +395,17 @@ func TestOneElementRangeProof(t *testing.T) { } // Test the mini trie with only a single element. - tinyTrie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + tinyTrie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) entry := &kv{randBytes(32), randBytes(20), false} tinyTrie.MustUpdate(entry.k, entry.v) first = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes() last = entry.k proof = memorydb.New() - if err := tinyTrie.Prove(first, 0, proof); err != nil { + if err := tinyTrie.Prove(first, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := tinyTrie.Prove(last, 0, proof); err != nil { + if err := tinyTrie.Prove(last, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } _, err = VerifyRangeProof(tinyTrie.Hash(), first, last, [][]byte{entry.k}, [][]byte{entry.v}, proof) @@ -424,11 +418,11 @@ func TestOneElementRangeProof(t *testing.T) { // The edge proofs can be nil. func TestAllElementsProof(t *testing.T) { trie, vals := randomTrie(4096) - var entries entrySlice + var entries []*kv for _, kv := range vals { entries = append(entries, kv) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) var k [][]byte var v [][]byte @@ -443,10 +437,10 @@ func TestAllElementsProof(t *testing.T) { // With edge proofs, it should still work. proof := memorydb.New() - if err := trie.Prove(entries[0].k, 0, proof); err != nil { + if err := trie.Prove(entries[0].k, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[len(entries)-1].k, 0, proof); err != nil { + if err := trie.Prove(entries[len(entries)-1].k, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } _, err = VerifyRangeProof(trie.Hash(), k[0], k[len(k)-1], k, v, proof) @@ -458,10 +452,10 @@ func TestAllElementsProof(t *testing.T) { proof = memorydb.New() first := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes() last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes() - if err := trie.Prove(first, 0, proof); err != nil { + if err := trie.Prove(first, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(last, 0, proof); err != nil { + if err := trie.Prove(last, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } _, err = VerifyRangeProof(trie.Hash(), first, last, k, v, proof) @@ -473,22 +467,22 @@ func TestAllElementsProof(t *testing.T) { // TestSingleSideRangeProof tests the range starts from zero. func TestSingleSideRangeProof(t *testing.T) { for i := 0; i < 64; i++ { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) - var entries entrySlice + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) + var entries []*kv for i := 0; i < 4096; i++ { value := &kv{randBytes(32), randBytes(20), false} trie.MustUpdate(value.k, value.v) entries = append(entries, value) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) var cases = []int{0, 1, 50, 100, 1000, 2000, len(entries) - 1} for _, pos := range cases { proof := memorydb.New() - if err := trie.Prove(common.Hash{}.Bytes(), 0, proof); err != nil { + if err := trie.Prove(common.Hash{}.Bytes(), proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[pos].k, 0, proof); err != nil { + if err := trie.Prove(entries[pos].k, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } k := make([][]byte, 0) @@ -508,23 +502,23 @@ func TestSingleSideRangeProof(t *testing.T) { // TestReverseSingleSideRangeProof tests the range ends with 0xffff...fff. func TestReverseSingleSideRangeProof(t *testing.T) { for i := 0; i < 64; i++ { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) - var entries entrySlice + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) + var entries []*kv for i := 0; i < 4096; i++ { value := &kv{randBytes(32), randBytes(20), false} trie.MustUpdate(value.k, value.v) entries = append(entries, value) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) var cases = []int{0, 1, 50, 100, 1000, 2000, len(entries) - 1} for _, pos := range cases { proof := memorydb.New() - if err := trie.Prove(entries[pos].k, 0, proof); err != nil { + if err := trie.Prove(entries[pos].k, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - if err := trie.Prove(last.Bytes(), 0, proof); err != nil { + if err := trie.Prove(last.Bytes(), proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } k := make([][]byte, 0) @@ -545,20 +539,20 @@ func TestReverseSingleSideRangeProof(t *testing.T) { // The prover is expected to detect the error. func TestBadRangeProof(t *testing.T) { trie, vals := randomTrie(4096) - var entries entrySlice + var entries []*kv for _, kv := range vals { entries = append(entries, kv) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) for i := 0; i < 500; i++ { start := mrand.Intn(len(entries)) end := mrand.Intn(len(entries)-start) + start + 1 proof := memorydb.New() - if err := trie.Prove(entries[start].k, 0, proof); err != nil { + if err := trie.Prove(entries[start].k, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { + if err := trie.Prove(entries[end-1].k, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } var keys [][]byte @@ -615,7 +609,7 @@ func TestBadRangeProof(t *testing.T) { // TestGappedRangeProof focuses on the small trie with embedded nodes. // If the gapped node is embedded in the trie, it should be detected too. func TestGappedRangeProof(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) var entries []*kv // Sorted entries for i := byte(0); i < 10; i++ { value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} @@ -624,10 +618,10 @@ func TestGappedRangeProof(t *testing.T) { } first, last := 2, 8 proof := memorydb.New() - if err := trie.Prove(entries[first].k, 0, proof); err != nil { + if err := trie.Prove(entries[first].k, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[last-1].k, 0, proof); err != nil { + if err := trie.Prove(entries[last-1].k, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } var keys [][]byte @@ -648,11 +642,11 @@ func TestGappedRangeProof(t *testing.T) { // TestSameSideProofs tests the element is not in the range covered by proofs func TestSameSideProofs(t *testing.T) { trie, vals := randomTrie(4096) - var entries entrySlice + var entries []*kv for _, kv := range vals { entries = append(entries, kv) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) pos := 1000 first := decreaseKey(common.CopyBytes(entries[pos].k)) @@ -660,10 +654,10 @@ func TestSameSideProofs(t *testing.T) { last := decreaseKey(common.CopyBytes(entries[pos].k)) proof := memorydb.New() - if err := trie.Prove(first, 0, proof); err != nil { + if err := trie.Prove(first, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(last, 0, proof); err != nil { + if err := trie.Prove(last, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } _, err := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) @@ -676,10 +670,10 @@ func TestSameSideProofs(t *testing.T) { last = increaseKey(last) proof = memorydb.New() - if err := trie.Prove(first, 0, proof); err != nil { + if err := trie.Prove(first, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(last, 0, proof); err != nil { + if err := trie.Prove(last, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } _, err = VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) @@ -689,14 +683,14 @@ func TestSameSideProofs(t *testing.T) { } func TestHasRightElement(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) - var entries entrySlice + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) + var entries []*kv for i := 0; i < 4096; i++ { value := &kv{randBytes(32), randBytes(20), false} trie.MustUpdate(value.k, value.v) entries = append(entries, value) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) var cases = []struct { start int @@ -724,23 +718,23 @@ func TestHasRightElement(t *testing.T) { ) if c.start == -1 { firstKey, start = common.Hash{}.Bytes(), 0 - if err := trie.Prove(firstKey, 0, proof); err != nil { + if err := trie.Prove(firstKey, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } } else { firstKey = entries[c.start].k - if err := trie.Prove(entries[c.start].k, 0, proof); err != nil { + if err := trie.Prove(entries[c.start].k, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } } if c.end == -1 { lastKey, end = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes(), len(entries) - if err := trie.Prove(lastKey, 0, proof); err != nil { + if err := trie.Prove(lastKey, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } } else { lastKey = entries[c.end-1].k - if err := trie.Prove(entries[c.end-1].k, 0, proof); err != nil { + if err := trie.Prove(entries[c.end-1].k, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } } @@ -764,11 +758,11 @@ func TestHasRightElement(t *testing.T) { // The first edge proof must be a non-existent proof. func TestEmptyRangeProof(t *testing.T) { trie, vals := randomTrie(4096) - var entries entrySlice + var entries []*kv for _, kv := range vals { entries = append(entries, kv) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) var cases = []struct { pos int @@ -780,7 +774,7 @@ func TestEmptyRangeProof(t *testing.T) { for _, c := range cases { proof := memorydb.New() first := increaseKey(common.CopyBytes(entries[c.pos].k)) - if err := trie.Prove(first, 0, proof); err != nil { + if err := trie.Prove(first, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } _, err := VerifyRangeProof(trie.Hash(), first, nil, nil, nil, proof) @@ -799,11 +793,11 @@ func TestEmptyRangeProof(t *testing.T) { func TestBloatedProof(t *testing.T) { // Use a small trie trie, kvs := nonRandomTrie(100) - var entries entrySlice + var entries []*kv for _, kv := range kvs { entries = append(entries, kv) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) var keys [][]byte var vals [][]byte @@ -811,7 +805,7 @@ func TestBloatedProof(t *testing.T) { // In the 'malicious' case, we add proofs for every single item // (but only one key/value pair used as leaf) for i, entry := range entries { - trie.Prove(entry.k, 0, proof) + trie.Prove(entry.k, proof) if i == 50 { keys = append(keys, entry.k) vals = append(vals, entry.v) @@ -820,8 +814,8 @@ func TestBloatedProof(t *testing.T) { // For reference, we use the same function, but _only_ prove the first // and last element want := memorydb.New() - trie.Prove(keys[0], 0, want) - trie.Prove(keys[len(keys)-1], 0, want) + trie.Prove(keys[0], want) + trie.Prove(keys[len(keys)-1], want) if _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof); err != nil { t.Fatalf("expected bloated proof to succeed, got %v", err) @@ -833,11 +827,11 @@ func TestBloatedProof(t *testing.T) { // noop technically, but practically should be rejected. func TestEmptyValueRangeProof(t *testing.T) { trie, values := randomTrie(512) - var entries entrySlice + var entries []*kv for _, kv := range values { entries = append(entries, kv) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) // Create a new entry with a slightly modified key mid := len(entries) / 2 @@ -854,10 +848,10 @@ func TestEmptyValueRangeProof(t *testing.T) { start, end := 1, len(entries)-1 proof := memorydb.New() - if err := trie.Prove(entries[start].k, 0, proof); err != nil { + if err := trie.Prove(entries[start].k, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { + if err := trie.Prove(entries[end-1].k, proof); err != nil { t.Fatalf("Failed to prove the last node %v", err) } var keys [][]byte @@ -877,11 +871,11 @@ func TestEmptyValueRangeProof(t *testing.T) { // practically should be rejected. func TestAllElementsEmptyValueRangeProof(t *testing.T) { trie, values := randomTrie(512) - var entries entrySlice + var entries []*kv for _, kv := range values { entries = append(entries, kv) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) // Create a new entry with a slightly modified key mid := len(entries) / 2 @@ -949,7 +943,7 @@ func BenchmarkProve(b *testing.B) { for i := 0; i < b.N; i++ { kv := vals[keys[i%len(keys)]] proofs := memorydb.New() - if trie.Prove(kv.k, 0, proofs); proofs.Len() == 0 { + if trie.Prove(kv.k, proofs); proofs.Len() == 0 { b.Fatalf("zero length proof for %x", kv.k) } } @@ -963,7 +957,7 @@ func BenchmarkVerifyProof(b *testing.B) { for k := range vals { keys = append(keys, k) proof := memorydb.New() - trie.Prove([]byte(k), 0, proof) + trie.Prove([]byte(k), proof) proofs = append(proofs, proof) } @@ -983,19 +977,19 @@ func BenchmarkVerifyRangeProof5000(b *testing.B) { benchmarkVerifyRangeProof(b, func benchmarkVerifyRangeProof(b *testing.B, size int) { trie, vals := randomTrie(8192) - var entries entrySlice + var entries []*kv for _, kv := range vals { entries = append(entries, kv) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) start := 2 end := start + size proof := memorydb.New() - if err := trie.Prove(entries[start].k, 0, proof); err != nil { + if err := trie.Prove(entries[start].k, proof); err != nil { b.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(entries[end-1].k, 0, proof); err != nil { + if err := trie.Prove(entries[end-1].k, proof); err != nil { b.Fatalf("Failed to prove the last node %v", err) } var keys [][]byte @@ -1020,11 +1014,11 @@ func BenchmarkVerifyRangeNoProof1000(b *testing.B) { benchmarkVerifyRangeNoProof func benchmarkVerifyRangeNoProof(b *testing.B, size int) { trie, vals := randomTrie(size) - var entries entrySlice + var entries []*kv for _, kv := range vals { entries = append(entries, kv) } - sort.Sort(entries) + slices.SortFunc(entries, (*kv).cmp) var keys [][]byte var values [][]byte @@ -1042,7 +1036,7 @@ func benchmarkVerifyRangeNoProof(b *testing.B, size int) { } func randomTrie(n int) (*Trie, map[string]*kv) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) vals := make(map[string]*kv) for i := byte(0); i < 100; i++ { value := &kv{common.LeftPadBytes([]byte{i}, 32), []byte{i}, false} @@ -1061,7 +1055,7 @@ func randomTrie(n int) (*Trie, map[string]*kv) { } func nonRandomTrie(n int) (*Trie, map[string]*kv) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) vals := make(map[string]*kv) max := uint64(0xffffffffffffffff) for i := uint64(0); i < uint64(n); i++ { @@ -1086,7 +1080,7 @@ func TestRangeProofKeysWithSharedPrefix(t *testing.T) { common.Hex2Bytes("02"), common.Hex2Bytes("03"), } - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) for i, key := range keys { trie.MustUpdate(key, vals[i]) } @@ -1094,10 +1088,10 @@ func TestRangeProofKeysWithSharedPrefix(t *testing.T) { proof := memorydb.New() start := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000") end := common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - if err := trie.Prove(start, 0, proof); err != nil { + if err := trie.Prove(start, proof); err != nil { t.Fatalf("failed to prove start: %v", err) } - if err := trie.Prove(end, 0, proof); err != nil { + if err := trie.Prove(end, proof); err != nil { t.Fatalf("failed to prove end: %v", err) } diff --git a/trie/secure_trie.go b/trie/secure_trie.go index b3cdeadb6b67..7f0685e30666 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -86,7 +86,12 @@ func (t *StateTrie) MustGet(key []byte) []byte { // If the specified storage slot is not in the trie, nil will be returned. // If a trie node is not found in the database, a MissingNodeError is returned. func (t *StateTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) { - return t.trie.Get(t.hashKey(key)) + enc, err := t.trie.Get(t.hashKey(key)) + if err != nil || len(enc) == 0 { + return nil, err + } + _, content, _, err := rlp.Split(enc) + return content, err } // GetAccount attempts to retrieve an account with provided account address. @@ -148,7 +153,8 @@ func (t *StateTrie) MustUpdate(key, value []byte) { // If a node is not found in the database, a MissingNodeError is returned. func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error { hk := t.hashKey(key) - err := t.trie.Update(hk, value) + v, _ := rlp.EncodeToBytes(value) + err := t.trie.Update(hk, v) if err != nil { return err } @@ -170,6 +176,10 @@ func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccoun return nil } +func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error { + return nil +} + // MustDelete removes any existing value for key from the trie. This function // will omit any encountered error but just print out an error message. func (t *StateTrie) MustDelete(key []byte) { @@ -213,7 +223,7 @@ func (t *StateTrie) GetKey(shaKey []byte) []byte { // All cached preimages will be also flushed if preimages recording is enabled. // Once the trie is committed, it's not usable anymore. A new trie must // be created with new root and updated trie database for following usage -func (t *StateTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) { +func (t *StateTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) { // Write all the pre-images to the actual disk database if len(t.getSecKeyCache()) > 0 { if t.preimages != nil { @@ -244,12 +254,18 @@ func (t *StateTrie) Copy() *StateTrie { } } -// NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration -// starts at the key after the given start key. -func (t *StateTrie) NodeIterator(start []byte) NodeIterator { +// NodeIterator returns an iterator that returns nodes of the underlying trie. +// Iteration starts at the key after the given start key. +func (t *StateTrie) NodeIterator(start []byte) (NodeIterator, error) { return t.trie.NodeIterator(start) } +// MustNodeIterator is a wrapper of NodeIterator and will omit any encountered +// error but just print out an error message. +func (t *StateTrie) MustNodeIterator(start []byte) NodeIterator { + return t.trie.MustNodeIterator(start) +} + // hashKey returns the hash of key as an ephemeral buffer. // The caller must not hold onto the return value because it will become // invalid on the next call to hashKey or secKey. diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index e4f6bca97dd7..2087866d3855 100644 --- a/trie/secure_trie_test.go +++ b/trie/secure_trie_test.go @@ -31,14 +31,14 @@ import ( ) func newEmptySecure() *StateTrie { - trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), NewDatabase(rawdb.NewMemoryDatabase())) + trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), NewDatabase(rawdb.NewMemoryDatabase(), nil)) return trie } // makeTestStateTrie creates a large enough secure trie for testing. func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) { // Create an empty trie - triedb := NewDatabase(rawdb.NewMemoryDatabase()) + triedb := NewDatabase(rawdb.NewMemoryDatabase(), nil) trie, _ := NewStateTrie(TrieID(types.EmptyRootHash), triedb) // Fill it with some arbitrary data @@ -60,8 +60,8 @@ func makeTestStateTrie() (*Database, *StateTrie, map[string][]byte) { trie.MustUpdate(key, val) } } - root, nodes := trie.Commit(false) - if err := triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)); err != nil { + root, nodes, _ := trie.Commit(false) + if err := triedb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil); err != nil { panic(fmt.Errorf("failed to commit db %v", err)) } // Re-create the trie based on the new state diff --git a/trie/stacktrie.go b/trie/stacktrie.go index ee1ce282918d..35208e1cb345 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -17,11 +17,7 @@ package trie import ( - "bufio" - "bytes" - "encoding/gob" "errors" - "io" "sync" "github.com/ethereum/go-ethereum/common" @@ -29,171 +25,87 @@ import ( "github.com/ethereum/go-ethereum/log" ) -var ErrCommitDisabled = errors.New("no database for committing") - -var stPool = sync.Pool{ - New: func() interface{} { - return NewStackTrie(nil) - }, -} +var ( + ErrCommitDisabled = errors.New("no database for committing") + stPool = sync.Pool{New: func() any { return new(stNode) }} + _ = types.TrieHasher((*StackTrie)(nil)) +) // NodeWriteFunc is used to provide all information of a dirty node for committing // so that callers can flush nodes into database with desired scheme. -type NodeWriteFunc = func(owner common.Hash, path []byte, hash common.Hash, blob []byte) - -func stackTrieFromPool(writeFn NodeWriteFunc, owner common.Hash) *StackTrie { - st := stPool.Get().(*StackTrie) - st.owner = owner - st.writeFn = writeFn - return st -} - -func returnToPool(st *StackTrie) { - st.Reset() - stPool.Put(st) -} +type NodeWriteFunc = func(path []byte, hash common.Hash, blob []byte) // StackTrie is a trie implementation that expects keys to be inserted // in order. Once it determines that a subtree will no longer be inserted // into, it will hash it and free up the memory it uses. type StackTrie struct { - owner common.Hash // the owner of the trie - nodeType uint8 // node type (as in branch, ext, leaf) - val []byte // value contained by this node if it's a leaf - key []byte // key chunk covered by this (leaf|ext) node - children [16]*StackTrie // list of children (for branch and exts) - writeFn NodeWriteFunc // function for committing nodes, can be nil + writeFn NodeWriteFunc // function for committing nodes, can be nil + root *stNode + h *hasher } // NewStackTrie allocates and initializes an empty trie. func NewStackTrie(writeFn NodeWriteFunc) *StackTrie { return &StackTrie{ - nodeType: emptyNode, - writeFn: writeFn, + writeFn: writeFn, + root: stPool.Get().(*stNode), + h: newHasher(false), } } -// NewStackTrieWithOwner allocates and initializes an empty trie, but with -// the additional owner field. -func NewStackTrieWithOwner(writeFn NodeWriteFunc, owner common.Hash) *StackTrie { - return &StackTrie{ - owner: owner, - nodeType: emptyNode, - writeFn: writeFn, +// Update inserts a (key, value) pair into the stack trie. +func (t *StackTrie) Update(key, value []byte) error { + k := keybytesToHex(key) + if len(value) == 0 { + panic("deletion not supported") } + t.insert(t.root, k[:len(k)-1], value, nil) + return nil } -// NewFromBinary initialises a serialized stacktrie with the given db. -func NewFromBinary(data []byte, writeFn NodeWriteFunc) (*StackTrie, error) { - var st StackTrie - if err := st.UnmarshalBinary(data); err != nil { - return nil, err - } - // If a database is used, we need to recursively add it to every child - if writeFn != nil { - st.setWriter(writeFn) +// MustUpdate is a wrapper of Update and will omit any encountered error but +// just print out an error message. +func (t *StackTrie) MustUpdate(key, value []byte) { + if err := t.Update(key, value); err != nil { + log.Error("Unhandled trie error in StackTrie.Update", "err", err) } - return &st, nil } -// MarshalBinary implements encoding.BinaryMarshaler -func (st *StackTrie) MarshalBinary() (data []byte, err error) { - var ( - b bytes.Buffer - w = bufio.NewWriter(&b) - ) - if err := gob.NewEncoder(w).Encode(struct { - Owner common.Hash - NodeType uint8 - Val []byte - Key []byte - }{ - st.owner, - st.nodeType, - st.val, - st.key, - }); err != nil { - return nil, err - } - for _, child := range st.children { - if child == nil { - w.WriteByte(0) - continue - } - w.WriteByte(1) - if childData, err := child.MarshalBinary(); err != nil { - return nil, err - } else { - w.Write(childData) - } - } - w.Flush() - return b.Bytes(), nil +func (t *StackTrie) Reset() { + t.writeFn = nil + t.root = stPool.Get().(*stNode) } -// UnmarshalBinary implements encoding.BinaryUnmarshaler -func (st *StackTrie) UnmarshalBinary(data []byte) error { - r := bytes.NewReader(data) - return st.unmarshalBinary(r) +// stNode represents a node within a StackTrie +type stNode struct { + typ uint8 // node type (as in branch, ext, leaf) + key []byte // key chunk covered by this (leaf|ext) node + val []byte // value contained by this node if it's a leaf + children [16]*stNode // list of children (for branch and exts) } -func (st *StackTrie) unmarshalBinary(r io.Reader) error { - var dec struct { - Owner common.Hash - NodeType uint8 - Val []byte - Key []byte - } - if err := gob.NewDecoder(r).Decode(&dec); err != nil { - return err - } - st.owner = dec.Owner - st.nodeType = dec.NodeType - st.val = dec.Val - st.key = dec.Key - - var hasChild = make([]byte, 1) - for i := range st.children { - if _, err := r.Read(hasChild); err != nil { - return err - } else if hasChild[0] == 0 { - continue - } - var child StackTrie - if err := child.unmarshalBinary(r); err != nil { - return err - } - st.children[i] = &child - } - return nil -} - -func (st *StackTrie) setWriter(writeFn NodeWriteFunc) { - st.writeFn = writeFn - for _, child := range st.children { - if child != nil { - child.setWriter(writeFn) - } - } -} - -func newLeaf(owner common.Hash, key, val []byte, writeFn NodeWriteFunc) *StackTrie { - st := stackTrieFromPool(writeFn, owner) - st.nodeType = leafNode +// newLeaf constructs a leaf node with provided node key and value. The key +// will be deep-copied in the function and safe to modify afterwards, but +// value is not. +func newLeaf(key, val []byte) *stNode { + st := stPool.Get().(*stNode) + st.typ = leafNode st.key = append(st.key, key...) st.val = val return st } -func newExt(owner common.Hash, key []byte, child *StackTrie, writeFn NodeWriteFunc) *StackTrie { - st := stackTrieFromPool(writeFn, owner) - st.nodeType = extNode +// newExt constructs an extension node with provided node key and child. The +// key will be deep-copied in the function and safe to modify afterwards. +func newExt(key []byte, child *stNode) *stNode { + st := stPool.Get().(*stNode) + st.typ = extNode st.key = append(st.key, key...) st.children[0] = child return st } -// List all values that StackTrie#nodeType can hold +// List all values that stNode#nodeType can hold const ( emptyNode = iota branchNode @@ -202,59 +114,40 @@ const ( hashedNode ) -// Update inserts a (key, value) pair into the stack trie. -func (st *StackTrie) Update(key, value []byte) error { - k := keybytesToHex(key) - if len(value) == 0 { - panic("deletion not supported") +func (n *stNode) reset() *stNode { + n.key = n.key[:0] + n.val = nil + for i := range n.children { + n.children[i] = nil } - st.insert(k[:len(k)-1], value, nil) - return nil -} - -// MustUpdate is a wrapper of Update and will omit any encountered error but -// just print out an error message. -func (st *StackTrie) MustUpdate(key, value []byte) { - if err := st.Update(key, value); err != nil { - log.Error("Unhandled trie error in StackTrie.Update", "err", err) - } -} - -func (st *StackTrie) Reset() { - st.owner = common.Hash{} - st.writeFn = nil - st.key = st.key[:0] - st.val = nil - for i := range st.children { - st.children[i] = nil - } - st.nodeType = emptyNode + n.typ = emptyNode + return n } // Helper function that, given a full key, determines the index // at which the chunk pointed by st.keyOffset is different from // the same chunk in the full key. -func (st *StackTrie) getDiffIndex(key []byte) int { - for idx, nibble := range st.key { +func (n *stNode) getDiffIndex(key []byte) int { + for idx, nibble := range n.key { if nibble != key[idx] { return idx } } - return len(st.key) + return len(n.key) } // Helper function to that inserts a (key, value) pair into // the trie. -func (st *StackTrie) insert(key, value []byte, prefix []byte) { - switch st.nodeType { +func (t *StackTrie) insert(st *stNode, key, value []byte, prefix []byte) { + switch st.typ { case branchNode: /* Branch */ idx := int(key[0]) // Unresolve elder siblings for i := idx - 1; i >= 0; i-- { if st.children[i] != nil { - if st.children[i].nodeType != hashedNode { - st.children[i].hash(append(prefix, byte(i))) + if st.children[i].typ != hashedNode { + t.hash(st.children[i], append(prefix, byte(i))) } break } @@ -262,9 +155,9 @@ func (st *StackTrie) insert(key, value []byte, prefix []byte) { // Add new child if st.children[idx] == nil { - st.children[idx] = newLeaf(st.owner, key[1:], value, st.writeFn) + st.children[idx] = newLeaf(key[1:], value) } else { - st.children[idx].insert(key[1:], value, append(prefix, key[0])) + t.insert(st.children[idx], key[1:], value, append(prefix, key[0])) } case extNode: /* Ext */ @@ -279,46 +172,46 @@ func (st *StackTrie) insert(key, value []byte, prefix []byte) { if diffidx == len(st.key) { // Ext key and key segment are identical, recurse into // the child node. - st.children[0].insert(key[diffidx:], value, append(prefix, key[:diffidx]...)) + t.insert(st.children[0], key[diffidx:], value, append(prefix, key[:diffidx]...)) return } // Save the original part. Depending if the break is // at the extension's last byte or not, create an // intermediate extension or use the extension's child // node directly. - var n *StackTrie + var n *stNode if diffidx < len(st.key)-1 { // Break on the non-last byte, insert an intermediate // extension. The path prefix of the newly-inserted // extension should also contain the different byte. - n = newExt(st.owner, st.key[diffidx+1:], st.children[0], st.writeFn) - n.hash(append(prefix, st.key[:diffidx+1]...)) + n = newExt(st.key[diffidx+1:], st.children[0]) + t.hash(n, append(prefix, st.key[:diffidx+1]...)) } else { // Break on the last byte, no need to insert // an extension node: reuse the current node. // The path prefix of the original part should // still be same. n = st.children[0] - n.hash(append(prefix, st.key...)) + t.hash(n, append(prefix, st.key...)) } - var p *StackTrie + var p *stNode if diffidx == 0 { // the break is on the first byte, so // the current node is converted into // a branch node. st.children[0] = nil p = st - st.nodeType = branchNode + st.typ = branchNode } else { // the common prefix is at least one byte // long, insert a new intermediate branch // node. - st.children[0] = stackTrieFromPool(st.writeFn, st.owner) - st.children[0].nodeType = branchNode + st.children[0] = stPool.Get().(*stNode) + st.children[0].typ = branchNode p = st.children[0] } // Create a leaf for the inserted part - o := newLeaf(st.owner, key[diffidx+1:], value, st.writeFn) + o := newLeaf(key[diffidx+1:], value) // Insert both child leaves where they belong: origIdx := st.key[diffidx] @@ -344,18 +237,18 @@ func (st *StackTrie) insert(key, value []byte, prefix []byte) { // Check if the split occurs at the first nibble of the // chunk. In that case, no prefix extnode is necessary. // Otherwise, create that - var p *StackTrie + var p *stNode if diffidx == 0 { // Convert current leaf into a branch - st.nodeType = branchNode + st.typ = branchNode p = st st.children[0] = nil } else { // Convert current node into an ext, // and insert a child branch node. - st.nodeType = extNode - st.children[0] = NewStackTrieWithOwner(st.writeFn, st.owner) - st.children[0].nodeType = branchNode + st.typ = extNode + st.children[0] = stPool.Get().(*stNode) + st.children[0].typ = branchNode p = st.children[0] } @@ -363,11 +256,11 @@ func (st *StackTrie) insert(key, value []byte, prefix []byte) { // value and another containing the new value. The child leaf // is hashed directly in order to free up some memory. origIdx := st.key[diffidx] - p.children[origIdx] = newLeaf(st.owner, st.key[diffidx+1:], st.val, st.writeFn) - p.children[origIdx].hash(append(prefix, st.key[:diffidx+1]...)) + p.children[origIdx] = newLeaf(st.key[diffidx+1:], st.val) + t.hash(p.children[origIdx], append(prefix, st.key[:diffidx+1]...)) newIdx := key[diffidx] - p.children[newIdx] = newLeaf(st.owner, key[diffidx+1:], value, st.writeFn) + p.children[newIdx] = newLeaf(key[diffidx+1:], value) // Finally, cut off the key part that has been passed // over to the children. @@ -375,7 +268,7 @@ func (st *StackTrie) insert(key, value []byte, prefix []byte) { st.val = nil case emptyNode: /* Empty */ - st.nodeType = leafNode + st.typ = leafNode st.key = key st.val = value @@ -398,25 +291,18 @@ func (st *StackTrie) insert(key, value []byte, prefix []byte) { // - And the 'st.type' will be 'hashedNode' AGAIN // // This method also sets 'st.type' to hashedNode, and clears 'st.key'. -func (st *StackTrie) hash(path []byte) { - h := newHasher(false) - defer returnHasherToPool(h) - - st.hashRec(h, path) -} - -func (st *StackTrie) hashRec(hasher *hasher, path []byte) { +func (t *StackTrie) hash(st *stNode, path []byte) { // The switch below sets this to the RLP-encoding of this node. var encodedNode []byte - switch st.nodeType { + switch st.typ { case hashedNode: return case emptyNode: st.val = types.EmptyRootHash.Bytes() st.key = st.key[:0] - st.nodeType = hashedNode + st.typ = hashedNode return case branchNode: @@ -426,50 +312,46 @@ func (st *StackTrie) hashRec(hasher *hasher, path []byte) { nodes.Children[i] = nilValueNode continue } - child.hashRec(hasher, append(path, byte(i))) + t.hash(child, append(path, byte(i))) + if len(child.val) < 32 { nodes.Children[i] = rawNode(child.val) } else { nodes.Children[i] = hashNode(child.val) } - - // Release child back to pool. st.children[i] = nil - returnToPool(child) + stPool.Put(child.reset()) // Release child back to pool. } - - nodes.encode(hasher.encbuf) - encodedNode = hasher.encodedBytes() + nodes.encode(t.h.encbuf) + encodedNode = t.h.encodedBytes() case extNode: - st.children[0].hashRec(hasher, append(path, st.key...)) + t.hash(st.children[0], append(path, st.key...)) - n := shortNode{Key: hexToCompact(st.key)} + n := shortNode{Key: hexToCompactInPlace(st.key)} if len(st.children[0].val) < 32 { n.Val = rawNode(st.children[0].val) } else { n.Val = hashNode(st.children[0].val) } + n.encode(t.h.encbuf) + encodedNode = t.h.encodedBytes() - n.encode(hasher.encbuf) - encodedNode = hasher.encodedBytes() - - // Release child back to pool. - returnToPool(st.children[0]) + stPool.Put(st.children[0].reset()) // Release child back to pool. st.children[0] = nil case leafNode: st.key = append(st.key, byte(16)) - n := shortNode{Key: hexToCompact(st.key), Val: valueNode(st.val)} + n := shortNode{Key: hexToCompactInPlace(st.key), Val: valueNode(st.val)} - n.encode(hasher.encbuf) - encodedNode = hasher.encodedBytes() + n.encode(t.h.encbuf) + encodedNode = t.h.encodedBytes() default: panic("invalid node type") } - st.nodeType = hashedNode + st.typ = hashedNode st.key = st.key[:0] if len(encodedNode) < 32 { st.val = common.CopyBytes(encodedNode) @@ -478,18 +360,16 @@ func (st *StackTrie) hashRec(hasher *hasher, path []byte) { // Write the hash to the 'val'. We allocate a new val here to not mutate // input values - st.val = hasher.hashData(encodedNode) - if st.writeFn != nil { - st.writeFn(st.owner, path, common.BytesToHash(st.val), encodedNode) + st.val = t.h.hashData(encodedNode) + if t.writeFn != nil { + t.writeFn(path, common.BytesToHash(st.val), encodedNode) } } // Hash returns the hash of the current node. -func (st *StackTrie) Hash() (h common.Hash) { - hasher := newHasher(false) - defer returnHasherToPool(hasher) - - st.hashRec(hasher, nil) +func (t *StackTrie) Hash() (h common.Hash) { + st := t.root + t.hash(st, nil) if len(st.val) == 32 { copy(h[:], st.val) return h @@ -497,9 +377,9 @@ func (st *StackTrie) Hash() (h common.Hash) { // If the node's RLP isn't 32 bytes long, the node will not // be hashed, and instead contain the rlp-encoding of the // node. For the top level node, we need to force the hashing. - hasher.sha.Reset() - hasher.sha.Write(st.val) - hasher.sha.Read(h[:]) + t.h.sha.Reset() + t.h.sha.Write(st.val) + t.h.sha.Read(h[:]) return h } @@ -510,14 +390,12 @@ func (st *StackTrie) Hash() (h common.Hash) { // // The associated database is expected, otherwise the whole commit // functionality should be disabled. -func (st *StackTrie) Commit() (h common.Hash, err error) { - if st.writeFn == nil { +func (t *StackTrie) Commit() (h common.Hash, err error) { + if t.writeFn == nil { return common.Hash{}, ErrCommitDisabled } - hasher := newHasher(false) - defer returnHasherToPool(hasher) - - st.hashRec(hasher, nil) + st := t.root + t.hash(st, nil) if len(st.val) == 32 { copy(h[:], st.val) return h, nil @@ -525,10 +403,10 @@ func (st *StackTrie) Commit() (h common.Hash, err error) { // If the node's RLP isn't 32 bytes long, the node will not // be hashed (and committed), and instead contain the rlp-encoding of the // node. For the top level node, we need to force the hashing+commit. - hasher.sha.Reset() - hasher.sha.Write(st.val) - hasher.sha.Read(h[:]) + t.h.sha.Reset() + t.h.sha.Write(st.val) + t.h.sha.Read(h[:]) - st.writeFn(st.owner, nil, h, st.val) + t.writeFn(nil, h, st.val) return h, nil } diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go index ea3eef78820d..0e52781c62ec 100644 --- a/trie/stacktrie_test.go +++ b/trie/stacktrie_test.go @@ -165,13 +165,44 @@ func TestStackTrieInsertAndHash(t *testing.T) { {"123e", "x___________________________2", "0d230561e398c579e09a9f7b69ceaf7d3970f5a436fdb28b68b7a37c5bdd6b80"}, {"13aa", "x___________________________3", "ff0dc70ce2e5db90ee42a4c2ad12139596b890e90eb4e16526ab38fa465b35cf"}, }, + { // branch node with short values + {"01", "a", "b48605025f5f4b129d40a420e721aa7d504487f015fce85b96e52126365ef7dc"}, + {"80", "b", "2dc6b680daf74db067cb7aeaad73265ded93d96fce190fcbf64f498d475672ab"}, + {"ee", "c", "017dc705a54ac5328dd263fa1bae68d655310fb3e3f7b7bc57e9a43ddf99c4bf"}, + {"ff", "d", "bd5a3584d271d459bd4eb95247b2fc88656b3671b60c1125ffe7bc0b689470d0"}, + }, + { // ext node with short branch node, then becoming long + {"a0", "a", "a83e028cb1e4365935661a9fd36a5c65c30b9ab416eaa877424146ca2a69d088"}, + {"a1", "b", "f586a4639b07b01798ca65e05c253b75d51135ebfbf6f8d6e87c0435089e65f0"}, + {"a2", "c", "63e297c295c008e09a8d531e18d57f270b6bc403e23179b915429db948cd62e3"}, + {"a3", "d", "94a7b721535578e9381f1f4e4b6ec29f8bdc5f0458a30320684c562f5d47b4b5"}, + {"a4", "e", "4b7e66d1c81965cdbe8fab8295ef56bc57fefdc5733d4782d2f8baf630f083c6"}, + {"a5", "f", "2997e7b502198ce1783b5277faacf52b25844fb55a99b63e88bdbbafac573106"}, + {"a6", "g", "bee629dd27a40772b2e1a67ec6db270d26acdf8d3b674dfae27866ad6ae1f48b"}, + }, + { // branch node with short values, then long ones + {"a001", "v1", "b9cc982d995392b51e6787f1915f0b88efd4ad8b30f138da0a3e2242f2323e35"}, + {"b002", "v2", "a7b474bc77ef5097096fa0ee6298fdae8928c0bc3724e7311cd0fa9ed1942fc7"}, + {"c003", "v___________________________3", "dceb5bb7c92b0e348df988a8d9fc36b101397e38ebd405df55ba6ee5f14a264a"}, + {"d004", "v___________________________4", "36e60ecb86b9626165e1c6543c42ecbe4d83bca58e8e1124746961511fce362a"}, + }, + { // ext node to branch node with short values, then long ones + {"8002", "v1", "3258fcb3e9e7d7234ecd3b8d4743999e4ab3a21592565e0a5ca64c141e8620d9"}, + {"8004", "v2", "b6cb95b7024a83c17624a3c9bed09b4b5e8ed426f49f54b8ad13c39028b1e75a"}, + {"8008", "v___________________________3", "c769d82963abe6f0900bf69754738eeb2f84559777cfa87a44f54e1aab417871"}, + {"800d", "v___________________________4", "1cad1fdaab1a6fa95d7b780fd680030e423eb76669971368ba04797a8d9cdfc9"}, + }, + { // ext node with a child of size 31 (Y) and branch node with a child of size 31 (X) + {"000001", "ZZZZZZZZZ", "cef154b87c03c563408520ff9b26923c360cbc3ddb590c079bedeeb25a8c9c77"}, + {"000002", "Y", "2130735e600f612f6e657a32bd7be64ddcaec6512c5694844b19de713922895d"}, + {"000003", "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", "962c0fffdeef7612a4f7bff1950d67e3e81c878e48b9ae45b3b374253b050bd8"}, + }, } - st := NewStackTrie(nil) for i, test := range tests { // The StackTrie does not allow Insert(), Hash(), Insert(), ... // so we will create new trie for every sequence length of inserts. for l := 1; l <= len(test); l++ { - st.Reset() + st := NewStackTrie(nil) for j := 0; j < l; j++ { kv := &test[j] if err := st.Update(common.FromHex(kv.K), []byte(kv.V)); err != nil { @@ -188,7 +219,7 @@ func TestStackTrieInsertAndHash(t *testing.T) { func TestSizeBug(t *testing.T) { st := NewStackTrie(nil) - nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") @@ -203,7 +234,7 @@ func TestSizeBug(t *testing.T) { func TestEmptyBug(t *testing.T) { st := NewStackTrie(nil) - nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") @@ -229,7 +260,7 @@ func TestEmptyBug(t *testing.T) { func TestValLength56(t *testing.T) { st := NewStackTrie(nil) - nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) //leaf := common.FromHex("290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") //value := common.FromHex("94cf40d0d2b44f2b66e07cace1372ca42b73cf21a3") @@ -254,7 +285,7 @@ func TestValLength56(t *testing.T) { // which causes a lot of node-within-node. This case was found via fuzzing. func TestUpdateSmallNodes(t *testing.T) { st := NewStackTrie(nil) - nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) kvs := []struct { K string V string @@ -282,7 +313,7 @@ func TestUpdateSmallNodes(t *testing.T) { func TestUpdateVariableKeys(t *testing.T) { t.SkipNow() st := NewStackTrie(nil) - nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + nt := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) kvs := []struct { K string V string @@ -345,48 +376,3 @@ func TestStacktrieNotModifyValues(t *testing.T) { } } } - -// TestStacktrieSerialization tests that the stacktrie works well if we -// serialize/unserialize it a lot -func TestStacktrieSerialization(t *testing.T) { - var ( - st = NewStackTrie(nil) - nt = NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) - keyB = big.NewInt(1) - keyDelta = big.NewInt(1) - vals [][]byte - keys [][]byte - ) - getValue := func(i int) []byte { - if i%2 == 0 { // large - return crypto.Keccak256(big.NewInt(int64(i)).Bytes()) - } else { //small - return big.NewInt(int64(i)).Bytes() - } - } - for i := 0; i < 10; i++ { - vals = append(vals, getValue(i)) - keys = append(keys, common.BigToHash(keyB).Bytes()) - keyB = keyB.Add(keyB, keyDelta) - keyDelta.Add(keyDelta, common.Big1) - } - for i, k := range keys { - nt.Update(k, common.CopyBytes(vals[i])) - } - - for i, k := range keys { - blob, err := st.MarshalBinary() - if err != nil { - t.Fatal(err) - } - newSt, err := NewFromBinary(blob, nil) - if err != nil { - t.Fatal(err) - } - st = newSt - st.Update(k, common.CopyBytes(vals[i])) - } - if have, want := st.Hash(), nt.Hash(); have != want { - t.Fatalf("have %#x want %#x", have, want) - } -} diff --git a/trie/sync.go b/trie/sync.go index 4f5584599179..6939aed76d4b 100644 --- a/trie/sync.go +++ b/trie/sync.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" ) // ErrNotRequested is returned by the trie sync when it's requested to process a @@ -42,6 +43,16 @@ var ErrAlreadyProcessed = errors.New("already processed") // memory if the node was configured with a significant number of peers. const maxFetchesPerDepth = 16384 +var ( + // deletionGauge is the metric to track how many trie node deletions + // are performed in total during the sync process. + deletionGauge = metrics.NewRegisteredGauge("trie/sync/delete", nil) + + // lookupGauge is the metric to track how many trie node lookups are + // performed to determine if node needs to be deleted. + lookupGauge = metrics.NewRegisteredGauge("trie/sync/lookup", nil) +) + // SyncPath is a path tuple identifying a particular trie node either in a single // trie (account) or a layered trie (account -> storage). // @@ -93,9 +104,10 @@ type LeafCallback func(keys [][]byte, path []byte, leaf []byte, parent common.Ha // nodeRequest represents a scheduled or already in-flight trie node retrieval request. type nodeRequest struct { - hash common.Hash // Hash of the trie node to retrieve - path []byte // Merkle path leading to this node for prioritization - data []byte // Data content of the node, cached until all subtrees complete + hash common.Hash // Hash of the trie node to retrieve + path []byte // Merkle path leading to this node for prioritization + data []byte // Data content of the node, cached until all subtrees complete + deletes [][]byte // List of internal path segments for trie nodes to delete parent *nodeRequest // Parent state node referencing this entry deps int // Number of dependencies before allowed to commit this node @@ -125,18 +137,20 @@ type CodeSyncResult struct { // syncMemBatch is an in-memory buffer of successfully downloaded but not yet // persisted data items. type syncMemBatch struct { - nodes map[string][]byte // In-memory membatch of recently completed nodes - hashes map[string]common.Hash // Hashes of recently completed nodes - codes map[common.Hash][]byte // In-memory membatch of recently completed codes - size uint64 // Estimated batch-size of in-memory data. + nodes map[string][]byte // In-memory membatch of recently completed nodes + hashes map[string]common.Hash // Hashes of recently completed nodes + deletes map[string]struct{} // List of paths for trie node to delete + codes map[common.Hash][]byte // In-memory membatch of recently completed codes + size uint64 // Estimated batch-size of in-memory data. } // newSyncMemBatch allocates a new memory-buffer for not-yet persisted trie nodes. func newSyncMemBatch() *syncMemBatch { return &syncMemBatch{ - nodes: make(map[string][]byte), - hashes: make(map[string]common.Hash), - codes: make(map[common.Hash][]byte), + nodes: make(map[string][]byte), + hashes: make(map[string]common.Hash), + deletes: make(map[string]struct{}), + codes: make(map[common.Hash][]byte), } } @@ -288,7 +302,7 @@ func (s *Sync) Missing(max int) ([]string, []common.Hash, []common.Hash) { } // ProcessCode injects the received data for requested item. Note it can -// happpen that the single response commits two pending requests(e.g. +// happen that the single response commits two pending requests(e.g. // there are two requests one for code and one for node but the hash // is same). In this case the second response for the same hash will // be treated as "non-requested" item or "already-processed" item but @@ -347,16 +361,23 @@ func (s *Sync) ProcessNode(result NodeSyncResult) error { // Commit flushes the data stored in the internal membatch out to persistent // storage, returning any occurred error. func (s *Sync) Commit(dbw ethdb.Batch) error { - // Dump the membatch into a database dbw + // Flush the pending node writes into database batch. for path, value := range s.membatch.nodes { owner, inner := ResolvePath([]byte(path)) rawdb.WriteTrieNode(dbw, owner, inner, s.membatch.hashes[path], value, s.scheme) } + // Flush the pending node deletes into the database batch. + // Please note that each written and deleted node has a + // unique path, ensuring no duplication occurs. + for path := range s.membatch.deletes { + owner, inner := ResolvePath([]byte(path)) + rawdb.DeleteTrieNode(dbw, owner, inner, common.Hash{} /* unused */, s.scheme) + } + // Flush the pending code writes into database batch. for hash, value := range s.membatch.codes { rawdb.WriteCode(dbw, hash, value) } - // Drop the membatch data and return - s.membatch = newSyncMemBatch() + s.membatch = newSyncMemBatch() // reset the batch return nil } @@ -370,7 +391,7 @@ func (s *Sync) Pending() int { return len(s.nodeReqs) + len(s.codeReqs) } -// schedule inserts a new state retrieval request into the fetch queue. If there +// scheduleNodeRequest inserts a new state retrieval request into the fetch queue. If there // is already a pending request for this node, the new request will be discarded // and only a parent reference added to the old one. func (s *Sync) scheduleNodeRequest(req *nodeRequest) { @@ -385,7 +406,7 @@ func (s *Sync) scheduleNodeRequest(req *nodeRequest) { s.queue.Push(string(req.path), prio) } -// schedule inserts a new state retrieval request into the fetch queue. If there +// scheduleCodeRequest inserts a new state retrieval request into the fetch queue. If there // is already a pending request for this node, the new request will be discarded // and only a parent reference added to the old one. func (s *Sync) scheduleCodeRequest(req *codeRequest) { @@ -425,6 +446,39 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { node: node.Val, path: append(append([]byte(nil), req.path...), key...), }} + // Mark all internal nodes between shortNode and its **in disk** + // child as invalid. This is essential in the case of path mode + // scheme; otherwise, state healing might overwrite existing child + // nodes silently while leaving a dangling parent node within the + // range of this internal path on disk. This would break the + // guarantee for state healing. + // + // While it's possible for this shortNode to overwrite a previously + // existing full node, the other branches of the fullNode can be + // retained as they remain untouched and complete. + // + // This step is only necessary for path mode, as there is no deletion + // in hash mode at all. + if _, ok := node.Val.(hashNode); ok && s.scheme == rawdb.PathScheme { + owner, inner := ResolvePath(req.path) + for i := 1; i < len(key); i++ { + // While checking for a non-existent item in Pebble can be less efficient + // without a bloom filter, the relatively low frequency of lookups makes + // the performance impact negligible. + var exists bool + if owner == (common.Hash{}) { + exists = rawdb.ExistsAccountTrieNode(s.database, append(inner, key[:i]...)) + } else { + exists = rawdb.ExistsStorageTrieNode(s.database, owner, append(inner, key[:i]...)) + } + if exists { + req.deletes = append(req.deletes, key[:i]) + deletionGauge.Inc(1) + log.Debug("Detected dangling node", "owner", owner, "path", append(inner, key[:i]...)) + } + } + lookupGauge.Inc(int64(len(key) - 1)) + } case *fullNode: for i := 0; i < 17; i++ { if node.Children[i] != nil { @@ -502,17 +556,26 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { return requests, nil } -// commit finalizes a retrieval request and stores it into the membatch. If any +// commitNodeRequest finalizes a retrieval request and stores it into the membatch. If any // of the referencing parent requests complete due to this commit, they are also // committed themselves. func (s *Sync) commitNodeRequest(req *nodeRequest) error { // Write the node content to the membatch s.membatch.nodes[string(req.path)] = req.data s.membatch.hashes[string(req.path)] = req.hash + // The size tracking refers to the db-batch, not the in-memory data. - // Therefore, we ignore the req.path, and account only for the hash+data - // which eventually is written to db. - s.membatch.size += common.HashLength + uint64(len(req.data)) + if s.scheme == rawdb.PathScheme { + s.membatch.size += uint64(len(req.path) + len(req.data)) + } else { + s.membatch.size += common.HashLength + uint64(len(req.data)) + } + // Delete the internal nodes which are marked as invalid + for _, segment := range req.deletes { + path := append(req.path, segment...) + s.membatch.deletes[string(path)] = struct{}{} + s.membatch.size += uint64(len(path)) + } delete(s.nodeReqs, string(req.path)) s.fetches[len(req.path)]-- @@ -528,7 +591,7 @@ func (s *Sync) commitNodeRequest(req *nodeRequest) error { return nil } -// commit finalizes a retrieval request and stores it into the membatch. If any +// commitCodeRequest finalizes a retrieval request and stores it into the membatch. If any // of the referencing parent requests complete due to this commit, they are also // committed themselves. func (s *Sync) commitCodeRequest(req *codeRequest) error { diff --git a/trie/sync_test.go b/trie/sync_test.go index 7cd4058176e2..3b7986ef6792 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -56,8 +56,8 @@ func makeTestTrie(scheme string) (ethdb.Database, *Database, *StateTrie, map[str trie.MustUpdate(key, val) } } - root, nodes := trie.Commit(false) - if err := triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)); err != nil { + root, nodes, _ := trie.Commit(false) + if err := triedb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil); err != nil { panic(fmt.Errorf("failed to commit db %v", err)) } if err := triedb.Commit(root, false); err != nil { @@ -70,31 +70,53 @@ func makeTestTrie(scheme string) (ethdb.Database, *Database, *StateTrie, map[str // checkTrieContents cross references a reconstructed trie with an expected data // content map. -func checkTrieContents(t *testing.T, db ethdb.Database, scheme string, root []byte, content map[string][]byte) { +func checkTrieContents(t *testing.T, db ethdb.Database, scheme string, root []byte, content map[string][]byte, rawTrie bool) { // Check root availability and trie contents ndb := newTestDatabase(db, scheme) - trie, err := NewStateTrie(TrieID(common.BytesToHash(root)), ndb) - if err != nil { - t.Fatalf("failed to create trie at %x: %v", root, err) - } - if err := checkTrieConsistency(db, scheme, common.BytesToHash(root)); err != nil { + if err := checkTrieConsistency(db, scheme, common.BytesToHash(root), rawTrie); err != nil { t.Fatalf("inconsistent trie at %x: %v", root, err) } + type reader interface { + MustGet(key []byte) []byte + } + var r reader + if rawTrie { + trie, err := New(TrieID(common.BytesToHash(root)), ndb) + if err != nil { + t.Fatalf("failed to create trie at %x: %v", root, err) + } + r = trie + } else { + trie, err := NewStateTrie(TrieID(common.BytesToHash(root)), ndb) + if err != nil { + t.Fatalf("failed to create trie at %x: %v", root, err) + } + r = trie + } for key, val := range content { - if have := trie.MustGet([]byte(key)); !bytes.Equal(have, val) { + if have := r.MustGet([]byte(key)); !bytes.Equal(have, val) { t.Errorf("entry %x: content mismatch: have %x, want %x", key, have, val) } } } // checkTrieConsistency checks that all nodes in a trie are indeed present. -func checkTrieConsistency(db ethdb.Database, scheme string, root common.Hash) error { +func checkTrieConsistency(db ethdb.Database, scheme string, root common.Hash, rawTrie bool) error { ndb := newTestDatabase(db, scheme) - trie, err := NewStateTrie(TrieID(root), ndb) - if err != nil { - return nil // Consider a non existent state consistent + var it NodeIterator + if rawTrie { + trie, err := New(TrieID(root), ndb) + if err != nil { + return nil // Consider a non existent state consistent + } + it = trie.MustNodeIterator(nil) + } else { + trie, err := NewStateTrie(TrieID(root), ndb) + if err != nil { + return nil // Consider a non existent state consistent + } + it = trie.MustNodeIterator(nil) } - it := trie.NodeIterator(nil) for it.Next(true) { } return it.Error() @@ -109,18 +131,18 @@ type trieElement struct { // Tests that an empty trie is not scheduled for syncing. func TestEmptySync(t *testing.T) { - dbA := NewDatabase(rawdb.NewMemoryDatabase()) - dbB := NewDatabase(rawdb.NewMemoryDatabase()) - //dbC := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) - //dbD := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) + dbA := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + dbB := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + dbC := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) + dbD := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) emptyA := NewEmpty(dbA) emptyB, _ := New(TrieID(types.EmptyRootHash), dbB) - //emptyC := NewEmpty(dbC) - //emptyD, _ := New(TrieID(types.EmptyRootHash), dbD) + emptyC := NewEmpty(dbC) + emptyD, _ := New(TrieID(types.EmptyRootHash), dbD) - for i, trie := range []*Trie{emptyA, emptyB /*emptyC, emptyD*/} { - sync := NewSync(trie.Hash(), memorydb.New(), nil, []*Database{dbA, dbB /*dbC, dbD*/}[i].Scheme()) + for i, trie := range []*Trie{emptyA, emptyB, emptyC, emptyD} { + sync := NewSync(trie.Hash(), memorydb.New(), nil, []*Database{dbA, dbB, dbC, dbD}[i].Scheme()) if paths, nodes, codes := sync.Missing(1); len(paths) != 0 || len(nodes) != 0 || len(codes) != 0 { t.Errorf("test %d: content requested for empty trie: %v, %v, %v", i, paths, nodes, codes) } @@ -134,10 +156,10 @@ func TestIterativeSync(t *testing.T) { testIterativeSync(t, 100, false, rawdb.HashScheme) testIterativeSync(t, 1, true, rawdb.HashScheme) testIterativeSync(t, 100, true, rawdb.HashScheme) - // testIterativeSync(t, 1, false, rawdb.PathScheme) - // testIterativeSync(t, 100, false, rawdb.PathScheme) - // testIterativeSync(t, 1, true, rawdb.PathScheme) - // testIterativeSync(t, 100, true, rawdb.PathScheme) + testIterativeSync(t, 1, false, rawdb.PathScheme) + testIterativeSync(t, 100, false, rawdb.PathScheme) + testIterativeSync(t, 1, true, rawdb.PathScheme) + testIterativeSync(t, 100, true, rawdb.PathScheme) } func testIterativeSync(t *testing.T, count int, bypath bool, scheme string) { @@ -159,12 +181,16 @@ func testIterativeSync(t *testing.T, count int, bypath bool, scheme string) { syncPath: NewSyncPath([]byte(paths[i])), }) } + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } for len(elements) > 0 { results := make([]NodeSyncResult, len(elements)) if !bypath { for i, element := range elements { owner, inner := ResolvePath([]byte(element.path)) - data, err := srcDb.Reader(srcTrie.Hash()).Node(owner, inner, element.hash) + data, err := reader.Node(owner, inner, element.hash) if err != nil { t.Fatalf("failed to retrieve node data for hash %x: %v", element.hash, err) } @@ -201,14 +227,14 @@ func testIterativeSync(t *testing.T, count int, bypath bool, scheme string) { } } // Cross check that the two tries are in sync - checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) + checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) } // Tests that the trie scheduler can correctly reconstruct the state even if only // partial results are returned, and the others sent only later. func TestIterativeDelayedSync(t *testing.T) { testIterativeDelayedSync(t, rawdb.HashScheme) - //testIterativeDelayedSync(t, rawdb.PathScheme) + testIterativeDelayedSync(t, rawdb.PathScheme) } func testIterativeDelayedSync(t *testing.T, scheme string) { @@ -230,12 +256,16 @@ func testIterativeDelayedSync(t *testing.T, scheme string) { syncPath: NewSyncPath([]byte(paths[i])), }) } + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } for len(elements) > 0 { // Sync only half of the scheduled nodes results := make([]NodeSyncResult, len(elements)/2+1) for i, element := range elements[:len(results)] { owner, inner := ResolvePath([]byte(element.path)) - data, err := srcDb.Reader(srcTrie.Hash()).Node(owner, inner, element.hash) + data, err := reader.Node(owner, inner, element.hash) if err != nil { t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err) } @@ -263,7 +293,7 @@ func testIterativeDelayedSync(t *testing.T, scheme string) { } } // Cross check that the two tries are in sync - checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) + checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) } // Tests that given a root hash, a trie can sync iteratively on a single thread, @@ -272,8 +302,8 @@ func testIterativeDelayedSync(t *testing.T, scheme string) { func TestIterativeRandomSyncIndividual(t *testing.T) { testIterativeRandomSync(t, 1, rawdb.HashScheme) testIterativeRandomSync(t, 100, rawdb.HashScheme) - // testIterativeRandomSync(t, 1, rawdb.PathScheme) - // testIterativeRandomSync(t, 100, rawdb.PathScheme) + testIterativeRandomSync(t, 1, rawdb.PathScheme) + testIterativeRandomSync(t, 100, rawdb.PathScheme) } func testIterativeRandomSync(t *testing.T, count int, scheme string) { @@ -295,12 +325,16 @@ func testIterativeRandomSync(t *testing.T, count int, scheme string) { syncPath: NewSyncPath([]byte(paths[i])), } } + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } for len(queue) > 0 { // Fetch all the queued nodes in a random order results := make([]NodeSyncResult, 0, len(queue)) for path, element := range queue { owner, inner := ResolvePath([]byte(element.path)) - data, err := srcDb.Reader(srcTrie.Hash()).Node(owner, inner, element.hash) + data, err := reader.Node(owner, inner, element.hash) if err != nil { t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err) } @@ -329,14 +363,14 @@ func testIterativeRandomSync(t *testing.T, count int, scheme string) { } } // Cross check that the two tries are in sync - checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) + checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) } // Tests that the trie scheduler can correctly reconstruct the state even if only // partial results are returned (Even those randomly), others sent only later. func TestIterativeRandomDelayedSync(t *testing.T) { testIterativeRandomDelayedSync(t, rawdb.HashScheme) - // testIterativeRandomDelayedSync(t, rawdb.PathScheme) + testIterativeRandomDelayedSync(t, rawdb.PathScheme) } func testIterativeRandomDelayedSync(t *testing.T, scheme string) { @@ -358,12 +392,16 @@ func testIterativeRandomDelayedSync(t *testing.T, scheme string) { syncPath: NewSyncPath([]byte(path)), } } + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } for len(queue) > 0 { // Sync only half of the scheduled nodes, even those in random order results := make([]NodeSyncResult, 0, len(queue)/2+1) for path, element := range queue { owner, inner := ResolvePath([]byte(element.path)) - data, err := srcDb.Reader(srcTrie.Hash()).Node(owner, inner, element.hash) + data, err := reader.Node(owner, inner, element.hash) if err != nil { t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err) } @@ -397,14 +435,14 @@ func testIterativeRandomDelayedSync(t *testing.T, scheme string) { } } // Cross check that the two tries are in sync - checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) + checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) } // Tests that a trie sync will not request nodes multiple times, even if they // have such references. func TestDuplicateAvoidanceSync(t *testing.T) { testDuplicateAvoidanceSync(t, rawdb.HashScheme) - // testDuplicateAvoidanceSync(t, rawdb.PathScheme) + testDuplicateAvoidanceSync(t, rawdb.PathScheme) } func testDuplicateAvoidanceSync(t *testing.T, scheme string) { @@ -426,13 +464,16 @@ func testDuplicateAvoidanceSync(t *testing.T, scheme string) { syncPath: NewSyncPath([]byte(paths[i])), }) } + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } requested := make(map[common.Hash]struct{}) - for len(elements) > 0 { results := make([]NodeSyncResult, len(elements)) for i, element := range elements { owner, inner := ResolvePath([]byte(element.path)) - data, err := srcDb.Reader(srcTrie.Hash()).Node(owner, inner, element.hash) + data, err := reader.Node(owner, inner, element.hash) if err != nil { t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err) } @@ -465,19 +506,17 @@ func testDuplicateAvoidanceSync(t *testing.T, scheme string) { } } // Cross check that the two tries are in sync - checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) + checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) } // Tests that at any point in time during a sync, only complete sub-tries are in // the database. func TestIncompleteSyncHash(t *testing.T) { testIncompleteSync(t, rawdb.HashScheme) - // testIncompleteSync(t, rawdb.PathScheme) + testIncompleteSync(t, rawdb.PathScheme) } func testIncompleteSync(t *testing.T, scheme string) { - t.Parallel() - // Create a random trie to copy _, srcDb, srcTrie, _ := makeTestTrie(scheme) @@ -501,12 +540,16 @@ func testIncompleteSync(t *testing.T, scheme string) { syncPath: NewSyncPath([]byte(paths[i])), }) } + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } for len(elements) > 0 { // Fetch a batch of trie nodes results := make([]NodeSyncResult, len(elements)) for i, element := range elements { owner, inner := ResolvePath([]byte(element.path)) - data, err := srcDb.Reader(srcTrie.Hash()).Node(owner, inner, element.hash) + data, err := reader.Node(owner, inner, element.hash) if err != nil { t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err) } @@ -548,7 +591,7 @@ func testIncompleteSync(t *testing.T, scheme string) { nodeHash := addedHashes[i] value := rawdb.ReadTrieNode(diskdb, owner, inner, nodeHash, scheme) rawdb.DeleteTrieNode(diskdb, owner, inner, nodeHash, scheme) - if err := checkTrieConsistency(diskdb, srcDb.Scheme(), root); err == nil { + if err := checkTrieConsistency(diskdb, srcDb.Scheme(), root, false); err == nil { t.Fatalf("trie inconsistency not caught, missing: %x", path) } rawdb.WriteTrieNode(diskdb, owner, inner, nodeHash, value, scheme) @@ -559,7 +602,7 @@ func testIncompleteSync(t *testing.T, scheme string) { // depth. func TestSyncOrdering(t *testing.T) { testSyncOrdering(t, rawdb.HashScheme) - // testSyncOrdering(t, rawdb.PathScheme) + testSyncOrdering(t, rawdb.PathScheme) } func testSyncOrdering(t *testing.T, scheme string) { @@ -585,12 +628,15 @@ func testSyncOrdering(t *testing.T, scheme string) { }) reqs = append(reqs, NewSyncPath([]byte(paths[i]))) } - + reader, err := srcDb.Reader(srcTrie.Hash()) + if err != nil { + t.Fatalf("State is not available %x", srcTrie.Hash()) + } for len(elements) > 0 { results := make([]NodeSyncResult, len(elements)) for i, element := range elements { owner, inner := ResolvePath([]byte(element.path)) - data, err := srcDb.Reader(srcTrie.Hash()).Node(owner, inner, element.hash) + data, err := reader.Node(owner, inner, element.hash) if err != nil { t.Fatalf("failed to retrieve node data for %x: %v", element.hash, err) } @@ -619,7 +665,7 @@ func testSyncOrdering(t *testing.T, scheme string) { } } // Cross check that the two tries are in sync - checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) + checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) // Check that the trie nodes have been requested path-ordered for i := 0; i < len(reqs)-1; i++ { @@ -640,7 +686,7 @@ func syncWith(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database // The code requests are ignored here since there is no code // at the testing trie. - paths, nodes, _ := sched.Missing(1) + paths, nodes, _ := sched.Missing(0) var elements []trieElement for i := 0; i < len(paths); i++ { elements = append(elements, trieElement{ @@ -649,11 +695,15 @@ func syncWith(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database syncPath: NewSyncPath([]byte(paths[i])), }) } + reader, err := srcDb.Reader(root) + if err != nil { + t.Fatalf("State is not available %x", root) + } for len(elements) > 0 { results := make([]NodeSyncResult, len(elements)) for i, element := range elements { owner, inner := ResolvePath([]byte(element.path)) - data, err := srcDb.Reader(root).Node(owner, inner, element.hash) + data, err := reader.Node(owner, inner, element.hash) if err != nil { t.Fatalf("failed to retrieve node data for hash %x: %v", element.hash, err) } @@ -670,7 +720,7 @@ func syncWith(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database } batch.Write() - paths, nodes, _ = sched.Missing(1) + paths, nodes, _ = sched.Missing(0) elements = elements[:0] for i := 0; i < len(paths); i++ { elements = append(elements, trieElement{ @@ -686,7 +736,7 @@ func syncWith(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database // states synced in the last cycle. func TestSyncMovingTarget(t *testing.T) { testSyncMovingTarget(t, rawdb.HashScheme) - // testSyncMovingTarget(t, rawdb.PathScheme) + testSyncMovingTarget(t, rawdb.PathScheme) } func testSyncMovingTarget(t *testing.T, scheme string) { @@ -696,7 +746,7 @@ func testSyncMovingTarget(t *testing.T, scheme string) { // Create a destination trie and sync with the scheduler diskdb := rawdb.NewMemoryDatabase() syncWith(t, srcTrie.Hash(), diskdb, srcDb) - checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData) + checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), srcData, false) // Push more modifications into the src trie, to see if dest trie can still // sync with it(overwrite stale states) @@ -709,8 +759,8 @@ func testSyncMovingTarget(t *testing.T, scheme string) { srcTrie.MustUpdate(key, val) diff[string(key)] = val } - root, nodes := srcTrie.Commit(false) - if err := srcDb.Update(root, preRoot, trienode.NewWithNodeSet(nodes)); err != nil { + root, nodes, _ := srcTrie.Commit(false) + if err := srcDb.Update(root, preRoot, 0, trienode.NewWithNodeSet(nodes), nil); err != nil { panic(err) } if err := srcDb.Commit(root, false); err != nil { @@ -720,7 +770,7 @@ func testSyncMovingTarget(t *testing.T, scheme string) { srcTrie, _ = NewStateTrie(TrieID(root), srcDb) syncWith(t, srcTrie.Hash(), diskdb, srcDb) - checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), diff) + checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), diff, false) // Revert added modifications from the src trie, to see if dest trie can still // sync with it(overwrite reverted states) @@ -734,8 +784,8 @@ func testSyncMovingTarget(t *testing.T, scheme string) { srcTrie.MustUpdate([]byte(k), val) reverted[k] = val } - root, nodes = srcTrie.Commit(false) - if err := srcDb.Update(root, preRoot, trienode.NewWithNodeSet(nodes)); err != nil { + root, nodes, _ = srcTrie.Commit(false) + if err := srcDb.Update(root, preRoot, 0, trienode.NewWithNodeSet(nodes), nil); err != nil { panic(err) } if err := srcDb.Commit(root, false); err != nil { @@ -744,5 +794,98 @@ func testSyncMovingTarget(t *testing.T, scheme string) { srcTrie, _ = NewStateTrie(TrieID(root), srcDb) syncWith(t, srcTrie.Hash(), diskdb, srcDb) - checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), reverted) + checkTrieContents(t, diskdb, srcDb.Scheme(), srcTrie.Hash().Bytes(), reverted, false) +} + +// Tests if state syncer can correctly catch up the pivot move. +func TestPivotMove(t *testing.T) { + testPivotMove(t, rawdb.HashScheme, true) + testPivotMove(t, rawdb.HashScheme, false) + testPivotMove(t, rawdb.PathScheme, true) + testPivotMove(t, rawdb.PathScheme, false) +} + +func testPivotMove(t *testing.T, scheme string, tiny bool) { + var ( + srcDisk = rawdb.NewMemoryDatabase() + srcTrieDB = newTestDatabase(srcDisk, scheme) + srcTrie, _ = New(TrieID(types.EmptyRootHash), srcTrieDB) + + deleteFn = func(key []byte, tr *Trie, states map[string][]byte) { + tr.Delete(key) + delete(states, string(key)) + } + writeFn = func(key []byte, val []byte, tr *Trie, states map[string][]byte) { + if val == nil { + if tiny { + val = randBytes(4) + } else { + val = randBytes(32) + } + } + tr.Update(key, val) + states[string(key)] = common.CopyBytes(val) + } + copyStates = func(states map[string][]byte) map[string][]byte { + cpy := make(map[string][]byte) + for k, v := range states { + cpy[k] = v + } + return cpy + } + ) + stateA := make(map[string][]byte) + writeFn([]byte{0x01, 0x23}, nil, srcTrie, stateA) + writeFn([]byte{0x01, 0x24}, nil, srcTrie, stateA) + writeFn([]byte{0x12, 0x33}, nil, srcTrie, stateA) + writeFn([]byte{0x12, 0x34}, nil, srcTrie, stateA) + writeFn([]byte{0x02, 0x34}, nil, srcTrie, stateA) + writeFn([]byte{0x13, 0x44}, nil, srcTrie, stateA) + + rootA, nodesA, _ := srcTrie.Commit(false) + if err := srcTrieDB.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodesA), nil); err != nil { + panic(err) + } + if err := srcTrieDB.Commit(rootA, false); err != nil { + panic(err) + } + // Create a destination trie and sync with the scheduler + destDisk := rawdb.NewMemoryDatabase() + syncWith(t, rootA, destDisk, srcTrieDB) + checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateA, true) + + // Delete element to collapse trie + stateB := copyStates(stateA) + srcTrie, _ = New(TrieID(rootA), srcTrieDB) + deleteFn([]byte{0x02, 0x34}, srcTrie, stateB) + deleteFn([]byte{0x13, 0x44}, srcTrie, stateB) + writeFn([]byte{0x01, 0x24}, nil, srcTrie, stateB) + + rootB, nodesB, _ := srcTrie.Commit(false) + if err := srcTrieDB.Update(rootB, rootA, 0, trienode.NewWithNodeSet(nodesB), nil); err != nil { + panic(err) + } + if err := srcTrieDB.Commit(rootB, false); err != nil { + panic(err) + } + syncWith(t, rootB, destDisk, srcTrieDB) + checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateB, true) + + // Add elements to expand trie + stateC := copyStates(stateB) + srcTrie, _ = New(TrieID(rootB), srcTrieDB) + + writeFn([]byte{0x01, 0x24}, stateA[string([]byte{0x01, 0x24})], srcTrie, stateC) + writeFn([]byte{0x02, 0x34}, nil, srcTrie, stateC) + writeFn([]byte{0x13, 0x44}, nil, srcTrie, stateC) + + rootC, nodesC, _ := srcTrie.Commit(false) + if err := srcTrieDB.Update(rootC, rootB, 0, trienode.NewWithNodeSet(nodesC), nil); err != nil { + panic(err) + } + if err := srcTrieDB.Commit(rootC, false); err != nil { + panic(err) + } + syncWith(t, rootC, destDisk, srcTrieDB) + checkTrieContents(t, destDisk, scheme, srcTrie.Hash().Bytes(), stateC, true) } diff --git a/trie/testutil/utils.go b/trie/testutil/utils.go new file mode 100644 index 000000000000..a75d0431b0f4 --- /dev/null +++ b/trie/testutil/utils.go @@ -0,0 +1,61 @@ +// Copyright 2023 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 testutil + +import ( + crand "crypto/rand" + "encoding/binary" + mrand "math/rand" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/trie/trienode" +) + +// Prng is a pseudo random number generator seeded by strong randomness. +// The randomness is printed on startup in order to make failures reproducible. +var prng = initRand() + +func initRand() *mrand.Rand { + var seed [8]byte + crand.Read(seed[:]) + rnd := mrand.New(mrand.NewSource(int64(binary.LittleEndian.Uint64(seed[:])))) + return rnd +} + +// RandBytes generates a random byte slice with specified length. +func RandBytes(n int) []byte { + r := make([]byte, n) + prng.Read(r) + return r +} + +// RandomHash generates a random blob of data and returns it as a hash. +func RandomHash() common.Hash { + return common.BytesToHash(RandBytes(common.HashLength)) +} + +// RandomAddress generates a random blob of data and returns it as an address. +func RandomAddress() common.Address { + return common.BytesToAddress(RandBytes(common.AddressLength)) +} + +// RandomNode generates a random node. +func RandomNode() *trienode.Node { + val := RandBytes(100) + return trienode.New(crypto.Keccak256Hash(val), val) +} diff --git a/trie/tracer.go b/trie/tracer.go index 2b5de8ec4714..5786af4d3ec9 100644 --- a/trie/tracer.go +++ b/trie/tracer.go @@ -18,7 +18,6 @@ package trie import ( "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/trie/trienode" ) // tracer tracks the changes of trie nodes. During the trie operations, @@ -114,16 +113,18 @@ func (t *tracer) copy() *tracer { } } -// markDeletions puts all tracked deletions into the provided nodeset. -func (t *tracer) markDeletions(set *trienode.NodeSet) { +// deletedNodes returns a list of node paths which are deleted from the trie. +func (t *tracer) deletedNodes() []string { + var paths []string for path := range t.deletes { // It's possible a few deleted nodes were embedded // in their parent before, the deletions can be no // effect by deleting nothing, filter them out. - prev, ok := t.accessList[path] + _, ok := t.accessList[path] if !ok { continue } - set.AddNode([]byte(path), trienode.NewWithPrev(common.Hash{}, nil, prev)) + paths = append(paths, path) } + return paths } diff --git a/trie/tracer_test.go b/trie/tracer_test.go index 5f61a1d6b3ed..acb8c2f6bf4f 100644 --- a/trie/tracer_test.go +++ b/trie/tracer_test.go @@ -61,7 +61,7 @@ func TestTrieTracer(t *testing.T) { // Tests if the trie diffs are tracked correctly. Tracer should capture // all non-leaf dirty nodes, no matter the node is embedded or not. func testTrieTracer(t *testing.T, vals []struct{ k, v string }) { - db := NewDatabase(rawdb.NewMemoryDatabase()) + db := NewDatabase(rawdb.NewMemoryDatabase(), nil) trie := NewEmpty(db) // Determine all new nodes are tracked @@ -70,8 +70,8 @@ func testTrieTracer(t *testing.T, vals []struct{ k, v string }) { } insertSet := copySet(trie.tracer.inserts) // copy before commit deleteSet := copySet(trie.tracer.deletes) // copy before commit - root, nodes := trie.Commit(false) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := trie.Commit(false) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) seen := setKeys(iterNodes(db, root)) if !compareSet(insertSet, seen) { @@ -104,7 +104,7 @@ func TestTrieTracerNoop(t *testing.T) { } func testTrieTracerNoop(t *testing.T, vals []struct{ k, v string }) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) for _, val := range vals { trie.MustUpdate([]byte(val.k), []byte(val.v)) } @@ -128,7 +128,7 @@ func TestAccessList(t *testing.T) { func testAccessList(t *testing.T, vals []struct{ k, v string }) { var ( - db = NewDatabase(rawdb.NewMemoryDatabase()) + db = NewDatabase(rawdb.NewMemoryDatabase(), nil) trie = NewEmpty(db) orig = trie.Copy() ) @@ -136,8 +136,8 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) { for _, val := range vals { trie.MustUpdate([]byte(val.k), []byte(val.v)) } - root, nodes := trie.Commit(false) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := trie.Commit(false) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) trie, _ = New(TrieID(root), db) if err := verifyAccessList(orig, trie, nodes); err != nil { @@ -151,8 +151,8 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) { for _, val := range vals { trie.MustUpdate([]byte(val.k), randBytes(32)) } - root, nodes = trie.Commit(false) - db.Update(root, parent, trienode.NewWithNodeSet(nodes)) + root, nodes, _ = trie.Commit(false) + db.Update(root, parent, 0, trienode.NewWithNodeSet(nodes), nil) trie, _ = New(TrieID(root), db) if err := verifyAccessList(orig, trie, nodes); err != nil { @@ -169,8 +169,8 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) { keys = append(keys, string(key)) trie.MustUpdate(key, randBytes(32)) } - root, nodes = trie.Commit(false) - db.Update(root, parent, trienode.NewWithNodeSet(nodes)) + root, nodes, _ = trie.Commit(false) + db.Update(root, parent, 0, trienode.NewWithNodeSet(nodes), nil) trie, _ = New(TrieID(root), db) if err := verifyAccessList(orig, trie, nodes); err != nil { @@ -184,8 +184,8 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) { for _, key := range keys { trie.MustUpdate([]byte(key), nil) } - root, nodes = trie.Commit(false) - db.Update(root, parent, trienode.NewWithNodeSet(nodes)) + root, nodes, _ = trie.Commit(false) + db.Update(root, parent, 0, trienode.NewWithNodeSet(nodes), nil) trie, _ = New(TrieID(root), db) if err := verifyAccessList(orig, trie, nodes); err != nil { @@ -199,8 +199,8 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) { for _, val := range vals { trie.MustUpdate([]byte(val.k), nil) } - root, nodes = trie.Commit(false) - db.Update(root, parent, trienode.NewWithNodeSet(nodes)) + root, nodes, _ = trie.Commit(false) + db.Update(root, parent, 0, trienode.NewWithNodeSet(nodes), nil) trie, _ = New(TrieID(root), db) if err := verifyAccessList(orig, trie, nodes); err != nil { @@ -211,29 +211,29 @@ func testAccessList(t *testing.T, vals []struct{ k, v string }) { // Tests origin values won't be tracked in Iterator or Prover func TestAccessListLeak(t *testing.T) { var ( - db = NewDatabase(rawdb.NewMemoryDatabase()) + db = NewDatabase(rawdb.NewMemoryDatabase(), nil) trie = NewEmpty(db) ) // Create trie from scratch for _, val := range standard { trie.MustUpdate([]byte(val.k), []byte(val.v)) } - root, nodes := trie.Commit(false) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := trie.Commit(false) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) var cases = []struct { op func(tr *Trie) }{ { func(tr *Trie) { - it := tr.NodeIterator(nil) + it := tr.MustNodeIterator(nil) for it.Next(true) { } }, }, { func(tr *Trie) { - it := NewIterator(tr.NodeIterator(nil)) + it := NewIterator(tr.MustNodeIterator(nil)) for it.Next() { } }, @@ -241,7 +241,7 @@ func TestAccessListLeak(t *testing.T) { { func(tr *Trie) { for _, val := range standard { - tr.Prove([]byte(val.k), 0, rawdb.NewMemoryDatabase()) + tr.Prove([]byte(val.k), rawdb.NewMemoryDatabase()) } }, }, @@ -262,14 +262,14 @@ func TestAccessListLeak(t *testing.T) { // in its parent due to the smaller size of the original tree node. func TestTinyTree(t *testing.T) { var ( - db = NewDatabase(rawdb.NewMemoryDatabase()) + db = NewDatabase(rawdb.NewMemoryDatabase(), nil) trie = NewEmpty(db) ) for _, val := range tiny { trie.MustUpdate([]byte(val.k), randBytes(32)) } - root, set := trie.Commit(false) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(set)) + root, set, _ := trie.Commit(false) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(set), nil) parent := root trie, _ = New(TrieID(root), db) @@ -277,8 +277,8 @@ func TestTinyTree(t *testing.T) { for _, val := range tiny { trie.MustUpdate([]byte(val.k), []byte(val.v)) } - root, set = trie.Commit(false) - db.Update(root, parent, trienode.NewWithNodeSet(set)) + root, set, _ = trie.Commit(false) + db.Update(root, parent, 0, trienode.NewWithNodeSet(set), nil) trie, _ = New(TrieID(root), db) if err := verifyAccessList(orig, trie, set); err != nil { @@ -300,7 +300,7 @@ func compareSet(setA, setB map[string]struct{}) bool { func forNodes(tr *Trie) map[string][]byte { var ( - it = tr.NodeIterator(nil) + it = tr.MustNodeIterator(nil) nodes = make(map[string][]byte) ) for it.Next(true) { @@ -319,7 +319,7 @@ func iterNodes(db *Database, root common.Hash) map[string][]byte { func forHashedNodes(tr *Trie) map[string][]byte { var ( - it = tr.NodeIterator(nil) + it = tr.MustNodeIterator(nil) nodes = make(map[string][]byte) ) for it.Next(true) { diff --git a/trie/trie.go b/trie/trie.go index e63b2d2830df..07467ac69c96 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -39,6 +39,10 @@ type Trie struct { root node owner common.Hash + // Flag whether the commit operation is already performed. If so the + // trie is not usable(latest states is invisible). + committed bool + // Keep track of the number leaves which have been inserted since the last // hashing operation. This number will not directly map to the number of // actually unhashed nodes. @@ -60,11 +64,12 @@ func (t *Trie) newFlag() nodeFlag { // Copy returns a copy of Trie. func (t *Trie) Copy() *Trie { return &Trie{ - root: t.root, - owner: t.owner, - unhashed: t.unhashed, - reader: t.reader, - tracer: t.tracer.copy(), + root: t.root, + owner: t.owner, + committed: t.committed, + unhashed: t.unhashed, + reader: t.reader, + tracer: t.tracer.copy(), } } @@ -74,7 +79,7 @@ func (t *Trie) Copy() *Trie { // zero hash or the sha3 hash of an empty string, then trie is initially // empty, otherwise, the root node must be present in database or returns // a MissingNodeError if not. -func New(id *ID, db NodeReader) (*Trie, error) { +func New(id *ID, db *Database) (*Trie, error) { reader, err := newTrieReader(id.StateRoot, id.Owner, db) if err != nil { return nil, err @@ -100,10 +105,24 @@ func NewEmpty(db *Database) *Trie { return tr } +// MustNodeIterator is a wrapper of NodeIterator and will omit any encountered +// error but just print out an error message. +func (t *Trie) MustNodeIterator(start []byte) NodeIterator { + it, err := t.NodeIterator(start) + if err != nil { + log.Error("Unhandled trie error in Trie.NodeIterator", "err", err) + } + return it +} + // NodeIterator returns an iterator that returns nodes of the trie. Iteration starts at // the key after the given start key. -func (t *Trie) NodeIterator(start []byte) NodeIterator { - return newNodeIterator(t, start) +func (t *Trie) NodeIterator(start []byte) (NodeIterator, error) { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return nil, ErrCommitted + } + return newNodeIterator(t, start), nil } // MustGet is a wrapper of Get and will omit any encountered error but just @@ -122,6 +141,10 @@ func (t *Trie) MustGet(key []byte) []byte { // If the requested node is not present in trie, no error will be returned. // If the trie is corrupted, a MissingNodeError is returned. func (t *Trie) Get(key []byte) ([]byte, error) { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return nil, ErrCommitted + } value, newroot, didResolve, err := t.get(t.root, keybytesToHex(key), 0) if err == nil && didResolve { t.root = newroot @@ -181,6 +204,10 @@ func (t *Trie) MustGetNode(path []byte) ([]byte, int) { // If the requested node is not present in trie, no error will be returned. // If the trie is corrupted, a MissingNodeError is returned. func (t *Trie) GetNode(path []byte) ([]byte, int, error) { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return nil, 0, ErrCommitted + } item, newroot, resolved, err := t.getNode(t.root, compactToHex(path), 0) if err != nil { return nil, resolved, err @@ -273,6 +300,10 @@ func (t *Trie) MustUpdate(key, value []byte) { // If the requested node is not present in trie, no error will be returned. // If the trie is corrupted, a MissingNodeError is returned. func (t *Trie) Update(key, value []byte) error { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return ErrCommitted + } return t.update(key, value) } @@ -387,6 +418,10 @@ func (t *Trie) MustDelete(key []byte) { // If the requested node is not present in trie, no error will be returned. // If the trie is corrupted, a MissingNodeError is returned. func (t *Trie) Delete(key []byte) error { + // Short circuit if the trie is already committed and not usable. + if t.committed { + return ErrCommitted + } t.unhashed++ k := keybytesToHex(key) _, n, err := t.delete(t.root, nil, k) @@ -572,17 +607,25 @@ func (t *Trie) Hash() common.Hash { // The returned nodeset can be nil if the trie is clean (nothing to commit). // Once the trie is committed, it's not usable anymore. A new trie must // be created with new root and updated trie database for following usage -func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) { +func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) { defer t.tracer.reset() - - nodes := trienode.NewNodeSet(t.owner) - t.tracer.markDeletions(nodes) - + defer func() { + t.committed = true + }() // Trie is empty and can be classified into two types of situations: - // - The trie was empty and no update happens - // - The trie was non-empty and all nodes are dropped + // (a) The trie was empty and no update happens => return nil + // (b) The trie was non-empty and all nodes are dropped => return + // the node set includes all deleted nodes if t.root == nil { - return types.EmptyRootHash, nodes + paths := t.tracer.deletedNodes() + if len(paths) == 0 { + return types.EmptyRootHash, nil, nil // case (a) + } + nodes := trienode.NewNodeSet(t.owner) + for _, path := range paths { + nodes.AddNode([]byte(path), trienode.NewDeleted()) + } + return types.EmptyRootHash, nodes, nil // case (b) } // Derive the hash for all dirty nodes first. We hold the assumption // in the following procedure that all nodes are hashed. @@ -594,10 +637,14 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) { // Replace the root node with the origin hash in order to // ensure all resolved nodes are dropped after the commit. t.root = hashedNode - return rootHash, nil + return rootHash, nil, nil + } + nodes := trienode.NewNodeSet(t.owner) + for _, path := range t.tracer.deletedNodes() { + nodes.AddNode([]byte(path), trienode.NewDeleted()) } t.root = newCommitter(nodes, t.tracer, collectLeaf).Commit(t.root) - return rootHash, nodes + return rootHash, nodes, nil } // hashRoot calculates the root hash of the given trie @@ -621,4 +668,5 @@ func (t *Trie) Reset() { t.owner = common.Hash{} t.unhashed = 0 t.tracer.reset() + t.committed = false } diff --git a/trie/trie_reader.go b/trie/trie_reader.go index 51855320a662..42159645590f 100644 --- a/trie/trie_reader.go +++ b/trie/trie_reader.go @@ -17,26 +17,25 @@ package trie import ( - "fmt" - "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie/triestate" ) // Reader wraps the Node method of a backing trie store. type Reader interface { - // Node retrieves the RLP-encoded trie node blob with the provided trie - // identifier, node path and the corresponding node hash. No error will - // be returned if the node is not found. + // Node retrieves the trie node blob with the provided trie identifier, node path and + // the corresponding node hash. No error will be returned if the node is not found. + // + // When looking up nodes in the account trie, 'owner' is the zero hash. For contract + // storage trie nodes, 'owner' is the hash of the account address that containing the + // storage. + // + // TODO(rjl493456442): remove the 'hash' parameter, it's redundant in PBSS. Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) } -// NodeReader wraps all the necessary functions for accessing trie node. -type NodeReader interface { - // Reader returns a reader for accessing all trie nodes with provided - // state root. Nil is returned in case the state is not available. - Reader(root common.Hash) Reader -} - // trieReader is a wrapper of the underlying node reader. It's not safe // for concurrent usage. type trieReader struct { @@ -46,10 +45,16 @@ type trieReader struct { } // newTrieReader initializes the trie reader with the given node reader. -func newTrieReader(stateRoot, owner common.Hash, db NodeReader) (*trieReader, error) { - reader := db.Reader(stateRoot) - if reader == nil { - return nil, fmt.Errorf("state not found #%x", stateRoot) +func newTrieReader(stateRoot, owner common.Hash, db *Database) (*trieReader, error) { + if stateRoot == (common.Hash{}) || stateRoot == types.EmptyRootHash { + if stateRoot == (common.Hash{}) { + log.Error("Zero state root hash!") + } + return &trieReader{owner: owner}, nil + } + reader, err := db.Reader(stateRoot) + if err != nil { + return nil, &MissingNodeError{Owner: owner, NodeHash: stateRoot, err: err} } return &trieReader{owner: owner, reader: reader}, nil } @@ -79,3 +84,18 @@ func (r *trieReader) node(path []byte, hash common.Hash) ([]byte, error) { } return blob, nil } + +// trieLoader implements triestate.TrieLoader for constructing tries. +type trieLoader struct { + db *Database +} + +// OpenTrie opens the main account trie. +func (l *trieLoader) OpenTrie(root common.Hash) (triestate.Trie, error) { + return New(TrieID(root), l.db) +} + +// OpenStorageTrie opens the storage trie of an account. +func (l *trieLoader) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (triestate.Trie, error) { + return New(StorageTrieID(stateRoot, addrHash, root), l.db) +} diff --git a/trie/trie_test.go b/trie/trie_test.go index 70de46022765..2dfe81ef81d2 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -45,7 +45,7 @@ func init() { } func TestEmptyTrie(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) res := trie.Hash() exp := types.EmptyRootHash if res != exp { @@ -54,7 +54,7 @@ func TestEmptyTrie(t *testing.T) { } func TestNull(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) key := make([]byte, 32) value := []byte("test") trie.MustUpdate(key, value) @@ -64,8 +64,13 @@ func TestNull(t *testing.T) { } func TestMissingRoot(t *testing.T) { + testMissingRoot(t, rawdb.HashScheme) + testMissingRoot(t, rawdb.PathScheme) +} + +func testMissingRoot(t *testing.T, scheme string) { root := common.HexToHash("0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33") - trie, err := New(TrieID(root), NewDatabase(rawdb.NewMemoryDatabase())) + trie, err := New(TrieID(root), newTestDatabase(rawdb.NewMemoryDatabase(), scheme)) if trie != nil { t.Error("New returned non-nil trie for invalid root") } @@ -76,9 +81,9 @@ func TestMissingRoot(t *testing.T) { func TestMissingNode(t *testing.T) { testMissingNode(t, false, rawdb.HashScheme) - //testMissingNode(t, false, rawdb.PathScheme) + testMissingNode(t, false, rawdb.PathScheme) testMissingNode(t, true, rawdb.HashScheme) - //testMissingNode(t, true, rawdb.PathScheme) + testMissingNode(t, true, rawdb.PathScheme) } func testMissingNode(t *testing.T, memonly bool, scheme string) { @@ -88,8 +93,8 @@ func testMissingNode(t *testing.T, memonly bool, scheme string) { trie := NewEmpty(triedb) updateString(trie, "120000", "qwerqwerqwerqwerqwerqwerqwerqwer") updateString(trie, "123456", "asdfasdfasdfasdfasdfasdfasdfasdf") - root, nodes := trie.Commit(false) - triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := trie.Commit(false) + triedb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) if !memonly { triedb.Commit(root, false) @@ -161,7 +166,7 @@ func testMissingNode(t *testing.T, memonly bool, scheme string) { } func TestInsert(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) updateString(trie, "doe", "reindeer") updateString(trie, "dog", "puppy") @@ -173,18 +178,18 @@ func TestInsert(t *testing.T) { t.Errorf("case 1: exp %x got %x", exp, root) } - trie = NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie = NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) updateString(trie, "A", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") exp = common.HexToHash("d23786fb4a010da3ce639d66d5e904a11dbc02746d1ce25029e53290cabf28ab") - root, _ = trie.Commit(false) + root, _, _ = trie.Commit(false) if root != exp { t.Errorf("case 2: exp %x got %x", exp, root) } } func TestGet(t *testing.T) { - db := NewDatabase(rawdb.NewMemoryDatabase()) + db := NewDatabase(rawdb.NewMemoryDatabase(), nil) trie := NewEmpty(db) updateString(trie, "doe", "reindeer") updateString(trie, "dog", "puppy") @@ -202,14 +207,14 @@ func TestGet(t *testing.T) { if i == 1 { return } - root, nodes := trie.Commit(false) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := trie.Commit(false) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) trie, _ = New(TrieID(root), db) } } func TestDelete(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) vals := []struct{ k, v string }{ {"do", "verb"}, {"ether", "wookiedoo"}, @@ -236,7 +241,7 @@ func TestDelete(t *testing.T) { } func TestEmptyValues(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) vals := []struct{ k, v string }{ {"do", "verb"}, @@ -260,7 +265,7 @@ func TestEmptyValues(t *testing.T) { } func TestReplication(t *testing.T) { - db := NewDatabase(rawdb.NewMemoryDatabase()) + db := NewDatabase(rawdb.NewMemoryDatabase(), nil) trie := NewEmpty(db) vals := []struct{ k, v string }{ {"do", "verb"}, @@ -274,8 +279,8 @@ func TestReplication(t *testing.T) { for _, val := range vals { updateString(trie, val.k, val.v) } - root, nodes := trie.Commit(false) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := trie.Commit(false) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) // create a new trie on top of the database and check that lookups work. trie2, err := New(TrieID(root), db) @@ -287,14 +292,14 @@ func TestReplication(t *testing.T) { t.Errorf("trie2 doesn't have %q => %q", kv.k, kv.v) } } - hash, nodes := trie2.Commit(false) + hash, nodes, _ := trie2.Commit(false) if hash != root { t.Errorf("root failure. expected %x got %x", root, hash) } // recreate the trie after commit if nodes != nil { - db.Update(hash, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + db.Update(hash, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) } trie2, err = New(TrieID(hash), db) if err != nil { @@ -321,7 +326,7 @@ func TestReplication(t *testing.T) { } func TestLargeValue(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) trie.MustUpdate([]byte("key1"), []byte{99, 99, 99, 99}) trie.MustUpdate([]byte("key2"), bytes.Repeat([]byte{1}, 32)) trie.Hash() @@ -422,44 +427,44 @@ func verifyAccessList(old *Trie, new *Trie, set *trienode.NodeSet) error { if !ok || n.IsDeleted() { return errors.New("expect new node") } - if len(n.Prev) > 0 { - return errors.New("unexpected origin value") - } + //if len(n.Prev) > 0 { + // return errors.New("unexpected origin value") + //} } // Check deletion set - for path, blob := range deletes { + for path := range deletes { n, ok := set.Nodes[path] if !ok || !n.IsDeleted() { return errors.New("expect deleted node") } - if len(n.Prev) == 0 { - return errors.New("expect origin value") - } - if !bytes.Equal(n.Prev, blob) { - return errors.New("invalid origin value") - } + //if len(n.Prev) == 0 { + // return errors.New("expect origin value") + //} + //if !bytes.Equal(n.Prev, blob) { + // return errors.New("invalid origin value") + //} } // Check update set - for path, blob := range updates { + for path := range updates { n, ok := set.Nodes[path] if !ok || n.IsDeleted() { return errors.New("expect updated node") } - if len(n.Prev) == 0 { - return errors.New("expect origin value") - } - if !bytes.Equal(n.Prev, blob) { - return errors.New("invalid origin value") - } + //if len(n.Prev) == 0 { + // return errors.New("expect origin value") + //} + //if !bytes.Equal(n.Prev, blob) { + // return errors.New("invalid origin value") + //} } return nil } func runRandTest(rt randTest) bool { var scheme = rawdb.HashScheme - //if rand.Intn(2) == 0 { - // scheme = rawdb.PathScheme - //} + if rand.Intn(2) == 0 { + scheme = rawdb.PathScheme + } var ( origin = types.EmptyRootHash triedb = newTestDatabase(rawdb.NewMemoryDatabase(), scheme) @@ -490,7 +495,7 @@ func runRandTest(rt randTest) bool { continue } proofDb := rawdb.NewMemoryDatabase() - err := tr.Prove(step.key, 0, proofDb) + err := tr.Prove(step.key, proofDb) if err != nil { rt[i].err = fmt.Errorf("failed for proving key %#x, %v", step.key, err) } @@ -501,9 +506,9 @@ func runRandTest(rt randTest) bool { case opHash: tr.Hash() case opCommit: - root, nodes := tr.Commit(true) + root, nodes, _ := tr.Commit(true) if nodes != nil { - triedb.Update(root, origin, trienode.NewWithNodeSet(nodes)) + triedb.Update(root, origin, 0, trienode.NewWithNodeSet(nodes), nil) } newtr, err := New(TrieID(root), triedb) if err != nil { @@ -521,7 +526,7 @@ func runRandTest(rt randTest) bool { origin = root case opItercheckhash: checktr := NewEmpty(triedb) - it := NewIterator(tr.NodeIterator(nil)) + it := NewIterator(tr.MustNodeIterator(nil)) for it.Next() { checktr.MustUpdate(it.Key, it.Value) } @@ -530,8 +535,8 @@ func runRandTest(rt randTest) bool { } case opNodeDiff: var ( - origIter = origTrie.NodeIterator(nil) - curIter = tr.NodeIterator(nil) + origIter = origTrie.MustNodeIterator(nil) + curIter = tr.MustNodeIterator(nil) origSeen = make(map[string]struct{}) curSeen = make(map[string]struct{}) ) @@ -604,12 +609,14 @@ func BenchmarkUpdateLE(b *testing.B) { benchUpdate(b, binary.LittleEndian) } const benchElemCount = 20000 func benchGet(b *testing.B) { - triedb := NewDatabase(rawdb.NewMemoryDatabase()) + triedb := NewDatabase(rawdb.NewMemoryDatabase(), nil) trie := NewEmpty(triedb) k := make([]byte, 32) for i := 0; i < benchElemCount; i++ { binary.LittleEndian.PutUint64(k, uint64(i)) - trie.MustUpdate(k, k) + v := make([]byte, 32) + binary.LittleEndian.PutUint64(v, uint64(i)) + trie.MustUpdate(k, v) } binary.LittleEndian.PutUint64(k, benchElemCount/2) @@ -621,12 +628,14 @@ func benchGet(b *testing.B) { } func benchUpdate(b *testing.B, e binary.ByteOrder) *Trie { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) k := make([]byte, 32) b.ReportAllocs() for i := 0; i < b.N; i++ { + v := make([]byte, 32) e.PutUint64(k, uint64(i)) - trie.MustUpdate(k, k) + e.PutUint64(v, uint64(i)) + trie.MustUpdate(k, v) } return trie } @@ -651,7 +660,7 @@ func BenchmarkHash(b *testing.B) { // entries, then adding N more. addresses, accounts := makeAccounts(2 * b.N) // Insert the accounts into the trie and hash it - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) i := 0 for ; i < len(addresses)/2; i++ { trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) @@ -682,7 +691,7 @@ func BenchmarkCommitAfterHash(b *testing.B) { func benchmarkCommitAfterHash(b *testing.B, collectLeaf bool) { // Make the random benchmark deterministic addresses, accounts := makeAccounts(b.N) - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) for i := 0; i < len(addresses); i++ { trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) } @@ -696,7 +705,7 @@ func benchmarkCommitAfterHash(b *testing.B, collectLeaf bool) { func TestTinyTrie(t *testing.T) { // Create a realistic account trie to hash _, accounts := makeAccounts(5) - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) trie.MustUpdate(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000001337"), accounts[3]) if exp, root := common.HexToHash("8c6a85a4d9fda98feff88450299e574e5378e32391f75a055d470ac0653f1005"), trie.Hash(); exp != root { t.Errorf("1: got %x, exp %x", root, exp) @@ -709,8 +718,8 @@ func TestTinyTrie(t *testing.T) { if exp, root := common.HexToHash("0608c1d1dc3905fa22204c7a0e43644831c3b6d3def0f274be623a948197e64a"), trie.Hash(); exp != root { t.Errorf("3: got %x, exp %x", root, exp) } - checktr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) - it := NewIterator(trie.NodeIterator(nil)) + checktr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) + it := NewIterator(trie.MustNodeIterator(nil)) for it.Next() { checktr.MustUpdate(it.Key, it.Value) } @@ -722,7 +731,7 @@ func TestTinyTrie(t *testing.T) { func TestCommitAfterHash(t *testing.T) { // Create a realistic account trie to hash addresses, accounts := makeAccounts(1000) - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) for i := 0; i < len(addresses); i++ { trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) } @@ -734,7 +743,7 @@ func TestCommitAfterHash(t *testing.T) { if exp != root { t.Errorf("got %x, exp %x", root, exp) } - root, _ = trie.Commit(false) + root, _, _ = trie.Commit(false) if exp != root { t.Errorf("got %x, exp %x", root, exp) } @@ -788,11 +797,17 @@ func (s *spongeDb) Stat(property string) (string, error) { panic("implement func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") } func (s *spongeDb) Close() error { return nil } func (s *spongeDb) Put(key []byte, value []byte) error { - valbrief := value + var ( + keybrief = key + valbrief = value + ) + if len(keybrief) > 8 { + keybrief = keybrief[:8] + } if len(valbrief) > 8 { valbrief = valbrief[:8] } - s.journal = append(s.journal, fmt.Sprintf("%v: PUT([%x...], [%d bytes] %x...)\n", s.id, key[:8], len(value), valbrief)) + s.journal = append(s.journal, fmt.Sprintf("%v: PUT([%x...], [%d bytes] %x...)\n", s.id, keybrief, len(value), valbrief)) s.sponge.Write(key) s.sponge.Write(value) return nil @@ -830,15 +845,15 @@ func TestCommitSequence(t *testing.T) { addresses, accounts := makeAccounts(tc.count) // This spongeDb is used to check the sequence of disk-db-writes s := &spongeDb{sponge: sha3.NewLegacyKeccak256()} - db := NewDatabase(rawdb.NewDatabase(s)) + db := NewDatabase(rawdb.NewDatabase(s), nil) trie := NewEmpty(db) // Fill the trie with elements for i := 0; i < tc.count; i++ { trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) } // Flush trie -> database - root, nodes := trie.Commit(false) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := trie.Commit(false) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) // Flush memdb -> disk (sponge) db.Commit(root, false) if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) { @@ -861,7 +876,7 @@ func TestCommitSequenceRandomBlobs(t *testing.T) { prng := rand.New(rand.NewSource(int64(i))) // This spongeDb is used to check the sequence of disk-db-writes s := &spongeDb{sponge: sha3.NewLegacyKeccak256()} - db := NewDatabase(rawdb.NewDatabase(s)) + db := NewDatabase(rawdb.NewDatabase(s), nil) trie := NewEmpty(db) // Fill the trie with elements for i := 0; i < tc.count; i++ { @@ -878,8 +893,8 @@ func TestCommitSequenceRandomBlobs(t *testing.T) { trie.MustUpdate(key, val) } // Flush trie -> database - root, nodes := trie.Commit(false) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := trie.Commit(false) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) // Flush memdb -> disk (sponge) db.Commit(root, false) if got, exp := s.sponge.Sum(nil), tc.expWriteSeqHash; !bytes.Equal(got, exp) { @@ -893,12 +908,12 @@ func TestCommitSequenceStackTrie(t *testing.T) { prng := rand.New(rand.NewSource(int64(count))) // This spongeDb is used to check the sequence of disk-db-writes s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"} - db := NewDatabase(rawdb.NewDatabase(s)) + db := NewDatabase(rawdb.NewDatabase(s), nil) trie := NewEmpty(db) // Another sponge is used for the stacktrie commits stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"} - stTrie := NewStackTrie(func(owner common.Hash, path []byte, hash common.Hash, blob []byte) { - rawdb.WriteTrieNode(stackTrieSponge, owner, path, hash, blob, db.Scheme()) + stTrie := NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(stackTrieSponge, common.Hash{}, path, hash, blob, db.Scheme()) }) // Fill the trie with elements for i := 0; i < count; i++ { @@ -917,9 +932,9 @@ func TestCommitSequenceStackTrie(t *testing.T) { stTrie.Update(key, val) } // Flush trie -> database - root, nodes := trie.Commit(false) + root, nodes, _ := trie.Commit(false) // Flush memdb -> disk (sponge) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) db.Commit(root, false) // And flush stacktrie -> disk stRoot, err := stTrie.Commit() @@ -952,12 +967,12 @@ func TestCommitSequenceStackTrie(t *testing.T) { // not fit into 32 bytes, rlp-encoded. However, it's still the correct thing to do. func TestCommitSequenceSmallRoot(t *testing.T) { s := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "a"} - db := NewDatabase(rawdb.NewDatabase(s)) + db := NewDatabase(rawdb.NewDatabase(s), nil) trie := NewEmpty(db) // Another sponge is used for the stacktrie commits stackTrieSponge := &spongeDb{sponge: sha3.NewLegacyKeccak256(), id: "b"} - stTrie := NewStackTrie(func(owner common.Hash, path []byte, hash common.Hash, blob []byte) { - rawdb.WriteTrieNode(stackTrieSponge, owner, path, hash, blob, db.Scheme()) + stTrie := NewStackTrie(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(stackTrieSponge, common.Hash{}, path, hash, blob, db.Scheme()) }) // Add a single small-element to the trie(s) key := make([]byte, 5) @@ -965,9 +980,9 @@ func TestCommitSequenceSmallRoot(t *testing.T) { trie.Update(key, []byte{0x1}) stTrie.Update(key, []byte{0x1}) // Flush trie -> database - root, nodes := trie.Commit(false) + root, nodes, _ := trie.Commit(false) // Flush memdb -> disk (sponge) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) db.Commit(root, false) // And flush stacktrie -> disk stRoot, err := stTrie.Commit() @@ -1029,7 +1044,7 @@ func BenchmarkHashFixedSize(b *testing.B) { func benchmarkHashFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) { b.ReportAllocs() - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) for i := 0; i < len(addresses); i++ { trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) } @@ -1080,7 +1095,7 @@ func BenchmarkCommitAfterHashFixedSize(b *testing.B) { func benchmarkCommitAfterHashFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) { b.ReportAllocs() - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) for i := 0; i < len(addresses); i++ { trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) } @@ -1132,14 +1147,14 @@ func BenchmarkDerefRootFixedSize(b *testing.B) { func benchmarkDerefRootFixedSize(b *testing.B, addresses [][20]byte, accounts [][]byte) { b.ReportAllocs() - triedb := NewDatabase(rawdb.NewMemoryDatabase()) + triedb := NewDatabase(rawdb.NewMemoryDatabase(), nil) trie := NewEmpty(triedb) for i := 0; i < len(addresses); i++ { trie.MustUpdate(crypto.Keccak256(addresses[i][:]), accounts[i]) } h := trie.Hash() - root, nodes := trie.Commit(false) - triedb.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) + root, nodes, _ := trie.Commit(false) + triedb.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) b.StartTimer() triedb.Dereference(h) b.StopTimer() diff --git a/trie/triedb/hashdb/database.go b/trie/triedb/hashdb/database.go index 8ebcff9f1f1e..be83c32e65c2 100644 --- a/trie/triedb/hashdb/database.go +++ b/trie/triedb/hashdb/database.go @@ -18,6 +18,7 @@ package hashdb import ( "errors" + "fmt" "reflect" "sync" "time" @@ -31,30 +32,31 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" ) var ( - memcacheCleanHitMeter = metrics.NewRegisteredMeter("trie/memcache/clean/hit", nil) - memcacheCleanMissMeter = metrics.NewRegisteredMeter("trie/memcache/clean/miss", nil) - memcacheCleanReadMeter = metrics.NewRegisteredMeter("trie/memcache/clean/read", nil) - memcacheCleanWriteMeter = metrics.NewRegisteredMeter("trie/memcache/clean/write", nil) - - memcacheDirtyHitMeter = metrics.NewRegisteredMeter("trie/memcache/dirty/hit", nil) - memcacheDirtyMissMeter = metrics.NewRegisteredMeter("trie/memcache/dirty/miss", nil) - memcacheDirtyReadMeter = metrics.NewRegisteredMeter("trie/memcache/dirty/read", nil) - memcacheDirtyWriteMeter = metrics.NewRegisteredMeter("trie/memcache/dirty/write", nil) - - memcacheFlushTimeTimer = metrics.NewRegisteredResettingTimer("trie/memcache/flush/time", nil) - memcacheFlushNodesMeter = metrics.NewRegisteredMeter("trie/memcache/flush/nodes", nil) - memcacheFlushSizeMeter = metrics.NewRegisteredMeter("trie/memcache/flush/size", nil) - - memcacheGCTimeTimer = metrics.NewRegisteredResettingTimer("trie/memcache/gc/time", nil) - memcacheGCNodesMeter = metrics.NewRegisteredMeter("trie/memcache/gc/nodes", nil) - memcacheGCSizeMeter = metrics.NewRegisteredMeter("trie/memcache/gc/size", nil) - - memcacheCommitTimeTimer = metrics.NewRegisteredResettingTimer("trie/memcache/commit/time", nil) - memcacheCommitNodesMeter = metrics.NewRegisteredMeter("trie/memcache/commit/nodes", nil) - memcacheCommitSizeMeter = metrics.NewRegisteredMeter("trie/memcache/commit/size", nil) + memcacheCleanHitMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/hit", nil) + memcacheCleanMissMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/miss", nil) + memcacheCleanReadMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/read", nil) + memcacheCleanWriteMeter = metrics.NewRegisteredMeter("hashdb/memcache/clean/write", nil) + + memcacheDirtyHitMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/hit", nil) + memcacheDirtyMissMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/miss", nil) + memcacheDirtyReadMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/read", nil) + memcacheDirtyWriteMeter = metrics.NewRegisteredMeter("hashdb/memcache/dirty/write", nil) + + memcacheFlushTimeTimer = metrics.NewRegisteredResettingTimer("hashdb/memcache/flush/time", nil) + memcacheFlushNodesMeter = metrics.NewRegisteredMeter("hashdb/memcache/flush/nodes", nil) + memcacheFlushBytesMeter = metrics.NewRegisteredMeter("hashdb/memcache/flush/bytes", nil) + + memcacheGCTimeTimer = metrics.NewRegisteredResettingTimer("hashdb/memcache/gc/time", nil) + memcacheGCNodesMeter = metrics.NewRegisteredMeter("hashdb/memcache/gc/nodes", nil) + memcacheGCBytesMeter = metrics.NewRegisteredMeter("hashdb/memcache/gc/bytes", nil) + + memcacheCommitTimeTimer = metrics.NewRegisteredResettingTimer("hashdb/memcache/commit/time", nil) + memcacheCommitNodesMeter = metrics.NewRegisteredMeter("hashdb/memcache/commit/nodes", nil) + memcacheCommitBytesMeter = metrics.NewRegisteredMeter("hashdb/memcache/commit/bytes", nil) ) // ChildResolver defines the required method to decode the provided @@ -63,6 +65,20 @@ type ChildResolver interface { ForEach(node []byte, onChild func(common.Hash)) } +// Config contains the settings for database. +type Config struct { + CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes +} + +// Defaults is the default setting for database if it's not specified. +// Notably, clean cache is disabled explicitly, +var Defaults = &Config{ + // Explicitly set clean cache size to 0 to avoid creating fastcache, + // otherwise database must be closed when it's no longer needed to + // prevent memory leak. + CleanCacheSize: 0, +} + // Database is an intermediate write layer between the trie data structures and // the disk database. The aim is to accumulate trie writes in-memory and only // periodically flush a couple tries to disk, garbage collecting the remainder. @@ -120,7 +136,14 @@ func (n *cachedNode) forChildren(resolver ChildResolver, onChild func(hash commo } // New initializes the hash-based node database. -func New(diskdb ethdb.Database, cleans *fastcache.Cache, resolver ChildResolver) *Database { +func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Database { + if config == nil { + config = Defaults + } + var cleans *fastcache.Cache + if config.CleanCacheSize > 0 { + cleans = fastcache.New(config.CleanCacheSize) + } return &Database{ diskdb: diskdb, resolver: resolver, @@ -268,7 +291,7 @@ func (db *Database) Dereference(root common.Hash) { db.gctime += time.Since(start) memcacheGCTimeTimer.Update(time.Since(start)) - memcacheGCSizeMeter.Mark(int64(storage - db.dirtiesSize)) + memcacheGCBytesMeter.Mark(int64(storage - db.dirtiesSize)) memcacheGCNodesMeter.Mark(int64(nodes - len(db.dirties))) log.Debug("Dereferenced trie from memory database", "nodes", nodes-len(db.dirties), "size", storage-db.dirtiesSize, "time", time.Since(start), @@ -329,20 +352,25 @@ func (db *Database) Cap(limit common.StorageSize) error { // outside code doesn't see an inconsistent state (referenced data removed from // memory cache during commit but not yet in persistent storage). This is ensured // by only uncaching existing data when the database write finalizes. - nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now() + start := time.Now() batch := db.diskdb.NewBatch() + db.lock.RLock() + nodes, storage := len(db.dirties), db.dirtiesSize // db.dirtiesSize only contains the useful data in the cache, but when reporting // the total memory consumption, the maintenance metadata is also needed to be // counted. size := db.dirtiesSize + common.StorageSize(len(db.dirties)*cachedNodeSize) size += db.childrenSize + db.lock.RUnlock() // Keep committing nodes from the flush-list until we're below allowance oldest := db.oldest for size > limit && oldest != (common.Hash{}) { // Fetch the oldest referenced node and push into the batch + db.lock.RLock() node := db.dirties[oldest] + db.lock.RUnlock() rawdb.WriteLegacyTrieNode(batch, oldest, node.node) // If we exceeded the ideal batch size, commit and reset @@ -389,7 +417,7 @@ func (db *Database) Cap(limit common.StorageSize) error { db.flushtime += time.Since(start) memcacheFlushTimeTimer.Update(time.Since(start)) - memcacheFlushSizeMeter.Mark(int64(storage - db.dirtiesSize)) + memcacheFlushBytesMeter.Mark(int64(storage - db.dirtiesSize)) memcacheFlushNodesMeter.Mark(int64(nodes - len(db.dirties))) log.Debug("Persisted nodes from memory database", "nodes", nodes-len(db.dirties), "size", storage-db.dirtiesSize, "time", time.Since(start), @@ -418,7 +446,9 @@ func (db *Database) Commit(node common.Hash, report bool) error { batch := db.diskdb.NewBatch() // Move the trie itself into the batch, flushing if enough data is accumulated + db.lock.RLock() nodes, storage := len(db.dirties), db.dirtiesSize + db.lock.RUnlock() uncacher := &cleaner{db} if err := db.commit(node, batch, uncacher); err != nil { @@ -440,7 +470,7 @@ func (db *Database) Commit(node common.Hash, report bool) error { // Reset the storage counters and bumped metrics memcacheCommitTimeTimer.Update(time.Since(start)) - memcacheCommitSizeMeter.Mark(int64(storage - db.dirtiesSize)) + memcacheCommitBytesMeter.Mark(int64(storage - db.dirtiesSize)) memcacheCommitNodesMeter.Mark(int64(nodes - len(db.dirties))) logger := log.Info @@ -460,7 +490,9 @@ func (db *Database) Commit(node common.Hash, report bool) error { // commit is the private locked version of Commit. func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleaner) error { // If the node does not exist, it's a previously committed node + db.lock.RLock() node, ok := db.dirties[hash] + db.lock.RUnlock() if !ok { return nil } @@ -553,7 +585,7 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool { // Update inserts the dirty nodes in provided nodeset into database and link the // account trie with multiple storage tries if necessary. -func (db *Database) Update(root common.Hash, parent common.Hash, nodes *trienode.MergedNodeSet) error { +func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { // Ensure the parent state is present and signal a warning if not. if parent != types.EmptyRootHash { if blob, _ := db.Node(parent); len(blob) == 0 { @@ -606,7 +638,10 @@ func (db *Database) Update(root common.Hash, parent common.Hash, nodes *trienode // Size returns the current storage size of the memory cache in front of the // persistent database layer. -func (db *Database) Size() common.StorageSize { +// +// The first return will always be 0, representing the memory stored in unbounded +// diff layers above the dirty cache. This is only available in pathdb. +func (db *Database) Size() (common.StorageSize, common.StorageSize) { db.lock.RLock() defer db.lock.RUnlock() @@ -614,11 +649,17 @@ func (db *Database) Size() common.StorageSize { // the total memory consumption, the maintenance metadata is also needed to be // counted. var metadataSize = common.StorageSize(len(db.dirties) * cachedNodeSize) - return db.dirtiesSize + db.childrenSize + metadataSize + return 0, db.dirtiesSize + db.childrenSize + metadataSize } // Close closes the trie database and releases all held resources. -func (db *Database) Close() error { return nil } +func (db *Database) Close() error { + if db.cleans != nil { + db.cleans.Reset() + db.cleans = nil + } + return nil +} // Scheme returns the node scheme used in the database. func (db *Database) Scheme() string { @@ -626,8 +667,12 @@ func (db *Database) Scheme() string { } // Reader retrieves a node reader belonging to the given state root. -func (db *Database) Reader(root common.Hash) *reader { - return &reader{db: db} +// An error will be returned if the requested state is not available. +func (db *Database) Reader(root common.Hash) (*reader, error) { + if _, err := db.Node(root); err != nil { + return nil, fmt.Errorf("state %#x is not available, %v", root, err) + } + return &reader{db: db}, nil } // reader is a state reader of Database which implements the Reader interface. diff --git a/trie/triedb/pathdb/database.go b/trie/triedb/pathdb/database.go new file mode 100644 index 000000000000..dc64414e9b52 --- /dev/null +++ b/trie/triedb/pathdb/database.go @@ -0,0 +1,465 @@ +// Copyright 2022 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 pathdb + +import ( + "errors" + "fmt" + "io" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +const ( + // maxDiffLayers is the maximum diff layers allowed in the layer tree. + maxDiffLayers = 128 + + // defaultCleanSize is the default memory allowance of clean cache. + defaultCleanSize = 16 * 1024 * 1024 + + // maxBufferSize is the maximum memory allowance of node buffer. + // Too large nodebuffer will cause the system to pause for a long + // time when write happens. Also, the largest batch that pebble can + // support is 4GB, node will panic if batch size exceeds this limit. + maxBufferSize = 256 * 1024 * 1024 + + // DefaultBufferSize is the default memory allowance of node buffer + // that aggregates the writes from above until it's flushed into the + // disk. It's meant to be used once the initial sync is finished. + // Do not increase the buffer size arbitrarily, otherwise the system + // pause time will increase when the database writes happen. + DefaultBufferSize = 64 * 1024 * 1024 +) + +// layer is the interface implemented by all state layers which includes some +// public methods and some additional methods for internal usage. +type layer interface { + // Node retrieves the trie node with the node info. An error will be returned + // if the read operation exits abnormally. For example, if the layer is already + // stale, or the associated state is regarded as corrupted. Notably, no error + // will be returned if the requested node is not found in database. + Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) + + // rootHash returns the root hash for which this layer was made. + rootHash() common.Hash + + // stateID returns the associated state id of layer. + stateID() uint64 + + // parentLayer returns the subsequent layer of it, or nil if the disk was reached. + parentLayer() layer + + // update creates a new layer on top of the existing layer diff tree with + // the provided dirty trie nodes along with the state change set. + // + // Note, the maps are retained by the method to avoid copying everything. + update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer + + // journal commits an entire diff hierarchy to disk into a single journal entry. + // This is meant to be used during shutdown to persist the layer without + // flattening everything down (bad for reorgs). + journal(w io.Writer) error +} + +// Config contains the settings for database. +type Config struct { + StateHistory uint64 // Number of recent blocks to maintain state history for + CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes + DirtyCacheSize int // Maximum memory allowance (in bytes) for caching dirty nodes + ReadOnly bool // Flag whether the database is opened in read only mode. +} + +// sanitize checks the provided user configurations and changes anything that's +// unreasonable or unworkable. +func (c *Config) sanitize() *Config { + conf := *c + if conf.DirtyCacheSize > maxBufferSize { + log.Warn("Sanitizing invalid node buffer size", "provided", common.StorageSize(conf.DirtyCacheSize), "updated", common.StorageSize(maxBufferSize)) + conf.DirtyCacheSize = maxBufferSize + } + return &conf +} + +// Defaults contains default settings for Ethereum mainnet. +var Defaults = &Config{ + StateHistory: params.FullImmutabilityThreshold, + CleanCacheSize: defaultCleanSize, + DirtyCacheSize: DefaultBufferSize, +} + +// ReadOnly is the config in order to open database in read only mode. +var ReadOnly = &Config{ReadOnly: true} + +// Database is a multiple-layered structure for maintaining in-memory trie nodes. +// It consists of one persistent base layer backed by a key-value store, on top +// of which arbitrarily many in-memory diff layers are stacked. The memory diffs +// can form a tree with branching, but the disk layer is singleton and common to +// all. If a reorg goes deeper than the disk layer, a batch of reverse diffs can +// be applied to rollback. The deepest reorg that can be handled depends on the +// amount of state histories tracked in the disk. +// +// At most one readable and writable database can be opened at the same time in +// the whole system which ensures that only one database writer can operate disk +// state. Unexpected open operations can cause the system to panic. +type Database struct { + // readOnly is the flag whether the mutation is allowed to be applied. + // It will be set automatically when the database is journaled during + // the shutdown to reject all following unexpected mutations. + readOnly bool // Flag if database is opened in read only mode + waitSync bool // Flag if database is deactivated due to initial state sync + bufferSize int // Memory allowance (in bytes) for caching dirty nodes + config *Config // Configuration for database + diskdb ethdb.Database // Persistent storage for matured trie nodes + tree *layerTree // The group for all known layers + freezer *rawdb.ResettableFreezer // Freezer for storing trie histories, nil possible in tests + lock sync.RWMutex // Lock to prevent mutations from happening at the same time +} + +// New attempts to load an already existing layer from a persistent key-value +// store (with a number of memory layers from a journal). If the journal is not +// matched with the base persistent layer, all the recorded diff layers are discarded. +func New(diskdb ethdb.Database, config *Config) *Database { + if config == nil { + config = Defaults + } + config = config.sanitize() + + db := &Database{ + readOnly: config.ReadOnly, + bufferSize: config.DirtyCacheSize, + config: config, + diskdb: diskdb, + } + // Construct the layer tree by resolving the in-disk singleton state + // and in-memory layer journal. + db.tree = newLayerTree(db.loadLayers()) + + // Open the freezer for state history if the passed database contains an + // ancient store. Otherwise, all the relevant functionalities are disabled. + // + // Because the freezer can only be opened once at the same time, this + // mechanism also ensures that at most one **non-readOnly** database + // is opened at the same time to prevent accidental mutation. + if ancient, err := diskdb.AncientDatadir(); err == nil && ancient != "" && !db.readOnly { + freezer, err := rawdb.NewStateFreezer(ancient, false) + if err != nil { + log.Crit("Failed to open state history freezer", "err", err) + } + db.freezer = freezer + + // Truncate the extra state histories above in freezer in case + // it's not aligned with the disk layer. + pruned, err := truncateFromHead(db.diskdb, freezer, db.tree.bottom().stateID()) + if err != nil { + log.Crit("Failed to truncate extra state histories", "err", err) + } + if pruned != 0 { + log.Warn("Truncated extra state histories", "number", pruned) + } + } + // Disable database in case node is still in the initial state sync stage. + if rawdb.ReadSnapSyncStatusFlag(diskdb) == rawdb.StateSyncRunning && !db.readOnly { + if err := db.Disable(); err != nil { + log.Crit("Failed to disable database", "err", err) // impossible to happen + } + } + log.Warn("Path-based state scheme is an experimental feature") + return db +} + +// Reader retrieves a layer belonging to the given state root. +func (db *Database) Reader(root common.Hash) (layer, error) { + l := db.tree.get(root) + if l == nil { + return nil, fmt.Errorf("state %#x is not available", root) + } + return l, nil +} + +// Update adds a new layer into the tree, if that can be linked to an existing +// old parent. It is disallowed to insert a disk layer (the origin of all). Apart +// from that this function will flatten the extra diff layers at bottom into disk +// to only keep 128 diff layers in memory by default. +// +// The passed in maps(nodes, states) will be retained to avoid copying everything. +// Therefore, these maps must not be changed afterwards. +func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { + // Hold the lock to prevent concurrent mutations. + db.lock.Lock() + defer db.lock.Unlock() + + // Short circuit if the mutation is not allowed. + if err := db.modifyAllowed(); err != nil { + return err + } + if err := db.tree.add(root, parentRoot, block, nodes, states); err != nil { + return 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 + // - head-128 layer(disk layer) is paired with HEAD-128 state + return db.tree.cap(root, maxDiffLayers) +} + +// Commit traverses downwards the layer tree from a specified layer with the +// provided state root and all the layers below are flattened downwards. It +// can be used alone and mostly for test purposes. +func (db *Database) Commit(root common.Hash, report bool) error { + // Hold the lock to prevent concurrent mutations. + db.lock.Lock() + defer db.lock.Unlock() + + // Short circuit if the mutation is not allowed. + if err := db.modifyAllowed(); err != nil { + return err + } + return db.tree.cap(root, 0) +} + +// Disable deactivates the database and invalidates all available state layers +// as stale to prevent access to the persistent state, which is in the syncing +// stage. +func (db *Database) Disable() error { + db.lock.Lock() + defer db.lock.Unlock() + + // Short circuit if the database is in read only mode. + if db.readOnly { + return errDatabaseReadOnly + } + // Prevent duplicated disable operation. + if db.waitSync { + log.Error("Reject duplicated disable operation") + return nil + } + db.waitSync = true + + // Mark the disk layer as stale to prevent access to persistent state. + db.tree.bottom().markStale() + + // Write the initial sync flag to persist it across restarts. + rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncRunning) + log.Info("Disabled trie database due to state sync") + return nil +} + +// Enable activates database and resets the state tree with the provided persistent +// state root once the state sync is finished. +func (db *Database) Enable(root common.Hash) error { + db.lock.Lock() + defer db.lock.Unlock() + + // Short circuit if the database is in read only mode. + if db.readOnly { + return errDatabaseReadOnly + } + // Ensure the provided state root matches the stored one. + root = types.TrieRootHash(root) + _, stored := rawdb.ReadAccountTrieNode(db.diskdb, nil) + if stored != root { + return fmt.Errorf("state root mismatch: stored %x, synced %x", stored, root) + } + // Drop the stale state journal in persistent database and + // reset the persistent state id back to zero. + batch := db.diskdb.NewBatch() + rawdb.DeleteTrieJournal(batch) + rawdb.WritePersistentStateID(batch, 0) + if err := batch.Write(); err != nil { + return err + } + // Clean up all state histories in freezer. Theoretically + // all root->id mappings should be removed as well. Since + // mappings can be huge and might take a while to clear + // them, just leave them in disk and wait for overwriting. + if db.freezer != nil { + if err := db.freezer.Reset(); err != nil { + return err + } + } + // Re-construct a new disk layer backed by persistent state + // with **empty clean cache and node buffer**. + db.tree.reset(newDiskLayer(root, 0, db, nil, newNodeBuffer(db.bufferSize, nil, 0))) + + // Re-enable the database as the final step. + db.waitSync = false + rawdb.WriteSnapSyncStatusFlag(db.diskdb, rawdb.StateSyncFinished) + log.Info("Rebuilt trie database", "root", root) + return nil +} + +// Recover rollbacks the database to a specified historical point. +// The state is supported as the rollback destination only if it's +// canonical state and the corresponding trie histories are existent. +func (db *Database) Recover(root common.Hash, loader triestate.TrieLoader) error { + db.lock.Lock() + defer db.lock.Unlock() + + // Short circuit if rollback operation is not supported. + if err := db.modifyAllowed(); err != nil { + return err + } + if db.freezer == nil { + return errors.New("state rollback is non-supported") + } + // Short circuit if the target state is not recoverable. + root = types.TrieRootHash(root) + if !db.Recoverable(root) { + return errStateUnrecoverable + } + // Apply the state histories upon the disk layer in order. + var ( + start = time.Now() + dl = db.tree.bottom() + ) + for dl.rootHash() != root { + h, err := readHistory(db.freezer, dl.stateID()) + if err != nil { + return err + } + dl, err = dl.revert(h, loader) + if err != nil { + return err + } + // reset layer with newly created disk layer. It must be + // done after each revert operation, otherwise the new + // disk layer won't be accessible from outside. + db.tree.reset(dl) + } + rawdb.DeleteTrieJournal(db.diskdb) + _, err := truncateFromHead(db.diskdb, db.freezer, dl.stateID()) + if err != nil { + return err + } + log.Debug("Recovered state", "root", root, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +// Recoverable returns the indicator if the specified state is recoverable. +func (db *Database) Recoverable(root common.Hash) bool { + // Ensure the requested state is a known state. + root = types.TrieRootHash(root) + id := rawdb.ReadStateID(db.diskdb, root) + if id == nil { + return false + } + // Recoverable state must below the disk layer. The recoverable + // state only refers the state that is currently not available, + // but can be restored by applying state history. + dl := db.tree.bottom() + if *id >= dl.stateID() { + return false + } + // Ensure the requested state is a canonical state and all state + // histories in range [id+1, disklayer.ID] are present and complete. + parent := root + return checkHistories(db.freezer, *id+1, dl.stateID()-*id, func(m *meta) error { + if m.parent != parent { + return errors.New("unexpected state history") + } + if len(m.incomplete) > 0 { + return errors.New("incomplete state history") + } + parent = m.root + return nil + }) == nil +} + +// Close closes the trie database and the held freezer. +func (db *Database) Close() error { + db.lock.Lock() + defer db.lock.Unlock() + + // Set the database to read-only mode to prevent all + // following mutations. + db.readOnly = true + + // Release the memory held by clean cache. + db.tree.bottom().resetCache() + + // Close the attached state history freezer. + if db.freezer == nil { + return nil + } + return db.freezer.Close() +} + +// Size returns the current storage size of the memory cache in front of the +// persistent database layer. +func (db *Database) Size() (diffs common.StorageSize, nodes common.StorageSize) { + db.tree.forEach(func(layer layer) { + if diff, ok := layer.(*diffLayer); ok { + diffs += common.StorageSize(diff.memory) + } + if disk, ok := layer.(*diskLayer); ok { + nodes += disk.size() + } + }) + return diffs, nodes +} + +// Initialized returns an indicator if the state data is already +// initialized in path-based scheme. +func (db *Database) Initialized(genesisRoot common.Hash) bool { + var inited bool + db.tree.forEach(func(layer layer) { + if layer.rootHash() != types.EmptyRootHash { + inited = true + } + }) + return inited +} + +// SetBufferSize sets the node buffer size to the provided value(in bytes). +func (db *Database) SetBufferSize(size int) error { + db.lock.Lock() + defer db.lock.Unlock() + + if size > maxBufferSize { + log.Info("Capped node buffer size", "provided", common.StorageSize(size), "adjusted", common.StorageSize(maxBufferSize)) + size = maxBufferSize + } + db.bufferSize = size + return db.tree.bottom().setBufferSize(db.bufferSize) +} + +// Scheme returns the node scheme used in the database. +func (db *Database) Scheme() string { + return rawdb.PathScheme +} + +// modifyAllowed returns the indicator if mutation is allowed. This function +// assumes the db.lock is already held. +func (db *Database) modifyAllowed() error { + if db.readOnly { + return errDatabaseReadOnly + } + if db.waitSync { + return errDatabaseWaitSync + } + return nil +} diff --git a/trie/triedb/pathdb/database_test.go b/trie/triedb/pathdb/database_test.go new file mode 100644 index 000000000000..912364f7f44a --- /dev/null +++ b/trie/triedb/pathdb/database_test.go @@ -0,0 +1,575 @@ +// Copyright 2022 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 pathdb + +import ( + "bytes" + "errors" + "fmt" + "math/big" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/testutil" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +func updateTrie(addrHash common.Hash, root common.Hash, dirties, cleans map[common.Hash][]byte) (common.Hash, *trienode.NodeSet) { + h, err := newTestHasher(addrHash, root, cleans) + if err != nil { + panic(fmt.Errorf("failed to create hasher, err: %w", err)) + } + for key, val := range dirties { + if len(val) == 0 { + h.Delete(key.Bytes()) + } else { + h.Update(key.Bytes(), val) + } + } + root, nodes, _ := h.Commit(false) + return root, nodes +} + +func generateAccount(storageRoot common.Hash) types.StateAccount { + return types.StateAccount{ + Nonce: uint64(rand.Intn(100)), + Balance: big.NewInt(rand.Int63()), + CodeHash: testutil.RandBytes(32), + Root: storageRoot, + } +} + +const ( + createAccountOp int = iota + modifyAccountOp + deleteAccountOp + opLen +) + +type genctx struct { + accounts map[common.Hash][]byte + storages map[common.Hash]map[common.Hash][]byte + accountOrigin map[common.Address][]byte + storageOrigin map[common.Address]map[common.Hash][]byte + nodes *trienode.MergedNodeSet +} + +func newCtx() *genctx { + return &genctx{ + accounts: make(map[common.Hash][]byte), + storages: make(map[common.Hash]map[common.Hash][]byte), + accountOrigin: make(map[common.Address][]byte), + storageOrigin: make(map[common.Address]map[common.Hash][]byte), + nodes: trienode.NewMergedNodeSet(), + } +} + +type tester struct { + db *Database + roots []common.Hash + preimages map[common.Hash]common.Address + accounts map[common.Hash][]byte + storages map[common.Hash]map[common.Hash][]byte + + // state snapshots + snapAccounts map[common.Hash]map[common.Hash][]byte + snapStorages map[common.Hash]map[common.Hash]map[common.Hash][]byte +} + +func newTester(t *testing.T) *tester { + var ( + disk, _ = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) + db = New(disk, &Config{CleanCacheSize: 256 * 1024, DirtyCacheSize: 256 * 1024}) + obj = &tester{ + db: db, + preimages: make(map[common.Hash]common.Address), + accounts: make(map[common.Hash][]byte), + storages: make(map[common.Hash]map[common.Hash][]byte), + snapAccounts: make(map[common.Hash]map[common.Hash][]byte), + snapStorages: make(map[common.Hash]map[common.Hash]map[common.Hash][]byte), + } + ) + for i := 0; i < 2*128; i++ { + var parent = types.EmptyRootHash + if len(obj.roots) != 0 { + parent = obj.roots[len(obj.roots)-1] + } + root, nodes, states := obj.generate(parent) + if err := db.Update(root, parent, uint64(i), nodes, states); err != nil { + panic(fmt.Errorf("failed to update state changes, err: %w", err)) + } + obj.roots = append(obj.roots, root) + } + return obj +} + +func (t *tester) release() { + t.db.Close() + t.db.diskdb.Close() +} + +func (t *tester) randAccount() (common.Address, []byte) { + for addrHash, account := range t.accounts { + return t.preimages[addrHash], account + } + return common.Address{}, nil +} + +func (t *tester) generateStorage(ctx *genctx, addr common.Address) common.Hash { + var ( + addrHash = crypto.Keccak256Hash(addr.Bytes()) + storage = make(map[common.Hash][]byte) + origin = make(map[common.Hash][]byte) + ) + for i := 0; i < 10; i++ { + v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(testutil.RandBytes(32))) + hash := testutil.RandomHash() + + storage[hash] = v + origin[hash] = nil + } + root, set := updateTrie(addrHash, types.EmptyRootHash, storage, nil) + + ctx.storages[addrHash] = storage + ctx.storageOrigin[addr] = origin + ctx.nodes.Merge(set) + return root +} + +func (t *tester) mutateStorage(ctx *genctx, addr common.Address, root common.Hash) common.Hash { + var ( + addrHash = crypto.Keccak256Hash(addr.Bytes()) + storage = make(map[common.Hash][]byte) + origin = make(map[common.Hash][]byte) + ) + for hash, val := range t.storages[addrHash] { + origin[hash] = val + storage[hash] = nil + + if len(origin) == 3 { + break + } + } + for i := 0; i < 3; i++ { + v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(testutil.RandBytes(32))) + hash := testutil.RandomHash() + + storage[hash] = v + origin[hash] = nil + } + root, set := updateTrie(crypto.Keccak256Hash(addr.Bytes()), root, storage, t.storages[addrHash]) + + ctx.storages[addrHash] = storage + ctx.storageOrigin[addr] = origin + ctx.nodes.Merge(set) + return root +} + +func (t *tester) clearStorage(ctx *genctx, addr common.Address, root common.Hash) common.Hash { + var ( + addrHash = crypto.Keccak256Hash(addr.Bytes()) + storage = make(map[common.Hash][]byte) + origin = make(map[common.Hash][]byte) + ) + for hash, val := range t.storages[addrHash] { + origin[hash] = val + storage[hash] = nil + } + root, set := updateTrie(addrHash, root, storage, t.storages[addrHash]) + if root != types.EmptyRootHash { + panic("failed to clear storage trie") + } + ctx.storages[addrHash] = storage + ctx.storageOrigin[addr] = origin + ctx.nodes.Merge(set) + return root +} + +func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNodeSet, *triestate.Set) { + var ( + ctx = newCtx() + dirties = make(map[common.Hash]struct{}) + ) + for i := 0; i < 20; i++ { + switch rand.Intn(opLen) { + case createAccountOp: + // account creation + addr := testutil.RandomAddress() + addrHash := crypto.Keccak256Hash(addr.Bytes()) + if _, ok := t.accounts[addrHash]; ok { + continue + } + if _, ok := dirties[addrHash]; ok { + continue + } + dirties[addrHash] = struct{}{} + + root := t.generateStorage(ctx, addr) + ctx.accounts[addrHash] = types.SlimAccountRLP(generateAccount(root)) + ctx.accountOrigin[addr] = nil + t.preimages[addrHash] = addr + + case modifyAccountOp: + // account mutation + addr, account := t.randAccount() + if addr == (common.Address{}) { + continue + } + addrHash := crypto.Keccak256Hash(addr.Bytes()) + if _, ok := dirties[addrHash]; ok { + continue + } + dirties[addrHash] = struct{}{} + + acct, _ := types.FullAccount(account) + stRoot := t.mutateStorage(ctx, addr, acct.Root) + newAccount := types.SlimAccountRLP(generateAccount(stRoot)) + + ctx.accounts[addrHash] = newAccount + ctx.accountOrigin[addr] = account + + case deleteAccountOp: + // account deletion + addr, account := t.randAccount() + if addr == (common.Address{}) { + continue + } + addrHash := crypto.Keccak256Hash(addr.Bytes()) + if _, ok := dirties[addrHash]; ok { + continue + } + dirties[addrHash] = struct{}{} + + acct, _ := types.FullAccount(account) + if acct.Root != types.EmptyRootHash { + t.clearStorage(ctx, addr, acct.Root) + } + ctx.accounts[addrHash] = nil + ctx.accountOrigin[addr] = account + } + } + root, set := updateTrie(common.Hash{}, parent, ctx.accounts, t.accounts) + ctx.nodes.Merge(set) + + // Save state snapshot before commit + t.snapAccounts[parent] = copyAccounts(t.accounts) + t.snapStorages[parent] = copyStorages(t.storages) + + // Commit all changes to live state set + for addrHash, account := range ctx.accounts { + if len(account) == 0 { + delete(t.accounts, addrHash) + } else { + t.accounts[addrHash] = account + } + } + for addrHash, slots := range ctx.storages { + if _, ok := t.storages[addrHash]; !ok { + t.storages[addrHash] = make(map[common.Hash][]byte) + } + for sHash, slot := range slots { + if len(slot) == 0 { + delete(t.storages[addrHash], sHash) + } else { + t.storages[addrHash][sHash] = slot + } + } + } + return root, ctx.nodes, triestate.New(ctx.accountOrigin, ctx.storageOrigin, nil) +} + +// lastRoot returns the latest root hash, or empty if nothing is cached. +func (t *tester) lastHash() common.Hash { + if len(t.roots) == 0 { + return common.Hash{} + } + return t.roots[len(t.roots)-1] +} + +func (t *tester) verifyState(root common.Hash) error { + reader, err := t.db.Reader(root) + if err != nil { + return err + } + _, err = reader.Node(common.Hash{}, nil, root) + if err != nil { + return errors.New("root node is not available") + } + for addrHash, account := range t.snapAccounts[root] { + blob, err := reader.Node(common.Hash{}, addrHash.Bytes(), crypto.Keccak256Hash(account)) + if err != nil || !bytes.Equal(blob, account) { + return fmt.Errorf("account is mismatched: %w", err) + } + } + for addrHash, slots := range t.snapStorages[root] { + for hash, slot := range slots { + blob, err := reader.Node(addrHash, hash.Bytes(), crypto.Keccak256Hash(slot)) + if err != nil || !bytes.Equal(blob, slot) { + return fmt.Errorf("slot is mismatched: %w", err) + } + } + } + return nil +} + +func (t *tester) verifyHistory() error { + bottom := t.bottomIndex() + for i, root := range t.roots { + // The state history related to the state above disk layer should not exist. + if i > bottom { + _, err := readHistory(t.db.freezer, uint64(i+1)) + if err == nil { + return errors.New("unexpected state history") + } + continue + } + // The state history related to the state below or equal to the disk layer + // should exist. + obj, err := readHistory(t.db.freezer, uint64(i+1)) + if err != nil { + return err + } + parent := types.EmptyRootHash + if i != 0 { + parent = t.roots[i-1] + } + if obj.meta.parent != parent { + return fmt.Errorf("unexpected parent, want: %x, got: %x", parent, obj.meta.parent) + } + if obj.meta.root != root { + return fmt.Errorf("unexpected root, want: %x, got: %x", root, obj.meta.root) + } + } + return nil +} + +// bottomIndex returns the index of current disk layer. +func (t *tester) bottomIndex() int { + bottom := t.db.tree.bottom() + for i := 0; i < len(t.roots); i++ { + if t.roots[i] == bottom.rootHash() { + return i + } + } + return -1 +} + +func TestDatabaseRollback(t *testing.T) { + // Verify state histories + tester := newTester(t) + defer tester.release() + + if err := tester.verifyHistory(); err != nil { + t.Fatalf("Invalid state history, err: %v", err) + } + // Revert database from top to bottom + for i := tester.bottomIndex(); i >= 0; i-- { + root := tester.roots[i] + parent := types.EmptyRootHash + if i > 0 { + parent = tester.roots[i-1] + } + loader := newHashLoader(tester.snapAccounts[root], tester.snapStorages[root]) + if err := tester.db.Recover(parent, loader); err != nil { + t.Fatalf("Failed to revert db, err: %v", err) + } + tester.verifyState(parent) + } + if tester.db.tree.len() != 1 { + t.Fatal("Only disk layer is expected") + } +} + +func TestDatabaseRecoverable(t *testing.T) { + var ( + tester = newTester(t) + index = tester.bottomIndex() + ) + defer tester.release() + + var cases = []struct { + root common.Hash + expect bool + }{ + // Unknown state should be unrecoverable + {common.Hash{0x1}, false}, + + // Initial state should be recoverable + {types.EmptyRootHash, true}, + + // Initial state should be recoverable + {common.Hash{}, true}, + + // Layers below current disk layer are recoverable + {tester.roots[index-1], true}, + + // Disklayer itself is not recoverable, since it's + // available for accessing. + {tester.roots[index], false}, + + // Layers above current disk layer are not recoverable + // since they are available for accessing. + {tester.roots[index+1], false}, + } + for i, c := range cases { + result := tester.db.Recoverable(c.root) + if result != c.expect { + t.Fatalf("case: %d, unexpected result, want %t, got %t", i, c.expect, result) + } + } +} + +func TestDisable(t *testing.T) { + tester := newTester(t) + defer tester.release() + + _, stored := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil) + if err := tester.db.Disable(); err != nil { + t.Fatal("Failed to deactivate database") + } + if err := tester.db.Enable(types.EmptyRootHash); err == nil { + t.Fatalf("Invalid activation should be rejected") + } + if err := tester.db.Enable(stored); err != nil { + t.Fatal("Failed to activate database") + } + + // Ensure journal is deleted from disk + if blob := rawdb.ReadTrieJournal(tester.db.diskdb); len(blob) != 0 { + t.Fatal("Failed to clean journal") + } + // Ensure all trie histories are removed + n, err := tester.db.freezer.Ancients() + if err != nil { + t.Fatal("Failed to clean state history") + } + if n != 0 { + t.Fatal("Failed to clean state history") + } + // Verify layer tree structure, single disk layer is expected + if tester.db.tree.len() != 1 { + t.Fatalf("Extra layer kept %d", tester.db.tree.len()) + } + if tester.db.tree.bottom().rootHash() != stored { + t.Fatalf("Root hash is not matched exp %x got %x", stored, tester.db.tree.bottom().rootHash()) + } +} + +func TestCommit(t *testing.T) { + tester := newTester(t) + defer tester.release() + + if err := tester.db.Commit(tester.lastHash(), false); err != nil { + t.Fatalf("Failed to cap database, err: %v", err) + } + // Verify layer tree structure, single disk layer is expected + if tester.db.tree.len() != 1 { + t.Fatal("Layer tree structure is invalid") + } + if tester.db.tree.bottom().rootHash() != tester.lastHash() { + t.Fatal("Layer tree structure is invalid") + } + // Verify states + if err := tester.verifyState(tester.lastHash()); err != nil { + t.Fatalf("State is invalid, err: %v", err) + } + // Verify state histories + if err := tester.verifyHistory(); err != nil { + t.Fatalf("State history is invalid, err: %v", err) + } +} + +func TestJournal(t *testing.T) { + tester := newTester(t) + defer tester.release() + + if err := tester.db.Journal(tester.lastHash()); err != nil { + t.Errorf("Failed to journal, err: %v", err) + } + tester.db.Close() + tester.db = New(tester.db.diskdb, nil) + + // Verify states including disk layer and all diff on top. + for i := 0; i < len(tester.roots); i++ { + if i >= tester.bottomIndex() { + if err := tester.verifyState(tester.roots[i]); err != nil { + t.Fatalf("Invalid state, err: %v", err) + } + continue + } + if err := tester.verifyState(tester.roots[i]); err == nil { + t.Fatal("Unexpected state") + } + } +} + +func TestCorruptedJournal(t *testing.T) { + tester := newTester(t) + defer tester.release() + + if err := tester.db.Journal(tester.lastHash()); err != nil { + t.Errorf("Failed to journal, err: %v", err) + } + tester.db.Close() + _, root := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil) + + // Mutate the journal in disk, it should be regarded as invalid + blob := rawdb.ReadTrieJournal(tester.db.diskdb) + blob[0] = 1 + rawdb.WriteTrieJournal(tester.db.diskdb, blob) + + // Verify states, all not-yet-written states should be discarded + tester.db = New(tester.db.diskdb, nil) + for i := 0; i < len(tester.roots); i++ { + if tester.roots[i] == root { + if err := tester.verifyState(root); err != nil { + t.Fatalf("Disk state is corrupted, err: %v", err) + } + continue + } + if err := tester.verifyState(tester.roots[i]); err == nil { + t.Fatal("Unexpected state") + } + } +} + +// copyAccounts returns a deep-copied account set of the provided one. +func copyAccounts(set map[common.Hash][]byte) map[common.Hash][]byte { + copied := make(map[common.Hash][]byte, len(set)) + for key, val := range set { + copied[key] = common.CopyBytes(val) + } + return copied +} + +// copyStorages returns a deep-copied storage set of the provided one. +func copyStorages(set map[common.Hash]map[common.Hash][]byte) map[common.Hash]map[common.Hash][]byte { + copied := make(map[common.Hash]map[common.Hash][]byte, len(set)) + for addrHash, subset := range set { + copied[addrHash] = make(map[common.Hash][]byte, len(subset)) + for key, val := range subset { + copied[addrHash][key] = common.CopyBytes(val) + } + } + return copied +} diff --git a/trie/triedb/pathdb/difflayer.go b/trie/triedb/pathdb/difflayer.go new file mode 100644 index 000000000000..10567715d2e7 --- /dev/null +++ b/trie/triedb/pathdb/difflayer.go @@ -0,0 +1,174 @@ +// Copyright 2022 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 pathdb + +import ( + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +// diffLayer represents a collection of modifications made to the in-memory tries +// along with associated state changes after running a block on top. +// +// The goal of a diff layer is to act as a journal, tracking recent modifications +// made to the state, that have not yet graduated into a semi-immutable state. +type diffLayer struct { + // Immutables + root common.Hash // Root hash to which this layer diff belongs to + id uint64 // Corresponding state id + block uint64 // Associated block number + nodes map[common.Hash]map[string]*trienode.Node // Cached trie nodes indexed by owner and path + states *triestate.Set // Associated state change set for building history + memory uint64 // Approximate guess as to how much memory we use + + parent layer // Parent layer modified by this one, never nil, **can be changed** + lock sync.RWMutex // Lock used to protect parent +} + +// newDiffLayer creates a new diff layer on top of an existing layer. +func newDiffLayer(parent layer, root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer { + var ( + size int64 + count int + ) + dl := &diffLayer{ + root: root, + id: id, + block: block, + nodes: nodes, + states: states, + parent: parent, + } + for _, subset := range nodes { + for path, n := range subset { + dl.memory += uint64(n.Size() + len(path)) + size += int64(len(n.Blob) + len(path)) + } + count += len(subset) + } + if states != nil { + dl.memory += uint64(states.Size()) + } + dirtyWriteMeter.Mark(size) + diffLayerNodesMeter.Mark(int64(count)) + diffLayerBytesMeter.Mark(int64(dl.memory)) + log.Debug("Created new diff layer", "id", id, "block", block, "nodes", count, "size", common.StorageSize(dl.memory)) + return dl +} + +// rootHash implements the layer interface, returning the root hash of +// corresponding state. +func (dl *diffLayer) rootHash() common.Hash { + return dl.root +} + +// stateID implements the layer interface, returning the state id of the layer. +func (dl *diffLayer) stateID() uint64 { + return dl.id +} + +// parentLayer implements the layer interface, returning the subsequent +// layer of the diff layer. +func (dl *diffLayer) parentLayer() layer { + dl.lock.RLock() + defer dl.lock.RUnlock() + + return dl.parent +} + +// node retrieves the node with provided node information. It's the internal +// version of Node function with additional accessed layer tracked. No error +// will be returned if node is not found. +func (dl *diffLayer) node(owner common.Hash, path []byte, hash common.Hash, depth int) ([]byte, error) { + // Hold the lock, ensure the parent won't be changed during the + // state accessing. + dl.lock.RLock() + defer dl.lock.RUnlock() + + // If the trie node is known locally, return it + subset, ok := dl.nodes[owner] + if ok { + n, ok := subset[string(path)] + if ok { + // If the trie node is not hash matched, or marked as removed, + // bubble up an error here. It shouldn't happen at all. + if n.Hash != hash { + dirtyFalseMeter.Mark(1) + log.Error("Unexpected trie node in diff layer", "owner", owner, "path", path, "expect", hash, "got", n.Hash) + return nil, newUnexpectedNodeError("diff", hash, n.Hash, owner, path, n.Blob) + } + dirtyHitMeter.Mark(1) + dirtyNodeHitDepthHist.Update(int64(depth)) + dirtyReadMeter.Mark(int64(len(n.Blob))) + return n.Blob, nil + } + } + // Trie node unknown to this layer, resolve from parent + if diff, ok := dl.parent.(*diffLayer); ok { + return diff.node(owner, path, hash, depth+1) + } + // Failed to resolve through diff layers, fallback to disk layer + return dl.parent.Node(owner, path, hash) +} + +// Node implements the layer interface, retrieving the trie node blob with the +// provided node information. No error will be returned if the node is not found. +func (dl *diffLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) { + return dl.node(owner, path, hash, 0) +} + +// update implements the layer interface, creating a new layer on top of the +// existing layer tree with the specified data items. +func (dl *diffLayer) update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer { + return newDiffLayer(dl, root, id, block, nodes, states) +} + +// persist flushes the diff layer and all its parent layers to disk layer. +func (dl *diffLayer) persist(force bool) (layer, error) { + if parent, ok := dl.parentLayer().(*diffLayer); ok { + // Hold the lock to prevent any read operation until the new + // parent is linked correctly. + dl.lock.Lock() + + // The merging of diff layers starts at the bottom-most layer, + // therefore we recurse down here, flattening on the way up + // (diffToDisk). + result, err := parent.persist(force) + if err != nil { + dl.lock.Unlock() + return nil, err + } + dl.parent = result + dl.lock.Unlock() + } + return diffToDisk(dl, force) +} + +// diffToDisk merges a bottom-most diff into the persistent disk layer underneath +// it. The method will panic if called onto a non-bottom-most diff layer. +func diffToDisk(layer *diffLayer, force bool) (layer, error) { + disk, ok := layer.parentLayer().(*diskLayer) + if !ok { + panic(fmt.Sprintf("unknown layer type: %T", layer.parentLayer())) + } + return disk.commit(layer, force) +} diff --git a/trie/triedb/pathdb/difflayer_test.go b/trie/triedb/pathdb/difflayer_test.go new file mode 100644 index 000000000000..9b5907c3c5b3 --- /dev/null +++ b/trie/triedb/pathdb/difflayer_test.go @@ -0,0 +1,170 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package pathdb + +import ( + "bytes" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/trie/testutil" + "github.com/ethereum/go-ethereum/trie/trienode" +) + +func emptyLayer() *diskLayer { + return &diskLayer{ + db: New(rawdb.NewMemoryDatabase(), nil), + buffer: newNodeBuffer(DefaultBufferSize, nil, 0), + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkSearch128Layers +// BenchmarkSearch128Layers-8 243826 4755 ns/op +func BenchmarkSearch128Layers(b *testing.B) { benchmarkSearch(b, 0, 128) } + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkSearch512Layers +// BenchmarkSearch512Layers-8 49686 24256 ns/op +func BenchmarkSearch512Layers(b *testing.B) { benchmarkSearch(b, 0, 512) } + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkSearch1Layer +// BenchmarkSearch1Layer-8 14062725 88.40 ns/op +func BenchmarkSearch1Layer(b *testing.B) { benchmarkSearch(b, 127, 128) } + +func benchmarkSearch(b *testing.B, depth int, total int) { + var ( + npath []byte + nhash common.Hash + nblob []byte + ) + // First, we set up 128 diff layers, with 3K items each + fill := func(parent layer, index int) *diffLayer { + nodes := make(map[common.Hash]map[string]*trienode.Node) + nodes[common.Hash{}] = make(map[string]*trienode.Node) + for i := 0; i < 3000; i++ { + var ( + path = testutil.RandBytes(32) + node = testutil.RandomNode() + ) + nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob) + if npath == nil && depth == index { + npath = common.CopyBytes(path) + nblob = common.CopyBytes(node.Blob) + nhash = node.Hash + } + } + return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil) + } + var layer layer + layer = emptyLayer() + for i := 0; i < total; i++ { + layer = fill(layer, i) + } + b.ResetTimer() + + var ( + have []byte + err error + ) + for i := 0; i < b.N; i++ { + have, err = layer.Node(common.Hash{}, npath, nhash) + if err != nil { + b.Fatal(err) + } + } + if !bytes.Equal(have, nblob) { + b.Fatalf("have %x want %x", have, nblob) + } +} + +// goos: darwin +// goarch: arm64 +// pkg: github.com/ethereum/go-ethereum/trie +// BenchmarkPersist +// BenchmarkPersist-8 10 111252975 ns/op +func BenchmarkPersist(b *testing.B) { + // First, we set up 128 diff layers, with 3K items each + fill := func(parent layer) *diffLayer { + nodes := make(map[common.Hash]map[string]*trienode.Node) + nodes[common.Hash{}] = make(map[string]*trienode.Node) + for i := 0; i < 3000; i++ { + var ( + path = testutil.RandBytes(32) + node = testutil.RandomNode() + ) + nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob) + } + return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil) + } + for i := 0; i < b.N; i++ { + b.StopTimer() + var layer layer + layer = emptyLayer() + for i := 1; i < 128; i++ { + layer = fill(layer) + } + b.StartTimer() + + dl, ok := layer.(*diffLayer) + if !ok { + break + } + dl.persist(false) + } +} + +// BenchmarkJournal benchmarks the performance for journaling the layers. +// +// BenchmarkJournal +// BenchmarkJournal-8 10 110969279 ns/op +func BenchmarkJournal(b *testing.B) { + b.SkipNow() + + // First, we set up 128 diff layers, with 3K items each + fill := func(parent layer) *diffLayer { + nodes := make(map[common.Hash]map[string]*trienode.Node) + nodes[common.Hash{}] = make(map[string]*trienode.Node) + for i := 0; i < 3000; i++ { + var ( + path = testutil.RandBytes(32) + node = testutil.RandomNode() + ) + nodes[common.Hash{}][string(path)] = trienode.New(node.Hash, node.Blob) + } + // TODO(rjl493456442) a non-nil state set is expected. + return newDiffLayer(parent, common.Hash{}, 0, 0, nodes, nil) + } + var layer layer + layer = emptyLayer() + for i := 0; i < 128; i++ { + layer = fill(layer) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + layer.journal(new(bytes.Buffer)) + } +} diff --git a/trie/triedb/pathdb/disklayer.go b/trie/triedb/pathdb/disklayer.go new file mode 100644 index 000000000000..d3b6419cc594 --- /dev/null +++ b/trie/triedb/pathdb/disklayer.go @@ -0,0 +1,310 @@ +// Copyright 2022 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 pathdb + +import ( + "errors" + "fmt" + "sync" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" + "golang.org/x/crypto/sha3" +) + +// diskLayer is a low level persistent layer built on top of a key-value store. +type diskLayer struct { + root common.Hash // Immutable, root hash to which this layer was made for + id uint64 // Immutable, corresponding state id + db *Database // Path-based trie database + cleans *fastcache.Cache // GC friendly memory cache of clean node RLPs + buffer *nodebuffer // Node buffer to aggregate writes + stale bool // Signals that the layer became stale (state progressed) + lock sync.RWMutex // Lock used to protect stale flag +} + +// newDiskLayer creates a new disk layer based on the passing arguments. +func newDiskLayer(root common.Hash, id uint64, db *Database, cleans *fastcache.Cache, buffer *nodebuffer) *diskLayer { + // Initialize a clean cache if the memory allowance is not zero + // or reuse the provided cache if it is not nil (inherited from + // the original disk layer). + if cleans == nil && db.config.CleanCacheSize != 0 { + cleans = fastcache.New(db.config.CleanCacheSize) + } + return &diskLayer{ + root: root, + id: id, + db: db, + cleans: cleans, + buffer: buffer, + } +} + +// root implements the layer interface, returning root hash of corresponding state. +func (dl *diskLayer) rootHash() common.Hash { + return dl.root +} + +// stateID implements the layer interface, returning the state id of disk layer. +func (dl *diskLayer) stateID() uint64 { + return dl.id +} + +// parent implements the layer interface, returning nil as there's no layer +// below the disk. +func (dl *diskLayer) parentLayer() layer { + return nil +} + +// isStale return whether this layer has become stale (was flattened across) or if +// it's still live. +func (dl *diskLayer) isStale() bool { + dl.lock.RLock() + defer dl.lock.RUnlock() + + return dl.stale +} + +// markStale sets the stale flag as true. +func (dl *diskLayer) markStale() { + dl.lock.Lock() + defer dl.lock.Unlock() + + if dl.stale { + panic("triedb disk layer is stale") // we've committed into the same base from two children, boom + } + dl.stale = true +} + +// Node implements the layer interface, retrieving the trie node with the +// provided node info. No error will be returned if the node is not found. +func (dl *diskLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) { + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.stale { + return nil, errSnapshotStale + } + // Try to retrieve the trie node from the not-yet-written + // node buffer first. Note the buffer is lock free since + // it's impossible to mutate the buffer before tagging the + // layer as stale. + n, err := dl.buffer.node(owner, path, hash) + if err != nil { + return nil, err + } + if n != nil { + dirtyHitMeter.Mark(1) + dirtyReadMeter.Mark(int64(len(n.Blob))) + return n.Blob, nil + } + dirtyMissMeter.Mark(1) + + // Try to retrieve the trie node from the clean memory cache + key := cacheKey(owner, path) + if dl.cleans != nil { + if blob := dl.cleans.Get(nil, key); len(blob) > 0 { + h := newHasher() + defer h.release() + + got := h.hash(blob) + if got == hash { + cleanHitMeter.Mark(1) + cleanReadMeter.Mark(int64(len(blob))) + return blob, nil + } + cleanFalseMeter.Mark(1) + log.Error("Unexpected trie node in clean cache", "owner", owner, "path", path, "expect", hash, "got", got) + } + cleanMissMeter.Mark(1) + } + // Try to retrieve the trie node from the disk. + var ( + nBlob []byte + nHash common.Hash + ) + if owner == (common.Hash{}) { + nBlob, nHash = rawdb.ReadAccountTrieNode(dl.db.diskdb, path) + } else { + nBlob, nHash = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path) + } + if nHash != hash { + diskFalseMeter.Mark(1) + log.Error("Unexpected trie node in disk", "owner", owner, "path", path, "expect", hash, "got", nHash) + return nil, newUnexpectedNodeError("disk", hash, nHash, owner, path, nBlob) + } + if dl.cleans != nil && len(nBlob) > 0 { + dl.cleans.Set(key, nBlob) + cleanWriteMeter.Mark(int64(len(nBlob))) + } + return nBlob, nil +} + +// update implements the layer interface, returning a new diff layer on top +// with the given state set. +func (dl *diskLayer) update(root common.Hash, id uint64, block uint64, nodes map[common.Hash]map[string]*trienode.Node, states *triestate.Set) *diffLayer { + return newDiffLayer(dl, root, id, block, nodes, states) +} + +// commit merges the given bottom-most diff layer into the node buffer +// and returns a newly constructed disk layer. Note the current disk +// layer must be tagged as stale first to prevent re-access. +func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) { + dl.lock.Lock() + defer dl.lock.Unlock() + + // Construct and store the state history first. If crash happens + // after storing the state history but without flushing the + // corresponding states(journal), the stored state history will + // be truncated in the next restart. + if dl.db.freezer != nil { + err := writeHistory(dl.db.diskdb, dl.db.freezer, bottom, dl.db.config.StateHistory) + if err != nil { + return nil, err + } + } + // Mark the diskLayer as stale before applying any mutations on top. + dl.stale = true + + // Store the root->id lookup afterwards. All stored lookups are + // identified by the **unique** state root. It's impossible that + // in the same chain blocks are not adjacent but have the same + // root. + if dl.id == 0 { + rawdb.WriteStateID(dl.db.diskdb, dl.root, 0) + } + rawdb.WriteStateID(dl.db.diskdb, bottom.rootHash(), bottom.stateID()) + + // Construct a new disk layer by merging the nodes from the provided + // diff layer, and flush the content in disk layer if there are too + // many nodes cached. The clean cache is inherited from the original + // disk layer for reusing. + ndl := newDiskLayer(bottom.root, bottom.stateID(), dl.db, dl.cleans, dl.buffer.commit(bottom.nodes)) + err := ndl.buffer.flush(ndl.db.diskdb, ndl.cleans, ndl.id, force) + if err != nil { + return nil, err + } + return ndl, nil +} + +// revert applies the given state history and return a reverted disk layer. +func (dl *diskLayer) revert(h *history, loader triestate.TrieLoader) (*diskLayer, error) { + if h.meta.root != dl.rootHash() { + return nil, errUnexpectedHistory + } + // Reject if the provided state history is incomplete. It's due to + // a large construct SELF-DESTRUCT which can't be handled because + // of memory limitation. + if len(h.meta.incomplete) > 0 { + return nil, errors.New("incomplete state history") + } + if dl.id == 0 { + return nil, fmt.Errorf("%w: zero state id", errStateUnrecoverable) + } + // Apply the reverse state changes upon the current state. This must + // be done before holding the lock in order to access state in "this" + // layer. + nodes, err := triestate.Apply(h.meta.parent, h.meta.root, h.accounts, h.storages, loader) + if err != nil { + return nil, err + } + // Mark the diskLayer as stale before applying any mutations on top. + dl.lock.Lock() + defer dl.lock.Unlock() + + dl.stale = true + + // State change may be applied to node buffer, or the persistent + // state, depends on if node buffer is empty or not. If the node + // buffer is not empty, it means that the state transition that + // needs to be reverted is not yet flushed and cached in node + // buffer, otherwise, manipulate persistent state directly. + if !dl.buffer.empty() { + err := dl.buffer.revert(dl.db.diskdb, nodes) + if err != nil { + return nil, err + } + } else { + batch := dl.db.diskdb.NewBatch() + writeNodes(batch, nodes, dl.cleans) + rawdb.WritePersistentStateID(batch, dl.id-1) + if err := batch.Write(); err != nil { + log.Crit("Failed to write states", "err", err) + } + } + return newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.cleans, dl.buffer), nil +} + +// setBufferSize sets the node buffer size to the provided value. +func (dl *diskLayer) setBufferSize(size int) error { + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.stale { + return errSnapshotStale + } + return dl.buffer.setSize(size, dl.db.diskdb, dl.cleans, dl.id) +} + +// size returns the approximate size of cached nodes in the disk layer. +func (dl *diskLayer) size() common.StorageSize { + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.stale { + return 0 + } + return common.StorageSize(dl.buffer.size) +} + +// resetCache releases the memory held by clean cache to prevent memory leak. +func (dl *diskLayer) resetCache() { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // Stale disk layer loses the ownership of clean cache. + if dl.stale { + return + } + if dl.cleans != nil { + dl.cleans.Reset() + } +} + +// hasher is used to compute the sha256 hash of the provided data. +type hasher struct{ sha crypto.KeccakState } + +var hasherPool = sync.Pool{ + New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} }, +} + +func newHasher() *hasher { + return hasherPool.Get().(*hasher) +} + +func (h *hasher) hash(data []byte) common.Hash { + return crypto.HashData(h.sha, data) +} + +func (h *hasher) release() { + hasherPool.Put(h) +} diff --git a/trie/triedb/pathdb/errors.go b/trie/triedb/pathdb/errors.go new file mode 100644 index 000000000000..78ee4459fe50 --- /dev/null +++ b/trie/triedb/pathdb/errors.go @@ -0,0 +1,60 @@ +// Copyright 2023 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 pathdb + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +var ( + // errDatabaseReadOnly is returned if the database is opened in read only mode + // to prevent any mutation. + errDatabaseReadOnly = errors.New("read only") + + // errDatabaseWaitSync is returned if the initial state sync is not completed + // yet and database is disabled to prevent accessing state. + errDatabaseWaitSync = errors.New("waiting for sync") + + // errSnapshotStale is returned from data accessors if the underlying layer + // layer had been invalidated due to the chain progressing forward far enough + // to not maintain the layer's original state. + errSnapshotStale = errors.New("layer stale") + + // errUnexpectedHistory is returned if an unmatched state history is applied + // to the database for state rollback. + errUnexpectedHistory = errors.New("unexpected state history") + + // errStateUnrecoverable is returned if state is required to be reverted to + // a destination without associated state history available. + errStateUnrecoverable = errors.New("state is unrecoverable") + + // errUnexpectedNode is returned if the requested node with specified path is + // not hash matched with expectation. + errUnexpectedNode = errors.New("unexpected node") +) + +func newUnexpectedNodeError(loc string, expHash common.Hash, gotHash common.Hash, owner common.Hash, path []byte, blob []byte) error { + blobHex := "nil" + if len(blob) > 0 { + blobHex = hexutil.Encode(blob) + } + return fmt.Errorf("%w, loc: %s, node: (%x %v), %x!=%x, blob: %s", errUnexpectedNode, loc, owner, path, expHash, gotHash, blobHex) +} diff --git a/trie/triedb/pathdb/history.go b/trie/triedb/pathdb/history.go new file mode 100644 index 000000000000..ce82532507c7 --- /dev/null +++ b/trie/triedb/pathdb/history.go @@ -0,0 +1,641 @@ +// Copyright 2022 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 pathdb + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie/triestate" + "golang.org/x/exp/slices" +) + +// State history records the state changes involved in executing a block. The +// state can be reverted to the previous version by applying the associated +// history object (state reverse diff). State history objects are kept to +// guarantee that the system can perform state rollbacks in case of deep reorg. +// +// Each state transition will generate a state history object. Note that not +// every block has a corresponding state history object. If a block performs +// no state changes whatsoever, no state is created for it. Each state history +// will have a sequentially increasing number acting as its unique identifier. +// +// The state history is written to disk (ancient store) when the corresponding +// diff layer is merged into the disk layer. At the same time, system can prune +// the oldest histories according to config. +// +// Disk State +// ^ +// | +// +------------+ +---------+ +---------+ +---------+ +// | Init State |---->| State 1 |---->| ... |---->| State n | +// +------------+ +---------+ +---------+ +---------+ +// +// +-----------+ +------+ +-----------+ +// | History 1 |----> | ... |---->| History n | +// +-----------+ +------+ +-----------+ +// +// # Rollback +// +// If the system wants to roll back to a previous state n, it needs to ensure +// all history objects from n+1 up to the current disk layer are existent. The +// history objects are applied to the state in reverse order, starting from the +// current disk layer. + +const ( + accountIndexSize = common.AddressLength + 13 // The length of encoded account index + slotIndexSize = common.HashLength + 5 // The length of encoded slot index + historyMetaSize = 9 + 2*common.HashLength // The length of fixed size part of meta object + + stateHistoryVersion = uint8(0) // initial version of state history structure. +) + +// Each state history entry is consisted of five elements: +// +// # metadata +// This object contains a few meta fields, such as the associated state root, +// block number, version tag and so on. This object may contain an extra +// accountHash list which means the storage changes belong to these accounts +// are not complete due to large contract destruction. The incomplete history +// can not be used for rollback and serving archive state request. +// +// # account index +// This object contains some index information of account. For example, offset +// and length indicate the location of the data belonging to the account. Besides, +// storageOffset and storageSlots indicate the storage modification location +// belonging to the account. +// +// The size of each account index is *fixed*, and all indexes are sorted +// lexicographically. Thus binary search can be performed to quickly locate a +// specific account. +// +// # account data +// Account data is a concatenated byte stream composed of all account data. +// The account data can be solved by the offset and length info indicated +// by corresponding account index. +// +// fixed size +// ^ ^ +// / \ +// +-----------------+-----------------+----------------+-----------------+ +// | Account index 1 | Account index 2 | ... | Account index N | +// +-----------------+-----------------+----------------+-----------------+ +// | +// | length +// offset |----------------+ +// v v +// +----------------+----------------+----------------+----------------+ +// | Account data 1 | Account data 2 | ... | Account data N | +// +----------------+----------------+----------------+----------------+ +// +// # storage index +// This object is similar with account index. It's also fixed size and contains +// the location info of storage slot data. +// +// # storage data +// Storage data is a concatenated byte stream composed of all storage slot data. +// The storage slot data can be solved by the location info indicated by +// corresponding account index and storage slot index. +// +// fixed size +// ^ ^ +// / \ +// +-----------------+-----------------+----------------+-----------------+ +// | Account index 1 | Account index 2 | ... | Account index N | +// +-----------------+-----------------+----------------+-----------------+ +// | +// | storage slots +// storage offset |-----------------------------------------------------+ +// v v +// +-----------------+-----------------+-----------------+ +// | storage index 1 | storage index 2 | storage index 3 | +// +-----------------+-----------------+-----------------+ +// | length +// offset |-------------+ +// v v +// +-------------+ +// | slot data 1 | +// +-------------+ + +// accountIndex describes the metadata belonging to an account. +type accountIndex struct { + address common.Address // The address of account + length uint8 // The length of account data, size limited by 255 + offset uint32 // The offset of item in account data table + storageOffset uint32 // The offset of storage index in storage index table + storageSlots uint32 // The number of mutated storage slots belonging to the account +} + +// encode packs account index into byte stream. +func (i *accountIndex) encode() []byte { + var buf [accountIndexSize]byte + copy(buf[:], i.address.Bytes()) + buf[common.AddressLength] = i.length + binary.BigEndian.PutUint32(buf[common.AddressLength+1:], i.offset) + binary.BigEndian.PutUint32(buf[common.AddressLength+5:], i.storageOffset) + binary.BigEndian.PutUint32(buf[common.AddressLength+9:], i.storageSlots) + return buf[:] +} + +// decode unpacks account index from byte stream. +func (i *accountIndex) decode(blob []byte) { + i.address = common.BytesToAddress(blob[:common.AddressLength]) + i.length = blob[common.AddressLength] + i.offset = binary.BigEndian.Uint32(blob[common.AddressLength+1:]) + i.storageOffset = binary.BigEndian.Uint32(blob[common.AddressLength+5:]) + i.storageSlots = binary.BigEndian.Uint32(blob[common.AddressLength+9:]) +} + +// slotIndex describes the metadata belonging to a storage slot. +type slotIndex struct { + hash common.Hash // The hash of slot key + length uint8 // The length of storage slot, up to 32 bytes defined in protocol + offset uint32 // The offset of item in storage slot data table +} + +// encode packs slot index into byte stream. +func (i *slotIndex) encode() []byte { + var buf [slotIndexSize]byte + copy(buf[:common.HashLength], i.hash.Bytes()) + buf[common.HashLength] = i.length + binary.BigEndian.PutUint32(buf[common.HashLength+1:], i.offset) + return buf[:] +} + +// decode unpack slot index from the byte stream. +func (i *slotIndex) decode(blob []byte) { + i.hash = common.BytesToHash(blob[:common.HashLength]) + i.length = blob[common.HashLength] + i.offset = binary.BigEndian.Uint32(blob[common.HashLength+1:]) +} + +// meta describes the meta data of state history object. +type meta struct { + version uint8 // version tag of history object + parent common.Hash // prev-state root before the state transition + root common.Hash // post-state root after the state transition + block uint64 // associated block number + incomplete []common.Address // list of address whose storage set is incomplete +} + +// encode packs the meta object into byte stream. +func (m *meta) encode() []byte { + buf := make([]byte, historyMetaSize+len(m.incomplete)*common.AddressLength) + buf[0] = m.version + copy(buf[1:1+common.HashLength], m.parent.Bytes()) + copy(buf[1+common.HashLength:1+2*common.HashLength], m.root.Bytes()) + binary.BigEndian.PutUint64(buf[1+2*common.HashLength:historyMetaSize], m.block) + for i, h := range m.incomplete { + copy(buf[i*common.AddressLength+historyMetaSize:], h.Bytes()) + } + return buf[:] +} + +// decode unpacks the meta object from byte stream. +func (m *meta) decode(blob []byte) error { + if len(blob) < 1 { + return fmt.Errorf("no version tag") + } + switch blob[0] { + case stateHistoryVersion: + if len(blob) < historyMetaSize { + return fmt.Errorf("invalid state history meta, len: %d", len(blob)) + } + if (len(blob)-historyMetaSize)%common.AddressLength != 0 { + return fmt.Errorf("corrupted state history meta, len: %d", len(blob)) + } + m.version = blob[0] + m.parent = common.BytesToHash(blob[1 : 1+common.HashLength]) + m.root = common.BytesToHash(blob[1+common.HashLength : 1+2*common.HashLength]) + m.block = binary.BigEndian.Uint64(blob[1+2*common.HashLength : historyMetaSize]) + for pos := historyMetaSize; pos < len(blob); { + m.incomplete = append(m.incomplete, common.BytesToAddress(blob[pos:pos+common.AddressLength])) + pos += common.AddressLength + } + return nil + default: + return fmt.Errorf("unknown version %d", blob[0]) + } +} + +// history represents a set of state changes belong to a block along with +// the metadata including the state roots involved in the state transition. +// State history objects in disk are linked with each other by a unique id +// (8-bytes integer), the oldest state history object can be pruned on demand +// in order to control the storage size. +type history struct { + meta *meta // Meta data of history + accounts map[common.Address][]byte // Account data keyed by its address hash + accountList []common.Address // Sorted account hash list + storages map[common.Address]map[common.Hash][]byte // Storage data keyed by its address hash and slot hash + storageList map[common.Address][]common.Hash // Sorted slot hash list +} + +// newHistory constructs the state history object with provided state change set. +func newHistory(root common.Hash, parent common.Hash, block uint64, states *triestate.Set) *history { + var ( + accountList []common.Address + storageList = make(map[common.Address][]common.Hash) + incomplete []common.Address + ) + for addr := range states.Accounts { + accountList = append(accountList, addr) + } + slices.SortFunc(accountList, common.Address.Cmp) + + for addr, slots := range states.Storages { + slist := make([]common.Hash, 0, len(slots)) + for slotHash := range slots { + slist = append(slist, slotHash) + } + slices.SortFunc(slist, common.Hash.Cmp) + storageList[addr] = slist + } + for addr := range states.Incomplete { + incomplete = append(incomplete, addr) + } + slices.SortFunc(incomplete, common.Address.Cmp) + + return &history{ + meta: &meta{ + version: stateHistoryVersion, + parent: parent, + root: root, + block: block, + incomplete: incomplete, + }, + accounts: states.Accounts, + accountList: accountList, + storages: states.Storages, + storageList: storageList, + } +} + +// encode serializes the state history and returns four byte streams represent +// concatenated account/storage data, account/storage indexes respectively. +func (h *history) encode() ([]byte, []byte, []byte, []byte) { + var ( + slotNumber uint32 // the number of processed slots + accountData []byte // the buffer for concatenated account data + storageData []byte // the buffer for concatenated storage data + accountIndexes []byte // the buffer for concatenated account index + storageIndexes []byte // the buffer for concatenated storage index + ) + for _, addr := range h.accountList { + accIndex := accountIndex{ + address: addr, + length: uint8(len(h.accounts[addr])), + offset: uint32(len(accountData)), + } + slots, exist := h.storages[addr] + if exist { + // Encode storage slots in order + for _, slotHash := range h.storageList[addr] { + sIndex := slotIndex{ + hash: slotHash, + length: uint8(len(slots[slotHash])), + offset: uint32(len(storageData)), + } + storageData = append(storageData, slots[slotHash]...) + storageIndexes = append(storageIndexes, sIndex.encode()...) + } + // Fill up the storage meta in account index + accIndex.storageOffset = slotNumber + accIndex.storageSlots = uint32(len(slots)) + slotNumber += uint32(len(slots)) + } + accountData = append(accountData, h.accounts[addr]...) + accountIndexes = append(accountIndexes, accIndex.encode()...) + } + return accountData, storageData, accountIndexes, storageIndexes +} + +// decoder wraps the byte streams for decoding with extra meta fields. +type decoder struct { + accountData []byte // the buffer for concatenated account data + storageData []byte // the buffer for concatenated storage data + accountIndexes []byte // the buffer for concatenated account index + storageIndexes []byte // the buffer for concatenated storage index + + lastAccount *common.Address // the address of last resolved account + lastAccountRead uint32 // the read-cursor position of account data + lastSlotIndexRead uint32 // the read-cursor position of storage slot index + lastSlotDataRead uint32 // the read-cursor position of storage slot data +} + +// verify validates the provided byte streams for decoding state history. A few +// checks will be performed to quickly detect data corruption. The byte stream +// is regarded as corrupted if: +// +// - account indexes buffer is empty(empty state set is invalid) +// - account indexes/storage indexer buffer is not aligned +// +// note, these situations are allowed: +// +// - empty account data: all accounts were not present +// - empty storage set: no slots are modified +func (r *decoder) verify() error { + if len(r.accountIndexes)%accountIndexSize != 0 || len(r.accountIndexes) == 0 { + return fmt.Errorf("invalid account index, len: %d", len(r.accountIndexes)) + } + if len(r.storageIndexes)%slotIndexSize != 0 { + return fmt.Errorf("invalid storage index, len: %d", len(r.storageIndexes)) + } + return nil +} + +// readAccount parses the account from the byte stream with specified position. +func (r *decoder) readAccount(pos int) (accountIndex, []byte, error) { + // Decode account index from the index byte stream. + var index accountIndex + if (pos+1)*accountIndexSize > len(r.accountIndexes) { + return accountIndex{}, nil, errors.New("account data buffer is corrupted") + } + index.decode(r.accountIndexes[pos*accountIndexSize : (pos+1)*accountIndexSize]) + + // Perform validation before parsing account data, ensure + // - account is sorted in order in byte stream + // - account data is strictly encoded with no gap inside + // - account data is not out-of-slice + if r.lastAccount != nil { // zero address is possible + if bytes.Compare(r.lastAccount.Bytes(), index.address.Bytes()) >= 0 { + return accountIndex{}, nil, errors.New("account is not in order") + } + } + if index.offset != r.lastAccountRead { + return accountIndex{}, nil, errors.New("account data buffer is gaped") + } + last := index.offset + uint32(index.length) + if uint32(len(r.accountData)) < last { + return accountIndex{}, nil, errors.New("account data buffer is corrupted") + } + data := r.accountData[index.offset:last] + + r.lastAccount = &index.address + r.lastAccountRead = last + + return index, data, nil +} + +// readStorage parses the storage slots from the byte stream with specified account. +func (r *decoder) readStorage(accIndex accountIndex) ([]common.Hash, map[common.Hash][]byte, error) { + var ( + last common.Hash + list []common.Hash + storage = make(map[common.Hash][]byte) + ) + for j := 0; j < int(accIndex.storageSlots); j++ { + var ( + index slotIndex + start = (accIndex.storageOffset + uint32(j)) * uint32(slotIndexSize) + end = (accIndex.storageOffset + uint32(j+1)) * uint32(slotIndexSize) + ) + // Perform validation before parsing storage slot data, ensure + // - slot index is not out-of-slice + // - slot data is not out-of-slice + // - slot is sorted in order in byte stream + // - slot indexes is strictly encoded with no gap inside + // - slot data is strictly encoded with no gap inside + if start != r.lastSlotIndexRead { + return nil, nil, errors.New("storage index buffer is gapped") + } + if uint32(len(r.storageIndexes)) < end { + return nil, nil, errors.New("storage index buffer is corrupted") + } + index.decode(r.storageIndexes[start:end]) + + if bytes.Compare(last.Bytes(), index.hash.Bytes()) >= 0 { + return nil, nil, errors.New("storage slot is not in order") + } + if index.offset != r.lastSlotDataRead { + return nil, nil, errors.New("storage data buffer is gapped") + } + sEnd := index.offset + uint32(index.length) + if uint32(len(r.storageData)) < sEnd { + return nil, nil, errors.New("storage data buffer is corrupted") + } + storage[index.hash] = r.storageData[r.lastSlotDataRead:sEnd] + list = append(list, index.hash) + + last = index.hash + r.lastSlotIndexRead = end + r.lastSlotDataRead = sEnd + } + return list, storage, nil +} + +// decode deserializes the account and storage data from the provided byte stream. +func (h *history) decode(accountData, storageData, accountIndexes, storageIndexes []byte) error { + var ( + accounts = make(map[common.Address][]byte) + storages = make(map[common.Address]map[common.Hash][]byte) + accountList []common.Address + storageList = make(map[common.Address][]common.Hash) + + r = &decoder{ + accountData: accountData, + storageData: storageData, + accountIndexes: accountIndexes, + storageIndexes: storageIndexes, + } + ) + if err := r.verify(); err != nil { + return err + } + for i := 0; i < len(accountIndexes)/accountIndexSize; i++ { + // Resolve account first + accIndex, accData, err := r.readAccount(i) + if err != nil { + return err + } + accounts[accIndex.address] = accData + accountList = append(accountList, accIndex.address) + + // Resolve storage slots + slotList, slotData, err := r.readStorage(accIndex) + if err != nil { + return err + } + if len(slotList) > 0 { + storageList[accIndex.address] = slotList + storages[accIndex.address] = slotData + } + } + h.accounts = accounts + h.accountList = accountList + h.storages = storages + h.storageList = storageList + return nil +} + +// readHistory reads and decodes the state history object by the given id. +func readHistory(freezer *rawdb.ResettableFreezer, id uint64) (*history, error) { + blob := rawdb.ReadStateHistoryMeta(freezer, id) + if len(blob) == 0 { + return nil, fmt.Errorf("state history not found %d", id) + } + var m meta + if err := m.decode(blob); err != nil { + return nil, err + } + var ( + dec = history{meta: &m} + accountData = rawdb.ReadStateAccountHistory(freezer, id) + storageData = rawdb.ReadStateStorageHistory(freezer, id) + accountIndexes = rawdb.ReadStateAccountIndex(freezer, id) + storageIndexes = rawdb.ReadStateStorageIndex(freezer, id) + ) + if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil { + return nil, err + } + return &dec, nil +} + +// writeHistory writes the state history with provided state set. After +// storing the corresponding state history, it will also prune the stale +// histories from the disk with the given threshold. +func writeHistory(db ethdb.KeyValueStore, freezer *rawdb.ResettableFreezer, dl *diffLayer, limit uint64) error { + // Short circuit if state set is not available. + if dl.states == nil { + return errors.New("state change set is not available") + } + var ( + err error + n int + start = time.Now() + h = newHistory(dl.rootHash(), dl.parentLayer().rootHash(), dl.block, dl.states) + ) + accountData, storageData, accountIndex, storageIndex := h.encode() + dataSize := common.StorageSize(len(accountData) + len(storageData)) + indexSize := common.StorageSize(len(accountIndex) + len(storageIndex)) + + // Write history data into five freezer table respectively. + rawdb.WriteStateHistory(freezer, dl.stateID(), h.meta.encode(), accountIndex, storageIndex, accountData, storageData) + + // Prune stale state histories based on the config. + if limit != 0 && dl.stateID() > limit { + n, err = truncateFromTail(db, freezer, dl.stateID()-limit) + if err != nil { + return err + } + } + historyDataBytesMeter.Mark(int64(dataSize)) + historyIndexBytesMeter.Mark(int64(indexSize)) + historyBuildTimeMeter.UpdateSince(start) + log.Debug("Stored state history", "id", dl.stateID(), "block", dl.block, "data", dataSize, "index", indexSize, "pruned", n, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} + +// checkHistories retrieves a batch of meta objects with the specified range +// and performs the callback on each item. +func checkHistories(freezer *rawdb.ResettableFreezer, start, count uint64, check func(*meta) error) error { + for count > 0 { + number := count + if number > 10000 { + number = 10000 // split the big read into small chunks + } + blobs, err := rawdb.ReadStateHistoryMetaList(freezer, start, number) + if err != nil { + return err + } + for _, blob := range blobs { + var dec meta + if err := dec.decode(blob); err != nil { + return err + } + if err := check(&dec); err != nil { + return err + } + } + count -= uint64(len(blobs)) + start += uint64(len(blobs)) + } + return nil +} + +// truncateFromHead removes the extra state histories from the head with the given +// parameters. It returns the number of items removed from the head. +func truncateFromHead(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, nhead uint64) (int, error) { + ohead, err := freezer.Ancients() + if err != nil { + return 0, err + } + if ohead <= nhead { + return 0, nil + } + // Load the meta objects in range [nhead+1, ohead] + blobs, err := rawdb.ReadStateHistoryMetaList(freezer, nhead+1, ohead-nhead) + if err != nil { + return 0, err + } + batch := db.NewBatch() + for _, blob := range blobs { + var m meta + if err := m.decode(blob); err != nil { + return 0, err + } + rawdb.DeleteStateID(batch, m.root) + } + if err := batch.Write(); err != nil { + return 0, err + } + ohead, err = freezer.TruncateHead(nhead) + if err != nil { + return 0, err + } + return int(ohead - nhead), nil +} + +// truncateFromTail removes the extra state histories from the tail with the given +// parameters. It returns the number of items removed from the tail. +func truncateFromTail(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, ntail uint64) (int, error) { + otail, err := freezer.Tail() + if err != nil { + return 0, err + } + if otail >= ntail { + return 0, nil + } + // Load the meta objects in range [otail+1, ntail] + blobs, err := rawdb.ReadStateHistoryMetaList(freezer, otail+1, ntail-otail) + if err != nil { + return 0, err + } + batch := db.NewBatch() + for _, blob := range blobs { + var m meta + if err := m.decode(blob); err != nil { + return 0, err + } + rawdb.DeleteStateID(batch, m.root) + } + if err := batch.Write(); err != nil { + return 0, err + } + otail, err = freezer.TruncateTail(ntail) + if err != nil { + return 0, err + } + return int(ntail - otail), nil +} diff --git a/trie/triedb/pathdb/history_test.go b/trie/triedb/pathdb/history_test.go new file mode 100644 index 000000000000..677103e2b0ad --- /dev/null +++ b/trie/triedb/pathdb/history_test.go @@ -0,0 +1,290 @@ +// Copyright 2022 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 pathdb + +import ( + "bytes" + "fmt" + "reflect" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/testutil" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +// randomStateSet generates a random state change set. +func randomStateSet(n int) *triestate.Set { + var ( + accounts = make(map[common.Address][]byte) + storages = make(map[common.Address]map[common.Hash][]byte) + ) + for i := 0; i < n; i++ { + addr := testutil.RandomAddress() + storages[addr] = make(map[common.Hash][]byte) + for j := 0; j < 3; j++ { + v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(testutil.RandBytes(32))) + storages[addr][testutil.RandomHash()] = v + } + account := generateAccount(types.EmptyRootHash) + accounts[addr] = types.SlimAccountRLP(account) + } + return triestate.New(accounts, storages, nil) +} + +func makeHistory() *history { + return newHistory(testutil.RandomHash(), types.EmptyRootHash, 0, randomStateSet(3)) +} + +func makeHistories(n int) []*history { + var ( + parent = types.EmptyRootHash + result []*history + ) + for i := 0; i < n; i++ { + root := testutil.RandomHash() + h := newHistory(root, parent, uint64(i), randomStateSet(3)) + parent = root + result = append(result, h) + } + return result +} + +func TestEncodeDecodeHistory(t *testing.T) { + var ( + m meta + dec history + obj = makeHistory() + ) + // check if meta data can be correctly encode/decode + blob := obj.meta.encode() + if err := m.decode(blob); err != nil { + t.Fatalf("Failed to decode %v", err) + } + if !reflect.DeepEqual(&m, obj.meta) { + t.Fatal("meta is mismatched") + } + + // check if account/storage data can be correctly encode/decode + accountData, storageData, accountIndexes, storageIndexes := obj.encode() + if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil { + t.Fatalf("Failed to decode, err: %v", err) + } + if !compareSet(dec.accounts, obj.accounts) { + t.Fatal("account data is mismatched") + } + if !compareStorages(dec.storages, obj.storages) { + t.Fatal("storage data is mismatched") + } + if !compareList(dec.accountList, obj.accountList) { + t.Fatal("account list is mismatched") + } + if !compareStorageList(dec.storageList, obj.storageList) { + t.Fatal("storage list is mismatched") + } +} + +func checkHistory(t *testing.T, db ethdb.KeyValueReader, freezer *rawdb.ResettableFreezer, id uint64, root common.Hash, exist bool) { + blob := rawdb.ReadStateHistoryMeta(freezer, id) + if exist && len(blob) == 0 { + t.Fatalf("Failed to load trie history, %d", id) + } + if !exist && len(blob) != 0 { + t.Fatalf("Unexpected trie history, %d", id) + } + if exist && rawdb.ReadStateID(db, root) == nil { + t.Fatalf("Root->ID mapping is not found, %d", id) + } + if !exist && rawdb.ReadStateID(db, root) != nil { + t.Fatalf("Unexpected root->ID mapping, %d", id) + } +} + +func checkHistoriesInRange(t *testing.T, db ethdb.KeyValueReader, freezer *rawdb.ResettableFreezer, from, to uint64, roots []common.Hash, exist bool) { + for i, j := from, 0; i <= to; i, j = i+1, j+1 { + checkHistory(t, db, freezer, i, roots[j], exist) + } +} + +func TestTruncateHeadHistory(t *testing.T) { + var ( + roots []common.Hash + hs = makeHistories(10) + db = rawdb.NewMemoryDatabase() + freezer, _ = openFreezer(t.TempDir(), false) + ) + defer freezer.Close() + + for i := 0; i < len(hs); i++ { + accountData, storageData, accountIndex, storageIndex := hs[i].encode() + rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) + rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1)) + roots = append(roots, hs[i].meta.root) + } + for size := len(hs); size > 0; size-- { + pruned, err := truncateFromHead(db, freezer, uint64(size-1)) + if err != nil { + t.Fatalf("Failed to truncate from head %v", err) + } + if pruned != 1 { + t.Error("Unexpected pruned items", "want", 1, "got", pruned) + } + checkHistoriesInRange(t, db, freezer, uint64(size), uint64(10), roots[size-1:], false) + checkHistoriesInRange(t, db, freezer, uint64(1), uint64(size-1), roots[:size-1], true) + } +} + +func TestTruncateTailHistory(t *testing.T) { + var ( + roots []common.Hash + hs = makeHistories(10) + db = rawdb.NewMemoryDatabase() + freezer, _ = openFreezer(t.TempDir(), false) + ) + defer freezer.Close() + + for i := 0; i < len(hs); i++ { + accountData, storageData, accountIndex, storageIndex := hs[i].encode() + rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) + rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1)) + roots = append(roots, hs[i].meta.root) + } + for newTail := 1; newTail < len(hs); newTail++ { + pruned, _ := truncateFromTail(db, freezer, uint64(newTail)) + if pruned != 1 { + t.Error("Unexpected pruned items", "want", 1, "got", pruned) + } + checkHistoriesInRange(t, db, freezer, uint64(1), uint64(newTail), roots[:newTail], false) + checkHistoriesInRange(t, db, freezer, uint64(newTail+1), uint64(10), roots[newTail:], true) + } +} + +func TestTruncateTailHistories(t *testing.T) { + var cases = []struct { + limit uint64 + expPruned int + maxPruned uint64 + minUnpruned uint64 + empty bool + }{ + { + 1, 9, 9, 10, false, + }, + { + 0, 10, 10, 0 /* no meaning */, true, + }, + { + 10, 0, 0, 1, false, + }, + } + for i, c := range cases { + var ( + roots []common.Hash + hs = makeHistories(10) + db = rawdb.NewMemoryDatabase() + freezer, _ = openFreezer(t.TempDir()+fmt.Sprintf("%d", i), false) + ) + defer freezer.Close() + + for i := 0; i < len(hs); i++ { + accountData, storageData, accountIndex, storageIndex := hs[i].encode() + rawdb.WriteStateHistory(freezer, uint64(i+1), hs[i].meta.encode(), accountIndex, storageIndex, accountData, storageData) + rawdb.WriteStateID(db, hs[i].meta.root, uint64(i+1)) + roots = append(roots, hs[i].meta.root) + } + pruned, _ := truncateFromTail(db, freezer, uint64(10)-c.limit) + if pruned != c.expPruned { + t.Error("Unexpected pruned items", "want", c.expPruned, "got", pruned) + } + if c.empty { + checkHistoriesInRange(t, db, freezer, uint64(1), uint64(10), roots, false) + } else { + tail := 10 - int(c.limit) + checkHistoriesInRange(t, db, freezer, uint64(1), c.maxPruned, roots[:tail], false) + checkHistoriesInRange(t, db, freezer, c.minUnpruned, uint64(10), roots[tail:], true) + } + } +} + +// openFreezer initializes the freezer instance for storing state histories. +func openFreezer(datadir string, readOnly bool) (*rawdb.ResettableFreezer, error) { + return rawdb.NewStateFreezer(datadir, readOnly) +} + +func compareSet[k comparable](a, b map[k][]byte) bool { + if len(a) != len(b) { + return false + } + for key, valA := range a { + valB, ok := b[key] + if !ok { + return false + } + if !bytes.Equal(valA, valB) { + return false + } + } + return true +} + +func compareList[k comparable](a, b []k) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + +func compareStorages(a, b map[common.Address]map[common.Hash][]byte) bool { + if len(a) != len(b) { + return false + } + for h, subA := range a { + subB, ok := b[h] + if !ok { + return false + } + if !compareSet(subA, subB) { + return false + } + } + return true +} + +func compareStorageList(a, b map[common.Address][]common.Hash) bool { + if len(a) != len(b) { + return false + } + for h, la := range a { + lb, ok := b[h] + if !ok { + return false + } + if !compareList(la, lb) { + return false + } + } + return true +} diff --git a/trie/triedb/pathdb/journal.go b/trie/triedb/pathdb/journal.go new file mode 100644 index 000000000000..ac770763e38d --- /dev/null +++ b/trie/triedb/pathdb/journal.go @@ -0,0 +1,387 @@ +// Copyright 2022 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 pathdb + +import ( + "bytes" + "errors" + "fmt" + "io" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +var ( + errMissJournal = errors.New("journal not found") + errMissVersion = errors.New("version not found") + errUnexpectedVersion = errors.New("unexpected journal version") + errMissDiskRoot = errors.New("disk layer root not found") + errUnmatchedJournal = errors.New("unmatched journal") +) + +const journalVersion uint64 = 0 + +// journalNode represents a trie node persisted in the journal. +type journalNode struct { + Path []byte // Path of the node in the trie + Blob []byte // RLP-encoded trie node blob, nil means the node is deleted +} + +// journalNodes represents a list trie nodes belong to a single account +// or the main account trie. +type journalNodes struct { + Owner common.Hash + Nodes []journalNode +} + +// journalAccounts represents a list accounts belong to the layer. +type journalAccounts struct { + Addresses []common.Address + Accounts [][]byte +} + +// journalStorage represents a list of storage slots belong to an account. +type journalStorage struct { + Incomplete bool + Account common.Address + Hashes []common.Hash + Slots [][]byte +} + +// loadJournal tries to parse the layer journal from the disk. +func (db *Database) loadJournal(diskRoot common.Hash) (layer, error) { + journal := rawdb.ReadTrieJournal(db.diskdb) + if len(journal) == 0 { + return nil, errMissJournal + } + r := rlp.NewStream(bytes.NewReader(journal), 0) + + // Firstly, resolve the first element as the journal version + version, err := r.Uint64() + if err != nil { + return nil, errMissVersion + } + if version != journalVersion { + return nil, fmt.Errorf("%w want %d got %d", errUnexpectedVersion, journalVersion, version) + } + // Secondly, resolve the disk layer root, ensure it's continuous + // with disk layer. Note now we can ensure it's the layer journal + // correct version, so we expect everything can be resolved properly. + var root common.Hash + if err := r.Decode(&root); err != nil { + return nil, errMissDiskRoot + } + // The journal is not matched with persistent state, discard them. + // It can happen that geth crashes without persisting the journal. + if !bytes.Equal(root.Bytes(), diskRoot.Bytes()) { + return nil, fmt.Errorf("%w want %x got %x", errUnmatchedJournal, root, diskRoot) + } + // Load the disk layer from the journal + base, err := db.loadDiskLayer(r) + if err != nil { + return nil, err + } + // Load all the diff layers from the journal + head, err := db.loadDiffLayer(base, r) + if err != nil { + return nil, err + } + log.Debug("Loaded layer journal", "diskroot", diskRoot, "diffhead", head.rootHash()) + return head, nil +} + +// loadLayers loads a pre-existing state layer backed by a key-value store. +func (db *Database) loadLayers() layer { + // Retrieve the root node of persistent state. + _, root := rawdb.ReadAccountTrieNode(db.diskdb, nil) + root = types.TrieRootHash(root) + + // Load the layers by resolving the journal + head, err := db.loadJournal(root) + if err == nil { + return head + } + // journal is not matched(or missing) with the persistent state, discard + // it. Display log for discarding journal, but try to avoid showing + // useless information when the db is created from scratch. + if !(root == types.EmptyRootHash && errors.Is(err, errMissJournal)) { + log.Info("Failed to load journal, discard it", "err", err) + } + // Return single layer with persistent state. + return newDiskLayer(root, rawdb.ReadPersistentStateID(db.diskdb), db, nil, newNodeBuffer(db.bufferSize, nil, 0)) +} + +// loadDiskLayer reads the binary blob from the layer journal, reconstructing +// a new disk layer on it. +func (db *Database) loadDiskLayer(r *rlp.Stream) (layer, error) { + // Resolve disk layer root + var root common.Hash + if err := r.Decode(&root); err != nil { + return nil, fmt.Errorf("load disk root: %v", err) + } + // Resolve the state id of disk layer, it can be different + // with the persistent id tracked in disk, the id distance + // is the number of transitions aggregated in disk layer. + var id uint64 + if err := r.Decode(&id); err != nil { + return nil, fmt.Errorf("load state id: %v", err) + } + stored := rawdb.ReadPersistentStateID(db.diskdb) + if stored > id { + return nil, fmt.Errorf("invalid state id: stored %d resolved %d", stored, id) + } + // Resolve nodes cached in node buffer + var encoded []journalNodes + if err := r.Decode(&encoded); err != nil { + return nil, fmt.Errorf("load disk nodes: %v", err) + } + nodes := make(map[common.Hash]map[string]*trienode.Node) + for _, entry := range encoded { + subset := make(map[string]*trienode.Node) + for _, n := range entry.Nodes { + if len(n.Blob) > 0 { + subset[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob) + } else { + subset[string(n.Path)] = trienode.NewDeleted() + } + } + nodes[entry.Owner] = subset + } + // Calculate the internal state transitions by id difference. + base := newDiskLayer(root, id, db, nil, newNodeBuffer(db.bufferSize, nodes, id-stored)) + return base, nil +} + +// loadDiffLayer reads the next sections of a layer journal, reconstructing a new +// diff and verifying that it can be linked to the requested parent. +func (db *Database) loadDiffLayer(parent layer, r *rlp.Stream) (layer, error) { + // Read the next diff journal entry + var root common.Hash + if err := r.Decode(&root); err != nil { + // The first read may fail with EOF, marking the end of the journal + if err == io.EOF { + return parent, nil + } + return nil, fmt.Errorf("load diff root: %v", err) + } + var block uint64 + if err := r.Decode(&block); err != nil { + return nil, fmt.Errorf("load block number: %v", err) + } + // Read in-memory trie nodes from journal + var encoded []journalNodes + if err := r.Decode(&encoded); err != nil { + return nil, fmt.Errorf("load diff nodes: %v", err) + } + nodes := make(map[common.Hash]map[string]*trienode.Node) + for _, entry := range encoded { + subset := make(map[string]*trienode.Node) + for _, n := range entry.Nodes { + if len(n.Blob) > 0 { + subset[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob) + } else { + subset[string(n.Path)] = trienode.NewDeleted() + } + } + nodes[entry.Owner] = subset + } + // Read state changes from journal + var ( + jaccounts journalAccounts + jstorages []journalStorage + accounts = make(map[common.Address][]byte) + storages = make(map[common.Address]map[common.Hash][]byte) + incomplete = make(map[common.Address]struct{}) + ) + if err := r.Decode(&jaccounts); err != nil { + return nil, fmt.Errorf("load diff accounts: %v", err) + } + for i, addr := range jaccounts.Addresses { + accounts[addr] = jaccounts.Accounts[i] + } + if err := r.Decode(&jstorages); err != nil { + return nil, fmt.Errorf("load diff storages: %v", err) + } + for _, entry := range jstorages { + set := make(map[common.Hash][]byte) + for i, h := range entry.Hashes { + if len(entry.Slots[i]) > 0 { + set[h] = entry.Slots[i] + } else { + set[h] = nil + } + } + if entry.Incomplete { + incomplete[entry.Account] = struct{}{} + } + storages[entry.Account] = set + } + return db.loadDiffLayer(newDiffLayer(parent, root, parent.stateID()+1, block, nodes, triestate.New(accounts, storages, incomplete)), r) +} + +// journal implements the layer interface, marshaling the un-flushed trie nodes +// along with layer meta data into provided byte buffer. +func (dl *diskLayer) journal(w io.Writer) error { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // Ensure the layer didn't get stale + if dl.stale { + return errSnapshotStale + } + // Step one, write the disk root into the journal. + if err := rlp.Encode(w, dl.root); err != nil { + return err + } + // Step two, write the corresponding state id into the journal + if err := rlp.Encode(w, dl.id); err != nil { + return err + } + // Step three, write all unwritten nodes into the journal + nodes := make([]journalNodes, 0, len(dl.buffer.nodes)) + for owner, subset := range dl.buffer.nodes { + entry := journalNodes{Owner: owner} + for path, node := range subset { + entry.Nodes = append(entry.Nodes, journalNode{Path: []byte(path), Blob: node.Blob}) + } + nodes = append(nodes, entry) + } + if err := rlp.Encode(w, nodes); err != nil { + return err + } + log.Debug("Journaled pathdb disk layer", "root", dl.root, "nodes", len(dl.buffer.nodes)) + return nil +} + +// journal implements the layer interface, writing the memory layer contents +// into a buffer to be stored in the database as the layer journal. +func (dl *diffLayer) journal(w io.Writer) error { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // journal the parent first + if err := dl.parent.journal(w); err != nil { + return err + } + // Everything below was journaled, persist this layer too + if err := rlp.Encode(w, dl.root); err != nil { + return err + } + if err := rlp.Encode(w, dl.block); err != nil { + return err + } + // Write the accumulated trie nodes into buffer + nodes := make([]journalNodes, 0, len(dl.nodes)) + for owner, subset := range dl.nodes { + entry := journalNodes{Owner: owner} + for path, node := range subset { + entry.Nodes = append(entry.Nodes, journalNode{Path: []byte(path), Blob: node.Blob}) + } + nodes = append(nodes, entry) + } + if err := rlp.Encode(w, nodes); err != nil { + return err + } + // Write the accumulated state changes into buffer + var jacct journalAccounts + for addr, account := range dl.states.Accounts { + jacct.Addresses = append(jacct.Addresses, addr) + jacct.Accounts = append(jacct.Accounts, account) + } + if err := rlp.Encode(w, jacct); err != nil { + return err + } + storage := make([]journalStorage, 0, len(dl.states.Storages)) + for addr, slots := range dl.states.Storages { + entry := journalStorage{Account: addr} + if _, ok := dl.states.Incomplete[addr]; ok { + entry.Incomplete = true + } + for slotHash, slot := range slots { + entry.Hashes = append(entry.Hashes, slotHash) + entry.Slots = append(entry.Slots, slot) + } + storage = append(storage, entry) + } + if err := rlp.Encode(w, storage); err != nil { + return err + } + log.Debug("Journaled pathdb diff layer", "root", dl.root, "parent", dl.parent.rootHash(), "id", dl.stateID(), "block", dl.block, "nodes", len(dl.nodes)) + return nil +} + +// Journal commits an entire diff hierarchy to disk into a single journal entry. +// This is meant to be used during shutdown to persist the layer without +// flattening everything down (bad for reorgs). And this function will mark the +// database as read-only to prevent all following mutation to disk. +func (db *Database) Journal(root common.Hash) error { + // Retrieve the head layer to journal from. + l := db.tree.get(root) + if l == nil { + return fmt.Errorf("triedb layer [%#x] missing", root) + } + disk := db.tree.bottom() + if l, ok := l.(*diffLayer); ok { + log.Info("Persisting dirty state to disk", "head", l.block, "root", root, "layers", l.id-disk.id+disk.buffer.layers) + } else { // disk layer only on noop runs (likely) or deep reorgs (unlikely) + log.Info("Persisting dirty state to disk", "root", root, "layers", disk.buffer.layers) + } + start := time.Now() + + // Run the journaling + db.lock.Lock() + defer db.lock.Unlock() + + // Short circuit if the database is in read only mode. + if db.readOnly { + return errDatabaseReadOnly + } + // Firstly write out the metadata of journal + journal := new(bytes.Buffer) + if err := rlp.Encode(journal, journalVersion); err != nil { + return err + } + // The stored state in disk might be empty, convert the + // root to emptyRoot in this case. + _, diskroot := rawdb.ReadAccountTrieNode(db.diskdb, nil) + diskroot = types.TrieRootHash(diskroot) + + // Secondly write out the state root in disk, ensure all layers + // on top are continuous with disk. + if err := rlp.Encode(journal, diskroot); err != nil { + return err + } + // Finally write out the journal of each layer in reverse order. + if err := l.journal(journal); err != nil { + return err + } + // Store the journal into the database and return + rawdb.WriteTrieJournal(db.diskdb, journal.Bytes()) + + // Set the db in read only mode to reject all following mutations + db.readOnly = true + log.Info("Persisted dirty state to disk", "size", common.StorageSize(journal.Len()), "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} diff --git a/trie/triedb/pathdb/layertree.go b/trie/triedb/pathdb/layertree.go new file mode 100644 index 000000000000..d314779910e9 --- /dev/null +++ b/trie/triedb/pathdb/layertree.go @@ -0,0 +1,214 @@ +// Copyright 2022 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 pathdb + +import ( + "errors" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" +) + +// layerTree is a group of state layers identified by the state root. +// This structure defines a few basic operations for manipulating +// state layers linked with each other in a tree structure. It's +// thread-safe to use. However, callers need to ensure the thread-safety +// of the referenced layer by themselves. +type layerTree struct { + lock sync.RWMutex + layers map[common.Hash]layer +} + +// newLayerTree constructs the layerTree with the given head layer. +func newLayerTree(head layer) *layerTree { + tree := new(layerTree) + tree.reset(head) + return tree +} + +// reset initializes the layerTree by the given head layer. +// All the ancestors will be iterated out and linked in the tree. +func (tree *layerTree) reset(head layer) { + tree.lock.Lock() + defer tree.lock.Unlock() + + var layers = make(map[common.Hash]layer) + for head != nil { + layers[head.rootHash()] = head + head = head.parentLayer() + } + tree.layers = layers +} + +// get retrieves a layer belonging to the given state root. +func (tree *layerTree) get(root common.Hash) layer { + tree.lock.RLock() + defer tree.lock.RUnlock() + + return tree.layers[types.TrieRootHash(root)] +} + +// forEach iterates the stored layers inside and applies the +// given callback on them. +func (tree *layerTree) forEach(onLayer func(layer)) { + tree.lock.RLock() + defer tree.lock.RUnlock() + + for _, layer := range tree.layers { + onLayer(layer) + } +} + +// len returns the number of layers cached. +func (tree *layerTree) len() int { + tree.lock.RLock() + defer tree.lock.RUnlock() + + return len(tree.layers) +} + +// add inserts a new layer into the tree if it can be linked to an existing old parent. +func (tree *layerTree) add(root common.Hash, parentRoot common.Hash, block uint64, nodes *trienode.MergedNodeSet, states *triestate.Set) error { + // Reject noop updates to avoid self-loops. This is a special case that can + // happen for clique networks and proof-of-stake networks where empty blocks + // don't modify the state (0 block subsidy). + // + // Although we could silently ignore this internally, it should be the caller's + // responsibility to avoid even attempting to insert such a layer. + root, parentRoot = types.TrieRootHash(root), types.TrieRootHash(parentRoot) + if root == parentRoot { + return errors.New("layer cycle") + } + parent := tree.get(parentRoot) + if parent == nil { + return fmt.Errorf("triedb parent [%#x] layer missing", parentRoot) + } + l := parent.update(root, parent.stateID()+1, block, nodes.Flatten(), states) + + tree.lock.Lock() + tree.layers[l.rootHash()] = l + tree.lock.Unlock() + return nil +} + +// cap traverses downwards the diff tree until the number of allowed diff layers +// are crossed. All diffs beyond the permitted number are flattened downwards. +func (tree *layerTree) cap(root common.Hash, layers int) error { + // Retrieve the head layer to cap from + root = types.TrieRootHash(root) + l := tree.get(root) + if l == nil { + return fmt.Errorf("triedb layer [%#x] missing", root) + } + diff, ok := l.(*diffLayer) + if !ok { + return fmt.Errorf("triedb layer [%#x] is disk layer", root) + } + tree.lock.Lock() + defer tree.lock.Unlock() + + // If full commit was requested, flatten the diffs and merge onto disk + if layers == 0 { + base, err := diff.persist(true) + if err != nil { + return err + } + // Replace the entire layer tree with the flat base + tree.layers = map[common.Hash]layer{base.rootHash(): base} + return nil + } + // Dive until we run out of layers or reach the persistent database + for i := 0; i < layers-1; i++ { + // If we still have diff layers below, continue down + if parent, ok := diff.parentLayer().(*diffLayer); ok { + diff = parent + } else { + // Diff stack too shallow, return without modifications + return nil + } + } + // We're out of layers, flatten anything below, stopping if it's the disk or if + // the memory limit is not yet exceeded. + switch parent := diff.parentLayer().(type) { + case *diskLayer: + return nil + + case *diffLayer: + // Hold the lock to prevent any read operations until the new + // parent is linked correctly. + diff.lock.Lock() + + base, err := parent.persist(false) + if err != nil { + diff.lock.Unlock() + return err + } + tree.layers[base.rootHash()] = base + diff.parent = base + + diff.lock.Unlock() + + default: + panic(fmt.Sprintf("unknown data layer in triedb: %T", parent)) + } + // Remove any layer that is stale or links into a stale layer + children := make(map[common.Hash][]common.Hash) + for root, layer := range tree.layers { + if dl, ok := layer.(*diffLayer); ok { + parent := dl.parentLayer().rootHash() + children[parent] = append(children[parent], root) + } + } + var remove func(root common.Hash) + remove = func(root common.Hash) { + delete(tree.layers, root) + for _, child := range children[root] { + remove(child) + } + delete(children, root) + } + for root, layer := range tree.layers { + if dl, ok := layer.(*diskLayer); ok && dl.isStale() { + remove(root) + } + } + return nil +} + +// bottom returns the bottom-most disk layer in this tree. +func (tree *layerTree) bottom() *diskLayer { + tree.lock.RLock() + defer tree.lock.RUnlock() + + if len(tree.layers) == 0 { + return nil // Shouldn't happen, empty tree + } + // pick a random one as the entry point + var current layer + for _, layer := range tree.layers { + current = layer + break + } + for current.parentLayer() != nil { + current = current.parentLayer() + } + return current.(*diskLayer) +} diff --git a/trie/triedb/pathdb/metrics.go b/trie/triedb/pathdb/metrics.go new file mode 100644 index 000000000000..9e2b1dcbf55e --- /dev/null +++ b/trie/triedb/pathdb/metrics.go @@ -0,0 +1,50 @@ +// Copyright 2022 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 pathdb + +import "github.com/ethereum/go-ethereum/metrics" + +var ( + cleanHitMeter = metrics.NewRegisteredMeter("pathdb/clean/hit", nil) + cleanMissMeter = metrics.NewRegisteredMeter("pathdb/clean/miss", nil) + cleanReadMeter = metrics.NewRegisteredMeter("pathdb/clean/read", nil) + cleanWriteMeter = metrics.NewRegisteredMeter("pathdb/clean/write", nil) + + dirtyHitMeter = metrics.NewRegisteredMeter("pathdb/dirty/hit", nil) + dirtyMissMeter = metrics.NewRegisteredMeter("pathdb/dirty/miss", nil) + dirtyReadMeter = metrics.NewRegisteredMeter("pathdb/dirty/read", nil) + dirtyWriteMeter = metrics.NewRegisteredMeter("pathdb/dirty/write", nil) + dirtyNodeHitDepthHist = metrics.NewRegisteredHistogram("pathdb/dirty/depth", nil, metrics.NewExpDecaySample(1028, 0.015)) + + cleanFalseMeter = metrics.NewRegisteredMeter("pathdb/clean/false", nil) + dirtyFalseMeter = metrics.NewRegisteredMeter("pathdb/dirty/false", nil) + diskFalseMeter = metrics.NewRegisteredMeter("pathdb/disk/false", nil) + + commitTimeTimer = metrics.NewRegisteredTimer("pathdb/commit/time", nil) + commitNodesMeter = metrics.NewRegisteredMeter("pathdb/commit/nodes", nil) + commitBytesMeter = metrics.NewRegisteredMeter("pathdb/commit/bytes", nil) + + gcNodesMeter = metrics.NewRegisteredMeter("pathdb/gc/nodes", nil) + gcBytesMeter = metrics.NewRegisteredMeter("pathdb/gc/bytes", nil) + + diffLayerBytesMeter = metrics.NewRegisteredMeter("pathdb/diff/bytes", nil) + diffLayerNodesMeter = metrics.NewRegisteredMeter("pathdb/diff/nodes", nil) + + historyBuildTimeMeter = metrics.NewRegisteredTimer("pathdb/history/time", nil) + historyDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/data", nil) + historyIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/index", nil) +) diff --git a/trie/triedb/pathdb/nodebuffer.go b/trie/triedb/pathdb/nodebuffer.go new file mode 100644 index 000000000000..4a7d328b9afb --- /dev/null +++ b/trie/triedb/pathdb/nodebuffer.go @@ -0,0 +1,275 @@ +// Copyright 2022 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 pathdb + +import ( + "fmt" + "time" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/trie/trienode" +) + +// nodebuffer is a collection of modified trie nodes to aggregate the disk +// write. The content of the nodebuffer must be checked before diving into +// disk (since it basically is not-yet-written data). +type nodebuffer struct { + layers uint64 // The number of diff layers aggregated inside + size uint64 // The size of aggregated writes + limit uint64 // The maximum memory allowance in bytes + nodes map[common.Hash]map[string]*trienode.Node // The dirty node set, mapped by owner and path +} + +// newNodeBuffer initializes the node buffer with the provided nodes. +func newNodeBuffer(limit int, nodes map[common.Hash]map[string]*trienode.Node, layers uint64) *nodebuffer { + if nodes == nil { + nodes = make(map[common.Hash]map[string]*trienode.Node) + } + var size uint64 + for _, subset := range nodes { + for path, n := range subset { + size += uint64(len(n.Blob) + len(path)) + } + } + return &nodebuffer{ + layers: layers, + nodes: nodes, + size: size, + limit: uint64(limit), + } +} + +// node retrieves the trie node with given node info. +func (b *nodebuffer) node(owner common.Hash, path []byte, hash common.Hash) (*trienode.Node, error) { + subset, ok := b.nodes[owner] + if !ok { + return nil, nil + } + n, ok := subset[string(path)] + if !ok { + return nil, nil + } + if n.Hash != hash { + dirtyFalseMeter.Mark(1) + log.Error("Unexpected trie node in node buffer", "owner", owner, "path", path, "expect", hash, "got", n.Hash) + return nil, newUnexpectedNodeError("dirty", hash, n.Hash, owner, path, n.Blob) + } + return n, nil +} + +// commit merges the dirty nodes into the nodebuffer. This operation won't take +// the ownership of the nodes map which belongs to the bottom-most diff layer. +// It will just hold the node references from the given map which are safe to +// copy. +func (b *nodebuffer) commit(nodes map[common.Hash]map[string]*trienode.Node) *nodebuffer { + var ( + delta int64 + overwrite int64 + overwriteSize int64 + ) + for owner, subset := range nodes { + current, exist := b.nodes[owner] + if !exist { + // Allocate a new map for the subset instead of claiming it directly + // from the passed map to avoid potential concurrent map read/write. + // The nodes belong to original diff layer are still accessible even + // after merging, thus the ownership of nodes map should still belong + // to original layer and any mutation on it should be prevented. + current = make(map[string]*trienode.Node) + for path, n := range subset { + current[path] = n + delta += int64(len(n.Blob) + len(path)) + } + b.nodes[owner] = current + continue + } + for path, n := range subset { + if orig, exist := current[path]; !exist { + delta += int64(len(n.Blob) + len(path)) + } else { + delta += int64(len(n.Blob) - len(orig.Blob)) + overwrite++ + overwriteSize += int64(len(orig.Blob) + len(path)) + } + current[path] = n + } + b.nodes[owner] = current + } + b.updateSize(delta) + b.layers++ + gcNodesMeter.Mark(overwrite) + gcBytesMeter.Mark(overwriteSize) + return b +} + +// revert is the reverse operation of commit. It also merges the provided nodes +// into the nodebuffer, the difference is that the provided node set should +// revert the changes made by the last state transition. +func (b *nodebuffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) error { + // Short circuit if no embedded state transition to revert. + if b.layers == 0 { + return errStateUnrecoverable + } + b.layers-- + + // Reset the entire buffer if only a single transition left. + if b.layers == 0 { + b.reset() + return nil + } + var delta int64 + for owner, subset := range nodes { + current, ok := b.nodes[owner] + if !ok { + panic(fmt.Sprintf("non-existent subset (%x)", owner)) + } + for path, n := range subset { + orig, ok := current[path] + if !ok { + // There is a special case in MPT that one child is removed from + // a fullNode which only has two children, and then a new child + // with different position is immediately inserted into the fullNode. + // In this case, the clean child of the fullNode will also be + // marked as dirty because of node collapse and expansion. + // + // In case of database rollback, don't panic if this "clean" + // node occurs which is not present in buffer. + var nhash common.Hash + if owner == (common.Hash{}) { + _, nhash = rawdb.ReadAccountTrieNode(db, []byte(path)) + } else { + _, nhash = rawdb.ReadStorageTrieNode(db, owner, []byte(path)) + } + // Ignore the clean node in the case described above. + if nhash == n.Hash { + continue + } + panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex())) + } + current[path] = n + delta += int64(len(n.Blob)) - int64(len(orig.Blob)) + } + } + b.updateSize(delta) + return nil +} + +// updateSize updates the total cache size by the given delta. +func (b *nodebuffer) updateSize(delta int64) { + size := int64(b.size) + delta + if size >= 0 { + b.size = uint64(size) + return + } + s := b.size + b.size = 0 + log.Error("Invalid pathdb buffer size", "prev", common.StorageSize(s), "delta", common.StorageSize(delta)) +} + +// reset cleans up the disk cache. +func (b *nodebuffer) reset() { + b.layers = 0 + b.size = 0 + b.nodes = make(map[common.Hash]map[string]*trienode.Node) +} + +// empty returns an indicator if nodebuffer contains any state transition inside. +func (b *nodebuffer) empty() bool { + return b.layers == 0 +} + +// setSize sets the buffer size to the provided number, and invokes a flush +// operation if the current memory usage exceeds the new limit. +func (b *nodebuffer) setSize(size int, db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64) error { + b.limit = uint64(size) + return b.flush(db, clean, id, false) +} + +// flush persists the in-memory dirty trie node into the disk if the configured +// memory threshold is reached. Note, all data must be written atomically. +func (b *nodebuffer) flush(db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64, force bool) error { + if b.size <= b.limit && !force { + return nil + } + // Ensure the target state id is aligned with the internal counter. + head := rawdb.ReadPersistentStateID(db) + if head+b.layers != id { + return fmt.Errorf("buffer layers (%d) cannot be applied on top of persisted state id (%d) to reach requested state id (%d)", b.layers, head, id) + } + var ( + start = time.Now() + batch = db.NewBatchWithSize(int(b.size)) + ) + nodes := writeNodes(batch, b.nodes, clean) + rawdb.WritePersistentStateID(batch, id) + + // Flush all mutations in a single batch + size := batch.ValueSize() + if err := batch.Write(); err != nil { + return err + } + commitBytesMeter.Mark(int64(size)) + commitNodesMeter.Mark(int64(nodes)) + commitTimeTimer.UpdateSince(start) + log.Debug("Persisted pathdb nodes", "nodes", len(b.nodes), "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start))) + b.reset() + return nil +} + +// writeNodes writes the trie nodes into the provided database batch. +// Note this function will also inject all the newly written nodes +// into clean cache. +func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string]*trienode.Node, clean *fastcache.Cache) (total int) { + for owner, subset := range nodes { + for path, n := range subset { + if n.IsDeleted() { + if owner == (common.Hash{}) { + rawdb.DeleteAccountTrieNode(batch, []byte(path)) + } else { + rawdb.DeleteStorageTrieNode(batch, owner, []byte(path)) + } + if clean != nil { + clean.Del(cacheKey(owner, []byte(path))) + } + } else { + if owner == (common.Hash{}) { + rawdb.WriteAccountTrieNode(batch, []byte(path), n.Blob) + } else { + rawdb.WriteStorageTrieNode(batch, owner, []byte(path), n.Blob) + } + if clean != nil { + clean.Set(cacheKey(owner, []byte(path)), n.Blob) + } + } + } + total += len(subset) + } + return total +} + +// cacheKey constructs the unique key of clean cache. +func cacheKey(owner common.Hash, path []byte) []byte { + if owner == (common.Hash{}) { + return path + } + return append(owner.Bytes(), path...) +} diff --git a/trie/triedb/pathdb/testutils.go b/trie/triedb/pathdb/testutils.go new file mode 100644 index 000000000000..d6fdacb4213e --- /dev/null +++ b/trie/triedb/pathdb/testutils.go @@ -0,0 +1,156 @@ +// Copyright 2023 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 pathdb + +import ( + "bytes" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/triestate" + "golang.org/x/exp/slices" +) + +// testHasher is a test utility for computing root hash of a batch of state +// elements. The hash algorithm is to sort all the elements in lexicographical +// order, concat the key and value in turn, and perform hash calculation on +// the concatenated bytes. Except the root hash, a nodeset will be returned +// once Commit is called, which contains all the changes made to hasher. +type testHasher struct { + owner common.Hash // owner identifier + root common.Hash // original root + dirties map[common.Hash][]byte // dirty states + cleans map[common.Hash][]byte // clean states +} + +// newTestHasher constructs a hasher object with provided states. +func newTestHasher(owner common.Hash, root common.Hash, cleans map[common.Hash][]byte) (*testHasher, error) { + if cleans == nil { + cleans = make(map[common.Hash][]byte) + } + if got, _ := hash(cleans); got != root { + return nil, fmt.Errorf("state root mismatched, want: %x, got: %x", root, got) + } + return &testHasher{ + owner: owner, + root: root, + dirties: make(map[common.Hash][]byte), + cleans: cleans, + }, nil +} + +// Get returns the value for key stored in the trie. +func (h *testHasher) Get(key []byte) ([]byte, error) { + hash := common.BytesToHash(key) + val, ok := h.dirties[hash] + if ok { + return val, nil + } + return h.cleans[hash], nil +} + +// Update associates key with value in the trie. +func (h *testHasher) Update(key, value []byte) error { + h.dirties[common.BytesToHash(key)] = common.CopyBytes(value) + return nil +} + +// Delete removes any existing value for key from the trie. +func (h *testHasher) Delete(key []byte) error { + h.dirties[common.BytesToHash(key)] = nil + return nil +} + +// Commit computes the new hash of the states and returns the set with all +// state changes. +func (h *testHasher) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) { + var ( + nodes = make(map[common.Hash][]byte) + set = trienode.NewNodeSet(h.owner) + ) + for hash, val := range h.cleans { + nodes[hash] = val + } + for hash, val := range h.dirties { + nodes[hash] = val + if bytes.Equal(val, h.cleans[hash]) { + continue + } + if len(val) == 0 { + set.AddNode(hash.Bytes(), trienode.NewDeleted()) + } else { + set.AddNode(hash.Bytes(), trienode.New(crypto.Keccak256Hash(val), val)) + } + } + root, blob := hash(nodes) + + // Include the dirty root node as well. + if root != types.EmptyRootHash && root != h.root { + set.AddNode(nil, trienode.New(root, blob)) + } + if root == types.EmptyRootHash && h.root != types.EmptyRootHash { + set.AddNode(nil, trienode.NewDeleted()) + } + return root, set, nil +} + +// hash performs the hash computation upon the provided states. +func hash(states map[common.Hash][]byte) (common.Hash, []byte) { + var hs []common.Hash + for hash := range states { + hs = append(hs, hash) + } + slices.SortFunc(hs, common.Hash.Cmp) + + var input []byte + for _, hash := range hs { + if len(states[hash]) == 0 { + continue + } + input = append(input, hash.Bytes()...) + input = append(input, states[hash]...) + } + if len(input) == 0 { + return types.EmptyRootHash, nil + } + return crypto.Keccak256Hash(input), input +} + +type hashLoader struct { + accounts map[common.Hash][]byte + storages map[common.Hash]map[common.Hash][]byte +} + +func newHashLoader(accounts map[common.Hash][]byte, storages map[common.Hash]map[common.Hash][]byte) *hashLoader { + return &hashLoader{ + accounts: accounts, + storages: storages, + } +} + +// OpenTrie opens the main account trie. +func (l *hashLoader) OpenTrie(root common.Hash) (triestate.Trie, error) { + return newTestHasher(common.Hash{}, root, l.accounts) +} + +// OpenStorageTrie opens the storage trie of an account. +func (l *hashLoader) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (triestate.Trie, error) { + return newTestHasher(addrHash, root, l.storages[addrHash]) +} diff --git a/trie/trienode/node.go b/trie/trienode/node.go index 8152eab6c0f7..98d5588b6d3a 100644 --- a/trie/trienode/node.go +++ b/trie/trienode/node.go @@ -25,8 +25,8 @@ import ( ) // Node is a wrapper which contains the encoded blob of the trie node and its -// unique hash identifier. It is general enough that can be used to represent -// trie nodes corresponding to different trie implementations. +// node hash. It is general enough that can be used to represent trie node +// corresponding to different trie implementations. type Node struct { Hash common.Hash // Node hash, empty for deleted node Blob []byte // Encoded node blob, nil for the deleted node @@ -42,35 +42,13 @@ func (n *Node) IsDeleted() bool { return n.Hash == (common.Hash{}) } -// WithPrev wraps the Node with the previous node value attached. -type WithPrev struct { - *Node - Prev []byte // Encoded original value, nil means it's non-existent -} - -// Unwrap returns the internal Node object. -func (n *WithPrev) Unwrap() *Node { - return n.Node -} - -// Size returns the total memory size used by this node. It overloads -// the function in Node by counting the size of previous value as well. -func (n *WithPrev) Size() int { - return n.Node.Size() + len(n.Prev) -} - // New constructs a node with provided node information. func New(hash common.Hash, blob []byte) *Node { return &Node{Hash: hash, Blob: blob} } -// NewWithPrev constructs a node with provided node information. -func NewWithPrev(hash common.Hash, blob []byte, prev []byte) *WithPrev { - return &WithPrev{ - Node: New(hash, blob), - Prev: prev, - } -} +// NewDeleted constructs a node which is deleted. +func NewDeleted() *Node { return New(common.Hash{}, nil) } // leaf represents a trie leaf node type leaf struct { @@ -83,7 +61,7 @@ type leaf struct { type NodeSet struct { Owner common.Hash Leaves []*leaf - Nodes map[string]*WithPrev + Nodes map[string]*Node updates int // the count of updated and inserted nodes deletes int // the count of deleted nodes } @@ -93,26 +71,26 @@ type NodeSet struct { func NewNodeSet(owner common.Hash) *NodeSet { return &NodeSet{ Owner: owner, - Nodes: make(map[string]*WithPrev), + Nodes: make(map[string]*Node), } } // ForEachWithOrder iterates the nodes with the order from bottom to top, // right to left, nodes with the longest path will be iterated first. func (set *NodeSet) ForEachWithOrder(callback func(path string, n *Node)) { - var paths sort.StringSlice + var paths []string for path := range set.Nodes { paths = append(paths, path) } - // Bottom-up, longest path first - sort.Sort(sort.Reverse(paths)) + // Bottom-up, the longest path first + sort.Sort(sort.Reverse(sort.StringSlice(paths))) for _, path := range paths { - callback(path, set.Nodes[path].Unwrap()) + callback(path, set.Nodes[path]) } } // AddNode adds the provided node into set. -func (set *NodeSet) AddNode(path []byte, n *WithPrev) { +func (set *NodeSet) AddNode(path []byte, n *Node) { if n.IsDeleted() { set.deletes += 1 } else { @@ -121,6 +99,26 @@ func (set *NodeSet) AddNode(path []byte, n *WithPrev) { set.Nodes[string(path)] = n } +// Merge adds a set of nodes into the set. +func (set *NodeSet) Merge(owner common.Hash, nodes map[string]*Node) error { + if set.Owner != owner { + return fmt.Errorf("nodesets belong to different owner are not mergeable %x-%x", set.Owner, owner) + } + for path, node := range nodes { + prev, ok := set.Nodes[path] + if ok { + // overwrite happens, revoke the counter + if prev.IsDeleted() { + set.deletes -= 1 + } else { + set.updates -= 1 + } + } + set.AddNode([]byte(path), node) + } + return nil +} + // AddLeaf adds the provided leaf node into set. TODO(rjl493456442) how can // we get rid of it? func (set *NodeSet) AddLeaf(parent common.Hash, blob []byte) { @@ -150,16 +148,11 @@ func (set *NodeSet) Summary() string { for path, n := range set.Nodes { // Deletion if n.IsDeleted() { - fmt.Fprintf(out, " [-]: %x prev: %x\n", path, n.Prev) + fmt.Fprintf(out, " [-]: %x\n", path) continue } - // Insertion - if len(n.Prev) == 0 { - fmt.Fprintf(out, " [+]: %x -> %v\n", path, n.Hash) - continue - } - // Update - fmt.Fprintf(out, " [*]: %x -> %v prev: %x\n", path, n.Hash, n.Prev) + // Insertion or update + fmt.Fprintf(out, " [+/*]: %x -> %v \n", path, n.Hash) } } for _, n := range set.Leaves { @@ -188,10 +181,19 @@ func NewWithNodeSet(set *NodeSet) *MergedNodeSet { // Merge merges the provided dirty nodes of a trie into the set. The assumption // is held that no duplicated set belonging to the same trie will be merged twice. func (set *MergedNodeSet) Merge(other *NodeSet) error { - _, present := set.Sets[other.Owner] + subset, present := set.Sets[other.Owner] if present { - return fmt.Errorf("duplicate trie for owner %#x", other.Owner) + return subset.Merge(other.Owner, other.Nodes) } set.Sets[other.Owner] = other return nil } + +// Flatten returns a two-dimensional map for internal nodes. +func (set *MergedNodeSet) Flatten() map[common.Hash]map[string]*Node { + nodes := make(map[common.Hash]map[string]*Node) + for owner, set := range set.Sets { + nodes[owner] = set.Nodes + } + return nodes +} diff --git a/light/nodeset.go b/trie/trienode/proof.go similarity index 73% rename from light/nodeset.go rename to trie/trienode/proof.go index 3662596785c7..012f0087dded 100644 --- a/light/nodeset.go +++ b/trie/trienode/proof.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package light +package trienode import ( "errors" @@ -26,9 +26,9 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -// NodeSet stores a set of trie nodes. It implements trie.Database and can also +// ProofSet stores a set of trie nodes. It implements trie.Database and can also // act as a cache for another trie.Database. -type NodeSet struct { +type ProofSet struct { nodes map[string][]byte order []string @@ -36,15 +36,15 @@ type NodeSet struct { lock sync.RWMutex } -// NewNodeSet creates an empty node set -func NewNodeSet() *NodeSet { - return &NodeSet{ +// NewProofSet creates an empty node set +func NewProofSet() *ProofSet { + return &ProofSet{ nodes: make(map[string][]byte), } } // Put stores a new node in the set -func (db *NodeSet) Put(key []byte, value []byte) error { +func (db *ProofSet) Put(key []byte, value []byte) error { db.lock.Lock() defer db.lock.Unlock() @@ -61,7 +61,7 @@ func (db *NodeSet) Put(key []byte, value []byte) error { } // Delete removes a node from the set -func (db *NodeSet) Delete(key []byte) error { +func (db *ProofSet) Delete(key []byte) error { db.lock.Lock() defer db.lock.Unlock() @@ -70,7 +70,7 @@ func (db *NodeSet) Delete(key []byte) error { } // Get returns a stored node -func (db *NodeSet) Get(key []byte) ([]byte, error) { +func (db *ProofSet) Get(key []byte) ([]byte, error) { db.lock.RLock() defer db.lock.RUnlock() @@ -81,13 +81,13 @@ func (db *NodeSet) Get(key []byte) ([]byte, error) { } // Has returns true if the node set contains the given key -func (db *NodeSet) Has(key []byte) (bool, error) { +func (db *ProofSet) Has(key []byte) (bool, error) { _, err := db.Get(key) return err == nil, nil } // KeyCount returns the number of nodes in the set -func (db *NodeSet) KeyCount() int { +func (db *ProofSet) KeyCount() int { db.lock.RLock() defer db.lock.RUnlock() @@ -95,19 +95,19 @@ func (db *NodeSet) KeyCount() int { } // DataSize returns the aggregated data size of nodes in the set -func (db *NodeSet) DataSize() int { +func (db *ProofSet) DataSize() int { db.lock.RLock() defer db.lock.RUnlock() return db.dataSize } -// NodeList converts the node set to a NodeList -func (db *NodeSet) NodeList() NodeList { +// List converts the node set to a ProofList +func (db *ProofSet) List() ProofList { db.lock.RLock() defer db.lock.RUnlock() - var values NodeList + var values ProofList for _, key := range db.order { values = append(values, db.nodes[key]) } @@ -115,7 +115,7 @@ func (db *NodeSet) NodeList() NodeList { } // Store writes the contents of the set to the given database -func (db *NodeSet) Store(target ethdb.KeyValueWriter) { +func (db *ProofSet) Store(target ethdb.KeyValueWriter) { db.lock.RLock() defer db.lock.RUnlock() @@ -124,36 +124,36 @@ func (db *NodeSet) Store(target ethdb.KeyValueWriter) { } } -// NodeList stores an ordered list of trie nodes. It implements ethdb.KeyValueWriter. -type NodeList []rlp.RawValue +// ProofList stores an ordered list of trie nodes. It implements ethdb.KeyValueWriter. +type ProofList []rlp.RawValue // Store writes the contents of the list to the given database -func (n NodeList) Store(db ethdb.KeyValueWriter) { +func (n ProofList) Store(db ethdb.KeyValueWriter) { for _, node := range n { db.Put(crypto.Keccak256(node), node) } } -// NodeSet converts the node list to a NodeSet -func (n NodeList) NodeSet() *NodeSet { - db := NewNodeSet() +// Set converts the node list to a ProofSet +func (n ProofList) Set() *ProofSet { + db := NewProofSet() n.Store(db) return db } // Put stores a new node at the end of the list -func (n *NodeList) Put(key []byte, value []byte) error { +func (n *ProofList) Put(key []byte, value []byte) error { *n = append(*n, value) return nil } // Delete panics as there's no reason to remove a node from the list. -func (n *NodeList) Delete(key []byte) error { +func (n *ProofList) Delete(key []byte) error { panic("not supported") } // DataSize returns the aggregated data size of nodes in the list -func (n NodeList) DataSize() int { +func (n ProofList) DataSize() int { var size int for _, node := range n { size += len(node) diff --git a/trie/triestate/state.go b/trie/triestate/state.go new file mode 100644 index 000000000000..4c47e9c39734 --- /dev/null +++ b/trie/triestate/state.go @@ -0,0 +1,276 @@ +// Copyright 2023 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 triestate + +import ( + "errors" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/trienode" + "golang.org/x/crypto/sha3" +) + +// Trie is an Ethereum state trie, can be implemented by Ethereum Merkle Patricia +// tree or Verkle tree. +type Trie interface { + // Get returns the value for key stored in the trie. + Get(key []byte) ([]byte, error) + + // Update associates key with value in the trie. + Update(key, value []byte) error + + // Delete removes any existing value for key from the trie. + Delete(key []byte) error + + // Commit the trie and returns a set of dirty nodes generated along with + // the new root hash. + Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) +} + +// TrieLoader wraps functions to load tries. +type TrieLoader interface { + // OpenTrie opens the main account trie. + OpenTrie(root common.Hash) (Trie, error) + + // OpenStorageTrie opens the storage trie of an account. + OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error) +} + +// Set represents a collection of mutated states during a state transition. +// The value refers to the original content of state before the transition +// is made. Nil means that the state was not present previously. +type Set struct { + Accounts map[common.Address][]byte // Mutated account set, nil means the account was not present + Storages map[common.Address]map[common.Hash][]byte // Mutated storage set, nil means the slot was not present + Incomplete map[common.Address]struct{} // Indicator whether the storage is incomplete due to large deletion + size common.StorageSize // Approximate size of set +} + +// New constructs the state set with provided data. +func New(accounts map[common.Address][]byte, storages map[common.Address]map[common.Hash][]byte, incomplete map[common.Address]struct{}) *Set { + return &Set{ + Accounts: accounts, + Storages: storages, + Incomplete: incomplete, + } +} + +// Size returns the approximate memory size occupied by the set. +func (s *Set) Size() common.StorageSize { + if s.size != 0 { + return s.size + } + for _, account := range s.Accounts { + s.size += common.StorageSize(common.AddressLength + len(account)) + } + for _, slots := range s.Storages { + for _, val := range slots { + s.size += common.StorageSize(common.HashLength + len(val)) + } + s.size += common.StorageSize(common.AddressLength) + } + s.size += common.StorageSize(common.AddressLength * len(s.Incomplete)) + return s.size +} + +// context wraps all fields for executing state diffs. +type context struct { + prevRoot common.Hash + postRoot common.Hash + accounts map[common.Address][]byte + storages map[common.Address]map[common.Hash][]byte + accountTrie Trie + nodes *trienode.MergedNodeSet +} + +// Apply traverses the provided state diffs, apply them in the associated +// post-state and return the generated dirty trie nodes. The state can be +// loaded via the provided trie loader. +func Apply(prevRoot common.Hash, postRoot common.Hash, accounts map[common.Address][]byte, storages map[common.Address]map[common.Hash][]byte, loader TrieLoader) (map[common.Hash]map[string]*trienode.Node, error) { + tr, err := loader.OpenTrie(postRoot) + if err != nil { + return nil, err + } + ctx := &context{ + prevRoot: prevRoot, + postRoot: postRoot, + accounts: accounts, + storages: storages, + accountTrie: tr, + nodes: trienode.NewMergedNodeSet(), + } + for addr, account := range accounts { + var err error + if len(account) == 0 { + err = deleteAccount(ctx, loader, addr) + } else { + err = updateAccount(ctx, loader, addr) + } + if err != nil { + return nil, fmt.Errorf("failed to revert state, err: %w", err) + } + } + root, result, err := tr.Commit(false) + if err != nil { + return nil, err + } + if root != prevRoot { + return nil, fmt.Errorf("failed to revert state, want %#x, got %#x", prevRoot, root) + } + if err := ctx.nodes.Merge(result); err != nil { + return nil, err + } + return ctx.nodes.Flatten(), nil +} + +// updateAccount the account was present in prev-state, and may or may not +// existent in post-state. Apply the reverse diff and verify if the storage +// root matches the one in prev-state account. +func updateAccount(ctx *context, loader TrieLoader, addr common.Address) error { + // The account was present in prev-state, decode it from the + // 'slim-rlp' format bytes. + h := newHasher() + defer h.release() + + addrHash := h.hash(addr.Bytes()) + prev, err := types.FullAccount(ctx.accounts[addr]) + if err != nil { + return err + } + // The account may or may not existent in post-state, try to + // load it and decode if it's found. + blob, err := ctx.accountTrie.Get(addrHash.Bytes()) + if err != nil { + return err + } + post := types.NewEmptyStateAccount() + if len(blob) != 0 { + if err := rlp.DecodeBytes(blob, &post); err != nil { + return err + } + } + // Apply all storage changes into the post-state storage trie. + st, err := loader.OpenStorageTrie(ctx.postRoot, addrHash, post.Root) + if err != nil { + return err + } + for key, val := range ctx.storages[addr] { + var err error + if len(val) == 0 { + err = st.Delete(key.Bytes()) + } else { + err = st.Update(key.Bytes(), val) + } + if err != nil { + return err + } + } + root, result, err := st.Commit(false) + if err != nil { + return err + } + if root != prev.Root { + return errors.New("failed to reset storage trie") + } + // The returned set can be nil if storage trie is not changed + // at all. + if result != nil { + if err := ctx.nodes.Merge(result); err != nil { + return err + } + } + // Write the prev-state account into the main trie + full, err := rlp.EncodeToBytes(prev) + if err != nil { + return err + } + return ctx.accountTrie.Update(addrHash.Bytes(), full) +} + +// deleteAccount the account was not present in prev-state, and is expected +// to be existent in post-state. Apply the reverse diff and verify if the +// account and storage is wiped out correctly. +func deleteAccount(ctx *context, loader TrieLoader, addr common.Address) error { + // The account must be existent in post-state, load the account. + h := newHasher() + defer h.release() + + addrHash := h.hash(addr.Bytes()) + blob, err := ctx.accountTrie.Get(addrHash.Bytes()) + if err != nil { + return err + } + if len(blob) == 0 { + return fmt.Errorf("account is non-existent %#x", addrHash) + } + var post types.StateAccount + if err := rlp.DecodeBytes(blob, &post); err != nil { + return err + } + st, err := loader.OpenStorageTrie(ctx.postRoot, addrHash, post.Root) + if err != nil { + return err + } + for key, val := range ctx.storages[addr] { + if len(val) != 0 { + return errors.New("expect storage deletion") + } + if err := st.Delete(key.Bytes()); err != nil { + return err + } + } + root, result, err := st.Commit(false) + if err != nil { + return err + } + if root != types.EmptyRootHash { + return errors.New("failed to clear storage trie") + } + // The returned set can be nil if storage trie is not changed + // at all. + if result != nil { + if err := ctx.nodes.Merge(result); err != nil { + return err + } + } + // Delete the post-state account from the main trie. + return ctx.accountTrie.Delete(addrHash.Bytes()) +} + +// hasher is used to compute the sha256 hash of the provided data. +type hasher struct{ sha crypto.KeccakState } + +var hasherPool = sync.Pool{ + New: func() interface{} { return &hasher{sha: sha3.NewLegacyKeccak256().(crypto.KeccakState)} }, +} + +func newHasher() *hasher { + return hasherPool.Get().(*hasher) +} + +func (h *hasher) hash(data []byte) common.Hash { + return crypto.HashData(h.sha, data) +} + +func (h *hasher) release() { + hasherPool.Put(h) +}