diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 0b712e69e..6f6ceb238 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.19 + go-version: 1.21.4 - name: Checkout code uses: actions/checkout@v2 diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 000000000..65e561a93 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,23 @@ +name: i386 linux tests + +on: + push: + branches: [master] + pull_request: + branches: [master] + workflow_dispatch: + +jobs: + build: + runs-on: self-hosted + steps: + - uses: actions/checkout@v2 + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.21.4 + - name: Run tests + run: go test ./... + env: + GOOS: linux + GOARCH: 386 diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 029f60b35..4c76a721a 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -28,8 +28,8 @@ jobs: - name: Lint run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.51.2 - ./bin/golangci-lint run + # curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.51.2 + # ./bin/golangci-lint run # Optional: working directory, useful for monorepos # working-directory: somedir diff --git a/.gitignore b/.gitignore index 29ce7ecd0..b117d3da3 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,4 @@ scripts/ logs/ coverage.out coverage.txt -logs/ +tests/spec-tests/ diff --git a/.golangci.yml b/.golangci.yml index 3b7922e31..d94446fd6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -12,7 +12,6 @@ run: linters: disable-all: true enable: - - goconst - goimports - gosimple - govet @@ -39,9 +38,6 @@ linters: linters-settings: gofmt: simplify: true - goconst: - min-len: 3 # minimum length of string constant - min-occurrences: 6 # minimum number of occurrences issues: exclude-rules: diff --git a/.travis.yml b/.travis.yml index 1691dc866..a55583a70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,18 +9,6 @@ jobs: - azure-osx include: - # This builder only tests code linters on latest version of Go - - stage: lint - os: linux - dist: bionic - go: 1.20.x - env: - - lint - git: - submodules: false # avoid cloning ethereum/tests - script: - - go run build/ci.go lint - # These builders create the Docker sub-images for multi-arch push and each # will attempt to push the multi-arch image if they are the last builder - stage: build @@ -28,7 +16,7 @@ jobs: os: linux arch: amd64 dist: bionic - go: 1.20.x + go: 1.21.x env: - docker services: @@ -45,7 +33,7 @@ jobs: os: linux arch: arm64 dist: bionic - go: 1.20.x + go: 1.21.x env: - docker services: @@ -63,10 +51,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,56 +84,51 @@ jobs: - stage: build if: type = push os: osx - go: 1.20.x + osx_image: xcode14.2 + go: 1.21.x env: - azure-osx - - GO111MODULE=on git: submodules: false # avoid cloning ethereum/tests script: - go run build/ci.go install -dlgo - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds + - go run build/ci.go install -dlgo -arch arm64 + - go run build/ci.go archive -arch arm64 -type tar -signer OSX_SIGNING_KEY -signify SIGNIFY_KEY -upload gethstore/builds # These builders run the tests - stage: build 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 +149,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,8 +162,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 -coverage $TEST_PACKAGES + - travis_wait 30 go run build/ci.go test -race $TEST_PACKAGES + diff --git a/Dockerfile b/Dockerfile index 54dbbcbe8..1a6ce9355 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ ARG USER=$USER # 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/Dockerfile.alltools b/Dockerfile.alltools index 70ccc3982..c317da25f 100644 --- a/Dockerfile.alltools +++ b/Dockerfile.alltools @@ -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 47a8bcc20..94fde1ed6 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,7 @@ GOBIN = ./build/bin GO ?= latest -GORUN = env GO111MODULE=on go run -U = $(USER) +GORUN = go run geth: $(GORUN) build/ci.go install ./cmd/geth @@ -24,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)/* run-geth-bsp: diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 55576070a..4abf29806 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 details +// 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 3486ffd1a..bc76df0dc 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -120,6 +120,7 @@ var methods = map[string]Method{ } func TestReader(t *testing.T) { + t.Parallel() abi := ABI{ Methods: methods, } @@ -151,6 +152,7 @@ func TestReader(t *testing.T) { } func TestInvalidABI(t *testing.T) { + t.Parallel() json := `[{ "type" : "function", "name" : "", "constant" : fals }]` _, err := JSON(strings.NewReader(json)) if err == nil { @@ -170,6 +172,7 @@ func TestInvalidABI(t *testing.T) { // constructor(uint256 a, uint256 b) public{} // } func TestConstructor(t *testing.T) { + t.Parallel() json := `[{ "inputs": [{"internalType": "uint256","name": "a","type": "uint256" },{ "internalType": "uint256","name": "b","type": "uint256"}],"stateMutability": "nonpayable","type": "constructor"}]` method := NewMethod("", "", Constructor, "nonpayable", false, false, []Argument{{"a", Uint256, false}, {"b", Uint256, false}}, nil) // Test from JSON @@ -199,6 +202,7 @@ func TestConstructor(t *testing.T) { } func TestTestNumbers(t *testing.T) { + t.Parallel() abi, err := JSON(strings.NewReader(jsondata)) if err != nil { t.Fatal(err) @@ -236,6 +240,7 @@ func TestTestNumbers(t *testing.T) { } func TestMethodSignature(t *testing.T) { + t.Parallel() m := NewMethod("foo", "foo", Function, "", false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil) exp := "foo(string,string)" if m.Sig != exp { @@ -274,6 +279,7 @@ func TestMethodSignature(t *testing.T) { } func TestOverloadedMethodSignature(t *testing.T) { + t.Parallel() json := `[{"constant":true,"inputs":[{"name":"i","type":"uint256"},{"name":"j","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"i","type":"uint256"}],"name":"foo","outputs":[],"payable":false,"stateMutability":"pure","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"}],"name":"bar","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"i","type":"uint256"},{"indexed":false,"name":"j","type":"uint256"}],"name":"bar","type":"event"}]` abi, err := JSON(strings.NewReader(json)) if err != nil { @@ -297,6 +303,7 @@ func TestOverloadedMethodSignature(t *testing.T) { } func TestCustomErrors(t *testing.T) { + t.Parallel() json := `[{ "inputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ],"name": "MyError", "type": "error"} ]` abi, err := JSON(strings.NewReader(json)) if err != nil { @@ -311,6 +318,7 @@ func TestCustomErrors(t *testing.T) { } func TestMultiPack(t *testing.T) { + t.Parallel() abi, err := JSON(strings.NewReader(jsondata)) if err != nil { t.Fatal(err) @@ -348,6 +356,7 @@ func ExampleJSON() { } func TestInputVariableInputLength(t *testing.T) { + t.Parallel() const definition = `[ { "type" : "function", "name" : "strOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "string" } ] }, { "type" : "function", "name" : "bytesOne", "constant" : true, "inputs" : [ { "name" : "str", "type" : "bytes" } ] }, @@ -476,6 +485,7 @@ func TestInputVariableInputLength(t *testing.T) { } func TestInputFixedArrayAndVariableInputLength(t *testing.T) { + t.Parallel() abi, err := JSON(strings.NewReader(jsondata)) if err != nil { t.Error(err) @@ -650,6 +660,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) { } func TestDefaultFunctionParsing(t *testing.T) { + t.Parallel() const definition = `[{ "name" : "balance", "type" : "function" }]` abi, err := JSON(strings.NewReader(definition)) @@ -663,6 +674,7 @@ func TestDefaultFunctionParsing(t *testing.T) { } func TestBareEvents(t *testing.T) { + t.Parallel() const definition = `[ { "type" : "event", "name" : "balance" }, { "type" : "event", "name" : "anon", "anonymous" : true}, @@ -739,6 +751,7 @@ func TestBareEvents(t *testing.T) { // // receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} func TestUnpackEvent(t *testing.T) { + t.Parallel() const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` abi, err := JSON(strings.NewReader(abiJSON)) if err != nil { @@ -777,6 +790,7 @@ func TestUnpackEvent(t *testing.T) { } func TestUnpackEventIntoMap(t *testing.T) { + t.Parallel() const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]` abi, err := JSON(strings.NewReader(abiJSON)) if err != nil { @@ -827,6 +841,7 @@ func TestUnpackEventIntoMap(t *testing.T) { } func TestUnpackMethodIntoMap(t *testing.T) { + t.Parallel() const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]` abi, err := JSON(strings.NewReader(abiJSON)) if err != nil { @@ -877,6 +892,7 @@ func TestUnpackMethodIntoMap(t *testing.T) { } func TestUnpackIntoMapNamingConflict(t *testing.T) { + t.Parallel() // Two methods have the same name var abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"get","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]` abi, err := JSON(strings.NewReader(abiJSON)) @@ -960,6 +976,7 @@ func TestUnpackIntoMapNamingConflict(t *testing.T) { } func TestABI_MethodById(t *testing.T) { + t.Parallel() abi, err := JSON(strings.NewReader(jsondata)) if err != nil { t.Fatal(err) @@ -992,6 +1009,7 @@ func TestABI_MethodById(t *testing.T) { } func TestABI_EventById(t *testing.T) { + t.Parallel() tests := []struct { name string json string @@ -1058,6 +1076,7 @@ func TestABI_EventById(t *testing.T) { } func TestABI_ErrorByID(t *testing.T) { + t.Parallel() abi, err := JSON(strings.NewReader(`[ {"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"MyError1","type":"error"}, {"inputs":[{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"string","name":"b","type":"string"},{"internalType":"address","name":"c","type":"address"}],"internalType":"struct MyError.MyStruct","name":"x","type":"tuple"},{"internalType":"address","name":"y","type":"address"},{"components":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"string","name":"b","type":"string"},{"internalType":"address","name":"c","type":"address"}],"internalType":"struct MyError.MyStruct","name":"z","type":"tuple"}],"name":"MyError2","type":"error"}, @@ -1088,6 +1107,7 @@ func TestABI_ErrorByID(t *testing.T) { // TestDoubleDuplicateMethodNames checks that if transfer0 already exists, there won't be a name // conflict and that the second transfer method will be renamed transfer1. func TestDoubleDuplicateMethodNames(t *testing.T) { + t.Parallel() abiJSON := `[{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"}],"name":"transfer0","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"},{"name":"data","type":"bytes"},{"name":"customFallback","type":"string"}],"name":"transfer","outputs":[{"name":"ok","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]` contractAbi, err := JSON(strings.NewReader(abiJSON)) if err != nil { @@ -1117,6 +1137,7 @@ func TestDoubleDuplicateMethodNames(t *testing.T) { // event send(); // } func TestDoubleDuplicateEventNames(t *testing.T) { + t.Parallel() abiJSON := `[{"anonymous": false,"inputs": [{"indexed": false,"internalType": "uint256","name": "a","type": "uint256"}],"name": "send","type": "event"},{"anonymous": false,"inputs": [],"name": "send0","type": "event"},{ "anonymous": false, "inputs": [],"name": "send","type": "event"}]` contractAbi, err := JSON(strings.NewReader(abiJSON)) if err != nil { @@ -1144,6 +1165,7 @@ func TestDoubleDuplicateEventNames(t *testing.T) { // event send(uint256, uint256); // } func TestUnnamedEventParam(t *testing.T) { + t.Parallel() abiJSON := `[{ "anonymous": false, "inputs": [{ "indexed": false,"internalType": "uint256", "name": "","type": "uint256"},{"indexed": false,"internalType": "uint256","name": "","type": "uint256"}],"name": "send","type": "event"}]` contractAbi, err := JSON(strings.NewReader(abiJSON)) if err != nil { @@ -1173,9 +1195,13 @@ 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 { + index, c := index, c t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) { + t.Parallel() got, err := UnpackRevert(common.Hex2Bytes(c.input)) if c.expectErr != nil { if err == nil { diff --git a/tests/fuzzers/abi/abifuzzer.go b/accounts/abi/abifuzzer_test.go similarity index 61% rename from tests/fuzzers/abi/abifuzzer.go rename to accounts/abi/abifuzzer_test.go index 60233d158..dbf6ab6c5 100644 --- a/tests/fuzzers/abi/abifuzzer.go +++ b/accounts/abi/abifuzzer_test.go @@ -20,20 +20,34 @@ import ( "fmt" "reflect" "strings" + "testing" - "github.com/ethereum/go-ethereum/accounts/abi" fuzz "github.com/google/gofuzz" ) +// TestReplicate can be used to replicate crashers from the fuzzing tests. +// Just replace testString with the data in .quoted +func TestReplicate(t *testing.T) { + t.Parallel() + //t.Skip("Test only useful for reproducing issues") + fuzzAbi([]byte("\x20\x20\x20\x20\x20\x20\x20\x20\x80\x00\x00\x00\x20\x20\x20\x20\x00")) + //fuzzAbi([]byte("asdfasdfkadsf;lasdf;lasd;lfk")) +} + +// FuzzABI is the main entrypoint for fuzzing +func FuzzABI(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzzAbi(data) + }) +} + var ( - names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"} - stateMut = []string{"", "pure", "view", "payable"} - stateMutabilites = []*string{&stateMut[0], &stateMut[1], &stateMut[2], &stateMut[3]} - pays = []string{"", "true", "false"} - payables = []*string{&pays[0], &pays[1]} - vNames = []string{"a", "b", "c", "d", "e", "f", "g"} - varNames = append(vNames, names...) - varTypes = []string{"bool", "address", "bytes", "string", + names = []string{"_name", "name", "NAME", "name_", "__", "_name_", "n"} + stateMut = []string{"pure", "view", "payable"} + pays = []string{"true", "false"} + vNames = []string{"a", "b", "c", "d", "e", "f", "g"} + varNames = append(vNames, names...) + varTypes = []string{"bool", "address", "bytes", "string", "uint8", "int8", "uint8", "int8", "uint16", "int16", "uint24", "int24", "uint32", "int32", "uint40", "int40", "uint48", "int48", "uint56", "int56", "uint64", "int64", "uint72", "int72", "uint80", "int80", "uint88", "int88", "uint96", "int96", @@ -47,7 +61,7 @@ var ( "bytes32", "bytes"} ) -func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool) { +func unpackPack(abi ABI, method string, input []byte) ([]interface{}, bool) { if out, err := abi.Unpack(method, input); err == nil { _, err := abi.Pack(method, out...) if err != nil { @@ -63,7 +77,7 @@ func unpackPack(abi abi.ABI, method string, input []byte) ([]interface{}, bool) return nil, false } -func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool { +func packUnpack(abi ABI, method string, input *[]interface{}) bool { if packed, err := abi.Pack(method, input); err == nil { outptr := reflect.New(reflect.TypeOf(input)) err := abi.UnpackIntoInterface(outptr.Interface(), method, packed) @@ -79,12 +93,12 @@ func packUnpack(abi abi.ABI, method string, input *[]interface{}) bool { return false } -type args struct { +type arg struct { name string typ string } -func createABI(name string, stateMutability, payable *string, inputs []args) (abi.ABI, error) { +func createABI(name string, stateMutability, payable *string, inputs []arg) (ABI, error) { sig := fmt.Sprintf(`[{ "type" : "function", "name" : "%v" `, name) if stateMutability != nil { sig += fmt.Sprintf(`, "stateMutability": "%v" `, *stateMutability) @@ -111,60 +125,55 @@ func createABI(name string, stateMutability, payable *string, inputs []args) (ab sig += "} ]" } sig += `}]` - - return abi.JSON(strings.NewReader(sig)) + //fmt.Printf("sig: %s\n", sig) + return JSON(strings.NewReader(sig)) } -func runFuzzer(input []byte) int { - good := false - fuzzer := fuzz.NewFromGoFuzz(input) - - name := names[getUInt(fuzzer)%len(names)] - stateM := stateMutabilites[getUInt(fuzzer)%len(stateMutabilites)] - payable := payables[getUInt(fuzzer)%len(payables)] - maxLen := 5 - for k := 1; k < maxLen; k++ { - var arg []args - for i := k; i > 0; i-- { - argName := varNames[i] - argTyp := varTypes[getUInt(fuzzer)%len(varTypes)] - if getUInt(fuzzer)%10 == 0 { - argTyp += "[]" - } else if getUInt(fuzzer)%10 == 0 { - arrayArgs := getUInt(fuzzer)%30 + 1 - argTyp += fmt.Sprintf("[%d]", arrayArgs) - } - arg = append(arg, args{ - name: argName, - typ: argTyp, - }) - } - abi, err := createABI(name, stateM, payable, arg) - if err != nil { - continue +func fuzzAbi(input []byte) { + var ( + fuzzer = fuzz.NewFromGoFuzz(input) + name = oneOf(fuzzer, names) + stateM = oneOfOrNil(fuzzer, stateMut) + payable = oneOfOrNil(fuzzer, pays) + arguments []arg + ) + for i := 0; i < upTo(fuzzer, 10); i++ { + argName := oneOf(fuzzer, varNames) + argTyp := oneOf(fuzzer, varTypes) + switch upTo(fuzzer, 10) { + case 0: // 10% chance to make it a slice + argTyp += "[]" + case 1: // 10% chance to make it an array + argTyp += fmt.Sprintf("[%d]", 1+upTo(fuzzer, 30)) + default: } - structs, b := unpackPack(abi, name, input) - c := packUnpack(abi, name, &structs) - good = good || b || c + arguments = append(arguments, arg{name: argName, typ: argTyp}) } - if good { - return 1 + abi, err := createABI(name, stateM, payable, arguments) + if err != nil { + //fmt.Printf("err: %v\n", err) + panic(err) } - return 0 + structs, _ := unpackPack(abi, name, input) + _ = packUnpack(abi, name, &structs) } -func Fuzz(input []byte) int { - return runFuzzer(input) -} - -func getUInt(fuzzer *fuzz.Fuzzer) int { +func upTo(fuzzer *fuzz.Fuzzer, max int) int { var i int fuzzer.Fuzz(&i) if i < 0 { - i = -i - if i < 0 { - return 0 - } + return (-1 - i) % max + } + return i % max +} + +func oneOf(fuzzer *fuzz.Fuzzer, options []string) string { + return options[upTo(fuzzer, len(options))] +} + +func oneOfOrNil(fuzzer *fuzz.Fuzzer, options []string) *string { + if i := upTo(fuzzer, len(options)+1); i < len(options) { + return &options[i] } - return i + return nil } diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index 2e48d539e..fa5461895 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -80,7 +80,7 @@ func (arguments Arguments) isTuple() bool { func (arguments Arguments) Unpack(data []byte) ([]interface{}, error) { if len(data) == 0 { if len(arguments.NonIndexed()) != 0 { - return nil, errors.New("abi: attempting to unmarshall an empty string while arguments are expected") + return nil, errors.New("abi: attempting to unmarshal an empty string while arguments are expected") } return make([]interface{}, 0), nil } @@ -95,7 +95,7 @@ func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) } if len(data) == 0 { if len(arguments.NonIndexed()) != 0 { - return errors.New("abi: attempting to unmarshall an empty string while arguments are expected") + return errors.New("abi: attempting to unmarshal an empty string while arguments are expected") } return nil // Nothing to unmarshal, return } diff --git a/accounts/abi/bind/auth.go b/accounts/abi/bind/auth.go index 494dc88a5..0740c6951 100644 --- a/accounts/abi/bind/auth.go +++ b/accounts/abi/bind/auth.go @@ -56,7 +56,7 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) { } // NewKeyStoreTransactor is a utility method to easily create a transaction signer from -// an decrypted key from a keystore. +// a decrypted key from a keystore. // // Deprecated: Use NewKeyStoreTransactorWithChainID instead. func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) { @@ -117,7 +117,7 @@ func NewTransactorWithChainID(keyin io.Reader, passphrase string, chainID *big.I } // NewKeyStoreTransactorWithChainID is a utility method to easily create a transaction signer from -// an decrypted key from a keystore. +// a decrypted key from a keystore. func NewKeyStoreTransactorWithChainID(keystore *keystore.KeyStore, account accounts.Account, chainID *big.Int) (*TransactOpts, error) { if chainID == nil { return nil, ErrNoChainID diff --git a/accounts/abi/bind/backend.go b/accounts/abi/bind/backend.go index 34530a645..38b304697 100644 --- a/accounts/abi/bind/backend.go +++ b/accounts/abi/bind/backend.go @@ -36,6 +36,10 @@ var ( // on a backend that doesn't implement PendingContractCaller. ErrNoPendingState = errors.New("backend does not support pending state") + // ErrNoBlockHashState is raised when attempting to perform a block hash action + // on a backend that doesn't implement BlockHashContractCaller. + ErrNoBlockHashState = errors.New("backend does not support block hash state") + // ErrNoCodeAfterDeploy is returned by WaitDeployed if contract creation leaves // an empty contract behind. ErrNoCodeAfterDeploy = errors.New("no contract code after deployment") @@ -64,11 +68,27 @@ type PendingContractCaller interface { PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) } +// BlockHashContractCaller defines methods to perform contract calls on a specific block hash. +// Call will try to discover this interface when access to a block by hash is requested. +// If the backend does not support the block hash state, Call returns ErrNoBlockHashState. +type BlockHashContractCaller interface { + // CodeAtHash returns the code of the given account in the state at the specified block hash. + CodeAtHash(ctx context.Context, contract common.Address, blockHash common.Hash) ([]byte, error) + + // CallContractAtHash executes an Ethereum contract call against the state at the specified block hash. + CallContractAtHash(ctx context.Context, call ethereum.CallMsg, blockHash common.Hash) ([]byte, error) +} + // ContractTransactor defines the methods needed to allow operating with a contract // on a write only basis. Besides the transacting method, the remainder are helpers // used when the user does not provide some needed values, but rather leaves it up // to the transactor to decide. type ContractTransactor interface { + ethereum.GasEstimator + ethereum.GasPricer + ethereum.GasPricer1559 + ethereum.TransactionSender + // HeaderByNumber returns a block header from the current canonical chain. If // number is nil, the latest known header is returned. HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) @@ -78,38 +98,6 @@ type ContractTransactor interface { // PendingNonceAt retrieves the current pending nonce associated with an account. PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) - - // SuggestGasPrice retrieves the currently suggested gas price to allow a timely - // execution of a transaction. - SuggestGasPrice(ctx context.Context) (*big.Int, error) - - // SuggestGasTipCap retrieves the currently suggested 1559 priority fee to allow - // a timely execution of a transaction. - SuggestGasTipCap(ctx context.Context) (*big.Int, error) - - // EstimateGas tries to estimate the gas needed to execute a specific - // transaction based on the current pending state of the backend blockchain. - // There is no guarantee that this is the true gas limit requirement as other - // transactions may be added or removed by miners, but it should provide a basis - // for setting a reasonable default. - EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) - - // SendTransaction injects the transaction into the pending pool for execution. - SendTransaction(ctx context.Context, tx *types.Transaction) error -} - -// ContractFilterer defines the methods needed to access log events using one-off -// queries or continuous event subscriptions. -type ContractFilterer interface { - // FilterLogs executes a log filter operation, blocking during execution and - // returning all the results in one batch. - // - // TODO(karalabe): Deprecate when the subscription one can return past data too. - FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) - - // SubscribeFilterLogs creates a background log filtering operation, returning - // a subscription immediately, which can be used to stream the found events. - SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) } // DeployBackend wraps the operations needed by WaitMined and WaitDeployed. @@ -118,6 +106,12 @@ type DeployBackend interface { CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) } +// ContractFilterer defines the methods needed to access log events using one-off +// queries or continuous event subscriptions. +type ContractFilterer interface { + ethereum.LogFilterer +} + // ContractBackend defines the methods needed to work with contracts on a read-write basis. type ContractBackend interface { ContractCaller diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 83b91f7f8..927156669 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -18,921 +18,35 @@ package backends import ( "context" - "errors" - "fmt" - "math/big" - "sync" - "time" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "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/ethash" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/bloombits" - "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/eth/filters" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/ethclient/simulated" ) -// This nil assignment ensures at compile time that SimulatedBackend implements bind.ContractBackend. -var _ bind.ContractBackend = (*SimulatedBackend)(nil) - -var ( - errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block") - errBlockDoesNotExist = errors.New("block does not exist in blockchain") - errTransactionDoesNotExist = errors.New("transaction does not exist") -) - -// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in -// the background. Its main purpose is to allow for easy testing of contract bindings. -// Simulated backend implements the following interfaces: -// ChainReader, ChainStateReader, ContractBackend, ContractCaller, ContractFilterer, ContractTransactor, -// DeployBackend, GasEstimator, GasPricer, LogFilterer, PendingContractCaller, TransactionReader, and TransactionSender +// SimulatedBackend is a simulated blockchain. +// Deprecated: use package github.com/ethereum/go-ethereum/ethclient/simulated instead. type SimulatedBackend struct { - database ethdb.Database // In memory database to store our testing data - blockchain *core.BlockChain // Ethereum blockchain to handle the consensus - - mu sync.Mutex - pendingBlock *types.Block // Currently pending block that will be imported on request - pendingState *state.StateDB // Currently pending state that will be the active on request - pendingReceipts types.Receipts // Currently receipts for the pending block - - events *filters.EventSystem // for filtering log events live - filterSystem *filters.FilterSystem // for filtering database logs - - config *params.ChainConfig + *simulated.Backend + simulated.Client } -// NewSimulatedBackendWithDatabase creates a new binding backend based on the given database -// and uses a simulated blockchain for testing purposes. -// A simulated backend always uses chainID 1337. -func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { - genesis := core.Genesis{ - Config: params.AllEthashProtocolChanges, - GasLimit: gasLimit, - Alloc: alloc, - } - blockchain, _ := core.NewBlockChain(database, nil, &genesis, nil, ethash.NewFaker(), vm.Config{}, nil, nil) - - backend := &SimulatedBackend{ - database: database, - blockchain: blockchain, - config: genesis.Config, - } - - filterBackend := &filterBackend{database, blockchain, backend} - backend.filterSystem = filters.NewFilterSystem(filterBackend, filters.Config{}) - backend.events = filters.NewEventSystem(backend.filterSystem, false) - - header := backend.blockchain.CurrentBlock() - block := backend.blockchain.GetBlock(header.Hash(), header.Number.Uint64()) - - backend.rollback(block) - return backend +// Fork sets the head to a new block, which is based on the provided parentHash. +func (b *SimulatedBackend) Fork(ctx context.Context, parentHash common.Hash) error { + return b.Backend.Fork(parentHash) } // NewSimulatedBackend creates a new binding backend using a simulated blockchain // for testing purposes. -// A simulated backend always uses chainID 1337. -func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { - return NewSimulatedBackendWithDatabase(rawdb.NewMemoryDatabase(), alloc, gasLimit) -} - -// Close terminates the underlying blockchain's update loop. -func (b *SimulatedBackend) Close() error { - b.blockchain.Stop() - return nil -} - -// Commit imports all the pending transactions as a single block and starts a -// fresh new state. -func (b *SimulatedBackend) Commit() common.Hash { - b.mu.Lock() - defer b.mu.Unlock() - - if _, err := b.blockchain.InsertChain([]*types.Block{b.pendingBlock}); err != nil { - panic(err) // This cannot happen unless the simulator is wrong, fail in that case - } - blockHash := b.pendingBlock.Hash() - - // Using the last inserted block here makes it possible to build on a side - // chain after a fork. - b.rollback(b.pendingBlock) - - return blockHash -} - -// Rollback aborts all pending transactions, reverting to the last committed state. -func (b *SimulatedBackend) Rollback() { - b.mu.Lock() - defer b.mu.Unlock() - - header := b.blockchain.CurrentBlock() - block := b.blockchain.GetBlock(header.Hash(), header.Number.Uint64()) - - b.rollback(block) -} - -func (b *SimulatedBackend) rollback(parent *types.Block) { - blocks, _ := core.GenerateChain(b.config, parent, ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {}) - - b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), b.blockchain.StateCache(), nil) -} - -// Fork creates a side-chain that can be used to simulate reorgs. -// -// This function should be called with the ancestor block where the new side -// chain should be started. Transactions (old and new) can then be applied on -// top and Commit-ed. // -// Note, the side-chain will only become canonical (and trigger the events) when -// it becomes longer. Until then CallContract will still operate on the current -// canonical chain. -// -// There is a % chance that the side chain becomes canonical at the same length -// to simulate live network behavior. -func (b *SimulatedBackend) Fork(ctx context.Context, parent common.Hash) error { - b.mu.Lock() - defer b.mu.Unlock() - - if len(b.pendingBlock.Transactions()) != 0 { - return errors.New("pending block dirty") - } - block, err := b.blockByHash(ctx, parent) - if err != nil { - return err - } - b.rollback(block) - return nil -} - -// stateByBlockNumber retrieves a state by a given blocknumber. -func (b *SimulatedBackend) stateByBlockNumber(ctx context.Context, blockNumber *big.Int) (*state.StateDB, error) { - if blockNumber == nil || blockNumber.Cmp(b.blockchain.CurrentBlock().Number) == 0 { - return b.blockchain.State() - } - block, err := b.blockByNumber(ctx, blockNumber) - if err != nil { - return nil, err - } - return b.blockchain.StateAt(block.Root()) -} - -// CodeAt returns the code associated with a certain account in the blockchain. -func (b *SimulatedBackend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { - b.mu.Lock() - defer b.mu.Unlock() - - stateDB, err := b.stateByBlockNumber(ctx, blockNumber) - if err != nil { - return nil, err - } - - return stateDB.GetCode(contract), nil -} - -// BalanceAt returns the wei balance of a certain account in the blockchain. -func (b *SimulatedBackend) BalanceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (*big.Int, error) { - b.mu.Lock() - defer b.mu.Unlock() - - stateDB, err := b.stateByBlockNumber(ctx, blockNumber) - if err != nil { - return nil, err - } - - return stateDB.GetBalance(contract), nil -} - -// NonceAt returns the nonce of a certain account in the blockchain. -func (b *SimulatedBackend) NonceAt(ctx context.Context, contract common.Address, blockNumber *big.Int) (uint64, error) { - b.mu.Lock() - defer b.mu.Unlock() - - stateDB, err := b.stateByBlockNumber(ctx, blockNumber) - if err != nil { - return 0, err - } - - return stateDB.GetNonce(contract), nil -} - -// StorageAt returns the value of key in the storage of an account in the blockchain. -func (b *SimulatedBackend) StorageAt(ctx context.Context, contract common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { - b.mu.Lock() - defer b.mu.Unlock() - - stateDB, err := b.stateByBlockNumber(ctx, blockNumber) - if err != nil { - return nil, err - } - - val := stateDB.GetState(contract, key) - return val[:], nil -} - -// TransactionReceipt returns the receipt of a transaction. -func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { - b.mu.Lock() - defer b.mu.Unlock() - - receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash, b.config) - if receipt == nil { - return nil, ethereum.NotFound - } - return receipt, nil -} - -// TransactionByHash checks the pool of pending transactions in addition to the -// blockchain. The isPending return value indicates whether the transaction has been -// mined yet. Note that the transaction may not be part of the canonical chain even if -// it's not pending. -func (b *SimulatedBackend) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { - b.mu.Lock() - defer b.mu.Unlock() - - tx := b.pendingBlock.Transaction(txHash) - if tx != nil { - return tx, true, nil - } - tx, _, _, _ = rawdb.ReadTransaction(b.database, txHash) - if tx != nil { - return tx, false, nil - } - return nil, false, ethereum.NotFound -} - -// BlockByHash retrieves a block based on the block hash. -func (b *SimulatedBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - b.mu.Lock() - defer b.mu.Unlock() - - return b.blockByHash(ctx, hash) -} - -// blockByHash retrieves a block based on the block hash without Locking. -func (b *SimulatedBackend) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - if hash == b.pendingBlock.Hash() { - return b.pendingBlock, nil - } - - block := b.blockchain.GetBlockByHash(hash) - if block != nil { - return block, nil - } - - return nil, errBlockDoesNotExist -} - -// BlockByNumber retrieves a block from the database by number, caching it -// (associated with its hash) if found. -func (b *SimulatedBackend) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { - b.mu.Lock() - defer b.mu.Unlock() - - return b.blockByNumber(ctx, number) -} - -// blockByNumber retrieves a block from the database by number, caching it -// (associated with its hash) if found without Lock. -func (b *SimulatedBackend) blockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) { - if number == nil || number.Cmp(b.pendingBlock.Number()) == 0 { - return b.blockByHash(ctx, b.blockchain.CurrentBlock().Hash()) - } - - block := b.blockchain.GetBlockByNumber(uint64(number.Int64())) - if block == nil { - return nil, errBlockDoesNotExist - } - - return block, nil -} - -// HeaderByHash returns a block header from the current canonical chain. -func (b *SimulatedBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { - b.mu.Lock() - defer b.mu.Unlock() - - if hash == b.pendingBlock.Hash() { - return b.pendingBlock.Header(), nil - } - - header := b.blockchain.GetHeaderByHash(hash) - if header == nil { - return nil, errBlockDoesNotExist - } - - return header, nil -} - -// HeaderByNumber returns a block header from the current canonical chain. If number is -// nil, the latest known header is returned. -func (b *SimulatedBackend) HeaderByNumber(ctx context.Context, block *big.Int) (*types.Header, error) { - b.mu.Lock() - defer b.mu.Unlock() - - if block == nil || block.Cmp(b.pendingBlock.Number()) == 0 { - return b.blockchain.CurrentHeader(), nil - } - - return b.blockchain.GetHeaderByNumber(uint64(block.Int64())), nil -} - -// TransactionCount returns the number of transactions in a given block. -func (b *SimulatedBackend) TransactionCount(ctx context.Context, blockHash common.Hash) (uint, error) { - b.mu.Lock() - defer b.mu.Unlock() - - if blockHash == b.pendingBlock.Hash() { - return uint(b.pendingBlock.Transactions().Len()), nil - } - - block := b.blockchain.GetBlockByHash(blockHash) - if block == nil { - return uint(0), errBlockDoesNotExist - } - - return uint(block.Transactions().Len()), nil -} - -// TransactionInBlock returns the transaction for a specific block at a specific index. -func (b *SimulatedBackend) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) { - b.mu.Lock() - defer b.mu.Unlock() - - if blockHash == b.pendingBlock.Hash() { - transactions := b.pendingBlock.Transactions() - if uint(len(transactions)) < index+1 { - return nil, errTransactionDoesNotExist - } - - return transactions[index], nil - } - - block := b.blockchain.GetBlockByHash(blockHash) - if block == nil { - return nil, errBlockDoesNotExist - } - - transactions := block.Transactions() - if uint(len(transactions)) < index+1 { - return nil, errTransactionDoesNotExist - } - - return transactions[index], nil -} - -// PendingCodeAt returns the code associated with an account in the pending state. -func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) { - b.mu.Lock() - defer b.mu.Unlock() - - return b.pendingState.GetCode(contract), nil -} - -func newRevertError(result *core.ExecutionResult) *revertError { - reason, errUnpack := abi.UnpackRevert(result.Revert()) - err := errors.New("execution reverted") - if errUnpack == nil { - err = fmt.Errorf("execution reverted: %v", reason) - } - return &revertError{ - error: err, - reason: hexutil.Encode(result.Revert()), - } -} - -// revertError is an API error that encompasses an EVM revert with JSON error -// code and a binary data blob. -type revertError struct { - error - reason string // revert reason hex encoded -} - -// ErrorCode returns the JSON error code for a revert. -// See: https://github.com/ethereum/wiki/wiki/JSON-RPC-Error-Codes-Improvement-Proposal -func (e *revertError) ErrorCode() int { - return 3 -} - -// ErrorData returns the hex encoded revert reason. -func (e *revertError) ErrorData() interface{} { - return e.reason -} - -// CallContract executes a contract call. -func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { - b.mu.Lock() - defer b.mu.Unlock() - - if blockNumber != nil && blockNumber.Cmp(b.blockchain.CurrentBlock().Number) != 0 { - return nil, errBlockNumberUnsupported - } - stateDB, err := b.blockchain.State() - if err != nil { - return nil, err - } - res, err := b.callContract(ctx, call, b.blockchain.CurrentBlock(), stateDB) - if err != nil { - return nil, err - } - // If the result contains a revert reason, try to unpack and return it. - if len(res.Revert()) > 0 { - return nil, newRevertError(res) - } - return res.Return(), res.Err -} - -// PendingCallContract executes a contract call on the pending state. -func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { - b.mu.Lock() - defer b.mu.Unlock() - defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot()) - - res, err := b.callContract(ctx, call, b.pendingBlock.Header(), b.pendingState) - if err != nil { - return nil, err - } - // If the result contains a revert reason, try to unpack and return it. - if len(res.Revert()) > 0 { - return nil, newRevertError(res) - } - return res.Return(), res.Err -} - -// PendingNonceAt implements PendingStateReader.PendingNonceAt, retrieving -// the nonce currently pending for the account. -func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { - b.mu.Lock() - defer b.mu.Unlock() - - return b.pendingState.GetOrNewStateObject(account).Nonce(), nil -} - -// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated -// chain doesn't have miners, we just return a gas price of 1 for any call. -func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) { - b.mu.Lock() - defer b.mu.Unlock() - - if b.pendingBlock.Header().BaseFee != nil { - return b.pendingBlock.Header().BaseFee, nil - } - return big.NewInt(1), nil -} - -// SuggestGasTipCap implements ContractTransactor.SuggestGasTipCap. Since the simulated -// chain doesn't have miners, we just return a gas tip of 1 for any call. -func (b *SimulatedBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { - return big.NewInt(1), nil -} - -// EstimateGas executes the requested code against the currently pending block/state and -// returns the used amount of gas. -func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) { - b.mu.Lock() - defer b.mu.Unlock() - - // Determine the lowest and highest possible gas limits to binary search in between - var ( - lo uint64 = params.TxGas - 1 - hi uint64 - cap uint64 - ) - if call.Gas >= params.TxGas { - hi = call.Gas - } else { - hi = b.pendingBlock.GasLimit() - } - // Normalize the max fee per gas the call is willing to spend. - var feeCap *big.Int - if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) { - return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") - } else if call.GasPrice != nil { - feeCap = call.GasPrice - } else if call.GasFeeCap != nil { - feeCap = call.GasFeeCap - } else { - feeCap = common.Big0 - } - // Recap the highest gas allowance with account's balance. - if feeCap.BitLen() != 0 { - balance := b.pendingState.GetBalance(call.From) // from can't be nil - available := new(big.Int).Set(balance) - if call.Value != nil { - if call.Value.Cmp(available) >= 0 { - return 0, core.ErrInsufficientFundsForTransfer - } - available.Sub(available, call.Value) - } - allowance := new(big.Int).Div(available, feeCap) - if allowance.IsUint64() && hi > allowance.Uint64() { - transfer := call.Value - if transfer == nil { - transfer = new(big.Int) - } - log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance, - "sent", transfer, "feecap", feeCap, "fundable", allowance) - hi = allowance.Uint64() - } - } - cap = hi - - // Create a helper to check if a gas allowance results in an executable transaction - executable := func(gas uint64) (bool, *core.ExecutionResult, error) { - call.Gas = gas - - snapshot := b.pendingState.Snapshot() - res, err := b.callContract(ctx, call, b.pendingBlock.Header(), b.pendingState) - b.pendingState.RevertToSnapshot(snapshot) - - 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 res.Failed(), res, nil - } - // Execute the binary search and hone in on an executable gas limit - 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 err != nil { - return 0, err - } - if failed { - lo = mid - } else { - 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 hi, nil -} - -// callContract implements common code between normal and pending contract calls. -// state is modified during execution, make sure to copy it if necessary. -func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, header *types.Header, stateDB *state.StateDB) (*core.ExecutionResult, error) { - // Gas prices post 1559 need to be initialized - 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 there's no basefee, then it must be a non-1559 execution - if call.GasPrice == nil { - call.GasPrice = new(big.Int) - } - call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice - } else { - // A basefee is provided, necessitating 1559-type execution - if call.GasPrice != nil { - // User specified the legacy gas field, convert to 1559 gas typing - call.GasFeeCap, call.GasTipCap = call.GasPrice, call.GasPrice - } else { - // User specified 1559 gas fields (or none), use those - if call.GasFeeCap == nil { - call.GasFeeCap = new(big.Int) - } - if call.GasTipCap == nil { - call.GasTipCap = new(big.Int) - } - // 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) - } - } - } - // Ensure message is initialized properly. - if call.Gas == 0 { - call.Gas = 50000000 - } - if call.Value == nil { - call.Value = new(big.Int) - } - - // Set infinite balance to the fake caller account. - from := stateDB.GetOrNewStateObject(call.From) - from.SetBalance(math.MaxBig256) - - // Execute the call. - msg := &core.Message{ - From: call.From, - To: call.To, - Value: call.Value, - GasLimit: call.Gas, - GasPrice: call.GasPrice, - GasFeeCap: call.GasFeeCap, - GasTipCap: call.GasTipCap, - Data: call.Data, - AccessList: call.AccessList, - SkipAccountChecks: true, - } - - // Create a new environment which holds all relevant information - // about the transaction and calling mechanisms. - txContext := core.NewEVMTxContext(msg) - evmContext := core.NewEVMBlockContext(header, b.blockchain, nil) - vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true}) - gasPool := new(core.GasPool).AddGas(math.MaxUint64) - - return core.ApplyMessage(vmEnv, msg, gasPool) -} - -// SendTransaction updates the pending block to include the given transaction. -func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { - b.mu.Lock() - defer b.mu.Unlock() - - // Get the last block - block, err := b.blockByHash(ctx, b.pendingBlock.ParentHash()) - if err != nil { - return errors.New("could not fetch parent") - } - // Check transaction validity - signer := types.MakeSigner(b.blockchain.Config(), block.Number(), block.Time()) - sender, err := types.Sender(signer, tx) - if err != nil { - return fmt.Errorf("invalid transaction: %v", err) - } - nonce := b.pendingState.GetNonce(sender) - if tx.Nonce() != nonce { - return fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce) - } - // Include tx in chain - blocks, receipts := core.GenerateChain(b.config, block, ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { - for _, tx := range b.pendingBlock.Transactions() { - block.AddTxWithChain(b.blockchain, tx) - } - block.AddTxWithChain(b.blockchain, tx) - }) - stateDB, _ := b.blockchain.State() - - b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil) - b.pendingReceipts = receipts[0] - return nil -} - -// FilterLogs executes a log filter operation, blocking during execution and -// returning all the results in one batch. +// A simulated backend always uses chainID 1337. // -// TODO(karalabe): Deprecate when the subscription one can return past data too. -func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) { - var filter *filters.Filter - if query.BlockHash != nil { - // Block filter requested, construct a single-shot filter - filter = b.filterSystem.NewBlockFilter(*query.BlockHash, query.Addresses, query.Topics) - } else { - // Initialize unset filter boundaries to run from genesis to chain head - from := int64(0) - if query.FromBlock != nil { - from = query.FromBlock.Int64() - } - to := int64(-1) - if query.ToBlock != nil { - to = query.ToBlock.Int64() - } - // Construct the range filter - filter = b.filterSystem.NewRangeFilter(from, to, query.Addresses, query.Topics) - } - // Run the filter and return all the logs - logs, err := filter.Logs(ctx) - if err != nil { - return nil, err - } - res := make([]types.Log, len(logs)) - for i, nLog := range logs { - res[i] = *nLog - } - return res, nil -} - -// SubscribeFilterLogs creates a background log filtering operation, returning a -// subscription immediately, which can be used to stream the found events. -func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { - // Subscribe to contract events - sink := make(chan []*types.Log) - - sub, err := b.events.SubscribeLogs(query, sink) - if err != nil { - return nil, err - } - // Since we're getting logs in batches, we need to flatten them into a plain stream - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case logs := <-sink: - for _, nlog := range logs { - select { - case ch <- *nlog: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// SubscribeNewHead returns an event subscription for a new header. -func (b *SimulatedBackend) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { - // subscribe to a new head - sink := make(chan *types.Header) - sub := b.events.SubscribeNewHeads(sink) - - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case head := <-sink: - select { - case ch <- head: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// AdjustTime adds a time shift to the simulated clock. -// It can only be called on empty blocks. -func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { - b.mu.Lock() - defer b.mu.Unlock() - - if len(b.pendingBlock.Transactions()) != 0 { - return errors.New("Could not adjust time on non-empty block") - } - // Get the last block - block := b.blockchain.GetBlockByHash(b.pendingBlock.ParentHash()) - if block == nil { - 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() - - b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), stateDB.Database(), nil) - - return nil -} - -// Blockchain returns the underlying blockchain. -func (b *SimulatedBackend) Blockchain() *core.BlockChain { - return b.blockchain -} - -// filterBackend implements filters.Backend to support filtering for logs without -// taking bloom-bits acceleration structures into account. -type filterBackend struct { - db ethdb.Database - bc *core.BlockChain - backend *SimulatedBackend -} - -func (fb *filterBackend) ChainDb() ethdb.Database { return fb.db } - -func (fb *filterBackend) EventMux() *event.TypeMux { panic("not supported") } - -func (fb *filterBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { - switch number { - case rpc.PendingBlockNumber: - if block := fb.backend.pendingBlock; block != nil { - return block.Header(), nil - } - return nil, nil - case rpc.LatestBlockNumber: - return fb.bc.CurrentHeader(), nil - case rpc.FinalizedBlockNumber: - return fb.bc.CurrentFinalBlock(), nil - case rpc.SafeBlockNumber: - return fb.bc.CurrentSafeBlock(), nil - default: - return fb.bc.GetHeaderByNumber(uint64(number.Int64())), nil - } -} - -func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { - return fb.bc.GetHeaderByHash(hash), nil -} - -func (fb *filterBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { - if body := fb.bc.GetBody(hash); body != nil { - return body, nil - } - return nil, errors.New("block body not found") -} - -func (fb *filterBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { - return fb.backend.pendingBlock, fb.backend.pendingReceipts -} - -func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { - number := rawdb.ReadHeaderNumber(fb.db, hash) - if number == nil { - return nil, nil - } - header := rawdb.ReadHeader(fb.db, hash, *number) - if header == nil { - return nil, nil +// Deprecated: please use simulated.Backend from package +// github.com/ethereum/go-ethereum/ethclient/simulated instead. +func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { + b := simulated.New(alloc, gasLimit) + return &SimulatedBackend{ + Backend: b, + Client: b.Client(), } - return rawdb.ReadReceipts(fb.db, hash, *number, header.Time, fb.bc.Config()), nil -} - -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()) - return logs, nil -} - -func (fb *filterBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { - return nullSubscription() -} - -func (fb *filterBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { - return fb.bc.SubscribeChainEvent(ch) -} - -func (fb *filterBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { - return fb.bc.SubscribeRemovedLogsEvent(ch) -} - -func (fb *filterBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { - return fb.bc.SubscribeLogsEvent(ch) -} - -func (fb *filterBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { - return nullSubscription() -} - -func (fb *filterBackend) BloomStatus() (uint64, uint64) { return 4096, 0 } - -func (fb *filterBackend) ServiceFilter(ctx context.Context, ms *bloombits.MatcherSession) { - panic("not supported") -} - -func (fb *filterBackend) ChainConfig() *params.ChainConfig { - panic("not supported") -} - -func (fb *filterBackend) CurrentHeader() *types.Header { - panic("not supported") -} - -func nullSubscription() event.Subscription { - return event.NewSubscription(func(quit <-chan struct{}) error { - <-quit - return nil - }) } diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go deleted file mode 100644 index 11900a6cf..000000000 --- a/accounts/abi/bind/backends/simulated_test.go +++ /dev/null @@ -1,1400 +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 backends - -import ( - "bytes" - "context" - "errors" - "math/big" - "math/rand" - "reflect" - "strings" - "testing" - "time" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "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/params" -) - -func TestSimulatedBackend(t *testing.T) { - var gasLimit uint64 = 8000029 - key, _ := crypto.GenerateKey() // nolint: gosec - auth, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - genAlloc := make(core.GenesisAlloc) - genAlloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(9223372036854775807)} - - sim := NewSimulatedBackend(genAlloc, gasLimit) - defer sim.Close() - - // should return an error if the tx is not found - txHash := common.HexToHash("2") - _, isPending, err := sim.TransactionByHash(context.Background(), txHash) - - if isPending { - t.Fatal("transaction should not be pending") - } - if err != ethereum.NotFound { - t.Fatalf("err should be `ethereum.NotFound` but received %v", err) - } - - // generate a transaction and confirm you can retrieve it - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - code := `6060604052600a8060106000396000f360606040526008565b00` - var gas uint64 = 3000000 - tx := types.NewContractCreation(0, big.NewInt(0), gas, gasPrice, common.FromHex(code)) - tx, _ = types.SignTx(tx, types.HomesteadSigner{}, key) - - err = sim.SendTransaction(context.Background(), tx) - if err != nil { - t.Fatal("error sending transaction") - } - - txHash = tx.Hash() - _, isPending, err = sim.TransactionByHash(context.Background(), txHash) - if err != nil { - t.Fatalf("error getting transaction with hash: %v", txHash.String()) - } - if !isPending { - t.Fatal("transaction should have pending status") - } - - sim.Commit() - _, isPending, err = sim.TransactionByHash(context.Background(), txHash) - if err != nil { - t.Fatalf("error getting transaction with hash: %v", txHash.String()) - } - if isPending { - t.Fatal("transaction should not have pending status") - } -} - -var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - -// the following is based on this contract: -// -// contract T { -// event received(address sender, uint amount, bytes memo); -// event receivedAddr(address sender); -// -// function receive(bytes calldata memo) external payable returns (string memory res) { -// emit received(msg.sender, msg.value, memo); -// emit receivedAddr(msg.sender); -// return "hello world"; -// } -// } -const abiJSON = `[ { "constant": false, "inputs": [ { "name": "memo", "type": "bytes" } ], "name": "receive", "outputs": [ { "name": "res", "type": "string" } ], "payable": true, "stateMutability": "payable", "type": "function" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" }, { "indexed": false, "name": "amount", "type": "uint256" }, { "indexed": false, "name": "memo", "type": "bytes" } ], "name": "received", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "name": "sender", "type": "address" } ], "name": "receivedAddr", "type": "event" } ]` -const abiBin = `0x608060405234801561001057600080fd5b506102a0806100206000396000f3fe60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063a69b6ed014610040575b600080fd5b6100b76004803603602081101561005657600080fd5b810190808035906020019064010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b9091929391929390505050610132565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f75780820151818401526020810190506100dc565b50505050905090810190601f1680156101245780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60607f75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed33348585604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060405180910390a17f46923992397eac56cf13058aced2a1871933622717e27b24eabc13bf9dd329c833604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a16040805190810160405280600b81526020017f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525090509291505056fea165627a7a72305820ff0c57dad254cfeda48c9cfb47f1353a558bccb4d1bc31da1dae69315772d29e0029` -const deployedCode = `60806040526004361061003b576000357c010000000000000000000000000000000000000000000000000000000090048063a69b6ed014610040575b600080fd5b6100b76004803603602081101561005657600080fd5b810190808035906020019064010000000081111561007357600080fd5b82018360208201111561008557600080fd5b803590602001918460018302840111640100000000831117156100a757600080fd5b9091929391929390505050610132565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156100f75780820151818401526020810190506100dc565b50505050905090810190601f1680156101245780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60607f75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed33348585604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001806020018281038252848482818152602001925080828437600081840152601f19601f8201169050808301925050509550505050505060405180910390a17f46923992397eac56cf13058aced2a1871933622717e27b24eabc13bf9dd329c833604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a16040805190810160405280600b81526020017f68656c6c6f20776f726c6400000000000000000000000000000000000000000081525090509291505056fea165627a7a72305820ff0c57dad254cfeda48c9cfb47f1353a558bccb4d1bc31da1dae69315772d29e0029` - -// expected return value contains "hello world" -var expectedReturn = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - -func simTestBackend(testAddr common.Address) *SimulatedBackend { - return NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000000000)}, - }, 10000000, - ) -} - -func TestNewSimulatedBackend(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - expectedBal := big.NewInt(10000000000000000) - sim := simTestBackend(testAddr) - defer sim.Close() - - if sim.config != params.AllEthashProtocolChanges { - t.Errorf("expected sim config to equal params.AllEthashProtocolChanges, got %v", sim.config) - } - - if sim.blockchain.Config() != params.AllEthashProtocolChanges { - t.Errorf("expected sim blockchain config to equal params.AllEthashProtocolChanges, got %v", sim.config) - } - - stateDB, _ := sim.blockchain.State() - bal := stateDB.GetBalance(testAddr) - if bal.Cmp(expectedBal) != 0 { - t.Errorf("expected balance for test address not received. expected: %v actual: %v", expectedBal, bal) - } -} - -func TestAdjustTime(t *testing.T) { - sim := NewSimulatedBackend( - core.GenesisAlloc{}, 10000000, - ) - defer sim.Close() - - prevTime := sim.pendingBlock.Time() - if err := sim.AdjustTime(time.Second); err != nil { - t.Error(err) - } - newTime := sim.pendingBlock.Time() - - if newTime-prevTime != uint64(time.Second.Seconds()) { - t.Errorf("adjusted time not equal to a second. prev: %v, new: %v", prevTime, newTime) - } -} - -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 - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - sim.SendTransaction(context.Background(), signedTx) - // AdjustTime should fail on non-empty block - if err := sim.AdjustTime(time.Second); err == nil { - t.Error("Expected adjust time to error on non-empty block") - } - sim.Commit() - - prevTime := sim.pendingBlock.Time() - if err := sim.AdjustTime(time.Minute); err != nil { - t.Error(err) - } - newTime := sim.pendingBlock.Time() - if newTime-prevTime != uint64(time.Minute.Seconds()) { - t.Errorf("adjusted time not equal to a minute. prev: %v, new: %v", prevTime, newTime) - } - // Put a transaction after adjusting time - tx2 := types.NewTransaction(1, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx2, err := types.SignTx(tx2, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - sim.SendTransaction(context.Background(), signedTx2) - sim.Commit() - newTime = sim.pendingBlock.Time() - if newTime-prevTime >= uint64(time.Minute.Seconds()) { - t.Errorf("time adjusted, but shouldn't be: prev: %v, new: %v", prevTime, newTime) - } -} - -func TestBalanceAt(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - expectedBal := big.NewInt(10000000000000000) - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - bal, err := sim.BalanceAt(bgCtx, testAddr, nil) - if err != nil { - t.Error(err) - } - - if bal.Cmp(expectedBal) != 0 { - t.Errorf("expected balance for test address not received. expected: %v actual: %v", expectedBal, bal) - } -} - -func TestBlockByHash(t *testing.T) { - sim := NewSimulatedBackend( - core.GenesisAlloc{}, 10000000, - ) - defer sim.Close() - bgCtx := context.Background() - - block, err := sim.BlockByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get recent block: %v", err) - } - blockByHash, err := sim.BlockByHash(bgCtx, block.Hash()) - if err != nil { - t.Errorf("could not get recent block: %v", err) - } - - if block.Hash() != blockByHash.Hash() { - t.Errorf("did not get expected block") - } -} - -func TestBlockByNumber(t *testing.T) { - sim := NewSimulatedBackend( - core.GenesisAlloc{}, 10000000, - ) - defer sim.Close() - bgCtx := context.Background() - - block, err := sim.BlockByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get recent block: %v", err) - } - if block.NumberU64() != 0 { - t.Errorf("did not get most recent block, instead got block number %v", block.NumberU64()) - } - - // create one block - sim.Commit() - - block, err = sim.BlockByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get recent block: %v", err) - } - if block.NumberU64() != 1 { - t.Errorf("did not get most recent block, instead got block number %v", block.NumberU64()) - } - - blockByNumber, err := sim.BlockByNumber(bgCtx, big.NewInt(1)) - if err != nil { - t.Errorf("could not get block by number: %v", err) - } - if blockByNumber.Hash() != block.Hash() { - t.Errorf("did not get the same block with height of 1 as before") - } -} - -func TestNonceAt(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - nonce, err := sim.NonceAt(bgCtx, testAddr, big.NewInt(0)) - if err != nil { - t.Errorf("could not get nonce for test addr: %v", err) - } - - if nonce != uint64(0) { - t.Errorf("received incorrect nonce. expected 0, got %v", nonce) - } - - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(nonce, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - sim.Commit() - - newNonce, err := sim.NonceAt(bgCtx, testAddr, big.NewInt(1)) - if err != nil { - t.Errorf("could not get nonce for test addr: %v", err) - } - - if newNonce != nonce+uint64(1) { - t.Errorf("received incorrect nonce. expected 1, got %v", nonce) - } - // create some more blocks - sim.Commit() - // Check that we can get data for an older block/state - newNonce, err = sim.NonceAt(bgCtx, testAddr, big.NewInt(1)) - if err != nil { - t.Fatalf("could not get nonce for test addr: %v", err) - } - if newNonce != nonce+uint64(1) { - t.Fatalf("received incorrect nonce. expected 1, got %v", nonce) - } -} - -func TestSendTransaction(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - sim.Commit() - - block, err := sim.BlockByNumber(bgCtx, big.NewInt(1)) - if err != nil { - t.Errorf("could not get block at height 1: %v", err) - } - - if signedTx.Hash() != block.Transactions()[0].Hash() { - t.Errorf("did not commit sent transaction. expected hash %v got hash %v", block.Transactions()[0].Hash(), signedTx.Hash()) - } -} - -func TestTransactionByHash(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := NewSimulatedBackend( - core.GenesisAlloc{ - testAddr: {Balance: big.NewInt(10000000000000000)}, - }, 10000000, - ) - defer sim.Close() - bgCtx := context.Background() - - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - - // ensure tx is committed pending - receivedTx, pending, err := sim.TransactionByHash(bgCtx, signedTx.Hash()) - if err != nil { - t.Errorf("could not get transaction by hash %v: %v", signedTx.Hash(), err) - } - if !pending { - t.Errorf("expected transaction to be in pending state") - } - if receivedTx.Hash() != signedTx.Hash() { - t.Errorf("did not received committed transaction. expected hash %v got hash %v", signedTx.Hash(), receivedTx.Hash()) - } - - sim.Commit() - - // ensure tx is not and committed pending - receivedTx, pending, err = sim.TransactionByHash(bgCtx, signedTx.Hash()) - if err != nil { - t.Errorf("could not get transaction by hash %v: %v", signedTx.Hash(), err) - } - if pending { - t.Errorf("expected transaction to not be in pending state") - } - if receivedTx.Hash() != signedTx.Hash() { - t.Errorf("did not received committed transaction. expected hash %v got hash %v", signedTx.Hash(), receivedTx.Hash()) - } -} - -func TestEstimateGas(t *testing.T) { - /* - pragma solidity ^0.6.4; - contract GasEstimation { - function PureRevert() public { revert(); } - function Revert() public { revert("revert reason");} - function OOG() public { for (uint i = 0; ; i++) {}} - function Assert() public { assert(false);} - function Valid() public {} - } - */ - const contractAbi = "[{\"inputs\":[],\"name\":\"Assert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OOG\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PureRevert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Revert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"Valid\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" - const contractBin = "0x60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040523480156100115760006000fd5b506004361061005c5760003560e01c806350f6fe3414610062578063aa8b1d301461006c578063b9b046f914610076578063d8b9839114610080578063e09fface1461008a5761005c565b60006000fd5b61006a610094565b005b6100746100ad565b005b61007e6100b5565b005b6100886100c2565b005b610092610135565b005b6000600090505b5b808060010191505061009b565b505b565b60006000fd5b565b600015156100bf57fe5b5b565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600d8152602001807f72657665727420726561736f6e0000000000000000000000000000000000000081526020015060200191505060405180910390fd5b565b5b56fea2646970667358221220345bbcbb1a5ecf22b53a78eaebf95f8ee0eceff6d10d4b9643495084d2ec934a64736f6c63430006040033" - - key, _ := crypto.GenerateKey() - addr := crypto.PubkeyToAddress(key.PublicKey) - opts, _ := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - - sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether)}}, 10000000) - defer sim.Close() - - parsed, _ := abi.JSON(strings.NewReader(contractAbi)) - contractAddr, _, _, _ := bind.DeployContract(opts, parsed, common.FromHex(contractBin), sim) - sim.Commit() - - var cases = []struct { - name string - message ethereum.CallMsg - expect uint64 - expectError error - expectData interface{} - }{ - {"plain transfer(valid)", ethereum.CallMsg{ - From: addr, - To: &addr, - Gas: 0, - GasPrice: big.NewInt(0), - Value: big.NewInt(1), - Data: nil, - }, params.TxGas, nil, nil}, - - {"plain transfer(invalid)", ethereum.CallMsg{ - From: addr, - To: &contractAddr, - Gas: 0, - GasPrice: big.NewInt(0), - Value: big.NewInt(1), - Data: nil, - }, 0, errors.New("execution reverted"), nil}, - - {"Revert", ethereum.CallMsg{ - From: addr, - To: &contractAddr, - Gas: 0, - GasPrice: big.NewInt(0), - Value: nil, - Data: common.Hex2Bytes("d8b98391"), - }, 0, errors.New("execution reverted: revert reason"), "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000"}, - - {"PureRevert", ethereum.CallMsg{ - From: addr, - To: &contractAddr, - Gas: 0, - GasPrice: big.NewInt(0), - Value: nil, - Data: common.Hex2Bytes("aa8b1d30"), - }, 0, errors.New("execution reverted"), nil}, - - {"OOG", ethereum.CallMsg{ - From: addr, - To: &contractAddr, - Gas: 100000, - GasPrice: big.NewInt(0), - Value: nil, - Data: common.Hex2Bytes("50f6fe34"), - }, 0, errors.New("gas required exceeds allowance (100000)"), nil}, - - {"Assert", ethereum.CallMsg{ - From: addr, - To: &contractAddr, - Gas: 100000, - GasPrice: big.NewInt(0), - Value: nil, - Data: common.Hex2Bytes("b9b046f9"), - }, 0, errors.New("invalid opcode: INVALID"), nil}, - - {"Valid", ethereum.CallMsg{ - From: addr, - To: &contractAddr, - Gas: 100000, - GasPrice: big.NewInt(0), - Value: nil, - Data: common.Hex2Bytes("e09fface"), - }, 21275, nil, nil}, - } - for _, c := range cases { - got, err := sim.EstimateGas(context.Background(), c.message) - if c.expectError != nil { - if err == nil { - t.Fatalf("Expect error, got nil") - } - if c.expectError.Error() != err.Error() { - t.Fatalf("Expect error, want %v, got %v", c.expectError, err) - } - if c.expectData != nil { - if err, ok := err.(*revertError); !ok { - t.Fatalf("Expect revert error, got %T", err) - } else if !reflect.DeepEqual(err.ErrorData(), c.expectData) { - t.Fatalf("Error data mismatch, want %v, got %v", c.expectData, err.ErrorData()) - } - } - continue - } - if got != c.expect { - t.Fatalf("Gas estimation mismatch, want %d, got %d", c.expect, got) - } - } -} - -func TestEstimateGasWithPrice(t *testing.T) { - key, _ := crypto.GenerateKey() - addr := crypto.PubkeyToAddress(key.PublicKey) - - sim := NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(params.Ether*2 + 2e17)}}, 10000000) - defer sim.Close() - - recipient := common.HexToAddress("deadbeef") - var cases = []struct { - name string - message ethereum.CallMsg - expect uint64 - expectError error - }{ - {"EstimateWithoutPrice", ethereum.CallMsg{ - From: addr, - To: &recipient, - Gas: 0, - GasPrice: big.NewInt(0), - Value: big.NewInt(100000000000), - Data: nil, - }, 21000, nil}, - - {"EstimateWithPrice", ethereum.CallMsg{ - From: addr, - To: &recipient, - Gas: 0, - GasPrice: big.NewInt(100000000000), - Value: big.NewInt(100000000000), - Data: nil, - }, 21000, nil}, - - {"EstimateWithVeryHighPrice", ethereum.CallMsg{ - From: addr, - To: &recipient, - Gas: 0, - GasPrice: big.NewInt(1e14), // gascost = 2.1ether - Value: big.NewInt(1e17), // the remaining balance for fee is 2.1ether - Data: nil, - }, 21000, nil}, - - {"EstimateWithSuperhighPrice", ethereum.CallMsg{ - From: addr, - To: &recipient, - Gas: 0, - GasPrice: big.NewInt(2e14), // gascost = 4.2ether - Value: big.NewInt(100000000000), - Data: nil, - }, 21000, errors.New("gas required exceeds allowance (10999)")}, // 10999=(2.2ether-1000wei)/(2e14) - - {"EstimateEIP1559WithHighFees", ethereum.CallMsg{ - From: addr, - To: &addr, - Gas: 0, - GasFeeCap: big.NewInt(1e14), // maxgascost = 2.1ether - GasTipCap: big.NewInt(1), - Value: big.NewInt(1e17), // the remaining balance for fee is 2.1ether - Data: nil, - }, params.TxGas, nil}, - - {"EstimateEIP1559WithSuperHighFees", ethereum.CallMsg{ - From: addr, - To: &addr, - Gas: 0, - GasFeeCap: big.NewInt(1e14), // maxgascost = 2.1ether - GasTipCap: big.NewInt(1), - Value: big.NewInt(1e17 + 1), // the remaining balance for fee is 2.1ether - Data: nil, - }, params.TxGas, errors.New("gas required exceeds allowance (20999)")}, // 20999=(2.2ether-0.1ether-1wei)/(1e14) - } - for i, c := range cases { - got, err := sim.EstimateGas(context.Background(), c.message) - if c.expectError != nil { - if err == nil { - t.Fatalf("test %d: expect error, got nil", i) - } - if c.expectError.Error() != err.Error() { - t.Fatalf("test %d: expect error, want %v, got %v", i, c.expectError, err) - } - continue - } - if c.expectError == nil && err != nil { - t.Fatalf("test %d: didn't expect error, got %v", i, err) - } - if got != c.expect { - t.Fatalf("test %d: gas estimation mismatch, want %d, got %d", i, c.expect, got) - } - } -} - -func TestHeaderByHash(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - header, err := sim.HeaderByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get recent block: %v", err) - } - headerByHash, err := sim.HeaderByHash(bgCtx, header.Hash()) - if err != nil { - t.Errorf("could not get recent block: %v", err) - } - - if header.Hash() != headerByHash.Hash() { - t.Errorf("did not get expected block") - } -} - -func TestHeaderByNumber(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - latestBlockHeader, err := sim.HeaderByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get header for tip of chain: %v", err) - } - if latestBlockHeader == nil { - t.Errorf("received a nil block header") - } else if latestBlockHeader.Number.Uint64() != uint64(0) { - t.Errorf("expected block header number 0, instead got %v", latestBlockHeader.Number.Uint64()) - } - - sim.Commit() - - latestBlockHeader, err = sim.HeaderByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get header for blockheight of 1: %v", err) - } - - blockHeader, err := sim.HeaderByNumber(bgCtx, big.NewInt(1)) - if err != nil { - t.Errorf("could not get header for blockheight of 1: %v", err) - } - - if blockHeader.Hash() != latestBlockHeader.Hash() { - t.Errorf("block header and latest block header are not the same") - } - if blockHeader.Number.Int64() != int64(1) { - t.Errorf("did not get blockheader for block 1. instead got block %v", blockHeader.Number.Int64()) - } - - block, err := sim.BlockByNumber(bgCtx, big.NewInt(1)) - if err != nil { - t.Errorf("could not get block for blockheight of 1: %v", err) - } - - if block.Hash() != blockHeader.Hash() { - t.Errorf("block hash and block header hash do not match. expected %v, got %v", block.Hash(), blockHeader.Hash()) - } -} - -func TestTransactionCount(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - currentBlock, err := sim.BlockByNumber(bgCtx, nil) - if err != nil || currentBlock == nil { - t.Error("could not get current block") - } - - count, err := sim.TransactionCount(bgCtx, currentBlock.Hash()) - if err != nil { - t.Error("could not get current block's transaction count") - } - - if count != 0 { - t.Errorf("expected transaction count of %v does not match actual count of %v", 0, count) - } - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - - sim.Commit() - - lastBlock, err := sim.BlockByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get header for tip of chain: %v", err) - } - - count, err = sim.TransactionCount(bgCtx, lastBlock.Hash()) - if err != nil { - t.Error("could not get current block's transaction count") - } - - if count != 1 { - t.Errorf("expected transaction count of %v does not match actual count of %v", 1, count) - } -} - -func TestTransactionInBlock(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - transaction, err := sim.TransactionInBlock(bgCtx, sim.pendingBlock.Hash(), uint(0)) - if err == nil && err != errTransactionDoesNotExist { - t.Errorf("expected a transaction does not exist error to be received but received %v", err) - } - if transaction != nil { - t.Errorf("expected transaction to be nil but received %v", transaction) - } - - // expect pending nonce to be 0 since account has not been used - pendingNonce, err := sim.PendingNonceAt(bgCtx, testAddr) - if err != nil { - t.Errorf("did not get the pending nonce: %v", err) - } - - if pendingNonce != uint64(0) { - t.Errorf("expected pending nonce of 0 got %v", pendingNonce) - } - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - - sim.Commit() - - lastBlock, err := sim.BlockByNumber(bgCtx, nil) - if err != nil { - t.Errorf("could not get header for tip of chain: %v", err) - } - - transaction, err = sim.TransactionInBlock(bgCtx, lastBlock.Hash(), uint(1)) - if err == nil && err != errTransactionDoesNotExist { - t.Errorf("expected a transaction does not exist error to be received but received %v", err) - } - if transaction != nil { - t.Errorf("expected transaction to be nil but received %v", transaction) - } - - transaction, err = sim.TransactionInBlock(bgCtx, lastBlock.Hash(), uint(0)) - if err != nil { - t.Errorf("could not get transaction in the lastest block with hash %v: %v", lastBlock.Hash().String(), err) - } - - if signedTx.Hash().String() != transaction.Hash().String() { - t.Errorf("received transaction that did not match the sent transaction. expected hash %v, got hash %v", signedTx.Hash().String(), transaction.Hash().String()) - } -} - -func TestPendingNonceAt(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - // expect pending nonce to be 0 since account has not been used - pendingNonce, err := sim.PendingNonceAt(bgCtx, testAddr) - if err != nil { - t.Errorf("did not get the pending nonce: %v", err) - } - - if pendingNonce != uint64(0) { - t.Errorf("expected pending nonce of 0 got %v", pendingNonce) - } - - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - - // expect pending nonce to be 1 since account has submitted one transaction - pendingNonce, err = sim.PendingNonceAt(bgCtx, testAddr) - if err != nil { - t.Errorf("did not get the pending nonce: %v", err) - } - - if pendingNonce != uint64(1) { - t.Errorf("expected pending nonce of 1 got %v", pendingNonce) - } - - // make a new transaction with a nonce of 1 - tx = types.NewTransaction(uint64(1), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err = types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not send tx: %v", err) - } - - // expect pending nonce to be 2 since account now has two transactions - pendingNonce, err = sim.PendingNonceAt(bgCtx, testAddr) - if err != nil { - t.Errorf("did not get the pending nonce: %v", err) - } - - if pendingNonce != uint64(2) { - t.Errorf("expected pending nonce of 2 got %v", pendingNonce) - } -} - -func TestTransactionReceipt(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - // create a signed transaction to send - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - tx := types.NewTransaction(uint64(0), testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - signedTx, err := types.SignTx(tx, types.HomesteadSigner{}, testKey) - if err != nil { - t.Errorf("could not sign tx: %v", err) - } - - // send tx to simulated backend - err = sim.SendTransaction(bgCtx, signedTx) - if err != nil { - t.Errorf("could not add tx to pending block: %v", err) - } - sim.Commit() - - receipt, err := sim.TransactionReceipt(bgCtx, signedTx.Hash()) - if err != nil { - t.Errorf("could not get transaction receipt: %v", err) - } - - if receipt.ContractAddress != testAddr && receipt.TxHash != signedTx.Hash() { - t.Errorf("received receipt is not correct: %v", receipt) - } -} - -func TestSuggestGasPrice(t *testing.T) { - sim := NewSimulatedBackend( - core.GenesisAlloc{}, - 10000000, - ) - defer sim.Close() - bgCtx := context.Background() - gasPrice, err := sim.SuggestGasPrice(bgCtx) - if err != nil { - t.Errorf("could not get gas price: %v", err) - } - if gasPrice.Uint64() != sim.pendingBlock.Header().BaseFee.Uint64() { - t.Errorf("gas price was not expected value of %v. actual: %v", sim.pendingBlock.Header().BaseFee.Uint64(), gasPrice.Uint64()) - } -} - -func TestPendingCodeAt(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - code, err := sim.CodeAt(bgCtx, testAddr, nil) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - if len(code) != 0 { - t.Errorf("got code for account that does not have contract code") - } - - parsed, err := abi.JSON(strings.NewReader(abiJSON)) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) - contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) - if err != nil { - t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract) - } - - code, err = sim.PendingCodeAt(bgCtx, contractAddr) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - if len(code) == 0 { - t.Errorf("did not get code for account that has contract code") - } - // ensure code received equals code deployed - if !bytes.Equal(code, common.FromHex(deployedCode)) { - t.Errorf("code received did not match expected deployed code:\n expected %v\n actual %v", common.FromHex(deployedCode), code) - } -} - -func TestCodeAt(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - code, err := sim.CodeAt(bgCtx, testAddr, nil) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - if len(code) != 0 { - t.Errorf("got code for account that does not have contract code") - } - - parsed, err := abi.JSON(strings.NewReader(abiJSON)) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) - contractAddr, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(abiBin), sim) - if err != nil { - t.Errorf("could not deploy contract: %v tx: %v contract: %v", err, tx, contract) - } - - sim.Commit() - code, err = sim.CodeAt(bgCtx, contractAddr, nil) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - if len(code) == 0 { - t.Errorf("did not get code for account that has contract code") - } - // ensure code received equals code deployed - if !bytes.Equal(code, common.FromHex(deployedCode)) { - t.Errorf("code received did not match expected deployed code:\n expected %v\n actual %v", common.FromHex(deployedCode), code) - } -} - -// When receive("X") is called with sender 0x00... and value 1, it produces this tx receipt: -// -// receipt{status=1 cgas=23949 bloom=00000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000040200000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 logs=[log: b6818c8064f645cd82d99b59a1a267d6d61117ef [75fd880d39c1daf53b6547ab6cb59451fc6452d27caa90e5b6649dd8293b9eed] 000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158 9ae378b6d4409eada347a5dc0c180f186cb62dc68fcc0f043425eb917335aa28 0 95d429d309bb9d753954195fe2d69bd140b4ae731b9b5b605c34323de162cf00 0]} -func TestPendingAndCallContract(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - parsed, err := abi.JSON(strings.NewReader(abiJSON)) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - contractAuth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) - addr, _, _, err := bind.DeployContract(contractAuth, parsed, common.FromHex(abiBin), sim) - if err != nil { - t.Errorf("could not deploy contract: %v", err) - } - - input, err := parsed.Pack("receive", []byte("X")) - if err != nil { - t.Errorf("could not pack receive function on contract: %v", err) - } - - // make sure you can call the contract in pending state - res, err := sim.PendingCallContract(bgCtx, ethereum.CallMsg{ - From: testAddr, - To: &addr, - Data: input, - }) - if err != nil { - t.Errorf("could not call receive method on contract: %v", err) - } - if len(res) == 0 { - t.Errorf("result of contract call was empty: %v", res) - } - - // while comparing against the byte array is more exact, also compare against the human readable string for readability - if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") { - t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res)) - } - - sim.Commit() - - // make sure you can call the contract - res, err = sim.CallContract(bgCtx, ethereum.CallMsg{ - From: testAddr, - To: &addr, - Data: input, - }, nil) - if err != nil { - t.Errorf("could not call receive method on contract: %v", err) - } - if len(res) == 0 { - t.Errorf("result of contract call was empty: %v", res) - } - - if !bytes.Equal(res, expectedReturn) || !strings.Contains(string(res), "hello world") { - t.Errorf("response from calling contract was expected to be 'hello world' instead received %v", string(res)) - } -} - -// This test is based on the following contract: -/* -contract Reverter { - function revertString() public pure{ - require(false, "some error"); - } - function revertNoString() public pure { - require(false, ""); - } - function revertASM() public pure { - assembly { - revert(0x0, 0x0) - } - } - function noRevert() public pure { - assembly { - // Assembles something that looks like require(false, "some error") but is not reverted - mstore(0x0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(0x4, 0x0000000000000000000000000000000000000000000000000000000000000020) - mstore(0x24, 0x000000000000000000000000000000000000000000000000000000000000000a) - mstore(0x44, 0x736f6d65206572726f7200000000000000000000000000000000000000000000) - return(0x0, 0x64) - } - } -}*/ -func TestCallContractRevert(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - bgCtx := context.Background() - - reverterABI := `[{"inputs": [],"name": "noRevert","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertASM","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertNoString","outputs": [],"stateMutability": "pure","type": "function"},{"inputs": [],"name": "revertString","outputs": [],"stateMutability": "pure","type": "function"}]` - reverterBin := "608060405234801561001057600080fd5b506101d3806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c80634b409e01146100515780639b340e361461005b5780639bd6103714610065578063b7246fc11461006f575b600080fd5b610059610079565b005b6100636100ca565b005b61006d6100cf565b005b610077610145565b005b60006100c8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526000815260200160200191505060405180910390fd5b565b600080fd5b6000610143576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600a8152602001807f736f6d65206572726f720000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b7f08c379a0000000000000000000000000000000000000000000000000000000006000526020600452600a6024527f736f6d65206572726f720000000000000000000000000000000000000000000060445260646000f3fea2646970667358221220cdd8af0609ec4996b7360c7c780bad5c735740c64b1fffc3445aa12d37f07cb164736f6c63430006070033" - - parsed, err := abi.JSON(strings.NewReader(reverterABI)) - if err != nil { - t.Errorf("could not get code at test addr: %v", err) - } - contractAuth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) - addr, _, _, err := bind.DeployContract(contractAuth, parsed, common.FromHex(reverterBin), sim) - if err != nil { - t.Errorf("could not deploy contract: %v", err) - } - - inputs := make(map[string]interface{}, 3) - inputs["revertASM"] = nil - inputs["revertNoString"] = "" - inputs["revertString"] = "some error" - - call := make([]func([]byte) ([]byte, error), 2) - call[0] = func(input []byte) ([]byte, error) { - return sim.PendingCallContract(bgCtx, ethereum.CallMsg{ - From: testAddr, - To: &addr, - Data: input, - }) - } - call[1] = func(input []byte) ([]byte, error) { - return sim.CallContract(bgCtx, ethereum.CallMsg{ - From: testAddr, - To: &addr, - Data: input, - }, nil) - } - - // Run pending calls then commit - for _, cl := range call { - for key, val := range inputs { - input, err := parsed.Pack(key) - if err != nil { - t.Errorf("could not pack %v function on contract: %v", key, err) - } - - res, err := cl(input) - if err == nil { - t.Errorf("call to %v was not reverted", key) - } - if res != nil { - t.Errorf("result from %v was not nil: %v", key, res) - } - if val != nil { - rerr, ok := err.(*revertError) - if !ok { - t.Errorf("expect revert error") - } - if rerr.Error() != "execution reverted: "+val.(string) { - t.Errorf("error was malformed: got %v want %v", rerr.Error(), val) - } - } else { - // revert(0x0,0x0) - if err.Error() != "execution reverted" { - t.Errorf("error was malformed: got %v want %v", err, "execution reverted") - } - } - } - input, err := parsed.Pack("noRevert") - if err != nil { - t.Errorf("could not pack noRevert function on contract: %v", err) - } - res, err := cl(input) - if err != nil { - t.Error("call to noRevert was reverted") - } - if res == nil { - t.Errorf("result from noRevert was nil") - } - sim.Commit() - } -} - -// TestFork check that the chain length after a reorg is correct. -// Steps: -// 1. Save the current block which will serve as parent for the fork. -// 2. Mine n blocks with n ∈ [0, 20]. -// 3. Assert that the chain length is n. -// 4. Fork by using the parent block as ancestor. -// 5. Mine n+1 blocks which should trigger a reorg. -// 6. Assert that the chain length is n+1. -// Since Commit() was called 2n+1 times in total, -// having a chain length of just n+1 means that a reorg occurred. -func TestFork(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - // 1. - parent := sim.blockchain.CurrentBlock() - // 2. - n := int(rand.Int31n(21)) - for i := 0; i < n; i++ { - sim.Commit() - } - // 3. - if sim.blockchain.CurrentBlock().Number.Uint64() != uint64(n) { - t.Error("wrong chain length") - } - // 4. - sim.Fork(context.Background(), parent.Hash()) - // 5. - for i := 0; i < n+1; i++ { - sim.Commit() - } - // 6. - if sim.blockchain.CurrentBlock().Number.Uint64() != uint64(n+1) { - t.Error("wrong chain length") - } -} - -/* -Example contract to test event emission: - - pragma solidity >=0.7.0 <0.9.0; - contract Callable { - event Called(); - function Call() public { emit Called(); } - } -*/ -const callableAbi = "[{\"anonymous\":false,\"inputs\":[],\"name\":\"Called\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"Call\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" - -const callableBin = "6080604052348015600f57600080fd5b5060998061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806334e2292114602d575b600080fd5b60336035565b005b7f81fab7a4a0aa961db47eefc81f143a5220e8c8495260dd65b1356f1d19d3c7b860405160405180910390a156fea2646970667358221220029436d24f3ac598ceca41d4d712e13ced6d70727f4cdc580667de66d2f51d8b64736f6c63430008010033" - -// TestForkLogsReborn check that the simulated reorgs -// correctly remove and reborn logs. -// Steps: -// 1. Deploy the Callable contract. -// 2. Set up an event subscription. -// 3. Save the current block which will serve as parent for the fork. -// 4. Send a transaction. -// 5. Check that the event was included. -// 6. Fork by using the parent block as ancestor. -// 7. Mine two blocks to trigger a reorg. -// 8. Check that the event was removed. -// 9. Re-send the transaction and mine a block. -// 10. Check that the event was reborn. -func TestForkLogsReborn(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - // 1. - parsed, _ := abi.JSON(strings.NewReader(callableAbi)) - auth, _ := bind.NewKeyedTransactorWithChainID(testKey, big.NewInt(1337)) - _, _, contract, err := bind.DeployContract(auth, parsed, common.FromHex(callableBin), sim) - if err != nil { - t.Errorf("deploying contract: %v", err) - } - sim.Commit() - // 2. - logs, sub, err := contract.WatchLogs(nil, "Called") - if err != nil { - t.Errorf("watching logs: %v", err) - } - defer sub.Unsubscribe() - // 3. - parent := sim.blockchain.CurrentBlock() - // 4. - tx, err := contract.Transact(auth, "Call") - if err != nil { - t.Errorf("transacting: %v", err) - } - sim.Commit() - // 5. - log := <-logs - if log.TxHash != tx.Hash() { - t.Error("wrong event tx hash") - } - if log.Removed { - t.Error("Event should be included") - } - // 6. - if err := sim.Fork(context.Background(), parent.Hash()); err != nil { - t.Errorf("forking: %v", err) - } - // 7. - sim.Commit() - sim.Commit() - // 8. - log = <-logs - if log.TxHash != tx.Hash() { - t.Error("wrong event tx hash") - } - if !log.Removed { - t.Error("Event should be removed") - } - // 9. - if err := sim.SendTransaction(context.Background(), tx); err != nil { - t.Errorf("sending transaction: %v", err) - } - sim.Commit() - // 10. - log = <-logs - if log.TxHash != tx.Hash() { - t.Error("wrong event tx hash") - } - if log.Removed { - t.Error("Event should be included") - } -} - -// TestForkResendTx checks that re-sending a TX after a fork -// is possible and does not cause a "nonce mismatch" panic. -// Steps: -// 1. Save the current block which will serve as parent for the fork. -// 2. Send a transaction. -// 3. Check that the TX is included in block 1. -// 4. Fork by using the parent block as ancestor. -// 5. Mine a block, Re-send the transaction and mine another one. -// 6. Check that the TX is now included in block 2. -func TestForkResendTx(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - // 1. - parent := sim.blockchain.CurrentBlock() - // 2. - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - - _tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - tx, _ := types.SignTx(_tx, types.HomesteadSigner{}, testKey) - sim.SendTransaction(context.Background(), tx) - sim.Commit() - // 3. - receipt, _ := sim.TransactionReceipt(context.Background(), tx.Hash()) - if h := receipt.BlockNumber.Uint64(); h != 1 { - t.Errorf("TX included in wrong block: %d", h) - } - // 4. - if err := sim.Fork(context.Background(), parent.Hash()); err != nil { - t.Errorf("forking: %v", err) - } - // 5. - sim.Commit() - if err := sim.SendTransaction(context.Background(), tx); err != nil { - t.Errorf("sending transaction: %v", err) - } - sim.Commit() - // 6. - receipt, _ = sim.TransactionReceipt(context.Background(), tx.Hash()) - if h := receipt.BlockNumber.Uint64(); h != 2 { - t.Errorf("TX included in wrong block: %d", h) - } -} - -func TestCommitReturnValue(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - - startBlockHeight := sim.blockchain.CurrentBlock().Number.Uint64() - - // Test if Commit returns the correct block hash - h1 := sim.Commit() - if h1 != sim.blockchain.CurrentBlock().Hash() { - t.Error("Commit did not return the hash of the last block.") - } - - // Create a block in the original chain (containing a transaction to force different block hashes) - head, _ := sim.HeaderByNumber(context.Background(), nil) // Should be child's, good enough - gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) - _tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) - tx, _ := types.SignTx(_tx, types.HomesteadSigner{}, testKey) - sim.SendTransaction(context.Background(), tx) - h2 := sim.Commit() - - // Create another block in the original chain - sim.Commit() - - // Fork at the first bock - if err := sim.Fork(context.Background(), h1); err != nil { - t.Errorf("forking: %v", err) - } - - // Test if Commit returns the correct block hash after the reorg - h2fork := sim.Commit() - if h2 == h2fork { - t.Error("The block in the fork and the original block are the same block!") - } - if sim.blockchain.GetHeader(h2fork, startBlockHeight+2) == nil { - t.Error("Could not retrieve the just created block (side-chain)") - } -} - -// TestAdjustTimeAfterFork ensures that after a fork, AdjustTime uses the pending fork -// block's parent rather than the canonical head's parent. -func TestAdjustTimeAfterFork(t *testing.T) { - testAddr := crypto.PubkeyToAddress(testKey.PublicKey) - sim := simTestBackend(testAddr) - defer sim.Close() - - sim.Commit() // h1 - h1 := sim.blockchain.CurrentHeader().Hash() - sim.Commit() // h2 - sim.Fork(context.Background(), h1) - sim.AdjustTime(1 * time.Second) - sim.Commit() - - head := sim.blockchain.CurrentHeader() - if head.Number == common.Big2 && head.ParentHash != h1 { - t.Errorf("failed to build block on fork") - } -} diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index b03f431f7..96d284cdc 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -48,6 +48,7 @@ type CallOpts struct { Pending bool // Whether to operate on the pending state or the last known one From common.Address // Optional the sender address, otherwise the first account is used BlockNumber *big.Int // Optional the block number on which the call should be performed + BlockHash common.Hash // Optional the block hash on which the call should be performed Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) } @@ -189,6 +190,23 @@ func (c *BoundContract) Call(opts *CallOpts, results *[]interface{}, method stri return ErrNoCode } } + } else if opts.BlockHash != (common.Hash{}) { + bh, ok := c.caller.(BlockHashContractCaller) + if !ok { + return ErrNoBlockHashState + } + output, err = bh.CallContractAtHash(ctx, msg, opts.BlockHash) + if err != nil { + return err + } + if len(output) == 0 { + // Make sure we have a contract to operate on, and bail out otherwise. + if code, err = bh.CodeAtHash(ctx, c.address, opts.BlockHash); err != nil { + return err + } else if len(code) == 0 { + return ErrNoCode + } + } } else { output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber) if err != nil { @@ -220,7 +238,7 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in if err != nil { return nil, err } - // todo(rjl493456442) check the method is payable or not, + // todo(rjl493456442) check whether the method is payable or not, // reject invalid transaction at the first place return c.transact(opts, &c.address, input) } @@ -228,7 +246,7 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in // RawTransact initiates a transaction with the given raw calldata as the input. // It's usually used to initiate transactions for invoking **Fallback** function. func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) { - // todo(rjl493456442) check the method is payable or not, + // todo(rjl493456442) check whether the method is payable or not, // reject invalid transaction at the first place return c.transact(opts, &c.address, calldata) } diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go index ca0128148..f7eb7d14d 100644 --- a/accounts/abi/bind/base_test.go +++ b/accounts/abi/bind/base_test.go @@ -114,7 +114,28 @@ func (mc *mockPendingCaller) PendingCallContract(ctx context.Context, call ether return mc.pendingCallContractBytes, mc.pendingCallContractErr } +type mockBlockHashCaller struct { + *mockCaller + codeAtHashBytes []byte + codeAtHashErr error + codeAtHashCalled bool + callContractAtHashCalled bool + callContractAtHashBytes []byte + callContractAtHashErr error +} + +func (mc *mockBlockHashCaller) CodeAtHash(ctx context.Context, contract common.Address, hash common.Hash) ([]byte, error) { + mc.codeAtHashCalled = true + return mc.codeAtHashBytes, mc.codeAtHashErr +} + +func (mc *mockBlockHashCaller) CallContractAtHash(ctx context.Context, call ethereum.CallMsg, hash common.Hash) ([]byte, error) { + mc.callContractAtHashCalled = true + return mc.callContractAtHashBytes, mc.callContractAtHashErr +} + func TestPassingBlockNumber(t *testing.T) { + t.Parallel() mc := &mockPendingCaller{ mockCaller: &mockCaller{ codeAtBytes: []byte{1, 2, 3}, @@ -166,6 +187,7 @@ func TestPassingBlockNumber(t *testing.T) { const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158" func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) { + t.Parallel() hash := crypto.Keccak256Hash([]byte("testName")) topics := []common.Hash{ crypto.Keccak256Hash([]byte("received(string,address,uint256,bytes)")), @@ -187,6 +209,7 @@ func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) { } func TestUnpackAnonymousLogIntoMap(t *testing.T) { + t.Parallel() mockLog := newMockLog(nil, common.HexToHash("0x0")) abiString := `[{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"received","type":"event"}]` @@ -204,6 +227,7 @@ func TestUnpackAnonymousLogIntoMap(t *testing.T) { } func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) { + t.Parallel() sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"}) if err != nil { t.Fatal(err) @@ -229,6 +253,7 @@ func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) { } func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) { + t.Parallel() arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")}) if err != nil { t.Fatal(err) @@ -254,6 +279,7 @@ func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) { } func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) { + t.Parallel() mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2") addrBytes := mockAddress.Bytes() hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)")) @@ -280,6 +306,7 @@ func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) { } func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) { + t.Parallel() bytes := []byte{1, 2, 3, 4, 5} hash := crypto.Keccak256Hash(bytes) topics := []common.Hash{ @@ -302,6 +329,7 @@ func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) { } func TestTransactGasFee(t *testing.T) { + t.Parallel() assert := assert.New(t) // GasTipCap and GasFeeCap @@ -377,6 +405,7 @@ func newMockLog(topics []common.Hash, txHash common.Hash) types.Log { } func TestCall(t *testing.T) { + t.Parallel() var method, methodWithArg = "something", "somethingArrrrg" tests := []struct { name, method string @@ -400,6 +429,15 @@ func TestCall(t *testing.T) { Pending: true, }, method: method, + }, { + name: "ok hash", + mc: &mockBlockHashCaller{ + codeAtHashBytes: []byte{0}, + }, + opts: &bind.CallOpts{ + BlockHash: common.Hash{0xaa}, + }, + method: method, }, { name: "pack error, no method", mc: new(mockCaller), @@ -413,6 +451,14 @@ func TestCall(t *testing.T) { }, method: method, wantErrExact: bind.ErrNoPendingState, + }, { + name: "interface error, blockHash but not a BlockHashContractCaller", + mc: new(mockCaller), + opts: &bind.CallOpts{ + BlockHash: common.Hash{0xaa}, + }, + method: method, + wantErrExact: bind.ErrNoBlockHashState, }, { name: "pending call canceled", mc: &mockPendingCaller{ @@ -460,6 +506,34 @@ func TestCall(t *testing.T) { mc: new(mockCaller), method: method, wantErrExact: bind.ErrNoCode, + }, { + name: "call contract at hash error", + mc: &mockBlockHashCaller{ + callContractAtHashErr: context.DeadlineExceeded, + }, + opts: &bind.CallOpts{ + BlockHash: common.Hash{0xaa}, + }, + method: method, + wantErrExact: context.DeadlineExceeded, + }, { + name: "code at error", + mc: &mockBlockHashCaller{ + codeAtHashErr: errors.New(""), + }, + opts: &bind.CallOpts{ + BlockHash: common.Hash{0xaa}, + }, + method: method, + wantErr: true, + }, { + name: "no code at hash", + mc: new(mockBlockHashCaller), + opts: &bind.CallOpts{ + BlockHash: common.Hash{0xaa}, + }, + method: method, + wantErrExact: bind.ErrNoCode, }, { name: "unpack error missing arg", mc: &mockCaller{ @@ -507,6 +581,7 @@ func TestCall(t *testing.T) { // TestCrashers contains some strings which previously caused the abi codec to crash. func TestCrashers(t *testing.T) { + t.Parallel() abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"_1"}]}]}]`)) abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"&"}]}]}]`)) abi.JSON(strings.NewReader(`[{"inputs":[{"type":"tuple[]","components":[{"type":"bool","name":"----"}]}]}]`)) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 8a54a0e6e..e902345f0 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -79,7 +79,7 @@ func isKeyWord(arg string) bool { // Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant // to be used as is in client code, but rather as an intermediate struct which -// enforces compile time type safety and naming convention opposed to having to +// enforces compile time type safety and naming convention as opposed to having to // manually maintain hard coded strings that break on runtime. func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) { var ( @@ -363,7 +363,7 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { // parameters that are not value types i.e. arrays and structs are not // stored directly but instead a keccak256-hash of an encoding is stored. // - // We only convert stringS and bytes to hash, still need to deal with + // We only convert strings and bytes to hash, still need to deal with // array(both fixed-size and dynamic-size) and struct. if bound == "string" || bound == "[]byte" { bound = "common.Hash" diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 6e6dfd152..e96d2ddc6 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -305,6 +305,7 @@ var bindTests = []struct { if err != nil { t.Fatalf("Failed to deploy interactor contract: %v", err) } + sim.Commit() if _, err := interactor.Transact(auth, "Transact string"); err != nil { t.Fatalf("Failed to transact with interactor contract: %v", err) } @@ -512,6 +513,7 @@ var bindTests = []struct { if err != nil { t.Fatalf("Failed to deploy defaulter contract: %v", err) } + sim.Commit() if _, err := (&DefaulterRaw{defaulter}).Transfer(auth); err != nil { t.Fatalf("Failed to invoke default method: %v", err) } @@ -1677,7 +1679,7 @@ var bindTests = []struct { } sim.Commit() - // This test the existence of the free retreiver call for view and pure functions + // This test the existence of the free retriever call for view and pure functions if num, err := pav.PureFunc(nil); err != nil { t.Fatalf("Failed to call anonymous field retriever: %v", err) } else if num.Cmp(big.NewInt(42)) != 0 { @@ -1874,6 +1876,7 @@ var bindTests = []struct { []string{"0x6080604052348015600f57600080fd5b5060998061001e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063726c638214602d575b600080fd5b60336035565b005b60405163024876cd60e61b815260016004820152600260248201526003604482015260640160405180910390fdfea264697066735822122093f786a1bc60216540cd999fbb4a6109e0fef20abcff6e9107fb2817ca968f3c64736f6c63430008070033"}, []string{`[{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError1","type":"error"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"MyError2","type":"error"},{"inputs":[{"internalType":"uint256","name":"a","type":"uint256"},{"internalType":"uint256","name":"b","type":"uint256"},{"internalType":"uint256","name":"c","type":"uint256"}],"name":"MyError3","type":"error"},{"inputs":[],"name":"Error","outputs":[],"stateMutability":"pure","type":"function"}]`}, ` + "context" "math/big" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -1895,7 +1898,7 @@ var bindTests = []struct { t.Fatal(err) } sim.Commit() - _, err = bind.WaitDeployed(nil, sim, tx) + _, err = bind.WaitDeployed(context.Background(), sim, tx) if err != nil { t.Error(err) } @@ -1926,6 +1929,7 @@ var bindTests = []struct { bytecode: []string{`0x608060405234801561001057600080fd5b506040516101c43803806101c48339818101604052810190610032919061014a565b50610177565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100958261004c565b810181811067ffffffffffffffff821117156100b4576100b361005d565b5b80604052505050565b60006100c7610038565b90506100d3828261008c565b919050565b6000819050919050565b6100eb816100d8565b81146100f657600080fd5b50565b600081519050610108816100e2565b92915050565b60006020828403121561012457610123610047565b5b61012e60206100bd565b9050600061013e848285016100f9565b60008301525092915050565b6000602082840312156101605761015f610042565b5b600061016e8482850161010e565b91505092915050565b603f806101856000396000f3fe6080604052600080fdfea2646970667358221220cdffa667affecefac5561f65f4a4ba914204a8d4eb859d8cd426fb306e5c12a364736f6c634300080a0033`}, abi: []string{`[{"inputs":[{"components":[{"internalType":"uint256","name":"field","type":"uint256"}],"internalType":"struct ConstructorWithStructParam.StructType","name":"st","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"}]`}, imports: ` + "context" "math/big" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -1948,7 +1952,7 @@ var bindTests = []struct { } sim.Commit() - if _, err = bind.WaitDeployed(nil, sim, tx); err != nil { + if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil { t.Logf("Deployment tx: %+v", tx) t.Errorf("bind.WaitDeployed(nil, %T, ) got err %v; want nil err", sim, err) } @@ -1974,6 +1978,7 @@ var bindTests = []struct { bytecode: []string{"0x608060405234801561001057600080fd5b5061042b806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063c2bb515f1461003b578063cce7b04814610059575b600080fd5b610043610075565b60405161005091906101af565b60405180910390f35b610073600480360381019061006e91906103ac565b6100b5565b005b61007d6100b8565b604051806040016040528060405180602001604052806000815250815260200160405180602001604052806000815250815250905090565b50565b604051806040016040528060608152602001606081525090565b600081519050919050565b600082825260208201905092915050565b60005b8381101561010c5780820151818401526020810190506100f1565b8381111561011b576000848401525b50505050565b6000601f19601f8301169050919050565b600061013d826100d2565b61014781856100dd565b93506101578185602086016100ee565b61016081610121565b840191505092915050565b600060408301600083015184820360008601526101888282610132565b915050602083015184820360208601526101a28282610132565b9150508091505092915050565b600060208201905081810360008301526101c9818461016b565b905092915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61022282610121565b810181811067ffffffffffffffff82111715610241576102406101ea565b5b80604052505050565b60006102546101d1565b90506102608282610219565b919050565b600080fd5b600080fd5b600080fd5b600067ffffffffffffffff82111561028f5761028e6101ea565b5b61029882610121565b9050602081019050919050565b82818337600083830152505050565b60006102c76102c284610274565b61024a565b9050828152602081018484840111156102e3576102e261026f565b5b6102ee8482856102a5565b509392505050565b600082601f83011261030b5761030a61026a565b5b813561031b8482602086016102b4565b91505092915050565b60006040828403121561033a576103396101e5565b5b610344604061024a565b9050600082013567ffffffffffffffff81111561036457610363610265565b5b610370848285016102f6565b600083015250602082013567ffffffffffffffff81111561039457610393610265565b5b6103a0848285016102f6565b60208301525092915050565b6000602082840312156103c2576103c16101db565b5b600082013567ffffffffffffffff8111156103e0576103df6101e0565b5b6103ec84828501610324565b9150509291505056fea264697066735822122033bca1606af9b6aeba1673f98c52003cec19338539fb44b86690ce82c51483b564736f6c634300080e0033"}, abi: []string{`[ { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "int256", "name": "msg", "type": "int256" }, { "indexed": false, "internalType": "int256", "name": "_msg", "type": "int256" } ], "name": "log", "type": "event" }, { "inputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "req", "type": "tuple" } ], "name": "addRequest", "outputs": [], "stateMutability": "pure", "type": "function" }, { "inputs": [], "name": "getRequest", "outputs": [ { "components": [ { "internalType": "bytes", "name": "data", "type": "bytes" }, { "internalType": "bytes", "name": "_data", "type": "bytes" } ], "internalType": "struct oracle.request", "name": "", "type": "tuple" } ], "stateMutability": "pure", "type": "function" } ]`}, imports: ` + "context" "math/big" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -1996,7 +2001,7 @@ var bindTests = []struct { } sim.Commit() - if _, err = bind.WaitDeployed(nil, sim, tx); err != nil { + if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil { t.Logf("Deployment tx: %+v", tx) t.Errorf("bind.WaitDeployed(nil, %T, ) got err %v; want nil err", sim, err) } @@ -2014,6 +2019,7 @@ var bindTests = []struct { bytecode: []string{"0x608060405234801561001057600080fd5b5060dc8061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063527a119f14602d575b600080fd5b60436004803603810190603f9190605b565b6045565b005b50565b6000813590506055816092565b92915050565b600060208284031215606e57606d608d565b5b6000607a848285016048565b91505092915050565b6000819050919050565b600080fd5b6099816083565b811460a357600080fd5b5056fea2646970667358221220d4f4525e2615516394055d369fb17df41c359e5e962734f27fd683ea81fd9db164736f6c63430008070033"}, abi: []string{`[{"inputs":[{"internalType":"uint256","name":"range","type":"uint256"}],"name":"functionWithKeywordParameter","outputs":[],"stateMutability":"pure","type":"function"}]`}, imports: ` + "context" "math/big" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -2034,7 +2040,7 @@ var bindTests = []struct { } sim.Commit() - if _, err = bind.WaitDeployed(nil, sim, tx); err != nil { + if _, err = bind.WaitDeployed(context.Background(), sim, tx); err != nil { t.Errorf("error deploying the contract: %v", err) } `, @@ -2067,6 +2073,7 @@ var bindTests = []struct { // Tests that packages generated by the binder can be successfully compiled and // the requested tester run against it. func TestGolangBindings(t *testing.T) { + t.Parallel() // Skip the test if no Go command can be found gocmd := runtime.GOROOT() + "/bin/go" if !common.FileExist(gocmd) { diff --git a/accounts/abi/bind/util_test.go b/accounts/abi/bind/util_test.go index 75fbc91ce..244eeebdd 100644 --- a/accounts/abi/bind/util_test.go +++ b/accounts/abi/bind/util_test.go @@ -24,11 +24,12 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "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/ethclient/simulated" + "github.com/ethereum/go-ethereum/params" ) var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") @@ -53,8 +54,9 @@ var waitDeployedTests = map[string]struct { } func TestWaitDeployed(t *testing.T) { + t.Parallel() for name, test := range waitDeployedTests { - backend := backends.NewSimulatedBackend( + backend := simulated.New( core.GenesisAlloc{ crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)}, }, @@ -63,11 +65,11 @@ func TestWaitDeployed(t *testing.T) { defer backend.Close() // Create the transaction - head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough + head, _ := backend.Client().HeaderByNumber(context.Background(), nil) // Should be child's, good enough gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) tx := types.NewContractCreation(0, big.NewInt(0), test.gas, gasPrice, common.FromHex(test.code)) - tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + tx, _ = types.SignTx(tx, types.LatestSignerForChainID(big.NewInt(1337)), testKey) // Wait for it to get mined in the background. var ( @@ -77,12 +79,12 @@ func TestWaitDeployed(t *testing.T) { ctx = context.Background() ) go func() { - address, err = bind.WaitDeployed(ctx, backend, tx) + address, err = bind.WaitDeployed(ctx, backend.Client(), tx) close(mined) }() // Send and mine the transaction. - backend.SendTransaction(ctx, tx) + backend.Client().SendTransaction(ctx, tx) backend.Commit() select { @@ -100,7 +102,7 @@ func TestWaitDeployed(t *testing.T) { } func TestWaitDeployedCornerCases(t *testing.T) { - backend := backends.NewSimulatedBackend( + backend := simulated.New( core.GenesisAlloc{ crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000000000)}, }, @@ -108,33 +110,33 @@ func TestWaitDeployedCornerCases(t *testing.T) { ) defer backend.Close() - head, _ := backend.HeaderByNumber(context.Background(), nil) // Should be child's, good enough + head, _ := backend.Client().HeaderByNumber(context.Background(), nil) // Should be child's, good enough gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) // Create a transaction to an account. code := "6060604052600a8060106000396000f360606040526008565b00" tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, gasPrice, common.FromHex(code)) - tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - backend.SendTransaction(ctx, tx) + backend.Client().SendTransaction(ctx, tx) backend.Commit() - notContentCreation := errors.New("tx is not contract creation") - if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != notContentCreation.Error() { - t.Errorf("error missmatch: want %q, got %q, ", notContentCreation, err) + notContractCreation := errors.New("tx is not contract creation") + if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != notContractCreation.Error() { + t.Errorf("error mismatch: want %q, got %q, ", notContractCreation, err) } // Create a transaction that is not mined. tx = types.NewContractCreation(1, big.NewInt(0), 3000000, gasPrice, common.FromHex(code)) - tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey) + tx, _ = types.SignTx(tx, types.LatestSigner(params.AllDevChainProtocolChanges), testKey) go func() { contextCanceled := errors.New("context canceled") - if _, err := bind.WaitDeployed(ctx, backend, tx); err.Error() != contextCanceled.Error() { - t.Errorf("error missmatch: want %q, got %q, ", contextCanceled, err) + if _, err := bind.WaitDeployed(ctx, backend.Client(), tx); err.Error() != contextCanceled.Error() { + t.Errorf("error mismatch: want %q, got %q, ", contextCanceled, err) } }() - backend.SendTransaction(ctx, tx) + backend.Client().SendTransaction(ctx, tx) cancel() } diff --git a/accounts/abi/error.go b/accounts/abi/error.go index 218a22f1e..8e50112ec 100644 --- a/accounts/abi/error.go +++ b/accounts/abi/error.go @@ -18,7 +18,6 @@ package abi import ( "bytes" - "errors" "fmt" "strings" @@ -84,10 +83,10 @@ func (e Error) String() string { func (e *Error) Unpack(data []byte) (interface{}, error) { if len(data) < 4 { - return "", errors.New("invalid data for unpacking") + return "", fmt.Errorf("insufficient data for unpacking: have %d, want at least 4", len(data)) } if !bytes.Equal(data[:4], e.ID[:4]) { - return "", errors.New("invalid data for unpacking") + return "", fmt.Errorf("invalid identifier, have %#x want %#x", data[:4], e.ID[:4]) } return e.Inputs.Unpack(data[4:]) } diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go index 8f7341949..fffe28ea6 100644 --- a/accounts/abi/event_test.go +++ b/accounts/abi/event_test.go @@ -81,6 +81,7 @@ var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241" func TestEventId(t *testing.T) { + t.Parallel() var table = []struct { definition string expectations map[string]common.Hash @@ -112,6 +113,7 @@ func TestEventId(t *testing.T) { } func TestEventString(t *testing.T) { + t.Parallel() var table = []struct { definition string expectations map[string]string @@ -146,6 +148,7 @@ func TestEventString(t *testing.T) { // TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array. func TestEventMultiValueWithArrayUnpack(t *testing.T) { + t.Parallel() definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` abi, err := JSON(strings.NewReader(definition)) require.NoError(t, err) @@ -161,6 +164,7 @@ func TestEventMultiValueWithArrayUnpack(t *testing.T) { } func TestEventTupleUnpack(t *testing.T) { + t.Parallel() type EventTransfer struct { Value *big.Int } @@ -351,6 +355,7 @@ func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, ass // TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder. func TestEventUnpackIndexed(t *testing.T) { + t.Parallel() definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]` type testStruct struct { Value1 uint8 // indexed @@ -368,6 +373,7 @@ func TestEventUnpackIndexed(t *testing.T) { // TestEventIndexedWithArrayUnpack verifies that decoder will not overflow when static array is indexed input. func TestEventIndexedWithArrayUnpack(t *testing.T) { + t.Parallel() definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]` type testStruct struct { Value1 [2]uint8 // indexed diff --git a/accounts/abi/method.go b/accounts/abi/method.go index f69e3ee9b..c5a1a71f4 100644 --- a/accounts/abi/method.go +++ b/accounts/abi/method.go @@ -117,24 +117,23 @@ func NewMethod(name string, rawName string, funType FunctionType, mutability str sig = fmt.Sprintf("%v(%v)", rawName, strings.Join(types, ",")) id = crypto.Keccak256([]byte(sig))[:4] } - // Extract meaningful state mutability of solidity method. - // If it's default value, never print it. - state := mutability - if state == "nonpayable" { - state = "" - } - if state != "" { - 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, ", ")) + var str string + // Extract meaningful state mutability of solidity method. + // If it's empty string or default value "nonpayable", never print it. + if mutability == "" || mutability == "nonpayable" { + str = fmt.Sprintf("%v(%v) returns(%v)", identity, strings.Join(inputNames, ", "), strings.Join(outputNames, ", ")) + } else { + str = fmt.Sprintf("%v(%v) %s returns(%v)", identity, strings.Join(inputNames, ", "), mutability, strings.Join(outputNames, ", ")) + } return Method{ Name: name, diff --git a/accounts/abi/method_test.go b/accounts/abi/method_test.go index 395a52896..632217392 100644 --- a/accounts/abi/method_test.go +++ b/accounts/abi/method_test.go @@ -35,6 +35,7 @@ const methoddata = ` ]` func TestMethodString(t *testing.T) { + t.Parallel() var table = []struct { method string expectation string @@ -84,11 +85,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 { @@ -98,6 +100,7 @@ func TestMethodString(t *testing.T) { } func TestMethodSig(t *testing.T) { + t.Parallel() var cases = []struct { method string expect string diff --git a/accounts/abi/pack.go b/accounts/abi/pack.go index 0cd91cb4f..beef1fa37 100644 --- a/accounts/abi/pack.go +++ b/accounts/abi/pack.go @@ -57,7 +57,7 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) { reflectValue = mustArrayToByteSlice(reflectValue) } if reflectValue.Type() != reflect.TypeOf([]byte{}) { - return []byte{}, errors.New("Bytes type is neither slice nor array") + return []byte{}, errors.New("bytes type is neither slice nor array") } return packBytesSlice(reflectValue.Bytes(), reflectValue.Len()), nil case FixedBytesTy, FunctionTy: @@ -66,7 +66,7 @@ func packElement(t Type, reflectValue reflect.Value) ([]byte, error) { } return common.RightPadBytes(reflectValue.Bytes(), 32), nil default: - return []byte{}, fmt.Errorf("Could not pack element, unknown type: %v", t.T) + return []byte{}, fmt.Errorf("could not pack element, unknown type: %v", t.T) } } diff --git a/accounts/abi/pack_test.go b/accounts/abi/pack_test.go index 5c7cb1cc1..00bdae469 100644 --- a/accounts/abi/pack_test.go +++ b/accounts/abi/pack_test.go @@ -32,8 +32,11 @@ import ( // TestPack tests the general pack/unpack tests in packing_test.go func TestPack(t *testing.T) { + t.Parallel() for i, test := range packUnpackTests { + i, test := i, test t.Run(strconv.Itoa(i), func(t *testing.T) { + t.Parallel() encb, err := hex.DecodeString(test.packed) if err != nil { t.Fatalf("invalid hex %s: %v", test.packed, err) @@ -57,6 +60,7 @@ func TestPack(t *testing.T) { } func TestMethodPack(t *testing.T) { + t.Parallel() abi, err := JSON(strings.NewReader(jsondata)) if err != nil { t.Fatal(err) @@ -177,6 +181,7 @@ func TestMethodPack(t *testing.T) { } func TestPackNumber(t *testing.T) { + t.Parallel() tests := []struct { value reflect.Value packed []byte diff --git a/accounts/abi/reflect.go b/accounts/abi/reflect.go index 48d2ef41e..1863e5bb7 100644 --- a/accounts/abi/reflect.go +++ b/accounts/abi/reflect.go @@ -134,7 +134,7 @@ func setSlice(dst, src reflect.Value) error { dst.Set(slice) return nil } - return errors.New("Cannot set slice, destination not settable") + return errors.New("cannot set slice, destination not settable") } func setArray(dst, src reflect.Value) error { @@ -155,7 +155,7 @@ func setArray(dst, src reflect.Value) error { dst.Set(array) return nil } - return errors.New("Cannot set array, destination not settable") + return errors.New("cannot set array, destination not settable") } func setStruct(dst, src reflect.Value) error { @@ -163,7 +163,7 @@ func setStruct(dst, src reflect.Value) error { srcField := src.Field(i) dstField := dst.Field(i) if !dstField.IsValid() || !srcField.IsValid() { - return fmt.Errorf("Could not find src field: %v value: %v in destination", srcField.Type().Name(), srcField) + return fmt.Errorf("could not find src field: %v value: %v in destination", srcField.Type().Name(), srcField) } if err := set(dstField, srcField); err != nil { return err diff --git a/accounts/abi/reflect_test.go b/accounts/abi/reflect_test.go index 76ef1ad2a..6c7ae5708 100644 --- a/accounts/abi/reflect_test.go +++ b/accounts/abi/reflect_test.go @@ -170,8 +170,11 @@ var reflectTests = []reflectTest{ } func TestReflectNameToStruct(t *testing.T) { + t.Parallel() for _, test := range reflectTests { + test := test t.Run(test.name, func(t *testing.T) { + t.Parallel() m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc)) if len(test.err) > 0 { if err == nil || err.Error() != test.err { @@ -192,6 +195,7 @@ func TestReflectNameToStruct(t *testing.T) { } func TestConvertType(t *testing.T) { + t.Parallel() // Test Basic Struct type T struct { X *big.Int diff --git a/accounts/abi/selector_parser_test.go b/accounts/abi/selector_parser_test.go index f6f134492..6cb0ae0e7 100644 --- a/accounts/abi/selector_parser_test.go +++ b/accounts/abi/selector_parser_test.go @@ -24,6 +24,7 @@ import ( ) func TestParseSelector(t *testing.T) { + t.Parallel() mkType := func(types ...interface{}) []ArgumentMarshaling { var result []ArgumentMarshaling for i, typeOrComponents := range types { diff --git a/accounts/abi/topics.go b/accounts/abi/topics.go index 360df7d5e..7ce9b7273 100644 --- a/accounts/abi/topics.go +++ b/accounts/abi/topics.go @@ -24,6 +24,7 @@ import ( "reflect" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/crypto" ) @@ -41,8 +42,7 @@ func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) { case common.Address: copy(topic[common.HashLength-common.AddressLength:], rule[:]) case *big.Int: - blob := rule.Bytes() - copy(topic[common.HashLength-len(blob):], blob) + copy(topic[:], math.U256Bytes(rule)) case bool: if rule { topic[common.HashLength-1] = 1 @@ -75,7 +75,7 @@ func MakeTopics(query ...[]interface{}) ([][]common.Hash, error) { copy(topic[:], hash[:]) default: - // todo(rjl493456442) according solidity documentation, indexed event + // todo(rjl493456442) according to solidity documentation, indexed event // parameters that are not value types i.e. arrays and structs are not // stored directly but instead a keccak256-hash of an encoding is stored. // diff --git a/accounts/abi/topics_test.go b/accounts/abi/topics_test.go index 30cf21d0b..9e1efd382 100644 --- a/accounts/abi/topics_test.go +++ b/accounts/abi/topics_test.go @@ -17,6 +17,7 @@ package abi import ( + "math" "math/big" "reflect" "testing" @@ -26,6 +27,7 @@ import ( ) func TestMakeTopics(t *testing.T) { + t.Parallel() type args struct { query [][]interface{} } @@ -54,9 +56,27 @@ func TestMakeTopics(t *testing.T) { false, }, { - "support *big.Int types in topics", - args{[][]interface{}{{big.NewInt(1).Lsh(big.NewInt(2), 254)}}}, - [][]common.Hash{{common.Hash{128}}}, + "support positive *big.Int types in topics", + args{[][]interface{}{ + {big.NewInt(1)}, + {big.NewInt(1).Lsh(big.NewInt(2), 254)}, + }}, + [][]common.Hash{ + {common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001")}, + {common.Hash{128}}, + }, + false, + }, + { + "support negative *big.Int types in topics", + args{[][]interface{}{ + {big.NewInt(-1)}, + {big.NewInt(math.MinInt64)}, + }}, + [][]common.Hash{ + {common.MaxHash}, + {common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffff8000000000000000")}, + }, false, }, { @@ -117,7 +137,9 @@ func TestMakeTopics(t *testing.T) { }, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() got, err := MakeTopics(tt.args.query...) if (err != nil) != tt.wantErr { t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr) @@ -347,10 +369,13 @@ func setupTopicsTests() []topicTest { } func TestParseTopics(t *testing.T) { + t.Parallel() tests := setupTopicsTests() for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() createObj := tt.args.createObj() if err := ParseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr { t.Errorf("parseTopics() error = %v, wantErr %v", err, tt.wantErr) @@ -364,10 +389,13 @@ func TestParseTopics(t *testing.T) { } func TestParseTopicsIntoMap(t *testing.T) { + t.Parallel() tests := setupTopicsTests() for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() outMap := make(map[string]interface{}) if err := ParseTopicsIntoMap(outMap, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr { t.Errorf("parseTopicsIntoMap() error = %v, wantErr %v", err, tt.wantErr) diff --git a/accounts/abi/type_test.go b/accounts/abi/type_test.go index a72531ba2..ae69872ad 100644 --- a/accounts/abi/type_test.go +++ b/accounts/abi/type_test.go @@ -31,6 +31,7 @@ type typeWithoutStringer Type // Tests that all allowed types get recognized by the type parser. func TestTypeRegexp(t *testing.T) { + t.Parallel() tests := []struct { blob string components []ArgumentMarshaling @@ -117,6 +118,7 @@ func TestTypeRegexp(t *testing.T) { } func TestTypeCheck(t *testing.T) { + t.Parallel() for i, test := range []struct { typ string components []ArgumentMarshaling @@ -308,6 +310,7 @@ func TestTypeCheck(t *testing.T) { } func TestInternalType(t *testing.T) { + t.Parallel() components := []ArgumentMarshaling{{Name: "a", Type: "int64"}} internalType := "struct a.b[]" kind := Type{ @@ -332,6 +335,7 @@ func TestInternalType(t *testing.T) { } func TestGetTypeSize(t *testing.T) { + t.Parallel() var testCases = []struct { typ string components []ArgumentMarshaling @@ -368,6 +372,7 @@ func TestGetTypeSize(t *testing.T) { } func TestNewFixedBytesOver32(t *testing.T) { + t.Parallel() _, err := NewType("bytes4096", "", nil) if err == nil { t.Errorf("fixed bytes with size over 32 is not spec'd") diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 68451483c..905b5ce62 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -160,13 +160,14 @@ 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 { + default: return nil, errors.New("abi: invalid type in array/slice unpacking stage") } diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index 6dd2db0d5..29891ec0a 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -33,6 +33,7 @@ import ( // TestUnpack tests the general pack/unpack tests in packing_test.go func TestUnpack(t *testing.T) { + t.Parallel() for i, test := range packUnpackTests { t.Run(strconv.Itoa(i)+" "+test.def, func(t *testing.T) { //Unpack @@ -206,13 +207,13 @@ var unpackTests = []unpackTest{ def: `[{"type":"bool"}]`, enc: "", want: false, - err: "abi: attempting to unmarshall an empty string while arguments are expected", + err: "abi: attempting to unmarshal an empty string while arguments are expected", }, { def: `[{"type":"bytes32","indexed":true},{"type":"uint256","indexed":false}]`, enc: "", want: false, - err: "abi: attempting to unmarshall an empty string while arguments are expected", + err: "abi: attempting to unmarshal an empty string while arguments are expected", }, { def: `[{"type":"bool","indexed":true},{"type":"uint64","indexed":true}]`, @@ -224,6 +225,7 @@ var unpackTests = []unpackTest{ // TestLocalUnpackTests runs test specially designed only for unpacking. // All test cases that can be used to test packing and unpacking should move to packing_test.go func TestLocalUnpackTests(t *testing.T) { + t.Parallel() for i, test := range unpackTests { t.Run(strconv.Itoa(i), func(t *testing.T) { //Unpack @@ -251,6 +253,7 @@ func TestLocalUnpackTests(t *testing.T) { } func TestUnpackIntoInterfaceSetDynamicArrayOutput(t *testing.T) { + t.Parallel() abi, err := JSON(strings.NewReader(`[{"constant":true,"inputs":[],"name":"testDynamicFixedBytes15","outputs":[{"name":"","type":"bytes15[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"testDynamicFixedBytes32","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"}]`)) if err != nil { t.Fatal(err) @@ -321,6 +324,7 @@ func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOut } func TestMethodMultiReturn(t *testing.T) { + t.Parallel() type reversed struct { String string Int *big.Int @@ -400,6 +404,7 @@ func TestMethodMultiReturn(t *testing.T) { } func TestMultiReturnWithArray(t *testing.T) { + t.Parallel() const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]` abi, err := JSON(strings.NewReader(definition)) if err != nil { @@ -423,6 +428,7 @@ func TestMultiReturnWithArray(t *testing.T) { } func TestMultiReturnWithStringArray(t *testing.T) { + t.Parallel() const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]` abi, err := JSON(strings.NewReader(definition)) if err != nil { @@ -453,6 +459,7 @@ func TestMultiReturnWithStringArray(t *testing.T) { } func TestMultiReturnWithStringSlice(t *testing.T) { + t.Parallel() const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]` abi, err := JSON(strings.NewReader(definition)) if err != nil { @@ -485,6 +492,7 @@ func TestMultiReturnWithStringSlice(t *testing.T) { } func TestMultiReturnWithDeeplyNestedArray(t *testing.T) { + t.Parallel() // Similar to TestMultiReturnWithArray, but with a special case in mind: // values of nested static arrays count towards the size as well, and any element following // after such nested array argument should be read with the correct offset, @@ -525,6 +533,7 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) { } func TestUnmarshal(t *testing.T) { + t.Parallel() const definition = `[ { "name" : "int", "type": "function", "outputs": [ { "type": "uint256" } ] }, { "name" : "bool", "type": "function", "outputs": [ { "type": "bool" } ] }, @@ -774,6 +783,7 @@ func TestUnmarshal(t *testing.T) { } func TestUnpackTuple(t *testing.T) { + t.Parallel() const simpleTuple = `[{"name":"tuple","type":"function","outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]` abi, err := JSON(strings.NewReader(simpleTuple)) if err != nil { @@ -876,6 +886,7 @@ func TestUnpackTuple(t *testing.T) { } func TestOOMMaliciousInput(t *testing.T) { + t.Parallel() oomTests := []unpackTest{ { def: `[{"type": "uint8[]"}]`, @@ -946,6 +957,7 @@ func TestOOMMaliciousInput(t *testing.T) { } func TestPackAndUnpackIncompatibleNumber(t *testing.T) { + t.Parallel() var encodeABI Arguments uint256Ty, err := NewType("uint256", "", nil) if err != nil { diff --git a/accounts/accounts_test.go b/accounts/accounts_test.go index e8274f9f0..2c4138aa7 100644 --- a/accounts/accounts_test.go +++ b/accounts/accounts_test.go @@ -24,6 +24,7 @@ import ( ) func TestTextHash(t *testing.T) { + t.Parallel() hash := TextHash([]byte("Hello Joe")) want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b") if !bytes.Equal(hash, want) { diff --git a/accounts/hd_test.go b/accounts/hd_test.go index 0743bbe66..118ec5187 100644 --- a/accounts/hd_test.go +++ b/accounts/hd_test.go @@ -25,6 +25,7 @@ import ( // Tests that HD derivation paths can be correctly parsed into our internal binary // representation. func TestHDPathParsing(t *testing.T) { + t.Parallel() tests := []struct { input string output DerivationPath @@ -89,6 +90,7 @@ func testDerive(t *testing.T, next func() DerivationPath, expected []string) { } func TestHdPathIteration(t *testing.T) { + t.Parallel() testDerive(t, DefaultIterator(DefaultBaseDerivationPath), []string{ "m/44'/60'/0'/0/0", "m/44'/60'/0'/0/1", diff --git a/accounts/keystore/account_cache_test.go b/accounts/keystore/account_cache_test.go index 3847e9daf..48a238048 100644 --- a/accounts/keystore/account_cache_test.go +++ b/accounts/keystore/account_cache_test.go @@ -68,7 +68,7 @@ func waitWatcherStart(ks *KeyStore) bool { func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error { var list []accounts.Account - for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(200 * time.Millisecond) { + for t0 := time.Now(); time.Since(t0) < 5*time.Second; time.Sleep(100 * time.Millisecond) { list = ks.Accounts() if reflect.DeepEqual(list, wantAccounts) { // ks should have also received change notifications @@ -152,6 +152,7 @@ func TestWatchNoDir(t *testing.T) { } func TestCacheInitialReload(t *testing.T) { + t.Parallel() cache, _ := newAccountCache(cachetestDir) accounts := cache.accounts() if !reflect.DeepEqual(accounts, cachetestAccounts) { @@ -160,6 +161,7 @@ func TestCacheInitialReload(t *testing.T) { } func TestCacheAddDeleteOrder(t *testing.T) { + t.Parallel() cache, _ := newAccountCache("testdata/no-such-dir") cache.watcher.running = true // prevent unexpected reloads @@ -244,6 +246,7 @@ func TestCacheAddDeleteOrder(t *testing.T) { } func TestCacheFind(t *testing.T) { + t.Parallel() dir := filepath.Join("testdata", "dir") cache, _ := newAccountCache(dir) cache.watcher.running = true // prevent unexpected reloads @@ -350,7 +353,7 @@ func TestUpdatedKeyfileContents(t *testing.T) { return } // needed so that modTime of `file` is different to its current value after forceCopyFile - time.Sleep(time.Second) + os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second)) // Now replace file contents if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil { @@ -366,7 +369,7 @@ func TestUpdatedKeyfileContents(t *testing.T) { } // needed so that modTime of `file` is different to its current value after forceCopyFile - time.Sleep(time.Second) + os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second)) // Now replace file contents again if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil { @@ -382,7 +385,7 @@ func TestUpdatedKeyfileContents(t *testing.T) { } // needed so that modTime of `file` is different to its current value after os.WriteFile - time.Sleep(time.Second) + os.Chtimes(file, time.Now().Add(-time.Second), time.Now().Add(-time.Second)) // Now replace file contents with crap if err := os.WriteFile(file, []byte("foo"), 0600); err != nil { diff --git a/tests/fuzzers/keystore/keystore-fuzzer.go b/accounts/keystore/keystore_fuzzing_test.go similarity index 66% rename from tests/fuzzers/keystore/keystore-fuzzer.go rename to accounts/keystore/keystore_fuzzing_test.go index e3bcae92e..793b46336 100644 --- a/tests/fuzzers/keystore/keystore-fuzzer.go +++ b/accounts/keystore/keystore_fuzzing_test.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 @@ -17,21 +17,18 @@ package keystore import ( - "os" - - "github.com/ethereum/go-ethereum/accounts/keystore" + "testing" ) -func Fuzz(input []byte) int { - ks := keystore.NewKeyStore("/tmp/ks", keystore.LightScryptN, keystore.LightScryptP) - - a, err := ks.NewAccount(string(input)) - if err != nil { - panic(err) - } - if err := ks.Unlock(a, string(input)); err != nil { - panic(err) - } - os.Remove(a.URL.Path) - return 1 +func FuzzPassword(f *testing.F) { + f.Fuzz(func(t *testing.T, password string) { + ks := NewKeyStore(t.TempDir(), LightScryptN, LightScryptP) + a, err := ks.NewAccount(password) + if err != nil { + t.Fatal(err) + } + if err := ks.Unlock(a, password); err != nil { + t.Fatal(err) + } + }) } diff --git a/accounts/keystore/keystore_test.go b/accounts/keystore/keystore_test.go index deb7cae9f..c9a23eddd 100644 --- a/accounts/keystore/keystore_test.go +++ b/accounts/keystore/keystore_test.go @@ -36,6 +36,7 @@ import ( var testSigData = make([]byte, 32) func TestKeyStore(t *testing.T) { + t.Parallel() dir, ks := tmpKeyStore(t, true) a, err := ks.NewAccount("foo") @@ -70,6 +71,7 @@ func TestKeyStore(t *testing.T) { } func TestSign(t *testing.T) { + t.Parallel() _, ks := tmpKeyStore(t, true) pass := "" // not used but required by API @@ -86,6 +88,7 @@ func TestSign(t *testing.T) { } func TestSignWithPassphrase(t *testing.T) { + t.Parallel() _, ks := tmpKeyStore(t, true) pass := "passwd" @@ -280,6 +283,7 @@ type walletEvent struct { // Tests that wallet notifications and correctly fired when accounts are added // or deleted from the keystore. func TestWalletNotifications(t *testing.T) { + t.Parallel() _, ks := tmpKeyStore(t, false) // Subscribe to the wallet feed and collect events. @@ -341,6 +345,7 @@ func TestWalletNotifications(t *testing.T) { // TestImportExport tests the import functionality of a keystore. func TestImportECDSA(t *testing.T) { + t.Parallel() _, ks := tmpKeyStore(t, true) key, err := crypto.GenerateKey() if err != nil { @@ -359,6 +364,7 @@ func TestImportECDSA(t *testing.T) { // TestImportECDSA tests the import and export functionality of a keystore. func TestImportExport(t *testing.T) { + t.Parallel() _, ks := tmpKeyStore(t, true) acc, err := ks.NewAccount("old") if err != nil { @@ -387,6 +393,7 @@ func TestImportExport(t *testing.T) { // TestImportRace tests the keystore on races. // This test should fail under -race if importing races. func TestImportRace(t *testing.T) { + t.Parallel() _, ks := tmpKeyStore(t, true) acc, err := ks.NewAccount("old") if err != nil { diff --git a/accounts/keystore/passphrase.go b/accounts/keystore/passphrase.go index 8d6ed2b14..e7a7f8d0c 100644 --- a/accounts/keystore/passphrase.go +++ b/accounts/keystore/passphrase.go @@ -136,7 +136,7 @@ func (ks keyStorePassphrase) JoinPath(filename string) string { return filepath.Join(ks.keysDirPath, filename) } -// Encryptdata encrypts the data given as 'data' with the password 'auth'. +// EncryptDataV3 encrypts the data given as 'data' with the password 'auth'. func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) { salt := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, salt); err != nil { diff --git a/accounts/keystore/passphrase_test.go b/accounts/keystore/passphrase_test.go index 1356b3178..20ec0f551 100644 --- a/accounts/keystore/passphrase_test.go +++ b/accounts/keystore/passphrase_test.go @@ -30,6 +30,7 @@ const ( // Tests that a json key file can be decrypted and encrypted in multiple rounds. func TestKeyEncryptDecrypt(t *testing.T) { + t.Parallel() keyjson, err := os.ReadFile("testdata/very-light-scrypt.json") if err != nil { t.Fatal(err) @@ -54,7 +55,7 @@ func TestKeyEncryptDecrypt(t *testing.T) { // Recrypt with a new password and start over password += "new data appended" // nolint: gosec if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil { - t.Errorf("test %d: failed to recrypt key %v", i, err) + t.Errorf("test %d: failed to re-encrypt key %v", i, err) } } } diff --git a/accounts/keystore/plain_test.go b/accounts/keystore/plain_test.go index 93165d5cd..737eb7fd6 100644 --- a/accounts/keystore/plain_test.go +++ b/accounts/keystore/plain_test.go @@ -40,6 +40,7 @@ func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) { } func TestKeyStorePlain(t *testing.T) { + t.Parallel() _, ks := tmpKeyStoreIface(t, false) pass := "" // not used but required by API @@ -60,6 +61,7 @@ func TestKeyStorePlain(t *testing.T) { } func TestKeyStorePassphrase(t *testing.T) { + t.Parallel() _, ks := tmpKeyStoreIface(t, true) pass := "foo" @@ -80,6 +82,7 @@ func TestKeyStorePassphrase(t *testing.T) { } func TestKeyStorePassphraseDecryptionFail(t *testing.T) { + t.Parallel() _, ks := tmpKeyStoreIface(t, true) pass := "foo" @@ -93,6 +96,7 @@ func TestKeyStorePassphraseDecryptionFail(t *testing.T) { } func TestImportPreSaleKey(t *testing.T) { + t.Parallel() dir, ks := tmpKeyStoreIface(t, true) // file content of a presale key file generated with: diff --git a/accounts/keystore/watch.go b/accounts/keystore/watch.go index 3f64b89c5..1bef321cd 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 } @@ -122,7 +125,7 @@ func (w *watcher) loop() { if !ok { return } - log.Info("Filsystem watcher error", "err", err) + log.Info("Filesystem watcher error", "err", err) case <-debounce.C: w.ac.scanAccounts() rescanTriggered = false diff --git a/accounts/manager.go b/accounts/manager.go index a0b5c329c..cbe4f7c79 100644 --- a/accounts/manager.go +++ b/accounts/manager.go @@ -98,6 +98,9 @@ func NewManager(config *Config, backends ...Backend) *Manager { // Close terminates the account manager's internal notification processes. func (am *Manager) Close() error { + for _, w := range am.wallets { + w.Close() + } errc := make(chan error) am.quit <- errc return <-errc diff --git a/accounts/scwallet/README.md b/accounts/scwallet/README.md index 4313d9c6b..28079c474 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/wallet.go b/accounts/scwallet/wallet.go index 067bda83f..f0ca9085b 100644 --- a/accounts/scwallet/wallet.go +++ b/accounts/scwallet/wallet.go @@ -776,16 +776,16 @@ func (w *Wallet) findAccountPath(account accounts.Account) (accounts.DerivationP return nil, fmt.Errorf("scheme %s does not match wallet scheme %s", account.URL.Scheme, w.Hub.scheme) } - parts := strings.SplitN(account.URL.Path, "/", 2) - if len(parts) != 2 { + url, path, found := strings.Cut(account.URL.Path, "/") + if !found { return nil, fmt.Errorf("invalid URL format: %s", account.URL) } - if parts[0] != fmt.Sprintf("%x", w.PublicKey[1:3]) { + if url != fmt.Sprintf("%x", w.PublicKey[1:3]) { return nil, fmt.Errorf("URL %s is not for this wallet", account.URL) } - return accounts.ParseDerivationPath(parts[1]) + return accounts.ParseDerivationPath(path) } // Session represents a secured communication session with the wallet. diff --git a/accounts/url_test.go b/accounts/url_test.go index 239aa06d2..f481a1016 100644 --- a/accounts/url_test.go +++ b/accounts/url_test.go @@ -21,6 +21,7 @@ import ( ) func TestURLParsing(t *testing.T) { + t.Parallel() url, err := parseURL("https://ethereum.org") if err != nil { t.Errorf("unexpected error: %v", err) @@ -40,6 +41,7 @@ func TestURLParsing(t *testing.T) { } func TestURLString(t *testing.T) { + t.Parallel() url := URL{Scheme: "https", Path: "ethereum.org"} if url.String() != "https://ethereum.org" { t.Errorf("expected: %v, got: %v", "https://ethereum.org", url.String()) @@ -52,10 +54,11 @@ func TestURLString(t *testing.T) { } func TestURLMarshalJSON(t *testing.T) { + t.Parallel() url := URL{Scheme: "https", Path: "ethereum.org"} json, err := url.MarshalJSON() if err != nil { - t.Errorf("unexpcted error: %v", err) + t.Errorf("unexpected error: %v", err) } if string(json) != "\"https://ethereum.org\"" { t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json)) @@ -63,10 +66,11 @@ func TestURLMarshalJSON(t *testing.T) { } func TestURLUnmarshalJSON(t *testing.T) { + t.Parallel() url := &URL{} err := url.UnmarshalJSON([]byte("\"https://ethereum.org\"")) if err != nil { - t.Errorf("unexpcted error: %v", err) + t.Errorf("unexpected error: %v", err) } if url.Scheme != "https" { t.Errorf("expected: %v, got: %v", "https", url.Scheme) @@ -77,6 +81,7 @@ func TestURLUnmarshalJSON(t *testing.T) { } func TestURLComparison(t *testing.T) { + t.Parallel() tests := []struct { urlA URL urlB URL diff --git a/accounts/usbwallet/wallet.go b/accounts/usbwallet/wallet.go index 05add081a..69083dc89 100644 --- a/accounts/usbwallet/wallet.go +++ b/accounts/usbwallet/wallet.go @@ -483,6 +483,10 @@ func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun w.stateLock.Lock() defer w.stateLock.Unlock() + if w.device == nil { + return accounts.Account{}, accounts.ErrWalletClosed + } + if _, ok := w.paths[address]; !ok { w.accounts = append(w.accounts, account) w.paths[address] = make(accounts.DerivationPath, len(path)) diff --git a/appveyor.yml b/appveyor.yml index 114aec644..4a8c4b737 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -54,4 +54,4 @@ for: - go run build/ci.go archive -arch %GETH_ARCH% -type zip -signer WINDOWS_SIGNING_KEY -upload gethstore/builds - go run build/ci.go nsis -arch %GETH_ARCH% -signer WINDOWS_SIGNING_KEY -upload gethstore/builds test_script: - - go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% + - go run build/ci.go test -dlgo -arch %GETH_ARCH% -cc %GETH_CC% -short diff --git a/beacon/engine/errors.go b/beacon/engine/errors.go index 769001b9e..62773a0ea 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 0dd2b5259..b1f01b50f 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_epe.go b/beacon/engine/gen_epe.go index 664a50fc5..e69f9a595 100644 --- a/beacon/engine/gen_epe.go +++ b/beacon/engine/gen_epe.go @@ -18,11 +18,13 @@ func (e ExecutionPayloadEnvelope) MarshalJSON() ([]byte, error) { 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) } @@ -32,6 +34,7 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error { 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 { @@ -48,5 +51,8 @@ func (e *ExecutionPayloadEnvelope) UnmarshalJSON(input []byte) error { 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 f1801edd1..67f30d445 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/trie" ) @@ -36,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. @@ -86,6 +86,7 @@ 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 { @@ -172,7 +173,7 @@ func decodeTransactions(enc [][]byte) ([]*types.Transaction, error) { // 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, versionedHashes []common.Hash) (*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 @@ -208,24 +209,25 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash) 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, - ExcessBlobGas: params.ExcessBlobGas, - BlobGasUsed: params.BlobGasUsed, + 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 { @@ -236,7 +238,7 @@ func ExecutableDataToBlock(params ExecutableData, versionedHashes []common.Hash) // 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, blobs []kzg4844.Blob, commitments []kzg4844.Commitment, proofs []kzg4844.Proof) *ExecutionPayloadEnvelope { +func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar) *ExecutionPayloadEnvelope { data := &ExecutableData{ BlockHash: block.Hash(), ParentHash: block.ParentHash(), @@ -256,17 +258,19 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, blobs []kzg4844.Bl BlobGasUsed: block.BlobGasUsed(), ExcessBlobGas: block.ExcessBlobGas(), } - blobsBundle := BlobsBundleV1{ + bundle := BlobsBundleV1{ Commitments: make([]hexutil.Bytes, 0), Blobs: make([]hexutil.Bytes, 0), Proofs: make([]hexutil.Bytes, 0), } - for i := range blobs { - blobsBundle.Blobs = append(blobsBundle.Blobs, hexutil.Bytes(blobs[i][:])) - blobsBundle.Commitments = append(blobsBundle.Commitments, hexutil.Bytes(commitments[i][:])) - blobsBundle.Proofs = append(blobsBundle.Proofs, hexutil.Bytes(proofs[i][:])) + 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, BlobsBundle: &blobsBundle} + return &ExecutionPayloadEnvelope{ExecutionPayload: data, BlockValue: fees, BlobsBundle: &bundle, Override: false} } // ExecutionPayloadBodyV1 is used in the response to GetPayloadBodiesByHashV1 and GetPayloadBodiesByRangeV1 diff --git a/beacon/light/canonical.go b/beacon/light/canonical.go new file mode 100644 index 000000000..b5371493b --- /dev/null +++ b/beacon/light/canonical.go @@ -0,0 +1,125 @@ +// 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 light + +import ( + "encoding/binary" + "fmt" + + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +// canonicalStore stores instances of the given type in a database and caches +// them in memory, associated with a continuous range of period numbers. +// Note: canonicalStore is not thread safe and it is the caller's responsibility +// to avoid concurrent access. +type canonicalStore[T any] struct { + keyPrefix []byte + periods periodRange + cache *lru.Cache[uint64, T] +} + +// newCanonicalStore creates a new canonicalStore and loads all keys associated +// with the keyPrefix in order to determine the ranges available in the database. +func newCanonicalStore[T any](db ethdb.Iteratee, keyPrefix []byte) (*canonicalStore[T], error) { + cs := &canonicalStore[T]{ + keyPrefix: keyPrefix, + cache: lru.NewCache[uint64, T](100), + } + var ( + iter = db.NewIterator(keyPrefix, nil) + kl = len(keyPrefix) + first = true + ) + defer iter.Release() + + for iter.Next() { + if len(iter.Key()) != kl+8 { + log.Warn("Invalid key length in the canonical chain database", "key", fmt.Sprintf("%#x", iter.Key())) + continue + } + period := binary.BigEndian.Uint64(iter.Key()[kl : kl+8]) + if first { + cs.periods.Start = period + } else if cs.periods.End != period { + return nil, fmt.Errorf("gap in the canonical chain database between periods %d and %d", cs.periods.End, period-1) + } + first = false + cs.periods.End = period + 1 + } + return cs, nil +} + +// databaseKey returns the database key belonging to the given period. +func (cs *canonicalStore[T]) databaseKey(period uint64) []byte { + return binary.BigEndian.AppendUint64(append([]byte{}, cs.keyPrefix...), period) +} + +// add adds the given item to the database. It also ensures that the range remains +// continuous. Can be used either with a batch or database backend. +func (cs *canonicalStore[T]) add(backend ethdb.KeyValueWriter, period uint64, value T) error { + if !cs.periods.canExpand(period) { + return fmt.Errorf("period expansion is not allowed, first: %d, next: %d, period: %d", cs.periods.Start, cs.periods.End, period) + } + enc, err := rlp.EncodeToBytes(value) + if err != nil { + return err + } + if err := backend.Put(cs.databaseKey(period), enc); err != nil { + return err + } + cs.cache.Add(period, value) + cs.periods.expand(period) + return nil +} + +// deleteFrom removes items starting from the given period. +func (cs *canonicalStore[T]) deleteFrom(db ethdb.KeyValueWriter, fromPeriod uint64) (deleted periodRange) { + keepRange, deleteRange := cs.periods.split(fromPeriod) + deleteRange.each(func(period uint64) { + db.Delete(cs.databaseKey(period)) + cs.cache.Remove(period) + }) + cs.periods = keepRange + return deleteRange +} + +// get returns the item at the given period or the null value of the given type +// if no item is present. +func (cs *canonicalStore[T]) get(backend ethdb.KeyValueReader, period uint64) (T, bool) { + var null, value T + if !cs.periods.contains(period) { + return null, false + } + if value, ok := cs.cache.Get(period); ok { + return value, true + } + enc, err := backend.Get(cs.databaseKey(period)) + if err != nil { + log.Error("Canonical store value not found", "period", period, "start", cs.periods.Start, "end", cs.periods.End) + return null, false + } + if err := rlp.DecodeBytes(enc, &value); err != nil { + log.Error("Error decoding canonical store value", "error", err) + return null, false + } + cs.cache.Add(period, value) + return value, true +} diff --git a/beacon/light/committee_chain.go b/beacon/light/committee_chain.go new file mode 100644 index 000000000..d707f8cc3 --- /dev/null +++ b/beacon/light/committee_chain.go @@ -0,0 +1,514 @@ +// 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 light + +import ( + "errors" + "fmt" + "math" + "sync" + "time" + + "github.com/ethereum/go-ethereum/beacon/params" + "github.com/ethereum/go-ethereum/beacon/types" + "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/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +var ( + ErrNeedCommittee = errors.New("sync committee required") + ErrInvalidUpdate = errors.New("invalid committee update") + ErrInvalidPeriod = errors.New("invalid update period") + ErrWrongCommitteeRoot = errors.New("wrong committee root") + ErrCannotReorg = errors.New("can not reorg committee chain") +) + +// CommitteeChain is a passive data structure that can validate, hold and update +// a chain of beacon light sync committees and updates. It requires at least one +// externally set fixed committee root at the beginning of the chain which can +// be set either based on a BootstrapData or a trusted source (a local beacon +// full node). This makes the structure useful for both light client and light +// server setups. +// +// It always maintains the following consistency constraints: +// - a committee can only be present if its root hash matches an existing fixed +// root or if it is proven by an update at the previous period +// - an update can only be present if a committee is present at the same period +// and the update signature is valid and has enough participants. +// The committee at the next period (proven by the update) should also be +// present (note that this means they can only be added together if neither +// is present yet). If a fixed root is present at the next period then the +// update can only be present if it proves the same committee root. +// +// Once synced to the current sync period, CommitteeChain can also validate +// signed beacon headers. +type CommitteeChain struct { + // chainmu guards against concurrent access to the canonicalStore structures + // (updates, committees, fixedCommitteeRoots) and ensures that they stay consistent + // with each other and with committeeCache. + chainmu sync.RWMutex + db ethdb.KeyValueStore + updates *canonicalStore[*types.LightClientUpdate] + committees *canonicalStore[*types.SerializedSyncCommittee] + fixedCommitteeRoots *canonicalStore[common.Hash] + committeeCache *lru.Cache[uint64, syncCommittee] // cache deserialized committees + + clock mclock.Clock // monotonic clock (simulated clock in tests) + unixNano func() int64 // system clock (simulated clock in tests) + sigVerifier committeeSigVerifier // BLS sig verifier (dummy verifier in tests) + + config *types.ChainConfig + signerThreshold int + minimumUpdateScore types.UpdateScore + enforceTime bool // enforceTime specifies whether the age of a signed header should be checked +} + +// NewCommitteeChain creates a new CommitteeChain. +func NewCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool) *CommitteeChain { + return newCommitteeChain(db, config, signerThreshold, enforceTime, blsVerifier{}, &mclock.System{}, func() int64 { return time.Now().UnixNano() }) +} + +// newCommitteeChain creates a new CommitteeChain with the option of replacing the +// clock source and signature verification for testing purposes. +func newCommitteeChain(db ethdb.KeyValueStore, config *types.ChainConfig, signerThreshold int, enforceTime bool, sigVerifier committeeSigVerifier, clock mclock.Clock, unixNano func() int64) *CommitteeChain { + s := &CommitteeChain{ + committeeCache: lru.NewCache[uint64, syncCommittee](10), + db: db, + sigVerifier: sigVerifier, + clock: clock, + unixNano: unixNano, + config: config, + signerThreshold: signerThreshold, + enforceTime: enforceTime, + minimumUpdateScore: types.UpdateScore{ + SignerCount: uint32(signerThreshold), + SubPeriodIndex: params.SyncPeriodLength / 16, + }, + } + + var err1, err2, err3 error + if s.fixedCommitteeRoots, err1 = newCanonicalStore[common.Hash](db, rawdb.FixedCommitteeRootKey); err1 != nil { + log.Error("Error creating fixed committee root store", "error", err1) + } + if s.committees, err2 = newCanonicalStore[*types.SerializedSyncCommittee](db, rawdb.SyncCommitteeKey); err2 != nil { + log.Error("Error creating committee store", "error", err2) + } + if s.updates, err3 = newCanonicalStore[*types.LightClientUpdate](db, rawdb.BestUpdateKey); err3 != nil { + log.Error("Error creating update store", "error", err3) + } + if err1 != nil || err2 != nil || err3 != nil || !s.checkConstraints() { + log.Info("Resetting invalid committee chain") + s.Reset() + } + // roll back invalid updates (might be necessary if forks have been changed since last time) + for !s.updates.periods.isEmpty() { + update, ok := s.updates.get(s.db, s.updates.periods.End-1) + if !ok { + log.Error("Sync committee update missing", "period", s.updates.periods.End-1) + s.Reset() + break + } + if valid, err := s.verifyUpdate(update); err != nil { + log.Error("Error validating update", "period", s.updates.periods.End-1, "error", err) + } else if valid { + break + } + if err := s.rollback(s.updates.periods.End); err != nil { + log.Error("Error writing batch into chain database", "error", err) + } + } + if !s.committees.periods.isEmpty() { + log.Trace("Sync committee chain loaded", "first period", s.committees.periods.Start, "last period", s.committees.periods.End-1) + } + return s +} + +// checkConstraints checks committee chain validity constraints +func (s *CommitteeChain) checkConstraints() bool { + isNotInFixedCommitteeRootRange := func(r periodRange) bool { + return s.fixedCommitteeRoots.periods.isEmpty() || + r.Start < s.fixedCommitteeRoots.periods.Start || + r.Start >= s.fixedCommitteeRoots.periods.End + } + + valid := true + if !s.updates.periods.isEmpty() { + if isNotInFixedCommitteeRootRange(s.updates.periods) { + log.Error("Start update is not in the fixed roots range") + valid = false + } + if s.committees.periods.Start > s.updates.periods.Start || s.committees.periods.End <= s.updates.periods.End { + log.Error("Missing committees in update range") + valid = false + } + } + if !s.committees.periods.isEmpty() { + if isNotInFixedCommitteeRootRange(s.committees.periods) { + log.Error("Start committee is not in the fixed roots range") + valid = false + } + if s.committees.periods.End > s.fixedCommitteeRoots.periods.End && s.committees.periods.End > s.updates.periods.End+1 { + log.Error("Last committee is neither in the fixed roots range nor proven by updates") + valid = false + } + } + return valid +} + +// Reset resets the committee chain. +func (s *CommitteeChain) Reset() { + s.chainmu.Lock() + defer s.chainmu.Unlock() + + if err := s.rollback(0); err != nil { + log.Error("Error writing batch into chain database", "error", err) + } +} + +// CheckpointInit initializes a CommitteeChain based on the checkpoint. +// Note: if the chain is already initialized and the committees proven by the +// checkpoint do match the existing chain then the chain is retained and the +// new checkpoint becomes fixed. +func (s *CommitteeChain) CheckpointInit(bootstrap *types.BootstrapData) error { + s.chainmu.Lock() + defer s.chainmu.Unlock() + + if err := bootstrap.Validate(); err != nil { + return err + } + + period := bootstrap.Header.SyncPeriod() + if err := s.deleteFixedCommitteeRootsFrom(period + 2); err != nil { + s.Reset() + return err + } + if s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot) != nil { + s.Reset() + if err := s.addFixedCommitteeRoot(period, bootstrap.CommitteeRoot); err != nil { + s.Reset() + return err + } + } + if err := s.addFixedCommitteeRoot(period+1, common.Hash(bootstrap.CommitteeBranch[0])); err != nil { + s.Reset() + return err + } + if err := s.addCommittee(period, bootstrap.Committee); err != nil { + s.Reset() + return err + } + return nil +} + +// addFixedCommitteeRoot sets a fixed committee root at the given period. +// Note that the period where the first committee is added has to have a fixed +// root which can either come from a BootstrapData or a trusted source. +func (s *CommitteeChain) addFixedCommitteeRoot(period uint64, root common.Hash) error { + if root == (common.Hash{}) { + return ErrWrongCommitteeRoot + } + + batch := s.db.NewBatch() + oldRoot := s.getCommitteeRoot(period) + if !s.fixedCommitteeRoots.periods.canExpand(period) { + // Note: the fixed committee root range should always be continuous and + // therefore the expected syncing method is to forward sync and optionally + // backward sync periods one by one, starting from a checkpoint. The only + // case when a root that is not adjacent to the already fixed ones can be + // fixed is when the same root has already been proven by an update chain. + // In this case the all roots in between can and should be fixed. + // This scenario makes sense when a new trusted checkpoint is added to an + // existing chain, ensuring that it will not be rolled back (might be + // important in case of low signer participation rate). + if root != oldRoot { + return ErrInvalidPeriod + } + // if the old root exists and matches the new one then it is guaranteed + // that the given period is after the existing fixed range and the roots + // in between can also be fixed. + for p := s.fixedCommitteeRoots.periods.End; p < period; p++ { + if err := s.fixedCommitteeRoots.add(batch, p, s.getCommitteeRoot(p)); err != nil { + return err + } + } + } + if oldRoot != (common.Hash{}) && (oldRoot != root) { + // existing old root was different, we have to reorg the chain + if err := s.rollback(period); err != nil { + return err + } + } + if err := s.fixedCommitteeRoots.add(batch, period, root); err != nil { + return err + } + if err := batch.Write(); err != nil { + log.Error("Error writing batch into chain database", "error", err) + return err + } + return nil +} + +// deleteFixedCommitteeRootsFrom deletes fixed roots starting from the given period. +// It also maintains chain consistency, meaning that it also deletes updates and +// committees if they are no longer supported by a valid update chain. +func (s *CommitteeChain) deleteFixedCommitteeRootsFrom(period uint64) error { + if period >= s.fixedCommitteeRoots.periods.End { + return nil + } + batch := s.db.NewBatch() + s.fixedCommitteeRoots.deleteFrom(batch, period) + if s.updates.periods.isEmpty() || period <= s.updates.periods.Start { + // Note: the first period of the update chain should always be fixed so if + // the fixed root at the first update is removed then the entire update chain + // and the proven committees have to be removed. Earlier committees in the + // remaining fixed root range can stay. + s.updates.deleteFrom(batch, period) + s.deleteCommitteesFrom(batch, period) + } else { + // The update chain stays intact, some previously fixed committee roots might + // get unfixed but are still proven by the update chain. If there were + // committees present after the range proven by updates, those should be + // removed if the belonging fixed roots are also removed. + fromPeriod := s.updates.periods.End + 1 // not proven by updates + if period > fromPeriod { + fromPeriod = period // also not justified by fixed roots + } + s.deleteCommitteesFrom(batch, fromPeriod) + } + if err := batch.Write(); err != nil { + log.Error("Error writing batch into chain database", "error", err) + return err + } + return nil +} + +// deleteCommitteesFrom deletes committees starting from the given period. +func (s *CommitteeChain) deleteCommitteesFrom(batch ethdb.Batch, period uint64) { + deleted := s.committees.deleteFrom(batch, period) + for period := deleted.Start; period < deleted.End; period++ { + s.committeeCache.Remove(period) + } +} + +// addCommittee adds a committee at the given period if possible. +func (s *CommitteeChain) addCommittee(period uint64, committee *types.SerializedSyncCommittee) error { + if !s.committees.periods.canExpand(period) { + return ErrInvalidPeriod + } + root := s.getCommitteeRoot(period) + if root == (common.Hash{}) { + return ErrInvalidPeriod + } + if root != committee.Root() { + return ErrWrongCommitteeRoot + } + if !s.committees.periods.contains(period) { + if err := s.committees.add(s.db, period, committee); err != nil { + return err + } + s.committeeCache.Remove(period) + } + return nil +} + +// InsertUpdate adds a new update if possible. +func (s *CommitteeChain) InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error { + s.chainmu.Lock() + defer s.chainmu.Unlock() + + period := update.AttestedHeader.Header.SyncPeriod() + if !s.updates.periods.canExpand(period) || !s.committees.periods.contains(period) { + return ErrInvalidPeriod + } + if s.minimumUpdateScore.BetterThan(update.Score()) { + return ErrInvalidUpdate + } + oldRoot := s.getCommitteeRoot(period + 1) + reorg := oldRoot != (common.Hash{}) && oldRoot != update.NextSyncCommitteeRoot + if oldUpdate, ok := s.updates.get(s.db, period); ok && !update.Score().BetterThan(oldUpdate.Score()) { + // a better or equal update already exists; no changes, only fail if new one tried to reorg + if reorg { + return ErrCannotReorg + } + return nil + } + if s.fixedCommitteeRoots.periods.contains(period+1) && reorg { + return ErrCannotReorg + } + if ok, err := s.verifyUpdate(update); err != nil { + return err + } else if !ok { + return ErrInvalidUpdate + } + addCommittee := !s.committees.periods.contains(period+1) || reorg + if addCommittee { + if nextCommittee == nil { + return ErrNeedCommittee + } + if nextCommittee.Root() != update.NextSyncCommitteeRoot { + return ErrWrongCommitteeRoot + } + } + if reorg { + if err := s.rollback(period + 1); err != nil { + return err + } + } + batch := s.db.NewBatch() + if addCommittee { + if err := s.committees.add(batch, period+1, nextCommittee); err != nil { + return err + } + s.committeeCache.Remove(period + 1) + } + if err := s.updates.add(batch, period, update); err != nil { + return err + } + if err := batch.Write(); err != nil { + log.Error("Error writing batch into chain database", "error", err) + return err + } + log.Info("Inserted new committee update", "period", period, "next committee root", update.NextSyncCommitteeRoot) + return nil +} + +// NextSyncPeriod returns the next period where an update can be added and also +// whether the chain is initialized at all. +func (s *CommitteeChain) NextSyncPeriod() (uint64, bool) { + s.chainmu.RLock() + defer s.chainmu.RUnlock() + + if s.committees.periods.isEmpty() { + return 0, false + } + if !s.updates.periods.isEmpty() { + return s.updates.periods.End, true + } + return s.committees.periods.End - 1, true +} + +// rollback removes all committees and fixed roots from the given period and updates +// starting from the previous period. +func (s *CommitteeChain) rollback(period uint64) error { + max := s.updates.periods.End + 1 + if s.committees.periods.End > max { + max = s.committees.periods.End + } + if s.fixedCommitteeRoots.periods.End > max { + max = s.fixedCommitteeRoots.periods.End + } + for max > period { + max-- + batch := s.db.NewBatch() + s.deleteCommitteesFrom(batch, max) + s.fixedCommitteeRoots.deleteFrom(batch, max) + if max > 0 { + s.updates.deleteFrom(batch, max-1) + } + if err := batch.Write(); err != nil { + log.Error("Error writing batch into chain database", "error", err) + return err + } + } + return nil +} + +// getCommitteeRoot returns the committee root at the given period, either fixed, +// proven by a previous update or both. It returns an empty hash if the committee +// root is unknown. +func (s *CommitteeChain) getCommitteeRoot(period uint64) common.Hash { + if root, ok := s.fixedCommitteeRoots.get(s.db, period); ok || period == 0 { + return root + } + if update, ok := s.updates.get(s.db, period-1); ok { + return update.NextSyncCommitteeRoot + } + return common.Hash{} +} + +// getSyncCommittee returns the deserialized sync committee at the given period. +func (s *CommitteeChain) getSyncCommittee(period uint64) (syncCommittee, error) { + if c, ok := s.committeeCache.Get(period); ok { + return c, nil + } + if sc, ok := s.committees.get(s.db, period); ok { + c, err := s.sigVerifier.deserializeSyncCommittee(sc) + if err != nil { + return nil, fmt.Errorf("Sync committee #%d deserialization error: %v", period, err) + } + s.committeeCache.Add(period, c) + return c, nil + } + return nil, fmt.Errorf("Missing serialized sync committee #%d", period) +} + +// VerifySignedHeader returns true if the given signed header has a valid signature +// according to the local committee chain. The caller should ensure that the +// committees advertised by the same source where the signed header came from are +// synced before verifying the signature. +// The age of the header is also returned (the time elapsed since the beginning +// of the given slot, according to the local system clock). If enforceTime is +// true then negative age (future) headers are rejected. +func (s *CommitteeChain) VerifySignedHeader(head types.SignedHeader) (bool, time.Duration, error) { + s.chainmu.RLock() + defer s.chainmu.RUnlock() + + return s.verifySignedHeader(head) +} + +func (s *CommitteeChain) verifySignedHeader(head types.SignedHeader) (bool, time.Duration, error) { + var age time.Duration + now := s.unixNano() + if head.Header.Slot < (uint64(now-math.MinInt64)/uint64(time.Second)-s.config.GenesisTime)/12 { + age = time.Duration(now - int64(time.Second)*int64(s.config.GenesisTime+head.Header.Slot*12)) + } else { + age = time.Duration(math.MinInt64) + } + if s.enforceTime && age < 0 { + return false, age, nil + } + committee, err := s.getSyncCommittee(types.SyncPeriod(head.SignatureSlot)) + if err != nil { + return false, 0, err + } + if committee == nil { + return false, age, nil + } + if signingRoot, err := s.config.Forks.SigningRoot(head.Header); err == nil { + return s.sigVerifier.verifySignature(committee, signingRoot, &head.Signature), age, nil + } + return false, age, nil +} + +// verifyUpdate checks whether the header signature is correct and the update +// fits into the specified constraints (assumes that the update has been +// successfully validated previously) +func (s *CommitteeChain) verifyUpdate(update *types.LightClientUpdate) (bool, error) { + // Note: SignatureSlot determines the sync period of the committee used for signature + // verification. Though in reality SignatureSlot is always bigger than update.Header.Slot, + // setting them as equal here enforces the rule that they have to be in the same sync + // period in order for the light client update proof to be meaningful. + ok, age, err := s.verifySignedHeader(update.AttestedHeader) + if age < 0 { + log.Warn("Future committee update received", "age", age) + } + return ok, err +} diff --git a/beacon/light/committee_chain_test.go b/beacon/light/committee_chain_test.go new file mode 100644 index 000000000..60ea2a0ef --- /dev/null +++ b/beacon/light/committee_chain_test.go @@ -0,0 +1,356 @@ +// 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 light + +import ( + "crypto/rand" + "testing" + "time" + + "github.com/ethereum/go-ethereum/beacon/params" + "github.com/ethereum/go-ethereum/beacon/types" + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/ethdb/memorydb" +) + +var ( + testGenesis = newTestGenesis() + testGenesis2 = newTestGenesis() + + tfBase = newTestForks(testGenesis, types.Forks{ + &types.Fork{Epoch: 0, Version: []byte{0}}, + }) + tfAlternative = newTestForks(testGenesis, types.Forks{ + &types.Fork{Epoch: 0, Version: []byte{0}}, + &types.Fork{Epoch: 0x700, Version: []byte{1}}, + }) + tfAnotherGenesis = newTestForks(testGenesis2, types.Forks{ + &types.Fork{Epoch: 0, Version: []byte{0}}, + }) + + tcBase = newTestCommitteeChain(nil, tfBase, true, 0, 10, 400, false) + tcBaseWithInvalidUpdates = newTestCommitteeChain(tcBase, tfBase, false, 5, 10, 200, false) // signer count too low + tcBaseWithBetterUpdates = newTestCommitteeChain(tcBase, tfBase, false, 5, 10, 440, false) + tcReorgWithWorseUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 400, false) + tcReorgWithWorseUpdates2 = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 380, false) + tcReorgWithBetterUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 420, false) + tcReorgWithFinalizedUpdates = newTestCommitteeChain(tcBase, tfBase, true, 5, 10, 400, true) + tcFork = newTestCommitteeChain(tcBase, tfAlternative, true, 7, 10, 400, false) + tcAnotherGenesis = newTestCommitteeChain(nil, tfAnotherGenesis, true, 0, 10, 400, false) +) + +func TestCommitteeChainFixedCommitteeRoots(t *testing.T) { + for _, reload := range []bool{false, true} { + c := newCommitteeChainTest(t, tfBase, 300, true) + c.setClockPeriod(7) + c.addFixedCommitteeRoot(tcBase, 4, nil) + c.addFixedCommitteeRoot(tcBase, 5, nil) + c.addFixedCommitteeRoot(tcBase, 6, nil) + c.addFixedCommitteeRoot(tcBase, 8, ErrInvalidPeriod) // range has to be continuous + c.addFixedCommitteeRoot(tcBase, 3, nil) + c.addFixedCommitteeRoot(tcBase, 2, nil) + if reload { + c.reloadChain() + } + c.addCommittee(tcBase, 4, nil) + c.addCommittee(tcBase, 6, ErrInvalidPeriod) // range has to be continuous + c.addCommittee(tcBase, 5, nil) + c.addCommittee(tcBase, 6, nil) + c.addCommittee(tcAnotherGenesis, 3, ErrWrongCommitteeRoot) + c.addCommittee(tcBase, 3, nil) + if reload { + c.reloadChain() + } + c.verifyRange(tcBase, 3, 6) + } +} + +func TestCommitteeChainCheckpointSync(t *testing.T) { + for _, enforceTime := range []bool{false, true} { + for _, reload := range []bool{false, true} { + c := newCommitteeChainTest(t, tfBase, 300, enforceTime) + if enforceTime { + c.setClockPeriod(6) + } + c.insertUpdate(tcBase, 3, true, ErrInvalidPeriod) + c.addFixedCommitteeRoot(tcBase, 3, nil) + c.addFixedCommitteeRoot(tcBase, 4, nil) + c.insertUpdate(tcBase, 4, true, ErrInvalidPeriod) // still no committee + c.addCommittee(tcBase, 3, nil) + c.addCommittee(tcBase, 4, nil) + if reload { + c.reloadChain() + } + c.verifyRange(tcBase, 3, 4) + c.insertUpdate(tcBase, 3, false, nil) // update can be added without committee here + c.insertUpdate(tcBase, 4, false, ErrNeedCommittee) // but not here as committee 5 is not there yet + c.insertUpdate(tcBase, 4, true, nil) + c.verifyRange(tcBase, 3, 5) + c.insertUpdate(tcBaseWithInvalidUpdates, 5, true, ErrInvalidUpdate) // signer count too low + c.insertUpdate(tcBase, 5, true, nil) + if reload { + c.reloadChain() + } + if enforceTime { + c.insertUpdate(tcBase, 6, true, ErrInvalidUpdate) // future update rejected + c.setClockPeriod(7) + } + c.insertUpdate(tcBase, 6, true, nil) // when the time comes it's accepted + if reload { + c.reloadChain() + } + if enforceTime { + c.verifyRange(tcBase, 3, 6) // committee 7 is there but still in the future + c.setClockPeriod(8) + } + c.verifyRange(tcBase, 3, 7) // now period 7 can also be verified + // try reverse syncing an update + c.insertUpdate(tcBase, 2, false, ErrInvalidPeriod) // fixed committee is needed first + c.addFixedCommitteeRoot(tcBase, 2, nil) + c.addCommittee(tcBase, 2, nil) + c.insertUpdate(tcBase, 2, false, nil) + c.verifyRange(tcBase, 2, 7) + } + } +} + +func TestCommitteeChainReorg(t *testing.T) { + for _, reload := range []bool{false, true} { + for _, addBetterUpdates := range []bool{false, true} { + c := newCommitteeChainTest(t, tfBase, 300, true) + c.setClockPeriod(11) + c.addFixedCommitteeRoot(tcBase, 3, nil) + c.addFixedCommitteeRoot(tcBase, 4, nil) + c.addCommittee(tcBase, 3, nil) + for period := uint64(3); period < 10; period++ { + c.insertUpdate(tcBase, period, true, nil) + } + if reload { + c.reloadChain() + } + c.verifyRange(tcBase, 3, 10) + c.insertUpdate(tcReorgWithWorseUpdates, 5, true, ErrCannotReorg) + c.insertUpdate(tcReorgWithWorseUpdates2, 5, true, ErrCannotReorg) + if addBetterUpdates { + // add better updates for the base chain and expect first reorg to fail + // (only add updates as committees should be the same) + for period := uint64(5); period < 10; period++ { + c.insertUpdate(tcBaseWithBetterUpdates, period, false, nil) + } + if reload { + c.reloadChain() + } + c.verifyRange(tcBase, 3, 10) // still on the same chain + c.insertUpdate(tcReorgWithBetterUpdates, 5, true, ErrCannotReorg) + } else { + // reorg with better updates + c.insertUpdate(tcReorgWithBetterUpdates, 5, false, ErrNeedCommittee) + c.verifyRange(tcBase, 3, 10) // no success yet, still on the base chain + c.verifyRange(tcReorgWithBetterUpdates, 3, 5) + c.insertUpdate(tcReorgWithBetterUpdates, 5, true, nil) + // successful reorg, base chain should only match before the reorg period + if reload { + c.reloadChain() + } + c.verifyRange(tcBase, 3, 5) + c.verifyRange(tcReorgWithBetterUpdates, 3, 6) + for period := uint64(6); period < 10; period++ { + c.insertUpdate(tcReorgWithBetterUpdates, period, true, nil) + } + c.verifyRange(tcReorgWithBetterUpdates, 3, 10) + } + // reorg with finalized updates; should succeed even if base chain updates + // have been improved because a finalized update beats everything else + c.insertUpdate(tcReorgWithFinalizedUpdates, 5, false, ErrNeedCommittee) + c.insertUpdate(tcReorgWithFinalizedUpdates, 5, true, nil) + if reload { + c.reloadChain() + } + c.verifyRange(tcReorgWithFinalizedUpdates, 3, 6) + for period := uint64(6); period < 10; period++ { + c.insertUpdate(tcReorgWithFinalizedUpdates, period, true, nil) + } + c.verifyRange(tcReorgWithFinalizedUpdates, 3, 10) + } + } +} + +func TestCommitteeChainFork(t *testing.T) { + c := newCommitteeChainTest(t, tfAlternative, 300, true) + c.setClockPeriod(11) + // trying to sync a chain on an alternative fork with the base chain data + c.addFixedCommitteeRoot(tcBase, 0, nil) + c.addFixedCommitteeRoot(tcBase, 1, nil) + c.addCommittee(tcBase, 0, nil) + // shared section should sync without errors + for period := uint64(0); period < 7; period++ { + c.insertUpdate(tcBase, period, true, nil) + } + c.insertUpdate(tcBase, 7, true, ErrInvalidUpdate) // wrong fork + // committee root #7 is still the same but signatures are already signed with + // a different fork id so period 7 should only verify on the alternative fork + c.verifyRange(tcBase, 0, 6) + c.verifyRange(tcFork, 0, 7) + for period := uint64(7); period < 10; period++ { + c.insertUpdate(tcFork, period, true, nil) + } + c.verifyRange(tcFork, 0, 10) + // reload the chain while switching to the base fork + c.config = tfBase + c.reloadChain() + // updates 7..9 should be rolled back now + c.verifyRange(tcFork, 0, 6) // again, period 7 only verifies on the right fork + c.verifyRange(tcBase, 0, 7) + c.insertUpdate(tcFork, 7, true, ErrInvalidUpdate) // wrong fork + for period := uint64(7); period < 10; period++ { + c.insertUpdate(tcBase, period, true, nil) + } + c.verifyRange(tcBase, 0, 10) +} + +type committeeChainTest struct { + t *testing.T + db *memorydb.Database + clock *mclock.Simulated + config types.ChainConfig + signerThreshold int + enforceTime bool + chain *CommitteeChain +} + +func newCommitteeChainTest(t *testing.T, config types.ChainConfig, signerThreshold int, enforceTime bool) *committeeChainTest { + c := &committeeChainTest{ + t: t, + db: memorydb.New(), + clock: &mclock.Simulated{}, + config: config, + signerThreshold: signerThreshold, + enforceTime: enforceTime, + } + c.chain = newCommitteeChain(c.db, &config, signerThreshold, enforceTime, dummyVerifier{}, c.clock, func() int64 { return int64(c.clock.Now()) }) + return c +} + +func (c *committeeChainTest) reloadChain() { + c.chain = newCommitteeChain(c.db, &c.config, c.signerThreshold, c.enforceTime, dummyVerifier{}, c.clock, func() int64 { return int64(c.clock.Now()) }) +} + +func (c *committeeChainTest) setClockPeriod(period float64) { + target := mclock.AbsTime(period * float64(time.Second*12*params.SyncPeriodLength)) + wait := time.Duration(target - c.clock.Now()) + if wait < 0 { + c.t.Fatalf("Invalid setClockPeriod") + } + c.clock.Run(wait) +} + +func (c *committeeChainTest) addFixedCommitteeRoot(tc *testCommitteeChain, period uint64, expErr error) { + if err := c.chain.addFixedCommitteeRoot(period, tc.periods[period].committee.Root()); err != expErr { + c.t.Errorf("Incorrect error output from addFixedCommitteeRoot at period %d (expected %v, got %v)", period, expErr, err) + } +} + +func (c *committeeChainTest) addCommittee(tc *testCommitteeChain, period uint64, expErr error) { + if err := c.chain.addCommittee(period, tc.periods[period].committee); err != expErr { + c.t.Errorf("Incorrect error output from addCommittee at period %d (expected %v, got %v)", period, expErr, err) + } +} + +func (c *committeeChainTest) insertUpdate(tc *testCommitteeChain, period uint64, addCommittee bool, expErr error) { + var committee *types.SerializedSyncCommittee + if addCommittee { + committee = tc.periods[period+1].committee + } + if err := c.chain.InsertUpdate(tc.periods[period].update, committee); err != expErr { + c.t.Errorf("Incorrect error output from InsertUpdate at period %d (expected %v, got %v)", period, expErr, err) + } +} + +func (c *committeeChainTest) verifySignedHeader(tc *testCommitteeChain, period float64, expOk bool) { + slot := uint64(period * float64(params.SyncPeriodLength)) + signedHead := GenerateTestSignedHeader(types.Header{Slot: slot}, &tc.config, tc.periods[types.SyncPeriod(slot)].committee, slot+1, 400) + if ok, _, _ := c.chain.VerifySignedHeader(signedHead); ok != expOk { + c.t.Errorf("Incorrect output from VerifySignedHeader at period %f (expected %v, got %v)", period, expOk, ok) + } +} + +func (c *committeeChainTest) verifyRange(tc *testCommitteeChain, begin, end uint64) { + if begin > 0 { + c.verifySignedHeader(tc, float64(begin)-0.5, false) + } + for period := begin; period <= end; period++ { + c.verifySignedHeader(tc, float64(period)+0.5, true) + } + c.verifySignedHeader(tc, float64(end)+1.5, false) +} + +func newTestGenesis() types.ChainConfig { + var config types.ChainConfig + rand.Read(config.GenesisValidatorsRoot[:]) + return config +} + +func newTestForks(config types.ChainConfig, forks types.Forks) types.ChainConfig { + for _, fork := range forks { + config.AddFork(fork.Name, fork.Epoch, fork.Version) + } + return config +} + +func newTestCommitteeChain(parent *testCommitteeChain, config types.ChainConfig, newCommittees bool, begin, end int, signerCount int, finalizedHeader bool) *testCommitteeChain { + tc := &testCommitteeChain{ + config: config, + } + if parent != nil { + tc.periods = make([]testPeriod, len(parent.periods)) + copy(tc.periods, parent.periods) + } + if newCommittees { + if begin == 0 { + tc.fillCommittees(begin, end+1) + } else { + tc.fillCommittees(begin+1, end+1) + } + } + tc.fillUpdates(begin, end, signerCount, finalizedHeader) + return tc +} + +type testPeriod struct { + committee *types.SerializedSyncCommittee + update *types.LightClientUpdate +} + +type testCommitteeChain struct { + periods []testPeriod + config types.ChainConfig +} + +func (tc *testCommitteeChain) fillCommittees(begin, end int) { + if len(tc.periods) <= end { + tc.periods = append(tc.periods, make([]testPeriod, end+1-len(tc.periods))...) + } + for i := begin; i <= end; i++ { + tc.periods[i].committee = GenerateTestCommittee() + } +} + +func (tc *testCommitteeChain) fillUpdates(begin, end int, signerCount int, finalizedHeader bool) { + for i := begin; i <= end; i++ { + tc.periods[i].update = GenerateTestUpdate(&tc.config, uint64(i), tc.periods[i].committee, tc.periods[i+1].committee, signerCount, finalizedHeader) + } +} diff --git a/beacon/light/range.go b/beacon/light/range.go new file mode 100644 index 000000000..76ebe2381 --- /dev/null +++ b/beacon/light/range.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 light + +// periodRange represents a (possibly zero-length) range of integers (sync periods). +type periodRange struct { + Start, End uint64 +} + +// isEmpty returns true if the length of the range is zero. +func (a periodRange) isEmpty() bool { + return a.End == a.Start +} + +// contains returns true if the range includes the given period. +func (a periodRange) contains(period uint64) bool { + return period >= a.Start && period < a.End +} + +// canExpand returns true if the range includes or can be expanded with the given +// period (either the range is empty or the given period is inside, right before or +// right after the range). +func (a periodRange) canExpand(period uint64) bool { + return a.isEmpty() || (period+1 >= a.Start && period <= a.End) +} + +// expand expands the range with the given period. +// This method assumes that canExpand returned true: otherwise this is a no-op. +func (a *periodRange) expand(period uint64) { + if a.isEmpty() { + a.Start, a.End = period, period+1 + return + } + if a.Start == period+1 { + a.Start-- + } + if a.End == period { + a.End++ + } +} + +// split splits the range into two ranges. The 'fromPeriod' will be the first +// element in the second range (if present). +// The original range is unchanged by this operation +func (a *periodRange) split(fromPeriod uint64) (periodRange, periodRange) { + if fromPeriod <= a.Start { + // First range empty, everything in second range, + return periodRange{}, *a + } + if fromPeriod >= a.End { + // Second range empty, everything in first range, + return *a, periodRange{} + } + x := periodRange{a.Start, fromPeriod} + y := periodRange{fromPeriod, a.End} + return x, y +} + +// each invokes the supplied function fn once per period in range +func (a *periodRange) each(fn func(uint64)) { + for p := a.Start; p < a.End; p++ { + fn(p) + } +} diff --git a/beacon/light/test_helpers.go b/beacon/light/test_helpers.go new file mode 100644 index 000000000..f537d963a --- /dev/null +++ b/beacon/light/test_helpers.go @@ -0,0 +1,152 @@ +// 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 light + +import ( + "crypto/rand" + "crypto/sha256" + mrand "math/rand" + + "github.com/ethereum/go-ethereum/beacon/merkle" + "github.com/ethereum/go-ethereum/beacon/params" + "github.com/ethereum/go-ethereum/beacon/types" + "github.com/ethereum/go-ethereum/common" +) + +func GenerateTestCommittee() *types.SerializedSyncCommittee { + s := new(types.SerializedSyncCommittee) + rand.Read(s[:32]) + return s +} + +func GenerateTestUpdate(config *types.ChainConfig, period uint64, committee, nextCommittee *types.SerializedSyncCommittee, signerCount int, finalizedHeader bool) *types.LightClientUpdate { + update := new(types.LightClientUpdate) + update.NextSyncCommitteeRoot = nextCommittee.Root() + var attestedHeader types.Header + if finalizedHeader { + update.FinalizedHeader = new(types.Header) + *update.FinalizedHeader, update.NextSyncCommitteeBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+100, params.StateIndexNextSyncCommittee, merkle.Value(update.NextSyncCommitteeRoot)) + attestedHeader, update.FinalityBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+200, params.StateIndexFinalBlock, merkle.Value(update.FinalizedHeader.Hash())) + } else { + attestedHeader, update.NextSyncCommitteeBranch = makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+2000, params.StateIndexNextSyncCommittee, merkle.Value(update.NextSyncCommitteeRoot)) + } + update.AttestedHeader = GenerateTestSignedHeader(attestedHeader, config, committee, attestedHeader.Slot+1, signerCount) + return update +} + +func GenerateTestSignedHeader(header types.Header, config *types.ChainConfig, committee *types.SerializedSyncCommittee, signatureSlot uint64, signerCount int) types.SignedHeader { + bitmask := makeBitmask(signerCount) + signingRoot, _ := config.Forks.SigningRoot(header) + c, _ := dummyVerifier{}.deserializeSyncCommittee(committee) + return types.SignedHeader{ + Header: header, + Signature: types.SyncAggregate{ + Signers: bitmask, + Signature: makeDummySignature(c.(dummySyncCommittee), signingRoot, bitmask), + }, + SignatureSlot: signatureSlot, + } +} + +func GenerateTestCheckpoint(period uint64, committee *types.SerializedSyncCommittee) *types.BootstrapData { + header, branch := makeTestHeaderWithMerkleProof(types.SyncPeriodStart(period)+200, params.StateIndexSyncCommittee, merkle.Value(committee.Root())) + return &types.BootstrapData{ + Header: header, + Committee: committee, + CommitteeRoot: committee.Root(), + CommitteeBranch: branch, + } +} + +func makeBitmask(signerCount int) (bitmask [params.SyncCommitteeBitmaskSize]byte) { + for i := 0; i < params.SyncCommitteeSize; i++ { + if mrand.Intn(params.SyncCommitteeSize-i) < signerCount { + bitmask[i/8] += byte(1) << (i & 7) + signerCount-- + } + } + return +} + +func makeTestHeaderWithMerkleProof(slot, index uint64, value merkle.Value) (types.Header, merkle.Values) { + var branch merkle.Values + hasher := sha256.New() + for index > 1 { + var proofHash merkle.Value + rand.Read(proofHash[:]) + hasher.Reset() + if index&1 == 0 { + hasher.Write(value[:]) + hasher.Write(proofHash[:]) + } else { + hasher.Write(proofHash[:]) + hasher.Write(value[:]) + } + hasher.Sum(value[:0]) + index >>= 1 + branch = append(branch, proofHash) + } + return types.Header{Slot: slot, StateRoot: common.Hash(value)}, branch +} + +// syncCommittee holds either a blsSyncCommittee or a fake dummySyncCommittee used for testing +type syncCommittee interface{} + +// committeeSigVerifier verifies sync committee signatures (either proper BLS +// signatures or fake signatures used for testing) +type committeeSigVerifier interface { + deserializeSyncCommittee(s *types.SerializedSyncCommittee) (syncCommittee, error) + verifySignature(committee syncCommittee, signedRoot common.Hash, aggregate *types.SyncAggregate) bool +} + +// blsVerifier implements committeeSigVerifier +type blsVerifier struct{} + +// deserializeSyncCommittee implements committeeSigVerifier +func (blsVerifier) deserializeSyncCommittee(s *types.SerializedSyncCommittee) (syncCommittee, error) { + return s.Deserialize() +} + +// verifySignature implements committeeSigVerifier +func (blsVerifier) verifySignature(committee syncCommittee, signingRoot common.Hash, aggregate *types.SyncAggregate) bool { + return committee.(*types.SyncCommittee).VerifySignature(signingRoot, aggregate) +} + +type dummySyncCommittee [32]byte + +// dummyVerifier implements committeeSigVerifier +type dummyVerifier struct{} + +// deserializeSyncCommittee implements committeeSigVerifier +func (dummyVerifier) deserializeSyncCommittee(s *types.SerializedSyncCommittee) (syncCommittee, error) { + var sc dummySyncCommittee + copy(sc[:], s[:32]) + return sc, nil +} + +// verifySignature implements committeeSigVerifier +func (dummyVerifier) verifySignature(committee syncCommittee, signingRoot common.Hash, aggregate *types.SyncAggregate) bool { + return aggregate.Signature == makeDummySignature(committee.(dummySyncCommittee), signingRoot, aggregate.Signers) +} + +func makeDummySignature(committee dummySyncCommittee, signingRoot common.Hash, bitmask [params.SyncCommitteeBitmaskSize]byte) (sig [params.BLSSignatureSize]byte) { + for i, b := range committee[:] { + sig[i] = b ^ signingRoot[i] + } + copy(sig[32:], bitmask[:]) + return +} diff --git a/beacon/types/update.go b/beacon/types/light_sync.go similarity index 88% rename from beacon/types/update.go rename to beacon/types/light_sync.go index 06c1b6179..3284081e4 100644 --- a/beacon/types/update.go +++ b/beacon/types/light_sync.go @@ -25,6 +25,24 @@ import ( "github.com/ethereum/go-ethereum/common" ) +// BootstrapData contains a sync committee where light sync can be started, +// together with a proof through a beacon header and corresponding state. +// Note: BootstrapData is fetched from a server based on a known checkpoint hash. +type BootstrapData struct { + Header Header + CommitteeRoot common.Hash + Committee *SerializedSyncCommittee `rlp:"-"` + CommitteeBranch merkle.Values +} + +// Validate verifies the proof included in BootstrapData. +func (c *BootstrapData) Validate() error { + if c.CommitteeRoot != c.Committee.Root() { + return errors.New("wrong committee root") + } + return merkle.VerifyProof(c.Header.StateRoot, params.StateIndexSyncCommittee, c.CommitteeBranch, merkle.Value(c.CommitteeRoot)) +} + // LightClientUpdate is a proof of the next sync committee root based on a header // signed by the sync committee of the given period. Optionally, the update can // prove quasi-finality by the signed header referring to a previous, finalized diff --git a/build/checksums.txt b/build/checksums.txt index 6e33f456d..b9d322aa1 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,46 +1,65 @@ # This file contains sha256 checksums of optional build dependencies. -2c5ee9c9ec1e733b0dbbc2bdfed3f62306e51d8172bf38f4f4e542b27520f597 go1.20.7.src.tar.gz -785170eab380a8985d53896808b0a71336d0ea60e0a26099b4ccec77798b1cf4 go1.20.7.darwin-amd64.tar.gz -eea1e7e4c2f75c72629050e6a6c7c46c446d64056732a7787fb3ba16ace1982e go1.20.7.darwin-arm64.tar.gz -d8cff0357ac24eb06f3f280535397eeaacf95611d29f9b2abc3060f3d6dce3b4 go1.20.7.freebsd-386.tar.gz -26918dcebf474a9e81ccf9f648cdf36968dfb76b481518cf615d78455dda4416 go1.20.7.freebsd-amd64.tar.gz -ddb48145f05bda2f4617a22c979d4e94b22802cdb1a1fde1b1974e733b26f091 go1.20.7.linux-386.tar.gz -f0a87f1bcae91c4b69f8dc2bc6d7e6bfcd7524fceec130af525058c0c17b1b44 go1.20.7.linux-amd64.tar.gz -44781ae3b153c3b07651d93b6bc554e835a36e2d72a696281c1e4dad9efffe43 go1.20.7.linux-arm64.tar.gz -7cc231b415b94f2f7065870a73f67dd2b0ec12b5a98052b7ee0121c42bc04f8d go1.20.7.linux-armv6l.tar.gz -6318a1db307c12b8afe68808bd6fae4fba1e558a85b958216096869ed506dcb3 go1.20.7.linux-ppc64le.tar.gz -26aea2ede8722ceecbd9db883328a8d963136fd96c11dacc356c44c4c19c6515 go1.20.7.linux-s390x.tar.gz -5b0ef6f58d3e04d6cc003aa98e9172f41ba9e091b1c98e7339b41c4c87fb78a1 go1.20.7.windows-386.zip -736dc6c7fcab1c96b682c8c93e38d7e371e62a17d34cb2c37d451a1147f66af9 go1.20.7.windows-amd64.zip -fc6f79c1e1ed9e506c65f2112ac4e387479916f1accb0d046a6a19ff6938baa5 go1.20.7.windows-arm64.zip +# version:spec-tests 1.0.6 +# https://github.com/ethereum/execution-spec-tests/releases +# https://github.com/ethereum/execution-spec-tests/releases/download/v1.0.6/ +485af7b66cf41eb3a8c1bd46632913b8eb95995df867cf665617bbc9b4beedd1 fixtures_develop.tar.gz -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 -623ce2d0fa4d35cc2e8d69fa7334227ab592380962a13b4d9cdc77cf41db2008 golangci-lint-1.51.1-freebsd-amd64.tar.gz -131365feb0584cc2736c43192fa673ca50e5b6b765456990cb379ecfb787e568 golangci-lint-1.51.1-freebsd-armv6.tar.gz -98fb627927cbb654f5bf85dcffc5f646666b2ce96ea0fed977c9fb28abd51532 golangci-lint-1.51.1-freebsd-armv7.tar.gz -b36a99702fa762c15840261bc0fb41b4b1b16b8b19b8c0941bae98c85bb0f8b8 golangci-lint-1.51.1-linux-386.tar.gz -17aeb26c76820c22efa0e1838b0ab93e90cfedef43fbfc9a2f33f27eb9e5e070 golangci-lint-1.51.1-linux-amd64.tar.gz -9744bc34e7b8d82ca788b667bfb7155a39b4be9aef43bf9f10318b1372cea338 golangci-lint-1.51.1-linux-arm64.tar.gz -0dda8dbeb2ff7455a044ec8e347f2fc6d655d2e99d281b3b95e88167031c673d golangci-lint-1.51.1-linux-armv6.tar.gz -0512f311b11d43b8b22989d929f0fe8a2e1e5ebe497f1eb0ff73a0fc3d188fd1 golangci-lint-1.51.1-linux-armv7.tar.gz -d767108dcf84a8eaa844df3454cb0f75a492f4e7102ecc2b0a3545cfe073a566 golangci-lint-1.51.1-linux-loong64.tar.gz -3bd56c54daec16585b2668e0dfabb27af2c2b38cc0fdb46923e2521e1634846b golangci-lint-1.51.1-linux-mips64.tar.gz -f72f5adfa2219e15d2414c9a2966f86e74556cf17a85c727a7fb7770a16cf814 golangci-lint-1.51.1-linux-mips64le.tar.gz -e605521dac98096d8737e1997c954f41f1d0d8275b8731f62783d410c23574b9 golangci-lint-1.51.1-linux-ppc64le.tar.gz -2f683217b814339e74d61ca700922d8407f15addd6d4c5e8b156fbab79f26a87 golangci-lint-1.51.1-linux-riscv64.tar.gz -d98528292b65971a3594e5880530e7624597dc9806fcfccdfbe39be411713d63 golangci-lint-1.51.1-linux-s390x.tar.gz -9bb2d0fe9e692ed0aea4f2537e3e6862b2f6768fe2849a84f4a6ad09da9fd971 golangci-lint-1.51.1-netbsd-386.tar.gz -34cafdcd11ae73ae88d66c33eb8449f5c976fc3e37b44774dbe9c71caa95e592 golangci-lint-1.51.1-netbsd-amd64.tar.gz -f8b4e1e47ac17caafe8a5f32f975a2b6a7cb14c27c0f73c1fb15c20ca91c2e03 golangci-lint-1.51.1-netbsd-armv6.tar.gz -c4f58b7e227b9fd41f0e9310dc83f4a4e7d026598e2f6e95b78761081a6d9bd2 golangci-lint-1.51.1-netbsd-armv7.tar.gz -6710e2f5375dc75521c1a17980a6cbbe6ff76c2f8b852964a8af558899a97cf5 golangci-lint-1.51.1-windows-386.zip -722d7b87b9cdda0a3835d5030b3fc5385c2eba4c107f63f6391cfb2ac35f051d golangci-lint-1.51.1-windows-amd64.zip -eb57f9bcb56646f2e3d6ccaf02ec227815fb05077b2e0b1bf9e755805acdc2b9 golangci-lint-1.51.1-windows-arm64.zip -bce02f7232723cb727755ee11f168a700a00896a25d37f87c4b173bce55596b4 golangci-lint-1.51.1-windows-armv6.zip -cf6403f84707ce8c98664736772271bc8874f2e760c2fd0f00cf3e85963507e9 golangci-lint-1.51.1-windows-armv7.zip +# version:golang 1.21.5 +# https://go.dev/dl/ +285cbbdf4b6e6e62ed58f370f3f6d8c30825d6e56c5853c66d3c23bcdb09db19 go1.21.5.src.tar.gz +a2e1d5743e896e5fe1e7d96479c0a769254aed18cf216cf8f4c3a2300a9b3923 go1.21.5.darwin-amd64.tar.gz +d0f8ac0c4fb3efc223a833010901d02954e3923cfe2c9a2ff0e4254a777cc9cc go1.21.5.darwin-arm64.tar.gz +2c05bbe0dc62456b90b7ddd354a54f373b7c377a98f8b22f52ab694b4f6cca58 go1.21.5.freebsd-386.tar.gz +30b6c64e9a77129605bc12f836422bf09eec577a8c899ee46130aeff81567003 go1.21.5.freebsd-amd64.tar.gz +8f4dba9cf5c61757bbd7e9ebdb93b6a30a1b03f4a636a1ba0cc2f27b907ab8e1 go1.21.5.linux-386.tar.gz +e2bc0b3e4b64111ec117295c088bde5f00eeed1567999ff77bc859d7df70078e go1.21.5.linux-amd64.tar.gz +841cced7ecda9b2014f139f5bab5ae31785f35399f236b8b3e75dff2a2978d96 go1.21.5.linux-arm64.tar.gz +837f4bf4e22fcdf920ffeaa4abf3d02d1314e03725431065f4d44c46a01b42fe go1.21.5.linux-armv6l.tar.gz +907b8c6ec4be9b184952e5d3493be66b1746442394a8bc78556c56834cd7c38b go1.21.5.linux-ppc64le.tar.gz +9c4a81b72ebe44368813cd03684e1080a818bf915d84163abae2ed325a1b2dc0 go1.21.5.linux-s390x.tar.gz +6da2418889dfb37763d0eb149c4a8d728c029e12f0cd54fbca0a31ae547e2d34 go1.21.5.windows-386.zip +bbe603cde7c9dee658f45164b4d06de1eff6e6e6b800100824e7c00d56a9a92f go1.21.5.windows-amd64.zip +9b7acca50e674294e43202df4fbc26d5af4d8bc3170a3342a1514f09a2dab5e9 go1.21.5.windows-arm64.zip + +# version:golangci 1.55.2 +# https://github.com/golangci/golangci-lint/releases/ +# https://github.com/golangci/golangci-lint/releases/download/v1.55.2/ +632e96e6d5294fbbe7b2c410a49c8fa01c60712a0af85a567de85bcc1623ea21 golangci-lint-1.55.2-darwin-amd64.tar.gz +234463f059249f82045824afdcdd5db5682d0593052f58f6a3039a0a1c3899f6 golangci-lint-1.55.2-darwin-arm64.tar.gz +2bdd105e2d4e003a9058c33a22bb191a1e0f30fa0790acca0d8fbffac1d6247c golangci-lint-1.55.2-freebsd-386.tar.gz +e75056e8b082386676ce23eba455cf893931a792c0d87e1e3743c0aec33c7fb5 golangci-lint-1.55.2-freebsd-amd64.tar.gz +5789b933facaf6136bd23f1d50add67b79bbcf8dfdfc9069a37f729395940a66 golangci-lint-1.55.2-freebsd-armv6.tar.gz +7f21ab1008d05f32c954f99470fc86a83a059e530fe2add1d0b7d8ed4d8992a7 golangci-lint-1.55.2-freebsd-armv7.tar.gz +33ab06139b9219a28251f10821da94423db30285cc2af97494cbb2a281927de9 golangci-lint-1.55.2-illumos-amd64.tar.gz +57ce6f8ce3ad6ee45d7cc3d9a047545a851c2547637834a3fcb086c7b40b1e6b golangci-lint-1.55.2-linux-386.tar.gz +ca21c961a33be3bc15e4292dc40c98c8dcc5463a7b6768a3afc123761630c09c golangci-lint-1.55.2-linux-amd64.tar.gz +8eb0cee9b1dbf0eaa49871798c7f8a5b35f2960c52d776a5f31eb7d886b92746 golangci-lint-1.55.2-linux-arm64.tar.gz +3195f3e0f37d353fd5bd415cabcd4e263f5c29d3d0ffb176c26ff3d2c75eb3bb golangci-lint-1.55.2-linux-armv6.tar.gz +c823ee36eb1a719e171de1f2f5ca3068033dce8d9817232fd10ed71fd6650406 golangci-lint-1.55.2-linux-armv7.tar.gz +758a5d2a356dc494bd13ed4c0d4bf5a54a4dc91267ea5ecdd87b86c7ca0624e7 golangci-lint-1.55.2-linux-loong64.tar.gz +2c7b9abdce7cae802a67d583cd7c6dca520bff6d0e17c8535a918e2f2b437aa0 golangci-lint-1.55.2-linux-mips64.tar.gz +024e0a15b85352cc27271285526e16a4ab66d3e67afbbe446c9808c06cb8dbed golangci-lint-1.55.2-linux-mips64le.tar.gz +6b00f89ba5506c1de1efdd9fa17c54093013a294fefd8b9b31534db626a672ee golangci-lint-1.55.2-linux-ppc64le.tar.gz +0faa0d047d9bf7b703ed3ea65b6117043c93504f9ca1de25ae929d3901c73d4a golangci-lint-1.55.2-linux-riscv64.tar.gz +30dec9b22e7d5bb4e9d5ccea96da20f71cd7db3c8cf30b8ddc7cb9174c4d742a golangci-lint-1.55.2-linux-s390x.tar.gz +5a0ede48f79ad707902fdb29be8cd2abd8302dc122b65ebae3fdfc86751c7698 golangci-lint-1.55.2-netbsd-386.tar.gz +95af20a2e617126dd5b08122ece7819101070e1582a961067ce8c41172f901ad golangci-lint-1.55.2-netbsd-amd64.tar.gz +94fb7dacb7527847cc95d7120904e19a2a0a81a0d50d61766c9e0251da72ab9d golangci-lint-1.55.2-netbsd-armv6.tar.gz +ca906bce5fee9619400e4a321c56476fe4a4efb6ac4fc989d340eb5563348873 golangci-lint-1.55.2-netbsd-armv7.tar.gz +45b442f69fc8915c4500201c0247b7f3f69544dbc9165403a61f9095f2c57355 golangci-lint-1.55.2-windows-386.zip +f57d434d231d43417dfa631587522f8c1991220b43c8ffadb9c7bd279508bf81 golangci-lint-1.55.2-windows-amd64.zip +fd7dc8f4c6829ee6fafb252a4d81d2155cd35da7833665cbb25d53ce7cecd990 golangci-lint-1.55.2-windows-arm64.zip +1892c3c24f9e7ef44b02f6750c703864b6dc350129f3ec39510300007b2376f1 golangci-lint-1.55.2-windows-armv6.zip +a5e68ae73d38748b5269fad36ac7575e3c162a5dc63ef58abdea03cc5da4522a golangci-lint-1.55.2-windows-armv7.zip # This is the builder on PPA that will build Go itself (inception-y), don't modify! +# +# This version is fine to be old and full of security holes, we just use it +# to build the latest Go. Don't change it. If it ever becomes insufficient, +# we need to switch over to a recursive builder to jump across supported +# versions. +# +# version:ppa-builder 1.19.6 +# https://go.dev/dl/ d7f0013f82e6d7f862cc6cb5c8cdb48eef5f2e239b35baa97e2f1a7466043767 go1.19.6.src.tar.gz diff --git a/build/ci.go b/build/ci.go index 3102572d4..b3f395dde 100644 --- a/build/ci.go +++ b/build/ci.go @@ -120,15 +120,16 @@ 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", // 14.04, EOL: 04/2024 + "xenial": "golang-go", // 16.04, EOL: 04/2026 + "bionic": "golang-go", // 18.04, EOL: 04/2028 + "focal": "golang-go", // 20.04, EOL: 04/2030 + "jammy": "golang-go", // 22.04, EOL: 04/2032 + "lunar": "golang-go", // 23.04, EOL: 01/2024 + "mantic": "golang-go", // 23.10, EOL: 07/2024 } debGoBootPaths = map[string]string{ @@ -136,18 +137,8 @@ var ( "golang-go": "/usr/lib/go", } - // This is the version of Go that will be downloaded by - // - // go run ci.go install -dlgo - dlgoVersion = "1.20.7" - - // This is the version of Go that will be used to bootstrap the PPA builder. - // - // This version is fine to be old and full of security holes, we just use it - // to build the latest Go. Don't change it. If it ever becomes insufficient, - // we need to switch over to a recursive builder to jumpt across supported - // versions. - gobootVersion = "1.19.6" + // This is where the tests should be unpacked. + executionSpecTestsDir = "tests/spec-tests" ) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) @@ -185,6 +176,8 @@ func main() { doWindowsInstaller(os.Args[2:]) case "purge": doPurge(os.Args[2:]) + case "sanitycheck": + doSanityCheck() default: log.Fatal("unknown command ", os.Args[1]) } @@ -206,9 +199,8 @@ func doInstall(cmdline []string) { tc := build.GoToolchain{GOARCH: *arch, CC: *cc} if *dlgo { csdb := build.MustLoadChecksums("build/checksums.txt") - tc.Root = build.DownloadGo(csdb, dlgoVersion) + tc.Root = build.DownloadGo(csdb) } - // Disable CLI markdown doc generation in release builds. buildTags := []string{"urfave_cli_no_docs"} @@ -294,14 +286,19 @@ 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") + short = flag.Bool("short", false, "Pass the 'short'-flag to go test") + 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) + tc.Root = build.DownloadGo(csdb) } gotest := tc.Go("test") @@ -311,6 +308,9 @@ func doTest(cmdline []string) { // Enable CKZG backend in CI. gotest.Args = append(gotest.Args, "-tags=ckzg") + // Enable integration-tests + gotest.Args = append(gotest.Args, "-tags=integrationtests") + // Test a single package at a time. CI builders are slow // and some tests run into timeouts under load. gotest.Args = append(gotest.Args, "-p", "1") @@ -323,6 +323,9 @@ func doTest(cmdline []string) { if *race { gotest.Args = append(gotest.Args, "-race") } + if *short { + gotest.Args = append(gotest.Args, "-short") + } packages := []string{"./..."} if len(flag.CommandLine.Args()) > 0 { @@ -332,6 +335,25 @@ func doTest(cmdline []string) { build.MustRun(gotest) } +// downloadSpecTestFixtures downloads and extracts the execution-spec-tests fixtures. +func downloadSpecTestFixtures(csdb *build.ChecksumDB, cachedir string) string { + executionSpecTestsVersion, err := build.Version(csdb, "spec-tests") + if err != nil { + log.Fatal(err) + } + ext := ".tar.gz" + base := "fixtures_develop" // 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 ( @@ -345,15 +367,17 @@ func doLint(cmdline []string) { linter := downloadLinter(*cachedir) lflags := []string{"run", "--config", ".golangci.yml"} - build.MustRunCommand(linter, append(lflags, packages...)...) + build.MustRunCommandWithOutput(linter, append(lflags, packages...)...) fmt.Println("You have achieved perfection.") } // downloadLinter downloads and unpacks golangci-lint. func downloadLinter(cachedir string) string { - const version = "1.51.1" - csdb := build.MustLoadChecksums("build/checksums.txt") + version, err := build.Version(csdb, "golangci") + if err != nil { + log.Fatal(err) + } arch := runtime.GOARCH ext := ".tar.gz" @@ -735,6 +759,10 @@ func doDebianSource(cmdline []string) { // to bootstrap the builder Go. func downloadGoBootstrapSources(cachedir string) string { csdb := build.MustLoadChecksums("build/checksums.txt") + gobootVersion, err := build.Version(csdb, "ppa-builder") + if err != nil { + log.Fatal(err) + } file := fmt.Sprintf("go%s.src.tar.gz", gobootVersion) url := "https://dl.google.com/go/" + file dst := filepath.Join(cachedir, file) @@ -747,6 +775,10 @@ func downloadGoBootstrapSources(cachedir string) string { // downloadGoSources downloads the Go source tarball. func downloadGoSources(cachedir string) string { csdb := build.MustLoadChecksums("build/checksums.txt") + dlgoVersion, err := build.Version(csdb, "golang") + if err != nil { + log.Fatal(err) + } file := fmt.Sprintf("go%s.src.tar.gz", dlgoVersion) url := "https://dl.google.com/go/" + file dst := filepath.Join(cachedir, file) @@ -1073,3 +1105,7 @@ func doPurge(cmdline []string) { log.Fatal(err) } } + +func doSanityCheck() { + build.DownloadAndVerifyChecksums(build.MustLoadChecksums("build/checksums.txt")) +} diff --git a/build/update-license.go b/build/update-license.go index 52a54bf66..70e2de06c 100644 --- a/build/update-license.go +++ b/build/update-license.go @@ -65,10 +65,8 @@ var ( "vendor/", "tests/testdata/", "build/", // don't relicense vendored sources - "cmd/internal/browser", "common/bitutil/bitutil", "common/prque/", - "consensus/ethash/xor.go", "crypto/blake2b/", "crypto/bn256/", "crypto/bls12381/", @@ -78,6 +76,7 @@ var ( "log/", "metrics/", "signer/rules/deps", + "internal/reexec", // skip special licenses "crypto/secp256k1", // Relicensed to BSD-3 via https://github.com/ethereum/go-ethereum/pull/17225 diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index 221f45c07..0149dec52 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -232,7 +232,7 @@ func abigen(c *cli.Context) error { } func main() { - log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true))) if err := app.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) diff --git a/cmd/abigen/namefilter_test.go b/cmd/abigen/namefilter_test.go index 42ba55be5..ccee71201 100644 --- a/cmd/abigen/namefilter_test.go +++ b/cmd/abigen/namefilter_test.go @@ -8,6 +8,7 @@ import ( ) func TestNameFilter(t *testing.T) { + t.Parallel() _, err := newNameFilter("Foo") require.Error(t, err) _, err = newNameFilter("too/many:colons:Foo") diff --git a/cmd/bootnode/main.go b/cmd/bootnode/main.go index 5c1635de3..350b85df1 100644 --- a/cmd/bootnode/main.go +++ b/cmd/bootnode/main.go @@ -44,7 +44,7 @@ func main() { natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|pmp:|extip:)") netrestrict = flag.String("netrestrict", "", "restrict network communication to the given IP networks (CIDR masks)") runv5 = flag.Bool("v5", false, "run a v5 topic discovery bootnode") - verbosity = flag.Int("verbosity", int(log.LvlInfo), "log verbosity (0-5)") + verbosity = flag.Int("verbosity", 3, "log verbosity (0-5)") vmodule = flag.String("vmodule", "", "log verbosity pattern") nodeKey *ecdsa.PrivateKey @@ -52,10 +52,11 @@ func main() { ) flag.Parse() - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) - glogger.Verbosity(log.Lvl(*verbosity)) + glogger := log.NewGlogHandler(log.NewTerminalHandler(os.Stderr, false)) + slogVerbosity := log.FromLegacyLevel(*verbosity) + glogger.Verbosity(slogVerbosity) glogger.Vmodule(*vmodule) - log.Root().SetHandler(glogger) + log.SetDefault(log.NewLogger(glogger)) natm, err := nat.Parse(*natdesc) if err != nil { diff --git a/cmd/clef/README.md b/cmd/clef/README.md index 85c9c7060..3a43db8c9 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -2,7 +2,7 @@ Clef can be used to sign transactions and data and is meant as a(n eventual) replacement for Geth's account management. This allows DApps to not depend on Geth's account management. When a DApp wants to sign data (or a transaction), it can send the content to Clef, which will then provide the user with context and asks for permission to sign the content. If the users grants the signing request, Clef will send the signature back to the DApp. -This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can help in situations when a DApp is connected to an untrusted remote Ethereum node, because a local one is not available, not synchronised with the chain, or is a node that has no built-in (or limited) account management. +This setup allows a DApp to connect to a remote Ethereum node and send transactions that are locally signed. This can help in situations when a DApp is connected to an untrusted remote Ethereum node, because a local one is not available, not synchronized with the chain, or is a node that has no built-in (or limited) account management. Clef can run as a daemon on the same machine, off a usb-stick like [USB armory](https://inversepath.com/usbarmory), or even a separate VM in a [QubesOS](https://www.qubes-os.org/) type setup. diff --git a/cmd/clef/consolecmd_test.go b/cmd/clef/consolecmd_test.go index 283d7e8de..c8b37f5b9 100644 --- a/cmd/clef/consolecmd_test.go +++ b/cmd/clef/consolecmd_test.go @@ -26,12 +26,13 @@ import ( // TestImportRaw tests clef --importraw func TestImportRaw(t *testing.T) { + t.Parallel() keyPath := filepath.Join(os.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name())) os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777) t.Cleanup(func() { os.Remove(keyPath) }) - t.Parallel() t.Run("happy-path", func(t *testing.T) { + t.Parallel() // Run clef importraw clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath) clef.input("myverylongpassword").input("myverylongpassword") @@ -43,6 +44,7 @@ func TestImportRaw(t *testing.T) { }) // tests clef --importraw with mismatched passwords. t.Run("pw-mismatch", func(t *testing.T) { + t.Parallel() // Run clef importraw clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath) clef.input("myverylongpassword1").input("myverylongpassword2").WaitExit() @@ -52,6 +54,7 @@ func TestImportRaw(t *testing.T) { }) // tests clef --importraw with a too short password. t.Run("short-pw", func(t *testing.T) { + t.Parallel() // Run clef importraw clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath) clef.input("shorty").input("shorty").WaitExit() @@ -64,12 +67,13 @@ func TestImportRaw(t *testing.T) { // TestListAccounts tests clef --list-accounts func TestListAccounts(t *testing.T) { + t.Parallel() keyPath := filepath.Join(os.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name())) os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777) t.Cleanup(func() { os.Remove(keyPath) }) - t.Parallel() t.Run("no-accounts", func(t *testing.T) { + t.Parallel() clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "list-accounts") if out := string(clef.Output()); !strings.Contains(out, "The keystore is empty.") { t.Logf("Output\n%v", out) @@ -77,6 +81,7 @@ func TestListAccounts(t *testing.T) { } }) t.Run("one-account", func(t *testing.T) { + t.Parallel() // First, we need to import clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath) clef.input("myverylongpassword").input("myverylongpassword").WaitExit() @@ -91,12 +96,13 @@ func TestListAccounts(t *testing.T) { // TestListWallets tests clef --list-wallets func TestListWallets(t *testing.T) { + t.Parallel() keyPath := filepath.Join(os.TempDir(), fmt.Sprintf("%v-tempkey.test", t.Name())) os.WriteFile(keyPath, []byte("0102030405060708090a0102030405060708090a0102030405060708090a0102"), 0777) t.Cleanup(func() { os.Remove(keyPath) }) - t.Parallel() t.Run("no-accounts", func(t *testing.T) { + t.Parallel() clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "list-wallets") if out := string(clef.Output()); !strings.Contains(out, "There are no wallets.") { t.Logf("Output\n%v", out) @@ -104,6 +110,7 @@ func TestListWallets(t *testing.T) { } }) t.Run("one-account", func(t *testing.T) { + t.Parallel() // First, we need to import clef := runClef(t, "--suppress-bootwarn", "--lightkdf", "importraw", keyPath) clef.input("myverylongpassword").input("myverylongpassword").WaitExit() diff --git a/cmd/clef/main.go b/cmd/clef/main.go index a8b0bf816..f9b00e4a1 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -492,7 +492,8 @@ func initialize(c *cli.Context) error { if usecolor { output = colorable.NewColorable(logOutput) } - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int(logLevelFlag.Name)), log.StreamHandler(output, log.TerminalFormat(usecolor)))) + verbosity := log.FromLegacyLevel(c.Int(logLevelFlag.Name)) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(output, verbosity, usecolor))) return nil } @@ -581,6 +582,7 @@ func accountImport(c *cli.Context) error { return err } if first != second { + //lint:ignore ST1005 This is a message for the user return errors.New("Passwords do not match") } acc, err := internalApi.ImportRawKey(hex.EncodeToString(crypto.FromECDSA(pKey)), first) @@ -702,6 +704,7 @@ func signer(c *cli.Context) error { log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc, "light-kdf", lightKdf, "advanced", advanced) am := core.StartClefAccountManager(ksLoc, nousb, lightKdf, scpath) + defer am.Close() apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage) // Establish the bidirectional communication, by creating a new UI backend and registering @@ -1206,7 +1209,7 @@ func GenDoc(ctx *cli.Context) error { URL: accounts.URL{Path: ".. ignored .."}, }, { - Address: common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"), + Address: common.MaxAddress, }, }}) } diff --git a/cmd/clef/pythonsigner.py b/cmd/clef/pythonsigner.py index b9ea1e406..5d0eb18dc 100644 --- a/cmd/clef/pythonsigner.py +++ b/cmd/clef/pythonsigner.py @@ -91,7 +91,7 @@ def approveTx(self, req): {"jsonrpc":"2.0","id":20,"method":"ui_approveTx","params":[{"transaction":{"from":"0xDEADbEeF000000000000000000000000DeaDbeEf","to":"0xDEADbEeF000000000000000000000000DeaDbeEf","gas":"0x3e8","gasPrice":"0x5","maxFeePerGas":null,"maxPriorityFeePerGas":null,"value":"0x6","nonce":"0x1","data":"0x"},"call_info":null,"meta":{"remote":"clef binary","local":"main","scheme":"in-proc","User-Agent":"","Origin":""}}]} :param transaction: transaction info - :param call_info: info abou the call, e.g. if ABI info could not be + :param call_info: info about the call, e.g. if ABI info could not be :param meta: metadata about the request, e.g. where the call comes from :return: """ # noqa: E501 diff --git a/cmd/clef/run_test.go b/cmd/clef/run_test.go index fc3145b1e..5fa6e02e1 100644 --- a/cmd/clef/run_test.go +++ b/cmd/clef/run_test.go @@ -21,8 +21,8 @@ import ( "os" "testing" - "github.com/docker/docker/pkg/reexec" "github.com/ethereum/go-ethereum/internal/cmdtest" + "github.com/ethereum/go-ethereum/internal/reexec" ) const registeredName = "clef-test" diff --git a/cmd/devp2p/README.md b/cmd/devp2p/README.md index 7922a6f37..284dfe0a4 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 @@ -108,31 +108,32 @@ Start the test by running `devp2p discv5 test -listen1 127.0.0.1 -listen2 127.0. The Eth Protocol test suite is a conformance test suite for the [eth protocol][eth]. -To run the eth protocol test suite against your implementation, the node needs to be initialized as such: +To run the eth protocol test suite against your implementation, the node needs to be initialized +with our test chain. The chain files are located in `./cmd/devp2p/internal/ethtest/testdata`. -1. initialize the geth node with the `genesis.json` file contained in the `testdata` directory -2. import the `halfchain.rlp` file in the `testdata` directory -3. run geth with the following flags: -``` -geth --datadir --nodiscover --nat=none --networkid 19763 --verbosity 5 -``` +1. initialize the geth node with the `genesis.json` file +2. import blocks from `chain.rlp` +3. run the client using the resulting database. For geth, use a command like the one below: -Then, run the following command, replacing `` with the enode of the geth node: - ``` - devp2p rlpx eth-test cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json -``` + geth \ + --datadir \ + --nodiscover \ + --nat=none \ + --networkid 3503995874084926 \ + --verbosity 5 \ + --authrpc.jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365 -Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again. +Note that the tests also require access to the engine API. +The test suite can now be executed using the devp2p tool. -#### Eth66 Test Suite + devp2p rlpx eth-test \ + --chain internal/ethtest/testdata \ + --node enode://.... \ + --engineapi http://127.0.0.1:8551 \ + --jwtsecret 0x7365637265747365637265747365637265747365637265747365637265747365 -The Eth66 test suite is also a conformance test suite for the eth 66 protocol version specifically. -To run the eth66 protocol test suite, initialize a geth node as described above and run the following command, -replacing `` with the enode of the geth node: +Repeat the above process (re-initialising the node) in order to run the Eth Protocol test suite again. - ``` - devp2p rlpx eth66-test cmd/devp2p/internal/ethtest/testdata/chain.rlp cmd/devp2p/internal/ethtest/testdata/genesis.json -``` [eth]: https://github.com/ethereum/devp2p/blob/master/caps/eth.md [dns-tutorial]: https://geth.ethereum.org/docs/developers/geth-developer/dns-discovery-setup diff --git a/cmd/devp2p/crawl.go b/cmd/devp2p/crawl.go index 8c0defff6..4288a5feb 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 { diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go index 0117c7eb8..45bcdcd36 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) @@ -229,7 +236,7 @@ func discv4Crawl(ctx *cli.Context) error { func discv4Test(ctx *cli.Context) error { // Configure test package globals. if !ctx.IsSet(remoteEnodeFlag.Name) { - return fmt.Errorf("Missing -%v", remoteEnodeFlag.Name) + return fmt.Errorf("missing -%v", remoteEnodeFlag.Name) } v4test.Remote = ctx.String(remoteEnodeFlag.Name) v4test.Listen1 = ctx.String(testListen1Flag.Name) @@ -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) { diff --git a/cmd/devp2p/discv5cmd.go b/cmd/devp2p/discv5cmd.go index c5e226f0d..0dac94526 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_cloudflare.go b/cmd/devp2p/dns_cloudflare.go index 903d754dd..a3cc69cf1 100644 --- a/cmd/devp2p/dns_cloudflare.go +++ b/cmd/devp2p/dns_cloudflare.go @@ -114,7 +114,7 @@ func (c *cloudflareClient) uploadRecords(name string, records map[string]string) records = lrecords log.Info(fmt.Sprintf("Retrieving existing TXT records on %s", name)) - entries, err := c.DNSRecords(context.Background(), c.zoneID, cloudflare.DNSRecord{Type: "TXT"}) + entries, _, err := c.ListDNSRecords(context.Background(), cloudflare.ZoneIdentifier(c.zoneID), cloudflare.ListDNSRecordsParams{Type: "TXT"}) if err != nil { return err } @@ -141,14 +141,25 @@ func (c *cloudflareClient) uploadRecords(name string, records map[string]string) if path != name { ttl = treeNodeTTLCloudflare // Max TTL permitted by Cloudflare } - record := cloudflare.DNSRecord{Type: "TXT", Name: path, Content: val, TTL: ttl} - _, err = c.CreateDNSRecord(context.Background(), c.zoneID, record) + record := cloudflare.CreateDNSRecordParams{Type: "TXT", Name: path, Content: val, TTL: ttl} + _, err = c.CreateDNSRecord(context.Background(), cloudflare.ZoneIdentifier(c.zoneID), record) } else if old.Content != val { // Entry already exists, only change its content. log.Info(fmt.Sprintf("Updating %s from %q to %q", path, old.Content, val)) updated++ - old.Content = val - err = c.UpdateDNSRecord(context.Background(), c.zoneID, old.ID, old) + + record := cloudflare.UpdateDNSRecordParams{ + Type: old.Type, + Name: old.Name, + Content: val, + Data: old.Data, + ID: old.ID, + Priority: old.Priority, + TTL: old.TTL, + Proxied: old.Proxied, + Tags: old.Tags, + } + _, err = c.UpdateDNSRecord(context.Background(), cloudflare.ZoneIdentifier(c.zoneID), record) } else { skipped++ log.Debug(fmt.Sprintf("Skipping %s = %q", path, val)) @@ -168,7 +179,7 @@ func (c *cloudflareClient) uploadRecords(name string, records map[string]string) // Stale entry, nuke it. log.Debug(fmt.Sprintf("Deleting %s = %q", path, entry.Content)) deleted++ - if err := c.DeleteDNSRecord(context.Background(), c.zoneID, entry.ID); err != nil { + if err := c.DeleteDNSRecord(context.Background(), cloudflare.ZoneIdentifier(c.zoneID), entry.ID); err != nil { return fmt.Errorf("failed to delete %s: %v", path, err) } } diff --git a/cmd/devp2p/dns_route53_test.go b/cmd/devp2p/dns_route53_test.go index e6eb516e6..af39c70a3 100644 --- a/cmd/devp2p/dns_route53_test.go +++ b/cmd/devp2p/dns_route53_test.go @@ -26,6 +26,7 @@ import ( // This test checks that computeChanges/splitChanges create DNS changes in // leaf-added -> root-changed -> leaf-deleted order. func TestRoute53ChangeSort(t *testing.T) { + t.Parallel() testTree0 := map[string]recordSet{ "2kfjogvxdqtxxugbh7gs7naaai.n": {ttl: 3333, values: []string{ `"enr:-HW4QO1ml1DdXLeZLsUxewnthhUy8eROqkDyoMTyavfks9JlYQIlMFEUoM78PovJDPQrAkrb3LRJ-""vtrymDguKCOIAWAgmlkgnY0iXNlY3AyNTZrMaEDffaGfJzgGhUif1JqFruZlYmA31HzathLSWxfbq_QoQ4"`, @@ -164,6 +165,7 @@ func TestRoute53ChangeSort(t *testing.T) { // This test checks that computeChanges compares the quoted value of the records correctly. func TestRoute53NoChange(t *testing.T) { + t.Parallel() // Existing record set. testTree0 := map[string]recordSet{ "n": {ttl: rootTTL, values: []string{ diff --git a/cmd/devp2p/internal/ethtest/chain.go b/cmd/devp2p/internal/ethtest/chain.go index 5e3d9fe98..e8b3725b1 100644 --- a/cmd/devp2p/internal/ethtest/chain.go +++ b/cmd/devp2p/internal/ethtest/chain.go @@ -17,27 +17,118 @@ package ethtest import ( + "bytes" "compress/gzip" + "crypto/ecdsa" "encoding/json" "errors" "fmt" "io" "math/big" "os" + "path" + "sort" "strings" "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/forkid" + "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/eth/protocols/eth" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/exp/slices" ) +// Chain is a lightweight blockchain-like store which can read a hivechain +// created chain. type Chain struct { - genesis core.Genesis - blocks []*types.Block - chainConfig *params.ChainConfig + genesis core.Genesis + blocks []*types.Block + state map[common.Address]state.DumpAccount // state of head block + senders map[common.Address]*senderInfo + config *params.ChainConfig +} + +// NewChain takes the given chain.rlp file, and decodes and returns +// the blocks from the file. +func NewChain(dir string) (*Chain, error) { + gen, err := loadGenesis(path.Join(dir, "genesis.json")) + if err != nil { + return nil, err + } + gblock := gen.ToBlock() + + blocks, err := blocksFromFile(path.Join(dir, "chain.rlp"), gblock) + if err != nil { + return nil, err + } + state, err := readState(path.Join(dir, "headstate.json")) + if err != nil { + return nil, err + } + accounts, err := readAccounts(path.Join(dir, "accounts.json")) + if err != nil { + return nil, err + } + return &Chain{ + genesis: gen, + blocks: blocks, + state: state, + senders: accounts, + config: gen.Config, + }, nil +} + +// senderInfo is an account record as output in the "accounts.json" file from +// hivechain. +type senderInfo struct { + Key *ecdsa.PrivateKey `json:"key"` + Nonce uint64 `json:"nonce"` +} + +// Head returns the chain head. +func (c *Chain) Head() *types.Block { + return c.blocks[c.Len()-1] +} + +// AccountsInHashOrder returns all accounts of the head state, ordered by hash of address. +func (c *Chain) AccountsInHashOrder() []state.DumpAccount { + list := make([]state.DumpAccount, len(c.state)) + i := 0 + for addr, acc := range c.state { + addr := addr + list[i] = acc + list[i].Address = &addr + if len(acc.AddressHash) != 32 { + panic(fmt.Errorf("missing/invalid SecureKey in dump account %v", addr)) + } + i++ + } + slices.SortFunc(list, func(x, y state.DumpAccount) int { + return bytes.Compare(x.AddressHash, y.AddressHash) + }) + return list +} + +// CodeHashes returns all bytecode hashes contained in the head state. +func (c *Chain) CodeHashes() []common.Hash { + var hashes []common.Hash + seen := make(map[common.Hash]struct{}) + seen[types.EmptyCodeHash] = struct{}{} + for _, acc := range c.state { + h := common.BytesToHash(acc.CodeHash) + if _, ok := seen[h]; ok { + continue + } + hashes = append(hashes, h) + seen[h] = struct{}{} + } + slices.SortFunc(hashes, (common.Hash).Cmp) + return hashes } // Len returns the length of the chain. @@ -45,6 +136,11 @@ func (c *Chain) Len() int { return len(c.blocks) } +// ForkID gets the fork id of the chain. +func (c *Chain) ForkID() forkid.ID { + return forkid.NewID(c.config, c.blocks[0], uint64(c.Len()), c.blocks[c.Len()-1].Time()) +} + // TD calculates the total difficulty of the chain at the // chain head. func (c *Chain) TD() *big.Int { @@ -55,19 +151,12 @@ func (c *Chain) TD() *big.Int { return sum } -// TotalDifficultyAt calculates the total difficulty of the chain -// at the given block height. -func (c *Chain) TotalDifficultyAt(height int) *big.Int { - sum := new(big.Int) - if height >= c.Len() { - return sum - } - for _, block := range c.blocks[:height+1] { - sum.Add(sum, block.Difficulty()) - } - return sum +// GetBlock returns the block at the specified number. +func (c *Chain) GetBlock(number int) *types.Block { + return c.blocks[number] } +// RootAt returns the state root for the block at the given height. func (c *Chain) RootAt(height int) common.Hash { if height < c.Len() { return c.blocks[height].Root() @@ -75,37 +164,56 @@ func (c *Chain) RootAt(height int) common.Hash { return 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()) +// GetSender returns the address associated with account at the index in the +// pre-funded accounts list. +func (c *Chain) GetSender(idx int) (common.Address, uint64) { + var accounts Addresses + for addr := range c.senders { + accounts = append(accounts, addr) + } + sort.Sort(accounts) + addr := accounts[idx] + return addr, c.senders[addr].Nonce } -// Shorten returns a copy chain of a desired height from the imported -func (c *Chain) Shorten(height int) *Chain { - blocks := make([]*types.Block, height) - copy(blocks, c.blocks[:height]) +// IncNonce increases the specified signing account's pending nonce. +func (c *Chain) IncNonce(addr common.Address, amt uint64) { + if _, ok := c.senders[addr]; !ok { + panic("nonce increment for non-signer") + } + c.senders[addr].Nonce += amt +} - config := *c.chainConfig - return &Chain{ - blocks: blocks, - chainConfig: &config, +// Balance returns the balance of an account at the head of the chain. +func (c *Chain) Balance(addr common.Address) *big.Int { + bal := new(big.Int) + if acc, ok := c.state[addr]; ok { + bal, _ = bal.SetString(acc.Balance, 10) } + return bal } -// Head returns the chain head. -func (c *Chain) Head() *types.Block { - return c.blocks[c.Len()-1] +// SignTx signs a transaction for the specified from account, so long as that +// account was in the hivechain accounts dump. +func (c *Chain) SignTx(from common.Address, tx *types.Transaction) (*types.Transaction, error) { + signer := types.LatestSigner(c.config) + acc, ok := c.senders[from] + if !ok { + return nil, fmt.Errorf("account not available for signing: %s", from) + } + return types.SignTx(tx, signer, acc.Key) } -func (c *Chain) GetHeaders(req *GetBlockHeaders) ([]*types.Header, error) { +// GetHeaders returns the headers base on an ethGetPacketHeadersPacket. +func (c *Chain) GetHeaders(req *eth.GetBlockHeadersPacket) ([]*types.Header, error) { if req.Amount < 1 { return nil, errors.New("no block headers requested") } - - headers := make([]*types.Header, req.Amount) - var blockNumber uint64 - - // range over blocks to check if our chain has the requested header + var ( + headers = make([]*types.Header, req.Amount) + blockNumber uint64 + ) + // Range over blocks to check if our chain has the requested header. for _, block := range c.blocks { if block.Hash() == req.Origin.Hash || block.Number().Uint64() == req.Origin.Number { headers[0] = block.Header() @@ -115,40 +223,30 @@ func (c *Chain) GetHeaders(req *GetBlockHeaders) ([]*types.Header, error) { if headers[0] == nil { return nil, fmt.Errorf("no headers found for given origin number %v, hash %v", req.Origin.Number, req.Origin.Hash) } - if req.Reverse { for i := 1; i < int(req.Amount); i++ { blockNumber -= (1 - req.Skip) headers[i] = c.blocks[blockNumber].Header() } - return headers, nil } - for i := 1; i < int(req.Amount); i++ { blockNumber += (1 + req.Skip) headers[i] = c.blocks[blockNumber].Header() } - return headers, nil } -// loadChain takes the given chain.rlp file, and decodes and returns -// the blocks from the file. -func loadChain(chainfile string, genesis string) (*Chain, error) { - gen, err := loadGenesis(genesis) - if err != nil { - return nil, err - } - gblock := gen.ToBlock() +// Shorten returns a copy chain of a desired height from the imported +func (c *Chain) Shorten(height int) *Chain { + blocks := make([]*types.Block, height) + copy(blocks, c.blocks[:height]) - blocks, err := blocksFromFile(chainfile, gblock) - if err != nil { - return nil, err + config := *c.config + return &Chain{ + blocks: blocks, + config: &config, } - - c := &Chain{genesis: gen, blocks: blocks, chainConfig: gen.Config} - return c, nil } func loadGenesis(genesisFile string) (core.Genesis, error) { @@ -163,6 +261,22 @@ func loadGenesis(genesisFile string) (core.Genesis, error) { return gen, nil } +type Addresses []common.Address + +func (a Addresses) Len() int { + return len(a) +} + +func (a Addresses) Less(i, j int) bool { + return bytes.Compare(a[i][:], a[j][:]) < 0 +} + +func (a Addresses) Swap(i, j int) { + tmp := a[i] + a[i] = a[j] + a[j] = tmp +} + func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, error) { // Load chain.rlp. fh, err := os.Open(chainfile) @@ -193,3 +307,47 @@ func blocksFromFile(chainfile string, gblock *types.Block) ([]*types.Block, erro } return blocks, nil } + +func readState(file string) (map[common.Address]state.DumpAccount, error) { + f, err := os.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("unable to read state: %v", err) + } + var dump state.Dump + if err := json.Unmarshal(f, &dump); err != nil { + return nil, fmt.Errorf("unable to unmarshal state: %v", err) + } + + state := make(map[common.Address]state.DumpAccount) + for key, acct := range dump.Accounts { + var addr common.Address + if err := addr.UnmarshalText([]byte(key)); err != nil { + return nil, fmt.Errorf("invalid address %q", key) + } + state[addr] = acct + } + return state, nil +} + +func readAccounts(file string) (map[common.Address]*senderInfo, error) { + f, err := os.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("unable to read state: %v", err) + } + type account struct { + Key hexutil.Bytes `json:"key"` + } + keys := make(map[common.Address]account) + if err := json.Unmarshal(f, &keys); err != nil { + return nil, fmt.Errorf("unable to unmarshal accounts: %v", err) + } + accounts := make(map[common.Address]*senderInfo) + for addr, acc := range keys { + pk, err := crypto.HexToECDSA(common.Bytes2Hex(acc.Key)) + if err != nil { + return nil, fmt.Errorf("unable to read private key for %s: %v", err, addr) + } + accounts[addr] = &senderInfo{Key: pk, Nonce: 0} + } + return accounts, nil +} diff --git a/cmd/devp2p/internal/ethtest/chain_test.go b/cmd/devp2p/internal/ethtest/chain_test.go index 67221923a..62bd6d26e 100644 --- a/cmd/devp2p/internal/ethtest/chain_test.go +++ b/cmd/devp2p/internal/ethtest/chain_test.go @@ -30,6 +30,7 @@ import ( // TestEthProtocolNegotiation tests whether the test suite // can negotiate the highest eth protocol in a status message exchange func TestEthProtocolNegotiation(t *testing.T) { + t.Parallel() var tests = []struct { conn *Conn caps []p2p.Cap @@ -122,30 +123,27 @@ func TestEthProtocolNegotiation(t *testing.T) { } } -// TestChain_GetHeaders tests whether the test suite can correctly +// TestChainGetHeaders tests whether the test suite can correctly // respond to a GetBlockHeaders request from a node. -func TestChain_GetHeaders(t *testing.T) { - chainFile, err := filepath.Abs("./testdata/chain.rlp") - if err != nil { - t.Fatal(err) - } - genesisFile, err := filepath.Abs("./testdata/genesis.json") +func TestChainGetHeaders(t *testing.T) { + t.Parallel() + + dir, err := filepath.Abs("./testdata") if err != nil { t.Fatal(err) } - - chain, err := loadChain(chainFile, genesisFile) + chain, err := NewChain(dir) if err != nil { t.Fatal(err) } var tests = []struct { - req GetBlockHeaders + req eth.GetBlockHeadersPacket expected []*types.Header }{ { - req: GetBlockHeaders{ - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + req: eth.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{Number: uint64(2)}, Amount: uint64(5), Skip: 1, @@ -161,8 +159,8 @@ func TestChain_GetHeaders(t *testing.T) { }, }, { - req: GetBlockHeaders{ - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + req: eth.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{Number: uint64(chain.Len() - 1)}, Amount: uint64(3), Skip: 0, @@ -176,8 +174,8 @@ func TestChain_GetHeaders(t *testing.T) { }, }, { - req: GetBlockHeaders{ - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + req: eth.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{Hash: chain.Head().Hash()}, Amount: uint64(1), Skip: 0, diff --git a/cmd/devp2p/internal/ethtest/conn.go b/cmd/devp2p/internal/ethtest/conn.go new file mode 100644 index 000000000..2d36ccb42 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/conn.go @@ -0,0 +1,361 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package ethtest + +import ( + "crypto/ecdsa" + "errors" + "fmt" + "net" + "reflect" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/protocols/eth" + "github.com/ethereum/go-ethereum/eth/protocols/snap" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/rlpx" + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + pretty = spew.ConfigState{ + Indent: " ", + DisableCapacities: true, + DisablePointerAddresses: true, + SortKeys: true, + } + timeout = 2 * time.Second +) + +// dial attempts to dial the given node and perform a handshake, returning the +// created Conn if successful. +func (s *Suite) dial() (*Conn, error) { + key, _ := crypto.GenerateKey() + return s.dialAs(key) +} + +// dialAs attempts to dial a given node and perform a handshake using the given +// private key. +func (s *Suite) dialAs(key *ecdsa.PrivateKey) (*Conn, error) { + fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP())) + if err != nil { + return nil, err + } + conn := Conn{Conn: rlpx.NewConn(fd, s.Dest.Pubkey())} + conn.ourKey = key + _, err = conn.Handshake(conn.ourKey) + if err != nil { + conn.Close() + return nil, err + } + conn.caps = []p2p.Cap{ + {Name: "eth", Version: 67}, + {Name: "eth", Version: 68}, + } + conn.ourHighestProtoVersion = 68 + return &conn, nil +} + +// dialSnap creates a connection with snap/1 capability. +func (s *Suite) dialSnap() (*Conn, error) { + conn, err := s.dial() + if err != nil { + return nil, fmt.Errorf("dial failed: %v", err) + } + conn.caps = append(conn.caps, p2p.Cap{Name: "snap", Version: 1}) + conn.ourHighestSnapProtoVersion = 1 + return conn, nil +} + +// Conn represents an individual connection with a peer +type Conn struct { + *rlpx.Conn + ourKey *ecdsa.PrivateKey + negotiatedProtoVersion uint + negotiatedSnapProtoVersion uint + ourHighestProtoVersion uint + ourHighestSnapProtoVersion uint + caps []p2p.Cap +} + +// Read reads a packet from the connection. +func (c *Conn) Read() (uint64, []byte, error) { + c.SetReadDeadline(time.Now().Add(timeout)) + code, data, _, err := c.Conn.Read() + if err != nil { + return 0, nil, err + } + return code, data, nil +} + +// ReadMsg attempts to read a devp2p message with a specific code. +func (c *Conn) ReadMsg(proto Proto, code uint64, msg any) error { + c.SetReadDeadline(time.Now().Add(timeout)) + for { + got, data, err := c.Read() + if err != nil { + return err + } + if protoOffset(proto)+code == got { + return rlp.DecodeBytes(data, msg) + } + } +} + +// Write writes a eth packet to the connection. +func (c *Conn) Write(proto Proto, code uint64, msg any) error { + c.SetWriteDeadline(time.Now().Add(timeout)) + payload, err := rlp.EncodeToBytes(msg) + if err != nil { + return err + } + _, err = c.Conn.Write(protoOffset(proto)+code, payload) + return err +} + +// ReadEth reads an Eth sub-protocol wire message. +func (c *Conn) ReadEth() (any, error) { + c.SetReadDeadline(time.Now().Add(timeout)) + for { + code, data, _, err := c.Conn.Read() + if err != nil { + return nil, err + } + if code == pingMsg { + c.Write(baseProto, pongMsg, []byte{}) + continue + } + if getProto(code) != ethProto { + // Read until eth message. + continue + } + code -= baseProtoLen + + var msg any + switch int(code) { + case eth.StatusMsg: + msg = new(eth.StatusPacket) + case eth.GetBlockHeadersMsg: + msg = new(eth.GetBlockHeadersPacket) + case eth.BlockHeadersMsg: + msg = new(eth.BlockHeadersPacket) + case eth.GetBlockBodiesMsg: + msg = new(eth.GetBlockBodiesPacket) + case eth.BlockBodiesMsg: + msg = new(eth.BlockBodiesPacket) + case eth.NewBlockMsg: + msg = new(eth.NewBlockPacket) + case eth.NewBlockHashesMsg: + msg = new(eth.NewBlockHashesPacket) + case eth.TransactionsMsg: + msg = new(eth.TransactionsPacket) + case eth.NewPooledTransactionHashesMsg: + msg = new(eth.NewPooledTransactionHashesPacket68) + case eth.GetPooledTransactionsMsg: + msg = new(eth.GetPooledTransactionsPacket) + case eth.PooledTransactionsMsg: + msg = new(eth.PooledTransactionsPacket) + default: + panic(fmt.Sprintf("unhandled eth msg code %d", code)) + } + if err := rlp.DecodeBytes(data, msg); err != nil { + return nil, fmt.Errorf("unable to decode eth msg: %v", err) + } + return msg, nil + } +} + +// ReadSnap reads a snap/1 response with the given id from the connection. +func (c *Conn) ReadSnap() (any, error) { + c.SetReadDeadline(time.Now().Add(timeout)) + for { + code, data, _, err := c.Conn.Read() + if err != nil { + return nil, err + } + if getProto(code) != snapProto { + // Read until snap message. + continue + } + code -= baseProtoLen + ethProtoLen + + var msg any + switch int(code) { + case snap.GetAccountRangeMsg: + msg = new(snap.GetAccountRangePacket) + case snap.AccountRangeMsg: + msg = new(snap.AccountRangePacket) + case snap.GetStorageRangesMsg: + msg = new(snap.GetStorageRangesPacket) + case snap.StorageRangesMsg: + msg = new(snap.StorageRangesPacket) + case snap.GetByteCodesMsg: + msg = new(snap.GetByteCodesPacket) + case snap.ByteCodesMsg: + msg = new(snap.ByteCodesPacket) + case snap.GetTrieNodesMsg: + msg = new(snap.GetTrieNodesPacket) + case snap.TrieNodesMsg: + msg = new(snap.TrieNodesPacket) + default: + panic(fmt.Errorf("unhandled snap code: %d", code)) + } + if err := rlp.DecodeBytes(data, msg); err != nil { + return nil, fmt.Errorf("could not rlp decode message: %v", err) + } + return msg, nil + } +} + +// peer performs both the protocol handshake and the status message +// exchange with the node in order to peer with it. +func (c *Conn) peer(chain *Chain, status *eth.StatusPacket) error { + if err := c.handshake(); err != nil { + return fmt.Errorf("handshake failed: %v", err) + } + if err := c.statusExchange(chain, status); err != nil { + return fmt.Errorf("status exchange failed: %v", err) + } + return nil +} + +// handshake performs a protocol handshake with the node. +func (c *Conn) handshake() error { + // Write hello to client. + pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] + ourHandshake := &protoHandshake{ + Version: 5, + Caps: c.caps, + ID: pub0, + } + if err := c.Write(baseProto, handshakeMsg, ourHandshake); err != nil { + return fmt.Errorf("write to connection failed: %v", err) + } + // Read hello from client. + code, data, err := c.Read() + if err != nil { + return fmt.Errorf("erroring reading handshake: %v", err) + } + switch code { + case handshakeMsg: + msg := new(protoHandshake) + if err := rlp.DecodeBytes(data, &msg); err != nil { + return fmt.Errorf("error decoding handshake msg: %v", err) + } + // Set snappy if version is at least 5. + if msg.Version >= 5 { + c.SetSnappy(true) + } + c.negotiateEthProtocol(msg.Caps) + if c.negotiatedProtoVersion == 0 { + return fmt.Errorf("could not negotiate eth protocol (remote caps: %v, local eth version: %v)", msg.Caps, c.ourHighestProtoVersion) + } + // If we require snap, verify that it was negotiated. + if c.ourHighestSnapProtoVersion != c.negotiatedSnapProtoVersion { + return fmt.Errorf("could not negotiate snap protocol (remote caps: %v, local snap version: %v)", msg.Caps, c.ourHighestSnapProtoVersion) + } + return nil + default: + return fmt.Errorf("bad handshake: got msg code %d", code) + } +} + +// negotiateEthProtocol sets the Conn's eth protocol version to highest +// advertised capability from peer. +func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { + var highestEthVersion uint + var highestSnapVersion uint + for _, capability := range caps { + switch capability.Name { + case "eth": + if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion { + highestEthVersion = capability.Version + } + case "snap": + if capability.Version > highestSnapVersion && capability.Version <= c.ourHighestSnapProtoVersion { + highestSnapVersion = capability.Version + } + } + } + c.negotiatedProtoVersion = highestEthVersion + c.negotiatedSnapProtoVersion = highestSnapVersion +} + +// statusExchange performs a `Status` message exchange with the given node. +func (c *Conn) statusExchange(chain *Chain, status *eth.StatusPacket) error { +loop: + for { + code, data, err := c.Read() + if err != nil { + return fmt.Errorf("failed to read from connection: %w", err) + } + switch code { + case eth.StatusMsg + protoOffset(ethProto): + msg := new(eth.StatusPacket) + if err := rlp.DecodeBytes(data, &msg); err != nil { + return fmt.Errorf("error decoding status packet: %w", err) + } + if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want { + return fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x", + want, chain.blocks[chain.Len()-1].NumberU64(), have) + } + if have, want := msg.TD.Cmp(chain.TD()), 0; have != want { + return fmt.Errorf("wrong TD in status: have %v want %v", have, want) + } + if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) { + return fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want) + } + if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) { + return fmt.Errorf("wrong protocol version: have %v, want %v", have, want) + } + break loop + case discMsg: + var msg []p2p.DiscReason + if rlp.DecodeBytes(data, &msg); len(msg) == 0 { + return errors.New("invalid disconnect message") + } + return fmt.Errorf("disconnect received: %v", pretty.Sdump(msg)) + case pingMsg: + // TODO (renaynay): in the future, this should be an error + // (PINGs should not be a response upon fresh connection) + c.Write(baseProto, pongMsg, nil) + default: + return fmt.Errorf("bad status message: code %d", code) + } + } + // make sure eth protocol version is set for negotiation + if c.negotiatedProtoVersion == 0 { + return errors.New("eth protocol version must be set in Conn") + } + if status == nil { + // default status message + status = ð.StatusPacket{ + ProtocolVersion: uint32(c.negotiatedProtoVersion), + NetworkID: chain.config.ChainID.Uint64(), + TD: chain.TD(), + Head: chain.blocks[chain.Len()-1].Hash(), + Genesis: chain.blocks[0].Hash(), + ForkID: chain.ForkID(), + } + } + if err := c.Write(ethProto, eth.StatusMsg, status); err != nil { + return fmt.Errorf("write to connection failed: %v", err) + } + return nil +} diff --git a/cmd/devp2p/internal/ethtest/engine.go b/cmd/devp2p/internal/ethtest/engine.go new file mode 100644 index 000000000..ea4fc76e6 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/engine.go @@ -0,0 +1,69 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package ethtest + +import ( + "bytes" + "fmt" + "io" + "net/http" + "os" + "path" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/golang-jwt/jwt/v4" +) + +// EngineClient is a wrapper around engine-related data. +type EngineClient struct { + url string + jwt [32]byte + headfcu []byte +} + +// NewEngineClient creates a new engine client. +func NewEngineClient(dir, url, jwt string) (*EngineClient, error) { + headfcu, err := os.ReadFile(path.Join(dir, "headfcu.json")) + if err != nil { + return nil, fmt.Errorf("failed to read headfcu: %w", err) + } + return &EngineClient{url, common.HexToHash(jwt), headfcu}, nil +} + +// token returns the jwt claim token for authorization. +func (ec *EngineClient) token() string { + claims := jwt.RegisteredClaims{IssuedAt: jwt.NewNumericDate(time.Now())} + token, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(ec.jwt[:]) + return token +} + +// sendForkchoiceUpdated sends an fcu for the head of the generated chain. +func (ec *EngineClient) sendForkchoiceUpdated() error { + var ( + req, _ = http.NewRequest(http.MethodPost, ec.url, io.NopCloser(bytes.NewReader(ec.headfcu))) + header = make(http.Header) + ) + // Set header + header.Set("accept", "application/json") + header.Set("content-type", "application/json") + header.Set("Authorization", fmt.Sprintf("Bearer %v", ec.token())) + req.Header = header + + _, err := new(http.Client).Do(req) + return err +} diff --git a/cmd/devp2p/internal/ethtest/helpers.go b/cmd/devp2p/internal/ethtest/helpers.go deleted file mode 100644 index bc901bdeb..000000000 --- a/cmd/devp2p/internal/ethtest/helpers.go +++ /dev/null @@ -1,651 +0,0 @@ -// Copyright 2021 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package ethtest - -import ( - "errors" - "fmt" - "net" - "reflect" - "strings" - "time" - - "github.com/davecgh/go-spew/spew" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/protocols/eth" - "github.com/ethereum/go-ethereum/internal/utesting" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/rlpx" -) - -var ( - pretty = spew.ConfigState{ - Indent: " ", - DisableCapacities: true, - DisablePointerAddresses: true, - SortKeys: true, - } - timeout = 20 * time.Second -) - -// dial attempts to dial the given node and perform a handshake, -// returning the created Conn if successful. -func (s *Suite) dial() (*Conn, error) { - // dial - fd, err := net.Dial("tcp", fmt.Sprintf("%v:%d", s.Dest.IP(), s.Dest.TCP())) - if err != nil { - return nil, err - } - conn := Conn{Conn: rlpx.NewConn(fd, s.Dest.Pubkey())} - // do encHandshake - conn.ourKey, _ = crypto.GenerateKey() - _, err = conn.Handshake(conn.ourKey) - if err != nil { - conn.Close() - return nil, err - } - // set default p2p capabilities - conn.caps = []p2p.Cap{ - {Name: "eth", Version: 66}, - {Name: "eth", Version: 67}, - {Name: "eth", Version: 68}, - } - conn.ourHighestProtoVersion = 68 - return &conn, nil -} - -// dialSnap creates a connection with snap/1 capability. -func (s *Suite) dialSnap() (*Conn, error) { - conn, err := s.dial() - if err != nil { - return nil, fmt.Errorf("dial failed: %v", err) - } - conn.caps = append(conn.caps, p2p.Cap{Name: "snap", Version: 1}) - conn.ourHighestSnapProtoVersion = 1 - return conn, nil -} - -// peer performs both the protocol handshake and the status message -// exchange with the node in order to peer with it. -func (c *Conn) peer(chain *Chain, status *Status) error { - if err := c.handshake(); err != nil { - return fmt.Errorf("handshake failed: %v", err) - } - if _, err := c.statusExchange(chain, status); err != nil { - return fmt.Errorf("status exchange failed: %v", err) - } - return nil -} - -// handshake performs a protocol handshake with the node. -func (c *Conn) handshake() error { - defer c.SetDeadline(time.Time{}) - c.SetDeadline(time.Now().Add(10 * time.Second)) - // write hello to client - pub0 := crypto.FromECDSAPub(&c.ourKey.PublicKey)[1:] - ourHandshake := &Hello{ - Version: 5, - Caps: c.caps, - ID: pub0, - } - if err := c.Write(ourHandshake); err != nil { - return fmt.Errorf("write to connection failed: %v", err) - } - // read hello from client - switch msg := c.Read().(type) { - case *Hello: - // set snappy if version is at least 5 - if msg.Version >= 5 { - c.SetSnappy(true) - } - c.negotiateEthProtocol(msg.Caps) - if c.negotiatedProtoVersion == 0 { - return fmt.Errorf("could not negotiate eth protocol (remote caps: %v, local eth version: %v)", msg.Caps, c.ourHighestProtoVersion) - } - // If we require snap, verify that it was negotiated - if c.ourHighestSnapProtoVersion != c.negotiatedSnapProtoVersion { - return fmt.Errorf("could not negotiate snap protocol (remote caps: %v, local snap version: %v)", msg.Caps, c.ourHighestSnapProtoVersion) - } - return nil - default: - return fmt.Errorf("bad handshake: %#v", msg) - } -} - -// negotiateEthProtocol sets the Conn's eth protocol version to highest -// advertised capability from peer. -func (c *Conn) negotiateEthProtocol(caps []p2p.Cap) { - var highestEthVersion uint - var highestSnapVersion uint - for _, capability := range caps { - switch capability.Name { - case "eth": - if capability.Version > highestEthVersion && capability.Version <= c.ourHighestProtoVersion { - highestEthVersion = capability.Version - } - case "snap": - if capability.Version > highestSnapVersion && capability.Version <= c.ourHighestSnapProtoVersion { - highestSnapVersion = capability.Version - } - } - } - c.negotiatedProtoVersion = highestEthVersion - c.negotiatedSnapProtoVersion = highestSnapVersion -} - -// statusExchange performs a `Status` message exchange with the given node. -func (c *Conn) statusExchange(chain *Chain, status *Status) (Message, error) { - defer c.SetDeadline(time.Time{}) - c.SetDeadline(time.Now().Add(20 * time.Second)) - - // read status message from client - var message Message -loop: - for { - switch msg := c.Read().(type) { - case *Status: - if have, want := msg.Head, chain.blocks[chain.Len()-1].Hash(); have != want { - return nil, fmt.Errorf("wrong head block in status, want: %#x (block %d) have %#x", - want, chain.blocks[chain.Len()-1].NumberU64(), have) - } - if have, want := msg.TD.Cmp(chain.TD()), 0; have != want { - return nil, fmt.Errorf("wrong TD in status: have %v want %v", have, want) - } - if have, want := msg.ForkID, chain.ForkID(); !reflect.DeepEqual(have, want) { - return nil, fmt.Errorf("wrong fork ID in status: have %v, want %v", have, want) - } - if have, want := msg.ProtocolVersion, c.ourHighestProtoVersion; have != uint32(want) { - return nil, fmt.Errorf("wrong protocol version: have %v, want %v", have, want) - } - message = msg - break loop - case *Disconnect: - return nil, fmt.Errorf("disconnect received: %v", msg.Reason) - case *Ping: - c.Write(&Pong{}) // TODO (renaynay): in the future, this should be an error - // (PINGs should not be a response upon fresh connection) - default: - return nil, fmt.Errorf("bad status message: %s", pretty.Sdump(msg)) - } - } - // make sure eth protocol version is set for negotiation - if c.negotiatedProtoVersion == 0 { - return nil, errors.New("eth protocol version must be set in Conn") - } - if status == nil { - // default status message - status = &Status{ - ProtocolVersion: uint32(c.negotiatedProtoVersion), - NetworkID: chain.chainConfig.ChainID.Uint64(), - TD: chain.TD(), - Head: chain.blocks[chain.Len()-1].Hash(), - Genesis: chain.blocks[0].Hash(), - ForkID: chain.ForkID(), - } - } - if err := c.Write(status); err != nil { - return nil, fmt.Errorf("write to connection failed: %v", err) - } - return message, nil -} - -// createSendAndRecvConns creates two connections, one for sending messages to the -// node, and one for receiving messages from the node. -func (s *Suite) createSendAndRecvConns() (*Conn, *Conn, error) { - sendConn, err := s.dial() - if err != nil { - return nil, nil, fmt.Errorf("dial failed: %v", err) - } - recvConn, err := s.dial() - if err != nil { - sendConn.Close() - return nil, nil, fmt.Errorf("dial failed: %v", err) - } - return sendConn, recvConn, nil -} - -// readAndServe serves GetBlockHeaders requests while waiting -// on another message from the node. -func (c *Conn) readAndServe(chain *Chain, timeout time.Duration) Message { - start := time.Now() - for time.Since(start) < timeout { - c.SetReadDeadline(time.Now().Add(10 * time.Second)) - - msg := c.Read() - switch msg := msg.(type) { - case *Ping: - c.Write(&Pong{}) - case *GetBlockHeaders: - headers, err := chain.GetHeaders(msg) - if err != nil { - return errorf("could not get headers for inbound header request: %v", err) - } - resp := &BlockHeaders{ - RequestId: msg.ReqID(), - BlockHeadersPacket: eth.BlockHeadersPacket(headers), - } - if err := c.Write(resp); err != nil { - return errorf("could not write to connection: %v", err) - } - default: - return msg - } - } - return errorf("no message received within %v", timeout) -} - -// headersRequest executes the given `GetBlockHeaders` request. -func (c *Conn) headersRequest(request *GetBlockHeaders, chain *Chain, reqID uint64) ([]*types.Header, error) { - defer c.SetReadDeadline(time.Time{}) - c.SetReadDeadline(time.Now().Add(20 * time.Second)) - - // write request - request.RequestId = reqID - if err := c.Write(request); err != nil { - return nil, fmt.Errorf("could not write to connection: %v", err) - } - - // wait for response - msg := c.waitForResponse(chain, timeout, request.RequestId) - resp, ok := msg.(*BlockHeaders) - if !ok { - return nil, fmt.Errorf("unexpected message received: %s", pretty.Sdump(msg)) - } - headers := []*types.Header(resp.BlockHeadersPacket) - return headers, nil -} - -func (c *Conn) snapRequest(msg Message, id uint64, chain *Chain) (Message, error) { - defer c.SetReadDeadline(time.Time{}) - c.SetReadDeadline(time.Now().Add(5 * time.Second)) - if err := c.Write(msg); err != nil { - return nil, fmt.Errorf("could not write to connection: %v", err) - } - return c.ReadSnap(id) -} - -// headersMatch returns whether the received headers match the given request -func headersMatch(expected []*types.Header, headers []*types.Header) bool { - return reflect.DeepEqual(expected, headers) -} - -// waitForResponse reads from the connection until a response with the expected -// request ID is received. -func (c *Conn) waitForResponse(chain *Chain, timeout time.Duration, requestID uint64) Message { - for { - msg := c.readAndServe(chain, timeout) - if msg.ReqID() == requestID { - return msg - } - } -} - -// sendNextBlock broadcasts the next block in the chain and waits -// for the node to propagate the block and import it into its chain. -func (s *Suite) sendNextBlock() error { - // set up sending and receiving connections - sendConn, recvConn, err := s.createSendAndRecvConns() - if err != nil { - return err - } - defer sendConn.Close() - defer recvConn.Close() - if err = sendConn.peer(s.chain, nil); err != nil { - return fmt.Errorf("peering failed: %v", err) - } - if err = recvConn.peer(s.chain, nil); err != nil { - return fmt.Errorf("peering failed: %v", err) - } - // create new block announcement - nextBlock := s.fullChain.blocks[s.chain.Len()] - blockAnnouncement := &NewBlock{ - Block: nextBlock, - TD: s.fullChain.TotalDifficultyAt(s.chain.Len()), - } - // send announcement and wait for node to request the header - if err = s.testAnnounce(sendConn, recvConn, blockAnnouncement); err != nil { - return fmt.Errorf("failed to announce block: %v", err) - } - // wait for client to update its chain - if err = s.waitForBlockImport(recvConn, nextBlock); err != nil { - return fmt.Errorf("failed to receive confirmation of block import: %v", err) - } - // update test suite chain - s.chain.blocks = append(s.chain.blocks, nextBlock) - return nil -} - -// testAnnounce writes a block announcement to the node and waits for the node -// to propagate it. -func (s *Suite) testAnnounce(sendConn, receiveConn *Conn, blockAnnouncement *NewBlock) error { - if err := sendConn.Write(blockAnnouncement); err != nil { - return fmt.Errorf("could not write to connection: %v", err) - } - return s.waitAnnounce(receiveConn, blockAnnouncement) -} - -// waitAnnounce waits for a NewBlock or NewBlockHashes announcement from the node. -func (s *Suite) waitAnnounce(conn *Conn, blockAnnouncement *NewBlock) error { - for { - switch msg := conn.readAndServe(s.chain, timeout).(type) { - case *NewBlock: - if !reflect.DeepEqual(blockAnnouncement.Block.Header(), msg.Block.Header()) { - return fmt.Errorf("wrong header in block announcement: \nexpected %v "+ - "\ngot %v", blockAnnouncement.Block.Header(), msg.Block.Header()) - } - if !reflect.DeepEqual(blockAnnouncement.TD, msg.TD) { - return fmt.Errorf("wrong TD in announcement: expected %v, got %v", blockAnnouncement.TD, msg.TD) - } - return nil - case *NewBlockHashes: - hashes := *msg - if blockAnnouncement.Block.Hash() != hashes[0].Hash { - return fmt.Errorf("wrong block hash in announcement: expected %v, got %v", blockAnnouncement.Block.Hash(), hashes[0].Hash) - } - return nil - - // ignore tx announcements from previous tests - case *NewPooledTransactionHashes66: - continue - case *NewPooledTransactionHashes: - continue - case *Transactions: - continue - - default: - return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) - } - } -} - -func (s *Suite) waitForBlockImport(conn *Conn, block *types.Block) error { - defer conn.SetReadDeadline(time.Time{}) - conn.SetReadDeadline(time.Now().Add(20 * time.Second)) - // create request - req := &GetBlockHeaders{ - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ - Origin: eth.HashOrNumber{Hash: block.Hash()}, - Amount: 1, - }, - } - - // loop until BlockHeaders response contains desired block, confirming the - // node imported the block - for { - requestID := uint64(54) - headers, err := conn.headersRequest(req, s.chain, requestID) - if err != nil { - return fmt.Errorf("GetBlockHeader request failed: %v", err) - } - // if headers response is empty, node hasn't imported block yet, try again - if len(headers) == 0 { - time.Sleep(100 * time.Millisecond) - continue - } - if !reflect.DeepEqual(block.Header(), headers[0]) { - return fmt.Errorf("wrong header returned: wanted %v, got %v", block.Header(), headers[0]) - } - return nil - } -} - -func (s *Suite) oldAnnounce() error { - sendConn, receiveConn, err := s.createSendAndRecvConns() - if err != nil { - return err - } - defer sendConn.Close() - defer receiveConn.Close() - if err := sendConn.peer(s.chain, nil); err != nil { - return fmt.Errorf("peering failed: %v", err) - } - if err := receiveConn.peer(s.chain, nil); err != nil { - return fmt.Errorf("peering failed: %v", err) - } - // create old block announcement - oldBlockAnnounce := &NewBlock{ - Block: s.chain.blocks[len(s.chain.blocks)/2], - TD: s.chain.blocks[len(s.chain.blocks)/2].Difficulty(), - } - if err := sendConn.Write(oldBlockAnnounce); err != nil { - return fmt.Errorf("could not write to connection: %v", err) - } - // wait to see if the announcement is propagated - switch msg := receiveConn.readAndServe(s.chain, time.Second*8).(type) { - case *NewBlock: - block := *msg - if block.Block.Hash() == oldBlockAnnounce.Block.Hash() { - return fmt.Errorf("unexpected: block propagated: %s", pretty.Sdump(msg)) - } - case *NewBlockHashes: - hashes := *msg - for _, hash := range hashes { - if hash.Hash == oldBlockAnnounce.Block.Hash() { - return fmt.Errorf("unexpected: block announced: %s", pretty.Sdump(msg)) - } - } - case *Error: - errMsg := *msg - // check to make sure error is timeout (propagation didn't come through == test successful) - if !strings.Contains(errMsg.String(), "timeout") { - return fmt.Errorf("unexpected error: %v", pretty.Sdump(msg)) - } - default: - return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) - } - return nil -} - -func (s *Suite) maliciousHandshakes(t *utesting.T) error { - conn, err := s.dial() - if err != nil { - return fmt.Errorf("dial failed: %v", err) - } - defer conn.Close() - - // write hello to client - pub0 := crypto.FromECDSAPub(&conn.ourKey.PublicKey)[1:] - handshakes := []*Hello{ - { - Version: 5, - Caps: []p2p.Cap{ - {Name: largeString(2), Version: 64}, - }, - ID: pub0, - }, - { - Version: 5, - Caps: []p2p.Cap{ - {Name: "eth", Version: 64}, - {Name: "eth", Version: 65}, - }, - ID: append(pub0, byte(0)), - }, - { - Version: 5, - Caps: []p2p.Cap{ - {Name: "eth", Version: 64}, - {Name: "eth", Version: 65}, - }, - ID: append(pub0, pub0...), - }, - { - Version: 5, - Caps: []p2p.Cap{ - {Name: "eth", Version: 64}, - {Name: "eth", Version: 65}, - }, - ID: largeBuffer(2), - }, - { - Version: 5, - Caps: []p2p.Cap{ - {Name: largeString(2), Version: 64}, - }, - ID: largeBuffer(2), - }, - } - for i, handshake := range handshakes { - t.Logf("Testing malicious handshake %v\n", i) - if err := conn.Write(handshake); err != nil { - return fmt.Errorf("could not write to connection: %v", err) - } - // check that the peer disconnected - for i := 0; i < 2; i++ { - switch msg := conn.readAndServe(s.chain, 20*time.Second).(type) { - case *Disconnect: - case *Error: - case *Hello: - // Discard one hello as Hello's are sent concurrently - continue - default: - return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) - } - } - // dial for the next round - conn, err = s.dial() - if err != nil { - return fmt.Errorf("dial failed: %v", err) - } - } - return nil -} - -func (s *Suite) maliciousStatus(conn *Conn) error { - if err := conn.handshake(); err != nil { - return fmt.Errorf("handshake failed: %v", err) - } - status := &Status{ - ProtocolVersion: uint32(conn.negotiatedProtoVersion), - NetworkID: s.chain.chainConfig.ChainID.Uint64(), - TD: largeNumber(2), - Head: s.chain.blocks[s.chain.Len()-1].Hash(), - Genesis: s.chain.blocks[0].Hash(), - ForkID: s.chain.ForkID(), - } - - // get status - msg, err := conn.statusExchange(s.chain, status) - if err != nil { - return fmt.Errorf("status exchange failed: %v", err) - } - switch msg := msg.(type) { - case *Status: - default: - return fmt.Errorf("expected status, got: %#v ", msg) - } - - // wait for disconnect - switch msg := conn.readAndServe(s.chain, timeout).(type) { - case *Disconnect: - return nil - case *Error: - return nil - default: - return fmt.Errorf("expected disconnect, got: %s", pretty.Sdump(msg)) - } -} - -func (s *Suite) hashAnnounce() error { - // create connections - sendConn, recvConn, err := s.createSendAndRecvConns() - if err != nil { - return fmt.Errorf("failed to create connections: %v", err) - } - defer sendConn.Close() - defer recvConn.Close() - if err := sendConn.peer(s.chain, nil); err != nil { - return fmt.Errorf("peering failed: %v", err) - } - if err := recvConn.peer(s.chain, nil); err != nil { - return fmt.Errorf("peering failed: %v", err) - } - - // create NewBlockHashes announcement - type anno struct { - Hash common.Hash // Hash of one particular block being announced - Number uint64 // Number of one particular block being announced - } - nextBlock := s.fullChain.blocks[s.chain.Len()] - announcement := anno{Hash: nextBlock.Hash(), Number: nextBlock.Number().Uint64()} - newBlockHash := &NewBlockHashes{announcement} - if err := sendConn.Write(newBlockHash); err != nil { - return fmt.Errorf("failed to write to connection: %v", err) - } - - // Announcement sent, now wait for a header request - msg := sendConn.Read() - blockHeaderReq, ok := msg.(*GetBlockHeaders) - if !ok { - return fmt.Errorf("unexpected %s", pretty.Sdump(msg)) - } - if blockHeaderReq.Amount != 1 { - return fmt.Errorf("unexpected number of block headers requested: %v", blockHeaderReq.Amount) - } - if blockHeaderReq.Origin.Hash != announcement.Hash { - return fmt.Errorf("unexpected block header requested. Announced:\n %v\n Remote request:\n%v", - pretty.Sdump(announcement), - pretty.Sdump(blockHeaderReq)) - } - err = sendConn.Write(&BlockHeaders{ - RequestId: blockHeaderReq.ReqID(), - BlockHeadersPacket: eth.BlockHeadersPacket{nextBlock.Header()}, - }) - if err != nil { - return fmt.Errorf("failed to write to connection: %v", err) - } - - // wait for block announcement - msg = recvConn.readAndServe(s.chain, timeout) - switch msg := msg.(type) { - case *NewBlockHashes: - hashes := *msg - if len(hashes) != 1 { - return fmt.Errorf("unexpected new block hash announcement: wanted 1 announcement, got %d", len(hashes)) - } - if nextBlock.Hash() != hashes[0].Hash { - return fmt.Errorf("unexpected block hash announcement, wanted %v, got %v", nextBlock.Hash(), - hashes[0].Hash) - } - - case *NewBlock: - // node should only propagate NewBlock without having requested the body if the body is empty - nextBlockBody := nextBlock.Body() - if len(nextBlockBody.Transactions) != 0 || len(nextBlockBody.Uncles) != 0 { - return fmt.Errorf("unexpected non-empty new block propagated: %s", pretty.Sdump(msg)) - } - if msg.Block.Hash() != nextBlock.Hash() { - return fmt.Errorf("mismatched hash of propagated new block: wanted %v, got %v", - nextBlock.Hash(), msg.Block.Hash()) - } - // check to make sure header matches header that was sent to the node - if !reflect.DeepEqual(nextBlock.Header(), msg.Block.Header()) { - return fmt.Errorf("incorrect header received: wanted %v, got %v", nextBlock.Header(), msg.Block.Header()) - } - default: - return fmt.Errorf("unexpected: %s", pretty.Sdump(msg)) - } - // confirm node imported block - if err := s.waitForBlockImport(recvConn, nextBlock); err != nil { - return fmt.Errorf("error waiting for node to import new block: %v", err) - } - // update the chain - s.chain.blocks = append(s.chain.blocks, nextBlock) - return nil -} diff --git a/cmd/devp2p/internal/ethtest/large.go b/cmd/devp2p/internal/ethtest/large.go deleted file mode 100644 index 40626c206..000000000 --- a/cmd/devp2p/internal/ethtest/large.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2020 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package ethtest - -import ( - "crypto/rand" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" -) - -// largeNumber returns a very large big.Int. -func largeNumber(megabytes int) *big.Int { - buf := make([]byte, megabytes*1024*1024) - rand.Read(buf) - bigint := new(big.Int) - bigint.SetBytes(buf) - return bigint -} - -// largeBuffer returns a very large buffer. -func largeBuffer(megabytes int) []byte { - buf := make([]byte, megabytes*1024*1024) - rand.Read(buf) - return buf -} - -// largeString returns a very large string. -func largeString(megabytes int) string { - buf := make([]byte, megabytes*1024*1024) - rand.Read(buf) - return hexutil.Encode(buf) -} - -func largeBlock() *types.Block { - return types.NewBlockWithHeader(largeHeader()) -} - -// Returns a random hash -func randHash() common.Hash { - var h common.Hash - rand.Read(h[:]) - return h -} - -func largeHeader() *types.Header { - return &types.Header{ - MixDigest: randHash(), - ReceiptHash: randHash(), - TxHash: randHash(), - Nonce: types.BlockNonce{}, - Extra: []byte{}, - Bloom: types.Bloom{}, - GasUsed: 0, - Coinbase: common.Address{}, - GasLimit: 0, - UncleHash: types.EmptyUncleHash, - Time: 1337, - ParentHash: randHash(), - Root: randHash(), - Number: largeNumber(2), - Difficulty: largeNumber(2), - } -} diff --git a/cmd/devp2p/internal/ethtest/mkchain.sh b/cmd/devp2p/internal/ethtest/mkchain.sh new file mode 100644 index 000000000..b9253e8ca --- /dev/null +++ b/cmd/devp2p/internal/ethtest/mkchain.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +hivechain generate \ + --fork-interval 6 \ + --tx-interval 1 \ + --length 500 \ + --outdir testdata \ + --lastfork cancun \ + --outputs accounts,genesis,chain,headstate,txinfo,headblock,headfcu,newpayload,forkenv diff --git a/cmd/devp2p/internal/ethtest/protocol.go b/cmd/devp2p/internal/ethtest/protocol.go new file mode 100644 index 000000000..f5f5f7e48 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/protocol.go @@ -0,0 +1,87 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . +package ethtest + +import ( + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rlp" +) + +// Unexported devp2p message codes from p2p/peer.go. +const ( + handshakeMsg = 0x00 + discMsg = 0x01 + pingMsg = 0x02 + pongMsg = 0x03 +) + +// Unexported devp2p protocol lengths from p2p package. +const ( + baseProtoLen = 16 + ethProtoLen = 17 + snapProtoLen = 8 +) + +// Unexported handshake structure from p2p/peer.go. +type protoHandshake struct { + Version uint64 + Name string + Caps []p2p.Cap + ListenPort uint64 + ID []byte + Rest []rlp.RawValue `rlp:"tail"` +} + +type Hello = protoHandshake + +// Proto is an enum representing devp2p protocol types. +type Proto int + +const ( + baseProto Proto = iota + ethProto + snapProto +) + +// getProto returns the protocol a certain message code is associated with +// (assuming the negotiated capabilities are exactly {eth,snap}) +func getProto(code uint64) Proto { + switch { + case code < baseProtoLen: + return baseProto + case code < baseProtoLen+ethProtoLen: + return ethProto + case code < baseProtoLen+ethProtoLen+snapProtoLen: + return snapProto + default: + panic("unhandled msg code beyond last protocol") + } +} + +// protoOffset will return the offset at which the specified protocol's messages +// begin. +func protoOffset(proto Proto) uint64 { + switch proto { + case baseProto: + return 0 + case ethProto: + return baseProtoLen + case snapProto: + return baseProtoLen + ethProtoLen + default: + panic("unhandled protocol") + } +} diff --git a/cmd/devp2p/internal/ethtest/snap.go b/cmd/devp2p/internal/ethtest/snap.go index f947e4bc9..64e063358 100644 --- a/cmd/devp2p/internal/ethtest/snap.go +++ b/cmd/devp2p/internal/ethtest/snap.go @@ -20,18 +20,28 @@ import ( "bytes" "errors" "fmt" + "math/big" "math/rand" + "reflect" "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/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" ) +func (c *Conn) snapRequest(code uint64, msg any) (any, error) { + if err := c.Write(snapProto, code, msg); err != nil { + return nil, fmt.Errorf("could not write to connection: %v", err) + } + return c.ReadSnap() +} + func (s *Suite) TestSnapStatus(t *utesting.T) { conn, err := s.dialSnap() if err != nil { @@ -44,74 +54,269 @@ func (s *Suite) TestSnapStatus(t *utesting.T) { } type accRangeTest struct { - nBytes uint64 - root common.Hash - origin common.Hash - limit common.Hash + nBytes uint64 + root common.Hash + startingHash common.Hash + limitHash common.Hash expAccounts int expFirst common.Hash expLast common.Hash + + desc string } // TestSnapGetAccountRange various forms of GetAccountRange requests. func (s *Suite) TestSnapGetAccountRange(t *utesting.T) { var ( - root = s.chain.RootAt(999) - ffHash = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - zero = common.Hash{} - firstKeyMinus1 = common.HexToHash("0x00bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf29") - firstKey = common.HexToHash("0x00bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a") - firstKeyPlus1 = common.HexToHash("0x00bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2b") - secondKey = common.HexToHash("0x09e47cd5056a689e708f22fe1f932709a320518e444f5f7d8d46a3da523d6606") - storageRoot = common.HexToHash("0xbe3d75a1729be157e79c3b77f00206db4d54e3ea14375a015451c88ec067c790") + ffHash = common.MaxHash + zero = common.Hash{} + + // test values derived from chain/ account dump + root = s.chain.Head().Root() + headstate = s.chain.AccountsInHashOrder() + firstKey = common.BytesToHash(headstate[0].AddressHash) + secondKey = common.BytesToHash(headstate[1].AddressHash) + storageRoot = findNonEmptyStorageRoot(headstate) ) - for i, tc := range []accRangeTest{ + + tests := []accRangeTest{ // Tests decreasing the number of bytes - {4000, root, zero, ffHash, 76, firstKey, common.HexToHash("0xd2669dcf3858e7f1eecb8b5fedbf22fbea3e9433848a75035f79d68422c2dcda")}, - {3000, root, zero, ffHash, 57, firstKey, common.HexToHash("0x9b63fa753ece5cb90657d02ecb15df4dc1508d8c1d187af1bf7f1a05e747d3c7")}, - {2000, root, zero, ffHash, 38, firstKey, common.HexToHash("0x5e6140ecae4354a9e8f47559a8c6209c1e0e69cb077b067b528556c11698b91f")}, - {1, root, zero, ffHash, 1, firstKey, firstKey}, + { + nBytes: 4000, + root: root, + startingHash: zero, + limitHash: ffHash, + expAccounts: 86, + expFirst: firstKey, + expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"), + desc: "In this test, we request the entire state range, but limit the response to 4000 bytes.", + }, + { + nBytes: 3000, + root: root, + startingHash: zero, + limitHash: ffHash, + expAccounts: 65, + expFirst: firstKey, + expLast: common.HexToHash("0x2e6fe1362b3e388184fd7bf08e99e74170b26361624ffd1c5f646da7067b58b6"), + desc: "In this test, we request the entire state range, but limit the response to 3000 bytes.", + }, + { + nBytes: 2000, + root: root, + startingHash: zero, + limitHash: ffHash, + expAccounts: 44, + expFirst: firstKey, + expLast: common.HexToHash("0x1c3f74249a4892081ba0634a819aec9ed25f34c7653f5719b9098487e65ab595"), + desc: "In this test, we request the entire state range, but limit the response to 2000 bytes.", + }, + { + nBytes: 1, + root: root, + startingHash: zero, + limitHash: ffHash, + expAccounts: 1, + expFirst: firstKey, + expLast: firstKey, + desc: `In this test, we request the entire state range, but limit the response to 1 byte. +The server should return the first account of the state.`, + }, + { + nBytes: 0, + root: root, + startingHash: zero, + limitHash: ffHash, + expAccounts: 1, + expFirst: firstKey, + expLast: firstKey, + desc: `Here we request with a responseBytes limit of zero. +The server should return one account.`, + }, // Tests variations of the range - // - // [00b to firstkey]: should return [firstkey, secondkey], where secondkey is out of bounds - {4000, root, common.HexToHash("0x00bf000000000000000000000000000000000000000000000000000000000000"), common.HexToHash("0x00bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2b"), 2, firstKey, secondKey}, - // [00b0 to 0bf0]: where both are before firstkey. Should return firstKey (even though it's out of bounds) - {4000, root, common.HexToHash("0x00b0000000000000000000000000000000000000000000000000000000000000"), common.HexToHash("0x00bf100000000000000000000000000000000000000000000000000000000000"), 1, firstKey, firstKey}, - {4000, root, zero, zero, 1, firstKey, firstKey}, - {4000, root, firstKey, ffHash, 76, firstKey, common.HexToHash("0xd2669dcf3858e7f1eecb8b5fedbf22fbea3e9433848a75035f79d68422c2dcda")}, - {4000, root, firstKeyPlus1, ffHash, 76, secondKey, common.HexToHash("0xd28f55d3b994f16389f36944ad685b48e0fc3f8fbe86c3ca92ebecadf16a783f")}, + { + nBytes: 4000, + root: root, + startingHash: hashAdd(firstKey, -500), + limitHash: hashAdd(firstKey, 1), + expAccounts: 2, + expFirst: firstKey, + expLast: secondKey, + desc: `In this test, we request a range where startingHash is before the first available +account key, and limitHash is after. The server should return the first and second +account of the state (because the second account is the 'next available').`, + }, + + { + nBytes: 4000, + root: root, + startingHash: hashAdd(firstKey, -500), + limitHash: hashAdd(firstKey, -450), + expAccounts: 1, + expFirst: firstKey, + expLast: firstKey, + desc: `Here we request range where both bounds are before the first available account key. +This should return the first account (even though it's out of bounds).`, + }, + + // More range tests: + { + nBytes: 4000, + root: root, + startingHash: zero, + limitHash: zero, + expAccounts: 1, + expFirst: firstKey, + expLast: firstKey, + desc: `In this test, both startingHash and limitHash are zero. +The server should return the first available account.`, + }, + { + nBytes: 4000, + root: root, + startingHash: firstKey, + limitHash: ffHash, + expAccounts: 86, + expFirst: firstKey, + expLast: common.HexToHash("0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099"), + desc: `In this test, startingHash is exactly the first available account key. +The server should return the first available account of the state as the first item.`, + }, + { + nBytes: 4000, + root: root, + startingHash: hashAdd(firstKey, 1), + limitHash: ffHash, + expAccounts: 86, + expFirst: secondKey, + expLast: common.HexToHash("0x4615e5f5df5b25349a00ad313c6cd0436b6c08ee5826e33a018661997f85ebaa"), + desc: `In this test, startingHash is after the first available key. +The server should return the second account of the state as the first item.`, + }, // Test different root hashes - // - // A stateroot that does not exist - {4000, common.Hash{0x13, 37}, zero, ffHash, 0, zero, zero}, + + { + nBytes: 4000, + root: common.Hash{0x13, 0x37}, + startingHash: zero, + limitHash: ffHash, + expAccounts: 0, + expFirst: zero, + expLast: zero, + desc: `This test requests a non-existent state root.`, + }, + // The genesis stateroot (we expect it to not be served) - {4000, s.chain.RootAt(0), zero, ffHash, 0, zero, zero}, - // A 127 block old stateroot, expected to be served - {4000, s.chain.RootAt(999 - 127), zero, ffHash, 77, firstKey, common.HexToHash("0xe4c6fdef5dd4e789a2612390806ee840b8ec0fe52548f8b4efe41abb20c37aac")}, - // A root which is not actually an account root, but a storage root - {4000, storageRoot, zero, ffHash, 0, zero, zero}, + { + nBytes: 4000, + root: s.chain.RootAt(0), + startingHash: zero, + limitHash: ffHash, + expAccounts: 0, + expFirst: zero, + expLast: zero, + desc: `This test requests data at the state root of the genesis block. We expect the +server to return no data because genesis is older than 127 blocks.`, + }, + + { + nBytes: 4000, + root: s.chain.RootAt(int(s.chain.Head().Number().Uint64()) - 127), + startingHash: zero, + limitHash: ffHash, + expAccounts: 84, + expFirst: firstKey, + expLast: common.HexToHash("0x580aa878e2f92d113a12c0a3ce3c21972b03dbe80786858d49a72097e2c491a3"), + desc: `This test requests data at a state root that is 127 blocks old. +We expect the server to have this state available.`, + }, + + { + nBytes: 4000, + root: storageRoot, + startingHash: zero, + limitHash: ffHash, + expAccounts: 0, + expFirst: zero, + expLast: zero, + desc: `This test requests data at a state root that is actually the storage root of +an existing account. The server is supposed to ignore this request.`, + }, // And some non-sensical requests - // - // range from [0xFF to 0x00], wrong order. Expect not to be serviced - {4000, root, ffHash, zero, 0, zero, zero}, - // range from [firstkey, firstkey-1], wrong order. Expect to get first key. - {4000, root, firstKey, firstKeyMinus1, 1, firstKey, firstKey}, + + { + nBytes: 4000, + root: root, + startingHash: ffHash, + limitHash: zero, + expAccounts: 0, + expFirst: zero, + expLast: zero, + desc: `In this test, the startingHash is after limitHash (wrong order). The server +should ignore this invalid request.`, + }, + + { + nBytes: 4000, + root: root, + startingHash: firstKey, + limitHash: hashAdd(firstKey, -1), + expAccounts: 1, + expFirst: firstKey, + expLast: firstKey, + desc: `In this test, the startingHash is the first available key, and limitHash is +a key before startingHash (wrong order). The server should return the first available key.`, + }, + // range from [firstkey, 0], wrong order. Expect to get first key. - {4000, root, firstKey, zero, 1, firstKey, firstKey}, - // Max bytes: 0. Expect to deliver one account. - {0, root, zero, ffHash, 1, firstKey, firstKey}, - } { + { + nBytes: 4000, + root: root, + startingHash: firstKey, + limitHash: zero, + expAccounts: 1, + expFirst: firstKey, + expLast: firstKey, + desc: `In this test, the startingHash is the first available key and limitHash is zero. +(wrong order). The server should return the first available key.`, + }, + } + + for i, tc := range tests { tc := tc + if i > 0 { + t.Log("\n") + } + t.Logf("-- Test %d", i) + t.Log(tc.desc) + t.Log(" request:") + t.Logf(" root: %x", tc.root) + t.Logf(" range: %#x - %#x", tc.startingHash, tc.limitHash) + t.Logf(" responseBytes: %d", tc.nBytes) if err := s.snapGetAccountRange(t, &tc); err != nil { - t.Errorf("test %d \n root: %x\n range: %#x - %#x\n bytes: %d\nfailed: %v", i, tc.root, tc.origin, tc.limit, tc.nBytes, err) + t.Errorf("test %d failed: %v", i, err) } } } +func hashAdd(h common.Hash, n int64) common.Hash { + hb := h.Big() + return common.BigToHash(hb.Add(hb, big.NewInt(n))) +} + +func findNonEmptyStorageRoot(accounts []state.DumpAccount) common.Hash { + for i := range accounts { + if len(accounts[i].Storage) != 0 { + return common.BytesToHash(accounts[i].Root) + } + } + panic("can't find account with non-empty storage") +} + type stRangesTest struct { root common.Hash accounts []common.Hash @@ -119,87 +324,125 @@ type stRangesTest struct { limit []byte nBytes uint64 - expSlots int + expSlots [][]*snap.StorageData + + desc string } // TestSnapGetStorageRanges various forms of GetStorageRanges requests. func (s *Suite) TestSnapGetStorageRanges(t *utesting.T) { var ( - ffHash = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + acct = common.HexToAddress("0x8bebc8ba651aee624937e7d897853ac30c95a067") + acctHash = common.BytesToHash(s.chain.state[acct].AddressHash) + ffHash = common.MaxHash zero = common.Hash{} - firstKey = common.HexToHash("0x00bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a") - secondKey = common.HexToHash("0x09e47cd5056a689e708f22fe1f932709a320518e444f5f7d8d46a3da523d6606") + blockroot = s.chain.Head().Root() ) - for i, tc := range []stRangesTest{ + + // These are the storage slots of the test account, encoded as snap response data. + acctSlots := []*snap.StorageData{ { - root: s.chain.RootAt(999), - accounts: []common.Hash{secondKey, firstKey}, - origin: zero[:], - limit: ffHash[:], - nBytes: 500, - expSlots: 0, + Hash: common.HexToHash("0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace"), + Body: []byte{0x02}, + }, + { + Hash: common.HexToHash("0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6"), + Body: []byte{0x01}, + }, + { + Hash: common.HexToHash("0xc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b"), + Body: []byte{0x03}, }, + } + tests := []stRangesTest{ /* Some tests against this account: - { - "balance": "0", - "nonce": 1, - "root": "0xbe3d75a1729be157e79c3b77f00206db4d54e3ea14375a015451c88ec067c790", - "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "storage": { - "0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace": "02", - "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6": "01", - "0xc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b": "03" - }, - "key": "0xf493f79c43bd747129a226ad42529885a4b108aba6046b2d12071695a6627844" + + "0x8bebc8ba651aee624937e7d897853ac30c95a067": { + "balance": "1", + "nonce": 1, + "root": "0xe318dff15b33aa7f2f12d5567d58628e3e3f2e8859e46b56981a4083b391da17", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + // Note: keys below are hashed!!! + "0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace": "02", + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6": "01", + "0xc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b": "03" + }, + "key": "0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099" } */ + { // [:] -> [slot1, slot2, slot3] - root: s.chain.RootAt(999), - accounts: []common.Hash{common.HexToHash("0xf493f79c43bd747129a226ad42529885a4b108aba6046b2d12071695a6627844")}, + desc: `This request has a range of 00..ff. +The server should return all storage slots of the test account.`, + root: blockroot, + accounts: []common.Hash{acctHash}, origin: zero[:], limit: ffHash[:], nBytes: 500, - expSlots: 3, + expSlots: [][]*snap.StorageData{acctSlots}, }, + { // [slot1:] -> [slot1, slot2, slot3] - root: s.chain.RootAt(999), - accounts: []common.Hash{common.HexToHash("0xf493f79c43bd747129a226ad42529885a4b108aba6046b2d12071695a6627844")}, + desc: `This test requests slots starting at the first available key. +The server should return all storage slots of the test account.`, + root: blockroot, + accounts: []common.Hash{acctHash}, origin: common.FromHex("0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace"), limit: ffHash[:], - nBytes: 500, - expSlots: 3, + nBytes: 1000, + expSlots: [][]*snap.StorageData{acctSlots}, }, - { // [slot1+ :] -> [slot2, slot3] - root: s.chain.RootAt(999), - accounts: []common.Hash{common.HexToHash("0xf493f79c43bd747129a226ad42529885a4b108aba6046b2d12071695a6627844")}, + + { // [slot1+:] -> [slot2, slot3] + desc: `This test requests slots starting at a key one past the first available key. +The server should return the remaining two slots of the test account.`, + root: blockroot, + accounts: []common.Hash{acctHash}, origin: common.FromHex("0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5acf"), limit: ffHash[:], nBytes: 500, - expSlots: 2, + expSlots: [][]*snap.StorageData{acctSlots[1:]}, }, + { // [slot1:slot2] -> [slot1, slot2] - root: s.chain.RootAt(999), - accounts: []common.Hash{common.HexToHash("0xf493f79c43bd747129a226ad42529885a4b108aba6046b2d12071695a6627844")}, + desc: `This test requests a range which is exactly the first and second available key.`, + root: blockroot, + accounts: []common.Hash{acctHash}, origin: common.FromHex("0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace"), limit: common.FromHex("0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6"), nBytes: 500, - expSlots: 2, + expSlots: [][]*snap.StorageData{acctSlots[:2]}, }, + { // [slot1+:slot2+] -> [slot2, slot3] - root: s.chain.RootAt(999), - accounts: []common.Hash{common.HexToHash("0xf493f79c43bd747129a226ad42529885a4b108aba6046b2d12071695a6627844")}, + desc: `This test requests a range where limitHash is after the second, but before the third slot +of the test account. The server should return slots [2,3] (i.e. the 'next available' needs to be returned).`, + root: blockroot, + accounts: []common.Hash{acctHash}, origin: common.FromHex("0x4fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), limit: common.FromHex("0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf7"), nBytes: 500, - expSlots: 2, + expSlots: [][]*snap.StorageData{acctSlots[1:]}, }, - } { + } + + for i, tc := range tests { tc := tc + if i > 0 { + t.Log("\n") + } + t.Logf("-- Test %d", i) + t.Log(tc.desc) + t.Log(" request:") + t.Logf(" root: %x", tc.root) + t.Logf(" accounts: %x", tc.accounts) + t.Logf(" range: %#x - %#x", tc.origin, tc.limit) + t.Logf(" responseBytes: %d", tc.nBytes) if err := s.snapGetStorageRanges(t, &tc); err != nil { - t.Errorf("test %d \n root: %x\n range: %#x - %#x\n bytes: %d\n #accounts: %d\nfailed: %v", - i, tc.root, tc.origin, tc.limit, tc.nBytes, len(tc.accounts), err) + t.Errorf(" failed: %v", err) } } } @@ -209,87 +452,92 @@ type byteCodesTest struct { hashes []common.Hash expHashes int + + desc string } // TestSnapGetByteCodes various forms of GetByteCodes requests. func (s *Suite) TestSnapGetByteCodes(t *utesting.T) { - // The halfchain import should yield these bytecodes - var hcBytecodes []common.Hash - for _, s := range []string{ - "0x200c90460d8b0063210d5f5b9918e053c8f2c024485e0f1b48be8b1fc71b1317", - "0x20ba67ed4ac6aff626e0d1d4db623e2fada9593daeefc4a6eb4b70e6cff986f3", - "0x24b5b4902cb3d897c1cee9f16be8e897d8fa277c04c6dc8214f18295fca5de44", - "0x320b9d0a2be39b8a1c858f9f8cb96b1df0983071681de07ded3a7c0d05db5fd6", - "0x48cb0d5275936a24632babc7408339f9f7b051274809de565b8b0db76e97e03c", - "0x67c7a6f5cdaa43b4baa0e15b2be63346d1b9ce9f2c3d7e5804e0cacd44ee3b04", - "0x6d8418059bdc8c3fabf445e6bfc662af3b6a4ae45999b953996e42c7ead2ab49", - "0x7043422e5795d03f17ee0463a37235258e609fdd542247754895d72695e3e142", - "0x727f9e6f0c4bac1ff8d72c2972122d9c8d37ccb37e04edde2339e8da193546f1", - "0x86ccd5e23c78568a8334e0cebaf3e9f48c998307b0bfb1c378cee83b4bfb29cb", - "0x8fc89b00d6deafd4c4279531e743365626dbfa28845ec697919d305c2674302d", - "0x92cfc353bcb9746bb6f9996b6b9df779c88af2e9e0eeac44879ca19887c9b732", - "0x941b4872104f0995a4898fcf0f615ea6bf46bfbdfcf63ea8f2fd45b3f3286b77", - "0xa02fe8f41159bb39d2b704c633c3d6389cf4bfcb61a2539a9155f60786cf815f", - "0xa4b94e0afdffcb0af599677709dac067d3145489ea7aede57672bee43e3b7373", - "0xaf4e64edd3234c1205b725e42963becd1085f013590bd7ed93f8d711c5eb65fb", - "0xb69a18fa855b742031420081999086f6fb56c3930ae8840944e8b8ae9931c51e", - "0xc246c217bc73ce6666c93a93a94faa5250564f50a3fdc27ea74c231c07fe2ca6", - "0xcd6e4ab2c3034df2a8a1dfaaeb1c4baecd162a93d22de35e854ee2945cbe0c35", - "0xe24b692d09d6fc2f3d1a6028c400a27c37d7cbb11511907c013946d6ce263d3b", - "0xe440c5f0e8603fd1ed25976eee261ccee8038cf79d6a4c0eb31b2bf883be737f", - "0xe6eacbc509203d21ac814b350e72934fde686b7f673c19be8cf956b0c70078ce", - "0xe8530de4371467b5be7ea0e69e675ab36832c426d6c1ce9513817c0f0ae1486b", - "0xe85d487abbbc83bf3423cf9731360cf4f5a37220e18e5add54e72ee20861196a", - "0xf195ea389a5eea28db0be93660014275b158963dec44af1dfa7d4743019a9a49", - } { - hcBytecodes = append(hcBytecodes, common.HexToHash(s)) - } - - for i, tc := range []byteCodesTest{ + var ( + allHashes = s.chain.CodeHashes() + headRoot = s.chain.Head().Root() + genesisRoot = s.chain.RootAt(0) + ) + + tests := []byteCodesTest{ // A few stateroots { - nBytes: 10000, hashes: []common.Hash{s.chain.RootAt(0), s.chain.RootAt(999)}, + desc: `Here we request state roots as code hashes. The server should deliver an empty response with no items.`, + nBytes: 10000, + hashes: []common.Hash{genesisRoot, headRoot}, expHashes: 0, }, { - nBytes: 10000, hashes: []common.Hash{s.chain.RootAt(0), s.chain.RootAt(0)}, + desc: `Here we request the genesis state root (which is not an existing code hash) two times. The server should deliver an empty response with no items.`, + nBytes: 10000, + hashes: []common.Hash{genesisRoot, genesisRoot}, expHashes: 0, }, // Empties { - nBytes: 10000, hashes: []common.Hash{types.EmptyRootHash}, + desc: `Here we request the empty state root (which is not an existing code hash). The server should deliver an empty response with no items.`, + nBytes: 10000, + hashes: []common.Hash{types.EmptyRootHash}, expHashes: 0, }, { - nBytes: 10000, hashes: []common.Hash{types.EmptyCodeHash}, + desc: `Here we request the empty code hash. The server should deliver an empty response item.`, + nBytes: 10000, + hashes: []common.Hash{types.EmptyCodeHash}, expHashes: 1, }, { - nBytes: 10000, hashes: []common.Hash{types.EmptyCodeHash, types.EmptyCodeHash, types.EmptyCodeHash}, + desc: `In this test, we request the empty code hash three times. The server should deliver the empty item three times.`, + nBytes: 10000, + hashes: []common.Hash{types.EmptyCodeHash, types.EmptyCodeHash, types.EmptyCodeHash}, expHashes: 3, }, // The existing bytecodes { - nBytes: 10000, hashes: hcBytecodes, - expHashes: len(hcBytecodes), + desc: `Here we request all available contract codes. The server should deliver them all in one response.`, + nBytes: 100000, + hashes: allHashes, + expHashes: len(allHashes), }, // The existing, with limited byte arg { - nBytes: 1, hashes: hcBytecodes, + desc: `In this test, the request has a bytes limit of one. The server should deliver one item.`, + nBytes: 1, + hashes: allHashes, expHashes: 1, }, { - nBytes: 0, hashes: hcBytecodes, + desc: `In this test, the request has a bytes limit of zero. The server should deliver one item.`, + nBytes: 0, + hashes: allHashes, expHashes: 1, }, + // Request the same hash multiple times. { - nBytes: 1000, hashes: []common.Hash{hcBytecodes[0], hcBytecodes[0], hcBytecodes[0], hcBytecodes[0]}, + desc: `This test requests the same code hash multiple times. The server should deliver it multiple times.`, + nBytes: 1000, + hashes: []common.Hash{allHashes[0], allHashes[0], allHashes[0], allHashes[0]}, expHashes: 4, }, - } { + } + + for i, tc := range tests { tc := tc + if i > 0 { + t.Log("\n") + } + t.Logf("-- Test %d", i) + t.Log(tc.desc) + t.Log(" request:") + t.Logf(" hashes: %x", tc.hashes) + t.Logf(" responseBytes: %d", tc.nBytes) if err := s.snapGetByteCodes(t, &tc); err != nil { - t.Errorf("test %d \n bytes: %d\n #hashes: %d\nfailed: %v", i, tc.nBytes, len(tc.hashes), err) + t.Errorf("failed: %v", err) } } } @@ -299,8 +547,10 @@ type trieNodesTest struct { paths []snap.TrieNodePathSet nBytes uint64 - expHashes []common.Hash - expReject bool + expHashes []common.Hash // expected response + expReject bool // if true, request should be rejected + + desc string } func decodeNibbles(nibbles []byte, bytes []byte) { @@ -344,29 +594,32 @@ func hexToCompact(hex []byte) []byte { // TestSnapTrieNodes various forms of GetTrieNodes requests. func (s *Suite) TestSnapTrieNodes(t *utesting.T) { - key := common.FromHex("0x00bf49f440a1cd0527e4d06e2765654c0f56452257516d793a9b8d604dcfdf2a") - // helper function to iterate the key, and generate the compact-encoded - // trie paths along the way. - pathTo := func(length int) snap.TrieNodePathSet { - hex := keybytesToHex(key)[:length] - hex[len(hex)-1] = 0 // remove term flag - hKey := hexToCompact(hex) - return snap.TrieNodePathSet{hKey} - } - var accPaths []snap.TrieNodePathSet + var ( + // This is the known address of the snap storage testing contract. + storageAcct = common.HexToAddress("0x8bebc8ba651aee624937e7d897853ac30c95a067") + storageAcctHash = common.BytesToHash(s.chain.state[storageAcct].AddressHash) + // This is the known address of an existing account. + key = common.FromHex("0xa87387b50b481431c6ccdb9ae99a54d4dcdd4a3eff75d7b17b4818f7bbfc21e9") + empty = types.EmptyCodeHash + accPaths []snap.TrieNodePathSet + ) for i := 1; i <= 65; i++ { - accPaths = append(accPaths, pathTo(i)) + accPaths = append(accPaths, makeSnapPath(key, i)) } - empty := types.EmptyCodeHash - for i, tc := range []trieNodesTest{ + + tests := []trieNodesTest{ { - root: s.chain.RootAt(999), + desc: `In this test, we send an empty request to the node.`, + root: s.chain.Head().Root(), paths: nil, nBytes: 500, expHashes: nil, }, + { - root: s.chain.RootAt(999), + desc: `In this test, we send a request containing an empty path-set. +The server should reject the request.`, + root: s.chain.Head().Root(), paths: []snap.TrieNodePathSet{ {}, // zero-length pathset should 'abort' and kick us off {[]byte{0}}, @@ -375,18 +628,21 @@ func (s *Suite) TestSnapTrieNodes(t *utesting.T) { expHashes: []common.Hash{}, expReject: true, }, + { - root: s.chain.RootAt(999), + desc: `Here we request the root node of the trie. The server should respond with the root node.`, + root: s.chain.RootAt(int(s.chain.Head().NumberU64() - 1)), paths: []snap.TrieNodePathSet{ {[]byte{0}}, {[]byte{1}, []byte{0}}, }, - nBytes: 5000, - //0x6b3724a41b8c38b46d4d02fba2bb2074c47a507eb16a9a4b978f91d32e406faf - expHashes: []common.Hash{s.chain.RootAt(999)}, + nBytes: 5000, + expHashes: []common.Hash{s.chain.RootAt(int(s.chain.Head().NumberU64() - 1))}, }, + { // nonsensically long path - root: s.chain.RootAt(999), + desc: `In this test, we request a very long trie node path. The server should respond with an empty node (keccak256("")).`, + root: s.chain.Head().Root(), paths: []snap.TrieNodePathSet{ {[]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8}}, @@ -394,25 +650,19 @@ func (s *Suite) TestSnapTrieNodes(t *utesting.T) { nBytes: 5000, expHashes: []common.Hash{common.HexToHash("0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")}, }, - { - root: s.chain.RootAt(0), - paths: []snap.TrieNodePathSet{ - {[]byte{0}}, - {[]byte{1}, []byte{0}}, - }, - nBytes: 5000, - expHashes: []common.Hash{ - common.HexToHash("0x1ee1bb2fbac4d46eab331f3e8551e18a0805d084ed54647883aa552809ca968d"), - }, - }, + { // The leaf is only a couple of levels down, so the continued trie traversal causes lookup failures. - root: s.chain.RootAt(999), + desc: `Here we request some known accounts from the state.`, + root: s.chain.Head().Root(), paths: accPaths, nBytes: 5000, expHashes: []common.Hash{ - common.HexToHash("0xbcefee69b37cca1f5bf3a48aebe08b35f2ea1864fa958bb0723d909a0e0d28d8"), - common.HexToHash("0x4fb1e4e2391e4b4da471d59641319b8fa25d76c973d4bec594d7b00a69ae5135"), + // It's a bit unfortunate these are hard-coded, but the result depends on + // a lot of aspects of the state trie and can't be guessed in a simple + // way. So you'll have to update this when the test chain is changed. + common.HexToHash("0x3e963a69401a70224cbfb8c0cc2249b019041a538675d71ccf80c9328d114e2e"), + common.HexToHash("0xd0670d09cdfbf3c6320eb3e92c47c57baa6c226551a2d488c05581091e6b1689"), empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, @@ -420,55 +670,84 @@ func (s *Suite) TestSnapTrieNodes(t *utesting.T) { empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty, empty}, }, + { - // Basically the same as above, with different ordering - root: s.chain.RootAt(999), + desc: `In this test, we request some known accounts in state. The requested paths are NOT in key order.`, + root: s.chain.Head().Root(), paths: []snap.TrieNodePathSet{ accPaths[10], accPaths[1], accPaths[0], }, nBytes: 5000, + // As with the previous test, this result depends on the whole tree and will have to + // be updated when the test chain is changed. expHashes: []common.Hash{ empty, - common.HexToHash("0x4fb1e4e2391e4b4da471d59641319b8fa25d76c973d4bec594d7b00a69ae5135"), - common.HexToHash("0xbcefee69b37cca1f5bf3a48aebe08b35f2ea1864fa958bb0723d909a0e0d28d8"), + common.HexToHash("0xd0670d09cdfbf3c6320eb3e92c47c57baa6c226551a2d488c05581091e6b1689"), + common.HexToHash("0x3e963a69401a70224cbfb8c0cc2249b019041a538675d71ccf80c9328d114e2e"), }, }, + + // Storage tests. + // These use the known storage test account. + { - /* - A test against this account, requesting trie nodes for the storage trie + desc: `This test requests the storage root node of a known account.`, + root: s.chain.Head().Root(), + paths: []snap.TrieNodePathSet{ { - "balance": "0", - "nonce": 1, - "root": "0xbe3d75a1729be157e79c3b77f00206db4d54e3ea14375a015451c88ec067c790", - "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "storage": { - "0x405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace": "02", - "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6": "01", - "0xc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b": "03" - }, - "key": "0xf493f79c43bd747129a226ad42529885a4b108aba6046b2d12071695a6627844" - } - */ - root: s.chain.RootAt(999), + storageAcctHash[:], + []byte{0}, + }, + }, + nBytes: 5000, + expHashes: []common.Hash{ + common.HexToHash("0xbe3d75a1729be157e79c3b77f00206db4d54e3ea14375a015451c88ec067c790"), + }, + }, + + { + desc: `This test requests multiple storage nodes of a known account.`, + root: s.chain.Head().Root(), paths: []snap.TrieNodePathSet{ { - common.FromHex("0xf493f79c43bd747129a226ad42529885a4b108aba6046b2d12071695a6627844"), + storageAcctHash[:], []byte{0}, + []byte{0x1b}, }, }, nBytes: 5000, expHashes: []common.Hash{ common.HexToHash("0xbe3d75a1729be157e79c3b77f00206db4d54e3ea14375a015451c88ec067c790"), + common.HexToHash("0xf4984a11f61a2921456141df88de6e1a710d28681b91af794c5a721e47839cd7"), }, }, - }[7:] { + } + + for i, tc := range tests { tc := tc + if i > 0 { + t.Log("\n") + } + t.Logf("-- Test %d", i) + t.Log(tc.desc) + t.Log(" request:") + t.Logf(" root: %x", tc.root) + t.Logf(" paths: %x", tc.paths) + t.Logf(" responseBytes: %d", tc.nBytes) + if err := s.snapGetTrieNodes(t, &tc); err != nil { - t.Errorf("test %d \n #hashes %x\n root: %#x\n bytes: %d\nfailed: %v", i, len(tc.expHashes), tc.root, tc.nBytes, err) + t.Errorf(" failed: %v", err) } } } +func makeSnapPath(key []byte, length int) snap.TrieNodePathSet { + hex := keybytesToHex(key)[:length] + hex[len(hex)-1] = 0 // remove term flag + hKey := hexToCompact(hex) + return snap.TrieNodePathSet{hKey} +} + func (s *Suite) snapGetAccountRange(t *utesting.T, tc *accRangeTest) error { conn, err := s.dialSnap() if err != nil { @@ -479,22 +758,20 @@ func (s *Suite) snapGetAccountRange(t *utesting.T, tc *accRangeTest) error { t.Fatalf("peering failed: %v", err) } // write request - req := &GetAccountRange{ + req := &snap.GetAccountRangePacket{ ID: uint64(rand.Int63()), Root: tc.root, - Origin: tc.origin, - Limit: tc.limit, + Origin: tc.startingHash, + Limit: tc.limitHash, Bytes: tc.nBytes, } - resp, err := conn.snapRequest(req, req.ID, s.chain) + msg, err := conn.snapRequest(snap.GetAccountRangeMsg, req) if err != nil { return fmt.Errorf("account range request failed: %v", err) } - var res *snap.AccountRangePacket - if r, ok := resp.(*AccountRange); !ok { - return fmt.Errorf("account range response wrong: %T %v", resp, resp) - } else { - res = (*snap.AccountRangePacket)(r) + res, ok := msg.(*snap.AccountRangePacket) + if !ok { + return fmt.Errorf("account range response wrong: %T %v", msg, msg) } if exp, got := tc.expAccounts, len(res.Accounts); exp != got { return fmt.Errorf("expected %d accounts, got %d", exp, got) @@ -530,17 +807,13 @@ 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 { - end = keys[len(keys)-1] - } - _, err = trie.VerifyRangeProof(tc.root, tc.origin[:], end, keys, accounts, proofdb) + _, err = trie.VerifyRangeProof(tc.root, tc.startingHash[:], keys, accounts, proofdb) return err } @@ -553,8 +826,9 @@ func (s *Suite) snapGetStorageRanges(t *utesting.T, tc *stRangesTest) error { if err = conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } + // write request - req := &GetStorageRanges{ + req := &snap.GetStorageRangesPacket{ ID: uint64(rand.Int63()), Root: tc.root, Accounts: tc.accounts, @@ -562,28 +836,38 @@ func (s *Suite) snapGetStorageRanges(t *utesting.T, tc *stRangesTest) error { Limit: tc.limit, Bytes: tc.nBytes, } - resp, err := conn.snapRequest(req, req.ID, s.chain) + msg, err := conn.snapRequest(snap.GetStorageRangesMsg, req) if err != nil { return fmt.Errorf("account range request failed: %v", err) } - var res *snap.StorageRangesPacket - if r, ok := resp.(*StorageRanges); !ok { - return fmt.Errorf("account range response wrong: %T %v", resp, resp) - } else { - res = (*snap.StorageRangesPacket)(r) + res, ok := msg.(*snap.StorageRangesPacket) + if !ok { + return fmt.Errorf("account range response wrong: %T %v", msg, msg) } - gotSlots := 0 + // Ensure the ranges are monotonically increasing for i, slots := range res.Slots { - gotSlots += len(slots) for j := 1; j < len(slots); j++ { if bytes.Compare(slots[j-1].Hash[:], slots[j].Hash[:]) >= 0 { return fmt.Errorf("storage slots not monotonically increasing for account #%d: #%d [%x] vs #%d [%x]", i, j-1, slots[j-1].Hash[:], j, slots[j].Hash[:]) } } } - if exp, got := tc.expSlots, gotSlots; exp != got { - return fmt.Errorf("expected %d slots, got %d", exp, got) + + // Compute expected slot hashes. + var expHashes [][]common.Hash + for _, acct := range tc.expSlots { + var list []common.Hash + for _, s := range acct { + list = append(list, s.Hash) + } + expHashes = append(expHashes, list) + } + + // Check response. + if !reflect.DeepEqual(res.Slots, tc.expSlots) { + t.Log(" expected slot hashes:", expHashes) + return fmt.Errorf("wrong storage slots in response: %#v", res.Slots) } return nil } @@ -598,24 +882,22 @@ func (s *Suite) snapGetByteCodes(t *utesting.T, tc *byteCodesTest) error { t.Fatalf("peering failed: %v", err) } // write request - req := &GetByteCodes{ + req := &snap.GetByteCodesPacket{ ID: uint64(rand.Int63()), Hashes: tc.hashes, Bytes: tc.nBytes, } - resp, err := conn.snapRequest(req, req.ID, s.chain) + msg, err := conn.snapRequest(snap.GetByteCodesMsg, req) if err != nil { return fmt.Errorf("getBytecodes request failed: %v", err) } - var res *snap.ByteCodesPacket - if r, ok := resp.(*ByteCodes); !ok { - return fmt.Errorf("bytecodes response wrong: %T %v", resp, resp) - } else { - res = (*snap.ByteCodesPacket)(r) + res, ok := msg.(*snap.ByteCodesPacket) + if !ok { + return fmt.Errorf("bytecodes response wrong: %T %v", msg, msg) } if exp, got := tc.expHashes, len(res.Codes); exp != got { for i, c := range res.Codes { - fmt.Printf("%d. %#x\n", i, c) + t.Logf("%d. %#x\n", i, c) } return fmt.Errorf("expected %d bytecodes, got %d", exp, got) } @@ -658,25 +940,24 @@ func (s *Suite) snapGetTrieNodes(t *utesting.T, tc *trieNodesTest) error { if err = conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } - // write request - req := &GetTrieNodes{ + + // write0 request + req := &snap.GetTrieNodesPacket{ ID: uint64(rand.Int63()), Root: tc.root, Paths: tc.paths, Bytes: tc.nBytes, } - resp, err := conn.snapRequest(req, req.ID, s.chain) + msg, err := conn.snapRequest(snap.GetTrieNodesMsg, req) if err != nil { if tc.expReject { return nil } return fmt.Errorf("trienodes request failed: %v", err) } - var res *snap.TrieNodesPacket - if r, ok := resp.(*TrieNodes); !ok { - return fmt.Errorf("trienodes response wrong: %T %v", resp, resp) - } else { - res = (*snap.TrieNodesPacket)(r) + res, ok := msg.(*snap.TrieNodesPacket) + if !ok { + return fmt.Errorf("trienodes response wrong: %T %v", msg, msg) } // Check the correctness @@ -687,14 +968,14 @@ func (s *Suite) snapGetTrieNodes(t *utesting.T, tc *trieNodesTest) error { hash := make([]byte, 32) trienodes := res.Nodes if got, want := len(trienodes), len(tc.expHashes); got != want { - return fmt.Errorf("wrong trienode count, got %d, want %d\n", got, want) + return fmt.Errorf("wrong trienode count, got %d, want %d", got, want) } for i, trienode := range trienodes { hasher.Reset() hasher.Write(trienode) hasher.Read(hash) if got, want := hash, tc.expHashes[i]; !bytes.Equal(got, want[:]) { - fmt.Printf("hash %d wrong, got %#x, want %#x\n", i, got, want) + t.Logf(" hash %d wrong, got %#x, want %#x\n", i, got, want) err = fmt.Errorf("hash %d wrong, got %#x, want %#x", i, got, want) } } diff --git a/cmd/devp2p/internal/ethtest/snapTypes.go b/cmd/devp2p/internal/ethtest/snapTypes.go deleted file mode 100644 index 6bcaa9291..000000000 --- a/cmd/devp2p/internal/ethtest/snapTypes.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2022 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package ethtest - -import "github.com/ethereum/go-ethereum/eth/protocols/snap" - -// GetAccountRange represents an account range query. -type GetAccountRange snap.GetAccountRangePacket - -func (msg GetAccountRange) Code() int { return 33 } -func (msg GetAccountRange) ReqID() uint64 { return msg.ID } - -type AccountRange snap.AccountRangePacket - -func (msg AccountRange) Code() int { return 34 } -func (msg AccountRange) ReqID() uint64 { return msg.ID } - -type GetStorageRanges snap.GetStorageRangesPacket - -func (msg GetStorageRanges) Code() int { return 35 } -func (msg GetStorageRanges) ReqID() uint64 { return msg.ID } - -type StorageRanges snap.StorageRangesPacket - -func (msg StorageRanges) Code() int { return 36 } -func (msg StorageRanges) ReqID() uint64 { return msg.ID } - -type GetByteCodes snap.GetByteCodesPacket - -func (msg GetByteCodes) Code() int { return 37 } -func (msg GetByteCodes) ReqID() uint64 { return msg.ID } - -type ByteCodes snap.ByteCodesPacket - -func (msg ByteCodes) Code() int { return 38 } -func (msg ByteCodes) ReqID() uint64 { return msg.ID } - -type GetTrieNodes snap.GetTrieNodesPacket - -func (msg GetTrieNodes) Code() int { return 39 } -func (msg GetTrieNodes) ReqID() uint64 { return msg.ID } - -type TrieNodes snap.TrieNodesPacket - -func (msg TrieNodes) Code() int { return 40 } -func (msg TrieNodes) ReqID() uint64 { return msg.ID } diff --git a/cmd/devp2p/internal/ethtest/suite.go b/cmd/devp2p/internal/ethtest/suite.go index 815353be7..f62d25a83 100644 --- a/cmd/devp2p/internal/ethtest/suite.go +++ b/cmd/devp2p/internal/ethtest/suite.go @@ -17,35 +17,47 @@ package ethtest import ( - "time" + "crypto/rand" + "math/big" + "reflect" "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/crypto/kzg4844" "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/internal/utesting" + "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/holiman/uint256" ) // Suite represents a structure used to test a node's conformance // to the eth protocol. type Suite struct { - Dest *enode.Node - - chain *Chain - fullChain *Chain + Dest *enode.Node + chain *Chain + engine *EngineClient } // NewSuite creates and returns a new eth-test suite that can // be used to test the given node against the given blockchain // data. -func NewSuite(dest *enode.Node, chainfile string, genesisfile string) (*Suite, error) { - chain, err := loadChain(chainfile, genesisfile) +func NewSuite(dest *enode.Node, chainDir, engineURL, jwt string) (*Suite, error) { + chain, err := NewChain(chainDir) if err != nil { return nil, err } + engine, err := NewEngineClient(chainDir, engineURL, jwt) + if err != nil { + return nil, err + } + return &Suite{ - Dest: dest, - chain: chain.Shorten(1000), - fullChain: chain, + Dest: dest, + chain: chain, + engine: engine, }, nil } @@ -60,34 +72,30 @@ func (s *Suite) EthTests() []utesting.Test { {Name: "TestZeroRequestID", Fn: s.TestZeroRequestID}, // get block bodies {Name: "TestGetBlockBodies", Fn: s.TestGetBlockBodies}, - // broadcast - {Name: "TestBroadcast", Fn: s.TestBroadcast}, - {Name: "TestLargeAnnounce", Fn: s.TestLargeAnnounce}, - {Name: "TestOldAnnounce", Fn: s.TestOldAnnounce}, - {Name: "TestBlockHashAnnounce", Fn: s.TestBlockHashAnnounce}, - // malicious handshakes + status + // // malicious handshakes + status {Name: "TestMaliciousHandshake", Fn: s.TestMaliciousHandshake}, {Name: "TestMaliciousStatus", Fn: s.TestMaliciousStatus}, // test transactions {Name: "TestTransaction", Fn: s.TestTransaction}, - {Name: "TestMaliciousTx", Fn: s.TestMaliciousTx}, + {Name: "TestInvalidTxs", Fn: s.TestInvalidTxs}, {Name: "TestLargeTxRequest", Fn: s.TestLargeTxRequest}, {Name: "TestNewPooledTxs", Fn: s.TestNewPooledTxs}, + {Name: "TestBlobViolations", Fn: s.TestBlobViolations}, } } func (s *Suite) SnapTests() []utesting.Test { return []utesting.Test{ - {Name: "TestSnapStatus", Fn: s.TestSnapStatus}, - {Name: "TestSnapAccountRange", Fn: s.TestSnapGetAccountRange}, - {Name: "TestSnapGetByteCodes", Fn: s.TestSnapGetByteCodes}, - {Name: "TestSnapGetTrieNodes", Fn: s.TestSnapTrieNodes}, - {Name: "TestSnapGetStorageRanges", Fn: s.TestSnapGetStorageRanges}, + {Name: "Status", Fn: s.TestSnapStatus}, + {Name: "AccountRange", Fn: s.TestSnapGetAccountRange}, + {Name: "GetByteCodes", Fn: s.TestSnapGetByteCodes}, + {Name: "GetTrieNodes", Fn: s.TestSnapTrieNodes}, + {Name: "GetStorageRanges", Fn: s.TestSnapGetStorageRanges}, } } -// TestStatus attempts to connect to the given node and exchange -// a status message with it on the eth protocol. +// TestStatus attempts to connect to the given node and exchange a status +// message with it on the eth protocol. func (s *Suite) TestStatus(t *utesting.T) { conn, err := s.dial() if err != nil { @@ -99,8 +107,13 @@ func (s *Suite) TestStatus(t *utesting.T) { } } -// TestGetBlockHeaders tests whether the given node can respond to -// an eth `GetBlockHeaders` request and that the response is accurate. +// headersMatch returns whether the received headers match the given request +func headersMatch(expected []*types.Header, headers []*types.Header) bool { + return reflect.DeepEqual(expected, headers) +} + +// TestGetBlockHeaders tests whether the given node can respond to an eth +// `GetBlockHeaders` request and that the response is accurate. func (s *Suite) TestGetBlockHeaders(t *utesting.T) { conn, err := s.dial() if err != nil { @@ -110,34 +123,41 @@ func (s *Suite) TestGetBlockHeaders(t *utesting.T) { if err = conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } - // write request - req := &GetBlockHeaders{ - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + // Send headers request. + req := ð.GetBlockHeadersPacket{ + RequestId: 33, + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{Hash: s.chain.blocks[1].Hash()}, Amount: 2, Skip: 1, Reverse: false, }, } - headers, err := conn.headersRequest(req, s.chain, 33) - if err != nil { - t.Fatalf("could not get block headers: %v", err) + // Read headers response. + if err := conn.Write(ethProto, eth.GetBlockHeadersMsg, req); err != nil { + t.Fatalf("could not write to connection: %v", err) + } + headers := new(eth.BlockHeadersPacket) + if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers); err != nil { + t.Fatalf("error reading msg: %v", err) } - // check for correct headers + if got, want := headers.RequestId, req.RequestId; got != want { + t.Fatalf("unexpected request id") + } + // Check for correct headers. expected, err := s.chain.GetHeaders(req) if err != nil { t.Fatalf("failed to get headers for given request: %v", err) } - if !headersMatch(expected, headers) { + if !headersMatch(expected, headers.BlockHeadersRequest) { t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers) } } -// TestSimultaneousRequests sends two simultaneous `GetBlockHeader` requests from -// the same connection with different request IDs and checks to make sure the node -// responds with the correct headers per request. +// TestSimultaneousRequests sends two simultaneous `GetBlockHeader` requests +// from the same connection with different request IDs and checks to make sure +// the node responds with the correct headers per request. func (s *Suite) TestSimultaneousRequests(t *utesting.T) { - // create a connection conn, err := s.dial() if err != nil { t.Fatalf("dial failed: %v", err) @@ -147,10 +167,10 @@ func (s *Suite) TestSimultaneousRequests(t *utesting.T) { t.Fatalf("peering failed: %v", err) } - // create two requests - req1 := &GetBlockHeaders{ + // Create two different requests. + req1 := ð.GetBlockHeadersPacket{ RequestId: uint64(111), - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{ Hash: s.chain.blocks[1].Hash(), }, @@ -159,9 +179,9 @@ func (s *Suite) TestSimultaneousRequests(t *utesting.T) { Reverse: false, }, } - req2 := &GetBlockHeaders{ + req2 := ð.GetBlockHeadersPacket{ RequestId: uint64(222), - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{ Hash: s.chain.blocks[1].Hash(), }, @@ -171,46 +191,45 @@ func (s *Suite) TestSimultaneousRequests(t *utesting.T) { }, } - // write the first request - if err := conn.Write(req1); err != nil { + // Send both requests. + if err := conn.Write(ethProto, eth.GetBlockHeadersMsg, req1); err != nil { t.Fatalf("failed to write to connection: %v", err) } - // write the second request - if err := conn.Write(req2); err != nil { + if err := conn.Write(ethProto, eth.GetBlockHeadersMsg, req2); err != nil { t.Fatalf("failed to write to connection: %v", err) } - // wait for responses - msg := conn.waitForResponse(s.chain, timeout, req1.RequestId) - headers1, ok := msg.(*BlockHeaders) - if !ok { - t.Fatalf("unexpected %s", pretty.Sdump(msg)) + // Wait for responses. + headers1 := new(eth.BlockHeadersPacket) + if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers1); err != nil { + t.Fatalf("error reading block headers msg: %v", err) + } + if got, want := headers1.RequestId, req1.RequestId; got != want { + t.Fatalf("unexpected request id in response: got %d, want %d", got, want) + } + headers2 := new(eth.BlockHeadersPacket) + if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers2); err != nil { + t.Fatalf("error reading block headers msg: %v", err) } - msg = conn.waitForResponse(s.chain, timeout, req2.RequestId) - headers2, ok := msg.(*BlockHeaders) - if !ok { - t.Fatalf("unexpected %s", pretty.Sdump(msg)) + if got, want := headers2.RequestId, req2.RequestId; got != want { + t.Fatalf("unexpected request id in response: got %d, want %d", got, want) } - // check received headers for accuracy - expected1, err := s.chain.GetHeaders(req1) - if err != nil { + // Check received headers for accuracy. + if expected, err := s.chain.GetHeaders(req1); err != nil { t.Fatalf("failed to get expected headers for request 1: %v", err) + } else if !headersMatch(expected, headers1.BlockHeadersRequest) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers1) } - expected2, err := s.chain.GetHeaders(req2) - if err != nil { + if expected, err := s.chain.GetHeaders(req2); err != nil { t.Fatalf("failed to get expected headers for request 2: %v", err) - } - if !headersMatch(expected1, headers1.BlockHeadersPacket) { - t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected1, headers1) - } - if !headersMatch(expected2, headers2.BlockHeadersPacket) { - t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected2, headers2) + } else if !headersMatch(expected, headers2.BlockHeadersRequest) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2) } } -// TestSameRequestID sends two requests with the same request ID to a -// single node. +// TestSameRequestID sends two requests with the same request ID to a single +// node. func (s *Suite) TestSameRequestID(t *utesting.T) { conn, err := s.dial() if err != nil { @@ -220,20 +239,21 @@ func (s *Suite) TestSameRequestID(t *utesting.T) { if err := conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } - // create requests + + // Create two different requests with the same ID. reqID := uint64(1234) - request1 := &GetBlockHeaders{ + request1 := ð.GetBlockHeadersPacket{ RequestId: reqID, - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{ Number: 1, }, Amount: 2, }, } - request2 := &GetBlockHeaders{ + request2 := ð.GetBlockHeadersPacket{ RequestId: reqID, - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{ Number: 33, }, @@ -241,40 +261,40 @@ func (s *Suite) TestSameRequestID(t *utesting.T) { }, } - // write the requests - if err = conn.Write(request1); err != nil { + // Send the requests. + if err = conn.Write(ethProto, eth.GetBlockHeadersMsg, request1); err != nil { t.Fatalf("failed to write to connection: %v", err) } - if err = conn.Write(request2); err != nil { + if err = conn.Write(ethProto, eth.GetBlockHeadersMsg, request2); err != nil { t.Fatalf("failed to write to connection: %v", err) } - // wait for responses - msg := conn.waitForResponse(s.chain, timeout, reqID) - headers1, ok := msg.(*BlockHeaders) - if !ok { - t.Fatalf("unexpected %s", pretty.Sdump(msg)) + // Wait for the responses. + headers1 := new(eth.BlockHeadersPacket) + if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers1); err != nil { + t.Fatalf("error reading from connection: %v", err) + } + if got, want := headers1.RequestId, request1.RequestId; got != want { + t.Fatalf("unexpected request id: got %d, want %d", got, want) + } + headers2 := new(eth.BlockHeadersPacket) + if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers2); err != nil { + t.Fatalf("error reading from connection: %v", err) } - msg = conn.waitForResponse(s.chain, timeout, reqID) - headers2, ok := msg.(*BlockHeaders) - if !ok { - t.Fatalf("unexpected %s", pretty.Sdump(msg)) + if got, want := headers2.RequestId, request2.RequestId; got != want { + t.Fatalf("unexpected request id: got %d, want %d", got, want) } - // check if headers match - expected1, err := s.chain.GetHeaders(request1) - if err != nil { + // Check if headers match. + if expected, err := s.chain.GetHeaders(request1); err != nil { t.Fatalf("failed to get expected block headers: %v", err) + } else if !headersMatch(expected, headers1.BlockHeadersRequest) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers1) } - expected2, err := s.chain.GetHeaders(request2) - if err != nil { + if expected, err := s.chain.GetHeaders(request2); err != nil { t.Fatalf("failed to get expected block headers: %v", err) - } - if !headersMatch(expected1, headers1.BlockHeadersPacket) { - t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected1, headers1) - } - if !headersMatch(expected2, headers2.BlockHeadersPacket) { - t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected2, headers2) + } else if !headersMatch(expected, headers2.BlockHeadersRequest) { + t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers2) } } @@ -289,27 +309,32 @@ func (s *Suite) TestZeroRequestID(t *utesting.T) { if err := conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } - req := &GetBlockHeaders{ - GetBlockHeadersPacket: ð.GetBlockHeadersPacket{ + req := ð.GetBlockHeadersPacket{ + GetBlockHeadersRequest: ð.GetBlockHeadersRequest{ Origin: eth.HashOrNumber{Number: 0}, Amount: 2, }, } - headers, err := conn.headersRequest(req, s.chain, 0) - if err != nil { - t.Fatalf("failed to get block headers: %v", err) + // Read headers response. + if err := conn.Write(ethProto, eth.GetBlockHeadersMsg, req); err != nil { + t.Fatalf("could not write to connection: %v", err) } - expected, err := s.chain.GetHeaders(req) - if err != nil { - t.Fatalf("failed to get expected block headers: %v", err) + headers := new(eth.BlockHeadersPacket) + if err := conn.ReadMsg(ethProto, eth.BlockHeadersMsg, &headers); err != nil { + t.Fatalf("error reading msg: %v", err) + } + if got, want := headers.RequestId, req.RequestId; got != want { + t.Fatalf("unexpected request id") } - if !headersMatch(expected, headers) { + if expected, err := s.chain.GetHeaders(req); err != nil { + t.Fatalf("failed to get expected block headers: %v", err) + } else if !headersMatch(expected, headers.BlockHeadersRequest) { t.Fatalf("header mismatch: \nexpected %v \ngot %v", expected, headers) } } -// TestGetBlockBodies tests whether the given node can respond to -// a `GetBlockBodies` request and that the response is accurate. +// TestGetBlockBodies tests whether the given node can respond to a +// `GetBlockBodies` request and that the response is accurate. func (s *Suite) TestGetBlockBodies(t *utesting.T) { conn, err := s.dial() if err != nil { @@ -319,104 +344,110 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) { if err := conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } - // create block bodies request - req := &GetBlockBodies{ - RequestId: uint64(55), - GetBlockBodiesPacket: eth.GetBlockBodiesPacket{ + // Create block bodies request. + req := ð.GetBlockBodiesPacket{ + RequestId: 55, + GetBlockBodiesRequest: eth.GetBlockBodiesRequest{ s.chain.blocks[54].Hash(), s.chain.blocks[75].Hash(), }, } - if err := conn.Write(req); err != nil { + if err := conn.Write(ethProto, eth.GetBlockBodiesMsg, req); err != nil { t.Fatalf("could not write to connection: %v", err) } - // wait for block bodies response - msg := conn.waitForResponse(s.chain, timeout, req.RequestId) - resp, ok := msg.(*BlockBodies) - if !ok { - t.Fatalf("unexpected: %s", pretty.Sdump(msg)) + // Wait for response. + resp := new(eth.BlockBodiesPacket) + if err := conn.ReadMsg(ethProto, eth.BlockBodiesMsg, &resp); err != nil { + t.Fatalf("error reading block bodies msg: %v", err) + } + if got, want := resp.RequestId, req.RequestId; got != want { + t.Fatalf("unexpected request id in respond", got, want) } - bodies := resp.BlockBodiesPacket - t.Logf("received %d block bodies", len(bodies)) - if len(bodies) != len(req.GetBlockBodiesPacket) { - t.Fatalf("wrong bodies in response: expected %d bodies, "+ - "got %d", len(req.GetBlockBodiesPacket), len(bodies)) + bodies := resp.BlockBodiesResponse + if len(bodies) != len(req.GetBlockBodiesRequest) { + t.Fatalf("wrong bodies in response: expected %d bodies, got %d", len(req.GetBlockBodiesRequest), len(bodies)) } } -// TestBroadcast tests whether a block announcement is correctly -// propagated to the node's peers. -func (s *Suite) TestBroadcast(t *utesting.T) { - if err := s.sendNextBlock(); err != nil { - t.Fatalf("block broadcast failed: %v", err) - } +// randBuf makes a random buffer size kilobytes large. +func randBuf(size int) []byte { + buf := make([]byte, size*1024) + rand.Read(buf) + return buf } -// TestLargeAnnounce tests the announcement mechanism with a large block. -func (s *Suite) TestLargeAnnounce(t *utesting.T) { - nextBlock := len(s.chain.blocks) - blocks := []*NewBlock{ +// TestMaliciousHandshake tries to send malicious data during the handshake. +func (s *Suite) TestMaliciousHandshake(t *utesting.T) { + key, _ := crypto.GenerateKey() + + // Write hello to client. + var ( + pub0 = crypto.FromECDSAPub(&key.PublicKey)[1:] + version = eth.ProtocolVersions[0] + ) + handshakes := []*protoHandshake{ { - Block: largeBlock(), - TD: s.fullChain.TotalDifficultyAt(nextBlock), + Version: 5, + Caps: []p2p.Cap{ + {Name: string(randBuf(2)), Version: version}, + }, + ID: pub0, }, { - Block: s.fullChain.blocks[nextBlock], - TD: largeNumber(2), + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: version}, + }, + ID: append(pub0, byte(0)), }, { - Block: largeBlock(), - TD: largeNumber(2), + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: version}, + }, + ID: append(pub0, pub0...), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: "eth", Version: version}, + }, + ID: randBuf(2), + }, + { + Version: 5, + Caps: []p2p.Cap{ + {Name: string(randBuf(2)), Version: version}, + }, + ID: randBuf(2), }, } - - for i, blockAnnouncement := range blocks[0:3] { - t.Logf("Testing malicious announcement: %v\n", i) - conn, err := s.dial() + for _, handshake := range handshakes { + conn, err := s.dialAs(key) if err != nil { t.Fatalf("dial failed: %v", err) } - if err := conn.peer(s.chain, nil); err != nil { - t.Fatalf("peering failed: %v", err) - } - if err := conn.Write(blockAnnouncement); err != nil { + defer conn.Close() + + if err := conn.Write(ethProto, handshakeMsg, handshake); err != nil { t.Fatalf("could not write to connection: %v", err) } - // Invalid announcement, check that peer disconnected - switch msg := conn.readAndServe(s.chain, 8*time.Second).(type) { - case *Disconnect: - case *Error: - break - default: - t.Fatalf("unexpected: %s wanted disconnect", pretty.Sdump(msg)) + // Check that the peer disconnected + for i := 0; i < 2; i++ { + code, _, err := conn.Read() + if err != nil { + // Client may have disconnected without sending disconnect msg. + continue + } + switch code { + case discMsg: + case handshakeMsg: + // Discard one hello as Hello's are sent concurrently + continue + default: + t.Fatalf("unexpected msg: code %d", code) + } } - conn.Close() - } - // Test the last block as a valid block - if err := s.sendNextBlock(); err != nil { - t.Fatalf("failed to broadcast next block: %v", err) - } -} - -// TestOldAnnounce tests the announcement mechanism with an old block. -func (s *Suite) TestOldAnnounce(t *utesting.T) { - if err := s.oldAnnounce(); err != nil { - t.Fatal(err) - } -} - -// TestBlockHashAnnounce sends a new block hash announcement and expects -// the node to perform a `GetBlockHeaders` request. -func (s *Suite) TestBlockHashAnnounce(t *utesting.T) { - if err := s.hashAnnounce(); err != nil { - t.Fatalf("block hash announcement failed: %v", err) - } -} - -// TestMaliciousHandshake tries to send malicious data during the handshake. -func (s *Suite) TestMaliciousHandshake(t *utesting.T) { - if err := s.maliciousHandshakes(t); err != nil { - t.Fatal(err) } } @@ -427,46 +458,184 @@ func (s *Suite) TestMaliciousStatus(t *utesting.T) { t.Fatalf("dial failed: %v", err) } defer conn.Close() - - if err := s.maliciousStatus(conn); err != nil { - t.Fatal(err) + if err := conn.handshake(); err != nil { + t.Fatalf("handshake failed: %v", err) + } + // Create status with large total difficulty. + status := ð.StatusPacket{ + ProtocolVersion: uint32(conn.negotiatedProtoVersion), + NetworkID: s.chain.config.ChainID.Uint64(), + TD: new(big.Int).SetBytes(randBuf(2048)), + Head: s.chain.Head().Hash(), + Genesis: s.chain.GetBlock(0).Hash(), + ForkID: s.chain.ForkID(), + } + if err := conn.statusExchange(s.chain, status); err != nil { + t.Fatalf("status exchange failed: %v", err) + } + // Wait for disconnect. + code, _, err := conn.Read() + if err != nil { + t.Fatalf("error reading from connection: %v", err) + } + switch code { + case discMsg: + break + default: + t.Fatalf("expected disconnect, got: %d", code) } } -// TestTransaction sends a valid transaction to the node and -// checks if the transaction gets propagated. +// TestTransaction sends a valid transaction to the node and checks if the +// transaction gets propagated. func (s *Suite) TestTransaction(t *utesting.T) { - if err := s.sendSuccessfulTxs(t); err != nil { + // Nudge client out of syncing mode to accept pending txs. + if err := s.engine.sendForkchoiceUpdated(); err != nil { + t.Fatalf("failed to send next block: %v", err) + } + from, nonce := s.chain.GetSender(0) + inner := &types.DynamicFeeTx{ + ChainID: s.chain.config.ChainID, + Nonce: nonce, + GasTipCap: common.Big1, + GasFeeCap: s.chain.Head().BaseFee(), + Gas: 30000, + To: &common.Address{0xaa}, + Value: common.Big1, + } + tx, err := s.chain.SignTx(from, types.NewTx(inner)) + if err != nil { + t.Fatalf("failed to sign tx: %v", err) + } + if err := s.sendTxs([]*types.Transaction{tx}); err != nil { t.Fatal(err) } + s.chain.IncNonce(from, 1) } -// TestMaliciousTx sends several invalid transactions and tests whether +// TestInvalidTxs sends several invalid transactions and tests whether // the node will propagate them. -func (s *Suite) TestMaliciousTx(t *utesting.T) { - if err := s.sendMaliciousTxs(t); err != nil { - t.Fatal(err) +func (s *Suite) TestInvalidTxs(t *utesting.T) { + // Nudge client out of syncing mode to accept pending txs. + if err := s.engine.sendForkchoiceUpdated(); err != nil { + t.Fatalf("failed to send next block: %v", err) + } + + from, nonce := s.chain.GetSender(0) + inner := &types.DynamicFeeTx{ + ChainID: s.chain.config.ChainID, + Nonce: nonce, + GasTipCap: common.Big1, + GasFeeCap: s.chain.Head().BaseFee(), + Gas: 30000, + To: &common.Address{0xaa}, + } + tx, err := s.chain.SignTx(from, types.NewTx(inner)) + if err != nil { + t.Fatalf("failed to sign tx: %v", err) + } + if err := s.sendTxs([]*types.Transaction{tx}); err != nil { + t.Fatalf("failed to send txs: %v", err) + } + s.chain.IncNonce(from, 1) + + inners := []*types.DynamicFeeTx{ + // Nonce already used + { + ChainID: s.chain.config.ChainID, + Nonce: nonce - 1, + GasTipCap: common.Big1, + GasFeeCap: s.chain.Head().BaseFee(), + Gas: 100000, + }, + // Value exceeds balance + { + Nonce: nonce, + GasTipCap: common.Big1, + GasFeeCap: s.chain.Head().BaseFee(), + Gas: 100000, + Value: s.chain.Balance(from), + }, + // Gas limit too low + { + Nonce: nonce, + GasTipCap: common.Big1, + GasFeeCap: s.chain.Head().BaseFee(), + Gas: 1337, + }, + // Code size too large + { + Nonce: nonce, + GasTipCap: common.Big1, + GasFeeCap: s.chain.Head().BaseFee(), + Data: randBuf(50), + Gas: 1_000_000, + }, + // Data too large + { + Nonce: nonce, + GasTipCap: common.Big1, + GasFeeCap: s.chain.Head().BaseFee(), + To: &common.Address{0xaa}, + Data: randBuf(128), + Gas: 5_000_000, + }, + } + + var txs []*types.Transaction + for _, inner := range inners { + tx, err := s.chain.SignTx(from, types.NewTx(inner)) + if err != nil { + t.Fatalf("failed to sign tx: %v", err) + } + txs = append(txs, tx) + } + if err := s.sendInvalidTxs(txs); err != nil { + t.Fatalf("failed to send invalid txs: %v", err) } } // TestLargeTxRequest tests whether a node can fulfill a large GetPooledTransactions // request. func (s *Suite) TestLargeTxRequest(t *utesting.T) { - // send the next block to ensure the node is no longer syncing and - // is able to accept txs - if err := s.sendNextBlock(); err != nil { + // Nudge client out of syncing mode to accept pending txs. + if err := s.engine.sendForkchoiceUpdated(); err != nil { t.Fatalf("failed to send next block: %v", err) } - // send 2000 transactions to the node - hashMap, txs, err := generateTxs(s, 2000) - if err != nil { - t.Fatalf("failed to generate transactions: %v", err) + + // Generate many transactions to seed target with. + var ( + from, nonce = s.chain.GetSender(1) + count = 2000 + txs []*types.Transaction + hashes []common.Hash + set = make(map[common.Hash]struct{}) + ) + for i := 0; i < count; i++ { + inner := &types.DynamicFeeTx{ + ChainID: s.chain.config.ChainID, + Nonce: nonce + uint64(i), + GasTipCap: common.Big1, + GasFeeCap: s.chain.Head().BaseFee(), + Gas: 75000, + } + tx, err := s.chain.SignTx(from, types.NewTx(inner)) + if err != nil { + t.Fatalf("failed to sign tx: err") + } + txs = append(txs, tx) + set[tx.Hash()] = struct{}{} + hashes = append(hashes, tx.Hash()) } - if err = sendMultipleSuccessfulTxs(t, s, txs); err != nil { - t.Fatalf("failed to send multiple txs: %v", err) + s.chain.IncNonce(from, uint64(count)) + + // Send txs. + if err := s.sendTxs(txs); err != nil { + t.Fatalf("failed to send txs: %v", err) } - // set up connection to receive to ensure node is peered with the receiving connection - // before tx request is sent + + // Set up receive connection to ensure node is peered with the receiving + // connection before tx request is sent. conn, err := s.dial() if err != nil { t.Fatalf("dial failed: %v", err) @@ -475,55 +644,62 @@ func (s *Suite) TestLargeTxRequest(t *utesting.T) { if err = conn.peer(s.chain, nil); err != nil { t.Fatalf("peering failed: %v", err) } - // create and send pooled tx request - hashes := make([]common.Hash, 0) - for _, hash := range hashMap { - hashes = append(hashes, hash) - } - getTxReq := &GetPooledTransactions{ - RequestId: 1234, - GetPooledTransactionsPacket: hashes, + // Create and send pooled tx request. + req := ð.GetPooledTransactionsPacket{ + RequestId: 1234, + GetPooledTransactionsRequest: hashes, } - if err = conn.Write(getTxReq); err != nil { + if err = conn.Write(ethProto, eth.GetPooledTransactionsMsg, req); err != nil { t.Fatalf("could not write to conn: %v", err) } - // 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 { - if _, exists := hashMap[gotTx.Hash()]; !exists { - t.Fatalf("unexpected tx received: %v", gotTx.Hash()) - } + // Check that all received transactions match those that were sent to node. + msg := new(eth.PooledTransactionsPacket) + if err := conn.ReadMsg(ethProto, eth.PooledTransactionsMsg, &msg); err != nil { + t.Fatalf("error reading from connection: %v", err) + } + if got, want := msg.RequestId, req.RequestId; got != want { + t.Fatalf("unexpected request id in response: got %d, want %d", got, want) + } + for _, got := range msg.PooledTransactionsResponse { + if _, exists := set[got.Hash()]; !exists { + t.Fatalf("unexpected tx received: %v", got.Hash()) } - default: - t.Fatalf("unexpected %s", pretty.Sdump(msg)) } } -// TestNewPooledTxs tests whether a node will do a GetPooledTransactions -// request upon receiving a NewPooledTransactionHashes announcement. +// TestNewPooledTxs tests whether a node will do a GetPooledTransactions request +// upon receiving a NewPooledTransactionHashes announcement. func (s *Suite) TestNewPooledTxs(t *utesting.T) { - // send the next block to ensure the node is no longer syncing and - // is able to accept txs - if err := s.sendNextBlock(); err != nil { + // Nudge client out of syncing mode to accept pending txs. + if err := s.engine.sendForkchoiceUpdated(); err != nil { t.Fatalf("failed to send next block: %v", err) } - - // generate 50 txs - _, txs, err := generateTxs(s, 50) - if err != nil { - t.Fatalf("failed to generate transactions: %v", err) - } - hashes := make([]common.Hash, len(txs)) - types := make([]byte, len(txs)) - sizes := make([]uint32, len(txs)) - for i, tx := range txs { + var ( + count = 50 + from, nonce = s.chain.GetSender(1) + hashes = make([]common.Hash, count) + txTypes = make([]byte, count) + sizes = make([]uint32, count) + ) + for i := 0; i < count; i++ { + inner := &types.DynamicFeeTx{ + ChainID: s.chain.config.ChainID, + Nonce: nonce + uint64(i), + GasTipCap: common.Big1, + GasFeeCap: s.chain.Head().BaseFee(), + Gas: 75000, + } + tx, err := s.chain.SignTx(from, types.NewTx(inner)) + if err != nil { + t.Fatalf("failed to sign tx: err") + } hashes[i] = tx.Hash() - types[i] = tx.Type() + txTypes[i] = tx.Type() sizes[i] = uint32(tx.Size()) } + s.chain.IncNonce(from, uint64(count)) - // send announcement + // Connect to peer. conn, err := s.dial() if err != nil { t.Fatalf("dial failed: %v", err) @@ -533,40 +709,138 @@ func (s *Suite) TestNewPooledTxs(t *utesting.T) { t.Fatalf("peering failed: %v", err) } - var ann Message = NewPooledTransactionHashes{Types: types, Sizes: sizes, Hashes: hashes} - if conn.negotiatedProtoVersion < eth.ETH68 { - ann = NewPooledTransactionHashes66(hashes) - } - err = conn.Write(ann) + // Send announcement. + ann := eth.NewPooledTransactionHashesPacket68{Types: txTypes, Sizes: sizes, Hashes: hashes} + err = conn.Write(ethProto, eth.NewPooledTransactionHashesMsg, ann) if err != nil { t.Fatalf("failed to write to connection: %v", err) } - // wait for GetPooledTxs request + // Wait for GetPooledTxs request. for { - msg := conn.readAndServe(s.chain, timeout) + msg, err := conn.ReadEth() + if err != nil { + t.Fatalf("failed to read eth msg: %v", err) + } 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)) + case *eth.GetPooledTransactionsPacket: + if len(msg.GetPooledTransactionsRequest) != len(hashes) { + t.Fatalf("unexpected number of txs requested: wanted %d, got %d", len(hashes), len(msg.GetPooledTransactionsRequest)) } return - - // ignore propagated txs from previous tests - case *NewPooledTransactionHashes66: + case *eth.NewPooledTransactionHashesPacket68: continue - case *NewPooledTransactionHashes: - continue - case *Transactions: - continue - - // ignore block announcements from previous tests - case *NewBlockHashes: - continue - case *NewBlock: + case *eth.TransactionsPacket: continue default: t.Fatalf("unexpected %s", pretty.Sdump(msg)) } } } + +func makeSidecar(data ...byte) *types.BlobTxSidecar { + var ( + blobs = make([]kzg4844.Blob, len(data)) + commitments []kzg4844.Commitment + proofs []kzg4844.Proof + ) + for i := range blobs { + blobs[i][0] = data[i] + c, _ := kzg4844.BlobToCommitment(blobs[i]) + p, _ := kzg4844.ComputeBlobProof(blobs[i], c) + commitments = append(commitments, c) + proofs = append(proofs, p) + } + return &types.BlobTxSidecar{ + Blobs: blobs, + Commitments: commitments, + Proofs: proofs, + } +} + +func (s *Suite) makeBlobTxs(count, blobs int, discriminator byte) (txs types.Transactions) { + from, nonce := s.chain.GetSender(5) + for i := 0; i < count; i++ { + // Make blob data, max of 2 blobs per tx. + blobdata := make([]byte, blobs%2) + for i := range blobdata { + blobdata[i] = discriminator + blobs -= 1 + } + inner := &types.BlobTx{ + ChainID: uint256.MustFromBig(s.chain.config.ChainID), + Nonce: nonce + uint64(i), + GasTipCap: uint256.NewInt(1), + GasFeeCap: uint256.MustFromBig(s.chain.Head().BaseFee()), + Gas: 100000, + BlobFeeCap: uint256.MustFromBig(eip4844.CalcBlobFee(*s.chain.Head().ExcessBlobGas())), + BlobHashes: makeSidecar(blobdata...).BlobHashes(), + Sidecar: makeSidecar(blobdata...), + } + tx, err := s.chain.SignTx(from, types.NewTx(inner)) + if err != nil { + panic("blob tx signing failed") + } + txs = append(txs, tx) + } + return txs +} + +func (s *Suite) TestBlobViolations(t *utesting.T) { + if err := s.engine.sendForkchoiceUpdated(); err != nil { + t.Fatalf("send fcu failed: %v", err) + } + // Create blob txs for each tests with unique tx hashes. + var ( + t1 = s.makeBlobTxs(2, 3, 0x1) + t2 = s.makeBlobTxs(2, 3, 0x2) + ) + for _, test := range []struct { + ann eth.NewPooledTransactionHashesPacket68 + resp eth.PooledTransactionsResponse + }{ + // Invalid tx size. + { + ann: eth.NewPooledTransactionHashesPacket68{ + Types: []byte{types.BlobTxType, types.BlobTxType}, + Sizes: []uint32{uint32(t1[0].Size()), uint32(t1[1].Size() + 10)}, + Hashes: []common.Hash{t1[0].Hash(), t1[1].Hash()}, + }, + resp: eth.PooledTransactionsResponse(t1), + }, + // Wrong tx type. + { + ann: eth.NewPooledTransactionHashesPacket68{ + Types: []byte{types.DynamicFeeTxType, types.BlobTxType}, + Sizes: []uint32{uint32(t2[0].Size()), uint32(t2[1].Size())}, + Hashes: []common.Hash{t2[0].Hash(), t2[1].Hash()}, + }, + resp: eth.PooledTransactionsResponse(t2), + }, + } { + conn, err := s.dial() + if err != nil { + t.Fatalf("dial fail: %v", err) + } + if err := conn.peer(s.chain, nil); err != nil { + t.Fatalf("peering failed: %v", err) + } + if err := conn.Write(ethProto, eth.NewPooledTransactionHashesMsg, test.ann); err != nil { + t.Fatalf("sending announcement failed: %v", err) + } + req := new(eth.GetPooledTransactionsPacket) + if err := conn.ReadMsg(ethProto, eth.GetPooledTransactionsMsg, req); err != nil { + t.Fatalf("reading pooled tx request failed: %v", err) + } + resp := eth.PooledTransactionsPacket{RequestId: req.RequestId, PooledTransactionsResponse: test.resp} + if err := conn.Write(ethProto, eth.PooledTransactionsMsg, resp); err != nil { + t.Fatalf("writing pooled tx response failed: %v", err) + } + if code, _, err := conn.Read(); err != nil { + t.Fatalf("expected disconnect on blob violation, got err: %v", err) + } else if code != discMsg { + t.Fatalf("expected disconnect on blob violation, got msg code: %d", code) + } + conn.Close() + } +} diff --git a/cmd/devp2p/internal/ethtest/suite_test.go b/cmd/devp2p/internal/ethtest/suite_test.go index c5bcc3db1..79146c8ab 100644 --- a/cmd/devp2p/internal/ethtest/suite_test.go +++ b/cmd/devp2p/internal/ethtest/suite_test.go @@ -17,37 +17,53 @@ package ethtest import ( + crand "crypto/rand" + "fmt" "os" + "path" "testing" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" ) -var ( - genesisFile = "./testdata/genesis.json" - halfchainFile = "./testdata/halfchain.rlp" - fullchainFile = "./testdata/chain.rlp" -) +func makeJWTSecret() (string, [32]byte, error) { + var secret [32]byte + if _, err := crand.Read(secret[:]); err != nil { + return "", secret, fmt.Errorf("failed to create jwt secret: %v", err) + } + jwtPath := path.Join(os.TempDir(), "jwt_secret") + if err := os.WriteFile(jwtPath, []byte(hexutil.Encode(secret[:])), 0600); err != nil { + return "", secret, fmt.Errorf("failed to prepare jwt secret file: %v", err) + } + return jwtPath, secret, nil +} func TestEthSuite(t *testing.T) { - geth, err := runGeth() + jwtPath, secret, err := makeJWTSecret() + if err != nil { + t.Fatalf("could not make jwt secret: %v", err) + } + geth, err := runGeth("./testdata", jwtPath) if err != nil { t.Fatalf("could not run geth: %v", err) } defer geth.Close() - suite, err := NewSuite(geth.Server().Self(), fullchainFile, genesisFile) + suite, err := NewSuite(geth.Server().Self(), "./testdata", geth.HTTPAuthEndpoint(), common.Bytes2Hex(secret[:])) if err != nil { t.Fatalf("could not create new test suite: %v", err) } for _, test := range suite.EthTests() { t.Run(test.Name, func(t *testing.T) { - result := utesting.RunTAP([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout) + result := utesting.RunTests([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout) if result[0].Failed { t.Fatal() } @@ -56,19 +72,23 @@ func TestEthSuite(t *testing.T) { } func TestSnapSuite(t *testing.T) { - geth, err := runGeth() + jwtPath, secret, err := makeJWTSecret() + if err != nil { + t.Fatalf("could not make jwt secret: %v", err) + } + geth, err := runGeth("./testdata", jwtPath) if err != nil { t.Fatalf("could not run geth: %v", err) } defer geth.Close() - suite, err := NewSuite(geth.Server().Self(), fullchainFile, genesisFile) + suite, err := NewSuite(geth.Server().Self(), "./testdata", geth.HTTPAuthEndpoint(), common.Bytes2Hex(secret[:])) if err != nil { t.Fatalf("could not create new test suite: %v", err) } for _, test := range suite.SnapTests() { t.Run(test.Name, func(t *testing.T) { - result := utesting.RunTAP([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout) + result := utesting.RunTests([]utesting.Test{{Name: test.Name, Fn: test.Fn}}, os.Stdout) if result[0].Failed { t.Fatal() } @@ -77,20 +97,23 @@ func TestSnapSuite(t *testing.T) { } // runGeth creates and starts a geth node -func runGeth() (*node.Node, error) { +func runGeth(dir string, jwtPath string) (*node.Node, error) { stack, err := node.New(&node.Config{ + AuthAddr: "127.0.0.1", + AuthPort: 0, P2P: p2p.Config{ ListenAddr: "127.0.0.1:0", NoDiscovery: true, MaxPeers: 10, // in case a test requires multiple connections, can be changed in the future NoDial: true, }, + JWTSecret: jwtPath, }) if err != nil { return nil, err } - err = setupGeth(stack) + err = setupGeth(stack, dir) if err != nil { stack.Close() return nil, err @@ -102,12 +125,11 @@ func runGeth() (*node.Node, error) { return stack, nil } -func setupGeth(stack *node.Node) error { - chain, err := loadChain(halfchainFile, genesisFile) +func setupGeth(stack *node.Node, dir string) error { + chain, err := NewChain(dir) if err != nil { return err } - backend, err := eth.New(stack, ðconfig.Config{ Genesis: &chain.genesis, NetworkId: chain.genesis.Config.ChainID.Uint64(), // 19763 @@ -120,7 +142,9 @@ func setupGeth(stack *node.Node) error { if err != nil { return err } - + if err := catalyst.Register(stack, backend); err != nil { + return fmt.Errorf("failed to register catalyst service: %v", err) + } _, err = backend.BlockChain().InsertChain(chain.blocks[1:]) return err } diff --git a/cmd/devp2p/internal/ethtest/testdata/accounts.json b/cmd/devp2p/internal/ethtest/testdata/accounts.json new file mode 100644 index 000000000..c9666235a --- /dev/null +++ b/cmd/devp2p/internal/ethtest/testdata/accounts.json @@ -0,0 +1,62 @@ +{ + "0x0c2c51a0990aee1d73c1228de158688341557508": { + "key": "0xbfcd0e032489319f4e5ca03e643b2025db624be6cf99cbfed90c4502e3754850" + }, + "0x14e46043e63d0e3cdcf2530519f4cfaf35058cb2": { + "key": "0x457075f6822ac29481154792f65c5f1ec335b4fea9ca20f3fea8fa1d78a12c68" + }, + "0x16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": { + "key": "0x865898edcf43206d138c93f1bbd86311f4657b057658558888aa5ac4309626a6" + }, + "0x1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": { + "key": "0xee7f7875d826d7443ccc5c174e38b2c436095018774248a8074ee92d8914dcdb" + }, + "0x1f5bde34b4afc686f136c7a3cb6ec376f7357759": { + "key": "0x25e6ce8611cefb5cd338aeaa9292ed2139714668d123a4fb156cabb42051b5b7" + }, + "0x2d389075be5be9f2246ad654ce152cf05990b209": { + "key": "0x19168cd7767604b3d19b99dc3da1302b9ccb6ee9ad61660859e07acd4a2625dd" + }, + "0x3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": { + "key": "0x71aa7d299c7607dabfc3d0e5213d612b5e4a97455b596c2f642daac43fa5eeaa" + }, + "0x4340ee1b812acb40a1eb561c019c327b243b92df": { + "key": "0x47f666f20e2175606355acec0ea1b37870c15e5797e962340da7ad7972a537e8" + }, + "0x4a0f1452281bcec5bd90c3dce6162a5995bfe9df": { + "key": "0xa88293fefc623644969e2ce6919fb0dbd0fd64f640293b4bf7e1a81c97e7fc7f" + }, + "0x4dde844b71bcdf95512fb4dc94e84fb67b512ed8": { + "key": "0x6e1e16a9c15641c73bf6e237f9293ab1d4e7c12b9adf83cfc94bcf969670f72d" + }, + "0x5f552da00dfb4d3749d9e62dcee3c918855a86a0": { + "key": "0x41be4e00aac79f7ffbb3455053ec05e971645440d594c047cdcc56a3c7458bd6" + }, + "0x654aa64f5fbefb84c270ec74211b81ca8c44a72e": { + "key": "0xc825f31cd8792851e33a290b3d749e553983111fc1f36dfbbdb45f101973f6a9" + }, + "0x717f8aa2b982bee0e29f573d31df288663e1ce16": { + "key": "0x8d0faa04ae0f9bc3cd4c890aa025d5f40916f4729538b19471c0beefe11d9e19" + }, + "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": { + "key": "0x4552dbe6ca4699322b5d923d0c9bcdd24644f5db8bf89a085b67c6c49b8a1b91" + }, + "0x83c7e323d189f18725ac510004fdc2941f8c4a78": { + "key": "0x34391cbbf06956bb506f45ec179cdd84df526aa364e27bbde65db9c15d866d00" + }, + "0x84e75c28348fb86acea1a93a39426d7d60f4cc46": { + "key": "0xf6a8f1603b8368f3ca373292b7310c53bec7b508aecacd442554ebc1c5d0c856" + }, + "0xc7b99a164efd027a93f147376cc7da7c67c6bbe0": { + "key": "0x8d56bcbcf2c1b7109e1396a28d7a0234e33544ade74ea32c460ce4a443b239b1" + }, + "0xd803681e487e6ac18053afc5a6cd813c86ec3e4d": { + "key": "0xfc39d1c9ddbba176d806ebb42d7460189fe56ca163ad3eb6143bfc6beb6f6f72" + }, + "0xe7d13f7aa2a838d24c59b40186a0aca1e21cffcc": { + "key": "0x9ee3fd550664b246ad7cdba07162dd25530a3b1d51476dd1d85bbc29f0592684" + }, + "0xeda8645ba6948855e3b3cd596bbb07596d59c603": { + "key": "0x14cdde09d1640eb8c3cda063891b0453073f57719583381ff78811efa6d4199f" + } +} \ No newline at end of file diff --git a/cmd/devp2p/internal/ethtest/testdata/chain.rlp b/cmd/devp2p/internal/ethtest/testdata/chain.rlp index 5ebc2f3bb..2964c02bb 100644 Binary files a/cmd/devp2p/internal/ethtest/testdata/chain.rlp and b/cmd/devp2p/internal/ethtest/testdata/chain.rlp differ diff --git a/cmd/devp2p/internal/ethtest/testdata/forkenv.json b/cmd/devp2p/internal/ethtest/testdata/forkenv.json new file mode 100644 index 000000000..86c49e2b9 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/testdata/forkenv.json @@ -0,0 +1,20 @@ +{ + "HIVE_CANCUN_TIMESTAMP": "840", + "HIVE_CHAIN_ID": "3503995874084926", + "HIVE_FORK_ARROW_GLACIER": "60", + "HIVE_FORK_BERLIN": "48", + "HIVE_FORK_BYZANTIUM": "18", + "HIVE_FORK_CONSTANTINOPLE": "24", + "HIVE_FORK_GRAY_GLACIER": "66", + "HIVE_FORK_HOMESTEAD": "0", + "HIVE_FORK_ISTANBUL": "36", + "HIVE_FORK_LONDON": "54", + "HIVE_FORK_MUIR_GLACIER": "42", + "HIVE_FORK_PETERSBURG": "30", + "HIVE_FORK_SPURIOUS": "12", + "HIVE_FORK_TANGERINE": "6", + "HIVE_MERGE_BLOCK_ID": "72", + "HIVE_NETWORK_ID": "3503995874084926", + "HIVE_SHANGHAI_TIMESTAMP": "780", + "HIVE_TERMINAL_TOTAL_DIFFICULTY": "9454784" +} \ No newline at end of file diff --git a/cmd/devp2p/internal/ethtest/testdata/genesis.json b/cmd/devp2p/internal/ethtest/testdata/genesis.json index b4de6e85a..e8bb66bb3 100644 --- a/cmd/devp2p/internal/ethtest/testdata/genesis.json +++ b/cmd/devp2p/internal/ethtest/testdata/genesis.json @@ -1,27 +1,112 @@ { - "config": { - "chainId": 19763, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "terminalTotalDifficultyPassed": true, - "ethash": {} - }, - "nonce": "0xdeadbeefdeadbeef", - "timestamp": "0x0", - "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x80000000", - "difficulty": "0x20000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "alloc": { - "71562b71999873db5b286df957af199ec94617f7": { - "balance": "0xffffffffffffffffffffffffff" - } - }, - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" -} + "config": { + "chainId": 3503995874084926, + "homesteadBlock": 0, + "eip150Block": 6, + "eip155Block": 12, + "eip158Block": 12, + "byzantiumBlock": 18, + "constantinopleBlock": 24, + "petersburgBlock": 30, + "istanbulBlock": 36, + "muirGlacierBlock": 42, + "berlinBlock": 48, + "londonBlock": 54, + "arrowGlacierBlock": 60, + "grayGlacierBlock": 66, + "mergeNetsplitBlock": 72, + "shanghaiTime": 780, + "cancunTime": 840, + "terminalTotalDifficulty": 9454784, + "terminalTotalDifficultyPassed": true, + "ethash": {} + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x68697665636861696e", + "gasLimit": "0x23f3e20", + "difficulty": "0x20000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "balance": "0x2a" + }, + "0c2c51a0990aee1d73c1228de158688341557508": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "14e46043e63d0e3cdcf2530519f4cfaf35058cb2": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "1f5bde34b4afc686f136c7a3cb6ec376f7357759": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "2d389075be5be9f2246ad654ce152cf05990b209": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "4340ee1b812acb40a1eb561c019c327b243b92df": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "4a0f1452281bcec5bd90c3dce6162a5995bfe9df": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "4dde844b71bcdf95512fb4dc94e84fb67b512ed8": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "5f552da00dfb4d3749d9e62dcee3c918855a86a0": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "654aa64f5fbefb84c270ec74211b81ca8c44a72e": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "717f8aa2b982bee0e29f573d31df288663e1ce16": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "83c7e323d189f18725ac510004fdc2941f8c4a78": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "84e75c28348fb86acea1a93a39426d7d60f4cc46": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "8bebc8ba651aee624937e7d897853ac30c95a067": { + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000000000000000000000000000000000000000000003" + }, + "balance": "0x1", + "nonce": "0x1" + }, + "c7b99a164efd027a93f147376cc7da7c67c6bbe0": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "d803681e487e6ac18053afc5a6cd813c86ec3e4d": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "e7d13f7aa2a838d24c59b40186a0aca1e21cffcc": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + }, + "eda8645ba6948855e3b3cd596bbb07596d59c603": { + "balance": "0xc097ce7bc90715b34b9f1000000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": null, + "excessBlobGas": null, + "blobGasUsed": null +} \ No newline at end of file diff --git a/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp b/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp deleted file mode 100644 index 1a820734e..000000000 Binary files a/cmd/devp2p/internal/ethtest/testdata/halfchain.rlp and /dev/null differ diff --git a/cmd/devp2p/internal/ethtest/testdata/headblock.json b/cmd/devp2p/internal/ethtest/testdata/headblock.json new file mode 100644 index 000000000..e84e96b0f --- /dev/null +++ b/cmd/devp2p/internal/ethtest/testdata/headblock.json @@ -0,0 +1,23 @@ +{ + "parentHash": "0x96a73007443980c5e0985dfbb45279aa496dadea16918ad42c65c0bf8122ec39", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xea4c1f4d9fa8664c22574c5b2f948a78c4b1a753cebc1861e7fb5b1aa21c5a94", + "transactionsRoot": "0xecda39025fc4c609ce778d75eed0aa53b65ce1e3d1373b34bad8578cc31e5b48", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x1f4", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x1388", + "extraData": "0x", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x7", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "parentBeaconBlockRoot": "0xf653da50cdff4733f13f7a5e338290e883bdf04adf3f112709728063ea965d6c", + "hash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7" +} \ No newline at end of file diff --git a/cmd/devp2p/internal/ethtest/testdata/headfcu.json b/cmd/devp2p/internal/ethtest/testdata/headfcu.json new file mode 100644 index 000000000..920212d0c --- /dev/null +++ b/cmd/devp2p/internal/ethtest/testdata/headfcu.json @@ -0,0 +1,13 @@ +{ + "jsonrpc": "2.0", + "id": "fcu500", + "method": "engine_forkchoiceUpdatedV3", + "params": [ + { + "headBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7", + "safeBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7", + "finalizedBlockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7" + }, + null + ] +} \ No newline at end of file diff --git a/cmd/devp2p/internal/ethtest/testdata/headstate.json b/cmd/devp2p/internal/ethtest/testdata/headstate.json new file mode 100644 index 000000000..f7b076af6 --- /dev/null +++ b/cmd/devp2p/internal/ethtest/testdata/headstate.json @@ -0,0 +1,4204 @@ +{ + "root": "ea4c1f4d9fa8664c22574c5b2f948a78c4b1a753cebc1861e7fb5b1aa21c5a94", + "accounts": { + "0x0000000000000000000000000000000000000000": { + "balance": "233437500000029008737", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x5380c7b7ae81a58eb98d9c78de4a1fd7fd9535fc953ed2be602daaa41767312a" + }, + "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "balance": "42", + "nonce": 0, + "root": "0xac3162a8b9dbb4318b84219f3140e7a9ec35126234120297dde10f51b25f6a26", + "codeHash": "0xf57acd40259872606d76197ef052f3d35588dadf919ee1f0e3cb9b62d3f4b02c", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000348": "0348", + "0x0000000000000000000000000000000000000000000000000000000000000352": "0352", + "0x000000000000000000000000000000000000000000000000000000000000035c": "035c", + "0x0000000000000000000000000000000000000000000000000000000000000366": "0366", + "0x0000000000000000000000000000000000000000000000000000000000000370": "0370", + "0x000000000000000000000000000000000000000000000000000000000000037a": "037a", + "0x0000000000000000000000000000000000000000000000000000000000000384": "0384", + "0x000000000000000000000000000000000000000000000000000000000000038e": "038e", + "0x0000000000000000000000000000000000000000000000000000000000000398": "0398", + "0x00000000000000000000000000000000000000000000000000000000000003a2": "03a2", + "0x00000000000000000000000000000000000000000000000000000000000003ac": "03ac", + "0x00000000000000000000000000000000000000000000000000000000000003b6": "03b6", + "0x00000000000000000000000000000000000000000000000000000000000003c0": "03c0", + "0x00000000000000000000000000000000000000000000000000000000000003ca": "03ca", + "0x00000000000000000000000000000000000000000000000000000000000003d4": "03d4", + "0x00000000000000000000000000000000000000000000000000000000000003de": "03de", + "0x00000000000000000000000000000000000000000000000000000000000003e8": "03e8", + "0x00000000000000000000000000000000000000000000000000000000000003f2": "03f2", + "0x00000000000000000000000000000000000000000000000000000000000003fc": "03fc", + "0x0000000000000000000000000000000000000000000000000000000000000406": "0406", + "0x0000000000000000000000000000000000000000000000000000000000000410": "0410", + "0x000000000000000000000000000000000000000000000000000000000000041a": "041a", + "0x0000000000000000000000000000000000000000000000000000000000000424": "0424", + "0x000000000000000000000000000000000000000000000000000000000000042e": "042e", + "0x0000000000000000000000000000000000000000000000000000000000000438": "0438", + "0x0000000000000000000000000000000000000000000000000000000000000442": "0442", + "0x000000000000000000000000000000000000000000000000000000000000044c": "044c", + "0x0000000000000000000000000000000000000000000000000000000000000456": "0456", + "0x0000000000000000000000000000000000000000000000000000000000000460": "0460", + "0x000000000000000000000000000000000000000000000000000000000000046a": "046a", + "0x0000000000000000000000000000000000000000000000000000000000000474": "0474", + "0x000000000000000000000000000000000000000000000000000000000000047e": "047e", + "0x0000000000000000000000000000000000000000000000000000000000000488": "0488", + "0x0000000000000000000000000000000000000000000000000000000000000492": "0492", + "0x000000000000000000000000000000000000000000000000000000000000049c": "049c", + "0x00000000000000000000000000000000000000000000000000000000000004a6": "04a6", + "0x00000000000000000000000000000000000000000000000000000000000004b0": "04b0", + "0x00000000000000000000000000000000000000000000000000000000000004ba": "04ba", + "0x00000000000000000000000000000000000000000000000000000000000004c4": "04c4", + "0x00000000000000000000000000000000000000000000000000000000000004ce": "04ce", + "0x00000000000000000000000000000000000000000000000000000000000004d8": "04d8", + "0x00000000000000000000000000000000000000000000000000000000000004e2": "04e2", + "0x00000000000000000000000000000000000000000000000000000000000004ec": "04ec", + "0x00000000000000000000000000000000000000000000000000000000000004f6": "04f6", + "0x0000000000000000000000000000000000000000000000000000000000000500": "0500", + "0x000000000000000000000000000000000000000000000000000000000000050a": "050a", + "0x0000000000000000000000000000000000000000000000000000000000000514": "0514", + "0x000000000000000000000000000000000000000000000000000000000000051e": "051e", + "0x0000000000000000000000000000000000000000000000000000000000000528": "0528", + "0x0000000000000000000000000000000000000000000000000000000000000532": "0532", + "0x000000000000000000000000000000000000000000000000000000000000053c": "053c", + "0x0000000000000000000000000000000000000000000000000000000000000546": "0546", + "0x0000000000000000000000000000000000000000000000000000000000000550": "0550", + "0x000000000000000000000000000000000000000000000000000000000000055a": "055a", + "0x0000000000000000000000000000000000000000000000000000000000000564": "0564", + "0x000000000000000000000000000000000000000000000000000000000000056e": "056e", + "0x0000000000000000000000000000000000000000000000000000000000000578": "0578", + "0x0000000000000000000000000000000000000000000000000000000000000582": "0582", + "0x000000000000000000000000000000000000000000000000000000000000058c": "058c", + "0x0000000000000000000000000000000000000000000000000000000000000596": "0596", + "0x00000000000000000000000000000000000000000000000000000000000005a0": "05a0", + "0x00000000000000000000000000000000000000000000000000000000000005aa": "05aa", + "0x00000000000000000000000000000000000000000000000000000000000005b4": "05b4", + "0x00000000000000000000000000000000000000000000000000000000000005be": "05be", + "0x00000000000000000000000000000000000000000000000000000000000005c8": "05c8", + "0x00000000000000000000000000000000000000000000000000000000000005d2": "05d2", + "0x00000000000000000000000000000000000000000000000000000000000005dc": "05dc", + "0x00000000000000000000000000000000000000000000000000000000000005e6": "05e6", + "0x00000000000000000000000000000000000000000000000000000000000005f0": "05f0", + "0x00000000000000000000000000000000000000000000000000000000000005fa": "05fa", + "0x0000000000000000000000000000000000000000000000000000000000000604": "0604", + "0x000000000000000000000000000000000000000000000000000000000000060e": "060e", + "0x0000000000000000000000000000000000000000000000000000000000000618": "0618", + "0x0000000000000000000000000000000000000000000000000000000000000622": "0622", + "0x000000000000000000000000000000000000000000000000000000000000062c": "062c", + "0x0000000000000000000000000000000000000000000000000000000000000636": "0636", + "0x0000000000000000000000000000000000000000000000000000000000000640": "0640", + "0x000000000000000000000000000000000000000000000000000000000000064a": "064a", + "0x0000000000000000000000000000000000000000000000000000000000000654": "0654", + "0x000000000000000000000000000000000000000000000000000000000000065e": "065e", + "0x0000000000000000000000000000000000000000000000000000000000000668": "0668", + "0x0000000000000000000000000000000000000000000000000000000000000672": "0672", + "0x000000000000000000000000000000000000000000000000000000000000067c": "067c", + "0x0000000000000000000000000000000000000000000000000000000000000686": "0686", + "0x0000000000000000000000000000000000000000000000000000000000000690": "0690", + "0x000000000000000000000000000000000000000000000000000000000000069a": "069a", + "0x00000000000000000000000000000000000000000000000000000000000006a4": "06a4", + "0x00000000000000000000000000000000000000000000000000000000000006ae": "06ae", + "0x00000000000000000000000000000000000000000000000000000000000006b8": "06b8", + "0x00000000000000000000000000000000000000000000000000000000000006c2": "06c2", + "0x00000000000000000000000000000000000000000000000000000000000006cc": "06cc", + "0x00000000000000000000000000000000000000000000000000000000000006d6": "06d6", + "0x00000000000000000000000000000000000000000000000000000000000006e0": "06e0", + "0x00000000000000000000000000000000000000000000000000000000000006ea": "06ea", + "0x00000000000000000000000000000000000000000000000000000000000006f4": "06f4", + "0x00000000000000000000000000000000000000000000000000000000000006fe": "06fe", + "0x0000000000000000000000000000000000000000000000000000000000000708": "0708", + "0x0000000000000000000000000000000000000000000000000000000000000712": "0712", + "0x000000000000000000000000000000000000000000000000000000000000071c": "071c", + "0x0000000000000000000000000000000000000000000000000000000000000726": "0726", + "0x0000000000000000000000000000000000000000000000000000000000000730": "0730", + "0x000000000000000000000000000000000000000000000000000000000000073a": "073a", + "0x0000000000000000000000000000000000000000000000000000000000000744": "0744", + "0x000000000000000000000000000000000000000000000000000000000000074e": "074e", + "0x0000000000000000000000000000000000000000000000000000000000000758": "0758", + "0x0000000000000000000000000000000000000000000000000000000000000762": "0762", + "0x000000000000000000000000000000000000000000000000000000000000076c": "076c", + "0x0000000000000000000000000000000000000000000000000000000000000776": "0776", + "0x0000000000000000000000000000000000000000000000000000000000000780": "0780", + "0x000000000000000000000000000000000000000000000000000000000000078a": "078a", + "0x0000000000000000000000000000000000000000000000000000000000000794": "0794", + "0x000000000000000000000000000000000000000000000000000000000000079e": "079e", + "0x00000000000000000000000000000000000000000000000000000000000007a8": "07a8", + "0x00000000000000000000000000000000000000000000000000000000000007b2": "07b2", + "0x00000000000000000000000000000000000000000000000000000000000007bc": "07bc", + "0x00000000000000000000000000000000000000000000000000000000000007c6": "07c6", + "0x00000000000000000000000000000000000000000000000000000000000007d0": "07d0", + "0x00000000000000000000000000000000000000000000000000000000000007da": "07da", + "0x00000000000000000000000000000000000000000000000000000000000007e4": "07e4", + "0x00000000000000000000000000000000000000000000000000000000000007ee": "07ee", + "0x00000000000000000000000000000000000000000000000000000000000007f8": "07f8", + "0x0000000000000000000000000000000000000000000000000000000000000802": "0802", + "0x000000000000000000000000000000000000000000000000000000000000080c": "080c", + "0x0000000000000000000000000000000000000000000000000000000000000816": "0816", + "0x0000000000000000000000000000000000000000000000000000000000000820": "0820", + "0x000000000000000000000000000000000000000000000000000000000000082a": "082a", + "0x0000000000000000000000000000000000000000000000000000000000000834": "0834", + "0x000000000000000000000000000000000000000000000000000000000000083e": "083e", + "0x0000000000000000000000000000000000000000000000000000000000000848": "0848", + "0x0000000000000000000000000000000000000000000000000000000000000852": "0852", + "0x000000000000000000000000000000000000000000000000000000000000085c": "085c", + "0x0000000000000000000000000000000000000000000000000000000000000866": "0866", + "0x0000000000000000000000000000000000000000000000000000000000000870": "0870", + "0x000000000000000000000000000000000000000000000000000000000000087a": "087a", + "0x0000000000000000000000000000000000000000000000000000000000000884": "0884", + "0x000000000000000000000000000000000000000000000000000000000000088e": "088e", + "0x0000000000000000000000000000000000000000000000000000000000000898": "0898", + "0x00000000000000000000000000000000000000000000000000000000000008a2": "08a2", + "0x00000000000000000000000000000000000000000000000000000000000008ac": "08ac", + "0x00000000000000000000000000000000000000000000000000000000000008b6": "08b6", + "0x00000000000000000000000000000000000000000000000000000000000008c0": "08c0", + "0x00000000000000000000000000000000000000000000000000000000000008ca": "08ca", + "0x00000000000000000000000000000000000000000000000000000000000008d4": "08d4", + "0x00000000000000000000000000000000000000000000000000000000000008de": "08de", + "0x00000000000000000000000000000000000000000000000000000000000008e8": "08e8", + "0x00000000000000000000000000000000000000000000000000000000000008f2": "08f2", + "0x00000000000000000000000000000000000000000000000000000000000008fc": "08fc", + "0x0000000000000000000000000000000000000000000000000000000000000906": "0906", + "0x0000000000000000000000000000000000000000000000000000000000000910": "0910", + "0x000000000000000000000000000000000000000000000000000000000000091a": "091a", + "0x0000000000000000000000000000000000000000000000000000000000000924": "0924", + "0x000000000000000000000000000000000000000000000000000000000000092e": "092e", + "0x0000000000000000000000000000000000000000000000000000000000000938": "0938", + "0x0000000000000000000000000000000000000000000000000000000000000942": "0942", + "0x000000000000000000000000000000000000000000000000000000000000094c": "094c", + "0x0000000000000000000000000000000000000000000000000000000000000956": "0956", + "0x0000000000000000000000000000000000000000000000000000000000000960": "0960", + "0x000000000000000000000000000000000000000000000000000000000000096a": "096a", + "0x0000000000000000000000000000000000000000000000000000000000000974": "0974", + "0x000000000000000000000000000000000000000000000000000000000000097e": "097e", + "0x0000000000000000000000000000000000000000000000000000000000000988": "0988", + "0x0000000000000000000000000000000000000000000000000000000000000992": "0992", + "0x000000000000000000000000000000000000000000000000000000000000099c": "099c", + "0x00000000000000000000000000000000000000000000000000000000000009a6": "09a6", + "0x00000000000000000000000000000000000000000000000000000000000009b0": "09b0", + "0x00000000000000000000000000000000000000000000000000000000000009ba": "09ba", + "0x00000000000000000000000000000000000000000000000000000000000009c4": "09c4", + "0x00000000000000000000000000000000000000000000000000000000000009ce": "09ce", + "0x00000000000000000000000000000000000000000000000000000000000009d8": "09d8", + "0x00000000000000000000000000000000000000000000000000000000000009e2": "09e2", + "0x00000000000000000000000000000000000000000000000000000000000009ec": "09ec", + "0x00000000000000000000000000000000000000000000000000000000000009f6": "09f6", + "0x0000000000000000000000000000000000000000000000000000000000000a00": "0a00", + "0x0000000000000000000000000000000000000000000000000000000000000a0a": "0a0a", + "0x0000000000000000000000000000000000000000000000000000000000000a14": "0a14", + "0x0000000000000000000000000000000000000000000000000000000000000a1e": "0a1e", + "0x0000000000000000000000000000000000000000000000000000000000000a28": "0a28", + "0x0000000000000000000000000000000000000000000000000000000000000a32": "0a32", + "0x0000000000000000000000000000000000000000000000000000000000000a3c": "0a3c", + "0x0000000000000000000000000000000000000000000000000000000000000a46": "0a46", + "0x0000000000000000000000000000000000000000000000000000000000000a50": "0a50", + "0x0000000000000000000000000000000000000000000000000000000000000a5a": "0a5a", + "0x0000000000000000000000000000000000000000000000000000000000000a64": "0a64", + "0x0000000000000000000000000000000000000000000000000000000000000a6e": "0a6e", + "0x0000000000000000000000000000000000000000000000000000000000000a78": "0a78", + "0x0000000000000000000000000000000000000000000000000000000000000a82": "0a82", + "0x0000000000000000000000000000000000000000000000000000000000000a8c": "0a8c", + "0x0000000000000000000000000000000000000000000000000000000000000a96": "0a96", + "0x0000000000000000000000000000000000000000000000000000000000000aa0": "0aa0", + "0x0000000000000000000000000000000000000000000000000000000000000aaa": "0aaa", + "0x0000000000000000000000000000000000000000000000000000000000000ab4": "0ab4", + "0x0000000000000000000000000000000000000000000000000000000000000abe": "0abe", + "0x0000000000000000000000000000000000000000000000000000000000000ac8": "0ac8", + "0x0000000000000000000000000000000000000000000000000000000000000ad2": "0ad2", + "0x0000000000000000000000000000000000000000000000000000000000000adc": "0adc", + "0x0000000000000000000000000000000000000000000000000000000000000ae6": "0ae6", + "0x0000000000000000000000000000000000000000000000000000000000000af0": "0af0", + "0x0000000000000000000000000000000000000000000000000000000000000afa": "0afa", + "0x0000000000000000000000000000000000000000000000000000000000000b04": "0b04", + "0x0000000000000000000000000000000000000000000000000000000000000b0e": "0b0e", + "0x0000000000000000000000000000000000000000000000000000000000000b18": "0b18", + "0x0000000000000000000000000000000000000000000000000000000000000b22": "0b22", + "0x0000000000000000000000000000000000000000000000000000000000000b2c": "0b2c", + "0x0000000000000000000000000000000000000000000000000000000000000b36": "0b36", + "0x0000000000000000000000000000000000000000000000000000000000000b40": "0b40", + "0x0000000000000000000000000000000000000000000000000000000000000b4a": "0b4a", + "0x0000000000000000000000000000000000000000000000000000000000000b54": "0b54", + "0x0000000000000000000000000000000000000000000000000000000000000b5e": "0b5e", + "0x0000000000000000000000000000000000000000000000000000000000000b68": "0b68", + "0x0000000000000000000000000000000000000000000000000000000000000b72": "0b72", + "0x0000000000000000000000000000000000000000000000000000000000000b7c": "0b7c", + "0x0000000000000000000000000000000000000000000000000000000000000b86": "0b86", + "0x0000000000000000000000000000000000000000000000000000000000000b90": "0b90", + "0x0000000000000000000000000000000000000000000000000000000000000b9a": "0b9a", + "0x0000000000000000000000000000000000000000000000000000000000000ba4": "0ba4", + "0x0000000000000000000000000000000000000000000000000000000000000bae": "0bae", + "0x0000000000000000000000000000000000000000000000000000000000000bb8": "0bb8", + "0x0000000000000000000000000000000000000000000000000000000000000bc2": "0bc2", + "0x0000000000000000000000000000000000000000000000000000000000000bcc": "0bcc", + "0x0000000000000000000000000000000000000000000000000000000000000bd6": "0bd6", + "0x0000000000000000000000000000000000000000000000000000000000000be0": "0be0", + "0x0000000000000000000000000000000000000000000000000000000000000bea": "0bea", + "0x0000000000000000000000000000000000000000000000000000000000000bf4": "0bf4", + "0x0000000000000000000000000000000000000000000000000000000000000bfe": "0bfe", + "0x0000000000000000000000000000000000000000000000000000000000000c08": "0c08", + "0x0000000000000000000000000000000000000000000000000000000000000c12": "0c12", + "0x0000000000000000000000000000000000000000000000000000000000000c1c": "0c1c", + "0x0000000000000000000000000000000000000000000000000000000000000c26": "0c26", + "0x0000000000000000000000000000000000000000000000000000000000000c30": "0c30", + "0x0000000000000000000000000000000000000000000000000000000000000c3a": "0c3a", + "0x0000000000000000000000000000000000000000000000000000000000000c44": "0c44", + "0x0000000000000000000000000000000000000000000000000000000000000c4e": "0c4e", + "0x0000000000000000000000000000000000000000000000000000000000000c58": "0c58", + "0x0000000000000000000000000000000000000000000000000000000000000c62": "0c62", + "0x0000000000000000000000000000000000000000000000000000000000000c6c": "0c6c", + "0x0000000000000000000000000000000000000000000000000000000000000c76": "0c76", + "0x0000000000000000000000000000000000000000000000000000000000000c80": "0c80", + "0x0000000000000000000000000000000000000000000000000000000000000c8a": "0c8a", + "0x0000000000000000000000000000000000000000000000000000000000000c94": "0c94", + "0x0000000000000000000000000000000000000000000000000000000000000c9e": "0c9e", + "0x0000000000000000000000000000000000000000000000000000000000000ca8": "0ca8", + "0x0000000000000000000000000000000000000000000000000000000000000cb2": "0cb2", + "0x0000000000000000000000000000000000000000000000000000000000000cbc": "0cbc", + "0x0000000000000000000000000000000000000000000000000000000000000cc6": "0cc6", + "0x0000000000000000000000000000000000000000000000000000000000000cd0": "0cd0", + "0x0000000000000000000000000000000000000000000000000000000000000cda": "0cda", + "0x0000000000000000000000000000000000000000000000000000000000000ce4": "0ce4", + "0x0000000000000000000000000000000000000000000000000000000000000cee": "0cee", + "0x0000000000000000000000000000000000000000000000000000000000000cf8": "0cf8", + "0x0000000000000000000000000000000000000000000000000000000000000d02": "0d02", + "0x0000000000000000000000000000000000000000000000000000000000000d0c": "0d0c", + "0x0000000000000000000000000000000000000000000000000000000000000d16": "0d16", + "0x0000000000000000000000000000000000000000000000000000000000000d20": "0d20", + "0x0000000000000000000000000000000000000000000000000000000000000d2a": "0d2a", + "0x0000000000000000000000000000000000000000000000000000000000000d34": "0d34", + "0x0000000000000000000000000000000000000000000000000000000000000d3e": "0d3e", + "0x0000000000000000000000000000000000000000000000000000000000000d48": "0d48", + "0x0000000000000000000000000000000000000000000000000000000000000d52": "0d52", + "0x0000000000000000000000000000000000000000000000000000000000000d5c": "0d5c", + "0x0000000000000000000000000000000000000000000000000000000000000d66": "0d66", + "0x0000000000000000000000000000000000000000000000000000000000000d70": "0d70", + "0x0000000000000000000000000000000000000000000000000000000000000d7a": "0d7a", + "0x0000000000000000000000000000000000000000000000000000000000000d84": "0d84", + "0x0000000000000000000000000000000000000000000000000000000000000d8e": "0d8e", + "0x0000000000000000000000000000000000000000000000000000000000000d98": "0d98", + "0x0000000000000000000000000000000000000000000000000000000000000da2": "0da2", + "0x0000000000000000000000000000000000000000000000000000000000000dac": "0dac", + "0x0000000000000000000000000000000000000000000000000000000000000db6": "0db6", + "0x0000000000000000000000000000000000000000000000000000000000000dc0": "0dc0", + "0x0000000000000000000000000000000000000000000000000000000000000dca": "0dca", + "0x0000000000000000000000000000000000000000000000000000000000000dd4": "0dd4", + "0x0000000000000000000000000000000000000000000000000000000000000dde": "0dde", + "0x0000000000000000000000000000000000000000000000000000000000000de8": "0de8", + "0x0000000000000000000000000000000000000000000000000000000000000df2": "0df2", + "0x0000000000000000000000000000000000000000000000000000000000000dfc": "0dfc", + "0x0000000000000000000000000000000000000000000000000000000000000e06": "0e06", + "0x0000000000000000000000000000000000000000000000000000000000000e10": "0e10", + "0x0000000000000000000000000000000000000000000000000000000000000e1a": "0e1a", + "0x0000000000000000000000000000000000000000000000000000000000000e24": "0e24", + "0x0000000000000000000000000000000000000000000000000000000000000e2e": "0e2e", + "0x0000000000000000000000000000000000000000000000000000000000000e38": "0e38", + "0x0000000000000000000000000000000000000000000000000000000000000e42": "0e42", + "0x0000000000000000000000000000000000000000000000000000000000000e4c": "0e4c", + "0x0000000000000000000000000000000000000000000000000000000000000e56": "0e56", + "0x0000000000000000000000000000000000000000000000000000000000000e60": "0e60", + "0x0000000000000000000000000000000000000000000000000000000000000e6a": "0e6a", + "0x0000000000000000000000000000000000000000000000000000000000000e74": "0e74", + "0x0000000000000000000000000000000000000000000000000000000000000e7e": "0e7e", + "0x0000000000000000000000000000000000000000000000000000000000000e88": "0e88", + "0x0000000000000000000000000000000000000000000000000000000000000e92": "0e92", + "0x0000000000000000000000000000000000000000000000000000000000000e9c": "0e9c", + "0x0000000000000000000000000000000000000000000000000000000000000ea6": "0ea6", + "0x0000000000000000000000000000000000000000000000000000000000000eb0": "0eb0", + "0x0000000000000000000000000000000000000000000000000000000000000eba": "0eba", + "0x0000000000000000000000000000000000000000000000000000000000000ec4": "0ec4", + "0x0000000000000000000000000000000000000000000000000000000000000ece": "0ece", + "0x0000000000000000000000000000000000000000000000000000000000000ed8": "0ed8", + "0x0000000000000000000000000000000000000000000000000000000000000ee2": "0ee2", + "0x0000000000000000000000000000000000000000000000000000000000000eec": "0eec", + "0x0000000000000000000000000000000000000000000000000000000000000ef6": "0ef6", + "0x0000000000000000000000000000000000000000000000000000000000000f00": "0f00", + "0x0000000000000000000000000000000000000000000000000000000000000f0a": "0f0a", + "0x0000000000000000000000000000000000000000000000000000000000000f14": "0f14", + "0x0000000000000000000000000000000000000000000000000000000000000f1e": "0f1e", + "0x0000000000000000000000000000000000000000000000000000000000000f28": "0f28", + "0x0000000000000000000000000000000000000000000000000000000000000f32": "0f32", + "0x0000000000000000000000000000000000000000000000000000000000000f3c": "0f3c", + "0x0000000000000000000000000000000000000000000000000000000000000f46": "0f46", + "0x0000000000000000000000000000000000000000000000000000000000000f50": "0f50", + "0x0000000000000000000000000000000000000000000000000000000000000f5a": "0f5a", + "0x0000000000000000000000000000000000000000000000000000000000000f64": "0f64", + "0x0000000000000000000000000000000000000000000000000000000000000f6e": "0f6e", + "0x0000000000000000000000000000000000000000000000000000000000000f78": "0f78", + "0x0000000000000000000000000000000000000000000000000000000000000f82": "0f82", + "0x0000000000000000000000000000000000000000000000000000000000000f8c": "0f8c", + "0x0000000000000000000000000000000000000000000000000000000000000f96": "0f96", + "0x0000000000000000000000000000000000000000000000000000000000000fa0": "0fa0", + "0x0000000000000000000000000000000000000000000000000000000000000faa": "0faa", + "0x0000000000000000000000000000000000000000000000000000000000000fb4": "0fb4", + "0x0000000000000000000000000000000000000000000000000000000000000fbe": "0fbe", + "0x0000000000000000000000000000000000000000000000000000000000000fc8": "0fc8", + "0x0000000000000000000000000000000000000000000000000000000000000fd2": "0fd2", + "0x0000000000000000000000000000000000000000000000000000000000000fdc": "0fdc", + "0x0000000000000000000000000000000000000000000000000000000000000fe6": "0fe6", + "0x0000000000000000000000000000000000000000000000000000000000000ff0": "0ff0", + "0x0000000000000000000000000000000000000000000000000000000000000ffa": "0ffa", + "0x0000000000000000000000000000000000000000000000000000000000001004": "1004", + "0x000000000000000000000000000000000000000000000000000000000000100e": "100e", + "0x0000000000000000000000000000000000000000000000000000000000001018": "1018", + "0x0000000000000000000000000000000000000000000000000000000000001022": "1022", + "0x000000000000000000000000000000000000000000000000000000000000102c": "102c", + "0x0000000000000000000000000000000000000000000000000000000000001036": "1036", + "0x0000000000000000000000000000000000000000000000000000000000001040": "1040", + "0x000000000000000000000000000000000000000000000000000000000000104a": "104a", + "0x0000000000000000000000000000000000000000000000000000000000001054": "1054", + "0x000000000000000000000000000000000000000000000000000000000000105e": "105e", + "0x0000000000000000000000000000000000000000000000000000000000001068": "1068", + "0x0000000000000000000000000000000000000000000000000000000000001072": "1072", + "0x000000000000000000000000000000000000000000000000000000000000107c": "107c", + "0x0000000000000000000000000000000000000000000000000000000000001086": "1086", + "0x0000000000000000000000000000000000000000000000000000000000001090": "1090", + "0x000000000000000000000000000000000000000000000000000000000000109a": "109a", + "0x00000000000000000000000000000000000000000000000000000000000010a4": "10a4", + "0x00000000000000000000000000000000000000000000000000000000000010ae": "10ae", + "0x00000000000000000000000000000000000000000000000000000000000010b8": "10b8", + "0x00000000000000000000000000000000000000000000000000000000000010c2": "10c2", + "0x00000000000000000000000000000000000000000000000000000000000010cc": "10cc", + "0x00000000000000000000000000000000000000000000000000000000000010d6": "10d6", + "0x00000000000000000000000000000000000000000000000000000000000010e0": "10e0", + "0x00000000000000000000000000000000000000000000000000000000000010ea": "10ea", + "0x00000000000000000000000000000000000000000000000000000000000010f4": "10f4", + "0x00000000000000000000000000000000000000000000000000000000000010fe": "10fe", + "0x0000000000000000000000000000000000000000000000000000000000001108": "1108", + "0x0000000000000000000000000000000000000000000000000000000000001112": "1112", + "0x000000000000000000000000000000000000000000000000000000000000111c": "111c", + "0x0000000000000000000000000000000000000000000000000000000000001126": "1126", + "0x0000000000000000000000000000000000000000000000000000000000001130": "1130", + "0x000000000000000000000000000000000000000000000000000000000000113a": "113a", + "0x0000000000000000000000000000000000000000000000000000000000001144": "1144", + "0x000000000000000000000000000000000000000000000000000000000000114e": "114e", + "0x0000000000000000000000000000000000000000000000000000000000001158": "1158", + "0x0000000000000000000000000000000000000000000000000000000000001162": "1162", + "0x000000000000000000000000000000000000000000000000000000000000116c": "116c", + "0x0000000000000000000000000000000000000000000000000000000000001176": "1176", + "0x0000000000000000000000000000000000000000000000000000000000001180": "1180", + "0x000000000000000000000000000000000000000000000000000000000000118a": "118a", + "0x0000000000000000000000000000000000000000000000000000000000001194": "1194", + "0x000000000000000000000000000000000000000000000000000000000000119e": "119e", + "0x00000000000000000000000000000000000000000000000000000000000011a8": "11a8", + "0x00000000000000000000000000000000000000000000000000000000000011b2": "11b2", + "0x00000000000000000000000000000000000000000000000000000000000011bc": "11bc", + "0x00000000000000000000000000000000000000000000000000000000000011c6": "11c6", + "0x00000000000000000000000000000000000000000000000000000000000011d0": "11d0", + "0x00000000000000000000000000000000000000000000000000000000000011da": "11da", + "0x00000000000000000000000000000000000000000000000000000000000011e4": "11e4", + "0x00000000000000000000000000000000000000000000000000000000000011ee": "11ee", + "0x00000000000000000000000000000000000000000000000000000000000011f8": "11f8", + "0x0000000000000000000000000000000000000000000000000000000000001202": "1202", + "0x000000000000000000000000000000000000000000000000000000000000120c": "120c", + "0x0000000000000000000000000000000000000000000000000000000000001216": "1216", + "0x0000000000000000000000000000000000000000000000000000000000001220": "1220", + "0x000000000000000000000000000000000000000000000000000000000000122a": "122a", + "0x0000000000000000000000000000000000000000000000000000000000001234": "1234", + "0x000000000000000000000000000000000000000000000000000000000000123e": "123e", + "0x0000000000000000000000000000000000000000000000000000000000001248": "1248", + "0x0000000000000000000000000000000000000000000000000000000000001252": "1252", + "0x000000000000000000000000000000000000000000000000000000000000125c": "125c", + "0x0000000000000000000000000000000000000000000000000000000000001266": "1266", + "0x0000000000000000000000000000000000000000000000000000000000001270": "1270", + "0x000000000000000000000000000000000000000000000000000000000000127a": "127a", + "0x0000000000000000000000000000000000000000000000000000000000001284": "1284", + "0x000000000000000000000000000000000000000000000000000000000000128e": "128e", + "0x0000000000000000000000000000000000000000000000000000000000001298": "1298", + "0x00000000000000000000000000000000000000000000000000000000000012a2": "12a2", + "0x00000000000000000000000000000000000000000000000000000000000012ac": "12ac", + "0x00000000000000000000000000000000000000000000000000000000000012b6": "12b6", + "0x00000000000000000000000000000000000000000000000000000000000012c0": "12c0", + "0x00000000000000000000000000000000000000000000000000000000000012ca": "12ca", + "0x00000000000000000000000000000000000000000000000000000000000012d4": "12d4", + "0x00000000000000000000000000000000000000000000000000000000000012de": "12de", + "0x00000000000000000000000000000000000000000000000000000000000012e8": "12e8", + "0x00000000000000000000000000000000000000000000000000000000000012f2": "12f2", + "0x00000000000000000000000000000000000000000000000000000000000012fc": "12fc", + "0x0000000000000000000000000000000000000000000000000000000000001306": "1306", + "0x0000000000000000000000000000000000000000000000000000000000001310": "1310", + "0x000000000000000000000000000000000000000000000000000000000000131a": "131a", + "0x0000000000000000000000000000000000000000000000000000000000001324": "1324", + "0x000000000000000000000000000000000000000000000000000000000000132e": "132e", + "0x0000000000000000000000000000000000000000000000000000000000001338": "1338", + "0x0000000000000000000000000000000000000000000000000000000000001342": "1342", + "0x000000000000000000000000000000000000000000000000000000000000134c": "134c", + "0x0000000000000000000000000000000000000000000000000000000000001356": "1356", + "0x0000000000000000000000000000000000000000000000000000000000001360": "1360", + "0x000000000000000000000000000000000000000000000000000000000000136a": "136a", + "0x0000000000000000000000000000000000000000000000000000000000001374": "1374", + "0x000000000000000000000000000000000000000000000000000000000000137e": "137e", + "0x0000000000000000000000000000000000000000000000000000000000001388": "1388", + "0x0000000000000000000000000000000000000000000000000000000000002347": "83472eda6eb475906aeeb7f09e757ba9f6663b9f6a5bf8611d6306f677f67ebd", + "0x0000000000000000000000000000000000000000000000000000000000002351": "2c809fbc7e3991c8ab560d1431fa8b6f25be4ab50977f0294dfeca9677866b6e", + "0x000000000000000000000000000000000000000000000000000000000000235b": "756e335a8778f6aadb2cc18c5bc68892da05a4d8b458eee5ce3335a024000c67", + "0x0000000000000000000000000000000000000000000000000000000000002365": "4b118bd31ed2c4eeb81dc9e3919e9989994333fe36f147c2930f12c53f0d3c78", + "0x000000000000000000000000000000000000000000000000000000000000236f": "d0122166752d729620d41114ff5a94d36e5d3e01b449c23844900c023d1650a5", + "0x0000000000000000000000000000000000000000000000000000000000002379": "60c606c4c44709ac87b367f42d2453744639fc5bee099a11f170de98408c8089", + "0x0000000000000000000000000000000000000000000000000000000000002383": "6ee04e1c27edad89a8e5a2253e4d9cca06e4f57d063ed4fe7cc1c478bb57eeca", + "0x000000000000000000000000000000000000000000000000000000000000238d": "36616354a17658eb3c3e8e5adda6253660e3744cb8b213006f04302b723749a8", + "0x0000000000000000000000000000000000000000000000000000000000002397": "c13802d4378dcb9c616f0c60ea0edd90e6c2dacf61f39ca06add0eaa67473b94", + "0x00000000000000000000000000000000000000000000000000000000000023a1": "8b345497936c51d077f414534be3f70472e4df101dee8820eaaff91a6624557b", + "0x00000000000000000000000000000000000000000000000000000000000023ab": "e958485d4b3e47b38014cc4eaeb75f13228072e7b362a56fc3ffe10155882629", + "0x00000000000000000000000000000000000000000000000000000000000023b5": "3346706b38a2331556153113383581bc6f66f209fdef502f9fc9b6daf6ea555e", + "0x00000000000000000000000000000000000000000000000000000000000023bf": "346910f7e777c596be32f0dcf46ccfda2efe8d6c5d3abbfe0f76dba7437f5dad", + "0x00000000000000000000000000000000000000000000000000000000000023c9": "e62a7bd9263534b752176d1ff1d428fcc370a3b176c4a6312b6016c2d5f8d546", + "0x00000000000000000000000000000000000000000000000000000000000023d3": "ffe267d11268388fd0426a627dedddeb075d68327df9172c0445cd2979ec7e4d", + "0x00000000000000000000000000000000000000000000000000000000000023dd": "23cc648c9cd82c08214882b7e28e026d6eb56920f90f64731bb09b6acf515427", + "0x00000000000000000000000000000000000000000000000000000000000023e7": "47c896f5986ec29f58ec60eec56ed176910779e9fc9cf45c3c090126aeb21acd", + "0x00000000000000000000000000000000000000000000000000000000000023f1": "6d19894928a3ab44077bb85dcb47e0865ce1c4c187bba26bad059aa774c03cfe", + "0x00000000000000000000000000000000000000000000000000000000000023fb": "efc50f4fc1430b6d5d043065201692a4a02252fef0699394631f5213a5667547", + "0x0000000000000000000000000000000000000000000000000000000000002405": "3cc9f65fc1f46927eb46fbf6d14bc94af078fe8ff982a984bdd117152cd1549f", + "0x000000000000000000000000000000000000000000000000000000000000240f": "63eb547e9325bc34fbbbdfda327a71dc929fd8ab6509795e56479e95dbd40a80", + "0x0000000000000000000000000000000000000000000000000000000000002419": "67317288cf707b0325748c7947e2dda5e8b41e45e62330d00d80e9be403e5c4c", + "0x0000000000000000000000000000000000000000000000000000000000002423": "7fc37e0d22626f96f345b05516c8a3676b9e1de01d354e5eb9524f6776966885", + "0x000000000000000000000000000000000000000000000000000000000000242d": "c8c5ffb6f192e9bda046ecd4ebb995af53c9dd6040f4ba8d8db9292c1310e43f", + "0x0000000000000000000000000000000000000000000000000000000000002437": "e40a9cfd9babe862d482ca0c07c0a4086641d16c066620cb048c6e673c5a4f91", + "0x0000000000000000000000000000000000000000000000000000000000002441": "e82e7cff48aea45fb3f7b199b0b173497bf4c5ea66ff840e2ec618d7eb3d7470", + "0x000000000000000000000000000000000000000000000000000000000000244b": "84ceda57767ea709da7ab17897a70da1868c9670931da38f2438519a5249534d", + "0x0000000000000000000000000000000000000000000000000000000000002455": "e9dcf640383969359c944cff24b75f71740627f596110ee8568fa09f9a06db1c", + "0x000000000000000000000000000000000000000000000000000000000000245f": "430ef678bb92f1af44dcd77af9c5b59fb87d0fc4a09901a54398ad5b7e19a8f4", + "0x0000000000000000000000000000000000000000000000000000000000002469": "f7af0b8b729cd17b7826259bc183b196dbd318bd7229d5e8085bf4849c0b12bf", + "0x0000000000000000000000000000000000000000000000000000000000002473": "e134e19217f1b4c7e11f193561056303a1f67b69dac96ff79a6d0aafa994f7cb", + "0x000000000000000000000000000000000000000000000000000000000000247d": "9cc58ab1a8cb0e983550e61f754aea1dd4f58ac6482a816dc50658de750de613", + "0x0000000000000000000000000000000000000000000000000000000000002487": "79c2b067779a94fd3756070885fc8eab5e45033bde69ab17c0173d553df02978", + "0x0000000000000000000000000000000000000000000000000000000000002491": "d908ef75d05b895600d3f9938cb5259612c71223b68d30469ff657d61c6b1611", + "0x000000000000000000000000000000000000000000000000000000000000249b": "e0d31906b7c46ac7f38478c0872d3c634f7113d54ef0b57ebfaf7f993959f5a3", + "0x00000000000000000000000000000000000000000000000000000000000024a5": "2318f5c5e6865200ad890e0a8db21c780a226bec0b2e29af1cb3a0d9b40196ae", + "0x00000000000000000000000000000000000000000000000000000000000024af": "523997f8d8fed954658f547954fdeceab818b411862647f2b61a3619f6a4d4bc", + "0x00000000000000000000000000000000000000000000000000000000000024b9": "be3396540ea36c6928cccdcfe6c669666edbbbcd4be5e703f59de0e3c2720da7", + "0x00000000000000000000000000000000000000000000000000000000000024c3": "2d3fcfd65d0a6881a2e8684d03c2aa27aee6176514d9f6d8ebb3b766f85e1039", + "0x00000000000000000000000000000000000000000000000000000000000024cd": "7ce0d5c253a7f910cca7416e949ac04fdaec20a518ab6fcbe4a63d8b439a5cfc", + "0x00000000000000000000000000000000000000000000000000000000000024d7": "4da13d835ea44926ee13f34ce8fcd4b9d3dc65be0a351115cf404234c7fbd256", + "0x00000000000000000000000000000000000000000000000000000000000024e1": "c5ee7483802009b45feabf4c5f701ec485f27bf7d2c4477b200ac53e210e9844", + "0x00000000000000000000000000000000000000000000000000000000000024eb": "0fc71295326a7ae8e0776c61be67f3ed8770311df88e186405b8d75bd0be552b", + "0x00000000000000000000000000000000000000000000000000000000000024f5": "7313b4315dd27586f940f8f2bf8af76825d8f24d2ae2c24d885dcb0cdd8d50f5", + "0x00000000000000000000000000000000000000000000000000000000000024ff": "2739473baa23a9bca4e8d0f4f221cfa48440b4b73e2bae7386c14caccc6c2059", + "0x0000000000000000000000000000000000000000000000000000000000002509": "d4da00e33a11ee18f67b25ad5ff574cddcdccaa30e6743e01a531336b16cbf8f", + "0x0000000000000000000000000000000000000000000000000000000000002513": "e651765d4860f0c46f191212c8193e7c82708e5d8bef1ed6f19bdde577f980cf", + "0x000000000000000000000000000000000000000000000000000000000000251d": "5b5b49487967b3b60bd859ba2fb13290c6eaf67e97e9f9f9dda935c08564b5f6", + "0x0000000000000000000000000000000000000000000000000000000000002527": "57b73780cc42a6a36676ce7008459d5ba206389dc9300f1aecbd77c4b90277fa", + "0x0000000000000000000000000000000000000000000000000000000000002531": "217e8514ea30f1431dc3cd006fe730df721f961cebb5d0b52069d1b4e1ae5d13", + "0x000000000000000000000000000000000000000000000000000000000000253b": "14b775119c252908bb10b13de9f8ae988302e1ea8b2e7a1b6d3c8ae24ba9396b", + "0x0000000000000000000000000000000000000000000000000000000000002545": "e736f0b3c5672f76332a38a6c1e66e5f39e0d01f1ddede2c24671f48e78daf63", + "0x000000000000000000000000000000000000000000000000000000000000254f": "7d112c85b58c64c576d34ea7a7c18287981885892fbf95110e62add156ca572e", + "0x0000000000000000000000000000000000000000000000000000000000002559": "28fbeedc649ed9d2a6feda6e5a2576949da6812235ebdfd030f8105d012f5074", + "0x0000000000000000000000000000000000000000000000000000000000002563": "6f7410cf59e390abe233de2a3e3fe022b63b78a92f6f4e3c54aced57b6c3daa6", + "0x000000000000000000000000000000000000000000000000000000000000256d": "d5edc3d8781deea3b577e772f51949a8866f2aa933149f622f05cde2ebba9adb", + "0x0000000000000000000000000000000000000000000000000000000000002577": "20308d99bc1e1b1b0717f32b9a3a869f4318f5f0eb4ed81fddd10696c9746c6b", + "0x0000000000000000000000000000000000000000000000000000000000002581": "91f7a302057a2e21d5e0ef4b8eea75dfb8b37f2c2db05c5a84517aaebc9d5131", + "0x000000000000000000000000000000000000000000000000000000000000258b": "743e5d0a5be47d489b121edb9f98dad7d0a85fc260909083656fabaf6d404774", + "0x0000000000000000000000000000000000000000000000000000000000002595": "cdcf99c6e2e7d0951f762e787bdbe0e2b3b320815c9d2be91e9cd0848653e839", + "0x000000000000000000000000000000000000000000000000000000000000259f": "cc9476183d27810e9738f382c7f2124976735ed89bbafc7dc19c99db8cfa9ad1", + "0x00000000000000000000000000000000000000000000000000000000000025a9": "f67e5fab2e7cacf5b89acd75ec53b0527d45435adddac6ee7523a345dcbcdceb", + "0x00000000000000000000000000000000000000000000000000000000000025b3": "e20f8ab522b2f0d12c068043852139965161851ad910b840db53604c8774a579", + "0x00000000000000000000000000000000000000000000000000000000000025bd": "f982160785861cb970559d980208dd00e6a2ec315f5857df175891b171438eeb", + "0x00000000000000000000000000000000000000000000000000000000000025c7": "230954c737211b72d5c7dcfe420bb07d5d72f2b4868c5976dd22c00d3df0c0b6", + "0x00000000000000000000000000000000000000000000000000000000000025d1": "b7743e65d6bbe09d5531f1bc98964f75943d8c13e27527ca6afd40ca069265d4", + "0x00000000000000000000000000000000000000000000000000000000000025db": "31ac943dc649c639fa6221400183ca827c07b812a6fbfc1795eb835aa280adf3", + "0x00000000000000000000000000000000000000000000000000000000000025e5": "ded49c937c48d466987a4130f4b6d04ef658029673c3afc99f70f33b552e178d", + "0x00000000000000000000000000000000000000000000000000000000000025ef": "a0effc449cab515020d2012897155a792bce529cbd8d5a4cf94d0bbf141afeb6", + "0x00000000000000000000000000000000000000000000000000000000000025f9": "1f36d9c66a0d437d8e49ffaeaa00f341e9630791b374e8bc0c16059c7445721f", + "0x0000000000000000000000000000000000000000000000000000000000002603": "34f89e6134f26e7110b47ffc942a847d8c03deeed1b33b9c041218c4e1a1a4e6", + "0x000000000000000000000000000000000000000000000000000000000000260d": "774404c430041ca4a58fdc281e99bf6fcb014973165370556d9e73fdec6d597b", + "0x0000000000000000000000000000000000000000000000000000000000002617": "d616971210c381584bf4846ab5837b53e062cbbb89d112c758b4bd00ce577f09", + "0x0000000000000000000000000000000000000000000000000000000000002621": "cdf6383634b0431468f6f5af19a2b7a087478b42489608c64555ea1ae0a7ee19", + "0x000000000000000000000000000000000000000000000000000000000000262b": "ec22e5df77320b4142c54fceaf2fe7ea30d1a72dc9c969a22acf66858d582b", + "0x0000000000000000000000000000000000000000000000000000000000002635": "cb32d77facfda4decff9e08df5a5810fa42585fdf96f0db9b63b196116fbb6af", + "0x000000000000000000000000000000000000000000000000000000000000263f": "6d76316f272f0212123d0b4b21d16835fe6f7a2b4d1960386d8a161da2b7c6a2", + "0x0000000000000000000000000000000000000000000000000000000000002649": "2de2da72ae329e359b655fc6311a707b06dc930126a27261b0e8ec803bdb5cbf", + "0x0000000000000000000000000000000000000000000000000000000000002653": "08bed4b39d14dc1e72e80f605573cde6145b12693204f9af18bbc94a82389500", + "0x000000000000000000000000000000000000000000000000000000000000265d": "e437f0465ac29b0e889ef4f577c939dd39363c08fcfc81ee61aa0b4f55805f69", + "0x0000000000000000000000000000000000000000000000000000000000002667": "89ca120183cc7085b6d4674d779fc4fbc9de520779bfbc3ebf65f9663cb88080", + "0x0000000000000000000000000000000000000000000000000000000000002671": "b15d5954c7b78ab09ede922684487c7a60368e82fdc7b5a0916842e58a44422b", + "0x000000000000000000000000000000000000000000000000000000000000267b": "ad13055a49d2b6a4ffc8b781998ff79086adad2fd6470a0563a43b740128c5f2", + "0x0000000000000000000000000000000000000000000000000000000000002685": "9e9909e4ed44f5539427ee3bc70ee8b630ccdaea4d0f1ed5337a067e8337119f", + "0x000000000000000000000000000000000000000000000000000000000000268f": "bf1f3aba184e08d4c650f05fe3d948bdda6c2d6982f277f2cd6b1a60cd4f3dac", + "0x0000000000000000000000000000000000000000000000000000000000002699": "bb70fe131f94783dba356c8d4d9d319247ef61c768134303f0db85ee3ef0496f", + "0x00000000000000000000000000000000000000000000000000000000000026a3": "6a81ebd3bde6cc54a2521aa72de29ef191e3b56d94953439a72cafdaa2996da0", + "0x00000000000000000000000000000000000000000000000000000000000026ad": "4c83e809a52ac52a587d94590c35c71b72742bd15915fca466a9aaec4f2dbfed", + "0x00000000000000000000000000000000000000000000000000000000000026b7": "268fc70790f00ad0759497585267fbdc92afba63ba01e211faae932f0639854a", + "0x00000000000000000000000000000000000000000000000000000000000026c1": "7e544f42df99d5666085b70bc57b3ca175be50b7a9643f26f464124df632d562", + "0x00000000000000000000000000000000000000000000000000000000000026cb": "d59cf5f55903ba577be835706b27d78a50cacb25271f35a5f57fcb88a3b576f3", + "0x00000000000000000000000000000000000000000000000000000000000026d5": "551cced461be11efdeaf8e47f3a91bb66d532af7294c4461c8009c5833bdbf57", + "0x00000000000000000000000000000000000000000000000000000000000026df": "c1e0e6907a57eefd12f1f95d28967146c836d72d281e7609de23d0a02351e978", + "0x00000000000000000000000000000000000000000000000000000000000026e9": "9d580c0ac3a7f00fdc3b135b758ae7c80ab135e907793fcf9621a3a3023ca205", + "0x00000000000000000000000000000000000000000000000000000000000026f3": "a7fd4dbac4bb62307ac7ad285ffa6a11ec679d950de2bd41839b8a846e239886", + "0x00000000000000000000000000000000000000000000000000000000000026fd": "6ba7b0ac30a04e11a3116b43700d91359e6b06a49058e543198d4b21e75fb165", + "0x0000000000000000000000000000000000000000000000000000000000002707": "8835104ed35ffd4db64660b9049e1c0328e502fd4f3744749e69183677b8474b", + "0x0000000000000000000000000000000000000000000000000000000000002711": "562f276b9f9ed46303e700c8863ad75fadff5fc8df27a90744ea04ad1fe8e801", + "0x000000000000000000000000000000000000000000000000000000000000271b": "d19f68026d22ae0f60215cfe4a160986c60378f554c763651d872ed82ad69ebb", + "0x0000000000000000000000000000000000000000000000000000000000002725": "f087a515b4b62d707991988eb912d082b85ecdd52effc9e8a1ddf15a74388860", + "0x000000000000000000000000000000000000000000000000000000000000272f": "f7e28b7daff5fad40ec1ef6a2b7e9066558126f62309a2ab0d0d775d892a06d6", + "0x0000000000000000000000000000000000000000000000000000000000002739": "77361844a8f4dd2451e6218d336378b837ba3fab921709708655e3f1ea91a435", + "0x0000000000000000000000000000000000000000000000000000000000002743": "e3cb33c7b05692a6f25470fbd63ab9c986970190729fab43191379da38bc0d8c", + "0x000000000000000000000000000000000000000000000000000000000000274d": "c893f9de119ec83fe37b178b5671d63448e9b5cde4de9a88cace3f52c2591194", + "0x0000000000000000000000000000000000000000000000000000000000002757": "39c96a6461782ac2efbcb5aaac2e133079b86fb29cb5ea69b0101bdad684ef0d", + "0x0000000000000000000000000000000000000000000000000000000000002761": "72a2724cdf77138638a109f691465e55d32759d3c044a6cb41ab091c574e3bdb", + "0x000000000000000000000000000000000000000000000000000000000000276b": "178ba15f24f0a8c33eed561d7927979c1215ddec20e1aef318db697ccfad0e03", + "0x0000000000000000000000000000000000000000000000000000000000002775": "f7b2c01b7c625588c9596972fdebae61db89f0d0f2b21286d4c0fa76683ff946", + "0x000000000000000000000000000000000000000000000000000000000000277f": "16e43284b041a4086ad1cbab9283d4ad3e8cc7c3a162f60b3df5538344ecdf54", + "0x0000000000000000000000000000000000000000000000000000000000002789": "0a98ea7f737e17706432eba283d50dde10891b49c3424d46918ed2b6af8ecf90", + "0x0000000000000000000000000000000000000000000000000000000000002793": "7637225dd61f90c3cb05fae157272985993b34d6c369bfe8372720339fe4ffd2", + "0x000000000000000000000000000000000000000000000000000000000000279d": "6a7d064bc053c0f437707df7c36b820cca4a2e9653dd1761941af4070f5273b6", + "0x00000000000000000000000000000000000000000000000000000000000027a7": "91c1e6eec8f7944fd6aafdce5477f45d4f6e29298c9ef628a59e441a5e071fae", + "0x00000000000000000000000000000000000000000000000000000000000027b1": "a1c227db9bbd2e49934bef01cbb506dd1e1c0671a81aabb1f90a90025980a3c3", + "0x00000000000000000000000000000000000000000000000000000000000027bb": "8fcfc1af10f3e8671505afadfd459287ae98be634083b5a35a400cc9186694cf", + "0x00000000000000000000000000000000000000000000000000000000000027c5": "cc1ea9c015bd3a6470669f85c5c13e42c1161fc79704143df347c4a621dff44f", + "0x00000000000000000000000000000000000000000000000000000000000027cf": "b0a22c625dd0c6534e29bccc9ebf94a550736e2c68140b9afe3ddc7216f797de", + "0x00000000000000000000000000000000000000000000000000000000000027d9": "92b8e6ca20622e5fd91a8f58d0d4faaf7be48a53ea262e963bcf26a1698f9df3", + "0x00000000000000000000000000000000000000000000000000000000000027e3": "f6253b8e2f31df6ca7a97086c3b4d49d9cbbbdfc5be731b0c3040a4381161c53", + "0x00000000000000000000000000000000000000000000000000000000000027ed": "ea8d762903bd24b80037d7ffe80019a086398608ead66208c18f0a5778620e67", + "0x00000000000000000000000000000000000000000000000000000000000027f7": "543382975e955588ba19809cfe126ea15dc43c0bfe6a43d861d7ad40eac2c2f4", + "0x0000000000000000000000000000000000000000000000000000000000002801": "095294f7fe3eb90cf23b3127d40842f61b85da2f48f71234fb94d957d865a8a2", + "0x000000000000000000000000000000000000000000000000000000000000280b": "144c2dd25fd12003ccd2678d69d30245b0222ce2d2bfead687931a7f6688482f", + "0x0000000000000000000000000000000000000000000000000000000000002815": "7295f7d57a3547b191f55951f548479cbb9a60b47ba38beb8d85c4ccf0e4ae4c", + "0x000000000000000000000000000000000000000000000000000000000000281f": "9e8e241e13f76a4e6d777a2dc64072de4737ac39272bb4987bcecbf60739ccf4", + "0x0000000000000000000000000000000000000000000000000000000000002829": "fc753bcea3e720490efded4853ef1a1924665883de46c21039ec43e371e96bb9", + "0x0000000000000000000000000000000000000000000000000000000000002833": "5f5204c264b5967682836ed773aee0ea209840fe628fd1c8d61702c416b427ca", + "0x000000000000000000000000000000000000000000000000000000000000283d": "5ba9a0326069e000b65b759236f46e54a0e052f379a876d242740c24f6c47aed", + "0x0000000000000000000000000000000000000000000000000000000000002847": "b40e9621d5634cd21f70274c345704af2e060c5befaeb2df109a78c7638167c2", + "0x0000000000000000000000000000000000000000000000000000000000002851": "70e26b74456e6fea452e04f8144be099b0af0e279febdff17dd4cdf9281e12a7", + "0x000000000000000000000000000000000000000000000000000000000000285b": "43d7158f48fb1f124b2962dff613c5b4b8ea415967f2b528af6e7ae280d658e5", + "0x0000000000000000000000000000000000000000000000000000000000002865": "b50b2b14efba477dddca9682df1eafc66a9811c9c5bd1ae796abbef27ba14eb4", + "0x000000000000000000000000000000000000000000000000000000000000286f": "c14936902147e9a121121f424ecd4d90313ce7fc603f3922cebb7d628ab2c8dd", + "0x0000000000000000000000000000000000000000000000000000000000002879": "86609ed192561602f181a9833573213eb7077ee69d65107fa94f657f33b144d2", + "0x0000000000000000000000000000000000000000000000000000000000002883": "0a71a6dbc360e176a0f665787ed3e092541c655024d0b136a04ceedf572c57c5", + "0x000000000000000000000000000000000000000000000000000000000000288d": "a4bcbab632ddd52cb85f039e48c111a521e8944b9bdbaf79dd7c80b20221e4d6", + "0x0000000000000000000000000000000000000000000000000000000000002897": "2bc468eab4fad397f9136f80179729b54caa2cb47c06b0695aab85cf9813620d", + "0x00000000000000000000000000000000000000000000000000000000000028a1": "fc7f9a432e6fd69aaf025f64a326ab7221311147dd99d558633579a4d8a0667b", + "0x00000000000000000000000000000000000000000000000000000000000028ab": "949613bd67fb0a68cf58a22e60e7b9b2ccbabb60d1d58c64c15e27a9dec2fb35", + "0x00000000000000000000000000000000000000000000000000000000000028b5": "289ddb1aee772ad60043ecf17a882c36a988101af91ac177954862e62012fc0e", + "0x00000000000000000000000000000000000000000000000000000000000028bf": "bfa48b05faa1a2ee14b3eaed0b75f0d265686b6ce3f2b7fa051b8dc98bc23d6a", + "0x00000000000000000000000000000000000000000000000000000000000028c9": "7bf49590a866893dc77444d89717942e09acc299eea972e8a7908e9d694a1150", + "0x00000000000000000000000000000000000000000000000000000000000028d3": "992f76aee242737eb21f14b65827f3ebc42524fb422b17f414f33c35a24092db", + "0x00000000000000000000000000000000000000000000000000000000000028dd": "da6e4f935d966e90dffc6ac0f6d137d9e9c97d65396627e5486d0089b94076fa", + "0x00000000000000000000000000000000000000000000000000000000000028e7": "65467514ed80f25b299dcf74fb74e21e9bb929832a349711cf327c2f8b60b57f", + "0x00000000000000000000000000000000000000000000000000000000000028f1": "cc2ac03d7a26ff16c990c5f67fa03dabda95641a988deec72ed2fe38c0f289d6", + "0x00000000000000000000000000000000000000000000000000000000000028fb": "096dbe9a0190c6badf79de3747abfd4d5eda3ab95b439922cae7ec0cfcd79290", + "0x0000000000000000000000000000000000000000000000000000000000002905": "0c659c769744094f60332ec247799d7ed5ae311d5738daa5dcead3f47ca7a8a2", + "0x000000000000000000000000000000000000000000000000000000000000290f": "9cb8a0d41ede6b951c29182422db215e22aedfa1a3549cd27b960a768f6ed522", + "0x0000000000000000000000000000000000000000000000000000000000002919": "2510f8256a020f4735e2be224e3bc3e8c14e56f7588315f069630fe24ce2fa26", + "0x0000000000000000000000000000000000000000000000000000000000002923": "2d3deb2385a2d230512707ece0bc6098ea788e3d5debb3911abe9a710dd332ea", + "0x000000000000000000000000000000000000000000000000000000000000292d": "1cec4b230f3bccfff7ca197c4a35cb5b95ff7785d064be3628235971b7aff27c", + "0x0000000000000000000000000000000000000000000000000000000000002937": "18e4a4238d43929180c7a626ae6f8c87a88d723b661549f2f76ff51726833598", + "0x0000000000000000000000000000000000000000000000000000000000002941": "700e1755641a437c8dc888df24a5d80f80f9eaa0d17ddab17db4eb364432a1f5", + "0x000000000000000000000000000000000000000000000000000000000000294b": "cad29ceb73b2f3c90d864a2c27a464b36b980458e2d8c4c7f32f70afad707312", + "0x0000000000000000000000000000000000000000000000000000000000002955": "a85e892063a7fd41d37142ae38037967eb047436c727fcf0bad813d316efe09f", + "0x000000000000000000000000000000000000000000000000000000000000295f": "040100f17208bcbd9456c62d98846859f7a5efa0e45a5b3a6f0b763b9c700fec", + "0x0000000000000000000000000000000000000000000000000000000000002969": "49d54a5147de1f5208c509b194af6d64b509398e4f255c20315131e921f7bd04", + "0x0000000000000000000000000000000000000000000000000000000000002973": "810ff6fcafb9373a4df3e91ab1ca64a2955c9e42ad8af964f829e38e0ea4ee20", + "0x000000000000000000000000000000000000000000000000000000000000297d": "9b72096b8b672ac6ff5362c56f5d06446d1693c5d2daa94a30755aa636320e78", + "0x0000000000000000000000000000000000000000000000000000000000002987": "f68bff777db51db5f29afc4afe38bd1bf5cdec29caa0dc52535b529e6d99b742", + "0x0000000000000000000000000000000000000000000000000000000000002991": "9566690bde717eec59f828a2dba90988fa268a98ed224f8bc02b77bce10443c4", + "0x000000000000000000000000000000000000000000000000000000000000299b": "d0e821fbd57a4d382edd638b5c1e6deefb81352d41aa97da52db13f330e03097", + "0x00000000000000000000000000000000000000000000000000000000000029a5": "43f9aa6fa63739abec56c4604874523ac6dabfcc08bb283195072aeb29d38dfe", + "0x00000000000000000000000000000000000000000000000000000000000029af": "54ebfa924e887a63d643a8277c3394317de0e02e63651b58b6eb0e90df8a20cd", + "0x00000000000000000000000000000000000000000000000000000000000029b9": "9e414c994ee35162d3b718c47f8435edc2c93394a378cb41037b671366791fc8", + "0x00000000000000000000000000000000000000000000000000000000000029c3": "4356f072bb235238abefb3330465814821097327842b6e0dc4a0ef95680c4d34", + "0x00000000000000000000000000000000000000000000000000000000000029cd": "215df775ab368f17ed3f42058861768a3fba25e8d832a00b88559ca5078b8fbc", + "0x00000000000000000000000000000000000000000000000000000000000029d7": "d17835a18d61605a04d2e50c4f023966a47036e5c59356a0463db90a76f06e3e", + "0x00000000000000000000000000000000000000000000000000000000000029e1": "875032d74e62dbfd73d4617754d36cd88088d1e5a7c5354bf3e0906c749e6637", + "0x00000000000000000000000000000000000000000000000000000000000029eb": "6f22ae25f70f4b03a2a2b17f370ace1f2b15d17fc7c2457824348a8f2a1eff9f", + "0x00000000000000000000000000000000000000000000000000000000000029f5": "f11fdf2cb985ce7472dc7c6b422c3a8bf2dfbbc6b86b15a1fa62cf9ebae8f6cf", + "0x00000000000000000000000000000000000000000000000000000000000029ff": "bbc97696e588f80fbe0316ad430fd4146a29c19b926248febe757cd9408deddc", + "0x0000000000000000000000000000000000000000000000000000000000002a09": "71dd15be02efd9f3d5d94d0ed9b5e60a205f439bb46abe6226879e857668881e", + "0x0000000000000000000000000000000000000000000000000000000000002a13": "b90e98bd91f1f7cc5c4456bb7a8868a2bb2cd3dda4b5dd6463b88728526dceea", + "0x0000000000000000000000000000000000000000000000000000000000002a1d": "4e80fd3123fda9b404a737c9210ccb0bacc95ef93ac40e06ce9f7511012426c4", + "0x0000000000000000000000000000000000000000000000000000000000002a27": "afb50d96b2543048dc93045b62357cc18b64d0e103756ce3ad0e04689dd88282", + "0x0000000000000000000000000000000000000000000000000000000000002a31": "d73341a1c9edd04a890f949ede6cc1e942ad62b63b6a60177f0f692f141a7e95", + "0x0000000000000000000000000000000000000000000000000000000000002a3b": "c26601e9613493118999d9268b401707e42496944ccdbfa91d5d7b791a6d18f1", + "0x0000000000000000000000000000000000000000000000000000000000002a45": "fb4619fb12e1b9c4b508797833eef7df65fcf255488660d502def2a7ddceef6d", + "0x0000000000000000000000000000000000000000000000000000000000002a4f": "d08b7458cd9d52905403f6f4e9dac15ad18bea1f834858bf48ecae36bf854f98", + "0x0000000000000000000000000000000000000000000000000000000000002a59": "df979da2784a3bb9e07c368094dc640aafc514502a62a58b464e50e5e50a34bd", + "0x0000000000000000000000000000000000000000000000000000000000002a63": "15855037d4712ce0019f0169dcd58b58493be8373d29decfa80b8df046e3d6ba", + "0x0000000000000000000000000000000000000000000000000000000000002a6d": "fd1462a68630956a33e4b65c8e171a08a131097bc7faf5d7f90b5503ab30b69c", + "0x0000000000000000000000000000000000000000000000000000000000002a77": "edad57fee633c4b696e519f84ad1765afbef5d2781b382acd9b8dfcf6cd6d572", + "0x0000000000000000000000000000000000000000000000000000000000002a81": "c2641ba296c2daa6edf09b63d0f1cfcefd51451fbbc283b6802cbd5392fb145c", + "0x0000000000000000000000000000000000000000000000000000000000002a8b": "5615d64e1d3a10972cdea4e4b106b4b6e832bc261129f9ab1d10a670383ae446", + "0x0000000000000000000000000000000000000000000000000000000000002a95": "0757c6141fad938002092ff251a64190b060d0e31c31b08fb56b0f993cc4ef0d", + "0x0000000000000000000000000000000000000000000000000000000000002a9f": "14ddc31bc9f9c877ae92ca1958e6f3affca7cc3064537d0bbe8ba4d2072c0961", + "0x0000000000000000000000000000000000000000000000000000000000002aa9": "490b0f08777ad4364f523f94dccb3f56f4aacb2fb4db1bb042a786ecfd248c79", + "0x0000000000000000000000000000000000000000000000000000000000002ab3": "4a37c0e55f539f2ecafa0ce71ee3d80bc9fe33fb841583073c9f524cc5a2615a", + "0x0000000000000000000000000000000000000000000000000000000000002abd": "133295fdf94e5e4570e27125807a77272f24622750bcf408be0360ba0dcc89f2", + "0x0000000000000000000000000000000000000000000000000000000000002ac7": "a73eb87c45c96b121f9ab081c095bff9a49cfe5a374f316e9a6a66096f532972", + "0x0000000000000000000000000000000000000000000000000000000000002ad1": "9040bc28f6e830ca50f459fc3dac39a6cd261ccc8cd1cca5429d59230c10f34c", + "0x0000000000000000000000000000000000000000000000000000000000002adb": "ec1d134c49cde6046ee295672a8f11663b6403fb71338181a89dc6bc92f7dea8", + "0x0000000000000000000000000000000000000000000000000000000000002ae5": "3130a4c80497c65a7ee6ac20f6888a95bd5b05636d6b4bd13d616dcb01591e16", + "0x0000000000000000000000000000000000000000000000000000000000002aef": "ccdfd5b42f2cbd29ab125769380fc1b18a9d272ac5d3508a6bbe4c82360ebcca", + "0x0000000000000000000000000000000000000000000000000000000000002af9": "74342c7f25ee7dd1ae6eb9cf4e5ce5bcab56c798aea36b554ccb31a660e123af", + "0x0000000000000000000000000000000000000000000000000000000000002b03": "f6f75f51a452481c30509e5de96edae82892a61f8c02c88d710dc782b5f01fc7", + "0x0000000000000000000000000000000000000000000000000000000000002b0d": "7ce6539cc82db9730b8c21b12d6773925ff7d1a46c9e8f6c986ada96351f36e9", + "0x0000000000000000000000000000000000000000000000000000000000002b17": "1983684da5e48936b761c5e5882bbeb5e42c3a7efe92989281367fa5ab25e918", + "0x0000000000000000000000000000000000000000000000000000000000002b21": "c564aa993f2b446325ee674146307601dd87eb7409266a97e695e4bb09dd8bf5", + "0x0000000000000000000000000000000000000000000000000000000000002b2b": "9ca2ff57d59decb7670d5f49bcca68fdaf494ba7dc06214d8e838bfcf7a2824e", + "0x0000000000000000000000000000000000000000000000000000000000002b35": "6d7b7476cecc036d470a691755f9988409059bd104579c0a2ded58f144236045", + "0x0000000000000000000000000000000000000000000000000000000000002b3f": "417504d79d00b85a29f58473a7ad643f88e9cdfe5da2ed25a5965411390fda4a", + "0x0000000000000000000000000000000000000000000000000000000000002b49": "e910eb040bf32e56e9447d63497799419957ed7df2572e89768b9139c6fa6a23", + "0x0000000000000000000000000000000000000000000000000000000000002b53": "8e462d3d5b17f0157bc100e785e1b8d2ad3262e6f27238fa7e9c62ba29e9c692", + "0x0000000000000000000000000000000000000000000000000000000000002b5d": "3e6f040dc96b2e05961c4e28df076fa654761f4b0e2e30f5e36b06f65d1893c1", + "0x0000000000000000000000000000000000000000000000000000000000002b67": "07e71d03691704a4bd83c728529642884fc1b1a8cfeb1ddcbf659c9b71367637", + "0x0000000000000000000000000000000000000000000000000000000000002b71": "f4d05f5986e4b92a845467d2ae6209ca9b7c6c63ff9cdef3df180660158163ef", + "0x0000000000000000000000000000000000000000000000000000000000002b7b": "5ca251408392b25af49419f1ecd9338d1f4b5afa536dc579ab54e1e3ee6914d4", + "0x0000000000000000000000000000000000000000000000000000000000002b85": "e98b64599520cf62e68ce0e2cdf03a21d3712c81fa74b5ade4885b7d8aec531b", + "0x0000000000000000000000000000000000000000000000000000000000002b8f": "d62ec5a2650450e26aac71a21d45ef795e57c231d28a18d077a01f761bc648fe", + "0x0000000000000000000000000000000000000000000000000000000000002b99": "4d3fb38cf24faf44f5b37f248553713af2aa9c3d99ddad4a534e49cd06bb8098", + "0x0000000000000000000000000000000000000000000000000000000000002ba3": "36e90abacae8fbe712658e705ac28fa9d00118ef55fe56ea893633680147148a", + "0x0000000000000000000000000000000000000000000000000000000000002bad": "164177f08412f7e294fae37457d238c4dd76775263e2c7c9f39e8a7ceca9028a", + "0x0000000000000000000000000000000000000000000000000000000000002bb7": "aa5a5586bf2f68df5c206dbe45a9498de0a9b5a2ee92235b740971819838a010", + "0x0000000000000000000000000000000000000000000000000000000000002bc1": "99d001850f513efdc613fb7c8ede12a943ff543c578a54bebbb16daecc56cec5", + "0x0000000000000000000000000000000000000000000000000000000000002bcb": "30a4501d58b23fc7eee5310f5262783b2dd36a94922d11e5e173ec763be8accb", + "0x0000000000000000000000000000000000000000000000000000000000002bd5": "a804188a0434260c0825a988483de064ae01d3e50cb111642c4cfb65bfc2dfb7", + "0x0000000000000000000000000000000000000000000000000000000000002bdf": "c554c79292c950bce95e9ef57136684fffb847188607705454909aa5790edc64", + "0x0000000000000000000000000000000000000000000000000000000000002be9": "c89e3673025beff5031d48a885098da23d716b743449fd5533a04f25bd2cd203", + "0x0000000000000000000000000000000000000000000000000000000000002bf3": "44c310142a326a3822abeb9161413f91010858432d27c9185c800c9c2d92aea6", + "0x0000000000000000000000000000000000000000000000000000000000002bfd": "ae3f497ee4bd619d651097d3e04f50caac1f6af55b31b4cbde4faf1c5ddc21e8", + "0x0000000000000000000000000000000000000000000000000000000000002c07": "3287d70a7b87db98964e828d5c45a4fa4cd7907be3538a5e990d7a3573ccb9c1", + "0x0000000000000000000000000000000000000000000000000000000000002c11": "b52bb578e25d833410fcca7aa6f35f79844537361a43192dce8dcbc72d15e09b", + "0x0000000000000000000000000000000000000000000000000000000000002c1b": "ff8f6f17c0f6d208d27dd8b9147586037086b70baf4f70c3629e73f8f053d34f", + "0x0000000000000000000000000000000000000000000000000000000000002c25": "70bccc358ad584aacb115076c8aded45961f41920ffedf69ffa0483e0e91fa52", + "0x0000000000000000000000000000000000000000000000000000000000002c2f": "e3881eba45a97335a6d450cc37e7f82b81d297c111569e38b6ba0c5fb0ae5d71", + "0x0000000000000000000000000000000000000000000000000000000000002c39": "2217beb48c71769d8bf9caaac2858237552fd68cd4ddefb66d04551e7beaa176", + "0x0000000000000000000000000000000000000000000000000000000000002c43": "06b56638d2545a02757e7f268b25a0cd3bce792fcb1e88da21b0cc21883b9720", + "0x0000000000000000000000000000000000000000000000000000000000002c4d": "ebdc8c9e2a85a1fb6582ca30616a685ec8ec25e9c020a65a85671e8b9dacc6eb", + "0x0000000000000000000000000000000000000000000000000000000000002c57": "738f3edb9d8d273aac79f95f3877fd885e1db732e86115fa3d0da18e6c89e9cf", + "0x0000000000000000000000000000000000000000000000000000000000002c61": "ae5ccfc8201288b0c5981cdb60e16bc832ac92edc51149bfe40ff4a935a0c13a", + "0x0000000000000000000000000000000000000000000000000000000000002c6b": "69a7a19c159c0534e50a98e460707c6c280e7e355fb97cf2b5e0fd56c45a0a97", + "0x0000000000000000000000000000000000000000000000000000000000002c75": "4d2a1e9207a1466593e5903c5481a579e38e247afe5e80bd41d629ac3342e6a4", + "0x0000000000000000000000000000000000000000000000000000000000002c7f": "d3e7d679c0d232629818cbb94251c24797ce36dd2a45dbe8c77a6a345231c3b3", + "0x0000000000000000000000000000000000000000000000000000000000002c89": "d1835b94166e1856dddb6eaa1cfdcc6979193f2ff4541ab274738bd48072899c", + "0x0000000000000000000000000000000000000000000000000000000000002c93": "1f12c89436a94d427a69bca5a080edc328bd2424896f3f37223186b440deb45e", + "0x0000000000000000000000000000000000000000000000000000000000002c9d": "ccb765890b7107fd98056a257381b6b1d10a83474bbf1bdf8e6b0b8eb9cef2a9", + "0x0000000000000000000000000000000000000000000000000000000000002ca7": "8bbf4e534dbf4580edc5a973194a725b7283f7b9fbb7d7d8deb386aaceebfa84", + "0x0000000000000000000000000000000000000000000000000000000000002cb1": "85a0516088f78d837352dcf12547ee3c598dda398e78a9f4d95acfbef19f5e19", + "0x0000000000000000000000000000000000000000000000000000000000002cbb": "0f669bc7780e2e5719f9c05872a112f6511e7f189a8649cda5d8dda88d6b8ac3", + "0x0000000000000000000000000000000000000000000000000000000000002cc5": "a7816288f9712fcab6a2b6fbd0b941b8f48c2acb635580ed80c27bed7e840a57", + "0x0000000000000000000000000000000000000000000000000000000000002ccf": "da5168c8c83ac67dfc2772af49d689f11974e960dee4c4351bac637db1a39e82", + "0x0000000000000000000000000000000000000000000000000000000000002cd9": "3f720ecec02446f1af948de4eb0f54775562f2d615726375c377114515ac545b", + "0x0000000000000000000000000000000000000000000000000000000000002ce3": "273830a0087f6cef0fdb42179aa1c6c8c19f7bc83c3dc7aa1a56e4e05ca473ea", + "0x0000000000000000000000000000000000000000000000000000000000002ced": "7044f700543fd542e87e7cdb94f0126b0f6ad9488d0874a8ac903a72bade34e9", + "0x0000000000000000000000000000000000000000000000000000000000002cf7": "f63a7ff76bb9713bea8d47831a1510d2c8971accd22a403d5bbfaaa3dc310616", + "0x0000000000000000000000000000000000000000000000000000000000002d01": "a68dbd9898dd1589501ca3220784c44d41852ad997a270e215539d461ec090f8", + "0x0000000000000000000000000000000000000000000000000000000000002d0b": "59e501ae3ba9e0c3adafdf0f696d2e6a358e1bec43cbe9b0258c2335dd8d764f", + "0x0000000000000000000000000000000000000000000000000000000000002d15": "4f19cff0003bdc03c2fee20db950f0efb323be170f0b09c491a20abcf26ecf43", + "0x0000000000000000000000000000000000000000000000000000000000002d1f": "52b1b89795a8fabd3c8594bd571b44fd72279979aaa1d49ea7105c787f8f5fa6", + "0x0000000000000000000000000000000000000000000000000000000000002d29": "7c1416bd4838b93bc87990c9dcca108675bafab950dd0faf111d9eddc4e54327", + "0x0000000000000000000000000000000000000000000000000000000000002d33": "ef87a35bb6e56e7d5a1f804c63c978bbd1c1516c4eb70edad2b8143169262c9f", + "0x0000000000000000000000000000000000000000000000000000000000002d3d": "e978f25d16f468c0a0b585994d1e912837f55e1cd8849e140f484a2702385ef2", + "0x0000000000000000000000000000000000000000000000000000000000002d47": "c3e85e9260b6fad139e3c42587cc2df7a9da07fadaacaf2381ca0d4a0c91c819", + "0x0000000000000000000000000000000000000000000000000000000000002d51": "bd2647c989abfd1d340fd05add92800064ad742cd82be8c2ec5cc7df20eb0351", + "0x0000000000000000000000000000000000000000000000000000000000002d5b": "99ac5ad7b62dd843abca85e485a6d4331e006ef9d391b0e89fb2eeccef1d29a2", + "0x0000000000000000000000000000000000000000000000000000000000002d65": "02a4349c3ee7403fe2f23cad9cf2fb6933b1ae37e34c9d414dc4f64516ea9f97", + "0x0000000000000000000000000000000000000000000000000000000000002d6f": "627b41fdbdf4a95381da5e5186123bf808c119b849dfdd3f515fa8d54c19c771", + "0x0000000000000000000000000000000000000000000000000000000000002d79": "c087b16d7caa58e1361a7b158159469975f55582a4ef760465703a40123226d7", + "0x0000000000000000000000000000000000000000000000000000000000002d83": "f7a477c0c27d4890e3fb56eb2dc0386e7409d1c59cab6c7f22b84de45b4c6867", + "0x0000000000000000000000000000000000000000000000000000000000002d8d": "1cb440b7d88e98ceb953bc46b003fde2150860be05e11b9a5abae2c814a71571", + "0x0000000000000000000000000000000000000000000000000000000000002d97": "72613e3e30445e37af38976f6bb3e3bf7debbcf70156eb37c5ac4e41834f9dd2", + "0x0000000000000000000000000000000000000000000000000000000000002da1": "e69e7568b9e70ee7e71ebad9548fc8afad5ff4435df5d55624b39df9e8826c91", + "0x0000000000000000000000000000000000000000000000000000000000002dab": "c3f1682f65ee45ce7019ee7059d65f8f1b0c0a8f68f94383410f7e6f46f26577", + "0x0000000000000000000000000000000000000000000000000000000000002db5": "93ee1e4480ed7935097467737e54c595a2a6424cf8eaed5eacc2bf23ce368192", + "0x0000000000000000000000000000000000000000000000000000000000002dbf": "b07f8855348b496166d3906437b8b76fdf7918f2e87858d8a78b1deece6e2558", + "0x0000000000000000000000000000000000000000000000000000000000002dc9": "ec60e51de32061c531b80d2c515bfa8f81600b9b50fc02beaf4dc01dd6e0c9ca", + "0x0000000000000000000000000000000000000000000000000000000000002dd3": "2fc9f34b3ed6b3cabd7b2b65b4a21381ad4419670eed745007f9efa8dd365ef1", + "0x0000000000000000000000000000000000000000000000000000000000002ddd": "f4af3b701f9b088d23f93bb6d5868370ed1cdcb19532ddd164ed3f411f3e5a95", + "0x0000000000000000000000000000000000000000000000000000000000002de7": "8272e509366a028b8d6bbae2a411eb3818b5be7dac69104a4e72317e55a9e697", + "0x0000000000000000000000000000000000000000000000000000000000002df1": "a194d76f417dafe27d02a6044a913c0b494fe893840b5b745386ae6078a44e9c", + "0x0000000000000000000000000000000000000000000000000000000000002dfb": "a255e59e9a27c16430219b18984594fc1edaf88fe47dd427911020fbc0d92507", + "0x0000000000000000000000000000000000000000000000000000000000002e05": "7996946b8891ebd0623c7887dd09f50a939f6f29dea4ca3c3630f50ec3c575cb", + "0x0000000000000000000000000000000000000000000000000000000000002e0f": "b04cbab069405f18839e6c6cf85cc19beeb9ee98c159510fcb67cb84652b7db9", + "0x0000000000000000000000000000000000000000000000000000000000002e19": "6f241a5e530d1e261ef0f5800d7ff252c33ce148865926e6231d4718f0b9eded", + "0x0000000000000000000000000000000000000000000000000000000000002e23": "fcfa9f1759f8db6a7e452af747a972cf3b1b493a216dbd32db21f7c2ce279cce", + "0x0000000000000000000000000000000000000000000000000000000000002e2d": "df880227742710ac4f31c0466a6da7c56ec54caccfdb8f58e5d3f72e40e800f3", + "0x0000000000000000000000000000000000000000000000000000000000002e37": "adfe28a0f8afc89c371dc7b724c78c2e3677904d03580c7141d32ba32f0ed46f", + "0x0000000000000000000000000000000000000000000000000000000000002e41": "b264d19d2daf7d5fcf8d2214eba0aacf72cabbc7a2617219e535242258d43a31", + "0x0000000000000000000000000000000000000000000000000000000000002e4b": "f2207420648dccc4f01992831e219c717076ff3c74fb88a96676bbcfe1e63f38", + "0x0000000000000000000000000000000000000000000000000000000000002e55": "41e8fae73b31870db8546eea6e11b792e0c9daf74d2fbb6471f4f6c6aaead362", + "0x0000000000000000000000000000000000000000000000000000000000002e5f": "4e7a5876c1ee2f1833267b5bd85ac35744a258cc3d7171a8a8cd5c87811078a2", + "0x0000000000000000000000000000000000000000000000000000000000002e69": "8d4a424d1a0ee910ccdfc38c7e7f421780c337232d061e3528e025d74b362315", + "0x0000000000000000000000000000000000000000000000000000000000002e73": "fa65829d54aba84896370599f041413d50f1acdc8a178211b2960827c1f85cbf", + "0x0000000000000000000000000000000000000000000000000000000000002e7d": "da5dfc12da14eafad2ac2a1456c241c4683c6e7e40a7c3569bc618cfc9d6dca3", + "0x0000000000000000000000000000000000000000000000000000000000002e87": "16243e7995312ffa3983c5858c6560b2abc637c481746003b6c2b58c62e9a547", + "0x0000000000000000000000000000000000000000000000000000000000002e91": "b75f0189b31abbbd88cd32c47ed311c93ec429f1253ee715a1b00d1ca6a1e094", + "0x0000000000000000000000000000000000000000000000000000000000002e9b": "d087eb94d6347da9322e3904add7ff7dd0fd72b924b917a8e10dae208251b49d", + "0x0000000000000000000000000000000000000000000000000000000000002ea5": "bc17244b8519292d8fbb455f6253e57ecc16b5803bd58f62b0d94da7f8b2a1d6", + "0x0000000000000000000000000000000000000000000000000000000000002eaf": "3ff8b39a3c6de6646124497b27e8d4e657d103c72f2001bdd4c554208a0566e3", + "0x0000000000000000000000000000000000000000000000000000000000002eb9": "4d0f765d2b6a01f0c787bbb13b1360c1624704883e2fd420ea36037fa7e3a563", + "0x0000000000000000000000000000000000000000000000000000000000002ec3": "f6f1dc891258163196785ce9516a14056cbe823b17eb9b90eeee7a299c1ce0e0", + "0x0000000000000000000000000000000000000000000000000000000000002ecd": "1dbf19b70c0298507d20fb338cc167d9b07b8747351785047e1a736b42d999d1", + "0x0000000000000000000000000000000000000000000000000000000000002ed7": "c3b71007b20abbe908fdb7ea11e3a3f0abff3b7c1ced865f82b07f100167de57", + "0x0000000000000000000000000000000000000000000000000000000000002ee1": "3f45edc424499d0d4bbc0fd5837d1790cb41c08f0269273fdf66d682429c25cc", + "0x0000000000000000000000000000000000000000000000000000000000002eeb": "cb8f5db9446c485eaae7edbc03e3afed72892fa7f11ad8eb7fa9dffbe3c220eb", + "0x0000000000000000000000000000000000000000000000000000000000002ef5": "3d151527b5ba165352a450bee69f0afc78cf2ea9645bb5d8f36fb04435f0b67c", + "0x0000000000000000000000000000000000000000000000000000000000002eff": "dd96b35b4ffabce80d377420a0b00b7fbf0eff6a910210155d22d9bd981be5d3", + "0x0000000000000000000000000000000000000000000000000000000000002f09": "ace0c30b543d3f92f37eaac45d6f8730fb15fcaaaad4097ea42218abe57cb9f4", + "0x0000000000000000000000000000000000000000000000000000000000002f13": "f6342dd31867c9bef6ffa06b6cf192db23d0891ed8fe610eb8d1aaa79726da01", + "0x0000000000000000000000000000000000000000000000000000000000002f1d": "a6589e823979c2c2ac55e034d547b0c63aa02109133575d9f159e8a7677f03cb", + "0x0000000000000000000000000000000000000000000000000000000000002f27": "9ce48bc641cc1d54ffdb409aab7da1304d5ee08042596b3542ca9737bb2b79a8", + "0x0000000000000000000000000000000000000000000000000000000000002f31": "a44be801bd978629775c00d70df6d70b76d0ba918595e81415a27d1e3d6fdee9", + "0x0000000000000000000000000000000000000000000000000000000000002f3b": "ce17f1e7af9f7ea8a99b2780d87b15d8b80a68fb29ea52f962b00fecfc6634e0", + "0x0000000000000000000000000000000000000000000000000000000000002f45": "4bd91febab8df3770c957560e6185e8af59d2a42078756c525cd7769eb943894", + "0x0000000000000000000000000000000000000000000000000000000000002f4f": "414c2a52de31de93a3c69531247b016ac578435243073acc516d4ea673c8dd80", + "0x0000000000000000000000000000000000000000000000000000000000002f59": "647fb60bdf2683bd46b63d6884745782364a5522282ed1dc67d9e17c4aaab17d", + "0x0000000000000000000000000000000000000000000000000000000000002f63": "fa681ffd0b0dd6f6775e99a681241b86a3a24446bc8a69cdae915701243e3855", + "0x0000000000000000000000000000000000000000000000000000000000002f6d": "106ca692777b30cb2aa23ca59f5591514b28196ee8e9b06aa2b4deaea30d9ef6", + "0x0000000000000000000000000000000000000000000000000000000000002f77": "494ac6d09377eb6a07ff759df61c2508e65e5671373d756c82e648bd9086d91a", + "0x0000000000000000000000000000000000000000000000000000000000002f81": "0ae4ccd2bffa603714cc453bfd92f769dce6c9731c03ac3e2083f35388e6c795", + "0x0000000000000000000000000000000000000000000000000000000000002f8b": "d860c999490d9836cc00326207393c78445b7fb90b12aa1d3607e3662b3d32cd", + "0x0000000000000000000000000000000000000000000000000000000000002f95": "9587384f876dfec24da857c0bcdb3ded17f3328f28a4d59aa35ca7c25c8102cf", + "0x0000000000000000000000000000000000000000000000000000000000002f9f": "4df8093d29bc0ec4e2a82be427771e77a206566194734a73c23477e1a9e451f8", + "0x0000000000000000000000000000000000000000000000000000000000002fa9": "c56640f78acbd1da07701c365369766f09a19800ba70276f1f1d3cd1cf6e0686", + "0x0000000000000000000000000000000000000000000000000000000000002fb3": "7173d4210aa525eece6b4b19b16bab23686ff9ac71bb9d16008bb114365e79f2", + "0x0000000000000000000000000000000000000000000000000000000000002fbd": "89698b41d7ac70e767976a9f72ae6a46701456bc5ad8d146c248548409c90015", + "0x0000000000000000000000000000000000000000000000000000000000002fc7": "5b605ab5048d9e4a51ca181ac3fa7001ef5d415cb20335b095c54a40c621dbff", + "0x0000000000000000000000000000000000000000000000000000000000002fd1": "9129a84b729e7f69a5522a7020db57e27bf8cbb6042e030106c0cbd185bf0ab8", + "0x0000000000000000000000000000000000000000000000000000000000002fdb": "31a63d6d54153ab35fc57068db205a3e68908be238658ca82d8bee9873f82159", + "0x0000000000000000000000000000000000000000000000000000000000002fe5": "828641bcea1bc6ee1329bc39dca0afddc11e6867f3da13d4bb5170c54158860d", + "0x0000000000000000000000000000000000000000000000000000000000002fef": "7e0752ddd86339f512ec1b647d3bf4b9b50c45e309ab9e70911da7716454b053", + "0x0000000000000000000000000000000000000000000000000000000000002ff9": "31d973051189456d5998e05b500da6552138644f8cdbe4ec63f96f21173cb6a1", + "0x0000000000000000000000000000000000000000000000000000000000003003": "e33e65b3d29c3b55b2d7b584c5d0540eb5c00c9f157287863b0b619339c302f0", + "0x000000000000000000000000000000000000000000000000000000000000300d": "78d55514bcef24b40c7eb0fbe55f922d4468c194f313898f28ba85d8534df82c", + "0x0000000000000000000000000000000000000000000000000000000000003017": "2e0f4be4d8adf8690fd64deddbc543f35c5b4f3c3a27b10a77b1fdb8d590f1ee", + "0x0000000000000000000000000000000000000000000000000000000000003021": "e1b83ea8c4329f421296387826c89100d82bdc2263ffd8eb9368806a55d9b83b", + "0x000000000000000000000000000000000000000000000000000000000000302b": "4ddad36d7262dd9201c5bdd58523f4724e3b740fddbed2185e32687fecacdf6b", + "0x0000000000000000000000000000000000000000000000000000000000003035": "156c0674e46cdec70505443c5269d42c7bb14ee6c00f86a23962f08906cbb846", + "0x000000000000000000000000000000000000000000000000000000000000303f": "dfc56ec6c218a08b471d757e0e7de8dddec9e82f401cb7d77df1f2a9ca54c607", + "0x0000000000000000000000000000000000000000000000000000000000003049": "395d660f77c4360705cdc0be895907ec183097f749fac18b6eaa0245c1009074", + "0x0000000000000000000000000000000000000000000000000000000000003053": "84c0060087da2c95dbd517d0f2dd4dfba70691a5952fe4048c310e88e9c06e4f", + "0x000000000000000000000000000000000000000000000000000000000000305d": "f4df943c52b1d5fb9c1f73294ca743577d83914ec26d6e339b272cdeb62de586", + "0x0000000000000000000000000000000000000000000000000000000000003067": "0bb47661741695863ef89d5c2b56666772f871be1cc1dccf695bd357e4bb26d6", + "0x0000000000000000000000000000000000000000000000000000000000003071": "4a1f7691f29900287c6931545884881143ecae44cb26fdd644892844fde65dac", + "0x000000000000000000000000000000000000000000000000000000000000307b": "9b133cc50cbc46d55ce2910eebaf8a09ab6d4e606062c94aac906da1646bc33f", + "0x0000000000000000000000000000000000000000000000000000000000003085": "473b076b542da72798f9de31c282cb1dcd76cba2a22adc7391670ffdbc910766", + "0x000000000000000000000000000000000000000000000000000000000000308f": "225dd472ef6b36a51de5c322a31a9f71c80f0f350432884526d9844bb2e676d3", + "0x0000000000000000000000000000000000000000000000000000000000003099": "31df97b2c9fc65b5520b89540a42050212e487f46fac67685868f1c3e652a9aa", + "0x00000000000000000000000000000000000000000000000000000000000030a3": "4416d885f34ad479409bb9e05e8846456a9be7e74655b9a4d7568a8d710aa06a", + "0x00000000000000000000000000000000000000000000000000000000000030ad": "ae627f8802a46c1357fa42a8290fd1366ea21b8ccec1cc624e42022647c53802", + "0x00000000000000000000000000000000000000000000000000000000000030b7": "8961e8b83d91487fc32b3d6af26b1d5e7b4010dd8d028fe165187cdfb04e151c", + "0x00000000000000000000000000000000000000000000000000000000000030c1": "c22e39f021605c6f3d967aef37f0bf40b09d776bac3edb4264d0dc07389b9845", + "0x00000000000000000000000000000000000000000000000000000000000030cb": "7cfa4c7066c690c12b9e8727551bef5fe05b750ac6637a5af632fce4ceb4e2ce", + "0x00000000000000000000000000000000000000000000000000000000000030d5": "943d79e4329b86f8e53e8058961955f2b0a205fc3edeea2aae54ba0c22b40c31", + "0x00000000000000000000000000000000000000000000000000000000000030df": "66598070dab784e48a153bf9c6c3e57d8ca92bed6592f0b9e9abe308a17aedf0", + "0x00000000000000000000000000000000000000000000000000000000000030e9": "ac8fe4eb91577288510a9bdae0d5a8c40b8225172379cd70988465d8b98cfa70", + "0x00000000000000000000000000000000000000000000000000000000000030f3": "2b0018a8548e5ce2a6b6b879f56e3236cc69d2efff80f48add54efd53681dfce", + "0x00000000000000000000000000000000000000000000000000000000000030fd": "823445936237e14452e253a6692290c1be2e1be529ddbeecc35c9f54f7ea9887", + "0x0000000000000000000000000000000000000000000000000000000000003107": "3051a0d0701d233836b2c802060d6ee629816c856a25a62dc73bb2f2fc93b918", + "0x0000000000000000000000000000000000000000000000000000000000003111": "44a50fda08d2f7ca96034186475a285a8a570f42891f72d256a52849cb188c85", + "0x000000000000000000000000000000000000000000000000000000000000311b": "6e60069a12990ef960c0ac825fd0d9eb44aec9eb419d0df0c25d7a1d16c282e7", + "0x0000000000000000000000000000000000000000000000000000000000003125": "581ddf7753c91af00c894f8d5ab22b4733cfeb4e75c763725ebf46fb889fa76a", + "0x000000000000000000000000000000000000000000000000000000000000312f": "9a1dfba8b68440fcc9e89b86e2e290367c5e5fb0833b34612d1f4cfc53189526", + "0x0000000000000000000000000000000000000000000000000000000000003139": "54a623060b74d56f3c0d6793e40a9269c56f90bcd19898855113e5f9e42abc2d", + "0x0000000000000000000000000000000000000000000000000000000000003143": "1cfeb8cd5d56e1d202b4ec2851f22e99d6ad89af8a4e001eb014b724d2d64924", + "0x000000000000000000000000000000000000000000000000000000000000314d": "ad223cbf591f71ffd29e2f1c676428643313e3a8e8a7d0b0e623181b3047be92", + "0x0000000000000000000000000000000000000000000000000000000000003157": "e13f31f026d42cad54958ad2941f133d8bd85ee159f364a633a79472f7843b67", + "0x0000000000000000000000000000000000000000000000000000000000003161": "b45099ae3bbe17f4417d7d42951bd4425bce65f1db69a354a64fead61b56306d", + "0x000000000000000000000000000000000000000000000000000000000000316b": "9d2b65379c5561a607df4dae8b36eca78818acec4455eb47cfa437a0b1941707", + "0x0000000000000000000000000000000000000000000000000000000000003175": "5855b3546d3becda6d5dd78c6440f879340a5734a18b06340576a3ce6a48d9a0", + "0x000000000000000000000000000000000000000000000000000000000000317f": "d6a61c76ae029bb5bca86d68422c55e8241d9fd9b616556b375c91fb7224b79e", + "0x0000000000000000000000000000000000000000000000000000000000003189": "96ac5006561083735919ae3cc8d0762a9cba2bdefd4a73b8e69f447f689fba31", + "0x0000000000000000000000000000000000000000000000000000000000003193": "4ced18f55676b924d39aa7bcd7170bac6ff4fbf00f6a800d1489924c2a091412", + "0x000000000000000000000000000000000000000000000000000000000000319d": "c95a6a7efdbefa710a525085bcb57ea2bf2d4ae9ebfcee4be3777cfcc3e534ea", + "0x00000000000000000000000000000000000000000000000000000000000031a7": "2b2917b5b755eb6af226e16781382bd22a907c9c7411c34a248af2b5a0439079", + "0x00000000000000000000000000000000000000000000000000000000000031b1": "18d5804f2e9ad3f891ecf05e0bfc2142c2a9f7b4de03aebd1cf18067a1ec6490", + "0x00000000000000000000000000000000000000000000000000000000000031bb": "b47682f0ce3783700cbe5ffbb95d22c943cc74af12b9c79908c5a43f10677478", + "0x00000000000000000000000000000000000000000000000000000000000031c5": "e4b60e5cfb31d238ec412b0d0e3ad9e1eb00e029c2ded4fea89288f900f7db0e", + "0x00000000000000000000000000000000000000000000000000000000000031cf": "fc0ea3604298899c10287bba84c02b9ec5d6289c1493e9fc8d58920e4eaef659", + "0x00000000000000000000000000000000000000000000000000000000000031d9": "4c3301a70611b34e423cf713bda7f6f75bd2070f909681d3e54e3a9a6d202e5a", + "0x00000000000000000000000000000000000000000000000000000000000031e3": "84a5b4e32a62bf3298d846e64b3896dffbbcc1fafb236df3a047b5223577d07b", + "0x00000000000000000000000000000000000000000000000000000000000031ed": "ff70b97d34af8e2ae984ada7bc6f21ed294d9b392a903ad8bbb1be8b44083612", + "0x00000000000000000000000000000000000000000000000000000000000031f7": "73e186de72ef30e4be4aeebe3eaec84222f8a325d2d07cd0bd1a49f3939915ce", + "0x0000000000000000000000000000000000000000000000000000000000003201": "ed185ec518c0459392b274a3d10554e452577d33ecb72910f613941873e61215", + "0x000000000000000000000000000000000000000000000000000000000000320b": "5cfbad3e509733bce64e0f6492b3886300758c47a38e9edec4b279074c7966d4", + "0x0000000000000000000000000000000000000000000000000000000000003215": "867a7ab4c504e836dd175bd6a00e8489f36edaeda95db9ce4acbf9fb8df28926", + "0x000000000000000000000000000000000000000000000000000000000000321f": "0d01993fd605f101c950c68b4cc2b8096ef7d0009395dec6129f86f195eb2217", + "0x0000000000000000000000000000000000000000000000000000000000003229": "8e14fd675e72f78bca934e1ffad52b46fd26913063e7e937bce3fa11aed29075", + "0x0000000000000000000000000000000000000000000000000000000000003233": "4ec1847e4361c22cdecc67633e244b9e6d04ec103f4019137f9ba1ecc90198f4", + "0x000000000000000000000000000000000000000000000000000000000000323d": "ec69e9bbb0184bf0889df50ec7579fa4029651658d639af456a1f6a7543930ef", + "0x0000000000000000000000000000000000000000000000000000000000003247": "efdd626048ad0aa6fcf806c7c2ad7b9ae138136f10a3c2001dc5b6c920db1554", + "0x0000000000000000000000000000000000000000000000000000000000003251": "551de1e4cafd706535d77625558f8d3898173273b4353143e5e1c7e859848d6b", + "0x000000000000000000000000000000000000000000000000000000000000325b": "137efe559a31d9c5468259102cd8634bba72b0d7a0c7d5bcfc449c5f4bdb997a", + "0x0000000000000000000000000000000000000000000000000000000000003265": "fb0a1b66acf5f6bc2393564580d74637945891687e61535aae345dca0b0f5e78", + "0x000000000000000000000000000000000000000000000000000000000000326f": "96eea2615f9111ee8386319943898f15c50c0120b8f3263fab029123c5fff80c", + "0x0000000000000000000000000000000000000000000000000000000000003279": "68725bebed18cd052386fd6af9b398438c01356223c5cc15f49093b92b673eff", + "0x0000000000000000000000000000000000000000000000000000000000003283": "e2f1e4557ed105cf3bd8bc51ebaa4446f554dcb38c005619bd9f203f4494f5dd", + "0x000000000000000000000000000000000000000000000000000000000000328d": "48ef06d84d5ad34fe56ce62e095a34ea4a903bf597a8640868706af7b4de7288", + "0x0000000000000000000000000000000000000000000000000000000000003297": "5c57714b2a85d0d9331ce1ee539a231b33406ec19adcf1d8f4c88ab8c1f4fbae", + "0x00000000000000000000000000000000000000000000000000000000000032a1": "204299e7aa8dfe5328a0b863b20b6b4cea53a469d6dc8d4b31c7873848a93f33", + "0x00000000000000000000000000000000000000000000000000000000000032ab": "b74eea6df3ce54ee9f069bebb188f4023673f8230081811ab78ce1c9719879e5", + "0x00000000000000000000000000000000000000000000000000000000000032b5": "af5624a3927117b6f1055893330bdf07a64e96041241d3731b9315b5cd6d14d7", + "0x00000000000000000000000000000000000000000000000000000000000032bf": "c657b0e79c166b6fdb87c67c7fe2b085f52d12c6843b7d6090e8f230d8306cda", + "0x00000000000000000000000000000000000000000000000000000000000032c9": "a0e08ceff3f3c426ab2c30881eff2c2fc1edf04b28e1fb38e622648224ffbc6b", + "0x00000000000000000000000000000000000000000000000000000000000032d3": "c9792da588df98731dfcbf54a6264082e791540265acc2b3ccca5cbd5c0c16de", + "0x00000000000000000000000000000000000000000000000000000000000032dd": "c74f4bb0f324f42c06e7aeacb9446cd5ea500c3b014d5888d467610eafb69297", + "0x00000000000000000000000000000000000000000000000000000000000032e7": "1acd960a8e1dc68da5b1db467e80301438300e720a450ab371483252529a409b", + "0x00000000000000000000000000000000000000000000000000000000000032f1": "6cef279ba63cbac953676e889e4fe1b040994f044078196a6ec4e6d868b79aa1", + "0x00000000000000000000000000000000000000000000000000000000000032fb": "60eb986cb497a0642b684852f009a1da143adb3128764b772daf51f6efaae90a", + "0x0000000000000000000000000000000000000000000000000000000000003305": "c50024557485d98123c9d0e728db4fc392091f366e1639e752dd677901681acc", + "0x000000000000000000000000000000000000000000000000000000000000330f": "b860632e22f3e4feb0fdf969b4241442eae0ccf08f345a1cc4bb62076a92d93f", + "0x0000000000000000000000000000000000000000000000000000000000003319": "21085bf2d264529bd68f206abc87ac741a2b796919eeee6292ed043e36d23edb", + "0x0000000000000000000000000000000000000000000000000000000000003323": "80052afb1f39f11c67be59aef7fe6551a74f6b7d155a73e3d91b3a18392120a7", + "0x000000000000000000000000000000000000000000000000000000000000332d": "a3b0793132ed37459f24d6376ecfa8827c4b1d42afcd0a8c60f9066f230d7675", + "0x0000000000000000000000000000000000000000000000000000000000003337": "e69d353f4bc38681b4be8cd5bbce5eb4e819399688b0b6225b95384b08dcc8b0", + "0x0000000000000000000000000000000000000000000000000000000000003341": "221e784d42a121cd1d13d111128fcae99330408511609ca8b987cc6eecafefc4", + "0x000000000000000000000000000000000000000000000000000000000000334b": "dcd669ebef3fb5bebc952ce1c87ae4033b13f37d99cf887022428d024f3a3d2e", + "0x0000000000000000000000000000000000000000000000000000000000003355": "4dd1eb9319d86a31fd56007317e059808f7a76eead67aecc1f80597344975f46", + "0x000000000000000000000000000000000000000000000000000000000000335f": "5e1834c653d853d146db4ab6d17509579497c5f4c2f9004598bcd83172f07a5f", + "0x0000000000000000000000000000000000000000000000000000000000003369": "9f78a30e124d21168645b9196d752a63166a1cf7bbbb9342d0b8fee3363ca8de", + "0x0000000000000000000000000000000000000000000000000000000000003373": "1f7c1081e4c48cef7d3cb5fd64b05135775f533ae4dabb934ed198c7e97e7dd8", + "0x000000000000000000000000000000000000000000000000000000000000337d": "4d40a7ec354a68cf405cc57404d76de768ad71446e8951da553c91b06c7c2d51", + "0x0000000000000000000000000000000000000000000000000000000000003387": "f653da50cdff4733f13f7a5e338290e883bdf04adf3f112709728063ea965d6c" + }, + "key": "0x37d65eaa92c6bc4c13a5ec45527f0c18ea8932588728769ec7aecfe6d9f32e42" + }, + "0x00f691ca9e1403d01344ebbaca0201380cacc99c": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7c48e400de1f24b4de94c59068fcd91a028576d13a22f900a7fcbd8f4845bcf4" + }, + "0x0300100f529a704d19736a8714837adbc934db7f": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x97b25febb46f44607c87a3498088c605086df207c7ddcd8ee718836a516a9153" + }, + "0x043a718774c572bd8a25adbeb1bfcd5c0256ae11": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4c310e1f5d2f2e03562c4a5c473ae044b9ee19411f07097ced41e85bd99c3364" + }, + "0x046dc70a4eba21473beb6d9460d880b8cfd66613": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4fd7c8d583447b937576211163a542d945ac8c0a6e22d0c42ac54e2cbaff9281" + }, + "0x04b85539570fb9501f65453dbfad410a467becdd": { + "balance": "0", + "nonce": 1, + "root": "0x9e53f0a2ddb430d27f6fffa0a68b5f75db1d68e24113dcca6e33918cdae80846", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000019": "19", + "0x000000000000000000000000000000000000000000000000000000000000001a": "1a", + "0x000000000000000000000000000000000000000000000000000000000000001b": "1b" + }, + "key": "0xd84f7711be2f8eca69c742153230995afb483855b7c555b08da330139cdb9579" + }, + "0x04b8d34e20e604cadb04b9db8f6778c35f45a2d2": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe99460a483f3369006e3edeb356b3653699f246ec71f30568617ebc702058f59" + }, + "0x04d6c0c946716aac894fc1653383543a91faab60": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x98bb9ba48fda7bb8091271ab0e53d7e0022fb1f1fa8fa00814e193c7d4b91eb3" + }, + "0x050c9c302e904c7786b69caa9dd5b27a6e571b72": { + "balance": "0", + "nonce": 1, + "root": "0x818eaf5adb56c6728889ba66b6980cd66b41199f0007cdd905ae739405e3c630", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000077": "77", + "0x0000000000000000000000000000000000000000000000000000000000000078": "78", + "0x0000000000000000000000000000000000000000000000000000000000000079": "79" + }, + "key": "0xc3ac56e9e7f2f2c2c089e966d1b83414951586c3afeb86300531dfa350e38929" + }, + "0x06f647b157b8557a12979ba04cf5ba222b9747cf": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xaf38e0e6a4a4005507b5d3e9470e8ccc0273b74b6971f768cbdf85abeab8a95b" + }, + "0x075198bfe61765d35f990debe90959d438a943ce": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x1d38ada74301c31f3fd7d92dd5ce52dc37ae633e82ac29c4ef18dfc141298e26" + }, + "0x075db7ab5778cd5491d3ed7ab64c1ec0818148f3": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xf84223f460140ad56af9836cfa6c1c58c1397abf599c214689bc881066020ff7" + }, + "0x08037e79bb41c0f1eda6751f0dabb5293ca2d5bf": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xcd07379b0120ad9a9c7fa47e77190be321ab107670f3115fec485bebb467307d" + }, + "0x087d80f7f182dd44f184aa86ca34488853ebcc04": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x867bc89cf8d5b39f1712fbc77414bbd93012af454c226dcee0fb34ccc0017498" + }, + "0x08d3b23dbfe8ef7965a8b5e4d9c21feddbc11491": { + "balance": "0", + "nonce": 1, + "root": "0x9a4a33f978d84e0aceb3ac3670c2e2df6c8ae27c189a96ed00b806d10ed7b4ee", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001c6": "01c6", + "0x00000000000000000000000000000000000000000000000000000000000001c7": "01c7", + "0x00000000000000000000000000000000000000000000000000000000000001c8": "01c8" + }, + "key": "0x792cc9f20a61c16646d5b6136693e7789549adb7d8e35503d0004130ea6528b0" + }, + "0x09b9c1875399cd724b1017f155a193713cb23732": { + "balance": "0", + "nonce": 1, + "root": "0x47fa48e25d3669a9bb190c59938f4be49de2d083696eb939c3b4072ec67e43b1", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000005e": "5e", + "0x000000000000000000000000000000000000000000000000000000000000005f": "5f", + "0x0000000000000000000000000000000000000000000000000000000000000060": "60" + }, + "key": "0x23ddaac09188c12e5d88009afa4a34041175c5531f45be53f1560a1cbfec4e8a" + }, + "0x0a3aaee7ccfb1a64f6d7bcd46657c27cb1f4569a": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xc7fc033fe9f00d24cb9c479ddc0598e592737c305263d088001d7419d16feffa" + }, + "0x0badc617ca1bcb1cb1d5272f64b168cbf0e8f86f": { + "balance": "0", + "nonce": 1, + "root": "0xca39f5f4ee3c6b33efe7bc485439f97f9dc62f65852c7a1cdf54fab1e3b70429", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000002d": "2d", + "0x000000000000000000000000000000000000000000000000000000000000002e": "2e", + "0x000000000000000000000000000000000000000000000000000000000000002f": "2f" + }, + "key": "0xc250f30c01f4b7910c2eb8cdcd697cf493f6417bb2ed61d637d625a85a400912" + }, + "0x0c2c51a0990aee1d73c1228de158688341557508": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x28f25652ec67d8df6a2e33730e5d0983443e3f759792a0128c06756e8eb6c37f" + }, + "0x0d336bc3778662a1252d29a6f7216055f7a582bf": { + "balance": "0", + "nonce": 1, + "root": "0xa5a91cf9e815fb55df14b3ee8c1325a988cb3b6dd34796c901385c3cc2992073", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000013f": "013f", + "0x0000000000000000000000000000000000000000000000000000000000000140": "0140", + "0x0000000000000000000000000000000000000000000000000000000000000141": "0141" + }, + "key": "0x86a73e3c668eb065ecac3402c6dc912e8eb886788ea147c770f119dcd30780c6" + }, + "0x0e4aea2bbb2ae557728f2661ee3639360f1d787a": { + "balance": "0", + "nonce": 1, + "root": "0x74ed78eb16016d7ff3a173ab1bbcee9daa8e358a9d6c9be5e84ba6f4a34cf96a", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000d1": "d1", + "0x00000000000000000000000000000000000000000000000000000000000000d2": "d2", + "0x00000000000000000000000000000000000000000000000000000000000000d3": "d3" + }, + "key": "0x517bd5fbe28e4368b0b9fcba13d5e81fb51babdf4ed63bd83885235ee67a8fa0" + }, + "0x0ef32dec5f88a96c2eb042126e8ab982406e0267": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x181abdd5e212171007e085fdc284a84d42d5bfc160960d881ccb6a10005ff089" + }, + "0x0ef96a52f4510f82b049ba991c401a8f5eb823e5": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x59312f89c13e9e24c1cb8b103aa39a9b2800348d97a92c2c9e2a78fa02b70025" + }, + "0x0f228c3ba41142e702ee7306859026c99d3d2df5": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xedd9b1f966f1dfe50234523b479a45e95a1a8ec4a057ba5bfa7b69a13768197c" + }, + "0x0fdcca8fde6d69ecbc9bfadb056ecf62d1966370": { + "balance": "0", + "nonce": 1, + "root": "0x493f90435402df0907019bffc6dd25a17ce4acd6eb6077ef94c1626f0d77c9f0", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000f9": "f9", + "0x00000000000000000000000000000000000000000000000000000000000000fa": "fa", + "0x00000000000000000000000000000000000000000000000000000000000000fb": "fb" + }, + "key": "0xfb5a31c5cfd33dce2c80a30c5efc28e5f4025624adcc2205a2504a78c57bdd1c" + }, + "0x0fe037febcc3adf9185b4e2ad4ea43c125f05049": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb7d9d175039df1ba52c734547844f8805252893c029f7dbba9a63f8bce3ee306" + }, + "0x0fed138ec52bab88db6c068df9125936c7c3e11b": { + "balance": "0", + "nonce": 1, + "root": "0x66eb16071ba379bf0c632fcb52f9175a656bef62adf0bef5349a7f5a6aad5d88", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000176": "0176", + "0x0000000000000000000000000000000000000000000000000000000000000177": "0177", + "0x0000000000000000000000000000000000000000000000000000000000000178": "0178" + }, + "key": "0x255ec86eac03ba59f6dfcaa02128adbb22c561ae0c49e9e62e4fff363750626e" + }, + "0x102efa1f2e0ad16ada57759b815245b8f8d27ce4": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x9d42947ac5e61285567f65d4b400d90343dbd3192534c4c1f9d941c04f48f17c" + }, + "0x1037044fabf0421617c47c74681d7cc9c59f136c": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x2290ea88cc63f09ab5e8c989a67e2e06613311801e39c84aae3badd8bb38409c" + }, + "0x1042d41ee3def49e70df4e6c2be307b8015111e5": { + "balance": "0", + "nonce": 1, + "root": "0xdf3c1bfab8f7e70a8edf94792f91e4b6b2c2aa61caf687e4f6cb689d180adb80", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000095": "95", + "0x0000000000000000000000000000000000000000000000000000000000000096": "96", + "0x0000000000000000000000000000000000000000000000000000000000000097": "97" + }, + "key": "0xc0ce77c6a355e57b89cca643e70450612c0744c9f0f8bf7dee51d6633dc850b1" + }, + "0x104eb07eb9517a895828ab01a3595d3b94c766d5": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xfab4c6889992a3f4e96b005dfd851021e9e1ec2631a7ccd2a001433e35077968" + }, + "0x1219c38638722b91f3a909f930d3acc16e309804": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xd63070208c85e91c4c8c942cf52c416f0f3004c392a15f579350168f178dba2e" + }, + "0x132432ce1ce64304f1d145eba1772f6edd6cdd17": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x729953a43ed6c913df957172680a17e5735143ad767bda8f58ac84ec62fbec5e" + }, + "0x13dd437fc2ed1cd5d943ac1dd163524c815d305c": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x99e56541f21039c9b7c63655333841a3415de0d27b79d18ade9ec7ecde7a1139" + }, + "0x14e46043e63d0e3cdcf2530519f4cfaf35058cb2": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x9feaf0bd45df0fbf327c964c243b2fbc2f0a3cb48fedfeea1ae87ac1e66bc02f" + }, + "0x1534b43c6dfa3695446aaf2aa07d123132cceceb": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x2a248c1755e977920284c8054fceeb20530dc07cd8bbe876f3ce02000818cc3a" + }, + "0x15af6900147a8730b5ce3e1db6333f33f64ebb2c": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x5264e880ecf7b80afda6cc2a151bac470601ff8e376af91aaf913a36a30c4009" + }, + "0x16032a66fc011dab75416d2449fe1a3d5f4319d8": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe3c79e424fd3a7e5bf8e0426383abd518604272fda87ecd94e1633d36f55bbb6" + }, + "0x16c57edf7fa9d9525378b0b81bf8a3ced0620c1c": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xda81833ff053aff243d305449775c3fb1bd7f62c4a3c95dc9fb91b85e032faee" + }, + "0x17333b15b4a5afd16cac55a104b554fc63cc8731": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4ceaf2371fcfb54a4d8bc1c804d90b06b3c32c9f17112b57c29b30a25cf8ca12" + }, + "0x17b917f9d79d922b33e41582984712e32b3ad366": { + "balance": "0", + "nonce": 1, + "root": "0x944f095afbd1383e5d0f91ef02895d398f4f76fdb6d86adf4765f25bdc304f5f", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000081": "81", + "0x0000000000000000000000000000000000000000000000000000000000000082": "82", + "0x0000000000000000000000000000000000000000000000000000000000000083": "83" + }, + "key": "0x13cfc46f6bdb7a1c30448d41880d061c3b8d36c55a29f1c0c8d95a8e882b8c25" + }, + "0x18291b5f568e45ef0f16709b20c810e08750791f": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x315ccc15883d06b4e743f8252c999bf1ee994583ff6114d89c0f3ddee828302b" + }, + "0x189f40034be7a199f1fa9891668ee3ab6049f82d": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x6225e8f52719d564e8217b5f5260b1d1aac2bcb959e54bc60c5f479116c321b8" + }, + "0x18ac3e7343f016890c510e93f935261169d9e3f5": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xeba984db32038d7f4d71859a9a2fc6e19dde2e23f34b7cedf0c4bf228c319f17" + }, + "0x19041ad672875015bc4041c24b581eafc0869aab": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xfc8d513d1615c763865b984ea9c381032c14a983f80e5b2bd90b20b518329ed7" + }, + "0x19129f84d987b13468846f822882dba0c50ca07d": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x2b8d12301a8af18405b3c826b6edcc60e8e034810f00716ca48bebb84c4ce7ab" + }, + "0x194e49be24c1a94159f127aa9257ded12a0027db": { + "balance": "0", + "nonce": 1, + "root": "0xe0a3d3b839fca0f54745d0c50a048e424c9259f063b7416410a4422eeb7f837e", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000180": "0180", + "0x0000000000000000000000000000000000000000000000000000000000000181": "0181", + "0x0000000000000000000000000000000000000000000000000000000000000182": "0182" + }, + "key": "0xd57eafe6d4c5b91fe7114e199318ab640e55d67a1e9e3c7833253808b7dca75f" + }, + "0x19581e27de7ced00ff1ce50b2047e7a567c76b1c": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7bac5af423cb5e417fa6c103c7cb9777e80660ce3735ca830c238b0d41610186" + }, + "0x196d4a4c50eb47562596429fdecb4e3ac6b2a5fd": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4e258aa445a0e2a8704cbc57bbe32b859a502cd6f99190162236300fabd86c4a" + }, + "0x1a0eae9b9214d9269a4cff4982c45a67f4ca63aa": { + "balance": "0", + "nonce": 1, + "root": "0x5622801b1011de8403e44308bbf89a5809b7ad6586268cd72164523587f9b0e4", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000007c": "7c", + "0x000000000000000000000000000000000000000000000000000000000000007d": "7d", + "0x000000000000000000000000000000000000000000000000000000000000007e": "7e" + }, + "key": "0x6a2c8498657ae4f0f7b1a02492c554f7f8a077e454550727890188f7423ba014" + }, + "0x1ae59138ad95812304b117ee7b0d502bcb885af5": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xf164775805f47d8970d3282188009d4d7a2da1574fe97e5d7bc9836a2eed1d5b" + }, + "0x1b16b1df538ba12dc3f97edbb85caa7050d46c14": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x8ee17a1ec4bae15d8650323b996c55d5fa11a14ceec17ff1d77d725183904914" + }, + "0x1c123d5c0d6c5a22ef480dce944631369fc6ce28": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xa9fd2e3a6de5a9da5badd719bd6e048acefa6d29399d8a99e19fd9626805b60b" + }, + "0x1c972398125398a3665f212930758ae9518a8c94": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x5d97d758e8800d37b6d452a1b1812d0afedba11f3411a17a8d51ee13a38d73f0" + }, + "0x1e345d32d0864f75b16bde837543aa44fac35935": { + "balance": "0", + "nonce": 1, + "root": "0xd91acf305934a60c960a93fb00f927ec79308b8a919d2449faede722c2324cb3", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000153": "0153", + "0x0000000000000000000000000000000000000000000000000000000000000154": "0154", + "0x0000000000000000000000000000000000000000000000000000000000000155": "0155" + }, + "key": "0x961508ac3c93b30ee9a5a34a862c9fe1659e570546ac6c2e35da20f6d2bb5393" + }, + "0x1e8ce8258fb47f55bf2c1473acb89a10074b9d0e": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xfb2ab315988de92dcf6ba848e756676265b56e4b84778a2c955fb2b3c848c51c" + }, + "0x1f4924b14f34e24159387c0a4cdbaa32f3ddb0cf": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7963685967117ffb6fd019663dc9e782ebb1234a38501bffc2eb5380f8dc303b" + }, + "0x1f5746736c7741ae3e8fa0c6e947cade81559a86": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4e5bab4ebd077c3bbd8239995455989ea2e95427ddeed47d0618d9773332bb05" + }, + "0x1f5bde34b4afc686f136c7a3cb6ec376f7357759": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xc3791fc487a84f3731eb5a8129a7e26f357089971657813b48a821f5582514b3" + }, + "0x2143e52a9d8ad4c55c8fdda755f4889e3e3e7721": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xd9fa858992bc92386a7cebcd748eedd602bf432cb4b31607566bc92b85179624" + }, + "0x2144780b7d04d82239c6570f84ab66376b63dfc9": { + "balance": "0", + "nonce": 1, + "root": "0x59936c15c454933ebc4989afa77e350f7640301b07341aead5f1b2668eeb1dad", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000db": "db", + "0x00000000000000000000000000000000000000000000000000000000000000dc": "dc", + "0x00000000000000000000000000000000000000000000000000000000000000dd": "dd" + }, + "key": "0xd37b6f5e5f0fa6a1b3fd15c9b3cf0fb595ba245ab912ad8059e672fa55f061b8" + }, + "0x22694f8f2d0c62f63a25bd0057a80b89084c3b47": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x2369a492b6cddcc0218617a060b40df0e7dda26abe48ba4e4108c532d3f2b84f" + }, + "0x22b3f17adeb5f2ec22135d275fcc6e29f4989401": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xa3abdaefbb886078dc6c5c72e4bc8d12e117dbbd588236c3fa7e0c69420eb24a" + }, + "0x23262ad5ae496588bd793910b55ccf178fbd73f9": { + "balance": "0", + "nonce": 1, + "root": "0x3437803101a8040aca273fb734d7965a87f823ff1ef78c7edcaad358eb98dee3", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000171": "0171", + "0x0000000000000000000000000000000000000000000000000000000000000172": "0172", + "0x0000000000000000000000000000000000000000000000000000000000000173": "0173" + }, + "key": "0xd8489fd0ce5e1806b24d1a7ce0e4ba8f0856b87696456539fcbb625a9bed2ccc" + }, + "0x23b17315554bd2928c1f86dd526f7ee065a9607d": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x12e394ad62e51261b4b95c431496e46a39055d7ada7dbf243f938b6d79054630" + }, + "0x23c86a8aded0ad81f8111bb07e6ec0ffb00ce5bf": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xd72e318c1cea7baf503950c9b1bd67cf7caf2f663061fcde48d379047a38d075" + }, + "0x23e6931c964e77b02506b08ebf115bad0e1eca66": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x174f1a19ff1d9ef72d0988653f31074cb59e2cf37cd9d2992c7b0dd3d77d84f9" + }, + "0x24255ef5d941493b9978f3aabb0ed07d084ade19": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7583557e4e3918c95965fb610dc1424976c0eee606151b6dfc13640e69e5cb15" + }, + "0x245843abef9e72e7efac30138a994bf6301e7e1d": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xfe6e594c507ec0ac14917f7a8032f83cd0c3c58b461d459b822190290852c0e1" + }, + "0x25261a7e8395b6e798e9b411c962fccc0fb31e38": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x1017b10a7cc3732d729fe1f71ced25e5b7bc73dc62ca61309a8c7e5ac0af2f72" + }, + "0x2553ec67bc75f75d7de13db86b14290f0f76e342": { + "balance": "0", + "nonce": 1, + "root": "0x8078f3259d8199b7ca39d51e35d5b58d71ff148606731060386d323c5d19182c", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000185": "0185", + "0x0000000000000000000000000000000000000000000000000000000000000186": "0186", + "0x0000000000000000000000000000000000000000000000000000000000000187": "0187" + }, + "key": "0x0f30822f90f33f1d1ba6d1521a00935630d2c81ab12fa03d4a0f4915033134f3" + }, + "0x2604439a795970de2047e339293a450c0565f625": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x8678559b30b321b0f0420a4a3e8cecfde90c6e56766b78c1723062c93c1f041f" + }, + "0x26704bf05b1da795939788ef05c8804dcf4b9009": { + "balance": "0", + "nonce": 1, + "root": "0xd60ee4ad5abbe759622fca5c536109b11e85aa2b48c0be2aebf01df597e74dba", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000015d": "015d", + "0x000000000000000000000000000000000000000000000000000000000000015e": "015e", + "0x000000000000000000000000000000000000000000000000000000000000015f": "015f" + }, + "key": "0xd1691564c6a5ab1391f0495634e749b9782de33756b6a058f4a9536c1b37bca6" + }, + "0x2727d12b98783b2c3641b5672bcfcdf007971d28": { + "balance": "0", + "nonce": 1, + "root": "0x59739ba3b156eb78f8bbb14bbf3dacdebfde95140f586db66f72e3117b94bb67", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000112": "0112", + "0x0000000000000000000000000000000000000000000000000000000000000113": "0113", + "0x0000000000000000000000000000000000000000000000000000000000000114": "0114" + }, + "key": "0x88bf4121c2d189670cb4d0a16e68bdf06246034fd0a59d0d46fb5cec0209831e" + }, + "0x2795044ce0f83f718bc79c5f2add1e52521978df": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xee9186a01e5e1122b61223b0e6acc6a069c9dcdb7307b0a296421272275f821b" + }, + "0x27952171c7fcdf0ddc765ab4f4e1c537cb29e5e5": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x0a93a7231976ad485379a3b66c2d8983ba0b2ca87abaf0ca44836b2a06a2b102" + }, + "0x27abdeddfe8503496adeb623466caa47da5f63ab": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x482814ea8f103c39dcf6ba7e75df37145bde813964d82e81e5d7e3747b95303d" + }, + "0x281c93990bac2c69cf372c9a3b66c406c86cca82": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x81c0c51e15c9679ef12d02729c09db84220ba007efe7ced37a57132f6f0e83c9" + }, + "0x2847213288f0988543a76512fab09684131809d9": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe1b86a365b0f1583a07fc014602efc3f7dedfa90c66e738e9850719d34ac194e" + }, + "0x28969cdfa74a12c82f3bad960b0b000aca2ac329": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x96d7104053877823b058fd9248e0bba2a540328e52ffad9bb18805e89ff579dc" + }, + "0x2a0ab732b4e9d85ef7dc25303b64ab527c25a4d7": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x5e88e876a3af177e6daafe173b67f186a53f1771a663747f26b278c5acb4c219" + }, + "0x2aac4746638ae1457010747a5b0fd2380a388f4f": { + "balance": "0", + "nonce": 1, + "root": "0x5a82aff126ffebff76002b1e4de03c40ba494b81cb3fbc528f23e4be35a9afe6", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000004b": "4b", + "0x000000000000000000000000000000000000000000000000000000000000004c": "4c", + "0x000000000000000000000000000000000000000000000000000000000000004d": "4d" + }, + "key": "0x96c43ef9dce3410b78df97be69e7ccef8ed40d6e5bfe6582ea4cd7d577aa4569" + }, + "0x2bb3295506aa5a21b58f1fd40f3b0f16d6d06bbc": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x303f57a0355c50bf1a0e1cf0fa8f9bdbc8d443b70f2ad93ac1c6b9c1d1fe29a2" + }, + "0x2c0cd3c60f41d56ed7664dbce39630395614bf4b": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x92d0f0954f4ec68bd32163a2bd7bc69f933c7cdbfc6f3d2457e065f841666b1c" + }, + "0x2c1287779024c3a2f0924b54816d79b7e378907d": { + "balance": "0", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x09d6e6745d272389182a510994e2b54d14b731fac96b9c9ef434bc1924315371" + }, + "0x2c582db705c5721bb3ba59f4ec8e44fb4ef6b920": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe02ec497b66cb57679eb01de1bed2ad385a3d18130441a9d337bd14897e85d39" + }, + "0x2d389075be5be9f2246ad654ce152cf05990b209": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xa9233a729f0468c9c309c48b82934c99ba1fd18447947b3bc0621adb7a5fc643" + }, + "0x2d711642b726b04401627ca9fbac32f5c8530fb1": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xfe2149c5c256a5eb2578c013d33e3af6a87a514965c7ddf4a8131e2d978f09f9" + }, + "0x2e350f8e7f890a9301f33edbf55f38e67e02d72b": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xf33a7b66489679fa665dbfb4e6dd4b673495f853850eedc81d5f28bd2f4bd3b5" + }, + "0x2e5f413fd8d378ed081a76e1468dad8cbf6e9ed5": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe69f40f00148bf0d4dfa28b3f3f5a0297790555eca01a00e49517c6645096a6c" + }, + "0x2eb6db4e06119ab31a3acf4f406ccbaa85e39c66": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xaeaf19d38b69be4fb41cc89e4888708daa6b9b1c3f519fa28fe9a0da70cd8697" + }, + "0x2f01c1c8c735a9a1b89898d3f14bbf61c91bf0fd": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xd2f394b4549b085fb9b9a8b313a874ea660808a4323ab2598ee15ddd1eb7e897" + }, + "0x2fb64110da9389ce8567938a78f21b79222332f9": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x415ded122ff7b6fe5862f5c443ea0375e372862b9001c5fe527d276a3a420280" + }, + "0x2fc7b26c1fd501c57e57db3e876dc6ae7af6979b": { + "balance": "0", + "nonce": 1, + "root": "0x3d20fedd270b3771706fe00a580a155439be57e8d550762def10906e83ed58bb", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000009f": "9f", + "0x00000000000000000000000000000000000000000000000000000000000000a0": "a0", + "0x00000000000000000000000000000000000000000000000000000000000000a1": "a1" + }, + "key": "0xb9cddc73dfdacd009e55f27bdfd1cd37eef022ded5ce686ab0ffe890e6bf311e" + }, + "0x30a5bfa58e128af9e5a4955725d8ad26d4d574a5": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe1eb1e18ae510d0066d60db5c2752e8c33604d4da24c38d2bda07c0cb6ad19e4" + }, + "0x30c72b4fb390ff1d387821e210f3ab04fbe86d13": { + "balance": "0", + "nonce": 1, + "root": "0xdf97f94bc47471870606f626fb7a0b42eed2d45fcc84dc1200ce62f7831da990", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000d6": "d6", + "0x00000000000000000000000000000000000000000000000000000000000000d7": "d7", + "0x00000000000000000000000000000000000000000000000000000000000000d8": "d8" + }, + "key": "0x005e94bf632e80cde11add7d3447cd4ca93a5f2205d9874261484ae180718bd6" + }, + "0x311df588ca5f412f970891e4cc3ac23648968ca2": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x974a4800ec4c0e998f581c6ee8c3972530989e97a179c6b2d40b8710c036e7b1" + }, + "0x312e8fca5ac7dfc591031831bff6fede6ecf12a8": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x64bfba8a4688bdee41c4b998e101567b8b56fea53d30ab85393f2d5b70c5da90" + }, + "0x32c417b98c3d9bdd37550c0070310526347b4648": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x80cd4a7b601d4ba0cb09e527a246c2b5dd25b6dbf862ac4e87c6b189bfce82d7" + }, + "0x33afd8244c9c1a37f5bddb3254cd08779a196458": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x210ce6d692a21d75de3764b6c0356c63a51550ebec2c01f56c154c24b1cf8888" + }, + "0x33fc6e8ad066231eb5527d1a39214c1eb390985d": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x87e33f70e1dd3c6ff68e3b71757d697fbeb20daae7a3cc8a7b1b3aa894592c50" + }, + "0x360671abc40afd33ae0091e87e589fc320bf9e3d": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x12c1bb3dddf0f06f62d70ed5b7f7db7d89b591b3f23a838062631c4809c37196" + }, + "0x3632d1763078069ca77b90e27061147a3b17ddc3": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x0463e52cda557221b0b66bd7285b043071df4c2ab146260f4e010970f3a0cccf" + }, + "0x368b766f1e4d7bf437d2a709577a5210a99002b6": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4845aac9f26fcd628b39b83d1ccb5c554450b9666b66f83aa93a1523f4db0ab6" + }, + "0x36a9e7f1c95b82ffb99743e0c5c4ce95d83c9a43": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xcac96145454c46255fccca35343d9505164dabe319c17d81fda93cf1171e4c6e" + }, + "0x38d0bd409abe8d78f9f0e0a03671e44e81c41c27": { + "balance": "0", + "nonce": 1, + "root": "0x23a888c0a464ce461651fc1be2cfa0cb6ba4d1b125abe5b447eeadf9c5adf1f1", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000167": "0167", + "0x0000000000000000000000000000000000000000000000000000000000000168": "0168", + "0x0000000000000000000000000000000000000000000000000000000000000169": "0169" + }, + "key": "0xb58e67c536550fdf7140c8333ca62128df469a7270b16d528bc778909e0ac9a5" + }, + "0x3ae75c08b4c907eb63a8960c45b86e1e9ab6123c": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x878040f46b1b4a065e6b82abd35421eb69eededc0c9598b82e3587ae47c8a651" + }, + "0x3bcc2d6d48ffeade5ac5af3ee7acd7875082e50a": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb5bca5e9ccef948c2431372315acc3b96e098d0e962b0c99d634a0475b670dc3" + }, + "0x3c204ccddfebae334988367b5cf372387dc49ebd": { + "balance": "0", + "nonce": 1, + "root": "0xc7bf2b34294065afb9a2c15f906cba1f7a1a9f0da34ea9c46603b52cae9028ec", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000194": "0194", + "0x0000000000000000000000000000000000000000000000000000000000000195": "0195", + "0x0000000000000000000000000000000000000000000000000000000000000196": "0196" + }, + "key": "0x5ec55391e89ac4c3cf9e61801cd13609e8757ab6ed08687237b789f666ea781b" + }, + "0x3c2572436de9a5f3c450071e391c8a9410ba517d": { + "balance": "0", + "nonce": 1, + "root": "0xbfba1bc2ac42655f5a97450be62b9430822232f1ce4998eaf5239b0c243b2b84", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000090": "90", + "0x0000000000000000000000000000000000000000000000000000000000000091": "91", + "0x0000000000000000000000000000000000000000000000000000000000000092": "92" + }, + "key": "0x606059a65065e5f41347f38754e6ddb99b2d709fbff259343d399a4f9832b48f" + }, + "0x3c5c4713708c72b519144ba8e595a8865505000d": { + "balance": "0", + "nonce": 1, + "root": "0x52d6d2913ae44bca11b5a116021db97c91a13e385ed48ba06628e74201231dba", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001c1": "01c1", + "0x00000000000000000000000000000000000000000000000000000000000001c2": "01c2", + "0x00000000000000000000000000000000000000000000000000000000000001c3": "01c3" + }, + "key": "0x37ddfcbcb4b2498578f90e0fcfef9965dcde4d4dfabe2f2836d2257faa169947" + }, + "0x3cf2e7052ebd484a8d6fbca579ddb3cf920de9d3": { + "balance": "0", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xa95c88d7dc0f2373287c3b2407ba8e7419063833c424b06d8bb3b29181bb632e" + }, + "0x3ee253436fc50e5a136ee01489a318afe2bbd572": { + "balance": "0", + "nonce": 1, + "root": "0xc57604a461c94ecdac12dbb706a52b32913d72253baffb8906e742724ae12449", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001b2": "01b2", + "0x00000000000000000000000000000000000000000000000000000000000001b3": "01b3", + "0x00000000000000000000000000000000000000000000000000000000000001b4": "01b4" + }, + "key": "0xaf7c37d08a73483eff9ef5054477fb5d836a184aa07c3edb4409b9eb22dd56ca" + }, + "0x3f31becc97226d3c17bf574dd86f39735fe0f0c1": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb40cc623b26a22203675787ca05b3be2c2af34b6b565bab95d43e7057e458684" + }, + "0x3f79bb7b435b05321651daefd374cdc681dc06fa": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x8c7bfaa19ea367dec5272872114c46802724a27d9b67ea3eed85431df664664e" + }, + "0x3fba9ae304c21d19f50c23db133073f4f9665fc1": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x0b564e4a0203cbcec8301709a7449e2e7371910778df64c89f48507390f2d129" + }, + "0x402f57de890877def439a753fcc0c37ac7808ef5": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x5c20f6ee05edbb60beeab752d87412b2f6e12c8feefa2079e6bd989f814ed4da" + }, + "0x40b7ab67fb92dbcb4ff4e39e1155cad2fa066523": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xd352b05571154d9a2061143fe6df190a740a2d321c59eb94a54acb7f3054e489" + }, + "0x414a21e525a759e3ffeb22556be6348a92d5a13e": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x15293aec87177f6c88f58bc51274ba75f1331f5cb94f0c973b1deab8b3524dfe" + }, + "0x417fe11f58b6a2d089826b60722fbed1d2db96dd": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xd5e252ab2fba10107258010f154445cf7dffc42b7d8c5476de9a7adb533d73f1" + }, + "0x41b45640640c98c953feef23468e0d275515f82f": { + "balance": "0", + "nonce": 1, + "root": "0x82b326641825378faa11c641c916f2e22c01080f487de0463e30d5e32b960f97", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000013a": "013a", + "0x000000000000000000000000000000000000000000000000000000000000013b": "013b", + "0x000000000000000000000000000000000000000000000000000000000000013c": "013c" + }, + "key": "0xc2406cbd93e511ef493ac81ebe2b6a3fbecd05a3ba52d82a23a88eeb9d8604f0" + }, + "0x426fcdc383c8becb38926ec0569ec4a810105fab": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x6bd9fb206b22c76b4f9630248940855b842c684db89adff0eb9371846ea625a9" + }, + "0x4340ee1b812acb40a1eb561c019c327b243b92df": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xa13bfef92e05edee891599aa5e447ff2baa1708d9a6473a04ef66ab94f2a11e4" + }, + "0x44bd7ae60f478fae1061e11a7739f4b94d1daf91": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb66092bc3624d84ff94ee42b097e846baf6142197d2c31245734d56a275c8eb9" + }, + "0x452705f08c621987b14d5f729ca81829041f6373": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xac7183ebb421005a660509b070d3d47fc4e134cb7379c31dc35dc03ebd02e1cf" + }, + "0x45dcb3e20af2d8ba583d774404ee8fedcd97672b": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x465311df0bf146d43750ed7d11b0451b5f6d5bfc69b8a216ef2f1c79c93cd848" + }, + "0x45f83d17e10b34fca01eb8f4454dac34a777d940": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x6dc09fdec00aa9a30dd8db984406a33e3ca15e35222a74773071207a5e56d2c2" + }, + "0x469542b3ece7ae501372a11c673d7627294a85ca": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x6dbe5551f50400859d14228606bf221beff07238bfa3866454304abb572f9512" + }, + "0x469dacecdef1d68cb354c4a5c015df7cb6d655bf": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x8b76305d3f00d33f77bd41496b4144fd3d113a2ec032983bd5830a8b73f61cf0" + }, + "0x46b61db0aac95a332cecadad86e52531e578cf1f": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x5677600b2af87d21fdab2ac8ed39bd1be2f790c04600de0400c1989040d9879c" + }, + "0x478508483cbb05defd7dcdac355dadf06282a6f2": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x5fc13d7452287b5a8e3c3be9e4f9057b5c2dd82aeaff4ed892c96fc944ec31e7" + }, + "0x47ce7195b6d53aaa737ff17d57db20d0d4874ef1": { + "balance": "0", + "nonce": 1, + "root": "0x3d0e2ba537f35941068709450f25fee45aaf4dc6ae2ed22ad12e0743ac7c54a7", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000108": "0108", + "0x0000000000000000000000000000000000000000000000000000000000000109": "0109", + "0x000000000000000000000000000000000000000000000000000000000000010a": "010a" + }, + "key": "0x0579e46a5ed8a88504ac7d579b12eb346fbe4fd7e281bdd226b891f8abed4789" + }, + "0x47dc540c94ceb704a23875c11273e16bb0b8a87a": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x025f478d53bf78add6fa3708d9e061d59bfe14b21329b2a4cf1156d4f81b3d2d" + }, + "0x47e642c9a2f80499964cfda089e0b1f52ed0f57d": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x05f6de281d8c2b5d98e8e01cd529bd76416b248caf11e0552047c5f1d516aab6" + }, + "0x4816ce9dd68c07ab1e12b5ddc4dbef38792751c5": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x93843d6fa1fe5709a3035573f61cc06832f0377544d16d3a0725e78a0fa0267c" + }, + "0x48701721ec0115f04bc7404058f6c0f386946e09": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x12be3bf1f9b1dab5f908ca964115bee3bcff5371f84ede45bc60591b21117c51" + }, + "0x494d799e953876ac6022c3f7da5e0f3c04b549be": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x04d9aa4f67f8b24d70a0ffd757e82456d9184113106b7d9e8eb6c3e8a8df27ee" + }, + "0x4a0f1452281bcec5bd90c3dce6162a5995bfe9df": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x5c1d92594d6377fe6423257781b382f94dffcde4fadbf571aa328f6eb18f8fcd" + }, + "0x4a64a107f0cb32536e5bce6c98c393db21cca7f4": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xf16522fc36907ee1e9948240b0c1d1d105a75cc63b71006f16c20d79ad469bd7" + }, + "0x4ae81572f06e1b88fd5ced7a1a000945432e83e1": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x2116ab29b4cb8547af547fe472b7ce30713f234ed49cb1801ea6d3cf9c796d57" + }, + "0x4b227777d4dd1fc61c6f884f48641d02b4d121d3": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x246cc8a2b79a30ec71390d829d0cb37cce1b953e89cb14deae4945526714a71c" + }, + "0x4ba91e785d2361ddb198bcd71d6038305021a9b8": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x99ce1680f73f2adfa8e6bed135baa3360e3d17f185521918f9341fc236526321" + }, + "0x4bfa260a661d68110a7a0a45264d2d43af9727de": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x6f358b4e903d31fdd5c05cddaa174296bb30b6b2f72f1ff6410e6c1069198989" + }, + "0x4dde844b71bcdf95512fb4dc94e84fb67b512ed8": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x5602444769b5fd1ddfca48e3c38f2ecad326fe2433f22b90f6566a38496bd426" + }, + "0x4f362f9093bb8e7012f466224ff1237c0746d8c8": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xcb6f450b4720c6b36d3a12271e35ace27f1d527d46b073771541ad39cc59398d" + }, + "0x4f3e7da249f34e3cc8b261a7dc5b2d8e1cd85b78": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4d79fea6c7fef10cb0b5a8b3d85b66836a131bec0b04d891864e6fdb9794af75" + }, + "0x4fb733bedb74fec8d65bedf056b935189a289e92": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xa02abeb418f26179beafd96457bda8c690c6b1f3fbabac392d0920863edddbc6" + }, + "0x4fffb6fbd0372228cb5e4d1f033a29f30cb668c8": { + "balance": "0", + "nonce": 1, + "root": "0xcd3e75299e967d5f88d306be905a134343b224d3fd5a861b1a690de0e2dfe1ba", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000b3": "b3", + "0x00000000000000000000000000000000000000000000000000000000000000b4": "b4", + "0x00000000000000000000000000000000000000000000000000000000000000b5": "b5" + }, + "key": "0xf19ee923ed66b7b9264c2644aa20e5268a251b4914ca81b1dffee96ecb074cb1" + }, + "0x50996999ff63a9a1a07da880af8f8c745a7fe72c": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x0e57ffa6cc6cbd96c1400150417dd9b30d958c58f63c36230a90a02b076f78b5" + }, + "0x5123198d8a827fe0c788c409e7d2068afde64339": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xa15773c9bfabef49e9825460ed95bf67b22b67d7806c840e0eb546d73c424768" + }, + "0x526e1ff4cddb5033849a114c54eb71a176f6440c": { + "balance": "0", + "nonce": 1, + "root": "0x834718111121e2058fdb90a51f448028071857e11fbd55d43256174df56af01a", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000c7": "c7", + "0x00000000000000000000000000000000000000000000000000000000000000c8": "c8", + "0x00000000000000000000000000000000000000000000000000000000000000c9": "c9" + }, + "key": "0xb3a33a7f35ca5d08552516f58e9f76219716f9930a3a11ce9ae5db3e7a81445d" + }, + "0x5371ac01baa0b8aa9cbfcd36a49e0b5f7fb7109d": { + "balance": "0", + "nonce": 1, + "root": "0x385b84d27059a3c78e7ea63a691eeb9c5376f77af11336762f8c18882ff7471a", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000028": "28", + "0x0000000000000000000000000000000000000000000000000000000000000029": "29", + "0x000000000000000000000000000000000000000000000000000000000000002a": "2a" + }, + "key": "0x7a08bb8417e6b18da3ba926568f1022c15553b2b0f1a32f2fd9e5a605469e54f" + }, + "0x54314225e5efd5b8283d6ec2f7a03d5a92106374": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xcade985c7fb6d371d0c7f7cb40178e7873d623eadcc37545798ec33a04bb2173" + }, + "0x549abf1ae8db6de0d131a7b2b094c813ec1c6731": { + "balance": "0", + "nonce": 1, + "root": "0x73bffc68a947fa19b7becd45661d22c870fac8dbf2b25703e1bdab5367f29543", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000086": "86", + "0x0000000000000000000000000000000000000000000000000000000000000087": "87", + "0x0000000000000000000000000000000000000000000000000000000000000088": "88" + }, + "key": "0x910fb8b22867289cb57531ad39070ef8dbdbbe7aee941886a0e9f572b63ae9ee" + }, + "0x5502b2da1a3a08ad258aa08c0c6e0312cf047e64": { + "balance": "0", + "nonce": 1, + "root": "0xf73591e791af4c7c5fa039c33dd9d169cab14b1d9b0ca78bcc4e740d553b1acf", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000f4": "f4", + "0x00000000000000000000000000000000000000000000000000000000000000f5": "f5", + "0x00000000000000000000000000000000000000000000000000000000000000f6": "f6" + }, + "key": "0x1d6ee979097e29141ad6b97ae19bb592420652b7000003c55eb52d5225c3307d" + }, + "0x553f68e60e9f8ea74c831449525dc1bc4f6fc58e": { + "balance": "0", + "nonce": 1, + "root": "0x14f9f4b9445c7547d5a4671a38b0b12bbc0e7198c3b2934b82b695c8630d4972", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000126": "0126", + "0x0000000000000000000000000000000000000000000000000000000000000127": "0127", + "0x0000000000000000000000000000000000000000000000000000000000000128": "0128" + }, + "key": "0x6ad3ba011e031431dc057c808b85346d58001b85b32a4b5c90ccccea0f82e170" + }, + "0x56270eccd88bcd5ad8d2b08f82d96cd8dace4eb3": { + "balance": "0", + "nonce": 1, + "root": "0xb0700fe13dbaf94be50bcbec13a7b53e6cba034b29a3daba98fa861f5897213f", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000063": "63", + "0x0000000000000000000000000000000000000000000000000000000000000064": "64", + "0x0000000000000000000000000000000000000000000000000000000000000065": "65" + }, + "key": "0xcd6b3739d4dbce17dafc156790f2a3936eb75ce95e9bba039dd76661f40ea309" + }, + "0x56d3f289b889e65c4268a1b56b3da2d3860d0afb": { + "balance": "0", + "nonce": 0, + "root": "0x207f6c3e450546b0d1f3bc6a6faf5bfa0bff80396c55d567b834cf0e7c760347", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000000a": "0a", + "0x000000000000000000000000000000000000000000000000000000000000000b": "0b", + "0x000000000000000000000000000000000000000000000000000000000000000c": "0c" + }, + "key": "0xdc9ea08bdea052acab7c990edbb85551f2af3e1f1a236356ab345ac5bcc84562" + }, + "0x56dc3a6c5ca1e1b773e5fdfc8a92e9a42feaa6e9": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xdbd66b6a89e01c76ae5f8cb0dcd8a24e787f58f015c9b08972bfabefa2eae0d5" + }, + "0x579ab019e6b461188300c7fb202448d34669e5ff": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x18f4256a59e1b2e01e96ac465e1d14a45d789ce49728f42082289fc25cf32b8d" + }, + "0x5820871100e656b0d84b950f0a557e37419bf17d": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4615e5f5df5b25349a00ad313c6cd0436b6c08ee5826e33a018661997f85ebaa" + }, + "0x58d77a134c11f45f9573d5c105fa6c8ae9b4237a": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xd9f987fec216556304eba05bcdae47bb736eea5a4183eb3e2c3a5045734ae8c7" + }, + "0x591317752b32e45c9d44d925a4bcb4898f6b51fb": { + "balance": "0", + "nonce": 1, + "root": "0x89bde89df7f2d83344a503944bb347b847f208df837228bb2cdfd6c3228ca3df", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000011c": "011c", + "0x000000000000000000000000000000000000000000000000000000000000011d": "011d", + "0x000000000000000000000000000000000000000000000000000000000000011e": "011e" + }, + "key": "0x88a5635dabc83e4e021167be484b62cbed0ecdaa9ac282dab2cd9405e97ed602" + }, + "0x5a6e7a4754af8e7f47fc9493040d853e7b01e39d": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x3e57e37bc3f588c244ffe4da1f48a360fa540b77c92f0c76919ec4ee22b63599" + }, + "0x5b35d3e1ac7a2c61d247046d38773decf4f2839a": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x55cab9586acb40e66f66147ff3a059cfcbbad785dddd5c0cc31cb43edf98a5d5" + }, + "0x5c019738b38feae2a8944bd644f7acd5e6f40e5c": { + "balance": "0", + "nonce": 1, + "root": "0xea83389383152270104093ed5dfe34ba403c75308133aa1be8f51ad804b3e9ee", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000103": "0103", + "0x0000000000000000000000000000000000000000000000000000000000000104": "0104", + "0x0000000000000000000000000000000000000000000000000000000000000105": "0105" + }, + "key": "0xbccd85b63dba6300f84c561c5f52ce08a240564421e382e6f550ce0c12f2f632" + }, + "0x5c04401b6f6a5e318c7b6f3106a6217d20008427": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x6c37093a34016ae687da7aabb18e42009b71edff70a94733c904aea51a4853c1" + }, + "0x5c23d95614dce3317e7be72de3c81479c3172a8a": { + "balance": "0", + "nonce": 1, + "root": "0x4f446329b5ee3d13d4f6b5e5f210ddc2d90fedba384b950e36a1d19af95c5cb1", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000000f": "0f", + "0x0000000000000000000000000000000000000000000000000000000000000010": "10", + "0x0000000000000000000000000000000000000000000000000000000000000011": "11" + }, + "key": "0x34a715e08b77afd68cde30b62e222542f3db90758370400c94d0563959a1d1a0" + }, + "0x5c62e091b8c0565f1bafad0dad5934276143ae2c": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x1bf7626cec5330a127e439e68e6ee1a1537e73b2de1aa6d6f7e06bc0f1e9d763" + }, + "0x5d6bc8f87dd221a9f8c4144a256391979ff6426b": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xcc74930e1ee0e71a8081f247ec47442a3e5d00897966754a5b3ee8beb2c1160c" + }, + "0x5df7504bc193ee4c3deadede1459eccca172e87c": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4f458f480644b18c0e8207f405b82da7f75c7b3b5a34fe6771a0ecf644677f33" + }, + "0x5ee0dd4d4840229fab4a86438efbcaf1b9571af9": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x3848b7da914222540b71e398081d04e3849d2ee0d328168a3cc173a1cd4e783b" + }, + "0x5f4755a4bd689dc90425fb2fdb64a4b191a7264d": { + "balance": "0", + "nonce": 1, + "root": "0xaf867e6cbae810caa924b8b6ac3d8c0891831491a6906dd0be7ad324dcd1533d", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000016c": "016c", + "0x000000000000000000000000000000000000000000000000000000000000016d": "016d", + "0x000000000000000000000000000000000000000000000000000000000000016e": "016e" + }, + "key": "0x1c3f74249a4892081ba0634a819aec9ed25f34c7653f5719b9098487e65ab595" + }, + "0x5f552da00dfb4d3749d9e62dcee3c918855a86a0": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xd52564daf6d32a6ae29470732726859261f5a7409b4858101bd233ed5cc2f662" + }, + "0x5f553e0d115af809cfc1396b4823378b2c7cced5": { + "balance": "0", + "nonce": 1, + "root": "0xcc48f8d1c0dd6ec8ab7bbd792d94f6a74c8876b41bc859cee2228e8dad8207a4", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000ae": "ae", + "0x00000000000000000000000000000000000000000000000000000000000000af": "af", + "0x00000000000000000000000000000000000000000000000000000000000000b0": "b0" + }, + "key": "0xe3c2e12be28e2e36dc852e76dd32e091954f99f2a6480853cd7b9e01ec6cd889" + }, + "0x6096d8459f8e424f514468098e6a0f2a871c815d": { + "balance": "0", + "nonce": 1, + "root": "0xa20e6a21244af8ffccd5442297ad9b7a76ac72d7d8ac9e16f12fcc50e90b734e", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000018f": "018f", + "0x0000000000000000000000000000000000000000000000000000000000000190": "0190", + "0x0000000000000000000000000000000000000000000000000000000000000191": "0191" + }, + "key": "0x67cc0bf5341efbb7c8e1bdbf83d812b72170e6edec0263eeebdea6f107bbef0d" + }, + "0x60d0debc5c81432ee294b9a06dcf58964224bbc2": { + "balance": "0", + "nonce": 1, + "root": "0x5446b818f4c669669cd3314726ff134cf18c58a9a536df13c700610705a8b7c8", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000041": "41", + "0x0000000000000000000000000000000000000000000000000000000000000042": "42", + "0x0000000000000000000000000000000000000000000000000000000000000043": "43" + }, + "key": "0x395b92f75f8e06b5378a84ba03379f025d785d8b626b2b6a1c84b718244b9a91" + }, + "0x61774970e93c00a3e206a26c64707d3e33f89972": { + "balance": "0", + "nonce": 1, + "root": "0x869acb929f591c54cb85842a51f296635e7d895798c547a293afe43e7bf7f417", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000006d": "6d", + "0x000000000000000000000000000000000000000000000000000000000000006e": "6e", + "0x000000000000000000000000000000000000000000000000000000000000006f": "6f" + }, + "key": "0x07b49045c401bcc408f983d91a199c908cdf0d646049b5b83629a70b0117e295" + }, + "0x6269e930eee66e89863db1ff8e4744d65e1fb6bf": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x419809ad1512ed1ab3fb570f98ceb2f1d1b5dea39578583cd2b03e9378bbe418" + }, + "0x62b67e1f685b7fef51102005dddd27774be3fee3": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xf462aaa112b195c148974ff796a81c0e7f9a972d04e60c178ac109102d593a88" + }, + "0x6325c46e45d96f775754b39a17d733c4920d0038": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7c463797c90e9ba42b45ae061ffaa6bbd0dad48bb4998f761e81859f2a904a49" + }, + "0x63eb2d6ec7c526fd386631f71824bad098f39813": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xfdaf2549ea901a469b3e91cd1c4290fab376ef687547046751e10b7b461ff297" + }, + "0x6510225e743d73828aa4f73a3133818490bd8820": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe6d72f72fd2fc8af227f75ab3ab199f12dfb939bdcff5f0acdac06a90084def8" + }, + "0x653b3bb3e18ef84d5b1e8ff9884aecf1950c7a1c": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xc2c26fbc0b7893d872fa528d6c235caab9164feb5b54c48381ff3d82c8244e77" + }, + "0x654aa64f5fbefb84c270ec74211b81ca8c44a72e": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x00aa781aff39a8284ef43790e3a511b2caa50803613c5096bc782e8de08fa4c5" + }, + "0x65c74c15a686187bb6bbf9958f494fc6b8006803": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x570210539713235b442bbbad50c58bee81b70efd2dad78f99e41a6c462faeb43" + }, + "0x662fb906c0fb671022f9914d6bba12250ea6adfb": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb58e22a9ece8f9b3fdbaa7d17fe5fc92345df11d6863db4159647d64a34ff10b" + }, + "0x66378d2edcc2176820e951f080dd6e9e15a0e695": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xa02c8b02efb52fad3056fc96029467937c38c96d922250f6d2c0f77b923c85aa" + }, + "0x670dc376ecca46823e13bab90acab2004fb1706c": { + "balance": "0", + "nonce": 1, + "root": "0xae440143d21e24a931b6756f6b3d50d337eaf0db3e6c34e36ab46fe2d99ef83e", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000199": "0199", + "0x000000000000000000000000000000000000000000000000000000000000019a": "019a", + "0x000000000000000000000000000000000000000000000000000000000000019b": "019b" + }, + "key": "0xdcda5b5203c2257997a574bdf85b2bea6d04829e8d7e048a709badc0fb99288c" + }, + "0x6741149452787eb4384ebbd8456643f246217034": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x37e51740ad994839549a56ef8606d71ace79adc5f55c988958d1c450eea5ac2d" + }, + "0x684888c0ebb17f374298b65ee2807526c066094c": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb062c716d86a832649bccd53e9b11c77fc8a2a00ef0cc0dd2f561688a69d54f7" + }, + "0x6922e93e3827642ce4b883c756b31abf80036649": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x3be526914a7d688e00adca06a0c47c580cb7aa934115ca26006a1ed5455dd2ce" + }, + "0x6a632187a3abf9bebb66d43368fccd612f631cbc": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x9de451c4f48bdb56c6df198ff8e1f5e349a84a4dc11de924707718e6ac897aa6" + }, + "0x6b23c0d5f35d1b11f9b683f0b0a617355deb1127": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x099d5081762b8b265e8ba4cd8e43f08be4715d903a0b1d96b3d9c4e811cbfb33" + }, + "0x6b2884fef44bd4288621a2cda9f88ca07b480861": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe6c5edf6a0fbdcff100e5ceafb63cba9aea355ba397a93fdb42a1a67b91375f8" + }, + "0x6c49c19c40a44bbf1cf9d2d8741ec1126e815fc6": { + "balance": "0", + "nonce": 1, + "root": "0xe00c49a65849d05cbf27a4d7788a68bc7b6013ae33411d40bc89282fc064f33d", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001ad": "01ad", + "0x00000000000000000000000000000000000000000000000000000000000001ae": "01ae", + "0x00000000000000000000000000000000000000000000000000000000000001af": "01af" + }, + "key": "0x0304d8eaccf0b942c468074250cbcb625ec5c4688b6b5d17d2a9bdd8dd565d5a" + }, + "0x6ca60a92cbf88c7f527978dc183a22e774755551": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x52d034ca6ebd21c7ba62a2ad3b6359aa4a1cdc88bdaa64bb2271d898777293ab" + }, + "0x6cc0ab95752bf25ec58c91b1d603c5eb41b8fbd7": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xaa0ac2f707a3dc131374839d4ee969eeb1cb55adea878f56e7b5b83d187d925c" + }, + "0x6d09a879576c0d941bea7833fb2285051b10d511": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xaa0ffaa57269b865dccce764bf412de1dff3e7bba22ce319ef09e5907317b3e7" + }, + "0x6d8b8f27857e10b21c0ff227110d7533cea03d0e": { + "balance": "0", + "nonce": 1, + "root": "0xd3d9839f87c29fb007fd9928d38bbf84ef089f0cd640c838f4a42631e828c667", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000117": "0117", + "0x0000000000000000000000000000000000000000000000000000000000000118": "0118", + "0x0000000000000000000000000000000000000000000000000000000000000119": "0119" + }, + "key": "0xfdbb8ddca8cecfe275da1ea1c36e494536f581d64ddf0c4f2e6dae9c7d891427" + }, + "0x6e09a59a69b41abca97268b05595c074ad157872": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7a3870cc1ed4fc29e9ab4dd3218dbb239dd32c9bf05bff03e325b7ba68486c47" + }, + "0x6e3d512a9328fa42c7ca1e20064071f88958ed93": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xc1a6a0bf60ee7b3228ecf6cb7c9e5491fbf62642a3650d73314e976d9eb9a966" + }, + "0x6e3faf1e27d45fca70234ae8f6f0a734622cff8a": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x97f72ff641eb40ee1f1163544931635acb7550a0d44bfb9f4cc3aeae829b6d7d" + }, + "0x6f80f6a318ea88bf0115d693f564139a5fb488f6": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe73b3367629c8cb991f244ac073c0863ad1d8d88c2e180dd582cefda2de4415e" + }, + "0x7021bf21ecdbefcb33d09e4b812a47b273aa1d5c": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb9400acf38453fd206bc18f67ba04f55b807b20e4efc2157909d91d3a9f7bed2" + }, + "0x706be462488699e89b722822dcec9822ad7d05a7": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x78948842ff476b87544c189ce744d4d924ffd0907107a0dbaa4b71d0514f2225" + }, + "0x717f8aa2b982bee0e29f573d31df288663e1ce16": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xc3c8e2dc64e67baa83b844263fe31bfe24de17bb72bfed790ab345b97b007816" + }, + "0x7212449475dcc75d408ad62a9acc121d94288f6d": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe333845edc60ed469a894c43ed8c06ec807dafd079b3c948077da56e18436290" + }, + "0x72dfcfb0c470ac255cde83fb8fe38de8a128188e": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x2fe5767f605b7b821675b223a22e4e5055154f75e7f3041fdffaa02e4787fab8" + }, + "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f": { + "balance": "999999999999999999999518871495454239", + "nonce": 402, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4363d332a0d4df8582a84932729892387c623fe1ec42e2cfcbe85c183ed98e0e" + }, + "0x75b9236dfe7d0e12eb21b6d175276a7c5d4e851d": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xc54ffffcbaa5b566a7cf37386c4ce5a338d558612343caaa99788343d516aa5f" + }, + "0x77adfc95029e73b173f60e556f915b0cd8850848": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x0993fd5b750fe4414f93c7880b89744abb96f7af1171ed5f47026bdf01df1874" + }, + "0x788adf954fc28a524008ea1f2d0e87ae8893afdc": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x903f24b3d3d45bc50c082b2e71c7339c7060f633f868db2065ef611885abe37e" + }, + "0x7a19252e8c9b457eb07f52d0ddbe16820b5b7830": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xab7bdc41a80ae9c8fcb9426ba716d8d47e523f94ffb4b9823512d259c9eca8cd" + }, + "0x7ace431cb61584cb9b8dc7ec08cf38ac0a2d6496": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x71dee9adfef0940a36336903bd6830964865180b98c0506f9bf7ba8f2740fbf9" + }, + "0x7c5bd2d144fdde498406edcb9fe60ce65b0dfa5f": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xfcc08928955d4e5e17e17e46d5adbb8011e0a8a74cabbdd3e138c367e89a4428" + }, + "0x7cb7c4547cf2653590d7a9ace60cc623d25148ad": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x55d0609468d8d4147a942e88cfc5f667daff850788d821889fbb03298924767c" + }, + "0x7d80ad47bf8699f49853640b12ee55b1f51691f1": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x65cf42efacdee07ed87a1c2de0752a4e3b959f33f9f9f8c77424ba759e01fcf2" + }, + "0x7da59d0dfbe21f43e842e8afb43e12a6445bbac0": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7c3e44534b1398abc786e4591364c329e976dbde3b3ed3a4d55589de84bcb9a6" + }, + "0x7dcef881c305fb208500cc9509db689047ed0967": { + "balance": "0", + "nonce": 1, + "root": "0x6d2b8a074c78a0e5a8095d7a010d4961c639c541cf56fbb7049480cc8f199765", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001bc": "01bc", + "0x00000000000000000000000000000000000000000000000000000000000001bd": "01bd", + "0x00000000000000000000000000000000000000000000000000000000000001be": "01be" + }, + "key": "0x68fc814efedf52ac8032da358ddcb61eab4138cb56b536884b86e229c995689c" + }, + "0x7f2dce06acdeea2633ff324e5cb502ee2a42d979": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe04fdefc4f2eefd22721d5944411b282d0fcb1f9ac218f54793a35bca8199c25" + }, + "0x7f774bb46e7e342a2d9d0514b27cee622012f741": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x720f25b62fc39426f70eb219c9dd481c1621821c8c0fa5367a1df6e59e3edf59" + }, + "0x7fd02a3bb5d5926d4981efbf63b66de2a7b1aa63": { + "balance": "0", + "nonce": 1, + "root": "0x7bf542bdaff5bfe3d33c26a88777773b5e525461093c36acb0dab591a319e509", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000032": "32", + "0x0000000000000000000000000000000000000000000000000000000000000033": "33", + "0x0000000000000000000000000000000000000000000000000000000000000034": "34" + }, + "key": "0xfc3d2e27841c0913d10aa11fc4af4793bf376efe3d90ce8360aa392d0ecefa24" + }, + "0x8074971c7d405ba1e70af34f5af7d564ddc495df": { + "balance": "0", + "nonce": 1, + "root": "0x60fc69100d8e632667c80b94d434008823ed75416b71cbd112b4d0b02f563027", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000a4": "a4", + "0x00000000000000000000000000000000000000000000000000000000000000a5": "a5", + "0x00000000000000000000000000000000000000000000000000000000000000a6": "a6" + }, + "key": "0x0e0e4646090b881949ec9991e48dec768ccd1980896aefd0d51fd56fd5689790" + }, + "0x8120ff763f8283e574fc767702056b57fcc89003": { + "balance": "0", + "nonce": 1, + "root": "0xa2e7084ba9cec179519c7e8950c66ad3cba8586a60cff9f4d60c188dd621522a", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000037": "37", + "0x0000000000000000000000000000000000000000000000000000000000000038": "38", + "0x0000000000000000000000000000000000000000000000000000000000000039": "39" + }, + "key": "0x48e291f8a256ab15da8401c8cae555d5417a992dff3848926fa5b71655740059" + }, + "0x8176caac8654abc74a905b137a37ecf7be2a9e95": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xc4bab059ee8f7b36c82ada44d22129671d8f47f254ca6a48fded94a8ff591c88" + }, + "0x81bda6e29da8c3e4806b64dfa1cd32cd9c8fa70e": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xd5e5e7be8a61bb5bfa271dfc265aa9744dea85de957b6cffff0ecb403f9697db" + }, + "0x828a91cb304a669deff703bb8506a19eba28e250": { + "balance": "0", + "nonce": 1, + "root": "0x936ac6251848da69a191cc91174e4b7583a12a43d896e243841ea98b65f264ad", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000017b": "017b", + "0x000000000000000000000000000000000000000000000000000000000000017c": "017c", + "0x000000000000000000000000000000000000000000000000000000000000017d": "017d" + }, + "key": "0xea810ea64a420acfa917346a4a02580a50483890cba1d8d1d158d11f1c59ed02" + }, + "0x82c291ed50c5f02d7e15e655c6353c9278e1bbec": { + "balance": "0", + "nonce": 1, + "root": "0x12de4544640fc8a027e1a912d776b90675bebfd50710c2876b2a24ec9eced367", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000cc": "cc", + "0x00000000000000000000000000000000000000000000000000000000000000cd": "cd", + "0x00000000000000000000000000000000000000000000000000000000000000ce": "ce" + }, + "key": "0xa9970b3744a0e46b248aaf080a001441d24175b5534ad80755661d271b976d67" + }, + "0x83c7e323d189f18725ac510004fdc2941f8c4a78": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb17ea61d092bd5d77edd9d5214e9483607689cdcc35a30f7ea49071b3be88c64" + }, + "0x847f88846c35337cbf57e37ffc18316a99ac2f14": { + "balance": "0", + "nonce": 1, + "root": "0x310a2ac83d7e3e4d333102b1f7153bb0416b38427eb2e335dc6632d779a8b4af", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000bd": "bd", + "0x00000000000000000000000000000000000000000000000000000000000000be": "be", + "0x00000000000000000000000000000000000000000000000000000000000000bf": "bf" + }, + "key": "0xbea55c1dc9f4a9fb50cbedc70448a4e162792b9502bb28b936c7e0a2fd7fe41d" + }, + "0x84873854dba02cf6a765a6277a311301b2656a7f": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x3197690074092fe51694bdb96aaab9ae94dac87f129785e498ab171a363d3b40" + }, + "0x84e75c28348fb86acea1a93a39426d7d60f4cc46": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x5162f18d40405c59ef279ad71d87fbec2bbfedc57139d56986fbf47daf8bcbf2" + }, + "0x85f97e04d754c81dac21f0ce857adc81170d08c6": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x2baa718b760c0cbd0ec40a3c6df7f2948b40ba096e6e4b116b636f0cca023bde" + }, + "0x8642821710100a9a3ab10cd4223278a713318096": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4fbc5fc8df4f0a578c3be3549f1cb3ef135cbcdf75f620c7a1d412462e9b3b94" + }, + "0x8749e96779cd1b9fa62b2a19870d9efc28acae09": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xa3d8baf7ae7c96b1020753d12154e28cc7206402037c28c49c332a08cf7c4b51" + }, + "0x87610688d55c08238eacf52864b5a5920a00b764": { + "balance": "0", + "nonce": 1, + "root": "0x2da86eb3d4ffdd895170bc7ef02b69a116fe21ac2ce45a3ed8e0bb8af17cf92b", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000fe": "fe", + "0x00000000000000000000000000000000000000000000000000000000000000ff": "ff", + "0x0000000000000000000000000000000000000000000000000000000000000100": "0100" + }, + "key": "0x80a2c1f38f8e2721079a0de39f187adedcb81b2ab5ae718ec1b8d64e4aa6930e" + }, + "0x878dedd9474cfa24d91bccc8b771e180cf01ac40": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7e1ef9f8d2fa6d4f8e6717c3dcccff352ea9b8b46b57f6106cdbeed109441799" + }, + "0x882e7e5d12617c267a72948e716f231fa79e6d51": { + "balance": "0", + "nonce": 0, + "root": "0x491b2cfba976b2e78bd9be3bc15c9964927205fc34c9954a4d61bbe8170ba533", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000005": "05", + "0x0000000000000000000000000000000000000000000000000000000000000006": "06", + "0x0000000000000000000000000000000000000000000000000000000000000007": "07" + }, + "key": "0xd2501ae11a14bf0c2283a24b7e77c846c00a63e71908c6a5e1caff201bad0762" + }, + "0x88654f0e7be1751967bba901ed70257a3cb79940": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x30ce5b7591126d5464dfb4fc576a970b1368475ce097e244132b06d8cc8ccffe" + }, + "0x892f60b39450a0e770f00a836761c8e964fd7467": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x74614a0c4ba7d7c70b162dad186b6cc77984ab4070534ad9757e04a5b776dcc8" + }, + "0x8a5edab282632443219e051e4ade2d1d5bbc671c": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xc251a3acb75a90ff0cdca31da1408a27ef7dcaa42f18e648f2be1a28b35eac32" + }, + "0x8a817bc42b2e2146dc4ca4dc686db0a4051d2944": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x17984cc4b4aac0492699d37662b53ec2acf8cbe540c968b817061e4ed27026d0" + }, + "0x8a8950f7623663222542c9469c73be3c4c81bbdf": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xaef83ad0ab332330a20e88cd3b5a4bcf6ac6c175ee780ed4183d11340df17833" + }, + "0x8ba7e4a56d8d4a4a2fd7d0c8b9e6f032dc76cefb": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x72e962dfe7e2828809f5906996dedeba50950140555b193fceb94f12fd6f0a22" + }, + "0x8bebc8ba651aee624937e7d897853ac30c95a067": { + "balance": "1", + "nonce": 1, + "root": "0xbe3d75a1729be157e79c3b77f00206db4d54e3ea14375a015451c88ec067c790", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "01", + "0x0000000000000000000000000000000000000000000000000000000000000002": "02", + "0x0000000000000000000000000000000000000000000000000000000000000003": "03" + }, + "key": "0x445cb5c1278fdce2f9cbdb681bdd76c52f8e50e41dbd9e220242a69ba99ac099" + }, + "0x8cf42eb93b1426f22a30bd22539503bdf838830c": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x0267c643f67b47cac9efacf6fcf0e4f4e1b273a727ded155db60eb9907939eb6" + }, + "0x8d33f520a3c4cef80d2453aef81b612bfe1cb44c": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb8d9b988ed60dbf5dca3e9d169343ca667498605f34fb6c30b45b2ed0f996f1a" + }, + "0x8d36bbb3d6fbf24f38ba020d9ceeef5d4562f5f2": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xc13c19f53ce8b6411d6cdaafd8480dfa462ffdf39e2eb68df90181a128d88992" + }, + "0x8fa24283a8c1cc8a0f76ac69362139a173592567": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xefaff7acc3ad3417517b21a92187d2e63d7a77bc284290ed406d1bc07ab3d885" + }, + "0x8fb778e47caf2df14eca7a389955ca74ac8f4924": { + "balance": "0", + "nonce": 1, + "root": "0xae2e7f1c933c6ca84ce8be811ef411dee773fb69508056d72448048ea1db5c47", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001ee": "01ee", + "0x00000000000000000000000000000000000000000000000000000000000001ef": "01ef", + "0x00000000000000000000000000000000000000000000000000000000000001f0": "01f0" + }, + "key": "0x4973f6aa8cf5b1190fc95379aa01cff99570ee6b670725880217237fb49e4b24" + }, + "0x90fd8e600ae1a7c69fa6ef2c537b533ca77366e8": { + "balance": "0", + "nonce": 1, + "root": "0xee9821621aa5ec9ab7d5878b2a995228adcdcacb710df522d2f91b434d3bdc79", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000c2": "c2", + "0x00000000000000000000000000000000000000000000000000000000000000c3": "c3", + "0x00000000000000000000000000000000000000000000000000000000000000c4": "c4" + }, + "key": "0xbfaac98225451c56b2f9aec858cffc1eb253909615f3d9617627c793b938694f" + }, + "0x913f841dfc8703ae76a4e1b8b84cd67aab15f17a": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xcb54add475a18ea02ab1adf9e2e73da7f23ecd3e92c4fa8ca4e8f588258cb5d3" + }, + "0x923f800cf288500f8e53f04e4698c9b885dcf030": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb91824b28183c95881ada12404d5ee8af8123689a98054d41aaf4dd5bec50e90" + }, + "0x9344b07175800259691961298ca11c824e65032d": { + "balance": "0", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0x8e0388ecf64cfa76b3a6af159f77451519a7f9bb862e4cce24175c791fdcb0df", + "code": "0x60004381526020014681526020014181526020014881526020014481526020013281526020013481526020016000f3", + "key": "0x2e6fe1362b3e388184fd7bf08e99e74170b26361624ffd1c5f646da7067b58b6" + }, + "0x93747f73c18356c6b202f527f552436a0e06116a": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x73cd1b7cd355f3f77c570a01100a616757408bb7abb78fe9ee1262b99688fcc4" + }, + "0x9380b994c5738f68312f0e517902da81f63cdcfa": { + "balance": "0", + "nonce": 1, + "root": "0x51b829f0f2c3de9cfbd94e47828a89940c329a49cd59540ca3c6d751a8d214d6", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000135": "0135", + "0x0000000000000000000000000000000000000000000000000000000000000136": "0136", + "0x0000000000000000000000000000000000000000000000000000000000000137": "0137" + }, + "key": "0x50d83ef5194d06752cd5594b57e809b135f24eedd124a51137feaaf049bc2efd" + }, + "0x94d068bff1af651dd9d9c2e75adfb7eec6f66be7": { + "balance": "0", + "nonce": 1, + "root": "0x0754035aa4073381a211342b507de8e775c97c961096e6e2275df0bfcbb3a01c", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000059": "59", + "0x000000000000000000000000000000000000000000000000000000000000005a": "5a", + "0x000000000000000000000000000000000000000000000000000000000000005b": "5b" + }, + "key": "0x0cd2a7c53c76f228ed3aa7a29644b1915fde9ec22e0433808bf5467d914e7c7a" + }, + "0x956062137518b270d730d4753000896de17c100a": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x5aa3b4a2ebdd402721c3953b724f4fe90900250bb4ef89ce417ec440da318cd6" + }, + "0x96a1cabb97e1434a6e23e684dd4572e044c243ea": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe7c6828e1fe8c586b263a81aafc9587d313c609c6db8665a42ae1267cd9ade59" + }, + "0x984c16459ded76438d98ce9b608f175c28a910a0": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4b9f335ce0bdffdd77fdb9830961c5bc7090ae94703d0392d3f0ff10e6a4fbab" + }, + "0x99a1c0703485b331fa0302d6077b583082e242ea": { + "balance": "0", + "nonce": 1, + "root": "0x2cf292c1e382bdd0e72e126701d7b02484e6e272f4c0d814f5a6fae233fc7935", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000121": "0121", + "0x0000000000000000000000000000000000000000000000000000000000000122": "0122", + "0x0000000000000000000000000000000000000000000000000000000000000123": "0123" + }, + "key": "0x734ee4981754a3f1403c4e8887d35addfb31717d93de3e00ede78368c230861e" + }, + "0x99d40a710cb552eaaee1599d4040055859b1610d": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x946bfb429d90f1b39bb47ada75376a8d90a5778068027d4b8b8514ac13f53eca" + }, + "0x9a7b7b3a5d50781b4f4768cd7ce223168f6b449b": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xd16e029e8c67c3f330cddaa86f82d31f523028404dfccd16d288645d718eb9da" + }, + "0x9ae62b6d840756c238b5ce936b910bb99d565047": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x8989651e80c20af78b37fdb693d74ecafc9239426ff1315e1fb7b674dcdbdb75" + }, + "0x9b3cf956056937dfb6f9e3dc02e3979a4e421c0a": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb1b2c1c59637202bb0e0d21255e44e0df719fe990be05f213b1b813e3d8179d7" + }, + "0x9bb981f592bc1f9c31db67f30bbf1ff44b649886": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x1ee7e0292fba90d9733f619f976a2655c484adb30135ef0c5153b5a2f32169df" + }, + "0x9bfb328671c108c9ba4d45734d9f4462d8c9a9cb": { + "balance": "0", + "nonce": 1, + "root": "0xc15b43e5f4853ec8da53ebde03de87b94afce42a9c02f648ad8bdb224604c4ad", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001da": "01da", + "0x00000000000000000000000000000000000000000000000000000000000001db": "01db", + "0x00000000000000000000000000000000000000000000000000000000000001dc": "01dc" + }, + "key": "0xa683478d0c949580d5738b490fac8129275bb6e921dfe5eae37292be3ee281b9" + }, + "0x9defb0a9e163278be0e05aa01b312ec78cfa3726": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb31919583a759b75e83c14d00d0a89bb36adc452f73cee2933a346ccebaa8e31" + }, + "0x9e59004e909ff011e5882332e421b6772e68ed10": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x3897cb9b6f68765022f3c74f84a9f2833132858f661f4bc91ccd7a98f4e5b1ee" + }, + "0x9f50ec6c8a595869d71ce8c3b1c17c02599a5cc3": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x2705244734f69af78e16c74784e1dc921cb8b6a98fe76f577cc441c831e973bf" + }, + "0xa0794cd73f564baeeda23fa4ce635a3f8ae39621": { + "balance": "0", + "nonce": 1, + "root": "0xfb79021e7fa54b9bd2df64f6db57897d52ae85f7c195af518de48200a1325e2c", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000ef": "ef", + "0x00000000000000000000000000000000000000000000000000000000000000f0": "f0", + "0x00000000000000000000000000000000000000000000000000000000000000f1": "f1" + }, + "key": "0x60535eeb3ffb721c1688b879368c61a54e13f8881bdef6bd4a17b8b92e050e06" + }, + "0xa12b147dd542518f44f821a4d436066c64932b0d": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xae88076d02b19c4d09cb13fca14303687417b632444f3e30fc4880c225867be3" + }, + "0xa179dbdd51c56d0988551f92535797bcf47ca0e7": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x6d1da4cf1127d654ed731a93105f481b315ecfc2f62b1ccb5f6d2717d6a40f9b" + }, + "0xa1fce4363854ff888cff4b8e7875d600c2682390": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xad99b5bc38016547d5859f96be59bf18f994314116454def33ebfe9a892c508a" + }, + "0xa225fe6df11a4f364234dd6a785a17cd38309acb": { + "balance": "0", + "nonce": 1, + "root": "0xc1686045288a5952ad57de0e971bd25007723c9f749f49f391e715c27bf526c8", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000072": "72", + "0x0000000000000000000000000000000000000000000000000000000000000073": "73", + "0x0000000000000000000000000000000000000000000000000000000000000074": "74" + }, + "key": "0x4e0ab2902f57bf2a250c0f87f088acc325d55f2320f2e33abd8e50ba273c9244" + }, + "0xa25513c7e0f6eaa80a3337ee18081b9e2ed09e00": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xfb9474d0e5538fcd99e8d8d024db335b4e057f4bcd359e85d78f4a5226b33272" + }, + "0xa5ab782c805e8bfbe34cb65742a0471cf5a53a97": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x6188c4510d25576535a642b15b1dbdb8922fe572b099f504390f923c19799777" + }, + "0xa64f449891f282b87e566036f981023dba4ed477": { + "balance": "0", + "nonce": 1, + "root": "0x61176dbc05a8537d8de85f82a03b8e1049cea7ad0a9f0e5b60ee15fca6fe0d42", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000012b": "012b", + "0x000000000000000000000000000000000000000000000000000000000000012c": "012c", + "0x000000000000000000000000000000000000000000000000000000000000012d": "012d" + }, + "key": "0x7c1edabb98857d64572f03c64ac803e4a14b1698fccffffd51675d99ee3ba217" + }, + "0xa6515a495ec7723416665ebb54fc002bf1e9a873": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xbbdc59572cc62c338fb6e027ab00c57cdeed233c8732680a56a5747141d20c7c" + }, + "0xa6a54695341f038ad15e9e32f1096f5201236512": { + "balance": "0", + "nonce": 1, + "root": "0xe2a72f5bfbeba70fc9ab506237ba27c096a4e96c3968cabf5b1b2fb54431b5cf", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000023": "23", + "0x0000000000000000000000000000000000000000000000000000000000000024": "24", + "0x0000000000000000000000000000000000000000000000000000000000000025": "25" + }, + "key": "0xa87387b50b481431c6ccdb9ae99a54d4dcdd4a3eff75d7b17b4818f7bbfc21e9" + }, + "0xa8100ae6aa1940d0b663bb31cd466142ebbdbd51": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x02547b56492bfe767f3d18be2aab96441c449cd945770ef7ef8555acc505b2e4" + }, + "0xa8d5dd63fba471ebcb1f3e8f7c1e1879b7152a6e": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x913e2a02a28d71d595d7216a12311f6921a4caf40aeabf0f28edf937f1df72b4" + }, + "0xa92bb60b61e305ddd888015189d6591b0eab0233": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xdd1589b1fe1d9b4ca947f98ff324de7887af299d5490ed92ae40e95eec944118" + }, + "0xa956ca63bf28e7da621475d6b077da1ab9812b3a": { + "balance": "0", + "nonce": 1, + "root": "0xa090b66fbca46cb71abd1daa8d419d2c6e291094f52872978dfcb1c31ad7a900", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001e4": "01e4", + "0x00000000000000000000000000000000000000000000000000000000000001e5": "01e5", + "0x00000000000000000000000000000000000000000000000000000000000001e6": "01e6" + }, + "key": "0xaad7b91d085a94c11a2f7e77dc95cfcfc5daf4f509ca4e0c0e493b86c6cbff78" + }, + "0xaa0d6dfdb7588017c80ea088768a5f3d0cdeacdb": { + "balance": "0", + "nonce": 1, + "root": "0x89ecb0ceeea20ccd7d1b18cf1d35b7a2fd7b76ddc8d627f43304ed8b31b01248", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000144": "0144", + "0x0000000000000000000000000000000000000000000000000000000000000145": "0145", + "0x0000000000000000000000000000000000000000000000000000000000000146": "0146" + }, + "key": "0xb990eaca858ea15fda296f3f47baa2939e8aa8bbccc12ca0c3746d9b5d5fb2ae" + }, + "0xaa53ff4bb2334faf9f4447197ef69c39c0bb1379": { + "balance": "0", + "nonce": 1, + "root": "0xe547c0050253075b1be4210608bc639cffe70110194c316481235e738be961e7", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000ea": "ea", + "0x00000000000000000000000000000000000000000000000000000000000000eb": "eb", + "0x00000000000000000000000000000000000000000000000000000000000000ec": "ec" + }, + "key": "0xed263a22f0e8be37bcc1873e589c54fe37fdde92902dc75d656997a7158a9d8c" + }, + "0xaa7225e7d5b0a2552bbb58880b3ec00c286995b8": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x5a4a3feecfc77b402e938e28df0c4cbb874771cb3c5a92524f303cffb82a2862" + }, + "0xab12a5f97f03edbff03eded9d1a2a1179d2fc69e": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xba1d0afdfee510e8852f24dff964afd824bf36d458cf5f5d45f02f04b7c0b35d" + }, + "0xab557835ab3e5c43bf34ac9b2ab730c5e0bc9967": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xc9ea69dc9e84712b1349c9b271956cc0cb9473106be92d7a937b29e78e7e970e" + }, + "0xab9025d4a9f93c65cd4fe978d38526860af0aa62": { + "balance": "0", + "nonce": 1, + "root": "0x4ce79cd9645650f0a00effa86f6fea733cecea9ea26964828ff25cf0577bc974", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000009a": "9a", + "0x000000000000000000000000000000000000000000000000000000000000009b": "9b", + "0x000000000000000000000000000000000000000000000000000000000000009c": "9c" + }, + "key": "0x17350c7adae7f08d7bbb8befcc97234462831638443cd6dfea186cbf5a08b7c7" + }, + "0xabd693b23d55dec7d0d0cba2ecbc9298dc4edf02": { + "balance": "0", + "nonce": 1, + "root": "0xafd54e81f3e415407f0812a678856f1b4068ed64a08b3f3bf5b2190fcfb2322d", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001b7": "01b7", + "0x00000000000000000000000000000000000000000000000000000000000001b8": "01b8", + "0x00000000000000000000000000000000000000000000000000000000000001b9": "01b9" + }, + "key": "0xbe7d987a9265c0e44e9c5736fb2eb38c41973ce96e5e8e6c3c713f9d50a079ff" + }, + "0xabe2b033c497e091c1e494c98c178e8aa06bcb00": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x2374954008440ca3d17b1472d34cc52a6493a94fb490d5fb427184d7d5fd1cbf" + }, + "0xac4d51af4cb7bab4743fa57bc80b144d7a091268": { + "balance": "0", + "nonce": 1, + "root": "0xfb00729a5f4f9a2436b999aa7159497a9cd88d155770f873a818b55052c5f067", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000149": "0149", + "0x000000000000000000000000000000000000000000000000000000000000014a": "014a", + "0x000000000000000000000000000000000000000000000000000000000000014b": "014b" + }, + "key": "0xe42a85d04a1d0d9fe0703020ef98fa89ecdeb241a48de2db73f2feeaa2e49b0f" + }, + "0xac7d8d5f6be7d251ec843ddbc09095150df59965": { + "balance": "0", + "nonce": 1, + "root": "0xa9580109be2f7d35b5360050c2ced74e5d4dea2f82d46e8d266ed89157636004", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000046": "46", + "0x0000000000000000000000000000000000000000000000000000000000000047": "47", + "0x0000000000000000000000000000000000000000000000000000000000000048": "48" + }, + "key": "0x943f42ad91e8019f75695946d491bb95729f0dfc5dbbb953a7239ac73f208943" + }, + "0xac9e61d54eb6967e212c06aab15408292f8558c4": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xf2b9bc1163840284f3eb15c539972edad583cda91946f344f4cb57be15af9c8f" + }, + "0xaceac762ff518b4cf93a6eebbc55987e7b79b2ce": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x1960414a11f8896c7fc4243aba7ed8179b0bc6979b7c25da7557b17f5dee7bf7" + }, + "0xacfa6b0e008d0208f16026b4d17a4c070e8f9f8d": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x58e416a0dd96454bd2b1fe3138c3642f5dee52e011305c5c3416d97bc8ba5cf0" + }, + "0xad108e31c9632ad9e20614b3ca40644d32948dbb": { + "balance": "0", + "nonce": 1, + "root": "0x2625f8a23d24a5dff6a79f632b1020593362a6ac622fa5237460bc67b0aa0ed3", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001a3": "01a3", + "0x00000000000000000000000000000000000000000000000000000000000001a4": "01a4", + "0x00000000000000000000000000000000000000000000000000000000000001a5": "01a5" + }, + "key": "0xdce547cc70c79575ef72c061502d6066db1cbce200bd904d5d2b20d4f1cb5963" + }, + "0xae3f4619b0413d70d3004b9131c3752153074e45": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb1b2fd7758f73e25a2f9e72edde82995b2b32ab798bcffd2c7143f2fc8196fd8" + }, + "0xae58b7e08e266680e93e46639a2a7e89fde78a6f": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe09e5f27b8a7bf61805df6e5fefc24eb6894281550c2d06250adecfe1e6581d7" + }, + "0xaf17b30f5ab8e6a4d7a563bdb0194f3e0bd50209": { + "balance": "0", + "nonce": 1, + "root": "0x2434bfc643ec364116cd71519a397662b20c52d1adcff0b830e80a738e19f30e", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000b8": "b8", + "0x00000000000000000000000000000000000000000000000000000000000000b9": "b9", + "0x00000000000000000000000000000000000000000000000000000000000000ba": "ba" + }, + "key": "0x26ce7d83dfb0ab0e7f15c42aeb9e8c0c5dba538b07c8e64b35fb64a37267dd96" + }, + "0xaf193a8cdcd0e3fb39e71147e59efa5cad40763d": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x1a28912018f78f7e754df6b9fcec33bea25e5a232224db622e0c3343cf079eff" + }, + "0xaf2c6f1512d1cabedeaf129e0643863c57419732": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xad6a4a6ebd5166c9b5cc8cfbaec176cced40fa88c73d83c67f0c3ed426121ebc" + }, + "0xb0b2988b6bbe724bacda5e9e524736de0bc7dae4": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x053df2c3b574026812b154a99b13b626220af85cd01bb1693b1d42591054bce6" + }, + "0xb0ee91ba61e8a3914a7eab120786e9e61bfe4faf": { + "balance": "0", + "nonce": 1, + "root": "0xa14913d548ac1d3f9962a21a569fe52f1436b6d2f5ea4e36de13ea855ede54e0", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000068": "68", + "0x0000000000000000000000000000000000000000000000000000000000000069": "69", + "0x000000000000000000000000000000000000000000000000000000000000006a": "6a" + }, + "key": "0x4bd8ef9873a5e85d4805dbcb0dbf6810e558ea175167549ef80545a9cafbb0e1" + }, + "0xb12dc850a3b0a3b79fc2255e175241ce20489fe4": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4ccd31891378d2025ef58980481608f11f5b35a988e877652e7cbb0a6127287c" + }, + "0xb47f70b774d780c3ec5ac411f2f9198293b9df7a": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xdef989cb85107747de11222bd7418411f8f3264855e1939ef6bef9447e42076d" + }, + "0xb4bc136e1fb4ea0b3340d06b158277c4a8537a13": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb7c2ef96238f635f86f9950700e36368efaaa70e764865dddc43ff6e96f6b346" + }, + "0xb519be874447e0f0a38ee8ec84ecd2198a9fac77": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x92b13a73440c6421da22e848d23f9af80610085ab05662437d850c97a012d8d3" + }, + "0xb55a3d332d267493105927b892545d2cd4c83bd6": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xc781c7c3babeb06adfe8f09ecb61dbe0eb671e41f3a1163faac82fdfa2bc83e8" + }, + "0xb609bc528052bd9669595a35f6eb6a4d7a30ac3d": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe6388bfcbbd6000e90a10633c72c43b0b0fed7cf38eab785a71e6f0c5b80a26a" + }, + "0xb68176634dde4d9402ecb148265db047d17cb4ab": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xf4a1c4554b186a354b3e0c467eef03df9907cd5a5d96086c1a542b9e5160ca78" + }, + "0xb70654fead634e1ede4518ef34872c9d4f083a53": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7f9726a7b2f5f3a501b2d7b18ec726f25f22c86348fae0f459d882ec5fd7d0c7" + }, + "0xb71de80778f2783383f5d5a3028af84eab2f18a4": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x64d0de66ea29cbcf7f237dae1c5f883fa6ff0ba52b90f696bb0348224dbc82ce" + }, + "0xb787c848479278cfdb56950cda545cd45881722d": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x1098f06082dc467088ecedb143f9464ebb02f19dc10bd7491b03ba68d751ce45" + }, + "0xb911abeead298d03c21c6c5ff397cd80eb375d73": { + "balance": "0", + "nonce": 1, + "root": "0x54abcdbc8b04bc9b70e9bd46cb9db9b8eb08cfd4addba4c941dacc34dd28648e", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000054": "54", + "0x0000000000000000000000000000000000000000000000000000000000000055": "55", + "0x0000000000000000000000000000000000000000000000000000000000000056": "56" + }, + "key": "0x873429def7829ff8227e4ef554591291907892fc8f3a1a0667dada3dc2a3eb84" + }, + "0xb917b7f3d49770d3d2f0ad2f497e5bfe0f25dc5f": { + "balance": "0", + "nonce": 1, + "root": "0x11d4eec7df52cd54e74690a487884e56371976c2b8c49ffc4c8f34831166bf4e", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000162": "0162", + "0x0000000000000000000000000000000000000000000000000000000000000163": "0163", + "0x0000000000000000000000000000000000000000000000000000000000000164": "0164" + }, + "key": "0x65e6b6521e4f1f97e80710581f42063392c9b33e0aeea4081a102a32238992ea" + }, + "0xb9b85616fc8ed95979a5e31b8968847e7518b165": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x6a5e43139d88da6cfba857e458ae0b5359c3fde36e362b6e5f782a90ce351f14" + }, + "0xbac9d93678c9b032c393a23e4c013e37641ad850": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x8a8266874b43f78d4097f27b2842132faed7e7e430469eec7354541eb97c3ea0" + }, + "0xbbeebd879e1dff6918546dc0c179fdde505f2a21": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x170c927130fe8f1db3ae682c22b57f33f54eb987a7902ec251fe5dba358a2b25" + }, + "0xbbf3f11cb5b43e700273a78d12de55e4a7eab741": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe74ac72f03e8c514c2c75f3c4f54ba31e920374ea7744ef1c33937e64c7d54f1" + }, + "0xbc5959f43bc6e47175374b6716e53c9a7d72c594": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xfd3a8bacd3b2061cbe54f8d38cf13c5c87a92816937683652886dee936dfae10" + }, + "0xbceef655b5a034911f1c3718ce056531b45ef03b": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x6c05d8abc81143ce7c7568c98aadfe6561635c049c07b2b4bce3019cef328cb9" + }, + "0xbd079b0337a29cccd2ec95b395ef5c01e992b6a5": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xf0877d51b7712e08f2a3c96cddf50ff61b8b90f80b8b9817ea613a8a157b0c45" + }, + "0xbe3eea9a483308cb3134ce068e77b56e7c25af19": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7026c939a9158beedff127a64f07a98b328c3d1770690437afdb21c34560fc57" + }, + "0xc04b5bb1a5b2eb3e9cd4805420dba5a9d133da5b": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x72d91596112f9d7e61d09ffa7575f3587ad9636172ae09641882761cc369ecc0" + }, + "0xc18d2be47547904f88a4f46cee75f8f4a94e1807": { + "balance": "0", + "nonce": 1, + "root": "0x9c32ffd5059115bba9aed9174f5ab8b4352e3f51a85dde33000f703c9b9fe7c2", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000018a": "018a", + "0x000000000000000000000000000000000000000000000000000000000000018b": "018b", + "0x000000000000000000000000000000000000000000000000000000000000018c": "018c" + }, + "key": "0xa601eb611972ca80636bc39087a1dae7be5a189b94bda392f84d6ce0d3c866b9" + }, + "0xc19a797fa1fd590cd2e5b42d1cf5f246e29b9168": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x99dba7e9230d5151cc37ff592fa1592f27c7c81d203760dfaf62ddc9f3a6b8fd" + }, + "0xc305dd6cfc073cfe5e194fc817536c419410a27d": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x016d92531f4754834b0502de5b0342ceff21cde5bef386a83d2292f4445782c2" + }, + "0xc337ded6f56c07205fb7b391654d7d463c9e0c72": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7c608293e741d1eb5ae6916c249a87b6540cf0c2369e96d293b1a7b5b9bd8b31" + }, + "0xc57aa6a4279377063b17c554d3e33a3490e67a9a": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xc192ea2d2bb89e9bb7f17f3a282ebe8d1dd672355b5555f516b99b91799b01f6" + }, + "0xc5eaec262d853fbdaccca406cdcada6fa6dd0944": { + "balance": "0", + "nonce": 1, + "root": "0x471bf8988ad0d7602d6bd5493c08733096c116ac788b76f22a682bc4558e3aa7", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000158": "0158", + "0x0000000000000000000000000000000000000000000000000000000000000159": "0159", + "0x000000000000000000000000000000000000000000000000000000000000015a": "015a" + }, + "key": "0x580aa878e2f92d113a12c0a3ce3c21972b03dbe80786858d49a72097e2c491a3" + }, + "0xc7a0a19ea8fc63cc6021af2e11ac0584d75c97b7": { + "balance": "0", + "nonce": 1, + "root": "0xe2a164e2c30cf30391c88ff32a0e202194b08f2a61a9cd2927ea5ed6dfbf1056", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000e5": "e5", + "0x00000000000000000000000000000000000000000000000000000000000000e6": "e6", + "0x00000000000000000000000000000000000000000000000000000000000000e7": "e7" + }, + "key": "0x86d03d0f6bed220d046a4712ec4f451583b276df1aed33f96495d22569dc3485" + }, + "0xc7b99a164efd027a93f147376cc7da7c67c6bbe0": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x8e11480987056c309d7064ebbd887f086d815353cdbaadb796891ed25f8dcf61" + }, + "0xc7d4ef05550c226c50cf0d4231ba1566d03fa98d": { + "balance": "0", + "nonce": 1, + "root": "0x3a2985c6ada67e5604b99fa2fc1a302abd0dc241ee7f14c428fa67d476868bb6", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000010d": "010d", + "0x000000000000000000000000000000000000000000000000000000000000010e": "010e", + "0x000000000000000000000000000000000000000000000000000000000000010f": "010f" + }, + "key": "0x5a356862c79afffd6a01af752d950e11490146e4d86dfb8ab1531e9aef4945a1" + }, + "0xca358758f6d27e6cf45272937977a748fd88391d": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xbccd3d2f920dfb8d70a38c9ccd5ed68c2ef6e3372199381767ce222f13f36c87" + }, + "0xca87240ef598bd6e4b8f67b3761af07d5f575514": { + "balance": "0", + "nonce": 1, + "root": "0x11f5d399ca8fb7a9af5ad481be60cf61d45493cd20206c9d0a237ce7d7571e5f", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001f3": "01f3", + "0x00000000000000000000000000000000000000000000000000000000000001f4": "01f4", + "0x00000000000000000000000000000000000000000000000000000000000001f5": "01f5" + }, + "key": "0x4b238e08b80378d0815e109f350a08e5d41ec4094df2cfce7bc8b9e3115bda70" + }, + "0xcb925b74da97bdff2130523c2a788d4beff7b3c3": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe0c5acf66bda927704953fdf7fb4b99e116857121c069eca7fb9bd8acfc25434" + }, + "0xcccc369c5141675a9e9b1925164f30cdd60992dc": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xfe2511e8a33ac9973b773aaedcb4daa73ae82481fe5a1bf78b41281924260cf5" + }, + "0xce24f30695b735e48b67467d76f5185ee3c7a0c5": { + "balance": "0", + "nonce": 1, + "root": "0x5442e0279d3f1149de4ce8d9e2d3f01d1854755038ac1a0fae5c48749bf71f20", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001e9": "01e9", + "0x00000000000000000000000000000000000000000000000000000000000001ea": "01ea", + "0x00000000000000000000000000000000000000000000000000000000000001eb": "01eb" + }, + "key": "0x47450e5beefbd5e3a3f80cbbac474bb3db98d5e609aa8d15485c3f0d733dea3a" + }, + "0xd048d242574c45095c72eaf58d2808778117afcb": { + "balance": "0", + "nonce": 1, + "root": "0x7217cb747054306f826e78aa3fc68fe4441299a337ecea1d62582f2da8a7f336", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001a8": "01a8", + "0x00000000000000000000000000000000000000000000000000000000000001a9": "01a9", + "0x00000000000000000000000000000000000000000000000000000000000001aa": "01aa" + }, + "key": "0xa9656c0192bb27f0ef3f93ecc6cc990dd146da97ac11f3d8d0899fba68d5749a" + }, + "0xd0752b60adb148ca0b3b4d2591874e2dabd34637": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x625e5c85d5f4b6385574b572709d0f704b097527a251b7c658c0c4441aef2af6" + }, + "0xd089c853b406be547d8e331d31cbd5c4d472a349": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x389093badcaa24c3a8cbb4461f262fba44c4f178a162664087924e85f3d55710" + }, + "0xd0918e2e24c5ddc0557a61ca11e055d2ac210fe5": { + "balance": "0", + "nonce": 1, + "root": "0x25b42ec5480843a0328c63bc50eff8595d90f1d1b0afcab2f4a19b888c794f37", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000a9": "a9", + "0x00000000000000000000000000000000000000000000000000000000000000aa": "aa", + "0x00000000000000000000000000000000000000000000000000000000000000ab": "ab" + }, + "key": "0xbaae09901e990935de19456ac6a6c8bc1e339d0b80ca129b8622d989b5c79120" + }, + "0xd10b36aa74a59bcf4a88185837f658afaf3646ef": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x9fe8b6e43098a4df56e206d479c06480801485dfd8ec3da4ccc3cebf5fba89a1" + }, + "0xd1211001882d2ce16a8553e449b6c8b7f71e6183": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x61088707d2910974000e63c2d1a376f4480ba19dde19c4e6a757aeb3d62d5439" + }, + "0xd1347bfa3d09ec56b821e17c905605cd5225069f": { + "balance": "0", + "nonce": 1, + "root": "0x287acc7869421fb9f49a3549b902fb01b7accc032243bd7e1accd8965d95d915", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000019e": "019e", + "0x000000000000000000000000000000000000000000000000000000000000019f": "019f", + "0x00000000000000000000000000000000000000000000000000000000000001a0": "01a0" + }, + "key": "0x5b90bb05df9514b2d8e3a8feb3d6c8c22526b02398f289b42111426edc4fe6cf" + }, + "0xd20b702303d7d7c8afe50344d66a8a711bae1425": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4d67d989fdb264fa4b2524d306f7b3f70ddce0b723411581d1740407da325462" + }, + "0xd282cf9c585bb4f6ce71e16b6453b26aa8d34a53": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x0e27113c09de0a0cb0ff268c677aba17d39a3190fe15aec0ff7f54184955cba4" + }, + "0xd2e2adf7177b7a8afddbc12d1634cf23ea1a7102": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x79afb7a5ffe6ccd537f9adff8287b78f75c37d97ea8a4dd504a08bc09926c3fa" + }, + "0xd39b94587711196640659ec81855bcf397e419ff": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xa9de128e7d4347403eb97f45e969cd1882dfe22c1abe8857aab3af6d0f9e9b92" + }, + "0xd48171b7166f5e467abcba12698df579328e637d": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x188111c233bf6516bb9da8b5c4c31809a42e8604cd0158d933435cfd8e06e413" + }, + "0xd4f09e5c5af99a24c7e304ca7997d26cb0090169": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe1068e9986da7636501d8893f67aa94f5d73df849feab36505fd990e2d6240e9" + }, + "0xd803681e487e6ac18053afc5a6cd813c86ec3e4d": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe5302e42ca6111d3515cbbb2225265077da41d997f069a6c492fa3fcb0fdf284" + }, + "0xd854d6dd2b74dc45c9b883677584c3ac7854e01a": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x9a1896e612ca43ecb7601990af0c3bc135b9012c50d132769dfb75d0038cc3be" + }, + "0xd8c50d6282a1ba47f0a23430d177bbfbb72e2b84": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xfc4870c3cd21d694424c88f0f31f75b2426e1530fdea26a14031ccf9baed84c4" + }, + "0xd917458e88a37b9ae35f72d4cc315ef2020b2418": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x4c2765139cace1d217e238cc7ccfbb751ef200e0eae7ec244e77f37e92dfaee5" + }, + "0xdbe726e81a7221a385e007ef9e834a975a4b528c": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x5fcd9b6fce3394ad1d44733056b3e5f6306240974a16f9de8e96ebdd14ae06b1" + }, + "0xdc60d4434411b2608150f68c4c1b818b6208acc2": { + "balance": "0", + "nonce": 1, + "root": "0x27e9b6a54cf0fb188499c508bd96d450946cd6ba1cf76cf5343b5c74450f6690", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001df": "01df", + "0x00000000000000000000000000000000000000000000000000000000000001e0": "01e0", + "0x00000000000000000000000000000000000000000000000000000000000001e1": "01e1" + }, + "key": "0x8510660ad5e3d35a30d4fb7c2615c040f9f698faae2ac48022e366deaeecbe77" + }, + "0xdd1e2826c0124a6d4f7397a5a71f633928926c06": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xf0a51b55aadfa3cafdd214b0676816e574931a683f51218207c625375884e785" + }, + "0xdd9ee108e8d5d2e8937e9fd029ec3a6640708af0": { + "balance": "0", + "nonce": 1, + "root": "0x8289b558865f2ca1f54c98b5ff5df95f07c24ec605e247b58c7798605dcd794f", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001cb": "01cb", + "0x00000000000000000000000000000000000000000000000000000000000001cc": "01cc", + "0x00000000000000000000000000000000000000000000000000000000000001cd": "01cd" + }, + "key": "0x2a39afbe88f572c23c90da2d059af3de125f1da5c3753c530dc5619a4857119f" + }, + "0xde5a6f78116eca62d7fc5ce159d23ae6b889b365": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xbb861b82d884a70666afeb78bbf30cab7fdccf838f4d5ce5f4e5ca1be6be61b1" + }, + "0xde7d1b721a1e0632b7cf04edf5032c8ecffa9f9a": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x9966a8b4cd856b175855258fa7e412ffef06d9e92b519050fa7ac06d8952ac84" + }, + "0xdfe052578c96df94fa617102199e66110181ed2c": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x54c12444ede3e2567dd7f4d9a06d4db8c6ab800d5b3863f8ff22a0db6d09bf24" + }, + "0xe3a71b4caf54df7d2480743c5a6770a1a5a9bcda": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe4d9c31cc9b4a9050bbbf77cc08ac26d134253dcb6fd994275c5c3468f5b7810" + }, + "0xe3b98a4da31a127d4bde6e43033f66ba274cab0e": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x70aae390a762a4347a4d167a2431874554edf1d77579213e55fea3ec39a1257c" + }, + "0xe439e4ea04e52cf38d0925f0722d341097378b88": { + "balance": "0", + "nonce": 1, + "root": "0x6c00e091dae3d4226facd6be802c865d5db0f524754d22666406138b54fab0e6", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000008b": "8b", + "0x000000000000000000000000000000000000000000000000000000000000008c": "8c", + "0x000000000000000000000000000000000000000000000000000000000000008d": "8d" + }, + "key": "0x38152bce526b7e1c2bedfc9d297250fcead02818be7806638564377af145103b" + }, + "0xe43ce33cdb88a2efe8a3d652bfb252fd91a950a7": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xc157e0d637d64b90e2c59bc8bed2acd75696ea1ac6b633661c12ce8f2bce0d62" + }, + "0xe52c0f008957444c48eba77467eaf2b7c127e3c5": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb888c9946a84be90a9e77539b5ac68a3c459761950a460f3e671b708bb39c41f" + }, + "0xe5ec19296e6d1518a6a38c1dbc7ad024b8a1a248": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x519abb269c3c5710f1979ca84192e020ba5c838bdd267b2d07436a187f171232" + }, + "0xe6dddbffde545e58030d4b8ca9e00cfb68975b5d": { + "balance": "0", + "nonce": 1, + "root": "0x2afe93e1b0f26e588d2809127e4360ad7e28cf552498b2bc4847d6bcda738cdb", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000130": "0130", + "0x0000000000000000000000000000000000000000000000000000000000000131": "0131", + "0x0000000000000000000000000000000000000000000000000000000000000132": "0132" + }, + "key": "0xa0f5dc2d18608f8e522ffffd86828e3d792b36d924d5505c614383ddff9be2eb" + }, + "0xe75db02929f3d5d7c28ecdb064ece929602c06bd": { + "balance": "0", + "nonce": 1, + "root": "0x9eda8eb6ca03d7c4afe47279acc90a45d1b2ca6a11afd95206f8868d20520d06", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000001e": "1e", + "0x000000000000000000000000000000000000000000000000000000000000001f": "1f", + "0x0000000000000000000000000000000000000000000000000000000000000020": "20" + }, + "key": "0x600a7a5f41a67f6f759dcb664198f1c5d9b657fb51a870ce9e234e686dff008e" + }, + "0xe7b2ceb8674516c4aeb43979808b237656ab3b6b": { + "balance": "0", + "nonce": 1, + "root": "0xcd31ed5d5da79990afed0d993cb725c4e34dd97544b03466ed34212e42c28d68", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000014e": "014e", + "0x000000000000000000000000000000000000000000000000000000000000014f": "014f", + "0x0000000000000000000000000000000000000000000000000000000000000150": "0150" + }, + "key": "0x75d231f57a1a9751f58769d5691f4807ab31ac0e802b1a1f6bfc77f5dff0adbf" + }, + "0xe7d13f7aa2a838d24c59b40186a0aca1e21cffcc": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xec3e92967d10ac66eff64a5697258b8acf87e661962b2938a0edcd78788f360d" + }, + "0xe82c38488eded9fb72a5ed9e039404c537f20b13": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7a2464bc24d90557940e93a3b73308ea354ed7d988be720c545974a17959f93f" + }, + "0xe920ab4e34595482e98b2c0d16be164c49190546": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xd623b1845175b206c127c08046281c013e4a3316402a771f1b3b77a9831143f5" + }, + "0xe99c76a6c3b831a926ab623476d2ec14560c09b4": { + "balance": "0", + "nonce": 1, + "root": "0x0fd8e99b1b4ab4eb8c6c2218221ae6978cc67433341ed8a1ad6185d34fa82c61", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000014": "14", + "0x0000000000000000000000000000000000000000000000000000000000000015": "15", + "0x0000000000000000000000000000000000000000000000000000000000000016": "16" + }, + "key": "0x6641e3ed1f264cf275b53bb7012dabecf4c1fca700e3db989e314c24cc167074" + }, + "0xe9b17e54dba3344a23160cb2b64f88024648c53e": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xb4f179efc346197df9c3a1cb3e95ea743ddde97c27b31ad472d352dba09ee1f5" + }, + "0xebe708edc62858621542b7354bb478228eb95577": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7bff1b6b56891e66584ced453d09450c2fed9453b1644e8509bef9f9dd081bbb" + }, + "0xebf37af41b6d7913aed3b9cc650d1e8f58a3d785": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x209b102e507b8dfc6acfe2cf55f4133b9209357af679a6d507e6ee87112bfe10" + }, + "0xeda8645ba6948855e3b3cd596bbb07596d59c603": { + "balance": "1000000000000000000000000000000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xabd8afe9fbf5eaa36c506d7c8a2d48a35d013472f8182816be9c833be35e50da" + }, + "0xef6cbd2161eaea7943ce8693b9824d23d1793ffb": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xce732a5e3b88ae26790aeb390a2bc02c449fdf57665c6d2c2b0dbce338c4377e" + }, + "0xf031efa58744e97a34555ca98621d4e8a52ceb5f": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x00748bacab20da9ae19dd26a33bd10bbf825e28b3de84fc8fe1d15a21645067f" + }, + "0xf068ae4089a66c79afe47d6e513f718838d8f73f": { + "balance": "0", + "nonce": 1, + "root": "0x72c89221daedccdd3fbba66c1b081b3634ce89d5a069be97ff7832778f7b023a", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000003c": "3c", + "0x000000000000000000000000000000000000000000000000000000000000003d": "3d", + "0x000000000000000000000000000000000000000000000000000000000000003e": "3e" + }, + "key": "0x37310559ceaade42e45b3e3f05925aadca9e60aeeb9dd60d824875d9e9e71e26" + }, + "0xf0a279d2276de583ebcd7f69a6532f13349ad656": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x11eb0304c1baa92e67239f6947cb93e485a7db05e2b477e1167a8960458fa8cc" + }, + "0xf0a5f15ef71424b5d543394ec46c46bfd2817747": { + "balance": "0", + "nonce": 1, + "root": "0xbefe55b606a865c3898ec2093bd160b37c3976011516f43736cac2a9a7ecd4ca", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000000e0": "e0", + "0x00000000000000000000000000000000000000000000000000000000000000e1": "e1", + "0x00000000000000000000000000000000000000000000000000000000000000e2": "e2" + }, + "key": "0xdbea1fd70fe1c93dfef412ce5d8565d87d6843aac044d3a015fc3db4d20a351b" + }, + "0xf14d90dc2815f1fc7536fc66ca8f73562feeedd1": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xabdc44a9bc7ccf1ce76b942d25cd9d731425cd04989597d7a2e36423e2dac7ee" + }, + "0xf16ba6fa61da3398815be2a6c0f7cb1351982dbc": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x728325587fa336e318b54298e1701d246c4f90d6094eb95635d8a47f080f4603" + }, + "0xf1fc98c0060f0d12ae263986be65770e2ae42eae": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xca7ad42d3c4fe14ddb81bf27d4679725a1f6c3f23b688681bb6f24262d63212f" + }, + "0xf4f97c88c409dcf3789b5b518da3f7d266c48806": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x84c7ee50e102d0abf5750e781c1635d60346f20ab0d5e5f9830db1a592c658ff" + }, + "0xf5347043ae5fca9412ca2c72aee17a1d3ba37691": { + "balance": "0", + "nonce": 1, + "root": "0xf390264acaf1433c0ea670b2c094a30076641469524ae24f5fddc44e99c5b032", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000004f": "4f", + "0x0000000000000000000000000000000000000000000000000000000000000050": "50", + "0x0000000000000000000000000000000000000000000000000000000000000051": "51" + }, + "key": "0xa5541b637a896d30688a80b7affda987d9597aac7ccd9799c15999a1d7d094e2" + }, + "0xf57fd44ccea35d9c530ef23f3e55de2f6e5415bf": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x6d4162ce16817e46fa2ddc5e70cee790b80abc3d6f7778cfbaed327c5d2af36c" + }, + "0xf6152f2ad8a93dc0f8f825f2a8d162d6da46e81f": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x7e839d9fd8a767e90a8b2f48a571f111dd2451bc5910cf2bf3ae79963e47e34d" + }, + "0xf61ac2a10b7981a12822e3e48671ebd969bce9c2": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xbfe5dee42bddd2860a8ebbcdd09f9c52a588ba38659cf5e74b07d20f396e04d4" + }, + "0xf7eaadcf76ffcf006a86deb2f17d0b8fe0b211a8": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x1dff76635b74ddba16bba3054cc568eed2571ea6becaabd0592b980463f157e2" + }, + "0xf83af0ceb5f72a5725ffb7e5a6963647be7d8847": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x662d147a16d7c23a2ba6d3940133e65044a90985e26207501bfca9ae47a2468c" + }, + "0xf8d20e598df20877e4d826246fc31ffb4615cbc0": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xa248850a2e0d6fe62259d33fc498203389fa754c3bd098163e86946888e455bd" + }, + "0xf91193b7442e274125c63003ee53f4ce5836f424": { + "balance": "0", + "nonce": 1, + "root": "0xb25f9e4f6f913a4a1e8debf7d4752bfa521d147bb67c69d5855301e76dd80633", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001d5": "01d5", + "0x00000000000000000000000000000000000000000000000000000000000001d6": "01d6", + "0x00000000000000000000000000000000000000000000000000000000000001d7": "01d7" + }, + "key": "0xbfe731f071443795cef55325f32e6e03c8c0d0398671548dfd5bc96b5a6555c0" + }, + "0xf997ed224012b1323eb2a6a0c0044a956c6b8070": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xbcebc35bfc663ecd6d4410ee2363e5b7741ee953c7d3359aa585095e503d20c8" + }, + "0xfb7b49bc3178263f3a205349c0e8060f44584500": { + "balance": "0", + "nonce": 1, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xa03fe040e4264070290e95ffe06bf9da0006556091f17c5df5abaa041de0c2f7" + }, + "0xfb95aa98d6e6c5827a57ec17b978d647fcc01d98": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xf63360f8bb23f88b0a564f9e07631c38c73b4074ba4192d6131336ef02ee9cf2" + }, + "0xfcc8d4cd5a42cca8ac9f9437a6d0ac09f1d08785": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xd3443fa37ee617edc09a9c930be4873c21af2c47c99601d5e20483ce6d01960a" + }, + "0xfd5e6e8c850fafa2ba2293c851479308c0f0c9e7": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0x1c248f110218eaae2feb51bc82e9dcc2844bf93b88172c52afcb86383d262323" + }, + "0xfde502858306c235a3121e42326b53228b7ef469": { + "balance": "1", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe3d7213321be060ae2e1ff70871131ab3e4c9f4214a17fe9441453745c29365b" + }, + "0xfe1dcd3abfcd6b1655a026e60a05d03a7f71e4b6": { + "balance": "100000000000", + "nonce": 0, + "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "key": "0xe31747e6542bf4351087edfbeb23e225e4217b5fa25d385f33cd024df0c9ae12" + }, + "0xfe96089d9b79f2d10f3e8b0fb9629aeb6cc7cde6": { + "balance": "0", + "nonce": 1, + "root": "0xcf2123d110997f426821d3e541334e43fdd6b5286c3c33252c24b5f8aafc7aa2", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000001d0": "01d0", + "0x00000000000000000000000000000000000000000000000000000000000001d1": "01d1", + "0x00000000000000000000000000000000000000000000000000000000000001d2": "01d2" + }, + "key": "0xbf632670b6fa18a8ad174a36180202bfef9a92c2eeda55412460491ae0f6a969" + } + } +} \ No newline at end of file diff --git a/cmd/devp2p/internal/ethtest/testdata/newpayload.json b/cmd/devp2p/internal/ethtest/testdata/newpayload.json new file mode 100644 index 000000000..7f8c99afa --- /dev/null +++ b/cmd/devp2p/internal/ethtest/testdata/newpayload.json @@ -0,0 +1,13268 @@ +[ + { + "jsonrpc": "2.0", + "id": "np72", + "method": "engine_newPayloadV1", + "params": [ + { + "parentHash": "0x9e8a444b740df016941ecc815fe9eebeaa04a047db6569855573a52a8cb78cdd", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x74035b613e4ea1072fd029f35d0fa5b26fbfaa54cabebcec88b9ee07cca321ae", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x48", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x2d0", + "extraData": "0x", + "baseFeePerGas": "0x568d2f9", + "blockHash": "0xf0a50b18d597552b6ad8a711f4ac1f7ab225d59daa74137f689256a16a0ff809", + "transactions": [ + "0xf86a39840568d2fa8252089444bd7ae60f478fae1061e11a7739f4b94d1daf9101808718e5bb3abd10a0a050fc2310f542cf90b3376f54d296158f5be7ad852db200f9956e3210c0f8125ca04f880fe872915a7843c37147a69758eff0a93cfaf8ce54f36502190e54b6e5c7" + ], + "withdrawals": null, + "blobGasUsed": null, + "excessBlobGas": null + } + ] + }, + { + "jsonrpc": "2.0", + "id": "np73", + "method": "engine_newPayloadV1", + "params": [ + { + "parentHash": "0xf0a50b18d597552b6ad8a711f4ac1f7ab225d59daa74137f689256a16a0ff809", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x18b68edcdfc835d5db51310e7960eaf0c0afcc5a6611282d2085f3282b2f9e3f", + "receiptsRoot": "0xabc882591cb5b81b276a4e5cd873e1be7e1b4a69f630d2127f06d63c8db5acb2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x49", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146e8", + "timestamp": "0x2da", + "extraData": "0x", + "baseFeePerGas": "0x4bbd14a", + "blockHash": "0x662ab680f6b14375e7642874a16a514d1ecffc9921a9d8e143b5ade129ad554b", + "transactions": [ + "0xf8853a8404bbd14b830146e88080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a027748abc264040530bca00d1cc86b199586c1fe26955cd5e250b97e2b9ca3128a050a822d9df3b63e6911766d4ae8c722f5afee7a6c06a7b5eb73772a5b137ca36" + ], + "withdrawals": null, + "blobGasUsed": null, + "excessBlobGas": null + } + ] + }, + { + "jsonrpc": "2.0", + "id": "np74", + "method": "engine_newPayloadV1", + "params": [ + { + "parentHash": "0x662ab680f6b14375e7642874a16a514d1ecffc9921a9d8e143b5ade129ad554b", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6fb7295e0a62bff03ddeba56ba643cd817fab6bc8df11309f8e8a3dbcf7d502e", + "receiptsRoot": "0x7b9d8080a095524251324dc00e77d3ecf4c249c48eebed2e4a5acedc678c70b4", + "logsBloom": "0x000800000000000000000000000000000900000000000000000000000000c0080000000000000010000000020000000000000004100000000480008020100000000000000000000000000000001000200000000000000010000010000000000000000000000000000000000000000000000000000000000200000000000000800001000000000000000000000000000000000004000000000000000800000000008000000000000001000000000002000000000000000000000000000000080000000000000000200404000000000000000000000000000000000000000000000000080100000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x4a", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc61", + "timestamp": "0x2e4", + "extraData": "0x", + "baseFeePerGas": "0x424ad37", + "blockHash": "0x9981d4e953d402b0b1554ef62ebbeb7760790a5e53191c9753329b6a3eab3d13", + "transactions": [ + "0xf87c3b840424ad3883011f548080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a050e3677064fe82b08a8fae8cea250fbaf00dbca1b6921cffd311ca17c7979865a051e738138eab4b31f1ba163b8ed2cfd778af98eff583cd5a26fcd9bd673fe027" + ], + "withdrawals": null, + "blobGasUsed": null, + "excessBlobGas": null + } + ] + }, + { + "jsonrpc": "2.0", + "id": "np75", + "method": "engine_newPayloadV1", + "params": [ + { + "parentHash": "0x9981d4e953d402b0b1554ef62ebbeb7760790a5e53191c9753329b6a3eab3d13", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x65038690e44bf1ee49d47beb6efc7cc84d7f01d2ba645768e3a584a50979b36d", + "receiptsRoot": "0xf5419129ce2f36d1b2206d4723f3e499691ad9aee741223426cda1b22e601a19", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x4b", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36c", + "timestamp": "0x2ee", + "extraData": "0x", + "baseFeePerGas": "0x3a051bc", + "blockHash": "0xc5e8361f3f3ba7bfbed66940c015f351d498ed34d48f8de6e020ffffbcbbec61", + "transactions": [ + "0xf8673c8403a051bd83020888808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0dd01417c1ac62f9e593b07848f93c1f5ab729e73a493e22141f6e1c6e8a4f94fa00b9e979c6bae8ab4a90b7b2ba61d590d800e5411bc12be320efc3fb7310506e3" + ], + "withdrawals": null, + "blobGasUsed": null, + "excessBlobGas": null + } + ] + }, + { + "jsonrpc": "2.0", + "id": "np76", + "method": "engine_newPayloadV1", + "params": [ + { + "parentHash": "0xc5e8361f3f3ba7bfbed66940c015f351d498ed34d48f8de6e020ffffbcbbec61", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3b8d5706f2e3d66bb968de876e2683d75dce76d04118bc0184d6af44fb10196f", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x4c", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x2f8", + "extraData": "0x", + "baseFeePerGas": "0x32ca5cf", + "blockHash": "0xcb51fdebc936f135546a0ff78a7ce246aee0a5c73b41b7accdc547825bb97766", + "transactions": [ + "0x02f86d870c72dd9d5e883e3d0184032ca5d08252089472dfcfb0c470ac255cde83fb8fe38de8a128188e0180c080a0116da1fc19daf120ddc2cc3fa0a834f9c176028e65d5f5d4c86834a0b4fe2a36a017001c3ad456650dd1b28c12f41c94f50b4571da5b62e9f2a95dff4c8c3f61fd" + ], + "withdrawals": null, + "blobGasUsed": null, + "excessBlobGas": null + } + ] + }, + { + "jsonrpc": "2.0", + "id": "np77", + "method": "engine_newPayloadV1", + "params": [ + { + "parentHash": "0xcb51fdebc936f135546a0ff78a7ce246aee0a5c73b41b7accdc547825bb97766", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3b8d17721b733ce2b6e7607a69fb6bf678dbabcb708f64cb5d211915b3238090", + "receiptsRoot": "0xabc882591cb5b81b276a4e5cd873e1be7e1b4a69f630d2127f06d63c8db5acb2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x4d", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146e8", + "timestamp": "0x302", + "extraData": "0x", + "baseFeePerGas": "0x2c71f92", + "blockHash": "0x49b74bc0dea88f3125f95f1eb9c0503e90440f7f23b362c4f66269a14a2dcc3e", + "transactions": [ + "0xf8853e8402c71f93830146e88080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a00cb7fb1bba811ea1948e035550c66840f0491d29d0ae9a6e4726e77a57ca8058a041523fc7133a6473784720a68d7f7f1d54d8a5a1f868640783a0284fb22f4309" + ], + "withdrawals": null, + "blobGasUsed": null, + "excessBlobGas": null + } + ] + }, + { + "jsonrpc": "2.0", + "id": "np78", + "method": "engine_newPayloadV2", + "params": [ + { + "parentHash": "0x49b74bc0dea88f3125f95f1eb9c0503e90440f7f23b362c4f66269a14a2dcc3e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf21b9b380d6c5833270617a17ea187e1f85a6556f1c1dfaf6bcb0700c88abe24", + "receiptsRoot": "0xb08f0ccb7116304320035e77c514c9234f2d5a916d68de82ba20f0a24ab6d9e4", + "logsBloom": "0x00000000000000400000000000200000000000000000000000000000000000000000200010000000000000000000000000000040000400000010000000000020000000000000000000000000000000000000000000000000000000900000000000800000000800000010000008000000000000000000000102000000000000100000080000000100000000000000000000000000000008000000000000008000800800000000000000000000400000000008200000000200200000000000000000000000000000200000000000000000000000000000000000000000000000000000000011000000000000800000000000000000000000000000000000000008", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x4e", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x30c", + "extraData": "0x", + "baseFeePerGas": "0x26e6e24", + "blockHash": "0x157062b78da942ff0b0e892142e8230ffdf9330f60c5f82c2d66291a6472fd7c", + "transactions": [ + "0xf87c3f84026e6e2583011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0fa1ba7a3639ec15944466d72a2e972d5eda143fc54f07aa47ecd56769ba5fbf8a041018f9af7a55685cbfa25d35f353e4bccef32a5e0bcdb373191d34cfed9a8db" + ], + "withdrawals": [], + "blobGasUsed": null, + "excessBlobGas": null + } + ] + }, + { + "jsonrpc": "2.0", + "id": "np79", + "method": "engine_newPayloadV2", + "params": [ + { + "parentHash": "0x157062b78da942ff0b0e892142e8230ffdf9330f60c5f82c2d66291a6472fd7c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8bb2c279cf46bd7eb856cc00fdce9bb396b21f65da47fdf0f13b41e0c0e0aa7f", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x4f", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x316", + "extraData": "0x", + "baseFeePerGas": "0x220c283", + "blockHash": "0x39a05d1b50f4334060d2b37724df159784c5cbfe1a679f3b99d9f725aed4d619", + "transactions": [ + "0xf86740840220c2848302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0bcd36ef6498fd3ce093febc53b3e35004a9d9200816306515f5ffad98140426fa00656b7e75310845c1d2e47495ed7765d687f0a943a604644d9cf7b97b01f300f" + ], + "withdrawals": [], + "blobGasUsed": null, + "excessBlobGas": null + } + ] + }, + { + "jsonrpc": "2.0", + "id": "np80", + "method": "engine_newPayloadV2", + "params": [ + { + "parentHash": "0x39a05d1b50f4334060d2b37724df159784c5cbfe1a679f3b99d9f725aed4d619", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x62b57c9d164c28bc924ec89b1fe49adc736ee45e171f759f697899a766e3f7a4", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x50", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x320", + "extraData": "0x", + "baseFeePerGas": "0x1dce188", + "blockHash": "0xa7806a3f4d0f3d523bf65b89164372b524c897688d22d2ef2e218f7abb9cbddb", + "transactions": [ + "0xf869418401dce189825208945c62e091b8c0565f1bafad0dad5934276143ae2c01808718e5bb3abd10a0a0b82a5be85322581d1e611c5871123983563adb99e97980574d63257ab98807d59fdd49901bf0b0077d71c9922c4bd8449a78e2918c6d183a6653be9aaa334148" + ], + "withdrawals": [], + "blobGasUsed": null, + "excessBlobGas": null + } + ] + }, + { + "jsonrpc": "2.0", + "id": "np81", + "method": "engine_newPayloadV2", + "params": [ + { + "parentHash": "0xa7806a3f4d0f3d523bf65b89164372b524c897688d22d2ef2e218f7abb9cbddb", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x1820989c0844509c8b60af1baa9030bdcc357bc9462b8612493af9d17c76eb3d", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x51", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x32a", + "extraData": "0x", + "baseFeePerGas": "0x1a14dd8", + "blockHash": "0x7ec45b0f5667acb560d6e0fee704bb74f7738deb2711e5f380e4a9b2528d29c1", + "transactions": [], + "withdrawals": [ + { + "index": "0x0", + "validatorIndex": "0x5", + "address": "0x4ae81572f06e1b88fd5ced7a1a000945432e83e1", + "amount": "0x64" + } + ], + "blobGasUsed": null, + "excessBlobGas": null + } + ] + }, + { + "jsonrpc": "2.0", + "id": "np82", + "method": "engine_newPayloadV2", + "params": [ + { + "parentHash": "0x7ec45b0f5667acb560d6e0fee704bb74f7738deb2711e5f380e4a9b2528d29c1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8145365a52eb3a4b608966d28a8ed05598c13af426c7ab24f28f2bdc7a00b12b", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x52", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x334", + "extraData": "0x", + "baseFeePerGas": "0x16d241d", + "blockHash": "0x8dbcafaa0e32cd9f71f1d5b0f22f549aee0fddce3bda577ac200e24c7dc8ba62", + "transactions": [ + "0xf8854284016d241e830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a061c5ecaf5f73e89370f5b35c31bce60d04c7417cc70cc897beae6429cb6d3880a02271644378271ec296459da5181507d52bdbd4489600690c32998cdb4b032042" + ], + "withdrawals": [], + "blobGasUsed": null, + "excessBlobGas": null + } + ] + }, + { + "jsonrpc": "2.0", + "id": "np83", + "method": "engine_newPayloadV2", + "params": [ + { + "parentHash": "0x8dbcafaa0e32cd9f71f1d5b0f22f549aee0fddce3bda577ac200e24c7dc8ba62", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x07cc0bca2e8f3b243635dc6f988372dd2427b6090f1035d06f2eff2e99315170", + "receiptsRoot": "0xace7ae7e3c226cecca4b33082b19cd1023960138a576ef77fddadcc223b4250a", + "logsBloom": "0x40000010010000000000000100000c00000001000000000000000000000000000000000200000000000042000000000000001000000000000000000000000000000000000000000000000820040000800000000000004000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000001000800000001000000000000000000000000000000000000000004000004000000000000000410000000000000000000000040000000000000000004000000000000000000000000000400001000000000000000000400000000000000000000200080000000000000000000000010000000040000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x53", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x33e", + "extraData": "0x", + "baseFeePerGas": "0x13f998a", + "blockHash": "0x686c223412a42d17a7fe0fe2a8b15d6181afa366cccd26a0b35a7581c0686721", + "transactions": [ + "0xf87c4384013f998b83011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a01001f6f02c9dac33915eb5d0fe81d88599a29341d84ee6f46b1ef05d270a0c1fa05ea1dbc664d9f4a83b4743bc40579e6b727ff8b5e78c4249bd59aa47c33d770f" + ], + "withdrawals": [], + "blobGasUsed": null, + "excessBlobGas": null + } + ] + }, + { + "jsonrpc": "2.0", + "id": "np84", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x686c223412a42d17a7fe0fe2a8b15d6181afa366cccd26a0b35a7581c0686721", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe1a71059650ccefaf7d0a407c43a87ccc9fe63a6369a46509074658f714c54ad", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x54", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x348", + "extraData": "0x", + "baseFeePerGas": "0x117b7e1", + "blockHash": "0x8a76d39e76bdf6ccf937b5253ae5c1db1bdc80ca64a71edccd41ba0c35b17b84", + "transactions": [ + "0xf86744840117b7e28302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0b4a7e6c791f457a428f870b8df8ee0148acac74050aeea658c3dad552a7e8140a0793951ba22a6f628dd86ec8964b09c74e0f77306a28dd276dfe42f40ee76c73c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x83472eda6eb475906aeeb7f09e757ba9f6663b9f6a5bf8611d6306f677f67ebd" + ] + }, + { + "jsonrpc": "2.0", + "id": "np85", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8a76d39e76bdf6ccf937b5253ae5c1db1bdc80ca64a71edccd41ba0c35b17b84", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x198575d6df4370febe3a96865e4a2280a5caa2f7bd55058b27ea5f3082db8d99", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x55", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x352", + "extraData": "0x", + "baseFeePerGas": "0xf4dd4f", + "blockHash": "0xc0d03736d9e3c2d4e14115f9702497daf53b39875122e51932f4b9b752ba7059", + "transactions": [ + "0x02f86c870c72dd9d5e883e450183f4dd5082520894a25513c7e0f6eaa80a3337ee18081b9e2ed09e000180c080a0e8ac7cb5028b3e20e8fc1ec90520dab2be89c8f50f4a14e315f6aa2229d33ce8a07c2504ac2e5b2fe4d430db81a923f6cc2d73b8fd71281d9f4e75ee9fc18759b9" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x2c809fbc7e3991c8ab560d1431fa8b6f25be4ab50977f0294dfeca9677866b6e" + ] + }, + { + "jsonrpc": "2.0", + "id": "np86", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc0d03736d9e3c2d4e14115f9702497daf53b39875122e51932f4b9b752ba7059", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x90c402a8569aae0c095540a9762aefac4f43df4e97fc7a24df1d4051c555bc2c", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x56", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x35c", + "extraData": "0x", + "baseFeePerGas": "0xd64603", + "blockHash": "0xa7323a02aa9acf63f26368292292d4bcb9dc7ef33296bbd98f423b24db3408bd", + "transactions": [], + "withdrawals": [ + { + "index": "0x1", + "validatorIndex": "0x5", + "address": "0xde5a6f78116eca62d7fc5ce159d23ae6b889b365", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x756e335a8778f6aadb2cc18c5bc68892da05a4d8b458eee5ce3335a024000c67" + ] + }, + { + "jsonrpc": "2.0", + "id": "np87", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa7323a02aa9acf63f26368292292d4bcb9dc7ef33296bbd98f423b24db3408bd", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x7e1765cf5abdf835814ee20c9e401b0e99e2b31f2ad8ea14c62ef732c6e63d2d", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x57", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x366", + "extraData": "0x", + "baseFeePerGas": "0xbb7d43", + "blockHash": "0xbbd89c9c2805888d9d1397d066495db1ce1c570e23b5b6f853dc0ff698575a04", + "transactions": [ + "0xf8844683bb7d44830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a052c928a2062b214d44b9a641faf87e439fbc5a07f571021f0f3c8fd2a2087a57a0650c77ab1cd522a7d3a435058f53636b6ae86d19fd4f691bf61c13fd8b7de69a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4b118bd31ed2c4eeb81dc9e3919e9989994333fe36f147c2930f12c53f0d3c78" + ] + }, + { + "jsonrpc": "2.0", + "id": "np88", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xbbd89c9c2805888d9d1397d066495db1ce1c570e23b5b6f853dc0ff698575a04", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3ec8183c28814317cb7a7b86633041db40de94b45a47dab5614b21087c308648", + "receiptsRoot": "0xe2e7a47b1c0009f35c3a46c96e604a459822fe9f02929afa823f2c514f1fbd39", + "logsBloom": "0x00000000000000000000000000000002000000000000000000000000000000000000000800000000000000000000000200000000008000000000000000000000000000000800000000000000800000000000000000000002000000000100000000000000000000000000000000000000001000000000400000000000000000000000000000000001000000000000000000000000000000000000000000000020000000000000400000000000000100000000000100000000000000000000100200000000000000000000000000000010400000000000000050080004000000400000000010000000800030001000000000000000004000000000000000000a00", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x58", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x370", + "extraData": "0x", + "baseFeePerGas": "0xa41aed", + "blockHash": "0xe67371f91330dd937081250eeda098394453c2ced0b6ffd31a67f8d95261d849", + "transactions": [ + "0xf87b4783a41aee83011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa09799e22509fcf203235570e7ba0df80bad6124b89b812146b50bca27f03161a9a0118a4f264815d7cf1a069009bff736f04369e19e364bd1a409a4c4865ec7d81f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd0122166752d729620d41114ff5a94d36e5d3e01b449c23844900c023d1650a5" + ] + }, + { + "jsonrpc": "2.0", + "id": "np89", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe67371f91330dd937081250eeda098394453c2ced0b6ffd31a67f8d95261d849", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x07118ca8999c49a924f92b54d21cecad7cbcc27401d16181bbcdee05b613399c", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x59", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x37a", + "extraData": "0x", + "baseFeePerGas": "0x8fa090", + "blockHash": "0x395eda9767326b57bbab88abee96eea91286c412a7297bedc3f1956f56db8b18", + "transactions": [ + "0xf86648838fa0918302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0fd5a86a96cbf94d2bba5c7fb6efd2bf501dd30c8b37e896ae360b40ab693272aa0331e570a5b3ce2cef67731c331bba3e6de2ede8145dd0719ce6dfcca587c64ba" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x60c606c4c44709ac87b367f42d2453744639fc5bee099a11f170de98408c8089" + ] + }, + { + "jsonrpc": "2.0", + "id": "np90", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x395eda9767326b57bbab88abee96eea91286c412a7297bedc3f1956f56db8b18", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0d9d080dde44cc511dc9dc457b9839409e1b3a186e6b9a5ae642b5354acc6cc4", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x5a", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x384", + "extraData": "0x", + "baseFeePerGas": "0x7dbb15", + "blockHash": "0x919c92e04181d139a4860cce64252ab9c14a5be9fa6adfc76b4b27f804fce2b9", + "transactions": [ + "0xf86949837dbb1682520894bbeebd879e1dff6918546dc0c179fdde505f2a2101808718e5bb3abd10a0a002f0119acaae03520f87748a1a855d0ef7ac4d5d1961d8f72f42734b5316a849a0182ad3a9efddba6be75007e91afe800869a18a36a11feee4743dde2ab6cc54d9" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x6ee04e1c27edad89a8e5a2253e4d9cca06e4f57d063ed4fe7cc1c478bb57eeca" + ] + }, + { + "jsonrpc": "2.0", + "id": "np91", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x919c92e04181d139a4860cce64252ab9c14a5be9fa6adfc76b4b27f804fce2b9", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa5aea2e2c617a5a3a341e01c72fbf960e809dd589b4a988a04d50f6fb666b6c8", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x5b", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x38e", + "extraData": "0x", + "baseFeePerGas": "0x6e05f1", + "blockHash": "0x17a574ee7489840acc4a8aecd1d7b540ba9b033b7236c13d0b0a5403ff07f7f3", + "transactions": [], + "withdrawals": [ + { + "index": "0x2", + "validatorIndex": "0x5", + "address": "0x245843abef9e72e7efac30138a994bf6301e7e1d", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x36616354a17658eb3c3e8e5adda6253660e3744cb8b213006f04302b723749a8" + ] + }, + { + "jsonrpc": "2.0", + "id": "np92", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x17a574ee7489840acc4a8aecd1d7b540ba9b033b7236c13d0b0a5403ff07f7f3", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd1e927e1a7106591aa46d3e327e9e7d493248786b4c6284bd138d329c6cb1fbb", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x5c", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x398", + "extraData": "0x", + "baseFeePerGas": "0x604533", + "blockHash": "0x7848fe02daea45d47101fbe84b6d94576452c2d0cb9261bc346343b5b2df844f", + "transactions": [ + "0xf8844a83604534830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0667955bfddc6500ad6a0a298d08a0fdeb453d483be41f7496f557039c99d5b8ea06ad5f6871f3d78ea543484d51590454f8a65b5b1b89f58992ff94a02a30c0c93" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc13802d4378dcb9c616f0c60ea0edd90e6c2dacf61f39ca06add0eaa67473b94" + ] + }, + { + "jsonrpc": "2.0", + "id": "np93", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x7848fe02daea45d47101fbe84b6d94576452c2d0cb9261bc346343b5b2df844f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8d4e68f0a1ad7578b1627d665263c04856efa4eb4938014a8c794547d597f89b", + "receiptsRoot": "0xa37a62134a71ef21b16f2eee431b806a4d13c0a80a11ddeb5cbb18e3707aecdf", + "logsBloom": "0x00000000000000000000000002000000000021000000000000000000240000000000000000000000000004000000010000000000000000000000000000000000000008000000000000000000000000000020000000000000000400000400000000000400000000000000000000000000000000000080000004000000000000000000000000000800000000000000000000000000000000000000000000002000000080000002010000420000000000000000000000000040402002000200000000000000000000000000008000000000000000000000000100000000000000000000000000000000000084000000000080000000000000000000040000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x5d", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x3a2", + "extraData": "0x", + "baseFeePerGas": "0x544364", + "blockHash": "0x6c5d29870c54d8c4e318523a7ea7fb9756b6633bbdf70dcb1e4659ff3564615b", + "transactions": [ + "0xf87b4b8354436583011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a09662003f67b0c146ecaa0c074b010d1f27d0803dc1809fd4f6ea80a5f09c34aea0100a5c0fbfdbee733f1baecb893a33ce2d42316303a5ddf1515645dfbb40d103" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x8b345497936c51d077f414534be3f70472e4df101dee8820eaaff91a6624557b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np94", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x6c5d29870c54d8c4e318523a7ea7fb9756b6633bbdf70dcb1e4659ff3564615b", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xbd07ab096fc1b3e50229bcff0fc5fca9e9f7d368e77fe43a71e468b7b0adb133", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x5e", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x3ac", + "extraData": "0x", + "baseFeePerGas": "0x49bf97", + "blockHash": "0xe7b8c1ca432a521b1e7f0cf3cb63be25da67e3364cc0b02b0a28e06ba8deed80", + "transactions": [ + "0xf8664c8349bf988302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa03b3113a7b1919311fbc03ee25c4829b60f07341c72107de061da06eef7ec0856a01bc4eeb29301e1610984ee042f8236863ad78402d3d55c69a6922d67238dde75" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe958485d4b3e47b38014cc4eaeb75f13228072e7b362a56fc3ffe10155882629" + ] + }, + { + "jsonrpc": "2.0", + "id": "np95", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe7b8c1ca432a521b1e7f0cf3cb63be25da67e3364cc0b02b0a28e06ba8deed80", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4a154c665e5b68adadf9455bd905da607f0279b5d2b4bfb0c1a3db5b6a908d4d", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x5f", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x3b6", + "extraData": "0x", + "baseFeePerGas": "0x408f22", + "blockHash": "0xaa62b2faefe50fe1562f3fb5bf96a765ca7c92164465e226fc9a8ba75cabc387", + "transactions": [ + "0x02f86c870c72dd9d5e883e4d0183408f2382520894d2e2adf7177b7a8afddbc12d1634cf23ea1a71020180c001a08556dcfea479b34675db3fe08e29486fe719c2b22f6b0c1741ecbbdce4575cc6a01cd48009ccafd6b9f1290bbe2ceea268f94101d1d322c787018423ebcbc87ab4" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x3346706b38a2331556153113383581bc6f66f209fdef502f9fc9b6daf6ea555e" + ] + }, + { + "jsonrpc": "2.0", + "id": "np96", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xaa62b2faefe50fe1562f3fb5bf96a765ca7c92164465e226fc9a8ba75cabc387", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3b2adb11488a7634a20bc6f81bcc0211993fe790f75eeb1f4889a98d1bdbcb37", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x60", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x3c0", + "extraData": "0x", + "baseFeePerGas": "0x387e65", + "blockHash": "0x6a6df67e09c4411bb89664cbc78f78237bb6a2fc299bc6a682cca406feb8dd4d", + "transactions": [], + "withdrawals": [ + { + "index": "0x3", + "validatorIndex": "0x5", + "address": "0x8d33f520a3c4cef80d2453aef81b612bfe1cb44c", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x346910f7e777c596be32f0dcf46ccfda2efe8d6c5d3abbfe0f76dba7437f5dad" + ] + }, + { + "jsonrpc": "2.0", + "id": "np97", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x6a6df67e09c4411bb89664cbc78f78237bb6a2fc299bc6a682cca406feb8dd4d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd67c810501ca4f4ee4262e86dcaf793ca75637249bf157dee4800274372f236f", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x61", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x3ca", + "extraData": "0x", + "baseFeePerGas": "0x316e99", + "blockHash": "0xfec8ebc1c3d312ec3537d860b406110aeac3980763165d0026ecab156a377bdf", + "transactions": [ + "0xf8844e83316e9a830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a036b2adb5bbd4d43198587067bf0b669e00862b0807adb947ee4c9869d79f9d8ca063e0b200645435853dceed29fd3b4c55d94b868a0aa6513ca6bd730705f2c9ef" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe62a7bd9263534b752176d1ff1d428fcc370a3b176c4a6312b6016c2d5f8d546" + ] + }, + { + "jsonrpc": "2.0", + "id": "np98", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xfec8ebc1c3d312ec3537d860b406110aeac3980763165d0026ecab156a377bdf", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xae82dda9df38bcc8d99e311b63ae055591953577b6b560840658eca24ecacee9", + "receiptsRoot": "0x675ab823f90b9bdd3d04afb108bc1a1dcd77654a0de4c8a539e355b6d24f29f4", + "logsBloom": "0x10000000000000000010000000000020000000000008000000000000000000000000000000000000000000020000000000000000000000000000040000010000000000000000000000000000000000000000000000008000000000000000000000000080000110000000000800000002000000800040800000000040000000000000004000000000001000000000000000000000000000000000008000000000000000000000000000000020010080001000000000000000000000000004008000004000008000000000000000040000000400000000000001000000000000000000000008000000000000000000000200000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x62", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x3d4", + "extraData": "0x", + "baseFeePerGas": "0x2b4449", + "blockHash": "0x3124d842afa1334bb72f0a8f058d7d3ad489d6c6bd684f81d3ecdf71d287f517", + "transactions": [ + "0xf87b4f832b444a83011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0824522ae97a912dd75a883798f4f296d960f6a7be8510e2a4a121d85f496da16a008cade93390e31f7b0e6615b4defe3bd4225b7a4d97a7835c02ad0b4d004fb5b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xffe267d11268388fd0426a627dedddeb075d68327df9172c0445cd2979ec7e4d" + ] + }, + { + "jsonrpc": "2.0", + "id": "np99", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x3124d842afa1334bb72f0a8f058d7d3ad489d6c6bd684f81d3ecdf71d287f517", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x588419f24b32499745bbae81eb1a303d563c31b2743c8621d39b820c2affb3cb", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x63", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x3de", + "extraData": "0x", + "baseFeePerGas": "0x25de20", + "blockHash": "0x53d785a42c58a40edbc18e6bee93d4072a4281c744f697f9b5cae1d0b3bf2962", + "transactions": [ + "0xf866508325de218302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0744b7f5fb26cc6dd16b1849d0c04236e3b4e993f37e5b91de6e55f5f899450baa0456225c91372bddd4e3a1dde449e59ad62d63f0c850f9b869870ea2621494fd7" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x23cc648c9cd82c08214882b7e28e026d6eb56920f90f64731bb09b6acf515427" + ] + }, + { + "jsonrpc": "2.0", + "id": "np100", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x53d785a42c58a40edbc18e6bee93d4072a4281c744f697f9b5cae1d0b3bf2962", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xfee7a27147c7984caec35dc4cee4f3a38fee046e5d8f17ce7ec82b982decd9aa", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x64", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x3e8", + "extraData": "0x", + "baseFeePerGas": "0x212635", + "blockHash": "0x96d2a59527aa149efe64eef6b2fbf4722c9c833aba48e0c7cb0cb4033fa1af5e", + "transactions": [ + "0xf86951832126368252089418ac3e7343f016890c510e93f935261169d9e3f501808718e5bb3abd10a0a099aba91f70df4d53679a578ed17e955f944dc96c7c449506b577ac1288dac6d4a0582c7577f2343dd5a7c7892e723e98122227fca8486debd9a43cd86f65d4448a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x47c896f5986ec29f58ec60eec56ed176910779e9fc9cf45c3c090126aeb21acd" + ] + }, + { + "jsonrpc": "2.0", + "id": "np101", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x96d2a59527aa149efe64eef6b2fbf4722c9c833aba48e0c7cb0cb4033fa1af5e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb1600603ea31446c716fece48a379fb946eab40182133a8032914e868bb4929e", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x65", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x3f2", + "extraData": "0x", + "baseFeePerGas": "0x1d0206", + "blockHash": "0xf2750d7772a6dcdcad79562ddf2dee24c1c2b7862905024a8468adfb62f8ef14", + "transactions": [], + "withdrawals": [ + { + "index": "0x4", + "validatorIndex": "0x5", + "address": "0x3f79bb7b435b05321651daefd374cdc681dc06fa", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x6d19894928a3ab44077bb85dcb47e0865ce1c4c187bba26bad059aa774c03cfe" + ] + }, + { + "jsonrpc": "2.0", + "id": "np102", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf2750d7772a6dcdcad79562ddf2dee24c1c2b7862905024a8468adfb62f8ef14", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd3908889240ecc36175f7ac23e9596230ea200b98ee9c9ca078154288b69c637", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x66", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x3fc", + "extraData": "0x", + "baseFeePerGas": "0x1961c6", + "blockHash": "0x57054aa8d635c98b3b71d24e11e22e9235bc384995b7b7b4acd5ca271d0898b4", + "transactions": [ + "0xf88452831961c7830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0c43b4e8ddaecaadfc1fd4b35659ced2bbaa2ab24b1cff975685cd35f486a723fa056a91d2ff05b4eae02ee1d87442ec57759e66ec13bfd3ea2655cf4f04b6e863d" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xefc50f4fc1430b6d5d043065201692a4a02252fef0699394631f5213a5667547" + ] + }, + { + "jsonrpc": "2.0", + "id": "np103", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x57054aa8d635c98b3b71d24e11e22e9235bc384995b7b7b4acd5ca271d0898b4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd66957c43447a6edfb6b9bc9c4e985f28c24e6ce3253c68e5937c31c5d376f94", + "receiptsRoot": "0xd99d12e61c8e9be69f1eb49cea2f72664c7e569463415b064954bf5e0dbc6a01", + "logsBloom": "0x00000000000000000000100000000000200000000000000000200000000000000000000000040000000000200000000000000000000000000200000000000000000018000000000000000000010000000000000000000000000000000000100000000000000000000000000000000000000000000000000000800200000000021000000000002000000000002088400000000000000000000000000000000000000000000000000000000010000000000800000080000000000000000000000008000000000000000020000100001000000000080000002000400000000400000000000000002200000000000000000000000000000000000000000020000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x67", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x406", + "extraData": "0x", + "baseFeePerGas": "0x16375b", + "blockHash": "0xf4f1f726bcb9a3db29481be3a2e00c6ab4bf594ae85927414540ec9ede649d4d", + "transactions": [ + "0xf87b538316375c83011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0e59d36f30ed2dfc5eb71433457547f63bf4ad98e0a2181c4373a5e7ddf04d17ea06dce4f88f48f6fd93c2c834537a8baef27bb2965b9e2ce68dc437adb3d657d28" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x3cc9f65fc1f46927eb46fbf6d14bc94af078fe8ff982a984bdd117152cd1549f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np104", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf4f1f726bcb9a3db29481be3a2e00c6ab4bf594ae85927414540ec9ede649d4d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe06685d528d0c69051bcf8a6776d6c96c1f1c203da29851979c037be2faac486", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x68", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x410", + "extraData": "0x", + "baseFeePerGas": "0x1371a8", + "blockHash": "0xc8fe6583a2370fa9bda247532a8fb7845fceea9b54c9e81cda787947bb0ad41d", + "transactions": [ + "0xf86654831371a98302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0a427e65413948a8a1cf63c15214525d05bffca4667149c6a4019513defe57e6ba02819aa7d6a404a7f0194ef3ba7ec45b876f4226b278ebbcfa4012a90a1af3905" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x63eb547e9325bc34fbbbdfda327a71dc929fd8ab6509795e56479e95dbd40a80" + ] + }, + { + "jsonrpc": "2.0", + "id": "np105", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc8fe6583a2370fa9bda247532a8fb7845fceea9b54c9e81cda787947bb0ad41d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x32d5d07d12d91b8b4392872b740f46492fea678e9f5dc334c21101767bd54833", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x69", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x41a", + "extraData": "0x", + "baseFeePerGas": "0x11056d", + "blockHash": "0xb30b266de816c61ef16e4abfc94fbed8b4032710f4275407df2bf716a1f0bbd7", + "transactions": [ + "0x02f86c870c72dd9d5e883e55018311056e82520894de7d1b721a1e0632b7cf04edf5032c8ecffa9f9a0180c080a02a6c70afb68bff0d4e452f17042700e1ea43c10fc75e55d842344c1eb55e2e97a027c64f6f48cfa60dc47bfb2063f9f742a0a4f284d6b65cb394871caca2928cde" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x67317288cf707b0325748c7947e2dda5e8b41e45e62330d00d80e9be403e5c4c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np106", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb30b266de816c61ef16e4abfc94fbed8b4032710f4275407df2bf716a1f0bbd7", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf89d6d5f7a16d98062e1ef668ee9a1819b0634bd768ece2fc2b687f8968dc373", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x6a", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x424", + "extraData": "0x", + "baseFeePerGas": "0xee50e", + "blockHash": "0x35221530b572a05628d99d8ca9434287c581e30473f83d612cbbfb7f394c587b", + "transactions": [], + "withdrawals": [ + { + "index": "0x5", + "validatorIndex": "0x5", + "address": "0x189f40034be7a199f1fa9891668ee3ab6049f82d", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7fc37e0d22626f96f345b05516c8a3676b9e1de01d354e5eb9524f6776966885" + ] + }, + { + "jsonrpc": "2.0", + "id": "np107", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x35221530b572a05628d99d8ca9434287c581e30473f83d612cbbfb7f394c587b", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xaf4107a57da519d24d0c0e3ae6a5c81f3958ddc49e3f1c2792154b47d58d79a1", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x6b", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x42e", + "extraData": "0x", + "baseFeePerGas": "0xd086d", + "blockHash": "0xe3981baf40fc5dac54055fab95177a854a37ff2627208247697d5627b8ae3c35", + "transactions": [ + "0xf88456830d086e830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a04c088a3642c3cfad977a0927e6d694bd26be96246f127f03d37fe2b494b99da2a00ef5b6e7aca1ac95ef964978a7ec4bb66688fbb7abace43f90f0c344196379e5" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc8c5ffb6f192e9bda046ecd4ebb995af53c9dd6040f4ba8d8db9292c1310e43f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np108", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe3981baf40fc5dac54055fab95177a854a37ff2627208247697d5627b8ae3c35", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x9afc46d870489ac06cac1ea0b65c417d8e0086f0fb828dd92dca30da737c827b", + "receiptsRoot": "0x9b9c6d15a59d6b1c222cc63abe6aa28d734463877a3c34d4b3d9e80b768b77aa", + "logsBloom": "0x00000000000000000000000000000080000000000002000000000002000000000000004000000000000000000000010000000000000000000000000000000000000400000000000000100000000000000000200000000000000000000200000000000000000008000010000000000000000080000000000200000008000400000000000000000400000000000000000008000000001000000001000000000000000000000000008000000200000000000000000008400000000000000000000000001000000000000000000000001000010000000020000000040000000000000000000000000000000200080000000000000000000000040000000200000400", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x6c", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x438", + "extraData": "0x", + "baseFeePerGas": "0xb684d", + "blockHash": "0x54fcc3af800dbeae5c45ac8acba05313bd8d4c1bb06502702a14a225259367aa", + "transactions": [ + "0xf87b57830b684e83011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a06789a9252207970001fd703c22b2b7e5c0388bf018bc070a0469129f80cc5d63a048de0e437b02a8dd3a783892ad1691a1062cd73ddd35c481d9632f5158650317" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe40a9cfd9babe862d482ca0c07c0a4086641d16c066620cb048c6e673c5a4f91" + ] + }, + { + "jsonrpc": "2.0", + "id": "np109", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x54fcc3af800dbeae5c45ac8acba05313bd8d4c1bb06502702a14a225259367aa", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x02324f55d0548cb8743857fe938f91e6f15bfbe94654aadde56c59f83083980a", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x6d", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x442", + "extraData": "0x", + "baseFeePerGas": "0x9fbe4", + "blockHash": "0x62bb35defc0aac7bfbe789de02062f7ac622e9e354cfea5dceeccb792a61bae3", + "transactions": [ + "0xf866588309fbe58302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa07e3ef87807ccd797a0020fade1b7d65a7b190fbe40a6f8bdc35cd6a3a6fbed73a0283ad99e27eb389ca3b389bce3c29b3c711b74b6ecd05b290c7be33389830fab" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe82e7cff48aea45fb3f7b199b0b173497bf4c5ea66ff840e2ec618d7eb3d7470" + ] + }, + { + "jsonrpc": "2.0", + "id": "np110", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x62bb35defc0aac7bfbe789de02062f7ac622e9e354cfea5dceeccb792a61bae3", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x9932915761c4c894fc50819df189e875d3b025a7c045406fe415abe61d0e3086", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x6e", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x44c", + "extraData": "0x", + "baseFeePerGas": "0x8bd6c", + "blockHash": "0x2c4731fbb4f4adae94723c078548c510649e8973dfdb229fd6031b1b06eb75c0", + "transactions": [ + "0xf869598308bd6d825208941b16b1df538ba12dc3f97edbb85caa7050d46c1401808718e5bb3abd109fa0abbde17fddcc6495e854f86ae50052db04671ae3b6f502d45ba1363ae68ee62ca03aa20e294b56797a930e48eda73a4b036b0d9389893806f65af26b05f303100f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x84ceda57767ea709da7ab17897a70da1868c9670931da38f2438519a5249534d" + ] + }, + { + "jsonrpc": "2.0", + "id": "np111", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x2c4731fbb4f4adae94723c078548c510649e8973dfdb229fd6031b1b06eb75c0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2849b35fb3ec8146f637be768e3eaefda559928f8bb35753584d5b326a400ff5", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x6f", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x456", + "extraData": "0x", + "baseFeePerGas": "0x7a5e7", + "blockHash": "0x76b385d3f8a4b6e66ea8c246ed7c6275ad164d028ec5a986f9524bfe7437dcc7", + "transactions": [], + "withdrawals": [ + { + "index": "0x6", + "validatorIndex": "0x5", + "address": "0x65c74c15a686187bb6bbf9958f494fc6b8006803", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe9dcf640383969359c944cff24b75f71740627f596110ee8568fa09f9a06db1c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np112", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x76b385d3f8a4b6e66ea8c246ed7c6275ad164d028ec5a986f9524bfe7437dcc7", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2f24b6182543c677e7d1cab81bc020033c64e034571a20ecd632e252c8f202b3", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x70", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x460", + "extraData": "0x", + "baseFeePerGas": "0x6b12b", + "blockHash": "0x33385ec44cfd01ba27c927a3ebe607a27e55fd8e89965af09b991a7cdc127dbc", + "transactions": [ + "0xf8845a8306b12c830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0bf8a8863f63a16d43652b12e54dc61bd71c8ab86d88aebb756c6e420fca56a1aa01f62e0032c57f1629ee82b4fefb8d6c59a85c5c2889b1671ce0713581e773b6e" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x430ef678bb92f1af44dcd77af9c5b59fb87d0fc4a09901a54398ad5b7e19a8f4" + ] + }, + { + "jsonrpc": "2.0", + "id": "np113", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x33385ec44cfd01ba27c927a3ebe607a27e55fd8e89965af09b991a7cdc127dbc", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6d6c9c24ef7d93db6ba57324fb6f3604b09611301e12d250162c2b2b50871625", + "receiptsRoot": "0x257c29f688aaf63db2244378182225d104d84cfbd188c82b92323623d11574e9", + "logsBloom": "0x00000000000000000000080040000000000000000000000000008000000000000000000000000000000000000001000000000000000000000040000000040010000100000000000000400000000000000000020000000000000000800000000400000000000000000000000040000000000002000100400000000000000200000000000000000000000008000000010000000000000800000000000000000000000080000000000000000000000000000000080400000000000000000000400000000000010000000004000000000000000000000010020000000000000000000000000000000100000000040000000000000000000000200000001800000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x71", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x46a", + "extraData": "0x", + "baseFeePerGas": "0x5db80", + "blockHash": "0x66ad7aaacf3efede70dda0c82629af2046e67b96713cf3cf02a9a2613ca25b6f", + "transactions": [ + "0xf87b5b8305db8183011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0f893fcd21c2a882bc3968ea3c41dd37a8dbfbf07a34a8694a49fdd8081996e25a0502578b516e04b1939fdad45fd0688e636d57f59826a8e252b63f496b919d91c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf7af0b8b729cd17b7826259bc183b196dbd318bd7229d5e8085bf4849c0b12bf" + ] + }, + { + "jsonrpc": "2.0", + "id": "np114", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x66ad7aaacf3efede70dda0c82629af2046e67b96713cf3cf02a9a2613ca25b6f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x61c50266ae62e14edea48c9238f79f6369fd44e7f3d6519c7139aa1e87ee13ba", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x72", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x474", + "extraData": "0x", + "baseFeePerGas": "0x52063", + "blockHash": "0x00fd70a53be9c85c986d3dd87f46e079e4ce4a4a3dd95c1e497457c50bacbe2d", + "transactions": [ + "0xf8665c830520648302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0961de3e3657fdc49c722cc23de35eaf41de51c3aab3ca9a09b3d358fc19195aca060ee48b2fad3f3798111a93038fcb5c9c9791daf3c6acbaf70134fd182b5c663" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe134e19217f1b4c7e11f193561056303a1f67b69dac96ff79a6d0aafa994f7cb" + ] + }, + { + "jsonrpc": "2.0", + "id": "np115", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x00fd70a53be9c85c986d3dd87f46e079e4ce4a4a3dd95c1e497457c50bacbe2d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2bebf2f158ec1b8c7be21ef7c47c63fa5a3eb2292f409f365b40fa41bacb351e", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x73", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x47e", + "extraData": "0x", + "baseFeePerGas": "0x47cdc", + "blockHash": "0xbb9f244470573774df6fca785d3e11e6bd1b896213cacd43cdfcb131f806ca4c", + "transactions": [ + "0x02f86c870c72dd9d5e883e5d0183047cdd82520894043a718774c572bd8a25adbeb1bfcd5c0256ae110180c001a02ae4b3f6fa0e08145814f9e8da8305b9ca422e0da5508a7ae82e21f17d8c1196a077a6ea7a39bbfe93f6b43a48be83fa6f9363775a5bdb956c8d36d567216ea648" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9cc58ab1a8cb0e983550e61f754aea1dd4f58ac6482a816dc50658de750de613" + ] + }, + { + "jsonrpc": "2.0", + "id": "np116", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xbb9f244470573774df6fca785d3e11e6bd1b896213cacd43cdfcb131f806ca4c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3b359e20c5966cdcbb7b0298480621892d43f8efa58488b3548d84cf2ee514c1", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x74", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x488", + "extraData": "0x", + "baseFeePerGas": "0x3ed55", + "blockHash": "0x6d18b9bca4ee00bd7dc6ec4eb269cd4ba0aceb83a12520e5b825b827cb875fd9", + "transactions": [], + "withdrawals": [ + { + "index": "0x7", + "validatorIndex": "0x5", + "address": "0xe3b98a4da31a127d4bde6e43033f66ba274cab0e", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x79c2b067779a94fd3756070885fc8eab5e45033bde69ab17c0173d553df02978" + ] + }, + { + "jsonrpc": "2.0", + "id": "np117", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x6d18b9bca4ee00bd7dc6ec4eb269cd4ba0aceb83a12520e5b825b827cb875fd9", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x9776b87f7c94469bd3f80d7d9b639dace4981230bbb7c14df9326aafe66f3da4", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x75", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x492", + "extraData": "0x", + "baseFeePerGas": "0x36fab", + "blockHash": "0xcef84ea2c6fac4a2af80a594bbe5a40bf5f5285efe67fab7ceb858844c593ae9", + "transactions": [ + "0xf8845e83036fac830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a08315d9fb30662071b05a4e38240e4b85b8e240c0c3e190f27ada50678236c6e7a00ee07dc873780f17ac9d0c7b3d434f89be92231cfca042ca5f23d3f3d7346861" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd908ef75d05b895600d3f9938cb5259612c71223b68d30469ff657d61c6b1611" + ] + }, + { + "jsonrpc": "2.0", + "id": "np118", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xcef84ea2c6fac4a2af80a594bbe5a40bf5f5285efe67fab7ceb858844c593ae9", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xcd5cc668a3b28217e9fd05ddaea82d453a6a7394770a888b7d88013a4c9bcb22", + "receiptsRoot": "0xe35b2accd70b81901c8d0c931a12687e493a489ed7b82d78ade199815c466d5f", + "logsBloom": "0x0000000000000000000000000000000000000a00000000000000000000000000000000000000000000018000000000000000000000000000008000000000000048000000000000004000000000000000000008000000000000000000000020000000000000000002201010000000000000000400000000200000000000000000000000000000000000000000200000000000a200000001000000000000000000000000200000000000000000000400040000000000000000000000800000000000000000001800000000000802000000000000000000000080000000000000000000000000000000000000000000000000400000010800000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x76", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x49c", + "extraData": "0x", + "baseFeePerGas": "0x301f5", + "blockHash": "0x7b65cb3becfab6b30f0d643095b11c6853a33ca064a272f1326adb74e876e305", + "transactions": [ + "0xf87b5f830301f683011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0a3952a3372b48d4ef804b20a0ff5bbd5440156de3b71d37024356a3c1c5205d8a02ff03cae2dc449ca7ed7d25c91f99b17f0bafcdaf0ecc6e20bdeb80895c83e82" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe0d31906b7c46ac7f38478c0872d3c634f7113d54ef0b57ebfaf7f993959f5a3" + ] + }, + { + "jsonrpc": "2.0", + "id": "np119", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x7b65cb3becfab6b30f0d643095b11c6853a33ca064a272f1326adb74e876e305", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xdd592cc191ae4ba2be51a47d5056c2f0ba8799c74445ea3f294e0fc95a973f16", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x77", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x4a6", + "extraData": "0x", + "baseFeePerGas": "0x2a1e1", + "blockHash": "0x5d089bec3bbf3a0c83c7796afaa1ae4d21df034a3e33a6acb80e700e19bcaab0", + "transactions": [ + "0xf866608302a1e28302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0fd1714b8a15fa8a4e3ffe824632ec26f1daa6ce681e92845d1c1dfe60f032b4ea074bd5a60859bd735bbc70c9531a3ff48421f5c3b87e144406ee37ef78b8fda37" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x2318f5c5e6865200ad890e0a8db21c780a226bec0b2e29af1cb3a0d9b40196ae" + ] + }, + { + "jsonrpc": "2.0", + "id": "np120", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5d089bec3bbf3a0c83c7796afaa1ae4d21df034a3e33a6acb80e700e19bcaab0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4b5122bd4713cd58711f405c4bd9a0e924347ffce532693cce1dd51f36094676", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x78", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x4b0", + "extraData": "0x", + "baseFeePerGas": "0x24dea", + "blockHash": "0x02c9511703f78db34f67541d80704165d8a698726ef2cbcfbdc257bcf51594dd", + "transactions": [ + "0xf8696183024deb825208942d711642b726b04401627ca9fbac32f5c8530fb101808718e5bb3abd109fa0b4d70622cd8182ff705beb3dfa5ffa4b8c9e4b6ad5ad00a14613e28b076443f6a0676eb97410d3d70cfa78513f5ac156b9797abbecc7a8c69df814135947dc7d42" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x523997f8d8fed954658f547954fdeceab818b411862647f2b61a3619f6a4d4bc" + ] + }, + { + "jsonrpc": "2.0", + "id": "np121", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x02c9511703f78db34f67541d80704165d8a698726ef2cbcfbdc257bcf51594dd", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x18484e0a8e7bcccf7fbf4f6c7e1eff4b4a8c5b5e0ba7c2f2b27da315a0a06f97", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x79", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x4ba", + "extraData": "0x", + "baseFeePerGas": "0x20438", + "blockHash": "0x1edbbce4143b5cb30e707564f7ada75afe632e72b13d7de14224e3ed0044a403", + "transactions": [], + "withdrawals": [ + { + "index": "0x8", + "validatorIndex": "0x5", + "address": "0xa1fce4363854ff888cff4b8e7875d600c2682390", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xbe3396540ea36c6928cccdcfe6c669666edbbbcd4be5e703f59de0e3c2720da7" + ] + }, + { + "jsonrpc": "2.0", + "id": "np122", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1edbbce4143b5cb30e707564f7ada75afe632e72b13d7de14224e3ed0044a403", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6c921d64a95659dd6c62a919f2df9da2fda7cb8ec519aeb3b50ffb4e635dc561", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x7a", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x4c4", + "extraData": "0x", + "baseFeePerGas": "0x1c3b1", + "blockHash": "0x38e1ce2b062e29a9dbe5f29a5fc2b3c47bf2eed39c98d2b2689a2e01650e97ca", + "transactions": [ + "0xf884628301c3b2830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0f48d056f98b681d69f84fcde715c63b1669b11563164d7c17e03e5d3a4641a0fa010fce327ee99c5206995065cbb134d5458143a34cbc64b326476aeef47ae482a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x2d3fcfd65d0a6881a2e8684d03c2aa27aee6176514d9f6d8ebb3b766f85e1039" + ] + }, + { + "jsonrpc": "2.0", + "id": "np123", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x38e1ce2b062e29a9dbe5f29a5fc2b3c47bf2eed39c98d2b2689a2e01650e97ca", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x09df4733053f80da4904bce8d847883472e20bc3b1378eb1579e2e3df44d3948", + "receiptsRoot": "0x03ecb1b96e21ef88b48a9f1a85a170bdb0406e26918c7b14b9602e6f9a7e6937", + "logsBloom": "0x00000004000000000000002000000000000000004000000000000000000000000000400000400000000000000000010000080000000024404000000000000000000000000000000800000000020000000001000100000080000000000000000000000000000800000000000000000000000014000000000000000000000000001000000000000002000000100000000000000000000000000000040000000000000000000000000000040000020000000000000000200000000000000000000000000000000000000000000480010000000000000000000000040000000000000000000000000008000000000000000020000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x7b", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x4ce", + "extraData": "0x", + "baseFeePerGas": "0x18b5b", + "blockHash": "0xda82bddbddc44bf3ce23eb1f6f94ae987861720b6b180176080919015b1e4e90", + "transactions": [ + "0xf87b6383018b5c83011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0b223787310f8ba4f9271d98c8bfc4f7e926ced7773cab6b5c856fb4c43b6dad5a07d0edf043f5b767ffd513479a43cbdc3dcbd18f254e3eb11043d4d7aa4dd7445" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7ce0d5c253a7f910cca7416e949ac04fdaec20a518ab6fcbe4a63d8b439a5cfc" + ] + }, + { + "jsonrpc": "2.0", + "id": "np124", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xda82bddbddc44bf3ce23eb1f6f94ae987861720b6b180176080919015b1e4e90", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x15da947afcb1ba68f9fe2328e500881a302de649bd7d37f6e969bf7ec1aca37d", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x7c", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x4d8", + "extraData": "0x", + "baseFeePerGas": "0x15a06", + "blockHash": "0x8948407592d9c816f63c7194fa010c12115bee74e86c3b7d9e6ca30589830f21", + "transactions": [ + "0xf8666483015a078302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa037c41575c8abba9465870babe53a436d036974edf6a9de15d40fff1b4cca7552a07e815124c036ad7c603e7faa56d1d9e517d60cee33c1e47122a303e42d59b6fa" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4da13d835ea44926ee13f34ce8fcd4b9d3dc65be0a351115cf404234c7fbd256" + ] + }, + { + "jsonrpc": "2.0", + "id": "np125", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8948407592d9c816f63c7194fa010c12115bee74e86c3b7d9e6ca30589830f21", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa085ae940536d1e745cf78acd4001cb88fbc1e939151193c4e792cb659fe1aa0", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x7d", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x4e2", + "extraData": "0x", + "baseFeePerGas": "0x12ee9", + "blockHash": "0x5f66e4813f2b86dc401a90a05aafd8a2c38f6f1241e8a947bf54d679014a06a5", + "transactions": [ + "0x02f86c870c72dd9d5e883e650183012eea82520894d10b36aa74a59bcf4a88185837f658afaf3646ef0180c080a0882e961b849dc71672ce1014a55792da7aa8a43b07175d2b7452302c5b3cac2aa041356d00a158aa670c1a280b28b3bc8bb9d194a159c05812fa0a545f5b4bc57b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc5ee7483802009b45feabf4c5f701ec485f27bf7d2c4477b200ac53e210e9844" + ] + }, + { + "jsonrpc": "2.0", + "id": "np126", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5f66e4813f2b86dc401a90a05aafd8a2c38f6f1241e8a947bf54d679014a06a5", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xfb39354666f43e8f8b88f105333d6f595054b2e1b0019f89bf5dbddf7ec9a0ab", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x7e", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x4ec", + "extraData": "0x", + "baseFeePerGas": "0x10912", + "blockHash": "0x1b452f327c51d7a41d706af9b74ac14ff50b74dcef77fdb94333a8f5c86436a8", + "transactions": [], + "withdrawals": [ + { + "index": "0x9", + "validatorIndex": "0x5", + "address": "0x7ace431cb61584cb9b8dc7ec08cf38ac0a2d6496", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x0fc71295326a7ae8e0776c61be67f3ed8770311df88e186405b8d75bd0be552b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np127", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1b452f327c51d7a41d706af9b74ac14ff50b74dcef77fdb94333a8f5c86436a8", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb042b6a0d783d5e3757a9799dbc66d75515d0a511e5157650048a883a48d7c75", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x7f", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x4f6", + "extraData": "0x", + "baseFeePerGas": "0xe7f0", + "blockHash": "0x4831cdabfa81a5a7c4a8bb9fee309515e2d60dd5e754dfef4456794385771161", + "transactions": [ + "0xf8836682e7f1830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0e5232243797a918b702f03aa9ccf4e944ff52293e7f5b7b1cb6874047f064ed6a02ae2cefc3e4fdb15fb4172d6fe04c7d54a312d077dcd15f91bf5f7047c10d079" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7313b4315dd27586f940f8f2bf8af76825d8f24d2ae2c24d885dcb0cdd8d50f5" + ] + }, + { + "jsonrpc": "2.0", + "id": "np128", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x4831cdabfa81a5a7c4a8bb9fee309515e2d60dd5e754dfef4456794385771161", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0400502ad286f8ca3e6e362d38ec9f2119eddc480e9af1ec646bc48e5451a379", + "receiptsRoot": "0xdcfb036965921ecaf598a6a02e3fb77784da94be9ed9aeee279d085a20342e47", + "logsBloom": "0x00000002000041000000000200000200400000000000000008000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00080000000000000000000000000000000000000000000400000000000000008000000000000000000000014800000000000000000000000000000000000000000000000000000000000080000000000008000000000000000000000000000008000000000000000000000100000000000000000200000000000000000000000000000000000000030000800000000000000000000001000000002000000000000000020000400005002000004000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x80", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x500", + "extraData": "0x", + "baseFeePerGas": "0xcb03", + "blockHash": "0xfadcdb29ddbfaed75902beaecb3b9e859bf4faefe78591baf8ac9c99faec09d2", + "transactions": [ + "0xf87a6782cb0483011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa07e94803268c610035c580891ef0c6edd5c21babd8a2bb54d22373e982db9bf46a0375bc266e5e65f0a899b2299ddddbdc0e0d7d40c21e6d254d664abd7d0698076" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x2739473baa23a9bca4e8d0f4f221cfa48440b4b73e2bae7386c14caccc6c2059" + ] + }, + { + "jsonrpc": "2.0", + "id": "np129", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xfadcdb29ddbfaed75902beaecb3b9e859bf4faefe78591baf8ac9c99faec09d2", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x1f1fc8702bf538caf0df25f854999a44a7583b4339011bc24dadcee848e3daf5", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x81", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x50a", + "extraData": "0x", + "baseFeePerGas": "0xb1ae", + "blockHash": "0x5bc61ce8add484ead933542e385d4592d82aac6d47b46dcb2451390b884b8c3d", + "transactions": [ + "0xf8656882b1af8302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a064097d6048ea289fa6b8a002f4a7d53d8381ee46bf0dadd3ac1befa477cef309a0300f780844db5eaa99ff65752886da8b671329d7c12db4e65dd7f525abe9b1d8" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd4da00e33a11ee18f67b25ad5ff574cddcdccaa30e6743e01a531336b16cbf8f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np130", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5bc61ce8add484ead933542e385d4592d82aac6d47b46dcb2451390b884b8c3d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb04a7bb7f21e64f23bd415ee3ad1dc8a191975c86e0f0d43a92a4204a32ac090", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x82", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x514", + "extraData": "0x", + "baseFeePerGas": "0x9b8b", + "blockHash": "0x30fcf7ed7c580b55b92289383259c5c1d380d54c1f527bfdc8b062af1e898b8f", + "transactions": [ + "0xf86869829b8c82520894a5ab782c805e8bfbe34cb65742a0471cf5a53a9701808718e5bb3abd10a0a078e180a6afd88ae67d063c032ffa7e1ee629ec053306ce2c0eb305b2fb98245ea07563e1d27126c9294391a71da19044cb964fd6c093e8bc2a606b6cb5a0a604ac" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe651765d4860f0c46f191212c8193e7c82708e5d8bef1ed6f19bdde577f980cf" + ] + }, + { + "jsonrpc": "2.0", + "id": "np131", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x30fcf7ed7c580b55b92289383259c5c1d380d54c1f527bfdc8b062af1e898b8f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xfd2a1032389a1b7c6221d287a69e56a32d8a618396b8ef829601a9bcb3e91cce", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x83", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x51e", + "extraData": "0x", + "baseFeePerGas": "0x881d", + "blockHash": "0x8b3a8443b32d2085952d89ca1b1ecb7574b37483cb38e71b150c00d001fea498", + "transactions": [], + "withdrawals": [ + { + "index": "0xa", + "validatorIndex": "0x5", + "address": "0x5ee0dd4d4840229fab4a86438efbcaf1b9571af9", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x5b5b49487967b3b60bd859ba2fb13290c6eaf67e97e9f9f9dda935c08564b5f6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np132", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8b3a8443b32d2085952d89ca1b1ecb7574b37483cb38e71b150c00d001fea498", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x91120613028234db2b47071a122f6ff291d837abe46f1f79830276fd23934c56", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x84", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x528", + "extraData": "0x", + "baseFeePerGas": "0x771a", + "blockHash": "0xc9a9cc06b8a5d6edad0116a50740cb23d1cb130f6c3052bae9f69a20abf639c3", + "transactions": [ + "0xf8836a82771b830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0a295fe01d21a6f8ffd36f8415e00da318f965a12155808a0d3b51c2c1914cf65a055022813f479686f077e227f3b00dc983081ad361dd8c8240b84d1cf86721ccf" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x57b73780cc42a6a36676ce7008459d5ba206389dc9300f1aecbd77c4b90277fa" + ] + }, + { + "jsonrpc": "2.0", + "id": "np133", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc9a9cc06b8a5d6edad0116a50740cb23d1cb130f6c3052bae9f69a20abf639c3", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x1ec4000ab57cb0fec41b7221fff5ad7ec0dd4a042a739349045110b8116650c8", + "receiptsRoot": "0x870c88b91d896f4d6c0d6d8d9924dee345e36915e9244af9785f4ca1fea5fda3", + "logsBloom": "0x000000000008000000004000000000000000000000000000000000000000000400000000080000000000000000000000000000000000000000000000000c0000000000000000000002000000080000000000000000000004000000000000000000000000000000000000000000020000000400000000010000000040000000000000000000000000000004000000800008000100000202000000000000040000000000000000002000000000200000100000000000010000000000000001010000000000000000000000100000100000000401000000000000000000000000000000000000000000000000000000410000000800000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x85", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x532", + "extraData": "0x", + "baseFeePerGas": "0x6840", + "blockHash": "0x4d61445a8ece151e7938bc9c2f4f819a10afddf32c0f2600d62281ecd6b1af69", + "transactions": [ + "0xf87a6b82684183011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa09ce0e0b4fb662dd87cf69350e376568655ce9436941c42e7815a0688db3d8281a037208359ff73e2b9389d9d6e32df5203a0239e5dbbf016e87b3714c122ff081f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x217e8514ea30f1431dc3cd006fe730df721f961cebb5d0b52069d1b4e1ae5d13" + ] + }, + { + "jsonrpc": "2.0", + "id": "np134", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x4d61445a8ece151e7938bc9c2f4f819a10afddf32c0f2600d62281ecd6b1af69", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb078e743044057e03f894971bfc3dca4dc78990d5cba60c7c979182c419528cf", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x86", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x53c", + "extraData": "0x", + "baseFeePerGas": "0x5b3e", + "blockHash": "0xadcc471cc18ae64a1ece9ef42013441477843c72962bcc0f1291df9dc8906324", + "transactions": [ + "0xf8656c825b3f8302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0fd01a89a43af89dfba5de6077a24873a459ee0c8de3beaa03e444bb712fdbebda04f920e07882701d12f9016e32bfe5859d3c1bf971e844c6fcd336953190a8aad" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x14b775119c252908bb10b13de9f8ae988302e1ea8b2e7a1b6d3c8ae24ba9396b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np135", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xadcc471cc18ae64a1ece9ef42013441477843c72962bcc0f1291df9dc8906324", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5ed1a679a1844883bb7c09f1349702b93a298fc8a77885f18810230f0322d292", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x87", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x546", + "extraData": "0x", + "baseFeePerGas": "0x4fe0", + "blockHash": "0xd2e3126fb4b0cc3e1e98f8f2201e7a27192a721136d12c808f32a4ff0994601b", + "transactions": [ + "0x02f86b870c72dd9d5e883e6d01824fe1825208944bfa260a661d68110a7a0a45264d2d43af9727de0180c001a00bb105cab879992d2769014717857e3c9f036abf31aa59aed2c2da524d938ff8a03b5386a238de98973ff1a9cafa80c90cdcbdfdb4ca0e59ff2f48c925f0ea872e" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe736f0b3c5672f76332a38a6c1e66e5f39e0d01f1ddede2c24671f48e78daf63" + ] + }, + { + "jsonrpc": "2.0", + "id": "np136", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xd2e3126fb4b0cc3e1e98f8f2201e7a27192a721136d12c808f32a4ff0994601b", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd4db74075dc9ae020d6016214314a7602a834c72ec99e34396e1d326aa112a27", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x88", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x550", + "extraData": "0x", + "baseFeePerGas": "0x45e6", + "blockHash": "0xa503a85bc5c12d4108445d5eab6518f1e4ccaeab30434202b53204a9378419fa", + "transactions": [], + "withdrawals": [ + { + "index": "0xb", + "validatorIndex": "0x5", + "address": "0x4f362f9093bb8e7012f466224ff1237c0746d8c8", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7d112c85b58c64c576d34ea7a7c18287981885892fbf95110e62add156ca572e" + ] + }, + { + "jsonrpc": "2.0", + "id": "np137", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa503a85bc5c12d4108445d5eab6518f1e4ccaeab30434202b53204a9378419fa", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xfd5f001adc20a6ab7bcb9cd5ce2ea1de26d9ecc573a7b595d2f6d682cf006610", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x89", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x55a", + "extraData": "0x", + "baseFeePerGas": "0x3d2a", + "blockHash": "0xe0b036f2df5813e2e265d606ee533cd46924a8a7de2988e0e872c8b92c26399c", + "transactions": [ + "0xf8836e823d2b830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0e853c07d5aba01cfcacc3a4191551d7b47d2e90aba323bd29b5b552147bc4055a03a7e1dee0d461376b43ac4c0dd1a85cc94e9fa64aa8effec98c026293e47240a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x28fbeedc649ed9d2a6feda6e5a2576949da6812235ebdfd030f8105d012f5074" + ] + }, + { + "jsonrpc": "2.0", + "id": "np138", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe0b036f2df5813e2e265d606ee533cd46924a8a7de2988e0e872c8b92c26399c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3bf11932c08c5317c7463697409eba5a6904575cc03593cb0eac6c82093d79b7", + "receiptsRoot": "0x3ef7cc7ec86f1ace231cdf7c7fadaf27ae84ad4afdd5f2261b60d5be03794001", + "logsBloom": "0x00000000000000000000080000000000000000000000000000000000000000000000000010000000004000008000000000000000000000000000000080001000000020000000000000000000000000000000000000000000000010000010200000040220000000000000000000010001000000800000000000400000002000000000000000000000400000000000000800000000000400000000000000080000500000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000004002000000000008000000000002000000400000000000000000000000000002000000000002000000000000002000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x8a", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x564", + "extraData": "0x", + "baseFeePerGas": "0x358a", + "blockHash": "0xfcca6f4e35f290be297bf6403b84c99d1a7b6d78299b5e2690d915bf834e85da", + "transactions": [ + "0xf87a6f82358b83011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0c28b8f557aaf82e47d9e1425824709427513131908ac636f142990468e40909ea05fe11510da000868cfe1a05bdf689a8c1954c87afeb9ef2defbed3075458a6ad" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x6f7410cf59e390abe233de2a3e3fe022b63b78a92f6f4e3c54aced57b6c3daa6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np139", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xfcca6f4e35f290be297bf6403b84c99d1a7b6d78299b5e2690d915bf834e85da", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe8258bde0dceac7f4b4734c8fa80fe5be662ae7238d9beb9669bc3ae4699efa8", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x8b", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x56e", + "extraData": "0x", + "baseFeePerGas": "0x2edc", + "blockHash": "0x762df3955fc857f4c97acb59e4d7b69779986e20e3a8ea6bc5219dfd9e5a3d7e", + "transactions": [ + "0xf86570822edd8302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a02206472edd9c816508c6711c004500028a4a6a206caf23b20c6828dd60e1533fa0186dc116a92a8455d1cb92ed4b599c3f7cade6cf59da63b1aef46936c3a507e9" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd5edc3d8781deea3b577e772f51949a8866f2aa933149f622f05cde2ebba9adb" + ] + }, + { + "jsonrpc": "2.0", + "id": "np140", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x762df3955fc857f4c97acb59e4d7b69779986e20e3a8ea6bc5219dfd9e5a3d7e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x9ff9a193050e74dfa00105084fa236099def4aa7993691c911db0a3f93422aeb", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x8c", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x578", + "extraData": "0x", + "baseFeePerGas": "0x2906", + "blockHash": "0xffe6c202961ee6b5098db912c7203b49aa3b303b4482234371b49f7ef7a95f84", + "transactions": [ + "0xf86871822907825208949defb0a9e163278be0e05aa01b312ec78cfa372601808718e5bb3abd109fa04adf7509b10551a97f2cb6262c331096d354c6c8742aca384e63986006b8ac93a0581250d189e9e1557ccc88190cff66de404c99754b4eb3c94bb3c6ce89157281" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x20308d99bc1e1b1b0717f32b9a3a869f4318f5f0eb4ed81fddd10696c9746c6b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np141", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xffe6c202961ee6b5098db912c7203b49aa3b303b4482234371b49f7ef7a95f84", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf63dc083849dc5e722a7ca08620f43fc5cd558669664a485a3933b4dae3b84f4", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x8d", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x582", + "extraData": "0x", + "baseFeePerGas": "0x23e6", + "blockHash": "0xfa0dcd8b9d6e1c42eeea7bb90a311dd8b7215d858b6c4fb0f64ee01f2be00cfe", + "transactions": [], + "withdrawals": [ + { + "index": "0xc", + "validatorIndex": "0x5", + "address": "0x075198bfe61765d35f990debe90959d438a943ce", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x91f7a302057a2e21d5e0ef4b8eea75dfb8b37f2c2db05c5a84517aaebc9d5131" + ] + }, + { + "jsonrpc": "2.0", + "id": "np142", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xfa0dcd8b9d6e1c42eeea7bb90a311dd8b7215d858b6c4fb0f64ee01f2be00cfe", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6222bb96d397776358dd71f14580f5464202313769960ec680c50d9ccc2fa778", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x8e", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x58c", + "extraData": "0x", + "baseFeePerGas": "0x1f6a", + "blockHash": "0xe501e9f498cd6b1a6d22c96a556c9218e3a7960eea3e9ab4ac2760cc09fdca0d", + "transactions": [ + "0xf88372821f6b830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa067091ae37d21fdc5f9eed2877bddb24e52f69e80af27a89608b6fba1c5053f32a04817ab7dc0c3eaac266b08a1683c34fcd43098c6219ea5771d35fa3387b705a1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x743e5d0a5be47d489b121edb9f98dad7d0a85fc260909083656fabaf6d404774" + ] + }, + { + "jsonrpc": "2.0", + "id": "np143", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe501e9f498cd6b1a6d22c96a556c9218e3a7960eea3e9ab4ac2760cc09fdca0d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xbe8a51adbc81161927f0b6f3e562cd046f1894145010a1b3d77394780478df3c", + "receiptsRoot": "0x8c32e3da3725025cad909cb977e252fd127d54c4f4da3852d18ef3976bfe4610", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000001000000000000004000000000000000000000000000800000000000000000000028000000020008000000008000000000000000000000000010000000000080000100000400100000000000000000000000100000000010000000000000000000000000000004000000000000000000008000000000080008000000000000000000000000000000000000000000080002800000000000120000000000004000000000000000000000004000000400000002000800000020000000080000000000000008000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x8f", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x596", + "extraData": "0x", + "baseFeePerGas": "0x1b7f", + "blockHash": "0xdb3eb92355d58f317e762879ec891a76e0d9ba32a43f0a70f862af93780ef078", + "transactions": [ + "0xf87a73821b8083011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0de521643ceaf711d0d3b6cda406ef8fba599658fccc750139851846435eba8afa057f5427948ca8d46609925641f81f72115860c16228821020b8020846a4c3158" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xcdcf99c6e2e7d0951f762e787bdbe0e2b3b320815c9d2be91e9cd0848653e839" + ] + }, + { + "jsonrpc": "2.0", + "id": "np144", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xdb3eb92355d58f317e762879ec891a76e0d9ba32a43f0a70f862af93780ef078", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb7933a921b5acf566cc2b8edb815d81a221222a0ac36dac609927aa75744daaf", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x90", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x5a0", + "extraData": "0x", + "baseFeePerGas": "0x1811", + "blockHash": "0x6718dc62462698e0df2188c40596275679d2b8a49ab6fd6532a3d7c37efd30a6", + "transactions": [ + "0xf865748218128302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0afbda9fa76936bc6be4d26905bc000b4b14cae781a8e3acb69675b6c5be20835a03858ad4e7e694bf0da56994a1e5f855ff845bae344de14109ae46607aa4172ca" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xcc9476183d27810e9738f382c7f2124976735ed89bbafc7dc19c99db8cfa9ad1" + ] + }, + { + "jsonrpc": "2.0", + "id": "np145", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x6718dc62462698e0df2188c40596275679d2b8a49ab6fd6532a3d7c37efd30a6", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa8a6a6386a956afbc3163f2ccdcaeffeb9b12c10d4bb40f2ef67bcb6df7cf64c", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x91", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x5aa", + "extraData": "0x", + "baseFeePerGas": "0x1512", + "blockHash": "0x891051fb49d284166b72a30c29b63bfe59994c9db2d89e54ca0791b4dfdb68fb", + "transactions": [ + "0x02f86b870c72dd9d5e883e7501821513825208947da59d0dfbe21f43e842e8afb43e12a6445bbac00180c080a06ca026ba6084e875f3ae5220bc6beb1cdb34e8415b4082a23dd2a0f7c13f81eca0568da83b9f5855b786ac46fb241eee56b6165c3cc350d604e155aca72b0e0eb1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf67e5fab2e7cacf5b89acd75ec53b0527d45435adddac6ee7523a345dcbcdceb" + ] + }, + { + "jsonrpc": "2.0", + "id": "np146", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x891051fb49d284166b72a30c29b63bfe59994c9db2d89e54ca0791b4dfdb68fb", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x9ee7ad908d7c553d62d14ecd6a1e9eac6ed728f9a0d0dd8aa8db149e6e803262", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x92", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x5b4", + "extraData": "0x", + "baseFeePerGas": "0x1271", + "blockHash": "0x2ef94fa352357c07d9be6e271d8096b2cbf7dcae9bad922e95bc7c7c24375e7c", + "transactions": [], + "withdrawals": [ + { + "index": "0xd", + "validatorIndex": "0x5", + "address": "0x956062137518b270d730d4753000896de17c100a", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe20f8ab522b2f0d12c068043852139965161851ad910b840db53604c8774a579" + ] + }, + { + "jsonrpc": "2.0", + "id": "np147", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x2ef94fa352357c07d9be6e271d8096b2cbf7dcae9bad922e95bc7c7c24375e7c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x14111c2a0f5c36f6b8ea455b9b897ab921a0f530aaee00447af56ffc35940e32", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x93", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x5be", + "extraData": "0x", + "baseFeePerGas": "0x1023", + "blockHash": "0x406fbf5c2aa4db48fce6fe0041d09a3387c2c18c57a4fb77eca5d073586ca3ea", + "transactions": [ + "0xf88376821024830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa08e5e1207971ec2479337fa7c80f843dd80d51224eb9f9d8c37b1758d3d5acae4a04d2f89fb9005dc18fa4c72e8b1b4e611f90ca9c5e346b6201dfe4b83ec39c519" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf982160785861cb970559d980208dd00e6a2ec315f5857df175891b171438eeb" + ] + }, + { + "jsonrpc": "2.0", + "id": "np148", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x406fbf5c2aa4db48fce6fe0041d09a3387c2c18c57a4fb77eca5d073586ca3ea", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc43ae2200cea3bdd1b211157150bd773118c818669e2650659ef3807ac7d2c29", + "receiptsRoot": "0x1f4bdefd1b3ded1be79051fe46e6e09f4543d4c983fdc21dee02b1e43fb34959", + "logsBloom": "0x00000000000000000000000000000110000000000002000000000000000000020008000000000000000800001000000000000000000000000020000010000400000000000000000000001000000000000000000000000000000020000000000000101000000000800000000000000000080000000000000000000000000000010000080000080000800000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000200000020000000000000000000000000002000001000000000040002000000024000000000280000000000000000000000000020000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x94", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x5c8", + "extraData": "0x", + "baseFeePerGas": "0xe20", + "blockHash": "0x34ca9a29c1cef7e8011dcce6240c1e36ee8e64643fc0ed98cb436d2f9a21baa2", + "transactions": [ + "0xf87a77820e2183011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0b5b7b281fbe78ca0f9819a9015997a42ee896462db5ea7de089cd7e2cf84b346a02bc85175e51da947f89f947c30d7c1d77daa6e654a0007e56de98812039a76bd" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x230954c737211b72d5c7dcfe420bb07d5d72f2b4868c5976dd22c00d3df0c0b6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np149", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x34ca9a29c1cef7e8011dcce6240c1e36ee8e64643fc0ed98cb436d2f9a21baa2", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x21cafe51bfa7793c9a02f20282b59cbb5156dce1e252ab61f98fdd5cdecf8495", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x95", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x5d2", + "extraData": "0x", + "baseFeePerGas": "0xc5d", + "blockHash": "0xed939dcec9a20516bd7bb357af132b884efb9f6a6fc2bc04d4a1e5063f653031", + "transactions": [ + "0xf86578820c5e8302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0371194d9f0d8b28bc888d45cc571dd73c9dd620d54184b9776256d5e07049350a05f7bfb7cdccb54a2f0ea01374f1474e694daa1b128076bdc33efcee9bc0d56a7" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb7743e65d6bbe09d5531f1bc98964f75943d8c13e27527ca6afd40ca069265d4" + ] + }, + { + "jsonrpc": "2.0", + "id": "np150", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xed939dcec9a20516bd7bb357af132b884efb9f6a6fc2bc04d4a1e5063f653031", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x503c44cab4d6c0010c3493e219249f1e30cfff1979b9da7268fd1121af73d872", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x96", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x5dc", + "extraData": "0x", + "baseFeePerGas": "0xad3", + "blockHash": "0x136665ab7316f05d4419e1f96315d3386b85ec0baeed10c0233f6e4148815746", + "transactions": [ + "0xf86879820ad48252089484873854dba02cf6a765a6277a311301b2656a7f01808718e5bb3abd10a0a0ab3202c9ba5532322b9d4eb7f4bdf19369f04c97f008cf407a2668f5353e8a1fa05affa251c8d29f1741d26b42a8720c416f7832593cd3b64dff1311a337799e8f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x31ac943dc649c639fa6221400183ca827c07b812a6fbfc1795eb835aa280adf3" + ] + }, + { + "jsonrpc": "2.0", + "id": "np151", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x136665ab7316f05d4419e1f96315d3386b85ec0baeed10c0233f6e4148815746", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2e257bca2ea424f7c304c42fc35b14c8d3fd46c9066c7f895f775a2065a14bab", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x97", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x5e6", + "extraData": "0x", + "baseFeePerGas": "0x979", + "blockHash": "0xefc08cafa0b7c0e0bc67c0dbd563a855ba55f389d947bd9c524be5ef789505ba", + "transactions": [], + "withdrawals": [ + { + "index": "0xe", + "validatorIndex": "0x5", + "address": "0x2a0ab732b4e9d85ef7dc25303b64ab527c25a4d7", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xded49c937c48d466987a4130f4b6d04ef658029673c3afc99f70f33b552e178d" + ] + }, + { + "jsonrpc": "2.0", + "id": "np152", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xefc08cafa0b7c0e0bc67c0dbd563a855ba55f389d947bd9c524be5ef789505ba", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa124fe0abd3682da7263262172c9d2c57fb42d4f131cbc9f24ddea0ec1505e48", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x98", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x5f0", + "extraData": "0x", + "baseFeePerGas": "0x84a", + "blockHash": "0xb7a12ba1b0cd24019d0b9864ed28c0d460425eb1bd32837538d99da90f5c65b7", + "transactions": [ + "0xf8837a82084b830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0be58da9e68f28cf1dd209a610214982ba767249f3b92cd8c0fb3850a9ee194d6a0613f59eec6c092b6d2fc55de85bc67b21c261dc48f1ddb74af3aac438b27ccd5" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa0effc449cab515020d2012897155a792bce529cbd8d5a4cf94d0bbf141afeb6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np153", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb7a12ba1b0cd24019d0b9864ed28c0d460425eb1bd32837538d99da90f5c65b7", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3c05bdceef0bdc9f676a3a0c00151f975e469e5bb08ab08f3eed090987119672", + "receiptsRoot": "0x73faa109b88bfbf7e2a71c36d556d9286c0a26988680cbe3058f045fd361b3b0", + "logsBloom": "0x00004000000800000000000000000000000000000000000000000000000000000004000000080000000000000800000000000000500000000000000000000200000000001000000800000000000002008000080000000000000000000000000000000000000000000008000200000000000000000000000000000001000000000000000000101000004000000000000000000000000000000000000000000000000000000080000000000000000000008200000000000080000000000000000000000000000000000800000000000000000000000400000080020002000000001040000000000000000000000000004000000000000000008000008000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x99", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x5fa", + "extraData": "0x", + "baseFeePerGas": "0x742", + "blockHash": "0x0292db163d287eeb39bd22b82c483c9b83a9103a0c425a4f3954ef2330cc1718", + "transactions": [ + "0xf87a7b82074383011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0b9eb0510fdc334dde88b8ac75869aa2dd53988191ae1df94b7b926eae9b18050a00cbd9e12b7185723ed407175a7a70fa5cc0dbc4014b3040a9ade24a4eb97c8c1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x1f36d9c66a0d437d8e49ffaeaa00f341e9630791b374e8bc0c16059c7445721f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np154", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x0292db163d287eeb39bd22b82c483c9b83a9103a0c425a4f3954ef2330cc1718", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc2e93862e26d4df238b2b83a3ee0e008f25123aa211d83906fcd77bc9fd226ab", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x9a", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x604", + "extraData": "0x", + "baseFeePerGas": "0x65b", + "blockHash": "0xaeab3fe4b09329235bd8a0399db4d944fe1b247a91055c7de7f53703c94357ea", + "transactions": [ + "0xf8657c82065c8302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0e5959821e9fe4b896ef2559fe6524aadead228d89f923061b6d2d340f6b9307fa02ed2929f37d24a57229f7a579aaab2d9551e71b0822895e91f04e7824da9a861" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x34f89e6134f26e7110b47ffc942a847d8c03deeed1b33b9c041218c4e1a1a4e6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np155", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xaeab3fe4b09329235bd8a0399db4d944fe1b247a91055c7de7f53703c94357ea", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0c94e7ea002f7b3bcc5100783e1e792160fb73ff4e836cd295e34423ff72f2a6", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x9b", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x60e", + "extraData": "0x", + "baseFeePerGas": "0x591", + "blockHash": "0xcc221bd9ee16f8302994c688cd7cc18313e686cf21f29edea5da5ac08a28a9b6", + "transactions": [ + "0x02f86b870c72dd9d5e883e7d01820592825208948d36bbb3d6fbf24f38ba020d9ceeef5d4562f5f20180c001a0f9075613b9069dab277505c54e8381b0bb91032f688a6fe036ef83f016771897a04cb4fc2e695439af564635863f0855e1f40865997663d900bc2ab572e78a70a2" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x774404c430041ca4a58fdc281e99bf6fcb014973165370556d9e73fdec6d597b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np156", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xcc221bd9ee16f8302994c688cd7cc18313e686cf21f29edea5da5ac08a28a9b6", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x04ba5addea7916f0483658ea884c052ea6d759eeda62b9b47ee307bd46525bb0", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x9c", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x618", + "extraData": "0x", + "baseFeePerGas": "0x4df", + "blockHash": "0x8c922bb4a1c7aad6fdc09082e5c90427d0643ffd281d0154cdd71a372108c5da", + "transactions": [], + "withdrawals": [ + { + "index": "0xf", + "validatorIndex": "0x5", + "address": "0x6e3faf1e27d45fca70234ae8f6f0a734622cff8a", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd616971210c381584bf4846ab5837b53e062cbbb89d112c758b4bd00ce577f09" + ] + }, + { + "jsonrpc": "2.0", + "id": "np157", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8c922bb4a1c7aad6fdc09082e5c90427d0643ffd281d0154cdd71a372108c5da", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3ae465791b7ce8492961c071fc50b34434552a1ab36c1854fbc308f55729e827", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x9d", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x622", + "extraData": "0x", + "baseFeePerGas": "0x444", + "blockHash": "0x1a883eed15a2f61dc157140d45f50e4bc6cc08ead08adf3ff0804ec9f1104c8a", + "transactions": [ + "0xf8837e820445830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0140e450a0bc12c61bdf6acca1a56178466d88014d00a4a09c1088ce184128327a07daad374bb0d7fe879212bd7bdc8d454b4996bd7bebd6f6d0d4636ec7df28d0b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xcdf6383634b0431468f6f5af19a2b7a087478b42489608c64555ea1ae0a7ee19" + ] + }, + { + "jsonrpc": "2.0", + "id": "np158", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1a883eed15a2f61dc157140d45f50e4bc6cc08ead08adf3ff0804ec9f1104c8a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe01f0f54fba649cdc0d6da6a9f519b6918149d82f134845e99847ff7b362b050", + "receiptsRoot": "0x36340e11a5f180862d423a676049d1c934b8d27940fdd50dc8704563ffd27b0f", + "logsBloom": "0x00000000000000008000000000800080000000000000000018040000000100100000000000010000000000000000000000000000000000000000000000010000000080080000800000000000010000000010000000000802000000000000000000000000001000000000004000000000000000000000004000000000000000004000000000000000000000000000000000000000000000401000000000010000000000000000000000000000000080000000000000000000000040000240000000000000000000000001000000000000000000000000100000000080000040000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x9e", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x62c", + "extraData": "0x", + "baseFeePerGas": "0x3bc", + "blockHash": "0x5efcd9acd57f0652b1aa46406cf889b0da1f05e34fa9b642f7dec1bd924f3fd0", + "transactions": [ + "0xf87a7f8203bd83011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0331dd2ec5bf4bddde96cacb8a28ed1cc577d4a2289bae6da0e6ef3c9b1287fc3a04c2925895dfbed2b00ac9a2040371970da1a7fd333dc1e551e2e268c56717c79" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x00ec22e5df77320b4142c54fceaf2fe7ea30d1a72dc9c969a22acf66858d582b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np159", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5efcd9acd57f0652b1aa46406cf889b0da1f05e34fa9b642f7dec1bd924f3fd0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa6e1d00e54b539beb170e510a8594fdd73ad2bf8e695a0f052291454ee1f3ade", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x9f", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x636", + "extraData": "0x", + "baseFeePerGas": "0x345", + "blockHash": "0x97570840bed5a39a4580302a64cbaf7ed55bcc82e9296502c4873d84f8384004", + "transactions": [ + "0xf86681808203468302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0f4a1b0681bb3c513fa757b560ef9cf0f004b8da91d920e157506ebb60d0d3954a0738da3b003ce68a9b4032770c0fe6481f54ea43baba54cad7153369486728790" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xcb32d77facfda4decff9e08df5a5810fa42585fdf96f0db9b63b196116fbb6af" + ] + }, + { + "jsonrpc": "2.0", + "id": "np160", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x97570840bed5a39a4580302a64cbaf7ed55bcc82e9296502c4873d84f8384004", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x9277b9454326e993436cef0b9a2e775cff46439f3d683da55a983e9850943a20", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xa0", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x640", + "extraData": "0x", + "baseFeePerGas": "0x2dd", + "blockHash": "0x4b01a4f9f924e7e386d2c94653c80bab2e3069d744ab107dd181d9b5f5d176d0", + "transactions": [ + "0xf86981818202de82520894c19a797fa1fd590cd2e5b42d1cf5f246e29b916801808718e5bb3abd109fa0857754afc3330f54a3e6400f502ad4a850a968671b641e271dcb9f68aacea291a07d8f3fb2f3062c39d4271535a7d02960be9cb5a0a8de0baef2211604576369bf" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x6d76316f272f0212123d0b4b21d16835fe6f7a2b4d1960386d8a161da2b7c6a2" + ] + }, + { + "jsonrpc": "2.0", + "id": "np161", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x4b01a4f9f924e7e386d2c94653c80bab2e3069d744ab107dd181d9b5f5d176d0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc9f74f81ace1e39dd67d9903221e22f1558da032968a4aaff354eaa92289f5c6", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xa1", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x64a", + "extraData": "0x", + "baseFeePerGas": "0x282", + "blockHash": "0x9431a8d1844da9cc43e8b338de21722e23f78ed5b46391a6d924595759773286", + "transactions": [], + "withdrawals": [ + { + "index": "0x10", + "validatorIndex": "0x5", + "address": "0x8a8950f7623663222542c9469c73be3c4c81bbdf", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x2de2da72ae329e359b655fc6311a707b06dc930126a27261b0e8ec803bdb5cbf" + ] + }, + { + "jsonrpc": "2.0", + "id": "np162", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x9431a8d1844da9cc43e8b338de21722e23f78ed5b46391a6d924595759773286", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x853c0a8e4e964cc857f2dd40b10de2cefb2294a7da4d83d7b1da2f9581ee0961", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xa2", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x654", + "extraData": "0x", + "baseFeePerGas": "0x232", + "blockHash": "0x604f361dbc1085fb70812b618e53035d4747c3969a96620e4c179a93be5d124d", + "transactions": [ + "0xf8848182820233830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa01a7b8af754eba43e369957a413a3fef1255659f2bd05f902b29ee213c3989d46a00ca88ac892d58fdb0d9bd7640ca797280081275886cc2ac155a814eb498e7d7b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x08bed4b39d14dc1e72e80f605573cde6145b12693204f9af18bbc94a82389500" + ] + }, + { + "jsonrpc": "2.0", + "id": "np163", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x604f361dbc1085fb70812b618e53035d4747c3969a96620e4c179a93be5d124d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd89e02bde63bf214ad6a3bc94f3b092bc2a1fbc13f172049c854ecb070630fe6", + "receiptsRoot": "0x596413315e1e3fd6fc21e4ce81e618b76ad2bf7babfa040c822a5bcbffeb63be", + "logsBloom": "0x00080000001044010000000800000000000000000010000000040000000020000000800000000040000000000000000001008000000000800000000000000000000000001000000000020000080000000000000000000000000000000000000002000044000000000000000000000000000000000000000000000000000000000000002000000000000000800000000000000000000000000000000000104000800000000000000004000004000002000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000800020000000000000000000040000000000000000020", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xa3", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x65e", + "extraData": "0x", + "baseFeePerGas": "0x1ec", + "blockHash": "0x00979cd18ef128aa75a51ad8606b381ce53f72c37d17bc6c6613d8de722abcfa", + "transactions": [ + "0xf87b81838201ed83011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa06e6e8187c035f2788ba44e3f47b4102a1f263ae2f601b2fbfa9e2cdc3b0c22b1a06c229eebca1bdda1aba424cd8cf296f386cf2d50a6add950fd6cb34aac442c5a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe437f0465ac29b0e889ef4f577c939dd39363c08fcfc81ee61aa0b4f55805f69" + ] + }, + { + "jsonrpc": "2.0", + "id": "np164", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x00979cd18ef128aa75a51ad8606b381ce53f72c37d17bc6c6613d8de722abcfa", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe2672f9ae97aeaeb22f42c389301a3b79ad6c47ad88c54e18e1d7a4ed5e9c903", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xa4", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x668", + "extraData": "0x", + "baseFeePerGas": "0x1af", + "blockHash": "0xcabf8c1b47839908f6eb28261876b52404f3f8787c94d8aadc0aca721ff35d13", + "transactions": [ + "0xf86681848201b08302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa08ab61fe0265afe289954f7c2af8e070f3c40dda39e6cb6ff5c798fc7bc87b55ba00a8a440a7ba5a04a7bb73b093e94734dda228d33a43c640d719aef5ea5e81764" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x89ca120183cc7085b6d4674d779fc4fbc9de520779bfbc3ebf65f9663cb88080" + ] + }, + { + "jsonrpc": "2.0", + "id": "np165", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xcabf8c1b47839908f6eb28261876b52404f3f8787c94d8aadc0aca721ff35d13", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4d9cd7b52c0daaec9a019730c237a2c3424f5d5a004c8bc9fa23997f3ec33768", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xa5", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x672", + "extraData": "0x", + "baseFeePerGas": "0x17a", + "blockHash": "0x6dcec039f7777c1fd96bbdd342e0ed787211132f753cf73a59847dc6cb30a6ff", + "transactions": [ + "0x02f86c870c72dd9d5e883e81850182017b825208946922e93e3827642ce4b883c756b31abf800366490180c080a089e6d36baf81743f164397205ded9e5b3c807e943610d5b9adb9cfeb71b90299a03d56c57f842a92a5eb71c8f9f394fe106d993960421c711498013806957fdcaf" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb15d5954c7b78ab09ede922684487c7a60368e82fdc7b5a0916842e58a44422b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np166", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x6dcec039f7777c1fd96bbdd342e0ed787211132f753cf73a59847dc6cb30a6ff", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x03629dac3f669a8262e8246d46bac9acfb7cbca336d02e90c081561fa0b22aba", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xa6", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x67c", + "extraData": "0x", + "baseFeePerGas": "0x14b", + "blockHash": "0x760da169c77450231e6a0d2dd4aad67de84633eb6918fc8607a3a709eea07bef", + "transactions": [], + "withdrawals": [ + { + "index": "0x11", + "validatorIndex": "0x5", + "address": "0xfe1dcd3abfcd6b1655a026e60a05d03a7f71e4b6", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xad13055a49d2b6a4ffc8b781998ff79086adad2fd6470a0563a43b740128c5f2" + ] + }, + { + "jsonrpc": "2.0", + "id": "np167", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x760da169c77450231e6a0d2dd4aad67de84633eb6918fc8607a3a709eea07bef", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4f5e79d4af5565b3b53649b1ddc3a03209cb583e7beb03db8b32924c641e6912", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xa7", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x686", + "extraData": "0x", + "baseFeePerGas": "0x122", + "blockHash": "0xfcb210229cb48baf3d535e48a7577041268eadd6027942084a56dbec8f8423a9", + "transactions": [ + "0xf8848186820123830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0b2aafb3e2678dd48e6f31874bd478778480815c9d110ec8cc77a42f7d52999daa00705b1266fc1087167cc531caa9d2e0a0c8779e4ad5020d9d3a16500bf5b96a1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9e9909e4ed44f5539427ee3bc70ee8b630ccdaea4d0f1ed5337a067e8337119f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np168", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xfcb210229cb48baf3d535e48a7577041268eadd6027942084a56dbec8f8423a9", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8c6c100b7c75ced82b38315fd50c5439478a7ee256073ce17b845e0815912eab", + "receiptsRoot": "0xf8f8c85b17ada66c06f8e41b58b45213619bb309a197896adbaff4e9139967b1", + "logsBloom": "0x80000000000000000000000000000000800000000004008000200000000000000002820000000000000000000000000000000000000040020400000000000000000000000200000000000000000000000000000040000400000000000000000000000000000000000000000000000000000000000000100000000000000000000000000040080000000000000000000000000000000000200000000000000080000200000000000000000000000000000000000000000000000000000100000003000200000000000000000000000000000000000000200000000000000000000000004000000004000000040001010000000080400000000000000040000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xa8", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x690", + "extraData": "0x", + "baseFeePerGas": "0xfe", + "blockHash": "0x796a4e02d1da9c86b1a2e7b2ef1d82e1ebdac143ec7ff4a67dae2b241b22c3c1", + "transactions": [ + "0xf87a818781ff83011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0b1e7ca73ef581fc880deb34aa6cf7958f6ce110efd121d48fb2292a747864815a02bf94b17dc034d8934b885faa269a9430a755ebfb4c6e87378376a094704f464" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xbf1f3aba184e08d4c650f05fe3d948bdda6c2d6982f277f2cd6b1a60cd4f3dac" + ] + }, + { + "jsonrpc": "2.0", + "id": "np169", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x796a4e02d1da9c86b1a2e7b2ef1d82e1ebdac143ec7ff4a67dae2b241b22c3c1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x9f527744fd44cf4c2ba60fe62d25d4f19e64c034cbf24785e0128d5fafa19e2a", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xa9", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x69a", + "extraData": "0x", + "baseFeePerGas": "0xdf", + "blockHash": "0x29a0d081e8aec6b2dcb307d73ca48d7d50e434617daf0e81fd28b35be9c7995d", + "transactions": [ + "0xf865818881e08302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0c583bd1010c1e4158466575fb0c09ff710a5ff07c8f7a6e7960d90bffef8bd34a059ea0ba5c6fc64aad73252c780de287599d3100d80f7b1d3201b4865d82c0cad" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xbb70fe131f94783dba356c8d4d9d319247ef61c768134303f0db85ee3ef0496f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np170", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x29a0d081e8aec6b2dcb307d73ca48d7d50e434617daf0e81fd28b35be9c7995d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x45c5f07a7d94c320222f43c12b04081fdbe870be18a2b76f7122bd7f4554118b", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xaa", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x6a4", + "extraData": "0x", + "baseFeePerGas": "0xc4", + "blockHash": "0xe878e98d05f60a8fd741a4aaab17a91c538f21552ac41922fe2b755e4f0e534c", + "transactions": [ + "0xf868818981c582520894bceef655b5a034911f1c3718ce056531b45ef03b01808718e5bb3abd109fa0626dfd18ca500eedb8b439667d9b8d965da2f2d8ffcd36a5c5b60b9a05a52d9fa07271175e4b74032edeb9b678ffb5e460edb2986652e45ff9123aece5f6c66838" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x6a81ebd3bde6cc54a2521aa72de29ef191e3b56d94953439a72cafdaa2996da0" + ] + }, + { + "jsonrpc": "2.0", + "id": "np171", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe878e98d05f60a8fd741a4aaab17a91c538f21552ac41922fe2b755e4f0e534c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf4a19b9765604687783462dbf36a0063ada2ba7babb4dd1c4857b2449565a41d", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xab", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x6ae", + "extraData": "0x", + "baseFeePerGas": "0xac", + "blockHash": "0xc3f33c71274b456303efd80efacba7d5fccb0ed278ee24e5594a38c45a294315", + "transactions": [], + "withdrawals": [ + { + "index": "0x12", + "validatorIndex": "0x5", + "address": "0x087d80f7f182dd44f184aa86ca34488853ebcc04", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4c83e809a52ac52a587d94590c35c71b72742bd15915fca466a9aaec4f2dbfed" + ] + }, + { + "jsonrpc": "2.0", + "id": "np172", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc3f33c71274b456303efd80efacba7d5fccb0ed278ee24e5594a38c45a294315", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4f9e280291036fb6cd64598fe0517d64d6da264d07d7fc3b8d664221d7af9021", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xac", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x6b8", + "extraData": "0x", + "baseFeePerGas": "0x97", + "blockHash": "0xd785018f59628b9f13cc2d4a45e0b4b3af183acce4e5752346e79dbcdf7de4e5", + "transactions": [ + "0xf883818a8198830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a05f47b0ab77130dcc8f7143a2afaace6a2d1f82e25839cb9adee5aaebfe7dc681a05af90b75de35c90709b83861d8fdfd7805a89b1e76a4bdd5987e578ba72fc37e" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x268fc70790f00ad0759497585267fbdc92afba63ba01e211faae932f0639854a" + ] + }, + { + "jsonrpc": "2.0", + "id": "np173", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xd785018f59628b9f13cc2d4a45e0b4b3af183acce4e5752346e79dbcdf7de4e5", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe01cadedc509806ea9cd7475312a3768de034d1c849abadc46237b8cd4163179", + "receiptsRoot": "0x15dc68f6de1b068b96d32dbc11a048b915e7d62bd3662689ae5c095bb6ddab37", + "logsBloom": "0x00000100000000004000000004000000000000000000000000000000000000000000000004000000018004000000000000000000000000000002000000000000000020002000400000002000020000000100000000000000000080010000400000000000000200000000040000000000000000000000010000002000000000000000000000000004040000000000000000000000000000000000000000004000000000000000000000080000004000000000000000000000001000000000000100000800040000000000000000000000000000000000000000000000000000000000000000400000000000004001000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xad", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x6c2", + "extraData": "0x", + "baseFeePerGas": "0x85", + "blockHash": "0xbcff8f4e8c3d70d310900cd8246c3456e237ab8ea9fc036601995404b141e3bb", + "transactions": [ + "0xf87a818b818683011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa04a74ea0833e42d624ba0d9b589a16e05feae1c2dee89abfb29df95b650d3e756a037135f3e24572eb9d927a02c0c4eee7fd5d8a181e2384ef3b3b04c49c9dbbbe1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7e544f42df99d5666085b70bc57b3ca175be50b7a9643f26f464124df632d562" + ] + }, + { + "jsonrpc": "2.0", + "id": "np174", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xbcff8f4e8c3d70d310900cd8246c3456e237ab8ea9fc036601995404b141e3bb", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa862687747ffc388414ee5953589a70f2161a130886348157257a52347be9157", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xae", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x6cc", + "extraData": "0x", + "baseFeePerGas": "0x75", + "blockHash": "0x943b23302ffed329664d45fee15ca334c92aa6195b22cb44c7fdd5bdbbe4e7d4", + "transactions": [ + "0xf864818c768302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a024414367540c94b1bd3ce29dd0b4ee6bdece373f9417e96f0ef8d632e82c4ecba031dae9539e84f7351a5b92f1246dfd909dd5a383011fbd44bb8e87fb6870189b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd59cf5f55903ba577be835706b27d78a50cacb25271f35a5f57fcb88a3b576f3" + ] + }, + { + "jsonrpc": "2.0", + "id": "np175", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x943b23302ffed329664d45fee15ca334c92aa6195b22cb44c7fdd5bdbbe4e7d4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd70339e1158ecc97dc7db86b3177202ffa3dcba386fd52e54e6fe8b728003154", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xaf", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x6d6", + "extraData": "0x", + "baseFeePerGas": "0x67", + "blockHash": "0xd2a0fc154d0bb77b346c7bb3532d24581bc1a5b5bf9ced18b419a6309ff84351", + "transactions": [ + "0x02f86a870c72dd9d5e883e818d0168825208945a6e7a4754af8e7f47fc9493040d853e7b01e39d0180c001a08c62285d8318f84e669d3a135f99bbfe054422c48e44c5b9ce95891f87a37122a028e75a73707ee665c58ff54791b62bd43a79de1522918f4f13f00ed4bd82b71b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x551cced461be11efdeaf8e47f3a91bb66d532af7294c4461c8009c5833bdbf57" + ] + }, + { + "jsonrpc": "2.0", + "id": "np176", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xd2a0fc154d0bb77b346c7bb3532d24581bc1a5b5bf9ced18b419a6309ff84351", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x1bc27508b52de3a750cc928dd89954462b4e4dbfb60707442e60b4b23aabb816", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xb0", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x6e0", + "extraData": "0x", + "baseFeePerGas": "0x5b", + "blockHash": "0xe3072603b13de812d2c58ece96eeb4f32ff7e3e93c8b9121dd18f0682a750970", + "transactions": [], + "withdrawals": [ + { + "index": "0x13", + "validatorIndex": "0x5", + "address": "0xf4f97c88c409dcf3789b5b518da3f7d266c48806", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc1e0e6907a57eefd12f1f95d28967146c836d72d281e7609de23d0a02351e978" + ] + }, + { + "jsonrpc": "2.0", + "id": "np177", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe3072603b13de812d2c58ece96eeb4f32ff7e3e93c8b9121dd18f0682a750970", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x78497ebf1fbf03732772a8c96b2fe6902af5ab844e49f2685763b4366ce8ddf6", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xb1", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x6ea", + "extraData": "0x", + "baseFeePerGas": "0x50", + "blockHash": "0x996acbdde853cdc1e21426f4e53d07c09a13ed50798ee071582f24cc1014e238", + "transactions": [ + "0xf882818e51830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0fd5ea8b7df5c3ecd87220b8ad7d15198722d94a64b0e8e099c8c7384c1d08a33a039707925aba6dad8d06c162fd292df0bf03033b7b6d1204ae4be0ce6f487fa71" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9d580c0ac3a7f00fdc3b135b758ae7c80ab135e907793fcf9621a3a3023ca205" + ] + }, + { + "jsonrpc": "2.0", + "id": "np178", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x996acbdde853cdc1e21426f4e53d07c09a13ed50798ee071582f24cc1014e238", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x197c4166e7f8f68ee6965c87c8ce720bee776a7b7119870371e6262bc913468d", + "receiptsRoot": "0x7c66f99e4434aa19cdf8845c495068fa5be336b71978d6fa90966129f300218a", + "logsBloom": "0x00000000000000000000000000400200000000000000800000000000000000008000008000000000000000000000000000000000000000000040000004000041004000000000000000000000000800000000000000000000000000000000080100000000000000000000000020000000004200000000001000000002000000100008080200000004000000000000200000000000000010000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000800000000200000000000000000000100002000000000000000000002000000000000000000100000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xb2", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x6f4", + "extraData": "0x", + "baseFeePerGas": "0x47", + "blockHash": "0xf00d6a4f13579131abcd2c856040cf9295caed200698d7cf7a1574690b36b0bf", + "transactions": [ + "0xf879818f4883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a03e0f9aa0ca6ec8b4f9e7fccd9b710c0de4414618726e298b36816cd6d689a89aa07d3950b5ebbaa58f5c4e0bc0571499d9d58d563ce2c039664cf210815e43d0e5" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa7fd4dbac4bb62307ac7ad285ffa6a11ec679d950de2bd41839b8a846e239886" + ] + }, + { + "jsonrpc": "2.0", + "id": "np179", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf00d6a4f13579131abcd2c856040cf9295caed200698d7cf7a1574690b36b0bf", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf56610f73e08c2ccaaa314c23bc79022214919c02d450cab12975da3546b68fd", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xb3", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x6fe", + "extraData": "0x", + "baseFeePerGas": "0x3f", + "blockHash": "0x5711092388b2fd00bf4234aca7eede2bdc9329ea12e2777893d9001f4f2c8468", + "transactions": [ + "0xf8648190408302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0f41a67e92f032c43cc601daa205026cc5a97affb0f92064991122a1aa92428dfa0237053c462847907c840ada5076caab16adc071da181e9277926a310adcb8e3d" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x6ba7b0ac30a04e11a3116b43700d91359e6b06a49058e543198d4b21e75fb165" + ] + }, + { + "jsonrpc": "2.0", + "id": "np180", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5711092388b2fd00bf4234aca7eede2bdc9329ea12e2777893d9001f4f2c8468", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xfa57370da0cc72170d7838b8f8198b0ebd949e629ca3a09795b9c344dead4af5", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xb4", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x708", + "extraData": "0x", + "baseFeePerGas": "0x38", + "blockHash": "0xb58807a37c03cf3b0f1c9104cfd96f6cb02b1e08e0eecdd369cac48d0003b517", + "transactions": [ + "0xf8678191398252089427952171c7fcdf0ddc765ab4f4e1c537cb29e5e501808718e5bb3abd109fa076a045602a7de6b1414bdc881a321db0ce5255e878a65513bad6ac3b7f473aa7a01a33017b5bcf6e059de612293db8e62b4c4a3414a7ba057c08dd6172fb78a86c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x8835104ed35ffd4db64660b9049e1c0328e502fd4f3744749e69183677b8474b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np181", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb58807a37c03cf3b0f1c9104cfd96f6cb02b1e08e0eecdd369cac48d0003b517", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6d3f029e56f9ee3db9ed8f9156cd853fb1fcafe05475ec8c2a4dd337a5e3e20e", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xb5", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x712", + "extraData": "0x", + "baseFeePerGas": "0x32", + "blockHash": "0x56b5aa12ccfcbd86737fe279608cb7585fbc1e48ddfcdac859bb959f4d3aa92a", + "transactions": [], + "withdrawals": [ + { + "index": "0x14", + "validatorIndex": "0x5", + "address": "0x892f60b39450a0e770f00a836761c8e964fd7467", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x562f276b9f9ed46303e700c8863ad75fadff5fc8df27a90744ea04ad1fe8e801" + ] + }, + { + "jsonrpc": "2.0", + "id": "np182", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x56b5aa12ccfcbd86737fe279608cb7585fbc1e48ddfcdac859bb959f4d3aa92a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x58a6332c9e7b85155106515f20355c54bb03c6682024baa694cbaff31c3b84ff", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xb6", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x71c", + "extraData": "0x", + "baseFeePerGas": "0x2c", + "blockHash": "0xb0d7fbd46bd67d4c3fa51d0e1b1defaf69237d0f6e2049486c907b049b47e01c", + "transactions": [ + "0xf88281922d830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa01ae40537174a716b5f33d153e9251ae8c1d72852da25823f6d954b9dbc5740cca02ff07812990e0645cab5c9d89028f7255f50d0eee5bee334b3ba10d71485c421" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd19f68026d22ae0f60215cfe4a160986c60378f554c763651d872ed82ad69ebb" + ] + }, + { + "jsonrpc": "2.0", + "id": "np183", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb0d7fbd46bd67d4c3fa51d0e1b1defaf69237d0f6e2049486c907b049b47e01c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x55d4e87d040358926c84414b854fc47a75b9963df75e359a2182464c51201088", + "receiptsRoot": "0x1fccfe93768ce1ed60d0f83cbc8bef650cb1d056c35a4b233ae41a1b8219f92d", + "logsBloom": "0x00000080000000000000000000000000000000000000004000000000000010000000000000000000000000000000014000800000000000000100102000000000000000000000020000000000200000000000000000100000000000200000002000000000000000000000002000000000000000000000000000000000000000001000002000400020040000000000000200000000000000000000000000000000000000002000000000000000000000100000000000022000000000000000000000000000000004000000080000000000000000000000000004004000000000040002000040000000000000000000000000000000000000000000000000100000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xb7", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x726", + "extraData": "0x", + "baseFeePerGas": "0x27", + "blockHash": "0x66011454670d5664e8e555d01d612c70cadabfb6a4a317f375495ef3daa9d1b4", + "transactions": [ + "0xf87981932883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa02d1dcc844efba97a51917ab3d79f837680f42e2e76ab51b4b630cbe9a6e4e10ea03d3f624c82de14b23b0c5553621cc9a4c649cd856a616f5a91bad8bf0c0d1709" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf087a515b4b62d707991988eb912d082b85ecdd52effc9e8a1ddf15a74388860" + ] + }, + { + "jsonrpc": "2.0", + "id": "np184", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x66011454670d5664e8e555d01d612c70cadabfb6a4a317f375495ef3daa9d1b4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd3ec16ab633987e17a4e8c573014b1fc9919f004b3cb80da11280d1caad1fe3e", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xb8", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x730", + "extraData": "0x", + "baseFeePerGas": "0x23", + "blockHash": "0x36e1e3513460407c80dfcfab2d2826ea432dadb99aa7415f9cffcf56faf27f94", + "transactions": [ + "0xf8648194248302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a01e5301a3386e11893c0275367ac5d31fea88f31731e66ee769bfddc3486cff1aa0203dbf8bbfa9df2d635e1889d51e06611e8c2a769609908aeb5e97decb03b141" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf7e28b7daff5fad40ec1ef6a2b7e9066558126f62309a2ab0d0d775d892a06d6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np185", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x36e1e3513460407c80dfcfab2d2826ea432dadb99aa7415f9cffcf56faf27f94", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x692ddd6938f00a07474233619f579b30c1eaaef353a2b0cc24b47d7898aa5c49", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xb9", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x73a", + "extraData": "0x", + "baseFeePerGas": "0x1f", + "blockHash": "0x44e05b6820cf1d7cf9cd2148d6f71a6a649c9a829b861539d2c950f701e27260", + "transactions": [ + "0x02f86a870c72dd9d5e883e819501208252089404d6c0c946716aac894fc1653383543a91faab600180c080a0039c18634a9f085ba0cd63685a54ef8f5c5b648856382896c7b0812ee603cd8aa05ecfde61ea3757f59f0d8f0c77df00c0e68392eea1d8b76e726cb94fb5052b8a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x77361844a8f4dd2451e6218d336378b837ba3fab921709708655e3f1ea91a435" + ] + }, + { + "jsonrpc": "2.0", + "id": "np186", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x44e05b6820cf1d7cf9cd2148d6f71a6a649c9a829b861539d2c950f701e27260", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x9eac86abf4371646a564bb6df622644682e5de5bf01fed388ccaf10700e46e88", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xba", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x744", + "extraData": "0x", + "baseFeePerGas": "0x1c", + "blockHash": "0xcc3b1096f3ce63881c77751baec2048561baa2dc84ea0ef9d3a5515061aa74e0", + "transactions": [], + "withdrawals": [ + { + "index": "0x15", + "validatorIndex": "0x5", + "address": "0x281c93990bac2c69cf372c9a3b66c406c86cca82", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe3cb33c7b05692a6f25470fbd63ab9c986970190729fab43191379da38bc0d8c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np187", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xcc3b1096f3ce63881c77751baec2048561baa2dc84ea0ef9d3a5515061aa74e0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xff13e99ee95ffe82139758f33a816389654a5c73169b82983de9cf2f1f3dbd9f", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xbb", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x74e", + "extraData": "0x", + "baseFeePerGas": "0x19", + "blockHash": "0x871cb66f77db23f8e70541a647329c5ca9b6d40afd3950d48df4915f300e664a", + "transactions": [ + "0xf88281961a830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0276782d84f5f6ab0805be5e57923747bae9fa2b06ed4b45bcc364bdb4f09eca1a0484f9fc2a31a4b5f24ba33da54649e6a3261c0bee52d91576246bb54698c1535" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc893f9de119ec83fe37b178b5671d63448e9b5cde4de9a88cace3f52c2591194" + ] + }, + { + "jsonrpc": "2.0", + "id": "np188", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x871cb66f77db23f8e70541a647329c5ca9b6d40afd3950d48df4915f300e664a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x522c1eb0c4d1332668a2e3676efd54899579d85fd4e7007fd228702d9c964baa", + "receiptsRoot": "0x90d4e326daf1e15e41687f281f8e638992c4cdfbe590eb4956fd943aa39f1bba", + "logsBloom": "0x48000040000000000000000000004000000000000000000000400000000000002000000000000000000000000000000018000000000000000000002000000000000000000000100000002000000800000000000000000000000000002000000000000000000000000000000000000000040000020000040000000000000000000000000101000000000000000000010000000000040000000000000000000000008000000000000000000000800000000000201008000000000000001000000000000010000000000000100000000000000000000000040100000000000000000008000000000000000000000000000000000000004000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xbc", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x758", + "extraData": "0x", + "baseFeePerGas": "0x16", + "blockHash": "0x174a8681a0d28b9a3d49afb279714acb2bfe4a3abfe490522bb3d899d3c71c8d", + "transactions": [ + "0xf87981971783011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0870548904b9e901c294fd1c04a6cff92fbb40491e00a1ffcbc551c6c5eba2db3a0524ff53000a94b71aef3a2c516354bc5d7fdb3f236d4647020762a56d9bd2fbf" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x39c96a6461782ac2efbcb5aaac2e133079b86fb29cb5ea69b0101bdad684ef0d" + ] + }, + { + "jsonrpc": "2.0", + "id": "np189", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x174a8681a0d28b9a3d49afb279714acb2bfe4a3abfe490522bb3d899d3c71c8d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xfb0eecb29a002997c00e0f67a77d21dd4fa07f2db85e3e362af4bbfcb69b6c12", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xbd", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x762", + "extraData": "0x", + "baseFeePerGas": "0x14", + "blockHash": "0x1b56a73d407c9a5e222c2097149c2f2cbb480a70437ee41779974b8ab968a8e1", + "transactions": [ + "0xf8648198158302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0111d3d32f82c89fc830943a4aa0b20e013886491e06acede59ea4252b3366c05a07b9f9199ecdb210151db8a50c74fa1488b198db4e5dda3ad1fa003b70d9bd03a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x72a2724cdf77138638a109f691465e55d32759d3c044a6cb41ab091c574e3bdb" + ] + }, + { + "jsonrpc": "2.0", + "id": "np190", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1b56a73d407c9a5e222c2097149c2f2cbb480a70437ee41779974b8ab968a8e1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x36fce9409ec76cfda58bd4145be0289d761c81131ed0102347b96127fd0888e2", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xbe", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x76c", + "extraData": "0x", + "baseFeePerGas": "0x12", + "blockHash": "0x07d1571c1d0fbaf6cd5c2fa18e868d6dfc2aa56f7ee3bd5aaf61fa816d775ee9", + "transactions": [ + "0xf86781991382520894478508483cbb05defd7dcdac355dadf06282a6f201808718e5bb3abd109fa0910304dbb7d545a9c528785d26bf9e4c06d4c84fdb1b8d38bc6ee28f3db06178a02ffc39c46a66af7b3af96e1e016a62ca92fc5e7e6b9dbe631acbdc325b7230a1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x178ba15f24f0a8c33eed561d7927979c1215ddec20e1aef318db697ccfad0e03" + ] + }, + { + "jsonrpc": "2.0", + "id": "np191", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x07d1571c1d0fbaf6cd5c2fa18e868d6dfc2aa56f7ee3bd5aaf61fa816d775ee9", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd5d1fc871c3a4694da0e9a9f453c0e6f4c8f38fbef45db36c67cd354e22eb303", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xbf", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x776", + "extraData": "0x", + "baseFeePerGas": "0x10", + "blockHash": "0xda1708aede1e87f052ee6e9637f879462b613e4cbddacb18aa49907b55094ce4", + "transactions": [], + "withdrawals": [ + { + "index": "0x16", + "validatorIndex": "0x5", + "address": "0xb12dc850a3b0a3b79fc2255e175241ce20489fe4", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf7b2c01b7c625588c9596972fdebae61db89f0d0f2b21286d4c0fa76683ff946" + ] + }, + { + "jsonrpc": "2.0", + "id": "np192", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xda1708aede1e87f052ee6e9637f879462b613e4cbddacb18aa49907b55094ce4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4d19a2ce0d61642b6420c9f23ea32bb72ebe24384ed110394d7e5ca98589f055", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xc0", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x780", + "extraData": "0x", + "baseFeePerGas": "0xe", + "blockHash": "0x082079039cffbdf78a5cc86fddb47d96c888e0e90b092f9e0591e0099086cc45", + "transactions": [ + "0xf882819a0f830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa02356373d8d8ca7c15e547e717f7327ab0d803867cfabedf8d75e4d1cb264862ca011a3879ae15ab356e9558926382b7fa68b5c5a5c5b127b6f5176523dfe0ae986" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x16e43284b041a4086ad1cbab9283d4ad3e8cc7c3a162f60b3df5538344ecdf54" + ] + }, + { + "jsonrpc": "2.0", + "id": "np193", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x082079039cffbdf78a5cc86fddb47d96c888e0e90b092f9e0591e0099086cc45", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd67263b379522c5059bb0a7164b9cd3fa70697e4012b3b5c519ecf888dbc5700", + "receiptsRoot": "0xc1c820ad9bde8ce9524a7fa712d4849dc2f9f9553e8c00f1fe6c41323e31fbf7", + "logsBloom": "0x00000000000000000000000000000000000000000080000000000200000040000000000000000000000000000000000000000000000000000000001000000000000000000000000000001002000004020000000000000000000011000000000000000080000082000082080000000404000000080010000000000000000000000000100000010000000000000400000000000000000000000000000000400402000000000000000000000000000000000000000000200000000000002000000004000000400000002000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000800000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xc1", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x78a", + "extraData": "0x", + "baseFeePerGas": "0xd", + "blockHash": "0xe1207296a903bee61a02dd94d685640d76ab57ea96dd5789819583e35f2d7eb3", + "transactions": [ + "0xf879819b0e83011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa03423551e59962468cb263c416cb4025c462624b8c8c687177571976c345a8d20a0190d3ab5979e300998fc96429a75c50e1c195115cada83e01fb14a28f2e294de" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x0a98ea7f737e17706432eba283d50dde10891b49c3424d46918ed2b6af8ecf90" + ] + }, + { + "jsonrpc": "2.0", + "id": "np194", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe1207296a903bee61a02dd94d685640d76ab57ea96dd5789819583e35f2d7eb3", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2111d275b4901e864fcded894a9d9a046f9077d8f6c5af65a72c2243a32dbeaa", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xc2", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x794", + "extraData": "0x", + "baseFeePerGas": "0xc", + "blockHash": "0x8fd42cbdbbe1b8de72a5bb13684131e04572585077e0d61a0dfbb38d72ef309f", + "transactions": [ + "0xf864819c0d8302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0b4dac384ec258b1a752856b3fcda42244c3e648577bf52d74f25313b3327bf1ca02f7b54b9475768335aab1778fd7ec882f3adbc9e78d4d04a0b78e93e4d41a76b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7637225dd61f90c3cb05fae157272985993b34d6c369bfe8372720339fe4ffd2" + ] + }, + { + "jsonrpc": "2.0", + "id": "np195", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8fd42cbdbbe1b8de72a5bb13684131e04572585077e0d61a0dfbb38d72ef309f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2efd726637cb91156021ac4ae337a87f9a1f28efd620de55b77faef0d3b84b22", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xc3", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x79e", + "extraData": "0x", + "baseFeePerGas": "0xb", + "blockHash": "0x326484b702b3c743f907227c8aad8733b1a6b7fda510512fe4fec0380bfbc0f1", + "transactions": [ + "0x02f86a870c72dd9d5e883e819d010c82520894ae3f4619b0413d70d3004b9131c3752153074e450180c001a07cb73f8bf18eacc2c753098683a80208ac92089492d43bc0349e3ca458765c54a03bf3eb6da85497e7865d119fde3718cdac76e73109384a997000c0b153401677" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x6a7d064bc053c0f437707df7c36b820cca4a2e9653dd1761941af4070f5273b6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np196", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x326484b702b3c743f907227c8aad8733b1a6b7fda510512fe4fec0380bfbc0f1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xeba72457992e05a38b43a77a78ba648857cec13beb5412b632f6623521fe248d", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xc4", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x7a8", + "extraData": "0x", + "baseFeePerGas": "0xa", + "blockHash": "0x6a40d1d491a8624685fa20d913a684f691f1281da37059d527241526c965874d", + "transactions": [], + "withdrawals": [ + { + "index": "0x17", + "validatorIndex": "0x5", + "address": "0xd1211001882d2ce16a8553e449b6c8b7f71e6183", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x91c1e6eec8f7944fd6aafdce5477f45d4f6e29298c9ef628a59e441a5e071fae" + ] + }, + { + "jsonrpc": "2.0", + "id": "np197", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x6a40d1d491a8624685fa20d913a684f691f1281da37059d527241526c965874d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8713a1c42af83625ae9515312298d02425330b20a14b7040ec38f0655cb65317", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xc5", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x7b2", + "extraData": "0x", + "baseFeePerGas": "0x9", + "blockHash": "0x25702b83ea77e2ad219178c026a506fa7a9c3f625b023963bc9c13c0d5cfeb14", + "transactions": [ + "0xf882819e0a830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa02ed567eed3a763f56fe05c1e44575993df5b6cf67e093e0e9b5ec069ecaf76a2a04891e566e0d136b24d62ffe17f2bfaa0736a68f97b91e298b31897c790b2ed28" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa1c227db9bbd2e49934bef01cbb506dd1e1c0671a81aabb1f90a90025980a3c3" + ] + }, + { + "jsonrpc": "2.0", + "id": "np198", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x25702b83ea77e2ad219178c026a506fa7a9c3f625b023963bc9c13c0d5cfeb14", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2250f011d079600d76d5905dca93324f2fceb110390e8a7e7177569bd8ec73fd", + "receiptsRoot": "0x8027ec2e573bf62c00695cb9a0f67e28e4cce8dc44dc641d7388e4864d8ff78a", + "logsBloom": "0x00080000100000000000100000000840000000000000000000000000000000000000000000000000000000000000000000000000080080000080000000000000000000004000000000000000000000000000000000000000000000000000000200000000000000100100000000008001000000000000000000800000000000020010000000000000000000000000001000800000200000000000000000008000000000000000000000000000000000000000000000000000000000000001000000000000000000012000000000000000000040800000040004000000040000800001000000000000000000000000000000010000100000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xc6", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x7bc", + "extraData": "0x", + "baseFeePerGas": "0x8", + "blockHash": "0xa752bd3886362e9e5e57dba077628fedbfbca6b2a657df205ad20d739b035c22", + "transactions": [ + "0xf879819f0983011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0c362dc6d498fcbd0eab0518a012a348d87fe4f2e53f7843f350662c43258609ba026d83d49fd9654704da7435b3400713ed7909a7203d6c55b8d43dd1e9fe67226" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x8fcfc1af10f3e8671505afadfd459287ae98be634083b5a35a400cc9186694cf" + ] + }, + { + "jsonrpc": "2.0", + "id": "np199", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa752bd3886362e9e5e57dba077628fedbfbca6b2a657df205ad20d739b035c22", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa8faa1ccb44b8d8d3ad926bdcb75a9e9fd18fa77728ef12aa9c4ba7be1906d3f", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xc7", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x7c6", + "extraData": "0x", + "baseFeePerGas": "0x8", + "blockHash": "0x5d80c24a7a87ae0ab200b864029fbfe7bb750ba0a01c07191b7f52330d2c79ad", + "transactions": [ + "0xf86481a0098302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a08683c22fc25a5413b758a32c5a6515b1b055541ad523ae4159c4d04c3f864260a06c8f2e1e929e9df95158a161e793ae162e1e4297f8042bf9358dcc119f5545e5" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xcc1ea9c015bd3a6470669f85c5c13e42c1161fc79704143df347c4a621dff44f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np200", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5d80c24a7a87ae0ab200b864029fbfe7bb750ba0a01c07191b7f52330d2c79ad", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe4f7f192080fd853f053608561854cdb68eb8de9eda499fd7ad840ca729487d3", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xc8", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x7d0", + "extraData": "0x", + "baseFeePerGas": "0x8", + "blockHash": "0x0fd7e67081119b73ebe7ae0483ce2154a2dfb8c503545d231e2af1f8942406ae", + "transactions": [ + "0xf86781a109825208947c5bd2d144fdde498406edcb9fe60ce65b0dfa5f01808718e5bb3abd109fa015f510b05236b83a9370eb084e66272f93b4b646e225bdef016b01b3ac406391a03b4a2b683af1cb3ecae367c8a8e59c76c259ce2c5c5ffd1dc81de5066879e4b8" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb0a22c625dd0c6534e29bccc9ebf94a550736e2c68140b9afe3ddc7216f797de" + ] + }, + { + "jsonrpc": "2.0", + "id": "np201", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x0fd7e67081119b73ebe7ae0483ce2154a2dfb8c503545d231e2af1f8942406ae", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5188152524460d35f0c837dab28ac48f6aac93a75ecbb0bcb4af6a9c95e18a67", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xc9", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x7da", + "extraData": "0x", + "baseFeePerGas": "0x8", + "blockHash": "0x3043a03ed3369ba0dfdddac07cae4ca805dbbb0b411b3f5dd5e66198928a715b", + "transactions": [], + "withdrawals": [ + { + "index": "0x18", + "validatorIndex": "0x5", + "address": "0x4fb733bedb74fec8d65bedf056b935189a289e92", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x92b8e6ca20622e5fd91a8f58d0d4faaf7be48a53ea262e963bcf26a1698f9df3" + ] + }, + { + "jsonrpc": "2.0", + "id": "np202", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x3043a03ed3369ba0dfdddac07cae4ca805dbbb0b411b3f5dd5e66198928a715b", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x09f47830b792bc39aa6b0c12b7024fa34d561ff9e0d32c27eab5127239799bb0", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xca", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x7e4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x9178b45b38e39c3e3f4bc590a301254543eedb5b146bed0900465b194aaf94e8", + "transactions": [ + "0xf88281a208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a03b50dfd68a93199762b4b47c08ca4c9f67d99e772f3fec9843a4e1c3ae4d6963a070a7b2cc31e53de9d1fa14f55f28b212979bd83bbd9e9097e65845e05a9ee40f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf6253b8e2f31df6ca7a97086c3b4d49d9cbbbdfc5be731b0c3040a4381161c53" + ] + }, + { + "jsonrpc": "2.0", + "id": "np203", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x9178b45b38e39c3e3f4bc590a301254543eedb5b146bed0900465b194aaf94e8", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6a0d6e0a749247b4271d54ddfd2732ceb5b377c1db1ac40aa1d2339d3a143aaa", + "receiptsRoot": "0x189141497b4062bfbe61a7fb2f96cc8a95543e38c077c9150b740f8d01a313a8", + "logsBloom": "0x00000000000000000000040040000000000000000000080000000000004000000000000000004000000000008000000000000000000080000002008001000000000000000010000000000080000000000000000000200000002000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000020000000000000000000000800040000000000000000400000000000000000400001000000004000001000000000020000000000010000000000000000000000000000000000000000008000000000010000100000000000000001000000010000000000000800000000000000202000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xcb", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x7ee", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x9a575aa75a5f08a27533140141ffc7ed7d6e981da97316baf296dd1f8d1007d7", + "transactions": [ + "0xf87881a30883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109f9f3c7a9aedd154caa41f602593b4bc78db1101336a81095174d4487dd8338878a0458e45144a4d1a634950ae79ac251065204776baa96a3f94c6d71a00323fe9b4" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xea8d762903bd24b80037d7ffe80019a086398608ead66208c18f0a5778620e67" + ] + }, + { + "jsonrpc": "2.0", + "id": "np204", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x9a575aa75a5f08a27533140141ffc7ed7d6e981da97316baf296dd1f8d1007d7", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xda96365c5a33f358ed732463139254c4f186e899ad00b05d9a30ff39d4d1a27d", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xcc", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x7f8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb35f9d9c454a03adc1eeeaa9fef20caeb8f9445663a4768d18bc0bc1790650b1", + "transactions": [ + "0xf86481a4088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0a82c39f1be580d16334c133165d5ceb8d9942b184ecccea09e73ff45120ac523a04432d6958bb18882f9f07e851abe454039a5b38d61fd975c7da486a834107204" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x543382975e955588ba19809cfe126ea15dc43c0bfe6a43d861d7ad40eac2c2f4" + ] + }, + { + "jsonrpc": "2.0", + "id": "np205", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb35f9d9c454a03adc1eeeaa9fef20caeb8f9445663a4768d18bc0bc1790650b1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2258c0e37e5bedab21f7ea2f65190d1d51f781743653168d02181c8f16246c71", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xcd", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x802", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x97f4a4e64ede52b5dfd694236e783d130206d111cf6a5eb83a3bb9a230dfd952", + "transactions": [ + "0x02f86a870c72dd9d5e883e81a50108825208949a7b7b3a5d50781b4f4768cd7ce223168f6b449b0180c080a04f3e818870a240e585d8990561b00ad3538cf64a189d0f5703a9431bc8fd5f25a0312f64dd9ab223877e94c71d83cb3e7fe359b96250d6a3c7253238979dd2f32a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x095294f7fe3eb90cf23b3127d40842f61b85da2f48f71234fb94d957d865a8a2" + ] + }, + { + "jsonrpc": "2.0", + "id": "np206", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x97f4a4e64ede52b5dfd694236e783d130206d111cf6a5eb83a3bb9a230dfd952", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xda2ecb481078839fd39c044b3fceae6468338266d9572da0f2281e58b9596914", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xce", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x80c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb3c2c9a5de90f0637203e60288b50ecb21d17a2437cccf553d2424321fa112d4", + "transactions": [], + "withdrawals": [ + { + "index": "0x19", + "validatorIndex": "0x5", + "address": "0xc337ded6f56c07205fb7b391654d7d463c9e0c72", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x144c2dd25fd12003ccd2678d69d30245b0222ce2d2bfead687931a7f6688482f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np207", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb3c2c9a5de90f0637203e60288b50ecb21d17a2437cccf553d2424321fa112d4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf0f20309e2cec2fb6af448c58c40e206b788241bb88e62a8e7479aadc6bfa94e", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xcf", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x816", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x9e63e1a7df1b726901e3139cfb429592ef8d2107aa566bcae5f3b8e21f99f0da", + "transactions": [ + "0xf88281a608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa079aa26a33abe2e9504cfc6552c6b39434478b081f5cbbb613269d64980edaf93a079ffe44aec63b05644681b948ea0e5a996e106f3e074a90991c963ff3e7a8aa6" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7295f7d57a3547b191f55951f548479cbb9a60b47ba38beb8d85c4ccf0e4ae4c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np208", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x9e63e1a7df1b726901e3139cfb429592ef8d2107aa566bcae5f3b8e21f99f0da", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x177fc88f477d3dd466f7cac43b50d4b2b77fd468ef479177ed562d2401acd6c0", + "receiptsRoot": "0xd1458a51a7ca8d2c87390d85d986956f392bdd634ffbe4d5a7e2b09a142ce514", + "logsBloom": "0x00200000000000000000000000000000000000000400000000400000000000000000100400000000000000000010108000000000000000000000200800000000000004000000000000000002000000000000000000000000000000020002000408000021000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000010000000000002000000000000000000000400000000000000000000020000000000000000000000800000000000080000000000000000000000800000810002000000000400000000000000000000000000000000000000000000020000000000000000000000010080000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xd0", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x820", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x4e8e6e31a8922b68a96992288e49ab9716dd37f1da1ae5b22391bc62d61ac75a", + "transactions": [ + "0xf87981a70883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0969f6d3d90ca6b62cbda31ed28b7522b297d847e9aa41e0eae0b9f70c9de1e01a0274e038abf0b9f2fba70485f52e4566901af94c9645b22a46b19aebb53b4c25d" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9e8e241e13f76a4e6d777a2dc64072de4737ac39272bb4987bcecbf60739ccf4" + ] + }, + { + "jsonrpc": "2.0", + "id": "np209", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x4e8e6e31a8922b68a96992288e49ab9716dd37f1da1ae5b22391bc62d61ac75a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x55980d8ac0e8bfd779b40795a6d125a712db70daa937ace1f22a5fcd5fd2dfa6", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xd1", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x82a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xaf3e413fc388e1a5508f683df5806fe31d29f5df4552ccf2d6c6662816fae5fd", + "transactions": [ + "0xf86481a8088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa04973c2a9d2fcff13428e8a3b3f0979185222cad34366777db8dfc6438cdac357a0128ad521391c000e18211ad8ffa45b41962fca43be83a50ce299d3bd4407f44b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xfc753bcea3e720490efded4853ef1a1924665883de46c21039ec43e371e96bb9" + ] + }, + { + "jsonrpc": "2.0", + "id": "np210", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xaf3e413fc388e1a5508f683df5806fe31d29f5df4552ccf2d6c6662816fae5fd", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x522d0f5f8de1ef5b02ad61a3bff28c2bd0ce74abca03116e21f8af6e564d7fd2", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xd2", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x834", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa5b31d7aaa42b7be0c35a0fa375718d25441f90296550c10325a3e0f4d63217c", + "transactions": [ + "0xf86781a9088252089485f97e04d754c81dac21f0ce857adc81170d08c601808718e5bb3abd109fa0547e9550b5c687a2eb89c66ea85e7cd06aa776edd3b6e3e696676e22a90382b0a028cb3ab4ef2761a5b530f4e05ef50e5fc957cfbc0342f98b04aa2882eec906b2" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x5f5204c264b5967682836ed773aee0ea209840fe628fd1c8d61702c416b427ca" + ] + }, + { + "jsonrpc": "2.0", + "id": "np211", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa5b31d7aaa42b7be0c35a0fa375718d25441f90296550c10325a3e0f4d63217c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xda7dd7f5babcf1b3c407e141b4ea76932922489f13265a468fb6ab88891ff588", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xd3", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x83e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa1ffa80abb4f7f92b3932aa0ca90de5bb4a2908866b3d6727b05d5d41139e003", + "transactions": [], + "withdrawals": [ + { + "index": "0x1a", + "validatorIndex": "0x5", + "address": "0x28969cdfa74a12c82f3bad960b0b000aca2ac329", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x5ba9a0326069e000b65b759236f46e54a0e052f379a876d242740c24f6c47aed" + ] + }, + { + "jsonrpc": "2.0", + "id": "np212", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa1ffa80abb4f7f92b3932aa0ca90de5bb4a2908866b3d6727b05d5d41139e003", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb6724f1d73bee909624707836e66ffbb21b568dd5bd697668ce18a4ae31818a4", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xd4", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x848", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x9a77bcf7bf0d7e6cebeb8c60b4c36538b4fab0e633b9683ba589981c293a009c", + "transactions": [ + "0xf88281aa08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0e582e9d64ed6f95da074eaeb70ca1e47e8627bb7cd4e34d5aab01ff49ee6dd90a022cc32cc7c3030b0b47f1f69911311acd2ae3e95f19f766b69ebb67804676262" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb40e9621d5634cd21f70274c345704af2e060c5befaeb2df109a78c7638167c2" + ] + }, + { + "jsonrpc": "2.0", + "id": "np213", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x9a77bcf7bf0d7e6cebeb8c60b4c36538b4fab0e633b9683ba589981c293a009c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6a1c08c9dfcc48c37e349407f37f9c10d9d4c4b1d6c28d30af2630679c74ea96", + "receiptsRoot": "0x730ab6f592da8dfc7815bcba110f6de8dd0343aa932f55b589ff99d83b9ec358", + "logsBloom": "0x00000000000000000000000000000000000000200000000400000000000000002008008000100100000800000000000020000000000000000000000000000000800001000000002000000000000000800000010000000000000000420008000004000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000004000000000000008000000000000000000000000000000000000000000000000000000400010000000000004000000000000008000000840000000000000000040000000000000000000000000000120000001000000100000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xd5", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x852", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xecb42acc218101eb9c6d883a333d07c7736d7ed0b233f3730f5b9c9a75314cf5", + "transactions": [ + "0xf87981ab0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a08747d48d3358eb47195c17f67f22af5eca1177fba591b82b8b626058a347b2e5a0420e02657efee51f73f95017b354b1bca2850269a5de7b307a280c63830f3333" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x70e26b74456e6fea452e04f8144be099b0af0e279febdff17dd4cdf9281e12a7" + ] + }, + { + "jsonrpc": "2.0", + "id": "np214", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xecb42acc218101eb9c6d883a333d07c7736d7ed0b233f3730f5b9c9a75314cf5", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x67ca707e9bd81330c2fb9060e88ce0b0905c85c9be26ae4779874f3892ebab0c", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xd6", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x85c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x1191fbb4f2692461fc0ae4aa7141a1743a345c101dc9db157bc7ad3072fe1e9d", + "transactions": [ + "0xf86481ac088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a03752a40997c9b7b9c5dfd48f88990ddc727517540c403dadcb7476b8a4a9d4f6a0780178975646114017be4b06fae0689a979a45166f810604f76934239b0a2b9e" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x43d7158f48fb1f124b2962dff613c5b4b8ea415967f2b528af6e7ae280d658e5" + ] + }, + { + "jsonrpc": "2.0", + "id": "np215", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1191fbb4f2692461fc0ae4aa7141a1743a345c101dc9db157bc7ad3072fe1e9d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3b5ca86f1650f79fb42d74e523dc4e631989a3175023ced9a239e9bcc2c15a8e", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xd7", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x866", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x9b9e271d571b730c9e6acd133c99eba1ccd8b8174ffe080540fc3b1a5625943a", + "transactions": [ + "0x02f869870c72dd9d5e883e81ad010882520894414a21e525a759e3ffeb22556be6348a92d5a13e0180c001a0047b3309af68dd86089494d30d3356a69a33aa30945e1f52a924298f3167ab669fb8b7bd6670a8bbcb89555528ff5719165363988aad1905a90a26c02633f8b9" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb50b2b14efba477dddca9682df1eafc66a9811c9c5bd1ae796abbef27ba14eb4" + ] + }, + { + "jsonrpc": "2.0", + "id": "np216", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x9b9e271d571b730c9e6acd133c99eba1ccd8b8174ffe080540fc3b1a5625943a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc1ab95016db7b79d93ee0303af69ce00bdb090d39e20a739d280beb3e301c9d5", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xd8", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x870", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x4e27c497c83c3d06d4b209e7d5068920d7e22bb3c959daa4be5485d6ab0cce54", + "transactions": [], + "withdrawals": [ + { + "index": "0x1b", + "validatorIndex": "0x5", + "address": "0xaf193a8cdcd0e3fb39e71147e59efa5cad40763d", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc14936902147e9a121121f424ecd4d90313ce7fc603f3922cebb7d628ab2c8dd" + ] + }, + { + "jsonrpc": "2.0", + "id": "np217", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x4e27c497c83c3d06d4b209e7d5068920d7e22bb3c959daa4be5485d6ab0cce54", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf8c17319b995ce543f9ace79aab7f7c928b36facae4e6e0dd50991f95bed1542", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xd9", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x87a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xdf05b4a3aff6236d0d3c1ee058b874309c37005a2bbb41a37432b470ed49e678", + "transactions": [ + "0xf88281ae08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa05a8e9e2a3556016a65d5b99849bd44cd6ab17cfb15d7850356c9b491357f0611a01f7d3c43fe1759b4ec768275e918e12dae75db56a5d2140d1403ef3df41f56df" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x86609ed192561602f181a9833573213eb7077ee69d65107fa94f657f33b144d2" + ] + }, + { + "jsonrpc": "2.0", + "id": "np218", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xdf05b4a3aff6236d0d3c1ee058b874309c37005a2bbb41a37432b470ed49e678", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x05bd7288ee80780a92b234fec2f8bb5bb4d0425721ddbf89d866c62b288f6bff", + "receiptsRoot": "0xbebbd614564d81a64e904001523ad2e17a94b946d6dfc779928ec9048cf9a3f7", + "logsBloom": "0x40000000000020000000000040000000000000000000001000000000000000021000000000004008000000000002000001000100000000000000002000000000000400000000000008002000000000000000100000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000100000400000000000000000000000000000000000000000000000000000020000000000010040000002000000000000000000000000000000000000000000000000020000000000200000000000200000000000000011000000000201400000000000001000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xda", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x884", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf9a9d8409219172c2a602cfb9eadffdeb13a68c55a48e048a19c3b17d85e3b46", + "transactions": [ + "0xf87981af0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0b3a49ddc2fc9f12cb1dc0a67623d5a1a6a1b5bf59a8f1736c9f0ab3b564250d3a05fc1ca6dab6b9337827afb55342af8a51fae064157e9c78b76dacd66bbea55d1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x0a71a6dbc360e176a0f665787ed3e092541c655024d0b136a04ceedf572c57c5" + ] + }, + { + "jsonrpc": "2.0", + "id": "np219", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf9a9d8409219172c2a602cfb9eadffdeb13a68c55a48e048a19c3b17d85e3b46", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8236fb6bc66022c43d12c08612fd031d8b42852bef9a2dec04c1bc4b83cba489", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xdb", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x88e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf69983460a4d977eceea022607df6db15b3d8103f78e58d73eeac3593053dbc6", + "transactions": [ + "0xf86481b0088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa08a2bbd86fd1bb42e548fa4b4c4710f6c6ed03b4700f9e3a213bc70d17f016a3ca076d8bf736d722af615228680c31acd9815b9380a8bc5895cddb2361170274a7f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa4bcbab632ddd52cb85f039e48c111a521e8944b9bdbaf79dd7c80b20221e4d6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np220", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf69983460a4d977eceea022607df6db15b3d8103f78e58d73eeac3593053dbc6", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x1d9d412ef451097aa53e4fc8f67393acfd520382a1c4cfa6c99e2fb180a661db", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xdc", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x898", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xecea5d6aa092dc29520fcd6cd44102c571c415fd5d641e978af4933c476020a6", + "transactions": [ + "0xf86781b10882520894fb95aa98d6e6c5827a57ec17b978d647fcc01d9801808718e5bb3abd10a0a0c71a69f756a2ef145f1fb1c9b009ff10af72ba0ee80ce59269708f917878bfb0a03bfe6a6c41b3fe72e8e12c2927ee5df6d3d37bd94346a2398d4fcf80e1028dde" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x2bc468eab4fad397f9136f80179729b54caa2cb47c06b0695aab85cf9813620d" + ] + }, + { + "jsonrpc": "2.0", + "id": "np221", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xecea5d6aa092dc29520fcd6cd44102c571c415fd5d641e978af4933c476020a6", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x830dfc2fb9acb72d3c03a6181b026becbcdca1abf4ab584b2dd00c48fd2f6a62", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xdd", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x8a2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xbc664826810922530f7e9876cd57ef0185f2f5f9bbafb8ee9f6db2d6e67be311", + "transactions": [], + "withdrawals": [ + { + "index": "0x1c", + "validatorIndex": "0x5", + "address": "0x2795044ce0f83f718bc79c5f2add1e52521978df", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xfc7f9a432e6fd69aaf025f64a326ab7221311147dd99d558633579a4d8a0667b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np222", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xbc664826810922530f7e9876cd57ef0185f2f5f9bbafb8ee9f6db2d6e67be311", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x7492f26a06f6b66d802f0ac93de1640ec7001652e4f9498afa5d279c1c405ccd", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xde", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x8ac", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb908ac3bd269a873b62219e78d5f36fdfd6fb7c9393ad50c624b4e8fd045b794", + "transactions": [ + "0xf88281b208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa09765f880d3815c484a796d3fd4c1791ab32f501ba8167bfd55cde417b868e459a0310fdd4d8d953cf38b27fa32ad6e8922ef0d5bd7ba3e61539dd18942669187f1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x949613bd67fb0a68cf58a22e60e7b9b2ccbabb60d1d58c64c15e27a9dec2fb35" + ] + }, + { + "jsonrpc": "2.0", + "id": "np223", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb908ac3bd269a873b62219e78d5f36fdfd6fb7c9393ad50c624b4e8fd045b794", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x1e515c524c17bcb3f8a1e8bd65c8403ae534c5c2c2fc0bddce2e69942c57028a", + "receiptsRoot": "0x336f567c728ef05cbd3f71c4a9e9195b8e9cd61f8f040fdd6583daf0580a0551", + "logsBloom": "0x00000000000000000000000000000000000000000000400000000000002000080000080000000000000000000000000000000000000000000000000000000000008000000040000030040000000000800000000006000000000008010001000000004000000000000020000000000000000000000000000000000000040000000400080000000000000000020000000000000040000020000000000000000000000020000001000000000000000000000000100000000000010000000000000001000000000000000000000002000000000000800000000000000200000000000000000000000000000000000020000000000000000000001000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xdf", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x8b6", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x2e8980e0390ae8503a42316b0e8ceb3bbe99245131ab69115f2b5555d4ac1f4e", + "transactions": [ + "0xf87981b30883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0cdd0a69ca9a6c3977ae1734d40175aa0720a866ff9353ce4aadfd8a4cd762e53a0290a5ac57e2f318959aaadec811bf9f8017191594476415923ddafef9a25de7c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x289ddb1aee772ad60043ecf17a882c36a988101af91ac177954862e62012fc0e" + ] + }, + { + "jsonrpc": "2.0", + "id": "np224", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x2e8980e0390ae8503a42316b0e8ceb3bbe99245131ab69115f2b5555d4ac1f4e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa0a11d77a69e2c62b3cc952c07b650c8f13be0d6860ddf5ba26ef560cefd2000", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xe0", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x8c0", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xcd2f03e81d096f1c361b6b0a1d28ae2c0ec1d42a90909026754f3759717a65db", + "transactions": [ + "0xf86481b4088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0e55768f282e2db5f2e48da696a07d1bff5687ca7fa5941800d02a1c49a4781b4a00eb30d56234ac991413000037e0f7fb87c8c08b88ae75aa33cb316714b638e1b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xbfa48b05faa1a2ee14b3eaed0b75f0d265686b6ce3f2b7fa051b8dc98bc23d6a" + ] + }, + { + "jsonrpc": "2.0", + "id": "np225", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xcd2f03e81d096f1c361b6b0a1d28ae2c0ec1d42a90909026754f3759717a65db", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd8287e5675676595007edfbfff082b9f6f86f21bb0371e336ca22e12c6218f68", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xe1", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x8ca", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x2a2c4240cf6512959534cdaf586119243f718b4ff992ad851a61211a1ea744d8", + "transactions": [ + "0x02f86a870c72dd9d5e883e81b5010882520894f031efa58744e97a34555ca98621d4e8a52ceb5f0180c001a099b1b125ecb6df9a13deec5397266d4f19f7b87e067ef95a2bc8aba7b9822348a056e2ee0d8be47d342fe36c22d4a9be2f26136dba3bd79fa6fe47900e93e40bf3" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7bf49590a866893dc77444d89717942e09acc299eea972e8a7908e9d694a1150" + ] + }, + { + "jsonrpc": "2.0", + "id": "np226", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x2a2c4240cf6512959534cdaf586119243f718b4ff992ad851a61211a1ea744d8", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x1e3c75d8db0bd225181cc77b2ec19c7033a35ba033f036a97ba8b683d57d0909", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xe2", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x8d4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x1fb4e86909057635bfe8d130d4d606c1e9a32bd5e8da002df510861246633a96", + "transactions": [], + "withdrawals": [ + { + "index": "0x1d", + "validatorIndex": "0x5", + "address": "0x30a5bfa58e128af9e5a4955725d8ad26d4d574a5", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x992f76aee242737eb21f14b65827f3ebc42524fb422b17f414f33c35a24092db" + ] + }, + { + "jsonrpc": "2.0", + "id": "np227", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1fb4e86909057635bfe8d130d4d606c1e9a32bd5e8da002df510861246633a96", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xacca3ad17c81310c870a9cf0df50479973bd92ade4a46b61a2012fa87c7b8a0f", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xe3", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x8de", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x6f03c5c20de46ba707f29a6219e4902bc719b5f9e700c9182d76345fa8b86177", + "transactions": [ + "0xf88281b608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa05147ab82c47d0c6f6298c21b54a83bc404088dcf119f5719034a1154f2c69acaa035070fffcba987b70efcfc6efbf5a43974de5e11331879bbfbfe7556915da7b2" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xda6e4f935d966e90dffc6ac0f6d137d9e9c97d65396627e5486d0089b94076fa" + ] + }, + { + "jsonrpc": "2.0", + "id": "np228", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x6f03c5c20de46ba707f29a6219e4902bc719b5f9e700c9182d76345fa8b86177", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0468ebde6657b86a2f1561ae8ef57c6cbe23b7dc08cc0ad823ea3831388e1691", + "receiptsRoot": "0x591e45121efd9a319ad048f68a35db27c69b829a65d0c7817224a1c5071ab327", + "logsBloom": "0x00000005000000000010000000080000000000000000000000000008200000004000002080000001000000000000000000010000000000080000000000000000000000000000800000000000000000000000000000000000000000000000000b00000200000000000000000000200000000000200001400000000000100000000000000000000400000000000000000000000000000000000000000000000000000002008000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000020000000010000080000000000000114000000000000000000000040000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xe4", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x8e8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x8c7ac6681ed2a5020837149f8953a2762227b7bb41f2f46bc0c33508190c3e72", + "transactions": [ + "0xf87981b70883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa04ccccf5fa7c7ed5b48d30bee3e8b61c99f8ff9ddecff89747e5685b059d70fa7a042982d8d2a54f9a055fd75df65488462a0ceae67b8a80966427c5d7ea1cf563b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x65467514ed80f25b299dcf74fb74e21e9bb929832a349711cf327c2f8b60b57f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np229", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8c7ac6681ed2a5020837149f8953a2762227b7bb41f2f46bc0c33508190c3e72", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x9f5936ddc444db8ba3787be50038f195ddb86663f39b62d556f7700334f441d1", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xe5", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x8f2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x01bb09f016e5dfda9ef7170f45fe4b648dd3761b26c83c18bb0eea828bbc8663", + "transactions": [ + "0xf86481b8088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa07cc4f254afaef8c4953d8a30221c41a50b92629846448a90a62ebdc76de8b2eea073f46d5c867c718486a68dfdf1cd471d65caa8a2495faba0f0a19ca704201e1b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xcc2ac03d7a26ff16c990c5f67fa03dabda95641a988deec72ed2fe38c0f289d6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np230", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x01bb09f016e5dfda9ef7170f45fe4b648dd3761b26c83c18bb0eea828bbc8663", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x57dc2fdfe5e59055a9effb9660cfc7af5e87d25a03c9f90ce99ee320996a1991", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xe6", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x8fc", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x15d41d78de758ec47434a48dc695897705ad5990ac584d2a51d8b7a51419abe0", + "transactions": [ + "0xf86781b908825208940a3aaee7ccfb1a64f6d7bcd46657c27cb1f4569a01808718e5bb3abd109fa0d2aa10777b7c398921921258eeecaff46668278fd6f814ea4edb06f2a1076353a0542ef4ed484a1403494238e418bb8d613012871710e72dde77bb1fa877f1fae3" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x096dbe9a0190c6badf79de3747abfd4d5eda3ab95b439922cae7ec0cfcd79290" + ] + }, + { + "jsonrpc": "2.0", + "id": "np231", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x15d41d78de758ec47434a48dc695897705ad5990ac584d2a51d8b7a51419abe0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc6a8588f5fa71465604ccee5244d5c72a296994fb2bf1be478b664bc2aa77c39", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xe7", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x906", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa3b4d9f55cfc3ed49c694fa2a634b73f397d5847b73b340d123b2111ba5adc71", + "transactions": [], + "withdrawals": [ + { + "index": "0x1e", + "validatorIndex": "0x5", + "address": "0xd0752b60adb148ca0b3b4d2591874e2dabd34637", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x0c659c769744094f60332ec247799d7ed5ae311d5738daa5dcead3f47ca7a8a2" + ] + }, + { + "jsonrpc": "2.0", + "id": "np232", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa3b4d9f55cfc3ed49c694fa2a634b73f397d5847b73b340d123b2111ba5adc71", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x38cdb4e70eb9771bab194d9310b56dbfcba5d9912cd827406fff94bddf8549d3", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xe8", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x910", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x4ff6fbd3afcc33972501397c65fe211d7f0bf85a3bde8b31e4b6836375d09098", + "transactions": [ + "0xf88281ba08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a06f8db09016d87e96d45d0835a60822fb305336ab1d792944f6f0aa909b73c9d7a01da7c6ba739bf780143672031e860f222149e1e6314171737fee23537a1e7f0c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9cb8a0d41ede6b951c29182422db215e22aedfa1a3549cd27b960a768f6ed522" + ] + }, + { + "jsonrpc": "2.0", + "id": "np233", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x4ff6fbd3afcc33972501397c65fe211d7f0bf85a3bde8b31e4b6836375d09098", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xbf1db5dc400fd491fad1abd61287f081ebd7398c76f20ecc0a6c9afb30ba5508", + "receiptsRoot": "0xed257fe243a1ffa922e5a62e40ffb504d403afc1d870fdcacd7f0aaf714e9ca1", + "logsBloom": "0x200000000000000000000000000000000000000000000000000009000000800000000000104010000000000000000000000000000000000000000100000000080008000000000000000000800000000000000000000000000008000000000000400000000000004040000000000000000000002000000000080004010000000000000000100000000000000000000040000000000000000000000080010000000000000002000000020c0000000000000000000000000000000000000000000000000000000000000000000000000000080000000080000000000000000000000000400080000000400000000000080000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xe9", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x91a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf1364f41ffcf3f76e045b1634e4f62db38f5c053edfa7d0a13d87299896ddff9", + "transactions": [ + "0xf87981bb0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0ef0e59c1798c0a7645f75f893cf81eae4aff9f49159b7365b8d4e907367f91f6a0095a58cb4d8be1816acf8b4e11f9d9b2a03d3f392eee1f19bea70b50ed151584" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x2510f8256a020f4735e2be224e3bc3e8c14e56f7588315f069630fe24ce2fa26" + ] + }, + { + "jsonrpc": "2.0", + "id": "np234", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf1364f41ffcf3f76e045b1634e4f62db38f5c053edfa7d0a13d87299896ddff9", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5b75a7bfd5eb4c649cb36b69c5ccf86fecb002188d9e0f36c0fdbc8a160e4ac6", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xea", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x924", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa2474d57b356b865a29ccfb79623d9a34ed84db9f056da5dd4e963f816baa180", + "transactions": [ + "0xf86481bc088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a078dfab2121885d4181d63c7088757f7feb65131b155ad74541de35c055c31ec3a005cccd843ec8a535a567451c3b5034e05bac10f9328c63aa0b4893ee4f910ba2" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x2d3deb2385a2d230512707ece0bc6098ea788e3d5debb3911abe9a710dd332ea" + ] + }, + { + "jsonrpc": "2.0", + "id": "np235", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa2474d57b356b865a29ccfb79623d9a34ed84db9f056da5dd4e963f816baa180", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3f130c3409ad205204d14e6b5be4ccf2e65559d39cc98dfc265e1436990e5964", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xeb", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x92e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x9a195498e43997a5769957e54f0fa6f56d8442e54f8a26efafbf89130446fd4d", + "transactions": [ + "0x02f86a870c72dd9d5e883e81bd010882520894f8d20e598df20877e4d826246fc31ffb4615cbc00180c001a0c982933a25dd67a6d0b714f50be154f841a72970b3ed52d0d12c143e6a273350a07a9635960c75551def5d050beee4014e4fef2353c39d300e649c199eebc8fd5e" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x1cec4b230f3bccfff7ca197c4a35cb5b95ff7785d064be3628235971b7aff27c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np236", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x9a195498e43997a5769957e54f0fa6f56d8442e54f8a26efafbf89130446fd4d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x1ad56a036d6b544ee8f96f2d3e72dfdb360fa3c81edef33dd9e9fc1779d174a4", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xec", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x938", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xca48eaf8da077241a7938435cf1576b2628c65afea7b1aa2665c74573e352205", + "transactions": [], + "withdrawals": [ + { + "index": "0x1f", + "validatorIndex": "0x5", + "address": "0x45f83d17e10b34fca01eb8f4454dac34a777d940", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x18e4a4238d43929180c7a626ae6f8c87a88d723b661549f2f76ff51726833598" + ] + }, + { + "jsonrpc": "2.0", + "id": "np237", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xca48eaf8da077241a7938435cf1576b2628c65afea7b1aa2665c74573e352205", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf43c19d64439e20deb920de4efbb248d44d4f43d0dfecd11350501bc1a4bf240", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xed", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x942", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xd6a5ae0ebd55680da60432b756f7914f8fb8bbcead368348e3b7f07c8cfa501e", + "transactions": [ + "0xf88281be08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa06ba7d56fdfaf77a1a66bfef9529419b73d68fc1aa9edef961ac3a8898f04e5caa054635ee7b91858d97e66944311c81fd4f57d328ee4fbdf8ce730633909a75f01" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x700e1755641a437c8dc888df24a5d80f80f9eaa0d17ddab17db4eb364432a1f5" + ] + }, + { + "jsonrpc": "2.0", + "id": "np238", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xd6a5ae0ebd55680da60432b756f7914f8fb8bbcead368348e3b7f07c8cfa501e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x05a62e01803a967ff89e7e9febf8d50b1b3092aab5580c7f85f465e7d70fef3f", + "receiptsRoot": "0x294eca38bb21bd8afeb2e5f59d0d4625058d237e2109428dfb41b97138478318", + "logsBloom": "0x00000040000000001000000000008000000000000000200000000000000000000000008000000000000000020000000040000000000000000000000004000000000000000000800000000000000000000000000000000080000000000000001000000000400000000400000000000000000000000000000000000000000000000880000000000400002000000040000000000000000000000000000810000100000080000080000000000000080000000000000001000000000000000000000000000000080000002000000000000010000000000000000010000000000000002000000000800000000400000000000000000000080000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xee", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x94c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x06466f86e40c982578b247579fa1fa5773d6169e77a79a625950c4aa16ce88b1", + "transactions": [ + "0xf87981bf0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0548377761079f73162f83bdc2cfb09dcde9e08c8db66d4d983f1856c5145fe6fa06b2bd1223fbb1b72016150f57bc7ae1f8cce5c0fd301bb9216bb804c89bf0a97" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xcad29ceb73b2f3c90d864a2c27a464b36b980458e2d8c4c7f32f70afad707312" + ] + }, + { + "jsonrpc": "2.0", + "id": "np239", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x06466f86e40c982578b247579fa1fa5773d6169e77a79a625950c4aa16ce88b1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xbca1fef6bcfcbb170b7b349f92a3b92fe03296dac1fd64ccda295c496a261a16", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xef", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x956", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x74b00e695ebf3210bda9ad8b3aa1523475d922fd556e551cfd606ebcf807d681", + "transactions": [ + "0xf86481c0088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa08cfe044eb5748d538f72e560c45c7a01f94f4b7c6e9b1245bade89c0d97f9932a02b21fe651e5fb05d1f8de320dcf8cc037b2c0e989793f6b445f397c77f42a4f0" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa85e892063a7fd41d37142ae38037967eb047436c727fcf0bad813d316efe09f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np240", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x74b00e695ebf3210bda9ad8b3aa1523475d922fd556e551cfd606ebcf807d681", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x82a80ad266f2a1539a79b2dcf8827aabedcc1deeb6cfb4869a8ed2ea26923726", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xf0", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x960", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xd908ab400c351cee493619c9b0b56c6ae4d90bd6e995e59ac9302a7b20c13fc3", + "transactions": [ + "0xf86781c10882520894fde502858306c235a3121e42326b53228b7ef46901808718e5bb3abd10a0a03d79397e88a64f6c2ca58b5ec7ba305012e619331946e60d6ab7c40e84bf1a34a04278773d2796a0944f6bedadea3794b7ad6a18ffd01496aabf597d4a7cf75e17" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x040100f17208bcbd9456c62d98846859f7a5efa0e45a5b3a6f0b763b9c700fec" + ] + }, + { + "jsonrpc": "2.0", + "id": "np241", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xd908ab400c351cee493619c9b0b56c6ae4d90bd6e995e59ac9302a7b20c13fc3", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x59b60dbf89d7c0e705c1e05f6d861bfb38bec347663df6063be9eb020e49972a", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xf1", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x96a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x13fdff4106d52399ab52ee5d1e6a03097f6db6de8066597f88be7a797a183cb7", + "transactions": [], + "withdrawals": [ + { + "index": "0x20", + "validatorIndex": "0x5", + "address": "0xd4f09e5c5af99a24c7e304ca7997d26cb0090169", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x49d54a5147de1f5208c509b194af6d64b509398e4f255c20315131e921f7bd04" + ] + }, + { + "jsonrpc": "2.0", + "id": "np242", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x13fdff4106d52399ab52ee5d1e6a03097f6db6de8066597f88be7a797a183cb7", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x1e5d7390e70d057c7dc29e173e338e7285e276a108eaecf3164dc734ce2fd9b5", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xf2", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x974", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x9cfff0339ca5c7928180f0d37f080f2c8cc4c00bfa2b6be3754b9d228219779f", + "transactions": [ + "0xf88281c208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0057b9bf7b2b99c50cf16c0b995e2846ba833edc03f6efc1b97566022651cabeca0237b38f74a2a8c39a2c344ef2d7fe811c37cd20ed2f4d47bfc38d896f3c9db75" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x810ff6fcafb9373a4df3e91ab1ca64a2955c9e42ad8af964f829e38e0ea4ee20" + ] + }, + { + "jsonrpc": "2.0", + "id": "np243", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x9cfff0339ca5c7928180f0d37f080f2c8cc4c00bfa2b6be3754b9d228219779f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8725e2687d45a52ad320e82b90e009b1aa5effe7ebfe994116afa25daa09468f", + "receiptsRoot": "0x2f9d61b38064fb9da0bb0f93ff73e1021c62ba761714e96a6674cd927bde4f9c", + "logsBloom": "0x00000000000800000000000000000000001000000000000000000000000000010000000000000000000000000000000000000000000000040000000000000000000000000010000000008000400000000000600000000000000000000800000140000000000080000000000000000000000200000000000000000000000000000000000000000180000000000000000000000000000000000000000000001000000000000000000100020000000000000001020000000000000080000000000000080000000000000000040000000000000000000000000000024080200000000000000000040000000208000000000000010000000000000000001000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xf3", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x97e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x0c71dc4665ac65f63a44434a3d55ffc285af6ec8b90b4ddfd4b4001add0e93c0", + "transactions": [ + "0xf87981c30883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa017b61104ac6d28f1262b3750475b328dfd50f8496e0772bf19047d9d1ee9e56da01aed9f9280926e68fb66065edcf80320cab6f6d7c7af4bc8d9d007e1ea6a168d" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9b72096b8b672ac6ff5362c56f5d06446d1693c5d2daa94a30755aa636320e78" + ] + }, + { + "jsonrpc": "2.0", + "id": "np244", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x0c71dc4665ac65f63a44434a3d55ffc285af6ec8b90b4ddfd4b4001add0e93c0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc5f9b1665244a32dc0885794d5aaf3ce0b464eed1208412ca14abcfe4b908f64", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xf4", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x988", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x1f7b85304f578a197b65ce6f6f9e0c90cf680cdb3f35a95d10ea0a32238df606", + "transactions": [ + "0xf86481c4088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0a93d446ef64bccf6c88d5285e78e7625fd5c9ac9c8aa11ad45db01b95b6694a5a0761620f10b11ee3cc1932adf95133349f5107aed7b8c150192fa89665ecd7552" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf68bff777db51db5f29afc4afe38bd1bf5cdec29caa0dc52535b529e6d99b742" + ] + }, + { + "jsonrpc": "2.0", + "id": "np245", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1f7b85304f578a197b65ce6f6f9e0c90cf680cdb3f35a95d10ea0a32238df606", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5bacfe50ff7f0200bc1a4ea28e3fbe1a269ea7cbdbe7fb5d83bde19774c92e7e", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xf5", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x992", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x8f3a666d3d090603513d1e31ac73c5b47a7fe8279c7359a3bad523a8fd414a96", + "transactions": [ + "0x02f86a870c72dd9d5e883e81c501088252089427abdeddfe8503496adeb623466caa47da5f63ab0180c001a0deade75f98612138653ca1c81d8cc74eeda3e46ecf43c1f8fde86428a990ae25a065f40f1aaf4d29268956348b7cc7fa054133ccb1522a045873cb43a9ffa25283" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9566690bde717eec59f828a2dba90988fa268a98ed224f8bc02b77bce10443c4" + ] + }, + { + "jsonrpc": "2.0", + "id": "np246", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8f3a666d3d090603513d1e31ac73c5b47a7fe8279c7359a3bad523a8fd414a96", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x1c407d215d7fa96b64c583107e028bcf1e789783c39c37482326b4d4dd522e05", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xf6", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x99c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf1191b23680ae545b3ad4ffb3fd05209a7adefefc71e30970d1a4c72c383b5df", + "transactions": [], + "withdrawals": [ + { + "index": "0x21", + "validatorIndex": "0x5", + "address": "0xb0b2988b6bbe724bacda5e9e524736de0bc7dae4", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd0e821fbd57a4d382edd638b5c1e6deefb81352d41aa97da52db13f330e03097" + ] + }, + { + "jsonrpc": "2.0", + "id": "np247", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf1191b23680ae545b3ad4ffb3fd05209a7adefefc71e30970d1a4c72c383b5df", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x16a2c4f318277ea20b75f32c7c986673d92c14098e36dde553e451f131c21a66", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xf7", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x9a6", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x2e34605bbfd5f548e1e9003c8d573e41a9286968bec837ba1f2b7780e3337288", + "transactions": [ + "0xf88281c608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa02648ce9c5825b33559225aada97c08de484ab8282549d90cfc1e086052c22be8a02054d7eeb1e8bf4ab25b2581ccb0b0a3500625cf7a0315860202eb2eaf094f9c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x43f9aa6fa63739abec56c4604874523ac6dabfcc08bb283195072aeb29d38dfe" + ] + }, + { + "jsonrpc": "2.0", + "id": "np248", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x2e34605bbfd5f548e1e9003c8d573e41a9286968bec837ba1f2b7780e3337288", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x291c5ca9a114bdb7bf296b4ff4182b930dc869905eaa1219cbb5188e8feaa9ab", + "receiptsRoot": "0x9f35106348d01548df28e681773a27cffe40648e4d923974e4b87903f578da11", + "logsBloom": "0x00000001000000000000000800000000000000000000000000000000100000000000000200080000000000080001000000000000000000000000000001000000000202000000000000000000000000000002000002000000000000040000000000000000000000000200800000000000800002000000000000000000008000000000000000000000000400008000000000008000000000000000000002000000000000000000000010000000000000000000000000000000000000100000000000000000000000000000000181000000800000000000000000000000002000200000000000000000000000000280000000000000000000000000040000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xf8", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x9b0", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x22d7e31bcd496b70c0256f88d985be54cd46604897969a5edde95d8d75e2fc6a", + "transactions": [ + "0xf87981c70883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a09f49a6018e3736ea3599def5663a57cfe19cb3f27bfdd80657503262a5bcfc87a02a26782058025cfe1205be964cc9ac31cdf510a8a9f867bff2317275b13ed02c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x54ebfa924e887a63d643a8277c3394317de0e02e63651b58b6eb0e90df8a20cd" + ] + }, + { + "jsonrpc": "2.0", + "id": "np249", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x22d7e31bcd496b70c0256f88d985be54cd46604897969a5edde95d8d75e2fc6a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x359fe6cc7b7596b4455fdc075bc490d3697d4366c39c40dd6fc935da0ceac7e7", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xf9", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x9ba", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x1767170da9f173007588517f005241a12087642444518ce31bcf3ad27de4efcf", + "transactions": [ + "0xf86481c8088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa043e1ad9aa519d9a1e8a15918ee6bbc0fd98061db6058597bd984098600495f96a01d5edd1b3fc3b45ff2a17a9c7eee3ad4c75e24fc090a4a0e48f39da49e7ad263" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9e414c994ee35162d3b718c47f8435edc2c93394a378cb41037b671366791fc8" + ] + }, + { + "jsonrpc": "2.0", + "id": "np250", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1767170da9f173007588517f005241a12087642444518ce31bcf3ad27de4efcf", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc73008737d0cfdbec09b3074d48f44e406f0598003eab9a1f4c733de38512855", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xfa", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x9c4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x163dc4f5b453d5fb626263184121f08cdb616a75e2f8ef978d38e91f5b995ee6", + "transactions": [ + "0xf86781c90882520894aa7225e7d5b0a2552bbb58880b3ec00c286995b801808718e5bb3abd109fa00968ae76ffc10f7b50ca349156119aaf1d81a8772683d1c3ed005147f4682694a060f5f10a015e8685a3099140c2cc3ba0dc69026df97fb46748008c08978d162a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4356f072bb235238abefb3330465814821097327842b6e0dc4a0ef95680c4d34" + ] + }, + { + "jsonrpc": "2.0", + "id": "np251", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x163dc4f5b453d5fb626263184121f08cdb616a75e2f8ef978d38e91f5b995ee6", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x693db83454936d0dacd29b34de3d2c49dc469bbe4337faec428b028e0d967642", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xfb", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x9ce", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x8f9b318e4cd81ddd537dff3fcfe099d3609b357f3a4f2aed390edc103a5aa7a6", + "transactions": [], + "withdrawals": [ + { + "index": "0x22", + "validatorIndex": "0x5", + "address": "0x04b8d34e20e604cadb04b9db8f6778c35f45a2d2", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x215df775ab368f17ed3f42058861768a3fba25e8d832a00b88559ca5078b8fbc" + ] + }, + { + "jsonrpc": "2.0", + "id": "np252", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8f9b318e4cd81ddd537dff3fcfe099d3609b357f3a4f2aed390edc103a5aa7a6", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd358fe0baedc04a81fdaf6cdfc71c2c874291e47d16dd51cc032f0678078a009", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xfc", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x9d8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x23a585160ac1b5428ad1dea7e732b641ace396c4135dbf899ab2559f869bb5fb", + "transactions": [ + "0xf88281ca08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0941941ac43420c855cda955414a23d3bad4d0f2bfbeda999250f2f87d228878da0357223781ec5d666a8d5e8088721e9952f00a762d5fc078133bea6bc657c947e" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd17835a18d61605a04d2e50c4f023966a47036e5c59356a0463db90a76f06e3e" + ] + }, + { + "jsonrpc": "2.0", + "id": "np253", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x23a585160ac1b5428ad1dea7e732b641ace396c4135dbf899ab2559f869bb5fb", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xcb19946b2b5a905882151fff9a12cce6e4c3be46f7da6b67263b0cc781fbe80a", + "receiptsRoot": "0x9b15dea2f021c6c74dc60deea77fd6a1ce29c9efc2596cbaaf73ef60370a03e3", + "logsBloom": "0x0000000000000100800000000000000000800000000000010000000000000000000000000000000000000000000080000000000000000000000000400000000000002000000000000000000000000000000000000000000000000000100008000000000000000000000000000000000020000000000000a004200000000000800000000000000000000000000000100000000000000000000000000440000000000000001001000010000000010000004000000000000000000000200000000000000000000000000000000000000000000400000000000000000000000200000000000000000440000000000120000c00000001000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xfd", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x9e2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x706168d939a58a0dd048595d1c88fe1735dbeee42111dfbb2adee0ea9ef1d77b", + "transactions": [ + "0xf87981cb0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa06fb97cf9fb9b8f7159a9dc549e412001ca969f0dafc3c9294b0e081741aa3d9aa003ed12873ddb354ccf7b0f8e511136ff335a8e4ff6bb7f93ce19e097970c9774" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x875032d74e62dbfd73d4617754d36cd88088d1e5a7c5354bf3e0906c749e6637" + ] + }, + { + "jsonrpc": "2.0", + "id": "np254", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x706168d939a58a0dd048595d1c88fe1735dbeee42111dfbb2adee0ea9ef1d77b", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x907ae262cf7f9a93ecd0d1522c6a093ffe39594b65ec185c5059dfa7b3394371", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xfe", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x9ec", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xffd5337b506a04e2362e4a34847711bf688591ceb3ac4b7da257072ecef36a55", + "transactions": [ + "0xf86481cc088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a082f832d1212a980978d5716dca8820344200eb6967b24adb2bd112a896b4dda3a0393b965bcf272398cdd6de788c3aa929a67a42466883a472538fb1dad06c07ef" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x6f22ae25f70f4b03a2a2b17f370ace1f2b15d17fc7c2457824348a8f2a1eff9f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np255", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xffd5337b506a04e2362e4a34847711bf688591ceb3ac4b7da257072ecef36a55", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc39a9999ffd22de07bcf6a6a16b5cf1da7675dcb135e3503111a1dd50913cf0c", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0xff", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x9f6", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xcbf2f33d5616ea98f1b1cf12bdd145d35b4a928e4cb8b0fa41a6bd788ca3cbd2", + "transactions": [ + "0x02f86a870c72dd9d5e883e81cd010882520894a8100ae6aa1940d0b663bb31cd466142ebbdbd510180c080a054eafef27c71a73357c888f788f1936378929e1cdb226a205644dc1e2d68f32ba059af490b8ef4a4e98a282d9046655fc8818758e2af8ace2489927aaa3890fda3" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf11fdf2cb985ce7472dc7c6b422c3a8bf2dfbbc6b86b15a1fa62cf9ebae8f6cf" + ] + }, + { + "jsonrpc": "2.0", + "id": "np256", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xcbf2f33d5616ea98f1b1cf12bdd145d35b4a928e4cb8b0fa41a6bd788ca3cbd2", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x979018c4d3a004db4c94102d34d495dd3a4dc9c3c4bcd27d1a001f8095384208", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x100", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xa00", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x01b45ed6ccf0908b2e4b513eeea6aa86514677cb6d6d06d936e1871fc422daca", + "transactions": [], + "withdrawals": [ + { + "index": "0x23", + "validatorIndex": "0x5", + "address": "0x47dc540c94ceb704a23875c11273e16bb0b8a87a", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xbbc97696e588f80fbe0316ad430fd4146a29c19b926248febe757cd9408deddc" + ] + }, + { + "jsonrpc": "2.0", + "id": "np257", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x01b45ed6ccf0908b2e4b513eeea6aa86514677cb6d6d06d936e1871fc422daca", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x55e1fa04203cc0edebab3501d9552eaf0ac3bba421bf3480a50e1549cd479dc5", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x101", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xa0a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x48f84c09e8d4bd8effd3865e8b3ac4202cb0dc0fb72299f35c8bad4558b895dc", + "transactions": [ + "0xf88281ce08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a08a7526f8f209ff44329b503a7d726f569b861894584401651a83668be3971cbfa040314bdfa618ead4fa21933ed3a8af7e814620e3befa914828b981b391096441" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x71dd15be02efd9f3d5d94d0ed9b5e60a205f439bb46abe6226879e857668881e" + ] + }, + { + "jsonrpc": "2.0", + "id": "np258", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x48f84c09e8d4bd8effd3865e8b3ac4202cb0dc0fb72299f35c8bad4558b895dc", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8d05cad792af190bb84ad7a0bebd232c433cf16b90cffea9f4f824d562ec0eb5", + "receiptsRoot": "0x7b32e50058711e6aa1981f911bb5fb6bd05182c7e7850480874c3754788e5ee2", + "logsBloom": "0x000000000000000000000000000000000400000000000000000000000200000000000000000000000000000000002000000000000040000000000000000000800000000000000000000080080000000000040000000002002000002000008000000008000100000000000400000000000000000000000000000000000000200000000000002000000000002000000000004000000000000000000000000000020000000000000000010800000001000000000000000000000000000000000000000c0000010000000000000000000000000000000000000020000000000040000000000000000000000000300000000000000000000800008000000000400000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x102", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xa14", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xed832bf95db43a650d06fac15b9b6474b7d82d03b27bd43835eee199c95b64f1", + "transactions": [ + "0xf87981cf0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0fa1c9705b3794f376d02943123846aaae435a6590ddb802e16e91f87ae13c910a0609129061ec7d065ea3c154152c452f76a7894f2459c42c33675af6a20c9ad3c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb90e98bd91f1f7cc5c4456bb7a8868a2bb2cd3dda4b5dd6463b88728526dceea" + ] + }, + { + "jsonrpc": "2.0", + "id": "np259", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xed832bf95db43a650d06fac15b9b6474b7d82d03b27bd43835eee199c95b64f1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x06ffd1eba12cda277819f77a9a89a4f78265f7aed5158dc51332218976856e82", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x103", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xa1e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x65f5a3780beee8d82281e7fe3e82b81dae2a14ef861e9df584590dd429b8d632", + "transactions": [ + "0xf86481d0088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa07de64020fd82a08d2737ded6967d6a6095c02858161988f0626bad7dd2238057a00ad64af462ef2241d4e4c0da1dc108871126cf2aa2b82afd98d7069fc79d9085" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4e80fd3123fda9b404a737c9210ccb0bacc95ef93ac40e06ce9f7511012426c4" + ] + }, + { + "jsonrpc": "2.0", + "id": "np260", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x65f5a3780beee8d82281e7fe3e82b81dae2a14ef861e9df584590dd429b8d632", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3d72e9a90b2dbfc909c697987538e4e9a8f2b127a783109fbb869bf3760bd7a0", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x104", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xa28", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xcab55b4abc18bcf8e1b24ae34df180dc00edeadc072fa2e52ed54f2b09c6367f", + "transactions": [ + "0xf86781d10882520894a8d5dd63fba471ebcb1f3e8f7c1e1879b7152a6e01808718e5bb3abd109fa004c1d18013fb8b0554b8aaa549ee64a5a33c98edd5e51257447b4dd3b37f2adea05e3a37e5ddec2893b3fd38c4983b356c26dab5abb8b8ba6f56ac1ab9e747268b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xafb50d96b2543048dc93045b62357cc18b64d0e103756ce3ad0e04689dd88282" + ] + }, + { + "jsonrpc": "2.0", + "id": "np261", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xcab55b4abc18bcf8e1b24ae34df180dc00edeadc072fa2e52ed54f2b09c6367f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8c514217bbc30325a9d832e82e0f1816cff5d7fed0868f80269eb801957b22a0", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x105", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xa32", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x687b7f705112cf8d76b18d5ab3bc59fab146131c4b8efa05a38b42a14bcb251c", + "transactions": [], + "withdrawals": [ + { + "index": "0x24", + "validatorIndex": "0x5", + "address": "0xbc5959f43bc6e47175374b6716e53c9a7d72c594", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd73341a1c9edd04a890f949ede6cc1e942ad62b63b6a60177f0f692f141a7e95" + ] + }, + { + "jsonrpc": "2.0", + "id": "np262", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x687b7f705112cf8d76b18d5ab3bc59fab146131c4b8efa05a38b42a14bcb251c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x32fc9182d259ea7090be7140ec35dee534b5e755af25c3a41b2fe23452cd75ae", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x106", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xa3c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x59763420efabb84b6d4ae2b2a34f6db6108950debfe1feba4f706ad5227eca5f", + "transactions": [ + "0xf88281d208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0010b2ab5421f3fe86f38332dd1c862ddcfc711b2255d8f2a677985d3858b643aa025f4fec49790d44c9b50ed1bea3c5700de165dc239173328e0d0c045f0dd4558" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc26601e9613493118999d9268b401707e42496944ccdbfa91d5d7b791a6d18f1" + ] + }, + { + "jsonrpc": "2.0", + "id": "np263", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x59763420efabb84b6d4ae2b2a34f6db6108950debfe1feba4f706ad5227eca5f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xdbed2b577f83fcb221ae85377d9c4f41b8ca95de085a3a697098ceaa937d23f8", + "receiptsRoot": "0xf4e79fec628d38bdc719707be2f797b74efbc9468ba5a3ae9415877e11c21db4", + "logsBloom": "0x00000000000008004000000000000000000000000000000010800000000000000040000000000000020000000000800410800000008000040000000000000000000000000000000000040000040000000000000000000000000000001000000000020000000000000400200000000100002000000000000000000000000000000008000010000000000000000020004400000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000008000000000000080010000000000000000000000000000000200000000000020000000000000000000000000000020000800000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x107", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xa46", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf95bd5a6a4d1d51c8f00e6421bb1ecdb2a4b19222261aa412dcb4c371eea1af5", + "transactions": [ + "0xf87981d30883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0aa1f3a14b2bee05c15deffd1fcbad6d16deb140557251b04ddb61574fa8c70d8a0614a539b7fe8c276d26cabc1ff36c88c3f6b9cf3bc8836309a1d3f46626b5153" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xfb4619fb12e1b9c4b508797833eef7df65fcf255488660d502def2a7ddceef6d" + ] + }, + { + "jsonrpc": "2.0", + "id": "np264", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf95bd5a6a4d1d51c8f00e6421bb1ecdb2a4b19222261aa412dcb4c371eea1af5", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2eb443ed50d07a6b1dbb2c154cc221cfb0475593b39ca2d3569224ea7a08030e", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x108", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xa50", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x68bd9ab4e0b622e480296f040ad58d1b7f048c712ad5b46c7a596265d5f8e9fc", + "transactions": [ + "0xf86481d4088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0533560fb23c458df00902dbacef307e98096d91f179c49458d99e2eecaeaf3d3a0314508cba155f195ff77eff1a25ed4f454a07b404ac82d3ea73796bd9af3128d" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd08b7458cd9d52905403f6f4e9dac15ad18bea1f834858bf48ecae36bf854f98" + ] + }, + { + "jsonrpc": "2.0", + "id": "np265", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x68bd9ab4e0b622e480296f040ad58d1b7f048c712ad5b46c7a596265d5f8e9fc", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6e68dd5ff68bf8a325446716e5bc1629a4e77167c3b5c9249ac2e440b35dea9b", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x109", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xa5a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xfd61bbebf4026ea51b90fafefc671dc4540e83436c83eb9bc51e6b2b15db5dc9", + "transactions": [ + "0x02f86a870c72dd9d5e883e81d5010882520894ac9e61d54eb6967e212c06aab15408292f8558c40180c001a0898d514a1f15103335e066d0625c4ec34a69a03480d67dcb3d3fe0f4f932100aa07e130fed862c1482467d112f64fb59e005068b52c291003c908b625b4993e20e" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xdf979da2784a3bb9e07c368094dc640aafc514502a62a58b464e50e5e50a34bd" + ] + }, + { + "jsonrpc": "2.0", + "id": "np266", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xfd61bbebf4026ea51b90fafefc671dc4540e83436c83eb9bc51e6b2b15db5dc9", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4783eb369238bf2856e00bbc632735adf5ea404b766a0a70c27913314e170bac", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x10a", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xa64", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xaa8b392a2333d1f8a498c60f1c9884705d0bff7dd5a524b5a119f547b0d6579c", + "transactions": [], + "withdrawals": [ + { + "index": "0x25", + "validatorIndex": "0x5", + "address": "0xc04b5bb1a5b2eb3e9cd4805420dba5a9d133da5b", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x15855037d4712ce0019f0169dcd58b58493be8373d29decfa80b8df046e3d6ba" + ] + }, + { + "jsonrpc": "2.0", + "id": "np267", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xaa8b392a2333d1f8a498c60f1c9884705d0bff7dd5a524b5a119f547b0d6579c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd0b9db5bce164e65b476f578ff93039bad1be78c8d1f595ff8496c2f7a67fea4", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x10b", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xa6e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa33f601ca31d93d804b269042c783f9a6f79857919289dbb935e81ba1fed86ea", + "transactions": [ + "0xf88281d608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa03329c0816ba8af740dd07a393681abfd26c3f0a121cdfa2390607d0d1832e741a051d0d0b427004563def4552ee51b81a2ca1f41bb48e8b9ae20615381c353d9b3" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xfd1462a68630956a33e4b65c8e171a08a131097bc7faf5d7f90b5503ab30b69c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np268", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa33f601ca31d93d804b269042c783f9a6f79857919289dbb935e81ba1fed86ea", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xcb8d1404a32030e577a2628884f57433fe91b36b838f8576471bc36d87784132", + "receiptsRoot": "0x65c1a0ac45edc227576188f00c72612cd6c4d27cdac8d997bc6c9f499d21565c", + "logsBloom": "0x00000000020000000000000000000001000000000000000000000000402000000000000001000010000000000000000000000000000000000000000000000000000000000800040080000100000006000000000000000000000008000000000000000000000000000001000000000000001000040000000000000000000000000000000000000000080000100000000000000100200000000000000000000000000000000000080000000000000000000040000000000000000000000001000000000040000000000000000000000000000000000100000000000000000100002000000000200000000000000000008000000000000000008010000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x10c", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xa78", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x295de1a3c0821f092b15b4e51f02dd17ab7f1753f22f97c88a2081f9a19ffa01", + "transactions": [ + "0xf87981d70883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0310faf1dfcbc5597e207ab627226d2deeea1eedec7ffd8e68740fb76545586d1a01919f4683f202d4ccb3ab524d89d11119e7115645707333703d70f6fbe3c610d" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xedad57fee633c4b696e519f84ad1765afbef5d2781b382acd9b8dfcf6cd6d572" + ] + }, + { + "jsonrpc": "2.0", + "id": "np269", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x295de1a3c0821f092b15b4e51f02dd17ab7f1753f22f97c88a2081f9a19ffa01", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0199e03e7400c428fb1bba7126f4eb3a12becd96c4458bff54952e5535b4a3d0", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x10d", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xa82", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x494693083463dc335450802ab50c97022e63c21e326ff7cebd7870802411db3e", + "transactions": [ + "0xf86481d8088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0ea5ad6553fb67639cec694e6697ac7b718bd7044fcdf5608fa64f6058e67db93a03953b5792d7d9ef7fc602fbe260e7a290760e8adc634f99ab1896e2c0d55afcb" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc2641ba296c2daa6edf09b63d0f1cfcefd51451fbbc283b6802cbd5392fb145c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np270", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x494693083463dc335450802ab50c97022e63c21e326ff7cebd7870802411db3e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xad6e3dc4bf8e680448a8a6292fc7b9f69129c16eb7d853992c13ce0c91e7d1ce", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x10e", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xa8c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xc54e865454d4ba4a092904e151d7afdc9b7b7ef9723dee0325ee075eb6a9a5c0", + "transactions": [ + "0xf86781d90882520894653b3bb3e18ef84d5b1e8ff9884aecf1950c7a1c01808718e5bb3abd109fa0f1c5d5e335842170288da2c7c7af6856ea0b566d2b4ab4b00a19cb94144d466ca02043677d1c397a96a2f8a355431a59a0d5c40fc053e9c45b6872464f3c77c5dc" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x5615d64e1d3a10972cdea4e4b106b4b6e832bc261129f9ab1d10a670383ae446" + ] + }, + { + "jsonrpc": "2.0", + "id": "np271", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc54e865454d4ba4a092904e151d7afdc9b7b7ef9723dee0325ee075eb6a9a5c0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4347088d10fe319fb00e8eee17f1b872f2e044cbe1cb797657294404bf370e30", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x10f", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xa96", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xc33055476392adfe03f3bd812f9bb09b7184dc8d58beefab62db84ee34860bed", + "transactions": [], + "withdrawals": [ + { + "index": "0x26", + "validatorIndex": "0x5", + "address": "0x24255ef5d941493b9978f3aabb0ed07d084ade19", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x0757c6141fad938002092ff251a64190b060d0e31c31b08fb56b0f993cc4ef0d" + ] + }, + { + "jsonrpc": "2.0", + "id": "np272", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc33055476392adfe03f3bd812f9bb09b7184dc8d58beefab62db84ee34860bed", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa9c73e0cd551b43953f3b13ee9c65436102e647a83bfefa9443ad27733d0371c", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x110", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xaa0", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x39f74e3f7d2c3f4ab7e89f3b597535ffebd200abe4b1aa67f721ffaa13cbc2b4", + "transactions": [ + "0xf88281da08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0972f048bcd4f8e2678a209e354570de7452fa342744fab1e44b7af67b2484d9ea0076f82074ff9697256d2661ad9f9a7321ff54fa3100ecc479166286a9a22ada5" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x14ddc31bc9f9c877ae92ca1958e6f3affca7cc3064537d0bbe8ba4d2072c0961" + ] + }, + { + "jsonrpc": "2.0", + "id": "np273", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x39f74e3f7d2c3f4ab7e89f3b597535ffebd200abe4b1aa67f721ffaa13cbc2b4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf003762d896629dcd3a92a67ee13b96b080f4a3e71402a1dcbf9f444377329b5", + "receiptsRoot": "0x4d68fb9bfae6768b9578f5a63f455867ea5993ec2261fad2a25b45794d092f7c", + "logsBloom": "0x00000000000000000001000000000000000000000000000000000000000000000000000080000000000000100000008000000000000000800000000000000000000000000000000000000000000000400000000040000000240001100000000000000000000000000800000000000000000000000000000000060000000000000000000000000000040000000000002000000000000000080000000200000000000000000000000800000040000000040000000000000000000000000000100800000000000800100000000000000000000000000000002000800000000000000000000800000000014000040000000800000000000400000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x111", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xaaa", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x23408e1ac73e1dd9c3a735776a73b4c79249e5a9eb62ec9f9012f7f6c11ba7d0", + "transactions": [ + "0xf87981db0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a08883be3af4b0a273883412ad320e6dcace1f505d9b20194e8f9e2e092c8d5ce4a03da92647d3d92d2868d5b9c479d98faf263e78eb67f259101a65ff56ee1eccbf" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x490b0f08777ad4364f523f94dccb3f56f4aacb2fb4db1bb042a786ecfd248c79" + ] + }, + { + "jsonrpc": "2.0", + "id": "np274", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x23408e1ac73e1dd9c3a735776a73b4c79249e5a9eb62ec9f9012f7f6c11ba7d0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x741d337861f144fc811cfac1db596e3bedb837b0fb090a3d013e5492bf02b233", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x112", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xab4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xc7578d8b738ac9f5ab97605ce1c8101160faa615feeb8fc43282d8bd6ae450ac", + "transactions": [ + "0xf86481dc088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0bcd2b139343048e9174e86251017c9b7c4da9fc36e4a84cf98eaf3855561f8e3a01c25a7b3ff3ebd7d9cbed5aa65515f8ba06fb8860d0764a98591da24e7d1c842" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4a37c0e55f539f2ecafa0ce71ee3d80bc9fe33fb841583073c9f524cc5a2615a" + ] + }, + { + "jsonrpc": "2.0", + "id": "np275", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc7578d8b738ac9f5ab97605ce1c8101160faa615feeb8fc43282d8bd6ae450ac", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe1ce5b13d3189869321889bb12feb5da33a621bf0dbc4612b370a4b6973201f7", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x113", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xabe", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x0bd8ca5ecbf0c960433cbe52bec31810c325088860cd911a1df20174fd30243a", + "transactions": [ + "0x02f86a870c72dd9d5e883e81dd010882520894d8c50d6282a1ba47f0a23430d177bbfbb72e2b840180c001a04330fe20e8b84e751616253b9bccc5ff2d896e00593bfbef92e81e72b4d98a85a07977b87c7eca1f6a8e4a535cb26860e32487c6b4b826623a7390df521b21eac7" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x133295fdf94e5e4570e27125807a77272f24622750bcf408be0360ba0dcc89f2" + ] + }, + { + "jsonrpc": "2.0", + "id": "np276", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x0bd8ca5ecbf0c960433cbe52bec31810c325088860cd911a1df20174fd30243a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x76aa5a1d0fc7c2f7e01a8c515f018e30afb794badc14b5d8e3651096458947a0", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x114", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xac8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x604c3b8dbc400712146239b5b6e70426361e47c118c6fff4c1761554c3ad2e47", + "transactions": [], + "withdrawals": [ + { + "index": "0x27", + "validatorIndex": "0x5", + "address": "0xdbe726e81a7221a385e007ef9e834a975a4b528c", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa73eb87c45c96b121f9ab081c095bff9a49cfe5a374f316e9a6a66096f532972" + ] + }, + { + "jsonrpc": "2.0", + "id": "np277", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x604c3b8dbc400712146239b5b6e70426361e47c118c6fff4c1761554c3ad2e47", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xaad6081261920a2bddee7ad943a54ceebdb32edf169b206bd185bd957c029389", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x115", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xad2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x3dbceccc7aefcec187b98fc34ab00c1be2753676f6201a1e5e1356b5ce09c309", + "transactions": [ + "0xf88281de08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a092ec956d91708337ef4625bb87caed7a2bab63e40c8e65e8c9ee79a89b525b53a02bfff0c6dadfbf70dbd9fb2d75a12414d808ee6cce90826132d63f8ef2ce96b5" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9040bc28f6e830ca50f459fc3dac39a6cd261ccc8cd1cca5429d59230c10f34c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np278", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x3dbceccc7aefcec187b98fc34ab00c1be2753676f6201a1e5e1356b5ce09c309", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xac5c584edba5f948690abb0f1c0f9bef685dec896c8f6c5c66ef8dd65810d53e", + "receiptsRoot": "0xdd1d7486ff21ad1c1e17b4d06cf0af6b4a32f650ac495deff2aae6cb73338de3", + "logsBloom": "0x00000000000000000000002000000200400000000082000000000000020100000000000000000000000000000000000000000000000000088000000000000010000000000000000000000800000800000000000000000000000000000000000000000000100000000004001004880000000000000000000000000000000000480000000000000000002000000000801000000000000000000000000000000080000010000000800000000000000000000000000000000000000000000000000040000000000000000000000000008010000100000000000100000000000000000000000000000000000000000000000000000000000000200000000010000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x116", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xadc", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe69bddf40ecef2219c3ce0f27015125fb42d2339c75675f8e0dc587246cf617c", + "transactions": [ + "0xf87981df0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa099b6473bcd99e0f32d82c046bad2e1824a8468bae8347768d907768e2fe64a2ba051f3f8b7323eab23d5543c8e372e4e184bc3ee108eab5455b89d40d9cbc23008" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xec1d134c49cde6046ee295672a8f11663b6403fb71338181a89dc6bc92f7dea8" + ] + }, + { + "jsonrpc": "2.0", + "id": "np279", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe69bddf40ecef2219c3ce0f27015125fb42d2339c75675f8e0dc587246cf617c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x909007de8369b2fd9597dd7b84ab31e36b949026383fa8957befdba94703689b", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x117", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xae6", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x3f0eb43bfa229f0449d1b975632be01a69ed6c63eda12fb61bf83a2f8cde3c87", + "transactions": [ + "0xf86481e0088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0d8414a9d94412185c316893b53c874ae28ad6c0d67910ec66b39051f7842408ea05329ebb7080c9a6ae9372e8004706b78f7465746c3492816b6255fcba4d84979" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x3130a4c80497c65a7ee6ac20f6888a95bd5b05636d6b4bd13d616dcb01591e16" + ] + }, + { + "jsonrpc": "2.0", + "id": "np280", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x3f0eb43bfa229f0449d1b975632be01a69ed6c63eda12fb61bf83a2f8cde3c87", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4dcb18dbea7ec4b9dc13b208172da29eb275e2095a6f8c6aeee59d62d5c9dd76", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x118", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xaf0", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x12c3c44447da5af2e83d37224a825c26890db2483d5732e4bac08b87fe3ce5fa", + "transactions": [ + "0xf86781e10882520894b519be874447e0f0a38ee8ec84ecd2198a9fac7701808718e5bb3abd109fa0cfbd9ff7eeb9aef477970dcba479f89c7573e6167d16d0882ead77b20aaee690a01e34175b1b1758a581ca13f2ca021698933b1e8269c70fcb94c5e4aa39ee9b8e" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xccdfd5b42f2cbd29ab125769380fc1b18a9d272ac5d3508a6bbe4c82360ebcca" + ] + }, + { + "jsonrpc": "2.0", + "id": "np281", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x12c3c44447da5af2e83d37224a825c26890db2483d5732e4bac08b87fe3ce5fa", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xcbbdc9e51f0cde277f8f0ba02544d4d2be87cb7a5853a501524d760b00ec5e57", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x119", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xafa", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x2ca033d3c29586c8a38da6008d4a446814d845565ed5955418b125fdbe4602e0", + "transactions": [], + "withdrawals": [ + { + "index": "0x28", + "validatorIndex": "0x5", + "address": "0xae58b7e08e266680e93e46639a2a7e89fde78a6f", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x74342c7f25ee7dd1ae6eb9cf4e5ce5bcab56c798aea36b554ccb31a660e123af" + ] + }, + { + "jsonrpc": "2.0", + "id": "np282", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x2ca033d3c29586c8a38da6008d4a446814d845565ed5955418b125fdbe4602e0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa5f40d100045883afd309122196cd37e687124adc5ec4c609e9d4ea9e8050be1", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x11a", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xb04", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x38ae7bdbc3e96e43871baeea0577a4a6e40dd3b4d2c6fea0b50d63e24dd24382", + "transactions": [ + "0xf88281e208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a02a4dd1a40886d389cecff4ca095a57e2f1e924b8d0e80e95c67961bec5af4b34a00adc6e41c4fe22eb93c7bc6ac529c405a8beb3b75d3f82a24029c560d293bee1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf6f75f51a452481c30509e5de96edae82892a61f8c02c88d710dc782b5f01fc7" + ] + }, + { + "jsonrpc": "2.0", + "id": "np283", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x38ae7bdbc3e96e43871baeea0577a4a6e40dd3b4d2c6fea0b50d63e24dd24382", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x7a69e46d9beb12acb2476f649cf7fa7d31624c8b521351b533e302290b7ce166", + "receiptsRoot": "0x8f6545857c380d6f9aefa3a76d16cc79ce6d3e8d951a9041f19d57cbde82f55f", + "logsBloom": "0x00000800000000000004000000000001000000000040000000000800025000000000000000000000000000020000000000000000000080000008000000000000000000000000000000000000000041000000000008000000000000800000000000000000000000000080000000000000000080000000000000000000000000000000000000000000000000000010000000000000000000000000040000000000000040000000200000000081000400000000800000000010000000000000000000800000000000001000000000000000000000200000000000000000000008000000000000000000000000000000000000000080000004010000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x11b", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xb0e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xbf711951f526479f4c5a6a945594daacff51aacb288122fc4eea157e7f26c46b", + "transactions": [ + "0xf87981e30883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0ac71118aff6dbdfd117ed52c41169a3c1eec7c7b137fed7ec058a48916198f2da05b684d53b4cc1cdafdba987f894eb9c42da47785983593ee1318f8a79f83eff7" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7ce6539cc82db9730b8c21b12d6773925ff7d1a46c9e8f6c986ada96351f36e9" + ] + }, + { + "jsonrpc": "2.0", + "id": "np284", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xbf711951f526479f4c5a6a945594daacff51aacb288122fc4eea157e7f26c46b", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2d7b3e2f3ea5d7c34423a2461c1f17a4639b72a0a2f4715757ca44018b416be0", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x11c", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xb18", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf5882e396311b698818e2e02c699c77a0865ea6320dc69499197aaf8fd8e6daa", + "transactions": [ + "0xf86481e4088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa088575e3574fdfafb5c288b553973607350d846bd81b304beddaa6ef3dd349eada03cacc2455d5296189c0fc6890380a3c405b96cecfc45dc04a7f7dafe76be64c9" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x1983684da5e48936b761c5e5882bbeb5e42c3a7efe92989281367fa5ab25e918" + ] + }, + { + "jsonrpc": "2.0", + "id": "np285", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf5882e396311b698818e2e02c699c77a0865ea6320dc69499197aaf8fd8e6daa", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xdc0d40e96eaa22025544b17cc122fab8f236a1a5d0bfa1a07a6ea680fc31661c", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x11d", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xb22", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x0fabca07111b96e64ef425173cb375ed75f3e1b8ee34eed7593fe8930c9f487d", + "transactions": [ + "0x02f86a870c72dd9d5e883e81e5010882520894af2c6f1512d1cabedeaf129e0643863c574197320180c001a0c23170a740ba640770aca9fb699a2799d072b2466c97f126a834d86bdb22f516a03f242217b60ab672f352ae51249a8876a034ee51b6b4ad4a41b4d300c48e79f4" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc564aa993f2b446325ee674146307601dd87eb7409266a97e695e4bb09dd8bf5" + ] + }, + { + "jsonrpc": "2.0", + "id": "np286", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x0fabca07111b96e64ef425173cb375ed75f3e1b8ee34eed7593fe8930c9f487d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6d0b749b8735df89c9d0bd4fff2d180d87a7ff86301fc157573ff0e774a942fc", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x11e", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xb2c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x29d8373309b28aa3b206208c60bf6be454db83f0d5c4140604ec288251b4c5aa", + "transactions": [], + "withdrawals": [ + { + "index": "0x29", + "validatorIndex": "0x5", + "address": "0x5df7504bc193ee4c3deadede1459eccca172e87c", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9ca2ff57d59decb7670d5f49bcca68fdaf494ba7dc06214d8e838bfcf7a2824e" + ] + }, + { + "jsonrpc": "2.0", + "id": "np287", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x29d8373309b28aa3b206208c60bf6be454db83f0d5c4140604ec288251b4c5aa", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x06f453054ff02cd966887e3e22bf509aacb23ee18ca302b612f10d2fb473cfa3", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x11f", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xb36", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x84b99bc78800f925e5ba4da02f58581a21a3ae711a6306147ff4379435e655ee", + "transactions": [ + "0xf88281e608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0bac48741d1f314ffaab63f07d4e7a0bc34c68dde478b439f4bca7dcf0b0a1493a036448a9a4150cad5f24411e8a9bbe89096d555ad08818e90d524bbad8b380b7a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x6d7b7476cecc036d470a691755f9988409059bd104579c0a2ded58f144236045" + ] + }, + { + "jsonrpc": "2.0", + "id": "np288", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x84b99bc78800f925e5ba4da02f58581a21a3ae711a6306147ff4379435e655ee", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb45e7c8ace763c55943f9c73da1319566234dad9d29651d6b08227eb88c9c4fe", + "receiptsRoot": "0x490106e6f82f2847cc9eb542a9836943df09d8a6b2e4a4fafba322228449195a", + "logsBloom": "0x40000000000000000000100000000000000000000002000000000000000000000008000000000100000000400000000000000000000040000000000000040000000000000000004000002000000000000200000000000000000204000000000000000000000100000000000000000008000000000000000002000000200000000000000000000000000000000000000000000000008000000000000000010800000000000000004200000000000040008000000100000000000000000000000000000000000010000000000000000000000000000000080000400000000000000000000000000000000000000000000000000000040000200000000004800000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x120", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xb40", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xc7104befaf82feba7ad56db858cc6743e8ac2af4b6a1a0949c9c1ba51c0fe869", + "transactions": [ + "0xf87981e70883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa017d70f5a57065bf0973a62206ec4a9b7f1f329904de722faf30fff8e2dca5719a006d0438164dd0ff38d669ebaa44dd53cec0b81d8cfe855a9aedee94b3b1f724d" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x417504d79d00b85a29f58473a7ad643f88e9cdfe5da2ed25a5965411390fda4a" + ] + }, + { + "jsonrpc": "2.0", + "id": "np289", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc7104befaf82feba7ad56db858cc6743e8ac2af4b6a1a0949c9c1ba51c0fe869", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4c863fc026d042a28f4ee149361f77c9dae309e18ea2497255ae91f8c41e0055", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x121", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xb4a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x38c868f4adbaf9c38505eee26eb316eb5065c194df8aeed5c605f8c309d4b68a", + "transactions": [ + "0xf86481e8088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0428b809dd6147da7fc27a9520ae39b6a361b8f646b4eae45b3b32e3e406d766ea00c794c60066a8d4e435ba368662d9a6c0ffdd57ec6c49fdb0c2d4c07a69875cf" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe910eb040bf32e56e9447d63497799419957ed7df2572e89768b9139c6fa6a23" + ] + }, + { + "jsonrpc": "2.0", + "id": "np290", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x38c868f4adbaf9c38505eee26eb316eb5065c194df8aeed5c605f8c309d4b68a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4849e0698f5f4b970db7b185d122842a6f842611058a838fe4c48bf3c63b89b6", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x122", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xb54", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x962c229b0020efff007766682c8af73f38bc87fa2a83cf4a520b1e6706ced05e", + "transactions": [ + "0xf86781e90882520894b70654fead634e1ede4518ef34872c9d4f083a5301808718e5bb3abd10a0a0953d5aa69077225dba6a0333ea4d69a05f652e0d2abb8df492a7e6a9d0cdbe3da004e41cb847aa131b9bb1e19cb3dd5f7a6cc2ac8b7f459ab8c3061380d41721ff" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x8e462d3d5b17f0157bc100e785e1b8d2ad3262e6f27238fa7e9c62ba29e9c692" + ] + }, + { + "jsonrpc": "2.0", + "id": "np291", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x962c229b0020efff007766682c8af73f38bc87fa2a83cf4a520b1e6706ced05e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6334127515360bcab6eb39030e54b05d61d464576fb4f99fbece693ffa600610", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x123", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xb5e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xd8f175dd35dd4a5d97e51309a5fdeb6e713aef85c25c9e2d661075535cf8d8c1", + "transactions": [], + "withdrawals": [ + { + "index": "0x2a", + "validatorIndex": "0x5", + "address": "0xb71de80778f2783383f5d5a3028af84eab2f18a4", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x3e6f040dc96b2e05961c4e28df076fa654761f4b0e2e30f5e36b06f65d1893c1" + ] + }, + { + "jsonrpc": "2.0", + "id": "np292", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xd8f175dd35dd4a5d97e51309a5fdeb6e713aef85c25c9e2d661075535cf8d8c1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6a53dd10b53014df9fed6a4ae0fee8fc21111c58421916e9c770906b7676cbaf", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x124", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xb68", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x56a449bf5c7dba876a8f68b55d9dbbb06c0dddd3c5f586ec4a95317a0f00c79d", + "transactions": [ + "0xf88281ea08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a04efd756d15757c98077f04c9f50a22f7e74b1f28f970614a6824b4a406c11d0ba01c4bc3461a415a9c4dbfd4406c3c684a5427ce1490c93d7a9f5e43891dedc709" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x07e71d03691704a4bd83c728529642884fc1b1a8cfeb1ddcbf659c9b71367637" + ] + }, + { + "jsonrpc": "2.0", + "id": "np293", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x56a449bf5c7dba876a8f68b55d9dbbb06c0dddd3c5f586ec4a95317a0f00c79d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x82f613ee711de05f2cc6a4a107500bdd5045f1ba99ce2738222f343f6081efe6", + "receiptsRoot": "0x2c3a6865afbff0ff9319c72cb9974b085dfe9a34eb9b34e0f4bc267272a883ca", + "logsBloom": "0x00000800000000000000004000010000000000000000000000000000000000000180000000000000800000400000000000001000000000000000100000000000000000000000000008000400008000000000000000000000001000000004000001000000000000000008000000000000000000000000000000000000000000000000000000000000090800000000000000004000000000000100000000002400000000000800000000000000000000000000000000000000000000000000000000000000000000000000200001000000000000000000000000000000000000002000000000000000000200000040000000000008008000000000000000022000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x125", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xb72", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x45a502a5a428913c585b13dbdd0857fbf4ffc3e928b942b5e96c98aced1a1736", + "transactions": [ + "0xf87981eb0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a03cbaa69de647fe3ea352a6e71bab2ee53555fb8ab88c5e68efe28f2e5d687b9ea063c88d4e12b282eb4075d28f2fc6f36c7017ed0d91e36dbfd9d63a358e96abac" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf4d05f5986e4b92a845467d2ae6209ca9b7c6c63ff9cdef3df180660158163ef" + ] + }, + { + "jsonrpc": "2.0", + "id": "np294", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x45a502a5a428913c585b13dbdd0857fbf4ffc3e928b942b5e96c98aced1a1736", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x291d2f7ab3a39d6c34a1b1c66e69262273221f6a8b2bac448e37e64da2330694", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x126", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xb7c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x00f4447478e16a0e4dbe26e2398381d77367268754921e89d20bb152c1648910", + "transactions": [ + "0xf86481ec088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0aa81d6aa3b28238a33a52a3e3b5f00fa2300402a222f10c0e7451318b3f81e25a0223f13ffcec992f0ed7592df411b58352aad6d277dd16e7d0a55e5ab5702a18a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x5ca251408392b25af49419f1ecd9338d1f4b5afa536dc579ab54e1e3ee6914d4" + ] + }, + { + "jsonrpc": "2.0", + "id": "np295", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x00f4447478e16a0e4dbe26e2398381d77367268754921e89d20bb152c1648910", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe3e06a047edd89fc5a4f9ee475d8e10ace0a0bae37ad4df6613a6077870fcae4", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x127", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xb86", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x1480b67138d2eb8359bf102ee31219dea9776af6c7fed33e8f4847ce943365c4", + "transactions": [ + "0x02f86a870c72dd9d5e883e81ed010882520894be3eea9a483308cb3134ce068e77b56e7c25af190180c080a0190737acd3a2a298d5a6f96a60ced561e536dd9d676c8494bc6d71e8b8a90b60a02c407a67004643eba03f80965fea491c4a6c25d90d5a9fd53c6a61b62971e7c5" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe98b64599520cf62e68ce0e2cdf03a21d3712c81fa74b5ade4885b7d8aec531b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np296", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1480b67138d2eb8359bf102ee31219dea9776af6c7fed33e8f4847ce943365c4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8d04702ac0333be2a1e6ae46e4aa31fe4fe23f5458c6899a7fd8800d24162bc5", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x128", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xb90", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x5b6e5684623ac4835ad30948dca710bb10d4bf48695089a4eca9e472300f37d7", + "transactions": [], + "withdrawals": [ + { + "index": "0x2b", + "validatorIndex": "0x5", + "address": "0x1c972398125398a3665f212930758ae9518a8c94", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd62ec5a2650450e26aac71a21d45ef795e57c231d28a18d077a01f761bc648fe" + ] + }, + { + "jsonrpc": "2.0", + "id": "np297", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5b6e5684623ac4835ad30948dca710bb10d4bf48695089a4eca9e472300f37d7", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb59802d3b42a67087c2362fe27807e97ea95f8894d734e3711d61768b0779cc5", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x129", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xb9a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x5903dfb3ecee5d8bc0e0cc0208b17dfc9a0dc86de2eaaee48da23ea0877b6c87", + "transactions": [ + "0xf88281ee08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a01a3bb1f736220feefc5706b013d9cd88f2e5d5c1ee3398b15ba14e84ed6a12c9a078068efcdcd82d92408e849bb10f551cc406e796ff1d2e7d20e06a273d27dfdf" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4d3fb38cf24faf44f5b37f248553713af2aa9c3d99ddad4a534e49cd06bb8098" + ] + }, + { + "jsonrpc": "2.0", + "id": "np298", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5903dfb3ecee5d8bc0e0cc0208b17dfc9a0dc86de2eaaee48da23ea0877b6c87", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x18be7053419eb1d23d487c6a3df27d208a2f8973d17b6b3e78417df0d3ab1644", + "receiptsRoot": "0xa7318d908cd687d0e6d982ec99a33a54b0cb9d1bbe3782f31ae731231e79039f", + "logsBloom": "0x00000000000000000000000400000000000000000000000000000000000000000000000000000008000000000000000000000000040000000000800000000000000000000000000800000010000000110000000000000000000020000000000200000000000000000000000004000000001000000000000000000000000000040100000000000000000000000000200000000800040000080040000000004000000000000000200000000000000204000000000000000000000100000000400008008000080000000100000000000000000000000000000000000000000001000000000000000000000000000000001000000000000000000100000000800000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x12a", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xba4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x19f2a0716399f123d47e625de34fb2d6fbeadc26b2993e89504e73db85248052", + "transactions": [ + "0xf87981ef0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0dc80fe6320cc01dd2ab63a42dd099e2fa5e0a640e6ccdf8ed634ca0c7382bd9fa04b356107e6a61d8852e7dc24f02691a9bd203564fed22da46bc9d9cd560c3dd4" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x36e90abacae8fbe712658e705ac28fa9d00118ef55fe56ea893633680147148a" + ] + }, + { + "jsonrpc": "2.0", + "id": "np299", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x19f2a0716399f123d47e625de34fb2d6fbeadc26b2993e89504e73db85248052", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf704271ace032c151b512221e777247a677847e2588ffb6fdea3de9af775b059", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x12b", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xbae", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x2d68907fbe46b2958a1e07b483359dd1e1ac8a6fa0b13e0a9c012cb5de4bf458", + "transactions": [ + "0xf86481f0088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa06074cb58acfc1417684962272c546809696c6d2110b75735b19852066839a38ea03bd4f9b9b32c074215420391000ce0358e01e65745d7a6aa5513c4f857dd6579" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x164177f08412f7e294fae37457d238c4dd76775263e2c7c9f39e8a7ceca9028a" + ] + }, + { + "jsonrpc": "2.0", + "id": "np300", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x2d68907fbe46b2958a1e07b483359dd1e1ac8a6fa0b13e0a9c012cb5de4bf458", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0d6f0609afeda40249aad175bb482c3560b6f0e2fb612addd06c6f3953662531", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x12c", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xbb8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe7554d8e76e3ae2d92eceade591334e211020b97e176762c99573ba526c7fdc6", + "transactions": [ + "0xf86781f1088252089408037e79bb41c0f1eda6751f0dabb5293ca2d5bf01808718e5bb3abd109fa0e3edf14f32e7cacb36fd116b5381fac6b12325a5908dcec2b8e2c6b5517f5ec5a051429c4c1e479fa018b7907e7e3b02a448e968368a5ce9e2ea807525d363f85e" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xaa5a5586bf2f68df5c206dbe45a9498de0a9b5a2ee92235b740971819838a010" + ] + }, + { + "jsonrpc": "2.0", + "id": "np301", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe7554d8e76e3ae2d92eceade591334e211020b97e176762c99573ba526c7fdc6", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4ebd469b936b8d119664429fa99c55d75c007d4d12b7eb4db058248fa52b7f46", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x12d", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xbc2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x20cae70a3b0dbe466c0cb52294f4a0fcc2fdae8e8e23a070cfa0ebe6a9fabab9", + "transactions": [], + "withdrawals": [ + { + "index": "0x2c", + "validatorIndex": "0x5", + "address": "0x1c123d5c0d6c5a22ef480dce944631369fc6ce28", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x99d001850f513efdc613fb7c8ede12a943ff543c578a54bebbb16daecc56cec5" + ] + }, + { + "jsonrpc": "2.0", + "id": "np302", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x20cae70a3b0dbe466c0cb52294f4a0fcc2fdae8e8e23a070cfa0ebe6a9fabab9", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4e0e374db1e769d72af232e15f83b61024ab42a410b4088ad54ae31fb7ab24c2", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x12e", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xbcc", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x8088940507cc523f7c12bcec9729eed01e631ccef6faa8a6413a89d77f109c0b", + "transactions": [ + "0xf88281f208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a03f816a6f00b46ffee7ae7dc0a8472c822003d7f175c03fc883435b5303662e29a053e91a9fcfb952b9d2ee2d3017e3d02c8988bb4abcb9c343b66d90094e9b9817" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x30a4501d58b23fc7eee5310f5262783b2dd36a94922d11e5e173ec763be8accb" + ] + }, + { + "jsonrpc": "2.0", + "id": "np303", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8088940507cc523f7c12bcec9729eed01e631ccef6faa8a6413a89d77f109c0b", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5cd39242444b2f075de43272eb00a7435191e5d07d4da17022f05f91167f8a71", + "receiptsRoot": "0x8c5ae4043b8c3ac3c3faf57678b01a0a80043b682d0a8ae2681dc5c892d7a562", + "logsBloom": "0x00000000000000008000808000000040000000000008000000010000000100000000000040000000000000000000001000000000000000000000000000000000100004000000000000800000000000000008008000000008000000000000000020000000000000000000000000000000000000040000000400000000000000000002000000000000000000000000000000000060000000000000000010000000000000000000001020000000080000400000000000000000000000000000000000000400100000000000000000000000200000400000000000000000800000000000000000000000000000000000000010000000004000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x12f", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xbd6", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x3cfeeb3c000dbf1a34a7d601bacf17a26ab0618b14a821b61f847d10d41dd47d", + "transactions": [ + "0xf87981f30883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0cdff6973fabfb503b56e50264fa9d542805c351a2cf282d14e9a7e3f90df3bcea03fc2b2ef3d6e5c8d141f20dab6ea64a6ad2f7c5ab3da95c98cff7a73429036a1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa804188a0434260c0825a988483de064ae01d3e50cb111642c4cfb65bfc2dfb7" + ] + }, + { + "jsonrpc": "2.0", + "id": "np304", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x3cfeeb3c000dbf1a34a7d601bacf17a26ab0618b14a821b61f847d10d41dd47d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb8480fa4b2321e09e390c660f11ec0d4466411bae4a7016975b2b4fd843260dd", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x130", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xbe0", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xcb128d5be67707747d086abaf2a724879f3a54b7ca2bda6844678eb52a2d225f", + "transactions": [ + "0xf86481f4088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0ceb79cfa45773ae766de6daf76c67f63fbf14c7cd3853b6cd9ba8cd7cd1608baa019c783f138465d2c59039c902cc9b90cbff0e71a09672939e2373390b1f8c4c5" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc554c79292c950bce95e9ef57136684fffb847188607705454909aa5790edc64" + ] + }, + { + "jsonrpc": "2.0", + "id": "np305", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xcb128d5be67707747d086abaf2a724879f3a54b7ca2bda6844678eb52a2d225f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x022e2901949be09d1a92be5055ced3cd0770b41c850daf830834dc7da22c9af3", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x131", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xbea", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x36ddc7075c24073ea0b9b997ebf4a82596f13b41a831293600aaf876d5d1e0e0", + "transactions": [ + "0x02f86a870c72dd9d5e883e81f5010882520894f16ba6fa61da3398815be2a6c0f7cb1351982dbc0180c001a08dac03d829e6f8eab08661cd070c8a58eed41467ad9e526bb3b9c939e3fd4482a02ac7208f150195c44c455ddeea0bbe104b9121fef5cba865311940f4de428eec" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc89e3673025beff5031d48a885098da23d716b743449fd5533a04f25bd2cd203" + ] + }, + { + "jsonrpc": "2.0", + "id": "np306", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x36ddc7075c24073ea0b9b997ebf4a82596f13b41a831293600aaf876d5d1e0e0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xec2a36c595c95a6b095a795e22415b66f5875f243697e72c945361b4f440c3bc", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x132", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xbf4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x90eae29a9b788583ec3624dac546f4372b97d2b1b58edbcca1b9f82e62b0d3c6", + "transactions": [], + "withdrawals": [ + { + "index": "0x2d", + "validatorIndex": "0x5", + "address": "0x7f774bb46e7e342a2d9d0514b27cee622012f741", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x44c310142a326a3822abeb9161413f91010858432d27c9185c800c9c2d92aea6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np307", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x90eae29a9b788583ec3624dac546f4372b97d2b1b58edbcca1b9f82e62b0d3c6", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x427bbc009fe03135af46fb83f7cdcf27c022159be37615c8caceff14061d2f1f", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x133", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xbfe", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xdce2eeeafbf4e8ff4dbfa786434262fe7881254d7abcea2eabca03f5af5aa250", + "transactions": [ + "0xf88281f608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa07b4f78cff0cb04bb8cb3d81e0aabef7b54c34db7438322bc8c1554448a37b027a00b760535ea891c9b4af5c70ac5726b3829418f5b21632aa8dda9ed2a91a7e30f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xae3f497ee4bd619d651097d3e04f50caac1f6af55b31b4cbde4faf1c5ddc21e8" + ] + }, + { + "jsonrpc": "2.0", + "id": "np308", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xdce2eeeafbf4e8ff4dbfa786434262fe7881254d7abcea2eabca03f5af5aa250", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4b11a079f7e911f563ce2c7a0bcda57feaea847b827bfceb4b0f0a1fde490e41", + "receiptsRoot": "0x2cea15106bcab9c8122ea9fc8d7b5ace9f0650a79134ad9732b933221eb0c440", + "logsBloom": "0x000000020000080000000000000000000000000000000000800000000000040000000001000000000000000000000001010000000010000000000000800000000000000000020008000080000000000000000000000000000000000080080000000000000000000000000200000100000000000000000000000002001000000000000000000800200000000000000000000000000000002000000000020020000008000000000000000000000000000000000000000000000000000000000100000000000004c0000000000000000000000010000000000000200000000000000000000000000010000000000004000200000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x134", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xc08", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xd619d2e9c151c94d9610527d55ab721a092f2566b79a92821e4c7c8a106cce4f", + "transactions": [ + "0xf87981f70883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a03d6fef2d466b342db8155272b9e676d55fdc0fedab7d1fce3b3be54459203a44a016b740412be1021d3f480fbf75fa6733d5a233489a0e1cf72bf56c8b37a0ef80" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x3287d70a7b87db98964e828d5c45a4fa4cd7907be3538a5e990d7a3573ccb9c1" + ] + }, + { + "jsonrpc": "2.0", + "id": "np309", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xd619d2e9c151c94d9610527d55ab721a092f2566b79a92821e4c7c8a106cce4f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0d7fe7c7c5e17180dd3c5d11953d20c0df05569d83f29789680311e835d44c92", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x135", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xc12", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x722090df82f4d2bf93cc1d092239e427a1ed045284bc56b5aa142b02d2cb3955", + "transactions": [ + "0xf86481f8088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0b82807e311788292f679bc187111f494eb67171b03e417afdfb54e17f53b9ecfa05d9e1261b6bd95693c5e7859fa6e9ac0f380083750f46dec3f5058026c00aa54" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb52bb578e25d833410fcca7aa6f35f79844537361a43192dce8dcbc72d15e09b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np310", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x722090df82f4d2bf93cc1d092239e427a1ed045284bc56b5aa142b02d2cb3955", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x97742ddf818bf71e18497c37e9532561f45ff6f209555d67e694ec0cec856e7e", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x136", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xc1c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x81f5ce1e85499179e132dbe7b9eb21403c7f3df276820c668ed86a018065dbfa", + "transactions": [ + "0xf86781f9088252089417333b15b4a5afd16cac55a104b554fc63cc873101808718e5bb3abd109fa0f2179ec11444804bb595a6a2f569ea474b66e654ff8d6d162ec6ed565f83c1aaa0657ed11774d5d4bb0ed0eb1206d1d254735434a0c267912713099336c2dc147a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xff8f6f17c0f6d208d27dd8b9147586037086b70baf4f70c3629e73f8f053d34f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np311", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x81f5ce1e85499179e132dbe7b9eb21403c7f3df276820c668ed86a018065dbfa", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5feed3f1d6bc9de7faac7b8c1d3cfe80d29fbf205455bc25ac4c94ff5f514ca3", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x137", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xc26", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x685678cda85d28dbe24cd7ef896866decc88be80af44933953112194baeb70df", + "transactions": [], + "withdrawals": [ + { + "index": "0x2e", + "validatorIndex": "0x5", + "address": "0x06f647b157b8557a12979ba04cf5ba222b9747cf", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x70bccc358ad584aacb115076c8aded45961f41920ffedf69ffa0483e0e91fa52" + ] + }, + { + "jsonrpc": "2.0", + "id": "np312", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x685678cda85d28dbe24cd7ef896866decc88be80af44933953112194baeb70df", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xde4840156998638689e0d07c0c706d3f79031636ae0d810638ecdd66c85516f4", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x138", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xc30", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xabbd38fb9a670e62ceca6b3f5cb525834dc1208cd8bc51b3a855932951e34ee3", + "transactions": [ + "0xf88281fa08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa06193bab90c2a0e05f830df90babae78be711ea74e7fa7da80fb57bf1eac7b01ba007568dc41c59c9a3e9f4c46ad8bac850ecee5fdbe8add1a840db65266062453c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe3881eba45a97335a6d450cc37e7f82b81d297c111569e38b6ba0c5fb0ae5d71" + ] + }, + { + "jsonrpc": "2.0", + "id": "np313", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xabbd38fb9a670e62ceca6b3f5cb525834dc1208cd8bc51b3a855932951e34ee3", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6fc93047d0ff562c8abee419aecf2b174b1c382f506dedcbb5ba04955cd985c7", + "receiptsRoot": "0xcd59afd93dd989872aa9f89197f533f1c6a90364b872e145f50ff782af2b758b", + "logsBloom": "0x00000000000000000000000001000000000000000000000010000000000000000000000800000000000000000000000004011000000000400000001000000000004000000000000000080000000080000000000000004000800000000400001000000000000000000008000000800004000000000000000000000000080008000000000040000000000000000000000000000020000001000000000000000000000400000000000000000300000000000000000000000000000000400000000000000000000000000000000000000000000400000000000000000000000000000010000000000000001000000000000000010000000000000000400000000040", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x139", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xc3a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x323e57df6d8869c18eac5a0746e2e3fa96645813704b4af06659dfea08d2473c", + "transactions": [ + "0xf87981fb0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0465ab07ff3930a9a8f24c5108701be4a0475480d72147e12305f9d67017af925a07b3dd5fbeae129ce4ea30381c15b2afd9be701e4969422415e07ecea3df82db1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x2217beb48c71769d8bf9caaac2858237552fd68cd4ddefb66d04551e7beaa176" + ] + }, + { + "jsonrpc": "2.0", + "id": "np314", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x323e57df6d8869c18eac5a0746e2e3fa96645813704b4af06659dfea08d2473c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xcae6ffdf3092bcb2ebdc66df86177bce69bf2f5921e5c4d482d94f2fd5f6649b", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x13a", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xc44", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x36c686b156ca6fb1280730a2f86acfd8bcee71bb9666a473d00f0c7813fe5a2c", + "transactions": [ + "0xf86481fc088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0d92908e16f6965c17390bafa5649b05b9150b6db7cb63fccfa3d8ccc1f18ec7fa04082aba5936ac8d14c3f78d12f12d9437b575cebd82337c4499f2176afb74cba" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x06b56638d2545a02757e7f268b25a0cd3bce792fcb1e88da21b0cc21883b9720" + ] + }, + { + "jsonrpc": "2.0", + "id": "np315", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x36c686b156ca6fb1280730a2f86acfd8bcee71bb9666a473d00f0c7813fe5a2c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x1220b41d89a79f31d67f2373ea8563b54fb61661818e9aab06059361fc1412ca", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x13b", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xc4e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe5456434219d6d602162c56859d7a24895a28aac0958bd46bef986d7d8cab2e0", + "transactions": [ + "0x02f86a870c72dd9d5e883e81fd010882520894d20b702303d7d7c8afe50344d66a8a711bae14250180c001a067bed94b25c4f3ab70b3aae5cd44c648c9807cdf086299e77cf2977b9bce8244a076661b80df9b49579fce2e2201a51b08ecc4eb503d5f5517ecb20156fde7ec5a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xebdc8c9e2a85a1fb6582ca30616a685ec8ec25e9c020a65a85671e8b9dacc6eb" + ] + }, + { + "jsonrpc": "2.0", + "id": "np316", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe5456434219d6d602162c56859d7a24895a28aac0958bd46bef986d7d8cab2e0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x65c692f846c2dc380a912a71c1387fec7221a2b0fffae2451370c30ed15350d1", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x13c", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xc58", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa0f3387ab2dd15ebc6dc9522d5d0ee33f01548722c7fde856fb0f4f00fc6a7a1", + "transactions": [], + "withdrawals": [ + { + "index": "0x2f", + "validatorIndex": "0x5", + "address": "0xcccc369c5141675a9e9b1925164f30cdd60992dc", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x738f3edb9d8d273aac79f95f3877fd885e1db732e86115fa3d0da18e6c89e9cf" + ] + }, + { + "jsonrpc": "2.0", + "id": "np317", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa0f3387ab2dd15ebc6dc9522d5d0ee33f01548722c7fde856fb0f4f00fc6a7a1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3dc5f82b5983ab440abc575ac26ea2f4962c8c31f7e8721b537ea53d385827d5", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x13d", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xc62", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf0da8bf67d04b148efa37b1c72f83bad458c873c35390e45853916d2a6011efa", + "transactions": [ + "0xf88281fe08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0f5f035f73b86709cffa1134edc422e41e8ec49f3455943045c8571f4f12e8f6fa0659c80c0802ca16b9c71c90a8c1d7c32580b8dc2e33eb246d05e9c4920314a31" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xae5ccfc8201288b0c5981cdb60e16bc832ac92edc51149bfe40ff4a935a0c13a" + ] + }, + { + "jsonrpc": "2.0", + "id": "np318", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf0da8bf67d04b148efa37b1c72f83bad458c873c35390e45853916d2a6011efa", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb0a0edf94f736c11dfd920d0d386b5857441067979baff670d45380f5ce9c2b2", + "receiptsRoot": "0xbe0275e0c21d0b23665e6d0b34bbb1669b585dfb6ef89c0903dcf8586ec86d00", + "logsBloom": "0x00000020000000000000000000000000000100000000000000000000000040000000001000000000000040000000001000000000000000000000000000000000000000000000000000000000000001000000000000000400000000000002000000000000000000000000000000000000004800000000000000000000000000000000000020000000001004000000000004000000000000000100000800000401008000000000000000000800000000000100000000200000000000002000000000000000020002000000000000000000000000000000000000000200004002000004000000000000000000080200000000000000000000000000010000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x13e", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xc6c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x9b47d293dedc13b8b02e999ebaf1bf25c233229acf97e7ff9e9491ffbdbcf859", + "transactions": [ + "0xf87981ff0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a03b9f00d5731e51c973193cb6169cb8024b864d02e5347f287f8de4807e343922a04763ef63ac8ddc3fab7ccc70a4890b69fc944f330f5dd92f1b0266aaa6730eb6" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x69a7a19c159c0534e50a98e460707c6c280e7e355fb97cf2b5e0fd56c45a0a97" + ] + }, + { + "jsonrpc": "2.0", + "id": "np319", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x9b47d293dedc13b8b02e999ebaf1bf25c233229acf97e7ff9e9491ffbdbcf859", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe7a75428fc4aadb70c1e0ac2ae59a54df93458845525804742ae02a83d4f235e", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x13f", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xc76", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa2c25b920f18b7c73332a155d3ab99a4a88b6454f70c1bdfddfcbfe50311c702", + "transactions": [ + "0xf865820100088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa08c4b5e491ee67e169155453cdfc9f7ee6f122aeda5d73caf8337d6c29be1be3ca06b9a4038e45c6b5e858787dda6d1fe8d3c502a42996b4fe1abd2de1b834cf5fe" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4d2a1e9207a1466593e5903c5481a579e38e247afe5e80bd41d629ac3342e6a4" + ] + }, + { + "jsonrpc": "2.0", + "id": "np320", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa2c25b920f18b7c73332a155d3ab99a4a88b6454f70c1bdfddfcbfe50311c702", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xda3147e8c80cfa63013d1700016a432d64c00213231ac510ab15f7011eea14e8", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x140", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xc80", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xde65dcf316b36cd1d205e6df4a905df46ceedb163133ebffbab07fb6225d246d", + "transactions": [ + "0xf8688201010882520894dd1e2826c0124a6d4f7397a5a71f633928926c0601808718e5bb3abd109fa01f5208621cee9149c99848d808ee0fa8d57b358afbd39dc594f383b7f525f4c6a01960c6254e869f06cfa3263972aa8e7cc79aec12caa728515c420d35b1336c0e" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd3e7d679c0d232629818cbb94251c24797ce36dd2a45dbe8c77a6a345231c3b3" + ] + }, + { + "jsonrpc": "2.0", + "id": "np321", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xde65dcf316b36cd1d205e6df4a905df46ceedb163133ebffbab07fb6225d246d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x05dcc2c2d7e87e4e1d836888d7158131800d123c6b2de255ba83054dfa109b02", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x141", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xc8a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf2fb525ebc86939eeafb51c320f9793182f89f7bc58ad12900362db56d9d4322", + "transactions": [], + "withdrawals": [ + { + "index": "0x30", + "validatorIndex": "0x5", + "address": "0xacfa6b0e008d0208f16026b4d17a4c070e8f9f8d", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd1835b94166e1856dddb6eaa1cfdcc6979193f2ff4541ab274738bd48072899c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np322", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf2fb525ebc86939eeafb51c320f9793182f89f7bc58ad12900362db56d9d4322", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5efe08d743bbae45240fc20d02ab6e38e923dedc1027cf7bc3caff52a138dc06", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x142", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xc94", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x815a67d4526461c4d40a205f5e8cbd11964bd0ed1079edc334250475a0efe1f2", + "transactions": [ + "0xf88382010208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0f8cf692109b242d13af60f7def7e34fc16e4589de28a3fc445e83fece028b046a07ab0d98800bffd516adf4a56b048f67b4d5ffcf438c8463d82a0fe41509f51e6" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x1f12c89436a94d427a69bca5a080edc328bd2424896f3f37223186b440deb45e" + ] + }, + { + "jsonrpc": "2.0", + "id": "np323", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x815a67d4526461c4d40a205f5e8cbd11964bd0ed1079edc334250475a0efe1f2", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe6408a88797a6a4177fdb082766f6f35cd304745d96ceec7ba85908cf887ba77", + "receiptsRoot": "0xf0fa46b5337f820bd96b8bf1a50706c91cf6e2d8a9bb0fd9859f0f80d60009e3", + "logsBloom": "0x00400000000000080000000060000000000000020000000000100000100000040000040000000004000010000000000400000000000001020400000000000000000000000000000000000000000000000000000000000100000000000000400000000000020000800000100000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000014000000008000000000000000000000000800000000004000000000000000000000000000000020000000000010000800000000000000000000000000000000800000000000000000000000000000000000000000000400000000010000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x143", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xc9e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xc80438a37c405d0d3748ca7c92fb89f010ba9b06bd2136b919b563978f1ae6c1", + "transactions": [ + "0xf87a8201030883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa04d42d5415cbd9d939ef53ef60dbdffb80d016dc6e0704059b94ea4c1d398a2c6a06276655ceed05dd6ed9d6adcb9bb38bf699ae5f7ad1d8e47871404cd3ca98a00" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xccb765890b7107fd98056a257381b6b1d10a83474bbf1bdf8e6b0b8eb9cef2a9" + ] + }, + { + "jsonrpc": "2.0", + "id": "np324", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc80438a37c405d0d3748ca7c92fb89f010ba9b06bd2136b919b563978f1ae6c1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x38475f9f9a763356a2e995dd7ff0e2b3376078bd3048aa3d25bfec5257e1cf3f", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x144", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xca8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x1efad9b7aa7d15c849d6055ea15823066111fed8860177b6b0be3ed187a22664", + "transactions": [ + "0xf865820104088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0654811f90e5259072ba79ea3e5a6ca7bfe8659e198ded895d149d1fc2bfe0167a052842cb4b3a0b0f2d722ec25a5c948bb2b78c3cd2d750303a5869a8812f17eed" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x8bbf4e534dbf4580edc5a973194a725b7283f7b9fbb7d7d8deb386aaceebfa84" + ] + }, + { + "jsonrpc": "2.0", + "id": "np325", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1efad9b7aa7d15c849d6055ea15823066111fed8860177b6b0be3ed187a22664", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xda41e628e9aa8c362284b556f48a4e3f9e3e0daec75c7950cd5d4ea75b9f8223", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x145", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xcb2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x8cfb3cab3103d0431ed161ebec0a29ffce5b82e8fa5b00520169a8be360b9054", + "transactions": [ + "0x02f86b870c72dd9d5e883e8201050108825208941219c38638722b91f3a909f930d3acc16e3098040180c001a063adb9abb5014935b3dbf8c31059d6f1d9e12068a3f13bd3465db2b5a7f27f98a056f0f5bed39985d0921989b132e9638472405a2b1ba757e22df3276ca9b527fa" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x85a0516088f78d837352dcf12547ee3c598dda398e78a9f4d95acfbef19f5e19" + ] + }, + { + "jsonrpc": "2.0", + "id": "np326", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8cfb3cab3103d0431ed161ebec0a29ffce5b82e8fa5b00520169a8be360b9054", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xbcdb535ac430393001427eab3b9ff8330ae1c997c2631196da62db6c3c5a5a08", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x146", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xcbc", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xacef7ee8af09f4b94fc20d862eb2426993ad2e2807e22be468143ea8cb585d0f", + "transactions": [], + "withdrawals": [ + { + "index": "0x31", + "validatorIndex": "0x5", + "address": "0x6a632187a3abf9bebb66d43368fccd612f631cbc", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x0f669bc7780e2e5719f9c05872a112f6511e7f189a8649cda5d8dda88d6b8ac3" + ] + }, + { + "jsonrpc": "2.0", + "id": "np327", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xacef7ee8af09f4b94fc20d862eb2426993ad2e2807e22be468143ea8cb585d0f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf54751e3cc778e70000823cc9800dbecaf86c60afe48ddd4f942c9c26f606d6f", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x147", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xcc6", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x61642769719bcfbed733fd6b7c2cd51038dc1404f0e77f50c330ac8c9629b8c4", + "transactions": [ + "0xf88382010608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0af7d5214c1fa8aff20cfd3e89d0db2ff361cf5c23dae0823c6719d9bd3c3a996a0581c85fafb49fa0753c67f65e6ad04871fab4a72a9bf5d9ab3bd7aa33b230225" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa7816288f9712fcab6a2b6fbd0b941b8f48c2acb635580ed80c27bed7e840a57" + ] + }, + { + "jsonrpc": "2.0", + "id": "np328", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x61642769719bcfbed733fd6b7c2cd51038dc1404f0e77f50c330ac8c9629b8c4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xaf3502d0a6862e2cde40bbf084cba5e582a0ba3b3bc0beec6791a712c3d171e3", + "receiptsRoot": "0x52236ae99e7647366a3e31ba24153828332656ea5d242e422ffca1dbf576701d", + "logsBloom": "0x00000000000000004000000000000000000000001010000000000000000008000040000000000000000000000000000000020000200000000080000000000000000000200000000000000021000000000000400000000020000000000000000000000000000080000200000102000000000000000000000000000002000000000000000000000000000000000000000800000000000000000000000088000000800000000000000000000000000000000000020200004000000004000000000000000000002000000000000000000000020000002000080000000000000000000000402000000000000000000000000000000000000000000000000000000008", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x148", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xcd0", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x3acbeee2cb8786a166d6caf512afc82b72ed1ccbfbe39dd32dd53f842046866a", + "transactions": [ + "0xf87a8201070883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a015a81314b3c04efc725ff998badcf9278fb668561e5f9cdd42336845be60ec6ea04c593cfd5526eaf42203a3e6b5020e612ddd4053fa3123f51ae02bf8dde98eb3" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xda5168c8c83ac67dfc2772af49d689f11974e960dee4c4351bac637db1a39e82" + ] + }, + { + "jsonrpc": "2.0", + "id": "np329", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x3acbeee2cb8786a166d6caf512afc82b72ed1ccbfbe39dd32dd53f842046866a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2a4691469da94625b4626e0a10273a2854e342a71b0711acebc46c8553eb8f0e", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x149", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xcda", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x1c012e1db133493333b09aff51ca8a110b148221aaf1f28c3d21b41382b0d058", + "transactions": [ + "0xf865820108088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0f9f0dcc20f1b62b8c567ac92dc1fbf50908f8bcd504fff3a342de336052e66bea00d38043fb1b141dc3fa2b97eaf09bc490be62e1cf7c40b670503ce0fbd8f6dce" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x3f720ecec02446f1af948de4eb0f54775562f2d615726375c377114515ac545b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np330", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1c012e1db133493333b09aff51ca8a110b148221aaf1f28c3d21b41382b0d058", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x44248f33cb76fe58bf53afa7a07e7b3d1d1efb1dcde8379ba1719d987a4cb83e", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x14a", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xce4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x4e325a3f368a7235db02d7e604501ef2b416494a13136c23026e9dd3a3f38547", + "transactions": [ + "0xf86882010908825208941f5746736c7741ae3e8fa0c6e947cade81559a8601808718e5bb3abd109fa0edd3402a6c7a96114e4c8520d7bf3f06c00d9f24ee08de4c8afdbf05b4487b7da068cd4cf2242a8df916b3594055ee05551b77021bbea9b9eb9740f9a8e6466d80" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x273830a0087f6cef0fdb42179aa1c6c8c19f7bc83c3dc7aa1a56e4e05ca473ea" + ] + }, + { + "jsonrpc": "2.0", + "id": "np331", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x4e325a3f368a7235db02d7e604501ef2b416494a13136c23026e9dd3a3f38547", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x30f652c6dbb2b9b0f66b7031f6fd0a8c163866de7b7f33c3e8a0d1f9b37a6d20", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x14b", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xcee", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb1a056033a59f165c7df49320a7a67b1fdf266039f12ca8cd2ca8b904425dadf", + "transactions": [], + "withdrawals": [ + { + "index": "0x32", + "validatorIndex": "0x5", + "address": "0x984c16459ded76438d98ce9b608f175c28a910a0", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7044f700543fd542e87e7cdb94f0126b0f6ad9488d0874a8ac903a72bade34e9" + ] + }, + { + "jsonrpc": "2.0", + "id": "np332", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb1a056033a59f165c7df49320a7a67b1fdf266039f12ca8cd2ca8b904425dadf", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x7ef2bb0e7090f0d465ded8b1064d0aafb5da43bc603b3ae8e39b678616f22f04", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x14c", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xcf8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x961da5e8745e0e4ae8287d73382c5b0d651110a7c7f900abf5f04b3e114b4776", + "transactions": [ + "0xf88382010a08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0b67083c09c180ffba5ddc095999eaacd6d2cec077395c58d882c7a0706954896a02aaa853bfdbcdac9eefd90ff627107b5ca67b0c3969f3a770a4545a3b9d01514" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf63a7ff76bb9713bea8d47831a1510d2c8971accd22a403d5bbfaaa3dc310616" + ] + }, + { + "jsonrpc": "2.0", + "id": "np333", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x961da5e8745e0e4ae8287d73382c5b0d651110a7c7f900abf5f04b3e114b4776", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xff5f5d4ea4c9cb2944bea27f92a309b59ac66d45d231125258186ad3fcd58b61", + "receiptsRoot": "0xd5a4c662356c2fb912cf7df7798aabe0c8598dd3918c2c7e05db6619b76d855e", + "logsBloom": "0x00000000044004000000000000000100000000000000001000000000800000000000000000000000000000001000000000000002000101000002000000000000080000100000000000000000000000000000000000000200000000000000000010000000000000000000000100000800800000000000000000004000000800000000020000001000000002000000000000000000000000000000000000000000000000000000000000000100200000000000000000000000000200200080000000000000000000000000000002000000200000000080000000000008000000000000000000400000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x14d", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xd02", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa966ce90648fa40427896d7206976e783f96979437cbb3aed9cc9b050675763c", + "transactions": [ + "0xf87a82010b0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0e65e3fb877a256ecdcf4de6dc51df2bd755e14acad6b24c68e7168dbdfcf77b5a017ffeb5a31596ad459195610c5c5e3f348468dab79d930d49cddc0601cd5a965" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa68dbd9898dd1589501ca3220784c44d41852ad997a270e215539d461ec090f8" + ] + }, + { + "jsonrpc": "2.0", + "id": "np334", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa966ce90648fa40427896d7206976e783f96979437cbb3aed9cc9b050675763c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc8b1d4c2863741606d2cb870ed951e27495def1661f5192eef61cea97b8cd79d", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x14e", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xd0c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x266c92734d3a74137a12e4f6af6fe2cc401992b473d8af9121edbf3a78e4cf8a", + "transactions": [ + "0xf86582010c088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa041e92995e25443285655d748126496dbe98874a5cee8a1f0e58ea9f6a650f862a07feb73712a079a889322fcb61999780dab187d69eef21757af3eb0c9825f64c1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x59e501ae3ba9e0c3adafdf0f696d2e6a358e1bec43cbe9b0258c2335dd8d764f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np335", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x266c92734d3a74137a12e4f6af6fe2cc401992b473d8af9121edbf3a78e4cf8a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x53e02e88b716b3d80f9cac4ea6e30497d8a5e0f2dc4df131a20a9ffb78fe8cda", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x14f", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xd16", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xebbfb2910659e643ff415b200900100e8e116b6d84a3e8e17b87d3e93dcdf3be", + "transactions": [ + "0x02f86b870c72dd9d5e883e82010d0108825208949ae62b6d840756c238b5ce936b910bb99d5650470180c080a0025cc19f12be3ff2a51342412dc152953e8e8b61c9c3858c9d476cc214be4e30a0193960b0d01b790ef99b9a39b7475d18e83499f1635fc0a3868fc67c4da5b2c3" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4f19cff0003bdc03c2fee20db950f0efb323be170f0b09c491a20abcf26ecf43" + ] + }, + { + "jsonrpc": "2.0", + "id": "np336", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xebbfb2910659e643ff415b200900100e8e116b6d84a3e8e17b87d3e93dcdf3be", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6be6c01d240a951a6adb298d9cb4e7c9e5e8960540de958b4b458fcfa489bf36", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x150", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xd20", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x1f86807324e8cce9f4294076c96c4b2007acb0d2aba5c9ad2695e68aad468f8c", + "transactions": [], + "withdrawals": [ + { + "index": "0x33", + "validatorIndex": "0x5", + "address": "0x2847213288f0988543a76512fab09684131809d9", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x52b1b89795a8fabd3c8594bd571b44fd72279979aaa1d49ea7105c787f8f5fa6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np337", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1f86807324e8cce9f4294076c96c4b2007acb0d2aba5c9ad2695e68aad468f8c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd2cd6e558f19ab03db7ee9677a850741b4f1f763c3de94539a16d54c27f6cac0", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x151", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xd2a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x7cec5c4064e153c1c3adeda621a8764ebd7a693aa70891ef0bc7b6f95e64ae7b", + "transactions": [ + "0xf88382010e08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a057a97e1fae6dc03c4a29ad01b4d2ebea7069f1bef844b28b92875346d4454c46a01f5821fcf724aa6b0a3b082a6462e5f191a3c5659ba1b66b82cd42cf3175ba59" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7c1416bd4838b93bc87990c9dcca108675bafab950dd0faf111d9eddc4e54327" + ] + }, + { + "jsonrpc": "2.0", + "id": "np338", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x7cec5c4064e153c1c3adeda621a8764ebd7a693aa70891ef0bc7b6f95e64ae7b", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe1531cb938cfb7009f343d7ce9de03c63fe99878807b1ec8954b3a29a2d630f1", + "receiptsRoot": "0xa8c44170e431c7d7adf58109a7dbb58eeb38a19244c8a866311ef3a45fd13dfd", + "logsBloom": "0x00000000000000000000000000002000000000000000000000000000000080000000002000000000000000000000000000000000008000010000000000000000000000000000000000000000000000000800000040000420000400000000000000000000000000000000000000000000000000000000004000000000000000000200000018000000040000008400000000000000000000000000000001000000201000000010000001000400000000000000000000000000000002002000000000000400000000000000000000000000000000001000000000000000000000000000000000000080000000000004100000101000001000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x152", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xd34", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x11bd9e6153d072615b7e129ce56e720c40c048dd37afb5fdbfff09f994ae4a13", + "transactions": [ + "0xf87a82010f0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0de79b818723588fa8952e0d007ef1e1db2240b355f4f0f69f2af9df6b3408407a00962c062cd7fc4b8bf627bab2c0a00349d7b1bfc6f7875ca3a18967ad30ff219" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xef87a35bb6e56e7d5a1f804c63c978bbd1c1516c4eb70edad2b8143169262c9f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np339", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x11bd9e6153d072615b7e129ce56e720c40c048dd37afb5fdbfff09f994ae4a13", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd35f874d00597dfb19f0363bbab78f3356e12ec8b4ee89f2883285139d7a3c29", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x153", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xd3e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf7122487788d84678b120512a25b1393417a66e19db5b507d471dd17628a84ea", + "transactions": [ + "0xf865820110088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa083a20e0b736688ba1f10440def989495ff253a281368f0ca21154d327c0468b8a0119312bdfeff761612ef529e4066bd28b4ed46895e5b67593fb0a3a897d3aa16" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe978f25d16f468c0a0b585994d1e912837f55e1cd8849e140f484a2702385ef2" + ] + }, + { + "jsonrpc": "2.0", + "id": "np340", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf7122487788d84678b120512a25b1393417a66e19db5b507d471dd17628a84ea", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xfa5b72ef0354b0b53f973b5285234c441e1bbf86d26374dd3856b36627d5caa3", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x154", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xd48", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb1d88e8bd186bb264de8def507f6a5876ec6f3af27be936763dfd39213ab07e8", + "transactions": [ + "0xf8688201110882520894b55a3d332d267493105927b892545d2cd4c83bd601808718e5bb3abd10a0a073cc84153b8891468325ac12743faf7e373b78dbf8b9f856cb2622c7b4fd10e1a0388714fe9d2f85a88b962e213cbe1fa3c4a9823cea051cf91c607ecbd90093d8" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc3e85e9260b6fad139e3c42587cc2df7a9da07fadaacaf2381ca0d4a0c91c819" + ] + }, + { + "jsonrpc": "2.0", + "id": "np341", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb1d88e8bd186bb264de8def507f6a5876ec6f3af27be936763dfd39213ab07e8", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xac6b9759a537d44a1629532184219d1f658f68745491b27e81c87361e72ad602", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x155", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xd52", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x70dad5a0db225381e8f841db9d8adf9a350051128cc22c0e5a00ad990c592b0d", + "transactions": [], + "withdrawals": [ + { + "index": "0x34", + "validatorIndex": "0x5", + "address": "0x1037044fabf0421617c47c74681d7cc9c59f136c", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xbd2647c989abfd1d340fd05add92800064ad742cd82be8c2ec5cc7df20eb0351" + ] + }, + { + "jsonrpc": "2.0", + "id": "np342", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x70dad5a0db225381e8f841db9d8adf9a350051128cc22c0e5a00ad990c592b0d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe579eb979cbfd580c19ef8583f73a0fda902ee0895903a767d544ade95c50baa", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x156", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xd5c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb9b47ee8f7c38e7e1f69148756182d3da3a7d0c123948d2c56e5268357fced99", + "transactions": [ + "0xf88382011208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0874d69f306b86e76465f6f0ad314cadee41f0f0d1844d35408201c3b2f690de0a0698f29877cb7dec8ee91a42a74f0f5270cbb391836fdaeda1e0876d3c16177b9" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x99ac5ad7b62dd843abca85e485a6d4331e006ef9d391b0e89fb2eeccef1d29a2" + ] + }, + { + "jsonrpc": "2.0", + "id": "np343", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb9b47ee8f7c38e7e1f69148756182d3da3a7d0c123948d2c56e5268357fced99", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf9106c3c4fcc77588a382ba0c2f605f6e07fcc418edac1cdd7de3b0e70f81b9f", + "receiptsRoot": "0x07a001dcc7eec5d1e8aa3508d61fcf5d511b4f9b766801b63319aa423ef08c3f", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000010000800000000000000000000840000004000000000080000010000000000000000000000000000000000000000000000020000000000000000008000010000000000000000000000000000000100000000108000000000000210000000000100000000000000000000002000000408000000000030000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200008800008000008000000000000000400000100000000000000008000000000000000000000080000000000000000000001010000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x157", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xd66", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xde78022135caa19aa76718718d5de70d69e3f2488ff6769aee87c1d765237214", + "transactions": [ + "0xf87a8201130883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa084f91b21758f4c28d386fa99e8b7e126d27a1f9e293e5df2683057e09a9c6a2fa051772044b702ac375f615dc0d6aaa8c1d38c3ac2a830539d2ab62935c5132921" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x02a4349c3ee7403fe2f23cad9cf2fb6933b1ae37e34c9d414dc4f64516ea9f97" + ] + }, + { + "jsonrpc": "2.0", + "id": "np344", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xde78022135caa19aa76718718d5de70d69e3f2488ff6769aee87c1d765237214", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x19268b0f7992afe0cf1f3c0ac73b371ed7d9e79dddf0435b72bc45e1682a9c74", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x158", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xd70", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x8123c0650f836341cace6e65f0826a678974333748bc91a93d569224d63f832a", + "transactions": [ + "0xf865820114088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0904e2a23972254826c8f3f5efa2d39122f980811cb9dd3e5d2869618d458856aa00fd104e760443aa8abcbdfbf2263d45a32a7aec32e59548b3e73575bc21f0243" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x627b41fdbdf4a95381da5e5186123bf808c119b849dfdd3f515fa8d54c19c771" + ] + }, + { + "jsonrpc": "2.0", + "id": "np345", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8123c0650f836341cace6e65f0826a678974333748bc91a93d569224d63f832a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3ef70ee0614b3ae112271af4be70033c61a89f337aa527b8657df19422d94913", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x159", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xd7a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xc74bc2976b5c5cbcfd64757534333c98d56bcac3109fc4504e3c324801f27530", + "transactions": [ + "0x02f86b870c72dd9d5e883e820115010882520894b68176634dde4d9402ecb148265db047d17cb4ab0180c080a09f3175e9aa2fe2332600b71de0b0977c7c60ccbeee66ea360226326817f2d59ba06a870e0876002f789b3203f4a33d5e621ac67051704e1f2260b80d816260b3e6" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc087b16d7caa58e1361a7b158159469975f55582a4ef760465703a40123226d7" + ] + }, + { + "jsonrpc": "2.0", + "id": "np346", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc74bc2976b5c5cbcfd64757534333c98d56bcac3109fc4504e3c324801f27530", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xbbec06f293095304adb3f03ba055fd08a691c89d5de1ade4c1ed31b9c6672989", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x15a", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xd84", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x60a82197fb6b3b7d9a4912ec6ac783460863e449f48c28d68a45b4d4bf0a99f4", + "transactions": [], + "withdrawals": [ + { + "index": "0x35", + "validatorIndex": "0x5", + "address": "0x8cf42eb93b1426f22a30bd22539503bdf838830c", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf7a477c0c27d4890e3fb56eb2dc0386e7409d1c59cab6c7f22b84de45b4c6867" + ] + }, + { + "jsonrpc": "2.0", + "id": "np347", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x60a82197fb6b3b7d9a4912ec6ac783460863e449f48c28d68a45b4d4bf0a99f4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x169c28a15311ed314bc0a4529aaddacc79d5fd6becdaaae69276079408d57eda", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x15b", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xd8e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xefbb190d45953f5e6292e14fc50b51539bca514890f94eda3e3ba2553417303a", + "transactions": [ + "0xf88382011608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a065a20271c4b6acc45c7e172465adcdc218b164c0936999de9bdd37c4a4c63fd0a003792daae8ab2be81df0df962c26697830d30af560c8a85a0fba05e5cfc82d66" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x1cb440b7d88e98ceb953bc46b003fde2150860be05e11b9a5abae2c814a71571" + ] + }, + { + "jsonrpc": "2.0", + "id": "np348", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xefbb190d45953f5e6292e14fc50b51539bca514890f94eda3e3ba2553417303a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x704fde0fccaf5957d60018e958bfb8cc7bb7e77eed37cee3bdcdcca280b3b1fb", + "receiptsRoot": "0x0016ae7d40181cb711af89f17dc40dfb53384c5ef535847ae4982b1d58bfadd1", + "logsBloom": "0x00000000000000000000000004800000000000000000000000000000000200000000000000000000000000008000000020000000000200000000000000000000000000000000000000000000000000000000000000004000000000000008000000000008010000000000100008000000000020000000000400000080000000080000000000040000000080000000000002000000000000000000000000001000000000200000000000800100000000000000000040000008000000000000000000000040200000000000000000000000000000000000000000000000020000000000200000020000001008000001000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x15c", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xd98", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xac7efb8f8fa8949755e520c30b52d9c292eb7e46eb8cac907f1267f72de81237", + "transactions": [ + "0xf87a8201170883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0a7fe70291d9f18d3daffb9c6845116569c9be21f8b04c47235999ad35c20a079a03ad45b41a4993ea744bb28012bae4998ad6e97da464162d4ce51810e442e3ccc" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x72613e3e30445e37af38976f6bb3e3bf7debbcf70156eb37c5ac4e41834f9dd2" + ] + }, + { + "jsonrpc": "2.0", + "id": "np349", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xac7efb8f8fa8949755e520c30b52d9c292eb7e46eb8cac907f1267f72de81237", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x898ac18f3ec544e0908e3a1c5434515aa421b796a41501b0474375f49fba30c8", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x15d", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xda2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x498a0d50858ecbfd2fe317843b04c02a00dfa8c2ee6a0e3641947439f0eb7dba", + "transactions": [ + "0xf865820118088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0027208b707b49c8686502030a1029e738d91a7c0bf9dff86bb90ccda2e5fc158a04b1d06ac6269fc336d1e6d0bac45e82b7d47ca4c271c7fed3bd1c6599b4bd0c6" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe69e7568b9e70ee7e71ebad9548fc8afad5ff4435df5d55624b39df9e8826c91" + ] + }, + { + "jsonrpc": "2.0", + "id": "np350", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x498a0d50858ecbfd2fe317843b04c02a00dfa8c2ee6a0e3641947439f0eb7dba", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3a29d14904f05f088f4aede9ab588a53f6a54db4f43cd77f0227445a0d7c8386", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x15e", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xdac", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xac94cbd2aa423a9fc3dd35e9918a288b31a6b6127f829ef08b3d106212d5c005", + "transactions": [ + "0xf8688201190882520894dfe052578c96df94fa617102199e66110181ed2c01808718e5bb3abd109fa0020ee6a1ada31c18eac485e0281a56fc6d8c4152213d0629e6d8dd325adb60b1a00f72e01c463b98817219db62e689416c510866450efc878a6035e9346a70795f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc3f1682f65ee45ce7019ee7059d65f8f1b0c0a8f68f94383410f7e6f46f26577" + ] + }, + { + "jsonrpc": "2.0", + "id": "np351", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xac94cbd2aa423a9fc3dd35e9918a288b31a6b6127f829ef08b3d106212d5c005", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x693ec0330efa3e07b25a9a758d30a43389876e03846885dda5cdb009ff0e2674", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x15f", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xdb6", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x5c72e42631163c4ff7bb5a0e0051317b4b432609769052e2efe6043155ead48c", + "transactions": [], + "withdrawals": [ + { + "index": "0x36", + "validatorIndex": "0x5", + "address": "0x6b2884fef44bd4288621a2cda9f88ca07b480861", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x93ee1e4480ed7935097467737e54c595a2a6424cf8eaed5eacc2bf23ce368192" + ] + }, + { + "jsonrpc": "2.0", + "id": "np352", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5c72e42631163c4ff7bb5a0e0051317b4b432609769052e2efe6043155ead48c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc25a9e84540d654be4abb3e8581cd2cc7cf97e54895e7a62d08eb78431d3f244", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x160", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xdc0", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf3760efebd2ee1fbbda6bfff5aded8bb4ac38928857a4b22edab12bda293a2d7", + "transactions": [ + "0xf88382011a08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a00df7ffb1778e645f4fc3b0e2236b34c038c43aacbbc43abc8d710c3fc33901e5a00d7d3d9cbc790b2e206b30639a4b55c1d2f3c2ea18c058a5085f16d72b50455b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb07f8855348b496166d3906437b8b76fdf7918f2e87858d8a78b1deece6e2558" + ] + }, + { + "jsonrpc": "2.0", + "id": "np353", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf3760efebd2ee1fbbda6bfff5aded8bb4ac38928857a4b22edab12bda293a2d7", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x911acb703f25c08267716b25fc43b19bf4ce43a053393e6f1dce78c1cba8c485", + "receiptsRoot": "0x758b6a000deb6b7275c48ea96b2cbf580372445f0bc5b285eb764ed1800e8747", + "logsBloom": "0x00000000000005001000000000000000000000000000908000000200420000000000020000000000000000004000800010000000000000000200000000000000004000000002000000000000000080000000000000000000040000000000000000000000100400000000400000000000000000000000010000000400000000000000010000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000100000000000000000000000000000040000000000000000000000000000200000000000000000000000020000820000800000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x161", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xdca", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x9584dd2f0e20e3b4c274103aa168c495888b69ef8de7fe40cf413b6964c8393d", + "transactions": [ + "0xf87a82011b0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0c6d3e03aa8b0625a3225e077addb3cf47c9d061148da25021b22a0746083cc11a06176a93c704e6c5088e9d18cbaca7eab1de348207c2ba50083934c4e215a079d" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xec60e51de32061c531b80d2c515bfa8f81600b9b50fc02beaf4dc01dd6e0c9ca" + ] + }, + { + "jsonrpc": "2.0", + "id": "np354", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x9584dd2f0e20e3b4c274103aa168c495888b69ef8de7fe40cf413b6964c8393d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xaca595525f5aa4f17314e44a3fdc0dae0f4037a1ee0a12bfb1bec7b9219f8d6c", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x162", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xdd4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x55a29172dc5a0a9d27b1778bec1c1591c0c8ec114d322fe60f5a39258e1783a0", + "transactions": [ + "0xf86582011c088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0ad6f8d4d86d80157b67311edc959413ac3f525a5ec6334cc826125dfb1908b05a02e91a1d46e2df7c7eb4dc92224252298c66dbbf321fbb6c827a6e2d348277298" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x2fc9f34b3ed6b3cabd7b2b65b4a21381ad4419670eed745007f9efa8dd365ef1" + ] + }, + { + "jsonrpc": "2.0", + "id": "np355", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x55a29172dc5a0a9d27b1778bec1c1591c0c8ec114d322fe60f5a39258e1783a0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa307681299c7c385c512cbf83195ee62d35d29487665eb57cf2698c1b3e82066", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x163", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xdde", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb99cdd27bfb2535b0247fef5fe8097fc4e60f2a1c54a9adb3243192dafe1e657", + "transactions": [ + "0x02f86b870c72dd9d5e883e82011d01088252089433fc6e8ad066231eb5527d1a39214c1eb390985d0180c001a0167190e2e0fed95ab5c7265a53f25a92d659e1d46eb9ecbac193e7151b82ec1ca0269353e9c5ef331135563e2983279669220687652e7f231725303ccf7d2a8ebd" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf4af3b701f9b088d23f93bb6d5868370ed1cdcb19532ddd164ed3f411f3e5a95" + ] + }, + { + "jsonrpc": "2.0", + "id": "np356", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb99cdd27bfb2535b0247fef5fe8097fc4e60f2a1c54a9adb3243192dafe1e657", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb36926502e2ee904451fa5970a453aebe89f5bc25cd8c1dcae196810968617c1", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x164", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xde8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xbdd33f47e688a8c88c0bb8514d3eff12f6f1ca570d3ae31aab000689d8dd4af3", + "transactions": [], + "withdrawals": [ + { + "index": "0x37", + "validatorIndex": "0x5", + "address": "0xf6152f2ad8a93dc0f8f825f2a8d162d6da46e81f", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x8272e509366a028b8d6bbae2a411eb3818b5be7dac69104a4e72317e55a9e697" + ] + }, + { + "jsonrpc": "2.0", + "id": "np357", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xbdd33f47e688a8c88c0bb8514d3eff12f6f1ca570d3ae31aab000689d8dd4af3", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x67558b87a732daed74e1b9ed7aef6326aabe984df466494d2fc59d9ea951c6c6", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x165", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xdf2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x60fbbf44b7687b97e348c42a24637f027125b00a39e5e63995405da84de95ce0", + "transactions": [ + "0xf88382011e08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0b20886ab8d36222d79bf9dad933333062a51e71dbd6de720f872874edb727276a05f68ff1bcbb8019f43e4e37a481075cc5565512eb56d34ccb707e8aec00a4204" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa194d76f417dafe27d02a6044a913c0b494fe893840b5b745386ae6078a44e9c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np358", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x60fbbf44b7687b97e348c42a24637f027125b00a39e5e63995405da84de95ce0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x15bdc9a6fcc0d4d133bc86adbda378e2110d51fc60304207240f24f60d4fc99d", + "receiptsRoot": "0xf9f06ad2e1bbf826b5cbeabfd01d508c4d7bc0781b946c5afc105a2e20d9155a", + "logsBloom": "0x0020000200000000000004000000000000000000010000001000000000000400000000000000008000000000000400000000a820000000000000000004000001001000000800000000000000000000000000000000000000100020000002000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000040000000000000000000002000000000000000000000000008000000000000000000000000000000000000000000004000000000000000000000000000000000140000000000000800000000000010000000000000000000000004000000000010000000000004401000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x166", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xdfc", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb32a72eff6c1fed26a63381d9de7254e9a85e9c459fad22c037e8a11eb95d04f", + "transactions": [ + "0xf87a82011f0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0c35e2924126964cdf4a8847f4cb4a870f24a4654de527a3dc9fad248d338aab6a00d9292c8e92050bebef84a83b3deacddf95a33015a3d284b578cb0f1621c5a70" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa255e59e9a27c16430219b18984594fc1edaf88fe47dd427911020fbc0d92507" + ] + }, + { + "jsonrpc": "2.0", + "id": "np359", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb32a72eff6c1fed26a63381d9de7254e9a85e9c459fad22c037e8a11eb95d04f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x21d7cc2931eed33ddb03977b7d99c97ac378c41ed2ac25331478cd1fbd583e7a", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x167", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xe06", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x31483924290768786929b9836507966e24a775f86f3724200851b2eaa262ac36", + "transactions": [ + "0xf865820120088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0aab9710502eb45f06f5470674b88b22c30fdc865a22c86a7095f355629fb6d11a01d905abe10e39ed037ad29a46a81d0af6d52d9de2d7bef20e7b02db8c1cf13a0" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7996946b8891ebd0623c7887dd09f50a939f6f29dea4ca3c3630f50ec3c575cb" + ] + }, + { + "jsonrpc": "2.0", + "id": "np360", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x31483924290768786929b9836507966e24a775f86f3724200851b2eaa262ac36", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x128ac2d4c23be8773c460ed383defee0e767a4fe0a55e9f600a60e0fe051735b", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x168", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xe10", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xdfabceebb90036f92ea3e859b9fcadd8642f00dcdf45278c09d93fb56d320b04", + "transactions": [ + "0xf8688201210882520894662fb906c0fb671022f9914d6bba12250ea6adfb01808718e5bb3abd10a0a0d3a858be3712102b61ec73c8317d1e557043f308869f4a04e3a4578e2d9aa7e7a0202a5f044cc84da719ec69b7985345b2ef82cf6b0357976e99e46b38c77fe613" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb04cbab069405f18839e6c6cf85cc19beeb9ee98c159510fcb67cb84652b7db9" + ] + }, + { + "jsonrpc": "2.0", + "id": "np361", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xdfabceebb90036f92ea3e859b9fcadd8642f00dcdf45278c09d93fb56d320b04", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa8f8fd676089911db9824cafe64222a854d4767d0cc5fded3fa1643f735afd80", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x169", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xe1a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa7dab2cd20b59a5961ff34f49d421a579c939d6898b084ae4db8971604df1380", + "transactions": [], + "withdrawals": [ + { + "index": "0x38", + "validatorIndex": "0x5", + "address": "0x8fa24283a8c1cc8a0f76ac69362139a173592567", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x6f241a5e530d1e261ef0f5800d7ff252c33ce148865926e6231d4718f0b9eded" + ] + }, + { + "jsonrpc": "2.0", + "id": "np362", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa7dab2cd20b59a5961ff34f49d421a579c939d6898b084ae4db8971604df1380", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb19cdea25a29e5ba5bf0a69180560c2bcf35823b81d82d8b97499ad1cc22873b", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x16a", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xe24", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x0f9c87bb2b9d07ca411420399c22658ea7be36c5bd1fbbf1c759592959cc3a94", + "transactions": [ + "0xf88382012208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa034e8481ee12e75836d1e4cc88aef813a6bc8247b73aeb7a466a1ce95bca6e5fea07585402e69f5856a5724a9e83a9bf9cf77bc92cc619489f9903f09b8c3530f24" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xfcfa9f1759f8db6a7e452af747a972cf3b1b493a216dbd32db21f7c2ce279cce" + ] + }, + { + "jsonrpc": "2.0", + "id": "np363", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x0f9c87bb2b9d07ca411420399c22658ea7be36c5bd1fbbf1c759592959cc3a94", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x09f1a8d40ad941a3c47fd34c32682a0058f79387d467a7ebb5d957455aab9fb6", + "receiptsRoot": "0xde87ab5715c2af5f977bcf679cd4e771796d49365c3111487aba12fdb69483a2", + "logsBloom": "0x00800000000000000000000040000000000000000000000000000000000000000000000000080000000000000000000000000000008000000000000000000000000000002000000000000000000000000001020000000000000000000104000004000000000000000000000000000000000000000000800001000000040000000000000002000000000000000001000000000008400000000000000100000000000000000000001000000000000000040000000000000000010200000000000000000000000000080000000000000000000020002000000020000100400000000000000000000040000000000000100000010000000000000001000040000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x16b", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xe2e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xcdbbd78682fb1c3e75c9821acce03f6fd048226147e7041d84952c6aa3c18b5e", + "transactions": [ + "0xf87a8201230883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0317931eb522b3488621079d412251962cc5a02794939e3a3b0c94c92df0b4da5a001348209aa47bc1a55590243d5168b2beb06c929b46104d144ba526070b2e5ea" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xdf880227742710ac4f31c0466a6da7c56ec54caccfdb8f58e5d3f72e40e800f3" + ] + }, + { + "jsonrpc": "2.0", + "id": "np364", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xcdbbd78682fb1c3e75c9821acce03f6fd048226147e7041d84952c6aa3c18b5e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x96f076c6c4d61d649b8f9c4290ff81fad55bfebe6e171f2d2bedb4b941977873", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x16c", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xe38", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xab15226c228033c1118398e475d860a1ea7534e4d620ae9ceb2893fa3a73ff7a", + "transactions": [ + "0xf865820124088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0886d94140ef16f0079167a92ea5577d300a4e87982588af41676d8d9a7a7f043a0388a734d4f7a8eb510a5e7aba3141505773bd329a70ff438be40d7b378fdafa6" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xadfe28a0f8afc89c371dc7b724c78c2e3677904d03580c7141d32ba32f0ed46f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np365", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xab15226c228033c1118398e475d860a1ea7534e4d620ae9ceb2893fa3a73ff7a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x208cc1a739ecf1c8aed87a70e4f580b28d06f7dba19ef679a4b809870c0e66a4", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x16d", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xe42", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe3c3b2311857f76f1031d8384102288970bf25ab710e5e8ca3e7fee19ea3fcde", + "transactions": [ + "0x02f86b870c72dd9d5e883e820125010882520894f1fc98c0060f0d12ae263986be65770e2ae42eae0180c080a06563737b6bfddfb8bc5ec084651a8e51e3b95fe6ed4361065c988acaf764f210a00a96a1747559028cd02304adb52867678419ebef0f66012733fea03ee4eae43b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb264d19d2daf7d5fcf8d2214eba0aacf72cabbc7a2617219e535242258d43a31" + ] + }, + { + "jsonrpc": "2.0", + "id": "np366", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe3c3b2311857f76f1031d8384102288970bf25ab710e5e8ca3e7fee19ea3fcde", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf4a7f460684eacde84218991911d63333e89a5a8fe5293e43b2b283209bb7297", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x16e", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xe4c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf1f80c035c0860545aeb848923615c5bb8cbd15305ddc6a87b9d9a4d509a8d5c", + "transactions": [], + "withdrawals": [ + { + "index": "0x39", + "validatorIndex": "0x5", + "address": "0x19041ad672875015bc4041c24b581eafc0869aab", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf2207420648dccc4f01992831e219c717076ff3c74fb88a96676bbcfe1e63f38" + ] + }, + { + "jsonrpc": "2.0", + "id": "np367", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf1f80c035c0860545aeb848923615c5bb8cbd15305ddc6a87b9d9a4d509a8d5c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x44f6e5c8fd3452b71ade752a742ca9f61626aeeaa20e89d47fe414d1df414745", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x16f", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xe56", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xaa0d0fadd5774766ac1a78447bd5ef9f5a816c9068d28097c78d02737ce7f05a", + "transactions": [ + "0xf88382012608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a00d5ce478373461565e41764365499cc4a43519643829503796c5453e1bc7ff0ea03ef00a5fe608838a9156d394317734b358ac026af08b33c2aabfea8e9d485dfa" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x41e8fae73b31870db8546eea6e11b792e0c9daf74d2fbb6471f4f6c6aaead362" + ] + }, + { + "jsonrpc": "2.0", + "id": "np368", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xaa0d0fadd5774766ac1a78447bd5ef9f5a816c9068d28097c78d02737ce7f05a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x7fd199408596db163d237e6d25f64b90ac2bc04158524e8baac5d55f881bb52b", + "receiptsRoot": "0x8d3f058248d263db5e6d6d78ddf91fd2ec0183f2bdf3e9e142af87a53388e526", + "logsBloom": "0x00000000000000000000000000000100000200008000000000000000000000000000400000000000000000000000000000000000010008000000000000000000000020000600000000000020000002000010000000000000000000000000000000000000080000000020200000000000000000000000000000000001000000000000000000800000002000000800000000000000010000000000000000000000000000000000000000000000004000000000008000000000000000000000000000000000040000000000000000000000200000000000000000000000401000000009800000000010000000000000000000000000000000000000808000000800", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x170", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xe60", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x4b263e57c931fa090da8bc6890c9d6fc2ad2dd5a66bb3a5563cc477735893a96", + "transactions": [ + "0xf87a8201270883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a008faafda0a060040eca56f846ecbd6a399914482c31359f1ec04c98cc476ce82a04d2b02adc2c947898fa00cbedb4532f471cb5eb92ee19a30697ddd0c713132e3" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4e7a5876c1ee2f1833267b5bd85ac35744a258cc3d7171a8a8cd5c87811078a2" + ] + }, + { + "jsonrpc": "2.0", + "id": "np369", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x4b263e57c931fa090da8bc6890c9d6fc2ad2dd5a66bb3a5563cc477735893a96", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2a2360860a67f9187f50f56201c50d2309c961a2b408072e7c3d069c8c1216cd", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x171", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xe6a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x0adc9e078ab6f0799b5cbc8e46e53a0d96d4fe4ba0b6ff75088445c304000226", + "transactions": [ + "0xf865820128088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0da49c9575be5d906d247a5f4f0574e76d1edb1368dbdda1b4a5b58fba3fca82da00fa1c561fc766acefeeabf085384962f2599b3ca6b02996962095eed297df611" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x8d4a424d1a0ee910ccdfc38c7e7f421780c337232d061e3528e025d74b362315" + ] + }, + { + "jsonrpc": "2.0", + "id": "np370", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x0adc9e078ab6f0799b5cbc8e46e53a0d96d4fe4ba0b6ff75088445c304000226", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x971bbdee0e408ff826563636c5eccce30540c1cba590880849a72ac21f74a4e4", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x172", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xe74", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x1636ca42281a5e77303d2d1095d71b2c59f0b175c98a3adb9630cd6463d2be04", + "transactions": [ + "0xf8688201290882520894a92bb60b61e305ddd888015189d6591b0eab023301808718e5bb3abd109fa0626bd8978288bcf1d7719926fba91597d6aa8ead945c89044693d780523a05dda0074494ccf5362aa73db798940296b77b80a7ec6037f5ed2c946094b9df8a2347" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xfa65829d54aba84896370599f041413d50f1acdc8a178211b2960827c1f85cbf" + ] + }, + { + "jsonrpc": "2.0", + "id": "np371", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1636ca42281a5e77303d2d1095d71b2c59f0b175c98a3adb9630cd6463d2be04", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2a239ffb7957e73c3eebeb33b01444599ddcd5861f1dfb4bbe31584061f11389", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x173", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xe7e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xbf62c63fdcd8b0648bfec616e9270243233b47c513a9519932cb82d70ed5c2be", + "transactions": [], + "withdrawals": [ + { + "index": "0x3a", + "validatorIndex": "0x5", + "address": "0x2bb3295506aa5a21b58f1fd40f3b0f16d6d06bbc", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xda5dfc12da14eafad2ac2a1456c241c4683c6e7e40a7c3569bc618cfc9d6dca3" + ] + }, + { + "jsonrpc": "2.0", + "id": "np372", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xbf62c63fdcd8b0648bfec616e9270243233b47c513a9519932cb82d70ed5c2be", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xadbba859a71886f49ccd216fbc6c51a42a7a6eff927970b298d4e0f6e2a9597d", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x174", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xe88", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x349a0919fb81864d824dd7345c583a9fb5c99ef0bd9c549be68b10e72e7c8c2a", + "transactions": [ + "0xf88382012a08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa085c885407da43158c33afe4c9d10a846d4cf5bb820c70f019ff8b6ee9dfb027ba077c0e90a4a029bea55eadf3b0d39261b6204a5c1b8e5e80838ebeef5c9fd456c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x16243e7995312ffa3983c5858c6560b2abc637c481746003b6c2b58c62e9a547" + ] + }, + { + "jsonrpc": "2.0", + "id": "np373", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x349a0919fb81864d824dd7345c583a9fb5c99ef0bd9c549be68b10e72e7c8c2a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe5a5661f0d0f149de13c6a68eadbb59e31cb30cf6e18629346fe80789b1f3fbc", + "receiptsRoot": "0x97965a7b5cca18575c284022cd83e7efb8af6fcf19595c26001b159771ffb0ce", + "logsBloom": "0x80000000000000000000000100000000000000000000000000000010000008100180000000000000200040000000000002000000000000000000000040000000000000000000000000000000000100000000000000000000000000400000020020000000000000000000000000000000000008000000000000000000000000000000000000002000000000000000000000000000000000048200000000000000000000000000000000000000000000000000000000000000000000000100000800000000000000000000000000000000000000000000000000000000440000000000000000121400000000000000000000040001000020000000000040000200", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x175", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xe92", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x90c6119a5ecf366ff337473422f9872fddac4e2b193a2e0a065cf7de60644992", + "transactions": [ + "0xf87a82012b0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa03e8b18cd5d8c796e69f450a4c00e75d7e2d38cf9d25dd19e2033fbd56fbf4b84a0175ca19057500b32a52b668251a0aec6c8f3e1e92dec9c6741a13ffe3fb214cc" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb75f0189b31abbbd88cd32c47ed311c93ec429f1253ee715a1b00d1ca6a1e094" + ] + }, + { + "jsonrpc": "2.0", + "id": "np374", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x90c6119a5ecf366ff337473422f9872fddac4e2b193a2e0a065cf7de60644992", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0a82334be200ef303c1c3b95b92b6f397df138b7e6eb23d830fb306996f1c79b", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x176", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xe9c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb023fb820fb7f3cc5b8c8ffec71401eae32858e7f5e69ffbdbdd71751bf1c23d", + "transactions": [ + "0xf86582012c088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0519fcc6ae02e4901d4ccfcd2b0560f06bf13478b459310ddaae39f44b7ed1394a03b529b53be6c0451a4b644f5031746cb1db62cfbe43b962da26aff507d4293ef" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd087eb94d6347da9322e3904add7ff7dd0fd72b924b917a8e10dae208251b49d" + ] + }, + { + "jsonrpc": "2.0", + "id": "np375", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb023fb820fb7f3cc5b8c8ffec71401eae32858e7f5e69ffbdbdd71751bf1c23d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8e7778cdef2ec78802c7431cdd44768e4a4f6d9c6cc494ae02dc20c10bc6eead", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x177", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xea6", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xafe3e6ffafc8cd84d8aa5f81d7b622b3e18df979dbffb44601eb239bc22132bf", + "transactions": [ + "0x02f86b870c72dd9d5e883e82012d010882520894469542b3ece7ae501372a11c673d7627294a85ca0180c080a09add65921c40226ee4a686b9fa70c7582eba8c033ccc9c27775c6bc33c9232fba021a6e73ccb2f16e540594b4acbba2c852a3e853742359fcbc772880879fe1197" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xbc17244b8519292d8fbb455f6253e57ecc16b5803bd58f62b0d94da7f8b2a1d6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np376", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xafe3e6ffafc8cd84d8aa5f81d7b622b3e18df979dbffb44601eb239bc22132bf", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0b7ff239a80d7ca996fe534cf3d36898e55e3b4dbd6c130cc433dfb10d83c2dd", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x178", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xeb0", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x2b7573e48bca65c866e82401d2160b5bcaec5f8cd92fba6354d2fa8c50128e2c", + "transactions": [], + "withdrawals": [ + { + "index": "0x3b", + "validatorIndex": "0x5", + "address": "0x23c86a8aded0ad81f8111bb07e6ec0ffb00ce5bf", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x3ff8b39a3c6de6646124497b27e8d4e657d103c72f2001bdd4c554208a0566e3" + ] + }, + { + "jsonrpc": "2.0", + "id": "np377", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x2b7573e48bca65c866e82401d2160b5bcaec5f8cd92fba6354d2fa8c50128e2c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5cac010e2605b327b97a4ef6f78d4c65554588283336081d8ef497a3860fdbde", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x179", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xeba", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x77b6c58098c59ec84605e8f12c7fbe8a358d52adf77948577ce7396ae18aaac3", + "transactions": [ + "0xf88382012e08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a015118995d271e570428c3c349d49390af0fd81d3217f90159fc25b9d0791d6efa018c1a844d5d3523ce37308f0cd2e46e8d6ef99a9eb750e7325ca2c67d59aaf85" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4d0f765d2b6a01f0c787bbb13b1360c1624704883e2fd420ea36037fa7e3a563" + ] + }, + { + "jsonrpc": "2.0", + "id": "np378", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x77b6c58098c59ec84605e8f12c7fbe8a358d52adf77948577ce7396ae18aaac3", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe908771dea594628e0f4d2b5d3354bbc6f9cfa04a97249657a74b792c3254b77", + "receiptsRoot": "0x8dc461a171023c5f8e3f5d78e0842291fbe7b0a502495a334a1bc98337a8a1b4", + "logsBloom": "0x00000004000000000000000000002000000000000000000000000100080000000000000020000000000020000000000000200100000000000000000010000000000000000000000008001000000000000000040200000000000000000000020000000000080000000000000000000000000000000000000100000000000000000000000000010000000000000000200000000400000000010000120000000010000008000000000000000000100000000000000000080000000200000000000000000800000000000400010000000000000000000000000000000000000000000004000000000000004000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x17a", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xec4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x891853e3e9dd73b513556fa241d000aa63fecc5452cf39b3cc756619e9cea7b4", + "transactions": [ + "0xf87a82012f0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0d0666f18210bb986b7239269bfbd56336376ed77bb97b56e15df7647c1f06fe3a0718dc6abdefe863e76f0c3c356364d456d34d399b20ed93b61ed93a77bccbe80" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf6f1dc891258163196785ce9516a14056cbe823b17eb9b90eeee7a299c1ce0e0" + ] + }, + { + "jsonrpc": "2.0", + "id": "np379", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x891853e3e9dd73b513556fa241d000aa63fecc5452cf39b3cc756619e9cea7b4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x197f321622808ee71925004345aaf99ac87a833c97ee852265b6d8be5c0656fe", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x17b", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xece", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x8d3b2038418a6d5e44a3f5aef149d7d76a20f3ebd5aa3c9d4565ddaa94d00c07", + "transactions": [ + "0xf865820130088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa02f7e9b19c96e60b8bd18eaadf71b049e0f204d42e826667e5b741041663c1963a01ff9a63ae688fc0c05047b819d1b8326c55f60b62f84658814bf35c63b3e5c65" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x1dbf19b70c0298507d20fb338cc167d9b07b8747351785047e1a736b42d999d1" + ] + }, + { + "jsonrpc": "2.0", + "id": "np380", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8d3b2038418a6d5e44a3f5aef149d7d76a20f3ebd5aa3c9d4565ddaa94d00c07", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x19c0d6f1bcdcb2c419bb69ed7f176bd58c4833c057faede354566c4e6d6e9f20", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x17c", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xed8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa63604362866798edab2056c5ddadc63dc1490c6f13bf5dd54008e1e0f64ecd1", + "transactions": [ + "0xf86882013108825208947f2dce06acdeea2633ff324e5cb502ee2a42d97901808718e5bb3abd109fa0fd195ea41804b21ffffdbca38fd49a9874371e51e81642917d001d201a943e24a0542bca46a2dc92fddb9abffcf2b3e78dc491d6e95040692e6d1446a6b487a42a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc3b71007b20abbe908fdb7ea11e3a3f0abff3b7c1ced865f82b07f100167de57" + ] + }, + { + "jsonrpc": "2.0", + "id": "np381", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa63604362866798edab2056c5ddadc63dc1490c6f13bf5dd54008e1e0f64ecd1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6c634927494436f7c4daaee4ea5c99813ec3066af379315b031f40fdf12c74d8", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x17d", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xee2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x2187bb0b54e92e3bc6f0da1665631a818ac120ad68aa9674277d542f1e542f44", + "transactions": [], + "withdrawals": [ + { + "index": "0x3c", + "validatorIndex": "0x5", + "address": "0x96a1cabb97e1434a6e23e684dd4572e044c243ea", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x3f45edc424499d0d4bbc0fd5837d1790cb41c08f0269273fdf66d682429c25cc" + ] + }, + { + "jsonrpc": "2.0", + "id": "np382", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x2187bb0b54e92e3bc6f0da1665631a818ac120ad68aa9674277d542f1e542f44", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x04f641173e82bbe7455a3acd37242315859a80d9b4a19a56997645e31a1d1097", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x17e", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xeec", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xdd46cd98c3f0f31bf7b060263fa47e9b0aa1c4e4c7206af16ad3a01dac3bff5f", + "transactions": [ + "0xf88382013208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a08bc9a47ee84ed9389b94c57e8c7014515fefd3e891eff0e1deac8cb1266cfb05a06612fac81c3e0a0b905873bb3f9137f9f8ae952344a174e4d425564b31851350" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xcb8f5db9446c485eaae7edbc03e3afed72892fa7f11ad8eb7fa9dffbe3c220eb" + ] + }, + { + "jsonrpc": "2.0", + "id": "np383", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xdd46cd98c3f0f31bf7b060263fa47e9b0aa1c4e4c7206af16ad3a01dac3bff5f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2cc62d5cb6ca1b74dd31ced44a51655d15f0c67d9e8b4560584124ea91649145", + "receiptsRoot": "0x7288150e98b9056465e864af6976d5ec6de80da74cee77596b9a67de235177ac", + "logsBloom": "0x000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000040000000000008200000000000000000000000000000000000000000000000041000000000000000000000010000000800c0000000000000000400001000000000000001610000000080000200080000000000008000000001000000800000000000200000000008000000000000000000000000040000000002000000000080000000000000000000000000000000000000000000000000000000000000000000000000008000000000200000000040000800080", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x17f", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xef6", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x808dd663054b022868554929395cf380b27661a0ae7333a92d69160769afbbbe", + "transactions": [ + "0xf87a8201330883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0f1dbbd841499d2a51db61a05cf4a7a5650fd83eafe8516d0ad49e99db40c0d13a0542104414214add483f5e7397e9b98e95d336d60ff2b661eabfc8125548df848" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x3d151527b5ba165352a450bee69f0afc78cf2ea9645bb5d8f36fb04435f0b67c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np384", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x808dd663054b022868554929395cf380b27661a0ae7333a92d69160769afbbbe", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf88d2d5d961b54872a1475e17a9107724ba2cd0ca28cb7320aad2f903dc74deb", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x180", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xf00", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x86bca890ff8f5be8c986745f38ef4a87ce167fcaacc0de928f4c8db469bba94a", + "transactions": [ + "0xf865820134088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0922834adc69ced79913745b4a53a63ff0b0d73552c658f63c35b74fe831f1990a072af738962b2108e1e3e534c88145aa55764f2908bdbce0a4433ef88e3fbfb0c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xdd96b35b4ffabce80d377420a0b00b7fbf0eff6a910210155d22d9bd981be5d3" + ] + }, + { + "jsonrpc": "2.0", + "id": "np385", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x86bca890ff8f5be8c986745f38ef4a87ce167fcaacc0de928f4c8db469bba94a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x248cbc35df3f48575474369a9105962a22bff30f3e973711545bb9cae1e06dff", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x181", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xf0a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x83132e862eb410579a38d85bbec7fdd5b890647bc9ccc2ad881361a9389cd3fa", + "transactions": [ + "0x02f86b870c72dd9d5e883e8201350108825208943bcc2d6d48ffeade5ac5af3ee7acd7875082e50a0180c080a03931e5e7d02ed045834da39a409083c260fbc96dc256c1d927f1704147eeaeb6a0215269010bb3e7dd8f03d71db3e617985b447c2e0dd6fc0939c125db43039d0f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xace0c30b543d3f92f37eaac45d6f8730fb15fcaaaad4097ea42218abe57cb9f4" + ] + }, + { + "jsonrpc": "2.0", + "id": "np386", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x83132e862eb410579a38d85bbec7fdd5b890647bc9ccc2ad881361a9389cd3fa", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x899d1787e12b4ee7d5e497ac1b07d460146316edd86d589dd357e4e39e6e50a5", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x182", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xf14", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb2b948b9139c380319a045813000f17a02153426ae3db02065a7bc6fb1b3d41e", + "transactions": [], + "withdrawals": [ + { + "index": "0x3d", + "validatorIndex": "0x5", + "address": "0xfd5e6e8c850fafa2ba2293c851479308c0f0c9e7", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf6342dd31867c9bef6ffa06b6cf192db23d0891ed8fe610eb8d1aaa79726da01" + ] + }, + { + "jsonrpc": "2.0", + "id": "np387", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb2b948b9139c380319a045813000f17a02153426ae3db02065a7bc6fb1b3d41e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6fd59459f6805b1c3f35cd672f058d3f4215b8ba06217056195a249529106097", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x183", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xf1e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xec59612429465042cb5bfe00c2720e2b06608cc0befdf12185f61213dede36a3", + "transactions": [ + "0xf88382013608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa02f68ef0be353bceb12bd978567947ea2ade48f275f8488d4d9089a6a5df54ecaa01ea605cad7ded16c6744be5446342cef46c0f802938d30db72ee4e35eb0ee726" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa6589e823979c2c2ac55e034d547b0c63aa02109133575d9f159e8a7677f03cb" + ] + }, + { + "jsonrpc": "2.0", + "id": "np388", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xec59612429465042cb5bfe00c2720e2b06608cc0befdf12185f61213dede36a3", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x87aa8da72c4f54683d4ddfe7592b17518075332583bf40a0af34b072e1b8d5ca", + "receiptsRoot": "0x6b2a7f9df51def8b942a27f69021bd8954a4d01182bc78fe20171ec738d6a1cd", + "logsBloom": "0x00010000004000000000000000000000000000040000000020000000000000000000000100208000000000000004000000000000000000000000000000000000000000000000000020000000000002000000000000000000020000000000000048000000000000000000004810000000201000000000000000000000000000000000000000000008000000000000010000000000000000000000000080004000000000000000040000000000020000000000000010000000000000000000040000000080001000000400000000000000000000000000000000000000000000000000000000000000000000080000000000000020000200004000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x184", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xf28", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x18a50573c6144ce2d2c185b146827fbde1568f647d6bcc2c2556df64a00d3462", + "transactions": [ + "0xf87a8201370883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a036602b451fdd27281014a28c261ac59feabe8c6730619162c51ccd6452e0efcfa01dbbc3cb987dd50dbb59072a156ce01b7825d252e5855249afbda11fd763436e" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9ce48bc641cc1d54ffdb409aab7da1304d5ee08042596b3542ca9737bb2b79a8" + ] + }, + { + "jsonrpc": "2.0", + "id": "np389", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x18a50573c6144ce2d2c185b146827fbde1568f647d6bcc2c2556df64a00d3462", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4970ca728c597509e3afb689227e843d5da3be74aea9719a756d65db2694b152", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x185", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xf32", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x3ad7ba10baedb1b98556cd20670c57f2f3a4aa0ddfbf76c9a2cbbcec188dada5", + "transactions": [ + "0xf865820138088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a09a9bf09cafb07d6a97b972a3b405a1dd30dcd6945d9adda6cf921c211bc046e1a03c97b3b08d67e3ccfcb8408e39d2e0971761c1905fbd7028fb52a1f163fb92f3" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa44be801bd978629775c00d70df6d70b76d0ba918595e81415a27d1e3d6fdee9" + ] + }, + { + "jsonrpc": "2.0", + "id": "np390", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x3ad7ba10baedb1b98556cd20670c57f2f3a4aa0ddfbf76c9a2cbbcec188dada5", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc5da7efe2ca6d0468002914ea2c334be08121fb5450b4a1b74baf08e65115192", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x186", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xf3c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa2deafade520007dd7d127c439290f2bac7a2027b80ff616ccf8ce62eeba6506", + "transactions": [ + "0xf8688201390882520894f83af0ceb5f72a5725ffb7e5a6963647be7d884701808718e5bb3abd109fa0a38cf9766454bd02d4f06f5bd214f5fe9e53b7a299eda5c7523060704fcdb751a067c33351f6f7bbd9de5b5435f6cadc10ba5e94f3cbcc40ee53496c782f99d71f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xce17f1e7af9f7ea8a99b2780d87b15d8b80a68fb29ea52f962b00fecfc6634e0" + ] + }, + { + "jsonrpc": "2.0", + "id": "np391", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa2deafade520007dd7d127c439290f2bac7a2027b80ff616ccf8ce62eeba6506", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x7f1f4d793182771fbacb9ef07a0736edbe4aa2417bf775c7b499b35ad791575a", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x187", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xf46", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xfce102ce6fa4701cfa7ca7c4aae937b79190e29b55a453e67f31adece99c4f92", + "transactions": [], + "withdrawals": [ + { + "index": "0x3e", + "validatorIndex": "0x5", + "address": "0xf997ed224012b1323eb2a6a0c0044a956c6b8070", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4bd91febab8df3770c957560e6185e8af59d2a42078756c525cd7769eb943894" + ] + }, + { + "jsonrpc": "2.0", + "id": "np392", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xfce102ce6fa4701cfa7ca7c4aae937b79190e29b55a453e67f31adece99c4f92", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x58f87e8c7ffa26035df5258225c492a17f353b2d33420e0ac5b5413f0c29be1a", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x188", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xf50", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x5aff5c82ef6756d97e6caaf6bc6084f4091ed2503b88083a0c4b0484f6e9525d", + "transactions": [ + "0xf88382013a08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0e60cd99574bb50b626cf0b20d73ece21858aba52609136e6e2dc420a9fdc00eea00aeff0a4419c24268d9784a1ae211927004d8dbbbda3c47c0d0e2d32178ce8f4" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x414c2a52de31de93a3c69531247b016ac578435243073acc516d4ea673c8dd80" + ] + }, + { + "jsonrpc": "2.0", + "id": "np393", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5aff5c82ef6756d97e6caaf6bc6084f4091ed2503b88083a0c4b0484f6e9525d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x14a7327b3cff203afe17f16aca0470fbe12cfac971c79ef9bd5b3ef71bce5591", + "receiptsRoot": "0x3b0559fd9e27f69f8a378d27e3b5a82f18881f307f49ec63f89ad4bae18a1ee6", + "logsBloom": "0x00001000800000000000400040000000000010002000000000004000000000000000000000000000000000000000000000000000000000040000000020000000000000000000000000000000000001800000000200000000000000000000000021000000000000000000000000000000000000000000000000002000000000020000000008000002000000000000000000000000000000000000000000080100000000000000000000000000000020000000000040000000000000000000000000000000000000000020000000000000000001000000000000000200000000000000000000004000000000000000000004000080000000020000001000a00008", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x189", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xf5a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb22e30af8a7f23e2b73275e505b5c6f482357576c82e3d718b0c4c33914d97e6", + "transactions": [ + "0xf87a82013b0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa061705b5163977bf95976fb0d2f44c1c581d19de8f68084001ed516813a7f5785a07daeb176a18749f11e1cec56a72e988c8362c2e15b86a9c5ae3e2cb2ddde0ce2" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x647fb60bdf2683bd46b63d6884745782364a5522282ed1dc67d9e17c4aaab17d" + ] + }, + { + "jsonrpc": "2.0", + "id": "np394", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb22e30af8a7f23e2b73275e505b5c6f482357576c82e3d718b0c4c33914d97e6", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x02eb8f611a78bed4123c7b1ec6ca3148dee547538828183756744882a58b6993", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x18a", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xf64", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x9a348ddcb5d7c63d344358308acfd52c1be4432de1bdd02a4c1483521b95d7e0", + "transactions": [ + "0xf86582013c088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0463d74275ffee97deea0603bdab389823c88c03997f176d4c349514d78d4dbc4a06b9796eed221b40094ded3ec3fa9bdbf097561ac3f8a142fef5e2c894a8296de" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xfa681ffd0b0dd6f6775e99a681241b86a3a24446bc8a69cdae915701243e3855" + ] + }, + { + "jsonrpc": "2.0", + "id": "np395", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x9a348ddcb5d7c63d344358308acfd52c1be4432de1bdd02a4c1483521b95d7e0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe5d836ff1dc0a199a799bdb1aa945580acf9e06c96bd6b88cbc60903e5904b9c", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x18b", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xf6e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x584b00c97139674af12f17a4a4828e59951c7f7d0c4fae83d5711ce5e582fdca", + "transactions": [ + "0x02f86b870c72dd9d5e883e82013d010882520894469dacecdef1d68cb354c4a5c015df7cb6d655bf0180c001a06faf4090490862eba3c27dfe0a030a442ccc89d4478eca3ed09039386554f07ba0656f741b64c54808ac5a6956540d3f7aaec811bf4efa7239a0ca0c7fb410b4d6" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x106ca692777b30cb2aa23ca59f5591514b28196ee8e9b06aa2b4deaea30d9ef6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np396", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x584b00c97139674af12f17a4a4828e59951c7f7d0c4fae83d5711ce5e582fdca", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8fc7b0893f25c43c0dd53f57c7f98653e86d2570923f1831840c09c7c728efab", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x18c", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xf78", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x8e2b4e77e4fd7ab14ffaca65bc3a0868f14ce792ffe5f26cc0cc4abf8ebc5cd4", + "transactions": [], + "withdrawals": [ + { + "index": "0x3f", + "validatorIndex": "0x5", + "address": "0x6d09a879576c0d941bea7833fb2285051b10d511", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x494ac6d09377eb6a07ff759df61c2508e65e5671373d756c82e648bd9086d91a" + ] + }, + { + "jsonrpc": "2.0", + "id": "np397", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8e2b4e77e4fd7ab14ffaca65bc3a0868f14ce792ffe5f26cc0cc4abf8ebc5cd4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb87cec8c84db91856e9ae32af116b449b8cb1d61cae190a182aebfb85d691e8f", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x18d", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xf82", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x657f16f62e12433129b4b3f80e92eee4a65d1cb6e8b847ce632d32cb79ba5abe", + "transactions": [ + "0xf88382013e08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0065cd2e05815fd9bf6e9aced9947d0c43feed03d4bd010ce93828c5e45a9b483a019449b8fc18e639f9c1d7b0adbd3941622d1f2e8127b82993e0f8bb9cdc2999f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x0ae4ccd2bffa603714cc453bfd92f769dce6c9731c03ac3e2083f35388e6c795" + ] + }, + { + "jsonrpc": "2.0", + "id": "np398", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x657f16f62e12433129b4b3f80e92eee4a65d1cb6e8b847ce632d32cb79ba5abe", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe154fbe6ca3c192310dea977b202b7e57523be45dfb36cf46816f7b1b86c910b", + "receiptsRoot": "0x09e88b070a05aab53918792ba761837b32e299692e1ee33a27d3b654a45ea25f", + "logsBloom": "0x00000001000000000400000000001000000008120000000220000000000400000000008000000000000000002000000000000000000000000000000000000000020000008000000000000000000004000000000000000000000000000000000080000000000000000000000000010000000000000200000000000000004004000000000010000000000001000008000000000000000000000000000000000008000000100000000000000000000000000010000000000000000000000000001000000000000000000000000000000010200000000004000010040000100000000000000000000000000000000000000000000000000000000000020000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x18e", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xf8c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x677cd475087726e83d09edba4d2e6cdcaa5f1b9f5e7c26260ff6ebf4dd86a6aa", + "transactions": [ + "0xf87a82013f0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a017bd3457b4b843b788bd719c6e49a5efad177ca349fa23ee93130c68a6c123a6a0595becbedbd04d964a7e8ca826f50061e1b1f16bea32c966670f7dbcc63dbbff" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd860c999490d9836cc00326207393c78445b7fb90b12aa1d3607e3662b3d32cd" + ] + }, + { + "jsonrpc": "2.0", + "id": "np399", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x677cd475087726e83d09edba4d2e6cdcaa5f1b9f5e7c26260ff6ebf4dd86a6aa", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x771db9f41d228f8d3e1a33889cc04468bb9691860cbdbf28203d90713eed1fb1", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x18f", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xf96", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf60f85724891ffc25eb8c5c596e55846df4032b2edb35d0fc6ac64870db6b42f", + "transactions": [ + "0xf865820140088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0db06597d4b08ca3fef9b08c69896cef6505785b448bfd0e051ebc7616a2f5a1aa07ca5051c69a0dcb5fae23ba89cb806d860072426d2e450eda056e9e9d8ee360c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9587384f876dfec24da857c0bcdb3ded17f3328f28a4d59aa35ca7c25c8102cf" + ] + }, + { + "jsonrpc": "2.0", + "id": "np400", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf60f85724891ffc25eb8c5c596e55846df4032b2edb35d0fc6ac64870db6b42f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd210aa806d0d5c95200a88fcc329357fb03782cc236bdc5f184c80246391162f", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x190", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xfa0", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe7a757335322c1008ee83083154c9a787ea3d93efce41c1b32882c8a6ea3a14f", + "transactions": [ + "0xf8688201410882520894f14d90dc2815f1fc7536fc66ca8f73562feeedd101808718e5bb3abd109fa04a18131d30b0344910cae7c41ee5c1c23171c40292d34e9a82c9c7cef3d3836aa0598a3835ad1903c3d7ad158c57ff0db10e12d8acbef318ddd0514f671a08ce94" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4df8093d29bc0ec4e2a82be427771e77a206566194734a73c23477e1a9e451f8" + ] + }, + { + "jsonrpc": "2.0", + "id": "np401", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe7a757335322c1008ee83083154c9a787ea3d93efce41c1b32882c8a6ea3a14f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf77d84bb9077b7805492805f09aaeac8fdd72dadaba54464256d1b9633d7313d", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x191", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xfaa", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x32976c704b12fd1ec0e6a409b89c8d3d5d0802f676bfd1848ae07cbb612f0289", + "transactions": [], + "withdrawals": [ + { + "index": "0x40", + "validatorIndex": "0x5", + "address": "0x13dd437fc2ed1cd5d943ac1dd163524c815d305c", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc56640f78acbd1da07701c365369766f09a19800ba70276f1f1d3cd1cf6e0686" + ] + }, + { + "jsonrpc": "2.0", + "id": "np402", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x32976c704b12fd1ec0e6a409b89c8d3d5d0802f676bfd1848ae07cbb612f0289", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8c45d111367d1e2766e18c8ef100cb4cbdd1db4171d269d0dee91b7789bf302e", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x192", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xfb4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x4cbab31c513775bdd5b7f91a153fff77cf1602430cedcebec80bedf0b6533658", + "transactions": [ + "0xf88382014208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0bab2abc49f4f65119331667d5bd95daefb8eec437cb7950b46f1b9a890efd4b7a065396085f5f690d669006b05bab15614816e44cf88bf49fcdf0a5857f364e6a1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7173d4210aa525eece6b4b19b16bab23686ff9ac71bb9d16008bb114365e79f2" + ] + }, + { + "jsonrpc": "2.0", + "id": "np403", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x4cbab31c513775bdd5b7f91a153fff77cf1602430cedcebec80bedf0b6533658", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xade6dd841f231dcce74ab564f55972731c7eb4a0b5c3ec1a64bb979f754b786c", + "receiptsRoot": "0x424252c901f76c684b72e2637c97666a35b4020fe9fd8add1bd00fc83cf57512", + "logsBloom": "0x08000000000000000010000000000010002000000000000000000000040000200000000000000000001000000000020000000000000000000000000000000400010000000000000000000000000000000000000000000000000000000000000020400000000008100000040000000000000014000000028000000000000001000008000000000000000000000000000100000000001000000000000000000000000000020000000000000000000000000000000000000000000000000000800000000000080000000000000000000000000000000000000080000000000000001000000200800002000000000000000040000000000000000000000400000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x193", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xfbe", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x64bfcedbb6b431f370027c5e2414fa70536e4cadaedca69d960d815570b1a514", + "transactions": [ + "0xf87a8201430883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0097f470d08b374cc1ea0e0ecfb841f22e6f105c4989a6a41f23619320011f4dba06c843174399416f4a98ee5b5170a4330fbc487cc1bdc4e67f8eb3ca279fa8415" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x89698b41d7ac70e767976a9f72ae6a46701456bc5ad8d146c248548409c90015" + ] + }, + { + "jsonrpc": "2.0", + "id": "np404", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x64bfcedbb6b431f370027c5e2414fa70536e4cadaedca69d960d815570b1a514", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x96e966680b69cd6f8f3c95b0bfcaa337959db055f2b4329813dd02f9e5350742", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x194", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xfc8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x4a13ab52191afd567f4587bee39174c54ca458576730a03854abfad2aca2e0da", + "transactions": [ + "0xf865820144088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a09fd0702bca1c10269dcf83862a9f07981858a8a1579f3ed68642fdc8b77478cda027b1f49755229583c844b747c040251c2671dcfe83fa26df37d4bbfb54635864" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x5b605ab5048d9e4a51ca181ac3fa7001ef5d415cb20335b095c54a40c621dbff" + ] + }, + { + "jsonrpc": "2.0", + "id": "np405", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x4a13ab52191afd567f4587bee39174c54ca458576730a03854abfad2aca2e0da", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x7fa007461e28a3bd63c35eb625b4c122197ed1d63a00b0a0959652cb745c034d", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x195", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0xfd2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x3c7adb6035b88d99e1113b076cd7ee852294e0f651e87e779f93b9625f50f173", + "transactions": [ + "0x02f86b870c72dd9d5e883e820145010882520894360671abc40afd33ae0091e87e589fc320bf9e3d0180c080a09b0a44741dc7e6cb0f88199ca38f15034fab4164d9055788834e8123b7264c87a02c38a3ecda52aebc3725c65ee1cd0461a8d706ddfc9ed27d156cf50b61ef5069" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9129a84b729e7f69a5522a7020db57e27bf8cbb6042e030106c0cbd185bf0ab8" + ] + }, + { + "jsonrpc": "2.0", + "id": "np406", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x3c7adb6035b88d99e1113b076cd7ee852294e0f651e87e779f93b9625f50f173", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3d03d9ffcd17834d8b99988eb8c1c9f36b8e627f50e2d850a6538d7610ba8457", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x196", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0xfdc", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xda2511fe0f2d0c7384fdfaa42ba9d93127690645ed7f3bb5b48ab3bf31550561", + "transactions": [], + "withdrawals": [ + { + "index": "0x41", + "validatorIndex": "0x5", + "address": "0x6510225e743d73828aa4f73a3133818490bd8820", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x31a63d6d54153ab35fc57068db205a3e68908be238658ca82d8bee9873f82159" + ] + }, + { + "jsonrpc": "2.0", + "id": "np407", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xda2511fe0f2d0c7384fdfaa42ba9d93127690645ed7f3bb5b48ab3bf31550561", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xeb5feebaa9bd10619704d66efc97f95338c3e02dcebc2710be462faa47ddfc63", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x197", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0xfe6", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x32704763870e0504f0386bb2e87511ccb2d033c83e9ef57a72327f5d23fd3996", + "transactions": [ + "0xf88382014608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0e767d5dbf82d8857bccd947a04354b0023b0e283098f75e4d7d79348c24dca95a00a4d04094359f0817637570cf1ed12dcd2614da2e845751734d67175839a3903" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x828641bcea1bc6ee1329bc39dca0afddc11e6867f3da13d4bb5170c54158860d" + ] + }, + { + "jsonrpc": "2.0", + "id": "np408", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x32704763870e0504f0386bb2e87511ccb2d033c83e9ef57a72327f5d23fd3996", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xaadc011ce89c8dd628f56494b7f19d8cf66c1555b3cb6b38fd6e31c908e83804", + "receiptsRoot": "0x0c78f3779ab455eed4ce5e60071fff80a3d289a33fd656e17017d53978fada5d", + "logsBloom": "0x00000000000000020000000040000000000080010000000000000000000000010000000000000000000000000000000000000000000000000000000004000000000000000000000000000001000000000000008001000000000000000000000000000000000000001000000000002000000000000000000000000000002000000000000000000000000000000810001000000004000000000000000000000000000000000040008000200000000000400000000000000000000800000000001000000000000000000000040100000000000000001000000000000000108000000000000000020000800000002000000100000000000000002000000000004000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x198", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0xff0", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x16f96f705d6378a460f67690c9df7ba0b0130dfb7bda8d79ac2ffe9fdee84606", + "transactions": [ + "0xf87a8201470883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a00eab4059563c228f12cd79cdc77c5594af5bb5f9778dab439aead79a99c7da9aa010476536728e9bf977ad4c2cc25fb7d5587869148789e9fd6bf40d65b9e94bbb" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7e0752ddd86339f512ec1b647d3bf4b9b50c45e309ab9e70911da7716454b053" + ] + }, + { + "jsonrpc": "2.0", + "id": "np409", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x16f96f705d6378a460f67690c9df7ba0b0130dfb7bda8d79ac2ffe9fdee84606", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0ed00985c27ccb9453093f70f7cae8594259e64c8962ee22121019210fe01824", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x199", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0xffa", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x70c71163387d8226f299ed02fd7f266f79d708f11ea9133d28a6b13ee751e259", + "transactions": [ + "0xf865820148088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0310416be8b0e49ec34116f9c8eb4dd4d4dc6e39e5c97ccb94ac96e8cd21a7333a029b7a950def860ab8bfd4e49e5f34bc731344ab60770ea27f656e64e6b2f90de" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x31d973051189456d5998e05b500da6552138644f8cdbe4ec63f96f21173cb6a1" + ] + }, + { + "jsonrpc": "2.0", + "id": "np410", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x70c71163387d8226f299ed02fd7f266f79d708f11ea9133d28a6b13ee751e259", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x7ff6b18a2c62836e16cad9956e08422a430c268cda51f219422b628491066c6e", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x19a", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x1004", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x67c274a189945d313dccd5b9cb4b7fd47614b59c716a4ed0944d8a1429781e78", + "transactions": [ + "0xf8688201490882520894579ab019e6b461188300c7fb202448d34669e5ff01808718e5bb3abd10a0a0de600e017080351550412ac87f184ec2c3f672e08f1c362ab58b94631e8864dca047d41b8691a1f7f8818e59ad473451a0edfc88826a6b808f84f56baed90d5634" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe33e65b3d29c3b55b2d7b584c5d0540eb5c00c9f157287863b0b619339c302f0" + ] + }, + { + "jsonrpc": "2.0", + "id": "np411", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x67c274a189945d313dccd5b9cb4b7fd47614b59c716a4ed0944d8a1429781e78", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x95d1e2783fcf975ce0a79a05166ad33628065812d76f1f92f88d8f77f5a49e88", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x19b", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x100e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x7ae0486d0457d3261e308c1074c7a206e11f3a41a8b3b49ff379d0998a62278c", + "transactions": [], + "withdrawals": [ + { + "index": "0x42", + "validatorIndex": "0x5", + "address": "0xd282cf9c585bb4f6ce71e16b6453b26aa8d34a53", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x78d55514bcef24b40c7eb0fbe55f922d4468c194f313898f28ba85d8534df82c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np412", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x7ae0486d0457d3261e308c1074c7a206e11f3a41a8b3b49ff379d0998a62278c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa1e185a2970fcd9903cadff06453ace3bff731a5295334d332c3fafd1d50033a", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x19c", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x1018", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb075a9e715b341d481dfad3f02ff0a123aa8043d4ae24d5f0574a7249cc00bcf", + "transactions": [ + "0xf88382014a08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa06222a14090e09278dc92b9002ee33b54e5bbbecd9afe56fa18d00dfe761ce8a1a06e8ec220dc8219ae16f46f3a4696fc8b4046fd33fa41efb473222fc058d65ed4" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x2e0f4be4d8adf8690fd64deddbc543f35c5b4f3c3a27b10a77b1fdb8d590f1ee" + ] + }, + { + "jsonrpc": "2.0", + "id": "np413", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb075a9e715b341d481dfad3f02ff0a123aa8043d4ae24d5f0574a7249cc00bcf", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x19a70ab3d8102a74b87887d95a29fe82ce4d4ab36fe3f57f336ded8bd0a7b3d6", + "receiptsRoot": "0x2ac314ac40ad6f04e3ec1fc2b315d4ce6eb64537ae9bf3fad670a0a1df1e5e3a", + "logsBloom": "0x00000001008000000040000000000000000000000000000000000000000002000000000000000000000000200000000000001000000000002000000000000000001008000000002000000000000000000000000000000000000000000000000000000080000000000000008000080000002100000000000000000200000000000100000000000002040000000000000000000000000000000000200003000000000000004000000000000000000000020000000000000000000000000000000000040000000002000000002200000000000000000000000000000001000000004000000000000000000000000000000200000000100000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x19d", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x1022", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x936ce32cab37d0a985a937a8d3c7191ec7f48a10d524d04289d59efa4ca4e581", + "transactions": [ + "0xf87a82014b0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a07af33005afb5f1b38c17ed2bb2b83a0c1d0d6ecd30ab4e32091582d5a3eceb28a008bfc076226d8ebf0a2c86c5ea5f65ea1f1d0cb7b7036b2049444c2fcfb55031" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe1b83ea8c4329f421296387826c89100d82bdc2263ffd8eb9368806a55d9b83b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np414", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x936ce32cab37d0a985a937a8d3c7191ec7f48a10d524d04289d59efa4ca4e581", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0e4a2aebaaa31e943227335fd579582b6ed68abaa2706294b038ccb00ceae64f", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x19e", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x102c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xd352dfead0be49f8a1f2f7954f90df4b3e4383f8adb54062abd8041b0a0878fd", + "transactions": [ + "0xf86582014c088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa05e26cfc612b47c55ae5a521eca26d4adbeaefe893bf1b0226cd121cbd7cdb45aa00be4c1040e89e1db4b10b4f36b38ef682de4f3308fd65d4f39346ffcf016cfdb" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4ddad36d7262dd9201c5bdd58523f4724e3b740fddbed2185e32687fecacdf6b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np415", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xd352dfead0be49f8a1f2f7954f90df4b3e4383f8adb54062abd8041b0a0878fd", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc149cc44783e5dc5c6be9d4facfc2e9d3d31dff27f8495ea3fc2acfc22310516", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x19f", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x1036", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xddf15ae692657c7be84b2e663acd7d669dc84a83622c9bbca07aba3a8461d8a6", + "transactions": [ + "0x02f86b870c72dd9d5e883e82014d01088252089488654f0e7be1751967bba901ed70257a3cb799400180c001a0a79b0ff9846673061d1b90a17cd8bd9e7c7f62b99b39fbe4749777d3ed4544e0a0750ecfe9895402861ebea87e9b483b2c116bc2d4920329aa1c29efb9dcdf47e6" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x156c0674e46cdec70505443c5269d42c7bb14ee6c00f86a23962f08906cbb846" + ] + }, + { + "jsonrpc": "2.0", + "id": "np416", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xddf15ae692657c7be84b2e663acd7d669dc84a83622c9bbca07aba3a8461d8a6", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x39d41e6a842119b876ef50fcce4e677b2760950f191f0b17ac11bb61f5d271b0", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1a0", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x1040", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x5647e4a4349ab2ed23ddc1f61244c94f194735701ad4041ea62bc578654fecdb", + "transactions": [], + "withdrawals": [ + { + "index": "0x43", + "validatorIndex": "0x5", + "address": "0xa179dbdd51c56d0988551f92535797bcf47ca0e7", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xdfc56ec6c218a08b471d757e0e7de8dddec9e82f401cb7d77df1f2a9ca54c607" + ] + }, + { + "jsonrpc": "2.0", + "id": "np417", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5647e4a4349ab2ed23ddc1f61244c94f194735701ad4041ea62bc578654fecdb", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6f8f7979fade5692d7fd5e0f6253e0e3082614421af4bcfbd63c12f2df06876f", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1a1", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x104a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb50098d59b2351e10448f5560aff3f933bb24fed7101cda025bcdd5308fb4631", + "transactions": [ + "0xf88382014e08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0fea6631902fceb5662ca53076387bbbb0e0fd9bcac1df121172fd29bd6700434a0632755563256841b198d853ee1861224df35abe91c6d15ca60cb3f660ce05e2d" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x395d660f77c4360705cdc0be895907ec183097f749fac18b6eaa0245c1009074" + ] + }, + { + "jsonrpc": "2.0", + "id": "np418", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb50098d59b2351e10448f5560aff3f933bb24fed7101cda025bcdd5308fb4631", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x26b3aa514e4bfed98a760b1cc6d5c7c855232ecac4f00826049369385376458b", + "receiptsRoot": "0xa3ea729352d4252acd6b48dcc940d3acfe0d657ca5d3091eda1ae882c7c14776", + "logsBloom": "0x00000080200000000000000000030000000000000000000008000000000000002000000400000400000000000080802000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000800000000000002000000080000000000000000000000000010000000800000000000000000000000000000200000000000000000080000000000000001000000000000000000000004000080000100100000000000000000200000000000040040000000000000000000040000000000100010080000000000000000000000000010000000000000000000000000000000000000000000000000040000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1a2", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x1054", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xd87426372101b44c6fb40defa47f5e64ced815cf6bcbe830367d328e52fa3bd5", + "transactions": [ + "0xf87a82014f0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa08ded8700920cf761c49ef0831076f10597be8fe624b891585941b1a1d145a18fa05640b1e1c59257bc6b6352be6bb6a7862a541b3fca52da28912b08b8072b57e5" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x84c0060087da2c95dbd517d0f2dd4dfba70691a5952fe4048c310e88e9c06e4f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np419", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xd87426372101b44c6fb40defa47f5e64ced815cf6bcbe830367d328e52fa3bd5", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x730477c9b8be2e32598ff45ddf03837963e5d2fcd5c8c07d23b47b385c22d4b7", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1a3", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x105e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xac9d6592b309e9e3ec0d899eda9ccd7d508e846553ac4a87da8b420c99173211", + "transactions": [ + "0xf865820150088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0bb5b1c9e4a9e86b6381ce83f476e3efb45b847315ec3e27e1536539ba2290f42a07eee4b7b9b0d0dc1b873baf519a668f4605ccbb82ad619acb74598535a35bdd1" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf4df943c52b1d5fb9c1f73294ca743577d83914ec26d6e339b272cdeb62de586" + ] + }, + { + "jsonrpc": "2.0", + "id": "np420", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xac9d6592b309e9e3ec0d899eda9ccd7d508e846553ac4a87da8b420c99173211", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xef5088187720800d3dec63e4e25560c839cad852b7a795fd9e9876ee2a02b16a", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1a4", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x1068", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x834b28c2883caaa276a3a0f2603da1bb8171001967787b96071588f296b7671b", + "transactions": [ + "0xf868820151088252089447e642c9a2f80499964cfda089e0b1f52ed0f57d01808718e5bb3abd109fa0c37c23a91d6abced211855a2d6d5e383f54aa6ff40c26abc5f27a22cdafa5618a0190f82ff101eabad8b9c7041006dcb3e3a9a85c814938bef8ec7d1aa63fa5892" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x0bb47661741695863ef89d5c2b56666772f871be1cc1dccf695bd357e4bb26d6" + ] + }, + { + "jsonrpc": "2.0", + "id": "np421", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x834b28c2883caaa276a3a0f2603da1bb8171001967787b96071588f296b7671b", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8fa327b5c3e6a5036585a3b751910d613c3d2b6b56b0a5c1da7727ce50d4cb57", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1a5", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x1072", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x1232a401598e285a5e94aaa0644787458ac9e410b4b50cbc103523f2d2d4c198", + "transactions": [], + "withdrawals": [ + { + "index": "0x44", + "validatorIndex": "0x5", + "address": "0x494d799e953876ac6022c3f7da5e0f3c04b549be", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4a1f7691f29900287c6931545884881143ecae44cb26fdd644892844fde65dac" + ] + }, + { + "jsonrpc": "2.0", + "id": "np422", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1232a401598e285a5e94aaa0644787458ac9e410b4b50cbc103523f2d2d4c198", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa5eef4d5746f0409111e198bb292fd06bf9ac9a14dc734ca636005246e713e5c", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1a6", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x107c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x271fd072d8e81da656b1f06548d486ce23f9fd399e070d3a01a3bd28c2d4eb7c", + "transactions": [ + "0xf88382015208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0435a46c3720f21ff83b01b3d6e88f602e45dee024e69f7df083e47ee400fa063a020b2e545bea301a0322157c61d6f8bdee62066305c627c1c10fb9eb1fbdf0fed" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9b133cc50cbc46d55ce2910eebaf8a09ab6d4e606062c94aac906da1646bc33f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np423", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x271fd072d8e81da656b1f06548d486ce23f9fd399e070d3a01a3bd28c2d4eb7c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5486d8d4c4159eb6389774b47a76d5e347e3b31ecf92c08eda9e261e3106f0cc", + "receiptsRoot": "0xc0c07d0984b850e6ccc2e081d26ec135c42d526e9bb51a6c1987784d659c07d5", + "logsBloom": "0x00000000000000000000000000000200000000000020000000400000000000000000000000000000000000100000000000000000000400000000000000200010000000000a00000008000000000200000000000000000200000000000000000000000000000000000000000000000001000000000000000000000000000000000000000806000000000000000048000000000000002000000040000500001000000002000000000000000000000000000000000000000000000000000000000000000000000000000000800000000800002008000000000000000000000000000000020000100010000000000000000000000000000000000000000010000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1a7", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x1086", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x966f75efe4cd3d4171d4dd7dbe65453d3fae561f5af4d67142cc15ad53dae212", + "transactions": [ + "0xf87a8201530883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a015ae0fac40a467ff5ad10fe01c838c564f0d30707c8b02be656345842959fedda07a3d9842f721d8cb4494a2df6ff689c4c19e44c8c81f013d1f969624d49850b2" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x473b076b542da72798f9de31c282cb1dcd76cba2a22adc7391670ffdbc910766" + ] + }, + { + "jsonrpc": "2.0", + "id": "np424", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x966f75efe4cd3d4171d4dd7dbe65453d3fae561f5af4d67142cc15ad53dae212", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa9339a9c149937412b8c9d01a85c7af270578af9eebb80ad2cf208764c40e608", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1a8", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x1090", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x92a740edd1bceefb2f497e906a5f53bc10928c909069ba76b34663dabfc01f91", + "transactions": [ + "0xf865820154088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0c14791fa1c6907f6279226a31c5f287c93702ba72f19fb9999b93b8ad612b36fa0371a0819796295976ab02fcafbe818a711cf6485a21d038dcb72b5000f04d63d" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x225dd472ef6b36a51de5c322a31a9f71c80f0f350432884526d9844bb2e676d3" + ] + }, + { + "jsonrpc": "2.0", + "id": "np425", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x92a740edd1bceefb2f497e906a5f53bc10928c909069ba76b34663dabfc01f91", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xac3cc175fd0ba02252342155b4d9dd7fb790eb49b667058912b43f5bd6e939d5", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1a9", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x109a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x5a7a3b8f0d389c13d810588336964f1a94b29184e3d9bc751eb64ef4635ad0f5", + "transactions": [ + "0x02f86b870c72dd9d5e883e820155010882520894d854d6dd2b74dc45c9b883677584c3ac7854e01a0180c080a07a17de801de3309b57dd86df30b61553d5c04071581d243f33f43c4d64930e09a075f7e820212e8f96d7583c66548719db621537fe20f7568d5ee62176881b70e8" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x31df97b2c9fc65b5520b89540a42050212e487f46fac67685868f1c3e652a9aa" + ] + }, + { + "jsonrpc": "2.0", + "id": "np426", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5a7a3b8f0d389c13d810588336964f1a94b29184e3d9bc751eb64ef4635ad0f5", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe677d652ba3a8822155791a1d1491ee57497ebfa49e3e38c909752dd8067a9e8", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1aa", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x10a4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf963480776054d809830c23d97833cfbf2971fc0fa04a6fe4974ea25a761f8c9", + "transactions": [], + "withdrawals": [ + { + "index": "0x45", + "validatorIndex": "0x5", + "address": "0xb4bc136e1fb4ea0b3340d06b158277c4a8537a13", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4416d885f34ad479409bb9e05e8846456a9be7e74655b9a4d7568a8d710aa06a" + ] + }, + { + "jsonrpc": "2.0", + "id": "np427", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf963480776054d809830c23d97833cfbf2971fc0fa04a6fe4974ea25a761f8c9", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x08414161950ff53f6f053f2886c473a22eb595a0052de01fd24c7af1bc27a5ac", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1ab", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x10ae", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe8c8baed11565acb9d54e46ed79327292e07686ada5cd14fb02558ac39c518ec", + "transactions": [ + "0xf88382015608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa02f6b9a47dcc55d9130085e0dfd615fee0acea46517280eea07dff8ee6afd40e3a01fc33c02a467db6d30ccf56ad8b5bb32fd49ad9a7866db580e7a581987518921" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xae627f8802a46c1357fa42a8290fd1366ea21b8ccec1cc624e42022647c53802" + ] + }, + { + "jsonrpc": "2.0", + "id": "np428", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe8c8baed11565acb9d54e46ed79327292e07686ada5cd14fb02558ac39c518ec", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5afe7e66edc543cc377a33069ba58d5788801f1ef0f370d69ff71db5f63b6b88", + "receiptsRoot": "0xb278e6670351b21cd1c267f24972d7868327ae82ef7a3b377af968b4c6659925", + "logsBloom": "0xc0000000000000000020000000000000000000001000000001000000000020000000000000000004000008000000000000000000000000000000000000000000000000001000000000000000800010000000000000000001000000000000000000000000008000000000000000000201000001800000000000000000000000000000000001000840080000000000040000100000000000000000000000000000000000000000000000000000000800000000000000000000000000020000000000002000000000000000000000000040000000000000001000000400000000010000000000000000008000000000000000000000000000100000020000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1ac", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x10b8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xedf9debf0ac1be313a1f9e6f0121d36c284e2c7962acac1fa5c8aae207c07b34", + "transactions": [ + "0xf87a8201570883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa06cbb3f84663bf7369864941fe566b1beb8d5db0095cbd49ebfdee89c164031e6a0461b62f4b01d15206e95e6c7bfe9364456d8b7edd446d1b488a2688c47b83775" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x8961e8b83d91487fc32b3d6af26b1d5e7b4010dd8d028fe165187cdfb04e151c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np429", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xedf9debf0ac1be313a1f9e6f0121d36c284e2c7962acac1fa5c8aae207c07b34", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf1c25b007a4c84577aa49389214e8b8b63f81cb20b61095db784cd8e781fbdcc", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1ad", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x10c2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x94a14e5fafedb96bffc4624affb9a20762f447e5abb90865c4418a539743932e", + "transactions": [ + "0xf865820158088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0a285aa615fe480c778997ca57059b8ddec5cee0e5a94ec05cd028a03d04aadaba07549f0c6ded9fe03eb40b413803b8f02d9dc51591e29977d12a204518648008e" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc22e39f021605c6f3d967aef37f0bf40b09d776bac3edb4264d0dc07389b9845" + ] + }, + { + "jsonrpc": "2.0", + "id": "np430", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x94a14e5fafedb96bffc4624affb9a20762f447e5abb90865c4418a539743932e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x401f1feec84dc7c894bb9f03dd52b5af121262ab2f6bd29e6de4e96c1ed67870", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1ae", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x10cc", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x855b2ccb1c00d717f49ec7074cee1f781edfc072eeef44012e18613a9172fc9d", + "transactions": [ + "0xf8688201590882520894c305dd6cfc073cfe5e194fc817536c419410a27d01808718e5bb3abd109fa0163f29bc7be2e8fe3c6347fe4de06fa7330e3a3049c0e9dcded1795ff1c1e810a04ea7492a5e457fd21252166f5a5d5d9d5e5c7a19da2c7fd4a822bf60156b91a9" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x7cfa4c7066c690c12b9e8727551bef5fe05b750ac6637a5af632fce4ceb4e2ce" + ] + }, + { + "jsonrpc": "2.0", + "id": "np431", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x855b2ccb1c00d717f49ec7074cee1f781edfc072eeef44012e18613a9172fc9d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc97f5e63e102992e2a849afad97481ea818d213707de515acd9c2bc246cdf65f", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1af", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x10d6", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x4155e12dee1bb9ed17527871568425b8eb672004a2e2c19cb1947004fc5f0b0e", + "transactions": [], + "withdrawals": [ + { + "index": "0x46", + "validatorIndex": "0x5", + "address": "0x368b766f1e4d7bf437d2a709577a5210a99002b6", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x943d79e4329b86f8e53e8058961955f2b0a205fc3edeea2aae54ba0c22b40c31" + ] + }, + { + "jsonrpc": "2.0", + "id": "np432", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x4155e12dee1bb9ed17527871568425b8eb672004a2e2c19cb1947004fc5f0b0e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x9c177f669a297c904a6a6ad51765a5916a0e0a3d9858b289e70bf054b370d685", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1b0", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x10e0", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xd660a48f06384f7ee4402d24193c76d2f4a00b85ca53ae9883b4ee3c07260586", + "transactions": [ + "0xf88382015a08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa078d6fdbc4224106e1f59483aff597485ed0eebf922317913522a0693727b5ee8a035876b3170b9a88dc391f83dcac8088aeb65233613c74d8f50f1d1d3b1ce842f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x66598070dab784e48a153bf9c6c3e57d8ca92bed6592f0b9e9abe308a17aedf0" + ] + }, + { + "jsonrpc": "2.0", + "id": "np433", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xd660a48f06384f7ee4402d24193c76d2f4a00b85ca53ae9883b4ee3c07260586", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x98d101f68f7aa5bb170cfdd60281d7a5c3ae335ab03c0f87bdb5e72cc022d55f", + "receiptsRoot": "0x1919995eb19582a49f7b79b55e7ec75fae399916006f29e4177543d99cc2a5e3", + "logsBloom": "0x00000000000040000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000080000040004000001000000008000000000000000000000000000000000000000000000000000000000800000000000000080000001000000000000080000000000400020000400000000000000000000000000000000000000000000001000000000000000000000000000000000000000000012000400000000000002000000000000000200000000100000000000000000000000000000000000000000000000000000000004004000000000000002000000000000002010000004000014000000000000080810000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1b1", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x10ea", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x73404b62b42dbc6a6604152b87426e852cc3b34847f45f27c0fca1f3a619f84a", + "transactions": [ + "0xf87a82015b0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a09fb0d3ddf1fce9562d227b3cd6c35ac2e89f39141823d94cda0e6efb4519c715a06925af0950104623efa7954872196fe6d539eb269263a17db3740652382d100f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xac8fe4eb91577288510a9bdae0d5a8c40b8225172379cd70988465d8b98cfa70" + ] + }, + { + "jsonrpc": "2.0", + "id": "np434", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x73404b62b42dbc6a6604152b87426e852cc3b34847f45f27c0fca1f3a619f84a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x7d06044c1009a2320b83bdfe22ffe7b8ffa6fa1f65d5e42f7c1588417a8ff421", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1b2", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x10f4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x9832bc48443f86a5809f75ad91caa04101363a43b300cef39918deaae8594e08", + "transactions": [ + "0xf86582015c088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0aa24c6fb2c99f1ce21f7ffd84e87fb6f81ff76cebe06fb5c0871294a353210dfa0350602877ed48896e8b4124b35c0c47da66c17fc0d553d9248ca1de942114306" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x2b0018a8548e5ce2a6b6b879f56e3236cc69d2efff80f48add54efd53681dfce" + ] + }, + { + "jsonrpc": "2.0", + "id": "np435", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x9832bc48443f86a5809f75ad91caa04101363a43b300cef39918deaae8594e08", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x03d23380eb6a02b52fcfeb82c0fefd180c014e72a7f48f2627237e7bda6d5610", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1b3", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x10fe", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe5e23a0fd4a2515c0e1292823b094a1aeec3ed64db400675b591fc077bf34c3f", + "transactions": [ + "0x02f86b870c72dd9d5e883e82015d0108825208942143e52a9d8ad4c55c8fdda755f4889e3e3e77210180c001a0673c5473955d0d26d49b25b82af905ee33ba365178f44dc4ac39221efec23c88a017f46fc9b15ba0c1ea78d4d9f773582d94f61f6471f2918cb0598f33eb9bc89b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x823445936237e14452e253a6692290c1be2e1be529ddbeecc35c9f54f7ea9887" + ] + }, + { + "jsonrpc": "2.0", + "id": "np436", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe5e23a0fd4a2515c0e1292823b094a1aeec3ed64db400675b591fc077bf34c3f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf8829f712e0ea692e266ae3c78400816c5f5bc1d75a3bff3816f7fef71b2044c", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1b4", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x1108", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x6e26197c94723ba471d049f6082abd0a6e684225b2ee9d8fa675b18ef11492c1", + "transactions": [], + "withdrawals": [ + { + "index": "0x47", + "validatorIndex": "0x5", + "address": "0x5123198d8a827fe0c788c409e7d2068afde64339", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x3051a0d0701d233836b2c802060d6ee629816c856a25a62dc73bb2f2fc93b918" + ] + }, + { + "jsonrpc": "2.0", + "id": "np437", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x6e26197c94723ba471d049f6082abd0a6e684225b2ee9d8fa675b18ef11492c1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc66ecc1bdb4fa4b85c0b383d4db20fdaa2cba32973260dc444abb43e8536e93a", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1b5", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x1112", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb5f0ca3b4503c50b8eab9c63a95b209426af616a5b0d8468e63246c3f590caac", + "transactions": [ + "0xf88382015e08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0179370023b242bccf25d4899c2f29936353b5f1c37a8f7c665e55b75f80bf297a018a66d1d2ef7072f7fc54af07d15edc14ecf5a71f510be740c090f0815178ff2" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x44a50fda08d2f7ca96034186475a285a8a570f42891f72d256a52849cb188c85" + ] + }, + { + "jsonrpc": "2.0", + "id": "np438", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb5f0ca3b4503c50b8eab9c63a95b209426af616a5b0d8468e63246c3f590caac", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa9b50e298c6a4bbd23a659ab24a3a7426b1087497561c39de2f1bf27da019b83", + "receiptsRoot": "0x8f45041560ebf83ec428723c6d69db271346e4c5a1b234b56efe318d549187cb", + "logsBloom": "0x00000000000400400000000000041000000000000004000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000202000000000000000000000000000080000000000000000000000010000000040000000004000004800000000000000400000000020000400000000000000000008000020400000000000000000000000000000000000000000000000000000000000000000000000000000000000800002000000001000000000000800002000000100000000000000000000000000000000004000009000000000008000000080000000000000000000000000000000900", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1b6", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x111c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xfe8e1ceca43818cb8f2e4fc94ead6cea53a8fd515af2bc67a39a15584ec3cd86", + "transactions": [ + "0xf87a82015f0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0354b9d4a470abdae9da30183321b96b5fd09bc96c1ebd3137b3c6350c21e8de2a026877262b14edc851e17cba052b022dd1038fd51ef65ecbaff09dd07186f035a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x6e60069a12990ef960c0ac825fd0d9eb44aec9eb419d0df0c25d7a1d16c282e7" + ] + }, + { + "jsonrpc": "2.0", + "id": "np439", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xfe8e1ceca43818cb8f2e4fc94ead6cea53a8fd515af2bc67a39a15584ec3cd86", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xad6a72de336a98aec47ed431bf7d39d537741125313255629633cba91b0097bd", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1b7", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x1126", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xfd02d6b4d954d36af8829bf98464c0cc410de1e28216e45ac5e90fc1fc5780d3", + "transactions": [ + "0xf865820160088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa09c6b3542e181028aad33517584cd16e92836f975955abdcbf1205b6250c921d4a040816d88e011c2d3073502523867b94987fa0781793a7857ff2453ec2d121444" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x581ddf7753c91af00c894f8d5ab22b4733cfeb4e75c763725ebf46fb889fa76a" + ] + }, + { + "jsonrpc": "2.0", + "id": "np440", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xfd02d6b4d954d36af8829bf98464c0cc410de1e28216e45ac5e90fc1fc5780d3", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x863de67ea016127a436ee6670f8642bd5ab997ce75361c3cce667abbe90b7283", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1b8", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x1130", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe0c31051877e8d3f6f625498659eff12247ded622d4155f6fd4a498852e46192", + "transactions": [ + "0xf86882016108825208940fe037febcc3adf9185b4e2ad4ea43c125f0504901808718e5bb3abd10a0a0654dc39f93a879b9aec58ace2fdbd5c47e383cae2d14f1a49f6ec93d539be892a070505a0ef2e83f057e9844dbd56eda0949197f0c4a2b6d0f2979db1710fca4ed" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9a1dfba8b68440fcc9e89b86e2e290367c5e5fb0833b34612d1f4cfc53189526" + ] + }, + { + "jsonrpc": "2.0", + "id": "np441", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe0c31051877e8d3f6f625498659eff12247ded622d4155f6fd4a498852e46192", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x66989995258d8db8bd3b8eac83c7762c50323b8f21f1aaddf3ad0208afc6318d", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1b9", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x113a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe9da2b3df6fbc520bf3a80b36bd3437210880763ea7acbf422076049724a14ac", + "transactions": [], + "withdrawals": [ + { + "index": "0x48", + "validatorIndex": "0x5", + "address": "0xd39b94587711196640659ec81855bcf397e419ff", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x54a623060b74d56f3c0d6793e40a9269c56f90bcd19898855113e5f9e42abc2d" + ] + }, + { + "jsonrpc": "2.0", + "id": "np442", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe9da2b3df6fbc520bf3a80b36bd3437210880763ea7acbf422076049724a14ac", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf37b2d059d8764938039410fc2581f4793fb4f9c66abf4f8a32276dd60334f4d", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1ba", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x1144", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xeafce24dfb100daa2a1ee55da0030d8e057fc943b96b6e7f321af98b47e8107e", + "transactions": [ + "0xf88382016208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0bf7859d7e53ab582f4189f50f06832f2fa9763498350b739d7a677b34df97861a03ab21050f73bda7c737cef08e6a77edc9766aa0ef14dfdfc22fbcfdb6771825e" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x1cfeb8cd5d56e1d202b4ec2851f22e99d6ad89af8a4e001eb014b724d2d64924" + ] + }, + { + "jsonrpc": "2.0", + "id": "np443", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xeafce24dfb100daa2a1ee55da0030d8e057fc943b96b6e7f321af98b47e8107e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x23eacf2b963df64726e41314251669bf12f3925de3933e0b713863d1a7a6fc6b", + "receiptsRoot": "0xc97de406788b669a824183dab763b8caa8988371aea1f18b96e6b1f9abdee729", + "logsBloom": "0x04000000200100000000002000000204000000000000000000000000000008000800000000000000000000000000000001000080000000400000000000000000000000000000000000000000000000000000400000100000000000004000040000000000000000001000000000000000080000000000000000200000000000000000000000000000000000008000000000000000000000000000040000000000000000040100000000000000000000000000000000010000080000000000000000001000002000000000000400000100000000000004000040020000000000000000000000000000000000000000000010000010000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1bb", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x114e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa53dc7dc3ac37fdd69bedd119e5113397594ab4171b7c010913864890dbd7f96", + "transactions": [ + "0xf87a8201630883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0332397d6a00a7d2a3453bf053c8d158774d82d6ea252c2d564bbd48f9e882418a01187aef824b2759cba8c1574666919b77889353a9905720170518b03b38cc71d" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xad223cbf591f71ffd29e2f1c676428643313e3a8e8a7d0b0e623181b3047be92" + ] + }, + { + "jsonrpc": "2.0", + "id": "np444", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa53dc7dc3ac37fdd69bedd119e5113397594ab4171b7c010913864890dbd7f96", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x71e7debe9374beede2414966d6eb2c2eadf548c293ba65821869bc274709badb", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1bc", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x1158", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x48640726ce7b39f951f82d46cfd4f8d71c93534109a0f93810c41289f6c97d2e", + "transactions": [ + "0xf865820164088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0b7296876d0713a392d440d71244cda1a3ecb09009a2f4d0ae5d26a398a8bee92a04dd844c3b7cbf88f10b080a3a0fd8a0e21e8d3041450c69786efe9ee7af18dcc" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe13f31f026d42cad54958ad2941f133d8bd85ee159f364a633a79472f7843b67" + ] + }, + { + "jsonrpc": "2.0", + "id": "np445", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x48640726ce7b39f951f82d46cfd4f8d71c93534109a0f93810c41289f6c97d2e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x58a574089cbd9986bf63c3ee8e0e8d400e9b97b8d1280166f7505de051f4c661", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1bd", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x1162", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x40fa37188938bd349b17c8738f79a071533e0c0f6eaf4b1d6d6614fcae9925d6", + "transactions": [ + "0x02f86b870c72dd9d5e883e820165010882520894046dc70a4eba21473beb6d9460d880b8cfd666130180c080a09a954eff1b0e590a3a78b724b687c6ab944181990998780d56cc3593c704996ea0418db96b5dc1057f6acb018244f82ed6ece03d88c07f6ae767eaebe3b7ac9387" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb45099ae3bbe17f4417d7d42951bd4425bce65f1db69a354a64fead61b56306d" + ] + }, + { + "jsonrpc": "2.0", + "id": "np446", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x40fa37188938bd349b17c8738f79a071533e0c0f6eaf4b1d6d6614fcae9925d6", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xbd0820c57ebb5be91343940d7197af10c1d95a23a1b99bc5fa1a77997849273c", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1be", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x116c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xeebf24886684542e08624e438bfad2c52eded1a4924aef3fd58d60ed6eaa1d19", + "transactions": [], + "withdrawals": [ + { + "index": "0x49", + "validatorIndex": "0x5", + "address": "0x6ca60a92cbf88c7f527978dc183a22e774755551", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9d2b65379c5561a607df4dae8b36eca78818acec4455eb47cfa437a0b1941707" + ] + }, + { + "jsonrpc": "2.0", + "id": "np447", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xeebf24886684542e08624e438bfad2c52eded1a4924aef3fd58d60ed6eaa1d19", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xbc2acbe23d81c5bec8c73c20cfbb12be681cc92fa399ed4a44e7a91fb433c577", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1bf", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x1176", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x837fc89a9611fa0b6a0d2f5a7dec3e06eda2ea3ee84bc6ce214c432b243c256f", + "transactions": [ + "0xf88382016608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0daffe9dd6ca6d33e1a44ce5725c7e795639c4bd4a36cfb18d520c9fc892b7ca5a01286dcff57cb583238854ca89346c969387d982ca7e14cbd82413855fdda282a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x5855b3546d3becda6d5dd78c6440f879340a5734a18b06340576a3ce6a48d9a0" + ] + }, + { + "jsonrpc": "2.0", + "id": "np448", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x837fc89a9611fa0b6a0d2f5a7dec3e06eda2ea3ee84bc6ce214c432b243c256f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x95fea999be7fc8cfa0e1c8a9a10dc33d073417bf87ff698edab332c6e18ecc60", + "receiptsRoot": "0xcf29f818a1be0922fc0576d2500603f4e9ab8a9e251986d891170f993f0c8f0a", + "logsBloom": "0x00000000000000000000000004010001000020000000000000040000000000000000000000000000000000000001000000000080001000000000000000000400000000000800000000000000000000000000400004000000000000008000000000000000000000000000000000000000000010000000000000000040000008000000000000000000000000000000000020000004020000000000000000000002000208200000000000080000000000000000000000000000000080000020000000000001200000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000010018000100000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1c0", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x1180", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x05505959a8095b30ab40f55294926448248b48b0430ce33332c7b748e956aafa", + "transactions": [ + "0xf87a8201670883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0745918999757459ef7ab7145b734444d0437fa7b3939a6ca2a07652a727d1ef9a0074b0898accddb3ac54941b1fce130c31edd3d838dfefd506668cd989f4c5389" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xd6a61c76ae029bb5bca86d68422c55e8241d9fd9b616556b375c91fb7224b79e" + ] + }, + { + "jsonrpc": "2.0", + "id": "np449", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x05505959a8095b30ab40f55294926448248b48b0430ce33332c7b748e956aafa", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa4269875f0bd6dc1360830e3e07eae0956700e8c3aa69cd61b423abf51bfce54", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1c1", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x118a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x57095cf08428bbd1fff32a14f1a811750ff2de206ee3ea1d6f6f18f7a2606d30", + "transactions": [ + "0xf865820168088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa01f0e57c3b6f3908a7afb46717ef32caf9b73c4a4b2f48b09e0fcbea02ae716e1a017c79cab83300efab682d0c0438b23b49136a17e22560e75d32014c5951b4fd4" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x96ac5006561083735919ae3cc8d0762a9cba2bdefd4a73b8e69f447f689fba31" + ] + }, + { + "jsonrpc": "2.0", + "id": "np450", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x57095cf08428bbd1fff32a14f1a811750ff2de206ee3ea1d6f6f18f7a2606d30", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd315f1048882fde9bc00a0bae351ab3229cec00efa7ef4b61fd5c1be40619f81", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1c2", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x1194", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe6d3cb3da9e188604d0e8bc2f03a0df4fefa836f9bf4b679e54e97138f72dd08", + "transactions": [ + "0xf8688201690882520894104eb07eb9517a895828ab01a3595d3b94c766d501808718e5bb3abd10a0a0597dbb3f69603be721ae0f2a63eeee9f008829ff273b54243673f9ea192ddc0aa01f7dd04defb45af840d46a950b8bede0b3ce8a718004c1ca2f3bbd4efcbd7563" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4ced18f55676b924d39aa7bcd7170bac6ff4fbf00f6a800d1489924c2a091412" + ] + }, + { + "jsonrpc": "2.0", + "id": "np451", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe6d3cb3da9e188604d0e8bc2f03a0df4fefa836f9bf4b679e54e97138f72dd08", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3222feed7d40d321811eb16ac78aaa0561580b176e0605bfecc30427a7702996", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1c3", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x119e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x465bd8f010df142744fc22da07b631a4e2d11ae75bca1608f7592548c420178b", + "transactions": [], + "withdrawals": [ + { + "index": "0x4a", + "validatorIndex": "0x5", + "address": "0x102efa1f2e0ad16ada57759b815245b8f8d27ce4", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc95a6a7efdbefa710a525085bcb57ea2bf2d4ae9ebfcee4be3777cfcc3e534ea" + ] + }, + { + "jsonrpc": "2.0", + "id": "np452", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x465bd8f010df142744fc22da07b631a4e2d11ae75bca1608f7592548c420178b", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x96bcc6f26c5f94c33c57d1614edd2b385e36d9972250c79758eeaeb09927c0a8", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1c4", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x11a8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xc7131bb27a6e1395d028d543cfd6f9e71ec4f2d2ecbc44cef53b5b626e01cad9", + "transactions": [ + "0xf88382016a08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a03fc929c6e9476221ddd5f2f5093981cc13f4b8206ee3454720f06c0bd5c95caba038f23a2c21ba59155127a15502ddd731f30d6f94c6aafde8e73fbe39237766a2" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x2b2917b5b755eb6af226e16781382bd22a907c9c7411c34a248af2b5a0439079" + ] + }, + { + "jsonrpc": "2.0", + "id": "np453", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc7131bb27a6e1395d028d543cfd6f9e71ec4f2d2ecbc44cef53b5b626e01cad9", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x168c847144cfea8b88e6ec4f673ebddbf18331bde8002e044b1d1df7408edf04", + "receiptsRoot": "0x4ff26b781abcaf6d8a14f4f5283feeee87038dbcb46b9987d6042a01b1b07f9a", + "logsBloom": "0x00000000000400000000000000800000000000002000008000200000020000000000000000000000000040000000000000000000000000000000000000800000000000000020000000000000000000000000000000000000400000000000000000000000000000000000000000800400000000000000000000000400020000000000000000800000000010000000000000004000000000000000800000000000000000001000000000000000800000000000000004010000000000000004000000000000000000000000008000000000000000000000000004000020010000000008000000000000000000000000000000100100000010000020000004000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1c5", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x11b2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x6d6eba2abd0851251651f038c9bcd8b21c56e6cefc95adb259a2b0c3ae4f158d", + "transactions": [ + "0xf87a82016b0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa07061a8a3f917f765ec8aef5e4ad237d377c0131f63f31da7bdc6af9942a1bc4aa051bf3e7c6676f2fbde507834995f4e269113adf35b98bc71cd22d9c168692f5c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x18d5804f2e9ad3f891ecf05e0bfc2142c2a9f7b4de03aebd1cf18067a1ec6490" + ] + }, + { + "jsonrpc": "2.0", + "id": "np454", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x6d6eba2abd0851251651f038c9bcd8b21c56e6cefc95adb259a2b0c3ae4f158d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x7e75fe29c17414bea5febf41c577c117b57c1a731aa7a18b6c5d2ba9e3bc27dd", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1c6", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x11bc", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x5cfbc66c760f871b8cf6d87140887788db0622a0f54274737f9cd043b156f50c", + "transactions": [ + "0xf86582016c088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0ec3fe55b96a9d14e22fc0a8aa5991138ba954245754c0e0dda2b5b7dbb6711caa0296a6b87da18224fac7c922e2a7f0ec41330a6f510934a1e0e3c6a65dd72dfcb" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb47682f0ce3783700cbe5ffbb95d22c943cc74af12b9c79908c5a43f10677478" + ] + }, + { + "jsonrpc": "2.0", + "id": "np455", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5cfbc66c760f871b8cf6d87140887788db0622a0f54274737f9cd043b156f50c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xdf26695268f674fc809ad21c323bcab53727af440302b923eec2d46ee3cd7aa3", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1c7", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x11c6", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x8f4a24fc6a150744744d03371d34758cf69d4216538804395397ed081692c7fb", + "transactions": [ + "0x02f86b870c72dd9d5e883e82016d01088252089446b61db0aac95a332cecadad86e52531e578cf1f0180c080a0774ced5c8674413b351ae8ac3b96705d1d3db10deae39134572be985f16c008ba06f3e4b250f84fcf95ae85946da8a1c79f922a211dbe516fcfcff0180911429b8" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe4b60e5cfb31d238ec412b0d0e3ad9e1eb00e029c2ded4fea89288f900f7db0e" + ] + }, + { + "jsonrpc": "2.0", + "id": "np456", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x8f4a24fc6a150744744d03371d34758cf69d4216538804395397ed081692c7fb", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x358b2d72362d209f8c7131a484e49caff1dda8f550fe6103be80ac369cfe49fc", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1c8", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x11d0", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xafcf58746fc811dd74a0e4a66d91efbb00b2ab2c96680e132234a947798abf7a", + "transactions": [], + "withdrawals": [ + { + "index": "0x4b", + "validatorIndex": "0x5", + "address": "0xfcc8d4cd5a42cca8ac9f9437a6d0ac09f1d08785", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xfc0ea3604298899c10287bba84c02b9ec5d6289c1493e9fc8d58920e4eaef659" + ] + }, + { + "jsonrpc": "2.0", + "id": "np457", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xafcf58746fc811dd74a0e4a66d91efbb00b2ab2c96680e132234a947798abf7a", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x2dd7146f049ba679aae26c42d1da7f6660ea964a7b227509e5296a9d0170e93e", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1c9", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x11da", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x5346eb38677572982317e96be00144f1600800e5a738c875522183ad74f408d4", + "transactions": [ + "0xf88382016e08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa038214a2cc756a0ffe043200d5e12183223f81912c0156df732c3b1d85bc2a237a0744a52bf9fca64223bc279e589d21b9fda190325bf3b576f41a792ccbec5bc08" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4c3301a70611b34e423cf713bda7f6f75bd2070f909681d3e54e3a9a6d202e5a" + ] + }, + { + "jsonrpc": "2.0", + "id": "np458", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5346eb38677572982317e96be00144f1600800e5a738c875522183ad74f408d4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x62b1bdf3b4a63f480b24af9f3b55dc6ad6e52bb81caa13b286960694b3b600b0", + "receiptsRoot": "0x25a0fc424c07569fb4229958de04f1d6497b3d8b6a78757f42963f95c354e2b1", + "logsBloom": "0x10001020000000000000000000000000000000000000000100000100000000000000000000000000000000800000000000000000000000000000000040000000000000000000000000000000020000080000000000000000400000000000000000001400000000010006000000000000000000800200800000000000000000000000000000002000000000000000000080000000000000000000000001000000000000000000000000800000000000000000000000000000000000000000000000080000000008004000000080000000000000000000000000000000000000000200000000020000000000000000400080000008004000000400000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1ca", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x11e4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x83f464b150683ab5ce359179f4f9d6e960049959d2ec46a4ae7a07af2de41a6c", + "transactions": [ + "0xf87a82016f0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa05e304ec406ec4c83644417e1e58b49757d3ac78da5c5280fbda19b1f149137daa035b73caa8da3b6ce0e5f1b014c127f93f7be595f104cd933b5ff07549fd1812b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x84a5b4e32a62bf3298d846e64b3896dffbbcc1fafb236df3a047b5223577d07b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np459", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x83f464b150683ab5ce359179f4f9d6e960049959d2ec46a4ae7a07af2de41a6c", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5614ae860626ff1e044740a53f3cb5126f72002928c034aecbdfe4291ce73b91", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1cb", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x11ee", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x3c1ccfa2b5f88830245f76a22fa29ce22fb5b284de5937ff66adc67a445bf5c5", + "transactions": [ + "0xf865820170088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a05d0d172a5fb9787aa2ee5205e5986de935984adf6030d5668be0e31332f7b145a022c4c7a89391e8f4508095fc5c1ed16aa0c08da6790be108240dc64763d42dae" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xff70b97d34af8e2ae984ada7bc6f21ed294d9b392a903ad8bbb1be8b44083612" + ] + }, + { + "jsonrpc": "2.0", + "id": "np460", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x3c1ccfa2b5f88830245f76a22fa29ce22fb5b284de5937ff66adc67a445bf5c5", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x715b0d1e4306032fa54c79f84599828d98bc84ed9cdb52a407e58730b4c112db", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1cc", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x11f8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xfc7412d30ba5b6f5b319b07e51296906a42fdae50a88c1f90016d487b1df41f6", + "transactions": [ + "0xf86882017108825208948a817bc42b2e2146dc4ca4dc686db0a4051d294401808718e5bb3abd10a0a0a755d1c641b8965ea140ad348135496fc412ffa43a72bbd2c7c0e26b814a75f1a067d81cca370b6ea40ccd2ad3662d16fa36bd380845bee04c55c6531455d0687d" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x73e186de72ef30e4be4aeebe3eaec84222f8a325d2d07cd0bd1a49f3939915ce" + ] + }, + { + "jsonrpc": "2.0", + "id": "np461", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xfc7412d30ba5b6f5b319b07e51296906a42fdae50a88c1f90016d487b1df41f6", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8c6710fa12f6392a52eaa92d776fe1c24245dd52883ff2276547e65c34952eeb", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1cd", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x1202", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xdad167dfa9bb65a470a36a3996f0587d645b3fbfe9e3522a1436f1dd6a3a37f3", + "transactions": [], + "withdrawals": [ + { + "index": "0x4c", + "validatorIndex": "0x5", + "address": "0x48701721ec0115f04bc7404058f6c0f386946e09", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xed185ec518c0459392b274a3d10554e452577d33ecb72910f613941873e61215" + ] + }, + { + "jsonrpc": "2.0", + "id": "np462", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xdad167dfa9bb65a470a36a3996f0587d645b3fbfe9e3522a1436f1dd6a3a37f3", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x8c73a23f75ee594dacc63d24a5d5655a1ccbeead972dba58ad86787c44442c6c", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1ce", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x120c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xc50795e72a34041bdabf74a87f77d78f3a07f2005396dcf9925b08a8a686bd61", + "transactions": [ + "0xf88382017208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0567311948632a5f4d53e0491aa8e7f939a3e0da38be1db4b6c757422de3f8bf6a01134e092948e423c7f8867c02822c95f3ce21b6d4e8d3666e2cf47ca88ad7499" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x5cfbad3e509733bce64e0f6492b3886300758c47a38e9edec4b279074c7966d4" + ] + }, + { + "jsonrpc": "2.0", + "id": "np463", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc50795e72a34041bdabf74a87f77d78f3a07f2005396dcf9925b08a8a686bd61", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb6a995ce6f848e4f2f2ad8ced5491859a5d0a3b6767108f3ce5cfcb33303349f", + "receiptsRoot": "0x5bb341cd099f8898164b032e64db73752f528a10e8d9c60c9b4fff08af32dcf5", + "logsBloom": "0x000002040000000000000000000000000000000000000000200000000000000000000000000000000000000000000300000000000000000000010020000000080000000000000000000000000000000000000000000200000000000000201000000000000000080000000000000000000000000000000040100000000010000080100000002000000000000000004000000008a0000000100000000000400000000000000000200000000000200400000000000000000000000000000000000000000000000000200000000000000002000000000000000000000000000000000000000000000000030000000000000200000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1cf", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x1216", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x69a26219f28581c8898c2790cf785e3f2b0081a416d51722d85b5ac313d5f36d", + "transactions": [ + "0xf87a8201730883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a06092eab6a3d9e41841ad4b9c97154ac35269c852606da6dd04940a1a055fa979a052a6e3e769e27310acdef840cb1182f4a2b6b08583b01cb8325c98253feaf7aa" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x867a7ab4c504e836dd175bd6a00e8489f36edaeda95db9ce4acbf9fb8df28926" + ] + }, + { + "jsonrpc": "2.0", + "id": "np464", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x69a26219f28581c8898c2790cf785e3f2b0081a416d51722d85b5ac313d5f36d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf54cc52e78b0ea88b082230970d262fc78070bff347c000f60c53400d111a59c", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1d0", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x1220", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x3ed11b20d6eced6314897749d304a677d345ce9343fe964143548980ea71615e", + "transactions": [ + "0xf865820174088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0342c58642563f78afdb5cf7b9fbc935268a8fd81a5bd7997c33f61cdff8fb9c2a07466870d997603b5dd7755f151b76f056d4948ae82372b05babc01b9addaad19" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x0d01993fd605f101c950c68b4cc2b8096ef7d0009395dec6129f86f195eb2217" + ] + }, + { + "jsonrpc": "2.0", + "id": "np465", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x3ed11b20d6eced6314897749d304a677d345ce9343fe964143548980ea71615e", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5b583ecaeffb409a488709df2c592c932e93a9b954bb5b62c36739324ae7d89c", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1d1", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x122a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x20ca98d23c09a37aa1805c3989ca7a7bfff9ade344de4575f5063a10c60510ca", + "transactions": [ + "0x02f86b870c72dd9d5e883e82017501088252089423e6931c964e77b02506b08ebf115bad0e1eca660180c080a06263b1d5b9028231af73bfa386be8fc770e11f60137428378137c34f12c2c242a02b340f5b45217d9b914921a191ce5f7ba67af038e3b3c2c72aaca471412b02f7" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x8e14fd675e72f78bca934e1ffad52b46fd26913063e7e937bce3fa11aed29075" + ] + }, + { + "jsonrpc": "2.0", + "id": "np466", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x20ca98d23c09a37aa1805c3989ca7a7bfff9ade344de4575f5063a10c60510ca", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xdaf72de0a7092d2a2a6d31336c138ab45852ca65398578fbc435b3c591fa7c3a", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1d2", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x1234", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xd1c6165f74a48fb1da29dde0ec4588f1b5708d1b810696ab128a6db9ce08a1eb", + "transactions": [], + "withdrawals": [ + { + "index": "0x4d", + "validatorIndex": "0x5", + "address": "0x706be462488699e89b722822dcec9822ad7d05a7", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4ec1847e4361c22cdecc67633e244b9e6d04ec103f4019137f9ba1ecc90198f4" + ] + }, + { + "jsonrpc": "2.0", + "id": "np467", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xd1c6165f74a48fb1da29dde0ec4588f1b5708d1b810696ab128a6db9ce08a1eb", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb9f6529424870d0fbfe7d70438762f3ccf9d2f212d3e42c837f6e9218d72451a", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1d3", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x123e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xee2b973ebc00c239bf4fd6c382cc78890065370286476ae02a9b1bd76788f810", + "transactions": [ + "0xf88382017608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0e42b1ec38a455f867d421d170e634c86f8a84a2cb00ec5024f343667042f303ea067797c75de08e6eafd819d4c408324fba318e16b378b7dedbc0708056aebb696" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xec69e9bbb0184bf0889df50ec7579fa4029651658d639af456a1f6a7543930ef" + ] + }, + { + "jsonrpc": "2.0", + "id": "np468", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xee2b973ebc00c239bf4fd6c382cc78890065370286476ae02a9b1bd76788f810", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3017b68d781fb29ccbca4c6ff597a9e18d6cee4f02974dbb32f04b5a7f519271", + "receiptsRoot": "0x00fbb0bcdb236cd79dbbefe84d42f31ee3274cc5e9116ffb0d70301b983dbd52", + "logsBloom": "0x00000000010008200000000000000000200400000000000000000000022000000000000000000000000000000000000000000000000800000000000000000000000000200080000002000000000000000000000000008000000000000000000000000800080000000004004000000000000000001000000000000000000000000010000000000200000002000000000000010000000000000004000000000000000000000400000000000000000000000040000000000000000000000080000000000200000000000000000000000108000000000000000000000020010000000000000000000000000000000000000000000000000000000040000040000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1d4", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x1248", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x66a71dd383d4ead0e00787a06fcfb3c75c36fa72b5d98f39dc37ca129315b8d9", + "transactions": [ + "0xf87a8201770883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa01975b5adb5e05e7dbaf63d31d34e5dfb802c4ca28127176811ada2b0a9411be6a02b9cd65ba817631163e95275ec2bd5319edeef4f74eb6efb32150a523282db16" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xefdd626048ad0aa6fcf806c7c2ad7b9ae138136f10a3c2001dc5b6c920db1554" + ] + }, + { + "jsonrpc": "2.0", + "id": "np469", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x66a71dd383d4ead0e00787a06fcfb3c75c36fa72b5d98f39dc37ca129315b8d9", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x7da79133a491b6c2566dc329ed006ee0010fe59b515fbce5589eda0f31dd091b", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1d5", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x1252", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x7f166dd54e16fcd0e302579768e0bb090b06f4e35cba5b48b0b5c42e367c0832", + "transactions": [ + "0xf865820178088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa02a73665ddc16b8e231ef04b5f0ad8afa56248db6f43222848032c72e97a807b8a00a17dda1a1d0ba616354fda9e86c836bcb002c7e54153be4cc95776446c6b2a5" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x551de1e4cafd706535d77625558f8d3898173273b4353143e5e1c7e859848d6b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np470", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x7f166dd54e16fcd0e302579768e0bb090b06f4e35cba5b48b0b5c42e367c0832", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xcbeab9491879fdd48e387106f31e983546cff3f4795ff5190722d2ac1f3792b6", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1d6", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x125c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xeedbf487ab11603d1a8e08d672886d16cd318bc421a358d199df281a473ac7b0", + "transactions": [ + "0xf8688201790882520894878dedd9474cfa24d91bccc8b771e180cf01ac4001808718e5bb3abd109fa0515a62775619f55c366d080a7c397ea42dcfd2fdcce1862ef98dab875077f367a023756d4f3bd644dde1c25f8cde45fbea557dacf0492bbecb409f6b2cdacbb9b8" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x137efe559a31d9c5468259102cd8634bba72b0d7a0c7d5bcfc449c5f4bdb997a" + ] + }, + { + "jsonrpc": "2.0", + "id": "np471", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xeedbf487ab11603d1a8e08d672886d16cd318bc421a358d199df281a473ac7b0", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x60452d4fa157207a12986fb9c810855fe19a2492ad046335ec9b4fe41e48de19", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1d7", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x1266", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe2cefda7c9752d4706e180cf9228524bd767f36f6380f0c6255498abedc66ce7", + "transactions": [], + "withdrawals": [ + { + "index": "0x4e", + "validatorIndex": "0x5", + "address": "0xe5ec19296e6d1518a6a38c1dbc7ad024b8a1a248", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xfb0a1b66acf5f6bc2393564580d74637945891687e61535aae345dca0b0f5e78" + ] + }, + { + "jsonrpc": "2.0", + "id": "np472", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe2cefda7c9752d4706e180cf9228524bd767f36f6380f0c6255498abedc66ce7", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x0b580cdca4b5a562a85801f2e45bd99e764124b9715915fd4bfc6f6eb483ef96", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1d8", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x1270", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x35f103c6c3cfc385bf9f512f7b4d7903e314b60cb715df196cf574391b8506df", + "transactions": [ + "0xf88382017a08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa05c8cad8eec0edc7394b3bace08088ee19b7eacb754b0a5695fc52a0cd17c19f6a0033d27e9eeb87fa5ae4868a14d0b66d941f0ffa3a3781e60cbb751bab7b507da" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x96eea2615f9111ee8386319943898f15c50c0120b8f3263fab029123c5fff80c" + ] + }, + { + "jsonrpc": "2.0", + "id": "np473", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x35f103c6c3cfc385bf9f512f7b4d7903e314b60cb715df196cf574391b8506df", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd08b438590148463c602be8f8899fd6c2cb42972fe2df0e71cb42ebefea3f404", + "receiptsRoot": "0x307ca5ba4dfd34e9f362cea8e1f54ff58f9318a35cf7e1ae24823d41572d7742", + "logsBloom": "0x00000000000000000000000000000000800000000000000000008000000000000000000000040000000000000000100000000000000000000000000000000001000000000000000000000400000000000000000000000000300000000001000000002040000000000000008000000000000000000000000000000000000100000010000000000000000401000000000000000000000000000000000000000080000000000058400000000400000800000000000000000000000000000000000001000000000000000000000000004000000000000100100000000000000000000000000000000200400000000000100000000000002000040000000000100000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1d9", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x127a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x1f50e2662ba03c36242e9717f767077fd0d1659ed1a5e2e5024bf1a9de6303f1", + "transactions": [ + "0xf87a82017b0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa06cfb2ecb740895c1bdd352c502898651d83d35cb17ec4a0b30b04fe190a05758a02606cabbaa5b1d57ff9da73837cff8cbd03f242b83880f8cf3ba6f0ee907d538" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x68725bebed18cd052386fd6af9b398438c01356223c5cc15f49093b92b673eff" + ] + }, + { + "jsonrpc": "2.0", + "id": "np474", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x1f50e2662ba03c36242e9717f767077fd0d1659ed1a5e2e5024bf1a9de6303f1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6d86e3351111e6c2d4eafc36553273c03636a22fae54a9e076be2e7cb0cdf9d7", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1da", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x1284", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa3baf412ffd440d9baceb4d19fc213652de91fee569633fb5f8f77b737dd23f3", + "transactions": [ + "0xf86582017c088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a044380da66c7033fceaa15191e7549bd08fed4c16f96cf1282b2f39bccaad1ff0a00d036ed4649f8300b82a534b03a19b4547784997b61328ba41dd7fa5380de99b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe2f1e4557ed105cf3bd8bc51ebaa4446f554dcb38c005619bd9f203f4494f5dd" + ] + }, + { + "jsonrpc": "2.0", + "id": "np475", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa3baf412ffd440d9baceb4d19fc213652de91fee569633fb5f8f77b737dd23f3", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4e2eff0a0a0cfaa9726ffd557089d4a85855fabe4b81334326bd400289f5ed12", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1db", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x128e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa63c5dedb28356376c60a58b8b766be086203e9b8d9c016e0863fd4e8cf42a06", + "transactions": [ + "0x02f86b870c72dd9d5e883e82017d01088252089445dcb3e20af2d8ba583d774404ee8fedcd97672b0180c001a0d3b69c226bf73db84babb6185a83b0dd491467adfc01d279df4c09d5d2d3fba4a0368ddb772caa32963df97961cf8ef0db33e0df5945000f0e39d9a288bd73ee30" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x48ef06d84d5ad34fe56ce62e095a34ea4a903bf597a8640868706af7b4de7288" + ] + }, + { + "jsonrpc": "2.0", + "id": "np476", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa63c5dedb28356376c60a58b8b766be086203e9b8d9c016e0863fd4e8cf42a06", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x3de8e5ff6961615b029591cbe9ea51723c809d965421da4f3f8ae26ffe59d69d", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1dc", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x1298", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xbcf5e09e90541f9a8e36eca4ce43a64e1e05e93f4aba193be8e2da860b5ba0bc", + "transactions": [], + "withdrawals": [ + { + "index": "0x4f", + "validatorIndex": "0x5", + "address": "0x2e350f8e7f890a9301f33edbf55f38e67e02d72b", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x5c57714b2a85d0d9331ce1ee539a231b33406ec19adcf1d8f4c88ab8c1f4fbae" + ] + }, + { + "jsonrpc": "2.0", + "id": "np477", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xbcf5e09e90541f9a8e36eca4ce43a64e1e05e93f4aba193be8e2da860b5ba0bc", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x6eb0d2ff3e3dd2cdaad61b121b06afcf7863f34152ecbdf8b8773604630a56b3", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1dd", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x12a2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xd5c167589a4663ae0585e5fff8fe256f35baaa26843df17dedcf6040709d6257", + "transactions": [ + "0xf88382017e08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a0939d9f6f260f24b45073aeabe00660f617f1dbfcf522cd6c90ef189dfc9dbfa0a02dfd90c6f1a6822039b8fbd5bff435e939882da970ed1b58a4639eddcb79b23b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x204299e7aa8dfe5328a0b863b20b6b4cea53a469d6dc8d4b31c7873848a93f33" + ] + }, + { + "jsonrpc": "2.0", + "id": "np478", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xd5c167589a4663ae0585e5fff8fe256f35baaa26843df17dedcf6040709d6257", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xfadd61dbce8d90cae8144c1b2297209079517cb13f3a4e60a6c8f2ea7b4d3770", + "receiptsRoot": "0x3ec27c047700a74288e3ee48062fed9fbba71b1704febedea9f4e9e3a92faabf", + "logsBloom": "0x00100000000000000000000040004000000000000800008080000000000000100000000000000001000000000000000000000000000004000008000008200000002000004000000400000000000000000000000008000000000000000000004000000000000000000000000040000000800004000000000000400000000000000000001000000000000000000410010000000000000000000400000000020000000000000000000100000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010080000000000000000100000000000800000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1de", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x12ac", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xb061affdd716a0d4c5d081a1c3659d0201dce5c698ae942440565ca789e55b00", + "transactions": [ + "0xf87a82017f0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a0dffee1543462b1d024b5d54728f2e3284d90d8fd24b94fd96bd027b4ca51e768a02ed5ddd2050f1b7bcbc123e31fb0536fbf1661a8f7541c7a10729e8a505cc080" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb74eea6df3ce54ee9f069bebb188f4023673f8230081811ab78ce1c9719879e5" + ] + }, + { + "jsonrpc": "2.0", + "id": "np479", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xb061affdd716a0d4c5d081a1c3659d0201dce5c698ae942440565ca789e55b00", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x931dde8f1566d5b88162261e5f8c8fede3f14bfab1c11934aae8f2a38aca7b36", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1df", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x12b6", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xd8fd694b37ff2f40373350baa6cbf326e675330a7d070dedf57065b72304aece", + "transactions": [ + "0xf865820180088302088a808090435b8080556001015a6161a8106001578718e5bb3abd109fa0c2e07d6867be2220a74a18404d2b9b9adb2f6b1764907aaec954f46e0b9fd18aa01504fbbb49a910d6469e64741d99ea5031c14d4721e488998ef2f594022f34e2" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xaf5624a3927117b6f1055893330bdf07a64e96041241d3731b9315b5cd6d14d7" + ] + }, + { + "jsonrpc": "2.0", + "id": "np480", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xd8fd694b37ff2f40373350baa6cbf326e675330a7d070dedf57065b72304aece", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x452e515470ad9f96543d5a469c85e77c4f675f70a56662537491b01528898b99", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1e0", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x12c0", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xee3a60bb251ec04e27e020f297aa6f159dad08673e76b280e67114583478aec9", + "transactions": [ + "0xf868820181088252089450996999ff63a9a1a07da880af8f8c745a7fe72c01808718e5bb3abd109fa0f06ad492cdd04b44f321abe9cb98e5977f03909173e4b6361f50d44c080f9d6aa07fdc23c04fab8e0a576e6896b13a661b2dcb256cf8ca42fa21f0f370097a53a4" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc657b0e79c166b6fdb87c67c7fe2b085f52d12c6843b7d6090e8f230d8306cda" + ] + }, + { + "jsonrpc": "2.0", + "id": "np481", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xee3a60bb251ec04e27e020f297aa6f159dad08673e76b280e67114583478aec9", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa424a562451b0728dc1451b83451fb65f9cad240a6e12ae45314a3c0fc49c4bd", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1e1", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x12ca", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe261b4fbd07d32f5f19564c572258acbe4be1a6b2ea03a57ccbb94e254f37cd5", + "transactions": [], + "withdrawals": [ + { + "index": "0x50", + "validatorIndex": "0x5", + "address": "0xc57aa6a4279377063b17c554d3e33a3490e67a9a", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa0e08ceff3f3c426ab2c30881eff2c2fc1edf04b28e1fb38e622648224ffbc6b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np482", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe261b4fbd07d32f5f19564c572258acbe4be1a6b2ea03a57ccbb94e254f37cd5", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5a1ad989a90bb48e30208fafcd5131d4dec171928eb27a8ab446df6086df0f94", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1e2", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x12d4", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe8f039d9e217e95c5622ac64609dcaaa54abbf24376fe6c65a29d2b50060cff1", + "transactions": [ + "0xf88382018208830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd10a0a085873eb64b12c743e5652beb56056bd656368a87247a72b159667d4755d7a714a0134397c5062d25029e41def2275efe8c56e466e3a1547d3525533e29641d203f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc9792da588df98731dfcbf54a6264082e791540265acc2b3ccca5cbd5c0c16de" + ] + }, + { + "jsonrpc": "2.0", + "id": "np483", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe8f039d9e217e95c5622ac64609dcaaa54abbf24376fe6c65a29d2b50060cff1", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x54928b5673094b4ce9833ecf8c1490381f0317ac2e9d51f47673e386b82ae93d", + "receiptsRoot": "0xeda5fd4b20fab5a0732205bfe235b5b212cfa5eb525752ae8b9bb0ca224262ec", + "logsBloom": "0x04000000000420002000000000000000020000000000000000000000000000000000000000000000000000100000000040000102000000000000000080000000008000000000000000000000900000000000000000000000040000000000000000000000000000000000100000100000000000001000010000000000000000010000000000000001000040000000000000000000000000000100000000000000000000000020010000000008000000000002000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000080000000400000000000010000000000000000000000000000000002020000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1e3", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x12de", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x4b31828b7c27c371fdbc62a7b0b6807d1050d15ad53736f73c4063b391aa8b91", + "transactions": [ + "0xf87a8201830883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a05c87beb281558e43744b39a1d0b62e75dfb5ea245fd2d66c657ff053fa5c45e1a077a1c629133272d7fef83436c8f67f327fc77bedea95009b3d569a5f03485b50" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc74f4bb0f324f42c06e7aeacb9446cd5ea500c3b014d5888d467610eafb69297" + ] + }, + { + "jsonrpc": "2.0", + "id": "np484", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x4b31828b7c27c371fdbc62a7b0b6807d1050d15ad53736f73c4063b391aa8b91", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf92302c8ac6987ab39ddc9a7413f552337da61d611a086900a5e47b9b3c1422f", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1e4", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x12e8", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x56c5997ee01e4a2bad320a6af0120843f01908c525450d04458eca33799e7958", + "transactions": [ + "0xf865820184088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a0ef66b5859d5e5be7e02ce0b7d103b957ceba18d69047aec94746e87945b7230ba071c5785cce709e44dd94db5684b4e552e343a44862fba233c49a3fa99b0d63f9" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x1acd960a8e1dc68da5b1db467e80301438300e720a450ab371483252529a409b" + ] + }, + { + "jsonrpc": "2.0", + "id": "np485", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x56c5997ee01e4a2bad320a6af0120843f01908c525450d04458eca33799e7958", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x89822c6bc267d77690ae905ebc8dbe9426f9a83764224d4bc9624104881db28e", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1e5", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x12f2", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xa5d5571bc983cefbe29844e1914f948256b70833f1e99d8dcb0282e1f9dbbfef", + "transactions": [ + "0x02f86b870c72dd9d5e883e820185010882520894913f841dfc8703ae76a4e1b8b84cd67aab15f17a0180c080a0d4b8d15fc05f29b58f0459b336dc48b142e8d14572edad06e346aa7728491ce8a064c8078691ba1c4bb110f6dff74e26d3c0df2505940558746a1c617091ddc61a" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x6cef279ba63cbac953676e889e4fe1b040994f044078196a6ec4e6d868b79aa1" + ] + }, + { + "jsonrpc": "2.0", + "id": "np486", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xa5d5571bc983cefbe29844e1914f948256b70833f1e99d8dcb0282e1f9dbbfef", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe88ebfc2a7990356801a2e5a308418fa8fe4445548fafe8227f4382f64ad8597", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1e6", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x12fc", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x686566b93e0b0c5d08d2de9e0547a5639e6878d15c59baab066c48365ce7e350", + "transactions": [], + "withdrawals": [ + { + "index": "0x51", + "validatorIndex": "0x5", + "address": "0x311df588ca5f412f970891e4cc3ac23648968ca2", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x60eb986cb497a0642b684852f009a1da143adb3128764b772daf51f6efaae90a" + ] + }, + { + "jsonrpc": "2.0", + "id": "np487", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x686566b93e0b0c5d08d2de9e0547a5639e6878d15c59baab066c48365ce7e350", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xb852ee14e385a383f894d49c4dabd2d0704216e924283102b9b281ae5306a291", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1e7", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x1306", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xc005c46cb9de70c37edd02e3ae623bb8b6e4160674fafbbd34a802f85d2725b6", + "transactions": [ + "0xf88382018608830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0ddc578e5c190613c2dc0ce34585e98c512fc9b4ae877b0b3f9b85e01a36b90b5a044c7152f99374ce61bb3b9ebb9ec9e5c4f623faa9b8972cf80f891fd45be9bbf" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xc50024557485d98123c9d0e728db4fc392091f366e1639e752dd677901681acc" + ] + }, + { + "jsonrpc": "2.0", + "id": "np488", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc005c46cb9de70c37edd02e3ae623bb8b6e4160674fafbbd34a802f85d2725b6", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa3f2cdabc9ec81196b1930e223115ab39e3aa82a3267c2eab58dfcd4ac28879d", + "receiptsRoot": "0xa98965822a3cbebe261b9e53038a23e30a7a9ea1878b91ee19c2b9ae55907433", + "logsBloom": "0x0000000000000000000000000000000c000000000000000000000000000000000000000000002000000000800000000008000000000000000000000000000000000000000000200200000000002004000000000000000000000000000000000000000000000000000000020000040000000000080000000000004000000000000000000000000000000000000000100000000200000000000000200000000800040000000000000000000000441000000000000000000000000000000000004020400000000000000000000800000000000000002000000000040000000000000000000000000000000000000000000100000000000000400100000200000010", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1e8", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x1310", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xabe558d433bc22296ae2fc7412d05672f2ec66c7940ef6a76f9bb22aa09b219d", + "transactions": [ + "0xf87a8201870883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd10a0a09d84bd49c461dee138a01ba1116ba5a0866c4d398db99b3b6e8ec5119ddaf31da046d87610c10b340e616174c09a5addfb8ef7f1b64dcadf4edd14af37ec74a55c" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xb860632e22f3e4feb0fdf969b4241442eae0ccf08f345a1cc4bb62076a92d93f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np489", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xabe558d433bc22296ae2fc7412d05672f2ec66c7940ef6a76f9bb22aa09b219d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x39ebb75595ae4b664d792fdf4b702a8a4cec3fb1fa62debd297075d3543e05af", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1e9", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x131a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x5591e9a74a56e9765790e3088a82c8e6e39ef0d75071afe13fa51c9b130413db", + "transactions": [ + "0xf865820188088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a06ffd1874ec840566ae82b8a15038ee44b5241705bdb421b459c17100d1300d1aa0121f314d9f41658c831f52b82d4a13b333413d68809cea260e790de9283a434b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x21085bf2d264529bd68f206abc87ac741a2b796919eeee6292ed043e36d23edb" + ] + }, + { + "jsonrpc": "2.0", + "id": "np490", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5591e9a74a56e9765790e3088a82c8e6e39ef0d75071afe13fa51c9b130413db", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xa3d5920be7fa102b7b35c191800c65c8b8806bd7c8c04cdc0342a3d28aeafa3c", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1ea", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x1324", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x38cee342db6a91998dd73c4d25bca6d8730977aaa19f0a092d47c00ff10c4edb", + "transactions": [ + "0xf8688201890882520894b47f70b774d780c3ec5ac411f2f9198293b9df7a01808718e5bb3abd10a0a0d33c0cd7f521603ea8deaa363ab591627f5af193759f0aeb8cd9fe4f22a4dd5ca0667bb0ee041403cba2e562882bb9afc43bd560af3c95136c7bf4f1e361355316" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x80052afb1f39f11c67be59aef7fe6551a74f6b7d155a73e3d91b3a18392120a7" + ] + }, + { + "jsonrpc": "2.0", + "id": "np491", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x38cee342db6a91998dd73c4d25bca6d8730977aaa19f0a092d47c00ff10c4edb", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xf1034fb8a7585c73d7df19cae6b0581d6836278bd57d05fa19f18c6501eace46", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1eb", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x132e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x669efe3cceb25caf14b93a424eaa152070686561e028d50b8adbf87d45f4d18f", + "transactions": [], + "withdrawals": [ + { + "index": "0x52", + "validatorIndex": "0x5", + "address": "0x3f31becc97226d3c17bf574dd86f39735fe0f0c1", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xa3b0793132ed37459f24d6376ecfa8827c4b1d42afcd0a8c60f9066f230d7675" + ] + }, + { + "jsonrpc": "2.0", + "id": "np492", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x669efe3cceb25caf14b93a424eaa152070686561e028d50b8adbf87d45f4d18f", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x9b830dad01831671e183f743996cc400135e0b324f1270468af08b37e83b8b17", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1ec", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x1338", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x92932a0312ff65482174399e2cd29656c7051fa3747e47a906b54207c4fd1a92", + "transactions": [ + "0xf88382018a08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0e94663b4e19d1c2f86adde879e4cb965b7eda513a542ba26136b7010aae11681a03e7d58f3bef3bba01e70b75c70bc0d070f95bba8994c9f12705f2a5281160f47" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xe69d353f4bc38681b4be8cd5bbce5eb4e819399688b0b6225b95384b08dcc8b0" + ] + }, + { + "jsonrpc": "2.0", + "id": "np493", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x92932a0312ff65482174399e2cd29656c7051fa3747e47a906b54207c4fd1a92", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xc616c572be45daa3d7eae2481876e5d8f753631f976d4da110a6ad29bdfad30f", + "receiptsRoot": "0x78902fbbd0a8ab65f6b731f1145a5f6f467f9fdae375707236cff65e050bbfeb", + "logsBloom": "0x00000000002000000000080000000000800000000000000000800000000100000000000000000000000000000000000000000000000004000000000010000000000040000000000010000000000400000000000000000000000000080000000000020000000000000000000000000000000000000000000010000000000000000000000000008000000000000000000000000000000000400000000001000040000000000000000000000000000000000000000040000000040000000880000000008020000000800000008000000000000040020180000000000000000000400800000000000000000000000080000200000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1ed", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x1342", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x52b55abe0e252ea389cc21f01782fd70ca4e4ef6031883f6b79c097de33964d4", + "transactions": [ + "0xf87a82018b0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0fbd0141af6d135ce0949d33ba4beba57e9b7f388c37e9725b762cb61e8db17dea05ecd43ff335efc34b06551202c4223fc39e1c842d4edfad8e46f19bc7a93f57f" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x221e784d42a121cd1d13d111128fcae99330408511609ca8b987cc6eecafefc4" + ] + }, + { + "jsonrpc": "2.0", + "id": "np494", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x52b55abe0e252ea389cc21f01782fd70ca4e4ef6031883f6b79c097de33964d4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x7e626fcfe3b1ca7a31dc26a08fbc503c7a85876a64a22a270ec99ef534566c45", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1ee", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x134c", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x5370ce9fa467f03411f240030b4a0b9fcbb05c5b97b09356d071ade6548767e8", + "transactions": [ + "0xf86582018c088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a05a02d5d03439ebbdf2c3b2d98305dda7adbed1ce5549c474b4b9e4f7200d4beaa016d123a1de79c4a654c1d1ab2169ee672c66922fa036e951c60fec9fe4643ee9" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xdcd669ebef3fb5bebc952ce1c87ae4033b13f37d99cf887022428d024f3a3d2e" + ] + }, + { + "jsonrpc": "2.0", + "id": "np495", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x5370ce9fa467f03411f240030b4a0b9fcbb05c5b97b09356d071ade6548767e8", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x9d90c0fd0677204966d6fdbcafcfacc7fe93a465748d2ce8afbc76b6d9b9bbe1", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1ef", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x1356", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x58488c77f4726356a586e999547ffa283a73f17058064f3f56eeb02a5f67b4b4", + "transactions": [ + "0x02f86b870c72dd9d5e883e82018d0108825208946e3d512a9328fa42c7ca1e20064071f88958ed930180c080a0990aa3c805c666109799583317176d55a73d96137ff886be719a36537d577e3da05d1244d8c33e85b49e2061112549e616b166a1860b07f00ff963a0b37c29bcaa" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4dd1eb9319d86a31fd56007317e059808f7a76eead67aecc1f80597344975f46" + ] + }, + { + "jsonrpc": "2.0", + "id": "np496", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x58488c77f4726356a586e999547ffa283a73f17058064f3f56eeb02a5f67b4b4", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x32f6d8bc2270e39de3a25c3d8d7b31595eef7d3eb5122eece96edf18a7b8290f", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1f0", + "gasLimit": "0x47e7c40", + "gasUsed": "0x0", + "timestamp": "0x1360", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xe521dace14e46c9d8491f262d38c1741f6fa385466a68c7ceadd08c1515600d3", + "transactions": [], + "withdrawals": [ + { + "index": "0x53", + "validatorIndex": "0x5", + "address": "0x6cc0ab95752bf25ec58c91b1d603c5eb41b8fbd7", + "amount": "0x64" + } + ], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x5e1834c653d853d146db4ab6d17509579497c5f4c2f9004598bcd83172f07a5f" + ] + }, + { + "jsonrpc": "2.0", + "id": "np497", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xe521dace14e46c9d8491f262d38c1741f6fa385466a68c7ceadd08c1515600d3", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x4e1202b318372f0cacbc989e0aa420c4280dcb8ecd7c3bb05c645bf9fb27d54e", + "receiptsRoot": "0x18ff29662320d2c1d830d59b45b908cc2e4b65c1df400d3b8492ba583a1e3342", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1f1", + "gasLimit": "0x47e7c40", + "gasUsed": "0x146ec", + "timestamp": "0x136a", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x3464afae6c8c9839a124b8dba3d363e646b61c9160a61b1c231c67a6a72daff5", + "transactions": [ + "0xf88382018e08830146ec8080ae43600052600060205260405b604060002060208051600101905281526020016102408110600b57506102006040f38718e5bb3abd109fa0d9866a4e71a4efbccc717617f5c712557608513ce8b49f6e24fc06e0d717b7b6a056d3c051f6dbe09a1c94e23499ba8014f74e123caa3252068ee67e8f25e1e323" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x9f78a30e124d21168645b9196d752a63166a1cf7bbbb9342d0b8fee3363ca8de" + ] + }, + { + "jsonrpc": "2.0", + "id": "np498", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x3464afae6c8c9839a124b8dba3d363e646b61c9160a61b1c231c67a6a72daff5", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xd04a20f359c38d0cb7a31e5e7b002251c15e0242b864964ddbe9642d1c8f7e30", + "receiptsRoot": "0xe40714733f96bc282c17b688a91dfb6d070114fc7bc3f095887afa3567af588c", + "logsBloom": "0x00400000000001400400000000000000020000000000000000000000400000000000000000400000000000000000000000040100000000800000000000000000000000000000010000000000000000080000000000000000008100000000000000000000000000000000000000200000300000008000000000000010002000000000000000008000000000000000000000000000000000000000100000000000000000000000000000000004000000000000100001000000480000000000000000000000000000000000000000000000000000440000000000000000000010000000000100000000000000000000000000000000000000000000000000800000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1f2", + "gasLimit": "0x47e7c40", + "gasUsed": "0xfc65", + "timestamp": "0x1374", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0xf7ad3df877f1a7ac6d94087db3f3e01a80264b0909e681bf9c7d21879df0df5d", + "transactions": [ + "0xf87a82018f0883011f588080a54360005260006020525b604060002060208051600101905260206020a15a612710106009578718e5bb3abd109fa0dd12539d461aa41247581166cecdf2eb60a75ac780929c9e6b982d9625aadc1fa06b813ce4e36c5147759f90672f6e239fab2851a63ac3b998ead89c0ead85589b" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x1f7c1081e4c48cef7d3cb5fd64b05135775f533ae4dabb934ed198c7e97e7dd8" + ] + }, + { + "jsonrpc": "2.0", + "id": "np499", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xf7ad3df877f1a7ac6d94087db3f3e01a80264b0909e681bf9c7d21879df0df5d", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xbd38c27a1fad5fb839aad98a9c6719652d1714351f24d786b23bf23076b31ba6", + "receiptsRoot": "0x8e47c51ae3cc2e7f29232aac553163472046f2668ba19aa17733e156999a8234", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1f3", + "gasLimit": "0x47e7c40", + "gasUsed": "0x1d36e", + "timestamp": "0x137e", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x96a73007443980c5e0985dfbb45279aa496dadea16918ad42c65c0bf8122ec39", + "transactions": [ + "0xf865820190088302088a808090435b8080556001015a6161a8106001578718e5bb3abd10a0a012969b1c46cb1b69a3fdf15b8bbccc1574572b79b38bf81803c91b0384309545a06d1c09143ad2bfeccbb04d63441058c83b60a5cbfdad87db36421dfcf008cd16" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0x4d40a7ec354a68cf405cc57404d76de768ad71446e8951da553c91b06c7c2d51" + ] + }, + { + "jsonrpc": "2.0", + "id": "np500", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0x96a73007443980c5e0985dfbb45279aa496dadea16918ad42c65c0bf8122ec39", + "feeRecipient": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xea4c1f4d9fa8664c22574c5b2f948a78c4b1a753cebc1861e7fb5b1aa21c5a94", + "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": "0x1f4", + "gasLimit": "0x47e7c40", + "gasUsed": "0x5208", + "timestamp": "0x1388", + "extraData": "0x", + "baseFeePerGas": "0x7", + "blockHash": "0x36a166f0dcd160fc5e5c61c9a7c2d7f236d9175bf27f43aaa2150e291f092ef7", + "transactions": [ + "0xf868820191088252089415af6900147a8730b5ce3e1db6333f33f64ebb2c01808718e5bb3abd109fa085b3c275e830c2034a4666e3a57c8640a8e5e7b7c8d0687467e205c037b4c5d7a052e2aa8b60be142eee26f197b1e0a983f8df844c770881d820dfc4d1bb3d9adc" + ], + "withdrawals": [], + "blobGasUsed": "0x0", + "excessBlobGas": "0x0" + }, + [], + "0xf653da50cdff4733f13f7a5e338290e883bdf04adf3f112709728063ea965d6c" + ] + } +] \ No newline at end of file diff --git a/cmd/devp2p/internal/ethtest/testdata/txinfo.json b/cmd/devp2p/internal/ethtest/testdata/txinfo.json new file mode 100644 index 000000000..8e1d917fb --- /dev/null +++ b/cmd/devp2p/internal/ethtest/testdata/txinfo.json @@ -0,0 +1,3018 @@ +{ + "deploy-callenv": { + "contract": "0x9344b07175800259691961298ca11c824e65032d", + "block": "0x1" + }, + "deploy-callme": { + "contract": "0x17e7eedce4ac02ef114a7ed9fe6e2f33feba1667", + "block": "0x2" + }, + "randomcode": null, + "randomlogs": null, + "randomstorage": null, + "uncles": { + "11": { + "hashes": [ + "0x900edfd7e6de8a4a4ae18d2e7df829de69427e06eb9a381c3fe1e3002a750d75" + ] + }, + "16": { + "hashes": [ + "0x750eda0129037fbbcfcbfd6362a60ffbbc53a3f14ba9259cf2ac7f02da2a827c" + ] + }, + "21": { + "hashes": [ + "0x763d1a545e23079b4796461f2146cd3b24cc45ceab6e932db010bd2736e45403" + ] + }, + "26": { + "hashes": [ + "0x98180f6103a7e303444de4e152e81539ad614d0cd755e0e655715ab676d11e32" + ] + }, + "31": { + "hashes": [ + "0x04a8c9b6d23b2ada25bff618036c08bf6428fb35b89bce694607fac697f470e3" + ] + }, + "36": { + "hashes": [ + "0x9225da0395e14243f1e626b330ea8fe6afde356e50e8448936a29e1c203d661d" + ] + }, + "41": { + "hashes": [ + "0x74a80b9b13a264aff16e9156de67474c916de966327e9e1666fc2027e1bf63ad" + ] + }, + "46": { + "hashes": [ + "0xcf2bddf3649c7af6e9c8592aa5fad693f39f46369749e1c7127848d4ae9ff1ec" + ] + }, + "51": { + "hashes": [ + "0xeb31c29a94de8cf2fc3d0b80023b716fb5d31cc24d695d606eef2389705ade45" + ] + }, + "56": { + "hashes": [ + "0xb3a6af7632306e2dbd56b3bbf0e77d7b5c199053f348c74ce938afae615cd4fe" + ] + }, + "6": { + "hashes": [ + "0x97186bc5df663e72934212ab5a7b4449f07f12f44b267e119817791fe0ed66c5" + ] + }, + "61": { + "hashes": [ + "0x3a2cf075f456fcf264293a32d41f72506ad8cf9697d6b6d8ab3d8258cdaa90bd" + ] + }, + "66": { + "hashes": [ + "0x94d338db2e75740d17df19b0d8a111d5d68b2dfa38819b88929190b4b08b5993" + ] + }, + "71": { + "hashes": [ + "0xe9938f6ac90bc4dfdea315ed630b03ad9392b264d362ee1e1b2703fb3db5047a" + ] + } + }, + "valuetransfer": [ + { + "block": "0x7", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "nonce": "0x5", + "to": "0xca358758f6d27e6cf45272937977a748fd88391d", + "gas": "0x5208", + "gasPrice": "0x1", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x1c", + "r": "0x7252efaed5a8dbefd451c8e39a3940dc5c6a1e81899e0252e892af3060fd90ed", + "s": "0x30b6bd9550c9685a1175cece7f680732ac7d3d5445160f8d9309ec1ddba414be", + "hash": "0xd04f2bb15db6c40aaf1dcb5babc47914b5f6033b2925cb9daa3c0e0dab493fcb" + } + }, + { + "block": "0xc", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x9", + "to": "0xef6cbd2161eaea7943ce8693b9824d23d1793ffb", + "gas": "0x5208", + "gasPrice": "0x1", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x1160803ff1253dead1d84d68a06cb92fcbb265ddb0edb9a5200b28b8c834ce6b", + "s": "0x4f1f42c91a7b177f696fc1890de6936097c205f9dcd1d17a4a83ac4d93d84d9c", + "hash": "0x778450f223b07f789e343c18207a3388c01070c2f6a89506f2db4c656bc1a37f" + } + }, + { + "block": "0x11", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xd", + "to": "0x4a64a107f0cb32536e5bce6c98c393db21cca7f4", + "gas": "0x5208", + "gasPrice": "0x1", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0xea20f9d952a58697ffb40cefcab9627f552c9658b3181498fd706418f89a3360", + "s": "0x4988596c88fe69f7d032df8e6f515a618a2c2e30f330febb3b548eb4fc1e8ca2", + "hash": "0xc2cffc70d847fbe50a53d618de21a24629b97e8dd4c1bcbf73979b2a48ee16df" + } + }, + { + "block": "0x16", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x11", + "to": "0x7cb7c4547cf2653590d7a9ace60cc623d25148ad", + "gas": "0x5208", + "gasPrice": "0x1", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0x5f315b1989161bf29054e9e030a05b05b3d7efb4c60e39531b96af1690913f91", + "s": "0x6f1d8de5adad6f76ed0d2b4c6885d3a5502c12dae1d124b310e8c8856bd22099", + "hash": "0xfa9cd1e12446cd8c23fc76b0ae9beba0ebdc021aa87726b6febcd5ba4a504f01" + } + }, + { + "block": "0x1b", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x15", + "to": "0x77adfc95029e73b173f60e556f915b0cd8850848", + "gas": "0x5208", + "gasPrice": "0x1", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x148500c79a2f0d59158458da4e3b2a5ace441bf314942243c9e05da3457d394e", + "s": "0x2a83c5f921ffddd3c0b2a05999f820d1d03bce9ac9810941bb286c4db4ce9939", + "hash": "0xbfeeb9406545ede112801fe48aeaf30c8e2384739e8e585f1c0e726689abc4b8" + } + }, + { + "block": "0x20", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x19", + "to": "0x36a9e7f1c95b82ffb99743e0c5c4ce95d83c9a43", + "gas": "0x5208", + "gasPrice": "0x1", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x14346079d6d3690f923625efde8933b2ad99c2bfda9310983a21b60e3c261d3c", + "s": "0x501ae278f370f3c0283fb04f966b6c501cbee0ad4c784f4187e38fcc38a9ccbb", + "hash": "0x792614188c26e2f348ac3223813794c60de97b83a298e84f4bae51dda6de140c" + } + }, + { + "block": "0x25", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x1d", + "to": "0xbbf3f11cb5b43e700273a78d12de55e4a7eab741", + "gas": "0x5208", + "gasPrice": "0x1", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x86bc86521cc6091198253d75caf394a8e23fd4fb82b48236d29f81a95aeebec5", + "s": "0xae9de4ac4265e3f415514905d8f8c747c959771080fa031dc5fd9b7333ffc28", + "hash": "0xc44716fcd212d538b2d143ddec3003b209667bfc977e209e7da1e8bf3c5223b8" + } + }, + { + "block": "0x2a", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x21", + "to": "0x684888c0ebb17f374298b65ee2807526c066094c", + "gas": "0x5208", + "gasPrice": "0x1", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0x88fa9d9bbc92e44b8edcda67ee23aca611deac4cec336b215fb72547a1d0e07e", + "s": "0x297c4d7054cb545bee5221a70454b6270e098f39f91bf25c0526aa8c0a0a441c", + "hash": "0xc97ceb5b227ade5363592a68c39dcf1788abbf67b2440934b1ae11cf4b19417c" + } + }, + { + "block": "0x2f", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x25", + "to": "0x8a5edab282632443219e051e4ade2d1d5bbc671c", + "gas": "0x5208", + "gasPrice": "0x1", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0x649b4ad4dcf07bcfba3dd7afd2ce220d0ae463c1bcc891ab1fcae84eca6fcc69", + "s": "0x5c69b0ad46c90eee811e4b71ce0aed22f479c207bee813dac8cce07e5a65adae", + "hash": "0xaf340a1b347c756a11e331e771d37d9205eada520f4f0d8d27f725d7c196aed1" + } + }, + { + "block": "0x34", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x29", + "to": "0x4b227777d4dd1fc61c6f884f48641d02b4d121d3", + "gas": "0x5208", + "gasPrice": "0x1", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0x7d015036540013eb6aa141a2475fa1dd88d3bee57a67beaf6ef5de8f40969601", + "s": "0x4dc750a08f793ff3105479e7919508d14abe56748698375046b995d86267b18c", + "hash": "0x07a2a98ac904bcf4c17a773426b34d2b3120af65b12f9bfd437d48c175f364eb" + } + }, + { + "block": "0x39", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x2d", + "to": "0x19581e27de7ced00ff1ce50b2047e7a567c76b1c", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x27f555e9", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0xde8b08caa214d0087ffd11206d485cb5cde6a6b6a76b390f53d94a8c16691593", + "s": "0x14dfe16ec3e37b8c6d3257deaf987b70b0776b97e4213c1f912c367e7d558370", + "yParity": "0x1", + "hash": "0xa883c918fb6e392a2448ef21051482bfcbeb5d26b7ebfad2a010a40e188cb43b" + } + }, + { + "block": "0x3e", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x31", + "to": "0x62b67e1f685b7fef51102005dddd27774be3fee3", + "gas": "0x5208", + "gasPrice": "0x14847701", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x6797c616a0fe0fad65b6020fc658541fd25577a3f0e7de47a65690ab81c7a34b", + "s": "0x115e6d138f23c97d35422f53aa98d666877d513dbe5d4d8c4654500ead1f4f8f", + "hash": "0xb2203865a1a1eace5b82c5154f369d86de851d8c5cd6a19e187f437a1ae28e94" + } + }, + { + "block": "0x43", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x35", + "to": "0x6b23c0d5f35d1b11f9b683f0b0a617355deb1127", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0xa88fcba", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0xdc3f3d86de44ee4dd795ff8ab480f4f5273c8ca61edb4c7561a369c80fbbb983", + "s": "0x43a90e087a6f5ba014e17316ec63b97a5a9ada19ab78177c87cb39ded9b37b0d", + "yParity": "0x0", + "hash": "0x647d637e54f1de1216cdfd83477a067308365c837c6c317febc9d3593907c7cc" + } + }, + { + "block": "0x48", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x39", + "to": "0x44bd7ae60f478fae1061e11a7739f4b94d1daf91", + "gas": "0x5208", + "gasPrice": "0x568d2fa", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0x50fc2310f542cf90b3376f54d296158f5be7ad852db200f9956e3210c0f8125c", + "s": "0x4f880fe872915a7843c37147a69758eff0a93cfaf8ce54f36502190e54b6e5c7", + "hash": "0x77050c3fb6b1212cf2f739f781b024b210177b3bcbd5b62e2b3c00f1d41764d1" + } + }, + { + "block": "0x4c", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x3d", + "to": "0x72dfcfb0c470ac255cde83fb8fe38de8a128188e", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x32ca5d0", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x116da1fc19daf120ddc2cc3fa0a834f9c176028e65d5f5d4c86834a0b4fe2a36", + "s": "0x17001c3ad456650dd1b28c12f41c94f50b4571da5b62e9f2a95dff4c8c3f61fd", + "yParity": "0x0", + "hash": "0x3e4639389b6a41ff157523860ffc77eb3e66a31aee867eb4148dcc5ee8b3c66f" + } + }, + { + "block": "0x50", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x41", + "to": "0x5c62e091b8c0565f1bafad0dad5934276143ae2c", + "gas": "0x5208", + "gasPrice": "0x1dce189", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0xb82a5be85322581d1e611c5871123983563adb99e97980574d63257ab98807d5", + "s": "0xdd49901bf0b0077d71c9922c4bd8449a78e2918c6d183a6653be9aaa334148", + "hash": "0x9c9de14ea0ce069a4df1c658e70e48aa7baaf64fddd4ab31bf4cb6d5550a4691" + } + }, + { + "block": "0x55", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x45", + "to": "0xa25513c7e0f6eaa80a3337ee18081b9e2ed09e00", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0xf4dd50", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0xe8ac7cb5028b3e20e8fc1ec90520dab2be89c8f50f4a14e315f6aa2229d33ce8", + "s": "0x7c2504ac2e5b2fe4d430db81a923f6cc2d73b8fd71281d9f4e75ee9fc18759b9", + "yParity": "0x0", + "hash": "0xff5e3c25f68d57ee002b3b39229ffba0879390475a00fa67a679b707997df530" + } + }, + { + "block": "0x5a", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x49", + "to": "0xbbeebd879e1dff6918546dc0c179fdde505f2a21", + "gas": "0x5208", + "gasPrice": "0x7dbb16", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0x2f0119acaae03520f87748a1a855d0ef7ac4d5d1961d8f72f42734b5316a849", + "s": "0x182ad3a9efddba6be75007e91afe800869a18a36a11feee4743dde2ab6cc54d9", + "hash": "0xd696adb31daca7c3121e65d11dc00e5d5fdb72c227c701a2925dc19a46fbd43e" + } + }, + { + "block": "0x5f", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x4d", + "to": "0xd2e2adf7177b7a8afddbc12d1634cf23ea1a7102", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x408f23", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x8556dcfea479b34675db3fe08e29486fe719c2b22f6b0c1741ecbbdce4575cc6", + "s": "0x1cd48009ccafd6b9f1290bbe2ceea268f94101d1d322c787018423ebcbc87ab4", + "yParity": "0x1", + "hash": "0x385b9f1ba5dbbe419dcbbbbf0840b76b941f3c216d383ec9deb9b1a323ee0cea" + } + }, + { + "block": "0x64", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x51", + "to": "0x18ac3e7343f016890c510e93f935261169d9e3f5", + "gas": "0x5208", + "gasPrice": "0x212636", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0x99aba91f70df4d53679a578ed17e955f944dc96c7c449506b577ac1288dac6d4", + "s": "0x582c7577f2343dd5a7c7892e723e98122227fca8486debd9a43cd86f65d4448a", + "hash": "0xd622bf64af8b9bd305e0c86152721b0711b6d24abe3748e2a8cd3a3245f6f878" + } + }, + { + "block": "0x69", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x55", + "to": "0xde7d1b721a1e0632b7cf04edf5032c8ecffa9f9a", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x11056e", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x2a6c70afb68bff0d4e452f17042700e1ea43c10fc75e55d842344c1eb55e2e97", + "s": "0x27c64f6f48cfa60dc47bfb2063f9f742a0a4f284d6b65cb394871caca2928cde", + "yParity": "0x0", + "hash": "0x47efc21f94ef1ef4e9a7d76d9370713acdf8c2b822ad35409566b9251fb0bf5c" + } + }, + { + "block": "0x6e", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x59", + "to": "0x1b16b1df538ba12dc3f97edbb85caa7050d46c14", + "gas": "0x5208", + "gasPrice": "0x8bd6d", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0xabbde17fddcc6495e854f86ae50052db04671ae3b6f502d45ba1363ae68ee62c", + "s": "0x3aa20e294b56797a930e48eda73a4b036b0d9389893806f65af26b05f303100f", + "hash": "0xcf4a0a2b8229fa2f772a90fdef00d073c821c8f56d93bce703007fc5eb528e71" + } + }, + { + "block": "0x73", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x5d", + "to": "0x043a718774c572bd8a25adbeb1bfcd5c0256ae11", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x47cdd", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x2ae4b3f6fa0e08145814f9e8da8305b9ca422e0da5508a7ae82e21f17d8c1196", + "s": "0x77a6ea7a39bbfe93f6b43a48be83fa6f9363775a5bdb956c8d36d567216ea648", + "yParity": "0x1", + "hash": "0xded7c87461fb84ffd49426b474741c2eace8982edf07af918bf8794415742384" + } + }, + { + "block": "0x78", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x61", + "to": "0x2d711642b726b04401627ca9fbac32f5c8530fb1", + "gas": "0x5208", + "gasPrice": "0x24deb", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0xb4d70622cd8182ff705beb3dfa5ffa4b8c9e4b6ad5ad00a14613e28b076443f6", + "s": "0x676eb97410d3d70cfa78513f5ac156b9797abbecc7a8c69df814135947dc7d42", + "hash": "0x9e2b47fc494a2285f98c89949878e11f7c6d47d24ae95bdab2801333ea8d11a7" + } + }, + { + "block": "0x7d", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x65", + "to": "0xd10b36aa74a59bcf4a88185837f658afaf3646ef", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x12eea", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x882e961b849dc71672ce1014a55792da7aa8a43b07175d2b7452302c5b3cac2a", + "s": "0x41356d00a158aa670c1a280b28b3bc8bb9d194a159c05812fa0a545f5b4bc57b", + "yParity": "0x0", + "hash": "0x240efcc882536fad14fcd34be50b508cb4c39b39f1493b8d64682760505b6cf7" + } + }, + { + "block": "0x82", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x69", + "to": "0xa5ab782c805e8bfbe34cb65742a0471cf5a53a97", + "gas": "0x5208", + "gasPrice": "0x9b8c", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0x78e180a6afd88ae67d063c032ffa7e1ee629ec053306ce2c0eb305b2fb98245e", + "s": "0x7563e1d27126c9294391a71da19044cb964fd6c093e8bc2a606b6cb5a0a604ac", + "hash": "0xa28d808cbc5ef9e82cd5023ea542fab4052895618b8627c000bb8cc8ccc2e693" + } + }, + { + "block": "0x87", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x6d", + "to": "0x4bfa260a661d68110a7a0a45264d2d43af9727de", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x4fe1", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0xbb105cab879992d2769014717857e3c9f036abf31aa59aed2c2da524d938ff8", + "s": "0x3b5386a238de98973ff1a9cafa80c90cdcbdfdb4ca0e59ff2f48c925f0ea872e", + "yParity": "0x1", + "hash": "0x83adc66f82e98155384ae9ef0e5be253eba9be959a50bcb48a7a3e6df97d6996" + } + }, + { + "block": "0x8c", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x71", + "to": "0x9defb0a9e163278be0e05aa01b312ec78cfa3726", + "gas": "0x5208", + "gasPrice": "0x2907", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x4adf7509b10551a97f2cb6262c331096d354c6c8742aca384e63986006b8ac93", + "s": "0x581250d189e9e1557ccc88190cff66de404c99754b4eb3c94bb3c6ce89157281", + "hash": "0x8e285b12f0ec16977055c8bc17008411883af1b5b33883a8128e50ed3e585685" + } + }, + { + "block": "0x91", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x75", + "to": "0x7da59d0dfbe21f43e842e8afb43e12a6445bbac0", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x1513", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x6ca026ba6084e875f3ae5220bc6beb1cdb34e8415b4082a23dd2a0f7c13f81ec", + "s": "0x568da83b9f5855b786ac46fb241eee56b6165c3cc350d604e155aca72b0e0eb1", + "yParity": "0x0", + "hash": "0x41ca48c0312c6d3fc433f9fd363281dae924885f73ab7466f9e8c97d6ea3b993" + } + }, + { + "block": "0x96", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x79", + "to": "0x84873854dba02cf6a765a6277a311301b2656a7f", + "gas": "0x5208", + "gasPrice": "0xad4", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0xab3202c9ba5532322b9d4eb7f4bdf19369f04c97f008cf407a2668f5353e8a1f", + "s": "0x5affa251c8d29f1741d26b42a8720c416f7832593cd3b64dff1311a337799e8f", + "hash": "0x7527f1a2c9cad727c70ca0d2117fc52dbfff87962411d0b821e7418a42abd273" + } + }, + { + "block": "0x9b", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x7d", + "to": "0x8d36bbb3d6fbf24f38ba020d9ceeef5d4562f5f2", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x592", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0xf9075613b9069dab277505c54e8381b0bb91032f688a6fe036ef83f016771897", + "s": "0x4cb4fc2e695439af564635863f0855e1f40865997663d900bc2ab572e78a70a2", + "yParity": "0x1", + "hash": "0xab2e87692b96ba3083b497227a9a17671bc5eee7ff12d50b850f442a4cdcd8b5" + } + }, + { + "block": "0xa0", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x81", + "to": "0xc19a797fa1fd590cd2e5b42d1cf5f246e29b9168", + "gas": "0x5208", + "gasPrice": "0x2de", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x857754afc3330f54a3e6400f502ad4a850a968671b641e271dcb9f68aacea291", + "s": "0x7d8f3fb2f3062c39d4271535a7d02960be9cb5a0a8de0baef2211604576369bf", + "hash": "0x64f8f0ad9c6526cb33e626626a25b8660a546aefa002692e46cd4d0331cd26ed" + } + }, + { + "block": "0xa5", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x85", + "to": "0x6922e93e3827642ce4b883c756b31abf80036649", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x17b", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x89e6d36baf81743f164397205ded9e5b3c807e943610d5b9adb9cfeb71b90299", + "s": "0x3d56c57f842a92a5eb71c8f9f394fe106d993960421c711498013806957fdcaf", + "yParity": "0x0", + "hash": "0x33b886e4c1c43507a08f0da97d083aa507cf905a90c17ffe20a2a24296f2db31" + } + }, + { + "block": "0xaa", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x89", + "to": "0xbceef655b5a034911f1c3718ce056531b45ef03b", + "gas": "0x5208", + "gasPrice": "0xc5", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x626dfd18ca500eedb8b439667d9b8d965da2f2d8ffcd36a5c5b60b9a05a52d9f", + "s": "0x7271175e4b74032edeb9b678ffb5e460edb2986652e45ff9123aece5f6c66838", + "hash": "0xe92638806137815555a0ffe5cc4c2b63b29171fd6f2473736201d8c3c3dbb748" + } + }, + { + "block": "0xaf", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x8d", + "to": "0x5a6e7a4754af8e7f47fc9493040d853e7b01e39d", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x68", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x8c62285d8318f84e669d3a135f99bbfe054422c48e44c5b9ce95891f87a37122", + "s": "0x28e75a73707ee665c58ff54791b62bd43a79de1522918f4f13f00ed4bd82b71b", + "yParity": "0x1", + "hash": "0x3f9133ad0b7430b124cc4b1213bc3fa72be41a58584ca05e8d863ec728890873" + } + }, + { + "block": "0xb4", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x91", + "to": "0x27952171c7fcdf0ddc765ab4f4e1c537cb29e5e5", + "gas": "0x5208", + "gasPrice": "0x39", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x76a045602a7de6b1414bdc881a321db0ce5255e878a65513bad6ac3b7f473aa7", + "s": "0x1a33017b5bcf6e059de612293db8e62b4c4a3414a7ba057c08dd6172fb78a86c", + "hash": "0x201f5041569d4dd9e5cc533867f1864daf1a7ee1a424d703d7aa8a43b07b491d" + } + }, + { + "block": "0xb9", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x95", + "to": "0x04d6c0c946716aac894fc1653383543a91faab60", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x20", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x39c18634a9f085ba0cd63685a54ef8f5c5b648856382896c7b0812ee603cd8a", + "s": "0x5ecfde61ea3757f59f0d8f0c77df00c0e68392eea1d8b76e726cb94fb5052b8a", + "yParity": "0x0", + "hash": "0xf83394fd19018fd54a5004121bc780995f99cb47832ddb11f7c50bf507606202" + } + }, + { + "block": "0xbe", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x99", + "to": "0x478508483cbb05defd7dcdac355dadf06282a6f2", + "gas": "0x5208", + "gasPrice": "0x13", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x910304dbb7d545a9c528785d26bf9e4c06d4c84fdb1b8d38bc6ee28f3db06178", + "s": "0x2ffc39c46a66af7b3af96e1e016a62ca92fc5e7e6b9dbe631acbdc325b7230a1", + "hash": "0x586f6726554ffef84726c93123de9fb1f0194dfd55ed7ca3ceae67e27b1f4fef" + } + }, + { + "block": "0xc3", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x9d", + "to": "0xae3f4619b0413d70d3004b9131c3752153074e45", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0xc", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x7cb73f8bf18eacc2c753098683a80208ac92089492d43bc0349e3ca458765c54", + "s": "0x3bf3eb6da85497e7865d119fde3718cdac76e73109384a997000c0b153401677", + "yParity": "0x1", + "hash": "0xadfacbcb99b52f33c74cbd7c45d1f0d31efc4a3f025f9832cf28e666c79c8e4c" + } + }, + { + "block": "0xc8", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xa1", + "to": "0x7c5bd2d144fdde498406edcb9fe60ce65b0dfa5f", + "gas": "0x5208", + "gasPrice": "0x9", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x15f510b05236b83a9370eb084e66272f93b4b646e225bdef016b01b3ac406391", + "s": "0x3b4a2b683af1cb3ecae367c8a8e59c76c259ce2c5c5ffd1dc81de5066879e4b8", + "hash": "0xed00ce6bd533009ddfb39d7735f1e2c468a231cf4c5badb59d1e1234c5fe3794" + } + }, + { + "block": "0xcd", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xa5", + "to": "0x9a7b7b3a5d50781b4f4768cd7ce223168f6b449b", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x4f3e818870a240e585d8990561b00ad3538cf64a189d0f5703a9431bc8fd5f25", + "s": "0x312f64dd9ab223877e94c71d83cb3e7fe359b96250d6a3c7253238979dd2f32a", + "yParity": "0x0", + "hash": "0x883c915c1ef312df1e499ef78d09767a374706d8ec89af9c65c46acd675bf817" + } + }, + { + "block": "0xd2", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xa9", + "to": "0x85f97e04d754c81dac21f0ce857adc81170d08c6", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x547e9550b5c687a2eb89c66ea85e7cd06aa776edd3b6e3e696676e22a90382b0", + "s": "0x28cb3ab4ef2761a5b530f4e05ef50e5fc957cfbc0342f98b04aa2882eec906b2", + "hash": "0x27d83955c23134e42c9beaa88332f770d09e589354c1047870328b7a2f8612c9" + } + }, + { + "block": "0xd7", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xad", + "to": "0x414a21e525a759e3ffeb22556be6348a92d5a13e", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x47b3309af68dd86089494d30d3356a69a33aa30945e1f52a924298f3167ab66", + "s": "0xb8b7bd6670a8bbcb89555528ff5719165363988aad1905a90a26c02633f8b9", + "yParity": "0x1", + "hash": "0xb75adb0bd26a8060f67c947b699471d71a66c61f2b8c6903a776c3eca7ad731e" + } + }, + { + "block": "0xdc", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xb1", + "to": "0xfb95aa98d6e6c5827a57ec17b978d647fcc01d98", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0xc71a69f756a2ef145f1fb1c9b009ff10af72ba0ee80ce59269708f917878bfb0", + "s": "0x3bfe6a6c41b3fe72e8e12c2927ee5df6d3d37bd94346a2398d4fcf80e1028dde", + "hash": "0x0301d78cc4bc0330c468026de4671377a07560c2356293c2af44334e6424361a" + } + }, + { + "block": "0xe1", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xb5", + "to": "0xf031efa58744e97a34555ca98621d4e8a52ceb5f", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x99b1b125ecb6df9a13deec5397266d4f19f7b87e067ef95a2bc8aba7b9822348", + "s": "0x56e2ee0d8be47d342fe36c22d4a9be2f26136dba3bd79fa6fe47900e93e40bf3", + "yParity": "0x1", + "hash": "0x6e07cf26de1881f062629d9efa026c55b9e8084082086e974ddeb66654cd9530" + } + }, + { + "block": "0xe6", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xb9", + "to": "0x0a3aaee7ccfb1a64f6d7bcd46657c27cb1f4569a", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0xd2aa10777b7c398921921258eeecaff46668278fd6f814ea4edb06f2a1076353", + "s": "0x542ef4ed484a1403494238e418bb8d613012871710e72dde77bb1fa877f1fae3", + "hash": "0xd77aeb22fbd8f99b75c970995d226b6985f2dcac6f22d65aa5d492d66e90f53f" + } + }, + { + "block": "0xeb", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xbd", + "to": "0xf8d20e598df20877e4d826246fc31ffb4615cbc0", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0xc982933a25dd67a6d0b714f50be154f841a72970b3ed52d0d12c143e6a273350", + "s": "0x7a9635960c75551def5d050beee4014e4fef2353c39d300e649c199eebc8fd5e", + "yParity": "0x1", + "hash": "0x597bc815e8b0c315e692257aabe4ecfce7055fa3659f02dd8444c7d58c9055f3" + } + }, + { + "block": "0xf0", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xc1", + "to": "0xfde502858306c235a3121e42326b53228b7ef469", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0x3d79397e88a64f6c2ca58b5ec7ba305012e619331946e60d6ab7c40e84bf1a34", + "s": "0x4278773d2796a0944f6bedadea3794b7ad6a18ffd01496aabf597d4a7cf75e17", + "hash": "0xe9c1c01813ee52f2a9b8aa63e200714c7527315caf55d054890c10acc73c6cec" + } + }, + { + "block": "0xf5", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xc5", + "to": "0x27abdeddfe8503496adeb623466caa47da5f63ab", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0xdeade75f98612138653ca1c81d8cc74eeda3e46ecf43c1f8fde86428a990ae25", + "s": "0x65f40f1aaf4d29268956348b7cc7fa054133ccb1522a045873cb43a9ffa25283", + "yParity": "0x1", + "hash": "0x2beff883cd58f8d155069d608dfc47f730a07f1ed361987b008c17a4b8b84a4b" + } + }, + { + "block": "0xfa", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xc9", + "to": "0xaa7225e7d5b0a2552bbb58880b3ec00c286995b8", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x968ae76ffc10f7b50ca349156119aaf1d81a8772683d1c3ed005147f4682694", + "s": "0x60f5f10a015e8685a3099140c2cc3ba0dc69026df97fb46748008c08978d162a", + "hash": "0x084d5438c574a2332976d95cfae552edb797001b5af69eacf4486538ab4bdbd2" + } + }, + { + "block": "0xff", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xcd", + "to": "0xa8100ae6aa1940d0b663bb31cd466142ebbdbd51", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x54eafef27c71a73357c888f788f1936378929e1cdb226a205644dc1e2d68f32b", + "s": "0x59af490b8ef4a4e98a282d9046655fc8818758e2af8ace2489927aaa3890fda3", + "yParity": "0x0", + "hash": "0xecce661913425dbe38e2d30e7ec20ead32185d76f516525148d2647ee94aac8e" + } + }, + { + "block": "0x104", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xd1", + "to": "0xa8d5dd63fba471ebcb1f3e8f7c1e1879b7152a6e", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x4c1d18013fb8b0554b8aaa549ee64a5a33c98edd5e51257447b4dd3b37f2ade", + "s": "0x5e3a37e5ddec2893b3fd38c4983b356c26dab5abb8b8ba6f56ac1ab9e747268b", + "hash": "0x0d903532e3740a8fb644943befee0187e6180eb31a327afc73e042ec314c02cc" + } + }, + { + "block": "0x109", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xd5", + "to": "0xac9e61d54eb6967e212c06aab15408292f8558c4", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x898d514a1f15103335e066d0625c4ec34a69a03480d67dcb3d3fe0f4f932100a", + "s": "0x7e130fed862c1482467d112f64fb59e005068b52c291003c908b625b4993e20e", + "yParity": "0x1", + "hash": "0xdd62d8c48dd14b156b3ea74d123fe3ddd7bc7700d0f189df3761ec7a8d65d1e9" + } + }, + { + "block": "0x10e", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xd9", + "to": "0x653b3bb3e18ef84d5b1e8ff9884aecf1950c7a1c", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0xf1c5d5e335842170288da2c7c7af6856ea0b566d2b4ab4b00a19cb94144d466c", + "s": "0x2043677d1c397a96a2f8a355431a59a0d5c40fc053e9c45b6872464f3c77c5dc", + "hash": "0x284452da997f42dbe0e511078f5005514fdeda8d0905439fe2f3a5ecc3aec1ac" + } + }, + { + "block": "0x113", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xdd", + "to": "0xd8c50d6282a1ba47f0a23430d177bbfbb72e2b84", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x4330fe20e8b84e751616253b9bccc5ff2d896e00593bfbef92e81e72b4d98a85", + "s": "0x7977b87c7eca1f6a8e4a535cb26860e32487c6b4b826623a7390df521b21eac7", + "yParity": "0x1", + "hash": "0xd667f29e2cccf282a82791cb46f9181ad04c8179bc11af957c499b3627907a6f" + } + }, + { + "block": "0x118", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xe1", + "to": "0xb519be874447e0f0a38ee8ec84ecd2198a9fac77", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0xcfbd9ff7eeb9aef477970dcba479f89c7573e6167d16d0882ead77b20aaee690", + "s": "0x1e34175b1b1758a581ca13f2ca021698933b1e8269c70fcb94c5e4aa39ee9b8e", + "hash": "0x935596bc447ea87dca90e3bac15f679129af2c813abe1657811f70dcafe660c2" + } + }, + { + "block": "0x11d", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xe5", + "to": "0xaf2c6f1512d1cabedeaf129e0643863c57419732", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0xc23170a740ba640770aca9fb699a2799d072b2466c97f126a834d86bdb22f516", + "s": "0x3f242217b60ab672f352ae51249a8876a034ee51b6b4ad4a41b4d300c48e79f4", + "yParity": "0x1", + "hash": "0xc659a1be386492afe2ca97cbbe9d1645763b502030c17e3acf9d539e22b74093" + } + }, + { + "block": "0x122", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xe9", + "to": "0xb70654fead634e1ede4518ef34872c9d4f083a53", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0x953d5aa69077225dba6a0333ea4d69a05f652e0d2abb8df492a7e6a9d0cdbe3d", + "s": "0x4e41cb847aa131b9bb1e19cb3dd5f7a6cc2ac8b7f459ab8c3061380d41721ff", + "hash": "0x6f7f93620049c80ba6429e3c2f7563f7048f725f245c22bcc6de438fd394bb7e" + } + }, + { + "block": "0x127", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xed", + "to": "0xbe3eea9a483308cb3134ce068e77b56e7c25af19", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x190737acd3a2a298d5a6f96a60ced561e536dd9d676c8494bc6d71e8b8a90b60", + "s": "0x2c407a67004643eba03f80965fea491c4a6c25d90d5a9fd53c6a61b62971e7c5", + "yParity": "0x0", + "hash": "0xe48311c620199dfc77bc280caa0a1bcbbd00457b079a7154a6f8bc229beb41f1" + } + }, + { + "block": "0x12c", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xf1", + "to": "0x08037e79bb41c0f1eda6751f0dabb5293ca2d5bf", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0xe3edf14f32e7cacb36fd116b5381fac6b12325a5908dcec2b8e2c6b5517f5ec5", + "s": "0x51429c4c1e479fa018b7907e7e3b02a448e968368a5ce9e2ea807525d363f85e", + "hash": "0xa960e3583c41a164dc743eac939626f891f20f7dfdf71f204c2f84ca1087ae90" + } + }, + { + "block": "0x131", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xf5", + "to": "0xf16ba6fa61da3398815be2a6c0f7cb1351982dbc", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x8dac03d829e6f8eab08661cd070c8a58eed41467ad9e526bb3b9c939e3fd4482", + "s": "0x2ac7208f150195c44c455ddeea0bbe104b9121fef5cba865311940f4de428eec", + "yParity": "0x1", + "hash": "0xc7ccef252840e9fc1821f2c2eb0ca8c9508ff3f4c23f85322e09dd9313849694" + } + }, + { + "block": "0x136", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xf9", + "to": "0x17333b15b4a5afd16cac55a104b554fc63cc8731", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0xf2179ec11444804bb595a6a2f569ea474b66e654ff8d6d162ec6ed565f83c1aa", + "s": "0x657ed11774d5d4bb0ed0eb1206d1d254735434a0c267912713099336c2dc147a", + "hash": "0x45ed5258df6ecd5ba8b99db384e39d22c193662830e79f972547d81e3857cc70" + } + }, + { + "block": "0x13b", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0xfd", + "to": "0xd20b702303d7d7c8afe50344d66a8a711bae1425", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x67bed94b25c4f3ab70b3aae5cd44c648c9807cdf086299e77cf2977b9bce8244", + "s": "0x76661b80df9b49579fce2e2201a51b08ecc4eb503d5f5517ecb20156fde7ec5a", + "yParity": "0x1", + "hash": "0xa3b085cc524be64d822be105f3bb92c05c773cb93bffc774ba9aac21f9603ce6" + } + }, + { + "block": "0x140", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x101", + "to": "0xdd1e2826c0124a6d4f7397a5a71f633928926c06", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x1f5208621cee9149c99848d808ee0fa8d57b358afbd39dc594f383b7f525f4c6", + "s": "0x1960c6254e869f06cfa3263972aa8e7cc79aec12caa728515c420d35b1336c0e", + "hash": "0x34671329e36adeee3261ea7313388804f481e6a0e2f77cce6961aed112498803" + } + }, + { + "block": "0x145", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x105", + "to": "0x1219c38638722b91f3a909f930d3acc16e309804", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x63adb9abb5014935b3dbf8c31059d6f1d9e12068a3f13bd3465db2b5a7f27f98", + "s": "0x56f0f5bed39985d0921989b132e9638472405a2b1ba757e22df3276ca9b527fa", + "yParity": "0x1", + "hash": "0x7bfa3e961b16291e9ee2f4dc0b6489bb0b12ff7a6ed6491c100dd1041472ff9e" + } + }, + { + "block": "0x14a", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x109", + "to": "0x1f5746736c7741ae3e8fa0c6e947cade81559a86", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0xedd3402a6c7a96114e4c8520d7bf3f06c00d9f24ee08de4c8afdbf05b4487b7d", + "s": "0x68cd4cf2242a8df916b3594055ee05551b77021bbea9b9eb9740f9a8e6466d80", + "hash": "0x90ea391ff615d345ad4e35e53af26e283fc2fd9ecb3221a9610fb2a376c38caf" + } + }, + { + "block": "0x14f", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x10d", + "to": "0x9ae62b6d840756c238b5ce936b910bb99d565047", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x25cc19f12be3ff2a51342412dc152953e8e8b61c9c3858c9d476cc214be4e30", + "s": "0x193960b0d01b790ef99b9a39b7475d18e83499f1635fc0a3868fc67c4da5b2c3", + "yParity": "0x0", + "hash": "0xa1ea0831d6727a0e7316822d3cc3815f1e2ba71e124fcd8b886610d5d42fd5ff" + } + }, + { + "block": "0x154", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x111", + "to": "0xb55a3d332d267493105927b892545d2cd4c83bd6", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0x73cc84153b8891468325ac12743faf7e373b78dbf8b9f856cb2622c7b4fd10e1", + "s": "0x388714fe9d2f85a88b962e213cbe1fa3c4a9823cea051cf91c607ecbd90093d8", + "hash": "0xd30ff6e59e0e1278dab8083cb01e1e66900adc72cc4263cbdffc98e08a728b89" + } + }, + { + "block": "0x159", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x115", + "to": "0xb68176634dde4d9402ecb148265db047d17cb4ab", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x9f3175e9aa2fe2332600b71de0b0977c7c60ccbeee66ea360226326817f2d59b", + "s": "0x6a870e0876002f789b3203f4a33d5e621ac67051704e1f2260b80d816260b3e6", + "yParity": "0x0", + "hash": "0x5565d4f07ad007f4bfe27837904f2ce365cff6c036aa5169df651f217944b1f4" + } + }, + { + "block": "0x15e", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x119", + "to": "0xdfe052578c96df94fa617102199e66110181ed2c", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x20ee6a1ada31c18eac485e0281a56fc6d8c4152213d0629e6d8dd325adb60b1", + "s": "0xf72e01c463b98817219db62e689416c510866450efc878a6035e9346a70795f", + "hash": "0x9055a34f1c764ce297f1bce6c94680a0e8d532debeb6af642c956122f4c7d079" + } + }, + { + "block": "0x163", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x11d", + "to": "0x33fc6e8ad066231eb5527d1a39214c1eb390985d", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x167190e2e0fed95ab5c7265a53f25a92d659e1d46eb9ecbac193e7151b82ec1c", + "s": "0x269353e9c5ef331135563e2983279669220687652e7f231725303ccf7d2a8ebd", + "yParity": "0x1", + "hash": "0x0aa77f1fa0e9ab541616fb3104788109f84010d4b410508e5779f052ee49c5b9" + } + }, + { + "block": "0x168", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x121", + "to": "0x662fb906c0fb671022f9914d6bba12250ea6adfb", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0xd3a858be3712102b61ec73c8317d1e557043f308869f4a04e3a4578e2d9aa7e7", + "s": "0x202a5f044cc84da719ec69b7985345b2ef82cf6b0357976e99e46b38c77fe613", + "hash": "0x01bdc2fb7f53293c98e430dc42b1ef18773493f0f1bd03460eb45e438168048d" + } + }, + { + "block": "0x16d", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x125", + "to": "0xf1fc98c0060f0d12ae263986be65770e2ae42eae", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x6563737b6bfddfb8bc5ec084651a8e51e3b95fe6ed4361065c988acaf764f210", + "s": "0xa96a1747559028cd02304adb52867678419ebef0f66012733fea03ee4eae43b", + "yParity": "0x0", + "hash": "0x36cf0f21e046b484333889a22e4880ad05807f2922340e6e822591cfa5138815" + } + }, + { + "block": "0x172", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x129", + "to": "0xa92bb60b61e305ddd888015189d6591b0eab0233", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x626bd8978288bcf1d7719926fba91597d6aa8ead945c89044693d780523a05dd", + "s": "0x74494ccf5362aa73db798940296b77b80a7ec6037f5ed2c946094b9df8a2347", + "hash": "0x8cb5e311a3e79a31c06afaecbbf9c814759f039f55b06ead4e8a1c2933766c8c" + } + }, + { + "block": "0x177", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x12d", + "to": "0x469542b3ece7ae501372a11c673d7627294a85ca", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x9add65921c40226ee4a686b9fa70c7582eba8c033ccc9c27775c6bc33c9232fb", + "s": "0x21a6e73ccb2f16e540594b4acbba2c852a3e853742359fcbc772880879fe1197", + "yParity": "0x0", + "hash": "0x55c8ee8da8d54305ca22c9d7b4226539a60741ed599327d33013f8d0385c61bd" + } + }, + { + "block": "0x17c", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x131", + "to": "0x7f2dce06acdeea2633ff324e5cb502ee2a42d979", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0xfd195ea41804b21ffffdbca38fd49a9874371e51e81642917d001d201a943e24", + "s": "0x542bca46a2dc92fddb9abffcf2b3e78dc491d6e95040692e6d1446a6b487a42a", + "hash": "0x3964c50008f0dce6974ef2c088a84207191eb56ab4ac86cbf5d149a661ecb479" + } + }, + { + "block": "0x181", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x135", + "to": "0x3bcc2d6d48ffeade5ac5af3ee7acd7875082e50a", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x3931e5e7d02ed045834da39a409083c260fbc96dc256c1d927f1704147eeaeb6", + "s": "0x215269010bb3e7dd8f03d71db3e617985b447c2e0dd6fc0939c125db43039d0f", + "yParity": "0x0", + "hash": "0x23583194a4443b0144115327770bf71f645283515ca26fc775dd23244a876e83" + } + }, + { + "block": "0x186", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x139", + "to": "0xf83af0ceb5f72a5725ffb7e5a6963647be7d8847", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0xa38cf9766454bd02d4f06f5bd214f5fe9e53b7a299eda5c7523060704fcdb751", + "s": "0x67c33351f6f7bbd9de5b5435f6cadc10ba5e94f3cbcc40ee53496c782f99d71f", + "hash": "0x41019c72018f2f499368e96aed89293b24873f611018c3787eeb81a0a01b667b" + } + }, + { + "block": "0x18b", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x13d", + "to": "0x469dacecdef1d68cb354c4a5c015df7cb6d655bf", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x6faf4090490862eba3c27dfe0a030a442ccc89d4478eca3ed09039386554f07b", + "s": "0x656f741b64c54808ac5a6956540d3f7aaec811bf4efa7239a0ca0c7fb410b4d6", + "yParity": "0x1", + "hash": "0x054500013715ec41cb39492f2856925c7f22f80fd22365f19de8124b14e77e90" + } + }, + { + "block": "0x190", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x141", + "to": "0xf14d90dc2815f1fc7536fc66ca8f73562feeedd1", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x4a18131d30b0344910cae7c41ee5c1c23171c40292d34e9a82c9c7cef3d3836a", + "s": "0x598a3835ad1903c3d7ad158c57ff0db10e12d8acbef318ddd0514f671a08ce94", + "hash": "0x1b562d975247f54df92dc775c61ef8fb004714fd57d0c804dd64e44be2f10cb5" + } + }, + { + "block": "0x195", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x145", + "to": "0x360671abc40afd33ae0091e87e589fc320bf9e3d", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x9b0a44741dc7e6cb0f88199ca38f15034fab4164d9055788834e8123b7264c87", + "s": "0x2c38a3ecda52aebc3725c65ee1cd0461a8d706ddfc9ed27d156cf50b61ef5069", + "yParity": "0x0", + "hash": "0x3e3bec1253082bf314cb1155ef241912bc842b8ced86b70e5e3b24585a130d66" + } + }, + { + "block": "0x19a", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x149", + "to": "0x579ab019e6b461188300c7fb202448d34669e5ff", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0xde600e017080351550412ac87f184ec2c3f672e08f1c362ab58b94631e8864dc", + "s": "0x47d41b8691a1f7f8818e59ad473451a0edfc88826a6b808f84f56baed90d5634", + "hash": "0x519fbf530d16289510ebb27b099ad16ad03e72227497db7a62e6c0e89d3a708a" + } + }, + { + "block": "0x19f", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x14d", + "to": "0x88654f0e7be1751967bba901ed70257a3cb79940", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0xa79b0ff9846673061d1b90a17cd8bd9e7c7f62b99b39fbe4749777d3ed4544e0", + "s": "0x750ecfe9895402861ebea87e9b483b2c116bc2d4920329aa1c29efb9dcdf47e6", + "yParity": "0x1", + "hash": "0x6364bf260fee1aea143ec4a4c596d64e15252f8fa4c7ab7ae69d51ff4cbd343b" + } + }, + { + "block": "0x1a4", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x151", + "to": "0x47e642c9a2f80499964cfda089e0b1f52ed0f57d", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0xc37c23a91d6abced211855a2d6d5e383f54aa6ff40c26abc5f27a22cdafa5618", + "s": "0x190f82ff101eabad8b9c7041006dcb3e3a9a85c814938bef8ec7d1aa63fa5892", + "hash": "0x2ee70986d957daba62588ac40c9bf75f6707a34dc5ef5897ae7cd3998f2e05bc" + } + }, + { + "block": "0x1a9", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x155", + "to": "0xd854d6dd2b74dc45c9b883677584c3ac7854e01a", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x7a17de801de3309b57dd86df30b61553d5c04071581d243f33f43c4d64930e09", + "s": "0x75f7e820212e8f96d7583c66548719db621537fe20f7568d5ee62176881b70e8", + "yParity": "0x0", + "hash": "0xbaf8e87ba94a0d70e37443c4475b2525806827b3ae964b30eb4dad7936b2eb6e" + } + }, + { + "block": "0x1ae", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x159", + "to": "0xc305dd6cfc073cfe5e194fc817536c419410a27d", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x163f29bc7be2e8fe3c6347fe4de06fa7330e3a3049c0e9dcded1795ff1c1e810", + "s": "0x4ea7492a5e457fd21252166f5a5d5d9d5e5c7a19da2c7fd4a822bf60156b91a9", + "hash": "0x4a84eeb0addd194ae92631aa43ed4f4fece16258bcbbc91de6324e20bde0f914" + } + }, + { + "block": "0x1b3", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x15d", + "to": "0x2143e52a9d8ad4c55c8fdda755f4889e3e3e7721", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0x673c5473955d0d26d49b25b82af905ee33ba365178f44dc4ac39221efec23c88", + "s": "0x17f46fc9b15ba0c1ea78d4d9f773582d94f61f6471f2918cb0598f33eb9bc89b", + "yParity": "0x1", + "hash": "0x01b1e85401ca88bc02c33956d0bfeea9ec0b6c916f1478d4eae39818e999cb74" + } + }, + { + "block": "0x1b8", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x161", + "to": "0x0fe037febcc3adf9185b4e2ad4ea43c125f05049", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0x654dc39f93a879b9aec58ace2fdbd5c47e383cae2d14f1a49f6ec93d539be892", + "s": "0x70505a0ef2e83f057e9844dbd56eda0949197f0c4a2b6d0f2979db1710fca4ed", + "hash": "0xf8c7948d4418ad9948d7352c6c21dcb5b7f72664dfcfe553dfc444df7afc9c0b" + } + }, + { + "block": "0x1bd", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x165", + "to": "0x046dc70a4eba21473beb6d9460d880b8cfd66613", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x9a954eff1b0e590a3a78b724b687c6ab944181990998780d56cc3593c704996e", + "s": "0x418db96b5dc1057f6acb018244f82ed6ece03d88c07f6ae767eaebe3b7ac9387", + "yParity": "0x0", + "hash": "0xf09a7e0da3b14049923d019fb5d457531ddaa4456cf84124a17479b0bfd6261b" + } + }, + { + "block": "0x1c2", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x169", + "to": "0x104eb07eb9517a895828ab01a3595d3b94c766d5", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0x597dbb3f69603be721ae0f2a63eeee9f008829ff273b54243673f9ea192ddc0a", + "s": "0x1f7dd04defb45af840d46a950b8bede0b3ce8a718004c1ca2f3bbd4efcbd7563", + "hash": "0x00c458459a2d2f501907a6a4122fba7ae70fb3ef632676e492912231022f80c8" + } + }, + { + "block": "0x1c7", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x16d", + "to": "0x46b61db0aac95a332cecadad86e52531e578cf1f", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x774ced5c8674413b351ae8ac3b96705d1d3db10deae39134572be985f16c008b", + "s": "0x6f3e4b250f84fcf95ae85946da8a1c79f922a211dbe516fcfcff0180911429b8", + "yParity": "0x0", + "hash": "0x6603c100a34224ddb8aaeb9e234f0c611d40a5df807de68803b71e0ff0f3aea8" + } + }, + { + "block": "0x1cc", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x171", + "to": "0x8a817bc42b2e2146dc4ca4dc686db0a4051d2944", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0xa755d1c641b8965ea140ad348135496fc412ffa43a72bbd2c7c0e26b814a75f1", + "s": "0x67d81cca370b6ea40ccd2ad3662d16fa36bd380845bee04c55c6531455d0687d", + "hash": "0x46e00cb4ede9be515c8910a31881df229ebb2804722ad9d6723e1101a87f1889" + } + }, + { + "block": "0x1d1", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x175", + "to": "0x23e6931c964e77b02506b08ebf115bad0e1eca66", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x6263b1d5b9028231af73bfa386be8fc770e11f60137428378137c34f12c2c242", + "s": "0x2b340f5b45217d9b914921a191ce5f7ba67af038e3b3c2c72aaca471412b02f7", + "yParity": "0x0", + "hash": "0xa5b751caaaff89a472fb427c17ac7637b4a9de7cda34beaaf891516278655479" + } + }, + { + "block": "0x1d6", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x179", + "to": "0x878dedd9474cfa24d91bccc8b771e180cf01ac40", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x515a62775619f55c366d080a7c397ea42dcfd2fdcce1862ef98dab875077f367", + "s": "0x23756d4f3bd644dde1c25f8cde45fbea557dacf0492bbecb409f6b2cdacbb9b8", + "hash": "0x2e232fb6d73423c9dcaff38257d36fcad74a2c627a70030b43a0bed36d136625" + } + }, + { + "block": "0x1db", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x17d", + "to": "0x45dcb3e20af2d8ba583d774404ee8fedcd97672b", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x1", + "r": "0xd3b69c226bf73db84babb6185a83b0dd491467adfc01d279df4c09d5d2d3fba4", + "s": "0x368ddb772caa32963df97961cf8ef0db33e0df5945000f0e39d9a288bd73ee30", + "yParity": "0x1", + "hash": "0xc80615944f9bfeb945b7416052667eec0a78b2f3beb7c2811ebb9e9210e45c4c" + } + }, + { + "block": "0x1e0", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x181", + "to": "0x50996999ff63a9a1a07da880af8f8c745a7fe72c", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0xf06ad492cdd04b44f321abe9cb98e5977f03909173e4b6361f50d44c080f9d6a", + "s": "0x7fdc23c04fab8e0a576e6896b13a661b2dcb256cf8ca42fa21f0f370097a53a4", + "hash": "0x8c1f1466ce25a97e88ab37bc9b5362eaf95fb523fb80d176429fa41c2fa2d629" + } + }, + { + "block": "0x1e5", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x185", + "to": "0x913f841dfc8703ae76a4e1b8b84cd67aab15f17a", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0xd4b8d15fc05f29b58f0459b336dc48b142e8d14572edad06e346aa7728491ce8", + "s": "0x64c8078691ba1c4bb110f6dff74e26d3c0df2505940558746a1c617091ddc61a", + "yParity": "0x0", + "hash": "0x969e178ea1a76626b96bf06e207edb6299c36c6a14e46462960832feb93f6d42" + } + }, + { + "block": "0x1ea", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x189", + "to": "0xb47f70b774d780c3ec5ac411f2f9198293b9df7a", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd10a0", + "r": "0xd33c0cd7f521603ea8deaa363ab591627f5af193759f0aeb8cd9fe4f22a4dd5c", + "s": "0x667bb0ee041403cba2e562882bb9afc43bd560af3c95136c7bf4f1e361355316", + "hash": "0xa35c19e4e8154c35656544b92e88fb62c4210e38f09608248e2a99841ac99964" + } + }, + { + "block": "0x1ef", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x2", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x18d", + "to": "0x6e3d512a9328fa42c7ca1e20064071f88958ed93", + "gas": "0x5208", + "gasPrice": null, + "maxPriorityFeePerGas": "0x1", + "maxFeePerGas": "0x8", + "value": "0x1", + "input": "0x", + "accessList": [], + "v": "0x0", + "r": "0x990aa3c805c666109799583317176d55a73d96137ff886be719a36537d577e3d", + "s": "0x5d1244d8c33e85b49e2061112549e616b166a1860b07f00ff963a0b37c29bcaa", + "yParity": "0x0", + "hash": "0xeb282a48d309db881eead661ee7c64696b2699fa7c431d39a573ecaa0bc31052" + } + }, + { + "block": "0x1f4", + "sender": "0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f", + "tx": { + "type": "0x0", + "chainId": "0xc72dd9d5e883e", + "nonce": "0x191", + "to": "0x15af6900147a8730b5ce3e1db6333f33f64ebb2c", + "gas": "0x5208", + "gasPrice": "0x8", + "maxPriorityFeePerGas": null, + "maxFeePerGas": null, + "value": "0x1", + "input": "0x", + "v": "0x18e5bb3abd109f", + "r": "0x85b3c275e830c2034a4666e3a57c8640a8e5e7b7c8d0687467e205c037b4c5d7", + "s": "0x52e2aa8b60be142eee26f197b1e0a983f8df844c770881d820dfc4d1bb3d9adc", + "hash": "0x22e616c85493bcd23147d1c9f5dd081b32daf5c7b3e824f61b5fc1bd34a47e67" + } + } + ], + "withdrawals": { + "101": { + "withdrawals": [ + { + "index": "0x4", + "validatorIndex": "0x5", + "address": "0x3f79bb7b435b05321651daefd374cdc681dc06fa", + "amount": "0x64" + } + ] + }, + "106": { + "withdrawals": [ + { + "index": "0x5", + "validatorIndex": "0x5", + "address": "0x189f40034be7a199f1fa9891668ee3ab6049f82d", + "amount": "0x64" + } + ] + }, + "111": { + "withdrawals": [ + { + "index": "0x6", + "validatorIndex": "0x5", + "address": "0x65c74c15a686187bb6bbf9958f494fc6b8006803", + "amount": "0x64" + } + ] + }, + "116": { + "withdrawals": [ + { + "index": "0x7", + "validatorIndex": "0x5", + "address": "0xe3b98a4da31a127d4bde6e43033f66ba274cab0e", + "amount": "0x64" + } + ] + }, + "121": { + "withdrawals": [ + { + "index": "0x8", + "validatorIndex": "0x5", + "address": "0xa1fce4363854ff888cff4b8e7875d600c2682390", + "amount": "0x64" + } + ] + }, + "126": { + "withdrawals": [ + { + "index": "0x9", + "validatorIndex": "0x5", + "address": "0x7ace431cb61584cb9b8dc7ec08cf38ac0a2d6496", + "amount": "0x64" + } + ] + }, + "131": { + "withdrawals": [ + { + "index": "0xa", + "validatorIndex": "0x5", + "address": "0x5ee0dd4d4840229fab4a86438efbcaf1b9571af9", + "amount": "0x64" + } + ] + }, + "136": { + "withdrawals": [ + { + "index": "0xb", + "validatorIndex": "0x5", + "address": "0x4f362f9093bb8e7012f466224ff1237c0746d8c8", + "amount": "0x64" + } + ] + }, + "141": { + "withdrawals": [ + { + "index": "0xc", + "validatorIndex": "0x5", + "address": "0x075198bfe61765d35f990debe90959d438a943ce", + "amount": "0x64" + } + ] + }, + "146": { + "withdrawals": [ + { + "index": "0xd", + "validatorIndex": "0x5", + "address": "0x956062137518b270d730d4753000896de17c100a", + "amount": "0x64" + } + ] + }, + "151": { + "withdrawals": [ + { + "index": "0xe", + "validatorIndex": "0x5", + "address": "0x2a0ab732b4e9d85ef7dc25303b64ab527c25a4d7", + "amount": "0x64" + } + ] + }, + "156": { + "withdrawals": [ + { + "index": "0xf", + "validatorIndex": "0x5", + "address": "0x6e3faf1e27d45fca70234ae8f6f0a734622cff8a", + "amount": "0x64" + } + ] + }, + "161": { + "withdrawals": [ + { + "index": "0x10", + "validatorIndex": "0x5", + "address": "0x8a8950f7623663222542c9469c73be3c4c81bbdf", + "amount": "0x64" + } + ] + }, + "166": { + "withdrawals": [ + { + "index": "0x11", + "validatorIndex": "0x5", + "address": "0xfe1dcd3abfcd6b1655a026e60a05d03a7f71e4b6", + "amount": "0x64" + } + ] + }, + "171": { + "withdrawals": [ + { + "index": "0x12", + "validatorIndex": "0x5", + "address": "0x087d80f7f182dd44f184aa86ca34488853ebcc04", + "amount": "0x64" + } + ] + }, + "176": { + "withdrawals": [ + { + "index": "0x13", + "validatorIndex": "0x5", + "address": "0xf4f97c88c409dcf3789b5b518da3f7d266c48806", + "amount": "0x64" + } + ] + }, + "181": { + "withdrawals": [ + { + "index": "0x14", + "validatorIndex": "0x5", + "address": "0x892f60b39450a0e770f00a836761c8e964fd7467", + "amount": "0x64" + } + ] + }, + "186": { + "withdrawals": [ + { + "index": "0x15", + "validatorIndex": "0x5", + "address": "0x281c93990bac2c69cf372c9a3b66c406c86cca82", + "amount": "0x64" + } + ] + }, + "191": { + "withdrawals": [ + { + "index": "0x16", + "validatorIndex": "0x5", + "address": "0xb12dc850a3b0a3b79fc2255e175241ce20489fe4", + "amount": "0x64" + } + ] + }, + "196": { + "withdrawals": [ + { + "index": "0x17", + "validatorIndex": "0x5", + "address": "0xd1211001882d2ce16a8553e449b6c8b7f71e6183", + "amount": "0x64" + } + ] + }, + "201": { + "withdrawals": [ + { + "index": "0x18", + "validatorIndex": "0x5", + "address": "0x4fb733bedb74fec8d65bedf056b935189a289e92", + "amount": "0x64" + } + ] + }, + "206": { + "withdrawals": [ + { + "index": "0x19", + "validatorIndex": "0x5", + "address": "0xc337ded6f56c07205fb7b391654d7d463c9e0c72", + "amount": "0x64" + } + ] + }, + "211": { + "withdrawals": [ + { + "index": "0x1a", + "validatorIndex": "0x5", + "address": "0x28969cdfa74a12c82f3bad960b0b000aca2ac329", + "amount": "0x64" + } + ] + }, + "216": { + "withdrawals": [ + { + "index": "0x1b", + "validatorIndex": "0x5", + "address": "0xaf193a8cdcd0e3fb39e71147e59efa5cad40763d", + "amount": "0x64" + } + ] + }, + "221": { + "withdrawals": [ + { + "index": "0x1c", + "validatorIndex": "0x5", + "address": "0x2795044ce0f83f718bc79c5f2add1e52521978df", + "amount": "0x64" + } + ] + }, + "226": { + "withdrawals": [ + { + "index": "0x1d", + "validatorIndex": "0x5", + "address": "0x30a5bfa58e128af9e5a4955725d8ad26d4d574a5", + "amount": "0x64" + } + ] + }, + "231": { + "withdrawals": [ + { + "index": "0x1e", + "validatorIndex": "0x5", + "address": "0xd0752b60adb148ca0b3b4d2591874e2dabd34637", + "amount": "0x64" + } + ] + }, + "236": { + "withdrawals": [ + { + "index": "0x1f", + "validatorIndex": "0x5", + "address": "0x45f83d17e10b34fca01eb8f4454dac34a777d940", + "amount": "0x64" + } + ] + }, + "241": { + "withdrawals": [ + { + "index": "0x20", + "validatorIndex": "0x5", + "address": "0xd4f09e5c5af99a24c7e304ca7997d26cb0090169", + "amount": "0x64" + } + ] + }, + "246": { + "withdrawals": [ + { + "index": "0x21", + "validatorIndex": "0x5", + "address": "0xb0b2988b6bbe724bacda5e9e524736de0bc7dae4", + "amount": "0x64" + } + ] + }, + "251": { + "withdrawals": [ + { + "index": "0x22", + "validatorIndex": "0x5", + "address": "0x04b8d34e20e604cadb04b9db8f6778c35f45a2d2", + "amount": "0x64" + } + ] + }, + "256": { + "withdrawals": [ + { + "index": "0x23", + "validatorIndex": "0x5", + "address": "0x47dc540c94ceb704a23875c11273e16bb0b8a87a", + "amount": "0x64" + } + ] + }, + "261": { + "withdrawals": [ + { + "index": "0x24", + "validatorIndex": "0x5", + "address": "0xbc5959f43bc6e47175374b6716e53c9a7d72c594", + "amount": "0x64" + } + ] + }, + "266": { + "withdrawals": [ + { + "index": "0x25", + "validatorIndex": "0x5", + "address": "0xc04b5bb1a5b2eb3e9cd4805420dba5a9d133da5b", + "amount": "0x64" + } + ] + }, + "271": { + "withdrawals": [ + { + "index": "0x26", + "validatorIndex": "0x5", + "address": "0x24255ef5d941493b9978f3aabb0ed07d084ade19", + "amount": "0x64" + } + ] + }, + "276": { + "withdrawals": [ + { + "index": "0x27", + "validatorIndex": "0x5", + "address": "0xdbe726e81a7221a385e007ef9e834a975a4b528c", + "amount": "0x64" + } + ] + }, + "281": { + "withdrawals": [ + { + "index": "0x28", + "validatorIndex": "0x5", + "address": "0xae58b7e08e266680e93e46639a2a7e89fde78a6f", + "amount": "0x64" + } + ] + }, + "286": { + "withdrawals": [ + { + "index": "0x29", + "validatorIndex": "0x5", + "address": "0x5df7504bc193ee4c3deadede1459eccca172e87c", + "amount": "0x64" + } + ] + }, + "291": { + "withdrawals": [ + { + "index": "0x2a", + "validatorIndex": "0x5", + "address": "0xb71de80778f2783383f5d5a3028af84eab2f18a4", + "amount": "0x64" + } + ] + }, + "296": { + "withdrawals": [ + { + "index": "0x2b", + "validatorIndex": "0x5", + "address": "0x1c972398125398a3665f212930758ae9518a8c94", + "amount": "0x64" + } + ] + }, + "301": { + "withdrawals": [ + { + "index": "0x2c", + "validatorIndex": "0x5", + "address": "0x1c123d5c0d6c5a22ef480dce944631369fc6ce28", + "amount": "0x64" + } + ] + }, + "306": { + "withdrawals": [ + { + "index": "0x2d", + "validatorIndex": "0x5", + "address": "0x7f774bb46e7e342a2d9d0514b27cee622012f741", + "amount": "0x64" + } + ] + }, + "311": { + "withdrawals": [ + { + "index": "0x2e", + "validatorIndex": "0x5", + "address": "0x06f647b157b8557a12979ba04cf5ba222b9747cf", + "amount": "0x64" + } + ] + }, + "316": { + "withdrawals": [ + { + "index": "0x2f", + "validatorIndex": "0x5", + "address": "0xcccc369c5141675a9e9b1925164f30cdd60992dc", + "amount": "0x64" + } + ] + }, + "321": { + "withdrawals": [ + { + "index": "0x30", + "validatorIndex": "0x5", + "address": "0xacfa6b0e008d0208f16026b4d17a4c070e8f9f8d", + "amount": "0x64" + } + ] + }, + "326": { + "withdrawals": [ + { + "index": "0x31", + "validatorIndex": "0x5", + "address": "0x6a632187a3abf9bebb66d43368fccd612f631cbc", + "amount": "0x64" + } + ] + }, + "331": { + "withdrawals": [ + { + "index": "0x32", + "validatorIndex": "0x5", + "address": "0x984c16459ded76438d98ce9b608f175c28a910a0", + "amount": "0x64" + } + ] + }, + "336": { + "withdrawals": [ + { + "index": "0x33", + "validatorIndex": "0x5", + "address": "0x2847213288f0988543a76512fab09684131809d9", + "amount": "0x64" + } + ] + }, + "341": { + "withdrawals": [ + { + "index": "0x34", + "validatorIndex": "0x5", + "address": "0x1037044fabf0421617c47c74681d7cc9c59f136c", + "amount": "0x64" + } + ] + }, + "346": { + "withdrawals": [ + { + "index": "0x35", + "validatorIndex": "0x5", + "address": "0x8cf42eb93b1426f22a30bd22539503bdf838830c", + "amount": "0x64" + } + ] + }, + "351": { + "withdrawals": [ + { + "index": "0x36", + "validatorIndex": "0x5", + "address": "0x6b2884fef44bd4288621a2cda9f88ca07b480861", + "amount": "0x64" + } + ] + }, + "356": { + "withdrawals": [ + { + "index": "0x37", + "validatorIndex": "0x5", + "address": "0xf6152f2ad8a93dc0f8f825f2a8d162d6da46e81f", + "amount": "0x64" + } + ] + }, + "361": { + "withdrawals": [ + { + "index": "0x38", + "validatorIndex": "0x5", + "address": "0x8fa24283a8c1cc8a0f76ac69362139a173592567", + "amount": "0x64" + } + ] + }, + "366": { + "withdrawals": [ + { + "index": "0x39", + "validatorIndex": "0x5", + "address": "0x19041ad672875015bc4041c24b581eafc0869aab", + "amount": "0x64" + } + ] + }, + "371": { + "withdrawals": [ + { + "index": "0x3a", + "validatorIndex": "0x5", + "address": "0x2bb3295506aa5a21b58f1fd40f3b0f16d6d06bbc", + "amount": "0x64" + } + ] + }, + "376": { + "withdrawals": [ + { + "index": "0x3b", + "validatorIndex": "0x5", + "address": "0x23c86a8aded0ad81f8111bb07e6ec0ffb00ce5bf", + "amount": "0x64" + } + ] + }, + "381": { + "withdrawals": [ + { + "index": "0x3c", + "validatorIndex": "0x5", + "address": "0x96a1cabb97e1434a6e23e684dd4572e044c243ea", + "amount": "0x64" + } + ] + }, + "386": { + "withdrawals": [ + { + "index": "0x3d", + "validatorIndex": "0x5", + "address": "0xfd5e6e8c850fafa2ba2293c851479308c0f0c9e7", + "amount": "0x64" + } + ] + }, + "391": { + "withdrawals": [ + { + "index": "0x3e", + "validatorIndex": "0x5", + "address": "0xf997ed224012b1323eb2a6a0c0044a956c6b8070", + "amount": "0x64" + } + ] + }, + "396": { + "withdrawals": [ + { + "index": "0x3f", + "validatorIndex": "0x5", + "address": "0x6d09a879576c0d941bea7833fb2285051b10d511", + "amount": "0x64" + } + ] + }, + "401": { + "withdrawals": [ + { + "index": "0x40", + "validatorIndex": "0x5", + "address": "0x13dd437fc2ed1cd5d943ac1dd163524c815d305c", + "amount": "0x64" + } + ] + }, + "406": { + "withdrawals": [ + { + "index": "0x41", + "validatorIndex": "0x5", + "address": "0x6510225e743d73828aa4f73a3133818490bd8820", + "amount": "0x64" + } + ] + }, + "411": { + "withdrawals": [ + { + "index": "0x42", + "validatorIndex": "0x5", + "address": "0xd282cf9c585bb4f6ce71e16b6453b26aa8d34a53", + "amount": "0x64" + } + ] + }, + "416": { + "withdrawals": [ + { + "index": "0x43", + "validatorIndex": "0x5", + "address": "0xa179dbdd51c56d0988551f92535797bcf47ca0e7", + "amount": "0x64" + } + ] + }, + "421": { + "withdrawals": [ + { + "index": "0x44", + "validatorIndex": "0x5", + "address": "0x494d799e953876ac6022c3f7da5e0f3c04b549be", + "amount": "0x64" + } + ] + }, + "426": { + "withdrawals": [ + { + "index": "0x45", + "validatorIndex": "0x5", + "address": "0xb4bc136e1fb4ea0b3340d06b158277c4a8537a13", + "amount": "0x64" + } + ] + }, + "431": { + "withdrawals": [ + { + "index": "0x46", + "validatorIndex": "0x5", + "address": "0x368b766f1e4d7bf437d2a709577a5210a99002b6", + "amount": "0x64" + } + ] + }, + "436": { + "withdrawals": [ + { + "index": "0x47", + "validatorIndex": "0x5", + "address": "0x5123198d8a827fe0c788c409e7d2068afde64339", + "amount": "0x64" + } + ] + }, + "441": { + "withdrawals": [ + { + "index": "0x48", + "validatorIndex": "0x5", + "address": "0xd39b94587711196640659ec81855bcf397e419ff", + "amount": "0x64" + } + ] + }, + "446": { + "withdrawals": [ + { + "index": "0x49", + "validatorIndex": "0x5", + "address": "0x6ca60a92cbf88c7f527978dc183a22e774755551", + "amount": "0x64" + } + ] + }, + "451": { + "withdrawals": [ + { + "index": "0x4a", + "validatorIndex": "0x5", + "address": "0x102efa1f2e0ad16ada57759b815245b8f8d27ce4", + "amount": "0x64" + } + ] + }, + "456": { + "withdrawals": [ + { + "index": "0x4b", + "validatorIndex": "0x5", + "address": "0xfcc8d4cd5a42cca8ac9f9437a6d0ac09f1d08785", + "amount": "0x64" + } + ] + }, + "461": { + "withdrawals": [ + { + "index": "0x4c", + "validatorIndex": "0x5", + "address": "0x48701721ec0115f04bc7404058f6c0f386946e09", + "amount": "0x64" + } + ] + }, + "466": { + "withdrawals": [ + { + "index": "0x4d", + "validatorIndex": "0x5", + "address": "0x706be462488699e89b722822dcec9822ad7d05a7", + "amount": "0x64" + } + ] + }, + "471": { + "withdrawals": [ + { + "index": "0x4e", + "validatorIndex": "0x5", + "address": "0xe5ec19296e6d1518a6a38c1dbc7ad024b8a1a248", + "amount": "0x64" + } + ] + }, + "476": { + "withdrawals": [ + { + "index": "0x4f", + "validatorIndex": "0x5", + "address": "0x2e350f8e7f890a9301f33edbf55f38e67e02d72b", + "amount": "0x64" + } + ] + }, + "481": { + "withdrawals": [ + { + "index": "0x50", + "validatorIndex": "0x5", + "address": "0xc57aa6a4279377063b17c554d3e33a3490e67a9a", + "amount": "0x64" + } + ] + }, + "486": { + "withdrawals": [ + { + "index": "0x51", + "validatorIndex": "0x5", + "address": "0x311df588ca5f412f970891e4cc3ac23648968ca2", + "amount": "0x64" + } + ] + }, + "491": { + "withdrawals": [ + { + "index": "0x52", + "validatorIndex": "0x5", + "address": "0x3f31becc97226d3c17bf574dd86f39735fe0f0c1", + "amount": "0x64" + } + ] + }, + "496": { + "withdrawals": [ + { + "index": "0x53", + "validatorIndex": "0x5", + "address": "0x6cc0ab95752bf25ec58c91b1d603c5eb41b8fbd7", + "amount": "0x64" + } + ] + }, + "81": { + "withdrawals": [ + { + "index": "0x0", + "validatorIndex": "0x5", + "address": "0x4ae81572f06e1b88fd5ced7a1a000945432e83e1", + "amount": "0x64" + } + ] + }, + "86": { + "withdrawals": [ + { + "index": "0x1", + "validatorIndex": "0x5", + "address": "0xde5a6f78116eca62d7fc5ce159d23ae6b889b365", + "amount": "0x64" + } + ] + }, + "91": { + "withdrawals": [ + { + "index": "0x2", + "validatorIndex": "0x5", + "address": "0x245843abef9e72e7efac30138a994bf6301e7e1d", + "amount": "0x64" + } + ] + }, + "96": { + "withdrawals": [ + { + "index": "0x3", + "validatorIndex": "0x5", + "address": "0x8d33f520a3c4cef80d2453aef81b612bfe1cb44c", + "amount": "0x64" + } + ] + } + } +} \ No newline at end of file diff --git a/cmd/devp2p/internal/ethtest/transaction.go b/cmd/devp2p/internal/ethtest/transaction.go index db5199d33..0ea7c3275 100644 --- a/cmd/devp2p/internal/ethtest/transaction.go +++ b/cmd/devp2p/internal/ethtest/transaction.go @@ -19,429 +19,141 @@ package ethtest import ( "errors" "fmt" - "math/big" - "strings" + "os" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/internal/utesting" - "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/eth/protocols/eth" ) -// var faucetAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") -var faucetKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - -func (s *Suite) sendSuccessfulTxs(t *utesting.T) error { - tests := []*types.Transaction{ - getNextTxFromChain(s), - unknownTx(s), - } - for i, tx := range tests { - if tx == nil { - return errors.New("could not find tx to send") - } - t.Logf("Testing tx propagation %d: sending tx %v %v %v\n", i, tx.Hash().String(), tx.GasPrice(), tx.Gas()) - // get previous tx if exists for reference in case of old tx propagation - var prevTx *types.Transaction - if i != 0 { - prevTx = tests[i-1] - } - // write tx to connection - if err := sendSuccessfulTx(s, tx, prevTx); err != nil { - return fmt.Errorf("send successful tx test failed: %v", err) - } - } - return nil -} - -func sendSuccessfulTx(s *Suite, tx *types.Transaction, prevTx *types.Transaction) error { - sendConn, recvConn, err := s.createSendAndRecvConns() +// sendTxs sends the given transactions to the node and +// expects the node to accept and propagate them. +func (s *Suite) sendTxs(txs []*types.Transaction) error { + // Open sending conn. + sendConn, err := s.dial() if err != nil { return err } defer sendConn.Close() - defer recvConn.Close() if err = sendConn.peer(s.chain, nil); err != nil { return fmt.Errorf("peering failed: %v", err) } - // Send the transaction - if err = sendConn.Write(&Transactions{tx}); err != nil { - return fmt.Errorf("failed to write to connection: %v", err) - } - // peer receiving connection to node - if err = recvConn.peer(s.chain, nil); err != nil { - return fmt.Errorf("peering failed: %v", err) - } - - // update last nonce seen - nonce = tx.Nonce() - // Wait for the transaction announcement - for { - switch msg := recvConn.readAndServe(s.chain, timeout).(type) { - case *Transactions: - recTxs := *msg - // if you receive an old tx propagation, read from connection again - if len(recTxs) == 1 && prevTx != nil { - if recTxs[0] == prevTx { - continue - } - } - for _, gotTx := range recTxs { - if gotTx.Hash() == tx.Hash() { - // Ok - return nil - } - } - return fmt.Errorf("missing transaction: got %v missing %v", recTxs, tx.Hash()) - case *NewPooledTransactionHashes66: - txHashes := *msg - // if you receive an old tx propagation, read from connection again - if len(txHashes) == 1 && prevTx != nil { - if txHashes[0] == prevTx.Hash() { - continue - } - } - for _, gotHash := range txHashes { - if gotHash == tx.Hash() { - // Ok - return nil - } - } - return fmt.Errorf("missing transaction announcement: got %v missing %v", txHashes, tx.Hash()) - case *NewPooledTransactionHashes: - txHashes := msg.Hashes - if len(txHashes) != len(msg.Sizes) { - return fmt.Errorf("invalid msg size lengths: hashes: %v sizes: %v", len(txHashes), len(msg.Sizes)) - } - if len(txHashes) != len(msg.Types) { - return fmt.Errorf("invalid msg type lengths: hashes: %v types: %v", len(txHashes), len(msg.Types)) - } - // if you receive an old tx propagation, read from connection again - if len(txHashes) == 1 && prevTx != nil { - if txHashes[0] == prevTx.Hash() { - continue - } - } - for index, gotHash := range txHashes { - if gotHash == tx.Hash() { - if msg.Sizes[index] != uint32(tx.Size()) { - return fmt.Errorf("invalid tx size: got %v want %v", msg.Sizes[index], tx.Size()) - } - if msg.Types[index] != tx.Type() { - return fmt.Errorf("invalid tx type: got %v want %v", msg.Types[index], tx.Type()) - } - // Ok - return nil - } - } - return fmt.Errorf("missing transaction announcement: got %v missing %v", txHashes, tx.Hash()) - - default: - return fmt.Errorf("unexpected message in sendSuccessfulTx: %s", pretty.Sdump(msg)) - } - } -} - -func (s *Suite) sendMaliciousTxs(t *utesting.T) error { - badTxs := []*types.Transaction{ - getOldTxFromChain(s), - invalidNonceTx(s), - hugeAmount(s), - hugeGasPrice(s), - hugeData(s), - } - - // setup receiving connection before sending malicious txs + // Open receiving conn. recvConn, err := s.dial() - if err != nil { - return fmt.Errorf("dial failed: %v", err) - } - defer recvConn.Close() - if err = recvConn.peer(s.chain, nil); err != nil { - return fmt.Errorf("peering failed: %v", err) - } - - for i, tx := range badTxs { - t.Logf("Testing malicious tx propagation: %v\n", i) - if err = sendMaliciousTx(s, tx); err != nil { - return fmt.Errorf("malicious tx test failed:\ntx: %v\nerror: %v", tx, err) - } - } - // check to make sure bad txs aren't propagated - return checkMaliciousTxPropagation(s, badTxs, recvConn) -} - -func sendMaliciousTx(s *Suite, tx *types.Transaction) error { - conn, err := s.dial() - if err != nil { - return fmt.Errorf("dial failed: %v", err) - } - defer conn.Close() - if err = conn.peer(s.chain, nil); err != nil { - return fmt.Errorf("peering failed: %v", err) - } - - // write malicious tx - if err = conn.Write(&Transactions{tx}); err != nil { - return fmt.Errorf("failed to write to connection: %v", err) - } - return nil -} - -var nonce = uint64(99) - -// sendMultipleSuccessfulTxs sends the given transactions to the node and -// expects the node to accept and propagate them. -func sendMultipleSuccessfulTxs(t *utesting.T, s *Suite, txs []*types.Transaction) error { - txMsg := Transactions(txs) - t.Logf("sending %d txs\n", len(txs)) - - sendConn, recvConn, err := s.createSendAndRecvConns() if err != nil { return err } - defer sendConn.Close() defer recvConn.Close() - if err = sendConn.peer(s.chain, nil); err != nil { - return fmt.Errorf("peering failed: %v", err) - } if err = recvConn.peer(s.chain, nil); err != nil { return fmt.Errorf("peering failed: %v", err) } - // Send the transactions - if err = sendConn.Write(&txMsg); err != nil { + if err = sendConn.Write(ethProto, eth.TransactionsMsg, eth.TransactionsPacket(txs)); err != nil { return fmt.Errorf("failed to write message to connection: %v", err) } - // update nonce - nonce = txs[len(txs)-1].Nonce() - - // Wait for the transaction announcement(s) and make sure all sent txs are being propagated. - // all txs should be announced within a couple announcements. - recvHashes := make([]common.Hash, 0) + var ( + got = make(map[common.Hash]bool) + end = time.Now().Add(timeout) + ) - for i := 0; i < 20; i++ { - switch msg := recvConn.readAndServe(s.chain, timeout).(type) { - case *Transactions: + // Wait for the transaction announcements, make sure all txs ar propagated. + for time.Now().Before(end) { + msg, err := recvConn.ReadEth() + if err != nil { + return fmt.Errorf("failed to read from connection: %w", err) + } + switch msg := msg.(type) { + case *eth.TransactionsPacket: for _, tx := range *msg { - recvHashes = append(recvHashes, tx.Hash()) + got[tx.Hash()] = true } - case *NewPooledTransactionHashes66: - recvHashes = append(recvHashes, *msg...) - case *NewPooledTransactionHashes: - recvHashes = append(recvHashes, msg.Hashes...) - default: - if !strings.Contains(pretty.Sdump(msg), "i/o timeout") { - return fmt.Errorf("unexpected message while waiting to receive txs: %s", pretty.Sdump(msg)) + case *eth.NewPooledTransactionHashesPacket68: + for _, hash := range msg.Hashes { + got[hash] = true } + default: + return fmt.Errorf("unexpected eth wire msg: %s", pretty.Sdump(msg)) } - // break once all 2000 txs have been received - if len(recvHashes) == 2000 { - break - } - if len(recvHashes) > 0 { - _, missingTxs := compareReceivedTxs(recvHashes, txs) - if len(missingTxs) > 0 { - continue - } else { - t.Logf("successfully received all %d txs", len(txs)) - return nil + + // Check if all txs received. + allReceived := func() bool { + for _, tx := range txs { + if !got[tx.Hash()] { + return false + } } + return true } - } - _, missingTxs := compareReceivedTxs(recvHashes, txs) - if len(missingTxs) > 0 { - for _, missing := range missingTxs { - t.Logf("missing tx: %v", missing.Hash()) + if allReceived() { + return nil } - return fmt.Errorf("missing %d txs", len(missingTxs)) } - return nil -} -// checkMaliciousTxPropagation checks whether the given malicious transactions were -// propagated by the node. -func checkMaliciousTxPropagation(s *Suite, txs []*types.Transaction, conn *Conn) error { - switch msg := conn.readAndServe(s.chain, time.Second*8).(type) { - case *Transactions: - // check to see if any of the failing txs were in the announcement - recvTxs := make([]common.Hash, len(*msg)) - for i, recvTx := range *msg { - recvTxs[i] = recvTx.Hash() - } - badTxs, _ := compareReceivedTxs(recvTxs, txs) - if len(badTxs) > 0 { - return fmt.Errorf("received %d bad txs: \n%v", len(badTxs), badTxs) - } - case *NewPooledTransactionHashes66: - badTxs, _ := compareReceivedTxs(*msg, txs) - if len(badTxs) > 0 { - return fmt.Errorf("received %d bad txs: \n%v", len(badTxs), badTxs) - } - case *NewPooledTransactionHashes: - badTxs, _ := compareReceivedTxs(msg.Hashes, txs) - if len(badTxs) > 0 { - return fmt.Errorf("received %d bad txs: \n%v", len(badTxs), badTxs) - } - case *Error: - // Transaction should not be announced -> wait for timeout - return nil - default: - return fmt.Errorf("unexpected message in sendFailingTx: %s", pretty.Sdump(msg)) - } - return nil + return fmt.Errorf("timed out waiting for txs") } -// compareReceivedTxs compares the received set of txs against the given set of txs, -// returning both the set received txs that were present within the given txs, and -// the set of txs that were missing from the set of received txs -func compareReceivedTxs(recvTxs []common.Hash, txs []*types.Transaction) (present []*types.Transaction, missing []*types.Transaction) { - // create a map of the hashes received from node - recvHashes := make(map[common.Hash]common.Hash) - for _, hash := range recvTxs { - recvHashes[hash] = hash +func (s *Suite) sendInvalidTxs(txs []*types.Transaction) error { + // Open sending conn. + sendConn, err := s.dial() + if err != nil { + return err } - - // collect present txs and missing txs separately - present = make([]*types.Transaction, 0) - missing = make([]*types.Transaction, 0) - for _, tx := range txs { - if _, exists := recvHashes[tx.Hash()]; exists { - present = append(present, tx) - } else { - missing = append(missing, tx) - } + defer sendConn.Close() + if err = sendConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) } - return present, missing -} + sendConn.SetDeadline(time.Now().Add(timeout)) -func unknownTx(s *Suite) *types.Transaction { - tx := getNextTxFromChain(s) - if tx == nil { - return nil + // Open receiving conn. + recvConn, err := s.dial() + if err != nil { + return err } - var to common.Address - if tx.To() != nil { - to = *tx.To() + defer recvConn.Close() + if err = recvConn.peer(s.chain, nil); err != nil { + return fmt.Errorf("peering failed: %v", err) } - txNew := types.NewTransaction(tx.Nonce()+1, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data()) - return signWithFaucet(s.chain.chainConfig, txNew) -} + recvConn.SetDeadline(time.Now().Add(timeout)) -func getNextTxFromChain(s *Suite) *types.Transaction { - // Get a new transaction - for _, blocks := range s.fullChain.blocks[s.chain.Len():] { - txs := blocks.Transactions() - if txs.Len() != 0 { - return txs[0] - } + if err = sendConn.Write(ethProto, eth.TransactionsMsg, txs); err != nil { + return fmt.Errorf("failed to write message to connection: %w", err) } - return nil -} -func generateTxs(s *Suite, numTxs int) (map[common.Hash]common.Hash, []*types.Transaction, error) { - txHashMap := make(map[common.Hash]common.Hash, numTxs) - txs := make([]*types.Transaction, numTxs) - - nextTx := getNextTxFromChain(s) - if nextTx == nil { - return nil, nil, errors.New("failed to get the next transaction") - } - gas := nextTx.Gas() - - nonce = nonce + 1 - // generate txs - for i := 0; i < numTxs; i++ { - tx := generateTx(s.chain.chainConfig, nonce, gas) - if tx == nil { - return nil, nil, errors.New("failed to get the next transaction") - } - txHashMap[tx.Hash()] = tx.Hash() - txs[i] = tx - nonce = nonce + 1 + // Make map of invalid txs. + invalids := make(map[common.Hash]struct{}) + for _, tx := range txs { + invalids[tx.Hash()] = struct{}{} } - return txHashMap, txs, nil -} -func generateTx(chainConfig *params.ChainConfig, nonce uint64, gas uint64) *types.Transaction { - var to common.Address - tx := types.NewTransaction(nonce, to, big.NewInt(1), gas, big.NewInt(1), []byte{}) - return signWithFaucet(chainConfig, tx) -} - -func getOldTxFromChain(s *Suite) *types.Transaction { - for _, blocks := range s.fullChain.blocks[:s.chain.Len()-1] { - txs := blocks.Transactions() - if txs.Len() != 0 { - return txs[0] + // Get responses. + recvConn.SetReadDeadline(time.Now().Add(timeout)) + for { + msg, err := recvConn.ReadEth() + if errors.Is(err, os.ErrDeadlineExceeded) { + // Successful if no invalid txs are propagated before timeout. + return nil + } else if err != nil { + return fmt.Errorf("failed to read from connection: %w", err) + } + + switch msg := msg.(type) { + case *eth.TransactionsPacket: + for _, tx := range txs { + if _, ok := invalids[tx.Hash()]; ok { + return fmt.Errorf("received bad tx: %s", tx.Hash()) + } + } + case *eth.NewPooledTransactionHashesPacket68: + for _, hash := range msg.Hashes { + if _, ok := invalids[hash]; ok { + return fmt.Errorf("received bad tx: %s", hash) + } + } + default: + return fmt.Errorf("unexpected eth message: %v", pretty.Sdump(msg)) } } - return nil -} - -func invalidNonceTx(s *Suite) *types.Transaction { - tx := getNextTxFromChain(s) - if tx == nil { - return nil - } - var to common.Address - if tx.To() != nil { - to = *tx.To() - } - txNew := types.NewTransaction(tx.Nonce()-2, to, tx.Value(), tx.Gas(), tx.GasPrice(), tx.Data()) - return signWithFaucet(s.chain.chainConfig, txNew) -} - -func hugeAmount(s *Suite) *types.Transaction { - tx := getNextTxFromChain(s) - if tx == nil { - return nil - } - amount := largeNumber(2) - var to common.Address - if tx.To() != nil { - to = *tx.To() - } - txNew := types.NewTransaction(tx.Nonce(), to, amount, tx.Gas(), tx.GasPrice(), tx.Data()) - return signWithFaucet(s.chain.chainConfig, txNew) -} - -func hugeGasPrice(s *Suite) *types.Transaction { - tx := getNextTxFromChain(s) - if tx == nil { - return nil - } - gasPrice := largeNumber(2) - var to common.Address - if tx.To() != nil { - to = *tx.To() - } - txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), gasPrice, tx.Data()) - return signWithFaucet(s.chain.chainConfig, txNew) -} - -func hugeData(s *Suite) *types.Transaction { - tx := getNextTxFromChain(s) - if tx == nil { - return nil - } - var to common.Address - if tx.To() != nil { - to = *tx.To() - } - txNew := types.NewTransaction(tx.Nonce(), to, tx.Value(), tx.Gas(), tx.GasPrice(), largeBuffer(2)) - return signWithFaucet(s.chain.chainConfig, txNew) -} - -func signWithFaucet(chainConfig *params.ChainConfig, tx *types.Transaction) *types.Transaction { - signer := types.LatestSigner(chainConfig) - signedTx, err := types.SignTx(tx, signer, faucetKey) - if err != nil { - return nil - } - return signedTx } diff --git a/cmd/devp2p/internal/ethtest/types.go b/cmd/devp2p/internal/ethtest/types.go deleted file mode 100644 index afa9a9c8c..000000000 --- a/cmd/devp2p/internal/ethtest/types.go +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2020 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package ethtest - -import ( - "crypto/ecdsa" - "errors" - "fmt" - "time" - - "github.com/ethereum/go-ethereum/eth/protocols/eth" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/rlpx" - "github.com/ethereum/go-ethereum/rlp" -) - -type Message interface { - Code() int - ReqID() uint64 -} - -type Error struct { - err error -} - -func (e *Error) Unwrap() error { return e.err } -func (e *Error) Error() string { return e.err.Error() } -func (e *Error) String() string { return e.Error() } - -func (e *Error) Code() int { return -1 } -func (e *Error) ReqID() uint64 { return 0 } - -func errorf(format string, args ...interface{}) *Error { - return &Error{fmt.Errorf(format, args...)} -} - -// Hello is the RLP structure of the protocol handshake. -type Hello struct { - Version uint64 - Name string - Caps []p2p.Cap - ListenPort uint64 - ID []byte // secp256k1 public key - - // Ignore additional fields (for forward compatibility). - Rest []rlp.RawValue `rlp:"tail"` -} - -func (msg Hello) Code() int { return 0x00 } -func (msg Hello) ReqID() uint64 { return 0 } - -// Disconnect is the RLP structure for a disconnect message. -type Disconnect struct { - Reason p2p.DiscReason -} - -func (msg Disconnect) Code() int { return 0x01 } -func (msg Disconnect) ReqID() uint64 { return 0 } - -type Ping struct{} - -func (msg Ping) Code() int { return 0x02 } -func (msg Ping) ReqID() uint64 { return 0 } - -type Pong struct{} - -func (msg Pong) Code() int { return 0x03 } -func (msg Pong) ReqID() uint64 { return 0 } - -// Status is the network packet for the status message for eth/64 and later. -type Status eth.StatusPacket - -func (msg Status) Code() int { return 16 } -func (msg Status) ReqID() uint64 { return 0 } - -// NewBlockHashes is the network packet for the block announcements. -type NewBlockHashes eth.NewBlockHashesPacket - -func (msg NewBlockHashes) Code() int { return 17 } -func (msg NewBlockHashes) ReqID() uint64 { return 0 } - -type Transactions eth.TransactionsPacket - -func (msg Transactions) Code() int { return 18 } -func (msg Transactions) ReqID() uint64 { return 18 } - -// GetBlockHeaders represents a block header query. -type GetBlockHeaders eth.GetBlockHeadersPacket66 - -func (msg GetBlockHeaders) Code() int { return 19 } -func (msg GetBlockHeaders) ReqID() uint64 { return msg.RequestId } - -type BlockHeaders eth.BlockHeadersPacket66 - -func (msg BlockHeaders) Code() int { return 20 } -func (msg BlockHeaders) ReqID() uint64 { return msg.RequestId } - -// GetBlockBodies represents a GetBlockBodies request -type GetBlockBodies eth.GetBlockBodiesPacket66 - -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 - -func (msg BlockBodies) Code() int { return 22 } -func (msg BlockBodies) ReqID() uint64 { return msg.RequestId } - -// NewBlock is the network packet for the block propagation message. -type NewBlock eth.NewBlockPacket - -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 - -func (msg NewPooledTransactionHashes66) Code() int { return 24 } -func (msg NewPooledTransactionHashes66) ReqID() uint64 { return 0 } - -// NewPooledTransactionHashes is the network packet for the tx hash propagation message. -type NewPooledTransactionHashes eth.NewPooledTransactionHashesPacket68 - -func (msg NewPooledTransactionHashes) Code() int { return 24 } -func (msg NewPooledTransactionHashes) ReqID() uint64 { return 0 } - -type GetPooledTransactions eth.GetPooledTransactionsPacket66 - -func (msg GetPooledTransactions) Code() int { return 25 } -func (msg GetPooledTransactions) ReqID() uint64 { return msg.RequestId } - -type PooledTransactions eth.PooledTransactionsPacket66 - -func (msg PooledTransactions) Code() int { return 26 } -func (msg PooledTransactions) ReqID() uint64 { return msg.RequestId } - -// Conn represents an individual connection with a peer -type Conn struct { - *rlpx.Conn - ourKey *ecdsa.PrivateKey - negotiatedProtoVersion uint - negotiatedSnapProtoVersion uint - ourHighestProtoVersion uint - ourHighestSnapProtoVersion uint - caps []p2p.Cap -} - -// Read reads an eth66 packet from the connection. -func (c *Conn) Read() Message { - code, rawData, _, err := c.Conn.Read() - if err != nil { - return errorf("could not read from connection: %v", err) - } - - var msg Message - switch int(code) { - case (Hello{}).Code(): - msg = new(Hello) - case (Ping{}).Code(): - msg = new(Ping) - case (Pong{}).Code(): - msg = new(Pong) - case (Disconnect{}).Code(): - msg = new(Disconnect) - case (Status{}).Code(): - msg = new(Status) - case (GetBlockHeaders{}).Code(): - ethMsg := new(eth.GetBlockHeadersPacket66) - 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) - 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) - 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) - if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { - return errorf("could not rlp decode message: %v", err) - } - return (*BlockBodies)(ethMsg) - case (NewBlock{}).Code(): - msg = new(NewBlock) - case (NewBlockHashes{}).Code(): - msg = new(NewBlockHashes) - case (Transactions{}).Code(): - msg = new(Transactions) - case (NewPooledTransactionHashes66{}).Code(): - // Try decoding to eth68 - ethMsg := new(NewPooledTransactionHashes) - if err := rlp.DecodeBytes(rawData, ethMsg); err == nil { - return ethMsg - } - msg = new(NewPooledTransactionHashes66) - case (GetPooledTransactions{}.Code()): - ethMsg := new(eth.GetPooledTransactionsPacket66) - 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) - if err := rlp.DecodeBytes(rawData, ethMsg); err != nil { - return errorf("could not rlp decode message: %v", err) - } - return (*PooledTransactions)(ethMsg) - default: - msg = errorf("invalid message code: %d", code) - } - - if msg != nil { - if err := rlp.DecodeBytes(rawData, msg); err != nil { - return errorf("could not rlp decode message: %v", err) - } - return msg - } - return errorf("invalid message: %s", string(rawData)) -} - -// Write writes a eth packet to the connection. -func (c *Conn) Write(msg Message) error { - payload, err := rlp.EncodeToBytes(msg) - if err != nil { - return err - } - _, err = c.Conn.Write(uint64(msg.Code()), payload) - return err -} - -// ReadSnap reads a snap/1 response with the given id from the connection. -func (c *Conn) ReadSnap(id uint64) (Message, error) { - respId := id + 1 - start := time.Now() - for respId != id && time.Since(start) < timeout { - code, rawData, _, err := c.Conn.Read() - if err != nil { - return nil, fmt.Errorf("could not read from connection: %v", err) - } - var snpMsg interface{} - switch int(code) { - case (GetAccountRange{}).Code(): - snpMsg = new(GetAccountRange) - case (AccountRange{}).Code(): - snpMsg = new(AccountRange) - case (GetStorageRanges{}).Code(): - snpMsg = new(GetStorageRanges) - case (StorageRanges{}).Code(): - snpMsg = new(StorageRanges) - case (GetByteCodes{}).Code(): - snpMsg = new(GetByteCodes) - case (ByteCodes{}).Code(): - snpMsg = new(ByteCodes) - case (GetTrieNodes{}).Code(): - snpMsg = new(GetTrieNodes) - case (TrieNodes{}).Code(): - snpMsg = new(TrieNodes) - default: - //return nil, fmt.Errorf("invalid message code: %d", code) - continue - } - if err := rlp.DecodeBytes(rawData, snpMsg); err != nil { - return nil, fmt.Errorf("could not rlp decode message: %v", err) - } - return snpMsg.(Message), nil - } - return nil, errors.New("request timed out") -} diff --git a/cmd/devp2p/nodesetcmd.go b/cmd/devp2p/nodesetcmd.go index f1dd220d5..6fbc185ad 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,11 +229,13 @@ func ethFilter(args []string) (nodeFilter, error) { var filter forkid.Filter switch args[0] { case "mainnet": - filter = forkid.NewStaticFilter(params.MainnetChainConfig, params.MainnetGenesisHash) + 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/devp2p/rlpxcmd.go b/cmd/devp2p/rlpxcmd.go index dccecf3c3..aa7d06581 100644 --- a/cmd/devp2p/rlpxcmd.go +++ b/cmd/devp2p/rlpxcmd.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/cmd/devp2p/internal/ethtest" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/p2p/rlpx" "github.com/ethereum/go-ethereum/rlp" "github.com/urfave/cli/v2" @@ -46,22 +47,30 @@ var ( } rlpxEthTestCommand = &cli.Command{ Name: "eth-test", - Usage: "Runs tests against a node", - ArgsUsage: " ", + Usage: "Runs eth protocol tests against a node", + ArgsUsage: "", Action: rlpxEthTest, Flags: []cli.Flag{ testPatternFlag, testTAPFlag, + testChainDirFlag, + testNodeFlag, + testNodeJWTFlag, + testNodeEngineFlag, }, } rlpxSnapTestCommand = &cli.Command{ Name: "snap-test", - Usage: "Runs tests against a node", - ArgsUsage: " ", + Usage: "Runs snap protocol tests against a node", + ArgsUsage: "", Action: rlpxSnapTest, Flags: []cli.Flag{ testPatternFlag, testTAPFlag, + testChainDirFlag, + testNodeFlag, + testNodeJWTFlag, + testNodeEngineFlag, }, } ) @@ -103,10 +112,8 @@ func rlpxPing(ctx *cli.Context) error { // rlpxEthTest runs the eth protocol test suite. func rlpxEthTest(ctx *cli.Context) error { - if ctx.NArg() < 3 { - exit("missing path to chain.rlp as command-line argument") - } - suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args().Get(1), ctx.Args().Get(2)) + p := cliTestParams(ctx) + suite, err := ethtest.NewSuite(p.node, p.chainDir, p.engineAPI, p.jwt) if err != nil { exit(err) } @@ -115,12 +122,44 @@ func rlpxEthTest(ctx *cli.Context) error { // rlpxSnapTest runs the snap protocol test suite. func rlpxSnapTest(ctx *cli.Context) error { - if ctx.NArg() < 3 { - exit("missing path to chain.rlp as command-line argument") - } - suite, err := ethtest.NewSuite(getNodeArg(ctx), ctx.Args().Get(1), ctx.Args().Get(2)) + p := cliTestParams(ctx) + suite, err := ethtest.NewSuite(p.node, p.chainDir, p.engineAPI, p.jwt) if err != nil { exit(err) } return runTests(ctx, suite.SnapTests()) } + +type testParams struct { + node *enode.Node + engineAPI string + jwt string + chainDir string +} + +func cliTestParams(ctx *cli.Context) *testParams { + nodeStr := ctx.String(testNodeFlag.Name) + if nodeStr == "" { + exit(fmt.Errorf("missing -%s", testNodeFlag.Name)) + } + node, err := parseNode(nodeStr) + if err != nil { + exit(err) + } + p := testParams{ + node: node, + engineAPI: ctx.String(testNodeEngineFlag.Name), + jwt: ctx.String(testNodeJWTFlag.Name), + chainDir: ctx.String(testChainDirFlag.Name), + } + if p.engineAPI == "" { + exit(fmt.Errorf("missing -%s", testNodeEngineFlag.Name)) + } + if p.jwt == "" { + exit(fmt.Errorf("missing -%s", testNodeJWTFlag.Name)) + } + if p.chainDir == "" { + exit(fmt.Errorf("missing -%s", testChainDirFlag.Name)) + } + return &p +} diff --git a/cmd/devp2p/runtest.go b/cmd/devp2p/runtest.go index f72aa9111..7e3723c64 100644 --- a/cmd/devp2p/runtest.go +++ b/cmd/devp2p/runtest.go @@ -20,6 +20,7 @@ import ( "os" "github.com/ethereum/go-ethereum/cmd/devp2p/internal/v4test" + "github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/utesting" "github.com/ethereum/go-ethereum/log" "github.com/urfave/cli/v2" @@ -27,23 +28,51 @@ import ( var ( testPatternFlag = &cli.StringFlag{ - Name: "run", - Usage: "Pattern of test suite(s) to run", + Name: "run", + Usage: "Pattern of test suite(s) to run", + Category: flags.TestingCategory, } testTAPFlag = &cli.BoolFlag{ - Name: "tap", - Usage: "Output TAP", + Name: "tap", + Usage: "Output test results in TAP format", + Category: flags.TestingCategory, } + + // for eth/snap tests + testChainDirFlag = &cli.StringFlag{ + Name: "chain", + Usage: "Test chain directory (required)", + Category: flags.TestingCategory, + } + testNodeFlag = &cli.StringFlag{ + Name: "node", + Usage: "Peer-to-Peer endpoint (ENR) of the test node (required)", + Category: flags.TestingCategory, + } + testNodeJWTFlag = &cli.StringFlag{ + Name: "jwtsecret", + Usage: "JWT secret for the engine API of the test node (required)", + Category: flags.TestingCategory, + Value: "0x7365637265747365637265747365637265747365637265747365637265747365", + } + testNodeEngineFlag = &cli.StringFlag{ + Name: "engineapi", + Usage: "Engine API endpoint of the test node (required)", + Category: flags.TestingCategory, + } + // These two are specific to the discovery tests. testListen1Flag = &cli.StringFlag{ - Name: "listen1", - Usage: "IP address of the first tester", - Value: v4test.Listen1, + Name: "listen1", + Usage: "IP address of the first tester", + Value: v4test.Listen1, + Category: flags.TestingCategory, } testListen2Flag = &cli.StringFlag{ - Name: "listen2", - Usage: "IP address of the second tester", - Value: v4test.Listen2, + Name: "listen2", + Usage: "IP address of the second tester", + Value: v4test.Listen2, + Category: flags.TestingCategory, } ) @@ -54,7 +83,7 @@ func runTests(ctx *cli.Context, tests []utesting.Test) error { } // Disable logging unless explicitly enabled. if !ctx.IsSet("verbosity") && !ctx.IsSet("vmodule") { - log.Root().SetHandler(log.DiscardHandler()) + log.SetDefault(log.NewLogger(log.DiscardHandler())) } // Run the tests. var run = utesting.RunTests diff --git a/cmd/ethkey/message_test.go b/cmd/ethkey/message_test.go index 544a494cf..389bb8c8e 100644 --- a/cmd/ethkey/message_test.go +++ b/cmd/ethkey/message_test.go @@ -22,6 +22,7 @@ import ( ) func TestMessageSignVerify(t *testing.T) { + t.Parallel() tmpdir := t.TempDir() keyfile := filepath.Join(tmpdir, "the-keyfile") diff --git a/cmd/ethkey/run_test.go b/cmd/ethkey/run_test.go index 6006f6b5b..73506e5da 100644 --- a/cmd/ethkey/run_test.go +++ b/cmd/ethkey/run_test.go @@ -21,8 +21,8 @@ import ( "os" "testing" - "github.com/docker/docker/pkg/reexec" "github.com/ethereum/go-ethereum/internal/cmdtest" + "github.com/ethereum/go-ethereum/internal/reexec" ) type testEthkey struct { diff --git a/cmd/evm/README.md b/cmd/evm/README.md index e6c6fe06a..25647c18a 100644 --- a/cmd/evm/README.md +++ b/cmd/evm/README.md @@ -88,7 +88,7 @@ type Env struct { CurrentTimestamp uint64 `json:"currentTimestamp"` Withdrawals []*Withdrawal `json:"withdrawals"` // optional - CurrentDifficulty *big.Int `json:"currentDifficuly"` + CurrentDifficulty *big.Int `json:"currentDifficulty"` CurrentRandom *big.Int `json:"currentRandom"` CurrentBaseFee *big.Int `json:"currentBaseFee"` ParentDifficulty *big.Int `json:"parentDifficulty"` @@ -214,7 +214,7 @@ exitcode:3 OK The chain configuration to be used for a transition is specified via the `--state.fork` CLI flag. A list of possible values and configurations can be -found in [`tests/init.go`](tests/init.go). +found in [`tests/init.go`](../../tests/init.go). #### Examples ##### Basic usage diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go index ffd3165f5..c5d836e0e 100644 --- a/cmd/evm/blockrunner.go +++ b/cmd/evm/blockrunner.go @@ -21,29 +21,36 @@ import ( "errors" "fmt" "os" + "regexp" + "sort" + "github.com/ethereum/go-ethereum/core" + "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/log" "github.com/ethereum/go-ethereum/tests" "github.com/urfave/cli/v2" ) +var RunFlag = &cli.StringFlag{ + Name: "run", + Value: ".*", + Usage: "Run only those tests matching the regular expression.", +} + var blockTestCommand = &cli.Command{ Action: blockTestCmd, Name: "blocktest", - Usage: "executes the given blockchain tests", + Usage: "Executes the given blockchain tests", ArgsUsage: "", + Flags: []cli.Flag{RunFlag}, } 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) { @@ -63,9 +70,30 @@ func blockTestCmd(ctx *cli.Context) error { if err = json.Unmarshal(src, &tests); err != nil { return err } - for i, test := range tests { - if err := test.Run(false, tracer); err != nil { - return fmt.Errorf("test %v: %w", i, err) + re, err := regexp.Compile(ctx.String(RunFlag.Name)) + if err != nil { + return fmt.Errorf("invalid regex -%s: %v", RunFlag.Name, err) + } + + // Run them in order + var keys []string + for key := range tests { + keys = append(keys, key) + } + sort.Strings(keys) + for _, name := range keys { + if !re.MatchString(name) { + continue + } + test := tests[name] + if err := test.Run(false, rawdb.HashScheme, tracer, func(res error, chain *core.BlockChain) { + if ctx.Bool(DumpFlag.Name) { + if state, _ := chain.State(); state != nil { + fmt.Println(string(state.Dump(nil))) + } + } + }); err != nil { + return fmt.Errorf("test %v: %w", name, err) } } return nil diff --git a/cmd/evm/compiler.go b/cmd/evm/compiler.go index 699d434bb..c071834b5 100644 --- a/cmd/evm/compiler.go +++ b/cmd/evm/compiler.go @@ -29,7 +29,7 @@ import ( var compileCommand = &cli.Command{ Action: compileCmd, Name: "compile", - Usage: "compiles easm source to evm binary", + Usage: "Compiles easm source to evm binary", ArgsUsage: "", } diff --git a/cmd/evm/disasm.go b/cmd/evm/disasm.go index a6a16fd13..b1f35cbaf 100644 --- a/cmd/evm/disasm.go +++ b/cmd/evm/disasm.go @@ -29,7 +29,7 @@ import ( var disasmCommand = &cli.Command{ Action: disasmCmd, Name: "disasm", - Usage: "disassembles evm binary", + Usage: "Disassembles evm binary", ArgsUsage: "", } diff --git a/cmd/evm/internal/t8ntool/block.go b/cmd/evm/internal/t8ntool/block.go index 09dca8984..a2dc47343 100644 --- a/cmd/evm/internal/t8ntool/block.go +++ b/cmd/evm/internal/t8ntool/block.go @@ -30,40 +30,44 @@ import ( "github.com/ethereum/go-ethereum/consensus/clique" "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/urfave/cli/v2" ) //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 +117,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 +157,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) @@ -207,11 +214,6 @@ func (i *bbInput) sealClique(block *types.Block) (*types.Block, error) { // BuildBlock constructs a block from the given inputs. func BuildBlock(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) - baseDir, err := createBasedir(ctx) if err != nil { return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err)) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index c408623fe..b654cb219 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -59,7 +59,7 @@ type ExecutionResult struct { BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"` CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"` - CurrentBlobGasUsed *math.HexOrDecimal64 `json:"currentBlobGasUsed,omitempty"` + CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"` } type ommer struct { @@ -69,25 +69,26 @@ 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"` - ExcessBlobGas *uint64 `json:"excessBlobGas,omitempty"` - ParentExcessBlobGas *uint64 `json:"parentExcessBlobGas,omitempty"` - ParentBlobGasUsed *uint64 `json:"parentBlobGasUsed,omitempty"` + 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 { @@ -115,8 +116,8 @@ type rejectedTx struct { // Apply applies a set of transactions to a pre-state func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, - txs types.Transactions, miningReward int64, - getTracerFn func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error)) (*state.StateDB, *ExecutionResult, error) { + txIt txIterator, miningReward int64, + getTracerFn func(txIndex int, txHash common.Hash) (vm.EVMLogger, error)) (*state.StateDB, *ExecutionResult, []byte, error) { // Capture errors for BLOCKHASH operation, if we haven't been supplied the // required blockhashes var hashError error @@ -139,6 +140,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, rejectedTxs []*rejectedTx includedTxs types.Transactions gasUsed = uint64(0) + blobGasUsed = uint64(0) receipts = make(types.Receipts, 0) txIndex = 0 ) @@ -162,17 +164,19 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, rnd := common.BigToHash(pre.Env.Random) vmContext.Random = &rnd } - // If excessBlobGas is defined, add it to the vmContext. + // Calculate the BlobBaseFee + var excessBlobGas uint64 if pre.Env.ExcessBlobGas != nil { - vmContext.ExcessBlobGas = pre.Env.ExcessBlobGas + 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.ExcessBlobGas = &excessBlobGas + 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 @@ -182,9 +186,19 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, chainConfig.DAOForkBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 { misc.ApplyDAOHardFork(statedb) } - var blobGasUsed uint64 - for i, tx := range txs { - if tx.Type() == types.BlobTxType && vmContext.ExcessBlobGas == nil { + if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil { + evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig) + core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb) + } + + for i := 0; txIt.Next(); i++ { + tx, err := txIt.Tx() + if err != nil { + log.Warn("rejected tx", "index", i, "error", err) + rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) + continue + } + 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}) @@ -196,9 +210,19 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) continue } + txBlobGas := uint64(0) + if tx.Type() == types.BlobTxType { + txBlobGas = uint64(params.BlobTxBlobGasPerBlob * len(tx.BlobHashes())) + if used, max := blobGasUsed+txBlobGas, uint64(params.MaxBlobGasPerBlock); used > max { + err := fmt.Errorf("blob gas (%d) would exceed maximum allowance %d", used, max) + log.Warn("rejected tx", "index", i, "err", err) + rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) + continue + } + } tracer, err := getTracerFn(txIndex, tx.Hash()) if err != nil { - return nil, nil, err + return nil, nil, nil, err } vmConfig.Tracer = tracer statedb.SetTxContext(tx.Hash(), txIndex) @@ -219,13 +243,11 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, gaspool.SetGas(prevGas) continue } - if tx.Type() == types.BlobTxType { - blobGasUsed += params.BlobTxBlobGasPerBlob - } includedTxs = append(includedTxs, tx) if hashError != nil { - return nil, nil, NewError(ErrorMissingBlockhash, hashError) + return nil, nil, nil, NewError(ErrorMissingBlockhash, hashError) } + blobGasUsed += txBlobGas gasUsed += msgResult.UsedGas // Receipt: @@ -299,7 +321,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, // Commit block root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber)) if err != nil { - return nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err)) + return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err)) } execRs := &ExecutionResult{ StateRoot: root, @@ -317,17 +339,18 @@ 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.ExcessBlobGas != nil { - execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(vmContext.ExcessBlobGas) + 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 nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not reopen state: %v", err)) } - return statedb, execRs, nil + body, _ := rlp.EncodeToBytes(includedTxs) + return statedb, execRs, body, nil } func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB { diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go index de19dbc85..c2eca8cc2 100644 --- a/cmd/evm/internal/t8ntool/flags.go +++ b/cmd/evm/internal/t8ntool/flags.go @@ -28,12 +28,15 @@ import ( var ( TraceFlag = &cli.BoolFlag{ Name: "trace", - Usage: "Output full trace logs to files .jsonl", + Usage: "Configures the use of the JSON opcode tracer. This tracer emits traces to files as trace--.jsonl", } - TraceDisableMemoryFlag = &cli.BoolFlag{ - Name: "trace.nomemory", - Value: true, - Usage: "Disable full memory dump in traces (deprecated)", + TraceTracerFlag = &cli.StringFlag{ + Name: "trace.tracer", + Usage: "Configures the use of a custom tracer, e.g native or js tracers. Examples are callTracer and 4byteTracer. These tracers emit results into files as trace--.json", + } + TraceTracerConfigFlag = &cli.StringFlag{ + Name: "trace.jsonconfig", + Usage: "The configurations for the custom tracer specified by --trace.tracer. If provided, must be in JSON format", } TraceEnableMemoryFlag = &cli.BoolFlag{ Name: "trace.memory", @@ -43,11 +46,6 @@ var ( Name: "trace.nostack", Usage: "Disable stack output in traces", } - TraceDisableReturnDataFlag = &cli.BoolFlag{ - Name: "trace.noreturndata", - Value: true, - Usage: "Disable return data output in traces (deprecated)", - } TraceEnableReturnDataFlag = &cli.BoolFlag{ Name: "trace.returndata", Usage: "Enable return data output in traces", diff --git a/cmd/evm/internal/t8ntool/gen_header.go b/cmd/evm/internal/t8ntool/gen_header.go index 76228394d..a8c866897 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 f50fee5e3..d47db4a87 100644 --- a/cmd/evm/internal/t8ntool/gen_stenv.go +++ b/cmd/evm/internal/t8ntool/gen_stenv.go @@ -17,25 +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"` - ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas,omitempty"` - ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"` - ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"` + 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) @@ -57,31 +58,33 @@ func (s stEnv) MarshalJSON() ([]byte, error) { 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"` - ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas,omitempty"` - ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"` - ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"` + 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 { @@ -148,5 +151,8 @@ func (s *stEnv) UnmarshalJSON(input []byte) error { 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/tracewriter.go b/cmd/evm/internal/t8ntool/tracewriter.go new file mode 100644 index 000000000..e4efad112 --- /dev/null +++ b/cmd/evm/internal/t8ntool/tracewriter.go @@ -0,0 +1,81 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package t8ntool + +import ( + "encoding/json" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/eth/tracers" + "github.com/ethereum/go-ethereum/log" +) + +// traceWriter is an vm.EVMLogger which also holds an inner logger/tracer. +// When the TxEnd event happens, the inner tracer result is written to the file, and +// the file is closed. +type traceWriter struct { + inner vm.EVMLogger + f io.WriteCloser +} + +// Compile-time interface check +var _ = vm.EVMLogger((*traceWriter)(nil)) + +func (t *traceWriter) CaptureTxEnd(restGas uint64) { + t.inner.CaptureTxEnd(restGas) + defer t.f.Close() + + if tracer, ok := t.inner.(tracers.Tracer); ok { + result, err := tracer.GetResult() + if err != nil { + log.Warn("Error in tracer", "err", err) + return + } + err = json.NewEncoder(t.f).Encode(result) + if err != nil { + log.Warn("Error writing tracer output", "err", err) + return + } + } +} + +func (t *traceWriter) CaptureTxStart(gasLimit uint64) { t.inner.CaptureTxStart(gasLimit) } +func (t *traceWriter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + t.inner.CaptureStart(env, from, to, create, input, gas, value) +} + +func (t *traceWriter) CaptureEnd(output []byte, gasUsed uint64, err error) { + t.inner.CaptureEnd(output, gasUsed, err) +} + +func (t *traceWriter) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + t.inner.CaptureEnter(typ, from, to, input, gas, value) +} + +func (t *traceWriter) CaptureExit(output []byte, gasUsed uint64, err error) { + t.inner.CaptureExit(output, gasUsed, err) +} + +func (t *traceWriter) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { + t.inner.CaptureState(pc, op, gas, cost, scope, rData, depth, err) +} +func (t *traceWriter) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { + t.inner.CaptureFault(pc, op, gas, cost, scope, depth, err) +} diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index 03a2e2eb9..8533b7863 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "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" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/tests" @@ -65,11 +64,6 @@ func (r *result) MarshalJSON() ([]byte, error) { } func Transaction(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) - var ( err error ) diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index cbb39294d..c8ba69f40 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -17,14 +17,12 @@ package t8ntool import ( - "crypto/ecdsa" "encoding/json" "errors" "fmt" "math/big" "os" "path" - "strings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -33,11 +31,10 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/tests" "github.com/urfave/cli/v2" ) @@ -84,62 +81,43 @@ type input struct { } func Transition(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) - - var ( - err error - tracer vm.EVMLogger - ) - var getTracer func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) + var getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { return nil, nil } baseDir, err := createBasedir(ctx) if err != nil { return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err)) } - if ctx.Bool(TraceFlag.Name) { - if ctx.IsSet(TraceDisableMemoryFlag.Name) && ctx.IsSet(TraceEnableMemoryFlag.Name) { - return NewError(ErrorConfig, fmt.Errorf("can't use both flags --%s and --%s", TraceDisableMemoryFlag.Name, TraceEnableMemoryFlag.Name)) - } - if ctx.IsSet(TraceDisableReturnDataFlag.Name) && ctx.IsSet(TraceEnableReturnDataFlag.Name) { - return NewError(ErrorConfig, fmt.Errorf("can't use both flags --%s and --%s", TraceDisableReturnDataFlag.Name, TraceEnableReturnDataFlag.Name)) - } - if ctx.IsSet(TraceDisableMemoryFlag.Name) { - log.Warn(fmt.Sprintf("--%s has been deprecated in favour of --%s", TraceDisableMemoryFlag.Name, TraceEnableMemoryFlag.Name)) - } - if ctx.IsSet(TraceDisableReturnDataFlag.Name) { - log.Warn(fmt.Sprintf("--%s has been deprecated in favour of --%s", TraceDisableReturnDataFlag.Name, TraceEnableReturnDataFlag.Name)) - } + + if ctx.Bool(TraceFlag.Name) { // JSON opcode tracing // Configure the EVM logger logConfig := &logger.Config{ DisableStack: ctx.Bool(TraceDisableStackFlag.Name), - EnableMemory: !ctx.Bool(TraceDisableMemoryFlag.Name) || ctx.Bool(TraceEnableMemoryFlag.Name), - EnableReturnData: !ctx.Bool(TraceDisableReturnDataFlag.Name) || ctx.Bool(TraceEnableReturnDataFlag.Name), + EnableMemory: ctx.Bool(TraceEnableMemoryFlag.Name), + EnableReturnData: ctx.Bool(TraceEnableReturnDataFlag.Name), Debug: true, } - var prevFile *os.File - // This one closes the last file - defer func() { - if prevFile != nil { - prevFile.Close() - } - }() getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { - if prevFile != nil { - prevFile.Close() - } traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String()))) if err != nil { return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) } - prevFile = traceFile - return logger.NewJSONLogger(logConfig, traceFile), nil + return &traceWriter{logger.NewJSONLogger(logConfig, traceFile), traceFile}, nil } - } else { - getTracer = func(txIndex int, txHash common.Hash) (tracer vm.EVMLogger, err error) { - return nil, nil + } else if ctx.IsSet(TraceTracerFlag.Name) { + var config json.RawMessage + if ctx.IsSet(TraceTracerConfigFlag.Name) { + config = []byte(ctx.String(TraceTracerConfigFlag.Name)) + } + getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { + traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.json", txIndex, txHash.String()))) + if err != nil { + return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) + } + tracer, err := tracers.DefaultDirectory.New(ctx.String(TraceTracerFlag.Name), nil, config) + if err != nil { + return nil, NewError(ErrorConfig, fmt.Errorf("failed instantiating tracer: %w", err)) + } + return &traceWriter{tracer, traceFile}, nil } } // We need to load three things: alloc, env and transactions. May be either in @@ -147,7 +125,7 @@ func Transition(ctx *cli.Context) error { // Check if anything needs to be read from stdin var ( prestate Prestate - txs types.Transactions // txs to apply + txIt txIterator // txs to apply allocStr = ctx.String(InputAllocFlag.Name) envStr = ctx.String(InputEnvFlag.Name) @@ -178,9 +156,7 @@ func Transition(ctx *cli.Context) error { } prestate.Env = *inputData.Env - vmConfig := vm.Config{ - Tracer: tracer, - } + vmConfig := vm.Config{} // Construct the chainconfig var chainConfig *params.ChainConfig if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil { @@ -192,94 +168,71 @@ 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 txIt, err = loadTransactions(txStr, inputData, prestate.Env, chainConfig); err != nil { + return err + } + if err := applyLondonChecks(&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 err := applyShanghaiChecks(&prestate.Env, chainConfig); err != nil { + return err + } + if err := applyMergeChecks(&prestate.Env, chainConfig); err != nil { + return err + } + if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil { + return err + } + // Run the test and aggregate the result + s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, ctx.Int64(RewardFlag.Name), getTracer) + if err != nil { + return err + } + // Dump the excution result + collector := make(Alloc) + s.DumpToCollector(collector, nil) + return dispatchOutput(ctx, baseDir, result, collector, body) +} - if txs, err = signUnsignedTransactions(txsWithKeys, signer); err != nil { - return NewError(ErrorJson, fmt.Errorf("failed signing transactions: %v", err)) +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 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 = eip1559.CalcBaseFee(chainConfig, parent) - } else { - return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section")) - } + 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) { + return nil } - if chainConfig.IsShanghai(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) && prestate.Env.Withdrawals == 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 - 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")) + if !isMerged { + // pre-merge: If difficulty was not provided by caller, we need to calculate it. + if env.Difficulty != nil { + // already set + return nil } - 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")) @@ -289,102 +242,36 @@ func Transition(ctx *cli.Context) error { 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.Difficulty = calcDifficulty(chainConfig, env.Number, env.Timestamp, env.ParentTimestamp, env.ParentDifficulty, env.ParentUncleHash) + return nil } - // Run the test and aggregate the result - s, result, err := prestate.Apply(vmConfig, chainConfig, txs, ctx.Int64(RewardFlag.Name), getTracer) - if err != nil { - return err + // 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")) } - body, _ := rlp.EncodeToBytes(txs) - // Dump the excution result - collector := make(Alloc) - s.DumpToCollector(collector, nil) - return dispatchOutput(ctx, baseDir, result, collector, body) -} - -// txWithKey is a helper-struct, to allow us to use the types.Transaction along with -// a `secretKey`-field, for input -type txWithKey struct { - key *ecdsa.PrivateKey - tx *types.Transaction - protected bool + env.Difficulty = nil + return nil } -func (t *txWithKey) UnmarshalJSON(input []byte) error { - // Read the metadata, if present - type txMetadata struct { - Key *common.Hash `json:"secretKey"` - Protected *bool `json:"protected"` - } - var data txMetadata - if err := json.Unmarshal(input, &data); err != nil { - return err - } - if data.Key != nil { - k := data.Key.Hex()[2:] - if ecdsaKey, err := crypto.HexToECDSA(k); err != nil { - return err - } else { - t.key = ecdsaKey - } +func applyCancunChecks(env *stEnv, chainConfig *params.ChainConfig) error { + if !chainConfig.IsCancun(big.NewInt(int64(env.Number)), env.Timestamp) { + env.ParentBeaconBlockRoot = nil // un-set it if it has been set too early + return nil } - if data.Protected != nil { - t.protected = *data.Protected - } else { - t.protected = true + // 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")) } - // Now, read the transaction itself - var tx types.Transaction - if err := json.Unmarshal(input, &tx); err != nil { - return err - } - t.tx = &tx return nil } -// signUnsignedTransactions converts the input txs to canonical transactions. -// -// The transactions can have two forms, either -// 1. unsigned or -// 2. signed -// -// For (1), r, s, v, need so be zero, and the `secretKey` needs to be set. -// If so, we sign it here and now, with the given `secretKey` -// If the condition above is not met, then it's considered a signed transaction. -// -// To manage this, we read the transactions twice, first trying to read the secretKeys, -// 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 { - // Already signed - signedTxs = append(signedTxs, tx) - } - } - return signedTxs, nil -} - type Alloc map[common.Address]core.GenesisAccount func (g Alloc) OnRoot(common.Hash) {} diff --git a/cmd/evm/internal/t8ntool/tx_iterator.go b/cmd/evm/internal/t8ntool/tx_iterator.go new file mode 100644 index 000000000..8f28dc702 --- /dev/null +++ b/cmd/evm/internal/t8ntool/tx_iterator.go @@ -0,0 +1,194 @@ +// Copyright 2023 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package t8ntool + +import ( + "bytes" + "crypto/ecdsa" + "encoding/json" + "fmt" + "io" + "os" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" +) + +// txWithKey is a helper-struct, to allow us to use the types.Transaction along with +// a `secretKey`-field, for input +type txWithKey struct { + key *ecdsa.PrivateKey + tx *types.Transaction + protected bool +} + +func (t *txWithKey) UnmarshalJSON(input []byte) error { + // Read the metadata, if present + type txMetadata struct { + Key *common.Hash `json:"secretKey"` + Protected *bool `json:"protected"` + } + var data txMetadata + if err := json.Unmarshal(input, &data); err != nil { + return err + } + if data.Key != nil { + k := data.Key.Hex()[2:] + if ecdsaKey, err := crypto.HexToECDSA(k); err != nil { + return err + } else { + t.key = ecdsaKey + } + } + if data.Protected != nil { + t.protected = *data.Protected + } else { + t.protected = true + } + // Now, read the transaction itself + var tx types.Transaction + if err := json.Unmarshal(input, &tx); err != nil { + return err + } + t.tx = &tx + return nil +} + +// signUnsignedTransactions converts the input txs to canonical transactions. +// +// The transactions can have two forms, either +// 1. unsigned or +// 2. signed +// +// For (1), r, s, v, need so be zero, and the `secretKey` needs to be set. +// If so, we sign it here and now, with the given `secretKey` +// If the condition above is not met, then it's considered a signed transaction. +// +// To manage this, we read the transactions twice, first trying to read the secretKeys, +// 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, 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.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) (txIterator, error) { + var txsWithKeys []*txWithKey + 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 + } + return newRlpTxIterator(body), 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 + return newRlpTxIterator(common.FromHex(inputData.TxRlp)), nil + } + // JSON encoded transactions + txsWithKeys = inputData.Txs + } + // We may have to sign the transactions. + signer := types.LatestSignerForChainID(chainConfig.ChainID) + txs, err := signUnsignedTransactions(txsWithKeys, signer) + return newSliceTxIterator(txs), err +} + +type txIterator interface { + // Next returns true until EOF + Next() bool + // Tx returns the next transaction, OR an error. + Tx() (*types.Transaction, error) +} + +type sliceTxIterator struct { + idx int + txs []*types.Transaction +} + +func newSliceTxIterator(transactions types.Transactions) txIterator { + return &sliceTxIterator{0, transactions} +} + +func (ait *sliceTxIterator) Next() bool { + return ait.idx < len(ait.txs) +} + +func (ait *sliceTxIterator) Tx() (*types.Transaction, error) { + if ait.idx < len(ait.txs) { + ait.idx++ + return ait.txs[ait.idx-1], nil + } + return nil, io.EOF +} + +type rlpTxIterator struct { + in *rlp.Stream +} + +func newRlpTxIterator(rlpData []byte) txIterator { + in := rlp.NewStream(bytes.NewBuffer(rlpData), 1024*1024) + in.List() + return &rlpTxIterator{in} +} + +func (it *rlpTxIterator) Next() bool { + return it.in.MoreDataInList() +} + +func (it *rlpTxIterator) Tx() (*types.Transaction, error) { + var a types.Transaction + if err := it.in.Decode(&a); err != nil { + return nil, err + } + return &a, nil +} diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 024be62b9..c3e6a4af9 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -23,121 +23,134 @@ 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" + + // Force-load the tracer engines to trigger registration + _ "github.com/ethereum/go-ethereum/eth/tracers/js" + _ "github.com/ethereum/go-ethereum/eth/tracers/native" ) 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, } ) var stateTransitionCommand = &cli.Command{ Name: "transition", Aliases: []string{"t8n"}, - Usage: "executes a full state transition", + Usage: "Executes a full state transition", Action: t8ntool.Transition, Flags: []cli.Flag{ t8ntool.TraceFlag, - t8ntool.TraceDisableMemoryFlag, + t8ntool.TraceTracerFlag, + t8ntool.TraceTracerConfigFlag, t8ntool.TraceEnableMemoryFlag, t8ntool.TraceDisableStackFlag, - t8ntool.TraceDisableReturnDataFlag, t8ntool.TraceEnableReturnDataFlag, t8ntool.OutputBasedir, t8ntool.OutputAllocFlag, @@ -149,27 +162,25 @@ var stateTransitionCommand = &cli.Command{ t8ntool.ForknameFlag, t8ntool.ChainIDFlag, t8ntool.RewardFlag, - t8ntool.VerbosityFlag, }, } var transactionCommand = &cli.Command{ Name: "transaction", Aliases: []string{"t9n"}, - Usage: "performs transaction validation", + Usage: "Performs transaction validation", Action: t8ntool.Transaction, Flags: []cli.Flag{ t8ntool.InputTxsFlag, t8ntool.ChainIDFlag, t8ntool.ForknameFlag, - t8ntool.VerbosityFlag, }, } var blockBuilderCommand = &cli.Command{ Name: "block-builder", Aliases: []string{"b11r"}, - Usage: "builds a block", + Usage: "Builds a block", Action: t8ntool.BuildBlock, Flags: []cli.Flag{ t8ntool.OutputBasedir, @@ -179,38 +190,41 @@ var blockBuilderCommand = &cli.Command{ t8ntool.InputWithdrawalsFlag, t8ntool.InputTxsRlpFlag, t8ntool.SealCliqueFlag, - t8ntool.VerbosityFlag, }, } +// 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 +235,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 ed7989e87..f3ffb3ed9 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,23 +33,23 @@ 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" ) var runCommand = &cli.Command{ Action: runCmd, Name: "run", - Usage: "run arbitrary evm binary", + 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,15 +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) - blobHashes []common.Hash // TODO (MariusVanDerWijden) implement blob hashes in state tests + 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) @@ -138,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.AllDevChainProtocolChanges } + + 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)) } @@ -204,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, @@ -219,24 +222,12 @@ func runCmd(ctx *cli.Context) error { 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 { @@ -284,19 +275,6 @@ func runCmd(ctx *cli.Context) error { 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 231830f6d..6e751b630 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,25 +100,25 @@ 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, statedb *state.StateDB) { + var root common.Hash + if statedb != nil { + root = statedb.IntermediateRoot(false) + result.Root = &root + if jsonOut { + fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root) + } + if dump { // Dump any state to aid debugging + cpy, _ := state.New(root, statedb.Database(), nil) + dump := cpy.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 && s != nil { - s, _ = state.New(*result.Root, s.Database(), nil) - dump := s.RawDump(nil) - result.State = &dump + if err != nil { + // Test failed, mark as so + result.Pass, result.Error = false, err.Error() } - } - + }) results = append(results, *result) } } diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go index d7a4db739..ad36540de 100644 --- a/cmd/evm/t8n_test.go +++ b/cmd/evm/t8n_test.go @@ -24,9 +24,9 @@ import ( "strings" "testing" - "github.com/docker/docker/pkg/reexec" "github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool" "github.com/ethereum/go-ethereum/internal/cmdtest" + "github.com/ethereum/go-ethereum/internal/reexec" ) func TestMain(m *testing.M) { @@ -106,6 +106,7 @@ func (args *t8nOutput) get() (out []string) { } func TestT8n(t *testing.T) { + t.Parallel() tt := new(testT8n) tt.TestCmd = cmdtest.NewTestCmd(t, tt) for i, tc := range []struct { @@ -267,6 +268,22 @@ func TestT8n(t *testing.T) { 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", + }, + { // More cancun test, plus example of rlp-transaction that cannot be decoded properly + base: "./testdata/30", + input: t8nInput{ + "alloc.json", "txs_more.rlp", "env.json", "Cancun", "", + }, + output: t8nOutput{alloc: true, result: true}, + expOut: "exp.json", + }, } { args := []string{"t8n"} args = append(args, tc.output.get()...) @@ -322,6 +339,7 @@ func (args *t9nInput) get(base string) []string { } func TestT9n(t *testing.T) { + t.Parallel() tt := new(testT8n) tt.TestCmd = cmdtest.NewTestCmd(t, tt) for i, tc := range []struct { @@ -457,6 +475,7 @@ func (args *b11rInput) get(base string) []string { } func TestB11r(t *testing.T) { + t.Parallel() tt := new(testT8n) tt.TestCmd = cmdtest.NewTestCmd(t, tt) for i, tc := range []struct { diff --git a/cmd/evm/testdata/28/env.json b/cmd/evm/testdata/28/env.json index 83c12dcfc..82f22ac62 100644 --- a/cmd/evm/testdata/28/env.json +++ b/cmd/evm/testdata/28/env.json @@ -9,8 +9,7 @@ "parentDifficulty" : "0x00", "parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "currentRandom" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "withdrawals" : [ - ], + "withdrawals" : [], "parentBaseFee" : "0x0a", "parentGasUsed" : "0x00", "parentGasLimit" : "0x7fffffffffffffff", @@ -18,5 +17,6 @@ "parentBlobGasUsed" : "0x00", "blockHashes" : { "0" : "0x3a9b485972e7353edd9152712492f0c58d89ef80623686b6bf947a4a6dce6cb6" - } -} \ No newline at end of file + }, + "parentBeaconBlockRoot": "0x0000beac00beac00beac00beac00beac00beac00beac00beac00beac00beac00" +} diff --git a/cmd/evm/testdata/28/exp.json b/cmd/evm/testdata/28/exp.json index a55ce0aec..75c715e97 100644 --- a/cmd/evm/testdata/28/exp.json +++ b/cmd/evm/testdata/28/exp.json @@ -42,6 +42,6 @@ "currentBaseFee": "0x9", "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "currentExcessBlobGas": "0x0", - "currentBlobGasUsed": "0x20000" + "blobGasUsed": "0x20000" } -} \ 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 000000000..d2c879a45 --- /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 000000000..e752a909a --- /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 000000000..c4c001ec1 --- /dev/null +++ b/cmd/evm/testdata/29/exp.json @@ -0,0 +1,45 @@ +{ + "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": [ + { + "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 000000000..ab02ce9cf --- /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 000000000..d6743cc4d --- /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/30/README.txt b/cmd/evm/testdata/30/README.txt new file mode 100644 index 000000000..84c92de85 --- /dev/null +++ b/cmd/evm/testdata/30/README.txt @@ -0,0 +1,77 @@ +This example comes from https://github.com/ethereum/go-ethereum/issues/27730. +The input transactions contain three transactions, number `0` and `2` are taken from +`testdata/13`, whereas number `1` is taken from #27730. + +The problematic second transaction cannot be RLP-decoded, and the expectation is +that that particular transaction should be rejected, but number `0` and `1` should +still be accepted. + +``` +$ go run . t8n --input.alloc=./testdata/30/alloc.json --input.txs=./testdata/30/txs_more.rlp --input.env=./testdata/30/env.json --output.result=stdout --output.alloc=stdout --state.fork=Cancun +WARN [10-22|15:38:03.283] rejected tx index=1 error="rlp: input string too short for common.Address, decoding into (types.Transaction)(types.BlobTx).To" +INFO [10-22|15:38:03.284] Trie dumping started root=348312..915c93 +INFO [10-22|15:38:03.284] Trie dumping complete accounts=3 elapsed="160.831µs" +{ + "alloc": { + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87": { + "code": "0x60004960005500", + "balance": "0xde0b6b3a7640000" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xde0b6b3a7640000" + }, + "0xd02d72e067e77158444ef2020ff2d325f929b363": { + "balance": "0xfffffffb8390", + "nonce": "0x3" + } + }, + "result": { + "stateRoot": "0x3483124b6710486c9fb3e07975669c66924697c88cccdcc166af5e1218915c93", + "txRoot": "0x013509c8563d41c0ae4bf38f2d6d19fc6512a1d0d6be045079c8c9f68bf45f9d", + "receiptsRoot": "0x75308898d571eafb5cd8cde8278bf5b3d13c5f6ec074926de3bb895b519264e1", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "type": "0x2", + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + }, + { + "type": "0x2", + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0xa410", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x1" + } + ], + "rejected": [ + { + "index": 1, + "error": "rlp: input string too short for common.Address, decoding into (types.Transaction)(types.BlobTx).To" + } + ], + "currentDifficulty": null, + "gasUsed": "0xa410", + "currentBaseFee": "0x7", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + } +} + +``` \ No newline at end of file diff --git a/cmd/evm/testdata/30/alloc.json b/cmd/evm/testdata/30/alloc.json new file mode 100644 index 000000000..6bc93d255 --- /dev/null +++ b/cmd/evm/testdata/30/alloc.json @@ -0,0 +1,23 @@ +{ + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x60004960005500", + "nonce" : "0x00", + "storage" : { + } + }, + "0xd02d72e067e77158444ef2020ff2d325f929b363" : { + "balance": "0x01000000000000", + "code": "0x", + "nonce": "0x01", + "storage": { + } + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "0x0de0b6b3a7640000", + "code" : "0x", + "nonce" : "0x00", + "storage" : { + } + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/30/env.json b/cmd/evm/testdata/30/env.json new file mode 100644 index 000000000..4acd9794b --- /dev/null +++ b/cmd/evm/testdata/30/env.json @@ -0,0 +1,23 @@ +{ + "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentNumber" : "0x01", + "currentTimestamp" : "0x03e8", + "currentGasLimit" : "0x1000000000", + "previousHash" : "0xe4e2a30b340bec696242b67584264f878600dce98354ae0b6328740fd4ff18da", + "currentDataGasUsed" : "0x2000", + "parentTimestamp" : "0x00", + "parentDifficulty" : "0x00", + "parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "parentBeaconBlockRoot" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "currentRandom" : "0x0000000000000000000000000000000000000000000000000000000000020000", + "withdrawals" : [ + ], + "parentBaseFee" : "0x08", + "parentGasUsed" : "0x00", + "parentGasLimit" : "0x1000000000", + "parentExcessBlobGas" : "0x1000", + "parentBlobGasUsed" : "0x2000", + "blockHashes" : { + "0" : "0xe4e2a30b340bec696242b67584264f878600dce98354ae0b6328740fd4ff18da" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/30/exp.json b/cmd/evm/testdata/30/exp.json new file mode 100644 index 000000000..f0b19c6b3 --- /dev/null +++ b/cmd/evm/testdata/30/exp.json @@ -0,0 +1,64 @@ +{ + "alloc": { + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87": { + "code": "0x60004960005500", + "balance": "0xde0b6b3a7640000" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xde0b6b3a7640000" + }, + "0xd02d72e067e77158444ef2020ff2d325f929b363": { + "balance": "0xfffffffb8390", + "nonce": "0x3" + } + }, + "result": { + "stateRoot": "0x3483124b6710486c9fb3e07975669c66924697c88cccdcc166af5e1218915c93", + "txRoot": "0x013509c8563d41c0ae4bf38f2d6d19fc6512a1d0d6be045079c8c9f68bf45f9d", + "receiptsRoot": "0x75308898d571eafb5cd8cde8278bf5b3d13c5f6ec074926de3bb895b519264e1", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "type": "0x2", + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x5208", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + }, + { + "type": "0x2", + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0xa410", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x5208", + "effectiveGasPrice": null, + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x1" + } + ], + "rejected": [ + { + "index": 1, + "error": "rlp: input string too short for common.Address, decoding into (types.Transaction)(types.BlobTx).To" + } + ], + "currentDifficulty": null, + "gasUsed": "0xa410", + "currentBaseFee": "0x7", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "currentExcessBlobGas": "0x0", + "blobGasUsed": "0x0" + } +} \ No newline at end of file diff --git a/cmd/evm/testdata/30/txs.rlp b/cmd/evm/testdata/30/txs.rlp new file mode 100644 index 000000000..620c1a13a --- /dev/null +++ b/cmd/evm/testdata/30/txs.rlp @@ -0,0 +1 @@ +"0xf8dbb8d903f8d601800285012a05f200833d090080830186a000f85bf85994095e7baea6a6c7c4c2dfeb977efac326af552d87f842a00000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010ae1a001a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d880a0fc12b67159a3567f8bdbc49e0be369a2e20e09d57a51c41310543a4128409464a02de0cfe5495c4f58ff60645ceda0afd67a4c90a70bc89fe207269435b35e5b67" \ No newline at end of file diff --git a/cmd/evm/testdata/30/txs_more.rlp b/cmd/evm/testdata/30/txs_more.rlp new file mode 100644 index 000000000..35af8d1f2 --- /dev/null +++ b/cmd/evm/testdata/30/txs_more.rlp @@ -0,0 +1 @@ +"0xf901adb86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b8d903f8d601800285012a05f200833d090080830186a000f85bf85994095e7baea6a6c7c4c2dfeb977efac326af552d87f842a00000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010ae1a001a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d880a0fc12b67159a3567f8bdbc49e0be369a2e20e09d57a51c41310543a4128409464a02de0cfe5495c4f58ff60645ceda0afd67a4c90a70bc89fe207269435b35e5b67b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" \ No newline at end of file diff --git a/cmd/evm/testdata/8/readme.md b/cmd/evm/testdata/8/readme.md index 4dffdab91..85aae1892 100644 --- a/cmd/evm/testdata/8/readme.md +++ b/cmd/evm/testdata/8/readme.md @@ -32,7 +32,7 @@ dir=./testdata/8 && ./evm t8n --state.fork=Berlin --input.alloc=$dir/alloc.json {"pc":4,"op":84,"gas":"0x48456","gasCost":"0x64","memSize":0,"stack":["0x3"],"depth":1,"refund":0,"opName":"SLOAD"} ``` -Simlarly, we can provide the input transactions via `stdin` instead of as file: +Similarly, we can provide the input transactions via `stdin` instead of as file: ``` $ dir=./testdata/8 \ diff --git a/cmd/evm/testdata/9/readme.md b/cmd/evm/testdata/9/readme.md index 539478028..357e20068 100644 --- a/cmd/evm/testdata/9/readme.md +++ b/cmd/evm/testdata/9/readme.md @@ -1,6 +1,6 @@ ## 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 diff --git a/cmd/faucet/README.md b/cmd/faucet/README.md deleted file mode 100644 index 7e857fa0e..000000000 --- a/cmd/faucet/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Faucet - -The `faucet` is a simplistic web application with the goal of distributing small amounts of Ether in private and test networks. - -Users need to post their Ethereum addresses to fund in a Twitter status update or public Facebook post and share the link to the faucet. The faucet will in turn deduplicate user requests and send the Ether. After a funding round, the faucet prevents the same user from requesting again for a pre-configured amount of time, proportional to the amount of Ether requested. - -## Operation - -The `faucet` is a single binary app (everything included) with all configurations set via command line flags and a few files. - -First things first, the `faucet` needs to connect to an Ethereum network, for which it needs the necessary genesis and network infos. Each of the following flags must be set: - -- `-genesis` is a path to a file containing the network `genesis.json`. or using: - - `-goerli` with the faucet with Görli 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 - -The `faucet` will use the `les` protocol to join the configured Ethereum network and will store its data in `$HOME/.faucet` (currently not configurable). - -## Funding - -To be able to distribute funds, the `faucet` needs access to an already funded Ethereum account. This can be configured via: - -- `-account.json` is a path to the Ethereum account's JSON key file -- `-account.pass` is a path to a text file with the decryption passphrase - -The faucet is able to distribute various amounts of Ether in exchange for various timeouts. These can be configured via: - -- `-faucet.amount` is the number of Ethers to send by default -- `-faucet.minutes` is the time to wait before allowing a rerequest -- `-faucet.tiers` is the funding tiers to support (x3 time, x2.5 funds) - -## Sybil protection - -To prevent the same user from exhausting funds in a loop, the `faucet` ties requests to social networks and captcha resolvers. - -Captcha protection uses Google's invisible ReCaptcha, thus the `faucet` needs to run on a live domain. The domain needs to be registered in Google's systems to retrieve the captcha API token and secrets. After doing so, captcha protection may be enabled via: - -- `-captcha.token` is the API token for ReCaptcha -- `-captcha.secret` is the API secret for ReCaptcha - -Sybil protection via Twitter requires an API key as of 15th December, 2020. To obtain it, a Twitter user must be upgraded to developer status and a new Twitter App deployed with it. The app's `Bearer` token is required by the faucet to retrieve tweet data: - -- `-twitter.token` is the Bearer token for `v2` API access -- `-twitter.token.v1` is the Bearer token for `v1` API access - -Sybil protection via Facebook uses the website to directly download post data thus does not currently require an API configuration. - -## Miscellaneous - -Beside the above - mostly essential - CLI flags, there are a number that can be used to fine-tune the `faucet`'s operation. Please see `faucet --help` for a full list. diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go deleted file mode 100644 index e4d6ad697..000000000 --- a/cmd/faucet/faucet.go +++ /dev/null @@ -1,891 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -// faucet is an Ether faucet backed by a light client. -package main - -import ( - "bytes" - "context" - _ "embed" - "encoding/json" - "errors" - "flag" - "fmt" - "html/template" - "io" - "math" - "math/big" - "net/http" - "net/url" - "os" - "path/filepath" - "regexp" - "strconv" - "strings" - "sync" - "time" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/accounts/keystore" - "github.com/ethereum/go-ethereum/cmd/utils" - "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/downloader" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/ethstats" - "github.com/ethereum/go-ethereum/internal/version" - "github.com/ethereum/go-ethereum/les" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/nat" - "github.com/ethereum/go-ethereum/params" - "github.com/gorilla/websocket" -) - -var ( - genesisFlag = flag.String("genesis", "", "Genesis json file to seed the chain with") - apiPortFlag = flag.Int("apiport", 8080, "Listener port for the HTTP API connection") - ethPortFlag = flag.Int("ethport", 30303, "Listener port for the devp2p connection") - bootFlag = flag.String("bootnodes", "", "Comma separated bootnode enode URLs to seed with") - netFlag = flag.Uint64("network", 0, "Network ID to use for the Ethereum protocol") - statsFlag = flag.String("ethstats", "", "Ethstats network monitoring auth string") - - netnameFlag = flag.String("faucet.name", "", "Network name to assign to the faucet") - payoutFlag = flag.Int("faucet.amount", 1, "Number of Ethers to pay out per user request") - minutesFlag = flag.Int("faucet.minutes", 1440, "Number of minutes to wait between funding rounds") - tiersFlag = flag.Int("faucet.tiers", 3, "Number of funding tiers to enable (x3 time, x2.5 funds)") - - accJSONFlag = flag.String("account.json", "", "Key json file to fund user requests with") - accPassFlag = flag.String("account.pass", "", "Decryption password to access faucet funds") - - captchaToken = flag.String("captcha.token", "", "Recaptcha site key to authenticate client side") - captchaSecret = flag.String("captcha.secret", "", "Recaptcha secret key to authenticate server side") - - noauthFlag = flag.Bool("noauth", false, "Enables funding requests without authentication") - logFlag = flag.Int("loglevel", 3, "Log level to use for Ethereum and the faucet") - - twitterTokenFlag = flag.String("twitter.token", "", "Bearer token to authenticate with the v2 Twitter API") - 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") - sepoliaFlag = flag.Bool("sepolia", false, "Initializes the faucet with Sepolia network config") -) - -var ( - ether = new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil) -) - -//go:embed faucet.html -var websiteTmpl string - -func main() { - // Parse the flags and set up the logger to print everything requested - flag.Parse() - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*logFlag), log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) - - // Construct the payout tiers - amounts := make([]string, *tiersFlag) - periods := make([]string, *tiersFlag) - for i := 0; i < *tiersFlag; i++ { - // Calculate the amount for the next tier and format it - amount := float64(*payoutFlag) * math.Pow(2.5, float64(i)) - amounts[i] = fmt.Sprintf("%s Ethers", strconv.FormatFloat(amount, 'f', -1, 64)) - if amount == 1 { - amounts[i] = strings.TrimSuffix(amounts[i], "s") - } - // Calculate the period for the next tier and format it - period := *minutesFlag * int(math.Pow(3, float64(i))) - periods[i] = fmt.Sprintf("%d mins", period) - if period%60 == 0 { - period /= 60 - periods[i] = fmt.Sprintf("%d hours", period) - - if period%24 == 0 { - period /= 24 - periods[i] = fmt.Sprintf("%d days", period) - } - } - if period == 1 { - periods[i] = strings.TrimSuffix(periods[i], "s") - } - } - website := new(bytes.Buffer) - err := template.Must(template.New("").Parse(websiteTmpl)).Execute(website, map[string]interface{}{ - "Network": *netnameFlag, - "Amounts": amounts, - "Periods": periods, - "Recaptcha": *captchaToken, - "NoAuth": *noauthFlag, - }) - if err != nil { - 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, *sepoliaFlag) - if err != nil { - log.Crit("Failed to parse genesis config", "err", err) - } - // Convert the bootnodes to internal enode representations - var enodes []*enode.Node - for _, boot := range strings.Split(*bootFlag, ",") { - if url, err := enode.Parse(enode.ValidSchemes, boot); err == nil { - enodes = append(enodes, url) - } else { - log.Error("Failed to parse bootnode URL", "url", boot, "err", err) - } - } - // Load up the account key and decrypt its password - blob, err := os.ReadFile(*accPassFlag) - if err != nil { - log.Crit("Failed to read account password contents", "file", *accPassFlag, "err", err) - } - pass := strings.TrimSuffix(string(blob), "\n") - - ks := keystore.NewKeyStore(filepath.Join(os.Getenv("HOME"), ".faucet", "keys"), keystore.StandardScryptN, keystore.StandardScryptP) - if blob, err = os.ReadFile(*accJSONFlag); err != nil { - log.Crit("Failed to read account key contents", "file", *accJSONFlag, "err", err) - } - acc, err := ks.Import(blob, pass, pass) - if err != nil && err != keystore.ErrAccountAlreadyExists { - log.Crit("Failed to import faucet signer account", "err", err) - } - if err := ks.Unlock(acc, pass); err != nil { - log.Crit("Failed to unlock faucet signer account", "err", err) - } - // Assemble and start the faucet light service - faucet, err := newFaucet(genesis, *ethPortFlag, enodes, *netFlag, *statsFlag, ks, website.Bytes()) - if err != nil { - log.Crit("Failed to start faucet", "err", err) - } - defer faucet.close() - - if err := faucet.listenAndServe(*apiPortFlag); err != nil { - log.Crit("Failed to launch faucet API", "err", err) - } -} - -// request represents an accepted funding request. -type request struct { - Avatar string `json:"avatar"` // Avatar URL to make the UI nicer - Account common.Address `json:"account"` // Ethereum address being funded - Time time.Time `json:"time"` // Timestamp when the request was accepted - Tx *types.Transaction `json:"tx"` // Transaction funding the account -} - -// faucet represents a crypto faucet backed by an Ethereum light client. -type faucet struct { - config *params.ChainConfig // Chain configurations for signing - stack *node.Node // Ethereum protocol stack - client *ethclient.Client // Client connection to the Ethereum chain - index []byte // Index page to serve up on the web - - keystore *keystore.KeyStore // Keystore containing the single signer - account accounts.Account // Account funding user faucet requests - head *types.Header // Current head header of the faucet - balance *big.Int // Current balance of the faucet - nonce uint64 // Current pending nonce of the faucet - price *big.Int // Current gas price to issue funds with - - conns []*wsConn // Currently live websocket connections - timeouts map[string]time.Time // History of users and their funding timeouts - reqs []*request // Currently pending funding requests - update chan struct{} // Channel to signal request updates - - lock sync.RWMutex // Lock protecting the faucet's internals -} - -// wsConn wraps a websocket connection with a write mutex as the underlying -// websocket library does not synchronize access to the stream. -type wsConn struct { - conn *websocket.Conn - wlock sync.Mutex -} - -func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) { - // Assemble the raw devp2p protocol stack - git, _ := version.VCS() - stack, err := node.New(&node.Config{ - Name: "geth", - Version: params.VersionWithCommit(git.Commit, git.Date), - DataDir: filepath.Join(os.Getenv("HOME"), ".faucet"), - P2P: p2p.Config{ - NAT: nat.Any(), - NoDiscovery: true, - DiscoveryV5: true, - ListenAddr: fmt.Sprintf(":%d", port), - MaxPeers: 25, - BootstrapNodesV5: enodes, - }, - }) - if err != nil { - return nil, err - } - - // Assemble the Ethereum light client protocol - cfg := ethconfig.Defaults - cfg.SyncMode = downloader.LightSync - cfg.NetworkId = network - cfg.Genesis = genesis - utils.SetDNSDiscoveryDefaults(&cfg, genesis.ToBlock().Hash()) - - lesBackend, err := les.New(stack, &cfg) - if err != nil { - return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err) - } - - // Assemble the ethstats monitoring and reporting service' - if stats != "" { - if err := ethstats.New(stack, lesBackend.ApiBackend, lesBackend.Engine(), stats); err != nil { - return nil, err - } - } - // Boot up the client and ensure it connects to bootnodes - if err := stack.Start(); err != nil { - return nil, err - } - for _, boot := range enodes { - old, err := enode.Parse(enode.ValidSchemes, boot.String()) - if err == nil { - stack.Server().AddPeer(old) - } - } - // Attach to the client and retrieve and interesting metadatas - api := stack.Attach() - client := ethclient.NewClient(api) - - return &faucet{ - config: genesis.Config, - stack: stack, - client: client, - index: index, - keystore: ks, - account: ks.Accounts()[0], - timeouts: make(map[string]time.Time), - update: make(chan struct{}, 1), - }, nil -} - -// close terminates the Ethereum connection and tears down the faucet. -func (f *faucet) close() error { - return f.stack.Close() -} - -// listenAndServe registers the HTTP handlers for the faucet and boots it up -// for service user funding requests. -func (f *faucet) listenAndServe(port int) error { - go f.loop() - - http.HandleFunc("/", f.webHandler) - http.HandleFunc("/api", f.apiHandler) - return http.ListenAndServe(fmt.Sprintf(":%d", port), nil) -} - -// webHandler handles all non-api requests, simply flattening and returning the -// faucet website. -func (f *faucet) webHandler(w http.ResponseWriter, r *http.Request) { - w.Write(f.index) -} - -// apiHandler handles requests for Ether grants and transaction statuses. -func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) { - upgrader := websocket.Upgrader{} - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return - } - - // Start tracking the connection and drop at the end - defer conn.Close() - - f.lock.Lock() - wsconn := &wsConn{conn: conn} - f.conns = append(f.conns, wsconn) - f.lock.Unlock() - - defer func() { - f.lock.Lock() - for i, c := range f.conns { - if c.conn == conn { - f.conns = append(f.conns[:i], f.conns[i+1:]...) - break - } - } - f.lock.Unlock() - }() - // Gather the initial stats from the network to report - var ( - head *types.Header - balance *big.Int - nonce uint64 - ) - for head == nil || balance == nil { - // Retrieve the current stats cached by the faucet - f.lock.RLock() - if f.head != nil { - head = types.CopyHeader(f.head) - } - if f.balance != nil { - balance = new(big.Int).Set(f.balance) - } - nonce = f.nonce - f.lock.RUnlock() - - if head == nil || balance == nil { - // Report the faucet offline until initial stats are ready - //lint:ignore ST1005 This error is to be displayed in the browser - if err = sendError(wsconn, errors.New("Faucet offline")); err != nil { - log.Warn("Failed to send faucet error to client", "err", err) - return - } - time.Sleep(3 * time.Second) - } - } - // Send over the initial stats and the latest header - f.lock.RLock() - reqs := f.reqs - f.lock.RUnlock() - if err = send(wsconn, map[string]interface{}{ - "funds": new(big.Int).Div(balance, ether), - "funded": nonce, - "peers": f.stack.Server().PeerCount(), - "requests": reqs, - }, 3*time.Second); err != nil { - log.Warn("Failed to send initial stats to client", "err", err) - return - } - if err = send(wsconn, head, 3*time.Second); err != nil { - log.Warn("Failed to send initial header to client", "err", err) - return - } - // Keep reading requests from the websocket until the connection breaks - for { - // Fetch the next funding request and validate against github - var msg struct { - URL string `json:"url"` - Tier uint `json:"tier"` - Captcha string `json:"captcha"` - } - if err = conn.ReadJSON(&msg); err != nil { - return - } - if !*noauthFlag && !strings.HasPrefix(msg.URL, "https://twitter.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") { - if err = sendError(wsconn, errors.New("URL doesn't link to supported services")); err != nil { - log.Warn("Failed to send URL error to client", "err", err) - return - } - continue - } - if msg.Tier >= uint(*tiersFlag) { - //lint:ignore ST1005 This error is to be displayed in the browser - if err = sendError(wsconn, errors.New("Invalid funding tier requested")); err != nil { - log.Warn("Failed to send tier error to client", "err", err) - return - } - continue - } - log.Info("Faucet funds requested", "url", msg.URL, "tier", msg.Tier) - - // If captcha verifications are enabled, make sure we're not dealing with a robot - if *captchaToken != "" { - form := url.Values{} - form.Add("secret", *captchaSecret) - form.Add("response", msg.Captcha) - - res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form) - if err != nil { - if err = sendError(wsconn, err); err != nil { - log.Warn("Failed to send captcha post error to client", "err", err) - return - } - continue - } - var result struct { - Success bool `json:"success"` - Errors json.RawMessage `json:"error-codes"` - } - err = json.NewDecoder(res.Body).Decode(&result) - res.Body.Close() - if err != nil { - if err = sendError(wsconn, err); err != nil { - log.Warn("Failed to send captcha decode error to client", "err", err) - return - } - continue - } - if !result.Success { - log.Warn("Captcha verification failed", "err", string(result.Errors)) - //lint:ignore ST1005 it's funny and the robot won't mind - if err = sendError(wsconn, errors.New("Beep-bop, you're a robot!")); err != nil { - log.Warn("Failed to send captcha failure to client", "err", err) - return - } - continue - } - } - // Retrieve the Ethereum address to fund, the requesting user and a profile picture - var ( - id string - username string - avatar string - address common.Address - ) - switch { - case strings.HasPrefix(msg.URL, "https://twitter.com/"): - id, username, avatar, address, err = authTwitter(msg.URL, *twitterTokenV1Flag, *twitterTokenFlag) - case strings.HasPrefix(msg.URL, "https://www.facebook.com/"): - username, avatar, address, err = authFacebook(msg.URL) - id = username - case *noauthFlag: - username, avatar, address, err = authNoAuth(msg.URL) - id = username - default: - //lint:ignore ST1005 This error is to be displayed in the browser - err = errors.New("Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues") - } - if err != nil { - if err = sendError(wsconn, err); err != nil { - log.Warn("Failed to send prefix error to client", "err", err) - return - } - continue - } - log.Info("Faucet request valid", "url", msg.URL, "tier", msg.Tier, "user", username, "address", address) - - // Ensure the user didn't request funds too recently - f.lock.Lock() - var ( - fund bool - timeout time.Time - ) - if timeout = f.timeouts[id]; time.Now().After(timeout) { - // User wasn't funded recently, create the funding transaction - amount := new(big.Int).Mul(big.NewInt(int64(*payoutFlag)), ether) - amount = new(big.Int).Mul(amount, new(big.Int).Exp(big.NewInt(5), big.NewInt(int64(msg.Tier)), nil)) - amount = new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(msg.Tier)), nil)) - - tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, 21000, f.price, nil) - signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainID) - if err != nil { - f.lock.Unlock() - if err = sendError(wsconn, err); err != nil { - log.Warn("Failed to send transaction creation error to client", "err", err) - return - } - continue - } - // Submit the transaction and mark as funded if successful - if err := f.client.SendTransaction(context.Background(), signed); err != nil { - f.lock.Unlock() - if err = sendError(wsconn, err); err != nil { - log.Warn("Failed to send transaction transmission error to client", "err", err) - return - } - continue - } - f.reqs = append(f.reqs, &request{ - Avatar: avatar, - Account: address, - Time: time.Now(), - Tx: signed, - }) - timeout := time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute - grace := timeout / 288 // 24h timeout => 5m grace - - f.timeouts[id] = time.Now().Add(timeout - grace) - fund = true - } - f.lock.Unlock() - - // Send an error if too frequent funding, othewise a success - if !fund { - if err = sendError(wsconn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(time.Until(timeout)))); err != nil { // nolint: gosimple - log.Warn("Failed to send funding error to client", "err", err) - return - } - continue - } - if err = sendSuccess(wsconn, fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())); err != nil { - log.Warn("Failed to send funding success to client", "err", err) - return - } - select { - case f.update <- struct{}{}: - default: - } - } -} - -// refresh attempts to retrieve the latest header from the chain and extract the -// associated faucet balance and nonce for connectivity caching. -func (f *faucet) refresh(head *types.Header) error { - // Ensure a state update does not run for too long - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - // If no header was specified, use the current chain head - var err error - if head == nil { - if head, err = f.client.HeaderByNumber(ctx, nil); err != nil { - return err - } - } - // Retrieve the balance, nonce and gas price from the current head - var ( - balance *big.Int - nonce uint64 - price *big.Int - ) - if balance, err = f.client.BalanceAt(ctx, f.account.Address, head.Number); err != nil { - return err - } - if nonce, err = f.client.NonceAt(ctx, f.account.Address, head.Number); err != nil { - return err - } - if price, err = f.client.SuggestGasPrice(ctx); err != nil { - return err - } - // Everything succeeded, update the cached stats and eject old requests - f.lock.Lock() - f.head, f.balance = head, balance - f.price, f.nonce = price, nonce - for len(f.reqs) > 0 && f.reqs[0].Tx.Nonce() < f.nonce { - f.reqs = f.reqs[1:] - } - f.lock.Unlock() - - return nil -} - -// loop keeps waiting for interesting events and pushes them out to connected -// websockets. -func (f *faucet) loop() { - // Wait for chain events and push them to clients - heads := make(chan *types.Header, 16) - sub, err := f.client.SubscribeNewHead(context.Background(), heads) - if err != nil { - log.Crit("Failed to subscribe to head events", "err", err) - } - defer sub.Unsubscribe() - - // Start a goroutine to update the state from head notifications in the background - update := make(chan *types.Header) - - go func() { - for head := range update { - // New chain head arrived, query the current stats and stream to clients - timestamp := time.Unix(int64(head.Time), 0) - if time.Since(timestamp) > time.Hour { - log.Warn("Skipping faucet refresh, head too old", "number", head.Number, "hash", head.Hash(), "age", common.PrettyAge(timestamp)) - continue - } - if err := f.refresh(head); err != nil { - log.Warn("Failed to update faucet state", "block", head.Number, "hash", head.Hash(), "err", err) - continue - } - // Faucet state retrieved, update locally and send to clients - f.lock.RLock() - log.Info("Updated faucet state", "number", head.Number, "hash", head.Hash(), "age", common.PrettyAge(timestamp), "balance", f.balance, "nonce", f.nonce, "price", f.price) - - balance := new(big.Int).Div(f.balance, ether) - peers := f.stack.Server().PeerCount() - - for _, conn := range f.conns { - if err := send(conn, map[string]interface{}{ - "funds": balance, - "funded": f.nonce, - "peers": peers, - "requests": f.reqs, - }, time.Second); err != nil { - log.Warn("Failed to send stats to client", "err", err) - conn.conn.Close() - continue - } - if err := send(conn, head, time.Second); err != nil { - log.Warn("Failed to send header to client", "err", err) - conn.conn.Close() - } - } - f.lock.RUnlock() - } - }() - // Wait for various events and assing to the appropriate background threads - for { - select { - case head := <-heads: - // New head arrived, send if for state update if there's none running - select { - case update <- head: - default: - } - - case <-f.update: - // Pending requests updated, stream to clients - f.lock.RLock() - for _, conn := range f.conns { - if err := send(conn, map[string]interface{}{"requests": f.reqs}, time.Second); err != nil { - log.Warn("Failed to send requests to client", "err", err) - conn.conn.Close() - } - } - f.lock.RUnlock() - } - } -} - -// sends transmits a data packet to the remote end of the websocket, but also -// setting a write deadline to prevent waiting forever on the node. -func send(conn *wsConn, value interface{}, timeout time.Duration) error { - if timeout == 0 { - timeout = 60 * time.Second - } - conn.wlock.Lock() - defer conn.wlock.Unlock() - conn.conn.SetWriteDeadline(time.Now().Add(timeout)) - return conn.conn.WriteJSON(value) -} - -// sendError transmits an error to the remote end of the websocket, also setting -// the write deadline to 1 second to prevent waiting forever. -func sendError(conn *wsConn, err error) error { - return send(conn, map[string]string{"error": err.Error()}, time.Second) -} - -// sendSuccess transmits a success message to the remote end of the websocket, also -// setting the write deadline to 1 second to prevent waiting forever. -func sendSuccess(conn *wsConn, msg string) error { - return send(conn, map[string]string{"success": msg}, time.Second) -} - -// authTwitter tries to authenticate a faucet request using Twitter posts, returning -// the uniqueness identifier (user id/username), username, avatar URL and Ethereum address to fund on success. -func authTwitter(url string, tokenV1, tokenV2 string) (string, string, string, common.Address, error) { - // Ensure the user specified a meaningful URL, no fancy nonsense - parts := strings.Split(url, "/") - if len(parts) < 4 || parts[len(parts)-2] != "status" { - //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", "", common.Address{}, errors.New("Invalid Twitter status URL") - } - // Strip any query parameters from the tweet id and ensure it's numeric - tweetID := strings.Split(parts[len(parts)-1], "?")[0] - if !regexp.MustCompile("^[0-9]+$").MatchString(tweetID) { - return "", "", "", common.Address{}, errors.New("Invalid Tweet URL") - } - // Twitter's API isn't really friendly with direct links. - // It is restricted to 300 queries / 15 minute with an app api key. - // Anything more will require read only authorization from the users and that we want to avoid. - - // If Twitter bearer token is provided, use the API, selecting the version - // the user would prefer (currently there's a limit of 1 v2 app / developer - // but unlimited v1.1 apps). - switch { - case tokenV1 != "": - return authTwitterWithTokenV1(tweetID, tokenV1) - case tokenV2 != "": - return authTwitterWithTokenV2(tweetID, tokenV2) - } - // Twitter API token isn't provided so we just load the public posts - // and scrape it for the Ethereum address and profile URL. We need to load - // the mobile page though since the main page loads tweet contents via JS. - url = strings.Replace(url, "https://twitter.com/", "https://mobile.twitter.com/", 1) - - res, err := http.Get(url) - if err != nil { - return "", "", "", common.Address{}, err - } - defer res.Body.Close() - - // Resolve the username from the final redirect, no intermediate junk - parts = strings.Split(res.Request.URL.String(), "/") - if len(parts) < 4 || parts[len(parts)-2] != "status" { - //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", "", common.Address{}, errors.New("Invalid Twitter status URL") - } - username := parts[len(parts)-3] - - body, err := io.ReadAll(res.Body) - if err != nil { - return "", "", "", common.Address{}, err - } - address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body))) - if address == (common.Address{}) { - //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund") - } - var avatar string - if parts = regexp.MustCompile(`src="([^"]+twimg\.com/profile_images[^"]+)"`).FindStringSubmatch(string(body)); len(parts) == 2 { - avatar = parts[1] - } - return username + "@twitter", username, avatar, address, nil -} - -// authTwitterWithTokenV1 tries to authenticate a faucet request using Twitter's v1 -// API, returning the user id, username, avatar URL and Ethereum address to fund on -// success. -func authTwitterWithTokenV1(tweetID string, token string) (string, string, string, common.Address, error) { - // Query the tweet details from Twitter - url := fmt.Sprintf("https://api.twitter.com/1.1/statuses/show.json?id=%s", tweetID) - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return "", "", "", common.Address{}, err - } - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - res, err := http.DefaultClient.Do(req) - if err != nil { - return "", "", "", common.Address{}, err - } - defer res.Body.Close() - - var result struct { - Text string `json:"text"` - User struct { - ID string `json:"id_str"` - Username string `json:"screen_name"` - Avatar string `json:"profile_image_url"` - } `json:"user"` - } - err = json.NewDecoder(res.Body).Decode(&result) - if err != nil { - return "", "", "", common.Address{}, err - } - address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(result.Text)) - if address == (common.Address{}) { - //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund") - } - return result.User.ID + "@twitter", result.User.Username, result.User.Avatar, address, nil -} - -// authTwitterWithTokenV2 tries to authenticate a faucet request using Twitter's v2 -// API, returning the user id, username, avatar URL and Ethereum address to fund on -// success. -func authTwitterWithTokenV2(tweetID string, token string) (string, string, string, common.Address, error) { - // Query the tweet details from Twitter - url := fmt.Sprintf("https://api.twitter.com/2/tweets/%s?expansions=author_id&user.fields=profile_image_url", tweetID) - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return "", "", "", common.Address{}, err - } - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - res, err := http.DefaultClient.Do(req) - if err != nil { - return "", "", "", common.Address{}, err - } - defer res.Body.Close() - - var result struct { - Data struct { - AuthorID string `json:"author_id"` - Text string `json:"text"` - } `json:"data"` - Includes struct { - Users []struct { - ID string `json:"id"` - Username string `json:"username"` - Avatar string `json:"profile_image_url"` - } `json:"users"` - } `json:"includes"` - } - - err = json.NewDecoder(res.Body).Decode(&result) - if err != nil { - return "", "", "", common.Address{}, err - } - - address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(result.Data.Text)) - if address == (common.Address{}) { - //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund") - } - return result.Data.AuthorID + "@twitter", result.Includes.Users[0].Username, result.Includes.Users[0].Avatar, address, nil -} - -// authFacebook tries to authenticate a faucet request using Facebook posts, -// returning the username, avatar URL and Ethereum address to fund on success. -func authFacebook(url string) (string, string, common.Address, error) { - // Ensure the user specified a meaningful URL, no fancy nonsense - parts := strings.Split(strings.Split(url, "?")[0], "/") - if parts[len(parts)-1] == "" { - parts = parts[0 : len(parts)-1] - } - if len(parts) < 4 || parts[len(parts)-2] != "posts" { - //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", common.Address{}, errors.New("Invalid Facebook post URL") - } - username := parts[len(parts)-3] - - // Facebook's Graph API isn't really friendly with direct links. Still, we don't - // want to do ask read permissions from users, so just load the public posts and - // scrape it for the Ethereum address and profile URL. - // - // Facebook recently changed their desktop webpage to use AJAX for loading post - // content, so switch over to the mobile site for now. Will probably end up having - // to use the API eventually. - crawl := strings.Replace(url, "www.facebook.com", "m.facebook.com", 1) - - res, err := http.Get(crawl) - if err != nil { - return "", "", common.Address{}, err - } - defer res.Body.Close() - - body, err := io.ReadAll(res.Body) - if err != nil { - return "", "", common.Address{}, err - } - address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body))) - if address == (common.Address{}) { - //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", common.Address{}, errors.New("No Ethereum address found to fund. Please check the post URL and verify that it can be viewed publicly.") - } - var avatar string - if parts = regexp.MustCompile(`src="([^"]+fbcdn\.net[^"]+)"`).FindStringSubmatch(string(body)); len(parts) == 2 { - avatar = parts[1] - } - return username + "@facebook", avatar, address, nil -} - -// authNoAuth tries to interpret a faucet request as a plain Ethereum address, -// without actually performing any remote authentication. This mode is prone to -// Byzantine attack, so only ever use for truly private networks. -func authNoAuth(url string) (string, string, common.Address, error) { - address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(url)) - if address == (common.Address{}) { - //lint:ignore ST1005 This error is to be displayed in the browser - return "", "", common.Address{}, errors.New("No Ethereum address found to fund") - } - return address.Hex() + "@noauth", "", address, nil -} - -// getGenesis returns a genesis based on input args -func getGenesis(genesisFlag string, goerliFlag bool, sepoliaFlag bool) (*core.Genesis, error) { - switch { - case genesisFlag != "": - var genesis core.Genesis - err := common.LoadJSON(genesisFlag, &genesis) - return &genesis, err - case goerliFlag: - return core.DefaultGoerliGenesisBlock(), nil - case sepoliaFlag: - return core.DefaultSepoliaGenesisBlock(), nil - default: - return nil, errors.New("no genesis flag provided") - } -} diff --git a/cmd/faucet/faucet.html b/cmd/faucet/faucet.html deleted file mode 100644 index dad5ad84f..000000000 --- a/cmd/faucet/faucet.html +++ /dev/null @@ -1,233 +0,0 @@ - - - - - - - - {{.Network}}: Authenticated Faucet - - - - - - - - - - - - - -
-
-
-
-

{{.Network}} Authenticated Faucet

-
-
-
-
-
- - - - - -
{{if .Recaptcha}} -
{{end}} -
-
-
-
-
-
-
-
- -
-
-
-
-
-

How does this work?

-

This Ether faucet is running on the {{.Network}} network. To prevent malicious actors from exhausting all available funds or accumulating enough Ether to mount long running spam attacks, requests are tied to common 3rd party social network accounts. Anyone having a Twitter or Facebook account may request funds within the permitted limits.

-
-
-
To request funds via Twitter, make a tweet with your Ethereum address pasted into the contents (surrounding text doesn't matter).
Copy-paste the tweets URL into the above input box and fire away!
- -
-
To request funds via Facebook, publish a new public post with your Ethereum address embedded into the content (surrounding text doesn't matter).
Copy-paste the posts URL into the above input box and fire away!
- - {{if .NoAuth}} -
-
To request funds without authentication, simply copy-paste your Ethereum address into the above input box (surrounding text doesn't matter) and fire away.
This mode is susceptible to Byzantine attacks. Only use for debugging or private networks!
- {{end}} -
-

You can track the current pending requests below the input field to see how much you have to wait until your turn comes.

- {{if .Recaptcha}}The faucet is running invisible reCaptcha protection against bots.{{end}} -
-
-
-
- - {{if .Recaptcha}} - {{end}} - - diff --git a/cmd/faucet/faucet_test.go b/cmd/faucet/faucet_test.go deleted file mode 100644 index 58a1f22b5..000000000 --- a/cmd/faucet/faucet_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2021 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" -) - -func TestFacebook(t *testing.T) { - // TODO: Remove facebook auth or implement facebook api, which seems to require an API key - t.Skipf("The facebook access is flaky, needs to be reimplemented or removed") - for _, tt := range []struct { - url string - want common.Address - }{ - { - "https://www.facebook.com/fooz.gazonk/posts/2837228539847129", - common.HexToAddress("0xDeadDeaDDeaDbEefbEeFbEEfBeeFBeefBeeFbEEF"), - }, - } { - _, _, gotAddress, err := authFacebook(tt.url) - if err != nil { - t.Fatal(err) - } - if gotAddress != tt.want { - t.Fatalf("address wrong, have %v want %v", gotAddress, tt.want) - } - } -} diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index 84b9c33c2..ea3a7c3b6 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -43,11 +43,13 @@ func tmpDatadirWithKeystore(t *testing.T) string { } func TestAccountListEmpty(t *testing.T) { + t.Parallel() geth := runGeth(t, "account", "list") geth.ExpectExit() } func TestAccountList(t *testing.T) { + t.Parallel() datadir := tmpDatadirWithKeystore(t) var want = ` Account #0: {7ef5a6135f1fd6a02593eedc869c6d41d934aef8} keystore://{{.Datadir}}/keystore/UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8 @@ -74,6 +76,7 @@ Account #2: {289d485d9771714cce91d3393d764e1311907acc} keystore://{{.Datadir}}\k } func TestAccountNew(t *testing.T) { + t.Parallel() geth := runGeth(t, "account", "new", "--lightkdf") defer geth.ExpectExit() geth.Expect(` @@ -96,6 +99,7 @@ Path of the secret key file: .*UTC--.+--[0-9a-f]{40} } func TestAccountImport(t *testing.T) { + t.Parallel() tests := []struct{ name, key, output string }{ { name: "correct account", @@ -118,6 +122,7 @@ func TestAccountImport(t *testing.T) { } func TestAccountHelp(t *testing.T) { + t.Parallel() geth := runGeth(t, "account", "-h") geth.WaitExit() if have, want := geth.ExitStatus(), 0; have != want { @@ -147,6 +152,7 @@ func importAccountWithExpect(t *testing.T, key string, expected string) { } func TestAccountNewBadRepeat(t *testing.T) { + t.Parallel() geth := runGeth(t, "account", "new", "--lightkdf") defer geth.ExpectExit() geth.Expect(` @@ -159,6 +165,7 @@ Fatal: Passwords do not match } func TestAccountUpdate(t *testing.T) { + t.Parallel() datadir := tmpDatadirWithKeystore(t) geth := runGeth(t, "account", "update", "--datadir", datadir, "--lightkdf", @@ -175,6 +182,7 @@ Repeat password: {{.InputLine "foobar2"}} } func TestWalletImport(t *testing.T) { + t.Parallel() geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") defer geth.ExpectExit() geth.Expect(` @@ -190,6 +198,7 @@ Address: {d4584b5f6229b7be90727b0fc8c6b91bb427821f} } func TestWalletImportBadPassword(t *testing.T) { + t.Parallel() geth := runGeth(t, "wallet", "import", "--lightkdf", "testdata/guswallet.json") defer geth.ExpectExit() geth.Expect(` @@ -200,6 +209,7 @@ Fatal: could not decrypt key with given password } func TestUnlockFlag(t *testing.T) { + t.Parallel() geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "console", "--exec", "loadScript('testdata/empty.js')") geth.Expect(` @@ -222,6 +232,7 @@ undefined } func TestUnlockFlagWrongPassword(t *testing.T) { + t.Parallel() geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "console", "--exec", "loadScript('testdata/empty.js')") @@ -240,6 +251,7 @@ Fatal: Failed to unlock account f466859ead1932d743d622cb74fc058882e8648a (could // https://github.com/ethereum/go-ethereum/issues/1785 func TestUnlockFlagMultiIndex(t *testing.T) { + t.Parallel() geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--unlock", "0,2", "console", "--exec", "loadScript('testdata/empty.js')") @@ -266,6 +278,7 @@ undefined } func TestUnlockFlagPasswordFile(t *testing.T) { + t.Parallel() geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--password", "testdata/passwords.txt", "--unlock", "0,2", "console", "--exec", "loadScript('testdata/empty.js')") @@ -287,6 +300,7 @@ undefined } func TestUnlockFlagPasswordFileWrongPassword(t *testing.T) { + t.Parallel() geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--password", "testdata/wrong-passwords.txt", "--unlock", "0,2") @@ -297,6 +311,7 @@ Fatal: Failed to unlock account 0 (could not decrypt key with given password) } func TestUnlockFlagAmbiguous(t *testing.T) { + t.Parallel() store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--keystore", @@ -336,6 +351,7 @@ undefined } func TestUnlockFlagAmbiguousWrongPassword(t *testing.T) { + t.Parallel() store := filepath.Join("..", "..", "accounts", "keystore", "testdata", "dupes") geth := runMinimalGeth(t, "--port", "0", "--ipcdisable", "--datadir", tmpDatadirWithKeystore(t), "--unlock", "f466859ead1932d743d622cb74fc058882e8648a", "--keystore", diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index d34d814d3..ad8e350ac 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,11 @@ 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.OverrideCancun, + utils.OverrideVerkle, + }, 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,11 +97,12 @@ if one is set. Otherwise it prints the genesis from the datadir.`, utils.MetricsInfluxDBBucketFlag, utils.MetricsInfluxDBOrganizationFlag, utils.TxLookupLimitFlag, + utils.TransactionHistoryFlag, + utils.StateHistoryFlag, utils.BlockReplicationTargetsFlag, utils.ReplicaEnableSpecimenFlag, utils.ReplicaEnableResultFlag, - }, utils.DatabasePathFlags), - Category: "BLOCKCHAIN COMMANDS", + }, 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. @@ -114,7 +118,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 @@ -130,26 +134,13 @@ 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. `, } - exportPreimagesCommand = &cli.Command{ - Action: exportPreimages, - Name: "export-preimages", - Usage: "Export the preimage database into an RLP stream", - ArgsUsage: "", - Flags: flags.Merge([]cli.Flag{ - utils.CacheFlag, - utils.SyncModeFlag, - }, utils.DatabasePathFlags), - Description: ` -The export-preimages command exports hash preimages to an RLP encoded stream. -It's deprecated, please use "geth db export" instead. -`, - } + dumpCommand = &cli.Command{ Action: dump, Name: "dump", @@ -163,7 +154,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). `, @@ -194,33 +185,50 @@ func initGenesis(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() + var overrides core.ChainOverrides + if ctx.IsSet(utils.OverrideCancun.Name) { + v := ctx.Uint64(utils.OverrideCancun.Name) + overrides.OverrideCancun = &v + } + if ctx.IsSet(utils.OverrideVerkle.Name) { + v := ctx.Uint64(utils.OverrideVerkle.Name) + overrides.OverrideVerkle = &v + } for _, name := range []string{"chaindata", "lightchaindata"} { chaindb, err := stack.OpenDatabaseWithFreezer(name, 0, 0, ctx.String(utils.AncientFlag.Name), "", false) if err != nil { utils.Fatalf("Failed to open database: %v", err) } - triedb := trie.NewDatabaseWithConfig(chaindb, &trie.Config{ - Preimages: ctx.Bool(utils.CachePreimagesFlag.Name), - }) - _, hash, err := core.SetupGenesisBlock(chaindb, triedb, genesis) + defer chaindb.Close() + + triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle()) + defer triedb.Close() + + _, hash, err := core.SetupGenesisBlockWithOverride(chaindb, triedb, genesis, &overrides) 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 } func dumpGenesis(ctx *cli.Context) error { - // if there is a testnet preset enabled, dump that + // check if there is a testnet preset enabled + var genesis *core.Genesis if utils.IsNetworkPreset(ctx) { - genesis := utils.MakeGenesis(ctx) + genesis = utils.MakeGenesis(ctx) + } else if ctx.IsSet(utils.DeveloperFlag.Name) && !ctx.IsSet(utils.DataDirFlag.Name) { + genesis = core.DeveloperGenesisBlock(11_500_000, nil) + } + + if genesis != nil { if err := json.NewEncoder(os.Stdout).Encode(genesis); err != nil { utils.Fatalf("could not encode genesis: %s", err) } return nil } + // dump whatever already exists in the datadir stack, _ := makeConfigNode(ctx) for _, name := range []string{"chaindata", "lightchaindata"} { @@ -245,7 +253,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, and no genesis exists in the default datadir") return nil } @@ -340,7 +348,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 @@ -371,6 +380,9 @@ func exportChain(ctx *cli.Context) error { } // importPreimages imports preimage data from the specified file. +// it is deprecated, and the export function has been removed, but +// the import function is kept around for the time being so that +// older file formats can still be imported. func importPreimages(ctx *cli.Context) error { if ctx.Args().Len() < 1 { utils.Fatalf("This command requires an argument.") @@ -380,6 +392,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 { @@ -389,26 +402,10 @@ func importPreimages(ctx *cli.Context) error { return nil } -// exportPreimages dumps the preimage data to specified json file in streaming way. -func exportPreimages(ctx *cli.Context) error { - if ctx.Args().Len() < 1 { - utils.Fatalf("This command requires an argument.") - } - stack, _ := makeConfigNode(ctx) - defer stack.Close() - - db := utils.MakeChainDatabase(ctx, stack, true) - start := time.Now() - - if err := utils.ExportPreimages(db, ctx.Args().First()); err != nil { - utils.Fatalf("Export error: %v\n", err) - } - fmt.Printf("Export done in %v\n", time.Since(start)) - return nil -} - 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()) @@ -473,21 +470,16 @@ 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, false) // always enable preimage lookup + defer triedb.Close() + + state, err := state.New(root, state.NewDatabaseWithNodeDB(db, triedb), nil) if err != nil { return err } if ctx.Bool(utils.IterativeOutputFlag.Name) { state.IterativeDump(conf, json.NewEncoder(os.Stdout)) } else { - if conf.OnlyWithAddresses { - fmt.Fprintf(os.Stderr, "If you want to include accounts with missing preimages, you need iterative output, since"+ - " otherwise the accounts will overwrite each other in the resulting mapping.") - return errors.New("incompatible options") - } fmt.Println(string(state.Dump(conf))) } return nil diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 1a3de04bd..5f52f1df5 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -22,18 +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" "github.com/ethereum/go-ethereum/internal/flags" @@ -43,6 +44,7 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/naoina/toml" + "github.com/urfave/cli/v2" ) var ( @@ -177,6 +179,20 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { } 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) @@ -184,17 +200,18 @@ 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) { @@ -204,7 +221,7 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { } catalyst.RegisterSimulatedBeaconAPIs(stack, simBeacon) stack.RegisterLifecycle(simBeacon) - } else if cfg.Eth.SyncMode != downloader.LightSync { + } else { err := catalyst.Register(stack, eth) if err != nil { utils.Fatalf("failed to register catalyst service: %v", err) diff --git a/cmd/geth/consolecmd_test.go b/cmd/geth/consolecmd_test.go index 5046906c0..ef6ef5f28 100644 --- a/cmd/geth/consolecmd_test.go +++ b/cmd/geth/consolecmd_test.go @@ -50,6 +50,7 @@ func runMinimalGeth(t *testing.T, args ...string) *testgeth { // Tests that a node embedded within a console can be started up properly and // then terminated by closing the input stream. func TestConsoleWelcome(t *testing.T) { + t.Parallel() coinbase := "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" // Start a geth console, make sure it's cleaned up and terminate the console diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index aa7939851..1d885bd58 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -43,12 +43,22 @@ import ( ) var ( + removeStateDataFlag = &cli.BoolFlag{ + Name: "remove.state", + Usage: "If set, selects the state data for removal", + } + removeChainDataFlag = &cli.BoolFlag{ + Name: "remove.chain", + Usage: "If set, selects the state data for removal", + } + removedbCommand = &cli.Command{ Action: removeDB, Name: "removedb", Usage: "Remove blockchain and state databases", ArgsUsage: "", - Flags: utils.DatabasePathFlags, + Flags: flags.Merge(utils.DatabaseFlags, + []cli.Flag{removeStateDataFlag, removeChainDataFlag}), Description: ` Remove blockchain and state databases`, } @@ -77,7 +87,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 +95,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 +107,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,8 +117,8 @@ a data corruption.`, utils.SyncModeFlag, utils.CacheFlag, utils.CacheDatabaseFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), - Description: `This command performs a database compaction. + }, 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 +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 looks up the specified database key from the database.", } dbDeleteCmd = &cli.Command{ @@ -129,8 +139,8 @@ corruption if it is aborted during execution'!`, ArgsUsage: "", Flags: flags.Merge([]cli.Flag{ utils.SyncModeFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), - Description: `This command deletes the specified database key from the database. + }, 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!`, } dbPutCmd = &cli.Command{ @@ -140,8 +150,8 @@ WARNING: This is a low-level operation which may cause database corruption!`, ArgsUsage: " ", Flags: flags.Merge([]cli.Flag{ utils.SyncModeFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags), - Description: `This command sets a given database key to the given value. + }, 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!`, } dbGetSlotsCmd = &cli.Command{ @@ -151,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 looks up the specified database key from the database.", } dbDumpFreezerIndex = &cli.Command{ @@ -161,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: "This command displays information about the freezer index.", } dbImportCmd = &cli.Command{ @@ -171,7 +181,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 +200,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.", } ) @@ -198,60 +208,85 @@ WARNING: This is a low-level operation which may cause database corruption!`, func removeDB(ctx *cli.Context) error { stack, config := makeConfigNode(ctx) - // Remove the full node state database - path := stack.ResolvePath("chaindata") - if common.FileExist(path) { - confirmAndRemoveDB(path, "full node state database") - } else { - log.Info("Full node state database missing", "path", path) - } - // Remove the full node ancient database - path = config.Eth.DatabaseFreezer + // Resolve folder paths. + var ( + rootDir = stack.ResolvePath("chaindata") + ancientDir = config.Eth.DatabaseFreezer + ) switch { - case path == "": - path = filepath.Join(stack.ResolvePath("chaindata"), "ancient") - case !filepath.IsAbs(path): - path = config.Node.ResolvePath(path) - } - if common.FileExist(path) { - confirmAndRemoveDB(path, "full node ancient database") - } else { - log.Info("Full node ancient database missing", "path", path) - } - // Remove the light node database - path = stack.ResolvePath("lightchaindata") - if common.FileExist(path) { - confirmAndRemoveDB(path, "light node database") - } else { - log.Info("Light node database missing", "path", path) - } + case ancientDir == "": + ancientDir = filepath.Join(stack.ResolvePath("chaindata"), "ancient") + case !filepath.IsAbs(ancientDir): + ancientDir = config.Node.ResolvePath(ancientDir) + } + // Delete state data + statePaths := []string{rootDir, filepath.Join(ancientDir, rawdb.StateFreezerName)} + confirmAndRemoveDB(statePaths, "state data", ctx, removeStateDataFlag.Name) + + // Delete ancient chain + chainPaths := []string{filepath.Join(ancientDir, rawdb.ChainFreezerName)} + confirmAndRemoveDB(chainPaths, "ancient chain", ctx, removeChainDataFlag.Name) return nil } +// removeFolder deletes all files (not folders) inside the directory 'dir' (but +// not files in subfolders). +func removeFolder(dir string) { + filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + // If we're at the top level folder, recurse into + if path == dir { + return nil + } + // Delete all the files, but not subfolders + if !info.IsDir() { + os.Remove(path) + return nil + } + return filepath.SkipDir + }) +} + // confirmAndRemoveDB prompts the user for a last confirmation and removes the -// folder if accepted. -func confirmAndRemoveDB(database string, kind string) { - confirm, err := prompt.Stdin.PromptConfirm(fmt.Sprintf("Remove %s (%s)?", kind, database)) +// list of folders if accepted. +func confirmAndRemoveDB(paths []string, kind string, ctx *cli.Context, removeFlagName string) { + var ( + confirm bool + err error + ) + msg := fmt.Sprintf("Location(s) of '%s': \n", kind) + for _, path := range paths { + msg += fmt.Sprintf("\t- %s\n", path) + } + fmt.Println(msg) + if ctx.IsSet(removeFlagName) { + confirm = ctx.Bool(removeFlagName) + if confirm { + fmt.Printf("Remove '%s'? [y/n] y\n", kind) + } else { + fmt.Printf("Remove '%s'? [y/n] n\n", kind) + } + } else { + confirm, err = prompt.Stdin.PromptConfirm(fmt.Sprintf("Remove '%s'?", kind)) + } switch { case err != nil: utils.Fatalf("%v", err) case !confirm: - log.Info("Database deletion skipped", "path", database) + log.Info("Database deletion skipped", "kind", kind, "paths", paths) default: - start := time.Now() - filepath.Walk(database, func(path string, info os.FileInfo, err error) error { - // If we're at the top level folder, recurse into - if path == database { - return nil + var ( + deleted []string + start = time.Now() + ) + for _, path := range paths { + if common.FileExist(path) { + removeFolder(path) + deleted = append(deleted, path) + } else { + log.Info("Folder is not existent", "path", path) } - // Delete all the files, but not subfolders - if !info.IsDir() { - os.Remove(path) - return nil - } - return filepath.SkipDir - }) - log.Info("Database successfully deleted", "path", database, "elapsed", common.PrettyDuration(time.Since(start))) + } + log.Info("Database successfully deleted", "kind", kind, "paths", deleted, "elapsed", common.PrettyDuration(time.Since(start))) } } @@ -482,6 +517,9 @@ func dbDumpTrie(ctx *cli.Context) error { db := utils.MakeChainDatabase(ctx, stack, true) defer db.Close() + triedb := utils.MakeTrieDatabase(ctx, db, false, true, false) + defer triedb.Close() + var ( state []byte storage []byte @@ -515,7 +553,7 @@ 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 } @@ -591,6 +629,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) } @@ -687,6 +726,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) } @@ -694,6 +734,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/exportcmd_test.go b/cmd/geth/exportcmd_test.go index bbf08d820..9570b1ffd 100644 --- a/cmd/geth/exportcmd_test.go +++ b/cmd/geth/exportcmd_test.go @@ -27,6 +27,7 @@ import ( // TestExport does a basic test of "geth export", exporting the test-genesis. func TestExport(t *testing.T) { + t.Parallel() outfile := fmt.Sprintf("%v/testExport.out", os.TempDir()) defer os.Remove(outfile) geth := runGeth(t, "--datadir", initGeth(t), "export", outfile) diff --git a/cmd/geth/les_test.go b/cmd/geth/les_test.go deleted file mode 100644 index b36c3265a..000000000 --- a/cmd/geth/les_test.go +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2020 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "context" - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - "sync/atomic" - "testing" - "time" - - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/rpc" -) - -type gethrpc struct { - name string - rpc *rpc.Client - geth *testgeth - nodeInfo *p2p.NodeInfo -} - -func (g *gethrpc) killAndWait() { - g.geth.Kill() - g.geth.WaitExit() -} - -func (g *gethrpc) callRPC(result interface{}, method string, args ...interface{}) { - if err := g.rpc.Call(&result, method, args...); err != nil { - g.geth.Fatalf("callRPC %v: %v", method, err) - } -} - -func (g *gethrpc) addPeer(peer *gethrpc) { - g.geth.Logf("%v.addPeer(%v)", g.name, peer.name) - enode := peer.getNodeInfo().Enode - peerCh := make(chan *p2p.PeerEvent) - sub, err := g.rpc.Subscribe(context.Background(), "admin", peerCh, "peerEvents") - if err != nil { - g.geth.Fatalf("subscribe %v: %v", g.name, err) - } - defer sub.Unsubscribe() - g.callRPC(nil, "admin_addPeer", enode) - dur := 14 * time.Second - timeout := time.After(dur) - select { - case ev := <-peerCh: - g.geth.Logf("%v received event: type=%v, peer=%v", g.name, ev.Type, ev.Peer) - case err := <-sub.Err(): - g.geth.Fatalf("%v sub error: %v", g.name, err) - case <-timeout: - g.geth.Error("timeout adding peer after", dur) - } -} - -// Use this function instead of `g.nodeInfo` directly -func (g *gethrpc) getNodeInfo() *p2p.NodeInfo { - if g.nodeInfo != nil { - return g.nodeInfo - } - g.nodeInfo = &p2p.NodeInfo{} - g.callRPC(&g.nodeInfo, "admin_nodeInfo") - return g.nodeInfo -} - -// ipcEndpoint resolves an IPC endpoint based on a configured value, taking into -// account the set data folders as well as the designated platform we're currently -// running on. -func ipcEndpoint(ipcPath, datadir string) string { - // On windows we can only use plain top-level pipes - if runtime.GOOS == "windows" { - if strings.HasPrefix(ipcPath, `\\.\pipe\`) { - return ipcPath - } - return `\\.\pipe\` + ipcPath - } - // Resolve names into the data directory full paths otherwise - if filepath.Base(ipcPath) == ipcPath { - if datadir == "" { - return filepath.Join(os.TempDir(), ipcPath) - } - return filepath.Join(datadir, ipcPath) - } - return ipcPath -} - -// nextIPC ensures that each ipc pipe gets a unique name. -// On linux, it works well to use ipc pipes all over the filesystem (in datadirs), -// 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 atomic.Uint32 - -func startGethWithIpc(t *testing.T, name string, args ...string) *gethrpc { - 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) - - g := &gethrpc{ - name: name, - geth: runGeth(t, args...), - } - ipcpath := ipcEndpoint(ipcName, g.geth.Datadir) - // We can't know exactly how long geth will take to start, so we try 10 - // times over a 5 second period. - var err error - for i := 0; i < 10; i++ { - time.Sleep(500 * time.Millisecond) - if g.rpc, err = rpc.Dial(ipcpath); err == nil { - return g - } - } - t.Fatalf("%v rpc connect to %v: %v", name, ipcpath, err) - return nil -} - -func initGeth(t *testing.T) string { - args := []string{"--networkid=42", "init", "./testdata/clique.json"} - t.Logf("Initializing geth: %v ", args) - g := runGeth(t, args...) - datadir := g.Datadir - g.WaitExit() - return datadir -} - -func startLightServer(t *testing.T) *gethrpc { - datadir := initGeth(t) - 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", "--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, "--discv4=false", "--syncmode=light", "--nat=extip:127.0.0.1", "--verbosity=4") -} - -func TestPriorityClient(t *testing.T) { - lightServer := startLightServer(t) - defer lightServer.killAndWait() - - // Start client and add lightServer as peer - freeCli := startClient(t, "freeCli") - defer freeCli.killAndWait() - freeCli.addPeer(lightServer) - - var peers []*p2p.PeerInfo - freeCli.callRPC(&peers, "admin_peers") - if len(peers) != 1 { - t.Errorf("Expected: # of client peers == 1, actual: %v", len(peers)) - return - } - - // Set up priority client, get its nodeID, increase its balance on the lightServer - prioCli := startClient(t, "prioCli") - defer prioCli.killAndWait() - // 3_000_000_000 once we move to Go 1.13 - tokens := uint64(3000000000) - lightServer.callRPC(nil, "les_addBalance", prioCli.getNodeInfo().ID, tokens) - prioCli.addPeer(lightServer) - - // Check if priority client is actually syncing and the regular client got kicked out - prioCli.callRPC(&peers, "admin_peers") - if len(peers) != 1 { - t.Errorf("Expected: # of prio peers == 1, actual: %v", len(peers)) - } - - nodes := map[string]*gethrpc{ - lightServer.getNodeInfo().ID: lightServer, - freeCli.getNodeInfo().ID: freeCli, - prioCli.getNodeInfo().ID: prioCli, - } - time.Sleep(1 * time.Second) - lightServer.callRPC(&peers, "admin_peers") - peersWithNames := make(map[string]string) - for _, p := range peers { - peersWithNames[nodes[p.ID].name] = p.ID - } - if _, freeClientFound := peersWithNames[freeCli.name]; freeClientFound { - t.Error("client is still a peer of lightServer", peersWithNames) - } - if _, prioClientFound := peersWithNames[prioCli.name]; !prioClientFound { - t.Error("prio client is not among lightServer peers", peersWithNames) - } -} diff --git a/cmd/geth/logging_test.go b/cmd/geth/logging_test.go new file mode 100644 index 000000000..b5ce03f4b --- /dev/null +++ b/cmd/geth/logging_test.go @@ -0,0 +1,237 @@ +//go:build integrationtests + +// Copyright 2023 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "math/rand" + "os" + "os/exec" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/internal/reexec" +) + +func runSelf(args ...string) ([]byte, error) { + cmd := &exec.Cmd{ + Path: reexec.Self(), + Args: append([]string{"geth-test"}, args...), + } + return cmd.CombinedOutput() +} + +func split(input io.Reader) []string { + var output []string + scanner := bufio.NewScanner(input) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + output = append(output, strings.TrimSpace(scanner.Text())) + } + return output +} + +func censor(input string, start, end int) string { + if len(input) < end { + return input + } + return input[:start] + strings.Repeat("X", end-start) + input[end:] +} + +func TestLogging(t *testing.T) { + t.Parallel() + testConsoleLogging(t, "terminal", 6, 24) + testConsoleLogging(t, "logfmt", 2, 26) +} + +func testConsoleLogging(t *testing.T, format string, tStart, tEnd int) { + haveB, err := runSelf("--log.format", format, "logtest") + if err != nil { + t.Fatal(err) + } + readFile, err := os.Open(fmt.Sprintf("testdata/logging/logtest-%v.txt", format)) + if err != nil { + t.Fatal(err) + } + wantLines := split(readFile) + haveLines := split(bytes.NewBuffer(haveB)) + for i, want := range wantLines { + if i > len(haveLines)-1 { + t.Fatalf("format %v, line %d missing, want:%v", format, i, want) + } + have := haveLines[i] + for strings.Contains(have, "Unknown config environment variable") { + // This can happen on CI runs. Drop it. + haveLines = append(haveLines[:i], haveLines[i+1:]...) + have = haveLines[i] + } + + // Black out the timestamp + have = censor(have, tStart, tEnd) + want = censor(want, tStart, tEnd) + if have != want { + t.Logf(nicediff([]byte(have), []byte(want))) + t.Fatalf("format %v, line %d\nhave %v\nwant %v", format, i, have, want) + } + } + if len(haveLines) != len(wantLines) { + t.Errorf("format %v, want %d lines, have %d", format, len(haveLines), len(wantLines)) + } +} + +func TestJsonLogging(t *testing.T) { + t.Parallel() + haveB, err := runSelf("--log.format", "json", "logtest") + if err != nil { + t.Fatal(err) + } + readFile, err := os.Open("testdata/logging/logtest-json.txt") + if err != nil { + t.Fatal(err) + } + wantLines := split(readFile) + haveLines := split(bytes.NewBuffer(haveB)) + for i, wantLine := range wantLines { + if i > len(haveLines)-1 { + t.Fatalf("format %v, line %d missing, want:%v", "json", i, wantLine) + } + haveLine := haveLines[i] + for strings.Contains(haveLine, "Unknown config environment variable") { + // This can happen on CI runs. Drop it. + haveLines = append(haveLines[:i], haveLines[i+1:]...) + haveLine = haveLines[i] + } + var have, want []byte + { + var h map[string]any + if err := json.Unmarshal([]byte(haveLine), &h); err != nil { + t.Fatal(err) + } + h["t"] = "xxx" + have, _ = json.Marshal(h) + } + { + var w map[string]any + if err := json.Unmarshal([]byte(wantLine), &w); err != nil { + t.Fatal(err) + } + w["t"] = "xxx" + want, _ = json.Marshal(w) + } + if !bytes.Equal(have, want) { + // show an intelligent diff + t.Logf(nicediff(have, want)) + t.Errorf("file content wrong") + } + } +} + +func TestVmodule(t *testing.T) { + t.Parallel() + checkOutput := func(level int, want, wantNot string) { + t.Helper() + output, err := runSelf("--log.format", "terminal", "--verbosity=0", "--log.vmodule", fmt.Sprintf("logtestcmd_active.go=%d", level), "logtest") + if err != nil { + t.Fatal(err) + } + if len(want) > 0 && !strings.Contains(string(output), want) { // trace should be present at 5 + t.Errorf("failed to find expected string ('%s') in output", want) + } + if len(wantNot) > 0 && strings.Contains(string(output), wantNot) { // trace should be present at 5 + t.Errorf("string ('%s') should not be present in output", wantNot) + } + } + checkOutput(5, "log at level trace", "") // trace should be present at 5 + checkOutput(4, "log at level debug", "log at level trace") // debug should be present at 4, but trace should be missing + checkOutput(3, "log at level info", "log at level debug") // info should be present at 3, but debug should be missing + checkOutput(2, "log at level warn", "log at level info") // warn should be present at 2, but info should be missing + checkOutput(1, "log at level error", "log at level warn") // error should be present at 1, but warn should be missing +} + +func nicediff(have, want []byte) string { + var i = 0 + for ; i < len(have) && i < len(want); i++ { + if want[i] != have[i] { + break + } + } + var end = i + 40 + var start = i - 50 + if start < 0 { + start = 0 + } + var h, w string + if end < len(have) { + h = string(have[start:end]) + } else { + h = string(have[start:]) + } + if end < len(want) { + w = string(want[start:end]) + } else { + w = string(want[start:]) + } + return fmt.Sprintf("have vs want:\n%q\n%q\n", h, w) +} + +func TestFileOut(t *testing.T) { + t.Parallel() + var ( + have, want []byte + err error + path = fmt.Sprintf("%s/test_file_out-%d", os.TempDir(), rand.Int63()) + ) + t.Cleanup(func() { os.Remove(path) }) + if want, err = runSelf(fmt.Sprintf("--log.file=%s", path), "logtest"); err != nil { + t.Fatal(err) + } + if have, err = os.ReadFile(path); err != nil { + t.Fatal(err) + } + if !bytes.Equal(have, want) { + // show an intelligent diff + t.Logf(nicediff(have, want)) + t.Errorf("file content wrong") + } +} + +func TestRotatingFileOut(t *testing.T) { + t.Parallel() + var ( + have, want []byte + err error + path = fmt.Sprintf("%s/test_file_out-%d", os.TempDir(), rand.Int63()) + ) + t.Cleanup(func() { os.Remove(path) }) + if want, err = runSelf(fmt.Sprintf("--log.file=%s", path), "--log.rotate", "logtest"); err != nil { + t.Fatal(err) + } + if have, err = os.ReadFile(path); err != nil { + t.Fatal(err) + } + if !bytes.Equal(have, want) { + // show an intelligent diff + t.Logf(nicediff(have, want)) + t.Errorf("file content wrong") + } +} diff --git a/cmd/geth/logtestcmd_active.go b/cmd/geth/logtestcmd_active.go new file mode 100644 index 000000000..5cce1ec6a --- /dev/null +++ b/cmd/geth/logtestcmd_active.go @@ -0,0 +1,175 @@ +//go:build integrationtests + +// Copyright 2023 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "errors" + "fmt" + "math" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/internal/debug" + "github.com/ethereum/go-ethereum/log" + "github.com/holiman/uint256" + "github.com/urfave/cli/v2" +) + +var logTestCommand = &cli.Command{ + Action: logTest, + Name: "logtest", + Usage: "Print some log messages", + ArgsUsage: " ", + Description: ` +This command is only meant for testing. +`} + +type customQuotedStringer struct { +} + +func (c customQuotedStringer) String() string { + return "output with 'quotes'" +} + +// logTest is an entry point which spits out some logs. This is used by testing +// to verify expected outputs +func logTest(ctx *cli.Context) error { + // clear field padding map + debug.ResetLogging() + + { // big.Int + ba, _ := new(big.Int).SetString("111222333444555678999", 10) // "111,222,333,444,555,678,999" + bb, _ := new(big.Int).SetString("-111222333444555678999", 10) // "-111,222,333,444,555,678,999" + bc, _ := new(big.Int).SetString("11122233344455567899900", 10) // "11,122,233,344,455,567,899,900" + bd, _ := new(big.Int).SetString("-11122233344455567899900", 10) // "-11,122,233,344,455,567,899,900" + log.Info("big.Int", "111,222,333,444,555,678,999", ba) + log.Info("-big.Int", "-111,222,333,444,555,678,999", bb) + log.Info("big.Int", "11,122,233,344,455,567,899,900", bc) + log.Info("-big.Int", "-11,122,233,344,455,567,899,900", bd) + } + { //uint256 + ua, _ := uint256.FromDecimal("111222333444555678999") + ub, _ := uint256.FromDecimal("11122233344455567899900") + log.Info("uint256", "111,222,333,444,555,678,999", ua) + log.Info("uint256", "11,122,233,344,455,567,899,900", ub) + } + { // int64 + log.Info("int64", "1,000,000", int64(1000000)) + log.Info("int64", "-1,000,000", int64(-1000000)) + log.Info("int64", "9,223,372,036,854,775,807", int64(math.MaxInt64)) + log.Info("int64", "-9,223,372,036,854,775,808", int64(math.MinInt64)) + } + { // uint64 + log.Info("uint64", "1,000,000", uint64(1000000)) + log.Info("uint64", "18,446,744,073,709,551,615", uint64(math.MaxUint64)) + } + { // Special characters + log.Info("Special chars in value", "key", "special \r\n\t chars") + log.Info("Special chars in key", "special \n\t chars", "value") + + log.Info("nospace", "nospace", "nospace") + log.Info("with space", "with nospace", "with nospace") + + log.Info("Bash escapes in value", "key", "\u001b[1G\u001b[K\u001b[1A") + log.Info("Bash escapes in key", "\u001b[1G\u001b[K\u001b[1A", "value") + + log.Info("Bash escapes in message \u001b[1G\u001b[K\u001b[1A end", "key", "value") + + colored := fmt.Sprintf("\u001B[%dmColored\u001B[0m[", 35) + log.Info(colored, colored, colored) + err := errors.New("this is an 'error'") + log.Info("an error message with quotes", "error", err) + } + { // Custom Stringer() - type + log.Info("Custom Stringer value", "2562047h47m16.854s", common.PrettyDuration(time.Duration(9223372036854775807))) + var c customQuotedStringer + log.Info("a custom stringer that emits quoted text", "output", c) + } + { // Multi-line message + log.Info("A message with wonky \U0001F4A9 characters") + log.Info("A multiline message \nINFO [10-18|14:11:31.106] with wonky characters \U0001F4A9") + log.Info("A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above") + } + { // Miscellaneous json-quirks + // This will check if the json output uses strings or json-booleans to represent bool values + log.Info("boolean", "true", true, "false", false) + // Handling of duplicate keys. + // This is actually ill-handled by the current handler: the format.go + // uses a global 'fieldPadding' map and mixes up the two keys. If 'alpha' + // is shorter than beta, it sometimes causes erroneous padding -- and what's more + // it causes _different_ padding in multi-handler context, e.g. both file- + // and console output, making the two mismatch. + log.Info("repeated-key 1", "foo", "alpha", "foo", "beta") + log.Info("repeated-key 2", "xx", "short", "xx", "longer") + } + { // loglevels + log.Debug("log at level debug") + log.Trace("log at level trace") + log.Info("log at level info") + log.Warn("log at level warn") + log.Error("log at level error") + } + { + // The current log formatter has a global map of paddings, storing the + // longest seen padding per key in a map. This results in a statefulness + // which has some odd side-effects. Demonstrated here: + log.Info("test", "bar", "short", "a", "aligned left") + log.Info("test", "bar", "a long message", "a", 1) + log.Info("test", "bar", "short", "a", "aligned right") + } + { + // This sequence of logs should be output with alignment, so each field becoems a column. + log.Info("The following logs should align so that the key-fields make 5 columns") + log.Info("Inserted known block", "number", 1_012, "hash", common.HexToHash("0x1234"), "txs", 200, "gas", 1_123_123, "other", "first") + log.Info("Inserted new block", "number", 1, "hash", common.HexToHash("0x1235"), "txs", 2, "gas", 1_123, "other", "second") + log.Info("Inserted known block", "number", 99, "hash", common.HexToHash("0x12322"), "txs", 10, "gas", 1, "other", "third") + log.Warn("Inserted known block", "number", 1_012, "hash", common.HexToHash("0x1234"), "txs", 200, "gas", 99, "other", "fourth") + } + { // Various types of nil + type customStruct struct { + A string + B *uint64 + } + log.Info("(*big.Int)(nil)", "", (*big.Int)(nil)) + log.Info("(*uint256.Int)(nil)", "", (*uint256.Int)(nil)) + log.Info("(fmt.Stringer)(nil)", "res", (fmt.Stringer)(nil)) + log.Info("nil-concrete-stringer", "res", (*time.Time)(nil)) + + log.Info("error(nil) ", "res", error(nil)) + log.Info("nil-concrete-error", "res", (*customError)(nil)) + + log.Info("nil-custom-struct", "res", (*customStruct)(nil)) + log.Info("raw nil", "res", nil) + log.Info("(*uint64)(nil)", "res", (*uint64)(nil)) + } + { // Logging with 'reserved' keys + log.Info("Using keys 't', 'lvl', 'time', 'level' and 'msg'", "t", "t", "time", "time", "lvl", "lvl", "level", "level", "msg", "msg") + } + { // Logging with wrong attr-value pairs + log.Info("Odd pair (1 attr)", "key") + log.Info("Odd pair (3 attr)", "key", "value", "key2") + } + return nil +} + +// customError is a type which implements error +type customError struct{} + +func (c *customError) Error() string { return "" } diff --git a/cmd/geth/logtestcmd_inactive.go b/cmd/geth/logtestcmd_inactive.go new file mode 100644 index 000000000..691ab5bcd --- /dev/null +++ b/cmd/geth/logtestcmd_inactive.go @@ -0,0 +1,23 @@ +//go:build !integrationtests + +// Copyright 2023 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import "github.com/urfave/cli/v2" + +var logTestCommand *cli.Command diff --git a/cmd/geth/main.go b/cmd/geth/main.go index dbe3d7471..8b3c018bc 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -62,7 +62,7 @@ var ( utils.MinFreeDiskSpaceFlag, utils.KeyStoreDirFlag, utils.ExternalSignerFlag, - utils.NoUSBFlag, + utils.NoUSBFlag, // deprecated utils.USBFlag, utils.SmartCardDaemonPathFlag, utils.OverrideCancun, @@ -87,22 +87,24 @@ var ( utils.ExitWhenSyncedFlag, utils.GCModeFlag, utils.SnapshotFlag, - utils.TxLookupLimitFlag, - utils.LightServeFlag, - utils.LightIngressFlag, - utils.LightEgressFlag, - utils.LightMaxPeersFlag, - utils.LightNoPruneFlag, + utils.TxLookupLimitFlag, // deprecated + utils.TransactionHistoryFlag, + utils.StateHistoryFlag, + utils.LightServeFlag, // deprecated + utils.LightIngressFlag, // deprecated + utils.LightEgressFlag, // deprecated + utils.LightMaxPeersFlag, // deprecated + utils.LightNoPruneFlag, // deprecated utils.LightKDFFlag, - utils.LightNoSyncServeFlag, + utils.LightNoSyncServeFlag, // deprecated utils.EthRequiredBlocksFlag, - utils.LegacyWhitelistFlag, + utils.LegacyWhitelistFlag, // deprecated utils.BloomFilterSizeFlag, utils.CacheFlag, utils.CacheDatabaseFlag, utils.CacheTrieFlag, - utils.CacheTrieJournalFlag, - utils.CacheTrieRejournalFlag, + utils.CacheTrieJournalFlag, // deprecated + utils.CacheTrieRejournalFlag, // deprecated utils.CacheGCFlag, utils.CacheSnapshotFlag, utils.CacheNoPrefetchFlag, @@ -125,7 +127,7 @@ var ( utils.NoDiscoverFlag, utils.DiscoveryV4Flag, utils.DiscoveryV5Flag, - utils.LegacyDiscoveryV5Flag, + utils.LegacyDiscoveryV5Flag, // deprecated utils.NetrestrictFlag, utils.NodeKeyFileFlag, utils.NodeKeyHexFlag, @@ -142,10 +144,12 @@ var ( utils.GpoMaxGasPriceFlag, utils.GpoIgnoreGasPriceFlag, configFileFlag, + utils.LogDebugFlag, + utils.LogBacktraceAtFlag, utils.BlockReplicationTargetsFlag, utils.ReplicaEnableResultFlag, utils.ReplicaEnableSpecimenFlag, - }, utils.NetworkFlags, utils.DatabasePathFlags) + }, utils.NetworkFlags, utils.DatabaseFlags) rpcFlags = []cli.Flag{ utils.HTTPEnabledFlag, @@ -202,14 +206,12 @@ var app = flags.NewApp("the go-ethereum command line interface") func init() { // Initialize the CLI app and start Geth app.Action = geth - app.Copyright = "Copyright 2013-2023 The go-ethereum Authors" app.Commands = []*cli.Command{ // See chaincmd.go: initCommand, importCommand, exportCommand, importPreimagesCommand, - exportPreimagesCommand, removedbCommand, dumpCommand, dumpGenesisCommand, @@ -235,6 +237,9 @@ func init() { // See verkle.go verkleCommand, } + if logTestCommand != nil { + app.Commands = append(app.Commands, logTestCommand) + } sort.Sort(cli.CommandsByName(app.Commands)) app.Flags = flags.Merge( @@ -244,11 +249,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() @@ -275,6 +285,9 @@ func prepare(ctx *cli.Context) { 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: @@ -297,9 +310,10 @@ func prepare(ctx *cli.Context) { log.Info("Starting Geth on Ethereum mainnet...") } // 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) { + if !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) && + 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! @@ -307,11 +321,6 @@ func prepare(ctx *cli.Context) { ctx.Set(utils.CacheFlag.Name, strconv.Itoa(4096)) } } - // If we're running a light client on any network, drop the cache to some meaningfully low amount - if ctx.String(utils.SyncModeFlag.Name) == "light" && !ctx.IsSet(utils.CacheFlag.Name) { - log.Info("Dropping default light client cache", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 128) - ctx.Set(utils.CacheFlag.Name, strconv.Itoa(128)) - } // Start metrics export if enabled utils.SetupMetrics(ctx) diff --git a/cmd/geth/run_test.go b/cmd/geth/run_test.go index 0588623ac..1d3288032 100644 --- a/cmd/geth/run_test.go +++ b/cmd/geth/run_test.go @@ -23,8 +23,8 @@ import ( "testing" "time" - "github.com/docker/docker/pkg/reexec" "github.com/ethereum/go-ethereum/internal/cmdtest" + "github.com/ethereum/go-ethereum/internal/reexec" "github.com/ethereum/go-ethereum/rpc" ) @@ -55,6 +55,15 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +func initGeth(t *testing.T) string { + args := []string{"--networkid=42", "init", "./testdata/clique.json"} + t.Logf("Initializing geth: %v ", args) + g := runGeth(t, args...) + datadir := g.Datadir + g.WaitExit() + return datadir +} + // spawns geth with the given command line args. If the args don't set --datadir, the // child g gets a temporary data directory. func runGeth(t *testing.T, args ...string) *testgeth { diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index fafc82760..4284005a0 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "os" "time" @@ -51,7 +52,7 @@ var ( Action: pruneState, Flags: flags.Merge([]cli.Flag{ 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. @@ -61,10 +62,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)". `, }, { @@ -72,7 +70,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 @@ -85,10 +83,10 @@ 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. +geth snapshot check-dangling-storage traverses the snap storage +data, and verifies that all snapshot storage data has a corresponding account. `, }, { @@ -96,10 +94,10 @@ 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. +information about the specified address. `, }, { @@ -107,7 +105,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 @@ -122,13 +120,13 @@ 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 trie node or contract code is missing. This command can be used for state integrity verification. The default checking target is the HEAD state. It's basically identical -to traverse-state, but the check granularity is smaller. +to traverse-state, but the check granularity is smaller. It's also usable without snapshot enabled. `, @@ -143,13 +141,24 @@ 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. +as the backend data source, making this command a lot faster. The argument is interpreted as block number or hash. If none is provided, the latest block is used. +`, + }, + { + Action: snapshotExportPreimages, + Name: "export-preimages", + Usage: "Export the preimage in snapshot enumeration order", + ArgsUsage: " []", + Flags: utils.DatabaseFlags, + Description: ` +The export-preimages command exports hash preimages to a flat file, in exactly +the expected order for the overlay tree migration. `, }, }, @@ -165,6 +174,9 @@ func pruneState(ctx *cli.Context) error { 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(""), BloomSize: ctx.Uint64(utils.BloomFilterSizeFlag.Name), @@ -205,13 +217,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, false) + 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 @@ -242,7 +257,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. @@ -253,6 +270,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, false) + defer triedb.Close() + headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") @@ -277,7 +299,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) @@ -318,6 +339,11 @@ func traverseState(ctx *cli.Context) error { 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) @@ -353,6 +379,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, false) + defer triedb.Close() + headBlock := rawdb.ReadHeadBlock(chaindb) if headBlock == nil { log.Error("Failed to load head block") @@ -377,7 +408,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) @@ -398,6 +428,11 @@ func traverseRawState(ctx *cli.Context) error { 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() @@ -405,7 +440,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") @@ -446,7 +481,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") @@ -463,6 +498,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()) @@ -506,13 +545,16 @@ func dumpState(ctx *cli.Context) error { if err != nil { return err } + triedb := utils.MakeTrieDatabase(ctx, db, false, true, false) + 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 } @@ -538,11 +580,11 @@ func dumpState(ctx *cli.Context) error { return err } da := &state.DumpAccount{ - Balance: account.Balance.String(), - Nonce: account.Nonce, - Root: account.Root.Bytes(), - CodeHash: account.CodeHash, - SecureKey: accIt.Hash().Bytes(), + Balance: account.Balance.String(), + Nonce: account.Nonce, + Root: account.Root.Bytes(), + CodeHash: account.CodeHash, + AddressHash: accIt.Hash().Bytes(), } if !conf.SkipCode && !bytes.Equal(account.CodeHash, types.EmptyCodeHash.Bytes()) { da.Code = rawdb.ReadCode(db, common.BytesToHash(account.CodeHash)) @@ -574,6 +616,48 @@ func dumpState(ctx *cli.Context) error { return nil } +// snapshotExportPreimages dumps the preimage data to a flat file. +func snapshotExportPreimages(ctx *cli.Context) error { + if ctx.NArg() < 1 { + utils.Fatalf("This command requires an argument.") + } + stack, _ := makeConfigNode(ctx) + defer stack.Close() + + chaindb := utils.MakeChainDatabase(ctx, stack, true) + defer chaindb.Close() + + triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false) + defer triedb.Close() + + var root common.Hash + if ctx.NArg() > 1 { + rootBytes := common.FromHex(ctx.Args().Get(1)) + if len(rootBytes) != common.HashLength { + return fmt.Errorf("invalid hash: %s", ctx.Args().Get(1)) + } + root = common.BytesToHash(rootBytes) + } else { + headBlock := rawdb.ReadHeadBlock(chaindb) + if headBlock == nil { + log.Error("Failed to load head block") + return errors.New("no head block") + } + root = headBlock.Root() + } + snapConfig := snapshot.Config{ + CacheSize: 256, + Recovery: false, + NoBuild: true, + AsyncBuild: false, + } + snaptree, err := snapshot.New(snapConfig, chaindb, triedb, root) + if err != nil { + return err + } + return utils.ExportSnapshotPreimages(chaindb, snaptree, ctx.Args().First(), root) +} + // checkAccount iterates the snap data layers, and looks up the given account // across all layers. func checkAccount(ctx *cli.Context) error { diff --git a/cmd/geth/testdata/logging/logtest-json.txt b/cmd/geth/testdata/logging/logtest-json.txt new file mode 100644 index 000000000..d2bd0ad91 --- /dev/null +++ b/cmd/geth/testdata/logging/logtest-json.txt @@ -0,0 +1,52 @@ +{"t":"2023-11-22T15:42:00.407963+08:00","lvl":"info","msg":"big.Int","111,222,333,444,555,678,999":"111222333444555678999"} +{"t":"2023-11-22T15:42:00.408084+08:00","lvl":"info","msg":"-big.Int","-111,222,333,444,555,678,999":"-111222333444555678999"} +{"t":"2023-11-22T15:42:00.408092+08:00","lvl":"info","msg":"big.Int","11,122,233,344,455,567,899,900":"11122233344455567899900"} +{"t":"2023-11-22T15:42:00.408097+08:00","lvl":"info","msg":"-big.Int","-11,122,233,344,455,567,899,900":"-11122233344455567899900"} +{"t":"2023-11-22T15:42:00.408127+08:00","lvl":"info","msg":"uint256","111,222,333,444,555,678,999":"111222333444555678999"} +{"t":"2023-11-22T15:42:00.408133+08:00","lvl":"info","msg":"uint256","11,122,233,344,455,567,899,900":"11122233344455567899900"} +{"t":"2023-11-22T15:42:00.408137+08:00","lvl":"info","msg":"int64","1,000,000":1000000} +{"t":"2023-11-22T15:42:00.408145+08:00","lvl":"info","msg":"int64","-1,000,000":-1000000} +{"t":"2023-11-22T15:42:00.408149+08:00","lvl":"info","msg":"int64","9,223,372,036,854,775,807":9223372036854775807} +{"t":"2023-11-22T15:42:00.408153+08:00","lvl":"info","msg":"int64","-9,223,372,036,854,775,808":-9223372036854775808} +{"t":"2023-11-22T15:42:00.408156+08:00","lvl":"info","msg":"uint64","1,000,000":1000000} +{"t":"2023-11-22T15:42:00.40816+08:00","lvl":"info","msg":"uint64","18,446,744,073,709,551,615":18446744073709551615} +{"t":"2023-11-22T15:42:00.408164+08:00","lvl":"info","msg":"Special chars in value","key":"special \r\n\t chars"} +{"t":"2023-11-22T15:42:00.408167+08:00","lvl":"info","msg":"Special chars in key","special \n\t chars":"value"} +{"t":"2023-11-22T15:42:00.408171+08:00","lvl":"info","msg":"nospace","nospace":"nospace"} +{"t":"2023-11-22T15:42:00.408174+08:00","lvl":"info","msg":"with space","with nospace":"with nospace"} +{"t":"2023-11-22T15:42:00.408178+08:00","lvl":"info","msg":"Bash escapes in value","key":"\u001b[1G\u001b[K\u001b[1A"} +{"t":"2023-11-22T15:42:00.408182+08:00","lvl":"info","msg":"Bash escapes in key","\u001b[1G\u001b[K\u001b[1A":"value"} +{"t":"2023-11-22T15:42:00.408186+08:00","lvl":"info","msg":"Bash escapes in message \u001b[1G\u001b[K\u001b[1A end","key":"value"} +{"t":"2023-11-22T15:42:00.408194+08:00","lvl":"info","msg":"\u001b[35mColored\u001b[0m[","\u001b[35mColored\u001b[0m[":"\u001b[35mColored\u001b[0m["} +{"t":"2023-11-22T15:42:00.408197+08:00","lvl":"info","msg":"an error message with quotes","error":"this is an 'error'"} +{"t":"2023-11-22T15:42:00.408202+08:00","lvl":"info","msg":"Custom Stringer value","2562047h47m16.854s":"2562047h47m16.854s"} +{"t":"2023-11-22T15:42:00.408208+08:00","lvl":"info","msg":"a custom stringer that emits quoted text","output":"output with 'quotes'"} +{"t":"2023-11-22T15:42:00.408219+08:00","lvl":"info","msg":"A message with wonky 💩 characters"} +{"t":"2023-11-22T15:42:00.408222+08:00","lvl":"info","msg":"A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩"} +{"t":"2023-11-22T15:42:00.408226+08:00","lvl":"info","msg":"A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above"} +{"t":"2023-11-22T15:42:00.408229+08:00","lvl":"info","msg":"boolean","true":true,"false":false} +{"t":"2023-11-22T15:42:00.408234+08:00","lvl":"info","msg":"repeated-key 1","foo":"alpha","foo":"beta"} +{"t":"2023-11-22T15:42:00.408237+08:00","lvl":"info","msg":"repeated-key 2","xx":"short","xx":"longer"} +{"t":"2023-11-22T15:42:00.408241+08:00","lvl":"info","msg":"log at level info"} +{"t":"2023-11-22T15:42:00.408244+08:00","lvl":"warn","msg":"log at level warn"} +{"t":"2023-11-22T15:42:00.408247+08:00","lvl":"error","msg":"log at level error"} +{"t":"2023-11-22T15:42:00.408251+08:00","lvl":"info","msg":"test","bar":"short","a":"aligned left"} +{"t":"2023-11-22T15:42:00.408254+08:00","lvl":"info","msg":"test","bar":"a long message","a":1} +{"t":"2023-11-22T15:42:00.408258+08:00","lvl":"info","msg":"test","bar":"short","a":"aligned right"} +{"t":"2023-11-22T15:42:00.408261+08:00","lvl":"info","msg":"The following logs should align so that the key-fields make 5 columns"} +{"t":"2023-11-22T15:42:00.408275+08:00","lvl":"info","msg":"Inserted known block","number":1012,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","txs":200,"gas":1123123,"other":"first"} +{"t":"2023-11-22T15:42:00.408281+08:00","lvl":"info","msg":"Inserted new block","number":1,"hash":"0x0000000000000000000000000000000000000000000000000000000000001235","txs":2,"gas":1123,"other":"second"} +{"t":"2023-11-22T15:42:00.408287+08:00","lvl":"info","msg":"Inserted known block","number":99,"hash":"0x0000000000000000000000000000000000000000000000000000000000012322","txs":10,"gas":1,"other":"third"} +{"t":"2023-11-22T15:42:00.408296+08:00","lvl":"warn","msg":"Inserted known block","number":1012,"hash":"0x0000000000000000000000000000000000000000000000000000000000001234","txs":200,"gas":99,"other":"fourth"} +{"t":"2023-11-22T15:42:00.4083+08:00","lvl":"info","msg":"(*big.Int)(nil)","":""} +{"t":"2023-11-22T15:42:00.408303+08:00","lvl":"info","msg":"(*uint256.Int)(nil)","":""} +{"t":"2023-11-22T15:42:00.408311+08:00","lvl":"info","msg":"(fmt.Stringer)(nil)","res":null} +{"t":"2023-11-22T15:42:00.408318+08:00","lvl":"info","msg":"nil-concrete-stringer","res":""} +{"t":"2023-11-22T15:42:00.408322+08:00","lvl":"info","msg":"error(nil) ","res":null} +{"t":"2023-11-22T15:42:00.408326+08:00","lvl":"info","msg":"nil-concrete-error","res":""} +{"t":"2023-11-22T15:42:00.408334+08:00","lvl":"info","msg":"nil-custom-struct","res":null} +{"t":"2023-11-22T15:42:00.40835+08:00","lvl":"info","msg":"raw nil","res":null} +{"t":"2023-11-22T15:42:00.408354+08:00","lvl":"info","msg":"(*uint64)(nil)","res":null} +{"t":"2023-11-22T15:42:00.408361+08:00","lvl":"info","msg":"Using keys 't', 'lvl', 'time', 'level' and 'msg'","t":"t","time":"time","lvl":"lvl","level":"level","msg":"msg"} +{"t":"2023-11-29T15:13:00.195655931+01:00","lvl":"info","msg":"Odd pair (1 attr)","key":null,"LOG_ERROR":"Normalized odd number of arguments by adding nil"} +{"t":"2023-11-29T15:13:00.195681832+01:00","lvl":"info","msg":"Odd pair (3 attr)","key":"value","key2":null,"LOG_ERROR":"Normalized odd number of arguments by adding nil"} diff --git a/cmd/geth/testdata/logging/logtest-logfmt.txt b/cmd/geth/testdata/logging/logtest-logfmt.txt new file mode 100644 index 000000000..5c5316b7d --- /dev/null +++ b/cmd/geth/testdata/logging/logtest-logfmt.txt @@ -0,0 +1,52 @@ +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=big.Int 111,222,333,444,555,678,999=111222333444555678999 +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=-big.Int -111,222,333,444,555,678,999=-111222333444555678999 +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=big.Int 11,122,233,344,455,567,899,900=11122233344455567899900 +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=-big.Int -11,122,233,344,455,567,899,900=-11122233344455567899900 +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint256 111,222,333,444,555,678,999=111222333444555678999 +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint256 11,122,233,344,455,567,899,900=11122233344455567899900 +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 1,000,000=1000000 +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 -1,000,000=-1000000 +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 9,223,372,036,854,775,807=9223372036854775807 +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=int64 -9,223,372,036,854,775,808=-9223372036854775808 +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint64 1,000,000=1000000 +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=uint64 18,446,744,073,709,551,615=18446744073709551615 +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Special chars in value" key="special \r\n\t chars" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Special chars in key" "special \n\t chars"=value +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nospace nospace=nospace +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="with space" "with nospace"="with nospace" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Bash escapes in value" key="\x1b[1G\x1b[K\x1b[1A" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Bash escapes in key" "\x1b[1G\x1b[K\x1b[1A"=value +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m[" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="an error message with quotes" error="this is an 'error'" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Custom Stringer value" 2562047h47m16.854s=2562047h47m16.854s +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="a custom stringer that emits quoted text" output="output with 'quotes'" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="A message with wonky 💩 characters" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="A multiline message \nLALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=boolean true=true false=false +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="repeated-key 1" foo=alpha foo=beta +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="repeated-key 2" xx=short xx=longer +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="log at level info" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=warn msg="log at level warn" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=error msg="log at level error" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=test bar=short a="aligned left" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=test bar="a long message" a=1 +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=test bar=short a="aligned right" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="The following logs should align so that the key-fields make 5 columns" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=1123123 other=first +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Inserted new block" number=1 hash=0x0000000000000000000000000000000000000000000000000000000000001235 txs=2 gas=1123 other=second +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Inserted known block" number=99 hash=0x0000000000000000000000000000000000000000000000000000000000012322 txs=10 gas=1 other=third +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=warn msg="Inserted known block" number=1012 hash=0x0000000000000000000000000000000000000000000000000000000000001234 txs=200 gas=99 other=fourth +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(*big.Int)(nil) = +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(*uint256.Int)(nil) = +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(fmt.Stringer)(nil) res= +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nil-concrete-stringer res= +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="error(nil) " res= +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nil-concrete-error res="" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=nil-custom-struct res= +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="raw nil" res= +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg=(*uint64)(nil) res= +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Using keys 't', 'lvl', 'time', 'level' and 'msg'" t=t time=time lvl=lvl level=level msg=msg +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Odd pair (1 attr)" key= LOG_ERROR="Normalized odd number of arguments by adding nil" +t=xxxx-xx-xxTxx:xx:xx+xxxx lvl=info msg="Odd pair (3 attr)" key=value key2= LOG_ERROR="Normalized odd number of arguments by adding nil" diff --git a/cmd/geth/testdata/logging/logtest-terminal.txt b/cmd/geth/testdata/logging/logtest-terminal.txt new file mode 100644 index 000000000..e3b562117 --- /dev/null +++ b/cmd/geth/testdata/logging/logtest-terminal.txt @@ -0,0 +1,53 @@ +INFO [xx-xx|xx:xx:xx.xxx] big.Int 111,222,333,444,555,678,999=111,222,333,444,555,678,999 +INFO [xx-xx|xx:xx:xx.xxx] -big.Int -111,222,333,444,555,678,999=-111,222,333,444,555,678,999 +INFO [xx-xx|xx:xx:xx.xxx] big.Int 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900 +INFO [xx-xx|xx:xx:xx.xxx] -big.Int -11,122,233,344,455,567,899,900=-11,122,233,344,455,567,899,900 +INFO [xx-xx|xx:xx:xx.xxx] uint256 111,222,333,444,555,678,999=111,222,333,444,555,678,999 +INFO [xx-xx|xx:xx:xx.xxx] uint256 11,122,233,344,455,567,899,900=11,122,233,344,455,567,899,900 +INFO [xx-xx|xx:xx:xx.xxx] int64 1,000,000=1,000,000 +INFO [xx-xx|xx:xx:xx.xxx] int64 -1,000,000=-1,000,000 +INFO [xx-xx|xx:xx:xx.xxx] int64 9,223,372,036,854,775,807=9,223,372,036,854,775,807 +INFO [xx-xx|xx:xx:xx.xxx] int64 -9,223,372,036,854,775,808=-9,223,372,036,854,775,808 +INFO [xx-xx|xx:xx:xx.xxx] uint64 1,000,000=1,000,000 +INFO [xx-xx|xx:xx:xx.xxx] uint64 18,446,744,073,709,551,615=18,446,744,073,709,551,615 +INFO [xx-xx|xx:xx:xx.xxx] Special chars in value key="special \r\n\t chars" +INFO [xx-xx|xx:xx:xx.xxx] Special chars in key "special \n\t chars"=value +INFO [xx-xx|xx:xx:xx.xxx] nospace nospace=nospace +INFO [xx-xx|xx:xx:xx.xxx] with space "with nospace"="with nospace" +INFO [xx-xx|xx:xx:xx.xxx] Bash escapes in value key="\x1b[1G\x1b[K\x1b[1A" +INFO [xx-xx|xx:xx:xx.xxx] Bash escapes in key "\x1b[1G\x1b[K\x1b[1A"=value +INFO [xx-xx|xx:xx:xx.xxx] "Bash escapes in message \x1b[1G\x1b[K\x1b[1A end" key=value +INFO [xx-xx|xx:xx:xx.xxx] "\x1b[35mColored\x1b[0m[" "\x1b[35mColored\x1b[0m["="\x1b[35mColored\x1b[0m[" +INFO [xx-xx|xx:xx:xx.xxx] an error message with quotes error="this is an 'error'" +INFO [xx-xx|xx:xx:xx.xxx] Custom Stringer value 2562047h47m16.854s=2562047h47m16.854s +INFO [xx-xx|xx:xx:xx.xxx] a custom stringer that emits quoted text output="output with 'quotes'" +INFO [xx-xx|xx:xx:xx.xxx] "A message with wonky 💩 characters" +INFO [xx-xx|xx:xx:xx.xxx] "A multiline message \nINFO [10-18|14:11:31.106] with wonky characters 💩" +INFO [xx-xx|xx:xx:xx.xxx] A multiline message +LALA [ZZZZZZZZZZZZZZZZZZ] Actually part of message above +INFO [xx-xx|xx:xx:xx.xxx] boolean true=true false=false +INFO [xx-xx|xx:xx:xx.xxx] repeated-key 1 foo=alpha foo=beta +INFO [xx-xx|xx:xx:xx.xxx] repeated-key 2 xx=short xx=longer +INFO [xx-xx|xx:xx:xx.xxx] log at level info +WARN [xx-xx|xx:xx:xx.xxx] log at level warn +ERROR[xx-xx|xx:xx:xx.xxx] log at level error +INFO [xx-xx|xx:xx:xx.xxx] test bar=short a="aligned left" +INFO [xx-xx|xx:xx:xx.xxx] test bar="a long message" a=1 +INFO [xx-xx|xx:xx:xx.xxx] test bar=short a="aligned right" +INFO [xx-xx|xx:xx:xx.xxx] The following logs should align so that the key-fields make 5 columns +INFO [xx-xx|xx:xx:xx.xxx] Inserted known block number=1012 hash=000000..001234 txs=200 gas=1,123,123 other=first +INFO [xx-xx|xx:xx:xx.xxx] Inserted new block number=1 hash=000000..001235 txs=2 gas=1123 other=second +INFO [xx-xx|xx:xx:xx.xxx] Inserted known block number=99 hash=000000..012322 txs=10 gas=1 other=third +WARN [xx-xx|xx:xx:xx.xxx] Inserted known block number=1012 hash=000000..001234 txs=200 gas=99 other=fourth +INFO [xx-xx|xx:xx:xx.xxx] (*big.Int)(nil) = +INFO [xx-xx|xx:xx:xx.xxx] (*uint256.Int)(nil) = +INFO [xx-xx|xx:xx:xx.xxx] (fmt.Stringer)(nil) res= +INFO [xx-xx|xx:xx:xx.xxx] nil-concrete-stringer res= +INFO [xx-xx|xx:xx:xx.xxx] error(nil) res= +INFO [xx-xx|xx:xx:xx.xxx] nil-concrete-error res= +INFO [xx-xx|xx:xx:xx.xxx] nil-custom-struct res= +INFO [xx-xx|xx:xx:xx.xxx] raw nil res= +INFO [xx-xx|xx:xx:xx.xxx] (*uint64)(nil) res= +INFO [xx-xx|xx:xx:xx.xxx] Using keys 't', 'lvl', 'time', 'level' and 'msg' t=t time=time lvl=lvl level=level msg=msg +INFO [xx-xx|xx:xx:xx.xxx] Odd pair (1 attr) key= LOG_ERROR="Normalized odd number of arguments by adding nil" +INFO [xx-xx|xx:xx:xx.xxx] Odd pair (3 attr) key=value key2= LOG_ERROR="Normalized odd number of arguments by adding nil" 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 000000000..eaea9f905 --- /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 9ba2b4167..420b063d8 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 . @@ -84,7 +84,7 @@ func checkChildren(root verkle.VerkleNode, resolver verkle.NodeResolverFn) error return fmt.Errorf("could not find child %x in db: %w", childC, err) } // depth is set to 0, the tree isn't rebuilt so it's not a problem - childN, err := verkle.ParseNode(childS, 0, childC[:]) + childN, err := verkle.ParseNode(childS, 0) if err != nil { return fmt.Errorf("decode error child %x in db: %w", child.Commitment().Bytes(), err) } @@ -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") @@ -144,7 +145,7 @@ func verifyVerkle(ctx *cli.Context) error { if err != nil { return err } - root, err := verkle.ParseNode(serializedRoot, 0, rootC[:]) + root, err := verkle.ParseNode(serializedRoot, 0) if err != nil { return err } @@ -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 @@ -193,7 +195,7 @@ func expandVerkle(ctx *cli.Context) error { if err != nil { return err } - root, err := verkle.ParseNode(serializedRoot, 0, rootC[:]) + root, err := verkle.ParseNode(serializedRoot, 0) if err != nil { return err } diff --git a/cmd/geth/version_check_test.go b/cmd/geth/version_check_test.go index bd4d820a7..3676d25d0 100644 --- a/cmd/geth/version_check_test.go +++ b/cmd/geth/version_check_test.go @@ -30,17 +30,28 @@ 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 + t.Parallel() + // Signatures generated with `minisign`. Legacy format, not pre-hashed file. + t.Run("minisig-legacy", func(t *testing.T) { + t.Parallel() + // 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) { + t.Parallel() + // 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.Parallel() 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 +69,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 { @@ -87,6 +101,7 @@ func versionUint(v string) int { // TestMatching can be used to check that the regexps are correct func TestMatching(t *testing.T) { + t.Parallel() data, _ := os.ReadFile("./testdata/vcheck/vulnerabilities.json") var vulns []vulnJson if err := json.Unmarshal(data, &vulns); err != nil { @@ -131,6 +146,7 @@ func TestMatching(t *testing.T) { } func TestGethPubKeysParseable(t *testing.T) { + t.Parallel() for _, pubkey := range gethPubKeys { _, err := minisign.NewPublicKey(pubkey) if err != nil { @@ -140,6 +156,7 @@ func TestGethPubKeysParseable(t *testing.T) { } func TestKeyID(t *testing.T) { + t.Parallel() type args struct { id [8]byte } @@ -153,7 +170,9 @@ func TestKeyID(t *testing.T) { {"third key", args{id: extractKeyId(gethPubKeys[2])}, "FD9813B2D2098484"}, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() if got := keyID(tt.args.id); got != tt.want { t.Errorf("keyID() = %v, want %v", got, tt.want) } diff --git a/cmd/p2psim/main.go b/cmd/p2psim/main.go index a3546d405..a0f5f0d28 100644 --- a/cmd/p2psim/main.go +++ b/cmd/p2psim/main.go @@ -417,9 +417,7 @@ func rpcNode(ctx *cli.Context) error { } func rpcSubscribe(client *rpc.Client, out io.Writer, method string, args ...string) error { - parts := strings.SplitN(method, "_", 2) - namespace := parts[0] - method = parts[1] + namespace, method, _ := strings.Cut(method, "_") ch := make(chan interface{}) subArgs := make([]interface{}, len(args)+1) subArgs[0] = method diff --git a/cmd/rlpdump/rlpdump_test.go b/cmd/rlpdump/rlpdump_test.go index a9ab57fdb..8d55f4200 100644 --- a/cmd/rlpdump/rlpdump_test.go +++ b/cmd/rlpdump/rlpdump_test.go @@ -27,6 +27,7 @@ import ( ) func TestRoundtrip(t *testing.T) { + t.Parallel() for i, want := range []string{ "0xf880806482520894d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0a1010000000000000000000000000000000000000000000000000000000000000001801ba0c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549da06180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28", "0xd5c0d3cb84746573742a2a808213378667617a6f6e6b", @@ -51,6 +52,7 @@ func TestRoundtrip(t *testing.T) { } func TestTextToRlp(t *testing.T) { + t.Parallel() type tc struct { text string want string diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 16b126057..8b571be1e 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -33,6 +33,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/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/ethconfig" @@ -374,6 +375,101 @@ func ExportPreimages(db ethdb.Database, fn string) error { return nil } +// ExportSnapshotPreimages exports the preimages corresponding to the enumeration of +// the snapshot for a given root. +func ExportSnapshotPreimages(chaindb ethdb.Database, snaptree *snapshot.Tree, fn string, root common.Hash) error { + log.Info("Exporting preimages", "file", fn) + + fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) + if err != nil { + return err + } + defer fh.Close() + + // Enable gzip compressing if file name has gz suffix. + var writer io.Writer = fh + if strings.HasSuffix(fn, ".gz") { + gz := gzip.NewWriter(writer) + defer gz.Close() + writer = gz + } + buf := bufio.NewWriter(writer) + defer buf.Flush() + writer = buf + + type hashAndPreimageSize struct { + Hash common.Hash + Size int + } + hashCh := make(chan hashAndPreimageSize) + + var ( + start = time.Now() + logged = time.Now() + preimages int + ) + go func() { + defer close(hashCh) + accIt, err := snaptree.AccountIterator(root, common.Hash{}) + if err != nil { + log.Error("Failed to create account iterator", "error", err) + return + } + defer accIt.Release() + + for accIt.Next() { + acc, err := types.FullAccount(accIt.Account()) + if err != nil { + log.Error("Failed to get full account", "error", err) + return + } + preimages += 1 + hashCh <- hashAndPreimageSize{Hash: accIt.Hash(), Size: common.AddressLength} + + if acc.Root != (common.Hash{}) && acc.Root != types.EmptyRootHash { + stIt, err := snaptree.StorageIterator(root, accIt.Hash(), common.Hash{}) + if err != nil { + log.Error("Failed to create storage iterator", "error", err) + return + } + for stIt.Next() { + preimages += 1 + hashCh <- hashAndPreimageSize{Hash: stIt.Hash(), Size: common.HashLength} + + if time.Since(logged) > time.Second*8 { + logged = time.Now() + log.Info("Exporting preimages", "count", preimages, "elapsed", common.PrettyDuration(time.Since(start))) + } + } + stIt.Release() + } + if time.Since(logged) > time.Second*8 { + logged = time.Now() + log.Info("Exporting preimages", "count", preimages, "elapsed", common.PrettyDuration(time.Since(start))) + } + } + }() + + for item := range hashCh { + preimage := rawdb.ReadPreimage(chaindb, item.Hash) + if len(preimage) == 0 { + return fmt.Errorf("missing preimage for %v", item.Hash) + } + if len(preimage) != item.Size { + return fmt.Errorf("invalid preimage size, have %d", len(preimage)) + } + rlpenc, err := rlp.EncodeToBytes(preimage) + if err != nil { + return fmt.Errorf("error encoding preimage: %w", err) + } + if _, err := writer.Write(rlpenc); err != nil { + return fmt.Errorf("failed to write preimage: %w", err) + } + } + log.Info("Exported preimages", "count", preimages, "elapsed", common.PrettyDuration(time.Since(start)), "file", fn) + return nil +} + // exportHeader is used in the export/import flow. When we do an export, // the first element we output is the exportHeader. // Whenever a backwards-incompatible change is made, the Version header @@ -460,7 +556,7 @@ func ImportLDBData(db ethdb.Database, f string, startIndex int64, interrupt chan case OpBatchAdd: batch.Put(key, val) default: - return fmt.Errorf("unknown op %d\n", op) + return fmt.Errorf("unknown op %d", op) } if batch.ValueSize() > ethdb.IdealBatchSize { if err := batch.Write(); err != nil { diff --git a/cmd/utils/export_test.go b/cmd/utils/export_test.go index 445e3fac3..84ba8d0c3 100644 --- a/cmd/utils/export_test.go +++ b/cmd/utils/export_test.go @@ -170,6 +170,7 @@ func testDeletion(t *testing.T, f string) { // TestImportFutureFormat tests that we reject unsupported future versions. func TestImportFutureFormat(t *testing.T) { + t.Parallel() f := fmt.Sprintf("%v/tempdump-future", os.TempDir()) defer func() { os.Remove(f) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 870fcc657..4b25088af 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -18,7 +18,6 @@ package utils import ( - "bytes" "context" "crypto/ecdsa" "encoding/hex" @@ -40,16 +39,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/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/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" @@ -61,7 +58,6 @@ import ( "github.com/ethereum/go-ethereum/graphql" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/internal/flags" - "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics/exp" @@ -73,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" @@ -134,7 +132,7 @@ var ( } NetworkIdFlag = &cli.Uint64Flag{ Name: "networkid", - Usage: "Explicitly set network id (integer)(For testnets: use --goerli, --sepolia instead)", + Usage: "Explicitly set network id (integer)(For testnets: use --goerli, --sepolia, --holesky instead)", Value: ethconfig.Defaults.NetworkId, Category: flags.EthCategory, } @@ -153,7 +151,11 @@ 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", @@ -219,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", @@ -269,40 +253,34 @@ var ( Usage: "Manually specify the Verkle fork timestamp, overriding the bundled setting", Category: flags.EthCategory, } - // Light server and client settings - LightServeFlag = &cli.IntFlag{ - Name: "light.serve", - Usage: "Maximum percentage of time allowed for serving LES requests (multi-threaded processing allows values over 100)", - Value: ethconfig.Defaults.LightServ, - Category: flags.LightCategory, - } - LightIngressFlag = &cli.IntFlag{ - Name: "light.ingress", - Usage: "Incoming bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited)", - Value: ethconfig.Defaults.LightIngress, - Category: flags.LightCategory, - } - LightEgressFlag = &cli.IntFlag{ - Name: "light.egress", - Usage: "Outgoing bandwidth limit for serving light clients (kilobytes/sec, 0 = unlimited)", - Value: ethconfig.Defaults.LightEgress, - Category: flags.LightCategory, - } - LightMaxPeersFlag = &cli.IntFlag{ - Name: "light.maxpeers", - Usage: "Maximum number of light clients to serve, or light servers to attach to", - Value: ethconfig.Defaults.LightPeers, - Category: flags.LightCategory, - } - LightNoPruneFlag = &cli.BoolFlag{ - Name: "light.nopruning", - Usage: "Disable ancient light chain data pruning", - Category: flags.LightCategory, - } - LightNoSyncServeFlag = &cli.BoolFlag{ - Name: "light.nosyncserve", - Usage: "Enables serving light clients before syncing", - Category: flags.LightCategory, + SyncModeFlag = &flags.TextMarshalerFlag{ + Name: "syncmode", + Usage: `Blockchain sync mode ("snap" or "full")`, + 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, } // Transaction pool settings TxPoolLocalsFlag = &cli.StringFlag{ @@ -577,9 +555,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, } @@ -951,25 +929,22 @@ var ( TestnetFlags = []cli.Flag{ 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, + DBEngineFlag, + StateSchemeFlag, HttpHeaderFlag, } ) -func init() { - if rawdb.PebbleEnabled { - DatabasePathFlags = append(DatabasePathFlags, DBEngineFlag) - } -} - // MakeDataDir retrieves the currently requested data directory, terminating // if none (or the empty string) is specified. If the node is starting a testnet, // then a subdirectory of the specified datadir will be used. @@ -981,6 +956,9 @@ func MakeDataDir(ctx *cli.Context) string { 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)") @@ -989,7 +967,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) @@ -1022,33 +1000,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(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 @@ -1112,8 +1102,10 @@ func SplitAndTrim(input string) (ret []string) { // setHTTP creates the HTTP RPC listener interface string from the set // command line flags, returning empty if the HTTP endpoint is disabled. func setHTTP(ctx *cli.Context, cfg *node.Config) { - if ctx.Bool(HTTPEnabledFlag.Name) && cfg.HTTPHost == "" { - cfg.HTTPHost = "127.0.0.1" + if ctx.Bool(HTTPEnabledFlag.Name) { + if cfg.HTTPHost == "" { + cfg.HTTPHost = "127.0.0.1" + } if ctx.IsSet(HTTPListenAddrFlag.Name) { cfg.HTTPHost = ctx.String(HTTPListenAddrFlag.Name) } @@ -1177,8 +1169,10 @@ func setGraphQL(ctx *cli.Context, cfg *node.Config) { // setWS creates the WebSocket RPC listener interface string from the set // command line flags, returning empty if the HTTP endpoint is disabled. func setWS(ctx *cli.Context, cfg *node.Config) { - if ctx.Bool(WSEnabledFlag.Name) && cfg.WSHost == "" { - cfg.WSHost = "127.0.0.1" + if ctx.Bool(WSEnabledFlag.Name) { + if cfg.WSHost == "" { + cfg.WSHost = "127.0.0.1" + } if ctx.IsSet(WSListenAddrFlag.Name) { cfg.WSHost = ctx.String(WSListenAddrFlag.Name) } @@ -1212,25 +1206,25 @@ func setIPC(ctx *cli.Context, cfg *node.Config) { } } -// setLes configures the les server and ultra light client settings from the command line flags. +// setLes shows the deprecation warnings for LES flags. func setLes(ctx *cli.Context, cfg *ethconfig.Config) { if ctx.IsSet(LightServeFlag.Name) { - cfg.LightServ = ctx.Int(LightServeFlag.Name) + log.Warn("The light server has been deprecated, please remove this flag", "flag", LightServeFlag.Name) } if ctx.IsSet(LightIngressFlag.Name) { - cfg.LightIngress = ctx.Int(LightIngressFlag.Name) + log.Warn("The light server has been deprecated, please remove this flag", "flag", LightIngressFlag.Name) } if ctx.IsSet(LightEgressFlag.Name) { - cfg.LightEgress = ctx.Int(LightEgressFlag.Name) + log.Warn("The light server has been deprecated, please remove this flag", "flag", LightEgressFlag.Name) } if ctx.IsSet(LightMaxPeersFlag.Name) { - cfg.LightPeers = ctx.Int(LightMaxPeersFlag.Name) + log.Warn("The light server has been deprecated, please remove this flag", "flag", LightMaxPeersFlag.Name) } if ctx.IsSet(LightNoPruneFlag.Name) { - cfg.LightNoPrune = ctx.Bool(LightNoPruneFlag.Name) + log.Warn("The light server has been deprecated, please remove this flag", "flag", LightNoPruneFlag.Name) } if ctx.IsSet(LightNoSyncServeFlag.Name) { - cfg.LightNoSyncServe = ctx.Bool(LightNoSyncServeFlag.Name) + log.Warn("The light server has been deprecated, please remove this flag", "flag", LightNoSyncServeFlag.Name) } } @@ -1328,58 +1322,24 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) { setBootstrapNodes(ctx, cfg) setBootstrapNodesV5(ctx, cfg) - lightClient := ctx.String(SyncModeFlag.Name) == "light" - lightServer := (ctx.Int(LightServeFlag.Name) != 0) - - lightPeers := ctx.Int(LightMaxPeersFlag.Name) - if lightClient && !ctx.IsSet(LightMaxPeersFlag.Name) { - // dynamic default - for clients we use 1/10th of the default for servers - lightPeers /= 10 - } - if ctx.IsSet(MaxPeersFlag.Name) { cfg.MaxPeers = ctx.Int(MaxPeersFlag.Name) - if lightServer && !ctx.IsSet(LightMaxPeersFlag.Name) { - cfg.MaxPeers += lightPeers - } - } else { - if lightServer { - cfg.MaxPeers += lightPeers - } - if lightClient && ctx.IsSet(LightMaxPeersFlag.Name) && cfg.MaxPeers < lightPeers { - cfg.MaxPeers = lightPeers - } - } - if !(lightClient || lightServer) { - lightPeers = 0 - } - ethPeers := cfg.MaxPeers - lightPeers - if lightClient { - ethPeers = 0 } - log.Info("Maximum peer count", "ETH", ethPeers, "LES", lightPeers, "total", cfg.MaxPeers) + ethPeers := cfg.MaxPeers + log.Info("Maximum peer count", "ETH", ethPeers, "total", cfg.MaxPeers) if ctx.IsSet(MaxPendingPeersFlag.Name) { cfg.MaxPendingPeers = ctx.Int(MaxPendingPeersFlag.Name) } - if ctx.IsSet(NoDiscoverFlag.Name) || lightClient { + if ctx.IsSet(NoDiscoverFlag.Name) { cfg.NoDiscovery = true } - // 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 - } - if netrestrict := ctx.String(NetrestrictFlag.Name); netrestrict != "" { list, err := netutil.ParseNetlist(netrestrict) if err != nil { @@ -1447,6 +1407,13 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { log.Info(fmt.Sprintf("Using %s as db engine", dbEngine)) cfg.DBEngine = dbEngine } + // deprecation notice for log debug flags (TODO: find a more appropriate place to put these?) + if ctx.IsSet(LogBacktraceAtFlag.Name) { + log.Warn("log.backtrace flag is deprecated") + } + if ctx.IsSet(LogDebugFlag.Name) { + log.Warn("log.debug flag is deprecated") + } } func setSmartCard(ctx *cli.Context, cfg *node.Config) { @@ -1479,15 +1446,12 @@ func SetDataDir(ctx *cli.Context, cfg *node.Config) { 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") } } -func setGPO(ctx *cli.Context, cfg *gasprice.Config, light bool) { - // If we are running the light client, apply another group - // settings for gas oracle. - if light { - *cfg = ethconfig.LightClientGPO - } +func setGPO(ctx *cli.Context, cfg *gasprice.Config) { if ctx.IsSet(GpoBlocksFlag.Name) { cfg.Blocks = ctx.Int(GpoBlocksFlag.Name) } @@ -1635,18 +1599,12 @@ 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, GoerliFlag, SepoliaFlag) - CheckExclusive(ctx, LightServeFlag, SyncModeFlag, "light") + CheckExclusive(ctx, MainnetFlag, DeveloperFlag, GoerliFlag, SepoliaFlag, HoleskyFlag) 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") + setGPO(ctx, &cfg.GPO) setTxPool(ctx, &cfg.TxPool) setMiner(ctx, &cfg.Miner) setRequiredBlocks(ctx, cfg) @@ -1675,7 +1633,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) { @@ -1704,8 +1664,27 @@ 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(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.String(GCModeFlag.Name) == "archive" && cfg.TransactionHistory != 0 { + cfg.TransactionHistory = 0 + log.Warn("Disabled transaction unindexing for archive node") } if ctx.IsSet(CacheFlag.Name) || ctx.IsSet(CacheTrieFlag.Name) { cfg.TrieCleanCache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheTrieFlag.Name) / 100 @@ -1719,10 +1698,16 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if ctx.IsSet(CacheLogSizeFlag.Name) { cfg.FilterLogCacheSize = ctx.Int(CacheLogSizeFlag.Name) } - if !ctx.Bool(SnapshotFlag.Name) { + if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 { // If snap-sync is requested, this flag is also required if cfg.SyncMode == downloader.SnapSync { - log.Info("Snap sync requested, enabling --snapshot") + if !ctx.Bool(SnapshotFlag.Name) { + log.Warn("Snap sync requested, enabling --snapshot") + } + if cfg.SnapshotCache == 0 { + log.Warn("Snap sync requested, resetting --cache.snapshot") + cfg.SnapshotCache = ctx.Int(CacheFlag.Name) * CacheSnapshotFlag.Value / 100 + } } else { cfg.TrieCleanCache += cfg.SnapshotCache cfg.SnapshotCache = 0 // Disabled @@ -1768,6 +1753,12 @@ 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 @@ -1829,19 +1820,26 @@ 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(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 + + //validate genesis has PoS enabled in block 0 + genesis, err := core.ReadGenesis(chaindb) + if err != nil { + Fatalf("Could not read genesis from database: %v", err) + } + if !genesis.Config.TerminalTotalDifficultyPassed { + Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficultyPassed must be true in developer mode") + } + if genesis.Config.TerminalTotalDifficulty == nil { + Fatalf("Bad developer-mode genesis configuration: terminalTotalDifficulty must be specified.") + } + if genesis.Difficulty.Cmp(genesis.Config.TerminalTotalDifficulty) != 1 { + Fatalf("Bad developer-mode genesis configuration: genesis block difficulty must be > terminalTotalDifficulty") + } } chaindb.Close() } @@ -1870,9 +1868,6 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) { return // already set through flags/config } protocol := "all" - if cfg.SyncMode == downloader.LightSync { - protocol = "les" - } if url := params.KnownDNSNetwork(genesis, protocol); url != "" { cfg.EthDiscoveryURLs = []string{url} cfg.SnapDiscoveryURLs = cfg.EthDiscoveryURLs @@ -1880,27 +1875,12 @@ func SetDNSDiscoveryDefaults(cfg *ethconfig.Config, genesis common.Hash) { } // RegisterEthService adds an Ethereum client to the stack. -// The second return value is the full node instance, which may be nil if the -// node is running as a light client. +// The second return value is the full node instance. func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) { - if cfg.SyncMode == downloader.LightSync { - backend, err := les.New(stack, cfg) - if err != nil { - Fatalf("Failed to register the Ethereum service: %v", err) - } - stack.RegisterAPIs(tracers.APIs(backend.ApiBackend)) - return backend.ApiBackend, nil - } backend, err := eth.New(stack, cfg) if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } - if cfg.LightServ > 0 { - _, err := les.NewLesServer(stack, backend, cfg) - if err != nil { - Fatalf("Failed to create the LES server: %v", err) - } - } stack.RegisterAPIs(tracers.APIs(backend.APIBackend)) return backend.APIBackend, backend } @@ -1922,33 +1902,20 @@ func RegisterGraphQLService(stack *node.Node, backend ethapi.Backend, filterSyst // RegisterFilterAPI adds the eth log filtering RPC API to the node. func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconfig.Config) *filters.FilterSystem { - isLightClient := ethcfg.SyncMode == downloader.LightSync filterSystem := filters.NewFilterSystem(backend, filters.Config{ LogCacheSize: ethcfg.FilterLogCacheSize, }) stack.RegisterAPIs([]rpc.API{{ Namespace: "eth", - Service: filters.NewFilterAPI(filterSystem, isLightClient), + Service: filters.NewFilterAPI(filterSystem, false), }}) return filterSystem } // 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) { @@ -2029,12 +1996,11 @@ func SplitTagsFlag(tagsFlag string) map[string]string { return tagsMap } -// MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails. +// MakeChainDatabase opens a database using the flags passed to the client and will hard crash if it fails. func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb.Database { var ( cache = ctx.Int(CacheFlag.Name) * ctx.Int(CacheDatabaseFlag.Name) / 100 handles = MakeDatabaseHandles(ctx.Int(FDLimitFlag.Name)) - err error chainDb ethdb.Database ) @@ -2057,6 +2023,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) @@ -2078,7 +2056,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 { @@ -2096,6 +2074,8 @@ 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(GoerliFlag.Name): @@ -2123,6 +2103,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), @@ -2131,6 +2115,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 @@ -2232,3 +2218,28 @@ func DrainReplicators(replicators []*core.ChainReplicator) { replicator.Stop() } } + +// MakeTrieDatabase constructs a trie database based on the configured scheme. +func MakeTrieDatabase(ctx *cli.Context, disk ethdb.Database, preimage bool, readOnly bool, isVerkle bool) *trie.Database { + config := &trie.Config{ + Preimages: preimage, + IsVerkle: isVerkle, + } + 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 cf51b5193..243abd831 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" ) @@ -37,6 +38,15 @@ var DeprecatedFlags = []cli.Flag{ CacheTrieJournalFlag, CacheTrieRejournalFlag, LegacyDiscoveryV5Flag, + TxLookupLimitFlag, + LightServeFlag, + LightIngressFlag, + LightEgressFlag, + LightMaxPeersFlag, + LightNoPruneFlag, + LightNoSyncServeFlag, + LogBacktraceAtFlag, + LogDebugFlag, } var ( @@ -68,6 +78,60 @@ var ( 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, + } + // Light server and client settings, Deprecated November 2023 + LightServeFlag = &cli.IntFlag{ + Name: "light.serve", + Usage: "Maximum percentage of time allowed for serving LES requests (deprecated)", + Value: ethconfig.Defaults.LightServ, + Category: flags.LightCategory, + } + LightIngressFlag = &cli.IntFlag{ + Name: "light.ingress", + Usage: "Incoming bandwidth limit for serving light clients (deprecated)", + Value: ethconfig.Defaults.LightIngress, + Category: flags.LightCategory, + } + LightEgressFlag = &cli.IntFlag{ + Name: "light.egress", + Usage: "Outgoing bandwidth limit for serving light clients (deprecated)", + Value: ethconfig.Defaults.LightEgress, + Category: flags.LightCategory, + } + LightMaxPeersFlag = &cli.IntFlag{ + Name: "light.maxpeers", + Usage: "Maximum number of light clients to serve, or light servers to attach to (deprecated)", + Value: ethconfig.Defaults.LightPeers, + Category: flags.LightCategory, + } + LightNoPruneFlag = &cli.BoolFlag{ + Name: "light.nopruning", + Usage: "Disable ancient light chain data pruning (deprecated)", + Category: flags.LightCategory, + } + LightNoSyncServeFlag = &cli.BoolFlag{ + Name: "light.nosyncserve", + Usage: "Enables serving light clients before syncing (deprecated)", + Category: flags.LightCategory, + } + // Deprecated November 2023 + LogBacktraceAtFlag = &cli.StringFlag{ + Name: "log.backtrace", + Usage: "Request a stack trace at a specific logging statement (deprecated)", + Value: "", + Category: flags.DeprecatedCategory, + } + LogDebugFlag = &cli.BoolFlag{ + Name: "log.debug", + Usage: "Prepends log messages with call-site location (deprecated)", + Category: flags.DeprecatedCategory, + } ) // showDeprecated displays deprecated flags that will be soon removed from the codebase. diff --git a/cmd/utils/flags_test.go b/cmd/utils/flags_test.go index adfdd0903..00c73a526 100644 --- a/cmd/utils/flags_test.go +++ b/cmd/utils/flags_test.go @@ -23,6 +23,7 @@ import ( ) func Test_SplitTagsFlag(t *testing.T) { + t.Parallel() tests := []struct { name string args string @@ -55,7 +56,9 @@ func Test_SplitTagsFlag(t *testing.T) { }, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() if got := SplitTagsFlag(tt.args); !reflect.DeepEqual(got, tt.want) { t.Errorf("splitTagsFlag() = %v, want %v", got, tt.want) } diff --git a/cmd/utils/prompt_test.go b/cmd/utils/prompt_test.go index 86ee8b652..889bf71de 100644 --- a/cmd/utils/prompt_test.go +++ b/cmd/utils/prompt_test.go @@ -22,6 +22,7 @@ import ( ) func TestGetPassPhraseWithList(t *testing.T) { + t.Parallel() type args struct { text string confirmation bool @@ -65,7 +66,9 @@ func TestGetPassPhraseWithList(t *testing.T) { }, } for _, tt := range tests { + tt := tt t.Run(tt.name, func(t *testing.T) { + t.Parallel() if got := GetPassPhraseWithList(tt.args.text, tt.args.confirmation, tt.args.index, tt.args.passwords); got != tt.want { t.Errorf("GetPassPhraseWithList() = %v, want %v", got, tt.want) } diff --git a/common/bitutil/compress_test.go b/common/bitutil/compress_test.go index 13a13011d..c6f6fe8bc 100644 --- a/common/bitutil/compress_test.go +++ b/common/bitutil/compress_test.go @@ -18,6 +18,7 @@ package bitutil import ( "bytes" + "fmt" "math/rand" "testing" @@ -48,19 +49,23 @@ func TestEncodingCycle(t *testing.T) { "0xdf7070533534333636313639343638373532313536346c1bc333393438373130707063363430353639343638373532313536346c1bc333393438336336346c65fe", } for i, tt := range tests { - data := hexutil.MustDecode(tt) - - proc, err := bitsetDecodeBytes(bitsetEncodeBytes(data), len(data)) - if err != nil { - t.Errorf("test %d: failed to decompress compressed data: %v", i, err) - continue - } - if !bytes.Equal(data, proc) { - t.Errorf("test %d: compress/decompress mismatch: have %x, want %x", i, proc, data) + if err := testEncodingCycle(hexutil.MustDecode(tt)); err != nil { + t.Errorf("test %d: %v", i, err) } } } +func testEncodingCycle(data []byte) error { + proc, err := bitsetDecodeBytes(bitsetEncodeBytes(data), len(data)) + if err != nil { + return fmt.Errorf("failed to decompress compressed data: %v", err) + } + if !bytes.Equal(data, proc) { + return fmt.Errorf("compress/decompress mismatch: have %x, want %x", proc, data) + } + return nil +} + // Tests that data bitset decoding and rencoding works and is bijective. func TestDecodingCycle(t *testing.T) { tests := []struct { @@ -179,3 +184,40 @@ func benchmarkEncoding(b *testing.B, bytes int, fill float64) { bitsetDecodeBytes(bitsetEncodeBytes(data), len(data)) } } + +func FuzzEncoder(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + if err := testEncodingCycle(data); err != nil { + t.Fatal(err) + } + }) +} +func FuzzDecoder(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzzDecode(data) + }) +} + +// fuzzDecode implements a go-fuzz fuzzer method to test the bit decoding and +// reencoding algorithm. +func fuzzDecode(data []byte) { + blob, err := DecompressBytes(data, 1024) + if err != nil { + return + } + // re-compress it (it's OK if the re-compressed differs from the + // original - the first input may not have been compressed at all) + comp := CompressBytes(blob) + if len(comp) > len(blob) { + // After compression, it must be smaller or equal + panic("bad compression") + } + // But decompressing it once again should work + decomp, err := DecompressBytes(data, 1024) + if err != nil { + panic(err) + } + if !bytes.Equal(decomp, blob) { + panic("content mismatch") + } +} diff --git a/common/hexutil/json.go b/common/hexutil/json.go index 50db20811..e0ac98f52 100644 --- a/common/hexutil/json.go +++ b/common/hexutil/json.go @@ -23,6 +23,8 @@ import ( "math/big" "reflect" "strconv" + + "github.com/holiman/uint256" ) var ( @@ -30,6 +32,7 @@ var ( bigT = reflect.TypeOf((*Big)(nil)) uintT = reflect.TypeOf(Uint(0)) uint64T = reflect.TypeOf(Uint64(0)) + u256T = reflect.TypeOf((*uint256.Int)(nil)) ) // Bytes marshals/unmarshals as a JSON string with 0x prefix. @@ -225,6 +228,48 @@ func (b *Big) UnmarshalGraphQL(input interface{}) error { return err } +// U256 marshals/unmarshals as a JSON string with 0x prefix. +// The zero value marshals as "0x0". +type U256 uint256.Int + +// MarshalText implements encoding.TextMarshaler +func (b U256) MarshalText() ([]byte, error) { + u256 := (*uint256.Int)(&b) + return []byte(u256.Hex()), nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *U256) UnmarshalJSON(input []byte) error { + // The uint256.Int.UnmarshalJSON method accepts "dec", "0xhex"; we must be + // more strict, hence we check string and invoke SetFromHex directly. + if !isString(input) { + return errNonString(u256T) + } + // The hex decoder needs to accept empty string ("") as '0', which uint256.Int + // would reject. + if len(input) == 2 { + (*uint256.Int)(b).Clear() + return nil + } + err := (*uint256.Int)(b).SetFromHex(string(input[1 : len(input)-1])) + if err != nil { + return &json.UnmarshalTypeError{Value: err.Error(), Type: u256T} + } + return nil +} + +// UnmarshalText implements encoding.TextUnmarshaler +func (b *U256) UnmarshalText(input []byte) error { + // The uint256.Int.UnmarshalText method accepts "dec", "0xhex"; we must be + // more strict, hence we check string and invoke SetFromHex directly. + return (*uint256.Int)(b).SetFromHex(string(input)) +} + +// String returns the hex encoding of b. +func (b *U256) String() string { + return (*uint256.Int)(b).Hex() +} + // Uint64 marshals/unmarshals as a JSON string with 0x prefix. // The zero value marshals as "0x0". type Uint64 uint64 diff --git a/common/hexutil/json_test.go b/common/hexutil/json_test.go index ed7d6fad1..7cca30095 100644 --- a/common/hexutil/json_test.go +++ b/common/hexutil/json_test.go @@ -23,6 +23,8 @@ import ( "errors" "math/big" "testing" + + "github.com/holiman/uint256" ) func checkError(t *testing.T, input string, got, want error) bool { @@ -176,6 +178,64 @@ func TestUnmarshalBig(t *testing.T) { } } +var unmarshalU256Tests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(u256T)}, + {input: "10", wantErr: errNonString(u256T)}, + {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, u256T)}, + {input: `"0x"`, wantErr: wrapTypeError(ErrEmptyNumber, u256T)}, + {input: `"0x01"`, wantErr: wrapTypeError(ErrLeadingZero, u256T)}, + {input: `"0xx"`, wantErr: wrapTypeError(ErrSyntax, u256T)}, + {input: `"0x1zz01"`, wantErr: wrapTypeError(ErrSyntax, u256T)}, + { + input: `"0x10000000000000000000000000000000000000000000000000000000000000000"`, + wantErr: wrapTypeError(ErrBig256Range, u256T), + }, + + // valid encoding + {input: `""`, want: big.NewInt(0)}, + {input: `"0x0"`, want: big.NewInt(0)}, + {input: `"0x2"`, want: big.NewInt(0x2)}, + {input: `"0x2F2"`, want: big.NewInt(0x2f2)}, + {input: `"0X2F2"`, want: big.NewInt(0x2f2)}, + {input: `"0x1122aaff"`, want: big.NewInt(0x1122aaff)}, + {input: `"0xbBb"`, want: big.NewInt(0xbbb)}, + {input: `"0xfffffffff"`, want: big.NewInt(0xfffffffff)}, + { + input: `"0x112233445566778899aabbccddeeff"`, + want: referenceBig("112233445566778899aabbccddeeff"), + }, + { + input: `"0xffffffffffffffffffffffffffffffffffff"`, + want: referenceBig("ffffffffffffffffffffffffffffffffffff"), + }, + { + input: `"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"`, + want: referenceBig("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, +} + +func TestUnmarshalU256(t *testing.T) { + for _, test := range unmarshalU256Tests { + var v U256 + err := json.Unmarshal([]byte(test.input), &v) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if test.want == nil { + continue + } + want := new(uint256.Int) + want.SetFromBig(test.want.(*big.Int)) + have := (*uint256.Int)(&v) + if want.Cmp(have) != 0 { + t.Errorf("input %s: value mismatch: have %x, want %x", test.input, have, want) + continue + } + } +} + func BenchmarkUnmarshalBig(b *testing.B) { input := []byte(`"0x123456789abcdef123456789abcdef"`) for i := 0; i < b.N; i++ { diff --git a/common/types.go b/common/types.go index 3ca36f09f..f196e801a 100644 --- a/common/types.go +++ b/common/types.go @@ -45,6 +45,12 @@ var ( hashT = reflect.TypeOf(Hash{}) addressT = reflect.TypeOf(Address{}) EmptyHash = Hash{} + + // MaxAddress represents the maximum possible address value. + MaxAddress = HexToAddress("0xffffffffffffffffffffffffffffffffffffffff") + + // MaxHash represents the maximum possible hash value. + MaxHash = HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") ) // Hash represents the 32 byte Keccak256 hash of arbitrary data. @@ -240,9 +246,6 @@ func (a Address) Cmp(other Address) int { // 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 ad892671b..cec689ea3 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 1ad4358cf..e856f4e6c 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -269,15 +269,21 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa if !shanghai && header.WithdrawalsHash != nil { return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) } - // Verify the existence / non-existence of excessBlobGas + // Verify the existence / non-existence of cancun-specific header fields cancun := chain.Config().IsCancun(header.Number, header.Time) - if !cancun && header.ExcessBlobGas != nil { - return fmt.Errorf("invalid excessBlobGas: have %d, expected nil", header.ExcessBlobGas) - } - if !cancun && header.BlobGasUsed != nil { - return fmt.Errorf("invalid blobGasUsed: have %d, expected nil", header.BlobGasUsed) - } - if cancun { + 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 } diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index f708050ab..c693189ea 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -302,9 +302,22 @@ func (c *Clique) verifyHeader(chain consensus.ChainHeaderReader, header *types.H if chain.Config().IsShanghai(header.Number, header.Time) { return errors.New("clique does not support shanghai fork") } + // Verify the non-existence of withdrawalsHash. + if header.WithdrawalsHash != nil { + return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) + } if chain.Config().IsCancun(header.Number, header.Time) { return errors.New("clique does not support cancun fork") } + // Verify the non-existence of cancun-specific header fields + 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) + } // All basic checks passed, verify cascading fields return c.verifyCascadingFields(chain, header, parents) } @@ -753,6 +766,15 @@ func encodeSigHeader(w io.Writer, header *types.Header) { if header.WithdrawalsHash != nil { panic("unexpected withdrawal hash value in clique") } + if header.ExcessBlobGas != nil { + panic("unexpected excess blob gas value in clique") + } + if header.BlobGasUsed != nil { + panic("unexpected blob gas used value in clique") + } + if header.ParentBeaconRoot != nil { + panic("unexpected parent beacon root value in clique") + } if err := rlp.Encode(w, enc); err != nil { panic("can't encode: " + err.Error()) } diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 8eb9863da..130dfdf21 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -266,9 +266,22 @@ func (ethash *Ethash) verifyHeader(chain consensus.ChainHeaderReader, header, pa if chain.Config().IsShanghai(header.Number, header.Time) { return errors.New("ethash does not support shanghai fork") } + // Verify the non-existence of withdrawalsHash. + if header.WithdrawalsHash != nil { + return fmt.Errorf("invalid withdrawalsHash: have %x, expected nil", header.WithdrawalsHash) + } if chain.Config().IsCancun(header.Number, header.Time) { return errors.New("ethash does not support cancun fork") } + // Verify the non-existence of cancun-specific header fields + 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) + } // Add some fake checks for tests if ethash.fakeDelay != nil { time.Sleep(*ethash.fakeDelay) @@ -533,6 +546,15 @@ func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) { if header.WithdrawalsHash != nil { panic("withdrawal hash set on ethash") } + if header.ExcessBlobGas != nil { + panic("excess blob gas set on ethash") + } + if header.BlobGasUsed != nil { + panic("blob gas used set on ethash") + } + if header.ParentBeaconRoot != nil { + panic("parent beacon root set on ethash") + } rlp.Encode(hasher, enc) hasher.Sum(hash[:0]) return hash diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go index 583bcdeec..2dad9a0cd 100644 --- a/consensus/misc/eip4844/eip4844.go +++ b/consensus/misc/eip4844/eip4844.go @@ -42,8 +42,8 @@ func VerifyEIP4844Header(parent, header *types.Header) error { return errors.New("header is missing blobGasUsed") } // Verify that the blob gas used remains within reasonable limits. - if *header.BlobGasUsed > params.BlobTxMaxBlobGasPerBlock { - return fmt.Errorf("blob gas used %d exceeds maximum allowance %d", *header.BlobGasUsed, params.BlobTxMaxBlobGasPerBlock) + 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) diff --git a/consensus/misc/eip4844/eip4844_test.go b/consensus/misc/eip4844/eip4844_test.go index 677cdd252..ec417380f 100644 --- a/consensus/misc/eip4844/eip4844_test.go +++ b/consensus/misc/eip4844/eip4844_test.go @@ -45,14 +45,14 @@ func TestCalcExcessBlobGas(t *testing.T) { // 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.BlobTxBlobGasPerBlob}, - {params.BlobTxTargetBlobGasPerBlock, (params.BlobTxTargetBlobGasPerBlock / params.BlobTxBlobGasPerBlob) - 2, 0}, + {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 _, tt := range tests { + for i, tt := range tests { result := CalcExcessBlobGas(tt.excess, tt.blobs*params.BlobTxBlobGasPerBlob) if result != tt.want { - t.Errorf("excess blob gas mismatch: have %v, want %v", result, tt.want) + t.Errorf("test %d: excess blob gas mismatch: have %v, want %v", i, result, tt.want) } } } @@ -63,9 +63,9 @@ func TestCalcBlobFee(t *testing.T) { blobfee int64 }{ {0, 1}, - {1542706, 1}, - {1542707, 2}, - {10 * 1024 * 1024, 111}, + {2314057, 1}, + {2314058, 2}, + {10 * 1024 * 1024, 23}, } for i, tt := range tests { have := CalcBlobFee(tt.excessBlobGas) diff --git a/consensus/misc/gaslimit.go b/consensus/misc/gaslimit.go index 25f35300b..dfcabd9a8 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 c67686d6c..37578041c 100644 --- a/console/bridge.go +++ b/console/bridge.go @@ -78,7 +78,7 @@ func (b *bridge) NewAccount(call jsre.Call) (goja.Value, error) { return nil, err } if password != confirm { - return nil, errors.New("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: diff --git a/console/console_test.go b/console/console_test.go index ee5c36be4..a13be6a99 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(11_500_000, common.Address{}), + Genesis: core.DeveloperGenesisBlock(11_500_000, nil), Miner: miner.Config{ Etherbase: common.HexToAddress(testAddress), }, diff --git a/core/asm/asm.go b/core/asm/asm.go index 7c1e14ec0..294eb6ffa 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/asm_test.go b/core/asm/asm_test.go index 92b26b67a..cd7520ec6 100644 --- a/core/asm/asm_test.go +++ b/core/asm/asm_test.go @@ -22,53 +22,37 @@ import ( "encoding/hex" ) -// Tests disassembling the instructions for valid evm code -func TestInstructionIteratorValid(t *testing.T) { - cnt := 0 - script, _ := hex.DecodeString("61000000") - - it := NewInstructionIterator(script) - for it.Next() { - cnt++ - } - - if err := it.Error(); err != nil { - t.Errorf("Expected 2, but encountered error %v instead.", err) - } - if cnt != 2 { - t.Errorf("Expected 2, but got %v instead.", cnt) - } -} - -// Tests disassembling the instructions for invalid evm code -func TestInstructionIteratorInvalid(t *testing.T) { - cnt := 0 - script, _ := hex.DecodeString("6100") - - it := NewInstructionIterator(script) - for it.Next() { - cnt++ - } - - if it.Error() == nil { - t.Errorf("Expected an error, but got %v instead.", cnt) - } -} - -// Tests disassembling the instructions for empty evm code -func TestInstructionIteratorEmpty(t *testing.T) { - cnt := 0 - script, _ := hex.DecodeString("") - - it := NewInstructionIterator(script) - for it.Next() { - cnt++ - } - - if err := it.Error(); err != nil { - t.Errorf("Expected 0, but encountered error %v instead.", err) - } - if cnt != 0 { - t.Errorf("Expected 0, but got %v instead.", cnt) +// Tests disassembling instructions +func TestInstructionIterator(t *testing.T) { + for i, tc := range []struct { + want int + code string + wantErr string + }{ + {2, "61000000", ""}, // valid code + {0, "6100", "incomplete push instruction at 0"}, // invalid code + {2, "5900", ""}, // push0 + {0, "", ""}, // empty + + } { + var ( + have int + code, _ = hex.DecodeString(tc.code) + it = NewInstructionIterator(code) + ) + for it.Next() { + have++ + } + var haveErr = "" + if it.Error() != nil { + haveErr = it.Error().Error() + } + if haveErr != tc.wantErr { + t.Errorf("test %d: encountered error: %q want %q", i, haveErr, tc.wantErr) + continue + } + if have != tc.want { + t.Errorf("wrong instruction count, have %d want %d", have, tc.want) + } } } diff --git a/core/asm/compiler.go b/core/asm/compiler.go index 4b1d37920..02c589b2c 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 ce9df436b..3d64c96bc 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 53e05fbbb..1e62d776d 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 d1b79a1fb..e025c6f36 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 000000000..ade76aa36 --- /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/bench_test.go b/core/bench_test.go index 55fa980a8..c5991f10e 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -84,7 +84,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { toaddr := common.Address{} data := make([]byte, nbytes) gas, _ := IntrinsicGas(data, nil, false, false, false, false) - signer := types.MakeSigner(gen.config, big.NewInt(int64(i)), gen.header.Time) + signer := gen.Signer() gasPrice := big.NewInt(0) if gen.header.BaseFee != nil { gasPrice = gen.header.BaseFee @@ -128,7 +128,7 @@ func genTxRing(naccounts int) func(int, *BlockGen) { if gen.header.BaseFee != nil { gasPrice = gen.header.BaseFee } - signer := types.MakeSigner(gen.config, big.NewInt(int64(i)), gen.header.Time) + signer := gen.Signer() for { gas -= params.TxGas if gas < params.TxGas { diff --git a/core/block_validator.go b/core/block_validator.go index 3c9ac3dc4..f3d65cea2 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -68,6 +68,7 @@ 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. @@ -81,14 +82,23 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { // 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 _, tx := range block.Transactions() { + 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 the state_transition check. + // 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) @@ -98,6 +108,8 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { 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 460eaad21..48bdceff6 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -35,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} @@ -45,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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + chain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer chain.Stop() for i := 0; i < len(blocks); i++ { diff --git a/core/blockchain.go b/core/blockchain.go index 647e7e35c..b1fe3eabd 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -48,6 +48,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/exp/slices" ) @@ -58,6 +60,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) @@ -128,7 +132,7 @@ 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 TrieCleanNoPrefetch bool // Whether to disable heuristic state prefetching for followup blocks @@ -137,11 +141,31 @@ type CacheConfig struct { 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 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{ @@ -150,6 +174,15 @@ var defaultCacheConfig = &CacheConfig{ 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 @@ -244,10 +277,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis cacheConfig = defaultCacheConfig } // Open trie database with provided config - triedb := trie.NewDatabaseWithConfig(db, &trie.Config{ - Cache: cacheConfig.TrieCleanLimit, - Preimages: cacheConfig.Preimages, - }) + triedb := trie.NewDatabase(db, cacheConfig.triedbConfig()) + // Setup the genesis block, commit the provided genesis specification // to database if the genesis block is not present yet, or load the // stored one from database. @@ -308,6 +339,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis 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. @@ -318,31 +352,41 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis 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, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, diskRoot, true) - if err != nil { - return nil, err - } - // Chain rewound, persist old snapshot number to indicate recovery procedure - if snapDisk != 0 { - rawdb.WriteSnapshotRecoveryNumber(bc.db, snapDisk) - } + if head.Number.Uint64() == 0 { + // 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 scenarios, + // 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 { - log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash()) - if _, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, common.Hash{}, true); err != nil { - return nil, err + // 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, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, diskRoot, true) + if err != nil { + return nil, err + } + // Chain rewound, persist old snapshot number to indicate recovery procedure + if snapDisk != 0 { + rawdb.WriteSnapshotRecoveryNumber(bc.db, snapDisk) + } + } else { + log.Warn("Head state missing, repairing", "number", head.Number, "hash", head.Hash()) + if _, err := bc.setHeadBeyondRoot(head.Number.Uint64(), 0, common.Hash{}, true); err != nil { + return nil, err + } } } } @@ -547,7 +591,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()) @@ -569,7 +613,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()) @@ -637,9 +681,8 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha log.Error("Gap in the chain, rewinding to genesis", "number", header.Number, "hash", header.Hash()) newHeadBlock = bc.genesisBlock } else { - // Block exists, keep rewinding until we find one with state, - // keeping rewinding until we exceed the optional threshold - // root hash + // Block exists. Keep rewinding until either we find one with state + // or until we exceed the optional threshold root hash beyondRoot := (root == common.Hash{}) // Flag whether we're beyond the requested root (no root, always true) for { @@ -647,7 +690,7 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha if root != (common.Hash{}) && !beyondRoot && newHeadBlock.Root() == root { beyondRoot, rootNumber = 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) @@ -663,20 +706,14 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha } } if beyondRoot || newHeadBlock.NumberU64() == 0 { - if newHeadBlock.NumberU64() == 0 { - // 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()) { - 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 } + 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()) @@ -691,6 +728,15 @@ 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 snap block in a simpleton way to the target head if currentSnapBlock := bc.CurrentSnapBlock(); currentSnapBlock != nil && header.Number.Uint64() < currentSnapBlock.Number.Uint64() { @@ -787,7 +833,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]) } @@ -944,49 +996,56 @@ 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 if snapBase, err = bc.snaps.Journal(bc.CurrentBlock().Root); err != nil { log.Error("Failed to journal state snapshot", "err", err) } + bc.snaps.Release() } - - // 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, TriesInMemory - 1} { - if number := bc.CurrentBlock().Number.Uint64(); number > offset { - recent := bc.GetBlockByNumber(number - offset) - - 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 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, TriesInMemory - 1} { + if number := bc.CurrentBlock().Number.Uint64(); number > offset { + recent := bc.GetBlockByNumber(number - offset) + + 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) + } + } + } + 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()) + } + 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()) - } - 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 { - log.Error("Failed to close trie db", "err", err) + // 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 database", "err", err) } log.Info("Blockchain stopped") } @@ -1043,19 +1102,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) + } } } @@ -1138,7 +1208,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) @@ -1356,6 +1426,11 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. if err != nil { return err } + // 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, always flush if bc.cacheConfig.TrieDirtyDisabled { return bc.triedb.Commit(root, false) @@ -1364,15 +1439,15 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. bc.triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive bc.triegc.Push(root, -int64(block.NumberU64())) - current := block.NumberU64() // Flush limits are not considered for the first TriesInMemory blocks. + current := block.NumberU64() if current <= TriesInMemory { return nil } // 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) @@ -1809,8 +1884,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 { // Export Block Specimen @@ -1959,6 +2038,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()) @@ -2015,6 +2100,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) @@ -2359,6 +2450,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 { @@ -2416,6 +2513,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 { @@ -2502,7 +2608,7 @@ func (bc *BlockChain) SetTrieFlushInterval(interval time.Duration) { bc.flushInterval.Store(int64(interval)) } -// GetTrieFlushInterval gets the in-memroy tries flush 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_insert.go b/core/blockchain_insert.go index 8f496e182..9bf662b6b 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 f9d0e3531..466a86c14 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 diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index 6a4a9c9d2..b2df39d17 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, } ) defer engine.Close() @@ -1806,7 +1816,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) @@ -1828,21 +1840,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, gspec, nil, engine, vm.Config{}, nil, nil) + newChain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -1885,17 +1898,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) } @@ -1908,15 +1926,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, - } ) - chain, err := NewBlockChain(db, config, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -1929,7 +1940,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 { @@ -1943,7 +1954,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 { @@ -1951,20 +1962,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, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err = NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -1976,8 +1989,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 9dc350db8..fa739f924 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, } ) if snapshots { @@ -2007,7 +2021,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) @@ -2017,13 +2031,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 3bd876a58..dd012c430 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, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(basic.scheme), 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, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), 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, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), 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, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = NewBlockChain(newdb, DefaultCacheConfigWithScheme(snaptest.scheme), 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{ @@ -308,6 +311,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, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { @@ -317,7 +321,7 @@ func (snaptest *gappedSnapshotTest) test(t *testing.T) { newchain.Stop() // Restart the chain with enabling the snapshot - newchain, err = NewBlockChain(snaptest.db, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -345,7 +349,7 @@ func (snaptest *setHeadSnapshotTest) test(t *testing.T) { chain.SetHead(snaptest.setHead) chain.Stop() - newchain, err := NewBlockChain(snaptest.db, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err := NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("Failed to recreate chain: %v", err) } @@ -379,22 +383,24 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 0, + StateScheme: snaptest.scheme, } newchain, err := NewBlockChain(snaptest.db, config, 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{ TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 256, SnapshotWait: false, // Don't wait rebuild + StateScheme: snaptest.scheme, } tmp, err := NewBlockChain(snaptest.db, config, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) if err != nil { @@ -402,14 +408,15 @@ func (snaptest *wipeCrashSnapshotTest) test(t *testing.T) { } // Simulate the blockchain crash. + tmp.triedb.Close() tmp.stopWithoutSaving() - newchain, err = NewBlockChain(snaptest.db, nil, snaptest.gspec, nil, snaptest.engine, vm.Config{}, nil, nil) + newchain, err = NewBlockChain(snaptest.db, DefaultCacheConfigWithScheme(snaptest.scheme), 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 @@ -433,20 +440,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 @@ -472,20 +482,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 @@ -511,20 +524,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 @@ -550,20 +566,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 @@ -587,21 +610,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. @@ -625,21 +651,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 @@ -663,19 +692,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 1b4b56957..bc6f8112f 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -49,10 +49,10 @@ var ( ) // newCanonical creates a chain database, and injects a deterministic canonical -// chain. Depending on the full flag, if creates either a full block chain or a +// chain. Depending on the full flag, it 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, genesis, nil, engine, vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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) } @@ -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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ncm, err := NewBlockChain(blockchain.db, DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + archive, _ := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), 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, 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + fast, _ := NewBlockChain(fastDb, DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + light, _ := NewBlockChain(lightDb, DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, engine, vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ = NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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 < TriesInMemory; 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*TriesInMemory+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, genesis, nil, engine, vm.Config{}, nil, nil) + db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) + defer db.Close() + + chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), 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)-TriesInMemory] { - 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 := TriesInMemory + 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ancient, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ancient, _ = NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + ancientChain, _ := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), 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, genesis, nil, engine, vm.Config{}, nil, nil) + diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) + defer diskdb.Close() + + chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), 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, genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(chaindb, DefaultCacheConfigWithScheme(scheme), 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, genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l) + chain, err := NewBlockChain(ancientDb, DefaultCacheConfigWithScheme(scheme), 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*TriesInMemory, nil) - chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, genesis, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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) - TriesInMemory - 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 := TriesInMemory + if scheme == rawdb.PathScheme { + states = TriesInMemory + 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)-TriesInMemory] + 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, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, engine, vm.Config{ + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, engine, vm.Config{ + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, engine, vm.Config{ + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, engine, vm.Config{ + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, engine, vm.Config{}, nil, nil) + diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) + defer diskdb.Close() + + chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), 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, gspec, nil, engine, vm.Config{}, nil, nil) + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } @@ -4138,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, 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) { diff --git a/core/bloombits/matcher.go b/core/bloombits/matcher.go index d8f932041..6a4cfb23d 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 36764c3f1..7f3d5f279 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_makers.go b/core/chain_makers.go index 4e8e80b92..31c111b73 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -24,6 +24,7 @@ import ( "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" @@ -37,8 +38,8 @@ import ( // See GenerateChain for a detailed explanation. type BlockGen struct { i int + cm *chainMaker parent *types.Block - chain []*types.Block header *types.Header statedb *state.StateDB @@ -48,7 +49,6 @@ type BlockGen struct { uncles []*types.Header withdrawals []*types.Withdrawal - config *params.ChainConfig engine consensus.Engine } @@ -87,6 +87,22 @@ func (b *BlockGen) SetPoS() { b.header.Difficulty = new(big.Int) } +// Difficulty returns the currently calculated difficulty of the block. +func (b *BlockGen) Difficulty() *big.Int { + return new(big.Int).Set(b.header.Difficulty) +} + +// SetParentBeaconRoot sets the parent beacon root field of the generated +// block. +func (b *BlockGen) SetParentBeaconRoot(root common.Hash) { + b.header.ParentBeaconRoot = &root + var ( + blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase) + vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{}) + ) + ProcessBeaconBlockRoot(root, vmenv, b.statedb) +} + // addTx adds a transaction to the generated block. If no coinbase has // been set, the block's coinbase is set to the zero address. // @@ -99,22 +115,25 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti b.SetCoinbase(common.Address{}) } b.statedb.SetTxContext(tx.Hash(), len(b.txs)) - receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig) + receipt, err := ApplyTransaction(b.cm.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig) if err != nil { panic(err) } 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 // been set, the block's coinbase is set to the zero address. // -// AddTx panics if the transaction cannot be executed. In addition to -// the protocol-imposed limitations (gas limit, etc.), there are some -// further limitations on the content of transactions that can be -// added. Notably, contract code relying on the BLOCKHASH instruction -// will panic during execution. +// AddTx panics if the transaction cannot be executed. In addition to the protocol-imposed +// limitations (gas limit, etc.), there are some further limitations on the content of +// transactions that can be added. Notably, contract code relying on the BLOCKHASH +// instruction will panic during execution if it attempts to access a block number outside +// of the range created by GenerateChain. func (b *BlockGen) AddTx(tx *types.Transaction) { b.addTx(nil, vm.Config{}, tx) } @@ -122,11 +141,10 @@ func (b *BlockGen) AddTx(tx *types.Transaction) { // AddTxWithChain adds a transaction to the generated block. If no coinbase has // been set, the block's coinbase is set to the zero address. // -// AddTxWithChain panics if the transaction cannot be executed. In addition to -// the protocol-imposed limitations (gas limit, etc.), there are some -// further limitations on the content of transactions that can be -// added. If contract code relies on the BLOCKHASH instruction, -// the block in chain will be returned. +// AddTxWithChain panics if the transaction cannot be executed. In addition to the +// protocol-imposed limitations (gas limit, etc.), there are some further limitations on +// the content of transactions that can be added. If contract code relies on the BLOCKHASH +// instruction, the block in chain will be returned. func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) { b.addTx(bc, vm.Config{}, tx) } @@ -143,8 +161,7 @@ func (b *BlockGen) GetBalance(addr common.Address) *big.Int { return b.statedb.GetBalance(addr) } -// AddUncheckedTx forcefully adds a transaction to the block without any -// validation. +// AddUncheckedTx forcefully adds a transaction to the block without any validation. // // AddUncheckedTx will cause consensus failures when used during real // chain processing. This is best used in conjunction with raw block insertion. @@ -167,6 +184,16 @@ func (b *BlockGen) BaseFee() *big.Int { return new(big.Int).Set(b.header.BaseFee) } +// Gas returns the amount of gas left in the current block. +func (b *BlockGen) Gas() uint64 { + return b.header.GasLimit - b.header.GasUsed +} + +// Signer returns a valid signer instance for the current block. +func (b *BlockGen) Signer() types.Signer { + return types.MakeSigner(b.cm.config, b.header.Number, b.header.Time) +} + // AddUncheckedReceipt forcefully adds a receipts to the block without a // backing transaction. // @@ -192,20 +219,19 @@ func (b *BlockGen) AddUncle(h *types.Header) { var parent *types.Header for i := b.i - 1; i >= 0; i-- { - if b.chain[i].Hash() == h.ParentHash { - parent = b.chain[i].Header() + if b.cm.chain[i].Hash() == h.ParentHash { + parent = b.cm.chain[i].Header() break } } - chainreader := &fakeChainReader{config: b.config} - h.Difficulty = b.engine.CalcDifficulty(chainreader, b.header.Time, parent) + h.Difficulty = b.engine.CalcDifficulty(b.cm, b.header.Time, parent) // The gas limit and price should be derived from the parent h.GasLimit = parent.GasLimit - if b.config.IsLondon(h.Number) { - h.BaseFee = eip1559.CalcBaseFee(b.config, parent) - if !b.config.IsLondon(parent.Number) { - parentGasLimit := parent.GasLimit * b.config.ElasticityMultiplier() + if b.cm.config.IsLondon(h.Number) { + h.BaseFee = eip1559.CalcBaseFee(b.cm.config, parent) + if !b.cm.config.IsLondon(parent.Number) { + parentGasLimit := parent.GasLimit * b.cm.config.ElasticityMultiplier() h.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit) } } @@ -227,12 +253,12 @@ func (b *BlockGen) nextWithdrawalIndex() uint64 { return b.withdrawals[len(b.withdrawals)-1].Index + 1 } for i := b.i - 1; i >= 0; i-- { - if wd := b.chain[i].Withdrawals(); len(wd) != 0 { + if wd := b.cm.chain[i].Withdrawals(); len(wd) != 0 { return wd[len(wd)-1].Index + 1 } if i == 0 { // Correctly set the index if no parent had withdrawals. - if wd := b.parent.Withdrawals(); len(wd) != 0 { + if wd := b.cm.bottom.Withdrawals(); len(wd) != 0 { return wd[len(wd)-1].Index + 1 } } @@ -248,9 +274,9 @@ func (b *BlockGen) PrevBlock(index int) *types.Block { panic(fmt.Errorf("block index %d out of range (%d,%d)", index, -1, b.i)) } if index == -1 { - return b.parent + return b.cm.bottom } - return b.chain[index] + return b.cm.chain[index] } // OffsetTime modifies the time instance of a block, implicitly changing its @@ -258,11 +284,10 @@ func (b *BlockGen) PrevBlock(index int) *types.Block { // tied to chain length directly. func (b *BlockGen) OffsetTime(seconds int64) { b.header.Time += uint64(seconds) - if b.header.Time <= b.parent.Header().Time { + if b.header.Time <= b.cm.bottom.Header().Time { panic("block time out of range") } - chainreader := &fakeChainReader{config: b.config} - b.header.Difficulty = b.engine.CalcDifficulty(chainreader, b.header.Time, b.parent.Header()) + b.header.Difficulty = b.engine.CalcDifficulty(b.cm, b.header.Time, b.parent.Header()) } // GenerateChain creates a chain of n blocks. The first block's @@ -281,11 +306,14 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse if config == nil { config = params.TestChainConfig } - 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) { - b := &BlockGen{i: i, chain: blocks, parent: parent, statedb: statedb, config: config, engine: engine} - b.header = makeHeader(chainreader, parent, statedb, b.engine) + if engine == nil { + panic("nil consensus engine") + } + cm := newChainMaker(parent, config, engine) + + genblock := func(i int, parent *types.Block, triedb *trie.Database, statedb *state.StateDB) (*types.Block, types.Receipts) { + b := &BlockGen{i: i, cm: cm, parent: parent, statedb: statedb, engine: engine} + b.header = cm.makeHeader(parent, statedb, b.engine) // Set the difficulty for clique block. The chain maker doesn't have access // to a chain, so the difficulty will be left unset (nil). Set it here to the @@ -315,35 +343,62 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse if gen != nil { gen(i, b) } - if b.engine != nil { - block, err := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts, b.withdrawals) - if err != nil { - panic(err) - } - // Write state changes to db - 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 { - panic(fmt.Sprintf("trie write error: %v", err)) - } - return block, b.receipts + block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, b.txs, b.uncles, b.receipts, b.withdrawals) + if err != nil { + panic(err) + } + + // Write state changes to db + root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number)) + if err != nil { + panic(fmt.Sprintf("state write error: %v", err)) } - return nil, nil + if err = triedb.Commit(root, false); err != nil { + panic(fmt.Sprintf("trie write error: %v", err)) + } + return block, b.receipts } + + // 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) - blocks[i] = block - receipts[i] = receipt + block, receipts := genblock(i, parent, triedb, statedb) + + // Post-process the receipts. + // Here we assign the final block hash and other info into the receipt. + // In order for DeriveFields to work, the transaction and receipt lists need to be + // of equal length. If AddUncheckedTx or AddUncheckedReceipt are used, there will be + // extra ones, so we just trim the lists here. + receiptsCount := len(receipts) + txs := block.Transactions() + if len(receipts) > len(txs) { + receipts = receipts[:len(txs)] + } else if len(receipts) < len(txs) { + txs = txs[:len(receipts)] + } + var blobGasPrice *big.Int + if block.ExcessBlobGas() != nil { + blobGasPrice = eip4844.CalcBlobFee(*block.ExcessBlobGas()) + } + if err := receipts.DeriveFields(config, block.Hash(), block.NumberU64(), block.Time(), block.BaseFee(), blobGasPrice, txs); err != nil { + panic(err) + } + + // Re-expand to ensure all receipts are returned. + receipts = receipts[:receiptsCount] + + // Advance the chain. + cm.add(block, receipts) parent = block } - return blocks, receipts + return cm.chain, cm.receipts } // GenerateChainWithGenesis is a wrapper of GenerateChain which will initialize @@ -351,7 +406,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) } @@ -359,34 +416,39 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, return db, blocks, receipts } -func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header { - var time uint64 - if parent.Time() == 0 { - time = 10 - } else { - time = parent.Time() + 10 // block time is fixed at 10 seconds - } +func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header { + time := parent.Time() + 10 // block time is fixed at 10 seconds header := &types.Header{ - Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())), + Root: state.IntermediateRoot(cm.config.IsEIP158(parent.Number())), ParentHash: parent.Hash(), Coinbase: parent.Coinbase(), - Difficulty: engine.CalcDifficulty(chain, time, &types.Header{ - Number: parent.Number(), - Time: time - 10, - Difficulty: parent.Difficulty(), - UncleHash: parent.UncleHash(), - }), - GasLimit: parent.GasLimit(), - Number: new(big.Int).Add(parent.Number(), common.Big1), - Time: time, + Difficulty: engine.CalcDifficulty(cm, time, parent.Header()), + GasLimit: parent.GasLimit(), + Number: new(big.Int).Add(parent.Number(), common.Big1), + Time: time, } - if chain.Config().IsLondon(header.Number) { - header.BaseFee = eip1559.CalcBaseFee(chain.Config(), parent.Header()) - if !chain.Config().IsLondon(parent.Number()) { - parentGasLimit := parent.GasLimit() * chain.Config().ElasticityMultiplier() + + if cm.config.IsLondon(header.Number) { + header.BaseFee = eip1559.CalcBaseFee(cm.config, parent.Header()) + if !cm.config.IsLondon(parent.Number()) { + parentGasLimit := parent.GasLimit() * cm.config.ElasticityMultiplier() header.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit) } } + if cm.config.IsCancun(header.Number, header.Time) { + 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 } @@ -426,18 +488,86 @@ func makeBlockChainWithGenesis(genesis *Genesis, n int, engine consensus.Engine, return db, blocks } -type fakeChainReader struct { - config *params.ChainConfig +// chainMaker contains the state of chain generation. +type chainMaker struct { + bottom *types.Block + engine consensus.Engine + config *params.ChainConfig + chain []*types.Block + chainByHash map[common.Hash]*types.Block + receipts []types.Receipts +} + +func newChainMaker(bottom *types.Block, config *params.ChainConfig, engine consensus.Engine) *chainMaker { + return &chainMaker{ + bottom: bottom, + config: config, + engine: engine, + chainByHash: make(map[common.Hash]*types.Block), + } +} + +func (cm *chainMaker) add(b *types.Block, r []*types.Receipt) { + cm.chain = append(cm.chain, b) + cm.chainByHash[b.Hash()] = b + cm.receipts = append(cm.receipts, r) +} + +func (cm *chainMaker) blockByNumber(number uint64) *types.Block { + if number == cm.bottom.NumberU64() { + return cm.bottom + } + cur := cm.CurrentHeader().Number.Uint64() + lowest := cm.bottom.NumberU64() + 1 + if number < lowest || number > cur { + return nil + } + return cm.chain[number-lowest] +} + +// ChainReader/ChainContext implementation + +// Config returns the chain configuration (for consensus.ChainReader). +func (cm *chainMaker) Config() *params.ChainConfig { + return cm.config } -// Config returns the chain configuration. -func (cr *fakeChainReader) Config() *params.ChainConfig { - return cr.config +// Engine returns the consensus engine (for ChainContext). +func (cm *chainMaker) Engine() consensus.Engine { + return cm.engine } -func (cr *fakeChainReader) CurrentHeader() *types.Header { return nil } -func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header { return nil } -func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil } -func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil } -func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil } -func (cr *fakeChainReader) GetTd(hash common.Hash, number uint64) *big.Int { return nil } +func (cm *chainMaker) CurrentHeader() *types.Header { + if len(cm.chain) == 0 { + return cm.bottom.Header() + } + return cm.chain[len(cm.chain)-1].Header() +} + +func (cm *chainMaker) GetHeaderByNumber(number uint64) *types.Header { + b := cm.blockByNumber(number) + if b == nil { + return nil + } + return b.Header() +} + +func (cm *chainMaker) GetHeaderByHash(hash common.Hash) *types.Header { + b := cm.chainByHash[hash] + if b == nil { + return nil + } + return b.Header() +} + +func (cm *chainMaker) GetHeader(hash common.Hash, number uint64) *types.Header { + return cm.GetHeaderByNumber(number) +} + +func (cm *chainMaker) GetBlock(hash common.Hash, number uint64) *types.Block { + return cm.blockByNumber(number) +} + +func (cm *chainMaker) GetTd(hash common.Hash, number uint64) *big.Int { + return nil // not supported +} diff --git a/core/chain_makers_test.go b/core/chain_makers_test.go index 4f7355527..84148841f 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -19,8 +19,10 @@ package core import ( "fmt" "math/big" + "reflect" "testing" + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/beacon" "github.com/ethereum/go-ethereum/consensus/ethash" @@ -29,9 +31,10 @@ 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) { +func TestGeneratePOSChain(t *testing.T) { var ( keyHex = "9c647b8b7c4e7c3490668fb6c11473619db80c93704c70893d3813af4090c39c" key, _ = crypto.HexToECDSA(keyHex) @@ -40,21 +43,25 @@ func TestGenerateWithdrawalChain(t *testing.T) { bb = common.Address{0xbb} funds = big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(params.Ether)) config = *params.AllEthashProtocolChanges + asm4788 = common.Hex2Bytes("3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500") gspec = &Genesis{ - Config: &config, - Alloc: GenesisAlloc{address: {Balance: funds}}, + Config: &config, + Alloc: GenesisAlloc{ + address: {Balance: funds}, + params.BeaconRootsStorageAddress: {Balance: common.Big0, Code: asm4788}, + }, BaseFee: big.NewInt(params.InitialBaseFee), Difficulty: common.Big1, GasLimit: 5_000_000, } - gendb = rawdb.NewMemoryDatabase() - signer = types.LatestSigner(gspec.Config) - db = rawdb.NewMemoryDatabase() + gendb = rawdb.NewMemoryDatabase() + db = rawdb.NewMemoryDatabase() ) config.TerminalTotalDifficultyPassed = true config.TerminalTotalDifficulty = common.Big0 config.ShanghaiTime = u64(0) + config.CancunTime = u64(0) // init 0xaa with some storage elements storage := make(map[common.Hash]common.Hash) @@ -74,12 +81,22 @@ func TestGenerateWithdrawalChain(t *testing.T) { Storage: storage, Code: common.Hex2Bytes("600154600354"), } + genesis := gspec.MustCommit(gendb, trie.NewDatabase(gendb, trie.HashDefaults)) - genesis := gspec.MustCommit(gendb) + genchain, genreceipts := GenerateChain(gspec.Config, genesis, beacon.NewFaker(), gendb, 4, func(i int, gen *BlockGen) { + gen.SetParentBeaconRoot(common.Hash{byte(i + 1)}) - 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) + // Add value transfer tx. + tx := types.MustSignNewTx(key, gen.Signer(), &types.LegacyTx{ + Nonce: gen.TxNonce(address), + To: &address, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: new(big.Int).Add(gen.BaseFee(), common.Big1), + }) gen.AddTx(tx) + + // Add withdrawals. if i == 1 { gen.AddWithdrawal(&types.Withdrawal{ Validator: 42, @@ -110,21 +127,42 @@ func TestGenerateWithdrawalChain(t *testing.T) { blockchain, _ := NewBlockChain(db, nil, gspec, nil, beacon.NewFaker(), vm.Config{}, nil, nil) defer blockchain.Stop() - if i, err := blockchain.InsertChain(chain); err != nil { - fmt.Printf("insert error (block %d): %v\n", chain[i].NumberU64(), err) - return + if i, err := blockchain.InsertChain(genchain); err != nil { + t.Fatalf("insert error (block %d): %v\n", genchain[i].NumberU64(), err) } // enforce that withdrawal indexes are monotonically increasing from 0 var ( withdrawalIndex uint64 - head = blockchain.CurrentBlock().Number.Uint64() ) - for i := 0; i < int(head); i++ { - block := blockchain.GetBlockByNumber(uint64(i)) + for i := range genchain { + blocknum := genchain[i].NumberU64() + block := blockchain.GetBlockByNumber(blocknum) if block == nil { - t.Fatalf("block %d not found", i) + t.Fatalf("block %d not found", blocknum) } + + // Verify receipts. + genBlockReceipts := genreceipts[i] + for _, r := range genBlockReceipts { + if r.BlockNumber.Cmp(block.Number()) != 0 { + t.Errorf("receipt has wrong block number %d, want %d", r.BlockNumber, block.Number()) + } + if r.BlockHash != block.Hash() { + t.Errorf("receipt has wrong block hash %v, want %v", r.BlockHash, block.Hash()) + } + + // patch up empty logs list to make DeepEqual below work + if r.Logs == nil { + r.Logs = []*types.Log{} + } + } + blockchainReceipts := blockchain.GetReceiptsByHash(block.Hash()) + if !reflect.DeepEqual(genBlockReceipts, blockchainReceipts) { + t.Fatalf("receipts mismatch\ngenerated: %s\nblockchain: %s", spew.Sdump(genBlockReceipts), spew.Sdump(blockchainReceipts)) + } + + // Verify withdrawals. if len(block.Withdrawals()) == 0 { continue } @@ -134,6 +172,18 @@ func TestGenerateWithdrawalChain(t *testing.T) { } withdrawalIndex += 1 } + + // Verify parent beacon root. + want := common.Hash{byte(blocknum)} + if got := block.BeaconRoot(); *got != want { + t.Fatalf("block %d, wrong parent beacon root: got %s, want %s", i, got, want) + } + state, _ := blockchain.State() + idx := block.Time()%8191 + 8191 + got := state.GetState(params.BeaconRootsStorageAddress, common.BigToHash(new(big.Int).SetUint64(idx))) + if got != want { + t.Fatalf("block %d, wrong parent beacon root in state: got %s, want %s", i, got, want) + } } } @@ -146,6 +196,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 +204,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 +239,7 @@ func ExampleGenerateChain() { }) // Import the chain. This runs all block validation rules. - blockchain, _ := NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) + blockchain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.HashScheme), 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 f2e8dfe8f..b9a899ef2 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/evm.go b/core/evm.go index 104f2c09d..c4801dc79 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,31 +54,38 @@ 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 } return vm.BlockContext{ - CanTransfer: CanTransfer, - Transfer: Transfer, - GetHash: GetHashFn(header, chain), - Coinbase: beneficiary, - BlockNumber: new(big.Int).Set(header.Number), - Time: header.Time, - Difficulty: new(big.Int).Set(header.Difficulty), - BaseFee: baseFee, - GasLimit: header.GasLimit, - Random: random, - ExcessBlobGas: header.ExcessBlobGas, + CanTransfer: CanTransfer, + Transfer: Transfer, + GetHash: GetHashFn(header, chain), + Coinbase: beneficiary, + BlockNumber: new(big.Int).Set(header.Number), + Time: header.Time, + Difficulty: new(big.Int).Set(header.Difficulty), + BaseFee: baseFee, + BlobBaseFee: blobBaseFee, + GasLimit: header.GasLimit, + Random: random, } } // NewEVMTxContext creates a new transaction context for a single transaction. func NewEVMTxContext(msg *Message) vm.TxContext { - return vm.TxContext{ + ctx := vm.TxContext{ Origin: msg.From, GasPrice: new(big.Int).Set(msg.GasPrice), BlobHashes: msg.BlobHashes, } + if msg.BlobGasFeeCap != nil { + ctx.BlobFeeCap = new(big.Int).Set(msg.BlobGasFeeCap) + } + return ctx } // GetHashFn returns a GetHashFunc which retrieves header hashes by number diff --git a/core/forkid/forkid.go b/core/forkid/forkid.go index 896455884..76825d3be 100644 --- a/core/forkid/forkid.go +++ b/core/forkid/forkid.go @@ -26,7 +26,6 @@ import ( "reflect" "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" @@ -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() @@ -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 1dc65aec3..753a32b7e 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 @@ -77,7 +81,7 @@ func TestCreation(t *testing.T) { // 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 @@ -87,14 +91,16 @@ func TestCreation(t *testing.T) { {5000000, 0, ID{Hash: checksumToBytes(0x757a1c47), Next: 5062605}}, // Last Berlin block {5062605, 0, ID{Hash: checksumToBytes(0xB8C6299D), Next: 1678832736}}, // First London block {6000000, 1678832735, ID{Hash: checksumToBytes(0xB8C6299D), Next: 1678832736}}, // Last London block - {6000001, 1678832736, ID{Hash: checksumToBytes(0xf9843abf), Next: 0}}, // First Shanghai block - {6500000, 2678832736, ID{Hash: checksumToBytes(0xf9843abf), Next: 0}}, // Future Shanghai block + {6000001, 1678832736, ID{Hash: checksumToBytes(0xf9843abf), Next: 1705473120}}, // First Shanghai block + {6500002, 1705473119, ID{Hash: checksumToBytes(0xf9843abf), Next: 1705473120}}, // Last Shanghai block + {6500003, 1705473120, ID{Hash: checksumToBytes(0x70cc14e2), Next: 0}}, // First Cancun block + {6500003, 2705473120, ID{Hash: checksumToBytes(0x70cc14e2), Next: 0}}, // Future Cancun block }, }, // 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 @@ -103,6 +109,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 { @@ -352,8 +368,9 @@ func TestValidation(t *testing.T) { // TODO(karalabe): Enable this when Cancun is specced //{params.MainnetChainConfig, 20999999, 1677999999, ID{Hash: checksumToBytes(0x71147644), Next: 1678000000}, ErrLocalIncompatibleOrStale}, } + genesis := core.DefaultGenesisBlock().ToBlock() for i, tt := range tests { - filter := newFilter(tt.config, params.MainnetGenesisHash, func() (uint64, uint64) { return tt.head, tt.time }) + filter := newFilter(tt.config, genesis, 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) } @@ -382,3 +399,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/genesis.go b/core/genesis.go index 33716d0b6..634be9a9e 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -37,6 +37,7 @@ 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/pathdb" ) //go:generate go run github.com/fjl/gencodec -type Genesis -field-override genesisSpecMarshaling -out gen_genesis.go @@ -120,17 +121,29 @@ 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(isVerkle bool) (common.Hash, error) { + // If a genesis-time verkle trie is requested, create a trie config + // with the verkle trie enabled so that the tree can be initialized + // as such. + var config *trie.Config + if isVerkle { + config = &trie.Config{ + PathDB: pathdb.Defaults, + IsVerkle: true, + } + } // 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()) + db := state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), config) statedb, err := state.New(types.EmptyRootHash, db, nil) if err != nil { 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 { @@ -140,16 +153,18 @@ func (ga *GenesisAlloc) deriveHash() (common.Hash, error) { 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 { @@ -175,39 +190,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.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"` @@ -316,20 +298,23 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen } else { log.Info("Writing custom genesis block") } + applyOverrides(genesis.Config) block, err := genesis.Commit(db, triedb) if err != nil { return genesis.Config, common.Hash{}, err } - 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() } + applyOverrides(genesis.Config) // Ensure the stored genesis matches with the given one. hash := genesis.ToBlock().Hash() if hash != stored { @@ -339,11 +324,11 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen if err != nil { return genesis.Config, hash, err } - applyOverrides(genesis.Config) return genesis.Config, block.Hash(), nil } // Check whether the genesis block is already written. if genesis != nil { + applyOverrides(genesis.Config) hash := genesis.ToBlock().Hash() if hash != stored { return genesis.Config, hash, &GenesisMismatchError{stored, hash} @@ -436,9 +421,15 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { } } +// IsVerkle indicates whether the state is already stored in a verkle +// tree at genesis time. +func (g *Genesis) IsVerkle() bool { + return g.Config.IsVerkle(new(big.Int).SetUint64(g.Number), g.Timestamp) +} + // 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(g.IsVerkle()) if err != nil { panic(err) } @@ -477,6 +468,11 @@ func (g *Genesis) ToBlock() *types.Block { withdrawals = make([]*types.Withdrawal, 0) } if conf.IsCancun(num, g.Timestamp) { + // 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 { @@ -526,10 +522,8 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block // 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) } @@ -573,17 +567,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(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.AllDevChainProtocolChanges // Assemble and return the genesis with the precompiles and faucet pre-funded - return &Genesis{ + genesis := &Genesis{ Config: &config, GasLimit: gasLimit, BaseFee: big.NewInt(params.InitialBaseFee), - Difficulty: big.NewInt(0), + Difficulty: big.NewInt(1), Alloc: map[common.Address]GenesisAccount{ common.BytesToAddress([]byte{1}): {Balance: big.NewInt(1)}, // ECRecover common.BytesToAddress([]byte{2}): {Balance: big.NewInt(1)}, // SHA256 @@ -594,19 +600,43 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet common.Address) *Genesis { 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))}, }, } + if faucet != nil { + genesis.Alloc[*faucet] = GenesisAccount{Balance: new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 256), big.NewInt(9))} + } + return genesis } 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 4703e4519..94c4ec535 100644 --- a/core/genesis_alloc.go +++ b/core/genesis_alloc.go @@ -24,3 +24,4 @@ package core 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\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 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 723d1e476..1d85b510c 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -17,6 +17,7 @@ package core import ( + "bytes" "encoding/json" "math/big" "reflect" @@ -30,18 +31,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 +60,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 +71,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 +79,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 +87,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 +96,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 +106,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 +117,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 +129,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, &oldcustomg, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil) + bc, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), &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, @@ -175,7 +187,8 @@ func TestGenesisHashes(t *testing.T) { {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 @@ -193,7 +206,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") @@ -219,7 +232,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(false) ) blob, _ := json.Marshal(alloc) rawdb.WriteGenesisStateSpec(db, hash, blob) @@ -242,3 +255,73 @@ func TestReadWriteGenesisAlloc(t *testing.T) { } } } + +func newDbConfig(scheme string) *trie.Config { + if scheme == rawdb.HashScheme { + return trie.HashDefaults + } + return &trie.Config{PathDB: pathdb.Defaults} +} + +func TestVerkleGenesisCommit(t *testing.T) { + var verkleTime uint64 = 0 + verkleConfig := ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, + 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: nil, + ShanghaiTime: &verkleTime, + CancunTime: &verkleTime, + PragueTime: &verkleTime, + VerkleTime: &verkleTime, + TerminalTotalDifficulty: big.NewInt(0), + TerminalTotalDifficultyPassed: true, + Ethash: nil, + Clique: nil, + } + + genesis := &Genesis{ + BaseFee: big.NewInt(params.InitialBaseFee), + Config: verkleConfig, + Timestamp: verkleTime, + Difficulty: big.NewInt(0), + Alloc: GenesisAlloc{ + {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, + }, + } + + expected := common.Hex2Bytes("14398d42be3394ff8d50681816a4b7bf8d8283306f577faba2d5bc57498de23b") + got := genesis.ToBlock().Root().Bytes() + if !bytes.Equal(got, expected) { + t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got) + } + + db := rawdb.NewMemoryDatabase() + triedb := trie.NewDatabase(db, &trie.Config{IsVerkle: true, PathDB: pathdb.Defaults}) + block := genesis.MustCommit(db, triedb) + if !bytes.Equal(block.Root().Bytes(), expected) { + t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got) + } + + // Test that the trie is verkle + if !triedb.IsVerkle() { + t.Fatalf("expected trie to be verkle") + } + + if !rawdb.ExistsAccountTrieNode(db, nil) { + t.Fatal("could not find node") + } +} diff --git a/core/headerchain_test.go b/core/headerchain_test.go index 08d19f695..2c0323e6f 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 0e7355f63..12c40c14f 100644 --- a/core/mkalloc.go +++ b/core/mkalloc.go @@ -32,24 +32,51 @@ import ( "os" "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 allocItemMisc struct { + Nonce uint64 + Code []byte + Slots []allocItemStorageItem +} + +type allocItemStorageItem struct { + Key common.Hash + Val common.Hash +} 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()) - items = append(items, allocItem{bigAddr, account.Balance}) + items = append(items, allocItem{bigAddr, account.Balance, misc}) } - slices.SortFunc(items, func(a, b allocItem) bool { - return a.Addr.Cmp(b.Addr) < 0 + slices.SortFunc(items, func(a, b allocItem) int { + return a.Addr.Cmp(b.Addr) }) return items } diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index c7abd44f3..d9a89fe90 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -334,13 +334,18 @@ func ReadHeaderRange(db ethdb.Reader, number uint64, count uint64) []rlp.RawValu return rlpHeaders } // read remaining from ancients - max := count * 700 - data, err := db.AncientRange(ChainFreezerHeaderTable, i+1-count, count, max) - if err == nil && uint64(len(data)) == count { - // the data is on the order [h, h+1, .., n] -- reordering needed - for i := range data { - rlpHeaders = append(rlpHeaders, data[len(data)-1-i]) - } + data, err := db.AncientRange(ChainFreezerHeaderTable, i+1-count, count, 0) + if err != nil { + log.Error("Failed to read headers from freezer", "err", err) + return rlpHeaders + } + if uint64(len(data)) != count { + log.Warn("Incomplete read of headers from freezer", "wanted", count, "read", len(data)) + return rlpHeaders + } + // The data is on the order [h, h+1, .., n] -- reordering needed + for i := range data { + rlpHeaders = append(rlpHeaders, data[len(data)-1-i]) } return rlpHeaders } @@ -381,7 +386,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 } @@ -498,7 +503,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 } @@ -544,7 +549,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 } @@ -731,7 +736,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 { diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index beeec9f5a..a7ceb7299 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -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_metadata.go b/core/rawdb/accessors_metadata.go index 2ff29d1ad..859566f72 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_sync.go b/core/rawdb/accessors_sync.go index e87ad43c3..2dc08b3b7 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 12f1ecdf8..ea3367db3 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,7 +44,7 @@ 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" // hasher is used to compute the sha256 hash of the provided data. type hasher struct{ sha crypto.KeccakState } @@ -89,6 +89,16 @@ func HasAccountTrieNode(db ethdb.KeyValueReader, path []byte, hash common.Hash) 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. func WriteAccountTrieNode(db ethdb.KeyValueWriter, path []byte, node []byte) { if err := db.Put(accountTrieNodeKey(path), node); err != nil { @@ -127,6 +137,16 @@ func HasStorageTrieNode(db ethdb.KeyValueReader, accountHash common.Hash, path [ 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. func WriteStorageTrieNode(db ethdb.KeyValueWriter, accountHash common.Hash, path []byte, node []byte) { if err := db.Put(storageTrieNodeKey(accountHash, path), node); err != nil { @@ -263,3 +283,65 @@ 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 + } + // The root node might be deleted during the initial snap sync, check + // the persistent state id then. + if id := ReadPersistentStateID(db); id != 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 c1cd7fda1..e88867af0 100644 --- a/core/rawdb/ancient_scheme.go +++ b/core/rawdb/ancient_scheme.go @@ -58,7 +58,7 @@ const ( stateHistoryStorageData = "storage.data" ) -var stateHistoryFreezerNoSnappy = map[string]bool{ +var stateFreezerNoSnappy = map[string]bool{ stateHistoryMeta: true, stateHistoryAccountIndex: false, stateHistoryStorageIndex: false, @@ -68,14 +68,14 @@ var stateHistoryFreezerNoSnappy = map[string]bool{ // 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. + 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, stateFreezerName} +var freezers = []string{ChainFreezerName, StateFreezerName} -// NewStateHistoryFreezer initializes the freezer for state history. -func NewStateHistoryFreezer(ancientDir string, readOnly bool) (*ResettableFreezer, error) { - return NewResettableFreezer(filepath.Join(ancientDir, stateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateHistoryFreezerNoSnappy) +// 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 363a911ae..428cda544 100644 --- a/core/rawdb/ancient_utils.go +++ b/core/rawdb/ancient_utils.go @@ -18,6 +18,7 @@ package rawdb import ( "fmt" + "path/filepath" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" @@ -50,36 +51,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)}) + case ChainFreezerName: + 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: @@ -99,8 +125,10 @@ func InspectFreezerTable(ancient string, freezerName string, tableName string, s tables map[string]bool ) switch freezerName { - case chainFreezerName: + case ChainFreezerName: path, tables = resolveChainFreezerDir(ancient), chainFreezerNoSnappy + case StateFreezerName: + path, tables = filepath.Join(ancient, freezerName), stateFreezerNoSnappy default: return fmt.Errorf("unknown freezer, supported ones: %v", freezers) } diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go index 22dbda4a2..bb2c409db 100644 --- a/core/rawdb/chain_freezer.go +++ b/core/rawdb/chain_freezer.go @@ -131,7 +131,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { continue case *number < threshold: - log.Debug("Current full block not old enough", "number", *number, "hash", hash, "delay", threshold) + log.Debug("Current full block not old enough to freeze", "number", *number, "hash", hash, "delay", threshold) backoff = true continue @@ -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/database.go b/core/rawdb/database.go index 7bc910906..18b5bccb5 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -30,11 +30,12 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/leveldb" "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/ethdb/pebble" "github.com/ethereum/go-ethereum/log" "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 @@ -141,7 +142,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, @@ -177,7 +178,7 @@ func resolveChainFreezerDir(ancient string) string { // sub folder, if not then two possibilities: // - chain freezer is not initialized // - chain freezer exists in legacy location (root ancient folder) - freezer := path.Join(ancient, chainFreezerName) + freezer := path.Join(ancient, ChainFreezerName) if !common.FileExist(freezer) { if !common.FileExist(ancient) { // The entire ancient store is not initialized, still use the sub @@ -209,7 +210,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 +219,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 +254,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) @@ -321,15 +322,25 @@ func NewLevelDBDatabase(file string, cache int, handles int, namespace string, r return NewDatabase(db), nil } +// 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, ephemeral bool) (ethdb.Database, error) { + db, err := pebble.New(file, cache, handles, namespace, readonly, ephemeral) + if err != nil { + return nil, err + } + return NewDatabase(db), nil +} + const ( dbPebble = "pebble" 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 +363,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,31 +381,21 @@ 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) - } else { - return nil, errors.New("db.engine 'pebble' not supported on this platform") - } + log.Info("Using pebble as the backing database") + return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly, o.Ephemeral) } if o.Type == dbLeveldb || existingDb == dbLeveldb { log.Info("Using leveldb as the backing database") return NewLevelDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly) } - // No pre-existing database, no user-requested one either. Default to Pebble - // 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) - } else { - log.Info("Defaulting to leveldb as the backing database") - return NewLevelDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly) - } + // No pre-existing database, no user-requested one either. Default to Pebble. + log.Info("Defaulting to pebble as the backing database") + return NewPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly, o.Ephemeral) } // Open opens both a disk-based key-value database such as leveldb or pebble, but also @@ -463,7 +467,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 +511,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 +556,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 +585,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 +635,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 deleted file mode 100644 index 73bfeb208..000000000 --- a/core/rawdb/databases_64bit.go +++ /dev/null @@ -1,37 +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 - -//go:build (arm64 || amd64) && !openbsd - -package rawdb - -import ( - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/ethdb/pebble" -) - -// Pebble is unsuported 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) - if err != nil { - return nil, err - } - return NewDatabase(db), nil -} diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index a9fe23432..b7824ddc0 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -108,7 +108,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 := flock.New(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") diff --git a/core/rawdb/freezer_batch.go b/core/rawdb/freezer_batch.go index 3cc7d84f4..84a63a451 100644 --- a/core/rawdb/freezer_batch.go +++ b/core/rawdb/freezer_batch.go @@ -182,19 +182,27 @@ func (batch *freezerTableBatch) maybeCommit() error { // commit writes the batched items to the backing freezerTable. func (batch *freezerTableBatch) commit() error { - // Write data. + // Write data. The head file is fsync'd after write to ensure the + // data is truly transferred to disk. _, err := batch.t.head.Write(batch.dataBuffer) if err != nil { return err } + if err := batch.t.head.Sync(); err != nil { + return err + } dataSize := int64(len(batch.dataBuffer)) batch.dataBuffer = batch.dataBuffer[:0] - // Write indices. + // Write indices. The index file is fsync'd after write to ensure the + // data indexes are truly transferred to disk. _, err = batch.t.index.Write(batch.indexBuffer) if err != nil { return err } + if err := batch.t.index.Sync(); err != nil { + return err + } indexSize := int64(len(batch.indexBuffer)) batch.indexBuffer = batch.indexBuffer[:0] diff --git a/core/rawdb/freezer_resettable.go b/core/rawdb/freezer_resettable.go index 0a3892bcd..7a8548973 100644 --- a/core/rawdb/freezer_resettable.go +++ b/core/rawdb/freezer_resettable.go @@ -22,6 +22,7 @@ import ( "sync" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" ) const tmpSuffix = ".tmp" @@ -118,9 +119,10 @@ func (f *ResettableFreezer) 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 *ResettableFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { f.lock.RLock() defer f.lock.RUnlock() @@ -224,6 +226,7 @@ func cleanup(path string) error { } for _, name := range names { if name == filepath.Base(path)+tmpSuffix { + log.Info("Removed leftover freezer directory", "name", name) return os.RemoveAll(filepath.Join(parent, name)) } } diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index fc6316c95..4b9d510e8 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -212,7 +212,12 @@ func (t *freezerTable) repair() error { } // Ensure the index is a multiple of indexEntrySize bytes if overflow := stat.Size() % indexEntrySize; overflow != 0 { - truncateFreezerFile(t.index, stat.Size()-overflow) // New file can't trigger this path + if t.readonly { + return fmt.Errorf("index file(path: %s, name: %s) size is not a multiple of %d", t.path, t.name, indexEntrySize) + } + if err := truncateFreezerFile(t.index, stat.Size()-overflow); err != nil { + return err + } // New file can't trigger this path } // Retrieve the file sizes and prepare for truncation if stat, err = t.index.Stat(); err != nil { @@ -254,6 +259,12 @@ func (t *freezerTable) repair() error { t.index.ReadAt(buffer, offsetsSize-indexEntrySize) lastIndex.unmarshalBinary(buffer) } + // Print an error log if the index is corrupted due to an incorrect + // last index item. While it is theoretically possible to have a zero offset + // by storing all zero-size items, it is highly unlikely to occur in practice. + if lastIndex.offset == 0 && offsetsSize/indexEntrySize > 1 { + log.Error("Corrupted index file detected", "lastOffset", lastIndex.offset, "indexes", offsetsSize/indexEntrySize) + } if t.readonly { t.head, err = t.openFile(lastIndex.filenum, openFreezerFileForReadOnly) } else { @@ -270,6 +281,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 { @@ -343,7 +357,7 @@ func (t *freezerTable) repair() error { return err } if verbose { - t.logger.Info("Chain freezer table opened", "items", t.items.Load(), "size", t.headBytes) + t.logger.Info("Chain freezer table opened", "items", t.items.Load(), "deleted", t.itemOffset.Load(), "hidden", t.itemHidden.Load(), "tailId", t.tailId, "headId", t.headId, "size", t.headBytes) } else { t.logger.Debug("Chain freezer table opened", "items", t.items.Load(), "size", common.StorageSize(t.headBytes)) } @@ -404,6 +418,9 @@ func (t *freezerTable) truncateHead(items uint64) error { if err := truncateFreezerFile(t.index, int64(length+1)*indexEntrySize); err != nil { return err } + if err := t.index.Sync(); err != nil { + return err + } // Calculate the new expected size of the data file and truncate it var expected indexEntry if length == 0 { @@ -426,6 +443,7 @@ func (t *freezerTable) truncateHead(items uint64) error { // Release any files _after the current head -- both the previous head // and any files which may have been opened for reading t.releaseFilesAfter(expected.filenum, true) + // Set back the historic head t.head = newHead t.headId = expected.filenum @@ -433,6 +451,9 @@ func (t *freezerTable) truncateHead(items uint64) error { if err := truncateFreezerFile(t.head, int64(expected.offset)); err != nil { return err } + if err := t.head.Sync(); err != nil { + return err + } // All data files truncated, set internal counters and return t.headBytes = int64(expected.offset) t.items.Store(items) @@ -446,6 +467,20 @@ func (t *freezerTable) truncateHead(items uint64) error { return nil } +// sizeHidden returns the total data size of hidden items in the freezer table. +// This function assumes the lock is already held. +func (t *freezerTable) sizeHidden() (uint64, error) { + hidden, offset := t.itemHidden.Load(), t.itemOffset.Load() + if hidden <= offset { + return 0, nil + } + indices, err := t.getIndices(hidden-1, 1) + if err != nil { + return 0, err + } + return uint64(indices[1].offset), nil +} + // truncateTail discards any recent data before the provided threshold number. func (t *freezerTable) truncateTail(items uint64) error { t.lock.Lock() @@ -474,6 +509,12 @@ func (t *freezerTable) truncateTail(items uint64) error { newTail.unmarshalBinary(buffer) newTailId = newTail.filenum } + // Save the old size for metrics tracking. This needs to be done + // before any updates to either itemHidden or itemOffset. + oldSize, err := t.sizeNolock() + if err != nil { + return err + } // Update the virtual tail marker and hidden these entries in table. t.itemHidden.Store(items) if err := writeMetadata(t.meta, newMetadata(items)); err != nil { @@ -488,18 +529,12 @@ func (t *freezerTable) truncateTail(items uint64) error { if t.tailId > newTailId { return fmt.Errorf("invalid index, tail-file %d, item-file %d", t.tailId, newTailId) } - // Hidden items exceed the current tail file, drop the relevant - // data files. We need to truncate, save the old size for metrics - // tracking. - oldSize, err := t.sizeNolock() - if err != nil { - return err - } // Count how many items can be deleted from the file. var ( newDeleted = items deleted = t.itemOffset.Load() ) + // Hidden items exceed the current tail file, drop the relevant data files. for current := items - 1; current >= deleted; current -= 1 { if _, err := t.index.ReadAt(buffer, int64((current-deleted+1)*indexEntrySize)); err != nil { return err @@ -516,6 +551,10 @@ func (t *freezerTable) truncateTail(items uint64) error { if err := t.meta.Sync(); err != nil { return err } + // Close the index file before shorten it. + if err := t.index.Close(); err != nil { + return err + } // Truncate the deleted index entries from the index file. err = copyFrom(t.index.Name(), t.index.Name(), indexEntrySize*(newDeleted-deleted+1), func(f *os.File) error { tailIndex := indexEntry{ @@ -529,13 +568,14 @@ func (t *freezerTable) truncateTail(items uint64) error { return err } // Reopen the modified index file to load the changes - if err := t.index.Close(); err != nil { - return err - } t.index, err = openFreezerFileForAppend(t.index.Name()) if err != nil { return err } + // Sync the file to ensure changes are flushed to disk + if err := t.index.Sync(); err != nil { + return err + } // Release any files before the current tail t.tailId = newTailId t.itemOffset.Store(newDeleted) @@ -572,10 +612,12 @@ func (t *freezerTable) Close() error { // error on Windows. doClose(t.index, true, true) doClose(t.meta, true, true) + // The preopened non-head data-files are all opened in readonly. // The head is opened in rw-mode, so we sync it here - but since it's also // part of t.files, it will be closed in the loop below. doClose(t.head, true, false) // sync but do not close + for _, f := range t.files { doClose(f, false, true) // close but do not sync } @@ -652,6 +694,7 @@ func (t *freezerTable) releaseFilesBefore(num uint32, remove bool) { func (t *freezerTable) getIndices(from, count uint64) ([]*indexEntry, error) { // Apply the table-offset from = from - t.itemOffset.Load() + // For reading N items, we need N+1 indices. buffer := make([]byte, (count+1)*indexEntrySize) if _, err := t.index.ReadAt(buffer, int64(from*indexEntrySize)); err != nil { @@ -768,7 +811,7 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i return fmt.Errorf("missing data file %d", fileId) } if _, err := dataFile.ReadAt(output[len(output)-length:], int64(start)); err != nil { - return err + return fmt.Errorf("%w, fileid: %d, start: %d, length: %d", err, fileId, start, length) } return nil } @@ -842,14 +885,18 @@ func (t *freezerTable) size() (uint64, error) { return t.sizeNolock() } -// sizeNolock returns the total data size in the freezer table without obtaining -// the mutex first. +// sizeNolock returns the total data size in the freezer table. This function +// assumes the lock is already held. func (t *freezerTable) sizeNolock() (uint64, error) { stat, err := t.index.Stat() if err != nil { return 0, err } - total := uint64(t.maxFileSize)*uint64(t.headId-t.tailId) + uint64(t.headBytes) + uint64(stat.Size()) + hidden, err := t.sizeHidden() + if err != nil { + return 0, err + } + total := uint64(t.maxFileSize)*uint64(t.headId-t.tailId) + uint64(t.headBytes) + uint64(stat.Size()) - hidden return total, nil } diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go index 939d09394..447146393 100644 --- a/core/rawdb/freezer_table_test.go +++ b/core/rawdb/freezer_table_test.go @@ -658,6 +658,13 @@ func TestFreezerOffset(t *testing.T) { } } +func assertTableSize(t *testing.T, f *freezerTable, size int) { + t.Helper() + if got, err := f.size(); got != uint64(size) { + t.Fatalf("expected size of %d bytes, got %d, err: %v", size, got, err) + } +} + func TestTruncateTail(t *testing.T) { t.Parallel() rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() @@ -692,6 +699,9 @@ func TestTruncateTail(t *testing.T) { 5: getChunk(20, 0xaa), 6: getChunk(20, 0x11), }) + // maxFileSize*fileCount + headBytes + indexFileSize - hiddenBytes + expected := 20*7 + 48 - 0 + assertTableSize(t, f, expected) // truncate single element( item 0 ), deletion is only supported at file level f.truncateTail(1) @@ -707,6 +717,8 @@ func TestTruncateTail(t *testing.T) { 5: getChunk(20, 0xaa), 6: getChunk(20, 0x11), }) + expected = 20*7 + 48 - 20 + assertTableSize(t, f, expected) // Reopen the table, the deletion information should be persisted as well f.Close() @@ -739,6 +751,8 @@ func TestTruncateTail(t *testing.T) { 5: getChunk(20, 0xaa), 6: getChunk(20, 0x11), }) + expected = 20*5 + 36 - 0 + assertTableSize(t, f, expected) // Reopen the table, the above testing should still pass f.Close() @@ -760,6 +774,23 @@ func TestTruncateTail(t *testing.T) { 6: getChunk(20, 0x11), }) + // truncate 3 more elements( item 2, 3, 4), the file 1 should be deleted + // file 2 should only contain item 5 + f.truncateTail(5) + checkRetrieveError(t, f, map[uint64]error{ + 0: errOutOfBounds, + 1: errOutOfBounds, + 2: errOutOfBounds, + 3: errOutOfBounds, + 4: errOutOfBounds, + }) + checkRetrieve(t, f, map[uint64][]byte{ + 5: getChunk(20, 0xaa), + 6: getChunk(20, 0x11), + }) + expected = 20*3 + 24 - 20 + assertTableSize(t, f, expected) + // truncate all, the entire freezer should be deleted f.truncateTail(7) checkRetrieveError(t, f, map[uint64]error{ @@ -771,6 +802,8 @@ func TestTruncateTail(t *testing.T) { 5: errOutOfBounds, 6: errOutOfBounds, }) + expected = 12 + assertTableSize(t, f, expected) } func TestTruncateHead(t *testing.T) { diff --git a/core/rawdb/freezer_test.go b/core/rawdb/freezer_test.go index 96d24cc94..b4bd6a382 100644 --- a/core/rawdb/freezer_test.go +++ b/core/rawdb/freezer_test.go @@ -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 1bbb50c49..752e95ba6 100644 --- a/core/rawdb/freezer_utils.go +++ b/core/rawdb/freezer_utils.go @@ -73,11 +73,7 @@ func copyFrom(srcPath, destPath string, offset uint64, before func(f *os.File) e return err } f = nil - - if err := os.Rename(fname, destPath); err != nil { - return err - } - return nil + return os.Rename(fname, destPath) } // openFreezerFileForAppend opens a freezer table file and seeks to the end diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 940ce0154..be0372355 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -91,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 @@ -129,6 +132,10 @@ var ( CliqueSnapshotPrefix = []byte("clique-") + BestUpdateKey = []byte("update-") // bigEndian64(syncPeriod) -> RLP(types.LightClientUpdate) (nextCommittee only referenced by root hash) + FixedCommitteeRootKey = []byte("fixedRoot-") // bigEndian64(syncPeriod) -> committee root hash + SyncCommitteeKey = []byte("committee-") // bigEndian64(syncPeriod) -> serialized committee + preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil) preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil) ) @@ -195,7 +202,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 @@ -259,7 +270,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 @@ -273,9 +288,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 } @@ -288,9 +304,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 } @@ -306,3 +330,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 1895f61da..19e4ed5b5 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -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 a3b6322ae..b55f870d9 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" + "github.com/crate-crypto/go-ipa/banderwagon" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/core/rawdb" @@ -28,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/utils" ) const ( @@ -36,6 +38,12 @@ const ( // Cache size granted for caching clean code. codeCacheSize = 64 * 1024 * 1024 + + // commitmentSize is the size of commitment stored in cache. + commitmentSize = banderwagon.UncompressedSize + + // Cache item granted for caching commitment results. + commitmentCacheItems = 64 * 1024 * 1024 / (commitmentSize + common.AddressLength) ) // Database wraps access to tries and contract code. @@ -44,7 +52,7 @@ type Database interface { OpenTrie(root common.Hash) (Trie, error) // OpenStorageTrie opens the storage trie of an account. - OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash) (Trie, error) + OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, trie Trie) (Trie, error) // CopyTrie returns an independent copy of the given trie. CopyTrie(Trie) Trie @@ -58,7 +66,7 @@ type Database interface { // 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 } @@ -70,11 +78,6 @@ type Trie interface { // TODO(fjl): remove this when StateTrie is removed GetKey([]byte) []byte - // GetStorage returns the value for key stored in the trie. The value bytes - // must not be modified by the caller. If a node was not found in the database, - // a trie.MissingNodeError is returned. - GetStorage(addr common.Address, key []byte) ([]byte, error) - // GetAccount abstracts an account read from the trie. It retrieves the // account blob from the trie with provided account address and decodes it // with associated decoding algorithm. If the specified account is not in @@ -83,27 +86,32 @@ type Trie interface { // be returned. GetAccount(address common.Address) (*types.StateAccount, error) - // UpdateStorage associates key with value in the trie. If value has length zero, - // any existing value is deleted from the trie. The value bytes must not be modified - // by the caller while they are stored in the trie. If a node was not found in the - // database, a trie.MissingNodeError is returned. - UpdateStorage(addr common.Address, key, value []byte) error + // GetStorage returns the value for key stored in the trie. The value bytes + // must not be modified by the caller. If a node was not found in the database, + // a trie.MissingNodeError is returned. + GetStorage(addr common.Address, key []byte) ([]byte, error) // UpdateAccount abstracts an account write to the trie. It encodes the // provided account object with associated algorithm and then updates it // 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 + // UpdateStorage associates key with value in the trie. If value has length zero, + // any existing value is deleted from the trie. The value bytes must not be modified + // by the caller while they are stored in the trie. If a node was not found in the + // database, a trie.MissingNodeError is returned. + UpdateStorage(addr common.Address, key, value []byte) error + + // DeleteAccount abstracts an account deletion from the trie. + DeleteAccount(address common.Address) 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 - // DeleteAccount abstracts an account deletion from the trie. - DeleteAccount(address common.Address) 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 // Hash returns the root hash of the trie. It does not write to the database and // can be used even if the trie doesn't have one. @@ -147,7 +155,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), } } @@ -170,6 +178,9 @@ type cachingDB struct { // OpenTrie opens the main account trie at a specific root hash. func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { + if db.triedb.IsVerkle() { + return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(commitmentCacheItems)) + } tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb) if err != nil { return nil, err @@ -178,7 +189,13 @@ func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) { } // OpenStorageTrie opens the storage trie of an account. -func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash) (Trie, error) { +func (db *cachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) { + // In the verkle case, there is only one tree. But the two-tree structure + // is hardcoded in the codebase. So we need to return the same trie in this + // case. + if db.triedb.IsVerkle() { + return self, nil + } tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root), db.triedb) if err != nil { return nil, err diff --git a/core/state/dump.go b/core/state/dump.go index 9ce6cd394..55abb50f1 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -49,21 +49,24 @@ type DumpCollector interface { // DumpAccount represents an account in the state. type DumpAccount struct { - Balance string `json:"balance"` - Nonce uint64 `json:"nonce"` - Root hexutil.Bytes `json:"root"` - CodeHash hexutil.Bytes `json:"codeHash"` - Code hexutil.Bytes `json:"code,omitempty"` - Storage map[common.Hash]string `json:"storage,omitempty"` - Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode - SecureKey hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key + Balance string `json:"balance"` + Nonce uint64 `json:"nonce"` + Root hexutil.Bytes `json:"root"` + CodeHash hexutil.Bytes `json:"codeHash"` + Code hexutil.Bytes `json:"code,omitempty"` + Storage map[common.Hash]string `json:"storage,omitempty"` + Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode + AddressHash hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key } // Dump represents the full dump in a collected format, as one large map. type Dump struct { - Root string `json:"root"` - Accounts map[common.Address]DumpAccount `json:"accounts"` + Root string `json:"root"` + Accounts map[string]DumpAccount `json:"accounts"` + // Next can be set to represent that this dump is only partial, and Next + // is where an iterator should be positioned in order to continue the dump. + Next []byte `json:"next,omitempty"` // nil if no more accounts } // OnRoot implements DumpCollector interface @@ -73,27 +76,11 @@ func (d *Dump) OnRoot(root common.Hash) { // OnAccount implements DumpCollector interface func (d *Dump) OnAccount(addr *common.Address, account DumpAccount) { - if addr != nil { - d.Accounts[*addr] = account + if addr == nil { + d.Accounts[fmt.Sprintf("pre(%s)", account.AddressHash)] = account } -} - -// IteratorDump is an implementation for iterating over data. -type IteratorDump struct { - Root string `json:"root"` - Accounts map[common.Address]DumpAccount `json:"accounts"` - Next []byte `json:"next,omitempty"` // nil if no more accounts -} - -// OnRoot implements DumpCollector interface -func (d *IteratorDump) OnRoot(root common.Hash) { - d.Root = fmt.Sprintf("%x", root) -} - -// OnAccount implements DumpCollector interface -func (d *IteratorDump) OnAccount(addr *common.Address, account DumpAccount) { if addr != nil { - d.Accounts[*addr] = account + d.Accounts[(*addr).String()] = account } } @@ -105,14 +92,14 @@ type iterativeDump struct { // OnAccount implements DumpCollector interface func (d iterativeDump) OnAccount(addr *common.Address, account DumpAccount) { dumpAccount := &DumpAccount{ - Balance: account.Balance, - Nonce: account.Nonce, - Root: account.Root, - CodeHash: account.CodeHash, - Code: account.Code, - Storage: account.Storage, - SecureKey: account.SecureKey, - Address: addr, + Balance: account.Balance, + Nonce: account.Nonce, + Root: account.Root, + CodeHash: account.CodeHash, + Code: account.Code, + Storage: account.Storage, + AddressHash: account.AddressHash, + Address: addr, } d.Encode(dumpAccount) } @@ -142,6 +129,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] trieIt, err := s.trie.NodeIterator(conf.Start) if err != nil { + log.Error("Trie dumping error", "err", err) return nil } it := trie.NewIterator(trieIt) @@ -150,26 +138,27 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] if err := rlp.DecodeBytes(it.Value, &data); err != nil { panic(err) } - account := DumpAccount{ - Balance: data.Balance.String(), - Nonce: data.Nonce, - Root: data.Root[:], - CodeHash: data.CodeHash, - SecureKey: it.Key, - } var ( - addrBytes = s.trie.GetKey(it.Key) - addr = common.BytesToAddress(addrBytes) + account = DumpAccount{ + Balance: data.Balance.String(), + Nonce: data.Nonce, + Root: data.Root[:], + CodeHash: data.CodeHash, + AddressHash: it.Key, + } address *common.Address + addr common.Address + addrBytes = s.trie.GetKey(it.Key) ) if addrBytes == nil { - // Preimage missing missingPreimages++ if conf.OnlyWithAddresses { continue } } else { + addr = common.BytesToAddress(addrBytes) address = &addr + account.Address = address } obj := newObject(s, addr, &data) if !conf.SkipCode { @@ -220,12 +209,13 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] return nextKey } -// RawDump returns the entire state an a single large object +// RawDump returns the state. If the processing is aborted e.g. due to options +// reaching Max, the `Next` key is set on the returned Dump. func (s *StateDB) RawDump(opts *DumpConfig) Dump { dump := &Dump{ - Accounts: make(map[common.Address]DumpAccount), + Accounts: make(map[string]DumpAccount), } - s.DumpToCollector(dump, opts) + dump.Next = s.DumpToCollector(dump, opts) return *dump } @@ -234,7 +224,7 @@ func (s *StateDB) Dump(opts *DumpConfig) []byte { dump := s.RawDump(opts) json, err := json.MarshalIndent(dump, "", " ") if err != nil { - fmt.Println("Dump err", err) + log.Error("Error dumping state", "err", err) } return json } @@ -243,12 +233,3 @@ func (s *StateDB) Dump(opts *DumpConfig) []byte { func (s *StateDB) IterativeDump(opts *DumpConfig, output *json.Encoder) { s.DumpToCollector(iterativeDump{output}, opts) } - -// IteratorDump dumps out a batch of accounts starts with the given start key -func (s *StateDB) IteratorDump(opts *DumpConfig) IteratorDump { - iterator := &IteratorDump{ - Accounts: make(map[common.Address]DumpAccount), - } - iterator.Next = s.DumpToCollector(iterator, opts) - return *iterator -} diff --git a/core/state/iterator.go b/core/state/iterator.go index bb9af0820..dc84ce689 100644 --- a/core/state/iterator.go +++ b/core/state/iterator.go @@ -112,7 +112,7 @@ 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 @@ -123,7 +123,7 @@ func (it *nodeIterator) step() error { address := common.BytesToAddress(preimage) // Traverse the storage slots belong to the account - dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, address, account.Root) + dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, address, account.Root, it.state.trie) if err != nil { return err } diff --git a/core/state/iterator_test.go b/core/state/iterator_test.go index c8e1a181e..73cc22490 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/pruner/pruner.go b/core/state/pruner/pruner.go index 64c4a3a6e..a0f95078d 100644 --- a/core/state/pruner/pruner.go +++ b/core/state/pruner/pruner.go @@ -85,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 } @@ -122,12 +125,12 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta // dangling node is the state root is super low. So the dangling nodes in // theory will never ever be visited again. var ( - count int - size common.StorageSize - pstart = time.Now() - logged = time.Now() - batch = maindb.NewBatch() - iter = maindb.NewIterator(nil, nil) + skipped, count int + size common.StorageSize + pstart = time.Now() + logged = time.Now() + batch = maindb.NewBatch() + iter = maindb.NewIterator(nil, nil) ) for iter.Next() { key := iter.Key() @@ -146,6 +149,7 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta log.Debug("Forcibly delete the middle state roots", "hash", common.BytesToHash(checkKey)) } else { if stateBloom.Contain(checkKey) { + skipped += 1 continue } } @@ -162,7 +166,7 @@ func prune(snaptree *snapshot.Tree, root common.Hash, maindb ethdb.Database, sta eta = time.Duration(left/speed) * time.Millisecond } if time.Since(logged) > 8*time.Second { - log.Info("Pruning state data", "nodes", count, "size", size, + log.Info("Pruning state data", "nodes", count, "skipped", skipped, "size", size, "elapsed", common.PrettyDuration(time.Since(pstart)), "eta", common.PrettyDuration(eta)) logged = time.Now() } @@ -361,7 +365,9 @@ func RecoverPruning(datadir string, db ethdb.Database) error { 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 } @@ -403,7 +409,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { if genesis == nil { return errors.New("missing genesis block") } - t, err := trie.NewStateTrie(trie.StateTrieID(genesis.Root()), trie.NewDatabase(db)) + t, err := trie.NewStateTrie(trie.StateTrieID(genesis.Root()), trie.NewDatabase(db, trie.HashDefaults)) if err != nil { return err } @@ -427,7 +433,7 @@ func extractGenesis(db ethdb.Database, stateBloom *stateBloom) error { } if acc.Root != types.EmptyRootHash { id := trie.StorageTrieID(genesis.Root(), common.BytesToHash(accIter.LeafKey()), acc.Root) - storageTrie, err := trie.NewStateTrie(id, trie.NewDatabase(db)) + storageTrie, err := trie.NewStateTrie(id, trie.NewDatabase(db, trie.HashDefaults)) if err != nil { return err } diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index 1e683f76c..681be7ebc 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -362,21 +362,15 @@ 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 + options := trie.NewStackTrieOptions() if db != nil { - nodeWriter = func(owner common.Hash, path []byte, hash common.Hash, blob []byte) { + options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) { rawdb.WriteTrieNode(db, owner, path, hash, blob, scheme) - } + }) } - t := trie.NewStackTrieWithOwner(nodeWriter, owner) + t := trie.NewStackTrie(options) for leaf := range in { t.Update(leaf.key[:], leaf.value) } - var root common.Hash - if db == nil { - root = t.Hash() - } else { - root, _ = t.Commit() - } - out <- root + out <- t.Commit() } diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index 513f0f5ab..d563b67ca 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -45,6 +45,16 @@ type diskLayer struct { lock sync.RWMutex } +// Release releases underlying resources; specifically the fastcache requires +// Reset() in order to not leak memory. +// OBS: It does not invoke Close on the diskdb +func (dl *diskLayer) Release() error { + if dl.cache != nil { + dl.cache.Reset() + } + return nil +} + // Root returns root hash for which this snapshot was made. func (dl *diskLayer) Root() common.Hash { return dl.root diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 0a85f0006..f455a6db3 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -230,7 +230,9 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ if origin == nil && !diskMore { stackTr := trie.NewStackTrie(nil) for i, key := range keys { - stackTr.Update(key, vals[i]) + if err := stackTr.Update(key, vals[i]); err != nil { + return nil, err + } } if gotRoot := stackTr.Hash(); gotRoot != root { return &proofResult{ @@ -247,11 +249,6 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ ctx.stats.Log("Trie missing, state snapshotting paused", dl.root, dl.genMarker) return nil, errMissingTrie } - // Firstly find out the key of last iterated element. - var last []byte - if len(keys) > 0 { - last = keys[len(keys)-1] - } // Generate the Merkle proofs for the first and last element if origin == nil { origin = common.Hash{}.Bytes() @@ -266,9 +263,9 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ tr: tr, }, nil } - if last != nil { - if err := tr.Prove(last, proof); err != nil { - log.Debug("Failed to prove range", "kind", kind, "last", last, "err", err) + if len(keys) > 0 { + if err := tr.Prove(keys[len(keys)-1], proof); err != nil { + log.Debug("Failed to prove range", "kind", kind, "last", keys[len(keys)-1], "err", err) return &proofResult{ keys: keys, vals: vals, @@ -280,7 +277,7 @@ func (dl *diskLayer) proveRange(ctx *generatorContext, trieId *trie.ID, prefix [ } // Verify the snapshot segment with range prover, ensure that all flat states // in this range correspond to merkle trie. - cont, err := trie.VerifyRangeProof(root, origin, last, keys, vals, proof) + cont, err := trie.VerifyRangeProof(root, origin, keys, vals, proof) return &proofResult{ keys: keys, vals: vals, @@ -356,7 +353,8 @@ 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]) @@ -445,6 +443,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", iter.Err) return false, nil, iter.Err } // Delete all stale snapshot states remaining diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go index c50367624..c25f3e7e8 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,10 +47,15 @@ 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", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) @@ -79,10 +86,15 @@ 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", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) @@ -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, @@ -233,7 +251,12 @@ 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", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) @@ -325,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) @@ -380,10 +408,15 @@ 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", &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 @@ -391,9 +424,11 @@ func TestGenerateCorruptAccountTrie(t *testing.T) { 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 { @@ -414,11 +449,19 @@ 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() - + 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 @@ -427,8 +470,9 @@ func TestGenerateMissingStorageTrie(t *testing.T) { root := helper.Commit() - // Delete a storage trie root and ensure the generator chokes - helper.diskdb.Delete(stRoot.Bytes()) + // 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 { @@ -448,10 +492,15 @@ 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", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e @@ -461,8 +510,11 @@ func TestGenerateCorruptStorageTrie(t *testing.T) { 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 { @@ -481,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")), @@ -544,15 +601,20 @@ func TestGenerateWithExtraAccounts(t *testing.T) { } func enableLogging() { - log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) } // 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")), @@ -605,11 +667,16 @@ 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 := &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()} val, _ := rlp.EncodeToBytes(acc) @@ -642,11 +709,16 @@ 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 := &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()} val, _ := rlp.EncodeToBytes(acc) @@ -679,10 +751,15 @@ 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) @@ -714,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 @@ -813,7 +895,12 @@ 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", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) @@ -848,7 +935,12 @@ 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", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index efc0fc26a..638984238 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -564,7 +564,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) } @@ -590,7 +590,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) } @@ -656,6 +656,13 @@ func diffToDisk(bottom *diffLayer) *diskLayer { return res } +// Release releases resources +func (t *Tree) Release() { + if dl := t.disklayer(); dl != nil { + dl.Release() + } +} + // 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). @@ -852,3 +859,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/state_object.go b/core/state/state_object.go index adda1d752..cc9aba02f 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -98,7 +98,10 @@ func (s *stateObject) empty() bool { // newObject creates a state object. func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject { - origin := acct + var ( + origin = acct + created = acct == nil // true if the account was not existent + ) if acct == nil { acct = types.NewEmptyStateAccount() } @@ -111,6 +114,7 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s originStorage: make(Storage), pendingStorage: make(Storage), dirtyStorage: make(Storage), + created: created, } } @@ -145,7 +149,7 @@ func (s *stateObject) getTrie() (Trie, error) { s.trie = s.db.prefetcher.trie(s.addrHash, s.data.Root) } if s.trie == nil { - tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root) + tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie) if err != nil { return nil, err } @@ -269,12 +273,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. +// 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 } @@ -286,14 +295,13 @@ func (s *stateObject) updateTrie() (Trie, error) { var ( storage map[common.Hash][]byte origin map[common.Hash][]byte - hasher = s.db.hasher ) 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)) for key, value := range s.pendingStorage { // Skip noop changes, persist actual changes @@ -303,8 +311,7 @@ func (s *stateObject) updateTrie() (Trie, error) { prev := s.originStorage[key] s.originStorage[key] = value - // rlp-encoded value to be used by the snapshot - var snapshotVal []byte + var encoded []byte // rlp-encoded value to be used by the snapshot if (value == common.Hash{}) { if err := tr.DeleteStorage(s.address, key[:]); err != nil { s.db.setError(err) @@ -312,10 +319,10 @@ func (s *stateObject) updateTrie() (Trie, error) { } s.db.StorageDeleted += 1 } else { - trimmedVal := common.TrimLeftZeroes(value[:]) // Encoding []byte cannot fail, ok to ignore the error. - snapshotVal, _ = rlp.EncodeToBytes(trimmedVal) - if err := tr.UpdateStorage(s.address, key[:], trimmedVal); 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 } @@ -328,8 +335,8 @@ func (s *stateObject) updateTrie() (Trie, error) { s.db.storages[s.addrHash] = storage } } - khash := crypto.HashData(hasher, key[:]) - storage[khash] = snapshotVal // snapshotVal will be nil if it's deleted + 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 { @@ -354,21 +361,17 @@ func (s *stateObject) updateTrie() (Trie, error) { 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. +// 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 { - return - } - // If nothing changed, don't bother with hashing anything - if tr == nil { + if err != nil || tr == nil { return } // Track the amount of time wasted on hashing the storage trie @@ -378,14 +381,12 @@ func (s *stateObject) updateRoot() { s.data.Root = tr.Hash() } -// commit returns the changes made in storage trie and updates the account data. +// 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) { - tr, err := s.updateTrie() - if err != nil { - return nil, err - } - // If nothing changed, don't bother with committing anything - if tr == nil { + // 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 } @@ -393,7 +394,10 @@ func (s *stateObject) commit() (*trienode.NodeSet, error) { if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageCommits += time.Since(start) }(time.Now()) } - root, nodes, err := 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 } @@ -546,3 +550,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 2553133de..2f45ba44b 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -71,6 +71,7 @@ func TestDump(t *testing.T) { "nonce": 0, "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "address": "0x0000000000000000000000000000000000000001", "key": "0x1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d" }, "0x0000000000000000000000000000000000000002": { @@ -78,6 +79,7 @@ func TestDump(t *testing.T) { "nonce": 0, "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "address": "0x0000000000000000000000000000000000000002", "key": "0xd52688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf62" }, "0x0000000000000000000000000000000000000102": { @@ -86,6 +88,7 @@ func TestDump(t *testing.T) { "root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "codeHash": "0x87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3", "code": "0x03030303030303", + "address": "0x0000000000000000000000000000000000000102", "key": "0xa17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1" } } diff --git a/core/state/statedb.go b/core/state/statedb.go index 5c3bebda9..67de07c87 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -18,7 +18,6 @@ package state import ( - "errors" "fmt" "math/big" "sort" @@ -32,28 +31,22 @@ 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 { id int journalIndex 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: @@ -145,6 +138,8 @@ type StateDB struct { // Log of state data read from backing DB stateSpecimen *types.StateSpecimen + // Testing hooks + onCommit func(states *triestate.Set) // Hook invoked when commit is performed } // New creates a new state from a given trie. @@ -300,6 +295,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 { @@ -309,6 +305,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 @@ -332,10 +338,10 @@ func (s *StateDB) GetCodeSize(addr common.Address) int { func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { stateObject := s.getStateObject(addr) - if stateObject == nil { - return common.Hash{} + if stateObject != nil { + return common.BytesToHash(stateObject.CodeHash()) } - return common.BytesToHash(stateObject.CodeHash()) + return common.Hash{} } // GetState retrieves a value from the given account's storage trie. @@ -347,35 +353,6 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.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[:], &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()), &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) @@ -390,21 +367,6 @@ 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(); err != nil { - return nil, err - } - return cpy.getTrie() -} - func (s *StateDB) HasSelfDestructed(addr common.Address) bool { stateObject := s.getStateObject(addr) if stateObject != nil { @@ -707,9 +669,6 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) delete(s.accountsOrigin, prev.address) delete(s.storagesOrigin, prev.address) } - - newobj.created = true - s.setStateObject(newobj) if prev != nil && !prev.deleted { return newobj, prev @@ -734,43 +693,6 @@ func (s *StateDB) CreateAccount(addr common.Address) { } } -func (s *StateDB) ForEachStorage(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 -} - // 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 { @@ -1039,59 +961,130 @@ func (s *StateDB) clearJournalAndRefund() { s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entries } -// deleteStorage iterates the storage trie belongs to the account and mark all -// slots inside as deleted. -func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (bool, map[common.Hash][]byte, *trienode.NodeSet, error) { - start := time.Now() - tr, err := s.db.OpenStorageTrie(s.originalRoot, addr, root) +// 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) + ) + options := trie.NewStackTrieOptions() + options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) { + nodes.AddNode(path, trienode.NewDeleted()) + size += common.StorageSize(len(path)) + }) + stack := trie.NewStackTrie(options) + 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, s.trie) if err != nil { - return false, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err) + 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, nil, nil, fmt.Errorf("failed to open storage iterator, err: %w", err) + return false, 0, nil, nil, fmt.Errorf("failed to open storage iterator, err: %w", err) } var ( - set = trienode.NewNodeSet(addrHash) - slots = make(map[common.Hash][]byte) - stateSize common.StorageSize - nodeSize common.StorageSize + size common.StorageSize + nodes = trienode.NewNodeSet(addrHash) + slots = make(map[common.Hash][]byte) ) for it.Next(true) { - // arbitrary stateSize limit, make it configurable - if stateSize+nodeSize > 512*1024*1024 { - log.Info("Skip large storage deletion", "address", addr.Hex(), "states", stateSize, "nodes", nodeSize) - if metrics.EnabledExpensive { - slotDeletionSkip.Inc(1) - } - return true, nil, nil, nil + if size > storageDeleteLimit { + return true, size, nil, nil, nil } if it.Leaf() { slots[common.BytesToHash(it.LeafKey())] = common.CopyBytes(it.LeafBlob()) - stateSize += common.StorageSize(common.HashLength + len(it.LeafBlob())) + size += common.StorageSize(common.HashLength + len(it.LeafBlob())) continue } if it.Hash() == (common.Hash{}) { continue } - nodeSize += common.StorageSize(len(it.Path())) - set.AddNode(it.Path(), trienode.NewDeleted()) + 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 int64(len(slots)) > slotDeletionMaxCount.Value() { - slotDeletionMaxCount.Update(int64(len(slots))) - } - if int64(stateSize+nodeSize) > slotDeletionMaxSize.Value() { - slotDeletionMaxSize.Update(int64(stateSize + nodeSize)) + if aborted { + slotDeletionSkip.Inc(1) } + n := int64(len(slots)) + + slotDeletionMaxCount.UpdateIfGt(int64(len(slots))) + slotDeletionMaxSize.UpdateIfGt(int64(size)) + slotDeletionTimer.UpdateSince(start) - slotDeletionCount.Mark(int64(len(slots))) - slotDeletionSize.Mark(int64(stateSize + nodeSize)) + slotDeletionCount.Mark(n) + slotDeletionSize.Mark(int64(size)) } - return false, slots, set, nil + return aborted, slots, nodes, nil } // handleDestruction processes all destruction markers and deletes the account @@ -1119,7 +1112,13 @@ func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root // 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). @@ -1293,13 +1292,17 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er } if root != origin { start := time.Now() - if err := s.db.TrieDB().Update(root, origin, block, nodes, triestate.New(s.accountsOrigin, s.storagesOrigin, incomplete)); 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) diff --git a/core/state/statedb_fuzz_test.go b/core/state/statedb_fuzz_test.go index 17d60c9ba..c4704257c 100644 --- a/core/state/statedb_fuzz_test.go +++ b/core/state/statedb_fuzz_test.go @@ -31,10 +31,12 @@ 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/rlp" "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/trie/triedb/pathdb" "github.com/ethereum/go-ethereum/trie/triestate" ) @@ -179,19 +181,33 @@ func (test *stateTest) run() bool { storageList = append(storageList, copy2DSet(states.Storages)) } disk = rawdb.NewMemoryDatabase() - tdb = trie.NewDatabaseWithConfig(disk, &trie.Config{OnCommit: onCommit}) + 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, nil) + 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 { diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 66dda238e..df1cd5547 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -35,15 +35,23 @@ import ( "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/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++ { @@ -59,7 +67,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()) } @@ -77,8 +85,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))) @@ -110,7 +120,7 @@ func TestIntermediateLeaks(t *testing.T) { 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()) } @@ -118,7 +128,7 @@ func TestIntermediateLeaks(t *testing.T) { 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()) } @@ -416,10 +426,12 @@ func (test *snapshotTest) run() bool { state, _ = New(types.EmptyRootHash, NewDatabase(rawdb.NewMemoryDatabase()), nil) snapshotRevs = make([]int, len(test.snapshots)) sindex = 0 + checkstates = make([]*StateDB, len(test.snapshots)) ) for i, action := range test.actions { if len(test.snapshots) > sindex && i == test.snapshots[sindex] { snapshotRevs[sindex] = state.Snapshot() + checkstates[sindex] = state.Copy() sindex++ } action.fn(action, state) @@ -427,12 +439,8 @@ func (test *snapshotTest) run() bool { // Revert all snapshots in reverse order. Each revert must yield a state // that is equivalent to fresh state with all actions up the snapshot applied. for sindex--; sindex >= 0; sindex-- { - checkstate, _ := New(types.EmptyRootHash, state.Database(), nil) - for _, action := range test.actions[:test.snapshots[sindex]] { - action.fn(action, checkstate) - } state.RevertToSnapshot(snapshotRevs[sindex]) - if err := test.checkEqual(state, checkstate); err != nil { + if err := test.checkEqual(state, checkstates[sindex]); err != nil { test.err = fmt.Errorf("state mismatch after revert to snapshot %d\n%v", sindex, err) return false } @@ -440,6 +448,43 @@ func (test *snapshotTest) run() bool { return true } +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 +} + // checkEqual checks that methods of state and checkstate return the same values. func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error { for _, addr := range test.addrs { @@ -461,10 +506,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) }) } @@ -747,9 +792,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")) @@ -762,7 +826,7 @@ func TestMissingTrieNodes(t *testing.T) { 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) @@ -969,7 +1033,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++ { @@ -982,11 +1047,11 @@ func TestFlushOrderDataLoss(t *testing.T) { 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 @@ -1040,7 +1105,7 @@ func TestStateDBTransientStorage(t *testing.T) { func TestResetObject(t *testing.T) { var ( disk = rawdb.NewMemoryDatabase() - tdb = trie.NewDatabase(disk) + 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) @@ -1070,3 +1135,57 @@ func TestResetObject(t *testing.T) { 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 61097c646..d6775e889 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 b065ad835..6196e7781 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 := NewDatabaseWithConfig(db, &trie.Config{Preimages: true}) + nodeDb := trie.NewDatabase(db, config) + sdb := NewDatabaseWithNodeDB(db, nodeDb) state, _ := New(types.EmptyRootHash, sdb, nil) // Fill it with some arbitrary data @@ -67,24 +76,27 @@ func makeTestState() (ethdb.Database, Database, common.Hash, []*testAccount) { obj.SetState(hash, hash) } } - state.updateStateObject(obj) accounts = append(accounts, acc) } 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 { @@ -101,8 +113,12 @@ func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accou } // checkStateConsistency checks that all data of a state root is present. -func checkStateConsistency(db ethdb.Database, root common.Hash) error { - state, err := New(root, NewDatabaseWithConfig(db, &trie.Config{Preimages: true}), nil) +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, NewDatabaseWithConfig(db, config), nil) if err != nil { return err } @@ -114,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) } @@ -124,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). @@ -150,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 - srcDisk, 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 @@ -175,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 ( @@ -205,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) } @@ -216,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)) } @@ -260,18 +291,23 @@ func testIterativeStateSync(t *testing.T, count int, commit bool, bypath bool) { 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 - srcDisk, 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 @@ -286,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 @@ -313,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) } @@ -353,22 +392,28 @@ func TestIterativeDelayedStateSync(t *testing.T) { 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 - srcDisk, 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{}) @@ -383,6 +428,10 @@ 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 { @@ -403,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) } @@ -415,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) @@ -441,18 +490,23 @@ func testIterativeRandomStateSync(t *testing.T, count int) { 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 - srcDisk, 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{}) @@ -467,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 { @@ -495,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) } @@ -535,14 +594,19 @@ func TestIterativeRandomDelayedStateSync(t *testing.T) { 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,14 +619,14 @@ func TestIncompleteStateSync(t *testing.T) { // 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 := srcDb.TrieDB().Reader(srcRoot) + reader, err := ndb.Reader(srcRoot) if err != nil { t.Fatalf("state is not available %x", srcRoot) } @@ -649,12 +713,11 @@ func TestIncompleteStateSync(t *testing.T) { 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] @@ -663,7 +726,7 @@ 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) diff --git a/core/state/trie_prefetcher.go b/core/state/trie_prefetcher.go index 4e8fd1e10..c2a49417d 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,9 @@ func (sf *subfetcher) loop() { } sf.trie = trie } else { - trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root) + // The trie argument can be nil as verkle doesn't support prefetching + // yet. TODO FIX IT(rjl493456442), otherwise code will panic here. + trie, err := sf.db.OpenStorageTrie(sf.state, sf.addr, sf.root, nil) 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 fcaf5a8ff..9a4333f72 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -76,6 +76,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) @@ -132,6 +135,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 msg.To == nil { receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) @@ -157,6 +165,27 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo } // Create a new context to be used in the EVM environment blockContext := NewEVMBlockContext(header, bc, author) - vmenv := vm.NewEVM(blockContext, vm.TxContext{BlobHashes: tx.BlobHashes()}, statedb, config, cfg) + txContext := NewEVMTxContext(msg) + vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg) return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) } + +// 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 b4482acf3..5ff9353bd 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -95,7 +95,7 @@ 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 { + var mkBlobTx = func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap, blobGasFeeCap *big.Int, hashes []common.Hash) *types.Transaction { tx, err := types.SignTx(types.NewTx(&types.BlobTx{ Nonce: nonce, GasTipCap: uint256.MustFromBig(gasTipCap), @@ -103,6 +103,7 @@ func TestStateProcessorErrors(t *testing.T) { Gas: gasLimit, To: to, BlobHashes: hashes, + BlobFeeCap: uint256.MustFromBig(blobGasFeeCap), Value: new(uint256.Int), }), signer, key1) if err != nil { @@ -132,7 +133,7 @@ func TestStateProcessorErrors(t *testing.T) { ) defer blockchain.Stop() - bigNumber := new(big.Int).SetBytes(common.FromHex("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) + bigNumber := new(big.Int).SetBytes(common.MaxHash.Bytes()) tooBigNumber := new(big.Int).Set(bigNumber) tooBigNumber.Add(tooBigNumber, common.Big1) for i, tt := range []struct { @@ -196,7 +197,7 @@ func TestStateProcessorErrors(t *testing.T) { txs: []*types.Transaction{ mkDynamicTx(0, common.Address{}, params.TxGas, big.NewInt(0), big.NewInt(0)), }, - want: "could not apply tx 0 [0xc4ab868fef0c82ae0387b742aee87907f2d0fc528fc6ea0a021459fb0fc4a4a8]: max fee per gas less than block base fee: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxFeePerGas: 0 baseFee: 875000000", + want: "could not apply tx 0 [0xc4ab868fef0c82ae0387b742aee87907f2d0fc528fc6ea0a021459fb0fc4a4a8]: max fee per gas less than block base fee: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxFeePerGas: 0, baseFee: 875000000", }, { // ErrTipVeryHigh txs: []*types.Transaction{ @@ -247,9 +248,9 @@ func TestStateProcessorErrors(t *testing.T) { }, { // ErrBlobFeeCapTooLow txs: []*types.Transaction{ - mkBlobTx(0, common.Address{}, params.TxGas, big.NewInt(1), big.NewInt(1), []common.Hash{(common.Hash{1})}), + mkBlobTx(0, common.Address{}, params.TxGas, big.NewInt(1), big.NewInt(1), big.NewInt(0), []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", + 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(), beacon.New(ethash.NewFaker()), tt.txs, gspec.Config) @@ -359,7 +360,8 @@ func TestStateProcessorErrors(t *testing.T) { func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions, config *params.ChainConfig) *types.Block { difficulty := big.NewInt(0) if !config.TerminalTotalDifficultyPassed { - difficulty = engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{ + fakeChainReader := newChainMaker(nil, config, engine) + difficulty = engine.CalcDifficulty(fakeChainReader, parent.Time()+10, &types.Header{ Number: parent.Number(), Time: parent.Time(), Difficulty: parent.Difficulty(), @@ -410,6 +412,9 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr 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) { diff --git a/core/state_transition.go b/core/state_transition.go index f84757be7..540f63fda 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -24,7 +24,6 @@ import ( "github.com/ethereum/go-ethereum/common" cmath "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" @@ -33,9 +32,10 @@ import ( // ExecutionResult includes all output after executing given evm // message no matter the execution itself is successful or not. type ExecutionResult struct { - UsedGas uint64 // Total used gas but include the refunded gas - Err error // Any error encountered during the execution(listed in core/vm/errors.go) - ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) + UsedGas uint64 // Total used gas, not including the refunded gas + RefundedGas uint64 // Total gas refunded after execution + Err error // Any error encountered during the execution(listed in core/vm/errors.go) + ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) } // Unwrap returns the internal evm error which allows us for further @@ -248,7 +248,7 @@ func (st *StateTransition) buyGas() error { balanceCheck.Add(balanceCheck, blobBalanceCheck) // Pay for blobGasUsed * actual blob fee blobFee := new(big.Int).SetUint64(blobGas) - blobFee.Mul(blobFee, eip4844.CalcBlobFee(*st.evm.Context.ExcessBlobGas)) + blobFee.Mul(blobFee, st.evm.Context.BlobBaseFee) mgval.Add(mgval, blobFee) } } @@ -288,11 +288,11 @@ func (st *StateTransition) preCheck() error { msg.From.Hex(), codeHash) } } - // Make sure that transaction gasFeeCap is greater than the baseFee (post london) if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { // Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call) - if !st.evm.Config.NoBaseFee || msg.GasFeeCap.BitLen() > 0 || msg.GasTipCap.BitLen() > 0 { + skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0 + if !skipCheck { if l := msg.GasFeeCap.BitLen(); l > 256 { return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh, msg.From.Hex(), l) @@ -308,7 +308,7 @@ func (st *StateTransition) preCheck() error { // This will panic if baseFee is nil, but basefee presence is verified // as part of header validation. if msg.GasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 { - return fmt.Errorf("%w: address %v, maxFeePerGas: %s baseFee: %s", ErrFeeCapTooLow, + return fmt.Errorf("%w: address %v, maxFeePerGas: %s, baseFee: %s", ErrFeeCapTooLow, msg.From.Hex(), msg.GasFeeCap, st.evm.Context.BaseFee) } } @@ -325,17 +325,21 @@ func (st *StateTransition) preCheck() error { } } } - + // Check that the user is paying at least the current blob fee if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) { if st.blobGasUsed() > 0 { - // Check that the user is paying at least the current blob fee - blobFee := eip4844.CalcBlobFee(*st.evm.Context.ExcessBlobGas) - 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) + // Skip the checks if gas fields are zero and blobBaseFee was explicitly disabled (eth_call) + skipCheck := st.evm.Config.NoBaseFee && msg.BlobGasFeeCap.BitLen() == 0 + if !skipCheck { + // This will panic if blobBaseFee is nil, but blobBaseFee presence + // is verified as part of header validation. + if msg.BlobGasFeeCap.Cmp(st.evm.Context.BlobBaseFee) < 0 { + return fmt.Errorf("%w: address %v blobGasFeeCap: %v, blobBaseFee: %v", ErrBlobFeeCapTooLow, + msg.From.Hex(), msg.BlobGasFeeCap, st.evm.Context.BlobBaseFee) + } } } } - return st.buyGas() } @@ -416,12 +420,13 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, msg.Value) } + var gasRefund uint64 if !rules.IsLondon { // Before EIP-3529: refunds were capped to gasUsed / 2 - st.refundGas(params.RefundQuotient) + gasRefund = st.refundGas(params.RefundQuotient) } else { // After EIP-3529: refunds are capped to gasUsed / 5 - st.refundGas(params.RefundQuotientEIP3529) + gasRefund = st.refundGas(params.RefundQuotientEIP3529) } effectiveTip := msg.GasPrice if rules.IsLondon { @@ -439,13 +444,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } return &ExecutionResult{ - UsedGas: st.gasUsed(), - Err: vmerr, - ReturnData: ret, + UsedGas: st.gasUsed(), + RefundedGas: gasRefund, + Err: vmerr, + ReturnData: ret, }, nil } -func (st *StateTransition) refundGas(refundQuotient uint64) { +func (st *StateTransition) refundGas(refundQuotient uint64) uint64 { // Apply refund counter, capped to a refund quotient refund := st.gasUsed() / refundQuotient if refund > st.state.GetRefund() { @@ -460,6 +466,8 @@ func (st *StateTransition) refundGas(refundQuotient uint64) { // Also return remaining gas to the block gas counter so it is // available for the next transaction. st.gp.AddGas(st.gasRemaining) + + return refund } // gasUsed returns the amount of gas used up by the state transition. diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index c0dd6e8ac..195697a8f 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -19,6 +19,7 @@ package blobpool import ( "container/heap" + "errors" "fmt" "math" "math/big" @@ -35,7 +36,6 @@ import ( "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/kzg4844" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -53,7 +53,7 @@ const ( // 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.BlobTxMaxBlobGasPerBlock / params.BlobTxBlobGasPerBlob + 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. @@ -83,16 +83,6 @@ const ( limboedTransactionStore = "limbo" ) -// blobTx is a wrapper around types.BlobTx which also contains the literal blob -// data along with all the transaction metadata. -type blobTx struct { - Tx *types.Transaction - - Blobs []kzg4844.Blob - Commits []kzg4844.Commitment - Proofs []kzg4844.Proof -} - // 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 @@ -107,6 +97,8 @@ type blobTxMeta struct { 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 @@ -128,6 +120,8 @@ func newBlobTxMeta(id uint64, size uint32, tx *types.Transaction) *blobTxMeta { 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) @@ -317,8 +311,8 @@ type BlobPool struct { spent map[common.Address]*uint256.Int // Expenditure tracking for individual accounts evict *evictHeap // Heap of cheapest accounts for eviction when full - eventFeed event.Feed // Event feed to send out new tx events on pool inclusion - eventScope event.SubscriptionScope // Event scope to track and mass unsubscribe on termination + 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 } @@ -365,7 +359,13 @@ func (p *BlobPool) Init(gasTip *big.Int, head *types.Header, reserve txpool.Addr 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 } @@ -440,8 +440,6 @@ func (p *BlobPool) Close() error { if err := p.store.Close(); err != nil { errs = append(errs, err) } - p.eventScope.Close() - switch { case errs == nil: return nil @@ -455,22 +453,27 @@ func (p *BlobPool) Close() error { // 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 { - item := new(blobTx) - if err := rlp.DecodeBytes(blob, item); err != nil { + 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 } - meta := newBlobTxMeta(id, size, item.Tx) + 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(item.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", item.Tx.Hash(), "err", err) + log.Error("Failed to recover blob tx sender", "id", id, "hash", tx.Hash(), "err", err) return err } if _, ok := p.index[sender]; !ok { @@ -718,24 +721,24 @@ func (p *BlobPool) offload(addr common.Address, nonce uint64, id uint64, inclusi log.Error("Blobs missing for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err) return } - item := new(blobTx) - if err = rlp.DecodeBytes(data, item); err != nil { + 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[item.Tx.Hash()] + 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(item.Tx.Hash(), block, item.Blobs, item.Commits, item.Proofs); err != nil { + 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. +// kept in sync with the main transaction pool's internal state. func (p *BlobPool) Reset(oldHead, newHead *types.Header) { waitStart := time.Now() p.lock.Lock() @@ -757,15 +760,21 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) { // 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 { - p.reinject(addr, tx) + 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}) + } } // Flush out any blobs from limbo that are older than the latest finality if p.chain.Config().IsCancun(p.head.Number, p.head.Time) { @@ -920,32 +929,35 @@ func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]* // 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, tx *types.Transaction) { +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. - blobs, commits, proofs, err := p.limbo.pull(tx.Hash()) + tx, err := p.limbo.pull(txhash) if err != nil { log.Error("Blobs unavailable, dropping reorged tx", "err", err) - return + return err } - // Serialize the transaction back into the primary datastore - blob, err := rlp.EncodeToBytes(&blobTx{Tx: tx, Blobs: blobs, Commits: commits, Proofs: proofs}) + // 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 + 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 + 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 + return err } p.index[addr] = []*blobTxMeta{meta} p.spent[addr] = meta.costCap @@ -956,10 +968,11 @@ func (p *BlobPool) reinject(addr common.Address, tx *types.Transaction) { } 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. +// to be kept in sync with the main transaction pool's gas requirements. func (p *BlobPool) SetGasTip(tip *big.Int) { p.lock.Lock() defer p.lock.Unlock() @@ -1023,7 +1036,7 @@ func (p *BlobPool) SetGasTip(tip *big.Int) { // 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, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) error { +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{ @@ -1032,7 +1045,7 @@ func (p *BlobPool) validateTx(tx *types.Transaction, blobs []kzg4844.Blob, commi MaxSize: txMaxSize, MinTip: p.gasTip.ToBig(), } - if err := txpool.ValidateTransaction(tx, blobs, commits, proofs, p.head, p.signer, baseOpts); err != nil { + 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) @@ -1117,7 +1130,7 @@ func (p *BlobPool) Has(hash common.Hash) bool { } // Get returns a transaction if it is contained in the pool, or nil otherwise. -func (p *BlobPool) Get(hash common.Hash) *txpool.Transaction { +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() @@ -1139,32 +1152,37 @@ func (p *BlobPool) Get(hash common.Hash) *txpool.Transaction { log.Error("Tracked blob transaction missing from store", "hash", hash, "id", id, "err", err) return nil } - item := new(blobTx) + 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 &txpool.Transaction{ - Tx: item.Tx, - BlobTxBlobs: item.Blobs, - BlobTxCommits: item.Commits, - BlobTxProofs: item.Proofs, - } + 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 []*txpool.Transaction, local bool, sync bool) []error { - errs := make([]error, len(txs)) +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.Tx, tx.BlobTxBlobs, tx.BlobTxCommits, tx.BlobTxProofs) + 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, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) (err error) { +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. @@ -1178,7 +1196,7 @@ func (p *BlobPool) add(tx *types.Transaction, blobs []kzg4844.Blob, commits []kz }(time.Now()) // Ensure the transaction is valid from all perspectives - if err := p.validateTx(tx, blobs, commits, proofs); err != nil { + if err := p.validateTx(tx); err != nil { log.Trace("Transaction validation failed", "hash", tx.Hash(), "err", err) return err } @@ -1203,7 +1221,7 @@ func (p *BlobPool) add(tx *types.Transaction, blobs []kzg4844.Blob, commits []kz } // Transaction permitted into the pool from a nonce and cost perspective, // insert it into the database and update the indices - blob, err := rlp.EncodeToBytes(&blobTx{Tx: tx, Blobs: blobs, Commits: commits, Proofs: proofs}) + blob, err := rlp.EncodeToBytes(tx) if err != nil { log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err) return err @@ -1385,6 +1403,8 @@ func (p *BlobPool) Pending(enforceTips bool) map[common.Address][]*txpool.LazyTr 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 { @@ -1469,10 +1489,14 @@ func (p *BlobPool) updateLimboMetrics() { limboSlotusedGauge.Update(int64(slotused)) } -// SubscribeTransactions registers a subscription of NewTxsEvent and -// starts sending event to the given channel. -func (p *BlobPool) SubscribeTransactions(ch chan<- core.NewTxsEvent) event.Subscription { - return p.eventScope.Track(p.eventFeed.Subscribe(ch)) +// 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 diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 78a5039b5..b709ad0e5 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -193,8 +193,8 @@ func makeAddressReserver() txpool.AddressReserver { // 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 { - tx, _ := types.SignNewTx(key, types.LatestSigner(testChainConfig), makeUnsignedTx(nonce, gasTipCap, gasFeeCap, blobFeeCap)) - return tx + 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 @@ -209,6 +209,11 @@ func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap 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}, + }, } } @@ -314,7 +319,7 @@ func verifyPoolInternals(t *testing.T, pool *BlobPool) { // - 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)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) // Create a temporary folder for the persistent backend storage, _ := os.MkdirTemp("", "blobpool-") @@ -341,7 +346,7 @@ func TestOpenDrops(t *testing.T) { R: new(uint256.Int), S: new(uint256.Int), }) - blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx}) + blob, _ := rlp.EncodeToBytes(tx) badsig, _ := store.Put(blob) // Insert a sequence of transactions with a nonce gap in between to verify @@ -354,7 +359,7 @@ func TestOpenDrops(t *testing.T) { ) 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(&blobTx{Tx: tx}) + blob, _ := rlp.EncodeToBytes(tx) id, _ := store.Put(blob) if nonce < 2 { @@ -371,7 +376,7 @@ func TestOpenDrops(t *testing.T) { ) for _, nonce := range []uint64{1, 2, 3} { // first gap at #0, all set dangling tx := makeTx(nonce, 1, 1, 1, dangler) - blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx}) + blob, _ := rlp.EncodeToBytes(tx) id, _ := store.Put(blob) dangling[id] = struct{}{} @@ -384,7 +389,7 @@ func TestOpenDrops(t *testing.T) { ) for _, nonce := range []uint64{0, 1, 2} { // account nonce at 3, all set filled tx := makeTx(nonce, 1, 1, 1, filler) - blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx}) + blob, _ := rlp.EncodeToBytes(tx) id, _ := store.Put(blob) filled[id] = struct{}{} @@ -397,7 +402,7 @@ func TestOpenDrops(t *testing.T) { ) for _, nonce := range []uint64{0, 1, 2, 3} { // account nonce at 2, half filled tx := makeTx(nonce, 1, 1, 1, overlapper) - blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx}) + blob, _ := rlp.EncodeToBytes(tx) id, _ := store.Put(blob) if nonce >= 2 { @@ -419,7 +424,7 @@ func TestOpenDrops(t *testing.T) { } else { tx = makeTx(uint64(i), 1, 1, 1, underpayer) } - blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx}) + blob, _ := rlp.EncodeToBytes(tx) id, _ := store.Put(blob) underpaid[id] = struct{}{} @@ -438,7 +443,7 @@ func TestOpenDrops(t *testing.T) { } else { tx = makeTx(uint64(i), 1, 1, 1, outpricer) } - blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx}) + blob, _ := rlp.EncodeToBytes(tx) id, _ := store.Put(blob) if i < 2 { @@ -460,7 +465,7 @@ func TestOpenDrops(t *testing.T) { } else { tx = makeTx(nonce, 1, 1, 1, exceeder) } - blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx}) + blob, _ := rlp.EncodeToBytes(tx) id, _ := store.Put(blob) exceeded[id] = struct{}{} @@ -478,7 +483,7 @@ func TestOpenDrops(t *testing.T) { } else { tx = makeTx(nonce, 1, 1, 1, overdrafter) } - blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx}) + blob, _ := rlp.EncodeToBytes(tx) id, _ := store.Put(blob) if nonce < 1 { @@ -494,7 +499,7 @@ func TestOpenDrops(t *testing.T) { overcapped = make(map[uint64]struct{}) ) for nonce := uint64(0); nonce < maxTxsPerAccount+3; nonce++ { - blob, _ := rlp.EncodeToBytes(&blobTx{Tx: makeTx(nonce, 1, 1, 1, overcapper)}) + blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, overcapper)) id, _ := store.Put(blob) if nonce < maxTxsPerAccount { @@ -589,13 +594,13 @@ func TestOpenDrops(t *testing.T) { verifyPoolInternals(t, pool) } -// Tests that transactions loaded from disk are indexed corrently. +// Tests that transactions loaded from disk are indexed correctly. // // - 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)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) // Create a temporary folder for the persistent backend storage, _ := os.MkdirTemp("", "blobpool-") @@ -625,7 +630,7 @@ func TestOpenIndex(t *testing.T) { ) 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(&blobTx{Tx: tx}) + blob, _ := rlp.EncodeToBytes(tx) store.Put(blob) } store.Close() @@ -684,7 +689,7 @@ func TestOpenIndex(t *testing.T) { // 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)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) // Create a temporary folder for the persistent backend storage, _ := os.MkdirTemp("", "blobpool-") @@ -718,9 +723,9 @@ func TestOpenHeap(t *testing.T) { tx2 = makeTx(0, 1, 800, 70, key2) tx3 = makeTx(0, 1, 1500, 110, key3) - blob1, _ = rlp.EncodeToBytes(&blobTx{Tx: tx1}) - blob2, _ = rlp.EncodeToBytes(&blobTx{Tx: tx2}) - blob3, _ = rlp.EncodeToBytes(&blobTx{Tx: tx3}) + 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} @@ -771,7 +776,7 @@ func TestOpenHeap(t *testing.T) { // 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)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) // Create a temporary folder for the persistent backend storage, _ := os.MkdirTemp("", "blobpool-") @@ -794,9 +799,9 @@ func TestOpenCap(t *testing.T) { tx2 = makeTx(0, 1, 800, 70, key2) tx3 = makeTx(0, 1, 1500, 110, key3) - blob1, _ = rlp.EncodeToBytes(&blobTx{Tx: tx1, Blobs: []kzg4844.Blob{emptyBlob}, Commits: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}}) - blob2, _ = rlp.EncodeToBytes(&blobTx{Tx: tx2, Blobs: []kzg4844.Blob{emptyBlob}, Commits: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}}) - blob3, _ = rlp.EncodeToBytes(&blobTx{Tx: tx3, Blobs: []kzg4844.Blob{emptyBlob}, Commits: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}}) + blob1, _ = rlp.EncodeToBytes(tx1) + blob2, _ = rlp.EncodeToBytes(tx2) + blob3, _ = rlp.EncodeToBytes(tx3) keep = []common.Address{addr1, addr3} drop = []common.Address{addr2} @@ -863,7 +868,7 @@ func TestOpenCap(t *testing.T) { // 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)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) // seed is a helper tumpe to seed an initial state db and pool type seed struct { @@ -1210,10 +1215,8 @@ func TestAdd(t *testing.T) { // Sign the seed transactions and store them in the data store for _, tx := range seed.txs { - var ( - signed, _ = types.SignNewTx(keys[acc], types.LatestSigner(testChainConfig), tx) - blob, _ = rlp.EncodeToBytes(&blobTx{Tx: signed, Blobs: []kzg4844.Blob{emptyBlob}, Commits: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}}) - ) + signed := types.MustSignNewTx(keys[acc], types.LatestSigner(testChainConfig), tx) + blob, _ := rlp.EncodeToBytes(signed) store.Put(blob) } } @@ -1236,7 +1239,7 @@ func TestAdd(t *testing.T) { // 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, []kzg4844.Blob{emptyBlob}, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof}); !errors.Is(err, add.err) { + 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) diff --git a/core/txpool/blobpool/evictheap.go b/core/txpool/blobpool/evictheap.go index 7607a911c..df594099f 100644 --- a/core/txpool/blobpool/evictheap.go +++ b/core/txpool/blobpool/evictheap.go @@ -44,7 +44,7 @@ type evictHeap struct { index map[common.Address]int // Indices into the heap for replacements } -// newPriceHeap creates a new heap of cheapets accounts in the blob pool to evict +// 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{ diff --git a/core/txpool/blobpool/limbo.go b/core/txpool/blobpool/limbo.go index 4cb5042c2..d1fe9c739 100644 --- a/core/txpool/blobpool/limbo.go +++ b/core/txpool/blobpool/limbo.go @@ -21,7 +21,6 @@ import ( "github.com/ethereum/go-ethereum/common" "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/rlp" "github.com/holiman/billy" @@ -31,12 +30,9 @@ import ( // to which it belongs as well as the block number in which it was included for // finality eviction. type limboBlob struct { - Owner common.Hash // Owner transaction's hash to support resurrecting reorged txs - Block uint64 // Block in which the blob transaction was included - - Blobs []kzg4844.Blob // The opaque blobs originally part of the transaction - Commits []kzg4844.Commitment // The commitments for the original blobs - Proofs []kzg4844.Proof // The proofs verifying the commitments + 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 @@ -98,19 +94,19 @@ func (l *limbo) parseBlob(id uint64, data []byte) error { log.Error("Failed to decode blob limbo entry", "id", id, "err", err) return err } - if _, ok := l.index[item.Owner]; ok { + 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.Owner, "id", id) + log.Error("Dropping duplicate blob limbo entry", "owner", item.TxHash, "id", id) return errors.New("duplicate blob") } - l.index[item.Owner] = id + 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.Owner + l.groups[item.Block][id] = item.TxHash return nil } @@ -139,15 +135,15 @@ func (l *limbo) finalize(final *types.Header) { // push stores a new blob transaction into the limbo, waiting until finality for // it to be automatically evicted. -func (l *limbo) push(tx common.Hash, block uint64, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) error { +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]; ok { + 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, blobs, commits, proofs); err != nil { - log.Error("Failed to set and index liboed blobs", "tx", tx, "err", err) + 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 @@ -156,21 +152,21 @@ func (l *limbo) push(tx common.Hash, block uint64, blobs []kzg4844.Blob, commits // 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) ([]kzg4844.Blob, []kzg4844.Commitment, []kzg4844.Proof, error) { +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, nil, nil, errors.New("unseen blob transaction") + 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, nil, nil, err + return nil, err } - return item.Blobs, item.Commits, item.Proofs, nil + return item.Tx, nil } // update changes the block number under which a blob transaction is tracked. This @@ -180,33 +176,33 @@ func (l *limbo) pull(tx common.Hash) ([]kzg4844.Blob, []kzg4844.Commitment, []kz // 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(tx common.Hash, block uint64) { +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[tx] + id, ok := l.index[txhash] if !ok { - log.Trace("Limbo cannot update non-tracked blobs", "tx", tx) + 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", tx, "block", block) + log.Trace("Blob transaction unchanged in limbo", "tx", txhash, "block", block) return } - // Retrieve the old blobs from the data store and write tehm back with a new + // 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", tx, "id", id, "err", err) + log.Error("Failed to get and drop limboed blobs", "tx", txhash, "id", id, "err", err) return } - if err := l.setAndIndex(tx, block, item.Blobs, item.Commits, item.Proofs); err != nil { - log.Error("Failed to set and index limboed blobs", "tx", tx, "err", err) + 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", tx, "old-block", item.Block, "new-block", block) + 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 @@ -220,7 +216,7 @@ func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) { if err = rlp.DecodeBytes(data, item); err != nil { return nil, err } - delete(l.index, item.Owner) + delete(l.index, item.TxHash) delete(l.groups[item.Block], id) if len(l.groups[item.Block]) == 0 { delete(l.groups, item.Block) @@ -233,13 +229,12 @@ func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) { // setAndIndex assembles a limbo blob database entry and stores it, also updating // the in-memory indices. -func (l *limbo) setAndIndex(tx common.Hash, block uint64, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) error { +func (l *limbo) setAndIndex(tx *types.Transaction, block uint64) error { + txhash := tx.Hash() item := &limboBlob{ - Owner: tx, - Block: block, - Blobs: blobs, - Commits: commits, - Proofs: proofs, + TxHash: txhash, + Block: block, + Tx: tx, } data, err := rlp.EncodeToBytes(item) if err != nil { @@ -249,10 +244,10 @@ func (l *limbo) setAndIndex(tx common.Hash, block uint64, blobs []kzg4844.Blob, if err != nil { return err } - l.index[tx] = id + l.index[txhash] = id if _, ok := l.groups[block]; !ok { l.groups[block] = make(map[uint64]common.Hash) } - l.groups[block][id] = tx + l.groups[block][id] = txhash return nil } diff --git a/core/txpool/blobpool/metrics.go b/core/txpool/blobpool/metrics.go index 070cc5ca4..587804cc6 100644 --- a/core/txpool/blobpool/metrics.go +++ b/core/txpool/blobpool/metrics.go @@ -65,7 +65,7 @@ var ( 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 pool and whether 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)) diff --git a/core/txpool/blobpool/priority.go b/core/txpool/blobpool/priority.go index 18e545c2a..a8332bd9b 100644 --- a/core/txpool/blobpool/priority.go +++ b/core/txpool/blobpool/priority.go @@ -27,7 +27,7 @@ import ( var log2_1_125 = math.Log2(1.125) // evictionPriority calculates the eviction priority based on the algorithm -// described in the BlobPool docs for a both fee components. +// 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. diff --git a/core/txpool/blobpool/slotter_test.go b/core/txpool/blobpool/slotter_test.go index 2751a1872..a7b43b4d2 100644 --- a/core/txpool/blobpool/slotter_test.go +++ b/core/txpool/blobpool/slotter_test.go @@ -38,14 +38,16 @@ func TestNewSlotter(t *testing.T) { 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-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size - 6*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size - 7*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size - 8*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size - 9*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size - 10*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size - 11*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size - 12*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos >= 4 blobs + max tx metadata size + 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)) diff --git a/core/txpool/errors.go b/core/txpool/errors.go index bc26550f7..61daa999f 100644 --- a/core/txpool/errors.go +++ b/core/txpool/errors.go @@ -52,6 +52,6 @@ var ( 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. + // 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/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index b1ae8bac8..959e328b9 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -54,10 +54,6 @@ const ( ) 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") @@ -208,7 +204,6 @@ type LegacyPool struct { chain BlockChain gasTip atomic.Pointer[big.Int] txFeed event.Feed - scope event.SubscriptionScope signer types.Signer mu sync.RWMutex @@ -298,7 +293,20 @@ func (pool *LegacyPool) Init(gasTip *big.Int, head *types.Header, reserve txpool // Set the basic pool parameters pool.gasTip.Store(gasTip) - pool.reset(nil, head) + + // 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. @@ -391,9 +399,6 @@ func (pool *LegacyPool) loop() { // Close terminates the transaction pool. func (pool *LegacyPool) Close() error { - // Unsubscribe all subscriptions registered from txpool - pool.scope.Close() - // Terminate the pool reorger and return close(pool.reorgShutdownCh) pool.wg.Wait() @@ -406,16 +411,20 @@ func (pool *LegacyPool) Close() error { } // Reset implements txpool.SubPool, allowing the legacy pool's internal state to be -// kept in sync with the main transacion pool's internal state. +// 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 of NewTxsEvent and -// starts sending event to the given channel. -func (pool *LegacyPool) SubscribeTransactions(ch chan<- core.NewTxsEvent) event.Subscription { - return pool.scope.Track(pool.txFeed.Subscribe(ch)) +// 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 @@ -535,10 +544,12 @@ func (pool *LegacyPool) Pending(enforceTips bool) map[common.Address][]*txpool.L lazies[i] = &txpool.LazyTransaction{ Pool: pool, Hash: txs[i].Hash(), - Tx: &txpool.Transaction{Tx: txs[i]}, + 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 @@ -588,7 +599,7 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro if local { opts.MinTip = new(big.Int) } - if err := txpool.ValidateTransaction(tx, nil, nil, nil, pool.currentHead.Load(), pool.signer, opts); err != nil { + if err := txpool.ValidateTransaction(tx, pool.currentHead.Load(), pool.signer, opts); err != nil { return err } return nil @@ -637,7 +648,7 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error { // 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 +// added to the allowlist, preventing any associated transaction from being dropped // out of the pool due to pricing constraints. func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, err error) { // If the transaction is already known, discard it @@ -645,7 +656,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e if pool.all.Get(hash) != nil { log.Trace("Discarding already known transaction", "hash", hash) knownTxMeter.Mark(1) - return false, ErrAlreadyKnown + return false, txpool.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. @@ -900,33 +911,19 @@ func (pool *LegacyPool) promoteTx(addr common.Address, hash common.Hash, tx *typ return true } -// Add enqueues a batch of transactions into the pool if they are valid. Depending -// on the local flag, full pricing contraints 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 []*txpool.Transaction, local bool, sync bool) []error { - unwrapped := make([]*types.Transaction, len(txs)) - for i, tx := range txs { - unwrapped[i] = tx.Tx - } - return pool.addTxs(unwrapped, local, sync) -} - // 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. +// 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.addTxs(txs, !pool.config.NoLocals, true) + 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] + return pool.addLocals([]*types.Transaction{tx})[0] } // addRemotes enqueues a batch of transactions into the pool if they are valid. If the @@ -935,28 +932,34 @@ func (pool *LegacyPool) addLocal(tx *types.Transaction) error { // 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.addTxs(txs, false, false) + 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] + return pool.addRemotes([]*types.Transaction{tx})[0] } // addRemotesSync is like addRemotes, but waits for pool reorganization. Tests use this method. func (pool *LegacyPool) addRemotesSync(txs []*types.Transaction) []error { - return pool.addTxs(txs, false, true) + 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.addTxs([]*types.Transaction{tx}, false, true)[0] + return pool.Add([]*types.Transaction{tx}, false, true)[0] } -// addTxs attempts to queue a batch of transactions if they are valid. -func (pool *LegacyPool) addTxs(txs []*types.Transaction, local, sync bool) []error { +// 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 { + // Do not treat as local if local transactions have been disabled + local = local && !pool.config.NoLocals + // Filter out known ones without obtaining the pool lock or recovering signatures var ( errs = make([]error, len(txs)) @@ -965,7 +968,7 @@ func (pool *LegacyPool) addTxs(txs []*types.Transaction, local, sync bool) []err 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 + errs[i] = txpool.ErrAlreadyKnown knownTxMeter.Mark(1) continue } @@ -974,6 +977,7 @@ func (pool *LegacyPool) addTxs(txs []*types.Transaction, local, sync bool) []err // in transactions before obtaining lock if err := pool.validateTxBasics(tx, local); err != nil { errs[i] = err + log.Trace("Discarding invalid transaction", "hash", tx.Hash(), "err", err) invalidTxMeter.Mark(1) continue } @@ -1042,12 +1046,12 @@ func (pool *LegacyPool) Status(hash common.Hash) txpool.TxStatus { } // Get returns a transaction if it is contained in the pool and nil otherwise. -func (pool *LegacyPool) Get(hash common.Hash) *txpool.Transaction { +func (pool *LegacyPool) Get(hash common.Hash) *types.Transaction { tx := pool.get(hash) if tx == nil { return nil } - return &txpool.Transaction{Tx: tx} + return tx } // get returns a transaction if it is contained in the pool and nil otherwise. diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index a8f3dd7d8..0366a58d6 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -1492,6 +1492,50 @@ func TestRepricing(t *testing.T) { } } +func TestMinGasPriceEnforced(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(eip1559Config, 10000000, statedb, new(event.Feed)) + + txPoolConfig := DefaultConfig + txPoolConfig.NoLocals = true + pool := New(txPoolConfig, blockchain) + pool.Init(new(big.Int).SetUint64(txPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()) + defer pool.Close() + + key, _ := crypto.GenerateKey() + testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000)) + + tx := pricedTransaction(0, 100000, big.NewInt(2), key) + pool.SetGasTip(big.NewInt(tx.GasPrice().Int64() + 1)) + + if err := pool.addLocal(tx); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("Min tip not enforced") + } + + if err := pool.Add([]*types.Transaction{tx}, true, false)[0]; !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("Min tip not enforced") + } + + tx = dynamicFeeTx(0, 100000, big.NewInt(3), big.NewInt(2), key) + pool.SetGasTip(big.NewInt(tx.GasTipCap().Int64() + 1)) + + if err := pool.addLocal(tx); !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("Min tip not enforced") + } + + if err := pool.Add([]*types.Transaction{tx}, true, false)[0]; !errors.Is(err, txpool.ErrUnderpriced) { + t.Fatalf("Min tip not enforced") + } + // Make sure the tx is accepted if locals are enabled + pool.config.NoLocals = false + if err := pool.Add([]*types.Transaction{tx}, true, false)[0]; err != nil { + t.Fatalf("Min tip enforced with locals enabled, error: %v", err) + } +} + // Tests that setting the transaction pool gas price to a higher value correctly // discards everything cheaper (legacy & dynamic fee) than that and moves any // gapped transactions back from the pending pool to the queue. diff --git a/core/txpool/legacypool/list.go b/core/txpool/legacypool/list.go index d5d24c85a..05ae0b58c 100644 --- a/core/txpool/legacypool/list.go +++ b/core/txpool/legacypool/list.go @@ -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) diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go index 70c0918e1..de05b38d4 100644 --- a/core/txpool/subpool.go +++ b/core/txpool/subpool.go @@ -23,42 +23,42 @@ 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/crypto/kzg4844" "github.com/ethereum/go-ethereum/event" ) -// Transaction is a helper struct to group together a canonical transaction with -// satellite data items that are needed by the pool but are not part of the chain. -type Transaction struct { - Tx *types.Transaction // Canonical transaction - - BlobTxBlobs []kzg4844.Blob // Blobs needed by the blob pool - BlobTxCommits []kzg4844.Commitment // Commitments needed by the blob pool - BlobTxProofs []kzg4844.Proof // Proofs needed by the blob pool -} - // 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 SubPool // Transaction subpool to pull the real transaction up - Hash common.Hash // Transaction hash to pull up if needed - Tx *Transaction // Transaction if already resolved + 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() *Transaction { +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 @@ -99,19 +99,21 @@ type SubPool interface { Has(hash common.Hash) bool // Get returns a transaction if it is contained in the pool, or nil otherwise. - Get(hash common.Hash) *Transaction + 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 []*Transaction, local bool, sync bool) []error + 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. - SubscribeTransactions(ch 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 // Nonce returns the next nonce of an account, with all transactions executable // by the pool already applied on top. diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index b0e91fee6..0d4e05da4 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -70,7 +70,7 @@ type TxPool struct { reservations map[common.Address]SubPool // Map with the account to pool reservations reserveLock sync.Mutex // Lock protecting the account reservations - subs event.SubscriptionScope // Subscription scope to unscubscribe all on shutdown + subs event.SubscriptionScope // Subscription scope to unsubscribe all on shutdown quit chan chan error // Quit channel to tear down the head updater } @@ -155,13 +155,15 @@ func (p *TxPool) Close() error { if err := <-errc; err != nil { errs = append(errs, err) } - // Terminate each subpool for _, subpool := range p.subpools { if err := subpool.Close(); err != nil { errs = append(errs, err) } } + // Unsubscribe anyone still listening for tx events + p.subs.Close() + if len(errs) > 0 { return fmt.Errorf("subpool close errors: %v", errs) } @@ -249,7 +251,7 @@ func (p *TxPool) Has(hash common.Hash) bool { } // Get returns a transaction if it is contained in the pool, or nil otherwise. -func (p *TxPool) Get(hash common.Hash) *Transaction { +func (p *TxPool) Get(hash common.Hash) *types.Transaction { for _, subpool := range p.subpools { if tx := subpool.Get(hash); tx != nil { return tx @@ -261,14 +263,14 @@ func (p *TxPool) Get(hash common.Hash) *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. -func (p *TxPool) Add(txs []*Transaction, local bool, sync bool) []error { +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([][]*Transaction, len(p.subpools)) + txsets := make([][]*types.Transaction, len(p.subpools)) splits := make([]int, len(txs)) for i, tx := range txs { @@ -277,7 +279,7 @@ func (p *TxPool) Add(txs []*Transaction, local bool, sync bool) []error { // Try to find a subpool that accepts the transaction for j, subpool := range p.subpools { - if subpool.Filter(tx.Tx) { + if subpool.Filter(tx) { txsets[j] = append(txsets[j], tx) splits[i] = j break @@ -316,12 +318,12 @@ func (p *TxPool) Pending(enforceTips bool) map[common.Address][]*LazyTransaction return txs } -// SubscribeNewTxsEvent registers a subscription of NewTxsEvent and starts sending -// events to the given channel. -func (p *TxPool) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { +// 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) + subs[i] = subpool.SubscribeTransactions(ch, reorgs) } return p.subs.Track(event.JoinSubscriptions(subs...)) } @@ -404,7 +406,7 @@ func (p *TxPool) Locals() []common.Address { } // Status returns the known status (unknown/pending/queued) of a transaction -// identified by their hashes. +// identified by its hash. func (p *TxPool) Status(hash common.Hash) TxStatus { for _, subpool := range p.subpools { if status := subpool.Status(hash); status != TxStatusUnknown { diff --git a/core/txpool/validation.go b/core/txpool/validation.go index e1c0f314c..0df363d81 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -46,7 +46,7 @@ type ValidationOptions struct { // // 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, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof, head *types.Header, signer types.Signer, opts *ValidationOptions) error { +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< params.BlobTxMaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob { - return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.BlobTxMaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob) - } - if len(blobs) != len(hashes) { - return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(blobs), len(hashes)) + 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 len(commits) != len(hashes) { - return fmt.Errorf("invalid number of %d blob commitments compared to %d blob hashes", len(commits), len(hashes)) + if err := validateBlobSidecar(hashes, sidecar); err != nil { + return err } - if len(proofs) != len(hashes) { - return fmt.Errorf("invalid number of %d blob proofs compared to %d blob hashes", len(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(commits[i][:]) - hash := hasher.Sum(nil) - hasher.Reset() + } + return nil +} - var vhash common.Hash - vhash[0] = params.BlobTxHashVersion - copy(vhash[1:], hash[1:]) +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() - if vhash != want { - return fmt.Errorf("blob %d: computed hash %#x mismatches transaction one %#x", i, vhash, want) - } + 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 blobs { - if err := kzg4844.VerifyBlobProof(blobs[i], commits[i], proofs[i]); err != nil { - return fmt.Errorf("invalid blob %d: %v", i, err) - } + } + // 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 @@ -171,7 +182,7 @@ type ValidationOptionsWithState struct { // 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 cummulative + // 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 @@ -226,7 +237,7 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op 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 accoun + // 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) diff --git a/core/types/block.go b/core/types/block.go index 1b5c7fc8f..1a357baa3 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -90,6 +90,9 @@ type Header struct { // 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 @@ -297,6 +300,10 @@ func CopyHeader(h *Header) *Header { cpy.BlobGasUsed = new(uint64) *cpy.BlobGasUsed = *h.BlobGasUsed } + if h.ParentBeaconRoot != nil { + cpy.ParentBeaconRoot = new(common.Hash) + *cpy.ParentBeaconRoot = *h.ParentBeaconRoot + } return &cpy } @@ -376,6 +383,8 @@ func (b *Block) BaseFee() *big.Int { return new(big.Int).Set(b.header.BaseFee) } +func (b *Block) BeaconRoot() *common.Hash { return b.header.ParentBeaconRoot } + func (b *Block) ExcessBlobGas() *uint64 { var excessBlobGas *uint64 if b.header.ExcessBlobGas != nil { diff --git a/core/types/gen_account_rlp.go b/core/types/gen_account_rlp.go index 5181d8841..3fb36f403 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 bac475959..fb1f915d0 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -16,26 +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"` - BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` - ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" 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 @@ -57,6 +58,7 @@ func (h Header) MarshalJSON() ([]byte, error) { enc.WithdrawalsHash = h.WithdrawalsHash enc.BlobGasUsed = (*hexutil.Uint64)(h.BlobGasUsed) enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas) + enc.ParentBeaconRoot = h.ParentBeaconRoot enc.Hash = h.Hash() return json.Marshal(&enc) } @@ -64,25 +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"` - BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` - ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" 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 { @@ -157,5 +160,8 @@ func (h *Header) UnmarshalJSON(input []byte) error { 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 a5ed5cd15..ed6a1a002 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" @@ -44,7 +41,8 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { _tmp2 := obj.WithdrawalsHash != nil _tmp3 := obj.BlobGasUsed != nil _tmp4 := obj.ExcessBlobGas != nil - if _tmp1 || _tmp2 || _tmp3 || _tmp4 { + _tmp5 := obj.ParentBeaconRoot != nil + if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 { if obj.BaseFee == nil { w.Write(rlp.EmptyString) } else { @@ -54,27 +52,34 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBigInt(obj.BaseFee) } } - if _tmp2 || _tmp3 || _tmp4 { + if _tmp2 || _tmp3 || _tmp4 || _tmp5 { if obj.WithdrawalsHash == nil { w.Write([]byte{0x80}) } else { w.WriteBytes(obj.WithdrawalsHash[:]) } } - if _tmp3 || _tmp4 { + if _tmp3 || _tmp4 || _tmp5 { if obj.BlobGasUsed == nil { w.Write([]byte{0x80}) } else { w.WriteUint64((*obj.BlobGasUsed)) } } - if _tmp4 { + 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) return w.Flush() } diff --git a/core/types/gen_log_json.go b/core/types/gen_log_json.go index 90e1c14d9..3ffa9c2fe 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 4a6c6b009..7e8962966 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_withdrawal_rlp.go b/core/types/gen_withdrawal_rlp.go index d0b4e0147..6a97c04c8 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 3a787aa13..43e9130fd 100644 --- a/core/types/hashes.go +++ b/core/types/hashes.go @@ -23,7 +23,7 @@ import ( ) var ( - // EmptyRootHash is the known root hash of an empty trie. + // EmptyRootHash is the known root hash of an empty merkle trie. EmptyRootHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") // EmptyUncleHash is the known hash of the empty uncle set. @@ -40,6 +40,9 @@ var ( // EmptyWithdrawalsHash is the known hash of the empty withdrawal set. EmptyWithdrawalsHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // EmptyVerkleHash is the known hash of an empty verkle trie. + EmptyVerkleHash = common.Hash{} ) // TrieRootHash returns the hash itself if it's non-empty or the predefined diff --git a/core/types/hashing.go b/core/types/hashing.go index fbdeaf0d0..224d7a87e 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 c5b9f690d..d2a98ed7b 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 e48919136..54c7ff637 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/receipt.go b/core/types/receipt.go index a96eb6b8d..4f96fde59 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -153,7 +153,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 @@ -165,12 +165,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) } } @@ -264,7 +270,7 @@ func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error { w.WriteUint64(r.CumulativeGasUsed) 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 } } diff --git a/core/types/rlp_fuzzer_test.go b/core/types/rlp_fuzzer_test.go new file mode 100644 index 000000000..a3b9f7243 --- /dev/null +++ b/core/types/rlp_fuzzer_test.go @@ -0,0 +1,147 @@ +// 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 types + +import ( + "bytes" + "fmt" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" +) + +func decodeEncode(input []byte, val interface{}) error { + if err := rlp.DecodeBytes(input, val); err != nil { + // not valid rlp, nothing to do + return nil + } + // If it _were_ valid rlp, we can encode it again + output, err := rlp.EncodeToBytes(val) + if err != nil { + return err + } + if !bytes.Equal(input, output) { + return fmt.Errorf("encode-decode is not equal, \ninput : %x\noutput: %x", input, output) + } + return nil +} + +func FuzzRLP(f *testing.F) { + f.Fuzz(fuzzRlp) +} + +func fuzzRlp(t *testing.T, input []byte) { + if len(input) == 0 || len(input) > 500*1024 { + return + } + rlp.Split(input) + if elems, _, err := rlp.SplitList(input); err == nil { + rlp.CountValues(elems) + } + rlp.NewStream(bytes.NewReader(input), 0).Decode(new(interface{})) + if err := decodeEncode(input, new(interface{})); err != nil { + t.Fatal(err) + } + { + var v struct { + Int uint + String string + Bytes []byte + } + if err := decodeEncode(input, &v); err != nil { + t.Fatal(err) + } + } + { + type Types struct { + Bool bool + Raw rlp.RawValue + Slice []*Types + Iface []interface{} + } + var v Types + if err := decodeEncode(input, &v); err != nil { + t.Fatal(err) + } + } + { + type AllTypes struct { + Int uint + String string + Bytes []byte + Bool bool + Raw rlp.RawValue + Slice []*AllTypes + Array [3]*AllTypes + Iface []interface{} + } + var v AllTypes + if err := decodeEncode(input, &v); err != nil { + t.Fatal(err) + } + } + { + if err := decodeEncode(input, [10]byte{}); err != nil { + t.Fatal(err) + } + } + { + var v struct { + Byte [10]byte + Rool [10]bool + } + if err := decodeEncode(input, &v); err != nil { + t.Fatal(err) + } + } + { + var h Header + if err := decodeEncode(input, &h); err != nil { + t.Fatal(err) + } + var b Block + if err := decodeEncode(input, &b); err != nil { + t.Fatal(err) + } + var tx Transaction + if err := decodeEncode(input, &tx); err != nil { + t.Fatal(err) + } + var txs Transactions + if err := decodeEncode(input, &txs); err != nil { + t.Fatal(err) + } + var rs Receipts + if err := decodeEncode(input, &rs); err != nil { + t.Fatal(err) + } + } + { + var v struct { + AnIntPtr *big.Int + AnInt big.Int + AnU256Ptr *uint256.Int + AnU256 uint256.Int + NotAnU256 [4]uint64 + } + if err := decodeEncode(input, &v); err != nil { + t.Fatal(err) + } + } +} diff --git a/core/types/state_account.go b/core/types/state_account.go index 314f4943e..ad07ca3f3 100644 --- a/core/types/state_account.go +++ b/core/types/state_account.go @@ -87,7 +87,7 @@ func SlimAccountRLP(account StateAccount) []byte { return data } -// FullAccount decodes the data on the 'slim RLP' format and return +// FullAccount decodes the data on the 'slim RLP' format and returns // the consensus format account. func FullAccount(data []byte) (*StateAccount, error) { var slim SlimAccount diff --git a/core/types/transaction.go b/core/types/transaction.go index e774809c2..9ec0199a0 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -37,6 +37,9 @@ var ( ErrTxTypeNotSupported = errors.New("transaction type not supported") ErrGasFeeCapTooLow = errors.New("fee cap less than base fee") errShortTypedTx = errors.New("typed transaction too short") + errInvalidYParity = errors.New("'yParity' field must be 0 or 1") + errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match") + errVYParityMissing = errors.New("missing 'yParity' or 'v' field in transaction") ) // Transaction types. @@ -82,9 +85,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) @@ -96,6 +96,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 @@ -116,7 +119,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. @@ -145,22 +148,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) 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. @@ -172,7 +183,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) if err != nil { return err @@ -186,22 +197,19 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { if len(b) <= 1 { return nil, errShortTypedTx } + var inner TxData switch b[0] { case AccessListTxType: - var inner AccessListTx - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err + inner = new(AccessListTx) case DynamicFeeTxType: - var inner DynamicFeeTx - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err + inner = new(DynamicFeeTx) case BlobTxType: - var inner BlobTx - err := rlp.DecodeBytes(b[1:], &inner) - return &inner, err + inner = new(BlobTx) default: return nil, ErrTxTypeNotSupported } + err := inner.decode(b[1:]) + return inner, err } // setDecoded sets the inner transaction and size after decoding. @@ -288,15 +296,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 blob gas limit of the transaction for blob transactions, 0 otherwise. -func (tx *Transaction) BlobGas() uint64 { return tx.inner.blobGas() } - -// BlobGasFeeCap returns the blob gas fee cap per blob 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()) } @@ -383,14 +382,66 @@ 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 @@ -428,13 +479,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 } diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index 1378cb401..08ce80b07 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -57,18 +57,18 @@ 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") + return nil, errInvalidYParity } 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 nil, errVYParityMismatch } return bigval, nil } if tx.V != nil { return tx.V.ToInt(), nil } - return nil, errors.New("missing 'yParity' or 'v' field in transaction") + return nil, errVYParityMissing } // MarshalJSON marshals as JSON with a hash. @@ -294,9 +294,6 @@ 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 } @@ -361,9 +358,6 @@ 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 } @@ -373,20 +367,20 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { itx.BlobHashes = dec.BlobVersionedHashes // signature R - var ok bool + var overflow bool if dec.R == nil { return errors.New("missing required field 'r' in transaction") } - itx.R, ok = uint256.FromBig((*big.Int)(dec.R)) - if !ok { + 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, ok = uint256.FromBig((*big.Int)(dec.S)) - if !ok { + itx.S, overflow = uint256.FromBig((*big.Int)(dec.S)) + if overflow { return errors.New("'s' value overflows uint256") } // signature V @@ -394,8 +388,8 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { if err != nil { return err } - itx.V, ok = uint256.FromBig(vbig) - if !ok { + 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 { diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 863344082..9e26642f7 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -57,7 +57,7 @@ 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 all types of transacrions +// 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. // @@ -331,11 +331,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. @@ -372,15 +368,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 a984a9c70..76a010d2e 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -214,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 } @@ -451,3 +451,97 @@ func TestTransactionSizes(t *testing.T) { } } } + +func TestYParityJSONUnmarshalling(t *testing.T) { + baseJson := map[string]interface{}{ + // type is filled in by the test + "chainId": "0x7", + "nonce": "0x0", + "to": "0x1b442286e32ddcaa6e2570ce9ed85f4b4fc87425", + "gas": "0x124f8", + "gasPrice": "0x693d4ca8", + "maxPriorityFeePerGas": "0x3b9aca00", + "maxFeePerGas": "0x6fc23ac00", + "maxFeePerBlobGas": "0x3b9aca00", + "value": "0x0", + "input": "0x", + "accessList": []interface{}{}, + "blobVersionedHashes": []string{ + "0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014", + }, + + // v and yParity are filled in by the test + "r": "0x2a922afc784d07e98012da29f2f37cae1f73eda78aa8805d3df6ee5dbb41ec1", + "s": "0x4f1f75ae6bcdf4970b4f305da1a15d8c5ddb21f555444beab77c9af2baab14", + } + + tests := []struct { + name string + v string + yParity string + wantErr error + }{ + // Valid v and yParity + {"valid v and yParity, 0x0", "0x0", "0x0", nil}, + {"valid v and yParity, 0x1", "0x1", "0x1", nil}, + + // Valid v, missing yParity + {"valid v, missing yParity, 0x0", "0x0", "", nil}, + {"valid v, missing yParity, 0x1", "0x1", "", nil}, + + // Valid yParity, missing v + {"valid yParity, missing v, 0x0", "", "0x0", nil}, + {"valid yParity, missing v, 0x1", "", "0x1", nil}, + + // Invalid yParity + {"invalid yParity, 0x2", "", "0x2", errInvalidYParity}, + + // Conflicting v and yParity + {"conflicting v and yParity", "0x1", "0x0", errVYParityMismatch}, + + // Missing v and yParity + {"missing v and yParity", "", "", errVYParityMissing}, + } + + // Run for all types that accept yParity + t.Parallel() + for _, txType := range []uint64{ + AccessListTxType, + DynamicFeeTxType, + BlobTxType, + } { + txType := txType + for _, test := range tests { + test := test + t.Run(fmt.Sprintf("txType=%d: %s", txType, test.name), func(t *testing.T) { + // Copy the base json + testJson := make(map[string]interface{}) + for k, v := range baseJson { + testJson[k] = v + } + + // Set v, yParity and type + if test.v != "" { + testJson["v"] = test.v + } + if test.yParity != "" { + testJson["yParity"] = test.yParity + } + testJson["type"] = fmt.Sprintf("0x%x", txType) + + // Marshal the JSON + jsonBytes, err := json.Marshal(testJson) + if err != nil { + t.Fatal(err) + } + + // Unmarshal the tx + var tx Transaction + err = tx.UnmarshalJSON(jsonBytes) + if err != test.wantErr { + t.Fatalf("wrong error: got %v, want %v", err, test.wantErr) + } + }) + } + } +} diff --git a/core/types/tx_access_list.go b/core/types/tx_access_list.go index 7ce9da73e..730a77b75 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 a08121bf1..da4a9b72f 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" ) @@ -38,12 +42,56 @@ type BlobTx struct { 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{ @@ -90,24 +138,29 @@ 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 { tmp := tx.To; return &tmp } -func (tx *BlobTx) blobGas() uint64 { return params.BlobTxBlobGasPerBlob * 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) 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) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { if baseFee == nil { @@ -130,3 +183,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 000000000..44ac48cc6 --- /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 47b870abb..8b5b514fd 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 902e70cf9..71025b78f 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 bb0902969..e4b03bd74 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/rawdb/databases_non64bit.go b/core/vm/contracts_fuzz_test.go similarity index 57% rename from core/rawdb/databases_non64bit.go rename to core/vm/contracts_fuzz_test.go index 1f10c2f52..87c1fff7c 100644 --- a/core/rawdb/databases_non64bit.go +++ b/core/vm/contracts_fuzz_test.go @@ -14,21 +14,31 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -//go:build !((arm64 || amd64) && !openbsd) - -package rawdb +package vm import ( - "errors" + "testing" - "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/common" ) -// Pebble is unsuported on 32bit architecture -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) { - return nil, errors.New("pebble is not supported on this platform") +func FuzzPrecompiledContracts(f *testing.F) { + // Create list of addresses + var addrs []common.Address + for k := range allPrecompiles { + addrs = append(addrs, k) + } + f.Fuzz(func(t *testing.T, addr uint8, input []byte) { + a := addrs[int(addr)%len(addrs)] + p := allPrecompiles[a] + gas := p.RequiredGas(input) + if gas > 10_000_000 { + return + } + inWant := string(input) + RunPrecompiledContract(p, input, gas) + if inHave := string(input); inWant != inHave { + t.Errorf("Precompiled %v modified input data", a) + } + }) } diff --git a/core/vm/eips.go b/core/vm/eips.go index 704c1ce12..35f0a3f7c 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -282,9 +282,15 @@ func opBlobHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ return nil, nil } -// enable4844 applies EIP-4844 (DATAHASH opcode) +// opBlobBaseFee implements BLOBBASEFEE opcode +func opBlobBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + blobBaseFee, _ := uint256.FromBig(interpreter.evm.Context.BlobBaseFee) + scope.Stack.push(blobBaseFee) + return nil, nil +} + +// enable4844 applies EIP-4844 (BLOBHASH opcode) func enable4844(jt *JumpTable) { - // New opcode jt[BLOBHASH] = &operation{ execute: opBlobHash, constantGas: GasFastestStep, @@ -293,6 +299,16 @@ func enable4844(jt *JumpTable) { } } +// 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{ diff --git a/core/vm/evm.go b/core/vm/evm.go index 40e2f3554..088b18aaa 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -67,14 +67,14 @@ type BlockContext struct { GetHash GetHashFunc // Block information - Coinbase common.Address // Provides information for COINBASE - GasLimit uint64 // Provides information for GASLIMIT - BlockNumber *big.Int // Provides information for NUMBER - Time uint64 // Provides information for TIME - Difficulty *big.Int // Provides information for DIFFICULTY - BaseFee *big.Int // Provides information for BASEFEE - Random *common.Hash // Provides information for PREVRANDAO - ExcessBlobGas *uint64 // ExcessBlobGas field in the header, needed to compute the data + Coinbase common.Address // Provides information for COINBASE + GasLimit uint64 // Provides information for GASLIMIT + BlockNumber *big.Int // Provides information for NUMBER + Time uint64 // Provides information for TIME + Difficulty *big.Int // Provides information for DIFFICULTY + BaseFee *big.Int // Provides information for BASEFEE (0 if vm runs with NoBaseFee flag and 0 gas price) + BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price) + Random *common.Hash // Provides information for PREVRANDAO } // TxContext provides the EVM with information about a transaction. @@ -82,8 +82,9 @@ type BlockContext struct { type TxContext struct { // Message information Origin common.Address // Provides information for ORIGIN - GasPrice *big.Int // Provides information for GASPRICE + GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set) BlobHashes []common.Hash // Provides information for BLOBHASH + BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set } // EVM is the Ethereum Virtual Machine base object and provides @@ -125,6 +126,17 @@ type EVM struct { // NewEVM returns a new EVM. The returned EVM is not thread safe and should // only ever be used *once*. func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM { + // If basefee tracking is disabled (eth_call, eth_estimateGas, etc), and no + // gas prices were specified, lower the basefee to 0 to avoid breaking EVM + // invariants (basefee < feecap) + if config.NoBaseFee { + if txCtx.GasPrice.BitLen() == 0 { + blockCtx.BaseFee = new(big.Int) + } + if txCtx.BlobFeeCap != nil && txCtx.BlobFeeCap.BitLen() == 0 { + blockCtx.BlobBaseFee = new(big.Int) + } + } evm := &EVM{ Context: blockCtx, TxContext: txCtx, @@ -160,14 +172,6 @@ func (evm *EVM) Interpreter() *EVMInterpreter { return evm.interpreter } -// SetBlockContext updates the block context of the EVM. -func (evm *EVM) SetBlockContext(blockCtx BlockContext) { - evm.Context = blockCtx - num := blockCtx.BlockNumber - timestamp := blockCtx.Time - evm.chainRules = evm.chainConfig.Rules(num, blockCtx.Random != nil, timestamp) -} - // Call executes the contract associated with the addr with the given input as // parameters. It also handles any necessary value transfer required and takes // the necessary steps to create accounts and reverses the state in case of an diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 5153c8b7a..4b141d8f9 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -104,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) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 345c3dc1a..722976f8c 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -251,6 +251,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 @@ -267,6 +268,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 diff --git a/core/vm/interface.go b/core/vm/interface.go index 6ce7ca4be..9b82449b1 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -79,7 +79,6 @@ type StateDB interface { AddLog(*types.Log) AddPreimage(common.Hash, []byte) - ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error GetStateSpecimen() *types.StateSpecimen } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 873337850..28da2e80e 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -45,7 +45,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 diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 702b18661..fb8725832 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -82,7 +82,8 @@ func validate(jt JumpTable) JumpTable { func newCancunInstructionSet() JumpTable { instructionSet := newShanghaiInstructionSet() - enable4844(&instructionSet) // EIP-4844 (DATAHASH opcode) + 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 diff --git a/core/vm/jump_table_export.go b/core/vm/jump_table_export.go index 6ea47d63a..b74109da0 100644 --- a/core/vm/jump_table_export.go +++ b/core/vm/jump_table_export.go @@ -22,7 +22,7 @@ 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 { @@ -56,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/opcodes.go b/core/vm/opcodes.go index 2929b8ce9..2b9231fe1 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -25,7 +25,7 @@ type OpCode byte // IsPush specifies if an opcode is a PUSH opcode. func (op OpCode) IsPush() bool { - return PUSH1 <= op && op <= PUSH32 + return PUSH0 <= op && op <= PUSH32 } // 0x0 range - arithmetic ops. @@ -101,6 +101,7 @@ const ( SELFBALANCE OpCode = 0x47 BASEFEE OpCode = 0x48 BLOBHASH OpCode = 0x49 + BLOBBASEFEE OpCode = 0x4a ) // 0x50 range - 'storage' and execution. @@ -223,8 +224,7 @@ const ( SELFDESTRUCT OpCode = 0xff ) -// Since the opcodes aren't all in order we can't use a regular slice. -var opCodeToString = map[OpCode]string{ +var opCodeToString = [256]string{ // 0x0 range - arithmetic ops. STOP: "STOP", ADD: "ADD", @@ -287,6 +287,7 @@ var opCodeToString = map[OpCode]string{ SELFBALANCE: "SELFBALANCE", BASEFEE: "BASEFEE", BLOBHASH: "BLOBHASH", + BLOBBASEFEE: "BLOBBASEFEE", // 0x50 range - 'storage' and execution. POP: "POP", @@ -397,12 +398,10 @@ var opCodeToString = map[OpCode]string{ } func (op OpCode) String() string { - str := opCodeToString[op] - if len(str) == 0 { - return fmt.Sprintf("opcode %#x not defined", int(op)) + if s := opCodeToString[op]; s != "" { + return s } - - return str + return fmt.Sprintf("opcode %#x not defined", int(op)) } var stringToOp = map[string]OpCode{ @@ -444,6 +443,7 @@ var stringToOp = map[string]OpCode{ "CHAINID": CHAINID, "BASEFEE": BASEFEE, "BLOBHASH": BLOBHASH, + "BLOBBASEFEE": BLOBBASEFEE, "DELEGATECALL": DELEGATECALL, "STATICCALL": STATICCALL, "CODESIZE": CODESIZE, diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 04c6409eb..bca6d1e83 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -197,7 +197,7 @@ var ( gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall) gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode) gasSelfdestructEIP2929 = makeSelfdestructGasFn(true) - // gasSelfdestructEIP3529 implements the changes in EIP-2539 (no refunds) + // gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds) gasSelfdestructEIP3529 = makeSelfdestructGasFn(false) // gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929 @@ -214,12 +214,12 @@ var ( // see gasSStoreEIP2200(...) in core/vm/gas_table.go for more info about how EIP 2200 is specified gasSStoreEIP2929 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP2200) - // gasSStoreEIP2539 implements gas cost for SSTORE according to EIP-2539 + // gasSStoreEIP3529 implements gas cost for SSTORE according to EIP-3529 // Replace `SSTORE_CLEARS_SCHEDULE` with `SSTORE_RESET_GAS + ACCESS_LIST_STORAGE_KEY_COST` (4,800) gasSStoreEIP3529 = makeGasSStoreFunc(params.SstoreClearsScheduleRefundEIP3529) ) -// makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-2539 +// makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-3529 func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index ffc631a90..34335b8e9 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -26,6 +26,7 @@ func NewEnv(cfg *Config) *vm.EVM { Origin: cfg.Origin, GasPrice: cfg.GasPrice, BlobHashes: cfg.BlobHashes, + BlobFeeCap: cfg.BlobFeeCap, } blockContext := vm.BlockContext{ CanTransfer: core.CanTransfer, @@ -37,6 +38,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 a3e75c672..d10457e7f 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -44,7 +44,10 @@ type Config struct { Debug bool EVMConfig vm.Config BaseFee *big.Int + BlobBaseFee *big.Int BlobHashes []common.Hash + BlobFeeCap *big.Int + Random *common.Hash State *state.StateDB GetHashFn func(n uint64) common.Hash @@ -94,6 +97,9 @@ func setDefaults(cfg *Config) { if cfg.BaseFee == nil { cfg.BaseFee = big.NewInt(params.InitialBaseFee) } + if cfg.BlobBaseFee == nil { + cfg.BlobBaseFee = big.NewInt(params.BlobTxMinBlobGasprice) + } } // Execute executes the code using the input as call data during the execution. diff --git a/tests/fuzzers/snap/debug/main.go b/core/vm/runtime/runtime_fuzz_test.go similarity index 64% rename from tests/fuzzers/snap/debug/main.go rename to core/vm/runtime/runtime_fuzz_test.go index df46bb1e2..8a4d31d81 100644 --- a/tests/fuzzers/snap/debug/main.go +++ b/core/vm/runtime/runtime_fuzz_test.go @@ -1,4 +1,4 @@ -// Copyright 2020 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,25 +14,16 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package main +package runtime import ( - "fmt" - "os" - - "github.com/ethereum/go-ethereum/tests/fuzzers/snap" + "testing" ) -func main() { - if len(os.Args) != 2 { - fmt.Fprintf(os.Stderr, "Usage: debug \n") - os.Exit(1) - } - crasher := os.Args[1] - data, err := os.ReadFile(crasher) - if err != nil { - fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) - os.Exit(1) - } - snap.FuzzTrieNodes(data) +func FuzzVmRuntime(f *testing.F) { + f.Fuzz(func(t *testing.T, code, input []byte) { + Execute(code, input, &Config{ + GasLimit: 12000000, + }) + }) } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 796d3b443..e71760bb2 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -671,7 +671,7 @@ func TestColdAccountAccessCost(t *testing.T) { for ii, op := range tracer.StructLogs() { t.Logf("%d: %v %d", ii, op.OpName(), op.GasCost) } - t.Fatalf("tescase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want) + t.Fatalf("testcase %d, gas report wrong, step %d, have %d want %d", i, tc.step, have, want) } } } diff --git a/core/vm/testdata/precompiles/pointEvaluation.json b/core/vm/testdata/precompiles/pointEvaluation.json index 93fc66d83..dfb2cad2e 100644 --- a/core/vm/testdata/precompiles/pointEvaluation.json +++ b/core/vm/testdata/precompiles/pointEvaluation.json @@ -1,6 +1,6 @@ [ { - "Input": "01d18459b334ffe8e2226eef1db874fda6db2bdd9357268b39220af2d59464fb564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a1978a0d595c823c05947b1156175e72634a377808384256e9921ebf72181890be2d6b58d4a73a880541d1656875654806942307f266e636553e94006d11423f2688945ff3bdf515859eba1005c1a7708d620a94d91a1c0c285f9584e75ec2f82a", + "Input": "01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a", "Expected": "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", "Name": "pointEvaluation1", "Gas": 50000, diff --git a/crypto/blake2b/blake2b_f_fuzz.go b/crypto/blake2b/blake2b_f_fuzz_test.go similarity index 55% rename from crypto/blake2b/blake2b_f_fuzz.go rename to crypto/blake2b/blake2b_f_fuzz_test.go index b2f405707..1de9a62de 100644 --- a/crypto/blake2b/blake2b_f_fuzz.go +++ b/crypto/blake2b/blake2b_f_fuzz_test.go @@ -1,16 +1,24 @@ -//go:build gofuzz -// +build gofuzz +// Only enable fuzzer on platforms with AVX enabled +//go:build go1.7 && amd64 && !gccgo && !appengine +// +build go1.7,amd64,!gccgo,!appengine package blake2b import ( "encoding/binary" + "testing" ) -func Fuzz(data []byte) int { +func Fuzz(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(data) + }) +} + +func fuzz(data []byte) { // Make sure the data confirms to the input model if len(data) != 211 { - return 0 + return } // Parse everything and call all the implementations var ( @@ -21,6 +29,7 @@ func Fuzz(data []byte) int { t [2]uint64 f uint64 ) + for i := 0; i < 8; i++ { offset := 2 + i*8 h[i] = binary.LittleEndian.Uint64(data[offset : offset+8]) @@ -35,24 +44,32 @@ func Fuzz(data []byte) int { if data[210]%2 == 1 { // Avoid spinning the fuzzer to hit 0/1 f = 0xFFFFFFFFFFFFFFFF } + // Run the blake2b compression on all instruction sets and cross reference want := h fGeneric(&want, &m, t[0], t[1], f, uint64(rounds)) have := h - fSSE4(&have, &m, t[0], t[1], f, uint64(rounds)) - if have != want { - panic("SSE4 mismatches generic algo") + if useSSE4 { + fSSE4(&have, &m, t[0], t[1], f, uint64(rounds)) + if have != want { + panic("SSE4 mismatches generic algo") + } } - have = h - fAVX(&have, &m, t[0], t[1], f, uint64(rounds)) - if have != want { - panic("AVX mismatches generic algo") + + if useAVX { + have = h + fAVX(&have, &m, t[0], t[1], f, uint64(rounds)) + if have != want { + panic("AVX mismatches generic algo") + } } - have = h - fAVX2(&have, &m, t[0], t[1], f, uint64(rounds)) - if have != want { - panic("AVX2 mismatches generic algo") + + if useAVX2 { + have = h + fAVX2(&have, &m, t[0], t[1], f, uint64(rounds)) + if have != want { + panic("AVX2 mismatches generic algo") + } } - return 1 } diff --git a/crypto/bls12381/g2.go b/crypto/bls12381/g2.go index 4d6f1ff11..e5fe75af2 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 b71e50e3a..e8caa7a08 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/trusted_setup.json b/crypto/kzg4844/trusted_setup.json index 37108fee3..c6d724efa 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_test.go b/crypto/secp256k1/secp256_test.go index ef2a3a379..74408d06d 100644 --- a/crypto/secp256k1/secp256_test.go +++ b/crypto/secp256k1/secp256_test.go @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be found in // the LICENSE file. +//go:build !gofuzz && cgo +// +build !gofuzz,cgo + package secp256k1 import ( diff --git a/eth/api_admin.go b/eth/api_admin.go index 88d8f5a14..4a3ccb84e 100644 --- a/eth/api_admin.go +++ b/eth/api_admin.go @@ -118,6 +118,10 @@ func (api *AdminAPI) ImportChain(file string) (bool, error) { } 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() == 0 { + continue + } blocks = append(blocks, block) index++ } diff --git a/eth/api_backend.go b/eth/api_backend.go index b0d78ed8c..f285ceedd 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -80,24 +80,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 } @@ -138,9 +132,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") @@ -148,9 +139,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") @@ -218,7 +206,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) { @@ -237,7 +228,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") } @@ -247,7 +241,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 { @@ -257,7 +251,7 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { return nil } -func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) { +func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM { if vmConfig == nil { vmConfig = b.eth.blockchain.GetVMConfig() } @@ -268,7 +262,7 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *st } else { context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil) } - return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *vmConfig), state.Error + return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *vmConfig) } func (b *EthAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { @@ -296,7 +290,7 @@ 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.Add([]*txpool.Transaction{{Tx: signedTx}}, true, false)[0] + return b.eth.txPool.Add([]*types.Transaction{signedTx}, true, false)[0] } func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) { @@ -305,7 +299,7 @@ func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) { for _, batch := range pending { for _, lazy := range batch { if tx := lazy.Resolve(); tx != nil { - txs = append(txs, tx.Tx) + txs = append(txs, tx) } } } @@ -313,10 +307,7 @@ func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) { } func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction { - if tx := b.eth.txPool.Get(hash); tx != nil { - return tx.Tx - } - return nil + return b.eth.txPool.Get(hash) } func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { @@ -345,7 +336,7 @@ func (b *EthAPIBackend) TxPool() *txpool.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) SyncProgress() ethereum.SyncProgress { @@ -420,7 +411,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 index 9cfa9103f..05010a396 100644 --- a/eth/api_debug.go +++ b/eth/api_debug.go @@ -27,6 +27,7 @@ import ( "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" @@ -132,7 +133,7 @@ func (api *DebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, error) 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) { +func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hexutil.Bytes, maxResults int, nocode, nostorage, incompletes bool) (state.Dump, error) { var stateDb *state.StateDB var err error @@ -143,7 +144,7 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex // the miner and operate on those _, stateDb = api.eth.miner.Pending() if stateDb == nil { - return state.IteratorDump{}, errors.New("pending state is not available") + return state.Dump{}, errors.New("pending state is not available") } } else { var header *types.Header @@ -157,29 +158,29 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex default: block := api.eth.blockchain.GetBlockByNumber(uint64(number)) if block == nil { - return state.IteratorDump{}, fmt.Errorf("block #%d not found", number) + return state.Dump{}, fmt.Errorf("block #%d not found", number) } header = block.Header() } if header == nil { - return state.IteratorDump{}, fmt.Errorf("block #%d not found", number) + return state.Dump{}, fmt.Errorf("block #%d not found", number) } stateDb, err = api.eth.BlockChain().StateAt(header.Root) if err != nil { - return state.IteratorDump{}, err + return state.Dump{}, 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()) + return state.Dump{}, fmt.Errorf("block %s not found", hash.Hex()) } stateDb, err = api.eth.BlockChain().StateAt(block.Root()) if err != nil { - return state.IteratorDump{}, err + return state.Dump{}, err } } else { - return state.IteratorDump{}, errors.New("either block number or block hash must be specified") + return state.Dump{}, errors.New("either block number or block hash must be specified") } opts := &state.DumpConfig{ @@ -192,7 +193,7 @@ func (api *DebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start hex if maxResults > AccountRangeMaxResults || maxResults <= 0 { opts.Max = AccountRangeMaxResults } - return stateDb.IteratorDump(opts), nil + return stateDb.RawDump(opts), nil } // StorageRangeResult is the result of a debug_storageRangeAt API call. @@ -216,7 +217,6 @@ func (api *DebugAPI) StorageRangeAt(ctx context.Context, blockNrOrHash rpc.Block if err != nil { return StorageRangeResult{}, err } - if block == nil { return StorageRangeResult{}, fmt.Errorf("block %v not found", blockNrOrHash) } @@ -226,18 +226,20 @@ func (api *DebugAPI) StorageRangeAt(ctx context.Context, blockNrOrHash rpc.Block } defer release() - st, err := statedb.StorageTrie(contractAddress) + 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 } - 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) { - trieIt, err := st.NodeIterator(start) + trieIt, err := tr.NodeIterator(start) if err != nil { return StorageRangeResult{}, err } @@ -249,7 +251,7 @@ func storageRangeAt(st state.Trie, start []byte, maxResult int) (StorageRangeRes return StorageRangeResult{}, err } e := storageEntry{Value: common.BytesToHash(content)} - if preimage := st.GetKey(it.Key); preimage != nil { + if preimage := tr.GetKey(it.Key); preimage != nil { preimage := common.BytesToHash(preimage) e.Key = &preimage } @@ -322,7 +324,7 @@ func (api *DebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]c 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() + triedb := api.eth.BlockChain().TrieDB() oldTrie, err := trie.NewStateTrie(trie.StateTrieID(startBlock.Root()), triedb) if err != nil { @@ -360,6 +362,9 @@ func (api *DebugAPI) getModifiedAccounts(startBlock, endBlock *types.Block) ([]c // 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 { @@ -420,6 +425,9 @@ func (api *DebugAPI) GetAccessibleState(from, to rpc.BlockNumber) (uint64, error // 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 @@ -429,6 +437,9 @@ func (api *DebugAPI) SetTrieFlushInterval(interval string) error { } // GetTrieFlushInterval gets the current value of in-memory trie flush interval -func (api *DebugAPI) GetTrieFlushInterval() string { - return api.eth.blockchain.GetTrieFlushInterval().String() +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_debug_test.go b/eth/api_debug_test.go index 6a1b537c2..184b90dd0 100644 --- a/eth/api_debug_test.go +++ b/eth/api_debug_test.go @@ -21,6 +21,7 @@ import ( "fmt" "math/big" "reflect" + "strings" "testing" "github.com/davecgh/go-spew/spew" @@ -35,8 +36,8 @@ import ( var dumper = spew.ConfigState{Indent: " "} -func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.IteratorDump { - result := statedb.IteratorDump(&state.DumpConfig{ +func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.Dump { + result := statedb.RawDump(&state.DumpConfig{ SkipCode: true, SkipStorage: true, OnlyWithAddresses: false, @@ -47,12 +48,12 @@ func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, st if len(result.Accounts) != expectedNum { t.Fatalf("expected %d results, got %d", expectedNum, len(result.Accounts)) } - for address := range result.Accounts { - if address == (common.Address{}) { - t.Fatalf("empty address returned") + for addr, acc := range result.Accounts { + if strings.HasSuffix(addr, "pre") || acc.Address == nil { + t.Fatalf("account without prestate (address) returned: %v", addr) } - if !statedb.Exist(address) { - t.Fatalf("account not found in state %s", address.Hex()) + if !statedb.Exist(*acc.Address) { + t.Fatalf("account not found in state %s", acc.Address.Hex()) } } return result @@ -92,16 +93,16 @@ func TestAccountRange(t *testing.T) { secondResult := accountRangeTest(t, &trie, sdb, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults) hList := make([]common.Hash, 0) - for addr1 := range firstResult.Accounts { - // If address is empty, then it makes no sense to compare + for addr1, acc := range firstResult.Accounts { + // If address is non-available, then it makes no sense to compare // them as they might be two different accounts. - if addr1 == (common.Address{}) { + if acc.Address == nil { continue } if _, duplicate := secondResult.Accounts[addr1]; duplicate { t.Fatalf("pagination test failed: results should not overlap") } - hList = append(hList, crypto.Keccak256Hash(addr1.Bytes())) + hList = append(hList, crypto.Keccak256Hash(acc.Address.Bytes())) } // Test to see if it's possible to recover from the middle of the previous // set and get an even split between the first and second sets. @@ -140,7 +141,7 @@ func TestEmptyAccountRange(t *testing.T) { st.Commit(0, true) st, _ = state.New(types.EmptyRootHash, statedb, nil) - results := st.IteratorDump(&state.DumpConfig{ + results := st.RawDump(&state.DumpConfig{ SkipCode: true, SkipStorage: true, OnlyWithAddresses: true, @@ -159,9 +160,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"), @@ -175,8 +177,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 { @@ -206,11 +210,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/backend.go b/eth/backend.go index ce7f3a4df..996afd8fa 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -110,7 +110,7 @@ type Ethereum struct { func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { // Ensure configuration values are compatible and sane if config.SyncMode == downloader.LightSync { - return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") + return nil, errors.New("can't run eth.Ethereum in light sync mode, light mode has been deprecated") } if !config.SyncMode.IsValid() { return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode) @@ -135,8 +135,15 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } - if err := pruner.RecoverPruning(stack.ResolvePath(""), chainDb); 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) @@ -147,6 +154,10 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } + networkID := config.NetworkId + if networkID == 0 { + networkID = chainConfig.ChainID.Uint64() + } eth := &Ethereum{ config: config, merger: consensus.NewMerger(chainDb), @@ -155,7 +166,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { accountManager: stack.AccountManager(), engine: engine, closeBloomHandler: make(chan struct{}), - networkID: config.NetworkId, + networkID: networkID, gasPrice: config.Miner.GasPrice, etherbase: config.Miner.Etherbase, bloomRequests: make(chan chan *bloombits.Retrieval), @@ -178,13 +189,12 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { log.Info("Block replication started", "targets", targets, "network ID", config.NetworkId, "export block-specimen", eth.ReplicaConfig.EnableSpecimen, "export block-result", eth.ReplicaConfig.EnableResult) eth.blockReplicators = append(eth.blockReplicators, replicator) } - bcVersion := rawdb.ReadDatabaseVersion(chainDb) var dbVer = "" if bcVersion != nil { dbVer = fmt.Sprintf("%d", *bcVersion) } - log.Info("Initialising Ethereum protocol", "network", config.NetworkId, "dbversion", dbVer) + log.Info("Initialising Ethereum protocol", "network", networkID, "dbversion", dbVer) if !config.SkipBcVersionCheck { if bcVersion != nil && *bcVersion > core.BlockChainVersion { @@ -208,6 +218,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { TrieTimeLimit: config.TrieTimeout, SnapshotLimit: config.SnapshotCache, Preimages: config.Preimages, + StateHistory: config.StateHistory, + StateScheme: scheme, } ) // Override the chain config with provided settings. @@ -218,7 +230,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if config.OverrideVerkle != nil { overrides.OverrideVerkle = config.OverrideVerkle } - eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, eth.shouldPreserve, &config.TxLookupLimit) + eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, eth.shouldPreserve, &config.TransactionHistory) if err != nil { return nil, err } @@ -248,7 +260,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { Chain: eth.blockchain, TxPool: eth.txPool, Merger: eth.merger, - Network: config.NetworkId, + Network: networkID, Sync: config.SyncMode, BloomCache: uint64(cacheLimit), EventMux: eth.eventMux, @@ -282,7 +294,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { } // Start the RPC service - eth.netRPCService = ethapi.NewNetAPI(eth.p2pServer, config.NetworkId) + eth.netRPCService = ethapi.NewNetAPI(eth.p2pServer, networkID) // Register the backend on the node stack.RegisterAPIs(eth.APIs()) @@ -403,7 +415,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. @@ -458,7 +470,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() } @@ -490,8 +502,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 } diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 1a2219414..37b0248f2 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -78,6 +78,7 @@ const ( var caps = []string{ "engine_forkchoiceUpdatedV1", "engine_forkchoiceUpdatedV2", + "engine_forkchoiceUpdatedV3", "engine_exchangeTransitionConfigurationV1", "engine_getPayloadV1", "engine_getPayloadV2", @@ -135,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") } @@ -146,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 } @@ -187,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) { - // 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); 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); err != nil { + return fmt.Errorf("invalid parent beacon block root: %w", err) + } + return nil +} + +func checkAttribute(active func(*big.Int, uint64) bool, exists bool, block *big.Int, time uint64) error { + if active(block, time) && !exists { + return errors.New("fork active, missing expected attribute") + } + if !active(block, time) && exists { + return errors.New("fork inactive, unexpected attribute set") + } return nil } @@ -345,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 @@ -395,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 } @@ -404,17 +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) + 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, true) + data := api.localBlocks.get(payloadID, full) if data == nil { return nil, engine.UnknownPayload } @@ -426,7 +452,7 @@ 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, nil) + return api.newPayload(params, nil, nil) } // NewPayloadV2 creates an Eth1 block, inserts it in the chain, and returns the status of the chain. @@ -441,26 +467,32 @@ func (api *ConsensusAPI) NewPayloadV2(params engine.ExecutableData) (engine.Payl if api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) { return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("newPayloadV2 called post-cancun")) } - return api.newPayload(params, nil) + 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) (engine.PayloadStatusV1, error) { - if !api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) { - return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("newPayloadV3 called pre-cancun")) - } - +func (api *ConsensusAPI) NewPayloadV3(params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash) (engine.PayloadStatusV1, error) { if params.ExcessBlobGas == nil { return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(errors.New("nil excessBlobGas post-cancun")) } - var hashes []common.Hash - if versionedHashes != nil { - hashes = *versionedHashes + 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")) } - return api.newPayload(params, hashes) + + if !api.eth.BlockChain().Config().IsCancun(new(big.Int).SetUint64(params.Number), params.Timestamp) { + 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, versionedHashes []common.Hash) (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 @@ -478,10 +510,10 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe defer api.newPayloadLock.Unlock() log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) - block, err := engine.ExecutableDataToBlock(params, versionedHashes) + block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot) if err != nil { log.Warn("Invalid NewPayload params", "params", params, "error", err) - return engine.PayloadStatusV1{Status: engine.INVALID}, nil + return api.invalid(err, nil), nil } // Stash away the last update to warn the user if the beacon client goes offline api.lastNewPayloadLock.Lock() @@ -528,7 +560,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe 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. @@ -579,7 +611,8 @@ func (api *ConsensusAPI) delayPayloadImport(block *types.Block) (engine.PayloadS // Although we don't want to trigger a sync, if there is one already in // progress, try to extend if with the current payload request to relieve // some strain from the forkchoice update. - if err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header()); err == nil { + err := api.eth.Downloader().BeaconExtend(api.eth.SyncMode(), block.Header()) + if err == nil { log.Debug("Payload accepted for sync extension", "number", block.NumberU64(), "hash", block.Hash()) return engine.PayloadStatusV1{Status: engine.SYNCING}, nil } @@ -591,12 +624,12 @@ func (api *ConsensusAPI) delayPayloadImport(block *types.Block) (engine.PayloadS // In full sync mode, failure to import a well-formed block can only mean // that the parent state is missing and the syncer rejected extending the // current cycle with the new payload. - log.Warn("Ignoring payload with missing parent", "number", block.NumberU64(), "hash", block.Hash(), "parent", block.ParentHash()) + log.Warn("Ignoring payload with missing parent", "number", block.NumberU64(), "hash", block.Hash(), "parent", block.ParentHash(), "reason", err) } else { // In non-full sync mode (i.e. snap sync) all payloads are rejected until // snap sync terminates as snap sync relies on direct database injections // and cannot afford concurrent out-if-band modifications via imports. - log.Warn("Ignoring payload while snap syncing", "number", block.NumberU64(), "hash", block.Hash()) + log.Warn("Ignoring payload while snap syncing", "number", block.NumberU64(), "hash", block.Hash(), "reason", err) } return engine.PayloadStatusV1{Status: engine.SYNCING}, nil } @@ -662,20 +695,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 @@ -744,7 +778,7 @@ func (api *ConsensusAPI) ExchangeCapabilities([]string) []string { // 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 05ad3def4..c875c485d 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -35,19 +35,20 @@ import ( beaconConsensus "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/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/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 ( @@ -69,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), @@ -108,7 +112,7 @@ func TestEth2AssembleBlock(t *testing.T) { if err != nil { t.Fatalf("error signing transaction, err=%v", err) } - ethservice.TxPool().Add([]*txpool.Transaction{{Tx: tx}}, true, false) + ethservice.TxPool().Add([]*types.Transaction{tx}, true, false) blockParams := engine.PayloadAttributes{ Timestamp: blocks[9].Time() + 5, } @@ -145,11 +149,7 @@ func TestEth2AssembleBlockWithAnotherBlocksTxs(t *testing.T) { // Put the 10th block's tx in the pool and produce a new block txs := blocks[9].Transactions() - wrapped := make([]*txpool.Transaction, len(txs)) - for i, tx := range txs { - wrapped[i] = &txpool.Transaction{Tx: tx} - } - api.eth.TxPool().Add(wrapped, false, true) + api.eth.TxPool().Add(txs, false, true) blockParams := engine.PayloadAttributes{ Timestamp: blocks[8].Time() + 5, } @@ -189,11 +189,7 @@ func TestEth2PrepareAndGetPayload(t *testing.T) { // Put the 10th block's tx in the pool and produce a new block txs := blocks[9].Transactions() - wrapped := make([]*txpool.Transaction, len(txs)) - for i, tx := range txs { - wrapped[i] = &txpool.Transaction{Tx: tx} - } - ethservice.TxPool().Add(wrapped, true, false) + ethservice.TxPool().Add(txs, true, false) blockParams := engine.PayloadAttributes{ Timestamp: blocks[8].Time() + 5, } @@ -213,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 { @@ -315,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().Add([]*txpool.Transaction{{Tx: tx}}, true, false) + ethservice.TxPool().Add([]*types.Transaction{tx}, true, false) execData, err := assembleWithTransactions(api, parent.Hash(), &engine.PayloadAttributes{ Timestamp: parent.Time() + 5, @@ -323,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, nil) + block, err := engine.ExecutableDataToBlock(*execData, nil, nil) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } @@ -365,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, nil) + block, err := engine.ExecutableDataToBlock(*execData, nil, nil) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } @@ -484,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().Add([]*txpool.Transaction{{Tx: tx}}, true, false) + ethservice.TxPool().Add([]*types.Transaction{tx}, true, false) } setupBlocks(t, ethservice, 10, parent, callback, nil) @@ -610,7 +607,7 @@ func TestNewPayloadOnInvalidChain(t *testing.T) { GasPrice: big.NewInt(2 * params.InitialBaseFee), Data: logCode, }) - ethservice.TxPool().Add([]*txpool.Transaction{{Tx: tx}}, false, true) + ethservice.TxPool().Add([]*types.Transaction{tx}, false, true) var ( params = engine.PayloadAttributes{ Timestamp: parent.Time + 1, @@ -676,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 { @@ -997,7 +995,7 @@ func TestSimultaneousNewBlock(t *testing.T) { t.Fatal(testErr) } } - block, err := engine.ExecutableDataToBlock(*execData, nil) + block, err := engine.ExecutableDataToBlock(*execData, nil, nil) if err != nil { t.Fatalf("Failed to convert executable data to block %v", err) } @@ -1077,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 { @@ -1124,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 { @@ -1254,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 { @@ -1284,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().Add([]*txpool.Transaction{{Tx: tx}}, true, false) + ethservice.TxPool().Add([]*types.Transaction{tx}, false, false) } withdrawals := make([][]*types.Withdrawal, 10) @@ -1530,13 +1531,16 @@ func TestBlockToPayloadWithBlobs(t *testing.T) { } txs = append(txs, types.NewTx(&inner)) - - blobs := make([]kzg4844.Blob, 1) - commitments := make([]kzg4844.Commitment, 1) - proofs := make([]kzg4844.Proof, 1) + 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, blobs, commitments, proofs) + envelope := engine.BlockToExecutableData(block, nil, sidecars) var want int for _, tx := range txs { want += len(tx.BlobHashes()) @@ -1550,8 +1554,91 @@ func TestBlockToPayloadWithBlobs(t *testing.T) { 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)) + _, 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.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), log.LevelTrace, 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/simulated_beacon.go b/eth/catalyst/simulated_beacon.go index d4b08b7ba..3c081074c 100644 --- a/eth/catalyst/simulated_beacon.go +++ b/eth/catalyst/simulated_beacon.go @@ -17,21 +17,24 @@ package catalyst import ( + "crypto/rand" "errors" - "fmt" + "math/big" "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/params" "github.com/ethereum/go-ethereum/rpc" ) +const devEpochLength = 32 + // withdrawalQueue implements a FIFO queue which holds withdrawals that are // pending inclusion. type withdrawalQueue struct { @@ -79,18 +82,19 @@ type SimulatedBeacon struct { lastBlockTime uint64 } +// NewSimulatedBeacon constructs a new simulated beacon chain. +// Period sets the period in which blocks should be produced. +// +// - If period is set to 0, a block is produced on every transaction. +// via Commit, Fork and AdjustTime. 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 := NewConsensusAPI(eth) + engineAPI := newConsensusAPIWithoutHeartbeat(eth) // if genesis block, send forkchoiceUpdated to trigger transition to PoS if block.Number.Sign() == 0 { @@ -118,7 +122,9 @@ func (c *SimulatedBeacon) setFeeRecipient(feeRecipient common.Address) { // Start invokes the SimulatedBeacon life-cycle function in a goroutine. func (c *SimulatedBeacon) Start() error { if c.period == 0 { - go c.loopOnDemand() + // if period is set to 0, do not mine at all + // this is used in the simulated backend where blocks + // are explicitly mined via Commit, AdjustTime and Fork } else { go c.loop() } @@ -133,76 +139,67 @@ func (c *SimulatedBeacon) Stop() error { // 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 +func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp uint64) error { + if timestamp <= c.lastBlockTime { + timestamp = 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, + Timestamp: timestamp, SuggestedFeeRecipient: feeRecipient, Withdrawals: withdrawals, + Random: random, }) if err != nil { - return fmt.Errorf("error calling forkchoice update: %v", err) + return err + } + if fcResponse == engine.STATUS_SYNCING { + return errors.New("chain rewind prevented invocation of payload creation") } - envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID) + envelope, err := c.engineAPI.getPayload(*fcResponse.PayloadID, true) if err != nil { - return fmt.Errorf("error retrieving payload: %v", err) + return err } payload := envelope.ExecutionPayload - // mark the payload as canon - if _, err = c.engineAPI.NewPayloadV2(*payload); err != nil { - return fmt.Errorf("failed to mark payload as canonical: %v", err) + 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 + } } - c.curForkchoiceState = engine.ForkchoiceStateV1{ - HeadBlockHash: payload.BlockHash, - SafeBlockHash: payload.BlockHash, - FinalizedBlockHash: payload.BlockHash, + + // Mark the payload as canon + if _, err = c.engineAPI.NewPayloadV2(*payload); err != nil { + return err } - // mark the block containing the payload as canonical + c.setCurrentState(payload.BlockHash, finalizedHash) + + // Mark the block containing the payload as canonical if _, err = c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, nil); err != nil { - return fmt.Errorf("failed to mark block as canonical: %v", err) + 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().SubscribeNewTxsEvent(newTxs) - ) - 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.Error("Error performing sealing-work", "err", err) - return - } - case <-newTxs: - withdrawals := c.withdrawals.gatherPending(10) - if err := c.sealBlock(withdrawals); err != nil { - log.Error("Error performing sealing-work", "err", err) - return - } - } - } -} - -// loopOnDemand runs the block production loop for non-zero period configuration +// loop runs the block production loop for non-zero period configuration func (c *SimulatedBeacon) loop() { timer := time.NewTimer(0) for { @@ -211,20 +208,94 @@ func (c *SimulatedBeacon) loop() { return case <-timer.C: withdrawals := c.withdrawals.gatherPending(10) - if err := c.sealBlock(withdrawals); err != nil { - log.Error("Error performing sealing-work", "err", err) - return + if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil { + log.Warn("Error performing sealing work", "err", err) + } else { + timer.Reset(time.Second * time.Duration(c.period)) } - 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, + } +} + +// Commit seals a block on demand. +func (c *SimulatedBeacon) Commit() common.Hash { + withdrawals := c.withdrawals.gatherPending(10) + if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil { + log.Warn("Error performing sealing work", "err", err) + } + return c.eth.BlockChain().CurrentBlock().Hash() +} + +// Rollback un-sends previously added transactions. +func (c *SimulatedBeacon) Rollback() { + // Flush all transactions from the transaction pools + maxUint256 := new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1) + c.eth.TxPool().SetGasTip(maxUint256) + // Set the gas tip back to accept new transactions + // TODO (Marius van der Wijden): set gas tip to parameter passed by config + c.eth.TxPool().SetGasTip(big.NewInt(params.GWei)) +} + +// Fork sets the head to the provided hash. +func (c *SimulatedBeacon) Fork(parentHash common.Hash) error { + if len(c.eth.TxPool().Pending(false)) != 0 { + return errors.New("pending block dirty") + } + parent := c.eth.BlockChain().GetBlockByHash(parentHash) + if parent == nil { + return errors.New("parent not found") + } + return c.eth.BlockChain().SetHead(parent.NumberU64()) +} + +// AdjustTime creates a new block with an adjusted timestamp. +func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error { + if len(c.eth.TxPool().Pending(false)) != 0 { + return errors.New("could not adjust time on non-empty block") + } + parent := c.eth.BlockChain().CurrentBlock() + if parent == nil { + return errors.New("parent not found") + } + withdrawals := c.withdrawals.gatherPending(10) + return c.sealBlock(withdrawals, parent.Time+uint64(adjustment)) +} + func RegisterSimulatedBeaconAPIs(stack *node.Node, sim *SimulatedBeacon) { + api := &api{sim} + if sim.period == 0 { + // mine on demand if period is set to 0 + go api.loop() + } stack.RegisterAPIs([]rpc.API{ { Namespace: "dev", - Service: &api{sim}, + Service: api, Version: "1.0", }, }) diff --git a/eth/catalyst/simulated_beacon_api.go b/eth/catalyst/simulated_beacon_api.go index 93670257f..73d0a5921 100644 --- a/eth/catalyst/simulated_beacon_api.go +++ b/eth/catalyst/simulated_beacon_api.go @@ -18,19 +18,44 @@ package catalyst import ( "context" + "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/log" ) type api struct { - simBeacon *SimulatedBeacon + sim *SimulatedBeacon +} + +func (a *api) loop() { + var ( + newTxs = make(chan core.NewTxsEvent) + sub = a.sim.eth.TxPool().SubscribeTransactions(newTxs, true) + ) + defer sub.Unsubscribe() + + for { + select { + case <-a.sim.shutdownCh: + return + case w := <-a.sim.withdrawals.pending: + withdrawals := append(a.sim.withdrawals.gatherPending(9), w) + if err := a.sim.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil { + log.Warn("Error performing sealing work", "err", err) + } + case <-newTxs: + a.sim.Commit() + } + } } func (a *api) AddWithdrawal(ctx context.Context, withdrawal *types.Withdrawal) error { - return a.simBeacon.withdrawals.add(withdrawal) + return a.sim.withdrawals.add(withdrawal) } func (a *api) SetFeeRecipient(ctx context.Context, feeRecipient common.Address) { - a.simBeacon.setFeeRecipient(feeRecipient) + a.sim.setFeeRecipient(feeRecipient) } diff --git a/eth/catalyst/simulated_beacon_test.go b/eth/catalyst/simulated_beacon_test.go index 0df195fb9..6fa97ad87 100644 --- a/eth/catalyst/simulated_beacon_test.go +++ b/eth/catalyst/simulated_beacon_test.go @@ -85,7 +85,7 @@ func TestSimulatedBeaconSendWithdrawals(t *testing.T) { // short period (1 second) for testing purposes var gasLimit uint64 = 10_000_000 - genesis := core.DeveloperGenesisBlock(gasLimit, testAddr) + genesis := core.DeveloperGenesisBlock(gasLimit, &testAddr) node, ethService, mock := startSimulatedBeaconEthService(t, genesis) _ = mock defer node.Close() diff --git a/eth/catalyst/tester.go b/eth/catalyst/tester.go index c4eafd30d..0922ac0ba 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/api.go b/eth/downloader/api.go index b3f7113bc..606c6d4e7 100644 --- a/eth/downloader/api.go +++ b/eth/downloader/api.go @@ -101,16 +101,15 @@ func (api *DownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error go func() { statuses := make(chan interface{}) sub := api.SubscribeSyncStatus(statuses) + defer sub.Unsubscribe() for { select { case status := <-statuses: notifier.Notify(rpcSub.ID, status) case <-rpcSub.Err(): - sub.Unsubscribe() return case <-notifier.Closed(): - sub.Unsubscribe() return } } diff --git a/eth/downloader/beacondevsync.go b/eth/downloader/beacondevsync.go new file mode 100644 index 000000000..9a38fedd4 --- /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 9a805396c..f1cfa92d5 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 @@ -577,7 +576,7 @@ func (d *Downloader) syncWithPeer(p *peerConnection, hash common.Hash, td, ttd * // For non-merged networks, 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. This most is mostly a fallback for - // legacy networks, but should eventually be droppped. TODO(karalabe). + // legacy networks, but should eventually be dropped. TODO(karalabe). if beaconMode { // Beacon sync, use the latest finalized block as the ancient limit // or a reasonable height if no finalized block is yet announced. @@ -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 06c22afff..e4875b959 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 021e8c4f9..cc4279b0d 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 9440972c6..5105fda66 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 84c7f2098..8201f4ca7 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 1c853c218..3169f030b 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 6b8269495..4c43af527 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 c1df3b4e6..e55715879 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -798,7 +798,7 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH } } // Blocks must have a number of blobs corresponding to the header gas usage, - // and zero before the Cancun hardfork + // 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 @@ -814,6 +814,9 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH return errInvalidBody } } + if tx.BlobTxSidecar() != nil { + return errInvalidBody + } } } if header.BlobGasUsed != nil { diff --git a/eth/downloader/queue_test.go b/eth/downloader/queue_test.go index a8b1b45e0..50b9031a2 100644 --- a/eth/downloader/queue_test.go +++ b/eth/downloader/queue_test.go @@ -20,6 +20,7 @@ import ( "fmt" "math/big" "math/rand" + "os" "sync" "testing" "time" @@ -31,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" + "golang.org/x/exp/slog" ) // makeChain creates a chain of n blocks starting at and including parent. @@ -271,7 +273,7 @@ func XTestDelivery(t *testing.T) { world.chain = blo world.progress(10) if false { - log.Root().SetHandler(log.StdoutHandler) + log.SetDefault(log.NewLogger(slog.NewTextHandler(os.Stdout, nil))) } q := newQueue(10, 10) var wg sync.WaitGroup diff --git a/eth/downloader/resultstore.go b/eth/downloader/resultstore.go index 7f7f5a89e..e4323c04e 100644 --- a/eth/downloader/resultstore.go +++ b/eth/downloader/resultstore.go @@ -142,7 +142,7 @@ func (r *resultStore) HasCompletedItems() bool { // countCompleted returns the number of items ready for delivery, stopping at // the first non-complete item. // -// The mthod assumes (at least) rlock is held. +// 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 diff --git a/eth/downloader/skeleton.go b/eth/downloader/skeleton.go index 79f4e37dc..f40ca24d9 100644 --- a/eth/downloader/skeleton.go +++ b/eth/downloader/skeleton.go @@ -69,9 +69,17 @@ var errSyncReorged = errors.New("sync reorged") // might still be propagating. var errTerminated = errors.New("terminated") -// errReorgDenied is returned if an attempt is made to extend the beacon chain -// with a new header, but it does not link up to the existing sync. -var errReorgDenied = errors.New("non-forced head reorg denied") +// errChainReorged is an internal helper error to signal that the header chain +// of the current sync cycle was (partially) reorged. +var errChainReorged = errors.New("chain reorged") + +// errChainGapped is an internal helper error to signal that the header chain +// of the current sync cycle is gaped with the one advertised by consensus client. +var errChainGapped = errors.New("chain gapped") + +// errChainForked is an internal helper error to signal that the header chain +// of the current sync cycle is forked with the one advertised by consensus client. +var errChainForked = errors.New("chain forked") func init() { // Tuning parameters is nice, but the scratch space must be assignable in @@ -271,9 +279,9 @@ func (s *skeleton) startup() { newhead, err := s.sync(head) switch { case err == errSyncLinked: - // Sync cycle linked up to the genesis block. Tear down the loop - // and restart it so, it can properly notify the backfiller. Don't - // account a new head. + // Sync cycle linked up to the genesis block, or the existent chain + // segment. Tear down the loop and restart it so, it can properly + // notify the backfiller. Don't account a new head. head = nil case err == errSyncMerged: @@ -423,7 +431,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() } @@ -457,15 +465,16 @@ func (s *skeleton) sync(head *types.Header) (*types.Header, error) { // we don't seamlessly integrate reorgs to keep things simple. If the // network starts doing many mini reorgs, it might be worthwhile handling // a limited depth without an error. - if reorged := s.processNewHead(event.header, event.final, event.force); reorged { + if err := s.processNewHead(event.header, event.final); err != nil { // If a reorg is needed, and we're forcing the new head, signal // the syncer to tear down and start over. Otherwise, drop the // non-force reorg. if event.force { event.errc <- nil // forced head reorg accepted + log.Info("Restarting sync cycle", "reason", err) return event.header, errSyncReorged } - event.errc <- errReorgDenied + event.errc <- err continue } event.errc <- nil // head extension accepted @@ -610,7 +619,7 @@ func (s *skeleton) saveSyncStatus(db ethdb.KeyValueWriter) { // accepts and integrates it into the skeleton or requests a reorg. Upon reorg, // the syncer will tear itself down and restart with a fresh head. It is simpler // to reconstruct the sync state than to mutate it and hope for the best. -func (s *skeleton) processNewHead(head *types.Header, final *types.Header, force bool) bool { +func (s *skeleton) processNewHead(head *types.Header, final *types.Header) error { // If a new finalized block was announced, update the sync process independent // of what happens with the sync head below if final != nil { @@ -631,26 +640,17 @@ func (s *skeleton) processNewHead(head *types.Header, final *types.Header, force // once more, ignore it instead of tearing down sync for a noop. if lastchain.Head == lastchain.Tail { if current := rawdb.ReadSkeletonHeader(s.db, number); current.Hash() == head.Hash() { - return false + return nil } } // Not a noop / double head announce, abort with a reorg - if force { - log.Warn("Beacon chain reorged", "tail", lastchain.Tail, "head", lastchain.Head, "newHead", number) - } - return true + return fmt.Errorf("%w, tail: %d, head: %d, newHead: %d", errChainReorged, lastchain.Tail, lastchain.Head, number) } if lastchain.Head+1 < number { - if force { - log.Warn("Beacon chain gapped", "head", lastchain.Head, "newHead", number) - } - return true + return fmt.Errorf("%w, head: %d, newHead: %d", errChainGapped, lastchain.Head, number) } 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) - } - return true + return fmt.Errorf("%w, ancestor: %d, hash: %s, want: %s", errChainForked, number-1, parent.Hash(), head.ParentHash) } // New header seems to be in the last subchain range. Unwind any extra headers // from the chain tip and insert the new head. We won't delete any trimmed @@ -666,7 +666,7 @@ func (s *skeleton) processNewHead(head *types.Header, final *types.Header, force if err := batch.Write(); err != nil { log.Crit("Failed to write skeleton sync status", "err", err) } - return false + return nil } // assignTasks attempts to match idle peers to pending header retrievals. @@ -794,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 6a76d78ac..aceadd00d 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), @@ -434,7 +434,7 @@ func TestSkeletonSyncExtend(t *testing.T) { newstate: []*subchain{ {Head: 49, Tail: 49}, }, - err: errReorgDenied, + err: errChainReorged, }, // Initialize a sync and try to extend it with a number-wise sequential // header, but a hash wise non-linking one. @@ -444,7 +444,7 @@ func TestSkeletonSyncExtend(t *testing.T) { newstate: []*subchain{ {Head: 49, Tail: 49}, }, - err: errReorgDenied, + err: errChainForked, }, // Initialize a sync and try to extend it with a non-linking future block. { @@ -453,7 +453,7 @@ func TestSkeletonSyncExtend(t *testing.T) { newstate: []*subchain{ {Head: 49, Tail: 49}, }, - err: errReorgDenied, + err: errChainGapped, }, // Initialize a sync and try to extend it with a past canonical block. { @@ -462,7 +462,7 @@ func TestSkeletonSyncExtend(t *testing.T) { newstate: []*subchain{ {Head: 50, Tail: 50}, }, - err: errReorgDenied, + err: errChainReorged, }, // Initialize a sync and try to extend it with a past sidechain block. { @@ -471,7 +471,7 @@ func TestSkeletonSyncExtend(t *testing.T) { newstate: []*subchain{ {Head: 50, Tail: 50}, }, - err: errReorgDenied, + err: errChainReorged, }, } for i, tt := range tests { @@ -487,7 +487,7 @@ func TestSkeletonSyncExtend(t *testing.T) { skeleton.Sync(tt.head, nil, true) <-wait - if err := skeleton.Sync(tt.extend, nil, false); err != tt.err { + if err := skeleton.Sync(tt.extend, nil, false); !errors.Is(err, tt.err) { t.Errorf("test %d: extension failure mismatch: have %v, want %v", i, err, tt.err) } skeleton.Terminate() @@ -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 d791723b0..1bf03411d 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 aa93508c5..456b4a8c8 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -46,21 +46,13 @@ var FullNodeGPO = gasprice.Config{ IgnorePrice: gasprice.DefaultIgnorePrice, } -// LightClientGPO contains default gasprice oracle settings for light client. -var LightClientGPO = gasprice.Config{ - Blocks: 2, - Percentile: 60, - MaxHeaderHistory: 300, - MaxBlockHistory: 5, - MaxPrice: gasprice.DefaultMaxPrice, - IgnorePrice: gasprice.DefaultIgnorePrice, -} - // Defaults contains default settings for use on the Ethereum main net. var Defaults = Config{ SyncMode: downloader.SnapSync, - NetworkId: 1, + NetworkId: 0, // enable auto configuration of networkID == chainID TxLookupLimit: 2350000, + TransactionHistory: 2350000, + StateHistory: params.FullImmutabilityThreshold, LightPeers: 100, DatabaseCache: 512, TrieCleanCache: 154, @@ -79,14 +71,15 @@ var Defaults = Config{ //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. Genesis *core.Genesis `toml:",omitempty"` - // Protocol options - NetworkId uint64 // Network ID to use for selecting peers to connect to + // Network ID separates blockchains on the peer-to-peer networking level. When left + // zero, the chain ID is used as network ID. + NetworkId uint64 SyncMode downloader.SyncMode // This can be set to list of enrtree:// URLs which will be queried for @@ -97,7 +90,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 @@ -176,7 +177,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 324fbe380..2abddc9e0 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -25,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"` @@ -63,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 @@ -105,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"` @@ -162,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 } diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index 35608031d..126eaaea7 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()) 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 6d8516fa6..6927300b1 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 638727340..ea7892d8d 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 = 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, time.Time] // 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 @@ -169,9 +188,10 @@ type TxFetcher struct { alternates map[common.Hash]map[string]struct{} // In-flight transaction alternate origins if retrieval fails // Callbacks - hasTx func(common.Hash) bool // Retrieves a tx from the local txpool - addTxs func([]*txpool.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 + 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([]*txpool.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([]*txpool.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, time.Time](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.Before(time.Now().Add(-maxTxUnderpricedTimeout)) { + 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 { @@ -295,19 +330,12 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) ) batch := txs[i:end] - wrapped := make([]*txpool.Transaction, len(batch)) - for j, tx := range batch { - wrapped[j] = &txpool.Transaction{Tx: tx} - } - for j, err := range f.addTxs(wrapped) { + 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()) } // Track a few interesting failure types switch { @@ -323,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) @@ -331,11 +363,11 @@ func (f *TxFetcher) Enqueue(peer string, txs []*types.Transaction, direct bool) // If 'other reject' is >25% of the deliveries in any batch, sleep a bit. if otherreject > 128/4 { time.Sleep(200 * time.Millisecond) - log.Warn("Peer delivering stale transactions", "peer", peer, "rejected", otherreject) + log.Debug("Peer delivering stale transactions", "peer", peer, "rejected", otherreject) } } 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 @@ -392,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. @@ -407,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 } @@ -420,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 } @@ -430,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 } @@ -444,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 @@ -472,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 { @@ -543,10 +583,28 @@ 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 { + if math.Abs(float64(delivery.metas[i].size)-float64(meta.size)) > 8 { + log.Warn("Announced transaction size mismatch", "peer", peer, "tx", hash, "size", delivery.metas[i].size, "ann", meta.size) + + // 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) @@ -556,6 +614,23 @@ 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 { + if math.Abs(float64(delivery.metas[i].size)-float64(meta.size)) > 8 { + log.Warn("Announced transaction size mismatch", "peer", peer, "tx", hash, "size", delivery.metas[i].size, "ann", meta.size) + + // 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) @@ -792,25 +867,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 { @@ -855,27 +941,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 c5805d6ef..4a62e579b 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 checks 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{}{ @@ -378,10 +607,11 @@ func TestTransactionFetcherCleanup(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -417,10 +647,11 @@ func TestTransactionFetcherCleanupEmpty(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -455,10 +686,11 @@ func TestTransactionFetcherMissingRescheduling(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -501,10 +733,11 @@ func TestTransactionFetcherMissingCleanup(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -539,10 +772,11 @@ func TestTransactionFetcherBroadcasts(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { 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{}{ @@ -644,10 +879,11 @@ func TestTransactionFetcherTimeoutRescheduling(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { 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 reasonable 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{}{ @@ -865,7 +1165,7 @@ func TestTransactionFetcherUnderpricedDedup(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { errs := make([]error, len(txs)) for i := 0; i < len(errs); i++ { if i%2 == 0 { @@ -877,6 +1177,7 @@ func TestTransactionFetcherUnderpricedDedup(t *testing.T) { return errs }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -938,7 +1239,7 @@ func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { errs := make([]error, len(txs)) for i := 0; i < len(errs); i++ { errs[i] = txpool.ErrUnderpriced @@ -946,6 +1247,7 @@ func TestTransactionFetcherUnderpricedDoSProtection(t *testing.T) { return errs }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: append(steps, []interface{}{ @@ -964,10 +1266,11 @@ func TestTransactionFetcherOutOfBoundDeliveries(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -1017,10 +1320,11 @@ func TestTransactionFetcherDrop(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -1083,10 +1387,11 @@ func TestTransactionFetcherDropRescheduling(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { 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. @@ -1128,10 +1501,11 @@ func TestTransactionFetcherFuzzCrash01(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -1155,10 +1529,11 @@ func TestTransactionFetcherFuzzCrash02(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -1184,10 +1559,11 @@ func TestTransactionFetcherFuzzCrash03(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, ) }, steps: []interface{}{ @@ -1217,13 +1593,14 @@ func TestTransactionFetcherFuzzCrash04(t *testing.T) { init: func() *TxFetcher { return NewTxFetcher( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { return make([]error, len(txs)) }, func(string, []common.Hash) error { <-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 { @@ -1535,3 +1993,38 @@ func containsHash(slice []common.Hash, hash common.Hash) bool { } return false } + +// Tests that a transaction is forgotten after the timeout. +func TestTransactionForgotten(t *testing.T) { + fetcher := NewTxFetcher( + func(common.Hash) bool { return false }, + func(txs []*types.Transaction) []error { + errs := make([]error, len(txs)) + for i := 0; i < len(errs); i++ { + errs[i] = txpool.ErrUnderpriced + } + return errs + }, + func(string, []common.Hash) error { return nil }, + func(string) {}, + ) + fetcher.Start() + defer fetcher.Stop() + // Create one TX which is 5 minutes old, and one which is recent + tx1 := types.NewTx(&types.LegacyTx{Nonce: 0}) + tx1.SetTime(time.Now().Add(-maxTxUnderpricedTimeout - 1*time.Second)) + tx2 := types.NewTx(&types.LegacyTx{Nonce: 1}) + + // Enqueue both in the fetcher. They will be immediately tagged as underpriced + if err := fetcher.Enqueue("asdf", []*types.Transaction{tx1, tx2}, false); err != nil { + t.Fatal(err) + } + // isKnownUnderpriced should trigger removal of the first tx (no longer be known underpriced) + if fetcher.isKnownUnderpriced(tx1.Hash()) { + t.Fatal("transaction should be forgotten by now") + } + // isKnownUnderpriced should not trigger removal of the second + if !fetcher.isKnownUnderpriced(tx2.Hash()) { + t.Fatal("transaction should be known underpriced") + } +} diff --git a/eth/filters/api.go b/eth/filters/api.go index cc08b442e..8cf701ec5 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -34,10 +34,15 @@ import ( ) var ( - errInvalidTopic = errors.New("invalid topic(s)") - errFilterNotFound = errors.New("filter not found") + errInvalidTopic = errors.New("invalid topic(s)") + errFilterNotFound = errors.New("filter not found") + errInvalidBlockRange = errors.New("invalid block range params") + errExceedMaxTopics = errors.New("exceed max topics") ) +// The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0 +const maxTopics = 4 + // filter is a helper struct that holds meta information over the filter type // and associated subscription in the event system. type filter struct { @@ -154,6 +159,8 @@ func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) go func() { txs := make(chan []*types.Transaction, 128) pendingTxSub := api.events.SubscribePendingTxs(txs) + defer pendingTxSub.Unsubscribe() + chainConfig := api.sys.backend.ChainConfig() for { @@ -171,10 +178,8 @@ func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) } } case <-rpcSub.Err(): - pendingTxSub.Unsubscribe() return case <-notifier.Closed(): - pendingTxSub.Unsubscribe() return } } @@ -228,16 +233,15 @@ func (api *FilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { go func() { headers := make(chan *types.Header) headersSub := api.events.SubscribeNewHeads(headers) + defer headersSub.Unsubscribe() for { select { case h := <-headers: notifier.Notify(rpcSub.ID, h) case <-rpcSub.Err(): - headersSub.Unsubscribe() return case <-notifier.Closed(): - headersSub.Unsubscribe() return } } @@ -264,6 +268,7 @@ func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subsc } go func() { + defer logsSub.Unsubscribe() for { select { case logs := <-matchedLogs: @@ -272,10 +277,8 @@ func (api *FilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc.Subsc notifier.Notify(rpcSub.ID, &log) } case <-rpcSub.Err(): // client send an unsubscribe request - logsSub.Unsubscribe() return case <-notifier.Closed(): // connection dropped - logsSub.Unsubscribe() return } } @@ -333,6 +336,9 @@ func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { // GetLogs returns logs matching the given argument that are stored within the state. func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { + if len(crit.Topics) > maxTopics { + return nil, errExceedMaxTopics + } var filter *Filter if crit.BlockHash != nil { // Block filter requested, construct a single-shot filter @@ -347,6 +353,9 @@ func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*type if crit.ToBlock != nil { end = crit.ToBlock.Int64() } + if begin > 0 && end > 0 && begin > end { + return nil, errInvalidBlockRange + } // Construct the range filter filter = api.sys.NewRangeFilter(begin, end, crit.Addresses, crit.Topics) } diff --git a/eth/filters/filter.go b/eth/filters/filter.go index d65bbfacf..83e3284a2 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -114,7 +114,7 @@ func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) { // special case for pending logs if beginPending && !endPending { - return nil, errors.New("invalid block range") + return nil, errInvalidBlockRange } // Short-cut if all we care about is pending logs @@ -347,48 +347,47 @@ func (f *Filter) pendingLogs() []*types.Log { 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 35e396c23..f98a1f84c 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -20,7 +20,6 @@ package filters import ( "context" - "errors" "fmt" "sync" "sync/atomic" @@ -300,6 +299,9 @@ func (es *EventSystem) subscribe(sub *subscription) *Subscription { // given criteria to the given logs channel. Default value for the from and to // block is "latest". If the fromBlock > toBlock an error is returned. func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) (*Subscription, error) { + if len(crit.Topics) > maxTopics { + return nil, errExceedMaxTopics + } var from, to rpc.BlockNumber if crit.FromBlock == nil { from = rpc.LatestBlockNumber @@ -332,7 +334,7 @@ func (es *EventSystem) SubscribeLogs(crit ethereum.FilterQuery, logs chan []*typ if from >= 0 && to == rpc.LatestBlockNumber { return es.subscribeLogs(crit, logs), nil } - return nil, errors.New("invalid from and to block combination: from > to") + return nil, errInvalidBlockRange } // subscribeMinedPendingLogs creates a subscription that returned mined and diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 8f06dff1c..27cad8826 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -121,7 +121,7 @@ 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 } @@ -386,6 +386,8 @@ func TestLogFilterCreation(t *testing.T) { {FilterCriteria{FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)}, false}, // from block "higher" than to block {FilterCriteria{FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, false}, + // topics more then 4 + {FilterCriteria{Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, false}, } ) @@ -420,6 +422,7 @@ func TestInvalidLogFilterCreation(t *testing.T) { 0: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, 1: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)}, 2: {FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)}, + 3: {Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, } for i, test := range testCases { @@ -429,7 +432,10 @@ func TestInvalidLogFilterCreation(t *testing.T) { } } +// TestLogFilterUninstall tests invalid getLogs requests func TestInvalidGetLogsRequest(t *testing.T) { + t.Parallel() + var ( db = rawdb.NewMemoryDatabase() _, sys = newTestFilterSystem(t, db, Config{}) @@ -442,6 +448,7 @@ func TestInvalidGetLogsRequest(t *testing.T) { 0: {BlockHash: &blockHash, FromBlock: big.NewInt(100)}, 1: {BlockHash: &blockHash, ToBlock: big.NewInt(500)}, 2: {BlockHash: &blockHash, FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, + 3: {BlockHash: &blockHash, Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, } for i, test := range testCases { @@ -451,6 +458,21 @@ func TestInvalidGetLogsRequest(t *testing.T) { } } +// TestInvalidGetRangeLogsRequest tests getLogs with invalid block range +func TestInvalidGetRangeLogsRequest(t *testing.T) { + t.Parallel() + + var ( + db = rawdb.NewMemoryDatabase() + _, sys = newTestFilterSystem(t, db, Config{}) + api = NewFilterAPI(sys, false) + ) + + if _, err := api.GetLogs(context.Background(), FilterCriteria{FromBlock: big.NewInt(2), ToBlock: big.NewInt(1)}); err != errInvalidBlockRange { + t.Errorf("Expected Logs for invalid range return error, but got: %v", err) + } +} + // TestLogFilter tests whether log filters match the correct logs that are posted to the event feed. func TestLogFilter(t *testing.T) { t.Parallel() @@ -915,10 +937,14 @@ func TestPendingTxFilterDeadlock(t *testing.T) { // Create a bunch of filters that will // timeout either in 100ms or 200ms - fids := make([]rpc.ID, 20) - for i := 0; i < len(fids); i++ { + subs := make([]*Subscription, 20) + for i := 0; i < len(subs); i++ { fid := api.NewPendingTransactionFilter(nil) - fids[i] = fid + f, ok := api.filters[fid] + if !ok { + t.Fatalf("Filter %s should exist", fid) + } + subs[i] = f.s // Wait for at least one tx to arrive in filter for { hashes, err := api.GetFilterChanges(fid) @@ -932,21 +958,13 @@ func TestPendingTxFilterDeadlock(t *testing.T) { } } - // Wait until filters have timed out - time.Sleep(3 * timeout) - - // If tx loop doesn't consume `done` after a second - // it's hanging. - select { - case done <- struct{}{}: - // Check that all filters have been uninstalled - for _, fid := range fids { - if _, err := api.GetFilterChanges(fid); err == nil { - t.Errorf("Filter %s should have been uninstalled\n", fid) - } + // Wait until filters have timed out and have been uninstalled. + for _, sub := range subs { + select { + case <-sub.Err(): + case <-time.After(1 * time.Second): + t.Fatalf("Filter timeout is hanging") } - case <-time.After(1 * time.Second): - t.Error("Tx sending loop hangs") } } diff --git a/eth/filters/filter_test.go b/eth/filters/filter_test.go index 8eea0a267..1db917c96 100644 --- a/eth/filters/filter_test.go +++ b/eth/filters/filter_test.go @@ -86,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) @@ -123,35 +123,35 @@ func TestFilters(t *testing.T) { pragma solidity >=0.7.0 <0.9.0; contract Logger { - function log0() external { - assembly { - log0(0, 0) - } - } + function log0() external { + assembly { + log0(0, 0) + } + } - function log1(uint t1) external { - assembly { - log1(0, 0, t1) - } - } + function log1(uint t1) external { + assembly { + log1(0, 0, t1) + } + } - function log2(uint t1, uint t2) external { - assembly { - log2(0, 0, t1, t2) - } - } + 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 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) - } - } + function log4(uint t1, uint t2, uint t3, uint t4) external { + assembly { + log4(0, 0, t1, t2, t3, t4) + } + } } */ bytecode = common.FromHex("608060405234801561001057600080fd5b50600436106100575760003560e01c80630aa731851461005c5780632a4c08961461006657806378b9a1f314610082578063c670f8641461009e578063c683d6a3146100ba575b600080fd5b6100646100d6565b005b610080600480360381019061007b9190610143565b6100dc565b005b61009c60048036038101906100979190610196565b6100e8565b005b6100b860048036038101906100b391906101d6565b6100f2565b005b6100d460048036038101906100cf9190610203565b6100fa565b005b600080a0565b808284600080a3505050565b8082600080a25050565b80600080a150565b80828486600080a450505050565b600080fd5b6000819050919050565b6101208161010d565b811461012b57600080fd5b50565b60008135905061013d81610117565b92915050565b60008060006060848603121561015c5761015b610108565b5b600061016a8682870161012e565b935050602061017b8682870161012e565b925050604061018c8682870161012e565b9150509250925092565b600080604083850312156101ad576101ac610108565b5b60006101bb8582860161012e565b92505060206101cc8582860161012e565b9150509250929050565b6000602082840312156101ec576101eb610108565b5b60006101fa8482850161012e565b91505092915050565b6000806000806080858703121561021d5761021c610108565b5b600061022b8782880161012e565b945050602061023c8782880161012e565b935050604061024d8782880161012e565b925050606061025e8782880161012e565b9150509295919450925056fea264697066735822122073a4b156f487e59970dc1ef449cc0d51467268f676033a17188edafcee861f9864736f6c63430008110033") @@ -180,7 +180,7 @@ func TestFilters(t *testing.T) { // Hack: GenerateChainWithGenesis creates a new db. // Commit the genesis manually and use GenerateChain. - _, err = gspec.Commit(db, trie.NewDatabase(db)) + _, err = gspec.Commit(db, trie.NewDatabase(db, nil)) if err != nil { t.Fatal(err) } @@ -287,55 +287,73 @@ func TestFilters(t *testing.T) { { 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}]`, - }, { + }, + { f: sys.NewRangeFilter(900, 999, []common.Address{contract}, [][]common.Hash{{hash3}}), - }, { + }, + { 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}]`, - }, { + }, + { 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}]`, - }, { + }, + { 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}]`, - }, { + }, + { f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}}), - }, { + }, + { f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{common.BytesToAddress([]byte("failmenow"))}, nil), - }, { + }, + { f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}, {hash1}}), - }, { + }, + { 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}]`, - }, { + }, + { 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}]`, - }, { + }, + { 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}]`, - }, { + }, + { f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil), - }, { + }, + { f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), err: "safe header not found", - }, { + }, + { 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}]`, - }, { + want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696335"],"data":"0x","blockNumber":"0x3e9","transactionHash":"0x4110587c1b8d86edc85dce929a34127f1cb8809515a9f177c91c866de3eb0638","transactionIndex":"0x0","blockHash":"0xd5e8d4e4eb51a2a2a6ec20ef68a4c2801240743c8deb77a6a1d118ac3eefb725","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}]`, - }, { + 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":"0xd5e8d4e4eb51a2a2a6ec20ef68a4c2801240743c8deb77a6a1d118ac3eefb725","logIndex":"0x0","removed":false}]`, + }, + { f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.LatestBlockNumber), nil, nil), - err: "invalid block range", + err: errInvalidBlockRange.Error(), }, } { logs, err := tc.f.Logs(context.Background()) diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go new file mode 100644 index 000000000..a36c67074 --- /dev/null +++ b/eth/gasestimator/gasestimator.go @@ -0,0 +1,235 @@ +// 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 gasestimator + +import ( + "context" + "errors" + "fmt" + "math" + "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/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +// Options are the contextual parameters to execute the requested call. +// +// Whilst it would be possible to pass a blockchain object that aggregates all +// these together, it would be excessively hard to test. Splitting the parts out +// allows testing without needing a proper live chain. +type Options struct { + Config *params.ChainConfig // Chain configuration for hard fork selection + Chain core.ChainContext // Chain context to access past block hashes + Header *types.Header // Header defining the block context to execute in + State *state.StateDB // Pre-state on top of which to estimate the gas + + ErrorRatio float64 // Allowed overestimation ratio for faster estimation termination +} + +// Estimate returns the lowest possible gas limit that allows the transaction to +// run successfully with the provided context options. It returns an error if the +// transaction would always revert, or if there are unexpected failures. +func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uint64) (uint64, []byte, error) { + // Binary search the gas limit, as it may need to be higher than the amount used + var ( + lo uint64 // lowest-known gas limit where tx execution fails + hi uint64 // lowest-known gas limit where tx execution succeeds + ) + // Determine the highest gas limit can be used during the estimation. + hi = opts.Header.GasLimit + if call.GasLimit >= params.TxGas { + hi = call.GasLimit + } + // Normalize the max fee per gas the call is willing to spend. + var feeCap *big.Int + if call.GasFeeCap != nil { + feeCap = call.GasFeeCap + } else if call.GasPrice != nil { + feeCap = call.GasPrice + } else { + feeCap = common.Big0 + } + // Recap the highest gas limit with account's available balance. + if feeCap.BitLen() != 0 { + balance := opts.State.GetBalance(call.From) + + available := new(big.Int).Set(balance) + if call.Value != nil { + if call.Value.Cmp(available) >= 0 { + return 0, nil, core.ErrInsufficientFundsForTransfer + } + available.Sub(available, call.Value) + } + allowance := new(big.Int).Div(available, feeCap) + + // If the allowance is larger than maximum uint64, skip checking + if allowance.IsUint64() && hi > allowance.Uint64() { + transfer := call.Value + if transfer == nil { + transfer = new(big.Int) + } + log.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance, + "sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance) + hi = allowance.Uint64() + } + } + // Recap the highest gas allowance with specified gascap. + if gasCap != 0 && hi > gasCap { + log.Debug("Caller gas above allowance, capping", "requested", hi, "cap", gasCap) + hi = gasCap + } + // If the transaction is a plain value transfer, short circuit estimation and + // directly try 21000. Returning 21000 without any execution is dangerous as + // some tx field combos might bump the price up even for plain transfers (e.g. + // unused access list items). Ever so slightly wasteful, but safer overall. + if len(call.Data) == 0 { + if call.To != nil && opts.State.GetCodeSize(*call.To) == 0 { + failed, _, err := execute(ctx, call, opts, params.TxGas) + if !failed && err == nil { + return params.TxGas, nil, nil + } + } + } + // We first execute the transaction at the highest allowable gas limit, since if this fails we + // can return error immediately. + failed, result, err := execute(ctx, call, opts, hi) + if err != nil { + return 0, nil, err + } + if failed { + if result != nil && !errors.Is(result.Err, vm.ErrOutOfGas) { + return 0, result.Revert(), result.Err + } + return 0, nil, fmt.Errorf("gas required exceeds allowance (%d)", hi) + } + // 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 that explicitly check gas remaining in order to execute within a + // given limit, but we probably don't want to return the lowest possible gas + // limit for these cases anyway. + lo = result.UsedGas - 1 + + // There's a fairly high chance for the transaction to execute successfully + // with gasLimit set to the first execution's usedGas + gasRefund. Explicitly + // check that gas amount and use as a limit for the binary search. + optimisticGasLimit := (result.UsedGas + result.RefundedGas + params.CallStipend) * 64 / 63 + if optimisticGasLimit < hi { + failed, _, err = execute(ctx, call, opts, optimisticGasLimit) + 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, nil, err + } + if failed { + lo = optimisticGasLimit + } else { + hi = optimisticGasLimit + } + } + // Binary search for the smallest gas limit that allows the tx to execute successfully. + for lo+1 < hi { + if opts.ErrorRatio > 0 { + // It is a bit pointless to return a perfect estimation, as changing + // network conditions require the caller to bump it up anyway. Since + // wallets tend to use 20-25% bump, allowing a small approximation + // error is fine (as long as it's upwards). + if float64(hi-lo)/float64(hi) < opts.ErrorRatio { + break + } + } + mid := (hi + lo) / 2 + 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 = execute(ctx, call, opts, 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, nil, err + } + if failed { + lo = mid + } else { + hi = mid + } + } + return hi, nil, nil +} + +// execute 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 execute(ctx context.Context, call *core.Message, opts *Options, gasLimit uint64) (bool, *core.ExecutionResult, error) { + // Configure the call for this specific execution (and revert the change after) + defer func(gas uint64) { call.GasLimit = gas }(call.GasLimit) + call.GasLimit = gasLimit + + // Execute the call and separate execution faults caused by a lack of gas or + // other non-fixable conditions + result, err := run(ctx, call, opts) + 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 +} + +// run assembles the EVM as defined by the consensus rules and runs the requested +// call invocation. +func run(ctx context.Context, call *core.Message, opts *Options) (*core.ExecutionResult, error) { + // Assemble the call and the call context + var ( + msgContext = core.NewEVMTxContext(call) + evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil) + + dirtyState = opts.State.Copy() + evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true}) + ) + // Monitor the outer context and interrupt the EVM upon cancellation. To avoid + // a dangling goroutine until the outer estimation finishes, create an internal + // context for the lifetime of this method call. + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + go func() { + <-ctx.Done() + evm.Cancel() + }() + // Execute the call, returning a wrapped error or the result + result, err := core.ApplyMessage(evm, call, new(core.GasPool).AddGas(math.MaxUint64)) + if vmerr := dirtyState.Error(); vmerr != nil { + return nil, vmerr + } + if err != nil { + return result, fmt.Errorf("failed with %d gas: %w", call.GasLimit, err) + } + return result, nil +} diff --git a/eth/handler.go b/eth/handler.go index 2453c08af..a327af611 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -29,6 +29,7 @@ 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" @@ -40,6 +41,7 @@ import ( "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 ( @@ -53,9 +55,7 @@ const ( txMaxBroadcastSize = 4096 ) -var ( - syncChallengeTimeout = 15 * time.Second // Time allowance for a node to reply to the sync progress challenge -) +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. @@ -66,18 +66,19 @@ type txPool interface { // Get retrieves the transaction from local txpool with given // tx hash. - Get(hash common.Hash) *txpool.Transaction + Get(hash common.Hash) *types.Transaction // Add should add the given transactions to the pool. - Add(txs []*txpool.Transaction, local bool, sync bool) []error + 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][]*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 @@ -87,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` @@ -98,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 @@ -161,32 +162,28 @@ 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) + // If snap sync is requested but snapshots are disabled, fail loudly + if h.snapSync.Load() && config.Chain.Snapshots() == nil { + return nil, errors.New("snap sync not supported with snapshots disabled") } // 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") @@ -243,8 +240,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() { @@ -261,7 +258,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 { @@ -270,11 +267,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) @@ -285,10 +278,10 @@ func newHandler(config *handlerConfig) (*handler, error) { } return p.RequestTxs(hashes) } - addTxs := func(txs []*txpool.Transaction) []error { + addTxs := func(txs []*types.Transaction) []error { return h.txpool.Add(txs, false, false) } - h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, addTxs, fetchTx) + h.txFetcher = fetcher.NewTxFetcher(h.txpool.Has, addTxs, fetchTx, h.removePeer) h.chainSync = newChainSyncer(h) return h, nil } @@ -353,7 +346,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 @@ -426,7 +419,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 @@ -473,7 +466,7 @@ func (h *handler) runSnapExtension(peer *snap.Peer, handler snap.Handler) error snap.EgressRegistrationErrorMeter.Mark(1) } } - peer.Log().Warn("Snapshot extension registration failed", "err", err) + peer.Log().Debug("Snapshot extension registration failed", "err", err) return err } return handler(peer) @@ -521,10 +514,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 @@ -604,26 +597,33 @@ 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 - if tx.Size() <= txMaxBroadcastSize { + 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 @@ -641,13 +641,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. @@ -674,3 +673,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 3a5e6608b..2a839f615 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) diff --git a/eth/handler_eth_test.go b/eth/handler_eth_test.go index 8b6de4663..77f0b584a 100644 --- a/eth/handler_eth_test.go +++ b/eth/handler_eth_test.go @@ -28,7 +28,6 @@ import ( "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/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" @@ -59,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 @@ -71,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 @@ -82,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) } @@ -238,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) } @@ -249,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 @@ -297,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) } @@ -308,12 +304,11 @@ func testSendTransactions(t *testing.T, protocol uint) { handler := newTestHandler() defer handler.close() - insert := make([]*txpool.Transaction, 100) + insert := make([]*types.Transaction, 100) 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] = &txpool.Transaction{Tx: tx} + insert[nonce] = tx } 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) @@ -358,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 { @@ -376,15 +371,14 @@ func testSendTransactions(t *testing.T, protocol uint) { } } for _, tx := range insert { - if _, ok := seen[tx.Tx.Hash()]; !ok { - t.Errorf("missing transaction: %x", tx.Tx.Hash()) + if _, ok := seen[tx.Hash()]; !ok { + t.Errorf("missing transaction: %x", tx.Hash()) } } } // 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) } @@ -403,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 { @@ -430,16 +424,15 @@ 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 - txs := make([]*txpool.Transaction, 1024) + txs := make([]*types.Transaction, 1024) 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] = &txpool.Transaction{Tx: tx} + txs[nonce] = tx } source.txpool.Add(txs, false, false) @@ -493,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() @@ -546,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 7451e1701..6d6132ee4 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -72,32 +72,23 @@ func (p *testTxPool) Has(hash common.Hash) bool { // Get retrieves the transaction from local txpool with given // tx hash. -func (p *testTxPool) Get(hash common.Hash) *txpool.Transaction { +func (p *testTxPool) Get(hash common.Hash) *types.Transaction { p.lock.Lock() defer p.lock.Unlock() - - if tx := p.pool[hash]; tx != nil { - return &txpool.Transaction{Tx: tx} - } - return nil + return p.pool[hash] } // Add appends a batch of transactions to the pool, and notifies any // listeners if the addition channel is non nil -func (p *testTxPool) Add(txs []*txpool.Transaction, local bool, sync bool) []error { - unwrapped := make([]*types.Transaction, len(txs)) - for i, tx := range txs { - unwrapped[i] = tx.Tx - } +func (p *testTxPool) Add(txs []*types.Transaction, local bool, sync bool) []error { p.lock.Lock() defer p.lock.Unlock() - for _, tx := range unwrapped { + for _, tx := range txs { p.pool[tx.Hash()] = tx } - - p.txFeed.Send(core.NewTxsEvent{Txs: unwrapped}) - return make([]error, len(unwrapped)) + p.txFeed.Send(core.NewTxsEvent{Txs: txs}) + return make([]error, len(txs)) } // Pending returns all the transactions known to the pool @@ -118,19 +109,21 @@ func (p *testTxPool) Pending(enforceTips bool) map[common.Address][]*txpool.Lazy for _, tx := range batch { pending[addr] = append(pending[addr], &txpool.LazyTransaction{ Hash: tx.Hash(), - Tx: &txpool.Transaction{Tx: tx}, + 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 b9cc1e03a..b27d3964a 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/broadcast.go b/eth/protocols/eth/broadcast.go index c431aa400..3045303f2 100644 --- a/eth/protocols/eth/broadcast.go +++ b/eth/protocols/eth/broadcast.go @@ -81,8 +81,8 @@ func (p *Peer) broadcastTransactions() { ) for i := 0; i < len(queue) && size < maxTxPacketSize; i++ { if tx := p.txpool.Get(queue[i]); tx != nil { - txs = append(txs, tx.Tx) - size += common.StorageSize(tx.Tx.Size()) + txs = append(txs, tx) + size += common.StorageSize(tx.Size()) } hashesCount++ } @@ -151,8 +151,8 @@ func (p *Peer) announceTransactions() { for count = 0; count < len(queue) && size < maxTxPacketSize; count++ { if tx := p.txpool.Get(queue[count]); tx != nil { pending = append(pending, queue[count]) - pendingTypes = append(pendingTypes, tx.Tx.Type()) - pendingSizes = append(pendingSizes, uint32(tx.Tx.Size())) + pendingTypes = append(pendingTypes, tx.Type()) + pendingSizes = append(pendingSizes, uint32(tx.Size())) size += common.HashLength } } diff --git a/eth/protocols/eth/discovery.go b/eth/protocols/eth/discovery.go index 87857244b..a7bdd47da 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/dispatcher.go b/eth/protocols/eth/dispatcher.go index 3f81e045b..ae98820cd 100644 --- a/eth/protocols/eth/dispatcher.go +++ b/eth/protocols/eth/dispatcher.go @@ -41,7 +41,7 @@ var ( // Request is a pending request to allow tracking it and delivering a response // back to the requester on their chosen channel. type Request struct { - peer *Peer // Peer to which this request belogs for untracking + peer *Peer // Peer to which this request belongs for untracking id uint64 // Request ID to match up replies to sink chan *Response // Channel to deliver the response on @@ -224,7 +224,7 @@ func (p *Peer) dispatcher() { switch { case res.Req == nil: // Response arrived with an untracked ID. Since even cancelled - // requests are tracked until fulfilment, a dangling response + // requests are tracked until fulfillment, a dangling response // means the remote peer implements the protocol badly. resOp.fail <- errDanglingResponse diff --git a/eth/protocols/eth/handler.go b/eth/protocols/eth/handler.go index 7f51d4f5c..42d0412a1 100644 --- a/eth/protocols/eth/handler.go +++ b/eth/protocols/eth/handler.go @@ -23,7 +23,7 @@ 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/types" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" @@ -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 @@ -90,16 +86,20 @@ type Backend interface { // TxPool defines the methods needed by the protocol handler to serve transactions. type TxPool interface { // Get retrieves the transaction from the local txpool with the given hash. - Get(hash common.Hash) *txpool.Transaction + Get(hash common.Hash) *types.Transaction } // 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 } @@ -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 3f1dc9fe7..41e18bfb3 100644 --- a/eth/protocols/eth/handler_test.go +++ b/eth/protocols/eth/handler_test.go @@ -28,7 +28,6 @@ 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" @@ -112,7 +111,7 @@ 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 := legacypool.DefaultConfig txconfig.Journal = "" // Don't litter the disk with test journals @@ -151,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) } @@ -178,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(), @@ -209,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(), @@ -225,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(), @@ -257,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(), @@ -271,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(), @@ -280,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{}, }, } @@ -309,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) } @@ -324,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) } @@ -338,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) } @@ -420,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) } @@ -613,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 f9fbf72b7..069e92dad 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 @@ -498,7 +447,7 @@ func answerGetPooledTransactions(backend Backend, query GetPooledTransactionsPac continue } // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(tx.Tx); err != nil { + if encoded, err := rlp.EncodeToBytes(tx); err != nil { log.Error("Failed to encode transaction", "err", err) } else { hashes = append(hashes, hash) @@ -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_test.go b/eth/protocols/eth/handshake_test.go index 5c6727d91..d96cfc816 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/peer.go b/eth/protocols/eth/peer.go index 219f486c8..98ad22a8c 100644 --- a/eth/protocols/eth/peer.go +++ b/eth/protocols/eth/peer.go @@ -84,7 +84,7 @@ type Peer struct { txBroadcast chan []common.Hash // Channel used to queue transaction propagation requests txAnnounce chan []common.Hash // Channel used to queue transaction announcement requests - reqDispatch chan *request // Dispatch channel to send requests and track then until fulfilment + reqDispatch chan *request // Dispatch channel to send requests and track then until fulfillment reqCancel chan *cancel // Dispatch channel to cancel pending requests and untrack them resDispatch chan *response // Dispatch channel to fulfil pending requests and untrack them @@ -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 4b9f5ad6b..0f44f83de 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 @@ -62,8 +61,6 @@ const ( NewPooledTransactionHashesMsg = 0x08 GetPooledTransactionsMsg = 0x09 PooledTransactionsMsg = 0x0a - GetNodeDataMsg = 0x0d - NodeDataMsg = 0x0e GetReceiptsMsg = 0x0f ReceiptsMsg = 0x10 ) @@ -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 - -// GetNodeDataPacket66 represents a trie node data query over eth/66. -type GetNodeDataPacket66 struct { - RequestId uint64 - GetNodeDataPacket -} - -// NodeDataPacket is the network packet for trie node data distribution. -type NodeDataPacket [][]byte +// GetReceiptsRequest represents a block receipts query. +type GetReceiptsRequest []common.Hash -// NodeDataPacket66 is the network packet for trie node data distribution over eth/66. -type NodeDataPacket66 struct { +// GetReceiptsPacket represents a block receipts query with request ID wrapping. +type GetReceiptsPacket struct { RequestId uint64 - NodeDataPacket + GetReceiptsRequest } -// GetReceiptsPacket represents a block receipts query. -type GetReceiptsPacket []common.Hash +// ReceiptsResponse is the network packet for block receipts distribution. +type ReceiptsResponse [][]*types.Receipt -// GetReceiptsPacket66 represents a block receipts query over eth/66. -type GetReceiptsPacket66 struct { +// ReceiptsPacket is the network packet for block receipts distribution with +// request ID wrapping. +type ReceiptsPacket struct { RequestId uint64 - GetReceiptsPacket + ReceiptsResponse } -// ReceiptsPacket is the network packet for block receipts distribution. -type ReceiptsPacket [][]*types.Receipt +// ReceiptsRLPResponse is used for receipts, when we already have it encoded +type ReceiptsRLPResponse []rlp.RawValue -// ReceiptsPacket66 is the network packet for block receipts distribution over eth/66. -type ReceiptsPacket66 struct { +// ReceiptsRLPPacket is ReceiptsRLPResponse with request ID wrapping. +type ReceiptsRLPPacket struct { RequestId uint64 - ReceiptsPacket + ReceiptsRLPResponse } -// 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,40 +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 (*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 (*PooledTransactionsPacket) Name() string { return "PooledTransactions" } -func (*PooledTransactionsPacket) Kind() byte { return PooledTransactionsMsg } - -func (*GetNodeDataPacket) Name() string { return "GetNodeData" } -func (*GetNodeDataPacket) Kind() byte { return GetNodeDataMsg } +func (*GetPooledTransactionsRequest) Name() string { return "GetPooledTransactions" } +func (*GetPooledTransactionsRequest) Kind() byte { return GetPooledTransactionsMsg } -func (*NodeDataPacket) Name() string { return "NodeData" } -func (*NodeDataPacket) Kind() byte { return NodeDataMsg } +func (*PooledTransactionsResponse) Name() string { return "PooledTransactions" } +func (*PooledTransactionsResponse) Kind() byte { return PooledTransactionsMsg } -func (*GetReceiptsPacket) Name() string { return "GetReceipts" } -func (*GetReceiptsPacket) Kind() byte { return GetReceiptsMsg } +func (*GetReceiptsRequest) Name() string { return "GetReceipts" } +func (*GetReceiptsRequest) Kind() byte { return GetReceiptsMsg } -func (*ReceiptsPacket) Name() string { return "Receipts" } -func (*ReceiptsPacket) Kind() byte { return ReceiptsMsg } +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 a86fbb0a6..bc2545dea 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 1c6d80d35..bd7ce9e71 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 ( @@ -284,7 +284,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 } @@ -321,7 +321,7 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac it.Release() // Generate the Merkle proofs for the first and last account - proof := light.NewNodeSet() + 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 @@ -333,7 +333,7 @@ func ServiceGetAccountRangeQuery(chain *core.BlockChain, req *GetAccountRangePac } } var proofs [][]byte - for _, blob := range proof.NodeList() { + for _, blob := range proof.List() { proofs = append(proofs, blob) } return accounts, proofs @@ -367,7 +367,7 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP if len(req.Origin) > 0 { origin, req.Origin = common.BytesToHash(req.Origin), nil } - var limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + var limit = common.MaxHash if len(req.Limit) > 0 { limit, req.Limit = common.BytesToHash(req.Limit), nil } @@ -414,7 +414,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 } @@ -423,11 +423,11 @@ 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() + 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 @@ -438,7 +438,7 @@ func ServiceGetStorageRangesQuery(chain *core.BlockChain, req *GetStorageRangesP 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 @@ -487,7 +487,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/tests/fuzzers/snap/fuzz_handler.go b/eth/protocols/snap/handler_fuzzing_test.go similarity index 75% rename from tests/fuzzers/snap/fuzz_handler.go rename to eth/protocols/snap/handler_fuzzing_test.go index 784b526dc..daed7ed44 100644 --- a/tests/fuzzers/snap/fuzz_handler.go +++ b/eth/protocols/snap/handler_fuzzing_test.go @@ -21,6 +21,7 @@ import ( "encoding/binary" "fmt" "math/big" + "testing" "time" "github.com/ethereum/go-ethereum/common" @@ -28,7 +29,6 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/eth/protocols/snap" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/params" @@ -36,6 +36,56 @@ import ( fuzz "github.com/google/gofuzz" ) +func FuzzARange(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + doFuzz(data, &GetAccountRangePacket{}, GetAccountRangeMsg) + }) +} + +func FuzzSRange(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + doFuzz(data, &GetStorageRangesPacket{}, GetStorageRangesMsg) + }) +} + +func FuzzByteCodes(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + doFuzz(data, &GetByteCodesPacket{}, GetByteCodesMsg) + }) +} + +func FuzzTrieNodes(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + doFuzz(data, &GetTrieNodesPacket{}, GetTrieNodesMsg) + }) +} + +func doFuzz(input []byte, obj interface{}, code int) { + bc := getChain() + defer bc.Stop() + fuzz.NewFromGoFuzz(input).Fuzz(obj) + var data []byte + switch p := obj.(type) { + case *GetTrieNodesPacket: + p.Root = trieRoot + data, _ = rlp.EncodeToBytes(obj) + default: + data, _ = rlp.EncodeToBytes(obj) + } + cli := &dummyRW{ + code: uint64(code), + data: data, + } + peer := NewFakePeer(65, "gazonk01", cli) + err := HandleMessage(&dummyBackend{bc}, peer) + switch { + case err == nil && cli.writeCount != 1: + panic(fmt.Sprintf("Expected 1 response, got %d", cli.writeCount)) + case err != nil && cli.writeCount != 0: + panic(fmt.Sprintf("Expected 0 response, got %d", cli.writeCount)) + } +} + var trieRoot common.Hash func getChain() *core.BlockChain { @@ -86,10 +136,10 @@ type dummyBackend struct { chain *core.BlockChain } -func (d *dummyBackend) Chain() *core.BlockChain { return d.chain } -func (d *dummyBackend) RunPeer(*snap.Peer, snap.Handler) error { return nil } -func (d *dummyBackend) PeerInfo(enode.ID) interface{} { return "Foo" } -func (d *dummyBackend) Handle(*snap.Peer, snap.Packet) error { return nil } +func (d *dummyBackend) Chain() *core.BlockChain { return d.chain } +func (d *dummyBackend) RunPeer(*Peer, Handler) error { return nil } +func (d *dummyBackend) PeerInfo(enode.ID) interface{} { return "Foo" } +func (d *dummyBackend) Handle(*Peer, Packet) error { return nil } type dummyRW struct { code uint64 @@ -110,51 +160,3 @@ func (d *dummyRW) WriteMsg(msg p2p.Msg) error { d.writeCount++ return nil } - -func doFuzz(input []byte, obj interface{}, code int) int { - if len(input) > 1024*4 { - return -1 - } - bc := getChain() - defer bc.Stop() - backend := &dummyBackend{bc} - fuzz.NewFromGoFuzz(input).Fuzz(obj) - var data []byte - switch p := obj.(type) { - case *snap.GetTrieNodesPacket: - p.Root = trieRoot - data, _ = rlp.EncodeToBytes(obj) - default: - data, _ = rlp.EncodeToBytes(obj) - } - cli := &dummyRW{ - code: uint64(code), - data: data, - } - peer := snap.NewFakePeer(65, "gazonk01", cli) - err := snap.HandleMessage(backend, peer) - switch { - case err == nil && cli.writeCount != 1: - panic(fmt.Sprintf("Expected 1 response, got %d", cli.writeCount)) - case err != nil && cli.writeCount != 0: - panic(fmt.Sprintf("Expected 0 response, got %d", cli.writeCount)) - } - return 1 -} - -// To run a fuzzer, do -// $ CGO_ENABLED=0 go-fuzz-build -func FuzzTrieNodes -// $ go-fuzz - -func FuzzARange(input []byte) int { - return doFuzz(input, &snap.GetAccountRangePacket{}, snap.GetAccountRangeMsg) -} -func FuzzSRange(input []byte) int { - return doFuzz(input, &snap.GetStorageRangesPacket{}, snap.GetStorageRangesMsg) -} -func FuzzByteCodes(input []byte) int { - return doFuzz(input, &snap.GetByteCodesPacket{}, snap.GetByteCodesMsg) -} -func FuzzTrieNodes(input []byte) int { - return doFuzz(input, &snap.GetTrieNodesPacket{}, snap.GetTrieNodesMsg) -} diff --git a/eth/protocols/snap/metrics.go b/eth/protocols/snap/metrics.go index a9f35ca44..a7d071953 100644 --- a/eth/protocols/snap/metrics.go +++ b/eth/protocols/snap/metrics.go @@ -26,4 +26,32 @@ var ( IngressRegistrationErrorMeter = metrics.NewRegisteredMeter(ingressRegistrationErrorName, nil) EgressRegistrationErrorMeter = metrics.NewRegisteredMeter(egressRegistrationErrorName, nil) + + // deletionGauge is the metric to track how many trie node deletions + // are performed in total during the sync process. + deletionGauge = metrics.NewRegisteredGauge("eth/protocols/snap/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("eth/protocols/snap/sync/lookup", nil) + + // boundaryAccountNodesGauge is the metric to track how many boundary trie + // nodes in account trie are met. + boundaryAccountNodesGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/boundary/account", nil) + + // boundaryAccountNodesGauge is the metric to track how many boundary trie + // nodes in storage tries are met. + boundaryStorageNodesGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/boundary/storage", nil) + + // smallStorageGauge is the metric to track how many storages are small enough + // to retrieved in one or two request. + smallStorageGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/small", nil) + + // largeStorageGauge is the metric to track how many storages are large enough + // to retrieved concurrently. + largeStorageGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/large", nil) + + // skipStorageHealingGauge is the metric to track how many storages are retrieved + // in multiple requests but healing is not necessary. + skipStorageHealingGauge = metrics.NewRegisteredGauge("eth/protocols/snap/sync/storage/noheal", nil) ) diff --git a/eth/protocols/snap/range.go b/eth/protocols/snap/range.go index 2627cb954..8c98c71d5 100644 --- a/eth/protocols/snap/range.go +++ b/eth/protocols/snap/range.go @@ -67,7 +67,7 @@ func (r *hashRange) End() common.Hash { // If the end overflows (non divisible range), return a shorter interval next, overflow := new(uint256.Int).AddOverflow(r.current, r.step) if overflow { - return common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + return common.MaxHash } return next.SubUint64(next, 1).Bytes32() } diff --git a/eth/protocols/snap/range_test.go b/eth/protocols/snap/range_test.go index 3461439e5..ea643f136 100644 --- a/eth/protocols/snap/range_test.go +++ b/eth/protocols/snap/range_test.go @@ -45,7 +45,7 @@ func TestHashRanges(t *testing.T) { common.HexToHash("0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), common.HexToHash("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), common.HexToHash("0xbfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), - common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.MaxHash, }, }, // Split a divisible part of the hash range up into 2 chunks @@ -58,7 +58,7 @@ func TestHashRanges(t *testing.T) { }, ends: []common.Hash{ common.HexToHash("0x8fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), - common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.MaxHash, }, }, // Split the entire hash range into a non divisible 3 chunks @@ -73,7 +73,7 @@ func TestHashRanges(t *testing.T) { ends: []common.Hash{ common.HexToHash("0x5555555555555555555555555555555555555555555555555555555555555555"), common.HexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"), - common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.MaxHash, }, }, // Split a part of hash range into a non divisible 3 chunks @@ -88,7 +88,7 @@ func TestHashRanges(t *testing.T) { ends: []common.Hash{ common.HexToHash("0x6aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), common.HexToHash("0xb555555555555555555555555555555555555555555555555555555555555555"), - common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.MaxHash, }, }, // Split a part of hash range into a non divisible 3 chunks, but with a @@ -108,7 +108,7 @@ func TestHashRanges(t *testing.T) { ends: []common.Hash{ common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5"), common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb"), - common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + common.MaxHash, }, }, } diff --git a/eth/protocols/snap/sync.go b/eth/protocols/snap/sync.go index 0f5f2ccdf..887a50775 100644 --- a/eth/protocols/snap/sync.go +++ b/eth/protocols/snap/sync.go @@ -37,11 +37,11 @@ import ( "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" ) @@ -716,6 +716,19 @@ func (s *Syncer) Sync(root common.Hash, cancel chan struct{}) error { } } +// cleanPath is used to remove the dangling nodes in the stackTrie. +func (s *Syncer) cleanPath(batch ethdb.Batch, owner common.Hash, path []byte) { + if owner == (common.Hash{}) && rawdb.ExistsAccountTrieNode(s.db, path) { + rawdb.DeleteAccountTrieNode(batch, path) + deletionGauge.Inc(1) + } + if owner != (common.Hash{}) && rawdb.ExistsStorageTrieNode(s.db, owner, path) { + rawdb.DeleteStorageTrieNode(batch, owner, path) + deletionGauge.Inc(1) + } + lookupGauge.Inc(1) +} + // loadSyncStatus retrieves a previously aborted sync status from the database, // or generates a fresh one if none is available. func (s *Syncer) loadSyncStatus() { @@ -738,9 +751,22 @@ func (s *Syncer) loadSyncStatus() { 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) + options := trie.NewStackTrieOptions() + options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(task.genBatch, common.Hash{}, path, hash, blob, s.scheme) }) + if s.scheme == rawdb.PathScheme { + // Configure the dangling node cleaner and also filter out boundary nodes + // only in the context of the path scheme. Deletion is forbidden in the + // hash scheme, as it can disrupt state completeness. + options = options.WithCleaner(func(path []byte) { + s.cleanPath(task.genBatch, common.Hash{}, path) + }) + // Skip the left boundary if it's not the first range. + // Skip the right boundary if it's not the last range. + options = options.WithSkipBoundary(task.Next != (common.Hash{}), task.Last != common.MaxHash, boundaryAccountNodesGauge) + } + task.genTrie = trie.NewStackTrie(options) for accountHash, subtasks := range task.SubTasks { for _, subtask := range subtasks { subtask := subtask // closure for subtask.genBatch in the stacktrie writer callback @@ -751,9 +777,23 @@ func (s *Syncer) loadSyncStatus() { s.storageBytes += common.StorageSize(len(key) + len(value)) }, } - subtask.genTrie = trie.NewStackTrieWithOwner(func(owner common.Hash, path []byte, hash common.Hash, val []byte) { - rawdb.WriteTrieNode(subtask.genBatch, owner, path, hash, val, s.scheme) - }, accountHash) + owner := accountHash // local assignment for stacktrie writer closure + options := trie.NewStackTrieOptions() + options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(subtask.genBatch, owner, path, hash, blob, s.scheme) + }) + if s.scheme == rawdb.PathScheme { + // Configure the dangling node cleaner and also filter out boundary nodes + // only in the context of the path scheme. Deletion is forbidden in the + // hash scheme, as it can disrupt state completeness. + options = options.WithCleaner(func(path []byte) { + s.cleanPath(subtask.genBatch, owner, path) + }) + // Skip the left boundary if it's not the first range. + // Skip the right boundary if it's not the last range. + options = options.WithSkipBoundary(subtask.Next != common.Hash{}, subtask.Last != common.MaxHash, boundaryStorageNodesGauge) + } + subtask.genTrie = trie.NewStackTrie(options) } } } @@ -797,7 +837,7 @@ func (s *Syncer) loadSyncStatus() { last := common.BigToHash(new(big.Int).Add(next.Big(), step)) if i == accountConcurrency-1 { // Make sure we don't overflow if the step is not a proper divisor - last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + last = common.MaxHash } batch := ethdb.HookedBatch{ Batch: s.db.NewBatch(), @@ -805,14 +845,27 @@ func (s *Syncer) loadSyncStatus() { s.accountBytes += common.StorageSize(len(key) + len(value)) }, } + options := trie.NewStackTrieOptions() + options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(batch, common.Hash{}, path, hash, blob, s.scheme) + }) + if s.scheme == rawdb.PathScheme { + // Configure the dangling node cleaner and also filter out boundary nodes + // only in the context of the path scheme. Deletion is forbidden in the + // hash scheme, as it can disrupt state completeness. + options = options.WithCleaner(func(path []byte) { + s.cleanPath(batch, common.Hash{}, path) + }) + // Skip the left boundary if it's not the first range. + // Skip the right boundary if it's not the last range. + options = options.WithSkipBoundary(next != common.Hash{}, last != common.MaxHash, boundaryAccountNodesGauge) + } s.tasks = append(s.tasks, &accountTask{ Next: next, 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(options), }) log.Debug("Created account sync task", "from", next, "last", last) next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) @@ -1873,7 +1926,7 @@ func (s *Syncer) processAccountResponse(res *accountResponse) { return } // Some accounts are incomplete, leave as is for the storage and contract - // task assigners to pick up and fill. + // task assigners to pick up and fill } // processBytecodeResponse integrates an already validated bytecode response @@ -1961,6 +2014,7 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { if res.subTask == nil && res.mainTask.needState[j] && (i < len(res.hashes)-1 || !res.cont) { res.mainTask.needState[j] = false res.mainTask.pend-- + smallStorageGauge.Inc(1) } // If the last contract was chunked, mark it as needing healing // to avoid writing it out to disk prematurely. @@ -1996,7 +2050,11 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { log.Debug("Chunked large contract", "initiators", len(keys), "tail", lastKey, "chunks", chunks) } r := newHashRange(lastKey, chunks) - + if chunks == 1 { + smallStorageGauge.Inc(1) + } else { + largeStorageGauge.Inc(1) + } // Our first task is the one that was just filled by this response. batch := ethdb.HookedBatch{ Batch: s.db.NewBatch(), @@ -2004,14 +2062,25 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { s.storageBytes += common.StorageSize(len(key) + len(value)) }, } + owner := account // local assignment for stacktrie writer closure + options := trie.NewStackTrieOptions() + options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(batch, owner, path, hash, blob, s.scheme) + }) + if s.scheme == rawdb.PathScheme { + options = options.WithCleaner(func(path []byte) { + s.cleanPath(batch, owner, path) + }) + // Keep the left boundary as it's the first range. + // Skip the right boundary if it's not the last range. + options = options.WithSkipBoundary(false, r.End() != common.MaxHash, boundaryStorageNodesGauge) + } 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) { - rawdb.WriteTrieNode(batch, owner, path, hash, val, s.scheme) - }, account), + genTrie: trie.NewStackTrie(options), }) for r.Next() { batch := ethdb.HookedBatch{ @@ -2020,14 +2089,27 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { s.storageBytes += common.StorageSize(len(key) + len(value)) }, } + options := trie.NewStackTrieOptions() + options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(batch, owner, path, hash, blob, s.scheme) + }) + if s.scheme == rawdb.PathScheme { + // Configure the dangling node cleaner and also filter out boundary nodes + // only in the context of the path scheme. Deletion is forbidden in the + // hash scheme, as it can disrupt state completeness. + options = options.WithCleaner(func(path []byte) { + s.cleanPath(batch, owner, path) + }) + // Skip the left boundary as it's not the first range + // Skip the right boundary if it's not the last range. + options = options.WithSkipBoundary(true, r.End() != common.MaxHash, boundaryStorageNodesGauge) + } tasks = append(tasks, &storageTask{ Next: r.Start(), Last: r.End(), root: acc.Root, genBatch: batch, - genTrie: trie.NewStackTrieWithOwner(func(owner common.Hash, path []byte, hash common.Hash, val []byte) { - rawdb.WriteTrieNode(batch, owner, path, hash, val, s.scheme) - }, account), + genTrie: trie.NewStackTrie(options), }) } for _, task := range tasks { @@ -2072,9 +2154,23 @@ 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 + options := trie.NewStackTrieOptions() + options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(batch, account, path, hash, blob, s.scheme) + }) + if s.scheme == rawdb.PathScheme { + // Configure the dangling node cleaner only in the context of the + // path scheme. Deletion is forbidden in the hash scheme, as it can + // disrupt state completeness. + // + // Notably, boundary nodes can be also kept because the whole storage + // trie is complete. + options = options.WithCleaner(func(path []byte) { + s.cleanPath(batch, account, path) + }) + } + tr := trie.NewStackTrie(options) for j := 0; j < len(res.hashes[i]); j++ { tr.Update(res.hashes[i][j][:], res.slots[i][j]) } @@ -2096,18 +2192,25 @@ func (s *Syncer) processStorageResponse(res *storageResponse) { // Large contracts could have generated new trie nodes, flush them to disk if res.subTask != nil { if res.subTask.done { - if root, err := res.subTask.genTrie.Commit(); err != nil { - log.Error("Failed to commit stack slots", "err", err) - } else if root == res.subTask.root { - // If the chunk's root is an overflown but full delivery, clear the heal request + root := res.subTask.genTrie.Commit() + if err := res.subTask.genBatch.Write(); err != nil { + log.Error("Failed to persist stack slots", "err", err) + } + res.subTask.genBatch.Reset() + + // If the chunk's root is an overflown but full delivery, + // clear the heal request. + accountHash := res.accounts[len(res.accounts)-1] + if root == res.subTask.root && rawdb.HasStorageTrieNode(s.db, accountHash, nil, root) { for i, account := range res.mainTask.res.hashes { - if account == res.accounts[len(res.accounts)-1] { + if account == accountHash { res.mainTask.needHeal[i] = false + skipStorageHealingGauge.Inc(1) } } } } - if res.subTask.genBatch.ValueSize() > ethdb.IdealBatchSize || res.subTask.done { + if res.subTask.genBatch.ValueSize() > ethdb.IdealBatchSize { if err := res.subTask.genBatch.Write(); err != nil { log.Error("Failed to persist stack slots", "err", err) } @@ -2314,9 +2417,7 @@ func (s *Syncer) forwardAccountTask(task *accountTask) { // flush after finalizing task.done. It's fine even if we crash and lose this // write as it will only cause more data to be downloaded during heal. if task.done { - if _, err := task.genTrie.Commit(); err != nil { - log.Error("Failed to commit stack account", "err", err) - } + task.genTrie.Commit() } if task.genBatch.ValueSize() > ethdb.IdealBatchSize || task.done { if err := task.genBatch.Write(); err != nil { @@ -2394,17 +2495,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() - - var end []byte - if len(keys) > 0 { - end = keys[len(keys)-1] - } - cont, err := trie.VerifyRangeProof(root, req.origin[:], end, keys, accounts, proofdb) + cont, err := trie.VerifyRangeProof(root, req.origin[:], keys, accounts, nodes.Set()) if err != nil { logger.Warn("Account range failed proof", "err", err) // Signal this request as failed, and ready for rescheduling @@ -2621,7 +2716,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo // the requested data. For storage range queries that means the state being // retrieved was either already pruned remotely, or the peer is not yet // synced to our head. - if len(hashes) == 0 { + if len(hashes) == 0 && len(proof) == 0 { logger.Debug("Peer rejected storage request") s.statelessPeers[peer.ID()] = struct{}{} s.lock.Unlock() @@ -2633,13 +2728,20 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo // Reconstruct the partial tries from the response and verify them var cont bool + // If a proof was attached while the response is empty, it indicates that the + // requested range specified with 'origin' is empty. Construct an empty state + // response locally to finalize the range. + if len(hashes) == 0 && len(proof) > 0 { + hashes = append(hashes, []common.Hash{}) + slots = append(slots, [][]byte{}) + } for i := 0; i < len(hashes); i++ { // Convert the keys and proofs into an internal format keys := make([][]byte, len(hashes[i])) 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) @@ -2649,7 +2751,7 @@ func (s *Syncer) OnStorage(peer SyncPeer, id uint64, hashes [][]common.Hash, slo if len(nodes) == 0 { // No proof has been attached, the response must cover the entire key // space and hash to the origin root. - _, err = trie.VerifyRangeProof(req.roots[i], nil, nil, keys, slots[i], nil) + _, err = trie.VerifyRangeProof(req.roots[i], nil, keys, slots[i], nil) if err != nil { s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage slots failed proof", "err", err) @@ -2658,13 +2760,9 @@ 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 { - end = keys[len(keys)-1] - } - cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], end, keys, slots[i], proofdb) + cont, err = trie.VerifyRangeProof(req.roots[i], req.origin[:], keys, slots[i], proofdb) if err != nil { s.scheduleRevertStorageRequest(req) // reschedule request logger.Warn("Storage range failed proof", "err", err) diff --git a/eth/protocols/snap/sync_test.go b/eth/protocols/snap/sync_test.go index 0aa6fd873..5d4099a81 100644 --- a/eth/protocols/snap/sync_test.go +++ b/eth/protocols/snap/sync_test.go @@ -22,6 +22,7 @@ import ( "encoding/binary" "fmt" "math/big" + mrand "math/rand" "sync" "testing" "time" @@ -31,10 +32,11 @@ 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/testutil" + "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" @@ -253,7 +255,7 @@ func defaultAccountRequestHandler(t *testPeer, id uint64, root common.Hash, orig func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.Hash, limit common.Hash, cap uint64) (keys []common.Hash, vals [][]byte, proofs [][]byte) { var size uint64 if limit == (common.Hash{}) { - limit = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + limit = common.MaxHash } for _, entry := range t.accountValues { if size > cap { @@ -272,7 +274,7 @@ 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() + 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) } @@ -282,7 +284,7 @@ func createAccountRequestResponse(t *testPeer, root common.Hash, origin common.H 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 @@ -318,7 +320,7 @@ func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []comm if len(origin) > 0 { originHash = common.BytesToHash(origin) } - var limitHash = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + var limitHash = common.MaxHash if len(limit) > 0 { limitHash = common.BytesToHash(limit) } @@ -352,7 +354,7 @@ 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 @@ -367,7 +369,7 @@ func createStorageRequestResponse(t *testPeer, root common.Hash, accounts []comm 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,7 +412,7 @@ 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 @@ -426,7 +428,7 @@ func createStorageRequestResponseAlwaysProve(t *testPeer, root common.Hash, acco 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 +563,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 +577,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,9 +600,10 @@ func TestSyncBloatedProof(t *testing.T) { vals = append(vals, entry.v) } // The proofs - proof := light.NewNodeSet() + 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 { @@ -608,7 +616,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 +646,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 +660,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 +672,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 +680,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 +694,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 +708,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 +729,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 +743,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 +764,7 @@ func TestSyncWithStorage(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(3, 3000, true, false) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 3, 3000, true, false, false) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) @@ -746,19 +774,24 @@ func TestSyncWithStorage(t *testing.T) { source.storageValues = storageElems return source } - syncer := setupSyncer(nodeScheme, mkSource("sourceA")) + syncer := setupSyncer(scheme, mkSource("sourceA")) done := checkStall(t, term) if err := syncer.Sync(sourceAccountTrie.Hash(), cancel); err != nil { 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 +801,7 @@ func TestMultiSyncManyUseless(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false) mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer { source := newTestPeer(name, t, term) @@ -790,7 +823,7 @@ func TestMultiSyncManyUseless(t *testing.T) { } syncer := setupSyncer( - nodeScheme, + scheme, mkSource("full", true, true, true), mkSource("noAccounts", false, true, true), mkSource("noStorage", true, false, true), @@ -801,11 +834,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 +855,7 @@ func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false) mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer { source := newTestPeer(name, t, term) @@ -837,7 +877,7 @@ func TestMultiSyncManyUselessWithLowTimeout(t *testing.T) { } syncer := setupSyncer( - nodeScheme, + scheme, mkSource("full", true, true, true), mkSource("noAccounts", false, true, true), mkSource("noStorage", true, false, true), @@ -853,11 +893,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 +914,7 @@ func TestMultiSyncManyUnresponsive(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false) mkSource := func(name string, noAccount, noStorage, noTrieNode bool) *testPeer { source := newTestPeer(name, t, term) @@ -889,7 +936,7 @@ func TestMultiSyncManyUnresponsive(t *testing.T) { } syncer := setupSyncer( - nodeScheme, + scheme, mkSource("full", true, true, true), mkSource("noAccounts", false, true, true), mkSource("noStorage", true, false, true), @@ -903,7 +950,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 +972,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 +986,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 +1004,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 +1012,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 +1026,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 +1051,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 +1059,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 +1073,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 +1096,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 +1116,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 +1139,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 +1147,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 +1161,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 +1195,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 +1203,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 +1217,7 @@ func TestSyncBoundaryStorageTrie(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(10, 1000, false, true) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 10, 1000, false, true, false) mkSource := func(name string) *testPeer { source := newTestPeer(name, t, term) @@ -1151,7 +1228,7 @@ func TestSyncBoundaryStorageTrie(t *testing.T) { return source } syncer := setupSyncer( - nodeScheme, + scheme, mkSource("peer-a"), mkSource("peer-b"), ) @@ -1160,7 +1237,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 +1245,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 +1259,7 @@ func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(300, 1000, false, false) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 300, 1000, false, false, false) mkSource := func(name string, slow bool) *testPeer { source := newTestPeer(name, t, term) @@ -1193,7 +1275,7 @@ func TestSyncWithStorageAndOneCappedPeer(t *testing.T) { } syncer := setupSyncer( - nodeScheme, + scheme, mkSource("nice-a", false), mkSource("slow", true), ) @@ -1202,7 +1284,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 +1292,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 +1306,7 @@ func TestSyncWithStorageAndCorruptPeer(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false) mkSource := func(name string, handler storageHandlerFunc) *testPeer { source := newTestPeer(name, t, term) @@ -1232,7 +1319,7 @@ func TestSyncWithStorageAndCorruptPeer(t *testing.T) { } syncer := setupSyncer( - nodeScheme, + scheme, mkSource("nice-a", defaultStorageRequestHandler), mkSource("nice-b", defaultStorageRequestHandler), mkSource("nice-c", defaultStorageRequestHandler), @@ -1243,12 +1330,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 +1350,7 @@ func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { }) } ) - nodeScheme, sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(100, 3000, true, false) + sourceAccountTrie, elems, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 100, 3000, true, false, false) mkSource := func(name string, handler storageHandlerFunc) *testPeer { source := newTestPeer(name, t, term) @@ -1270,7 +1362,7 @@ func TestSyncWithStorageAndNonProvingPeer(t *testing.T) { return source } syncer := setupSyncer( - nodeScheme, + scheme, mkSource("nice-a", defaultStorageRequestHandler), mkSource("nice-b", defaultStorageRequestHandler), mkSource("nice-c", defaultStorageRequestHandler), @@ -1281,7 +1373,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 +1382,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 +1397,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,7 +1412,46 @@ 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) +} + +// TestSyncWithUnevenStorage tests sync where the storage trie is not even +// and with a few empty ranges. +func TestSyncWithUnevenStorage(t *testing.T) { + t.Parallel() + + testSyncWithUnevenStorage(t, rawdb.HashScheme) + testSyncWithUnevenStorage(t, rawdb.PathScheme) +} + +func testSyncWithUnevenStorage(t *testing.T, scheme string) { + var ( + once sync.Once + cancel = make(chan struct{}) + term = func() { + once.Do(func() { + close(cancel) + }) + } + ) + accountTrie, accounts, storageTries, storageElems := makeAccountTrieWithStorage(scheme, 3, 256, false, false, true) + + mkSource := func(name string) *testPeer { + source := newTestPeer(name, t, term) + source.accountTrie = accountTrie.Copy() + source.accountValues = accounts + source.setStorageTries(storageTries) + source.storageValues = storageElems + source.storageRequestHandler = func(t *testPeer, reqId uint64, root common.Hash, accounts []common.Hash, origin, limit []byte, max uint64) error { + return defaultStorageRequestHandler(t, reqId, root, accounts, origin, limit, 128) // retrieve storage in large mode + } + return source + } + syncer := setupSyncer(scheme, mkSource("source")) + if err := syncer.Sync(accountTrie.Hash(), cancel); err != nil { + t.Fatalf("sync failed: %v", err) + } + verifyTrie(scheme, syncer.db, accountTrie.Hash(), t) } type kv struct { @@ -1364,9 +1501,9 @@ func getCodeByHash(hash common.Hash) []byte { } // makeAccountTrieNoStorage spits out a trie, along with the leafs -func makeAccountTrieNoStorage(n int) (string, *trie.Trie, []*kv) { +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 []*kv ) @@ -1396,12 +1533,12 @@ func makeAccountTrieNoStorage(n int) (string, *trie.Trie, []*kv) { // 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, []*kv) { +func makeBoundaryAccountTrie(scheme string, n int) (string, *trie.Trie, []*kv) { var ( entries []*kv boundaries []common.Hash - db = trie.NewDatabase(rawdb.NewMemoryDatabase()) + db = trie.NewDatabase(rawdb.NewMemoryDatabase(), newDbConfig(scheme)) accTrie = trie.NewEmpty(db) ) // Initialize boundaries @@ -1415,7 +1552,7 @@ func makeBoundaryAccountTrie(n int) (string, *trie.Trie, []*kv) { for i := 0; i < accountConcurrency; i++ { last := common.BigToHash(new(big.Int).Add(next.Big(), step)) if i == accountConcurrency-1 { - last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + last = common.MaxHash } boundaries = append(boundaries, last) next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) @@ -1457,9 +1594,9 @@ func makeBoundaryAccountTrie(n int) (string, *trie.Trie, []*kv) { // makeAccountTrieWithStorageWithUniqueStorage creates an account trie where each accounts // has a unique storage set. -func makeAccountTrieWithStorageWithUniqueStorage(accounts, slots int, code bool) (string, *trie.Trie, []*kv, map[common.Hash]*trie.Trie, map[common.Hash][]*kv) { +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 []*kv storageRoots = make(map[common.Hash]common.Hash) @@ -1512,9 +1649,9 @@ 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, []*kv, map[common.Hash]*trie.Trie, map[common.Hash][]*kv) { +func makeAccountTrieWithStorage(scheme string, accounts, slots int, code, boundary bool, uneven bool) (*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 []*kv storageRoots = make(map[common.Hash]common.Hash) @@ -1537,6 +1674,8 @@ func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (strin ) if boundary { stRoot, stNodes, stEntries = makeBoundaryStorageTrie(common.BytesToHash(key), slots, db) + } else if uneven { + stRoot, stNodes, stEntries = makeUnevenStorageTrie(common.BytesToHash(key), slots, db) } else { stRoot, stNodes, stEntries = makeStorageTrieWithSeed(common.BytesToHash(key), uint64(slots), 0, db) } @@ -1579,7 +1718,7 @@ func makeAccountTrieWithStorage(accounts, slots int, code, boundary bool) (strin } storageTries[common.BytesToHash(key)] = trie } - return db.Scheme(), accTrie, entries, storageTries, storageEntries + return accTrie, entries, storageTries, storageEntries } // makeStorageTrieWithSeed fills a storage trie with n items, returning the @@ -1625,7 +1764,7 @@ func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (commo for i := 0; i < accountConcurrency; i++ { last := common.BigToHash(new(big.Int).Add(next.Big(), step)) if i == accountConcurrency-1 { - last = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + last = common.MaxHash } boundaries = append(boundaries, last) next = common.BigToHash(new(big.Int).Add(last.Big(), common.Big1)) @@ -1656,9 +1795,41 @@ func makeBoundaryStorageTrie(owner common.Hash, n int, db *trie.Database) (commo return root, nodes, entries } -func verifyTrie(db ethdb.KeyValueStore, root common.Hash, t *testing.T) { +// makeUnevenStorageTrie constructs a storage tries will states distributed in +// different range unevenly. +func makeUnevenStorageTrie(owner common.Hash, slots int, db *trie.Database) (common.Hash, *trienode.NodeSet, []*kv) { + var ( + entries []*kv + tr, _ = trie.New(trie.StorageTrieID(types.EmptyRootHash, owner, types.EmptyRootHash), db) + chosen = make(map[byte]struct{}) + ) + for i := 0; i < 3; i++ { + var n int + for { + n = mrand.Intn(15) // the last range is set empty deliberately + if _, ok := chosen[byte(n)]; ok { + continue + } + chosen[byte(n)] = struct{}{} + break + } + for j := 0; j < slots/3; j++ { + key := append([]byte{byte(n)}, testutil.RandBytes(31)...) + val, _ := rlp.EncodeToBytes(testutil.RandBytes(32)) + + elem := &kv{key, val} + tr.MustUpdate(elem.k, elem.v) + entries = append(entries, elem) + } + } + slices.SortFunc(entries, (*kv).cmp) + root, nodes, _ := tr.Commit(false) + return root, nodes, entries +} + +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) @@ -1700,6 +1871,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) @@ -1714,7 +1892,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) @@ -1727,7 +1905,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, @@ -1787,3 +1965,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 a725585e7..24694df66 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 } } // 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 @@ -175,17 +161,58 @@ 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) } // stateAtTransaction returns the execution environment of a certain transaction. @@ -201,7 +228,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 ba7a7427a..c7ba7c93d 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -197,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 } @@ -242,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 b5e00298b..d26cbb66e 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 740a38ab9..7c0028601 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -18,7 +18,6 @@ package tracers import ( "bufio" - "bytes" "context" "encoding/json" "errors" @@ -165,6 +164,7 @@ type TraceCallConfig struct { TraceConfig StateOverrides *ethapi.StateOverride BlockOverrides *ethapi.BlockOverrides + TxIndex *hexutil.Uint } // StdTraceConfig holds extra parameters to standard-json trace functions. @@ -369,8 +369,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 +453,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) @@ -864,11 +864,17 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * // TraceCall lets you trace a given eth_call. It collects the structured logs // created during the execution of EVM if the given transaction was added on // top of the provided block and returns them as a JSON object. +// If no transaction index is specified, the trace will be conducted on the state +// after executing the specified block. However, if a transaction index is provided, +// the trace will be conducted on the state after executing the specified transaction +// within the specified block. func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) { // Try to retrieve the specified block var ( - err error - block *types.Block + err error + block *types.Block + statedb *state.StateDB + release StateReleaseFunc ) if hash, ok := blockNrOrHash.Hash(); ok { block, err = api.blockByHash(ctx, hash) @@ -893,7 +899,12 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, release, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true, false) + + if config != nil && config.TxIndex != nil { + _, _, statedb, release, err = api.backend.StateAtTransaction(ctx, block, int(*config.TxIndex), reexec) + } else { + statedb, release, err = api.backend.StateAtBlock(ctx, block, reexec, nil, true, false) + } if err != nil { return nil, err } diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 0f78af9a0..49c3ebb67 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -200,13 +200,51 @@ func TestTraceCall(t *testing.T) { } genBlocks := 10 signer := types.HomesteadSigner{} + nonce := uint64(0) backend := newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { // Transfer from account[0] to account[1] // value: 1000 wei // fee: 0 wei - tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: nonce, + To: &accounts[1].addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil}), + signer, accounts[0].key) b.AddTx(tx) + nonce++ + + if i == genBlocks-2 { + // Transfer from account[0] to account[2] + tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: nonce, + To: &accounts[2].addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil}), + signer, accounts[0].key) + b.AddTx(tx) + nonce++ + + // Transfer from account[0] to account[1] again + tx, _ = types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: nonce, + To: &accounts[1].addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil}), + signer, accounts[0].key) + b.AddTx(tx) + nonce++ + } }) + + uintPtr := func(i int) *hexutil.Uint { x := hexutil.Uint(i); return &x } + defer backend.teardown() api := NewAPI(backend) var testSuite = []struct { @@ -240,6 +278,51 @@ func TestTraceCall(t *testing.T) { expectErr: nil, expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, }, + // Upon the last state, default to the post block's state + { + blockNumber: rpc.BlockNumber(genBlocks - 1), + call: ethapi.TransactionArgs{ + From: &accounts[2].addr, + To: &accounts[0].addr, + Value: (*hexutil.Big)(new(big.Int).Add(big.NewInt(params.Ether), big.NewInt(100))), + }, + config: nil, + expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, + }, + // Before the first transaction, should be failed + { + blockNumber: rpc.BlockNumber(genBlocks - 1), + call: ethapi.TransactionArgs{ + From: &accounts[2].addr, + To: &accounts[0].addr, + Value: (*hexutil.Big)(new(big.Int).Add(big.NewInt(params.Ether), big.NewInt(100))), + }, + config: &TraceCallConfig{TxIndex: uintPtr(0)}, + expectErr: fmt.Errorf("tracing failed: insufficient funds for gas * price + value: address %s have 1000000000000000000 want 1000000000000000100", accounts[2].addr), + }, + // Before the target transaction, should be failed + { + blockNumber: rpc.BlockNumber(genBlocks - 1), + call: ethapi.TransactionArgs{ + From: &accounts[2].addr, + To: &accounts[0].addr, + Value: (*hexutil.Big)(new(big.Int).Add(big.NewInt(params.Ether), big.NewInt(100))), + }, + config: &TraceCallConfig{TxIndex: uintPtr(1)}, + expectErr: fmt.Errorf("tracing failed: insufficient funds for gas * price + value: address %s have 1000000000000000000 want 1000000000000000100", accounts[2].addr), + }, + // After the target transaction, should be succeed + { + blockNumber: rpc.BlockNumber(genBlocks - 1), + call: ethapi.TransactionArgs{ + From: &accounts[2].addr, + To: &accounts[0].addr, + Value: (*hexutil.Big)(new(big.Int).Add(big.NewInt(params.Ether), big.NewInt(100))), + }, + config: &TraceCallConfig{TxIndex: uintPtr(2)}, + expectErr: nil, + expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, + }, // Standard JSON trace upon the non-existent block, error expects { blockNumber: rpc.BlockNumber(genBlocks + 1), @@ -297,8 +380,8 @@ func TestTraceCall(t *testing.T) { t.Errorf("test %d: expect error %v, got nothing", i, testspec.expectErr) continue } - if !reflect.DeepEqual(err, testspec.expectErr) { - t.Errorf("test %d: error mismatch, want %v, git %v", i, testspec.expectErr, err) + if !reflect.DeepEqual(err.Error(), testspec.expectErr.Error()) { + t.Errorf("test %d: error mismatch, want '%v', got '%v'", i, testspec.expectErr, err) } } else { if err != nil { @@ -338,7 +421,14 @@ func TestTraceTransaction(t *testing.T) { // Transfer from account[0] to account[1] // value: 1000 wei // fee: 0 wei - tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &accounts[1].addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil}), + signer, accounts[0].key) b.AddTx(tx) target = tx.Hash() }) @@ -388,7 +478,14 @@ func TestTraceBlock(t *testing.T) { // Transfer from account[0] to account[1] // value: 1000 wei // fee: 0 wei - tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &accounts[1].addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil}), + signer, accounts[0].key) b.AddTx(tx) txHash = tx.Hash() }) @@ -478,7 +575,14 @@ func TestTracingWithOverrides(t *testing.T) { // Transfer from account[0] to account[1] // value: 1000 wei // fee: 0 wei - tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &accounts[1].addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil}), + signer, accounts[0].key) b.AddTx(tx) }) defer backend.chain.Stop() diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 26c4455e3..5c74baacd 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -47,9 +47,10 @@ type callContext struct { // callLog is the result of LOG opCode type callLog struct { - Address common.Address `json:"address"` - Topics []common.Hash `json:"topics"` - Data hexutil.Bytes `json:"data"` + Address common.Address `json:"address"` + Topics []common.Hash `json:"topics"` + Data hexutil.Bytes `json:"data"` + Position hexutil.Uint `json:"position"` } // callTrace is the result of a callTracer run. @@ -137,8 +138,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) @@ -237,7 +240,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() @@ -321,7 +325,7 @@ func TestInternals(t *testing.T) { byte(vm.LOG0), }, tracer: mkTracer("callTracer", json.RawMessage(`{ "withLog": true }`)), - want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}],"value":"0x0","type":"CALL"}`, + want: `{"from":"0x000000000000000000000000000000000000feed","gas":"0x13880","gasUsed":"0x5b9e","to":"0x00000000000000000000000000000000deadbeef","input":"0x","logs":[{"address":"0x00000000000000000000000000000000deadbeef","topics":[],"data":"0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","position":"0x0"}],"value":"0x0","type":"CALL"}`, }, { // Leads to OOM on the prestate tracer @@ -363,7 +367,7 @@ func TestInternals(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), + triedb, _, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), core.GenesisAlloc{ to: core.GenesisAccount{ Code: tc.code, @@ -371,7 +375,9 @@ func TestInternals(t *testing.T) { origin: core.GenesisAccount{ Balance: big.NewInt(500000000000000), }, - }, false) + }, false, rawdb.HashScheme) + defer triedb.Close() + evm := vm.NewEVM(context, txContext, statedb, params.MainnetChainConfig, vm.Config{Tracer: tc.tracer}) msg := &core.Message{ To: &to, diff --git a/eth/tracers/internal/tracetest/flat_calltrace_test.go b/eth/tracers/internal/tracetest/flat_calltrace_test.go index 85e95401a..423167b13 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 991da10b3..b4fa5b627 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/internal/tracetest/testdata/call_tracer_withLog/calldata.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/calldata.json index 9264f1e2f..dbece7229 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/calldata.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/calldata.json @@ -95,14 +95,16 @@ "topics": [ "0xe1c52dc63b719ade82e8bea94cc41a0d5d28e4aaf536adb5e9cccc9ff8c1aeda" ], - "data": "0x0000000000000000000000004f5777744b500616697cb655dcb02ee6cd51deb5be96016bb57376da7a6d296e0a405ee1501778227dfa604df0a81cb1ae018598" + "data": "0x0000000000000000000000004f5777744b500616697cb655dcb02ee6cd51deb5be96016bb57376da7a6d296e0a405ee1501778227dfa604df0a81cb1ae018598", + "position": "0x0" }, { "address": "0x200edd17f30485a8735878661960cd7a9a95733f", "topics": [ "0xacbdb084c721332ac59f9b8e392196c9eb0e4932862da8eb9beaf0dad4f550da" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000000" + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "position": "0x0" } ], "value": "0x8ac7230489e80000", diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/delegatecall.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/delegatecall.json index f63dbd47d..2b03dbb8d 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/delegatecall.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/delegatecall.json @@ -257,7 +257,8 @@ "0x0000000000000000000000003de712784baf97260455ae25fb74f574ec9c1add", "0x0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5" ], - "data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac" + "data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac", + "position": "0x0" } ], "value": "0x0", @@ -278,7 +279,8 @@ "0x0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", "0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd" ], - "data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac" + "data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac", + "position": "0x0" } ], "value": "0x0", @@ -307,7 +309,8 @@ "0x0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", "0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd" ], - "data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac" + "data": "0x00000000000000000000000000000000000000000000000080d29fa5cccfadac", + "position": "0x0" } ], "value": "0x0", @@ -328,7 +331,8 @@ "0x0000000000000000000000005aae5c59d642e5fd45b427df6ed478b49d55fefd", "0x000000000000000000000000950ca4a06c78934a148b7a3ff3ea8fc366f77a06" ], - "data": "0x0000000000000000000000000000000000000000000000000041f50e27d56848" + "data": "0x0000000000000000000000000000000000000000000000000041f50e27d56848", + "position": "0x0" } ], "value": "0x0", @@ -391,7 +395,8 @@ "0x0000000000000000000000006ca7f214ab2ddbb9a8e1a1e2c8550e3164e9dba5", "0x0000000000000000000000003de712784baf97260455ae25fb74f574ec9c1add" ], - "data": "0x000000000000000000000000000000000000000000000000de0b6b3a76400000" + "data": "0x000000000000000000000000000000000000000000000000de0b6b3a76400000", + "position": "0x0" } ], "type": "DELEGATECALL", diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multi_contracts.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multi_contracts.json index 5e5d95386..263e88d6e 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multi_contracts.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multi_contracts.json @@ -357,7 +357,8 @@ "0x000000000000000000000000c0ee9db1a9e07ca63e4ff0d5fb6f86bf68d47b89", "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd" ], - "data": "0x00000000000000000000000000000000000000000001819451f999d617dafa93" + "data": "0x00000000000000000000000000000000000000000001819451f999d617dafa93", + "position": "0x0" } ], "value": "0x0", @@ -370,7 +371,8 @@ "topics": [ "0x69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2" ], - "data": "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd00000000000000000000000000000000000000000001819451f999d617dafa93" + "data": "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd00000000000000000000000000000000000000000001819451f999d617dafa93", + "position": "0x1" } ], "value": "0x0", @@ -491,7 +493,8 @@ "0x000000000000000000000000f835a0247b0063c04ef22006ebe57c5f11977cc4", "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd" ], - "data": "0x00000000000000000000000000000000000000000001819451f999d617dafa76" + "data": "0x00000000000000000000000000000000000000000001819451f999d617dafa76", + "position": "0x0" } ], "value": "0x0", @@ -504,7 +507,8 @@ "topics": [ "0x69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2" ], - "data": "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd00000000000000000000000000000000000000000001819451f999d617dafa76" + "data": "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd00000000000000000000000000000000000000000001819451f999d617dafa76", + "position": "0x1" } ], "value": "0x0", @@ -692,7 +696,8 @@ "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", "0x0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc" ], - "data": "0x0000000000000000000000000000000000000000000181a7ae53ea2f0bef8ccd" + "data": "0x0000000000000000000000000000000000000000000181a7ae53ea2f0bef8ccd", + "position": "0x0" } ], "value": "0x0", @@ -874,7 +879,8 @@ "0x0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc", "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd" ], - "data": "0x0000000000000000000000000000000000000000000181a7ae53ea2f0bef8ccc" + "data": "0x0000000000000000000000000000000000000000000181a7ae53ea2f0bef8ccc", + "position": "0x0" } ], "value": "0x0", @@ -892,7 +898,8 @@ "0x9735b0cb909f3d21d5c16bbcccd272d85fa11446f6d679f6ecb170d2dabfecfc", "0x0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc" ], - "data": "0x0000000000000000000000000000000000000000000000022b1c8c12279fffff" + "data": "0x0000000000000000000000000000000000000000000000022b1c8c12279fffff", + "position": "0x1" } ], "value": "0x0", @@ -914,7 +921,8 @@ "0x9735b0cb909f3d21d5c16bbcccd272d85fa11446f6d679f6ecb170d2dabfecfc", "0x0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc" ], - "data": "0x0000000000000000000000000000000000000000000000022b1c8c12279fffff" + "data": "0x0000000000000000000000000000000000000000000000022b1c8c12279fffff", + "position": "0x1" } ], "value": "0x0", @@ -939,7 +947,8 @@ "0x0000000000000000000000006e715ab4f598eacf0016b9b35ef33e4141844ccc", "0x0000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000001" + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "position": "0x0" } ], "value": "0x0", @@ -952,14 +961,16 @@ "topics": [ "0x07cf7e805770612a8b2ee8e0bcbba8aa908df5f85fbc4f9e2ef384cf75315038" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000000" + "data": "0x0000000000000000000000000000000000000000000000000000000000000000", + "position": "0x6" }, { "address": "0x6e715ab4f598eacf0016b9b35ef33e4141844ccc", "topics": [ "0x7027eecbd2a688fc1fa281702b311ed7168571514adfd17014a55d828cb43382" ], - "data": "0x000000000000000000000000000000000000000000000004563918244f400000" + "data": "0x000000000000000000000000000000000000000000000004563918244f400000", + "position": "0x8" } ], "value": "0x0", @@ -1035,7 +1046,8 @@ "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", "0x0000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000063" + "data": "0x0000000000000000000000000000000000000000000000000000000000000063", + "position": "0x0" } ], "value": "0x0", @@ -1162,7 +1174,8 @@ "0x0000000000000000000000006dbfc63479ffc031f23e94dc91befa38bec2c25f", "0x000000000000000000000000da4a4626d3e16e094de3225a751aab7128e96526" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000064" + "data": "0x0000000000000000000000000000000000000000000000000000000000000064", + "position": "0x0" } ], "value": "0x0", @@ -1175,14 +1188,16 @@ "topics": [ "0x4b0bc4f25f8d0b92d2e12b686ba96cd75e4e69325e6cf7b1f3119d14eaf2cbdf" ], - "data": "0x000000000000000000000000da4a4626d3e16e094de3225a751aab7128e96526" + "data": "0x000000000000000000000000da4a4626d3e16e094de3225a751aab7128e96526", + "position": "0x6" }, { "address": "0x6dbfc63479ffc031f23e94dc91befa38bec2c25f", "topics": [ "0xf340c079d598119636d42046c6a2d2faf7a68c04aecee516f0e0b8a9e79b8666" ], - "data": "0x000000000000000000000000da4a4626d3e16e094de3225a751aab7128e9652600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000" + "data": "0x000000000000000000000000da4a4626d3e16e094de3225a751aab7128e9652600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000", + "position": "0x9" } ], "value": "0x0", @@ -1231,7 +1246,8 @@ "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", "0x0000000000000000000000007498bb5749c9801f1f7e490baf5f966dbfe4e97b" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000001" + "data": "0x0000000000000000000000000000000000000000000000000000000000000001", + "position": "0x0" } ], "value": "0x0", @@ -1324,7 +1340,8 @@ "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", "0x0000000000000000000000000000000000000000000000000000000000000001" ], - "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000" + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" } ], "value": "0x0", @@ -1417,7 +1434,8 @@ "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", "0x0000000000000000000000000000000000000000000000000000000000000002" ], - "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000" + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" } ], "value": "0x0", @@ -1510,7 +1528,8 @@ "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", "0x0000000000000000000000000000000000000000000000000000000000000003" ], - "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000" + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" } ], "value": "0x0", @@ -1603,7 +1622,8 @@ "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", "0x0000000000000000000000000000000000000000000000000000000000000004" ], - "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000" + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" } ], "value": "0x0", @@ -1696,7 +1716,8 @@ "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", "0x0000000000000000000000000000000000000000000000000000000000000005" ], - "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000" + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" } ], "value": "0x0", @@ -1789,7 +1810,8 @@ "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", "0x0000000000000000000000000000000000000000000000000000000000000006" ], - "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000" + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" } ], "value": "0x0", @@ -1882,7 +1904,8 @@ "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", "0x0000000000000000000000000000000000000000000000000000000000000007" ], - "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000" + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" } ], "value": "0x0", @@ -1975,7 +1998,8 @@ "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", "0x0000000000000000000000000000000000000000000000000000000000000008" ], - "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000" + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" } ], "value": "0x0", @@ -2068,7 +2092,8 @@ "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", "0x0000000000000000000000000000000000000000000000000000000000000009" ], - "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000" + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" } ], "value": "0x0", @@ -2161,7 +2186,8 @@ "0x5790de2c279e58269b93b12828f56fd5f2bc8ad15e61ce08572585c81a38756f", "0x000000000000000000000000000000000000000000000000000000000000000a" ], - "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000" + "data": "0x000000000000000000000000be3ae5cb97c253dda67181c6e34e43f5c275e08b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000", + "position": "0x2" } ], "value": "0x0", @@ -2213,7 +2239,8 @@ "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", "0x0000000000000000000000007ccbc69292c7a6d7b538c91f3b283de97906cf30" ], - "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c" + "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "position": "0x0" } ], "value": "0x0", @@ -2234,7 +2261,8 @@ "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", "0x0000000000000000000000001b9ec8ba24630b75a7a958153ffff56dd6d4b6a2" ], - "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c" + "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "position": "0x0" } ], "value": "0x0", @@ -2255,7 +2283,8 @@ "0x0000000000000000000000004fd27b205895e698fa350f7ea57cec8a21927fcd", "0x000000000000000000000000c3a2c744ad1f5253c736875b93bacce5b01b060b" ], - "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c" + "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "position": "0x0" } ], "value": "0x0", @@ -2268,21 +2297,24 @@ "topics": [ "0xc6d8c0af6d21f291e7c359603aa97e0ed500f04db6e983b9fce75a91c6b8da6b" ], - "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c" + "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "position": "0x2" }, { "address": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", "topics": [ "0xc6d8c0af6d21f291e7c359603aa97e0ed500f04db6e983b9fce75a91c6b8da6b" ], - "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c" + "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "position": "0x3" }, { "address": "0x4fd27b205895e698fa350f7ea57cec8a21927fcd", "topics": [ "0xc6d8c0af6d21f291e7c359603aa97e0ed500f04db6e983b9fce75a91c6b8da6b" ], - "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c" + "data": "0x00000000000000000000000000000000000000000001010d8bfbbbe40fe7518c", + "position": "0x4" } ], "value": "0x0", diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multilogs.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multilogs.json index 1ffffd240..66d458200 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multilogs.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/multilogs.json @@ -178,350 +178,400 @@ "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefe0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefe0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfd0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfd0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffebebeb0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffebebeb0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8888880000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8888880000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb3b3b30000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb3b3b30000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcfcfc0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcfcfc0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe3e3e30000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe3e3e30000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3e3e3e0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3e3e3e0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdbdbdb0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdbdbdb0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4f4f40000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4f4f40000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfbfb0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034100000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfbfb0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002ff000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x0000000000000000000000000000000000000000000000000000000000000341000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0b0b00000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb0b0b00000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefe0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefe0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a0a00000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0a0a00000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b5b5b0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b5b5b0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbababa0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbababa0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeaeaea0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeaeaea0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa9a9a90000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa9a9a90000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb9b9b90000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffb9b9b90000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfbfb0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbfbfb0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefe0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefe0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefe0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefefe0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbababa0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbababa0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6363630000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6363630000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9f9f90000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9f9f90000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeaeaea0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeaeaea0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c9c9c0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c9c9c0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8f8f80000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8f8f80000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfd0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfd0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcfcfc0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034200000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffcfcfc0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfd0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fe000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdfdfd0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x0000000000000000000000000000000000000000000000000000000000000342000000000000000000000000000000000000000000000000000000000000003e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fd00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4d4e530000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fd00000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4d4e530000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x000000000000000000000000000000000000000000000000000000000000034300000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x000000000000000000000000000000000000000000000000000000000000034300000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" }, { "address": "0x350e0ffc780a6a75b44cc52e1ff9092870668945", "topics": [ "0xcacb62d8acea4678658eb5dc4aaa889b34d893b967c96a5f8c066e6549fa3f42" ], - "data": "0x00000000000000000000000000000000000000000000000000000000000002fd00000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4f494b0000000000000000000000000000000000000000000000000011c37937e08000" + "data": "0x00000000000000000000000000000000000000000000000000000000000002fd00000000000000000000000000000000000000000000000000000000000000370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fcb0342353c541e210013aaddc2e740b9a33d08ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4f494b0000000000000000000000000000000000000000000000000011c37937e08000", + "position": "0x0" } ], "value": "0x3782dace9d90000", diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/notopic.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/notopic.json index 116606b3c..762ccbe58 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/notopic.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/notopic.json @@ -266,7 +266,8 @@ "topics": [ "0xaf30e4d66b2f1f23e63ef4591058a897f67e6867233e33ca3508b982dcc4129b" ], - "data": "0x00000000000000000000000050739060a2c32dc076e507ae1a893aab28ecfe68d1b13c1538a940417bf0e73b2498634436753c854c7fb971224d971bd2ae3e8800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000249f011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000355524c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000436a736f6e2868747470733a2f2f6170692e72616e646f6d2e6f72672f6a736f6e2d7270632f312f696e766f6b65292e726573756c742e72616e646f6d2e646174612e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012c4244584a68725670424a35336f3243786c4a526c51745a4a4b5a714c5974354951652b37335944533448744e6a5335486f64624942337476666f773755717579416b303835566b4c6e4c3945704b67777157517a375a4c64477673516c526432734b78496f6c4e673944626e6650737047714c684c62625953566e4e38437776736a7041586353536f33632b34634e774339307946346f4e69626b764433797461706f5a37676f5453796f5559546677536a6e773374692b484a5648374e332b633069774f43715a6a4464734751556358336d33532f494857624f4f5151356f734f344c626a33476730783155644e7466557a5943465937396e7a596757495145464375524249306e364e42764251573732372b4f73445259304a2f392f676a74387563696248576963303d0000000000000000000000000000000000000000" + "data": "0x00000000000000000000000050739060a2c32dc076e507ae1a893aab28ecfe68d1b13c1538a940417bf0e73b2498634436753c854c7fb971224d971bd2ae3e8800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000249f011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000355524c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000436a736f6e2868747470733a2f2f6170692e72616e646f6d2e6f72672f6a736f6e2d7270632f312f696e766f6b65292e726573756c742e72616e646f6d2e646174612e300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012c4244584a68725670424a35336f3243786c4a526c51745a4a4b5a714c5974354951652b37335944533448744e6a5335486f64624942337476666f773755717579416b303835566b4c6e4c3945704b67777157517a375a4c64477673516c526432734b78496f6c4e673944626e6650737047714c684c62625953566e4e38437776736a7041586353536f33632b34634e774339307946346f4e69626b764433797461706f5a37676f5453796f5559546677536a6e773374692b484a5648374e332b633069774f43715a6a4464734751556358336d33532f494857624f4f5151356f734f344c626a33476730783155644e7466557a5943465937396e7a596757495145464375524249306e364e42764251573732372b4f73445259304a2f392f676a74387563696248576963303d0000000000000000000000000000000000000000", + "position": "0x4" } ], "value": "0x179d63013c5654", @@ -277,7 +278,8 @@ { "address": "0x50739060a2c32dc076e507ae1a893aab28ecfe68", "topics": [], - "data": "0x62616e6b726f6c6c5f6d69736d61746368" + "data": "0x62616e6b726f6c6c5f6d69736d61746368", + "position": "0x2" } ], "value": "0x429d069189e0000", diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/simple.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/simple.json index 30f177706..64941dd4d 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/simple.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/simple.json @@ -75,7 +75,8 @@ "0x000000000000000000000000d1220a0cf47c7b9be7a2e6ba89f429762e7b9adb", "0x000000000000000000000000dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb" ], - "data": "0x0000000000000000000000000000000000000000000000000000000000989680" + "data": "0x0000000000000000000000000000000000000000000000000000000000989680", + "position": "0x0" } ], "value": "0x0", diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_partial_failed.json b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_partial_failed.json index eb2514427..6faf898a0 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_partial_failed.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer_withLog/tx_partial_failed.json @@ -98,7 +98,8 @@ "topics": [ "0x92ca3a80853e6663fa31fa10b99225f18d4902939b4c53a9caae9043f6efd004" ], - "data": "0x00000000000000000000000001115b41bd2731353dd3e6abf44818fdc035aaf10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bb9bc244d798123fde783fcc1c72d3bb8c1894130000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008861393035396362623030303030303030303030303030303030303030303030303930643363313831326465323636396266383037626437373538636562316533343937616337653430303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303031633662663532363334303030" + "data": "0x00000000000000000000000001115b41bd2731353dd3e6abf44818fdc035aaf10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bb9bc244d798123fde783fcc1c72d3bb8c1894130000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000008861393035396362623030303030303030303030303030303030303030303030303930643363313831326465323636396266383037626437373538636562316533343937616337653430303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303031633662663532363334303030", + "position": "0x0" } ], "value": "0x0", diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index f3d63df8e..07c138bae 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -142,19 +142,29 @@ func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracer vm: vm, ctx: make(map[string]goja.Value), } + + t.setTypeConverters() + t.setBuiltinFunctions() + if ctx == nil { ctx = new(tracers.Context) } if ctx.BlockHash != (common.Hash{}) { - t.ctx["blockHash"] = vm.ToValue(ctx.BlockHash.Bytes()) + blockHash, err := t.toBuf(vm, ctx.BlockHash.Bytes()) + if err != nil { + return nil, err + } + t.ctx["blockHash"] = blockHash if ctx.TxHash != (common.Hash{}) { t.ctx["txIndex"] = vm.ToValue(ctx.TxIndex) - t.ctx["txHash"] = vm.ToValue(ctx.TxHash.Bytes()) + txHash, err := t.toBuf(vm, ctx.TxHash.Bytes()) + if err != nil { + return nil, err + } + t.ctx["txHash"] = txHash } } - t.setTypeConverters() - t.setBuiltinFunctions() ret, err := vm.RunString("(" + code + ")") if err != nil { return nil, err @@ -224,6 +234,10 @@ func (t *jsTracer) CaptureTxEnd(restGas uint64) { // CaptureStart implements the Tracer interface to initialize the tracing operation. func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + cancel := func(err error) { + t.err = err + t.env.Cancel() + } t.env = env db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf} t.dbValue = db.setupObject() @@ -232,14 +246,34 @@ func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr } else { t.ctx["type"] = t.vm.ToValue("CALL") } - t.ctx["from"] = t.vm.ToValue(from.Bytes()) - t.ctx["to"] = t.vm.ToValue(to.Bytes()) - t.ctx["input"] = t.vm.ToValue(input) + fromVal, err := t.toBuf(t.vm, from.Bytes()) + if err != nil { + cancel(err) + return + } + t.ctx["from"] = fromVal + toVal, err := t.toBuf(t.vm, to.Bytes()) + if err != nil { + cancel(err) + return + } + t.ctx["to"] = toVal + inputVal, err := t.toBuf(t.vm, input) + if err != nil { + cancel(err) + return + } + t.ctx["input"] = inputVal 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 { + cancel(err) + return + } + t.ctx["gasPrice"] = gasPriceBig valueBig, err := t.toBig(t.vm, value.String()) if err != nil { - t.err = err + cancel(err) return } t.ctx["value"] = valueBig @@ -288,10 +322,15 @@ func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope // CaptureEnd is called after the call finishes to finalize the tracing. func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { - t.ctx["output"] = t.vm.ToValue(output) if err != nil { t.ctx["error"] = t.vm.ToValue(err.Error()) } + outputVal, err := t.toBuf(t.vm, output) + if err != nil { + t.err = err + return + } + t.ctx["output"] = outputVal } // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). @@ -460,13 +499,13 @@ func (t *jsTracer) setBuiltinFunctions() { } return false }) - vm.Set("slice", func(slice goja.Value, start, end int) goja.Value { + vm.Set("slice", func(slice goja.Value, start, end int64) goja.Value { b, err := t.fromBuf(vm, slice, false) if err != nil { vm.Interrupt(err) return nil } - if start < 0 || start > end || end > len(b) { + if start < 0 || start > end || end > int64(len(b)) { vm.Interrupt(fmt.Sprintf("Tracer accessed out of bound memory: available %d, offset %d, size %d", len(b), start, end-start)) return nil } diff --git a/eth/tracers/logger/gen_structlog.go b/eth/tracers/logger/gen_structlog.go index df06a9ee6..b406cb344 100644 --- a/eth/tracers/logger/gen_structlog.go +++ b/eth/tracers/logger/gen_structlog.go @@ -23,7 +23,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) { GasCost math.HexOrDecimal64 `json:"gasCost"` Memory hexutil.Bytes `json:"memory,omitempty"` MemorySize int `json:"memSize"` - Stack []uint256.Int `json:"stack"` + Stack []hexutil.U256 `json:"stack"` ReturnData hexutil.Bytes `json:"returnData,omitempty"` Storage map[common.Hash]common.Hash `json:"-"` Depth int `json:"depth"` @@ -39,7 +39,12 @@ func (s StructLog) MarshalJSON() ([]byte, error) { enc.GasCost = math.HexOrDecimal64(s.GasCost) enc.Memory = s.Memory enc.MemorySize = s.MemorySize - enc.Stack = s.Stack + if s.Stack != nil { + enc.Stack = make([]hexutil.U256, len(s.Stack)) + for k, v := range s.Stack { + enc.Stack[k] = hexutil.U256(v) + } + } enc.ReturnData = s.ReturnData enc.Storage = s.Storage enc.Depth = s.Depth @@ -59,7 +64,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { GasCost *math.HexOrDecimal64 `json:"gasCost"` Memory *hexutil.Bytes `json:"memory,omitempty"` MemorySize *int `json:"memSize"` - Stack []uint256.Int `json:"stack"` + Stack []hexutil.U256 `json:"stack"` ReturnData *hexutil.Bytes `json:"returnData,omitempty"` Storage map[common.Hash]common.Hash `json:"-"` Depth *int `json:"depth"` @@ -89,7 +94,10 @@ func (s *StructLog) UnmarshalJSON(input []byte) error { s.MemorySize = *dec.MemorySize } if dec.Stack != nil { - s.Stack = dec.Stack + s.Stack = make([]uint256.Int, len(dec.Stack)) + for k, v := range dec.Stack { + s.Stack[k] = uint256.Int(v) + } } if dec.ReturnData != nil { s.ReturnData = *dec.ReturnData diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index 4c9b910a2..2b36f9f49 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -83,6 +83,7 @@ type structLogMarshaling struct { GasCost math.HexOrDecimal64 Memory hexutil.Bytes ReturnData hexutil.Bytes + Stack []hexutil.U256 OpName string `json:"opName"` // adds call to OpName() in MarshalJSON ErrorString string `json:"error,omitempty"` // adds call to ErrorString() in MarshalJSON } diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index 34cf027ac..f85cf6206 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -40,6 +40,9 @@ type callLog struct { Address common.Address `json:"address"` Topics []common.Hash `json:"topics"` Data hexutil.Bytes `json:"data"` + // Position of the log relative to subcalls within the same trace + // See https://github.com/ethereum/go-ethereum/pull/28389 for details + Position hexutil.Uint `json:"position"` } type callFrame struct { @@ -188,7 +191,12 @@ func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco return } - log := callLog{Address: scope.Contract.Address(), Topics: topics, Data: hexutil.Bytes(data)} + log := callLog{ + Address: scope.Contract.Address(), + Topics: topics, + Data: hexutil.Bytes(data), + Position: hexutil.Uint(len(t.callstack[len(t.callstack)-1].Calls)), + } t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, log) } } diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 759e3a4dd..b4989ec98 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 4508027fa..900335988 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"` @@ -297,10 +307,8 @@ func (ec *Client) TransactionInBlock(ctx context.Context, blockHash common.Hash, func (ec *Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { var r *types.Receipt err := ec.c.CallContext(ctx, &r, "eth_getTransactionReceipt", txHash) - if err == nil { - if r == nil { - return nil, ethereum.NotFound - } + if err == nil && r == nil { + return nil, ethereum.NotFound } return r, err } @@ -360,6 +368,13 @@ func (ec *Client) BalanceAt(ctx context.Context, account common.Address, blockNu return (*big.Int)(&result), err } +// BalanceAtHash returns the wei balance of the given account. +func (ec *Client) BalanceAtHash(ctx context.Context, account common.Address, blockHash common.Hash) (*big.Int, error) { + var result hexutil.Big + err := ec.c.CallContext(ctx, &result, "eth_getBalance", account, rpc.BlockNumberOrHashWithHash(blockHash, false)) + return (*big.Int)(&result), err +} + // StorageAt returns the value of key in the contract storage of the given account. // The block number can be nil, in which case the value is taken from the latest known block. func (ec *Client) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { @@ -368,6 +383,13 @@ func (ec *Client) StorageAt(ctx context.Context, account common.Address, key com return result, err } +// StorageAtHash returns the value of key in the contract storage of the given account. +func (ec *Client) StorageAtHash(ctx context.Context, account common.Address, key common.Hash, blockHash common.Hash) ([]byte, error) { + var result hexutil.Bytes + err := ec.c.CallContext(ctx, &result, "eth_getStorageAt", account, key, rpc.BlockNumberOrHashWithHash(blockHash, false)) + return result, err +} + // CodeAt returns the contract code of the given account. // The block number can be nil, in which case the code is taken from the latest known block. func (ec *Client) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { @@ -376,6 +398,13 @@ func (ec *Client) CodeAt(ctx context.Context, account common.Address, blockNumbe return result, err } +// CodeAtHash returns the contract code of the given account. +func (ec *Client) CodeAtHash(ctx context.Context, account common.Address, blockHash common.Hash) ([]byte, error) { + var result hexutil.Bytes + err := ec.c.CallContext(ctx, &result, "eth_getCode", account, rpc.BlockNumberOrHashWithHash(blockHash, false)) + return result, err +} + // NonceAt returns the account nonce of the given account. // The block number can be nil, in which case the nonce is taken from the latest known block. func (ec *Client) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) { @@ -384,6 +413,13 @@ func (ec *Client) NonceAt(ctx context.Context, account common.Address, blockNumb return uint64(result), err } +// NonceAtHash returns the account nonce of the given account. +func (ec *Client) NonceAtHash(ctx context.Context, account common.Address, blockHash common.Hash) (uint64, error) { + var result hexutil.Uint64 + err := ec.c.CallContext(ctx, &result, "eth_getTransactionCount", account, rpc.BlockNumberOrHashWithHash(blockHash, false)) + return uint64(result), err +} + // Filters // FilterLogs executes a filter query. @@ -609,7 +645,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) @@ -620,6 +656,12 @@ func toCallArg(msg ethereum.CallMsg) interface{} { if msg.GasPrice != nil { arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) } + if msg.GasFeeCap != nil { + arg["maxFeePerGas"] = (*hexutil.Big)(msg.GasFeeCap) + } + if msg.GasTipCap != nil { + arg["maxPriorityFeePerGas"] = (*hexutil.Big)(msg.GasTipCap) + } return arg } diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index d2f371093..0f87ad5f5 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -393,6 +393,7 @@ func testTransactionInBlockInterrupted(t *testing.T, client *rpc.Client) { // Test tx in block interrupted. ctx, cancel := context.WithCancel(context.Background()) cancel() + <-ctx.Done() // Ensure the close of the Done channel tx, err := ec.TransactionInBlock(ctx, block.Hash(), 0) if tx != nil { t.Fatal("transaction should be nil") @@ -480,7 +481,7 @@ func testStatusFunctions(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } - if networkID.Cmp(big.NewInt(0)) != 0 { + if networkID.Cmp(big.NewInt(1337)) != 0 { t.Fatalf("unexpected networkID: %v", networkID) } @@ -583,6 +584,11 @@ func testCallContract(t *testing.T, client *rpc.Client) { func testAtFunctions(t *testing.T, client *rpc.Client) { ec := NewClient(client) + block, err := ec.HeaderByNumber(context.Background(), big.NewInt(1)) + if err != nil { + t.Fatalf("BlockByNumber error: %v", err) + } + // send a transaction for some interesting pending status sendTransaction(ec) time.Sleep(100 * time.Millisecond) @@ -600,6 +606,13 @@ func testAtFunctions(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } + hashBalance, err := ec.BalanceAtHash(context.Background(), testAddr, block.Hash()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if balance.Cmp(hashBalance) == 0 { + t.Fatalf("unexpected balance at hash: %v %v", balance, hashBalance) + } penBalance, err := ec.PendingBalanceAt(context.Background(), testAddr) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -612,6 +625,13 @@ func testAtFunctions(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } + hashNonce, err := ec.NonceAtHash(context.Background(), testAddr, block.Hash()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if hashNonce == nonce { + t.Fatalf("unexpected nonce at hash: %v %v", nonce, hashNonce) + } penNonce, err := ec.PendingNonceAt(context.Background(), testAddr) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -624,6 +644,13 @@ func testAtFunctions(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } + hashStorage, err := ec.StorageAtHash(context.Background(), testAddr, common.Hash{}, block.Hash()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(storage, hashStorage) { + t.Fatalf("unexpected storage at hash: %v %v", storage, hashStorage) + } penStorage, err := ec.PendingStorageAt(context.Background(), testAddr, common.Hash{}) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -636,6 +663,13 @@ func testAtFunctions(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } + hashCode, err := ec.CodeAtHash(context.Background(), common.Address{}, block.Hash()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(code, hashCode) { + t.Fatalf("unexpected code at hash: %v %v", code, hashCode) + } penCode, err := ec.PendingCodeAt(context.Background(), testAddr) if err != nil { t.Fatalf("unexpected error: %v", err) @@ -666,6 +700,7 @@ func testTransactionSender(t *testing.T, client *rpc.Client) { // TransactionSender. Ensure the server is not asked by canceling the context here. canceledCtx, cancel := context.WithCancel(context.Background()) cancel() + <-canceledCtx.Done() // Ensure the close of the Done channel sender1, err := ec.TransactionSender(canceledCtx, tx1, block2.Hash(), 0) if err != nil { t.Fatal(err) diff --git a/ethclient/gethclient/gethclient.go b/ethclient/gethclient/gethclient.go index c02961167..e2c0ef3ed 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 5a0f4d253..fdd94a7d7 100644 --- a/ethclient/gethclient/gethclient_test.go +++ b/ethclient/gethclient/gethclient_test.go @@ -39,11 +39,13 @@ import ( ) var ( - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testAddr = crypto.PubkeyToAddress(testKey.PublicKey) - testSlot = common.HexToHash("0xdeadbeef") - testValue = crypto.Keccak256Hash(testSlot[:]) - testBalance = big.NewInt(2e15) + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) + testContract = common.HexToAddress("0xbeef") + testEmpty = common.HexToAddress("0xeeee") + testSlot = common.HexToHash("0xdeadbeef") + testValue = crypto.Keccak256Hash(testSlot[:]) + testBalance = big.NewInt(2e15) ) func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { @@ -78,8 +80,12 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { func generateTestChain() (*core.Genesis, []*types.Block) { genesis := &core.Genesis{ - Config: params.AllEthashProtocolChanges, - Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance, Storage: map[common.Hash]common.Hash{testSlot: testValue}}}, + Config: params.AllEthashProtocolChanges, + Alloc: core.GenesisAlloc{ + testAddr: {Balance: testBalance, Storage: map[common.Hash]common.Hash{testSlot: testValue}}, + testContract: {Nonce: 1, Code: []byte{0x13, 0x37}}, + testEmpty: {Balance: big.NewInt(1)}, + }, ExtraData: []byte("test genesis"), Timestamp: 9000, } @@ -103,8 +109,17 @@ func TestGethClient(t *testing.T) { test func(t *testing.T) }{ { - "TestGetProof", - func(t *testing.T) { testGetProof(t, client) }, + "TestGetProof1", + func(t *testing.T) { testGetProof(t, client, testAddr) }, + }, { + "TestGetProof2", + func(t *testing.T) { testGetProof(t, client, testContract) }, + }, { + "TestGetProofEmpty", + func(t *testing.T) { testGetProof(t, client, testEmpty) }, + }, { + "TestGetProofNonExistent", + func(t *testing.T) { testGetProofNonExistent(t, client) }, }, { "TestGetProofCanonicalizeKeys", func(t *testing.T) { testGetProofCanonicalizeKeys(t, client) }, @@ -201,38 +216,41 @@ func testAccessList(t *testing.T, client *rpc.Client) { } } -func testGetProof(t *testing.T, client *rpc.Client) { +func testGetProof(t *testing.T, client *rpc.Client, addr common.Address) { ec := New(client) ethcl := ethclient.NewClient(client) - result, err := ec.GetProof(context.Background(), testAddr, []string{testSlot.String()}, nil) + result, err := ec.GetProof(context.Background(), addr, []string{testSlot.String()}, nil) if err != nil { t.Fatal(err) } - if !bytes.Equal(result.Address[:], testAddr[:]) { - t.Fatalf("unexpected address, want: %v got: %v", testAddr, result.Address) + if result.Address != addr { + t.Fatalf("unexpected address, have: %v want: %v", result.Address, addr) } // test nonce - nonce, _ := ethcl.NonceAt(context.Background(), result.Address, nil) - if result.Nonce != nonce { + if nonce, _ := ethcl.NonceAt(context.Background(), addr, nil); result.Nonce != nonce { t.Fatalf("invalid nonce, want: %v got: %v", nonce, result.Nonce) } // test balance - balance, _ := ethcl.BalanceAt(context.Background(), result.Address, nil) - if result.Balance.Cmp(balance) != 0 { + if balance, _ := ethcl.BalanceAt(context.Background(), addr, nil); 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)) } - proof := result.StorageProof[0] - slotValue, _ := ethcl.StorageAt(context.Background(), testAddr, testSlot, nil) - if !bytes.Equal(slotValue, proof.Value.Bytes()) { - t.Fatalf("invalid storage proof value, want: %v, got: %v", slotValue, proof.Value.Bytes()) + for _, proof := range result.StorageProof { + if proof.Key != testSlot.String() { + t.Fatalf("invalid storage proof key, want: %q, got: %q", testSlot.String(), proof.Key) + } + slotValue, _ := ethcl.StorageAt(context.Background(), addr, common.HexToHash(proof.Key), nil) + if have, want := common.BigToHash(proof.Value), common.BytesToHash(slotValue); have != want { + t.Fatalf("addr %x, invalid storage proof value: have: %v, want: %v", addr, have, want) + } } - if proof.Key != testSlot.String() { - t.Fatalf("invalid storage proof key, want: %q, got: %q", testSlot.String(), proof.Key) + // test code + code, _ := ethcl.CodeAt(context.Background(), addr, nil) + if have, want := result.CodeHash, crypto.Keccak256Hash(code); have != want { + t.Fatalf("codehash wrong, have %v want %v ", have, want) } } @@ -266,6 +284,38 @@ func testGetProofCanonicalizeKeys(t *testing.T, client *rpc.Client) { } } +func testGetProofNonExistent(t *testing.T, client *rpc.Client) { + addr := common.HexToAddress("0x0001") + ec := New(client) + result, err := ec.GetProof(context.Background(), addr, nil, nil) + if err != nil { + t.Fatal(err) + } + if result.Address != addr { + t.Fatalf("unexpected address, have: %v want: %v", result.Address, addr) + } + // test nonce + if result.Nonce != 0 { + t.Fatalf("invalid nonce, want: %v got: %v", 0, result.Nonce) + } + // test balance + if result.Balance.Cmp(big.NewInt(0)) != 0 { + t.Fatalf("invalid balance, want: %v got: %v", 0, result.Balance) + } + // test storage + if have := len(result.StorageProof); have != 0 { + t.Fatalf("invalid storage proof, want 0 proof, got %v proof(s)", have) + } + // test codeHash + if have, want := result.CodeHash, (common.Hash{}); have != want { + t.Fatalf("codehash wrong, have %v want %v ", have, want) + } + // test codeHash + if have, want := result.StorageHash, (common.Hash{}); have != want { + t.Fatalf("storagehash wrong, have %v want %v ", have, want) + } +} + func testGCStats(t *testing.T, client *rpc.Client) { ec := New(client) _, err := ec.GCStats(context.Background()) @@ -400,7 +450,7 @@ func testCallContract(t *testing.T, client *rpc.Client) { func TestOverrideAccountMarshal(t *testing.T) { om := map[common.Address]OverrideAccount{ {0x11}: { - // Zero-valued nonce is not overriddden, but simply dropped by the encoder. + // Zero-valued nonce is not overridden, but simply dropped by the encoder. Nonce: 0, }, {0xaa}: { diff --git a/ethclient/simulated/backend.go b/ethclient/simulated/backend.go new file mode 100644 index 000000000..54675b6dd --- /dev/null +++ b/ethclient/simulated/backend.go @@ -0,0 +1,190 @@ +// 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 simulated + +import ( + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/eth" + "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" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" +) + +// Backend is a simulated blockchain. You can use it to test your contracts or +// other code that interacts with the Ethereum chain. +type Backend struct { + eth *eth.Ethereum + beacon *catalyst.SimulatedBeacon + client simClient +} + +// simClient wraps ethclient. This exists to prevent extracting ethclient.Client +// from the Client interface returned by Backend. +type simClient struct { + *ethclient.Client +} + +// Client exposes the methods provided by the Ethereum RPC client. +type Client interface { + ethereum.BlockNumberReader + ethereum.ChainReader + ethereum.ChainStateReader + ethereum.ContractCaller + ethereum.GasEstimator + ethereum.GasPricer + ethereum.GasPricer1559 + ethereum.FeeHistoryReader + ethereum.LogFilterer + ethereum.PendingStateReader + ethereum.PendingContractCaller + ethereum.TransactionReader + ethereum.TransactionSender + ethereum.ChainIDReader +} + +// New creates a new binding backend using a simulated blockchain +// for testing purposes. +// A simulated backend always uses chainID 1337. +func New(alloc core.GenesisAlloc, gasLimit uint64) *Backend { + // Setup the node object + nodeConf := node.DefaultConfig + nodeConf.DataDir = "" + nodeConf.P2P = p2p.Config{NoDiscovery: true} + stack, err := node.New(&nodeConf) + if err != nil { + // This should never happen, if it does, please open an issue + panic(err) + } + + // Setup ethereum + genesis := core.Genesis{ + Config: params.AllDevChainProtocolChanges, + GasLimit: gasLimit, + Alloc: alloc, + } + conf := ethconfig.Defaults + conf.Genesis = &genesis + conf.SyncMode = downloader.FullSync + conf.TxPool.NoLocals = true + sim, err := newWithNode(stack, &conf, 0) + if err != nil { + // This should never happen, if it does, please open an issue + panic(err) + } + return sim +} + +// newWithNode sets up a simulated backend on an existing node +// this allows users to do persistent simulations. +// The provided node must not be started and will be started by newWithNode +func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Backend, error) { + backend, err := eth.New(stack, conf) + if err != nil { + return nil, err + } + + // Register the filter system + filterSystem := filters.NewFilterSystem(backend.APIBackend, filters.Config{}) + stack.RegisterAPIs([]rpc.API{{ + Namespace: "eth", + Service: filters.NewFilterAPI(filterSystem, false), + }}) + + // Start the node + if err := stack.Start(); err != nil { + return nil, err + } + + // Set up the simulated beacon + beacon, err := catalyst.NewSimulatedBeacon(blockPeriod, backend) + if err != nil { + return nil, err + } + + // Reorg our chain back to genesis + if err := beacon.Fork(backend.BlockChain().GetCanonicalHash(0)); err != nil { + return nil, err + } + + return &Backend{ + eth: backend, + beacon: beacon, + client: simClient{ethclient.NewClient(stack.Attach())}, + }, nil +} + +// Close shuts down the simBackend. +// The simulated backend can't be used afterwards. +func (n *Backend) Close() error { + if n.client.Client != nil { + n.client.Close() + n.client = simClient{} + } + if n.beacon != nil { + err := n.beacon.Stop() + n.beacon = nil + return err + } + return nil +} + +// Commit seals a block and moves the chain forward to a new empty block. +func (n *Backend) Commit() common.Hash { + return n.beacon.Commit() +} + +// Rollback removes all pending transactions, reverting to the last committed state. +func (n *Backend) Rollback() { + n.beacon.Rollback() +} + +// Fork creates a side-chain that can be used to simulate reorgs. +// +// This function should be called with the ancestor block where the new side +// chain should be started. Transactions (old and new) can then be applied on +// top and Commit-ed. +// +// Note, the side-chain will only become canonical (and trigger the events) when +// it becomes longer. Until then CallContract will still operate on the current +// canonical chain. +// +// There is a % chance that the side chain becomes canonical at the same length +// to simulate live network behavior. +func (n *Backend) Fork(parentHash common.Hash) error { + return n.beacon.Fork(parentHash) +} + +// AdjustTime changes the block timestamp and creates a new block. +// It can only be called on empty blocks. +func (n *Backend) AdjustTime(adjustment time.Duration) error { + return n.beacon.AdjustTime(adjustment) +} + +// Client returns a client that accesses the simulated chain. +func (n *Backend) Client() Client { + return n.client +} diff --git a/ethclient/simulated/backend_test.go b/ethclient/simulated/backend_test.go new file mode 100644 index 000000000..16a2acdf4 --- /dev/null +++ b/ethclient/simulated/backend_test.go @@ -0,0 +1,309 @@ +// 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 simulated + +import ( + "context" + "crypto/ecdsa" + "math/big" + "math/rand" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "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/params" +) + +var _ bind.ContractBackend = (Client)(nil) + +var ( + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) +) + +func simTestBackend(testAddr common.Address) *Backend { + return New( + core.GenesisAlloc{ + testAddr: {Balance: big.NewInt(10000000000000000)}, + }, 10000000, + ) +} + +func newTx(sim *Backend, key *ecdsa.PrivateKey) (*types.Transaction, error) { + client := sim.Client() + + // create a signed transaction to send + head, _ := client.HeaderByNumber(context.Background(), nil) // Should be child's, good enough + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + addr := crypto.PubkeyToAddress(key.PublicKey) + chainid, _ := client.ChainID(context.Background()) + nonce, err := client.PendingNonceAt(context.Background(), addr) + if err != nil { + return nil, err + } + tx := types.NewTx(&types.DynamicFeeTx{ + ChainID: chainid, + Nonce: nonce, + GasTipCap: big.NewInt(1), + GasFeeCap: gasPrice, + Gas: 21000, + To: &addr, + }) + return types.SignTx(tx, types.LatestSignerForChainID(chainid), key) +} + +func TestNewSim(t *testing.T) { + sim := New(core.GenesisAlloc{}, 30_000_000) + defer sim.Close() + + client := sim.Client() + num, err := client.BlockNumber(context.Background()) + if err != nil { + t.Fatal(err) + } + if num != 0 { + t.Fatalf("expected 0 got %v", num) + } + // Create a block + sim.Commit() + num, err = client.BlockNumber(context.Background()) + if err != nil { + t.Fatal(err) + } + if num != 1 { + t.Fatalf("expected 1 got %v", num) + } +} + +func TestAdjustTime(t *testing.T) { + sim := New(core.GenesisAlloc{}, 10_000_000) + defer sim.Close() + + client := sim.Client() + block1, _ := client.BlockByNumber(context.Background(), nil) + + // Create a block + if err := sim.AdjustTime(time.Minute); err != nil { + t.Fatal(err) + } + block2, _ := client.BlockByNumber(context.Background(), nil) + prevTime := block1.Time() + newTime := block2.Time() + if newTime-prevTime != uint64(time.Minute) { + t.Errorf("adjusted time not equal to 60 seconds. prev: %v, new: %v", prevTime, newTime) + } +} + +func TestSendTransaction(t *testing.T) { + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + signedTx, err := newTx(sim, testKey) + if err != nil { + t.Errorf("could not create transaction: %v", err) + } + // send tx to simulated backend + err = client.SendTransaction(ctx, signedTx) + if err != nil { + t.Errorf("could not add tx to pending block: %v", err) + } + sim.Commit() + block, err := client.BlockByNumber(ctx, big.NewInt(1)) + if err != nil { + t.Errorf("could not get block at height 1: %v", err) + } + + if signedTx.Hash() != block.Transactions()[0].Hash() { + t.Errorf("did not commit sent transaction. expected hash %v got hash %v", block.Transactions()[0].Hash(), signedTx.Hash()) + } +} + +// TestFork check that the chain length after a reorg is correct. +// Steps: +// 1. Save the current block which will serve as parent for the fork. +// 2. Mine n blocks with n ∈ [0, 20]. +// 3. Assert that the chain length is n. +// 4. Fork by using the parent block as ancestor. +// 5. Mine n+1 blocks which should trigger a reorg. +// 6. Assert that the chain length is n+1. +// Since Commit() was called 2n+1 times in total, +// having a chain length of just n+1 means that a reorg occurred. +func TestFork(t *testing.T) { + t.Parallel() + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + // 1. + parent, _ := client.HeaderByNumber(ctx, nil) + + // 2. + n := int(rand.Int31n(21)) + for i := 0; i < n; i++ { + sim.Commit() + } + + // 3. + b, _ := client.BlockNumber(ctx) + if b != uint64(n) { + t.Error("wrong chain length") + } + + // 4. + sim.Fork(parent.Hash()) + + // 5. + for i := 0; i < n+1; i++ { + sim.Commit() + } + + // 6. + b, _ = client.BlockNumber(ctx) + if b != uint64(n+1) { + t.Error("wrong chain length") + } +} + +// TestForkResendTx checks that re-sending a TX after a fork +// is possible and does not cause a "nonce mismatch" panic. +// Steps: +// 1. Save the current block which will serve as parent for the fork. +// 2. Send a transaction. +// 3. Check that the TX is included in block 1. +// 4. Fork by using the parent block as ancestor. +// 5. Mine a block, Re-send the transaction and mine another one. +// 6. Check that the TX is now included in block 2. +func TestForkResendTx(t *testing.T) { + t.Parallel() + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + // 1. + parent, _ := client.HeaderByNumber(ctx, nil) + + // 2. + tx, err := newTx(sim, testKey) + if err != nil { + t.Fatalf("could not create transaction: %v", err) + } + client.SendTransaction(ctx, tx) + sim.Commit() + + // 3. + receipt, _ := client.TransactionReceipt(ctx, tx.Hash()) + if h := receipt.BlockNumber.Uint64(); h != 1 { + t.Errorf("TX included in wrong block: %d", h) + } + + // 4. + if err := sim.Fork(parent.Hash()); err != nil { + t.Errorf("forking: %v", err) + } + + // 5. + sim.Commit() + if err := client.SendTransaction(ctx, tx); err != nil { + t.Fatalf("sending transaction: %v", err) + } + sim.Commit() + receipt, _ = client.TransactionReceipt(ctx, tx.Hash()) + if h := receipt.BlockNumber.Uint64(); h != 2 { + t.Errorf("TX included in wrong block: %d", h) + } +} + +func TestCommitReturnValue(t *testing.T) { + t.Parallel() + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + // Test if Commit returns the correct block hash + h1 := sim.Commit() + cur, _ := client.HeaderByNumber(ctx, nil) + if h1 != cur.Hash() { + t.Error("Commit did not return the hash of the last block.") + } + + // Create a block in the original chain (containing a transaction to force different block hashes) + head, _ := client.HeaderByNumber(ctx, nil) // Should be child's, good enough + gasPrice := new(big.Int).Add(head.BaseFee, big.NewInt(1)) + _tx := types.NewTransaction(0, testAddr, big.NewInt(1000), params.TxGas, gasPrice, nil) + tx, _ := types.SignTx(_tx, types.HomesteadSigner{}, testKey) + client.SendTransaction(ctx, tx) + + h2 := sim.Commit() + + // Create another block in the original chain + sim.Commit() + + // Fork at the first bock + if err := sim.Fork(h1); err != nil { + t.Errorf("forking: %v", err) + } + + // Test if Commit returns the correct block hash after the reorg + h2fork := sim.Commit() + if h2 == h2fork { + t.Error("The block in the fork and the original block are the same block!") + } + if header, err := client.HeaderByHash(ctx, h2fork); err != nil || header == nil { + t.Error("Could not retrieve the just created block (side-chain)") + } +} + +// TestAdjustTimeAfterFork ensures that after a fork, AdjustTime uses the pending fork +// block's parent rather than the canonical head's parent. +func TestAdjustTimeAfterFork(t *testing.T) { + t.Parallel() + testAddr := crypto.PubkeyToAddress(testKey.PublicKey) + sim := simTestBackend(testAddr) + defer sim.Close() + + client := sim.Client() + ctx := context.Background() + + sim.Commit() // h1 + h1, _ := client.HeaderByNumber(ctx, nil) + + sim.Commit() // h2 + sim.Fork(h1.Hash()) + sim.AdjustTime(1 * time.Second) + sim.Commit() + + head, _ := client.HeaderByNumber(ctx, nil) + if head.Number.Uint64() == 2 && head.ParentHash != h1.Hash() { + t.Errorf("failed to build block on fork") + } +} diff --git a/ethdb/dbtest/testsuite.go b/ethdb/dbtest/testsuite.go index 0d3d5f5aa..29bd24364 100644 --- a/ethdb/dbtest/testsuite.go +++ b/ethdb/dbtest/testsuite.go @@ -273,9 +273,13 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { b.Put([]byte("5"), nil) b.Delete([]byte("1")) b.Put([]byte("6"), nil) - b.Delete([]byte("3")) + + b.Delete([]byte("3")) // delete then put b.Put([]byte("3"), nil) + b.Put([]byte("7"), nil) // put then delete + b.Delete([]byte("7")) + if err := b.Write(); err != nil { t.Fatal(err) } diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index c0e0eb250..e58efbddb 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -22,6 +22,7 @@ package leveldb import ( "fmt" + "strings" "sync" "time" @@ -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) } diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go index f9f74322b..2a939f9a1 100644 --- a/ethdb/memorydb/memorydb.go +++ b/ethdb/memorydb/memorydb.go @@ -207,7 +207,7 @@ func (db *Database) Len() int { // keyvalue is a key-value tuple tagged with a deletion field to allow creating // memory-database write batches. type keyvalue struct { - key []byte + key string value []byte delete bool } @@ -222,14 +222,14 @@ type batch struct { // Put inserts the given value into the batch for later committing. func (b *batch) Put(key, value []byte) error { - b.writes = append(b.writes, keyvalue{common.CopyBytes(key), common.CopyBytes(value), false}) + b.writes = append(b.writes, keyvalue{string(key), common.CopyBytes(value), false}) b.size += len(key) + len(value) return nil } // Delete inserts the a key removal into the batch for later committing. func (b *batch) Delete(key []byte) error { - b.writes = append(b.writes, keyvalue{common.CopyBytes(key), nil, true}) + b.writes = append(b.writes, keyvalue{string(key), nil, true}) b.size += len(key) return nil } @@ -249,10 +249,10 @@ func (b *batch) Write() error { } for _, keyvalue := range b.writes { if keyvalue.delete { - delete(b.db.db, string(keyvalue.key)) + delete(b.db.db, keyvalue.key) continue } - b.db.db[string(keyvalue.key)] = keyvalue.value + b.db.db[keyvalue.key] = keyvalue.value } return nil } @@ -267,12 +267,12 @@ func (b *batch) Reset() { func (b *batch) Replay(w ethdb.KeyValueWriter) error { for _, keyvalue := range b.writes { if keyvalue.delete { - if err := w.Delete(keyvalue.key); err != nil { + if err := w.Delete([]byte(keyvalue.key)); err != nil { return err } continue } - if err := w.Put(keyvalue.key, keyvalue.value); err != nil { + if err := w.Put([]byte(keyvalue.key), keyvalue.value); err != nil { return err } } diff --git a/ethdb/memorydb/memorydb_test.go b/ethdb/memorydb/memorydb_test.go index dba18ad30..51499c3b1 100644 --- a/ethdb/memorydb/memorydb_test.go +++ b/ethdb/memorydb/memorydb_test.go @@ -17,6 +17,7 @@ package memorydb import ( + "encoding/binary" "testing" "github.com/ethereum/go-ethereum/ethdb" @@ -30,3 +31,20 @@ func TestMemoryDB(t *testing.T) { }) }) } + +// BenchmarkBatchAllocs measures the time/allocs for storing 120 kB of data +func BenchmarkBatchAllocs(b *testing.B) { + b.ReportAllocs() + var key = make([]byte, 20) + var val = make([]byte, 100) + // 120 * 1_000 -> 120_000 == 120kB + for i := 0; i < b.N; i++ { + batch := New().NewBatch() + for j := uint64(0); j < 1000; j++ { + binary.BigEndian.PutUint64(key, j) + binary.BigEndian.PutUint64(val, j) + batch.Put(key, val) + } + batch.Write() + } +} diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go index a06f59bcf..af4686cf5 100644 --- a/ethdb/pebble/pebble.go +++ b/ethdb/pebble/pebble.go @@ -14,8 +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 (arm64 || amd64) && !openbsd - // Package pebble implements the key-value database layer based on pebble. package pebble @@ -70,6 +68,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 +84,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 +118,24 @@ 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) Errorf(format string, args ...interface{}) { +} + +func (l panicLogger) Fatalf(format string, args ...interface{}) { + panic(fmt.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 @@ -131,20 +148,35 @@ func New(file string, cache int, handles int, namespace string, readonly bool) ( // The max memtable size is limited by the uint32 offsets stored in // internal/arenaskl.node, DeferredBatchOp, and flushableBatchEntry. - // Taken from https://github.com/cockroachdb/pebble/blob/master/open.go#L38 - maxMemTableSize := 4<<30 - 1 // Capped by 4 GB + // + // - MaxUint32 on 64-bit platforms; + // - MaxInt on 32-bit platforms. + // + // It is used when slices are limited to Uint32 on 64-bit platforms (the + // length limit for slices is naturally MaxInt on 32-bit platforms). + // + // Taken from https://github.com/cockroachdb/pebble/blob/master/internal/constants/constants.go + maxMemTableSize := (1<<31)<<(^uint(0)>>63) - 1 // Two memory tables is configured which is identical to leveldb, // including a frozen memory table and another live one. memTableLimit := 2 memTableSize := cache * 1024 * 1024 / 2 / memTableLimit - if memTableSize > maxMemTableSize { - memTableSize = maxMemTableSize + + // The memory table size is currently capped at maxMemTableSize-1 due to a + // known bug in the pebble where maxMemTableSize is not recognized as a + // valid size. + // + // TODO use the maxMemTableSize as the maximum table size once the issue + // in pebble is fixed. + if memTableSize >= maxMemTableSize { + memTableSize = maxMemTableSize - 1 } 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 +187,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 +218,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 +246,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 +312,7 @@ func (d *Database) Put(key []byte, value []byte) error { if d.closed { return pebble.ErrClosed } - return d.db.Set(key, value, pebble.Sync) + return d.db.Set(key, value, d.writeOptions) } // Delete removes the key from the key-value store. @@ -302,12 +335,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 +406,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 +443,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 +466,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 +477,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 +506,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 +515,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 +576,7 @@ func (b *batch) Write() error { if b.db.closed { return pebble.ErrClosed } - return b.b.Commit(pebble.Sync) + return b.b.Commit(b.db.writeOptions) } // Reset resets the batch for reuse. @@ -567,21 +608,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 +660,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/pebble/pebble_test.go b/ethdb/pebble/pebble_test.go index 590d5bf03..1d5611f21 100644 --- a/ethdb/pebble/pebble_test.go +++ b/ethdb/pebble/pebble_test.go @@ -14,8 +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 (arm64 || amd64) && !openbsd - package pebble import ( diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index e059844a1..75d0faac5 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -38,7 +38,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" ethproto "github.com/ethereum/go-ethereum/eth/protocols/eth" "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/les" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" @@ -76,12 +75,18 @@ type backend interface { // reporting to ethstats type fullNodeBackend interface { backend - Miner() *miner.Miner BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) - CurrentBlock() *types.Block + CurrentBlock() *types.Header SuggestGasTipCap(ctx context.Context) (*big.Int, error) } +// miningNodeBackend encompasses the functionality necessary for a mining node +// reporting to ethstats +type miningNodeBackend interface { + fullNodeBackend + Miner() *miner.Miner +} + // Service implements an Ethereum netstats reporting daemon that pushes local // chain statistics up to a monitoring server. type Service struct { @@ -480,7 +485,7 @@ func (s *Service) login(conn *connWrapper) error { if info := infos.Protocols["eth"]; info != nil { network = fmt.Sprintf("%d", info.(*ethproto.NodeInfo).Network) } else { - network = fmt.Sprintf("%d", infos.Protocols["les"].(*les.NodeInfo).Network) + return errors.New("no eth protocol available") } auth := &authMsg{ ID: s.node, @@ -634,7 +639,8 @@ func (s *Service) assembleBlockStats(block *types.Block) *blockStats { fullBackend, ok := s.backend.(fullNodeBackend) if ok { if block == nil { - block = fullBackend.CurrentBlock() + head := fullBackend.CurrentBlock() + block, _ = fullBackend.BlockByNumber(context.Background(), rpc.BlockNumber(head.Number.Uint64())) } header = block.Header() td = fullBackend.GetTd(context.Background(), header.Hash()) @@ -779,10 +785,11 @@ func (s *Service) reportStats(conn *connWrapper) error { gasprice int ) // check if backend is a full node - fullBackend, ok := s.backend.(fullNodeBackend) - if ok { - mining = fullBackend.Miner().Mining() - hashrate = int(fullBackend.Miner().Hashrate()) + if fullBackend, ok := s.backend.(fullNodeBackend); ok { + if miningBackend, ok := s.backend.(miningNodeBackend); ok { + mining = miningBackend.Miner().Mining() + hashrate = int(miningBackend.Miner().Hashrate()) + } sync := fullBackend.SyncProgress() syncing = fullBackend.CurrentHeader().Number.Uint64() >= sync.HighestBlock diff --git a/event/subscription.go b/event/subscription.go index 6c6287471..07e059c6d 100644 --- a/event/subscription.go +++ b/event/subscription.go @@ -120,7 +120,7 @@ func ResubscribeErr(backoffMax time.Duration, fn ResubscribeErrFunc) Subscriptio backoffMax: backoffMax, fn: fn, err: make(chan error), - unsub: make(chan struct{}), + unsub: make(chan struct{}, 1), } go s.loop() return s diff --git a/event/subscription_test.go b/event/subscription_test.go index ba081705c..743d0bf67 100644 --- a/event/subscription_test.go +++ b/event/subscription_test.go @@ -154,3 +154,27 @@ func TestResubscribeWithErrorHandler(t *testing.T) { t.Fatalf("unexpected subscription errors %v, want %v", subErrs, expectedSubErrs) } } + +func TestResubscribeWithCompletedSubscription(t *testing.T) { + t.Parallel() + + quitProducerAck := make(chan struct{}) + quitProducer := make(chan struct{}) + + sub := ResubscribeErr(100*time.Millisecond, func(ctx context.Context, lastErr error) (Subscription, error) { + return NewSubscription(func(unsubscribed <-chan struct{}) error { + select { + case <-quitProducer: + quitProducerAck <- struct{}{} + return nil + case <-unsubscribed: + return nil + } + }), nil + }) + + // Ensure producer has started and exited before Unsubscribe + close(quitProducer) + <-quitProducerAck + sub.Unsubscribe() +} diff --git a/go.mod b/go.mod index c9a3a0e49..666dd3fb8 100644 --- a/go.mod +++ b/go.mod @@ -1,52 +1,52 @@ 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/VictoriaMetrics/fastcache v1.10.0 - github.com/aws/aws-sdk-go-v2 v1.16.6 - github.com/aws/aws-sdk-go-v2/config v1.15.12 - github.com/aws/aws-sdk-go-v2/credentials v1.12.7 - github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1 + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 + github.com/Microsoft/go-winio v0.6.1 + github.com/VictoriaMetrics/fastcache v1.12.1 + github.com/aws/aws-sdk-go-v2 v1.21.2 + github.com/aws/aws-sdk-go-v2/config v1.18.45 + github.com/aws/aws-sdk-go-v2/credentials v1.13.43 + github.com/aws/aws-sdk-go-v2/service/route53 v1.30.2 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/cloudflare/cloudflare-go v0.79.0 + github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 github.com/consensys/gnark-crypto v0.12.1 - github.com/crate-crypto/go-kzg-4844 v0.3.0 + github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 + 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 v24.0.7+incompatible - github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 - github.com/ethereum/c-kzg-4844 v0.3.1 - github.com/fatih/color v1.15.0 + github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 + github.com/ethereum/c-kzg-4844 v0.4.0 + github.com/fatih/color v1.13.0 github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e - github.com/fjl/memsize v0.0.1 + github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 github.com/fsnotify/fsnotify v1.6.0 github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff - github.com/gballet/go-verkle v0.0.0-20230607174250-df487255f46b + github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 github.com/go-redis/redis/v7 v7.4.1 - github.com/go-stack/stack v1.8.1 github.com/gofrs/flock v0.8.1 github.com/golang-jwt/jwt/v4 v4.5.0 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/gofuzz v1.2.0 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.3 - github.com/huin/goupnp v1.0.3 - github.com/influxdata/influxdb-client-go/v2 v2.9.1 + github.com/holiman/uint256 v1.2.4 + 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.3-0.20230711191512-61db3e06439c + 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.17 @@ -57,83 +57,88 @@ require ( github.com/rs/cors v1.7.0 github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible github.com/status-im/keycard-go v0.2.0 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.4 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.24.1 + github.com/urfave/cli/v2 v2.25.7 go.uber.org/automaxprocs v1.5.2 - golang.org/x/crypto v0.14.0 - golang.org/x/exp v0.0.0-20230810033253-352e893a4cad - golang.org/x/sync v0.3.0 - golang.org/x/sys v0.13.0 - golang.org/x/text v0.13.0 + golang.org/x/crypto v0.17.0 + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa + golang.org/x/sync v0.5.0 + golang.org/x/sys v0.15.0 + golang.org/x/text v0.14.0 golang.org/x/time v0.3.0 - golang.org/x/tools v0.9.1 + golang.org/x/tools v0.15.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/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.7 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.13 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.7 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.3.14 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.7 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.11.10 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.16.8 // indirect - github.com/aws/smithy-go v1.12.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // 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.13.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 // indirect + github.com/aws/smithy-go v1.15.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.10.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/errors v1.8.1 // 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-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/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect - github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.4 // indirect + github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect + github.com/jmespath/go-jmespath v0.4.0 // 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/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/mattn/go-runewidth v0.0.13 // 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.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/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.39.0 // indirect - github.com/prometheus/procfs v0.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/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // 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.11.0 // indirect - golang.org/x/net v0.17.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.14.0 // indirect + golang.org/x/net v0.18.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gotest.tools/v3 v3.4.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index 803ca23c6..115e313dd 100644 --- a/go.sum +++ b/go.sum @@ -1,85 +1,139 @@ 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= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3 h1:E+m3SkZCN0Bf5q7YdTs5lSm2CYY3CK4spn5OmUIiQtk= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSuH8w8yEK6DpFl3LP5rhdvAb7Yz5I= -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/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 h1:8q4SaHjFsClSvuVne0ID/5Ka8u3fcIHyqkLjcFpNRHQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 h1:gggzg0SUMs6SQbEw+3LoSsYf9YMjkupeAnHMX8O9mmY= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0/go.mod h1:+6KLcKIVgxoBDMqMO/Nvy7bZ9a0nbU3I1DtFQK3YvB4= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -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/VictoriaMetrics/fastcache v1.10.0 h1:5hDJnLsKLpnUEToub7ETuRu8RCkb40woBZAUiKonXzY= -github.com/VictoriaMetrics/fastcache v1.10.0/go.mod h1:tjiYeEfYXCqacuvYw/7UoDIeJaNxq6132xHICNP77w8= +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.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= 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= -github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= -github.com/aws/aws-sdk-go-v2 v1.16.6 h1:kzafGZYwkwVgLZ2zEX7P+vTwLli6uIMXF8aGjunN6UI= -github.com/aws/aws-sdk-go-v2 v1.16.6/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= -github.com/aws/aws-sdk-go-v2/config v1.15.12 h1:D4mdf0cOSmZRgJe0DDOd1Qm6tkwHJ7r5i1lz0asa+AA= -github.com/aws/aws-sdk-go-v2/config v1.15.12/go.mod h1:oxRNnH11J580bxDEXyfTqfB3Auo2fxzhV052LD4HnyA= -github.com/aws/aws-sdk-go-v2/credentials v1.12.7 h1:e2DcCR0gP+T2zVj5eQPMQoRdxo+vd2p9BkpJ72BdyzA= -github.com/aws/aws-sdk-go-v2/credentials v1.12.7/go.mod h1:8b1nSHdDaKLho9VEK+K8WivifA/2K5pPm4sfI21NlQ8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.7 h1:8yi2ORCwXpXEPnj0vP3DjYhejwDQD/5klgBoxXcKOxY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.7/go.mod h1:81k6q0UUZj6AdQZ1E/VQ27cLrTUpJGraZR6/hVHRxjE= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.13 h1:WuQ1yGs3TMJgxpGVLspcsU/5q1omSA0SG6Cu0yZ4jkM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.13/go.mod h1:wLLesU+LdMZDM3U0PP9vZXJW39zmD/7L4nY2pSrYZ/g= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.7 h1:mCeDDYeDXp3loo/xKi7nkx34eeh7q3n1mUBtzptsj8c= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.7/go.mod h1:93Uot80ddyVzSl//xEJreNKMhxntr71WtR3v/A1cRYk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.14 h1:bJv4Y9QOiW0GZPStgLgpGrpdfRDSR3XM4V4M3YCQRZo= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.14/go.mod h1:R1HF8ZDdcRFfAGF+13En4LSHi2IrrNuPQCaxgWCeGyY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.7 h1:M7/BzQNsu0XXiJRe3gUn8UA8tExF6kLMAfvo5PT/KJY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.7/go.mod h1:HvVdEh/x4jsPBsjNvDy+MH3CDCPy4gTZEzFe2r4uJY8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1 h1:cKr6St+CtC3/dl/rEBJvlk7A/IN5D5F02GNkGzfbtVU= -github.com/aws/aws-sdk-go-v2/service/route53 v1.1.1/go.mod h1:rLiOUrPLW/Er5kRcQ7NkwbjlijluLsrIbu/iyl35RO4= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.10 h1:icon5WWg9Yg5nkB0pJF6bfKw6M0xozukeGKSNKtnqzw= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.10/go.mod h1:UHxA35uPrCykRySBV5iSPZhZRlYnWSS2c/aaZVsoU94= -github.com/aws/aws-sdk-go-v2/service/sts v1.16.8 h1:GLGfpqX+1bmjNvUJkwB1ZaDpNFXQwJ3z9RkQDA58OBY= -github.com/aws/aws-sdk-go-v2/service/sts v1.16.8/go.mod h1:50YdFq1WIuxA0AGrygvYGucnNYrG24WYzu5fNp7lMgY= -github.com/aws/smithy-go v1.1.0/go.mod h1:EzMw8dbp/YJL4A5/sbhGddag+NPT7q084agLbB9LgIw= -github.com/aws/smithy-go v1.12.0 h1:gXpeZel/jPoWQ7OEmLIgCUnhkFftqNfwWUwAHSlp1v0= -github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/aws-sdk-go-v2 v1.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA= +github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM= +github.com/aws/aws-sdk-go-v2/config v1.18.45 h1:Aka9bI7n8ysuwPeFdm77nfbyHCAKQ3z9ghB3S/38zes= +github.com/aws/aws-sdk-go-v2/config v1.18.45/go.mod h1:ZwDUgFnQgsazQTnWfeLWk5GjeqTQTL8lMkoE1UXzxdE= +github.com/aws/aws-sdk-go-v2/credentials v1.13.43 h1:LU8vo40zBlo3R7bAvBVy/ku4nxGEyZe9N8MqAeFTzF8= +github.com/aws/aws-sdk-go-v2/credentials v1.13.43/go.mod h1:zWJBz1Yf1ZtX5NGax9ZdNjhhI4rgjfgsyk6vTY1yfVg= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 h1:PIktER+hwIG286DqXyvVENjgLTAwGgoeriLDD5C+YlQ= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13/go.mod h1:f/Ib/qYjhV2/qdsf79H3QP/eRE4AkVyEf6sk7XfZ1tg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 h1:nFBQlGtkbPzp/NjZLuFxRqmT91rLJkgvsEQs68h962Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43/go.mod h1:auo+PiyLl0n1l8A0e8RIeR8tOzYPfZZH/JNlrJ8igTQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 h1:JRVhO25+r3ar2mKGP7E0LDl8K9/G36gjlqca5iQbaqc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37/go.mod h1:Qe+2KtKml+FEsQF/DHmDV+xjtche/hwoF75EG4UlHW8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 h1:hze8YsjSh8Wl1rYa1CJpRmXP21BvOBuc76YhW0HsuQ4= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45/go.mod h1:lD5M20o09/LCuQ2mE62Mb/iSdSlCNuj6H5ci7tW7OsE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 h1:WWZA/I2K4ptBS1kg0kV1JbBtG/umed0vwHRrmcr9z7k= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37/go.mod h1:vBmDnwWXWxNPFRMmG2m/3MKOe+xEcMDo1tanpaWCcck= +github.com/aws/aws-sdk-go-v2/service/route53 v1.30.2 h1:/RPQNjh1sDIezpXaFIkZb7MlXnSyAqjVdAwcJuGYTqg= +github.com/aws/aws-sdk-go-v2/service/route53 v1.30.2/go.mod h1:TQZBt/WaQy+zTHoW++rnl8JBrmZ0VO6EUbVua1+foCA= +github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 h1:JuPGc7IkOP4AaqcZSIcyqLpFSqBWK32rM9+a1g6u73k= +github.com/aws/aws-sdk-go-v2/service/sso v1.15.2/go.mod h1:gsL4keucRCgW+xA85ALBpRFfdSLH4kHOVSnLMSuBECo= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 h1:HFiiRkf1SdaAmV3/BHOFZ9DjFynPHj8G/UIO1lQS+fk= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3/go.mod h1:a7bHA82fyUXOm+ZSWKU6PIoBxrjSprdLoM8xPYvzYVg= +github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 h1:0BkLfgeDjfZnZ+MhB3ONb01u9pwFYTCZVhlsSSBvlbU= +github.com/aws/aws-sdk-go-v2/service/sts v1.23.2/go.mod h1:Eows6e1uQEsc4ZaHANmsPRzAKcVDrcmjjWiih2+HUUQ= +github.com/aws/smithy-go v1.15.0 h1:PS/durmlzvAFpQHDs4wi4sNNP9ExsqZh6IlfdHXgKK8= +github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= 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.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 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= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 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/cloudflare/cloudflare-go v0.79.0 h1:ErwCYDjFCYppDJlDJ/5WhsSmzegAUe2+K9qgFyQDg3M= +github.com/cloudflare/cloudflare-go v0.79.0/go.mod h1:gkHQf9xEubaQPEuerBuoinR9P8bf8a05Lq0X6WKy1Oc= +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= @@ -89,13 +143,12 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -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-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.3.0 h1:UBlWE0CgyFqqzTI+IFyCzA7A3Zw4iip6uzRv5NIXG0A= -github.com/crate-crypto/go-kzg-4844 v0.3.0/go.mod h1:SBP7ikXEgDnUPONgm33HtuDZEDtWa3L4QtN1ocJSEQ4= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +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= @@ -107,41 +160,38 @@ 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.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= -github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= +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/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= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -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 v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+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-20230605162241-28ee0ee714f3 h1:+3HCtB74++ClLy8GgjUQYeC8R4ILzVcIe8+5edAJJnE= -github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= +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.3.1 h1:sR65+68+WdnMKxseNWxSJuAv2tsUrihTpVBTfM/U5Zg= -github.com/ethereum/c-kzg-4844 v0.3.1/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +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.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e h1:bBLctRc7kr01YGvaDfgLbTwjFNW5jdp5y5rj8XXBHfY= github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e/go.mod h1:AzA8Lj6YtixmJWL+wkKoBGsLWy9gFrAzi4g+5bCKwpY= -github.com/fjl/memsize v0.0.1 h1:+zhkb+dhUgx0/e+M8sF0QqiouvMQUiKR+QYvdxIOKcQ= -github.com/fjl/memsize v0.0.1/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= +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= @@ -151,49 +201,67 @@ 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-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.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/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= +github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= 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-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI= github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= 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.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 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.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 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= @@ -202,6 +270,7 @@ 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/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= @@ -211,90 +280,129 @@ github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXi 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.2/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.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/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= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= 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-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= +github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= 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.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= -github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= 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.9.1 h1:5kbH226fmmiV0MMTs7a8L7/ECCKdJWBi1QZNNv4/TkI= -github.com/influxdata/influxdb-client-go/v2 v2.9.1/go.mod h1:x7Jo5UHHl+w8wu8UnGiNobDDHygojXwJX4mx7rXGKMk= +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-20210311194329-9aa0e372d097 h1:vilfsDSy7TDxedi9gyBkMvAirat/oRcL0lFdJBf6tdM= -github.com/influxdata/line-protocol v0.0.0-20210311194329-9aa0e372d097/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +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/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 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 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.3-0.20230711191512-61db3e06439c h1:AqsttAyEyIEsNz5WLRwuRwjiT5CMDUfLk6cFJDVPebs= -github.com/karalabe/usb v0.0.3-0.20230711191512-61db3e06439c/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/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.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/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= @@ -306,11 +414,10 @@ 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= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -318,7 +425,7 @@ 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.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 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= @@ -330,20 +437,22 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ 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/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 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= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= -github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= @@ -351,15 +460,16 @@ 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/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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= @@ -367,8 +477,8 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N 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.1/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.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -382,40 +492,60 @@ 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/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= 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/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -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/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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 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= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 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= @@ -427,34 +557,28 @@ 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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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.24.1 h1:/QYYr7g0EhwXEML8jO+8OYt5trPnLHS0p3mrgExJ5NU= -github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +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= @@ -471,178 +595,333 @@ 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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230810033253-352e893a4cad h1:g0bG7Z4uG+OgH2QDODnjp6ggkk1bJDsINcuWmJN1iJU= -golang.org/x/exp v0.0.0-20230810033253-352e893a4cad/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +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-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-20190923162816-aa69164e4478/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= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 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-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.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= 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.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/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20191010194322-b09406accb47/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-20210119212857-b64e53b001e4/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-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-20220405052023-b1e9470b6e64/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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.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/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +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.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.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= 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= @@ -651,11 +930,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= @@ -665,30 +946,31 @@ 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.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= -gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= 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 7aa427b45..49be23af6 100644 --- a/graphql/graphql.go +++ b/graphql/graphql.go @@ -41,7 +41,8 @@ import ( ) var ( - errBlockInvariant = errors.New("block objects must be instantiated with at least one of num or hash") + errBlockInvariant = errors.New("block objects must be instantiated with at least one of num or hash") + errInvalidBlockRange = errors.New("invalid from and to block combination: from > to") ) type Long int64 @@ -272,8 +273,6 @@ func (t *Transaction) GasPrice(ctx context.Context) hexutil.Big { return hexutil.Big{} } switch tx.Type() { - case types.AccessListTxType: - return hexutil.Big(*tx.GasPrice()) case types.DynamicFeeTxType: if block != nil { if baseFee, _ := block.BaseFeePerGas(ctx); baseFee != nil { @@ -312,9 +311,7 @@ func (t *Transaction) MaxFeePerGas(ctx context.Context) *hexutil.Big { return nil } switch tx.Type() { - case types.AccessListTxType: - return nil - case types.DynamicFeeTxType: + case types.DynamicFeeTxType, types.BlobTxType: return (*hexutil.Big)(tx.GasFeeCap()) default: return nil @@ -327,15 +324,33 @@ func (t *Transaction) MaxPriorityFeePerGas(ctx context.Context) *hexutil.Big { return nil } switch tx.Type() { - case types.AccessListTxType: - return nil - case types.DynamicFeeTxType: + case types.DynamicFeeTxType, types.BlobTxType: return (*hexutil.Big)(tx.GasTipCap()) default: 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 := t.resolve(ctx) if tx == nil { @@ -468,6 +483,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{}) { @@ -566,13 +615,13 @@ func (t *Transaction) V(ctx context.Context) hexutil.Big { return hexutil.Big(*v) } -func (t *Transaction) YParity(ctx context.Context) (*hexutil.Uint64, error) { +func (t *Transaction) YParity(ctx context.Context) (*hexutil.Big, error) { tx, _ := t.resolve(ctx) if tx == nil || tx.Type() == types.LegacyTxType { return nil, nil } v, _, _ := tx.RawSignatureValues() - ret := hexutil.Uint64(v.Int64()) + ret := hexutil.Big(*v) return &ret, nil } @@ -1019,6 +1068,30 @@ func (b *Block) Withdrawals(ctx context.Context) (*[]*Withdrawal, error) { 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 { @@ -1217,6 +1290,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 { @@ -1249,6 +1325,9 @@ func (r *Resolver) Blocks(ctx context.Context, args struct { From *Long To *Long }) ([]*Block, error) { + if args.From == nil { + return nil, errors.New("from block number must be specified") + } from := rpc.BlockNumber(*args.From) var to rpc.BlockNumber @@ -1258,7 +1337,7 @@ func (r *Resolver) Blocks(ctx context.Context, args struct { to = rpc.BlockNumber(r.backend.CurrentBlock().Number.Int64()) } if to < from { - return []*Block{}, nil + return nil, errInvalidBlockRange } var ret []*Block for i := from; i <= to; i++ { @@ -1341,6 +1420,9 @@ func (r *Resolver) Logs(ctx context.Context, args struct{ Filter FilterCriteria if args.Filter.ToBlock != nil { end = int64(*args.Filter.ToBlock) } + if begin > 0 && end > 0 && begin > end { + return nil, errInvalidBlockRange + } var addresses []common.Address if args.Filter.Addresses != nil { addresses = *args.Filter.Addresses diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 4bbfb7251..f91229d01 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -139,7 +139,7 @@ func TestGraphQLBlockSerialization(t *testing.T) { // should return `estimateGas` as decimal { body: `{"query": "{block{ estimateGas(data:{}) }}"}`, - want: `{"data":{"block":{"estimateGas":"0xcf08"}}}`, + want: `{"data":{"block":{"estimateGas":"0xd221"}}}`, code: 200, }, // should return `status` as decimal @@ -148,6 +148,11 @@ func TestGraphQLBlockSerialization(t *testing.T) { want: `{"data":{"block":{"number":"0xa","call":{"data":"0x","status":"0x1"}}}}`, code: 200, }, + { + body: `{"query": "{blocks {number}}"}`, + want: `{"errors":[{"message":"from block number must be specified","path":["blocks"]}],"data":null}`, + code: 400, + }, } { resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", strings.NewReader(tt.body)) if err != nil { @@ -164,6 +169,9 @@ func TestGraphQLBlockSerialization(t *testing.T) { if tt.code != resp.StatusCode { t.Errorf("testcase %d %s,\nwrong statuscode, have: %v, want: %v", i, tt.body, resp.StatusCode, tt.code) } + if ctype := resp.Header.Get("Content-Type"); ctype != "application/json" { + t.Errorf("testcase %d \nwrong Content-Type, have: %v, want: %v", i, ctype, "application/json") + } } } diff --git a/graphql/schema.go b/graphql/schema.go index 5de5bad30..8264f1c28 100644 --- a/graphql/schema.go +++ b/graphql/schema.go @@ -71,8 +71,8 @@ const schema string = ` transaction: Transaction! } - #EIP-2718 - type AccessTuple{ + # EIP-2718 + type AccessTuple { address: Address! storageKeys : [Bytes32!]! } @@ -112,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. @@ -141,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. @@ -151,7 +157,7 @@ const schema string = ` r: BigInt! s: BigInt! v: BigInt! - yParity: Long + yParity: BigInt # Envelope transaction support type: Long accessList: [AccessTuple!] @@ -162,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 @@ -171,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!]!] } @@ -267,6 +275,10 @@ const schema string = ` # 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. @@ -312,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. @@ -337,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 4ca427658..584165bdb 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -73,12 +73,12 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } // Setting this disables gzip compression in package node. - w.Header().Set("transfer-encoding", "identity") + w.Header().Set("Transfer-Encoding", "identity") // Flush the response. Since we are writing close to the response timeout, // chunked transfer encoding must be disabled by setting content-length. - w.Header().Set("content-type", "application/json") - w.Header().Set("content-length", strconv.Itoa(len(responseJSON))) + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Length", strconv.Itoa(len(responseJSON))) w.Write(responseJSON) if flush, ok := w.(http.Flusher); ok { flush.Flush() @@ -88,17 +88,19 @@ 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 { http.Error(w, err.Error(), http.StatusInternalServerError) return } + w.Header().Set("Content-Type", "application/json") if len(response.Errors) > 0 { w.WriteHeader(http.StatusBadRequest) } - w.Header().Set("Content-Type", "application/json") w.Write(responseJSON) }) } diff --git a/interfaces.go b/interfaces.go index eb9af6007..1892309ed 100644 --- a/interfaces.go +++ b/interfaces.go @@ -29,8 +29,6 @@ import ( // NotFound is returned by API methods if the requested item does not exist. var NotFound = errors.New("not found") -// TODO: move subscription to package event - // Subscription represents an event subscription where events are // delivered on a data channel. type Subscription interface { @@ -201,6 +199,16 @@ type GasPricer interface { SuggestGasPrice(ctx context.Context) (*big.Int, error) } +// GasPricer1559 provides access to the EIP-1559 gas price oracle. +type GasPricer1559 interface { + SuggestGasTipCap(ctx context.Context) (*big.Int, error) +} + +// FeeHistoryReader provides access to the fee history oracle. +type FeeHistoryReader interface { + FeeHistory(ctx context.Context, blockCount uint64, lastBlock *big.Int, rewardPercentiles []float64) (*FeeHistory, error) +} + // FeeHistory provides recent fee market data that consumers can use to determine // a reasonable maxPriorityFeePerGas value. type FeeHistory struct { @@ -241,3 +249,13 @@ type GasEstimator interface { type PendingStateEventer interface { SubscribePendingTransactions(ctx context.Context, ch chan<- *types.Transaction) (Subscription, error) } + +// BlockNumberReader provides access to the current block number. +type BlockNumberReader interface { + BlockNumber(ctx context.Context) (uint64, error) +} + +// ChainIDReader provides access to the chain ID. +type ChainIDReader interface { + ChainID(ctx context.Context) (*big.Int, error) +} diff --git a/internal/build/azure.go b/internal/build/azure.go index 9d1c4f300..4085228d1 100644 --- a/internal/build/azure.go +++ b/internal/build/azure.go @@ -22,6 +22,7 @@ import ( "os" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" ) // AzureBlobstoreConfig is an authentication and configuration struct containing @@ -48,8 +49,8 @@ func AzureBlobstoreUpload(path string, name string, config AzureBlobstoreConfig) if err != nil { return err } - u := fmt.Sprintf("https://%s.blob.core.windows.net/%s", config.Account, config.Container) - container, err := azblob.NewContainerClientWithSharedKey(u, credential, nil) + a := fmt.Sprintf("https://%s.blob.core.windows.net/", config.Account) + client, err := azblob.NewClientWithSharedKeyCredential(a, credential, nil) if err != nil { return err } @@ -60,38 +61,38 @@ func AzureBlobstoreUpload(path string, name string, config AzureBlobstoreConfig) } defer in.Close() - blockblob := container.NewBlockBlobClient(name) - _, err = blockblob.Upload(context.Background(), in, nil) + _, err = client.UploadFile(context.Background(), config.Container, name, in, nil) return err } // AzureBlobstoreList lists all the files contained within an azure blobstore. -func AzureBlobstoreList(config AzureBlobstoreConfig) ([]*azblob.BlobItemInternal, error) { +func AzureBlobstoreList(config AzureBlobstoreConfig) ([]*container.BlobItem, error) { // Create an authenticated client against the Azure cloud credential, err := azblob.NewSharedKeyCredential(config.Account, config.Token) if err != nil { return nil, err } - u := fmt.Sprintf("https://%s.blob.core.windows.net/%s", config.Account, config.Container) - container, err := azblob.NewContainerClientWithSharedKey(u, credential, nil) + a := fmt.Sprintf("https://%s.blob.core.windows.net/", config.Account) + client, err := azblob.NewClientWithSharedKeyCredential(a, credential, nil) if err != nil { return nil, err } - var maxResults int32 = 5000 - pager := container.ListBlobsFlat(&azblob.ContainerListBlobFlatSegmentOptions{ - Maxresults: &maxResults, - }) - var allBlobs []*azblob.BlobItemInternal - for pager.NextPage(context.Background()) { - res := pager.PageResponse() - allBlobs = append(allBlobs, res.ContainerListBlobFlatSegmentResult.Segment.BlobItems...) + pager := client.NewListBlobsFlatPager(config.Container, nil) + + var blobs []*container.BlobItem + for pager.More() { + page, err := pager.NextPage(context.TODO()) + if err != nil { + return nil, err + } + blobs = append(blobs, page.Segment.BlobItems...) } - return allBlobs, pager.Err() + return blobs, nil } // AzureBlobstoreDelete iterates over a list of files to delete and removes them // from the blobstore. -func AzureBlobstoreDelete(config AzureBlobstoreConfig, blobs []*azblob.BlobItemInternal) error { +func AzureBlobstoreDelete(config AzureBlobstoreConfig, blobs []*container.BlobItem) error { if *DryRunFlag { for _, blob := range blobs { fmt.Printf("would delete %s (%s) from %s/%s\n", *blob.Name, blob.Properties.LastModified, config.Account, config.Container) @@ -103,15 +104,14 @@ func AzureBlobstoreDelete(config AzureBlobstoreConfig, blobs []*azblob.BlobItemI if err != nil { return err } - u := fmt.Sprintf("https://%s.blob.core.windows.net/%s", config.Account, config.Container) - container, err := azblob.NewContainerClientWithSharedKey(u, credential, nil) + a := fmt.Sprintf("https://%s.blob.core.windows.net/", config.Account) + client, err := azblob.NewClientWithSharedKeyCredential(a, credential, nil) if err != nil { return err } // Iterate over the blobs and delete them for _, blob := range blobs { - blockblob := container.NewBlockBlobClient(*blob.Name) - if _, err := blockblob.Delete(context.Background(), &azblob.DeleteBlobOptions{}); err != nil { + if _, err := client.DeleteBlob(context.Background(), config.Container, *blob.Name, nil); err != nil { return err } fmt.Printf("deleted %s (%s)\n", *blob.Name, blob.Properties.LastModified) diff --git a/internal/build/gotool.go b/internal/build/gotool.go index 296ba8c36..2a4746041 100644 --- a/internal/build/gotool.go +++ b/internal/build/gotool.go @@ -84,7 +84,11 @@ func (g *GoToolchain) goTool(command string, args ...string) *exec.Cmd { // DownloadGo downloads the Go binary distribution and unpacks it into a temporary // directory. It returns the GOROOT of the unpacked toolchain. -func DownloadGo(csdb *ChecksumDB, version string) string { +func DownloadGo(csdb *ChecksumDB) string { + version, err := Version(csdb, "golang") + if err != nil { + log.Fatal(err) + } // Shortcut: if the Go version that runs this script matches the // requested version exactly, there is no need to download anything. activeGo := strings.TrimPrefix(runtime.Version(), "go") @@ -126,3 +130,51 @@ func DownloadGo(csdb *ChecksumDB, version string) string { } return goroot } + +// Version returns the versions defined in the checksumdb. +func Version(csdb *ChecksumDB, version string) (string, error) { + for _, l := range csdb.allChecksums { + if !strings.HasPrefix(l, "# version:") { + continue + } + v := strings.Split(l, ":")[1] + parts := strings.Split(v, " ") + if len(parts) != 2 { + log.Print("Erroneous version-string", "v", l) + continue + } + if parts[0] == version { + return parts[1], nil + } + } + return "", fmt.Errorf("no version found for '%v'", version) +} + +// DownloadAndVerifyChecksums downloads all files and checks that they match +// the checksum given in checksums.txt. +// This task can be used to sanity-check new checksums. +func DownloadAndVerifyChecksums(csdb *ChecksumDB) { + var ( + base = "" + ucache = os.TempDir() + ) + for _, l := range csdb.allChecksums { + if strings.HasPrefix(l, "# https://") { + base = l[2:] + continue + } + if strings.HasPrefix(l, "#") { + continue + } + hashFile := strings.Split(l, " ") + if len(hashFile) != 2 { + continue + } + file := hashFile[1] + url := base + file + dst := filepath.Join(ucache, file) + if err := csdb.DownloadFile(url, dst); err != nil { + log.Print(err) + } + } +} diff --git a/internal/build/util.go b/internal/build/util.go index 5c77b236d..b41014a16 100644 --- a/internal/build/util.go +++ b/internal/build/util.go @@ -68,6 +68,27 @@ func MustRunCommand(cmd string, args ...string) { MustRun(exec.Command(cmd, args...)) } +// MustRunCommandWithOutput runs the given command, and ensures that some output will be +// printed while it runs. This is useful for CI builds where the process will be stopped +// when there is no output. +func MustRunCommandWithOutput(cmd string, args ...string) { + interval := time.NewTicker(time.Minute) + done := make(chan struct{}) + defer interval.Stop() + defer close(done) + go func() { + for { + select { + case <-interval.C: + fmt.Printf("Waiting for command %q\n", cmd) + case <-done: + return + } + } + }() + MustRun(exec.Command(cmd, args...)) +} + var warnedAboutGit bool // RunGit runs a git subcommand and returns its output. diff --git a/internal/cmdtest/test_cmd.go b/internal/cmdtest/test_cmd.go index 43137053c..4890d0b7c 100644 --- a/internal/cmdtest/test_cmd.go +++ b/internal/cmdtest/test_cmd.go @@ -32,7 +32,7 @@ import ( "text/template" "time" - "github.com/docker/docker/pkg/reexec" + "github.com/ethereum/go-ethereum/internal/reexec" ) func NewTestCmd(t *testing.T, data interface{}) *TestCmd { diff --git a/internal/debug/api.go b/internal/debug/api.go index 42d0fa15e..482989e0d 100644 --- a/internal/debug/api.go +++ b/internal/debug/api.go @@ -37,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/hashicorp/go-bexpr" + "golang.org/x/exp/slog" ) // Handler is the global debugging handler. @@ -56,7 +57,7 @@ type HandlerT struct { // Verbosity sets the log verbosity ceiling. The verbosity of individual packages // and source files can be raised using Vmodule. func (*HandlerT) Verbosity(level int) { - glogger.Verbosity(log.Lvl(level)) + glogger.Verbosity(slog.Level(level)) } // Vmodule sets the log verbosity pattern. See package log for details on the @@ -65,12 +66,6 @@ func (*HandlerT) Vmodule(pattern string) error { return glogger.Vmodule(pattern) } -// BacktraceAt sets the log backtrace location. See package log for details on -// the pattern syntax. -func (*HandlerT) BacktraceAt(location string) error { - return glogger.BacktraceAt(location) -} - // MemStats returns detailed runtime memory statistics. func (*HandlerT) MemStats() *runtime.MemStats { s := new(runtime.MemStats) diff --git a/internal/debug/flags.go b/internal/debug/flags.go index a28ba784d..23e4745e8 100644 --- a/internal/debug/flags.go +++ b/internal/debug/flags.go @@ -34,6 +34,7 @@ import ( "github.com/mattn/go-colorable" "github.com/mattn/go-isatty" "github.com/urfave/cli/v2" + "golang.org/x/exp/slog" "gopkg.in/natefinch/lumberjack.v2" ) @@ -75,20 +76,10 @@ var ( Usage: "Write logs to a file", Category: flags.LoggingCategory, } - backtraceAtFlag = &cli.StringFlag{ - Name: "log.backtrace", - Usage: "Request a stack trace at a specific logging statement (e.g. \"block.go:271\")", - Value: "", - Category: flags.LoggingCategory, - } - debugFlag = &cli.BoolFlag{ - Name: "log.debug", - Usage: "Prepends log messages with call-site location (file and line number)", - 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", @@ -159,8 +150,6 @@ var Flags = []cli.Flag{ verbosityFlag, logVmoduleFlag, vmoduleFlag, - backtraceAtFlag, - debugFlag, logjsonFlag, logFormatFlag, logFileFlag, @@ -178,46 +167,37 @@ var Flags = []cli.Flag{ traceFlag, } -var glogger *log.GlogHandler +var ( + glogger *log.GlogHandler + logOutputFile io.WriteCloser + defaultTerminalHandler *log.TerminalHandler +) func init() { - glogger = log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false))) + defaultTerminalHandler = log.NewTerminalHandler(os.Stderr, false) + glogger = log.NewGlogHandler(defaultTerminalHandler) glogger.Verbosity(log.LvlInfo) - log.Root().SetHandler(glogger) + log.SetDefault(log.NewLogger(glogger)) +} + +func ResetLogging() { + if defaultTerminalHandler != nil { + defaultTerminalHandler.ResetFieldPadding() + } } // Setup initializes profiling and logging based on the CLI flags. // It should be called as early as possible in the program. func Setup(ctx *cli.Context) error { var ( - logfmt log.Format - output = io.Writer(os.Stderr) - logFmtFlag = ctx.String(logFormatFlag.Name) + handler slog.Handler + terminalOutput = io.Writer(os.Stderr) + output io.Writer + logFmtFlag = ctx.String(logFormatFlag.Name) ) - switch { - case ctx.Bool(logjsonFlag.Name): - // Retain backwards compatibility with `--log.json` flag if `--log.format` not set - defer log.Warn("The flag '--log.json' is deprecated, please use '--log.format=json' instead") - logfmt = log.JSONFormat() - case logFmtFlag == "json": - logfmt = log.JSONFormat() - case logFmtFlag == "logfmt": - logfmt = log.LogfmtFormat() - case logFmtFlag == "", logFmtFlag == "terminal": - useColor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" - if useColor { - output = colorable.NewColorableStderr() - } - logfmt = log.TerminalFormat(useColor) - default: - // Unknown log format specified - return fmt.Errorf("unknown log format: %v", ctx.String(logFormatFlag.Name)) - } var ( - stdHandler = log.StreamHandler(output, logfmt) - ostream = stdHandler - logFile = ctx.String(logFileFlag.Name) - rotation = ctx.Bool(logRotateFlag.Name) + logFile = ctx.String(logFileFlag.Name) + rotation = ctx.Bool(logRotateFlag.Name) ) if len(logFile) > 0 { if err := validateLogLocation(filepath.Dir(logFile)); err != nil { @@ -238,26 +218,55 @@ func Setup(ctx *cli.Context) error { } else { context = append(context, "location", filepath.Join(os.TempDir(), "geth-lumberjack.log")) } - ostream = log.MultiHandler(log.StreamHandler(&lumberjack.Logger{ + logOutputFile = &lumberjack.Logger{ Filename: logFile, MaxSize: ctx.Int(logMaxSizeMBsFlag.Name), MaxBackups: ctx.Int(logMaxBackupsFlag.Name), MaxAge: ctx.Int(logMaxAgeFlag.Name), Compress: ctx.Bool(logCompressFlag.Name), - }, logfmt), stdHandler) + } + output = io.MultiWriter(terminalOutput, logOutputFile) } else if logFile != "" { - if logOutputStream, err := log.FileHandler(logFile, logfmt); err != nil { + var err error + if logOutputFile, err = os.OpenFile(logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644); err != nil { return err - } else { - ostream = log.MultiHandler(logOutputStream, stdHandler) - context = append(context, "location", logFile) } + output = io.MultiWriter(logOutputFile, terminalOutput) + context = append(context, "location", logFile) + } else { + output = terminalOutput + } + + switch { + case ctx.Bool(logjsonFlag.Name): + // Retain backwards compatibility with `--log.json` flag if `--log.format` not set + defer log.Warn("The flag '--log.json' is deprecated, please use '--log.format=json' instead") + handler = log.JSONHandler(output) + case logFmtFlag == "json": + handler = log.JSONHandler(output) + case logFmtFlag == "logfmt": + handler = log.LogfmtHandler(output) + case logFmtFlag == "", logFmtFlag == "terminal": + useColor := (isatty.IsTerminal(os.Stderr.Fd()) || isatty.IsCygwinTerminal(os.Stderr.Fd())) && os.Getenv("TERM") != "dumb" + if useColor { + terminalOutput = colorable.NewColorableStderr() + if logOutputFile != nil { + output = io.MultiWriter(logOutputFile, terminalOutput) + } else { + output = terminalOutput + } + } + handler = log.NewTerminalHandler(output, useColor) + default: + // Unknown log format specified + return fmt.Errorf("unknown log format: %v", ctx.String(logFormatFlag.Name)) } - glogger.SetHandler(ostream) + + glogger = log.NewGlogHandler(handler) // logging - verbosity := ctx.Int(verbosityFlag.Name) - glogger.Verbosity(log.Lvl(verbosity)) + verbosity := log.FromLegacyLevel(ctx.Int(verbosityFlag.Name)) + glogger.Verbosity(verbosity) vmodule := ctx.String(logVmoduleFlag.Name) if vmodule == "" { // Retain backwards compatibility with `--vmodule` flag if `--log.vmodule` not set @@ -268,16 +277,7 @@ func Setup(ctx *cli.Context) error { } glogger.Vmodule(vmodule) - debug := ctx.Bool(debugFlag.Name) - if ctx.IsSet(debugFlag.Name) { - debug = ctx.Bool(debugFlag.Name) - } - log.PrintOrigins(debug) - - backtrace := ctx.String(backtraceAtFlag.Name) - glogger.BacktraceAt(backtrace) - - log.Root().SetHandler(glogger) + log.SetDefault(log.NewLogger(glogger)) // profiling, tracing runtime.MemProfileRate = memprofilerateFlag.Value @@ -337,6 +337,9 @@ func StartPProf(address string, withMetrics bool) { func Exit() { Handler.StopCPUProfile() Handler.StopGoTrace() + if logOutputFile != nil { + logOutputFile.Close() + } } func validateLogLocation(path string) error { diff --git a/internal/debug/loudpanic.go b/internal/debug/loudpanic.go index 86e6bc88f..a7296e7b3 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/trace.go b/internal/debug/trace.go index eea879823..e291030b8 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 52dbaf164..c0b28e4b6 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -40,15 +40,21 @@ import ( "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/gasestimator" "github.com/ethereum/go-ethereum/eth/tracers/logger" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" "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" ) +// estimateGasErrorRatio is the amount of overestimation eth_estimateGas is +// allowed to produce in order to speed up calculations. +const estimateGasErrorRatio = 0.015 + // EthereumAPI provides an API to access Ethereum related information. type EthereumAPI struct { b Backend @@ -674,9 +680,6 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st 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 { @@ -686,61 +689,65 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st return nil, err } } - - state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) - if state == nil || err != nil { - return nil, err - } - if storageTrie, err = state.StorageTrie(address); err != nil { + statedb, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if statedb == nil || err != nil { return nil, err } + codeHash := statedb.GetCodeHash(address) + storageRoot := statedb.GetStorageRoot(address) - // 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() - 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[:]) - } - - if storageTrie == nil { - storageProof[i] = StorageResult{outputKey, &hexutil.Big{}, []string{}} - continue + if len(keys) > 0 { + var storageTrie state.Trie + if storageRoot != types.EmptyRootHash && storageRoot != (common.Hash{}) { + id := trie.StorageTrieID(header.Root, crypto.Keccak256Hash(address.Bytes()), storageRoot) + st, err := trie.NewStateTrie(id, statedb.Database().TrieDB()) + if err != nil { + return nil, err + } + storageTrie = st } - var proof proofList - if err := storageTrie.Prove(crypto.Keccak256(key.Bytes()), &proof); err != nil { - return nil, err + // 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[:]) + } + if storageTrie == nil { + storageProof[i] = StorageResult{outputKey, &hexutil.Big{}, []string{}} + continue + } + var proof proofList + if err := storageTrie.Prove(crypto.Keccak256(key.Bytes()), &proof); err != nil { + return nil, err + } + value := (*hexutil.Big)(statedb.GetState(address, key).Big()) + storageProof[i] = StorageResult{outputKey, value, proof} } - 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 + tr, err := trie.NewStateTrie(trie.StateTrieID(header.Root), statedb.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), - Balance: (*hexutil.Big)(state.GetBalance(address)), + AccountProof: accountProof, + Balance: (*hexutil.Big)(statedb.GetBalance(address)), CodeHash: codeHash, - Nonce: hexutil.Uint64(state.GetNonce(address)), - StorageHash: storageHash, + Nonce: hexutil.Uint64(statedb.GetNonce(address)), + StorageHash: storageRoot, StorageProof: storageProof, - }, state.Error() + }, statedb.Error() } // decodeHash parses a hex-encoded 32-byte hash. The input may optionally @@ -897,6 +904,34 @@ 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] = marshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i) + } + + 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 @@ -955,13 +990,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. @@ -990,6 +1026,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. @@ -1049,7 +1088,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S if blockOverrides != nil { blockOverrides.Apply(&blockCtx) } - evm, vmError := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx) + evm := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx) // Wait for the context to be done and cancel the evm. Even if the // EVM has finished, cancelling may be done (repeatedly) @@ -1061,7 +1100,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S // Execute the message. gp := new(core.GasPool).AddGas(math.MaxUint64) result, err := core.ApplyMessage(evm, msg, gp) - if err := vmError(); err != nil { + if err := state.Error(); err != nil { return nil, err } @@ -1086,15 +1125,16 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash return doCall(ctx, b, args, state, header, overrides, blockOverrides, timeout, globalGasCap) } -func newRevertError(result *core.ExecutionResult) *revertError { - reason, errUnpack := abi.UnpackRevert(result.Revert()) - err := errors.New("execution reverted") +func newRevertError(revert []byte) *revertError { + err := vm.ErrExecutionReverted + + reason, errUnpack := abi.UnpackRevert(revert) if errUnpack == nil { - err = fmt.Errorf("execution reverted: %v", reason) + err = fmt.Errorf("%w: %v", vm.ErrExecutionReverted, reason) } return &revertError{ error: err, - reason: hexutil.Encode(result.Revert()), + reason: hexutil.Encode(revert), } } @@ -1122,153 +1162,63 @@ 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()) +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()) if err != nil { return nil, err } // If the result contains a revert reason, try to unpack and return it. if len(result.Revert()) > 0 { - return nil, newRevertError(result) + return nil, newRevertError(result.Revert()) } return result.Return(), result.Err } +// 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 requirement, as it may be higher than the amount used - var ( - lo uint64 = params.TxGas - 1 - hi uint64 - cap uint64 - ) - // Use zero address if sender unspecified. - if args.From == nil { - args.From = new(common.Address) - } - // Determine the highest gas limit can be used during the estimation. - if args.Gas != nil && uint64(*args.Gas) >= params.TxGas { - hi = uint64(*args.Gas) - } else { - // Retrieve the block to act as the gas ceiling - block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash) - if err != nil { - return 0, err - } - if block == nil { - return 0, errors.New("block not found") - } - hi = block.GasLimit() - } - // Normalize the max fee per gas the call is willing to spend. - var feeCap *big.Int - if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { - return 0, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") - } else if args.GasPrice != nil { - feeCap = args.GasPrice.ToInt() - } else if args.MaxFeePerGas != nil { - feeCap = args.MaxFeePerGas.ToInt() - } else { - feeCap = common.Big0 - } - // 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 - } - err = overrides.Apply(state) - 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 { - if args.Value.ToInt().Cmp(available) >= 0 { - return 0, core.ErrInsufficientFundsForTransfer - } - available.Sub(available, args.Value.ToInt()) - } - allowance := new(big.Int).Div(available, feeCap) - - // If the allowance is larger than maximum uint64, skip checking - if allowance.IsUint64() && hi > allowance.Uint64() { - transfer := args.Value - if transfer == nil { - transfer = new(hexutil.Big) - } - log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance, - "sent", transfer.ToInt(), "maxFeePerGas", feeCap, "fundable", allowance) - hi = allowance.Uint64() - } - } - // Recap the highest gas allowance with specified gascap. - if gasCap != 0 && hi > gasCap { - 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, state *state.StateDB, header *types.Header) (bool, *core.ExecutionResult, error) { - args.Gas = (*hexutil.Uint64)(&gas) - - result, err := doCall(ctx, b, args, state, header, nil, nil, 0, gasCap) - 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 - } + // Retrieve the base state and mutate it with any overrides state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) if state == nil || err != nil { return 0, err } - err = overrides.Apply(state) - if err != nil { + if err = overrides.Apply(state); err != nil { return 0, err } - // Execute the binary search and hone in on an executable gas limit - for lo+1 < hi { - s := state.Copy() - mid := (hi + lo) / 2 - failed, _, err := executable(mid, s, header) - - // 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 err != nil { - return 0, err - } - if failed { - lo = mid - } else { - hi = mid - } + // Construct the gas estimator option from the user input + opts := &gasestimator.Options{ + Config: b.ChainConfig(), + Chain: NewChainContext(ctx, b), + Header: header, + State: state, + ErrorRatio: estimateGasErrorRatio, } - // Reject the transaction as invalid if it still fails at the highest allowance - if hi == cap { - failed, result, err := executable(hi, state, header) - 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) + // Run the gas estimation andwrap any revertals into a custom return + call, err := args.ToMessage(gasCap, header.BaseFee) + if err != nil { + return 0, err + } + estimate, revert, err := gasestimator.Estimate(ctx, call, opts, gasCap) + if err != nil { + if len(revert) > 0 { + return 0, newRevertError(revert) } + return 0, err } - return hexutil.Uint64(hi), nil + return hexutil.Uint64(estimate), nil } -// EstimateGas returns an estimate of the amount of gas needed to execute the -// given transaction against the current pending block. +// 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.LatestBlockNumber) if blockNrOrHash != nil { @@ -1297,15 +1247,21 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} { "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 } @@ -1364,26 +1320,28 @@ func (s *BlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, inc // 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"` - YParity *hexutil.Uint64 `json:"yParity,omitempty"` + 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"` } // newRPCTransaction returns a transaction that will serialize to the RPC @@ -1437,15 +1395,43 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber // if the transaction has been mined, compute the effective gas price if baseFee != nil && blockHash != (common.Hash{}) { // price = min(gasTipCap + baseFee, gasFeeCap) - 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()) } + + 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{}) { + result.GasPrice = (*hexutil.Big)(effectiveGasPrice(tx, baseFee)) + } else { + result.GasPrice = (*hexutil.Big)(tx.GasFeeCap()) + } + result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap()) + result.BlobVersionedHashes = tx.BlobHashes() } 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 ( @@ -1558,7 +1544,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH // Apply the transaction with the access list tracer tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles) config := vm.Config{Tracer: tracer, NoBaseFee: true} - vmenv, _ := b.GetEVM(ctx, msg, statedb, header, &config, nil) + vmenv := b.GetEVM(ctx, msg, statedb, header, &config, nil) res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.GasLimit)) if err != nil { return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err) @@ -1717,13 +1703,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(receipt, blockHash, blockNumber, signer, tx, int(index)), nil +} + +// marshalReceipt marshals a transaction receipt into a JSON object. +func marshalReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, signer types.Signer, tx *types.Transaction, txIndex int) map[string]interface{} { 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), @@ -1745,11 +1736,16 @@ 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 } - return fields, nil + return fields } // sign is a helper function that signs a transaction with the private key of the given address. @@ -2097,20 +2093,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 } @@ -2163,12 +2162,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 b905274d0..f3d86a7d3 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -23,6 +23,8 @@ import ( "errors" "fmt" "math/big" + "os" + "path/filepath" "reflect" "testing" "time" @@ -32,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" @@ -45,19 +48,18 @@ import ( "github.com/ethereum/go-ethereum/internal/blocktest" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/assert" + "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) @@ -88,6 +90,23 @@ func TestTransaction_RoundTripRpcJSON(t *testing.T) { } } +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 @@ -338,15 +357,60 @@ func allTransactionTypes(addr common.Address, config *params.ChainConfig) []txDa } } +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 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() cacheConfig = &core.CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, @@ -472,8 +536,7 @@ func (b testBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { } return big.NewInt(1) } -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 } +func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext) *vm.EVM { if vmConfig == nil { vmConfig = b.chain.GetVMConfig() } @@ -482,7 +545,7 @@ func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state if blockContext != nil { context = *blockContext } - return vm.NewEVM(context, txContext, state, b.chain.Config(), *vmConfig), vmError + return vm.NewEVM(context, txContext, state, b.chain.Config(), *vmConfig) } func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { panic("implement me") @@ -551,7 +614,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 @@ -615,6 +678,47 @@ func TestEstimateGas(t *testing.T) { }, expectErr: core.ErrInsufficientFunds, }, + // Test for a bug where the gas price was set to zero but the basefee non-zero + // + // contract BasefeeChecker { + // constructor() { + // require(tx.gasprice >= block.basefee); + // if (tx.gasprice > 0) { + // require(block.basefee > 0); + // } + // } + //} + { + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{ + From: &accounts[0].addr, + Input: hex2Bytes("6080604052348015600f57600080fd5b50483a1015601c57600080fd5b60003a111560315760004811603057600080fd5b5b603f80603e6000396000f3fe6080604052600080fdfea264697066735822122060729c2cee02b10748fae5200f1c9da4661963354973d9154c13a8e9ce9dee1564736f6c63430008130033"), + GasPrice: (*hexutil.Big)(big.NewInt(1_000_000_000)), // Legacy as pricing + }, + expectErr: nil, + want: 67617, + }, + { + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{ + From: &accounts[0].addr, + Input: hex2Bytes("6080604052348015600f57600080fd5b50483a1015601c57600080fd5b60003a111560315760004811603057600080fd5b5b603f80603e6000396000f3fe6080604052600080fdfea264697066735822122060729c2cee02b10748fae5200f1c9da4661963354973d9154c13a8e9ce9dee1564736f6c63430008130033"), + MaxFeePerGas: (*hexutil.Big)(big.NewInt(1_000_000_000)), // 1559 gas pricing + }, + expectErr: nil, + want: 67617, + }, + { + blockNumber: rpc.LatestBlockNumber, + call: TransactionArgs{ + From: &accounts[0].addr, + Input: hex2Bytes("6080604052348015600f57600080fd5b50483a1015601c57600080fd5b60003a111560315760004811603057600080fd5b5b603f80603e6000396000f3fe6080604052600080fdfea264697066735822122060729c2cee02b10748fae5200f1c9da4661963354973d9154c13a8e9ce9dee1564736f6c63430008130033"), + GasPrice: nil, // No legacy gas pricing + MaxFeePerGas: nil, // No 1559 gas pricing + }, + expectErr: nil, + want: 67595, + }, } for i, tc := range testSuite { result, err := api.EstimateGas(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides) @@ -632,7 +736,7 @@ func TestEstimateGas(t *testing.T) { t.Errorf("test %d: want no error, have %v", i, err) continue } - if uint64(result) != tc.want { + if float64(result) > float64(tc.want)*(1+estimateGasErrorRatio) { t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, uint64(result), tc.want) } } @@ -654,7 +758,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 @@ -783,7 +887,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) @@ -807,18 +911,18 @@ func TestCall(t *testing.T) { } } -type Account struct { +type account struct { key *ecdsa.PrivateKey addr common.Address } -func newAccounts(n int) (accounts []Account) { +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}) + accounts = append(accounts, account{key: key, addr: addr}) } - slices.SortFunc(accounts, func(a, b Account) int { return a.addr.Cmp(b.addr) }) + slices.SortFunc(accounts, func(a, b account) int { return a.addr.Cmp(b.addr) }) return accounts } @@ -1037,7 +1141,7 @@ func TestRPCMarshalBlock(t *testing.T) { t.Errorf("test %d: json marshal error: %v", i, err) continue } - assert.JSONEqf(t, tc.want, string(out), "test %d", i) + require.JSONEqf(t, tc.want, string(out), "test %d", i) } } @@ -1075,7 +1179,7 @@ func TestRPCGetBlockOrHeader(t *testing.T) { } 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, func(i int, b *core.BlockGen) { + 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 @@ -1100,628 +1204,156 @@ func TestRPCGetBlockOrHeader(t *testing.T) { blockHash *common.Hash fullTx bool reqHeader bool - want string + file string expectErr error }{ // 0. latest header { blockNumber: rpc.LatestBlockNumber, reqHeader: true, - want: `{ - "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" - }`, + file: "tag-latest", }, // 1. genesis header { blockNumber: rpc.BlockNumber(0), reqHeader: true, - want: `{ - "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" - }`, + file: "number-0", }, // 2. #1 header { blockNumber: rpc.BlockNumber(1), reqHeader: true, - want: `{ - "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" - }`, + file: "number-1", }, // 3. latest-1 header { blockNumber: rpc.BlockNumber(9), reqHeader: true, - want: `{ - "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" - }`, + file: "number-latest-1", }, // 4. latest+1 header { blockNumber: rpc.BlockNumber(11), reqHeader: true, - want: "null", + file: "number-latest+1", }, // 5. pending header { blockNumber: rpc.PendingBlockNumber, reqHeader: true, - want: `{ - "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" - }`, + file: "tag-pending", }, // 6. latest block { blockNumber: rpc.LatestBlockNumber, - want: `{ - "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": [] - }`, + file: "tag-latest", }, // 7. genesis block { blockNumber: rpc.BlockNumber(0), - want: `{ - "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": [] - }`, + file: "number-0", }, // 8. #1 block { blockNumber: rpc.BlockNumber(1), - want: `{ - "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": [] - }`, + file: "number-1", }, // 9. latest-1 block { blockNumber: rpc.BlockNumber(9), fullTx: true, - want: `{ - "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": [] - }`, + file: "number-latest-1", }, // 10. latest+1 block { blockNumber: rpc.BlockNumber(11), fullTx: true, - want: "null", + file: "number-latest+1", }, // 11. pending block { blockNumber: rpc.PendingBlockNumber, - want: `{ - "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", - "withdrawals": [ - { - "index": "0x0", - "validatorIndex": "0x1", - "address": "0x1234000000000000000000000000000000000000", - "amount": "0xa" - } - ], - "withdrawalsRoot": "0x73d756269cdfc22e7e17a3548e36f42f750ca06d7e3cd98d1b6d0eb5add9dc84", - "uncles": [] - }`, + file: "tag-pending", }, // 12. pending block + fullTx { blockNumber: rpc.PendingBlockNumber, fullTx: true, - want: `{ - "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" - }`, + file: "tag-pending-fullTx", }, // 13. latest header by hash { blockHash: &blockHashes[len(blockHashes)-1], reqHeader: true, - want: `{ - "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" - }`, + file: "hash-latest", }, // 14. genesis header by hash { blockHash: &blockHashes[0], reqHeader: true, - want: `{ - "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" - }`, + file: "hash-0", }, // 15. #1 header { blockHash: &blockHashes[1], reqHeader: true, - want: `{ - "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" - }`, + file: "hash-1", }, // 16. latest-1 header { blockHash: &blockHashes[len(blockHashes)-2], reqHeader: true, - want: `{ - "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" - }`, + file: "hash-latest-1", }, // 17. empty hash { blockHash: &common.Hash{}, reqHeader: true, - want: "null", + file: "hash-empty", }, // 18. pending hash { blockHash: &pendingHash, reqHeader: true, - want: `null`, + file: `hash-pending`, }, // 19. latest block { blockHash: &blockHashes[len(blockHashes)-1], - want: `{ - "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": [] - }`, + file: "hash-latest", }, // 20. genesis block { blockHash: &blockHashes[0], - want: `{ - "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": [] - }`, + file: "hash-genesis", }, // 21. #1 block { blockHash: &blockHashes[1], - want: `{ - "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": [] - }`, + file: "hash-1", }, // 22. latest-1 block { blockHash: &blockHashes[len(blockHashes)-2], fullTx: true, - want: `{ - "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": [] - }`, + file: "hash-latest-1-fullTx", }, // 23. empty hash + body { blockHash: &common.Hash{}, fullTx: true, - want: "null", + file: "hash-empty-fullTx", }, // 24. pending block { blockHash: &pendingHash, - want: `null`, + file: `hash-pending`, }, // 25. pending block + fullTx { blockHash: &pendingHash, fullTx: true, - want: `null`, + file: "hash-pending-fullTx", }, } @@ -1729,18 +1361,23 @@ func TestRPCGetBlockOrHeader(t *testing.T) { 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 { @@ -1757,20 +1394,15 @@ func TestRPCGetBlockOrHeader(t *testing.T) { t.Errorf("test %d: want no error, have %v", i, err) continue } - data, err := json.Marshal(result) - if err != nil { - t.Errorf("test %d: json marshal error", i) - continue - } - want, have := tt.want, string(data) - require.JSONEqf(t, want, have, "test %d: json not match, want: %s, have: %s", i, want, have) + + testRPCResponseWithFile(t, i, result, rpc, tt.file) } } -func TestRPCGetTransactionReceipt(t *testing.T) { - t.Parallel() - - // Initialize test accounts +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") @@ -1778,7 +1410,9 @@ func TestRPCGetTransactionReceipt(t *testing.T) { acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey) contract = common.HexToAddress("0000000000000000000000000000000000031ec7") genesis = &core.Genesis{ - Config: params.TestChainConfig, + Config: &config, + ExcessBlobGas: new(uint64), + BlobGasUsed: new(uint64), Alloc: core.GenesisAlloc{ acc1Addr: {Balance: big.NewInt(params.Ether)}, acc2Addr: {Balance: big.NewInt(params.Ether)}, @@ -1795,11 +1429,14 @@ func TestRPCGetTransactionReceipt(t *testing.T) { contract: {Balance: big.NewInt(params.Ether), Code: common.FromHex("0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063a9059cbb14610030575b600080fd5b61004a6004803603810190610045919061016a565b610060565b60405161005791906101c5565b60405180910390f35b60008273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516100bf91906101ef565b60405180910390a36001905092915050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610101826100d6565b9050919050565b610111816100f6565b811461011c57600080fd5b50565b60008135905061012e81610108565b92915050565b6000819050919050565b61014781610134565b811461015257600080fd5b50565b6000813590506101648161013e565b92915050565b60008060408385031215610181576101806100d1565b5b600061018f8582860161011f565b92505060206101a085828601610155565b9150509250929050565b60008115159050919050565b6101bf816101aa565b82525050565b60006020820190506101da60008301846101b6565b92915050565b6101e981610134565b82525050565b600060208201905061020460008301846101e0565b9291505056fea2646970667358221220b469033f4b77b9565ee84e0a2f04d496b18160d26034d54f9487e57788fd36d564736f6c63430008120033")}, }, } - genBlocks = 5 - signer = types.LatestSignerForChainID(params.TestChainConfig.ChainID) - txHashes = make([]common.Hash, genBlocks) + signer = types.LatestSignerForChainID(params.TestChainConfig.ChainID) + txHashes = make([]common.Hash, genBlocks) ) - backend := newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + + // 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 @@ -1830,6 +1467,20 @@ func TestRPCGetTransactionReceipt(t *testing.T) { 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) @@ -1838,147 +1489,62 @@ func TestRPCGetTransactionReceipt(t *testing.T) { b.AddTx(tx) txHashes[i] = tx.Hash() } + b.SetPoS() }) - api := NewTransactionAPI(backend, new(AddrLocker)) - 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() - } + 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 - want string + file string }{ // 0. normal success { txHash: txHashes[0], - want: `{ - "blockHash": "0x1356e49a24d4504e450b303aa770f4ae13c29b9ffacaea1d7dd4043396229dd9", - "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" - }`, + file: "normal-transfer-tx", }, // 1. create contract { txHash: txHashes[1], - want: `{ - "blockHash": "0x4fc27a4efa7fb8faa04b12b53ec8c8424ab4c21aab1323846365f000e8b4a594", - "blockNumber": "0x2", - "contractAddress": "0xae9bea628c4ce503dcfd7e305cab4e29e7476592", - "cumulativeGasUsed": "0xcf4e", - "effectiveGasPrice": "0x2db16291", - "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", - "gasUsed": "0xcf4e", - "logs": [], - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "status": "0x1", - "to": null, - "transactionHash": "0x340e58cda5086495010b571fe25067fecc9954dc4ee3cedece00691fa3f5904a", - "transactionIndex": "0x0", - "type": "0x0" - }`, + file: "create-contract-tx", }, // 2. with logs success { txHash: txHashes[2], - want: `{ - "blockHash": "0x73385c190219326907524b0020ef453ebc450eaa971ebce16f79e2d23e7e8d4d", - "blockNumber": "0x3", - "contractAddress": null, - "cumulativeGasUsed": "0x5e28", - "effectiveGasPrice": "0x281c2534", - "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", - "gasUsed": "0x5e28", - "logs": [ - { - "address": "0x0000000000000000000000000000000000031ec7", - "topics": [ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x000000000000000000000000703c4b2bd70c169f5717101caee543299fc946c7", - "0x0000000000000000000000000000000000000000000000000000000000000003" - ], - "data": "0x000000000000000000000000000000000000000000000000000000000000000d", - "blockNumber": "0x3", - "transactionHash": "0x9dbf43ec9afc8d711932618616471088f66ba4f25fd5c672d97473d02dae967f", - "transactionIndex": "0x0", - "blockHash": "0x73385c190219326907524b0020ef453ebc450eaa971ebce16f79e2d23e7e8d4d", - "logIndex": "0x0", - "removed": false - } - ], - "logsBloom": "0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000800000000000000008000000000000000000000000000000000020000000080000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000400000000002000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000", - "status": "0x1", - "to": "0x0000000000000000000000000000000000031ec7", - "transactionHash": "0x9dbf43ec9afc8d711932618616471088f66ba4f25fd5c672d97473d02dae967f", - "transactionIndex": "0x0", - "type": "0x0" - }`, + file: "with-logs", }, // 3. dynamic tx with logs success { txHash: txHashes[3], - want: `{ - "blockHash": "0x77c3f8919590e0e68db4ce74a3da3140ac3e96dd3d078a48db1da4c08b07503d", - "blockNumber": "0x4", - "contractAddress": null, - "cumulativeGasUsed": "0x538d", - "effectiveGasPrice": "0x2325c3e8", - "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", - "gasUsed": "0x538d", - "logs": [], - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "status": "0x0", - "to": "0x0000000000000000000000000000000000031ec7", - "transactionHash": "0x672e3e39adf23b5656989b7a36e54d54004b1866f53871113bc52e137edb9faf", - "transactionIndex": "0x0", - "type": "0x2" - }`, + file: `dynamic-tx-with-logs`, }, // 4. access list tx with create contract { txHash: txHashes[4], - want: `{ - "blockHash": "0x08e23d8e3711a21fbb8becd7de22fda8fb0a49fba14e1be763d00f99063627e1", - "blockNumber": "0x5", - "contractAddress": "0xfdaa97661a584d977b4d3abb5370766ff5b86a18", - "cumulativeGasUsed": "0xe01a", - "effectiveGasPrice": "0x1ecb3f75", - "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", - "gasUsed": "0xe01a", - "logs": [], - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "status": "0x1", - "to": null, - "transactionHash": "0x8f3c4e2663af0312d508ebd8587f0c88dccbbc8a9bcc322421ff4bc28c456a92", - "transactionIndex": "0x0", - "type": "0x1" - }`, + file: "create-contract-with-access-list", }, // 5. txhash empty { txHash: common.Hash{}, - want: `null`, + file: "txhash-empty", }, // 6. txhash not found { txHash: common.HexToHash("deadbeef"), - want: `null`, + file: "txhash-notfound", + }, + // 7. blob tx + { + txHash: txHashes[5], + file: "blob-tx", }, } @@ -1992,12 +1558,121 @@ func TestRPCGetTransactionReceipt(t *testing.T) { t.Errorf("test %d: want no error, have %v", i, err) continue } - data, err := json.Marshal(result) + 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: json marshal error", i) + t.Errorf("test %d: want no error, have %v", i, err) continue } - want, have := tt.want, string(data) - require.JSONEqf(t, want, have, "test %d: json not match, want: %s, have: %s", i, want, have) + 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 95ed7a8de..1a8616058 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -68,7 +68,7 @@ type Backend interface { PendingBlockAndReceipts() (*types.Block, types.Receipts) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) GetTd(ctx context.Context, hash common.Hash) *big.Int - GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) + GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription diff --git a/internal/ethapi/multicall_api.go b/internal/ethapi/multicall_api.go index 82bdc9bbc..530d76ba0 100644 --- a/internal/ethapi/multicall_api.go +++ b/internal/ethapi/multicall_api.go @@ -59,15 +59,7 @@ func (s *BlockChainAPI) Multicall(ctx context.Context, commonCallArgs Transactio // get a new instance of the EVM to be used once // ethapi's vmError callback always returns nil, so it is dropped here //GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) - evm, getVmErr := s.b.GetEVM(ctx, msg, state, header, nil, nil) - vmErr := getVmErr() - - if vmErr != nil { - // if we cannot retrieve the an EVM for any message, that failure - // implies a fault in the node as a whole, so we should give up on - // processing the entire request - return nil, vmErr - } + evm := s.b.GetEVM(ctx, msg, state, header, nil, nil) execResult, applyMsgErr := core.ApplyMessage(evm, msg, gp) 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 000000000..379636d5f --- /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 000000000..ec747fa47 --- /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 000000000..759dbf69e --- /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 000000000..3526da121 --- /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 000000000..32fee8326 --- /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 000000000..ec747fa47 --- /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 000000000..ec747fa47 --- /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 000000000..759dbf69e --- /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 000000000..379636d5f --- /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 000000000..ec747fa47 --- /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 000000000..3526da121 --- /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 000000000..32fee8326 --- /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 000000000..1d524d6ec --- /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 000000000..c0e2b07bb --- /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 000000000..ec747fa47 --- /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 000000000..591fab673 --- /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 000000000..f1e0db22c --- /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 000000000..520e30e4e --- /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 000000000..a71cf4b37 --- /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 000000000..3e16c3062 --- /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 000000000..ec747fa47 --- /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 000000000..ec747fa47 --- /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 000000000..0637a088a --- /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 000000000..0637a088a --- /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 000000000..0637a088a --- /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 000000000..591fab673 --- /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 000000000..dc61aa9a2 --- /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 000000000..c1dc70f64 --- /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 000000000..ec747fa47 --- /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 000000000..a63ff8670 --- /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 000000000..f2affcc1c --- /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 000000000..ec747fa47 --- /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 000000000..dc61aa9a2 --- /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 000000000..c1dc70f64 --- /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 000000000..ec747fa47 --- /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 000000000..a63ff8670 --- /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 000000000..f2affcc1c --- /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 000000000..da177f218 --- /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 000000000..c3a4a0dee --- /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 000000000..ad6d6152e --- /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 000000000..b3362260a --- /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 000000000..cc0be1809 --- /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 000000000..d3b6ef1c9 --- /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 000000000..ec747fa47 --- /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 000000000..ec747fa47 --- /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 000000000..45a4f6d67 --- /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 e4cf81a3f..aaf2c05d8 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -137,20 +137,35 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b Backend) erro if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { return errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") } - // If the tx has completely specified a fee mechanism, no default is needed. This allows users - // who are not yet synced past London to get defaults for other tx values. See - // https://github.com/ethereum/go-ethereum/pull/23274 for more information. + // If the tx has completely specified a fee mechanism, no default is needed. + // This allows users who are not yet synced past London to get defaults for + // other tx values. See https://github.com/ethereum/go-ethereum/pull/23274 + // for more information. eip1559ParamsSet := args.MaxFeePerGas != nil && args.MaxPriorityFeePerGas != nil - if (args.GasPrice != nil && !eip1559ParamsSet) || (args.GasPrice == nil && eip1559ParamsSet) { - // Sanity check the EIP-1559 fee parameters if present. - if args.GasPrice == nil && args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 { + + // Sanity check the EIP-1559 fee parameters if present. + if args.GasPrice == nil && eip1559ParamsSet { + if args.MaxFeePerGas.ToInt().Sign() == 0 { + return errors.New("maxFeePerGas must be non-zero") + } + if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 { return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas) } - return nil + return nil // No need to set anything, user already set MaxFeePerGas and MaxPriorityFeePerGas } - // Now attempt to fill in default value depending on whether London is active or not. + // Sanity check the non-EIP-1559 fee parameters. head := b.CurrentHeader() - if b.ChainConfig().IsLondon(head.Number) { + isLondon := b.ChainConfig().IsLondon(head.Number) + if args.GasPrice != nil && !eip1559ParamsSet { + // Zero gas-price is not allowed after London fork + if args.GasPrice.ToInt().Sign() == 0 && isLondon { + return errors.New("gasPrice must be non-zero after london fork") + } + return nil // No need to set anything, user already set GasPrice + } + + // Now attempt to fill in default value depending on whether London is active or not. + if isLondon { // London is active, set maxPriorityFeePerGas and maxFeePerGas. if err := args.setLondonFeeDefaults(ctx, head, b); err != nil { return err diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 1f79555af..426cbee38 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -52,6 +52,7 @@ func TestSetFeeDefaults(t *testing.T) { var ( b = newBackendMock() + zero = (*hexutil.Big)(big.NewInt(0)) fortytwo = (*hexutil.Big)(big.NewInt(42)) maxFee = (*hexutil.Big)(new(big.Int).Add(new(big.Int).Mul(b.current.BaseFee, big.NewInt(2)), fortytwo.ToInt())) al = &types.AccessList{types.AccessTuple{Address: common.Address{0xaa}, StorageKeys: []common.Hash{{0x01}}}} @@ -66,6 +67,13 @@ func TestSetFeeDefaults(t *testing.T) { &TransactionArgs{GasPrice: fortytwo}, nil, }, + { + "legacy tx pre-London with zero price", + false, + &TransactionArgs{GasPrice: zero}, + &TransactionArgs{GasPrice: zero}, + nil, + }, { "legacy tx post-London, explicit gas price", true, @@ -73,6 +81,13 @@ func TestSetFeeDefaults(t *testing.T) { &TransactionArgs{GasPrice: fortytwo}, nil, }, + { + "legacy tx post-London with zero price", + true, + &TransactionArgs{GasPrice: zero}, + nil, + errors.New("gasPrice must be non-zero after london fork"), + }, // Access list txs { @@ -161,6 +176,13 @@ func TestSetFeeDefaults(t *testing.T) { nil, errors.New("maxFeePerGas (0x7) < maxPriorityFeePerGas (0x2a)"), }, + { + "dynamic fee tx post-London, explicit gas price", + true, + &TransactionArgs{MaxFeePerGas: zero, MaxPriorityFeePerGas: zero}, + nil, + errors.New("maxFeePerGas must be non-zero"), + }, // Misc { @@ -306,8 +328,8 @@ func (b *backendMock) GetLogs(ctx context.Context, blockHash common.Hash, number return nil, nil } func (b *backendMock) GetTd(ctx context.Context, hash common.Hash) *big.Int { return nil } -func (b *backendMock) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) { - return nil, nil +func (b *backendMock) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM { + return nil } func (b *backendMock) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { return nil } func (b *backendMock) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { diff --git a/internal/flags/categories.go b/internal/flags/categories.go index ac7193187..3ff076792 100644 --- a/internal/flags/categories.go +++ b/internal/flags/categories.go @@ -22,7 +22,7 @@ const ( EthCategory = "ETHEREUM" LightCategory = "LIGHT CLIENT" DevCategory = "DEVELOPER CHAIN" - EthashCategory = "ETHASH" + StateCategory = "STATE HISTORY MANAGEMENT" TxPoolCategory = "TRANSACTION POOL (EVM)" BlobPoolCategory = "TRANSACTION POOL (BLOB)" PerfCategory = "PERFORMANCE TUNING" @@ -35,6 +35,7 @@ const ( LoggingCategory = "LOGGING AND DEBUGGING" MetricsCategory = "METRICS AND STATS" MiscCategory = "MISC" + TestingCategory = "TESTING" DeprecatedCategory = "ALIASED (deprecated)" ) diff --git a/internal/flags/customflags.go b/internal/flags/customflags.go index 3d6e7acd9..58672ab7b 100644 --- a/internal/flags/customflags.go +++ b/internal/flags/customflags.go @@ -4,11 +4,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" @@ -44,6 +46,7 @@ type DirectoryFlag struct { Value DirectoryString Aliases []string + EnvVars []string } // For cli.Flag: @@ -55,6 +58,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) }) @@ -78,7 +89,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 != "" { @@ -124,6 +135,7 @@ type TextMarshalerFlag struct { Value TextMarshaler Aliases []string + EnvVars []string } // For cli.Flag: @@ -133,6 +145,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) }) @@ -155,7 +177,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() @@ -197,6 +219,7 @@ type BigFlag struct { Value *big.Int Aliases []string + EnvVars []string } // For cli.Flag: @@ -206,6 +229,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) @@ -231,7 +264,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 f210e729d..369a931e8 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() @@ -32,7 +41,7 @@ func NewApp(usage string) *cli.App { app.EnableBashCompletion = true app.Version = params.VersionWithCommit(git.Commit, git.Date) app.Usage = usage - app.Copyright = "Copyright 2013-2023 The go-ethereum Authors" + app.Copyright = "Copyright 2013-2024 The go-ethereum Authors" app.Before = func(ctx *cli.Context) error { MigrateGlobalFlags(ctx) return nil @@ -96,7 +105,7 @@ func MigrateGlobalFlags(ctx *cli.Context) { func doMigrateFlags(ctx *cli.Context) { // Figure out if there are any aliases of commands. If there are, we want // to ignore them when iterating over the flags. - var aliases = make(map[string]bool) + aliases := make(map[string]bool) for _, fl := range ctx.Command.Flags { for _, alias := range fl.Names()[1:] { aliases[alias] = true @@ -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,87 @@ 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.StringSliceFlag: + 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.Int64Flag: + flag.EnvVars = append(flag.EnvVars, envvar) + + case *cli.Uint64Flag: + flag.EnvVars = append(flag.EnvVars, envvar) + + case *cli.Float64Flag: + 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 7a09fddab..f23c65584 100644 --- a/internal/jsre/deps/web3.js +++ b/internal/jsre/deps/web3.js @@ -1033,7 +1033,7 @@ var formatOutputInt = function (param) { * * @method formatOutputUInt * @param {SolidityParam} - * @returns {BigNumeber} right-aligned output bytes formatted to uint + * @returns {BigNumber} right-aligned output bytes formatted to uint */ var formatOutputUInt = function (param) { var value = param.staticPart() || "0"; diff --git a/internal/reexec/reexec.go b/internal/reexec/reexec.go new file mode 100644 index 000000000..af8d34798 --- /dev/null +++ b/internal/reexec/reexec.go @@ -0,0 +1,35 @@ +// This file originates from Docker/Moby, +// https://github.com/moby/moby/blob/master/pkg/reexec/reexec.go +// Licensed under Apache License 2.0: https://github.com/moby/moby/blob/master/LICENSE +// Copyright 2013-2018 Docker, Inc. +// +// Package reexec facilitates the busybox style reexec of the docker binary that +// we require because of the forking limitations of using Go. Handlers can be +// registered with a name and the argv 0 of the exec of the binary will be used +// to find and execute custom init paths. +package reexec + +import ( + "fmt" + "os" +) + +var registeredInitializers = make(map[string]func()) + +// Register adds an initialization func under the specified name +func Register(name string, initializer func()) { + if _, exists := registeredInitializers[name]; exists { + panic(fmt.Sprintf("reexec func already registered under name %q", name)) + } + registeredInitializers[name] = initializer +} + +// Init is called as the first part of the exec process and returns true if an +// initialization function was called. +func Init() bool { + if initializer, ok := registeredInitializers[os.Args[0]]; ok { + initializer() + return true + } + return false +} diff --git a/internal/reexec/self_linux.go b/internal/reexec/self_linux.go new file mode 100644 index 000000000..956d09326 --- /dev/null +++ b/internal/reexec/self_linux.go @@ -0,0 +1,14 @@ +// This file originates from Docker/Moby, +// https://github.com/moby/moby/blob/master/pkg/reexec/ +// Licensed under Apache License 2.0: https://github.com/moby/moby/blob/master/LICENSE +// Copyright 2013-2018 Docker, Inc. + +//go:build linux + +package reexec + +// Self returns the path to the current process's binary. +// Returns "/proc/self/exe". +func Self() string { + return "/proc/self/exe" +} diff --git a/internal/reexec/self_others.go b/internal/reexec/self_others.go new file mode 100644 index 000000000..a9f502ca8 --- /dev/null +++ b/internal/reexec/self_others.go @@ -0,0 +1,32 @@ +// This file originates from Docker/Moby, +// https://github.com/moby/moby/blob/master/pkg/reexec/ +// Licensed under Apache License 2.0: https://github.com/moby/moby/blob/master/LICENSE +// Copyright 2013-2018 Docker, Inc. + +//go:build !linux + +package reexec + +import ( + "os" + "os/exec" + "path/filepath" +) + +// Self returns the path to the current process's binary. +// Uses os.Args[0]. +func Self() string { + name := os.Args[0] + if filepath.Base(name) == name { + if lp, err := exec.LookPath(name); err == nil { + return lp + } + } + // handle conversion of relative paths to absolute + if absName, err := filepath.Abs(name); err == nil { + return absName + } + // if we couldn't get absolute name, return original + // (NOTE: Go only errors on Abs() if os.Getwd fails) + return name +} diff --git a/internal/testlog/testlog.go b/internal/testlog/testlog.go index 684339f16..037b7ee9c 100644 --- a/internal/testlog/testlog.go +++ b/internal/testlog/testlog.go @@ -18,26 +18,19 @@ package testlog import ( + "bytes" + "context" + "fmt" "sync" "testing" "github.com/ethereum/go-ethereum/log" + "golang.org/x/exp/slog" ) -// Handler returns a log handler which logs to the unit test log of t. -func Handler(t *testing.T, level log.Lvl) log.Handler { - return log.LvlFilterHandler(level, &handler{t, log.TerminalFormat(false)}) -} - -type handler struct { - t *testing.T - fmt log.Format -} - -func (h *handler) Log(r *log.Record) error { - h.t.Logf("%s", h.fmt.Format(r)) - return nil -} +const ( + termTimeFormat = "01-02|15:04:05.000" +) // logger implements log.Logger such that all output goes to the unit test log via // t.Logf(). All methods in between logger.Trace, logger.Debug, etc. are marked as test @@ -51,25 +44,64 @@ type logger struct { } type bufHandler struct { - buf []*log.Record - fmt log.Format + buf []slog.Record + attrs []slog.Attr + level slog.Level } -func (h *bufHandler) Log(r *log.Record) error { +func (h *bufHandler) Handle(_ context.Context, r slog.Record) error { h.buf = append(h.buf, r) return nil } +func (h *bufHandler) Enabled(_ context.Context, lvl slog.Level) bool { + return lvl <= h.level +} + +func (h *bufHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + records := make([]slog.Record, len(h.buf)) + copy(records[:], h.buf[:]) + return &bufHandler{ + records, + append(h.attrs, attrs...), + h.level, + } +} + +func (h *bufHandler) WithGroup(_ string) slog.Handler { + panic("not implemented") +} + // Logger returns a logger which logs to the unit test log of t. -func Logger(t *testing.T, level log.Lvl) log.Logger { - l := &logger{ +func Logger(t *testing.T, level slog.Level) log.Logger { + handler := bufHandler{ + []slog.Record{}, + []slog.Attr{}, + level, + } + return &logger{ + t: t, + l: log.NewLogger(&handler), + mu: new(sync.Mutex), + h: &handler, + } +} + +// LoggerWithHandler returns +func LoggerWithHandler(t *testing.T, handler slog.Handler) log.Logger { + var bh bufHandler + return &logger{ t: t, - l: log.New(), + l: log.NewLogger(handler), mu: new(sync.Mutex), - h: &bufHandler{fmt: log.TerminalFormat(false)}, + h: &bh, } - l.l.SetHandler(log.LvlFilterHandler(level, l.h)) - return l +} + +func (l *logger) Write(level slog.Level, msg string, ctx ...interface{}) {} + +func (l *logger) Enabled(ctx context.Context, level slog.Level) bool { + return l.l.Enabled(ctx, level) } func (l *logger) Trace(msg string, ctx ...interface{}) { @@ -80,6 +112,14 @@ func (l *logger) Trace(msg string, ctx ...interface{}) { l.flush() } +func (l *logger) Log(level slog.Level, msg string, ctx ...interface{}) { + l.t.Helper() + l.mu.Lock() + defer l.mu.Unlock() + l.l.Log(level, msg, ctx...) + l.flush() +} + func (l *logger) Debug(msg string, ctx ...interface{}) { l.t.Helper() l.mu.Lock() @@ -120,23 +160,44 @@ func (l *logger) Crit(msg string, ctx ...interface{}) { l.flush() } -func (l *logger) New(ctx ...interface{}) log.Logger { - return &logger{l.t, l.l.New(ctx...), l.mu, l.h} +func (l *logger) With(ctx ...interface{}) log.Logger { + return &logger{l.t, l.l.With(ctx...), l.mu, l.h} } -func (l *logger) GetHandler() log.Handler { - return l.l.GetHandler() +func (l *logger) New(ctx ...interface{}) log.Logger { + return l.With(ctx...) } -func (l *logger) SetHandler(h log.Handler) { - l.l.SetHandler(h) +// terminalFormat formats a message similarly to the NewTerminalHandler in the log package. +// The difference is that terminalFormat does not escape messages/attributes and does not pad attributes. +func (h *bufHandler) terminalFormat(r slog.Record) string { + buf := &bytes.Buffer{} + lvl := log.LevelAlignedString(r.Level) + attrs := []slog.Attr{} + r.Attrs(func(attr slog.Attr) bool { + attrs = append(attrs, attr) + return true + }) + + attrs = append(h.attrs, attrs...) + + fmt.Fprintf(buf, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Message) + if length := len(r.Message); length < 40 { + buf.Write(bytes.Repeat([]byte{' '}, 40-length)) + } + + for _, attr := range attrs { + fmt.Fprintf(buf, " %s=%s", attr.Key, string(log.FormatSlogValue(attr.Value, nil))) + } + buf.WriteByte('\n') + return buf.String() } // flush writes all buffered messages and clears the buffer. func (l *logger) flush() { l.t.Helper() for _, r := range l.h.buf { - l.t.Logf("%s", l.h.fmt.Format(r)) + l.t.Logf("%s", l.h.terminalFormat(r)) } l.h.buf = nil } diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 68e486072..3fa37da81 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -623,6 +623,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({ diff --git a/les/api.go b/les/api.go deleted file mode 100644 index e8490f7b0..000000000 --- a/les/api.go +++ /dev/null @@ -1,349 +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" - "fmt" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - vfs "github.com/ethereum/go-ethereum/les/vflux/server" - "github.com/ethereum/go-ethereum/p2p/enode" -) - -var errUnknownBenchmarkType = errors.New("unknown benchmark type") - -// LightServerAPI provides an API to access the LES light server. -type LightServerAPI struct { - server *LesServer - defaultPosFactors, defaultNegFactors vfs.PriceFactors -} - -// NewLightServerAPI creates a new LES light server API. -func NewLightServerAPI(server *LesServer) *LightServerAPI { - return &LightServerAPI{ - server: server, - defaultPosFactors: defaultPosFactors, - defaultNegFactors: defaultNegFactors, - } -} - -// parseNode parses either an enode address a raw hex node id -func parseNode(node string) (enode.ID, error) { - if id, err := enode.ParseID(node); err == nil { - return id, nil - } - if node, err := enode.Parse(enode.ValidSchemes, node); err == nil { - return node.ID(), nil - } else { - return enode.ID{}, err - } -} - -// ServerInfo returns global server parameters -func (api *LightServerAPI) ServerInfo() map[string]interface{} { - res := make(map[string]interface{}) - res["minimumCapacity"] = api.server.minCapacity - res["maximumCapacity"] = api.server.maxCapacity - _, res["totalCapacity"] = api.server.clientPool.Limits() - _, res["totalConnectedCapacity"] = api.server.clientPool.Active() - res["priorityConnectedCapacity"] = 0 //TODO connect when token sale module is added - return res -} - -// ClientInfo returns information about clients listed in the ids list or matching the given tags -func (api *LightServerAPI) ClientInfo(nodes []string) map[enode.ID]map[string]interface{} { - var ids []enode.ID - for _, node := range nodes { - if id, err := parseNode(node); err == nil { - ids = append(ids, id) - } - } - - res := make(map[enode.ID]map[string]interface{}) - if len(ids) == 0 { - ids = api.server.peers.ids() - } - for _, id := range ids { - if peer := api.server.peers.peer(id); peer != nil { - res[id] = api.clientInfo(peer, peer.balance) - } else { - api.server.clientPool.BalanceOperation(id, "", func(balance vfs.AtomicBalanceOperator) { - res[id] = api.clientInfo(nil, balance) - }) - } - } - return res -} - -// PriorityClientInfo returns information about clients with a positive balance -// in the given ID range (stop excluded). If stop is null then the iterator stops -// only at the end of the ID space. MaxCount limits the number of results returned. -// If maxCount limit is applied but there are more potential results then the ID -// of the next potential result is included in the map with an empty structure -// assigned to it. -func (api *LightServerAPI) PriorityClientInfo(start, stop enode.ID, maxCount int) map[enode.ID]map[string]interface{} { - res := make(map[enode.ID]map[string]interface{}) - ids := api.server.clientPool.GetPosBalanceIDs(start, stop, maxCount+1) - if len(ids) > maxCount { - res[ids[maxCount]] = make(map[string]interface{}) - ids = ids[:maxCount] - } - for _, id := range ids { - if peer := api.server.peers.peer(id); peer != nil { - res[id] = api.clientInfo(peer, peer.balance) - } else { - api.server.clientPool.BalanceOperation(id, "", func(balance vfs.AtomicBalanceOperator) { - res[id] = api.clientInfo(nil, balance) - }) - } - } - return res -} - -// clientInfo creates a client info data structure -func (api *LightServerAPI) clientInfo(peer *clientPeer, balance vfs.ReadOnlyBalance) map[string]interface{} { - info := make(map[string]interface{}) - pb, nb := balance.GetBalance() - info["isConnected"] = peer != nil - info["pricing/balance"] = pb - info["priority"] = pb != 0 - // cb := api.server.clientPool.ndb.getCurrencyBalance(id) - // info["pricing/currency"] = cb.amount - if peer != nil { - info["connectionTime"] = float64(mclock.Now()-peer.connectedAt) / float64(time.Second) - info["capacity"] = peer.getCapacity() - info["pricing/negBalance"] = nb - } - return info -} - -// setParams either sets the given parameters for a single connected client (if specified) -// or the default parameters applicable to clients connected in the future -func (api *LightServerAPI) setParams(params map[string]interface{}, client *clientPeer, posFactors, negFactors *vfs.PriceFactors) (updateFactors bool, err error) { - defParams := client == nil - for name, value := range params { - errValue := func() error { - return fmt.Errorf("invalid value for parameter '%s'", name) - } - setFactor := func(v *float64) { - if val, ok := value.(float64); ok && val >= 0 { - *v = val / float64(time.Second) - updateFactors = true - } else { - err = errValue() - } - } - - switch { - case name == "pricing/timeFactor": - setFactor(&posFactors.TimeFactor) - case name == "pricing/capacityFactor": - setFactor(&posFactors.CapacityFactor) - case name == "pricing/requestCostFactor": - setFactor(&posFactors.RequestFactor) - case name == "pricing/negative/timeFactor": - setFactor(&negFactors.TimeFactor) - case name == "pricing/negative/capacityFactor": - setFactor(&negFactors.CapacityFactor) - case name == "pricing/negative/requestCostFactor": - setFactor(&negFactors.RequestFactor) - case !defParams && name == "capacity": - if capacity, ok := value.(float64); ok && uint64(capacity) >= api.server.minCapacity { - _, err = api.server.clientPool.SetCapacity(client.Node(), uint64(capacity), 0, false) - // time factor recalculation is performed automatically by the balance tracker - } else { - err = errValue() - } - default: - if defParams { - err = fmt.Errorf("invalid default parameter '%s'", name) - } else { - err = fmt.Errorf("invalid client parameter '%s'", name) - } - } - if err != nil { - return - } - } - return -} - -// SetClientParams sets client parameters for all clients listed in the ids list -// or all connected clients if the list is empty -func (api *LightServerAPI) SetClientParams(nodes []string, params map[string]interface{}) error { - var err error - for _, node := range nodes { - var id enode.ID - if id, err = parseNode(node); err != nil { - return err - } - if peer := api.server.peers.peer(id); peer != nil { - posFactors, negFactors := peer.balance.GetPriceFactors() - update, e := api.setParams(params, peer, &posFactors, &negFactors) - if update { - peer.balance.SetPriceFactors(posFactors, negFactors) - } - if e != nil { - err = e - } - } else { - err = fmt.Errorf("client %064x is not connected", id) - } - } - return err -} - -// SetDefaultParams sets the default parameters applicable to clients connected in the future -func (api *LightServerAPI) SetDefaultParams(params map[string]interface{}) error { - update, err := api.setParams(params, nil, &api.defaultPosFactors, &api.defaultNegFactors) - if update { - api.server.clientPool.SetDefaultFactors(api.defaultPosFactors, api.defaultNegFactors) - } - return err -} - -// SetConnectedBias set the connection bias, which is applied to already connected clients -// So that already connected client won't be kicked out very soon and we can ensure all -// connected clients can have enough time to request or sync some data. -// When the input parameter `bias` < 0 (illegal), return error. -func (api *LightServerAPI) SetConnectedBias(bias time.Duration) error { - if bias < time.Duration(0) { - return fmt.Errorf("bias illegal: %v less than 0", bias) - } - api.server.clientPool.SetConnectedBias(bias) - return nil -} - -// AddBalance adds the given amount to the balance of a client if possible and returns -// the balance before and after the operation -func (api *LightServerAPI) AddBalance(node string, amount int64) (balance [2]uint64, err error) { - var id enode.ID - if id, err = parseNode(node); err != nil { - return - } - api.server.clientPool.BalanceOperation(id, "", func(nb vfs.AtomicBalanceOperator) { - balance[0], balance[1], err = nb.AddBalance(amount) - }) - return -} - -// Benchmark runs a request performance benchmark with a given set of measurement setups -// in multiple passes specified by passCount. The measurement time for each setup in each -// pass is specified in milliseconds by length. -// -// Note: measurement time is adjusted for each pass depending on the previous ones. -// Therefore a controlled total measurement time is achievable in multiple passes. -func (api *LightServerAPI) Benchmark(setups []map[string]interface{}, passCount, length int) ([]map[string]interface{}, error) { - benchmarks := make([]requestBenchmark, len(setups)) - for i, setup := range setups { - if t, ok := setup["type"].(string); ok { - getInt := func(field string, def int) int { - if value, ok := setup[field].(float64); ok { - return int(value) - } - return def - } - getBool := func(field string, def bool) bool { - if value, ok := setup[field].(bool); ok { - return value - } - return def - } - switch t { - case "header": - benchmarks[i] = &benchmarkBlockHeaders{ - amount: getInt("amount", 1), - skip: getInt("skip", 1), - byHash: getBool("byHash", false), - reverse: getBool("reverse", false), - } - case "body": - benchmarks[i] = &benchmarkBodiesOrReceipts{receipts: false} - case "receipts": - benchmarks[i] = &benchmarkBodiesOrReceipts{receipts: true} - case "proof": - benchmarks[i] = &benchmarkProofsOrCode{code: false} - case "code": - benchmarks[i] = &benchmarkProofsOrCode{code: true} - case "cht": - benchmarks[i] = &benchmarkHelperTrie{ - bloom: false, - reqCount: getInt("amount", 1), - } - case "bloom": - benchmarks[i] = &benchmarkHelperTrie{ - bloom: true, - reqCount: getInt("amount", 1), - } - case "txSend": - benchmarks[i] = &benchmarkTxSend{} - case "txStatus": - benchmarks[i] = &benchmarkTxStatus{} - default: - return nil, errUnknownBenchmarkType - } - } else { - return nil, errUnknownBenchmarkType - } - } - rs := api.server.handler.runBenchmark(benchmarks, passCount, time.Millisecond*time.Duration(length)) - result := make([]map[string]interface{}, len(setups)) - for i, r := range rs { - res := make(map[string]interface{}) - if r.err == nil { - res["totalCount"] = r.totalCount - res["avgTime"] = r.avgTime - res["maxInSize"] = r.maxInSize - res["maxOutSize"] = r.maxOutSize - } else { - res["error"] = r.err.Error() - } - result[i] = res - } - return result, nil -} - -// DebugAPI provides an API to debug LES light server functionality. -type DebugAPI struct { - server *LesServer -} - -// NewDebugAPI creates a new LES light server debug API. -func NewDebugAPI(server *LesServer) *DebugAPI { - return &DebugAPI{ - server: server, - } -} - -// FreezeClient forces a temporary client freeze which normally happens when the server is overloaded -func (api *DebugAPI) FreezeClient(node string) error { - var ( - id enode.ID - err error - ) - if id, err = parseNode(node); err != nil { - return err - } - if peer := api.server.peers.peer(id); peer != nil { - peer.freeze() - return nil - } else { - return fmt.Errorf("client %064x is not connected", id[:]) - } -} diff --git a/les/api_backend.go b/les/api_backend.go deleted file mode 100644 index 8a288ddc4..000000000 --- a/les/api_backend.go +++ /dev/null @@ -1,348 +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 ( - "context" - "errors" - "math/big" - "time" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/bloombits" - "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/eth/gasprice" - "github.com/ethereum/go-ethereum/eth/tracers" - "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/params" - "github.com/ethereum/go-ethereum/rpc" -) - -type LesApiBackend struct { - extRPCEnabled bool - allowUnprotectedTxs bool - eth *LightEthereum - gpo *gasprice.Oracle -} - -func (b *LesApiBackend) ChainConfig() *params.ChainConfig { - return b.eth.chainConfig -} - -func (b *LesApiBackend) CurrentBlock() *types.Header { - return b.eth.BlockChain().CurrentHeader() -} - -func (b *LesApiBackend) SetHead(number uint64) { - b.eth.blockchain.SetHead(number) -} - -func (b *LesApiBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { - // Return the latest current as the pending one since there - // is no pending notion in the light client. TODO(rjl493456442) - // unify the behavior of `HeaderByNumber` and `PendingBlockAndReceipts`. - if number == rpc.PendingBlockNumber { - return b.eth.blockchain.CurrentHeader(), nil - } - if number == rpc.LatestBlockNumber { - return b.eth.blockchain.CurrentHeader(), nil - } - return b.eth.blockchain.GetHeaderByNumberOdr(ctx, uint64(number)) -} - -func (b *LesApiBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { - if blockNr, ok := blockNrOrHash.Number(); ok { - return b.HeaderByNumber(ctx, blockNr) - } - if hash, ok := blockNrOrHash.Hash(); ok { - header, err := b.HeaderByHash(ctx, hash) - if err != nil { - return nil, err - } - if header == nil { - return nil, errors.New("header for hash not found") - } - if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { - return nil, errors.New("hash is not currently canonical") - } - return header, nil - } - return nil, errors.New("invalid arguments; neither block nor hash specified") -} - -func (b *LesApiBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { - return b.eth.blockchain.GetHeaderByHash(hash), nil -} - -func (b *LesApiBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { - header, err := b.HeaderByNumber(ctx, number) - if header == nil || err != nil { - return nil, err - } - return b.BlockByHash(ctx, header.Hash()) -} - -func (b *LesApiBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - return b.eth.blockchain.GetBlockByHash(ctx, hash) -} - -func (b *LesApiBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { - if blockNr, ok := blockNrOrHash.Number(); ok { - return b.BlockByNumber(ctx, blockNr) - } - if hash, ok := blockNrOrHash.Hash(); ok { - block, err := b.BlockByHash(ctx, hash) - if err != nil { - return nil, err - } - if block == nil { - return nil, errors.New("header found, but block body is missing") - } - if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(block.NumberU64()) != hash { - return nil, errors.New("hash is not currently canonical") - } - return block, nil - } - return nil, errors.New("invalid arguments; neither block nor hash specified") -} - -func (b *LesApiBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { - return light.GetBody(ctx, b.eth.odr, hash, uint64(number)) -} - -func (b *LesApiBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { - return nil, nil -} - -func (b *LesApiBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { - header, err := b.HeaderByNumber(ctx, number) - if err != nil { - return nil, nil, err - } - if header == nil { - return nil, nil, errors.New("header not found") - } - return light.NewState(ctx, header, b.eth.odr), header, nil -} - -func (b *LesApiBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { - if blockNr, ok := blockNrOrHash.Number(); ok { - return b.StateAndHeaderByNumber(ctx, blockNr) - } - if hash, ok := blockNrOrHash.Hash(); ok { - header := b.eth.blockchain.GetHeaderByHash(hash) - if header == nil { - return nil, nil, errors.New("header for hash not found") - } - if blockNrOrHash.RequireCanonical && b.eth.blockchain.GetCanonicalHash(header.Number.Uint64()) != hash { - return nil, nil, errors.New("hash is not currently canonical") - } - return light.NewState(ctx, header, b.eth.odr), header, nil - } - return nil, nil, errors.New("invalid arguments; neither block nor hash specified") -} - -func (b *LesApiBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { - if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil { - return light.GetBlockReceipts(ctx, b.eth.odr, hash, *number) - } - return nil, nil -} - -func (b *LesApiBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) { - return light.GetBlockLogs(ctx, b.eth.odr, hash, number) -} - -func (b *LesApiBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { - if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil { - return b.eth.blockchain.GetTdOdr(ctx, hash, *number) - } - return nil -} - -func (b *LesApiBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error) { - if vmConfig == nil { - vmConfig = new(vm.Config) - } - txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(header, b.eth.blockchain, nil) - if blockCtx != nil { - context = *blockCtx - } - return vm.NewEVM(context, txContext, state, b.eth.chainConfig, *vmConfig), state.Error -} - -func (b *LesApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { - return b.eth.txPool.Add(ctx, signedTx) -} - -func (b *LesApiBackend) RemoveTx(txHash common.Hash) { - b.eth.txPool.RemoveTx(txHash) -} - -func (b *LesApiBackend) GetPoolTransactions() (types.Transactions, error) { - return b.eth.txPool.GetTransactions() -} - -func (b *LesApiBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { - return b.eth.txPool.GetTransaction(txHash) -} - -func (b *LesApiBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { - return light.GetTransaction(ctx, b.eth.odr, txHash) -} - -func (b *LesApiBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { - return b.eth.txPool.GetNonce(ctx, addr) -} - -func (b *LesApiBackend) Stats() (pending int, queued int) { - return b.eth.txPool.Stats(), 0 -} - -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.Transaction, []*types.Transaction) { - return b.eth.txPool.ContentFrom(addr) -} - -func (b *LesApiBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription { - return b.eth.txPool.SubscribeNewTxsEvent(ch) -} - -func (b *LesApiBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { - return b.eth.blockchain.SubscribeChainEvent(ch) -} - -func (b *LesApiBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { - return b.eth.blockchain.SubscribeChainHeadEvent(ch) -} - -func (b *LesApiBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { - return b.eth.blockchain.SubscribeChainSideEvent(ch) -} - -func (b *LesApiBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { - return b.eth.blockchain.SubscribeLogsEvent(ch) -} - -func (b *LesApiBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { - return event.NewSubscription(func(quit <-chan struct{}) error { - <-quit - return nil - }) -} - -func (b *LesApiBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { - return b.eth.blockchain.SubscribeRemovedLogsEvent(ch) -} - -func (b *LesApiBackend) SyncProgress() ethereum.SyncProgress { - return ethereum.SyncProgress{} -} - -func (b *LesApiBackend) ProtocolVersion() int { - return b.eth.LesVersion() + 10000 -} - -func (b *LesApiBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { - return b.gpo.SuggestTipCap(ctx) -} - -func (b *LesApiBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (firstBlock *big.Int, reward [][]*big.Int, baseFee []*big.Int, gasUsedRatio []float64, err error) { - return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) -} - -func (b *LesApiBackend) ChainDb() ethdb.Database { - return b.eth.chainDb -} - -func (b *LesApiBackend) AccountManager() *accounts.Manager { - return b.eth.accountManager -} - -func (b *LesApiBackend) ExtRPCEnabled() bool { - return b.extRPCEnabled -} - -func (b *LesApiBackend) UnprotectedAllowed() bool { - return b.allowUnprotectedTxs -} - -func (b *LesApiBackend) RPCGasCap() uint64 { - return b.eth.config.RPCGasCap -} - -func (b *LesApiBackend) RPCEVMTimeout() time.Duration { - return b.eth.config.RPCEVMTimeout -} - -func (b *LesApiBackend) RPCTxFeeCap() float64 { - return b.eth.config.RPCTxFeeCap -} - -func (b *LesApiBackend) BloomStatus() (uint64, uint64) { - if b.eth.bloomIndexer == nil { - return 0, 0 - } - sections, _, _ := b.eth.bloomIndexer.Sections() - return params.BloomBitsBlocksClient, sections -} - -func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { - for i := 0; i < bloomFilterThreads; i++ { - go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests) - } -} - -func (b *LesApiBackend) Engine() consensus.Engine { - return b.eth.engine -} - -func (b *LesApiBackend) CurrentHeader() *types.Header { - return b.eth.blockchain.CurrentHeader() -} - -func (b *LesApiBackend) 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) -} - -func (b *LesApiBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { - return b.eth.stateAtTransaction(ctx, block, txIndex, reexec) -} - -func (b *LesApiBackend) SetHistoricalBlocksSynced() bool { - if b.SyncProgress().CurrentBlock < b.SyncProgress().HighestBlock { - log.Crit("Light Client not fully synced yet, block specimen producer not supported in light client mode") - } else { - log.Crit("Fully Synced but block specimen producer not supported in light client mode") - } - - return false -} diff --git a/les/api_test.go b/les/api_test.go deleted file mode 100644 index 88a950afc..000000000 --- a/les/api_test.go +++ /dev/null @@ -1,513 +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 ( - "context" - crand "crypto/rand" - "errors" - "flag" - "math/rand" - "os" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "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/les/flowcontrol" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/simulations" - "github.com/ethereum/go-ethereum/p2p/simulations/adapters" - "github.com/ethereum/go-ethereum/rpc" - "github.com/mattn/go-colorable" -) - -// Additional command line flags for the test binary. -var ( - loglevel = flag.Int("loglevel", 0, "verbosity of logs") - simAdapter = flag.String("adapter", "exec", "type of simulation: sim|socket|exec|docker") -) - -func TestMain(m *testing.M) { - flag.Parse() - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) - // register the Delivery service which will run as a devp2p - // protocol when using the exec adapter - adapters.RegisterLifecycles(services) - os.Exit(m.Run()) -} - -// This test is not meant to be a part of the automatic testing process because it -// runs for a long time and also requires a large database in order to do a meaningful -// request performance test. When testServerDataDir is empty, the test is skipped. - -const ( - testServerDataDir = "" // should always be empty on the master branch - testServerCapacity = 200 - testMaxClients = 10 - testTolerance = 0.1 - minRelCap = 0.2 -) - -func TestCapacityAPI3(t *testing.T) { - testCapacityAPI(t, 3) -} - -func TestCapacityAPI6(t *testing.T) { - testCapacityAPI(t, 6) -} - -func TestCapacityAPI10(t *testing.T) { - testCapacityAPI(t, 10) -} - -// testCapacityAPI runs an end-to-end simulation test connecting one server with -// a given number of clients. It sets different priority capacities to all clients -// except a randomly selected one which runs in free client mode. All clients send -// similar requests at the maximum allowed rate and the test verifies whether the -// ratio of processed requests is close enough to the ratio of assigned capacities. -// Running multiple rounds with different settings ensures that changing capacity -// while connected and going back and forth between free and priority mode with -// the supplied API calls is also thoroughly tested. -func testCapacityAPI(t *testing.T, clientCount int) { - // Skip test if no data dir specified - if testServerDataDir == "" { - return - } - for !testSim(t, 1, clientCount, []string{testServerDataDir}, nil, func(ctx context.Context, net *simulations.Network, servers []*simulations.Node, clients []*simulations.Node) bool { - if len(servers) != 1 { - t.Fatalf("Invalid number of servers: %d", len(servers)) - } - server := servers[0] - - serverRpcClient, err := server.Client() - if err != nil { - t.Fatalf("Failed to obtain rpc client: %v", err) - } - headNum, headHash := getHead(ctx, t, serverRpcClient) - minCap, totalCap := getCapacityInfo(ctx, t, serverRpcClient) - testCap := totalCap * 3 / 4 - t.Logf("Server testCap: %d minCap: %d head number: %d head hash: %064x\n", testCap, minCap, headNum, headHash) - reqMinCap := uint64(float64(testCap) * minRelCap / (minRelCap + float64(len(clients)-1))) - if minCap > reqMinCap { - t.Fatalf("Minimum client capacity (%d) bigger than required minimum for this test (%d)", minCap, reqMinCap) - } - freeIdx := rand.Intn(len(clients)) - - clientRpcClients := make([]*rpc.Client, len(clients)) - for i, client := range clients { - var err error - clientRpcClients[i], err = client.Client() - if err != nil { - t.Fatalf("Failed to obtain rpc client: %v", err) - } - t.Log("connecting client", i) - if i != freeIdx { - setCapacity(ctx, t, serverRpcClient, client.ID(), testCap/uint64(len(clients))) - } - net.Connect(client.ID(), server.ID()) - - for { - select { - case <-ctx.Done(): - t.Fatalf("Timeout") - default: - } - num, hash := getHead(ctx, t, clientRpcClients[i]) - if num == headNum && hash == headHash { - t.Log("client", i, "synced") - break - } - time.Sleep(time.Millisecond * 200) - } - } - - var wg sync.WaitGroup - stop := make(chan struct{}) - - reqCount := make([]uint64, len(clientRpcClients)) - - // Send light request like crazy. - for i, c := range clientRpcClients { - wg.Add(1) - i, c := i, c - go func() { - defer wg.Done() - - queue := make(chan struct{}, 100) - reqCount[i] = 0 - for { - select { - case queue <- struct{}{}: - select { - case <-stop: - return - case <-ctx.Done(): - return - default: - wg.Add(1) - go func() { - ok := testRequest(ctx, t, c) - wg.Done() - <-queue - if ok { - count := atomic.AddUint64(&reqCount[i], 1) - if count%10000 == 0 { - freezeClient(ctx, t, serverRpcClient, clients[i].ID()) - } - } - }() - } - case <-stop: - return - case <-ctx.Done(): - return - } - } - }() - } - - processedSince := func(start []uint64) []uint64 { - res := make([]uint64, len(reqCount)) - for i := range reqCount { - res[i] = atomic.LoadUint64(&reqCount[i]) - if start != nil { - res[i] -= start[i] - } - } - return res - } - - weights := make([]float64, len(clients)) - for c := 0; c < 5; c++ { - setCapacity(ctx, t, serverRpcClient, clients[freeIdx].ID(), minCap) - freeIdx = rand.Intn(len(clients)) - var sum float64 - for i := range clients { - if i == freeIdx { - weights[i] = 0 - } else { - weights[i] = rand.Float64()*(1-minRelCap) + minRelCap - } - sum += weights[i] - } - for i, client := range clients { - weights[i] *= float64(testCap-minCap-100) / sum - capacity := uint64(weights[i]) - if i != freeIdx && capacity < getCapacity(ctx, t, serverRpcClient, client.ID()) { - setCapacity(ctx, t, serverRpcClient, client.ID(), capacity) - } - } - setCapacity(ctx, t, serverRpcClient, clients[freeIdx].ID(), 0) - for i, client := range clients { - capacity := uint64(weights[i]) - if i != freeIdx && capacity > getCapacity(ctx, t, serverRpcClient, client.ID()) { - setCapacity(ctx, t, serverRpcClient, client.ID(), capacity) - } - } - weights[freeIdx] = float64(minCap) - for i := range clients { - weights[i] /= float64(testCap) - } - - time.Sleep(flowcontrol.DecParamDelay) - t.Log("Starting measurement") - t.Logf("Relative weights:") - for i := range clients { - t.Logf(" %f", weights[i]) - } - t.Log() - start := processedSince(nil) - for { - select { - case <-ctx.Done(): - t.Fatalf("Timeout") - default: - } - - _, totalCap = getCapacityInfo(ctx, t, serverRpcClient) - if totalCap < testCap { - t.Log("Total capacity underrun") - close(stop) - wg.Wait() - return false - } - - processed := processedSince(start) - var avg uint64 - t.Logf("Processed") - for i, p := range processed { - t.Logf(" %d", p) - processed[i] = uint64(float64(p) / weights[i]) - avg += processed[i] - } - avg /= uint64(len(processed)) - - if avg >= 10000 { - var maxDev float64 - for _, p := range processed { - dev := float64(int64(p-avg)) / float64(avg) - t.Logf(" %7.4f", dev) - if dev < 0 { - dev = -dev - } - if dev > maxDev { - maxDev = dev - } - } - t.Logf(" max deviation: %f totalCap: %d\n", maxDev, totalCap) - if maxDev <= testTolerance { - t.Log("success") - break - } - } else { - t.Log() - } - time.Sleep(time.Millisecond * 200) - } - } - - close(stop) - wg.Wait() - - for i, count := range reqCount { - t.Log("client", i, "processed", count) - } - return true - }) { - t.Log("restarting test") - } -} - -func getHead(ctx context.Context, t *testing.T, client *rpc.Client) (uint64, common.Hash) { - res := make(map[string]interface{}) - if err := client.CallContext(ctx, &res, "eth_getBlockByNumber", "latest", false); err != nil { - t.Fatalf("Failed to obtain head block: %v", err) - } - numStr, ok := res["number"].(string) - if !ok { - t.Fatalf("RPC block number field invalid") - } - num, err := hexutil.DecodeUint64(numStr) - if err != nil { - t.Fatalf("Failed to decode RPC block number: %v", err) - } - hashStr, ok := res["hash"].(string) - if !ok { - t.Fatalf("RPC block number field invalid") - } - hash := common.HexToHash(hashStr) - return num, hash -} - -func testRequest(ctx context.Context, t *testing.T, client *rpc.Client) bool { - var res string - var addr common.Address - crand.Read(addr[:]) - c, cancel := context.WithTimeout(ctx, time.Second*12) - defer cancel() - err := client.CallContext(c, &res, "eth_getBalance", addr, "latest") - if err != nil { - t.Log("request error:", err) - } - return err == nil -} - -func freezeClient(ctx context.Context, t *testing.T, server *rpc.Client, clientID enode.ID) { - if err := server.CallContext(ctx, nil, "debug_freezeClient", clientID); err != nil { - t.Fatalf("Failed to freeze client: %v", err) - } -} - -func setCapacity(ctx context.Context, t *testing.T, server *rpc.Client, clientID enode.ID, cap uint64) { - params := make(map[string]interface{}) - params["capacity"] = cap - if err := server.CallContext(ctx, nil, "les_setClientParams", []enode.ID{clientID}, []string{}, params); err != nil { - t.Fatalf("Failed to set client capacity: %v", err) - } -} - -func getCapacity(ctx context.Context, t *testing.T, server *rpc.Client, clientID enode.ID) uint64 { - var res map[enode.ID]map[string]interface{} - if err := server.CallContext(ctx, &res, "les_clientInfo", []enode.ID{clientID}, []string{}); err != nil { - t.Fatalf("Failed to get client info: %v", err) - } - info, ok := res[clientID] - if !ok { - t.Fatalf("Missing client info") - } - v, ok := info["capacity"] - if !ok { - t.Fatalf("Missing field in client info: capacity") - } - vv, ok := v.(float64) - if !ok { - t.Fatalf("Failed to decode capacity field") - } - return uint64(vv) -} - -func getCapacityInfo(ctx context.Context, t *testing.T, server *rpc.Client) (minCap, totalCap uint64) { - var res map[string]interface{} - if err := server.CallContext(ctx, &res, "les_serverInfo"); err != nil { - t.Fatalf("Failed to query server info: %v", err) - } - decode := func(s string) uint64 { - v, ok := res[s] - if !ok { - t.Fatalf("Missing field in server info: %s", s) - } - vv, ok := v.(float64) - if !ok { - t.Fatalf("Failed to decode server info field: %s", s) - } - return uint64(vv) - } - minCap = decode("minimumCapacity") - totalCap = decode("totalCapacity") - return -} - -var services = adapters.LifecycleConstructors{ - "lesclient": newLesClientService, - "lesserver": newLesServerService, -} - -func NewNetwork() (*simulations.Network, func(), error) { - adapter, adapterTeardown, err := NewAdapter(*simAdapter, services) - if err != nil { - return nil, adapterTeardown, err - } - defaultService := "streamer" - net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ - ID: "0", - DefaultService: defaultService, - }) - teardown := func() { - adapterTeardown() - net.Shutdown() - } - return net, teardown, nil -} - -func NewAdapter(adapterType string, services adapters.LifecycleConstructors) (adapter adapters.NodeAdapter, teardown func(), err error) { - teardown = func() {} - switch adapterType { - case "sim": - adapter = adapters.NewSimAdapter(services) - // case "socket": - // adapter = adapters.NewSocketAdapter(services) - case "exec": - baseDir, err0 := os.MkdirTemp("", "les-test") - if err0 != nil { - return nil, teardown, err0 - } - teardown = func() { os.RemoveAll(baseDir) } - adapter = adapters.NewExecAdapter(baseDir) - /*case "docker": - adapter, err = adapters.NewDockerAdapter() - if err != nil { - return nil, teardown, err - }*/ - default: - return nil, teardown, errors.New("adapter needs to be one of sim, socket, exec, docker") - } - return adapter, teardown, nil -} - -func testSim(t *testing.T, serverCount, clientCount int, serverDir, clientDir []string, test func(ctx context.Context, net *simulations.Network, servers []*simulations.Node, clients []*simulations.Node) bool) bool { - net, teardown, err := NewNetwork() - defer teardown() - if err != nil { - t.Fatalf("Failed to create network: %v", err) - } - timeout := 1800 * time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - - servers := make([]*simulations.Node, serverCount) - clients := make([]*simulations.Node, clientCount) - - for i := range clients { - clientconf := adapters.RandomNodeConfig() - clientconf.Lifecycles = []string{"lesclient"} - if len(clientDir) == clientCount { - clientconf.DataDir = clientDir[i] - } - client, err := net.NewNodeWithConfig(clientconf) - if err != nil { - t.Fatalf("Failed to create client: %v", err) - } - clients[i] = client - } - - for i := range servers { - serverconf := adapters.RandomNodeConfig() - serverconf.Lifecycles = []string{"lesserver"} - if len(serverDir) == serverCount { - serverconf.DataDir = serverDir[i] - } - server, err := net.NewNodeWithConfig(serverconf) - if err != nil { - t.Fatalf("Failed to create server: %v", err) - } - servers[i] = server - } - - for _, client := range clients { - if err := net.Start(client.ID()); err != nil { - t.Fatalf("Failed to start client node: %v", err) - } - } - for _, server := range servers { - if err := net.Start(server.ID()); err != nil { - t.Fatalf("Failed to start server node: %v", err) - } - } - - return test(ctx, net, servers, clients) -} - -func newLesClientService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { - config := ethconfig.Defaults - config.SyncMode = downloader.LightSync - return New(stack, &config) -} - -func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.Lifecycle, error) { - config := ethconfig.Defaults - config.SyncMode = downloader.FullSync - config.LightServ = testServerCapacity - config.LightPeers = testMaxClients - ethereum, err := eth.New(stack, &config) - if err != nil { - return nil, err - } - _, err = NewLesServer(stack, ethereum, &config) - if err != nil { - return nil, err - } - return ethereum, nil -} diff --git a/les/benchmark.go b/les/benchmark.go deleted file mode 100644 index ab9351834..000000000 --- a/les/benchmark.go +++ /dev/null @@ -1,351 +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 ( - crand "crypto/rand" - "encoding/binary" - "errors" - "math/big" - "math/rand" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/mclock" - "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/les/flowcontrol" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" -) - -// requestBenchmark is an interface for different randomized request generators -type requestBenchmark interface { - // init initializes the generator for generating the given number of randomized requests - init(h *serverHandler, count int) error - // request initiates sending a single request to the given peer - request(peer *serverPeer, index int) error -} - -// benchmarkBlockHeaders implements requestBenchmark -type benchmarkBlockHeaders struct { - amount, skip int - reverse, byHash bool - offset, randMax int64 - hashes []common.Hash -} - -func (b *benchmarkBlockHeaders) init(h *serverHandler, count int) error { - d := int64(b.amount-1) * int64(b.skip+1) - b.offset = 0 - b.randMax = h.blockchain.CurrentHeader().Number.Int64() + 1 - d - if b.randMax < 0 { - return errors.New("chain is too short") - } - if b.reverse { - b.offset = d - } - if b.byHash { - b.hashes = make([]common.Hash, count) - for i := range b.hashes { - b.hashes[i] = rawdb.ReadCanonicalHash(h.chainDb, uint64(b.offset+rand.Int63n(b.randMax))) - } - } - return nil -} - -func (b *benchmarkBlockHeaders) request(peer *serverPeer, index int) error { - if b.byHash { - return peer.requestHeadersByHash(0, b.hashes[index], b.amount, b.skip, b.reverse) - } - return peer.requestHeadersByNumber(0, uint64(b.offset+rand.Int63n(b.randMax)), b.amount, b.skip, b.reverse) -} - -// benchmarkBodiesOrReceipts implements requestBenchmark -type benchmarkBodiesOrReceipts struct { - receipts bool - hashes []common.Hash -} - -func (b *benchmarkBodiesOrReceipts) init(h *serverHandler, count int) error { - randMax := h.blockchain.CurrentHeader().Number.Int64() + 1 - b.hashes = make([]common.Hash, count) - for i := range b.hashes { - b.hashes[i] = rawdb.ReadCanonicalHash(h.chainDb, uint64(rand.Int63n(randMax))) - } - return nil -} - -func (b *benchmarkBodiesOrReceipts) request(peer *serverPeer, index int) error { - if b.receipts { - return peer.requestReceipts(0, []common.Hash{b.hashes[index]}) - } - return peer.requestBodies(0, []common.Hash{b.hashes[index]}) -} - -// benchmarkProofsOrCode implements requestBenchmark -type benchmarkProofsOrCode struct { - code bool - headHash common.Hash -} - -func (b *benchmarkProofsOrCode) init(h *serverHandler, count int) error { - b.headHash = h.blockchain.CurrentHeader().Hash() - return nil -} - -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, AccountAddress: key}}) - } - return peer.requestProofs(0, []ProofReq{{BHash: b.headHash, Key: key}}) -} - -// benchmarkHelperTrie implements requestBenchmark -type benchmarkHelperTrie struct { - bloom bool - reqCount int - sectionCount, headNum uint64 -} - -func (b *benchmarkHelperTrie) init(h *serverHandler, count int) error { - if b.bloom { - b.sectionCount, b.headNum, _ = h.server.bloomTrieIndexer.Sections() - } else { - b.sectionCount, _, _ = h.server.chtIndexer.Sections() - b.headNum = b.sectionCount*params.CHTFrequency - 1 - } - if b.sectionCount == 0 { - return errors.New("no processed sections available") - } - return nil -} - -func (b *benchmarkHelperTrie) request(peer *serverPeer, index int) error { - reqs := make([]HelperTrieReq, b.reqCount) - - if b.bloom { - bitIdx := uint16(rand.Intn(2048)) - for i := range reqs { - key := make([]byte, 10) - binary.BigEndian.PutUint16(key[:2], bitIdx) - binary.BigEndian.PutUint64(key[2:], uint64(rand.Int63n(int64(b.sectionCount)))) - reqs[i] = HelperTrieReq{Type: htBloomBits, TrieIdx: b.sectionCount - 1, Key: key} - } - } else { - for i := range reqs { - key := make([]byte, 8) - binary.BigEndian.PutUint64(key[:], uint64(rand.Int63n(int64(b.headNum)))) - reqs[i] = HelperTrieReq{Type: htCanonical, TrieIdx: b.sectionCount - 1, Key: key, AuxReq: htAuxHeader} - } - } - - return peer.requestHelperTrieProofs(0, reqs) -} - -// benchmarkTxSend implements requestBenchmark -type benchmarkTxSend struct { - txs types.Transactions -} - -func (b *benchmarkTxSend) init(h *serverHandler, count int) error { - key, _ := crypto.GenerateKey() - addr := crypto.PubkeyToAddress(key.PublicKey) - signer := types.LatestSigner(h.server.chainConfig) - b.txs = make(types.Transactions, count) - - for i := range b.txs { - data := make([]byte, txSizeCostLimit) - crand.Read(data) - tx, err := types.SignTx(types.NewTransaction(0, addr, new(big.Int), 0, new(big.Int), data), signer, key) - if err != nil { - panic(err) - } - b.txs[i] = tx - } - return nil -} - -func (b *benchmarkTxSend) request(peer *serverPeer, index int) error { - enc, _ := rlp.EncodeToBytes(types.Transactions{b.txs[index]}) - return peer.sendTxs(0, 1, enc) -} - -// benchmarkTxStatus implements requestBenchmark -type benchmarkTxStatus struct{} - -func (b *benchmarkTxStatus) init(h *serverHandler, count int) error { - return nil -} - -func (b *benchmarkTxStatus) request(peer *serverPeer, index int) error { - var hash common.Hash - crand.Read(hash[:]) - return peer.requestTxStatus(0, []common.Hash{hash}) -} - -// benchmarkSetup stores measurement data for a single benchmark type -type benchmarkSetup struct { - req requestBenchmark - totalCount int - totalTime, avgTime time.Duration - maxInSize, maxOutSize uint32 - err error -} - -// runBenchmark runs a benchmark cycle for all benchmark types in the specified -// number of passes -func (h *serverHandler) runBenchmark(benchmarks []requestBenchmark, passCount int, targetTime time.Duration) []*benchmarkSetup { - setup := make([]*benchmarkSetup, len(benchmarks)) - for i, b := range benchmarks { - setup[i] = &benchmarkSetup{req: b} - } - for i := 0; i < passCount; i++ { - log.Info("Running benchmark", "pass", i+1, "total", passCount) - todo := make([]*benchmarkSetup, len(benchmarks)) - copy(todo, setup) - for len(todo) > 0 { - // select a random element - index := rand.Intn(len(todo)) - next := todo[index] - todo[index] = todo[len(todo)-1] - todo = todo[:len(todo)-1] - - if next.err == nil { - // calculate request count - count := 50 - if next.totalTime > 0 { - count = int(uint64(next.totalCount) * uint64(targetTime) / uint64(next.totalTime)) - } - if err := h.measure(next, count); err != nil { - next.err = err - } - } - } - } - log.Info("Benchmark completed") - - for _, s := range setup { - if s.err == nil { - s.avgTime = s.totalTime / time.Duration(s.totalCount) - } - } - return setup -} - -// meteredPipe implements p2p.MsgReadWriter and remembers the largest single -// message size sent through the pipe -type meteredPipe struct { - rw p2p.MsgReadWriter - maxSize uint32 -} - -func (m *meteredPipe) ReadMsg() (p2p.Msg, error) { - return m.rw.ReadMsg() -} - -func (m *meteredPipe) WriteMsg(msg p2p.Msg) error { - if msg.Size > m.maxSize { - m.maxSize = msg.Size - } - return m.rw.WriteMsg(msg) -} - -// measure runs a benchmark for a single type in a single pass, with the given -// number of requests -func (h *serverHandler) measure(setup *benchmarkSetup, count int) error { - clientPipe, serverPipe := p2p.MsgPipe() - clientMeteredPipe := &meteredPipe{rw: clientPipe} - serverMeteredPipe := &meteredPipe{rw: serverPipe} - var id enode.ID - crand.Read(id[:]) - - peer1 := newServerPeer(lpv2, NetworkId, false, p2p.NewPeer(id, "client", nil), clientMeteredPipe) - peer2 := newClientPeer(lpv2, NetworkId, p2p.NewPeer(id, "server", nil), serverMeteredPipe) - peer2.announceType = announceTypeNone - peer2.fcCosts = make(requestCostTable) - c := &requestCosts{} - for code := range requests { - peer2.fcCosts[code] = c - } - peer2.fcParams = flowcontrol.ServerParams{BufLimit: 1, MinRecharge: 1} - peer2.fcClient = flowcontrol.NewClientNode(h.server.fcManager, peer2.fcParams) - defer peer2.fcClient.Disconnect() - - if err := setup.req.init(h, count); err != nil { - return err - } - - errCh := make(chan error, 10) - start := mclock.Now() - - go func() { - for i := 0; i < count; i++ { - if err := setup.req.request(peer1, i); err != nil { - errCh <- err - return - } - } - }() - go func() { - for i := 0; i < count; i++ { - if err := h.handleMsg(peer2, &sync.WaitGroup{}); err != nil { - errCh <- err - return - } - } - }() - go func() { - for i := 0; i < count; i++ { - msg, err := clientPipe.ReadMsg() - if err != nil { - errCh <- err - return - } - var i interface{} - msg.Decode(&i) - } - // at this point we can be sure that the other two - // goroutines finished successfully too - close(errCh) - }() - select { - case err := <-errCh: - if err != nil { - return err - } - case <-h.closeCh: - clientPipe.Close() - serverPipe.Close() - return errors.New("Benchmark cancelled") - } - - setup.totalTime += time.Duration(mclock.Now() - start) - setup.totalCount += count - setup.maxInSize = clientMeteredPipe.maxSize - setup.maxOutSize = serverMeteredPipe.maxSize - clientPipe.Close() - serverPipe.Close() - return nil -} diff --git a/les/bloombits.go b/les/bloombits.go deleted file mode 100644 index a98524ce2..000000000 --- a/les/bloombits.go +++ /dev/null @@ -1,75 +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 les - -import ( - "time" - - "github.com/ethereum/go-ethereum/common/bitutil" - "github.com/ethereum/go-ethereum/light" -) - -const ( - // bloomServiceThreads is the number of goroutines used globally by an Ethereum - // instance to service bloombits lookups for all running filters. - bloomServiceThreads = 16 - - // bloomFilterThreads is the number of goroutines used locally per filter to - // multiplex requests onto the global servicing goroutines. - bloomFilterThreads = 3 - - // bloomRetrievalBatch is the maximum number of bloom bit retrievals to service - // in a single batch. - bloomRetrievalBatch = 16 - - // bloomRetrievalWait is the maximum time to wait for enough bloom bit requests - // to accumulate request an entire batch (avoiding hysteresis). - bloomRetrievalWait = time.Microsecond * 100 -) - -// startBloomHandlers starts a batch of goroutines to accept bloom bit database -// retrievals from possibly a range of filters and serving the data to satisfy. -func (eth *LightEthereum) startBloomHandlers(sectionSize uint64) { - for i := 0; i < bloomServiceThreads; i++ { - go func() { - defer eth.wg.Done() - for { - select { - case <-eth.closeCh: - return - - case request := <-eth.bloomRequests: - task := <-request - task.Bitsets = make([][]byte, len(task.Sections)) - compVectors, err := light.GetBloomBits(task.Context, eth.odr, task.Bit, task.Sections) - if err == nil { - for i := range task.Sections { - if blob, err := bitutil.DecompressBytes(compVectors[i], int(sectionSize/8)); err == nil { - task.Bitsets[i] = blob - } else { - task.Error = err - } - } - } else { - task.Error = err - } - request <- task - } - } - }() - } -} diff --git a/les/client.go b/les/client.go deleted file mode 100644 index 132c857aa..000000000 --- a/les/client.go +++ /dev/null @@ -1,376 +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 implements the Light Ethereum Subprotocol. -package les - -import ( - "errors" - "strings" - "time" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/bloombits" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/eth/gasprice" - "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/vflux" - vfc "github.com/ethereum/go-ethereum/les/vflux/client" - "github.com/ethereum/go-ethereum/light" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "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/params" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/trie" -) - -type LightEthereum struct { - lesCommons - - peers *serverPeerSet - reqDist *requestDistributor - retriever *retrieveManager - odr *LesOdr - relay *lesTxRelay - handler *clientHandler - txPool *light.TxPool - blockchain *light.LightChain - serverPool *vfc.ServerPool - serverPoolIterator enode.Iterator - merger *consensus.Merger - - bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests - bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports - - ApiBackend *LesApiBackend - eventMux *event.TypeMux - engine consensus.Engine - accountManager *accounts.Manager - netRPCService *ethapi.NetAPI - - p2pServer *p2p.Server - p2pConfig *p2p.Config - udpEnabled bool - - shutdownTracker *shutdowncheck.ShutdownTracker // Tracks if and when the node has shutdown ungracefully -} - -// New creates an instance of the light client. -func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { - chainDb, err := stack.OpenDatabase("lightchaindata", config.DatabaseCache, config.DatabaseHandles, "eth/db/chaindata/", false) - if err != nil { - return nil, err - } - lesDb, err := stack.OpenDatabase("les.client", 0, 0, "eth/db/lesclient/", false) - if err != nil { - return nil, err - } - var overrides core.ChainOverrides - if config.OverrideCancun != nil { - overrides.OverrideCancun = config.OverrideCancun - } - if config.OverrideVerkle != nil { - overrides.OverrideVerkle = config.OverrideVerkle - } - chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, trie.NewDatabase(chainDb), config.Genesis, &overrides) - if _, isCompat := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !isCompat { - return nil, genesisErr - } - engine, err := ethconfig.CreateConsensusEngine(chainConfig, chainDb) - if err != nil { - return nil, err - } - log.Info("") - log.Info(strings.Repeat("-", 153)) - for _, line := range strings.Split(chainConfig.Description(), "\n") { - log.Info(line) - } - log.Info(strings.Repeat("-", 153)) - log.Info("") - - peers := newServerPeerSet() - merger := consensus.NewMerger(chainDb) - leth := &LightEthereum{ - lesCommons: lesCommons{ - genesis: genesisHash, - config: config, - chainConfig: chainConfig, - iConfig: light.DefaultClientIndexerConfig, - chainDb: chainDb, - lesDb: lesDb, - closeCh: make(chan struct{}), - }, - peers: peers, - eventMux: stack.EventMux(), - reqDist: newRequestDistributor(peers, &mclock.System{}), - accountManager: stack.AccountManager(), - merger: merger, - engine: engine, - bloomRequests: make(chan chan *bloombits.Retrieval), - bloomIndexer: core.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), - p2pServer: stack.Server(), - p2pConfig: &stack.Config().P2P, - udpEnabled: stack.Config().P2P.DiscoveryV5, - shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb), - } - - var prenegQuery vfc.QueryFunc - if leth.udpEnabled { - prenegQuery = leth.prenegQuery - } - 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) - leth.relay = newLesTxRelay(peers, leth.retriever) - - leth.odr = NewLesOdr(chainDb, light.DefaultClientIndexerConfig, leth.peers, leth.retriever) - leth.chtIndexer = light.NewChtIndexer(chainDb, leth.odr, params.CHTFrequency, params.HelperTrieConfirmations, config.LightNoPrune) - leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, leth.odr, params.BloomBitsBlocksClient, params.BloomTrieFrequency, config.LightNoPrune) - leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer) - - // Note: NewLightChain adds the trusted checkpoint so it needs an ODR with - // indexers already set but not started yet - if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine); err != nil { - return nil, err - } - leth.chainReader = leth.blockchain - leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay) - - // Note: AddChildIndexer starts the update process for the child - leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer) - leth.chtIndexer.Start(leth.blockchain) - leth.bloomIndexer.Start(leth.blockchain) - - // 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) - if compat.RewindToTime > 0 { - leth.blockchain.SetHeadWithTimestamp(compat.RewindToTime) - } else { - leth.blockchain.SetHead(compat.RewindToBlock) - } - rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) - } - - leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, leth, nil} - gpoParams := config.GPO - if gpoParams.Default == nil { - gpoParams.Default = config.Miner.GasPrice - } - leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams) - - leth.handler = newClientHandler(leth) - leth.netRPCService = ethapi.NewNetAPI(leth.p2pServer, leth.config.NetworkId) - - // Register the backend on the node - stack.RegisterAPIs(leth.APIs()) - stack.RegisterProtocols(leth.Protocols()) - stack.RegisterLifecycle(leth) - - // Successful startup; push a marker and check previous unclean shutdowns. - leth.shutdownTracker.MarkStartup() - - return leth, nil -} - -// VfluxRequest sends a batch of requests to the given node through discv5 UDP TalkRequest and returns the responses -func (s *LightEthereum) VfluxRequest(n *enode.Node, reqs vflux.Requests) vflux.Replies { - if !s.udpEnabled { - return nil - } - reqsEnc, _ := rlp.EncodeToBytes(&reqs) - repliesEnc, _ := s.p2pServer.DiscV5.TalkRequest(s.serverPool.DialNode(n), "vfx", reqsEnc) - var replies vflux.Replies - if len(repliesEnc) == 0 || rlp.DecodeBytes(repliesEnc, &replies) != nil { - return nil - } - return replies -} - -// vfxVersion returns the version number of the "les" service subdomain of the vflux UDP -// service, as advertised in the ENR record -func (s *LightEthereum) vfxVersion(n *enode.Node) uint { - if n.Seq() == 0 { - var err error - if !s.udpEnabled { - return 0 - } - if n, err = s.p2pServer.DiscV5.RequestENR(n); n != nil && err == nil && n.Seq() != 0 { - s.serverPool.Persist(n) - } else { - return 0 - } - } - - var les []rlp.RawValue - if err := n.Load(enr.WithEntry("les", &les)); err != nil || len(les) < 1 { - return 0 - } - var version uint - rlp.DecodeBytes(les[0], &version) // Ignore additional fields (for forward compatibility). - return version -} - -// prenegQuery sends a capacity query to the given server node to determine whether -// a connection slot is immediately available -func (s *LightEthereum) prenegQuery(n *enode.Node) int { - if s.vfxVersion(n) < 1 { - // UDP query not supported, always try TCP connection - return 1 - } - - var requests vflux.Requests - requests.Add("les", vflux.CapacityQueryName, vflux.CapacityQueryReq{ - Bias: 180, - AddTokens: []vflux.IntOrInf{{}}, - }) - replies := s.VfluxRequest(n, requests) - var cqr vflux.CapacityQueryReply - if replies.Get(0, &cqr) != nil || len(cqr) != 1 { // Note: Get returns an error if replies is nil - return -1 - } - if cqr[0] > 0 { - return 1 - } - return 0 -} - -type LightDummyAPI struct{} - -// 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 sent to (alias for Etherbase) -func (s *LightDummyAPI) Coinbase() (common.Address, error) { - return common.Address{}, errors.New("mining is not supported in light mode") -} - -// Hashrate returns the POW hashrate -func (s *LightDummyAPI) Hashrate() hexutil.Uint { - return 0 -} - -// Mining returns an indication if this node is currently mining. -func (s *LightDummyAPI) Mining() bool { - return false -} - -// APIs returns the collection of RPC services the ethereum package offers. -// NOTE, some of these services probably need to be moved to somewhere else. -func (s *LightEthereum) APIs() []rpc.API { - apis := ethapi.GetAPIs(s.ApiBackend) - apis = append(apis, s.engine.APIs(s.BlockChain().HeaderChain())...) - return append(apis, []rpc.API{ - { - Namespace: "eth", - Service: &LightDummyAPI{}, - }, { - Namespace: "net", - Service: s.netRPCService, - }, { - Namespace: "vflux", - Service: s.serverPool.API(), - }, - }...) -} - -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) 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 { - return s.makeProtocols(ClientProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} { - if p := s.peers.peer(id.String()); p != nil { - return p.Info() - } - return nil - }, s.serverPoolIterator) -} - -// Start implements node.Lifecycle, starting all internal goroutines needed by the -// light ethereum protocol implementation. -func (s *LightEthereum) Start() error { - log.Warn("Light client mode is an experimental feature") - - // Regularly update shutdown marker - s.shutdownTracker.Start() - - if s.udpEnabled && s.p2pServer.DiscV5 == nil { - s.udpEnabled = false - log.Error("Discovery v5 is not initialized") - } - discovery, err := s.setupDiscovery() - if err != nil { - return err - } - s.serverPool.AddSource(discovery) - s.serverPool.Start() - // Start bloom request workers. - s.wg.Add(bloomServiceThreads) - s.startBloomHandlers(params.BloomBitsBlocksClient) - - return nil -} - -// Stop implements node.Lifecycle, terminating all internal goroutines used by the -// Ethereum protocol. -func (s *LightEthereum) Stop() error { - close(s.closeCh) - s.serverPool.Stop() - s.peers.close() - s.reqDist.close() - s.odr.Stop() - s.relay.Stop() - s.bloomIndexer.Close() - s.chtIndexer.Close() - s.blockchain.Stop() - s.handler.stop() - s.txPool.Stop() - s.engine.Close() - s.eventMux.Stop() - // Clean shutdown marker as the last thing before closing db - s.shutdownTracker.Stop() - - s.chainDb.Close() - s.lesDb.Close() - s.wg.Wait() - log.Info("Light ethereum stopped") - return nil -} diff --git a/les/client_handler.go b/les/client_handler.go deleted file mode 100644 index 39965d4c9..000000000 --- a/les/client_handler.go +++ /dev/null @@ -1,308 +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 ( - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "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/light" - "github.com/ethereum/go-ethereum/p2p" -) - -// clientHandler is responsible for receiving and processing all incoming server -// responses. -type clientHandler struct { - forkFilter forkid.Filter - backend *LightEthereum - - closeCh chan struct{} - wg sync.WaitGroup // WaitGroup used to track all connected peers. -} - -func newClientHandler(backend *LightEthereum) *clientHandler { - handler := &clientHandler{ - forkFilter: forkid.NewFilter(backend.blockchain), - backend: backend, - closeCh: make(chan struct{}), - } - return handler -} - -func (h *clientHandler) stop() { - close(h.closeCh) - 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 { - 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() - err := h.handle(peer, false) - return err -} - -func (h *clientHandler) handle(p *serverPeer, noInitAnnounce bool) error { - if h.backend.peers.len() >= h.backend.config.LightPeers && !p.Peer.Info().Network.Trusted { - return p2p.DiscTooManyPeers - } - 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) - 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 - } - // Register peer with the server pool - if h.backend.serverPool != nil { - if nvt, err := h.backend.serverPool.RegisterNode(p.Node()); err == nil { - p.setValueTracker(nvt) - p.updateVtParams() - defer func() { - p.setValueTracker(nil) - h.backend.serverPool.UnregisterNode(p.Node()) - }() - } else { - return err - } - } - // Register the peer locally - if err := h.backend.peers.register(p); err != nil { - p.Log().Error("Light Ethereum peer registration failed", "err", err) - return err - } - - serverConnectionGauge.Update(int64(h.backend.peers.len())) - - connectedAt := mclock.Now() - defer func() { - h.backend.peers.unregister(p.id) - connectionTimer.Update(time.Duration(mclock.Now() - connectedAt)) - serverConnectionGauge.Update(int64(h.backend.peers.len())) - }() - - // Mark the peer starts to be served. - p.serving.Store(true) - defer p.serving.Store(false) - - // Spawn a main loop to handle all incoming messages. - for { - if err := h.handleMsg(p); err != nil { - p.Log().Debug("Light Ethereum message handling failed", "err", err) - p.fcServer.DumpLogs() - return err - } - } -} - -// handleMsg is invoked whenever an inbound message is received from a remote -// peer. The remote connection is torn down upon returning any error. -func (h *clientHandler) handleMsg(p *serverPeer) error { - // Read the next message from the remote peer, and ensure it's fully consumed - msg, err := p.rw.ReadMsg() - if err != nil { - return err - } - p.Log().Trace("Light Ethereum message arrived", "code", msg.Code, "bytes", msg.Size) - - if msg.Size > ProtocolMaxMsgSize { - return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) - } - defer msg.Discard() - - var deliverMsg *Msg - - // Handle the message depending on its contents - switch { - case msg.Code == AnnounceMsg: - p.Log().Trace("Received announce message") - var req announceData - if err := msg.Decode(&req); err != nil { - return errResp(ErrDecode, "%v: %v", msg, err) - } - if err := req.sanityCheck(); err != nil { - return err - } - update, size := req.Update.decode() - if p.rejectUpdate(size) { - return errResp(ErrRequestRejected, "") - } - p.updateFlowControl(update) - p.updateVtParams() - - if req.Hash != (common.Hash{}) { - if p.announceType == announceTypeNone { - return errResp(ErrUnexpectedResponse, "") - } - if p.announceType == announceTypeSigned { - if err := req.checkSignature(p.ID(), update); err != nil { - p.Log().Trace("Invalid announcement signature", "err", err) - return err - } - p.Log().Trace("Valid announcement signature") - } - p.Log().Trace("Announce message content", "number", req.Number, "hash", req.Hash, "td", req.Td, "reorg", req.ReorgDepth) - - // Update peer head information first and then notify the announcement - p.updateHead(req.Hash, req.Number, req.Td) - } - case msg.Code == BlockHeadersMsg: - p.Log().Trace("Received block header response message") - var resp struct { - ReqID, BV uint64 - Headers []*types.Header - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.ReceivedReply(resp.ReqID, resp.BV) - p.answeredRequest(resp.ReqID) - - deliverMsg = &Msg{ - MsgType: MsgBlockHeaders, - ReqID: resp.ReqID, - Obj: resp.Headers, - } - case msg.Code == BlockBodiesMsg: - p.Log().Trace("Received block bodies response") - var resp struct { - ReqID, BV uint64 - Data []*types.Body - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.ReceivedReply(resp.ReqID, resp.BV) - p.answeredRequest(resp.ReqID) - deliverMsg = &Msg{ - MsgType: MsgBlockBodies, - ReqID: resp.ReqID, - Obj: resp.Data, - } - case msg.Code == CodeMsg: - p.Log().Trace("Received code response") - var resp struct { - ReqID, BV uint64 - Data [][]byte - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.ReceivedReply(resp.ReqID, resp.BV) - p.answeredRequest(resp.ReqID) - deliverMsg = &Msg{ - MsgType: MsgCode, - ReqID: resp.ReqID, - Obj: resp.Data, - } - case msg.Code == ReceiptsMsg: - p.Log().Trace("Received receipts response") - var resp struct { - ReqID, BV uint64 - Receipts []types.Receipts - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.ReceivedReply(resp.ReqID, resp.BV) - p.answeredRequest(resp.ReqID) - deliverMsg = &Msg{ - MsgType: MsgReceipts, - ReqID: resp.ReqID, - Obj: resp.Receipts, - } - case msg.Code == ProofsV2Msg: - p.Log().Trace("Received les/2 proofs response") - var resp struct { - ReqID, BV uint64 - Data light.NodeList - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.ReceivedReply(resp.ReqID, resp.BV) - p.answeredRequest(resp.ReqID) - deliverMsg = &Msg{ - MsgType: MsgProofsV2, - ReqID: resp.ReqID, - Obj: resp.Data, - } - case msg.Code == HelperTrieProofsMsg: - p.Log().Trace("Received helper trie proof response") - var resp struct { - ReqID, BV uint64 - Data HelperTrieResps - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.ReceivedReply(resp.ReqID, resp.BV) - p.answeredRequest(resp.ReqID) - deliverMsg = &Msg{ - MsgType: MsgHelperTrieProofs, - ReqID: resp.ReqID, - Obj: resp.Data, - } - case msg.Code == TxStatusMsg: - p.Log().Trace("Received tx status response") - var resp struct { - ReqID, BV uint64 - Status []light.TxStatus - } - if err := msg.Decode(&resp); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.ReceivedReply(resp.ReqID, resp.BV) - p.answeredRequest(resp.ReqID) - deliverMsg = &Msg{ - MsgType: MsgTxStatus, - ReqID: resp.ReqID, - Obj: resp.Status, - } - case msg.Code == StopMsg && p.version >= lpv3: - p.freeze() - h.backend.retriever.frozen(p) - p.Log().Debug("Service stopped") - case msg.Code == ResumeMsg && p.version >= lpv3: - var bv uint64 - if err := msg.Decode(&bv); err != nil { - return errResp(ErrDecode, "msg %v: %v", msg, err) - } - p.fcServer.ResumeFreeze(bv) - p.unfreeze() - p.Log().Debug("Service resumed") - default: - p.Log().Trace("Received invalid message", "code", msg.Code) - return errResp(ErrInvalidMsgCode, "%v", msg.Code) - } - // Deliver the received response to retriever. - if deliverMsg != nil { - if err := h.backend.retriever.deliver(p, deliverMsg); err != nil { - if val := p.errCount.Add(1, mclock.Now()); val > maxResponseErrors { - return err - } - } - } - return nil -} diff --git a/les/commons.go b/les/commons.go deleted file mode 100644 index cb3fc430b..000000000 --- a/les/commons.go +++ /dev/null @@ -1,99 +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 ( - "fmt" - "math/big" - "sync" - - "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/types" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/light" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/params" -) - -func errResp(code errCode, format string, v ...interface{}) error { - return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...)) -} - -type chainReader interface { - CurrentHeader() *types.Header -} - -// lesCommons contains fields needed by both server and client. -type lesCommons struct { - genesis common.Hash - config *ethconfig.Config - chainConfig *params.ChainConfig - iConfig *light.IndexerConfig - chainDb, lesDb ethdb.Database - chainReader chainReader - chtIndexer, bloomTrieIndexer *core.ChainIndexer - - closeCh chan struct{} - wg sync.WaitGroup -} - -// 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, 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 - Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block -} - -// makeProtocols creates protocol descriptors for the given LES versions. -func (c *lesCommons) makeProtocols(versions []uint, runPeer func(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error, peerInfo func(id enode.ID) interface{}, dialCandidates enode.Iterator) []p2p.Protocol { - protos := make([]p2p.Protocol, len(versions)) - for i, version := range versions { - version := version - protos[i] = p2p.Protocol{ - Name: "les", - Version: version, - Length: ProtocolLengths[version], - NodeInfo: c.nodeInfo, - Run: func(peer *p2p.Peer, rw p2p.MsgReadWriter) error { - return runPeer(version, peer, rw) - }, - PeerInfo: peerInfo, - DialCandidates: dialCandidates, - } - } - return protos -} - -// nodeInfo retrieves some protocol metadata about the running host node. -func (c *lesCommons) nodeInfo() interface{} { - head := c.chainReader.CurrentHeader() - hash := head.Hash() - return &NodeInfo{ - Network: c.config.NetworkId, - Difficulty: rawdb.ReadTd(c.chainDb, hash, head.Number.Uint64()), - Genesis: c.genesis, - Config: c.chainConfig, - Head: hash, - } -} diff --git a/les/costtracker.go b/les/costtracker.go deleted file mode 100644 index 43e32a5b2..000000000 --- a/les/costtracker.go +++ /dev/null @@ -1,517 +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 ( - "encoding/binary" - "math" - "sync" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/les/flowcontrol" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" -) - -const makeCostStats = false // make request cost statistics during operation - -var ( - // average request cost estimates based on serving time - reqAvgTimeCost = requestCostTable{ - GetBlockHeadersMsg: {150000, 30000}, - GetBlockBodiesMsg: {0, 700000}, - GetReceiptsMsg: {0, 1000000}, - GetCodeMsg: {0, 450000}, - GetProofsV2Msg: {0, 600000}, - GetHelperTrieProofsMsg: {0, 1000000}, - SendTxV2Msg: {0, 450000}, - GetTxStatusMsg: {0, 250000}, - } - // maximum incoming message size estimates - reqMaxInSize = requestCostTable{ - GetBlockHeadersMsg: {40, 0}, - GetBlockBodiesMsg: {0, 40}, - GetReceiptsMsg: {0, 40}, - GetCodeMsg: {0, 80}, - GetProofsV2Msg: {0, 80}, - GetHelperTrieProofsMsg: {0, 20}, - SendTxV2Msg: {0, 16500}, - GetTxStatusMsg: {0, 50}, - } - // maximum outgoing message size estimates - reqMaxOutSize = requestCostTable{ - GetBlockHeadersMsg: {0, 556}, - GetBlockBodiesMsg: {0, 100000}, - GetReceiptsMsg: {0, 200000}, - GetCodeMsg: {0, 50000}, - GetProofsV2Msg: {0, 4000}, - GetHelperTrieProofsMsg: {0, 4000}, - SendTxV2Msg: {0, 100}, - GetTxStatusMsg: {0, 100}, - } - // request amounts that have to fit into the minimum buffer size minBufferMultiplier times - minBufferReqAmount = map[uint64]uint64{ - GetBlockHeadersMsg: 192, - GetBlockBodiesMsg: 1, - GetReceiptsMsg: 1, - GetCodeMsg: 1, - GetProofsV2Msg: 1, - GetHelperTrieProofsMsg: 16, - SendTxV2Msg: 8, - GetTxStatusMsg: 64, - } - minBufferMultiplier = 3 -) - -const ( - maxCostFactor = 2 // ratio of maximum and average cost estimates - bufLimitRatio = 6000 // fixed bufLimit/MRR ratio - gfUsageThreshold = 0.5 - gfUsageTC = time.Second - gfRaiseTC = time.Second * 200 - gfDropTC = time.Second * 50 - gfDbKey = "_globalCostFactorV6" -) - -// costTracker is responsible for calculating costs and cost estimates on the -// server side. It continuously updates the global cost factor which is defined -// as the number of cost units per nanosecond of serving time in a single thread. -// It is based on statistics collected during serving requests in high-load periods -// and practically acts as a one-dimension request price scaling factor over the -// pre-defined cost estimate table. -// -// The reason for dynamically maintaining the global factor on the server side is: -// the estimated time cost of the request is fixed(hardcoded) but the configuration -// of the machine running the server is really different. Therefore, the request serving -// time in different machine will vary greatly. And also, the request serving time -// in same machine may vary greatly with different request pressure. -// -// In order to more effectively limit resources, we apply the global factor to serving -// time to make the result as close as possible to the estimated time cost no matter -// the server is slow or fast. And also we scale the totalRecharge with global factor -// so that fast server can serve more requests than estimation and slow server can -// reduce request pressure. -// -// Instead of scaling the cost values, the real value of cost units is changed by -// applying the factor to the serving times. This is more convenient because the -// changes in the cost factor can be applied immediately without always notifying -// the clients about the changed cost tables. -type costTracker struct { - db ethdb.Database - stopCh chan chan struct{} - - inSizeFactor float64 - outSizeFactor float64 - factor float64 - utilTarget float64 - minBufLimit uint64 - - gfLock sync.RWMutex - reqInfoCh chan reqInfo - totalRechargeCh chan uint64 - - stats map[uint64][]uint64 // Used for testing purpose. - - // TestHooks - testing bool // Disable real cost evaluation for testing purpose. - testCostList RequestCostList // Customized cost table for testing purpose. -} - -// newCostTracker creates a cost tracker and loads the cost factor statistics from the database. -// It also returns the minimum capacity that can be assigned to any peer. -func newCostTracker(db ethdb.Database, config *ethconfig.Config) (*costTracker, uint64) { - utilTarget := float64(config.LightServ) * flowcontrol.FixedPointMultiplier / 100 - ct := &costTracker{ - db: db, - stopCh: make(chan chan struct{}), - reqInfoCh: make(chan reqInfo, 100), - utilTarget: utilTarget, - } - if config.LightIngress > 0 { - ct.inSizeFactor = utilTarget / float64(config.LightIngress) - } - if config.LightEgress > 0 { - ct.outSizeFactor = utilTarget / float64(config.LightEgress) - } - if makeCostStats { - ct.stats = make(map[uint64][]uint64) - for code := range reqAvgTimeCost { - ct.stats[code] = make([]uint64, 10) - } - } - ct.gfLoop() - costList := ct.makeCostList(ct.globalFactor() * 1.25) - for _, c := range costList { - amount := minBufferReqAmount[c.MsgCode] - cost := c.BaseCost + amount*c.ReqCost - if cost > ct.minBufLimit { - ct.minBufLimit = cost - } - } - ct.minBufLimit *= uint64(minBufferMultiplier) - return ct, (ct.minBufLimit-1)/bufLimitRatio + 1 -} - -// stop stops the cost tracker and saves the cost factor statistics to the database -func (ct *costTracker) stop() { - stopCh := make(chan struct{}) - ct.stopCh <- stopCh - <-stopCh - if makeCostStats { - ct.printStats() - } -} - -// makeCostList returns upper cost estimates based on the hardcoded cost estimate -// tables and the optionally specified incoming/outgoing bandwidth limits -func (ct *costTracker) makeCostList(globalFactor float64) RequestCostList { - maxCost := func(avgTimeCost, inSize, outSize uint64) uint64 { - cost := avgTimeCost * maxCostFactor - inSizeCost := uint64(float64(inSize) * ct.inSizeFactor * globalFactor) - if inSizeCost > cost { - cost = inSizeCost - } - outSizeCost := uint64(float64(outSize) * ct.outSizeFactor * globalFactor) - if outSizeCost > cost { - cost = outSizeCost - } - return cost - } - var list RequestCostList - for code, data := range reqAvgTimeCost { - baseCost := maxCost(data.baseCost, reqMaxInSize[code].baseCost, reqMaxOutSize[code].baseCost) - reqCost := maxCost(data.reqCost, reqMaxInSize[code].reqCost, reqMaxOutSize[code].reqCost) - if ct.minBufLimit != 0 { - // if minBufLimit is set then always enforce maximum request cost <= minBufLimit - maxCost := baseCost + reqCost*minBufferReqAmount[code] - if maxCost > ct.minBufLimit { - mul := 0.999 * float64(ct.minBufLimit) / float64(maxCost) - baseCost = uint64(float64(baseCost) * mul) - reqCost = uint64(float64(reqCost) * mul) - } - } - - list = append(list, requestCostListItem{ - MsgCode: code, - BaseCost: baseCost, - ReqCost: reqCost, - }) - } - return list -} - -// reqInfo contains the estimated time cost and the actual request serving time -// which acts as a feed source to update factor maintained by costTracker. -type reqInfo struct { - // avgTimeCost is the estimated time cost corresponding to maxCostTable. - avgTimeCost float64 - - // servingTime is the CPU time corresponding to the actual processing of - // the request. - servingTime float64 - - // msgCode indicates the type of request. - msgCode uint64 -} - -// gfLoop starts an event loop which updates the global cost factor which is -// calculated as a weighted average of the average estimate / serving time ratio. -// The applied weight equals the serving time if gfUsage is over a threshold, -// zero otherwise. gfUsage is the recent average serving time per time unit in -// an exponential moving window. This ensures that statistics are collected only -// under high-load circumstances where the measured serving times are relevant. -// The total recharge parameter of the flow control system which controls the -// total allowed serving time per second but nominated in cost units, should -// also be scaled with the cost factor and is also updated by this loop. -func (ct *costTracker) gfLoop() { - var ( - factor, totalRecharge float64 - gfLog, recentTime, recentAvg float64 - - lastUpdate, expUpdate = mclock.Now(), mclock.Now() - ) - - // Load historical cost factor statistics from the database. - data, _ := ct.db.Get([]byte(gfDbKey)) - if len(data) == 8 { - gfLog = math.Float64frombits(binary.BigEndian.Uint64(data[:])) - } - ct.factor = math.Exp(gfLog) - factor, totalRecharge = ct.factor, ct.utilTarget*ct.factor - - // In order to perform factor data statistics under the high request pressure, - // we only adjust factor when recent factor usage beyond the threshold. - threshold := gfUsageThreshold * float64(gfUsageTC) * ct.utilTarget / flowcontrol.FixedPointMultiplier - - go func() { - saveCostFactor := func() { - var data [8]byte - binary.BigEndian.PutUint64(data[:], math.Float64bits(gfLog)) - ct.db.Put([]byte(gfDbKey), data[:]) - log.Debug("global cost factor saved", "value", factor) - } - saveTicker := time.NewTicker(time.Minute * 10) - defer saveTicker.Stop() - - for { - select { - case r := <-ct.reqInfoCh: - relCost := int64(factor * r.servingTime * 100 / r.avgTimeCost) // Convert the value to a percentage form - - // Record more metrics if we are debugging - if metrics.EnabledExpensive { - switch r.msgCode { - case GetBlockHeadersMsg: - relativeCostHeaderHistogram.Update(relCost) - case GetBlockBodiesMsg: - relativeCostBodyHistogram.Update(relCost) - case GetReceiptsMsg: - relativeCostReceiptHistogram.Update(relCost) - case GetCodeMsg: - relativeCostCodeHistogram.Update(relCost) - case GetProofsV2Msg: - relativeCostProofHistogram.Update(relCost) - case GetHelperTrieProofsMsg: - relativeCostHelperProofHistogram.Update(relCost) - case SendTxV2Msg: - relativeCostSendTxHistogram.Update(relCost) - case GetTxStatusMsg: - relativeCostTxStatusHistogram.Update(relCost) - } - } - // SendTxV2 and GetTxStatus requests are two special cases. - // All other requests will only put pressure on the database, and - // the corresponding delay is relatively stable. While these two - // requests involve txpool query, which is usually unstable. - // - // TODO(rjl493456442) fixes this. - if r.msgCode == SendTxV2Msg || r.msgCode == GetTxStatusMsg { - continue - } - requestServedMeter.Mark(int64(r.servingTime)) - requestServedTimer.Update(time.Duration(r.servingTime)) - requestEstimatedMeter.Mark(int64(r.avgTimeCost / factor)) - requestEstimatedTimer.Update(time.Duration(r.avgTimeCost / factor)) - relativeCostHistogram.Update(relCost) - - now := mclock.Now() - dt := float64(now - expUpdate) - expUpdate = now - exp := math.Exp(-dt / float64(gfUsageTC)) - - // calculate factor correction until now, based on previous values - var gfCorr float64 - max := recentTime - if recentAvg > max { - max = recentAvg - } - // we apply continuous correction when MAX(recentTime, recentAvg) > threshold - if max > threshold { - // calculate correction time between last expUpdate and now - if max*exp >= threshold { - gfCorr = dt - } else { - gfCorr = math.Log(max/threshold) * float64(gfUsageTC) - } - // calculate log(factor) correction with the right direction and time constant - if recentTime > recentAvg { - // drop factor if actual serving times are larger than average estimates - gfCorr /= -float64(gfDropTC) - } else { - // raise factor if actual serving times are smaller than average estimates - gfCorr /= float64(gfRaiseTC) - } - } - // update recent cost values with current request - recentTime = recentTime*exp + r.servingTime - recentAvg = recentAvg*exp + r.avgTimeCost/factor - - if gfCorr != 0 { - // Apply the correction to factor - gfLog += gfCorr - factor = math.Exp(gfLog) - // Notify outside modules the new factor and totalRecharge. - if time.Duration(now-lastUpdate) > time.Second { - totalRecharge, lastUpdate = ct.utilTarget*factor, now - ct.gfLock.Lock() - ct.factor = factor - ch := ct.totalRechargeCh - ct.gfLock.Unlock() - if ch != nil { - select { - case ct.totalRechargeCh <- uint64(totalRecharge): - default: - } - } - globalFactorGauge.Update(int64(1000 * factor)) - log.Debug("global cost factor updated", "factor", factor) - } - } - recentServedGauge.Update(int64(recentTime)) - recentEstimatedGauge.Update(int64(recentAvg)) - - case <-saveTicker.C: - saveCostFactor() - - case stopCh := <-ct.stopCh: - saveCostFactor() - close(stopCh) - return - } - } - }() -} - -// globalFactor returns the current value of the global cost factor -func (ct *costTracker) globalFactor() float64 { - ct.gfLock.RLock() - defer ct.gfLock.RUnlock() - - return ct.factor -} - -// totalRecharge returns the current total recharge parameter which is used by -// flowcontrol.ClientManager and is scaled by the global cost factor -func (ct *costTracker) totalRecharge() uint64 { - ct.gfLock.RLock() - defer ct.gfLock.RUnlock() - - return uint64(ct.factor * ct.utilTarget) -} - -// subscribeTotalRecharge returns all future updates to the total recharge value -// through a channel and also returns the current value -func (ct *costTracker) subscribeTotalRecharge(ch chan uint64) uint64 { - ct.gfLock.Lock() - defer ct.gfLock.Unlock() - - ct.totalRechargeCh = ch - return uint64(ct.factor * ct.utilTarget) -} - -// updateStats updates the global cost factor and (if enabled) the real cost vs. -// average estimate statistics -func (ct *costTracker) updateStats(code, amount, servingTime, realCost uint64) { - avg := reqAvgTimeCost[code] - avgTimeCost := avg.baseCost + amount*avg.reqCost - select { - case ct.reqInfoCh <- reqInfo{float64(avgTimeCost), float64(servingTime), code}: - default: - } - if makeCostStats { - realCost <<= 4 - l := 0 - for l < 9 && realCost > avgTimeCost { - l++ - realCost >>= 1 - } - atomic.AddUint64(&ct.stats[code][l], 1) - } -} - -// realCost calculates the final cost of a request based on actual serving time, -// incoming and outgoing message size -// -// Note: message size is only taken into account if bandwidth limitation is applied -// and the cost based on either message size is greater than the cost based on -// serving time. A maximum of the three costs is applied instead of their sum -// because the three limited resources (serving thread time and i/o bandwidth) can -// also be maxed out simultaneously. -func (ct *costTracker) realCost(servingTime uint64, inSize, outSize uint32) uint64 { - cost := float64(servingTime) - inSizeCost := float64(inSize) * ct.inSizeFactor - if inSizeCost > cost { - cost = inSizeCost - } - outSizeCost := float64(outSize) * ct.outSizeFactor - if outSizeCost > cost { - cost = outSizeCost - } - return uint64(cost * ct.globalFactor()) -} - -// printStats prints the distribution of real request cost relative to the average estimates -func (ct *costTracker) printStats() { - if ct.stats == nil { - 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]) - } -} - -type ( - // requestCostTable assigns a cost estimate function to each request type - // which is a linear function of the requested amount - // (cost = baseCost + reqCost * amount) - requestCostTable map[uint64]*requestCosts - requestCosts struct { - baseCost, reqCost uint64 - } - - // RequestCostList is a list representation of request costs which is used for - // database storage and communication through the network - RequestCostList []requestCostListItem - requestCostListItem struct { - MsgCode, BaseCost, ReqCost uint64 - } -) - -// getMaxCost calculates the estimated cost for a given request type and amount -func (table requestCostTable) getMaxCost(code, amount uint64) uint64 { - costs := table[code] - return costs.baseCost + amount*costs.reqCost -} - -// decode converts a cost list to a cost table -func (list RequestCostList) decode(protocolLength uint64) requestCostTable { - table := make(requestCostTable) - for _, e := range list { - if e.MsgCode < protocolLength { - table[e.MsgCode] = &requestCosts{ - baseCost: e.BaseCost, - reqCost: e.ReqCost, - } - } - } - return table -} - -// testCostList returns a dummy request cost list used by tests -func testCostList(testCost uint64) RequestCostList { - cl := make(RequestCostList, len(reqAvgTimeCost)) - var max uint64 - for code := range reqAvgTimeCost { - if code > max { - max = code - } - } - i := 0 - for code := uint64(0); code <= max; code++ { - if _, ok := reqAvgTimeCost[code]; ok { - cl[i].MsgCode = code - cl[i].BaseCost = testCost - cl[i].ReqCost = 0 - i++ - } - } - return cl -} diff --git a/les/distributor.go b/les/distributor.go deleted file mode 100644 index a0319c67f..000000000 --- a/les/distributor.go +++ /dev/null @@ -1,313 +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 les - -import ( - "container/list" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/les/utils" -) - -// requestDistributor implements a mechanism that distributes requests to -// suitable peers, obeying flow control rules and prioritizing them in creation -// order (even when a resend is necessary). -type requestDistributor struct { - clock mclock.Clock - reqQueue *list.List - lastReqOrder uint64 - peers map[distPeer]struct{} - peerLock sync.RWMutex - loopChn chan struct{} - loopNextSent bool - lock sync.Mutex - - closeCh chan struct{} - wg sync.WaitGroup -} - -// distPeer is an LES server peer interface for the request distributor. -// waitBefore returns either the necessary waiting time before sending a request -// with the given upper estimated cost or the estimated remaining relative buffer -// value after sending such a request (in which case the request can be sent -// immediately). At least one of these values is always zero. -type distPeer interface { - waitBefore(uint64) (time.Duration, float64) - canQueue() bool - queueSend(f func()) bool -} - -// distReq is the request abstraction used by the distributor. It is based on -// three callback functions: -// - getCost returns the upper estimate of the cost of sending the request to a given peer -// - canSend tells if the server peer is suitable to serve the request -// - request prepares sending the request to the given peer and returns a function that -// does the actual sending. Request order should be preserved but the callback itself should not -// block until it is sent because other peers might still be able to receive requests while -// one of them is blocking. Instead, the returned function is put in the peer's send queue. -type distReq struct { - getCost func(distPeer) uint64 - canSend func(distPeer) bool - request func(distPeer) func() - - reqOrder uint64 - sentChn chan distPeer - element *list.Element - waitForPeers mclock.AbsTime - enterQueue mclock.AbsTime -} - -// newRequestDistributor creates a new request distributor -func newRequestDistributor(peers *serverPeerSet, clock mclock.Clock) *requestDistributor { - d := &requestDistributor{ - clock: clock, - reqQueue: list.New(), - loopChn: make(chan struct{}, 2), - closeCh: make(chan struct{}), - peers: make(map[distPeer]struct{}), - } - if peers != nil { - peers.subscribe(d) - } - d.wg.Add(1) - go d.loop() - return d -} - -// registerPeer implements peerSetNotify -func (d *requestDistributor) registerPeer(p *serverPeer) { - d.peerLock.Lock() - d.peers[p] = struct{}{} - d.peerLock.Unlock() -} - -// unregisterPeer implements peerSetNotify -func (d *requestDistributor) unregisterPeer(p *serverPeer) { - d.peerLock.Lock() - delete(d.peers, p) - d.peerLock.Unlock() -} - -// registerTestPeer adds a new test peer -func (d *requestDistributor) registerTestPeer(p distPeer) { - d.peerLock.Lock() - d.peers[p] = struct{}{} - d.peerLock.Unlock() -} - -var ( - // distMaxWait is the maximum waiting time after which further necessary waiting - // times are recalculated based on new feedback from the servers - distMaxWait = time.Millisecond * 50 - - // waitForPeers is the time window in which a request does not fail even if it - // has no suitable peers to send to at the moment - waitForPeers = time.Second * 3 -) - -// main event loop -func (d *requestDistributor) loop() { - defer d.wg.Done() - for { - select { - case <-d.closeCh: - d.lock.Lock() - elem := d.reqQueue.Front() - for elem != nil { - req := elem.Value.(*distReq) - close(req.sentChn) - req.sentChn = nil - elem = elem.Next() - } - d.lock.Unlock() - return - case <-d.loopChn: - d.lock.Lock() - d.loopNextSent = false - loop: - for { - peer, req, wait := d.nextRequest() - if req != nil && wait == 0 { - chn := req.sentChn // save sentChn because remove sets it to nil - d.remove(req) - send := req.request(peer) - if send != nil { - peer.queueSend(send) - requestSendDelay.Update(time.Duration(d.clock.Now() - req.enterQueue)) - } - chn <- peer - close(chn) - } else { - if wait == 0 { - // no request to send and nothing to wait for; the next - // queued request will wake up the loop - break loop - } - d.loopNextSent = true // a "next" signal has been sent, do not send another one until this one has been received - if wait > distMaxWait { - // waiting times may be reduced by incoming request replies, if it is too long, recalculate it periodically - wait = distMaxWait - } - go func() { - d.clock.Sleep(wait) - d.loopChn <- struct{}{} - }() - break loop - } - } - d.lock.Unlock() - } - } -} - -// selectPeerItem represents a peer to be selected for a request by weightedRandomSelect -type selectPeerItem struct { - peer distPeer - req *distReq - weight uint64 -} - -func selectPeerWeight(i interface{}) uint64 { - return i.(selectPeerItem).weight -} - -// nextRequest returns the next possible request from any peer, along with the -// associated peer and necessary waiting time -func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) { - checkedPeers := make(map[distPeer]struct{}) - elem := d.reqQueue.Front() - var ( - bestWait time.Duration - sel *utils.WeightedRandomSelect - ) - - d.peerLock.RLock() - defer d.peerLock.RUnlock() - - peerCount := len(d.peers) - for (len(checkedPeers) < peerCount || elem == d.reqQueue.Front()) && elem != nil { - req := elem.Value.(*distReq) - canSend := false - now := d.clock.Now() - if req.waitForPeers > now { - canSend = true - wait := time.Duration(req.waitForPeers - now) - if bestWait == 0 || wait < bestWait { - bestWait = wait - } - } - for peer := range d.peers { - if _, ok := checkedPeers[peer]; !ok && peer.canQueue() && req.canSend(peer) { - canSend = true - cost := req.getCost(peer) - wait, bufRemain := peer.waitBefore(cost) - if wait == 0 { - if sel == nil { - sel = utils.NewWeightedRandomSelect(selectPeerWeight) - } - sel.Update(selectPeerItem{peer: peer, req: req, weight: uint64(bufRemain*1000000) + 1}) - } else { - if bestWait == 0 || wait < bestWait { - bestWait = wait - } - } - checkedPeers[peer] = struct{}{} - } - } - next := elem.Next() - if !canSend && elem == d.reqQueue.Front() { - close(req.sentChn) - d.remove(req) - } - elem = next - } - - if sel != nil { - c := sel.Choose().(selectPeerItem) - return c.peer, c.req, 0 - } - return nil, nil, bestWait -} - -// queue adds a request to the distribution queue, returns a channel where the -// receiving peer is sent once the request has been sent (request callback returned). -// If the request is cancelled or timed out without suitable peers, the channel is -// closed without sending any peer references to it. -func (d *requestDistributor) queue(r *distReq) chan distPeer { - d.lock.Lock() - defer d.lock.Unlock() - - if r.reqOrder == 0 { - d.lastReqOrder++ - r.reqOrder = d.lastReqOrder - r.waitForPeers = d.clock.Now().Add(waitForPeers) - } - // Assign the timestamp when the request is queued no matter it's - // a new one or re-queued one. - r.enterQueue = d.clock.Now() - - back := d.reqQueue.Back() - if back == nil || r.reqOrder > back.Value.(*distReq).reqOrder { - r.element = d.reqQueue.PushBack(r) - } else { - before := d.reqQueue.Front() - for before.Value.(*distReq).reqOrder < r.reqOrder { - before = before.Next() - } - r.element = d.reqQueue.InsertBefore(r, before) - } - - if !d.loopNextSent { - d.loopNextSent = true - d.loopChn <- struct{}{} - } - - r.sentChn = make(chan distPeer, 1) - return r.sentChn -} - -// cancel removes a request from the queue if it has not been sent yet (returns -// false if it has been sent already). It is guaranteed that the callback functions -// will not be called after cancel returns. -func (d *requestDistributor) cancel(r *distReq) bool { - d.lock.Lock() - defer d.lock.Unlock() - - if r.sentChn == nil { - return false - } - - close(r.sentChn) - d.remove(r) - return true -} - -// remove removes a request from the queue -func (d *requestDistributor) remove(r *distReq) { - r.sentChn = nil - if r.element != nil { - d.reqQueue.Remove(r.element) - r.element = nil - } -} - -func (d *requestDistributor) close() { - close(d.closeCh) - d.wg.Wait() -} diff --git a/les/distributor_test.go b/les/distributor_test.go deleted file mode 100644 index 9a93dba14..000000000 --- a/les/distributor_test.go +++ /dev/null @@ -1,189 +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 les - -import ( - "math/rand" - "sync" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" -) - -type testDistReq struct { - cost, procTime, order uint64 - canSendTo map[*testDistPeer]struct{} -} - -func (r *testDistReq) getCost(dp distPeer) uint64 { - return r.cost -} - -func (r *testDistReq) canSend(dp distPeer) bool { - _, ok := r.canSendTo[dp.(*testDistPeer)] - return ok -} - -func (r *testDistReq) request(dp distPeer) func() { - return func() { dp.(*testDistPeer).send(r) } -} - -type testDistPeer struct { - sent []*testDistReq - sumCost uint64 - lock sync.RWMutex -} - -func (p *testDistPeer) send(r *testDistReq) { - p.lock.Lock() - defer p.lock.Unlock() - - p.sent = append(p.sent, r) - p.sumCost += r.cost -} - -func (p *testDistPeer) worker(t *testing.T, checkOrder bool, stop chan struct{}) { - var last uint64 - for { - wait := time.Millisecond - p.lock.Lock() - if len(p.sent) > 0 { - rq := p.sent[0] - wait = time.Duration(rq.procTime) - p.sumCost -= rq.cost - if checkOrder { - if rq.order <= last { - t.Errorf("Requests processed in wrong order") - } - last = rq.order - } - p.sent = p.sent[1:] - } - p.lock.Unlock() - select { - case <-stop: - return - case <-time.After(wait): - } - } -} - -const ( - testDistBufLimit = 10000000 - testDistMaxCost = 1000000 - testDistPeerCount = 2 - testDistReqCount = 10 - testDistMaxResendCount = 3 -) - -func (p *testDistPeer) waitBefore(cost uint64) (time.Duration, float64) { - p.lock.RLock() - sumCost := p.sumCost + cost - p.lock.RUnlock() - if sumCost < testDistBufLimit { - return 0, float64(testDistBufLimit-sumCost) / float64(testDistBufLimit) - } - return time.Duration(sumCost - testDistBufLimit), 0 -} - -func (p *testDistPeer) canQueue() bool { - return true -} - -func (p *testDistPeer) queueSend(f func()) bool { - f() - return true -} - -func TestRequestDistributor(t *testing.T) { - testRequestDistributor(t, false) -} - -func TestRequestDistributorResend(t *testing.T) { - testRequestDistributor(t, true) -} - -func testRequestDistributor(t *testing.T, resend bool) { - stop := make(chan struct{}) - defer close(stop) - - dist := newRequestDistributor(nil, &mclock.System{}) - var peers [testDistPeerCount]*testDistPeer - for i := range peers { - peers[i] = &testDistPeer{} - go peers[i].worker(t, !resend, stop) - dist.registerTestPeer(peers[i]) - } - // Disable the mechanism that we will wait a few time for request - // even there is no suitable peer to send right now. - waitForPeers = 0 - - var wg sync.WaitGroup - - for i := 1; i <= testDistReqCount; i++ { - cost := uint64(rand.Int63n(testDistMaxCost)) - procTime := uint64(rand.Int63n(int64(cost + 1))) - rq := &testDistReq{ - cost: cost, - procTime: procTime, - order: uint64(i), - canSendTo: make(map[*testDistPeer]struct{}), - } - for _, peer := range peers { - if rand.Intn(2) != 0 { - rq.canSendTo[peer] = struct{}{} - } - } - - wg.Add(1) - req := &distReq{ - getCost: rq.getCost, - canSend: rq.canSend, - request: rq.request, - } - chn := dist.queue(req) - go func() { - cnt := 1 - if resend && len(rq.canSendTo) != 0 { - cnt = rand.Intn(testDistMaxResendCount) + 1 - } - for i := 0; i < cnt; i++ { - if i != 0 { - chn = dist.queue(req) - } - p := <-chn - if p == nil { - if len(rq.canSendTo) != 0 { - t.Errorf("Request that could have been sent was dropped") - } - } else { - peer := p.(*testDistPeer) - if _, ok := rq.canSendTo[peer]; !ok { - t.Errorf("Request sent to wrong peer") - } - } - } - wg.Done() - }() - if rand.Intn(1000) == 0 { - time.Sleep(time.Duration(rand.Intn(5000000))) - } - } - - wg.Wait() -} diff --git a/les/enr_entry.go b/les/enr_entry.go deleted file mode 100644 index 307313fb1..000000000 --- a/les/enr_entry.go +++ /dev/null @@ -1,72 +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 ( - "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/p2p/dnsdisc" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/rlp" -) - -// lesEntry is the "les" ENR entry. This is set for LES servers only. -type lesEntry struct { - // Ignore additional fields (for forward compatibility). - VfxVersion uint - Rest []rlp.RawValue `rlp:"tail"` -} - -func (lesEntry) ENRKey() string { return "les" } - -// ethEntry is the "eth" ENR entry. This is redeclared here to avoid depending on package eth. -type ethEntry struct { - ForkID forkid.ID - Tail []rlp.RawValue `rlp:"tail"` -} - -func (ethEntry) ENRKey() string { return "eth" } - -// setupDiscovery creates the node discovery source for the eth protocol. -func (eth *LightEthereum) setupDiscovery() (enode.Iterator, error) { - it := enode.NewFairMix(0) - - // Enable DNS discovery. - if len(eth.config.EthDiscoveryURLs) != 0 { - client := dnsdisc.NewClient(dnsdisc.Config{}) - dns, err := client.NewIterator(eth.config.EthDiscoveryURLs...) - if err != nil { - return nil, err - } - it.AddSource(dns) - } - - // Enable DHT. - if eth.udpEnabled { - it.AddSource(eth.p2pServer.DiscV5.RandomNodes()) - } - - forkFilter := forkid.NewFilter(eth.blockchain) - iterator := enode.Filter(it, func(n *enode.Node) bool { return nodeIsServer(forkFilter, n) }) - return iterator, nil -} - -// nodeIsServer checks whether n is an LES server node. -func nodeIsServer(forkFilter forkid.Filter, n *enode.Node) bool { - var les lesEntry - var eth ethEntry - return n.Load(&les) == nil && n.Load(ð) == nil && forkFilter(eth.ForkID) == nil -} diff --git a/les/flowcontrol/control.go b/les/flowcontrol/control.go deleted file mode 100644 index 76a241fa5..000000000 --- a/les/flowcontrol/control.go +++ /dev/null @@ -1,433 +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 flowcontrol implements a client side flow control mechanism -package flowcontrol - -import ( - "fmt" - "math" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/log" -) - -const ( - // fcTimeConst is the time constant applied for MinRecharge during linear - // buffer recharge period - fcTimeConst = time.Millisecond - // DecParamDelay is applied at server side when decreasing capacity in order to - // avoid a buffer underrun error due to requests sent by the client before - // receiving the capacity update announcement - DecParamDelay = time.Second * 2 - // keepLogs is the duration of keeping logs; logging is not used if zero - keepLogs = 0 -) - -// ServerParams are the flow control parameters specified by a server for a client -// -// Note: a server can assign different amounts of capacity to each client by giving -// different parameters to them. -type ServerParams struct { - BufLimit, MinRecharge uint64 -} - -// scheduledUpdate represents a delayed flow control parameter update -type scheduledUpdate struct { - time mclock.AbsTime - params ServerParams -} - -// ClientNode is the flow control system's representation of a client -// (used in server mode only) -type ClientNode struct { - params ServerParams - bufValue int64 - lastTime mclock.AbsTime - updateSchedule []scheduledUpdate - sumCost uint64 // sum of req costs received from this client - accepted map[uint64]uint64 // value = sumCost after accepting the given req - connected bool - lock sync.Mutex - cm *ClientManager - log *logger - cmNodeFields -} - -// NewClientNode returns a new ClientNode -func NewClientNode(cm *ClientManager, params ServerParams) *ClientNode { - node := &ClientNode{ - cm: cm, - params: params, - bufValue: int64(params.BufLimit), - lastTime: cm.clock.Now(), - accepted: make(map[uint64]uint64), - connected: true, - } - if keepLogs > 0 { - node.log = newLogger(keepLogs) - } - cm.connect(node) - return node -} - -// Disconnect should be called when a client is disconnected -func (node *ClientNode) Disconnect() { - node.lock.Lock() - defer node.lock.Unlock() - - node.connected = false - node.cm.disconnect(node) -} - -// BufferStatus returns the current buffer value and limit -func (node *ClientNode) BufferStatus() (uint64, uint64) { - node.lock.Lock() - defer node.lock.Unlock() - - if !node.connected { - return 0, 0 - } - now := node.cm.clock.Now() - node.update(now) - node.cm.updateBuffer(node, 0, now) - bv := node.bufValue - if bv < 0 { - bv = 0 - } - return uint64(bv), node.params.BufLimit -} - -// OneTimeCost subtracts the given amount from the node's buffer. -// -// Note: this call can take the buffer into the negative region internally. -// In this case zero buffer value is returned by exported calls and no requests -// are accepted. -func (node *ClientNode) OneTimeCost(cost uint64) { - node.lock.Lock() - defer node.lock.Unlock() - - now := node.cm.clock.Now() - node.update(now) - node.bufValue -= int64(cost) - node.cm.updateBuffer(node, -int64(cost), now) -} - -// Freeze notifies the client manager about a client freeze event in which case -// the total capacity allowance is slightly reduced. -func (node *ClientNode) Freeze() { - node.lock.Lock() - frozenCap := node.params.MinRecharge - node.lock.Unlock() - node.cm.reduceTotalCapacity(frozenCap) -} - -// update recalculates the buffer value at a specified time while also performing -// scheduled flow control parameter updates if necessary -func (node *ClientNode) update(now mclock.AbsTime) { - for len(node.updateSchedule) > 0 && node.updateSchedule[0].time <= now { - node.recalcBV(node.updateSchedule[0].time) - node.updateParams(node.updateSchedule[0].params, now) - node.updateSchedule = node.updateSchedule[1:] - } - node.recalcBV(now) -} - -// recalcBV recalculates the buffer value at a specified time -func (node *ClientNode) recalcBV(now mclock.AbsTime) { - dt := uint64(now - node.lastTime) - if now < node.lastTime { - dt = 0 - } - node.bufValue += int64(node.params.MinRecharge * dt / uint64(fcTimeConst)) - if node.bufValue > int64(node.params.BufLimit) { - node.bufValue = int64(node.params.BufLimit) - } - if node.log != nil { - node.log.add(now, fmt.Sprintf("updated bv=%d MRR=%d BufLimit=%d", node.bufValue, node.params.MinRecharge, node.params.BufLimit)) - } - node.lastTime = now -} - -// UpdateParams updates the flow control parameters of a client node -func (node *ClientNode) UpdateParams(params ServerParams) { - node.lock.Lock() - defer node.lock.Unlock() - - now := node.cm.clock.Now() - node.update(now) - if params.MinRecharge >= node.params.MinRecharge { - node.updateSchedule = nil - node.updateParams(params, now) - } else { - for i, s := range node.updateSchedule { - if params.MinRecharge >= s.params.MinRecharge { - s.params = params - node.updateSchedule = node.updateSchedule[:i+1] - return - } - } - node.updateSchedule = append(node.updateSchedule, scheduledUpdate{time: now.Add(DecParamDelay), params: params}) - } -} - -// updateParams updates the flow control parameters of the node -func (node *ClientNode) updateParams(params ServerParams, now mclock.AbsTime) { - diff := int64(params.BufLimit - node.params.BufLimit) - if diff > 0 { - node.bufValue += diff - } else if node.bufValue > int64(params.BufLimit) { - node.bufValue = int64(params.BufLimit) - } - node.cm.updateParams(node, params, now) -} - -// AcceptRequest returns whether a new request can be accepted and the missing -// buffer amount if it was rejected due to a buffer underrun. If accepted, maxCost -// is deducted from the flow control buffer. -func (node *ClientNode) AcceptRequest(reqID, index, maxCost uint64) (accepted bool, bufShort uint64, priority int64) { - node.lock.Lock() - defer node.lock.Unlock() - - now := node.cm.clock.Now() - node.update(now) - if int64(maxCost) > node.bufValue { - if node.log != nil { - node.log.add(now, fmt.Sprintf("rejected reqID=%d bv=%d maxCost=%d", reqID, node.bufValue, maxCost)) - node.log.dump(now) - } - return false, maxCost - uint64(node.bufValue), 0 - } - node.bufValue -= int64(maxCost) - node.sumCost += maxCost - if node.log != nil { - node.log.add(now, fmt.Sprintf("accepted reqID=%d bv=%d maxCost=%d sumCost=%d", reqID, node.bufValue, maxCost, node.sumCost)) - } - node.accepted[index] = node.sumCost - return true, 0, node.cm.accepted(node, maxCost, now) -} - -// RequestProcessed should be called when the request has been processed -func (node *ClientNode) RequestProcessed(reqID, index, maxCost, realCost uint64) uint64 { - node.lock.Lock() - defer node.lock.Unlock() - - now := node.cm.clock.Now() - node.update(now) - node.cm.processed(node, maxCost, realCost, now) - bv := node.bufValue + int64(node.sumCost-node.accepted[index]) - if node.log != nil { - node.log.add(now, fmt.Sprintf("processed reqID=%d bv=%d maxCost=%d realCost=%d sumCost=%d oldSumCost=%d reportedBV=%d", reqID, node.bufValue, maxCost, realCost, node.sumCost, node.accepted[index], bv)) - } - delete(node.accepted, index) - if bv < 0 { - return 0 - } - return uint64(bv) -} - -// ServerNode is the flow control system's representation of a server -// (used in client mode only) -type ServerNode struct { - clock mclock.Clock - bufEstimate uint64 - bufRecharge bool - lastTime mclock.AbsTime - params ServerParams - sumCost uint64 // sum of req costs sent to this server - pending map[uint64]uint64 // value = sumCost after sending the given req - log *logger - lock sync.RWMutex -} - -// NewServerNode returns a new ServerNode -func NewServerNode(params ServerParams, clock mclock.Clock) *ServerNode { - node := &ServerNode{ - clock: clock, - bufEstimate: params.BufLimit, - bufRecharge: false, - lastTime: clock.Now(), - params: params, - pending: make(map[uint64]uint64), - } - if keepLogs > 0 { - node.log = newLogger(keepLogs) - } - return node -} - -// UpdateParams updates the flow control parameters of the node -func (node *ServerNode) UpdateParams(params ServerParams) { - node.lock.Lock() - defer node.lock.Unlock() - - node.recalcBLE(mclock.Now()) - if params.BufLimit > node.params.BufLimit { - node.bufEstimate += params.BufLimit - node.params.BufLimit - } else { - if node.bufEstimate > params.BufLimit { - node.bufEstimate = params.BufLimit - } - } - node.params = params -} - -// recalcBLE recalculates the lowest estimate for the client's buffer value at -// the given server at the specified time -func (node *ServerNode) recalcBLE(now mclock.AbsTime) { - if now < node.lastTime { - return - } - if node.bufRecharge { - dt := uint64(now - node.lastTime) - node.bufEstimate += node.params.MinRecharge * dt / uint64(fcTimeConst) - if node.bufEstimate >= node.params.BufLimit { - node.bufEstimate = node.params.BufLimit - node.bufRecharge = false - } - } - node.lastTime = now - if node.log != nil { - node.log.add(now, fmt.Sprintf("updated bufEst=%d MRR=%d BufLimit=%d", node.bufEstimate, node.params.MinRecharge, node.params.BufLimit)) - } -} - -// safetyMargin is added to the flow control waiting time when estimated buffer value is low -const safetyMargin = time.Millisecond - -// CanSend returns the minimum waiting time required before sending a request -// with the given maximum estimated cost. Second return value is the relative -// estimated buffer level after sending the request (divided by BufLimit). -func (node *ServerNode) CanSend(maxCost uint64) (time.Duration, float64) { - node.lock.RLock() - defer node.lock.RUnlock() - - if node.params.BufLimit == 0 { - return time.Duration(math.MaxInt64), 0 - } - now := node.clock.Now() - node.recalcBLE(now) - maxCost += uint64(safetyMargin) * node.params.MinRecharge / uint64(fcTimeConst) - if maxCost > node.params.BufLimit { - maxCost = node.params.BufLimit - } - if node.bufEstimate >= maxCost { - relBuf := float64(node.bufEstimate-maxCost) / float64(node.params.BufLimit) - if node.log != nil { - node.log.add(now, fmt.Sprintf("canSend bufEst=%d maxCost=%d true relBuf=%f", node.bufEstimate, maxCost, relBuf)) - } - return 0, relBuf - } - timeLeft := time.Duration((maxCost - node.bufEstimate) * uint64(fcTimeConst) / node.params.MinRecharge) - if node.log != nil { - node.log.add(now, fmt.Sprintf("canSend bufEst=%d maxCost=%d false timeLeft=%v", node.bufEstimate, maxCost, timeLeft)) - } - return timeLeft, 0 -} - -// QueuedRequest should be called when the request has been assigned to the given -// server node, before putting it in the send queue. It is mandatory that requests -// are sent in the same order as the QueuedRequest calls are made. -func (node *ServerNode) QueuedRequest(reqID, maxCost uint64) { - node.lock.Lock() - defer node.lock.Unlock() - - now := node.clock.Now() - node.recalcBLE(now) - // Note: we do not know when requests actually arrive to the server so bufRecharge - // is not turned on here if buffer was full; in this case it is going to be turned - // on by the first reply's bufValue feedback - if node.bufEstimate >= maxCost { - node.bufEstimate -= maxCost - } else { - log.Error("Queued request with insufficient buffer estimate") - node.bufEstimate = 0 - } - node.sumCost += maxCost - node.pending[reqID] = node.sumCost - if node.log != nil { - node.log.add(now, fmt.Sprintf("queued reqID=%d bufEst=%d maxCost=%d sumCost=%d", reqID, node.bufEstimate, maxCost, node.sumCost)) - } -} - -// ReceivedReply adjusts estimated buffer value according to the value included in -// the latest request reply. -func (node *ServerNode) ReceivedReply(reqID, bv uint64) { - node.lock.Lock() - defer node.lock.Unlock() - - now := node.clock.Now() - node.recalcBLE(now) - if bv > node.params.BufLimit { - bv = node.params.BufLimit - } - sc, ok := node.pending[reqID] - if !ok { - return - } - delete(node.pending, reqID) - cc := node.sumCost - sc - newEstimate := uint64(0) - if bv > cc { - newEstimate = bv - cc - } - if newEstimate > node.bufEstimate { - // Note: we never reduce the buffer estimate based on the reported value because - // this can only happen because of the delayed delivery of the latest reply. - // The lowest estimate based on the previous reply can still be considered valid. - node.bufEstimate = newEstimate - } - - node.bufRecharge = node.bufEstimate < node.params.BufLimit - node.lastTime = now - if node.log != nil { - node.log.add(now, fmt.Sprintf("received reqID=%d bufEst=%d reportedBv=%d sumCost=%d oldSumCost=%d", reqID, node.bufEstimate, bv, node.sumCost, sc)) - } -} - -// ResumeFreeze cleans all pending requests and sets the buffer estimate to the -// reported value after resuming from a frozen state -func (node *ServerNode) ResumeFreeze(bv uint64) { - node.lock.Lock() - defer node.lock.Unlock() - - for reqID := range node.pending { - delete(node.pending, reqID) - } - now := node.clock.Now() - node.recalcBLE(now) - if bv > node.params.BufLimit { - bv = node.params.BufLimit - } - node.bufEstimate = bv - node.bufRecharge = node.bufEstimate < node.params.BufLimit - node.lastTime = now - if node.log != nil { - node.log.add(now, fmt.Sprintf("unfreeze bv=%d sumCost=%d", bv, node.sumCost)) - } -} - -// DumpLogs dumps the event log if logging is used -func (node *ServerNode) DumpLogs() { - node.lock.Lock() - defer node.lock.Unlock() - - if node.log != nil { - node.log.dump(node.clock.Now()) - } -} diff --git a/les/flowcontrol/logger.go b/les/flowcontrol/logger.go deleted file mode 100644 index 428d7fbf2..000000000 --- a/les/flowcontrol/logger.go +++ /dev/null @@ -1,65 +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 flowcontrol - -import ( - "fmt" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" -) - -// logger collects events in string format and discards events older than the -// "keep" parameter -type logger struct { - events map[uint64]logEvent - writePtr, delPtr uint64 - keep time.Duration -} - -// logEvent describes a single event -type logEvent struct { - time mclock.AbsTime - event string -} - -// newLogger creates a new logger -func newLogger(keep time.Duration) *logger { - return &logger{ - events: make(map[uint64]logEvent), - keep: keep, - } -} - -// add adds a new event and discards old events if possible -func (l *logger) add(now mclock.AbsTime, event string) { - keepAfter := now - mclock.AbsTime(l.keep) - for l.delPtr < l.writePtr && l.events[l.delPtr].time <= keepAfter { - delete(l.events, l.delPtr) - l.delPtr++ - } - l.events[l.writePtr] = logEvent{now, event} - l.writePtr++ -} - -// dump prints all stored events -func (l *logger) dump(now mclock.AbsTime) { - for i := l.delPtr; i < l.writePtr; i++ { - e := l.events[i] - fmt.Println(time.Duration(e.time-now), e.event) - } -} diff --git a/les/flowcontrol/manager.go b/les/flowcontrol/manager.go deleted file mode 100644 index b7cc9bd90..000000000 --- a/les/flowcontrol/manager.go +++ /dev/null @@ -1,476 +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 flowcontrol - -import ( - "fmt" - "math" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/common/prque" -) - -// cmNodeFields are ClientNode fields used by the client manager -// Note: these fields are locked by the client manager's mutex -type cmNodeFields struct { - corrBufValue int64 // buffer value adjusted with the extra recharge amount - rcLastIntValue int64 // past recharge integrator value when corrBufValue was last updated - rcFullIntValue int64 // future recharge integrator value when corrBufValue will reach maximum - queueIndex int // position in the recharge queue (-1 if not queued) -} - -// FixedPointMultiplier is applied to the recharge integrator and the recharge curve. -// -// Note: fixed point arithmetic is required for the integrator because it is a -// constantly increasing value that can wrap around int64 limits (which behavior is -// also supported by the priority queue). A floating point value would gradually lose -// precision in this application. -// The recharge curve and all recharge values are encoded as fixed point because -// sumRecharge is frequently updated by adding or subtracting individual recharge -// values and perfect precision is required. -const FixedPointMultiplier = 1000000 - -var ( - capacityDropFactor = 0.1 - capacityRaiseTC = 1 / (3 * float64(time.Hour)) // time constant for raising the capacity factor - capacityRaiseThresholdRatio = 1.125 // total/connected capacity ratio threshold for raising the capacity factor -) - -// ClientManager controls the capacity assigned to the clients of a server. -// Since ServerParams guarantee a safe lower estimate for processable requests -// even in case of all clients being active, ClientManager calculates a -// corrugated buffer value and usually allows a higher remaining buffer value -// to be returned with each reply. -type ClientManager struct { - clock mclock.Clock - lock sync.Mutex - stop chan chan struct{} - - curve PieceWiseLinear - sumRecharge, totalRecharge, totalConnected uint64 - logTotalCap, totalCapacity float64 - logTotalCapRaiseLimit float64 - minLogTotalCap, maxLogTotalCap float64 - capacityRaiseThreshold uint64 - capLastUpdate mclock.AbsTime - totalCapacityCh chan uint64 - - // recharge integrator is increasing in each moment with a rate of - // (totalRecharge / sumRecharge)*FixedPointMultiplier or 0 if sumRecharge==0 - rcLastUpdate mclock.AbsTime // last time the recharge integrator was updated - rcLastIntValue int64 // last updated value of the recharge integrator - priorityOffset int64 // offset for prque priority values ensures that all priorities stay in the int64 range - // recharge queue is a priority queue with currently recharging client nodes - // as elements. The priority value is rcFullIntValue which allows to quickly - // determine which client will first finish recharge. - rcQueue *prque.Prque[int64, *ClientNode] -} - -// NewClientManager returns a new client manager. -// Client manager enhances flow control performance by allowing client buffers -// to recharge quicker than the minimum guaranteed recharge rate if possible. -// The sum of all minimum recharge rates (sumRecharge) is updated each time -// a clients starts or finishes buffer recharging. Then an adjusted total -// recharge rate is calculated using a piecewise linear recharge curve: -// -// totalRecharge = curve(sumRecharge) -// (totalRecharge >= sumRecharge is enforced) -// -// Then the "bonus" buffer recharge is distributed between currently recharging -// clients proportionally to their minimum recharge rates. -// -// Note: total recharge is proportional to the average number of parallel running -// serving threads. A recharge value of 1000000 corresponds to one thread in average. -// The maximum number of allowed serving threads should always be considerably -// higher than the targeted average number. -// -// Note 2: although it is possible to specify a curve allowing the total target -// recharge starting from zero sumRecharge, it makes sense to add a linear ramp -// starting from zero in order to not let a single low-priority client use up -// the entire server capacity and thus ensure quick availability for others at -// any moment. -func NewClientManager(curve PieceWiseLinear, clock mclock.Clock) *ClientManager { - cm := &ClientManager{ - clock: clock, - rcQueue: prque.New[int64, *ClientNode](func(a *ClientNode, i int) { a.queueIndex = i }), - capLastUpdate: clock.Now(), - stop: make(chan chan struct{}), - } - if curve != nil { - cm.SetRechargeCurve(curve) - } - go func() { - // regularly recalculate and update total capacity - for { - select { - case <-time.After(time.Minute): - cm.lock.Lock() - cm.updateTotalCapacity(cm.clock.Now(), true) - cm.lock.Unlock() - case stop := <-cm.stop: - close(stop) - return - } - } - }() - return cm -} - -// Stop stops the client manager -func (cm *ClientManager) Stop() { - stop := make(chan struct{}) - cm.stop <- stop - <-stop -} - -// SetRechargeCurve updates the recharge curve -func (cm *ClientManager) SetRechargeCurve(curve PieceWiseLinear) { - cm.lock.Lock() - defer cm.lock.Unlock() - - now := cm.clock.Now() - cm.updateRecharge(now) - cm.curve = curve - if len(curve) > 0 { - cm.totalRecharge = curve[len(curve)-1].Y - } else { - cm.totalRecharge = 0 - } -} - -// SetCapacityLimits sets a threshold value used for raising capFactor. -// Either if the difference between total allowed and connected capacity is less -// than this threshold or if their ratio is less than capacityRaiseThresholdRatio -// then capFactor is allowed to slowly raise. -func (cm *ClientManager) SetCapacityLimits(min, max, raiseThreshold uint64) { - if min < 1 { - min = 1 - } - cm.minLogTotalCap = math.Log(float64(min)) - if max < 1 { - max = 1 - } - cm.maxLogTotalCap = math.Log(float64(max)) - cm.logTotalCap = cm.maxLogTotalCap - cm.capacityRaiseThreshold = raiseThreshold - cm.refreshCapacity() -} - -// connect should be called when a client is connected, before passing it to any -// other ClientManager function -func (cm *ClientManager) connect(node *ClientNode) { - cm.lock.Lock() - defer cm.lock.Unlock() - - now := cm.clock.Now() - cm.updateRecharge(now) - node.corrBufValue = int64(node.params.BufLimit) - node.rcLastIntValue = cm.rcLastIntValue - node.queueIndex = -1 - cm.updateTotalCapacity(now, true) - cm.totalConnected += node.params.MinRecharge - cm.updateRaiseLimit() -} - -// disconnect should be called when a client is disconnected -func (cm *ClientManager) disconnect(node *ClientNode) { - cm.lock.Lock() - defer cm.lock.Unlock() - - now := cm.clock.Now() - cm.updateRecharge(cm.clock.Now()) - cm.updateTotalCapacity(now, true) - cm.totalConnected -= node.params.MinRecharge - cm.updateRaiseLimit() -} - -// accepted is called when a request with given maximum cost is accepted. -// It returns a priority indicator for the request which is used to determine placement -// in the serving queue. Older requests have higher priority by default. If the client -// is almost out of buffer, request priority is reduced. -func (cm *ClientManager) accepted(node *ClientNode, maxCost uint64, now mclock.AbsTime) (priority int64) { - cm.lock.Lock() - defer cm.lock.Unlock() - - cm.updateNodeRc(node, -int64(maxCost), &node.params, now) - rcTime := (node.params.BufLimit - uint64(node.corrBufValue)) * FixedPointMultiplier / node.params.MinRecharge - return -int64(now) - int64(rcTime) -} - -// processed updates the client buffer according to actual request cost after -// serving has been finished. -// -// Note: processed should always be called for all accepted requests -func (cm *ClientManager) processed(node *ClientNode, maxCost, realCost uint64, now mclock.AbsTime) { - if realCost > maxCost { - realCost = maxCost - } - cm.updateBuffer(node, int64(maxCost-realCost), now) -} - -// updateBuffer recalculates the corrected buffer value, adds the given value to it -// and updates the node's actual buffer value if possible -func (cm *ClientManager) updateBuffer(node *ClientNode, add int64, now mclock.AbsTime) { - cm.lock.Lock() - defer cm.lock.Unlock() - - cm.updateNodeRc(node, add, &node.params, now) - if node.corrBufValue > node.bufValue { - if node.log != nil { - node.log.add(now, fmt.Sprintf("corrected bv=%d oldBv=%d", node.corrBufValue, node.bufValue)) - } - node.bufValue = node.corrBufValue - } -} - -// updateParams updates the flow control parameters of a client node -func (cm *ClientManager) updateParams(node *ClientNode, params ServerParams, now mclock.AbsTime) { - cm.lock.Lock() - defer cm.lock.Unlock() - - cm.updateRecharge(now) - cm.updateTotalCapacity(now, true) - cm.totalConnected += params.MinRecharge - node.params.MinRecharge - cm.updateRaiseLimit() - cm.updateNodeRc(node, 0, ¶ms, now) -} - -// updateRaiseLimit recalculates the limiting value until which logTotalCap -// can be raised when no client freeze events occur -func (cm *ClientManager) updateRaiseLimit() { - if cm.capacityRaiseThreshold == 0 { - cm.logTotalCapRaiseLimit = 0 - return - } - limit := float64(cm.totalConnected + cm.capacityRaiseThreshold) - limit2 := float64(cm.totalConnected) * capacityRaiseThresholdRatio - if limit2 > limit { - limit = limit2 - } - if limit < 1 { - limit = 1 - } - cm.logTotalCapRaiseLimit = math.Log(limit) -} - -// updateRecharge updates the recharge integrator and checks the recharge queue -// for nodes with recently filled buffers -func (cm *ClientManager) updateRecharge(now mclock.AbsTime) { - lastUpdate := cm.rcLastUpdate - cm.rcLastUpdate = now - // updating is done in multiple steps if node buffers are filled and sumRecharge - // is decreased before the given target time - for cm.sumRecharge > 0 { - sumRecharge := cm.sumRecharge - if sumRecharge > cm.totalRecharge { - sumRecharge = cm.totalRecharge - } - bonusRatio := float64(1) - v := cm.curve.ValueAt(sumRecharge) - s := float64(sumRecharge) - if v > s && s > 0 { - bonusRatio = v / s - } - dt := now - lastUpdate - // fetch the client that finishes first - rcqNode := cm.rcQueue.PopItem() // if sumRecharge > 0 then the queue cannot be empty - // check whether it has already finished - dtNext := mclock.AbsTime(float64(rcqNode.rcFullIntValue-cm.rcLastIntValue) / bonusRatio) - if dt < dtNext { - // not finished yet, put it back, update integrator according - // to current bonusRatio and return - cm.addToQueue(rcqNode) - cm.rcLastIntValue += int64(bonusRatio * float64(dt)) - return - } - lastUpdate += dtNext - // finished recharging, update corrBufValue and sumRecharge if necessary and do next step - if rcqNode.corrBufValue < int64(rcqNode.params.BufLimit) { - rcqNode.corrBufValue = int64(rcqNode.params.BufLimit) - cm.sumRecharge -= rcqNode.params.MinRecharge - } - cm.rcLastIntValue = rcqNode.rcFullIntValue - } -} - -func (cm *ClientManager) addToQueue(node *ClientNode) { - if cm.priorityOffset-node.rcFullIntValue < -0x4000000000000000 { - cm.priorityOffset += 0x4000000000000000 - // recreate priority queue with new offset to avoid overflow; should happen very rarely - newRcQueue := prque.New[int64, *ClientNode](func(a *ClientNode, i int) { a.queueIndex = i }) - for cm.rcQueue.Size() > 0 { - n := cm.rcQueue.PopItem() - newRcQueue.Push(n, cm.priorityOffset-n.rcFullIntValue) - } - cm.rcQueue = newRcQueue - } - cm.rcQueue.Push(node, cm.priorityOffset-node.rcFullIntValue) -} - -// updateNodeRc updates a node's corrBufValue and adds an external correction value. -// It also adds or removes the rcQueue entry and updates ServerParams and sumRecharge if necessary. -func (cm *ClientManager) updateNodeRc(node *ClientNode, bvc int64, params *ServerParams, now mclock.AbsTime) { - cm.updateRecharge(now) - wasFull := true - if node.corrBufValue != int64(node.params.BufLimit) { - wasFull = false - node.corrBufValue += (cm.rcLastIntValue - node.rcLastIntValue) * int64(node.params.MinRecharge) / FixedPointMultiplier - if node.corrBufValue > int64(node.params.BufLimit) { - node.corrBufValue = int64(node.params.BufLimit) - } - node.rcLastIntValue = cm.rcLastIntValue - } - node.corrBufValue += bvc - diff := int64(params.BufLimit - node.params.BufLimit) - if diff > 0 { - node.corrBufValue += diff - } - isFull := false - if node.corrBufValue >= int64(params.BufLimit) { - node.corrBufValue = int64(params.BufLimit) - isFull = true - } - if !wasFull { - cm.sumRecharge -= node.params.MinRecharge - } - if params != &node.params { - node.params = *params - } - if !isFull { - cm.sumRecharge += node.params.MinRecharge - if node.queueIndex != -1 { - cm.rcQueue.Remove(node.queueIndex) - } - node.rcLastIntValue = cm.rcLastIntValue - node.rcFullIntValue = cm.rcLastIntValue + (int64(node.params.BufLimit)-node.corrBufValue)*FixedPointMultiplier/int64(node.params.MinRecharge) - cm.addToQueue(node) - } -} - -// reduceTotalCapacity reduces the total capacity allowance in case of a client freeze event -func (cm *ClientManager) reduceTotalCapacity(frozenCap uint64) { - cm.lock.Lock() - defer cm.lock.Unlock() - - ratio := float64(1) - if frozenCap < cm.totalConnected { - ratio = float64(frozenCap) / float64(cm.totalConnected) - } - now := cm.clock.Now() - cm.updateTotalCapacity(now, false) - cm.logTotalCap -= capacityDropFactor * ratio - if cm.logTotalCap < cm.minLogTotalCap { - cm.logTotalCap = cm.minLogTotalCap - } - cm.updateTotalCapacity(now, true) -} - -// updateTotalCapacity updates the total capacity factor. The capacity factor allows -// the total capacity of the system to go over the allowed total recharge value -// if clients go to frozen state sufficiently rarely. -// The capacity factor is dropped instantly by a small amount if a clients is frozen. -// It is raised slowly (with a large time constant) if the total connected capacity -// is close to the total allowed amount and no clients are frozen. -func (cm *ClientManager) updateTotalCapacity(now mclock.AbsTime, refresh bool) { - dt := now - cm.capLastUpdate - cm.capLastUpdate = now - - if cm.logTotalCap < cm.logTotalCapRaiseLimit { - cm.logTotalCap += capacityRaiseTC * float64(dt) - if cm.logTotalCap > cm.logTotalCapRaiseLimit { - cm.logTotalCap = cm.logTotalCapRaiseLimit - } - } - if cm.logTotalCap > cm.maxLogTotalCap { - cm.logTotalCap = cm.maxLogTotalCap - } - if refresh { - cm.refreshCapacity() - } -} - -// refreshCapacity recalculates the total capacity value and sends an update to the subscription -// channel if the relative change of the value since the last update is more than 0.1 percent -func (cm *ClientManager) refreshCapacity() { - totalCapacity := math.Exp(cm.logTotalCap) - if totalCapacity >= cm.totalCapacity*0.999 && totalCapacity <= cm.totalCapacity*1.001 { - return - } - cm.totalCapacity = totalCapacity - if cm.totalCapacityCh != nil { - select { - case cm.totalCapacityCh <- uint64(cm.totalCapacity): - default: - } - } -} - -// SubscribeTotalCapacity returns all future updates to the total capacity value -// through a channel and also returns the current value -func (cm *ClientManager) SubscribeTotalCapacity(ch chan uint64) uint64 { - cm.lock.Lock() - defer cm.lock.Unlock() - - cm.totalCapacityCh = ch - return uint64(cm.totalCapacity) -} - -// PieceWiseLinear is used to describe recharge curves -type PieceWiseLinear []struct{ X, Y uint64 } - -// ValueAt returns the curve's value at a given point -func (pwl PieceWiseLinear) ValueAt(x uint64) float64 { - l := 0 - h := len(pwl) - if h == 0 { - return 0 - } - for h != l { - m := (l + h) / 2 - if x > pwl[m].X { - l = m + 1 - } else { - h = m - } - } - if l == 0 { - return float64(pwl[0].Y) - } - l-- - if h == len(pwl) { - return float64(pwl[l].Y) - } - dx := pwl[h].X - pwl[l].X - if dx < 1 { - return float64(pwl[l].Y) - } - return float64(pwl[l].Y) + float64(pwl[h].Y-pwl[l].Y)*float64(x-pwl[l].X)/float64(dx) -} - -// Valid returns true if the X coordinates of the curve points are non-strictly monotonic -func (pwl PieceWiseLinear) Valid() bool { - var lastX uint64 - for _, i := range pwl { - if i.X < lastX { - return false - } - lastX = i.X - } - return true -} diff --git a/les/flowcontrol/manager_test.go b/les/flowcontrol/manager_test.go deleted file mode 100644 index 3afc31272..000000000 --- a/les/flowcontrol/manager_test.go +++ /dev/null @@ -1,130 +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 flowcontrol - -import ( - "math" - "math/rand" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" -) - -type testNode struct { - node *ClientNode - bufLimit, capacity uint64 - waitUntil mclock.AbsTime - index, totalCost uint64 -} - -const ( - testMaxCost = 1000000 - testLength = 100000 -) - -// testConstantTotalCapacity simulates multiple request sender nodes and verifies -// whether the total amount of served requests matches the expected value based on -// the total capacity and the duration of the test. -// Some nodes are sending requests occasionally so that their buffer should regularly -// reach the maximum while other nodes (the "max capacity nodes") are sending at the -// maximum permitted rate. The max capacity nodes are changed multiple times during -// a single test. -func TestConstantTotalCapacity(t *testing.T) { - testConstantTotalCapacity(t, 10, 1, 0, false) - testConstantTotalCapacity(t, 10, 1, 1, false) - testConstantTotalCapacity(t, 30, 1, 0, false) - testConstantTotalCapacity(t, 30, 2, 3, false) - testConstantTotalCapacity(t, 100, 1, 0, false) - testConstantTotalCapacity(t, 100, 3, 5, false) - testConstantTotalCapacity(t, 100, 5, 10, false) - testConstantTotalCapacity(t, 100, 3, 5, true) -} - -func testConstantTotalCapacity(t *testing.T, nodeCount, maxCapacityNodes, randomSend int, priorityOverflow bool) { - clock := &mclock.Simulated{} - nodes := make([]*testNode, nodeCount) - var totalCapacity uint64 - for i := range nodes { - nodes[i] = &testNode{capacity: uint64(50000 + rand.Intn(100000))} - totalCapacity += nodes[i].capacity - } - m := NewClientManager(PieceWiseLinear{{0, totalCapacity}}, clock) - if priorityOverflow { - // provoke a situation where rcLastUpdate overflow needs to be handled - m.rcLastIntValue = math.MaxInt64 - 10000000000 - } - for _, n := range nodes { - n.bufLimit = n.capacity * 6000 - n.node = NewClientNode(m, ServerParams{BufLimit: n.bufLimit, MinRecharge: n.capacity}) - } - maxNodes := make([]int, maxCapacityNodes) - for i := range maxNodes { - // we don't care if some indexes are selected multiple times - // in that case we have fewer max nodes - maxNodes[i] = rand.Intn(nodeCount) - } - - var sendCount int - for i := 0; i < testLength; i++ { - now := clock.Now() - for _, idx := range maxNodes { - for nodes[idx].send(t, now) { - } - } - if rand.Intn(testLength) < maxCapacityNodes*3 { - maxNodes[rand.Intn(maxCapacityNodes)] = rand.Intn(nodeCount) - } - - sendCount += randomSend - failCount := randomSend * 10 - for sendCount > 0 && failCount > 0 { - if nodes[rand.Intn(nodeCount)].send(t, now) { - sendCount-- - } else { - failCount-- - } - } - clock.Run(time.Millisecond) - } - - var totalCost uint64 - for _, n := range nodes { - totalCost += n.totalCost - } - ratio := float64(totalCost) / float64(totalCapacity) / testLength - if ratio < 0.98 || ratio > 1.02 { - t.Errorf("totalCost/totalCapacity/testLength ratio incorrect (expected: 1, got: %f)", ratio) - } -} - -func (n *testNode) send(t *testing.T, now mclock.AbsTime) bool { - if now < n.waitUntil { - return false - } - n.index++ - if ok, _, _ := n.node.AcceptRequest(0, n.index, testMaxCost); !ok { - t.Fatalf("Rejected request after expected waiting time has passed") - } - rcost := uint64(rand.Int63n(testMaxCost)) - bv := n.node.RequestProcessed(0, n.index, testMaxCost, rcost) - if bv < testMaxCost { - n.waitUntil = now + mclock.AbsTime((testMaxCost-bv)*1001000/n.capacity) - } - n.totalCost += rcost - return true -} diff --git a/les/handler_test.go b/les/handler_test.go deleted file mode 100644 index 81a34a25e..000000000 --- a/les/handler_test.go +++ /dev/null @@ -1,753 +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 ( - "encoding/binary" - "math/big" - "math/rand" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/mclock" - "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/crypto" - "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" -) - -func expectResponse(r p2p.MsgReader, msgcode, reqID, bv uint64, data interface{}) error { - type resp struct { - ReqID, BV uint64 - Data interface{} - } - return p2p.ExpectMsg(r, msgcode, resp{reqID, bv, data}) -} - -// Tests that block headers can be retrieved from a remote chain based on user queries. -func TestGetBlockHeadersLes2(t *testing.T) { testGetBlockHeaders(t, 2) } -func TestGetBlockHeadersLes3(t *testing.T) { testGetBlockHeaders(t, 3) } -func TestGetBlockHeadersLes4(t *testing.T) { testGetBlockHeaders(t, 4) } - -func testGetBlockHeaders(t *testing.T, protocol int) { - netconfig := testnetConfig{ - blocks: downloader.MaxHeaderFetch + 15, - protocol: protocol, - nopruning: true, - } - server, _, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) - defer closePeer() - bc := server.handler.blockchain - - // Create a "random" unknown hash for testing - var unknown common.Hash - for i := range unknown { - unknown[i] = byte(i) - } - // Create a batch of tests for various scenarios - limit := uint64(MaxHeaderFetch) - tests := []struct { - query *GetBlockHeadersData // 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 and number too - { - &GetBlockHeadersData{Origin: hashOrNumber{Hash: bc.GetBlockByNumber(limit / 2).Hash()}, Amount: 1}, - []common.Hash{bc.GetBlockByNumber(limit / 2).Hash()}, - }, { - &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 1}, - []common.Hash{bc.GetBlockByNumber(limit / 2).Hash()}, - }, - // Multiple headers should be retrievable in both directions - { - &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3}, - []common.Hash{ - bc.GetBlockByNumber(limit / 2).Hash(), - bc.GetBlockByNumber(limit/2 + 1).Hash(), - bc.GetBlockByNumber(limit/2 + 2).Hash(), - }, - }, { - &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Amount: 3, Reverse: true}, - []common.Hash{ - bc.GetBlockByNumber(limit / 2).Hash(), - bc.GetBlockByNumber(limit/2 - 1).Hash(), - bc.GetBlockByNumber(limit/2 - 2).Hash(), - }, - }, - // Multiple headers with skip lists should be retrievable - { - &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3}, - []common.Hash{ - bc.GetBlockByNumber(limit / 2).Hash(), - bc.GetBlockByNumber(limit/2 + 4).Hash(), - bc.GetBlockByNumber(limit/2 + 8).Hash(), - }, - }, { - &GetBlockHeadersData{Origin: hashOrNumber{Number: limit / 2}, Skip: 3, Amount: 3, Reverse: true}, - []common.Hash{ - bc.GetBlockByNumber(limit / 2).Hash(), - bc.GetBlockByNumber(limit/2 - 4).Hash(), - bc.GetBlockByNumber(limit/2 - 8).Hash(), - }, - }, - // The chain endpoints should be retrievable - { - &GetBlockHeadersData{Origin: hashOrNumber{Number: 0}, Amount: 1}, - []common.Hash{bc.GetBlockByNumber(0).Hash()}, - }, { - &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().Number.Uint64()}, Amount: 1}, - []common.Hash{bc.CurrentBlock().Hash()}, - }, - // Ensure protocol limits are honored - //{ - // &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().Number.Uint64()() - 1}, Amount: limit + 10, Reverse: true}, - // []common.Hash{}, - //}, - // Check that requesting more than available is handled gracefully - { - &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().Number.Uint64() - 4}, Skip: 3, Amount: 3}, - []common.Hash{ - bc.GetBlockByNumber(bc.CurrentBlock().Number.Uint64() - 4).Hash(), - bc.GetBlockByNumber(bc.CurrentBlock().Number.Uint64()).Hash(), - }, - }, { - &GetBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 3, Amount: 3, Reverse: true}, - []common.Hash{ - bc.GetBlockByNumber(4).Hash(), - bc.GetBlockByNumber(0).Hash(), - }, - }, - // Check that requesting more than available is handled gracefully, even if mid skip - { - &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().Number.Uint64() - 4}, Skip: 2, Amount: 3}, - []common.Hash{ - bc.GetBlockByNumber(bc.CurrentBlock().Number.Uint64() - 4).Hash(), - bc.GetBlockByNumber(bc.CurrentBlock().Number.Uint64() - 1).Hash(), - }, - }, { - &GetBlockHeadersData{Origin: hashOrNumber{Number: 4}, Skip: 2, Amount: 3, Reverse: true}, - []common.Hash{ - bc.GetBlockByNumber(4).Hash(), - bc.GetBlockByNumber(1).Hash(), - }, - }, - // Check that non existing headers aren't returned - { - &GetBlockHeadersData{Origin: hashOrNumber{Hash: unknown}, Amount: 1}, - []common.Hash{}, - }, { - &GetBlockHeadersData{Origin: hashOrNumber{Number: bc.CurrentBlock().Number.Uint64() + 1}, Amount: 1}, - []common.Hash{}, - }, - } - // Run each of the tests and verify the results against the chain - var reqID uint64 - for i, tt := range tests { - // Collect the headers to expect in the response - var headers []*types.Header - for _, hash := range tt.expect { - headers = append(headers, bc.GetHeaderByHash(hash)) - } - // Send the hash request and verify the response - reqID++ - - sendRequest(rawPeer.app, GetBlockHeadersMsg, reqID, tt.query) - if err := expectResponse(rawPeer.app, BlockHeadersMsg, reqID, testBufLimit, headers); err != nil { - t.Errorf("test %d: headers mismatch: %v", i, err) - } - } -} - -// Tests that block contents can be retrieved from a remote chain based on their hashes. -func TestGetBlockBodiesLes2(t *testing.T) { testGetBlockBodies(t, 2) } -func TestGetBlockBodiesLes3(t *testing.T) { testGetBlockBodies(t, 3) } -func TestGetBlockBodiesLes4(t *testing.T) { testGetBlockBodies(t, 4) } - -func testGetBlockBodies(t *testing.T, protocol int) { - netconfig := testnetConfig{ - blocks: downloader.MaxHeaderFetch + 15, - protocol: protocol, - nopruning: true, - } - server, _, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) - defer closePeer() - - bc := server.handler.blockchain - - // Create a batch of tests for various scenarios - limit := MaxBodyFetch - tests := []struct { - random int // Number of blocks to fetch randomly from the chain - explicit []common.Hash // Explicitly requested blocks - available []bool // Availability of explicitly requested blocks - expected int // Total number of existing blocks to expect - }{ - {1, nil, nil, 1}, // A single random block should be retrievable - {10, nil, nil, 10}, // Multiple random blocks should be retrievable - {limit, nil, nil, limit}, // The maximum possible blocks should be retrievable - //{limit + 1, nil, nil, limit}, // No more than the possible block count should be returned - {0, []common.Hash{bc.Genesis().Hash()}, []bool{true}, 1}, // The genesis block should be retrievable - {0, []common.Hash{bc.CurrentBlock().Hash()}, []bool{true}, 1}, // The chains head block should be retrievable - {0, []common.Hash{{}}, []bool{false}, 0}, // A non existent block should not be returned - - // Existing and non-existing blocks interleaved should not cause problems - {0, []common.Hash{ - {}, - bc.GetBlockByNumber(1).Hash(), - {}, - bc.GetBlockByNumber(10).Hash(), - {}, - bc.GetBlockByNumber(100).Hash(), - {}, - }, []bool{false, true, false, true, false, true, false}, 3}, - } - // Run each of the tests and verify the results against the chain - var reqID uint64 - for i, tt := range tests { - // Collect the hashes to request, and the response to expect - var hashes []common.Hash - seen := make(map[int64]bool) - var bodies []*types.Body - - for j := 0; j < tt.random; j++ { - for { - num := rand.Int63n(int64(bc.CurrentBlock().Number.Uint64())) - if !seen[num] { - seen[num] = true - - block := bc.GetBlockByNumber(uint64(num)) - hashes = append(hashes, block.Hash()) - if len(bodies) < tt.expected { - bodies = append(bodies, &types.Body{Transactions: block.Transactions(), Uncles: block.Uncles()}) - } - break - } - } - } - for j, hash := range tt.explicit { - hashes = append(hashes, hash) - if tt.available[j] && len(bodies) < tt.expected { - block := bc.GetBlockByHash(hash) - bodies = append(bodies, &types.Body{Transactions: block.Transactions(), Uncles: block.Uncles()}) - } - } - reqID++ - - // Send the hash request and verify the response - sendRequest(rawPeer.app, GetBlockBodiesMsg, reqID, hashes) - if err := expectResponse(rawPeer.app, BlockBodiesMsg, reqID, testBufLimit, bodies); err != nil { - t.Errorf("test %d: bodies mismatch: %v", i, err) - } - } -} - -// Tests that the contract codes can be retrieved based on account addresses. -func TestGetCodeLes2(t *testing.T) { testGetCode(t, 2) } -func TestGetCodeLes3(t *testing.T) { testGetCode(t, 3) } -func TestGetCodeLes4(t *testing.T) { testGetCode(t, 4) } - -func testGetCode(t *testing.T, protocol int) { - // Assemble the test environment - netconfig := testnetConfig{ - blocks: 4, - protocol: protocol, - nopruning: true, - } - server, _, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) - defer closePeer() - - bc := server.handler.blockchain - - var codereqs []*CodeReq - var codes [][]byte - for i := uint64(0); i <= bc.CurrentBlock().Number.Uint64(); i++ { - header := bc.GetHeaderByNumber(i) - req := &CodeReq{ - BHash: header.Hash(), - AccountAddress: testContractAddr[:], - } - codereqs = append(codereqs, req) - if i >= testContractDeployed { - codes = append(codes, testContractCodeDeployed) - } - } - - sendRequest(rawPeer.app, GetCodeMsg, 42, codereqs) - if err := expectResponse(rawPeer.app, CodeMsg, 42, testBufLimit, codes); err != nil { - t.Errorf("codes mismatch: %v", err) - } -} - -// Tests that the stale contract codes can't be retrieved based on account addresses. -func TestGetStaleCodeLes2(t *testing.T) { testGetStaleCode(t, 2) } -func TestGetStaleCodeLes3(t *testing.T) { testGetStaleCode(t, 3) } -func TestGetStaleCodeLes4(t *testing.T) { testGetStaleCode(t, 4) } - -func testGetStaleCode(t *testing.T, protocol int) { - netconfig := testnetConfig{ - blocks: core.TriesInMemory + 4, - protocol: protocol, - nopruning: true, - } - server, _, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) - defer closePeer() - - bc := server.handler.blockchain - - check := func(number uint64, expected [][]byte) { - req := &CodeReq{ - 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 { - t.Errorf("codes mismatch: %v", err) - } - } - check(0, [][]byte{}) // Non-exist contract - check(testContractDeployed, [][]byte{}) // Stale contract - check(bc.CurrentHeader().Number.Uint64(), [][]byte{testContractCodeDeployed}) // Fresh contract -} - -// Tests that the transaction receipts can be retrieved based on hashes. -func TestGetReceiptLes2(t *testing.T) { testGetReceipt(t, 2) } -func TestGetReceiptLes3(t *testing.T) { testGetReceipt(t, 3) } -func TestGetReceiptLes4(t *testing.T) { testGetReceipt(t, 4) } - -func testGetReceipt(t *testing.T, protocol int) { - // Assemble the test environment - netconfig := testnetConfig{ - blocks: 4, - protocol: protocol, - nopruning: true, - } - server, _, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) - defer closePeer() - - bc := server.handler.blockchain - - // Collect the hashes to request, and the response to expect - var receipts []types.Receipts - var hashes []common.Hash - for i := uint64(0); i <= bc.CurrentBlock().Number.Uint64(); i++ { - block := bc.GetBlockByNumber(i) - - hashes = append(hashes, block.Hash()) - receipts = append(receipts, rawdb.ReadReceipts(server.db, block.Hash(), block.NumberU64(), block.Time(), bc.Config())) - } - // Send the hash request and verify the response - sendRequest(rawPeer.app, GetReceiptsMsg, 42, hashes) - if err := expectResponse(rawPeer.app, ReceiptsMsg, 42, testBufLimit, receipts); err != nil { - t.Errorf("receipts mismatch: %v", err) - } -} - -// Tests that trie merkle proofs can be retrieved -func TestGetProofsLes2(t *testing.T) { testGetProofs(t, 2) } -func TestGetProofsLes3(t *testing.T) { testGetProofs(t, 3) } -func TestGetProofsLes4(t *testing.T) { testGetProofs(t, 4) } - -func testGetProofs(t *testing.T, protocol int) { - // Assemble the test environment - netconfig := testnetConfig{ - blocks: 4, - protocol: protocol, - nopruning: true, - } - server, _, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) - defer closePeer() - - bc := server.handler.blockchain - - var proofreqs []ProofReq - proofsV2 := light.NewNodeSet() - - 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)) - - for _, acc := range accounts { - req := ProofReq{ - BHash: header.Hash(), - Key: crypto.Keccak256(acc[:]), - } - proofreqs = append(proofreqs, req) - 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 { - t.Errorf("proofs mismatch: %v", err) - } -} - -// Tests that the stale contract codes can't be retrieved based on account addresses. -func TestGetStaleProofLes2(t *testing.T) { testGetStaleProof(t, 2) } -func TestGetStaleProofLes3(t *testing.T) { testGetStaleProof(t, 3) } -func TestGetStaleProofLes4(t *testing.T) { testGetStaleProof(t, 4) } - -func testGetStaleProof(t *testing.T, protocol int) { - netconfig := testnetConfig{ - blocks: core.TriesInMemory + 4, - protocol: protocol, - nopruning: true, - } - server, _, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) - defer closePeer() - - bc := server.handler.blockchain - - check := func(number uint64, wantOK bool) { - var ( - header = bc.GetHeaderByNumber(number) - account = crypto.Keccak256(userAddr1.Bytes()) - ) - req := &ProofReq{ - BHash: header.Hash(), - Key: account, - } - sendRequest(rawPeer.app, GetProofsV2Msg, 42, []*ProofReq{req}) - - var expected []rlp.RawValue - if wantOK { - proofsV2 := light.NewNodeSet() - t, _ := trie.New(trie.StateTrieID(header.Root), trie.NewDatabase(server.db)) - t.Prove(account, proofsV2) - expected = proofsV2.NodeList() - } - if err := expectResponse(rawPeer.app, ProofsV2Msg, 42, testBufLimit, expected); err != nil { - t.Errorf("codes mismatch: %v", err) - } - } - check(0, false) // Non-exist proof - check(2, false) // Stale proof - check(bc.CurrentHeader().Number.Uint64(), true) // Fresh proof -} - -// Tests that CHT proofs can be correctly retrieved. -func TestGetCHTProofsLes2(t *testing.T) { testGetCHTProofs(t, 2) } -func TestGetCHTProofsLes3(t *testing.T) { testGetCHTProofs(t, 3) } -func TestGetCHTProofsLes4(t *testing.T) { testGetCHTProofs(t, 4) } - -func testGetCHTProofs(t *testing.T, protocol int) { - var ( - config = light.TestServerIndexerConfig - waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { - for { - cs, _, _ := cIndexer.Sections() - if cs >= 1 { - break - } - time.Sleep(10 * time.Millisecond) - } - } - netconfig = testnetConfig{ - blocks: int(config.ChtSize + config.ChtConfirms), - protocol: protocol, - indexFn: waitIndexers, - nopruning: true, - } - ) - server, _, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) - defer closePeer() - - bc := server.handler.blockchain - - // Assemble the proofs from the different protocols - header := bc.GetHeaderByNumber(config.ChtSize - 1) - rlp, _ := rlp.EncodeToBytes(header) - - key := make([]byte, 8) - binary.BigEndian.PutUint64(key, config.ChtSize-1) - - proofsV2 := HelperTrieResps{ - 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, &proofsV2.Proofs) - // Assemble the requests for the different protocols - requestsV2 := []HelperTrieReq{{ - Type: htCanonical, - TrieIdx: 0, - Key: key, - AuxReq: htAuxHeader, - }} - // Send the proof request and verify the response - sendRequest(rawPeer.app, GetHelperTrieProofsMsg, 42, requestsV2) - if err := expectResponse(rawPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofsV2); err != nil { - t.Errorf("proofs mismatch: %v", err) - } -} - -func TestGetBloombitsProofsLes2(t *testing.T) { testGetBloombitsProofs(t, 2) } -func TestGetBloombitsProofsLes3(t *testing.T) { testGetBloombitsProofs(t, 3) } -func TestGetBloombitsProofsLes4(t *testing.T) { testGetBloombitsProofs(t, 4) } - -// Tests that bloombits proofs can be correctly retrieved. -func testGetBloombitsProofs(t *testing.T, protocol int) { - var ( - config = light.TestServerIndexerConfig - waitIndexers = func(cIndexer, bIndexer, btIndexer *core.ChainIndexer) { - for { - bts, _, _ := btIndexer.Sections() - if bts >= 1 { - break - } - time.Sleep(10 * time.Millisecond) - } - } - netconfig = testnetConfig{ - blocks: int(config.BloomTrieSize + config.BloomTrieConfirms), - protocol: protocol, - indexFn: waitIndexers, - nopruning: true, - } - ) - server, _, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) - defer closePeer() - - bc := server.handler.blockchain - - // Request and verify each bit of the bloom bits proofs - for bit := 0; bit < 2048; bit++ { - // Assemble the request and proofs for the bloombits - key := make([]byte, 10) - - binary.BigEndian.PutUint16(key[:2], uint16(bit)) - // Only the first bloom section has data. - binary.BigEndian.PutUint64(key[2:], 0) - - requests := []HelperTrieReq{{ - Type: htBloomBits, - TrieIdx: 0, - Key: key, - }} - 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, &proofs.Proofs) - - // Send the proof request and verify the response - sendRequest(rawPeer.app, GetHelperTrieProofsMsg, 42, requests) - if err := expectResponse(rawPeer.app, HelperTrieProofsMsg, 42, testBufLimit, proofs); err != nil { - t.Errorf("bit %d: proofs mismatch: %v", bit, err) - } - } -} - -func TestTransactionStatusLes2(t *testing.T) { testTransactionStatus(t, lpv2) } -func TestTransactionStatusLes3(t *testing.T) { testTransactionStatus(t, lpv3) } -func TestTransactionStatusLes4(t *testing.T) { testTransactionStatus(t, lpv4) } - -func testTransactionStatus(t *testing.T, protocol int) { - netconfig := testnetConfig{ - protocol: protocol, - nopruning: true, - } - server, _, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) - defer closePeer() - - server.handler.addTxsSync = true - - chain := server.handler.blockchain - - 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}) - } else { - 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: %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: "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 - test(tx1, true, light.TxStatus{Status: txpool.TxStatusPending}) // send valid processable tx, should return pending - test(tx1, true, light.TxStatus{Status: txpool.TxStatusPending}) // adding it again should not return an error - - tx2, _ := types.SignTx(types.NewTransaction(1, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, bankKey) - tx3, _ := types.SignTx(types.NewTransaction(2, userAddr1, big.NewInt(10000), params.TxGas, big.NewInt(100000000000), nil), signer, bankKey) - // send transactions in the wrong order, tx3 should be queued - test(tx3, true, light.TxStatus{Status: txpool.TxStatusQueued}) - test(tx2, true, light.TxStatus{Status: txpool.TxStatusPending}) - // query again, now tx3 should be pending too - test(tx3, false, light.TxStatus{Status: txpool.TxStatusPending}) - - // generate and add a block with tx1 and tx2 included - gchain, _ := core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), ethash.NewFaker(), server.db, 1, func(i int, block *core.BlockGen) { - block.AddTx(tx1) - block.AddTx(tx2) - }) - if _, err := chain.InsertChain(gchain); err != nil { - panic(err) - } - // wait until TxPool processes the inserted block - for i := 0; i < 10; i++ { - if pending, _ := server.handler.txpool.Stats(); pending == 1 { - break - } - time.Sleep(100 * time.Millisecond) - } - if pending, _ := server.handler.txpool.Stats(); pending != 1 { - t.Fatalf("pending count mismatch: have %d, want 1", pending) - } - // Discard new block announcement - msg, _ := rawPeer.app.ReadMsg() - msg.Discard() - - // check if their status is included now - block1hash := rawdb.ReadCanonicalHash(server.db, 1) - test(tx1, false, light.TxStatus{Status: txpool.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 0}}) - - test(tx2, false, light.TxStatus{Status: txpool.TxStatusIncluded, Lookup: &rawdb.LegacyTxLookupEntry{BlockHash: block1hash, BlockIndex: 1, Index: 1}}) - - // create a reorg that rolls them back - gchain, _ = core.GenerateChain(params.TestChainConfig, chain.GetBlockByNumber(0), ethash.NewFaker(), server.db, 2, func(i int, block *core.BlockGen) {}) - if _, err := chain.InsertChain(gchain); err != nil { - panic(err) - } - // wait until TxPool processes the reorg - for i := 0; i < 10; i++ { - if pending, _ := server.handler.txpool.Stats(); pending == 3 { - break - } - time.Sleep(100 * time.Millisecond) - } - if pending, _ := server.handler.txpool.Stats(); pending != 3 { - t.Fatalf("pending count mismatch: have %d, want 3", pending) - } - // Discard new block announcement - msg, _ = rawPeer.app.ReadMsg() - msg.Discard() - - // check if their status is pending again - test(tx1, false, light.TxStatus{Status: txpool.TxStatusPending}) - test(tx2, false, light.TxStatus{Status: txpool.TxStatusPending}) -} - -func TestStopResumeLES3(t *testing.T) { testStopResume(t, lpv3) } -func TestStopResumeLES4(t *testing.T) { testStopResume(t, lpv4) } - -func testStopResume(t *testing.T, protocol int) { - netconfig := testnetConfig{ - protocol: protocol, - simClock: true, - nopruning: true, - } - server, _, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - server.handler.server.costTracker.testing = true - server.handler.server.costTracker.testCostList = testCostList(testBufLimit / 10) - - rawPeer, closePeer, _ := server.newRawPeer(t, "peer", protocol) - defer closePeer() - - var ( - reqID uint64 - expBuf = testBufLimit - testCost = testBufLimit / 10 - ) - header := server.handler.blockchain.CurrentHeader() - req := func() { - reqID++ - sendRequest(rawPeer.app, GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: header.Hash()}, Amount: 1}) - } - for i := 1; i <= 5; i++ { - // send requests while we still have enough buffer and expect a response - for expBuf >= testCost { - req() - expBuf -= testCost - if err := expectResponse(rawPeer.app, BlockHeadersMsg, reqID, expBuf, []*types.Header{header}); err != nil { - t.Errorf("expected response and failed: %v", err) - } - } - // send some more requests in excess and expect a single StopMsg - c := i - for c > 0 { - req() - c-- - } - if err := p2p.ExpectMsg(rawPeer.app, StopMsg, nil); err != nil { - t.Errorf("expected StopMsg and failed: %v", err) - } - // wait until the buffer is recharged by half of the limit - wait := testBufLimit / testBufRecharge / 2 - server.clock.(*mclock.Simulated).Run(time.Millisecond * time.Duration(wait)) - - // expect a ResumeMsg with the partially recharged buffer value - expBuf += testBufRecharge * wait - if err := p2p.ExpectMsg(rawPeer.app, ResumeMsg, expBuf); err != nil { - t.Errorf("expected ResumeMsg and failed: %v", err) - } - } -} diff --git a/les/metrics.go b/les/metrics.go deleted file mode 100644 index 07d3133c9..000000000 --- a/les/metrics.go +++ /dev/null @@ -1,151 +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 ( - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/p2p" -) - -var ( - miscInPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/total", nil) - miscInTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/total", nil) - miscInHeaderPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/header", nil) - miscInHeaderTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/header", nil) - miscInBodyPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/body", nil) - miscInBodyTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/body", nil) - miscInCodePacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/code", nil) - miscInCodeTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/code", nil) - miscInReceiptPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/receipt", nil) - miscInReceiptTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/receipt", nil) - miscInTrieProofPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/proof", nil) - miscInTrieProofTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/proof", nil) - miscInHelperTriePacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/helperTrie", nil) - miscInHelperTrieTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/helperTrie", nil) - miscInTxsPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/txs", nil) - miscInTxsTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/txs", nil) - miscInTxStatusPacketsMeter = metrics.NewRegisteredMeter("les/misc/in/packets/txStatus", nil) - miscInTxStatusTrafficMeter = metrics.NewRegisteredMeter("les/misc/in/traffic/txStatus", nil) - - miscOutPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/total", nil) - miscOutTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/total", nil) - miscOutHeaderPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/header", nil) - miscOutHeaderTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/header", nil) - miscOutBodyPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/body", nil) - miscOutBodyTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/body", nil) - miscOutCodePacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/code", nil) - miscOutCodeTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/code", nil) - miscOutReceiptPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/receipt", nil) - miscOutReceiptTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/receipt", nil) - miscOutTrieProofPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/proof", nil) - miscOutTrieProofTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/proof", nil) - miscOutHelperTriePacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/helperTrie", nil) - miscOutHelperTrieTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/helperTrie", nil) - miscOutTxsPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/txs", nil) - miscOutTxsTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/txs", nil) - miscOutTxStatusPacketsMeter = metrics.NewRegisteredMeter("les/misc/out/packets/txStatus", nil) - miscOutTxStatusTrafficMeter = metrics.NewRegisteredMeter("les/misc/out/traffic/txStatus", nil) - - miscServingTimeHeaderTimer = metrics.NewRegisteredTimer("les/misc/serve/header", nil) - miscServingTimeBodyTimer = metrics.NewRegisteredTimer("les/misc/serve/body", nil) - miscServingTimeCodeTimer = metrics.NewRegisteredTimer("les/misc/serve/code", nil) - miscServingTimeReceiptTimer = metrics.NewRegisteredTimer("les/misc/serve/receipt", nil) - miscServingTimeTrieProofTimer = metrics.NewRegisteredTimer("les/misc/serve/proof", nil) - miscServingTimeHelperTrieTimer = metrics.NewRegisteredTimer("les/misc/serve/helperTrie", nil) - miscServingTimeTxTimer = metrics.NewRegisteredTimer("les/misc/serve/txs", nil) - miscServingTimeTxStatusTimer = metrics.NewRegisteredTimer("les/misc/serve/txStatus", nil) - - connectionTimer = metrics.NewRegisteredTimer("les/connection/duration", nil) - serverConnectionGauge = metrics.NewRegisteredGauge("les/connection/server", nil) - - totalCapacityGauge = metrics.NewRegisteredGauge("les/server/totalCapacity", nil) - totalRechargeGauge = metrics.NewRegisteredGauge("les/server/totalRecharge", nil) - blockProcessingTimer = metrics.NewRegisteredTimer("les/server/blockProcessingTime", nil) - - requestServedMeter = metrics.NewRegisteredMeter("les/server/req/avgServedTime", nil) - requestServedTimer = metrics.NewRegisteredTimer("les/server/req/servedTime", nil) - requestEstimatedMeter = metrics.NewRegisteredMeter("les/server/req/avgEstimatedTime", nil) - requestEstimatedTimer = metrics.NewRegisteredTimer("les/server/req/estimatedTime", nil) - relativeCostHistogram = metrics.NewRegisteredHistogram("les/server/req/relative", nil, metrics.NewExpDecaySample(1028, 0.015)) - relativeCostHeaderHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/header", nil, metrics.NewExpDecaySample(1028, 0.015)) - relativeCostBodyHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/body", nil, metrics.NewExpDecaySample(1028, 0.015)) - relativeCostReceiptHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/receipt", nil, metrics.NewExpDecaySample(1028, 0.015)) - relativeCostCodeHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/code", nil, metrics.NewExpDecaySample(1028, 0.015)) - relativeCostProofHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/proof", nil, metrics.NewExpDecaySample(1028, 0.015)) - relativeCostHelperProofHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/helperTrie", nil, metrics.NewExpDecaySample(1028, 0.015)) - relativeCostSendTxHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/txs", nil, metrics.NewExpDecaySample(1028, 0.015)) - relativeCostTxStatusHistogram = metrics.NewRegisteredHistogram("les/server/req/relative/txStatus", nil, metrics.NewExpDecaySample(1028, 0.015)) - - globalFactorGauge = metrics.NewRegisteredGauge("les/server/globalFactor", nil) - recentServedGauge = metrics.NewRegisteredGauge("les/server/recentRequestServed", nil) - recentEstimatedGauge = metrics.NewRegisteredGauge("les/server/recentRequestEstimated", nil) - sqServedGauge = metrics.NewRegisteredGauge("les/server/servingQueue/served", nil) - sqQueuedGauge = metrics.NewRegisteredGauge("les/server/servingQueue/queued", nil) - - clientFreezeMeter = metrics.NewRegisteredMeter("les/server/clientEvent/freeze", nil) - clientErrorMeter = metrics.NewRegisteredMeter("les/server/clientEvent/error", nil) - - requestRTT = metrics.NewRegisteredTimer("les/client/req/rtt", nil) - requestSendDelay = metrics.NewRegisteredTimer("les/client/req/sendDelay", nil) - - serverSelectableGauge = metrics.NewRegisteredGauge("les/client/serverPool/selectable", nil) - serverDialedMeter = metrics.NewRegisteredMeter("les/client/serverPool/dialed", nil) - serverConnectedGauge = metrics.NewRegisteredGauge("les/client/serverPool/connected", nil) - sessionValueMeter = metrics.NewRegisteredMeter("les/client/serverPool/sessionValue", nil) - totalValueGauge = metrics.NewRegisteredGauge("les/client/serverPool/totalValue", nil) - suggestedTimeoutGauge = metrics.NewRegisteredGauge("les/client/serverPool/timeout", nil) -) - -// meteredMsgReadWriter is a wrapper around a p2p.MsgReadWriter, capable of -// accumulating the above defined metrics based on the data stream contents. -type meteredMsgReadWriter struct { - p2p.MsgReadWriter // Wrapped message stream to meter - version int // Protocol version to select correct meters -} - -// newMeteredMsgWriter wraps a p2p MsgReadWriter with metering support. If the -// metrics system is disabled, this function returns the original object. -func newMeteredMsgWriter(rw p2p.MsgReadWriter, version int) p2p.MsgReadWriter { - if !metrics.Enabled { - return rw - } - return &meteredMsgReadWriter{MsgReadWriter: rw, version: version} -} - -func (rw *meteredMsgReadWriter) ReadMsg() (p2p.Msg, error) { - // Read the message and short circuit in case of an error - msg, err := rw.MsgReadWriter.ReadMsg() - if err != nil { - return msg, err - } - // Account for the data traffic - packets, traffic := miscInPacketsMeter, miscInTrafficMeter - packets.Mark(1) - traffic.Mark(int64(msg.Size)) - - return msg, err -} - -func (rw *meteredMsgReadWriter) WriteMsg(msg p2p.Msg) error { - // Account for the data traffic - packets, traffic := miscOutPacketsMeter, miscOutTrafficMeter - packets.Mark(1) - traffic.Mark(int64(msg.Size)) - - // Send the packet to the p2p layer - return rw.MsgReadWriter.WriteMsg(msg) -} diff --git a/les/odr.go b/les/odr.go deleted file mode 100644 index 943b05fdf..000000000 --- a/les/odr.go +++ /dev/null @@ -1,237 +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 ( - "context" - "math/rand" - "sort" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/txpool" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/light" -) - -// LesOdr implements light.OdrBackend -type LesOdr struct { - db ethdb.Database - indexerConfig *light.IndexerConfig - chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer - peers *serverPeerSet - retriever *retrieveManager - stop chan struct{} -} - -func NewLesOdr(db ethdb.Database, config *light.IndexerConfig, peers *serverPeerSet, retriever *retrieveManager) *LesOdr { - return &LesOdr{ - db: db, - indexerConfig: config, - peers: peers, - retriever: retriever, - stop: make(chan struct{}), - } -} - -// Stop cancels all pending retrievals -func (odr *LesOdr) Stop() { - close(odr.stop) -} - -// Database returns the backing database -func (odr *LesOdr) Database() ethdb.Database { - return odr.db -} - -// SetIndexers adds the necessary chain indexers to the ODR backend -func (odr *LesOdr) SetIndexers(chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer) { - odr.chtIndexer = chtIndexer - odr.bloomTrieIndexer = bloomTrieIndexer - odr.bloomIndexer = bloomIndexer -} - -// ChtIndexer returns the CHT chain indexer -func (odr *LesOdr) ChtIndexer() *core.ChainIndexer { - return odr.chtIndexer -} - -// BloomTrieIndexer returns the bloom trie chain indexer -func (odr *LesOdr) BloomTrieIndexer() *core.ChainIndexer { - return odr.bloomTrieIndexer -} - -// BloomIndexer returns the bloombits chain indexer -func (odr *LesOdr) BloomIndexer() *core.ChainIndexer { - return odr.bloomIndexer -} - -// IndexerConfig returns the indexer config. -func (odr *LesOdr) IndexerConfig() *light.IndexerConfig { - return odr.indexerConfig -} - -const ( - MsgBlockHeaders = iota - MsgBlockBodies - MsgCode - MsgReceipts - MsgProofsV2 - MsgHelperTrieProofs - MsgTxStatus -) - -// Msg encodes a LES message that delivers reply data for a request -type Msg struct { - MsgType int - ReqID uint64 - Obj interface{} -} - -// peerByTxHistory is a heap.Interface implementation which can sort -// the peerset by transaction history. -type peerByTxHistory []*serverPeer - -func (h peerByTxHistory) Len() int { return len(h) } -func (h peerByTxHistory) Less(i, j int) bool { - if h[i].txHistory == txIndexUnlimited { - return false - } - if h[j].txHistory == txIndexUnlimited { - return true - } - return h[i].txHistory < h[j].txHistory -} -func (h peerByTxHistory) Swap(i, j int) { h[i], h[j] = h[j], h[i] } - -const ( - maxTxStatusRetry = 3 // The maximum retries will be made for tx status request. - maxTxStatusCandidates = 5 // The maximum les servers the tx status requests will be sent to. -) - -// RetrieveTxStatus retrieves the transaction status from the LES network. -// There is no guarantee in the LES protocol that the mined transaction will -// be retrieved back for sure because of different reasons(the transaction -// is unindexed, the malicious server doesn't reply it deliberately, etc). -// Therefore, unretrieved transactions(UNKNOWN) will receive a certain number -// of retries, thus giving a weak guarantee. -func (odr *LesOdr) RetrieveTxStatus(ctx context.Context, req *light.TxStatusRequest) error { - // Sort according to the transaction history supported by the peer and - // select the peers with longest history. - var ( - retries int - peers []*serverPeer - missing = len(req.Hashes) - result = make([]light.TxStatus, len(req.Hashes)) - canSend = make(map[string]bool) - ) - for _, peer := range odr.peers.allPeers() { - if peer.txHistory == txIndexDisabled { - continue - } - peers = append(peers, peer) - } - sort.Sort(sort.Reverse(peerByTxHistory(peers))) - for i := 0; i < maxTxStatusCandidates && i < len(peers); i++ { - canSend[peers[i].id] = true - } - // Send out the request and assemble the result. - for { - if retries >= maxTxStatusRetry || len(canSend) == 0 { - break - } - var ( - // Deep copy the request, so that the partial result won't be mixed. - req = &TxStatusRequest{Hashes: req.Hashes} - id = rand.Uint64() - distreq = &distReq{ - getCost: func(dp distPeer) uint64 { return req.GetCost(dp.(*serverPeer)) }, - canSend: func(dp distPeer) bool { return canSend[dp.(*serverPeer).id] }, - request: func(dp distPeer) func() { - p := dp.(*serverPeer) - p.fcServer.QueuedRequest(id, req.GetCost(p)) - delete(canSend, p.id) - return func() { req.Request(id, p) } - }, - } - ) - if err := odr.retriever.retrieve(ctx, id, distreq, func(p distPeer, msg *Msg) error { return req.Validate(odr.db, msg) }, odr.stop); err != nil { - return err - } - // Collect the response and assemble them to the final result. - // All the response is not verifiable, so always pick the first - // one we get. - for index, status := range req.Status { - if result[index].Status != txpool.TxStatusUnknown { - continue - } - if status.Status == txpool.TxStatusUnknown { - continue - } - result[index], missing = status, missing-1 - } - // Abort the procedure if all the status are retrieved - if missing == 0 { - break - } - retries += 1 - } - req.Status = result - return nil -} - -// Retrieve tries to fetch an object from the LES network. It's a common API -// for most of the LES requests except for the TxStatusRequest which needs -// the additional retry mechanism. -// If the network retrieval was successful, it stores the object in local db. -func (odr *LesOdr) Retrieve(ctx context.Context, req light.OdrRequest) (err error) { - lreq := LesRequest(req) - - reqID := rand.Uint64() - rq := &distReq{ - getCost: func(dp distPeer) uint64 { - return lreq.GetCost(dp.(*serverPeer)) - }, - canSend: func(dp distPeer) bool { - p := dp.(*serverPeer) - if !p.onlyAnnounce { - return lreq.CanSend(p) - } - return false - }, - request: func(dp distPeer) func() { - p := dp.(*serverPeer) - cost := lreq.GetCost(p) - p.fcServer.QueuedRequest(reqID, cost) - return func() { lreq.Request(reqID, p) } - }, - } - - defer func(sent mclock.AbsTime) { - if err != nil { - return - } - requestRTT.Update(time.Duration(mclock.Now() - sent)) - }(mclock.Now()) - - if err := odr.retriever.retrieve(ctx, reqID, rq, func(p distPeer, msg *Msg) error { return lreq.Validate(odr.db, msg) }, odr.stop); err != nil { - return err - } - req.StoreResult(odr.db) - return nil -} diff --git a/les/odr_requests.go b/les/odr_requests.go deleted file mode 100644 index 2b23e0540..000000000 --- a/les/odr_requests.go +++ /dev/null @@ -1,536 +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 ( - "encoding/binary" - "errors" - "fmt" - - "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" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie" -) - -var ( - errInvalidMessageType = errors.New("invalid message type") - errInvalidEntryCount = errors.New("invalid number of response entries") - errHeaderUnavailable = errors.New("header unavailable") - errTxHashMismatch = errors.New("transaction hash mismatch") - errUncleHashMismatch = errors.New("uncle hash mismatch") - errReceiptHashMismatch = errors.New("receipt hash mismatch") - errDataHashMismatch = errors.New("data hash mismatch") - errCHTHashMismatch = errors.New("cht hash mismatch") - errCHTNumberMismatch = errors.New("cht number mismatch") - errUselessNodes = errors.New("useless nodes in merkle proof nodeset") -) - -type LesOdrRequest interface { - GetCost(*serverPeer) uint64 - CanSend(*serverPeer) bool - Request(uint64, *serverPeer) error - Validate(ethdb.Database, *Msg) error -} - -func LesRequest(req light.OdrRequest) LesOdrRequest { - switch r := req.(type) { - case *light.BlockRequest: - return (*BlockRequest)(r) - case *light.ReceiptsRequest: - return (*ReceiptsRequest)(r) - case *light.TrieRequest: - return (*TrieRequest)(r) - case *light.CodeRequest: - return (*CodeRequest)(r) - case *light.ChtRequest: - return (*ChtRequest)(r) - case *light.BloomRequest: - return (*BloomRequest)(r) - case *light.TxStatusRequest: - return (*TxStatusRequest)(r) - default: - return nil - } -} - -// BlockRequest is the ODR request type for block bodies -type BlockRequest light.BlockRequest - -// GetCost returns the cost of the given ODR request according to the serving -// peer's cost table (implementation of LesOdrRequest) -func (r *BlockRequest) GetCost(peer *serverPeer) uint64 { - return peer.getRequestCost(GetBlockBodiesMsg, 1) -} - -// CanSend tells if a certain peer is suitable for serving the given request -func (r *BlockRequest) CanSend(peer *serverPeer) bool { - return peer.HasBlock(r.Hash, r.Number, false) -} - -// Request sends an ODR request to the LES network (implementation of LesOdrRequest) -func (r *BlockRequest) Request(reqID uint64, peer *serverPeer) error { - peer.Log().Debug("Requesting block body", "hash", r.Hash) - return peer.requestBodies(reqID, []common.Hash{r.Hash}) -} - -// Validate processes an ODR request reply message from the LES network -// returns true and stores results in memory if the message was a valid reply -// to the request (implementation of LesOdrRequest) -func (r *BlockRequest) Validate(db ethdb.Database, msg *Msg) error { - log.Debug("Validating block body", "hash", r.Hash) - - // Ensure we have a correct message with a single block body - if msg.MsgType != MsgBlockBodies { - return errInvalidMessageType - } - bodies := msg.Obj.([]*types.Body) - if len(bodies) != 1 { - return errInvalidEntryCount - } - body := bodies[0] - - // Retrieve our stored header and validate block content against it - if r.Header == nil { - r.Header = rawdb.ReadHeader(db, r.Hash, r.Number) - } - if r.Header == nil { - return errHeaderUnavailable - } - if r.Header.TxHash != types.DeriveSha(types.Transactions(body.Transactions), trie.NewStackTrie(nil)) { - return errTxHashMismatch - } - if r.Header.UncleHash != types.CalcUncleHash(body.Uncles) { - return errUncleHashMismatch - } - // Validations passed, encode and store RLP - data, err := rlp.EncodeToBytes(body) - if err != nil { - return err - } - r.Rlp = data - return nil -} - -// ReceiptsRequest is the ODR request type for block receipts by block hash -type ReceiptsRequest light.ReceiptsRequest - -// GetCost returns the cost of the given ODR request according to the serving -// peer's cost table (implementation of LesOdrRequest) -func (r *ReceiptsRequest) GetCost(peer *serverPeer) uint64 { - return peer.getRequestCost(GetReceiptsMsg, 1) -} - -// CanSend tells if a certain peer is suitable for serving the given request -func (r *ReceiptsRequest) CanSend(peer *serverPeer) bool { - return peer.HasBlock(r.Hash, r.Number, false) -} - -// Request sends an ODR request to the LES network (implementation of LesOdrRequest) -func (r *ReceiptsRequest) Request(reqID uint64, peer *serverPeer) error { - peer.Log().Debug("Requesting block receipts", "hash", r.Hash) - return peer.requestReceipts(reqID, []common.Hash{r.Hash}) -} - -// Validate processes an ODR request reply message from the LES network -// returns true and stores results in memory if the message was a valid reply -// to the request (implementation of LesOdrRequest) -func (r *ReceiptsRequest) Validate(db ethdb.Database, msg *Msg) error { - log.Debug("Validating block receipts", "hash", r.Hash) - - // Ensure we have a correct message with a single block receipt - if msg.MsgType != MsgReceipts { - return errInvalidMessageType - } - receipts := msg.Obj.([]types.Receipts) - if len(receipts) != 1 { - return errInvalidEntryCount - } - receipt := receipts[0] - - // Retrieve our stored header and validate receipt content against it - if r.Header == nil { - r.Header = rawdb.ReadHeader(db, r.Hash, r.Number) - } - if r.Header == nil { - return errHeaderUnavailable - } - if r.Header.ReceiptHash != types.DeriveSha(receipt, trie.NewStackTrie(nil)) { - return errReceiptHashMismatch - } - // Validations passed, store and return - r.Receipts = receipt - return nil -} - -type ProofReq struct { - BHash common.Hash - AccountAddress, Key []byte - FromLevel uint -} - -// ODR request type for state/storage trie entries, see LesOdrRequest interface -type TrieRequest light.TrieRequest - -// GetCost returns the cost of the given ODR request according to the serving -// peer's cost table (implementation of LesOdrRequest) -func (r *TrieRequest) GetCost(peer *serverPeer) uint64 { - return peer.getRequestCost(GetProofsV2Msg, 1) -} - -// CanSend tells if a certain peer is suitable for serving the given request -func (r *TrieRequest) CanSend(peer *serverPeer) bool { - return peer.HasBlock(r.Id.BlockHash, r.Id.BlockNumber, true) -} - -// Request sends an ODR request to the LES network (implementation of LesOdrRequest) -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, - AccountAddress: r.Id.AccountAddress, - Key: r.Key, - } - return peer.requestProofs(reqID, []ProofReq{req}) -} - -// Validate processes an ODR request reply message from the LES network -// returns true and stores results in memory if the message was a valid reply -// to the request (implementation of LesOdrRequest) -func (r *TrieRequest) Validate(db ethdb.Database, msg *Msg) error { - log.Debug("Validating trie proof", "root", r.Id.Root, "key", r.Key) - - if msg.MsgType != MsgProofsV2 { - return errInvalidMessageType - } - proofs := msg.Obj.(light.NodeList) - // Verify the proof and store if checks out - nodeSet := proofs.NodeSet() - 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) - } - // check if all nodes have been read by VerifyProof - if len(reads.reads) != nodeSet.KeyCount() { - return errUselessNodes - } - r.Proof = nodeSet - return nil -} - -type CodeReq struct { - BHash common.Hash - AccountAddress []byte -} - -// CodeRequest is the ODR request type for node data (used for retrieving contract code), see LesOdrRequest interface -type CodeRequest light.CodeRequest - -// GetCost returns the cost of the given ODR request according to the serving -// peer's cost table (implementation of LesOdrRequest) -func (r *CodeRequest) GetCost(peer *serverPeer) uint64 { - return peer.getRequestCost(GetCodeMsg, 1) -} - -// CanSend tells if a certain peer is suitable for serving the given request -func (r *CodeRequest) CanSend(peer *serverPeer) bool { - return peer.HasBlock(r.Id.BlockHash, r.Id.BlockNumber, true) -} - -// Request sends an ODR request to the LES network (implementation of LesOdrRequest) -func (r *CodeRequest) Request(reqID uint64, peer *serverPeer) error { - peer.Log().Debug("Requesting code data", "hash", r.Hash) - req := CodeReq{ - BHash: r.Id.BlockHash, - AccountAddress: r.Id.AccountAddress, - } - return peer.requestCode(reqID, []CodeReq{req}) -} - -// Validate processes an ODR request reply message from the LES network -// returns true and stores results in memory if the message was a valid reply -// to the request (implementation of LesOdrRequest) -func (r *CodeRequest) Validate(db ethdb.Database, msg *Msg) error { - log.Debug("Validating code data", "hash", r.Hash) - - // Ensure we have a correct message with a single code element - if msg.MsgType != MsgCode { - return errInvalidMessageType - } - reply := msg.Obj.([][]byte) - if len(reply) != 1 { - return errInvalidEntryCount - } - data := reply[0] - - // Verify the data and store if checks out - if hash := crypto.Keccak256Hash(data); r.Hash != hash { - return errDataHashMismatch - } - r.Data = data - return nil -} - -const ( - // helper trie type constants - htCanonical = iota // Canonical hash trie - htBloomBits // BloomBits trie - - // helper trie auxiliary types - // htAuxNone = 1 ; deprecated number, used in les2/3 previously. - htAuxHeader = 2 // applicable for htCanonical, requests for relevant headers -) - -type HelperTrieReq struct { - Type uint - TrieIdx uint64 - Key []byte - FromLevel, AuxReq uint -} - -type HelperTrieResps struct { // describes all responses, not just a single one - Proofs light.NodeList - AuxData [][]byte -} - -// ChtRequest is the ODR request type for requesting headers by Canonical Hash Trie, see LesOdrRequest interface -type ChtRequest light.ChtRequest - -// GetCost returns the cost of the given ODR request according to the serving -// peer's cost table (implementation of LesOdrRequest) -func (r *ChtRequest) GetCost(peer *serverPeer) uint64 { - return peer.getRequestCost(GetHelperTrieProofsMsg, 1) -} - -// CanSend tells if a certain peer is suitable for serving the given request -func (r *ChtRequest) CanSend(peer *serverPeer) bool { - peer.lock.RLock() - defer peer.lock.RUnlock() - - return peer.headInfo.Number >= r.Config.ChtConfirms && r.ChtNum <= (peer.headInfo.Number-r.Config.ChtConfirms)/r.Config.ChtSize -} - -// Request sends an ODR request to the LES network (implementation of LesOdrRequest) -func (r *ChtRequest) Request(reqID uint64, peer *serverPeer) error { - peer.Log().Debug("Requesting CHT", "cht", r.ChtNum, "block", r.BlockNum) - var encNum [8]byte - binary.BigEndian.PutUint64(encNum[:], r.BlockNum) - req := HelperTrieReq{ - Type: htCanonical, - TrieIdx: r.ChtNum, - Key: encNum[:], - AuxReq: htAuxHeader, - } - return peer.requestHelperTrieProofs(reqID, []HelperTrieReq{req}) -} - -// Validate processes an ODR request reply message from the LES network -// returns true and stores results in memory if the message was a valid reply -// to the request (implementation of LesOdrRequest) -func (r *ChtRequest) Validate(db ethdb.Database, msg *Msg) error { - log.Debug("Validating CHT", "cht", r.ChtNum, "block", r.BlockNum) - - if msg.MsgType != MsgHelperTrieProofs { - return errInvalidMessageType - } - resp := msg.Obj.(HelperTrieResps) - if len(resp.AuxData) != 1 { - return errInvalidEntryCount - } - nodeSet := resp.Proofs.NodeSet() - headerEnc := resp.AuxData[0] - if len(headerEnc) == 0 { - return errHeaderUnavailable - } - header := new(types.Header) - if err := rlp.DecodeBytes(headerEnc, header); err != nil { - return errHeaderUnavailable - } - // Verify the CHT - var ( - node light.ChtNode - encNumber [8]byte - ) - binary.BigEndian.PutUint64(encNumber[:], r.BlockNum) - - reads := &readTraceDB{db: nodeSet} - value, err := trie.VerifyProof(r.ChtRoot, encNumber[:], reads) - if err != nil { - return fmt.Errorf("merkle proof verification failed: %v", err) - } - if len(reads.reads) != nodeSet.KeyCount() { - return errUselessNodes - } - if err := rlp.DecodeBytes(value, &node); err != nil { - return err - } - if node.Hash != header.Hash() { - return errCHTHashMismatch - } - if r.BlockNum != header.Number.Uint64() { - return errCHTNumberMismatch - } - // Verifications passed, store and return - r.Header = header - r.Proof = nodeSet - r.Td = node.Td - return nil -} - -type BloomReq struct { - BloomTrieNum, BitIdx, SectionIndex, FromLevel uint64 -} - -// BloomRequest is the ODR request type for requesting headers by Canonical Hash Trie, see LesOdrRequest interface -type BloomRequest light.BloomRequest - -// GetCost returns the cost of the given ODR request according to the serving -// peer's cost table (implementation of LesOdrRequest) -func (r *BloomRequest) GetCost(peer *serverPeer) uint64 { - return peer.getRequestCost(GetHelperTrieProofsMsg, len(r.SectionIndexList)) -} - -// CanSend tells if a certain peer is suitable for serving the given request -func (r *BloomRequest) CanSend(peer *serverPeer) bool { - peer.lock.RLock() - defer peer.lock.RUnlock() - - if peer.version < lpv2 { - return false - } - return peer.headInfo.Number >= r.Config.BloomTrieConfirms && r.BloomTrieNum <= (peer.headInfo.Number-r.Config.BloomTrieConfirms)/r.Config.BloomTrieSize -} - -// Request sends an ODR request to the LES network (implementation of LesOdrRequest) -func (r *BloomRequest) Request(reqID uint64, peer *serverPeer) error { - peer.Log().Debug("Requesting BloomBits", "bloomTrie", r.BloomTrieNum, "bitIdx", r.BitIdx, "sections", r.SectionIndexList) - reqs := make([]HelperTrieReq, len(r.SectionIndexList)) - - var encNumber [10]byte - binary.BigEndian.PutUint16(encNumber[:2], uint16(r.BitIdx)) - - for i, sectionIdx := range r.SectionIndexList { - binary.BigEndian.PutUint64(encNumber[2:], sectionIdx) - reqs[i] = HelperTrieReq{ - Type: htBloomBits, - TrieIdx: r.BloomTrieNum, - Key: common.CopyBytes(encNumber[:]), - } - } - return peer.requestHelperTrieProofs(reqID, reqs) -} - -// Validate processes an ODR request reply message from the LES network -// returns true and stores results in memory if the message was a valid reply -// to the request (implementation of LesOdrRequest) -func (r *BloomRequest) Validate(db ethdb.Database, msg *Msg) error { - log.Debug("Validating BloomBits", "bloomTrie", r.BloomTrieNum, "bitIdx", r.BitIdx, "sections", r.SectionIndexList) - - // Ensure we have a correct message with a single proof element - if msg.MsgType != MsgHelperTrieProofs { - return errInvalidMessageType - } - resps := msg.Obj.(HelperTrieResps) - proofs := resps.Proofs - nodeSet := proofs.NodeSet() - reads := &readTraceDB{db: nodeSet} - - r.BloomBits = make([][]byte, len(r.SectionIndexList)) - - // Verify the proofs - var encNumber [10]byte - binary.BigEndian.PutUint16(encNumber[:2], uint16(r.BitIdx)) - - for i, idx := range r.SectionIndexList { - binary.BigEndian.PutUint64(encNumber[2:], idx) - value, err := trie.VerifyProof(r.BloomTrieRoot, encNumber[:], reads) - if err != nil { - return err - } - r.BloomBits[i] = value - } - - if len(reads.reads) != nodeSet.KeyCount() { - return errUselessNodes - } - r.Proofs = nodeSet - return nil -} - -// TxStatusRequest is the ODR request type for transaction status -type TxStatusRequest light.TxStatusRequest - -// GetCost returns the cost of the given ODR request according to the serving -// peer's cost table (implementation of LesOdrRequest) -func (r *TxStatusRequest) GetCost(peer *serverPeer) uint64 { - return peer.getRequestCost(GetTxStatusMsg, len(r.Hashes)) -} - -// CanSend tells if a certain peer is suitable for serving the given request -func (r *TxStatusRequest) CanSend(peer *serverPeer) bool { - return peer.txHistory != txIndexDisabled -} - -// Request sends an ODR request to the LES network (implementation of LesOdrRequest) -func (r *TxStatusRequest) Request(reqID uint64, peer *serverPeer) error { - peer.Log().Debug("Requesting transaction status", "count", len(r.Hashes)) - return peer.requestTxStatus(reqID, r.Hashes) -} - -// Validate processes an ODR request reply message from the LES network -// returns true and stores results in memory if the message was a valid reply -// to the request (implementation of LesOdrRequest) -func (r *TxStatusRequest) Validate(db ethdb.Database, msg *Msg) error { - log.Debug("Validating transaction status", "count", len(r.Hashes)) - - if msg.MsgType != MsgTxStatus { - return errInvalidMessageType - } - status := msg.Obj.([]light.TxStatus) - if len(status) != len(r.Hashes) { - return errInvalidEntryCount - } - r.Status = status - return nil -} - -// readTraceDB stores the keys of database reads. We use this to check that received node -// sets contain only the trie nodes necessary to make proofs pass. -type readTraceDB struct { - db ethdb.KeyValueReader - reads map[string]struct{} -} - -// Get returns a stored node -func (db *readTraceDB) Get(k []byte) ([]byte, error) { - if db.reads == nil { - db.reads = make(map[string]struct{}) - } - db.reads[string(k)] = struct{}{} - return db.db.Get(k) -} - -// Has returns true if the node set contains the given key -func (db *readTraceDB) Has(key []byte) (bool, error) { - _, err := db.Get(key) - return err == nil, nil -} diff --git a/les/odr_test.go b/les/odr_test.go deleted file mode 100644 index 853b4ab56..000000000 --- a/les/odr_test.go +++ /dev/null @@ -1,458 +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 - -// 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" - "crypto/rand" - "fmt" - "math/big" - "reflect" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/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/core/vm" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/light" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" -) - -type odrTestFn func(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte - -func TestOdrGetBlockLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetBlock) } -func TestOdrGetBlockLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetBlock) } -func TestOdrGetBlockLes4(t *testing.T) { testOdr(t, 4, 1, true, odrGetBlock) } - -func odrGetBlock(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { - var block *types.Block - if bc != nil { - block = bc.GetBlockByHash(bhash) - } else { - block, _ = lc.GetBlockByHash(ctx, bhash) - } - if block == nil { - return nil - } - rlp, _ := rlp.EncodeToBytes(block) - return rlp -} - -func TestOdrGetReceiptsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrGetReceipts) } -func TestOdrGetReceiptsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrGetReceipts) } -func TestOdrGetReceiptsLes4(t *testing.T) { testOdr(t, 4, 1, true, odrGetReceipts) } - -func odrGetReceipts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { - var receipts types.Receipts - if bc != nil { - if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { - if header := rawdb.ReadHeader(db, bhash, *number); header != nil { - receipts = rawdb.ReadReceipts(db, bhash, *number, header.Time, config) - } - } - } else { - if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { - receipts, _ = light.GetBlockReceipts(ctx, lc.Odr(), bhash, *number) - } - } - if receipts == nil { - return nil - } - rlp, _ := rlp.EncodeToBytes(receipts) - return rlp -} - -func TestOdrAccountsLes2(t *testing.T) { testOdr(t, 2, 1, true, odrAccounts) } -func TestOdrAccountsLes3(t *testing.T) { testOdr(t, 3, 1, true, odrAccounts) } -func TestOdrAccountsLes4(t *testing.T) { testOdr(t, 4, 1, true, odrAccounts) } - -func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { - dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678") - acc := []common.Address{bankAddr, userAddr1, userAddr2, dummyAddr} - - var ( - res []byte - st *state.StateDB - err error - ) - for _, addr := range acc { - if bc != nil { - header := bc.GetHeaderByHash(bhash) - st, err = state.New(header.Root, state.NewDatabase(db), nil) - } else { - header := lc.GetHeaderByHash(bhash) - st = light.NewState(ctx, header, lc.Odr()) - } - if err == nil { - bal := st.GetBalance(addr) - rlp, _ := rlp.EncodeToBytes(bal) - res = append(res, rlp...) - } - } - return res -} - -func TestOdrContractCallLes2(t *testing.T) { testOdr(t, 2, 2, true, odrContractCall) } -func TestOdrContractCallLes3(t *testing.T) { testOdr(t, 3, 2, true, odrContractCall) } -func TestOdrContractCallLes4(t *testing.T) { testOdr(t, 4, 2, true, odrContractCall) } - -func odrContractCall(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { - data := common.Hex2Bytes("60CD26850000000000000000000000000000000000000000000000000000000000000000") - - var res []byte - for i := 0; i < 3; i++ { - data[35] = byte(i) - if bc != nil { - header := bc.GetHeaderByHash(bhash) - statedb, err := state.New(header.Root, bc.StateCache(), nil) - - if err == nil { - from := statedb.GetOrNewStateObject(bankAddr) - from.SetBalance(math.MaxBig256) - - msg := &core.Message{ - From: from.Address(), - To: &testContractAddr, - Value: new(big.Int), - GasLimit: 100000, - GasPrice: big.NewInt(params.InitialBaseFee), - GasFeeCap: big.NewInt(params.InitialBaseFee), - GasTipCap: new(big.Int), - Data: data, - SkipAccountChecks: true, - } - - context := core.NewEVMBlockContext(header, bc, nil) - txContext := core.NewEVMTxContext(msg) - vmenv := vm.NewEVM(context, txContext, statedb, config, vm.Config{NoBaseFee: true}) - - //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) - gp := new(core.GasPool).AddGas(math.MaxUint64) - result, _ := core.ApplyMessage(vmenv, msg, gp) - res = append(res, result.Return()...) - } - } else { - header := lc.GetHeaderByHash(bhash) - state := light.NewState(ctx, header, lc.Odr()) - state.SetBalance(bankAddr, math.MaxBig256) - msg := &core.Message{ - From: bankAddr, - To: &testContractAddr, - Value: new(big.Int), - GasLimit: 100000, - GasPrice: big.NewInt(params.InitialBaseFee), - GasFeeCap: big.NewInt(params.InitialBaseFee), - GasTipCap: new(big.Int), - Data: data, - SkipAccountChecks: true, - } - context := core.NewEVMBlockContext(header, lc, nil) - txContext := core.NewEVMTxContext(msg) - vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{NoBaseFee: true}) - gp := new(core.GasPool).AddGas(math.MaxUint64) - result, _ := core.ApplyMessage(vmenv, msg, gp) - if state.Error() == nil { - res = append(res, result.Return()...) - } - } - } - return res -} - -func TestOdrTxStatusLes2(t *testing.T) { testOdr(t, 2, 1, false, odrTxStatus) } -func TestOdrTxStatusLes3(t *testing.T) { testOdr(t, 3, 1, false, odrTxStatus) } -func TestOdrTxStatusLes4(t *testing.T) { testOdr(t, 4, 1, false, odrTxStatus) } - -func odrTxStatus(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte { - var txs types.Transactions - if bc != nil { - block := bc.GetBlockByHash(bhash) - txs = block.Transactions() - } else { - if block, _ := lc.GetBlockByHash(ctx, bhash); block != nil { - btxs := block.Transactions() - txs = make(types.Transactions, len(btxs)) - for i, tx := range btxs { - var err error - txs[i], _, _, _, err = light.GetTransaction(ctx, lc.Odr(), tx.Hash()) - if err != nil { - return nil - } - } - } - } - rlp, _ := rlp.EncodeToBytes(txs) - return rlp -} - -// testOdr tests odr requests whose validation guaranteed by block headers. -func testOdr(t *testing.T, protocol int, expFail uint64, checkCached bool, fn odrTestFn) { - // Assemble the test environment - netconfig := testnetConfig{ - blocks: 4, - protocol: protocol, - connect: true, - nopruning: true, - } - server, client, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - // Ensure the client has synced all necessary data. - clientHead := client.handler.backend.blockchain.CurrentHeader() - if clientHead.Number.Uint64() != 4 { - t.Fatalf("Failed to sync the chain with server, head: %v", clientHead.Number.Uint64()) - } - // Disable the mechanism that we will wait a few time for request - // even there is no suitable peer to send right now. - waitForPeers = 0 - - test := func(expFail uint64) { - // Mark this as a helper to put the failures at the correct lines - t.Helper() - - for i := uint64(0); i <= server.handler.blockchain.CurrentHeader().Number.Uint64(); i++ { - bhash := rawdb.ReadCanonicalHash(server.db, i) - b1 := fn(light.NoOdr, server.db, server.handler.server.chainConfig, server.handler.blockchain, nil, bhash) - - // Set the timeout as 1 second here, ensure there is enough time - // for travis to make the action. - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - b2 := fn(ctx, client.db, client.handler.backend.chainConfig, nil, client.handler.backend.blockchain, bhash) - cancel() - - eq := bytes.Equal(b1, b2) - exp := i < expFail - if exp && !eq { - t.Fatalf("odr mismatch: have %x, want %x", b2, b1) - } - if !exp && eq { - t.Fatalf("unexpected odr match") - } - } - } - - // expect retrievals to fail (except genesis block) without a les peer - client.handler.backend.peers.lock.Lock() - client.peer.speer.hasBlockHook = func(common.Hash, uint64, bool) bool { return false } - client.handler.backend.peers.lock.Unlock() - test(expFail) - - // expect all retrievals to pass - client.handler.backend.peers.lock.Lock() - client.peer.speer.hasBlockHook = func(common.Hash, uint64, bool) bool { return true } - client.handler.backend.peers.lock.Unlock() - test(5) - - // still expect all retrievals to pass, now data should be cached locally - if checkCached { - client.handler.backend.peers.unregister(client.peer.speer.id) - time.Sleep(time.Millisecond * 10) // ensure that all peerSetNotify callbacks are executed - test(5) - } -} - -func TestGetTxStatusFromUnindexedPeersLES4(t *testing.T) { testGetTxStatusFromUnindexedPeers(t, lpv4) } - -func testGetTxStatusFromUnindexedPeers(t *testing.T, protocol int) { - var ( - blocks = 8 - netconfig = testnetConfig{ - blocks: blocks, - protocol: protocol, - nopruning: true, - } - ) - server, client, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - // Iterate the chain, create the tx indexes locally - var ( - testHash common.Hash - testStatus light.TxStatus - - txs = make(map[common.Hash]*types.Transaction) // Transaction objects set - blockNumbers = make(map[common.Hash]uint64) // Transaction hash to block number mappings - blockHashes = make(map[common.Hash]common.Hash) // Transaction hash to block hash mappings - intraIndex = make(map[common.Hash]uint64) // Transaction intra-index in block - ) - for number := uint64(1); number < server.backend.Blockchain().CurrentBlock().Number.Uint64(); number++ { - block := server.backend.Blockchain().GetBlockByNumber(number) - if block == nil { - t.Fatalf("Failed to retrieve block %d", number) - } - for index, tx := range block.Transactions() { - txs[tx.Hash()] = tx - blockNumbers[tx.Hash()] = number - blockHashes[tx.Hash()] = block.Hash() - intraIndex[tx.Hash()] = uint64(index) - - if testHash == (common.Hash{}) { - testHash = tx.Hash() - testStatus = light.TxStatus{ - Status: txpool.TxStatusIncluded, - Lookup: &rawdb.LegacyTxLookupEntry{ - BlockHash: block.Hash(), - BlockIndex: block.NumberU64(), - Index: uint64(index), - }, - } - } - } - } - // serveMsg processes incoming GetTxStatusMsg and sends the response back. - serveMsg := func(peer *testPeer, txLookup uint64) error { - msg, err := peer.app.ReadMsg() - if err != nil { - return err - } - if msg.Code != GetTxStatusMsg { - return fmt.Errorf("message code mismatch: got %d, expected %d", msg.Code, GetTxStatusMsg) - } - var r GetTxStatusPacket - if err := msg.Decode(&r); err != nil { - return err - } - stats := make([]light.TxStatus, len(r.Hashes)) - for i, hash := range r.Hashes { - number, exist := blockNumbers[hash] - if !exist { - continue // Filter out unknown transactions - } - min := uint64(blocks) - txLookup - if txLookup != txIndexUnlimited && (txLookup == txIndexDisabled || number < min) { - continue // Filter out unindexed transactions - } - stats[i].Status = txpool.TxStatusIncluded - stats[i].Lookup = &rawdb.LegacyTxLookupEntry{ - BlockHash: blockHashes[hash], - BlockIndex: number, - Index: intraIndex[hash], - } - } - data, _ := rlp.EncodeToBytes(stats) - reply := &reply{peer.app, TxStatusMsg, r.ReqID, data} - reply.send(testBufLimit) - return nil - } - - var testspecs = []struct { - peers int - txLookups []uint64 - txs []common.Hash - results []light.TxStatus - }{ - // Retrieve mined transaction from the empty peerset - { - peers: 0, - txLookups: []uint64{}, - txs: []common.Hash{testHash}, - results: []light.TxStatus{{}}, - }, - // Retrieve unknown transaction from the full peers - { - peers: 3, - txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited}, - txs: []common.Hash{randomHash()}, - results: []light.TxStatus{{}}, - }, - // Retrieve mined transaction from the full peers - { - peers: 3, - txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited}, - txs: []common.Hash{testHash}, - results: []light.TxStatus{testStatus}, - }, - // Retrieve mixed transactions from the full peers - { - peers: 3, - txLookups: []uint64{txIndexUnlimited, txIndexUnlimited, txIndexUnlimited}, - txs: []common.Hash{randomHash(), testHash}, - results: []light.TxStatus{{}, testStatus}, - }, - // Retrieve mixed transactions from unindexed peer(but the target is still available) - { - peers: 3, - txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2}, - txs: []common.Hash{randomHash(), testHash}, - results: []light.TxStatus{{}, testStatus}, - }, - // Retrieve mixed transactions from unindexed peer(but the target is not available) - { - peers: 3, - txLookups: []uint64{uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 1, uint64(blocks) - testStatus.Lookup.BlockIndex - 2}, - txs: []common.Hash{randomHash(), testHash}, - results: []light.TxStatus{{}, {}}, - }, - } - for _, testspec := range testspecs { - // Create a bunch of server peers with different tx history - var ( - closeFns []func() - ) - for i := 0; i < testspec.peers; i++ { - peer, closePeer, _ := client.newRawPeer(t, fmt.Sprintf("server-%d", i), protocol, testspec.txLookups[i]) - closeFns = append(closeFns, closePeer) - - // Create a one-time routine for serving message - go func(i int, peer *testPeer, lookup uint64) { - serveMsg(peer, lookup) - }(i, peer, testspec.txLookups[i]) - } - - // Send out the GetTxStatus requests, compare the result with - // expected value. - r := &light.TxStatusRequest{Hashes: testspec.txs} - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - - err := client.handler.backend.odr.RetrieveTxStatus(ctx, r) - if err != nil { - t.Errorf("Failed to retrieve tx status %v", err) - } else { - if !reflect.DeepEqual(testspec.results, r.Status) { - t.Errorf("Result mismatch, diff") - } - } - - // Close all connected peers and start the next round - for _, closeFn := range closeFns { - closeFn() - } - } -} - -// randomHash generates a random blob of data and returns it as a hash. -func randomHash() common.Hash { - var hash common.Hash - if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil { - panic(err) - } - return hash -} -*/ diff --git a/les/peer.go b/les/peer.go deleted file mode 100644 index 48381689e..000000000 --- a/les/peer.go +++ /dev/null @@ -1,1361 +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 ( - "crypto/ecdsa" - "errors" - "fmt" - "math/big" - "math/rand" - "net" - "sync" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/les/flowcontrol" - "github.com/ethereum/go-ethereum/les/utils" - vfc "github.com/ethereum/go-ethereum/les/vflux/client" - vfs "github.com/ethereum/go-ethereum/les/vflux/server" - "github.com/ethereum/go-ethereum/light" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/rlp" -) - -var ( - errClosed = errors.New("peer set is closed") - errAlreadyRegistered = errors.New("peer is already registered") - errNotRegistered = errors.New("peer is not registered") -) - -const ( - maxRequestErrors = 20 // number of invalid requests tolerated (makes the protocol less brittle but still avoids spam) - maxResponseErrors = 50 // number of invalid responses tolerated (makes the protocol less brittle but still avoids spam) - - allowedUpdateBytes = 100000 // initial/maximum allowed update size - allowedUpdateRate = time.Millisecond * 10 // time constant for recharging one byte of allowance - - freezeTimeBase = time.Millisecond * 700 // fixed component of client freeze time - freezeTimeRandom = time.Millisecond * 600 // random component of client freeze time - freezeCheckPeriod = time.Millisecond * 100 // buffer value recheck period after initial freeze time has elapsed - - // If the total encoded size of a sent transaction batch is over txSizeCostLimit - // per transaction then the request cost is calculated as proportional to the - // encoded size instead of the transaction count - txSizeCostLimit = 0x4000 - - // handshakeTimeout is the timeout LES handshake will be treated as failed. - handshakeTimeout = 5 * time.Second -) - -const ( - announceTypeNone = iota - announceTypeSimple - announceTypeSigned -) - -type keyValueEntry struct { - Key string - Value rlp.RawValue -} - -type keyValueList []keyValueEntry -type keyValueMap map[string]rlp.RawValue - -func (l keyValueList) add(key string, val interface{}) keyValueList { - var entry keyValueEntry - entry.Key = key - if val == nil { - val = uint64(0) - } - enc, err := rlp.EncodeToBytes(val) - if err == nil { - entry.Value = enc - } - return append(l, entry) -} - -func (l keyValueList) decode() (keyValueMap, uint64) { - m := make(keyValueMap) - var size uint64 - for _, entry := range l { - m[entry.Key] = entry.Value - size += uint64(len(entry.Key)) + uint64(len(entry.Value)) + 8 - } - return m, size -} - -func (m keyValueMap) get(key string, val interface{}) error { - enc, ok := m[key] - if !ok { - return errResp(ErrMissingKey, "%s", key) - } - if val == nil { - return nil - } - return rlp.DecodeBytes(enc, val) -} - -// peerCommons contains fields needed by both server peer and client peer. -type peerCommons struct { - *p2p.Peer - rw p2p.MsgReadWriter - - id string // Peer identity. - version int // Protocol version negotiated. - network uint64 // Network ID being on. - frozen atomic.Bool // Flag whether the peer is frozen. - announceType uint64 // New block announcement type. - serving atomic.Bool // The status indicates the peer is served. - headInfo blockInfo // Last announced block information. - - // Background task queue for caching peer tasks and executing in order. - sendQueue *utils.ExecQueue - - // Flow control agreement. - fcParams flowcontrol.ServerParams // The config for token bucket. - fcCosts requestCostTable // The Maximum request cost table. - - closeCh chan struct{} - lock sync.RWMutex // Lock used to protect all thread-sensitive fields. -} - -// isFrozen returns true if the client is frozen or the server has put our -// client in frozen state -func (p *peerCommons) isFrozen() bool { - return p.frozen.Load() -} - -// canQueue returns an indicator whether the peer can queue an operation. -func (p *peerCommons) canQueue() bool { - return p.sendQueue.CanQueue() && !p.isFrozen() -} - -// queueSend caches a peer operation in the background task queue. -// Please ensure to check `canQueue` before call this function -func (p *peerCommons) queueSend(f func()) bool { - return p.sendQueue.Queue(f) -} - -// String implements fmt.Stringer. -func (p *peerCommons) String() string { - return fmt.Sprintf("Peer %s [%s]", p.id, fmt.Sprintf("les/%d", p.version)) -} - -// PeerInfo represents a short summary of the `eth` sub-protocol metadata known -// about a connected peer. -type PeerInfo struct { - Version int `json:"version"` // Ethereum protocol version negotiated - Difficulty *big.Int `json:"difficulty"` // Total difficulty of the peer's blockchain - Head string `json:"head"` // SHA3 hash of the peer's best owned block -} - -// Info gathers and returns a collection of metadata known about a peer. -func (p *peerCommons) Info() *PeerInfo { - return &PeerInfo{ - Version: p.version, - Difficulty: p.Td(), - Head: fmt.Sprintf("%x", p.Head()), - } -} - -// Head retrieves a copy of the current head (most recent) hash of the peer. -func (p *peerCommons) Head() (hash common.Hash) { - p.lock.RLock() - defer p.lock.RUnlock() - - return p.headInfo.Hash -} - -// Td retrieves the current total difficulty of a peer. -func (p *peerCommons) Td() *big.Int { - p.lock.RLock() - defer p.lock.RUnlock() - - return new(big.Int).Set(p.headInfo.Td) -} - -// HeadAndTd retrieves the current head hash and total difficulty of a peer. -func (p *peerCommons) HeadAndTd() (hash common.Hash, td *big.Int) { - p.lock.RLock() - defer p.lock.RUnlock() - - return p.headInfo.Hash, new(big.Int).Set(p.headInfo.Td) -} - -// sendReceiveHandshake exchanges handshake packet with remote peer and returns any error -// if failed to send or receive packet. -func (p *peerCommons) sendReceiveHandshake(sendList keyValueList) (keyValueList, error) { - var ( - errc = make(chan error, 2) - recvList keyValueList - ) - // Send out own handshake in a new thread - go func() { - errc <- p2p.Send(p.rw, StatusMsg, &sendList) - }() - go func() { - // In the mean time retrieve the remote status message - msg, err := p.rw.ReadMsg() - if err != nil { - errc <- err - return - } - if msg.Code != StatusMsg { - errc <- errResp(ErrNoStatusMsg, "first msg has code %x (!= %x)", msg.Code, StatusMsg) - return - } - if msg.Size > ProtocolMaxMsgSize { - errc <- errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) - return - } - // Decode the handshake - if err := msg.Decode(&recvList); err != nil { - errc <- errResp(ErrDecode, "msg %v: %v", msg, err) - return - } - errc <- nil - }() - timeout := time.NewTimer(handshakeTimeout) - defer timeout.Stop() - for i := 0; i < 2; i++ { - select { - case err := <-errc: - if err != nil { - return nil, err - } - case <-timeout.C: - return nil, p2p.DiscReadTimeout - } - } - return recvList, nil -} - -// handshake executes the les protocol handshake, negotiating version number, -// network IDs, difficulties, head and genesis blocks. Besides the basic handshake -// fields, server and client can exchange and resolve some specified fields through -// two callback functions. -func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, sendCallback func(*keyValueList), recvCallback func(keyValueMap) error) error { - p.lock.Lock() - defer p.lock.Unlock() - - var send keyValueList - - // Add some basic handshake fields - send = send.add("protocolVersion", uint64(p.version)) - send = send.add("networkId", p.network) - // Note: the head info announced at handshake is only used in case of server peers - // but dummy values are still announced by clients for compatibility with older servers - send = send.add("headTd", td) - send = send.add("headHash", head) - send = send.add("headNum", headNum) - send = send.add("genesisHash", genesis) - - // If the protocol version is beyond les4, then pass the forkID - // as well. Check http://eips.ethereum.org/EIPS/eip-2124 for more - // spec detail. - if p.version >= lpv4 { - send = send.add("forkID", forkID) - } - // Add client-specified or server-specified fields - if sendCallback != nil { - sendCallback(&send) - } - // Exchange the handshake packet and resolve the received one. - recvList, err := p.sendReceiveHandshake(send) - if err != nil { - return err - } - recv, size := recvList.decode() - if size > allowedUpdateBytes { - return errResp(ErrRequestRejected, "") - } - var rGenesis common.Hash - var rVersion, rNetwork uint64 - if err := recv.get("protocolVersion", &rVersion); err != nil { - return err - } - if err := recv.get("networkId", &rNetwork); err != nil { - return err - } - if err := recv.get("genesisHash", &rGenesis); err != nil { - return err - } - if rGenesis != genesis { - return errResp(ErrGenesisBlockMismatch, "%x (!= %x)", rGenesis[:8], genesis[:8]) - } - if rNetwork != p.network { - return errResp(ErrNetworkIdMismatch, "%d (!= %d)", rNetwork, p.network) - } - if int(rVersion) != p.version { - return errResp(ErrProtocolVersionMismatch, "%d (!= %d)", rVersion, p.version) - } - // Check forkID if the protocol version is beyond the les4 - if p.version >= lpv4 { - var forkID forkid.ID - if err := recv.get("forkID", &forkID); err != nil { - return err - } - if err := forkFilter(forkID); err != nil { - return errResp(ErrForkIDRejected, "%v", err) - } - } - if recvCallback != nil { - return recvCallback(recv) - } - return nil -} - -// close closes the channel and notifies all background routines to exit. -func (p *peerCommons) close() { - close(p.closeCh) - p.sendQueue.Quit() -} - -// serverPeer represents each node to which the client is connected. -// The node here refers to the les server. -type serverPeer struct { - peerCommons - - // Status fields - trusted bool // The flag whether the server is selected as trusted server. - onlyAnnounce bool // The flag whether the server sends announcement only. - chainSince, chainRecent uint64 // The range of chain server peer can serve. - stateSince, stateRecent uint64 // The range of state server peer can serve. - txHistory uint64 // The length of available tx history, 0 means all, 1 means disabled - - fcServer *flowcontrol.ServerNode // Client side mirror token bucket. - vtLock sync.Mutex - nodeValueTracker *vfc.NodeValueTracker - sentReqs map[uint64]sentReqEntry - - // Statistics - errCount utils.LinearExpiredValue // Counter the invalid responses server has replied - updateCount uint64 - updateTime mclock.AbsTime - - // Test callback hooks - hasBlockHook func(common.Hash, uint64, bool) bool // Used to determine whether the server has the specified block. -} - -func newServerPeer(version int, network uint64, trusted bool, p *p2p.Peer, rw p2p.MsgReadWriter) *serverPeer { - return &serverPeer{ - peerCommons: peerCommons{ - Peer: p, - rw: rw, - id: p.ID().String(), - version: version, - network: network, - sendQueue: utils.NewExecQueue(100), - closeCh: make(chan struct{}), - }, - trusted: trusted, - errCount: utils.LinearExpiredValue{Rate: mclock.AbsTime(time.Hour)}, - } -} - -// rejectUpdate returns true if a parameter update has to be rejected because -// the size and/or rate of updates exceed the capacity limitation -func (p *serverPeer) rejectUpdate(size uint64) bool { - now := mclock.Now() - if p.updateCount == 0 { - p.updateTime = now - } else { - dt := now - p.updateTime - p.updateTime = now - - r := uint64(dt / mclock.AbsTime(allowedUpdateRate)) - if p.updateCount > r { - p.updateCount -= r - } else { - p.updateCount = 0 - } - } - p.updateCount += size - return p.updateCount > allowedUpdateBytes -} - -// freeze processes Stop messages from the given server and set the status as -// frozen. -func (p *serverPeer) freeze() { - if p.frozen.CompareAndSwap(false, true) { - p.sendQueue.Clear() - } -} - -// unfreeze processes Resume messages from the given server and set the status -// as unfrozen. -func (p *serverPeer) unfreeze() { - p.frozen.Store(false) -} - -// sendRequest send a request to the server based on the given message type -// and content. -func sendRequest(w p2p.MsgWriter, msgcode, reqID uint64, data interface{}) error { - type req struct { - ReqID uint64 - Data interface{} - } - return p2p.Send(w, msgcode, &req{reqID, data}) -} - -func (p *serverPeer) sendRequest(msgcode, reqID uint64, data interface{}, amount int) error { - p.sentRequest(reqID, uint32(msgcode), uint32(amount)) - return sendRequest(p.rw, msgcode, reqID, data) -} - -// requestHeadersByHash fetches a batch of blocks' headers corresponding to the -// specified header query, based on the hash of an origin block. -func (p *serverPeer) requestHeadersByHash(reqID uint64, origin common.Hash, amount int, skip int, reverse bool) error { - p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) - return p.sendRequest(GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount) -} - -// requestHeadersByNumber fetches a batch of blocks' headers corresponding to the -// specified header query, based on the number of an origin block. -func (p *serverPeer) requestHeadersByNumber(reqID, origin uint64, amount int, skip int, reverse bool) error { - p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) - return p.sendRequest(GetBlockHeadersMsg, reqID, &GetBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount) -} - -// requestBodies fetches a batch of blocks' bodies corresponding to the hashes -// specified. -func (p *serverPeer) requestBodies(reqID uint64, hashes []common.Hash) error { - p.Log().Debug("Fetching batch of block bodies", "count", len(hashes)) - return p.sendRequest(GetBlockBodiesMsg, reqID, hashes, len(hashes)) -} - -// requestCode fetches a batch of arbitrary data from a node's known state -// data, corresponding to the specified hashes. -func (p *serverPeer) requestCode(reqID uint64, reqs []CodeReq) error { - p.Log().Debug("Fetching batch of codes", "count", len(reqs)) - return p.sendRequest(GetCodeMsg, reqID, reqs, len(reqs)) -} - -// requestReceipts fetches a batch of transaction receipts from a remote node. -func (p *serverPeer) requestReceipts(reqID uint64, hashes []common.Hash) error { - p.Log().Debug("Fetching batch of receipts", "count", len(hashes)) - return p.sendRequest(GetReceiptsMsg, reqID, hashes, len(hashes)) -} - -// requestProofs fetches a batch of merkle proofs from a remote node. -func (p *serverPeer) requestProofs(reqID uint64, reqs []ProofReq) error { - p.Log().Debug("Fetching batch of proofs", "count", len(reqs)) - return p.sendRequest(GetProofsV2Msg, reqID, reqs, len(reqs)) -} - -// requestHelperTrieProofs fetches a batch of HelperTrie merkle proofs from a remote node. -func (p *serverPeer) requestHelperTrieProofs(reqID uint64, reqs []HelperTrieReq) error { - p.Log().Debug("Fetching batch of HelperTrie proofs", "count", len(reqs)) - return p.sendRequest(GetHelperTrieProofsMsg, reqID, reqs, len(reqs)) -} - -// requestTxStatus fetches a batch of transaction status records from a remote node. -func (p *serverPeer) requestTxStatus(reqID uint64, txHashes []common.Hash) error { - p.Log().Debug("Requesting transaction status", "count", len(txHashes)) - return p.sendRequest(GetTxStatusMsg, reqID, txHashes, len(txHashes)) -} - -// sendTxs creates a reply with a batch of transactions to be added to the remote transaction pool. -func (p *serverPeer) sendTxs(reqID uint64, amount int, txs rlp.RawValue) error { - p.Log().Debug("Sending batch of transactions", "amount", amount, "size", len(txs)) - sizeFactor := (len(txs) + txSizeCostLimit/2) / txSizeCostLimit - if sizeFactor > amount { - amount = sizeFactor - } - return p.sendRequest(SendTxV2Msg, reqID, txs, amount) -} - -// waitBefore implements distPeer interface -func (p *serverPeer) waitBefore(maxCost uint64) (time.Duration, float64) { - return p.fcServer.CanSend(maxCost) -} - -// getRequestCost returns an estimated request cost according to the flow control -// rules negotiated between the server and the client. -func (p *serverPeer) getRequestCost(msgcode uint64, amount int) uint64 { - p.lock.RLock() - defer p.lock.RUnlock() - - costs := p.fcCosts[msgcode] - if costs == nil { - return 0 - } - cost := costs.baseCost + costs.reqCost*uint64(amount) - if cost > p.fcParams.BufLimit { - cost = p.fcParams.BufLimit - } - return cost -} - -// getTxRelayCost returns an estimated relay cost according to the flow control -// rules negotiated between the server and the client. -func (p *serverPeer) getTxRelayCost(amount, size int) uint64 { - p.lock.RLock() - defer p.lock.RUnlock() - - costs := p.fcCosts[SendTxV2Msg] - if costs == nil { - return 0 - } - cost := costs.baseCost + costs.reqCost*uint64(amount) - sizeCost := costs.baseCost + costs.reqCost*uint64(size)/txSizeCostLimit - if sizeCost > cost { - cost = sizeCost - } - if cost > p.fcParams.BufLimit { - cost = p.fcParams.BufLimit - } - return cost -} - -// HasBlock checks if the peer has a given block -func (p *serverPeer) HasBlock(hash common.Hash, number uint64, hasState bool) bool { - p.lock.RLock() - defer p.lock.RUnlock() - - if p.hasBlockHook != nil { - return p.hasBlockHook(hash, number, hasState) - } - head := p.headInfo.Number - var since, recent uint64 - if hasState { - since = p.stateSince - recent = p.stateRecent - } else { - since = p.chainSince - recent = p.chainRecent - } - return head >= number && number >= since && (recent == 0 || number+recent+4 > head) -} - -// updateFlowControl updates the flow control parameters belonging to the server -// node if the announced key/value set contains relevant fields -func (p *serverPeer) updateFlowControl(update keyValueMap) { - p.lock.Lock() - defer p.lock.Unlock() - - // If any of the flow control params is nil, refuse to update. - var params flowcontrol.ServerParams - if update.get("flowControl/BL", ¶ms.BufLimit) == nil && update.get("flowControl/MRR", ¶ms.MinRecharge) == nil { - // todo can light client set a minimal acceptable flow control params? - p.fcParams = params - p.fcServer.UpdateParams(params) - } - var MRC RequestCostList - if update.get("flowControl/MRC", &MRC) == nil { - costUpdate := MRC.decode(ProtocolLengths[uint(p.version)]) - for code, cost := range costUpdate { - p.fcCosts[code] = cost - } - } -} - -// updateHead updates the head information based on the announcement from -// the peer. -func (p *serverPeer) updateHead(hash common.Hash, number uint64, td *big.Int) { - p.lock.Lock() - defer p.lock.Unlock() - - p.headInfo = blockInfo{Hash: hash, Number: number, Td: td} -} - -// Handshake executes the les protocol handshake, negotiating version number, -// network IDs and genesis blocks. -func (p *serverPeer) Handshake(genesis common.Hash, forkid forkid.ID, forkFilter forkid.Filter) error { - // Note: there is no need to share local head with a server but older servers still - // require these fields so we announce zero values. - return p.handshake(common.Big0, common.Hash{}, 0, genesis, forkid, forkFilter, func(lists *keyValueList) { - // Add some client-specific handshake fields - // - // Enable signed announcement randomly even the server is not trusted. - p.announceType = announceTypeSimple - if p.trusted { - p.announceType = announceTypeSigned - } - *lists = (*lists).add("announceType", p.announceType) - }, func(recv keyValueMap) error { - var ( - rHash common.Hash - rNum uint64 - rTd *big.Int - ) - if err := recv.get("headTd", &rTd); err != nil { - return err - } - if err := recv.get("headHash", &rHash); err != nil { - return err - } - if err := recv.get("headNum", &rNum); err != nil { - return err - } - p.headInfo = blockInfo{Hash: rHash, Number: rNum, Td: rTd} - if recv.get("serveChainSince", &p.chainSince) != nil { - p.onlyAnnounce = true - } - if recv.get("serveRecentChain", &p.chainRecent) != nil { - p.chainRecent = 0 - } - if recv.get("serveStateSince", &p.stateSince) != nil { - p.onlyAnnounce = true - } - if recv.get("serveRecentState", &p.stateRecent) != nil { - p.stateRecent = 0 - } - if recv.get("txRelay", nil) != nil { - p.onlyAnnounce = true - } - if p.version >= lpv4 { - var recentTx uint - if err := recv.get("recentTxLookup", &recentTx); err != nil { - return err - } - p.txHistory = uint64(recentTx) - } else { - // The weak assumption is held here that legacy les server(les2,3) - // has unlimited transaction history. The les serving in these legacy - // versions is disabled if the transaction is unindexed. - p.txHistory = txIndexUnlimited - } - if p.onlyAnnounce && !p.trusted { - return errResp(ErrUselessPeer, "peer cannot serve requests") - } - // Parse flow control handshake packet. - var sParams flowcontrol.ServerParams - if err := recv.get("flowControl/BL", &sParams.BufLimit); err != nil { - return err - } - if err := recv.get("flowControl/MRR", &sParams.MinRecharge); err != nil { - return err - } - var MRC RequestCostList - if err := recv.get("flowControl/MRC", &MRC); err != nil { - return err - } - p.fcParams = sParams - p.fcServer = flowcontrol.NewServerNode(sParams, &mclock.System{}) - p.fcCosts = MRC.decode(ProtocolLengths[uint(p.version)]) - - if !p.onlyAnnounce { - for msgCode := range reqAvgTimeCost { - if p.fcCosts[msgCode] == nil { - return errResp(ErrUselessPeer, "peer does not support message %d", msgCode) - } - } - } - return nil - }) -} - -// setValueTracker sets the value tracker references for connected servers. Note that the -// references should be removed upon disconnection by setValueTracker(nil, nil). -func (p *serverPeer) setValueTracker(nvt *vfc.NodeValueTracker) { - p.vtLock.Lock() - p.nodeValueTracker = nvt - if nvt != nil { - p.sentReqs = make(map[uint64]sentReqEntry) - } else { - p.sentReqs = nil - } - p.vtLock.Unlock() -} - -// updateVtParams updates the server's price table in the value tracker. -func (p *serverPeer) updateVtParams() { - p.vtLock.Lock() - defer p.vtLock.Unlock() - - if p.nodeValueTracker == nil { - return - } - reqCosts := make([]uint64, len(requestList)) - for code, costs := range p.fcCosts { - if m, ok := requestMapping[uint32(code)]; ok { - reqCosts[m.first] = costs.baseCost + costs.reqCost - if m.rest != -1 { - reqCosts[m.rest] = costs.reqCost - } - } - } - p.nodeValueTracker.UpdateCosts(reqCosts) -} - -// sentReqEntry remembers sent requests and their sending times -type sentReqEntry struct { - reqType, amount uint32 - at mclock.AbsTime -} - -// sentRequest marks a request sent at the current moment to this server. -func (p *serverPeer) sentRequest(id uint64, reqType, amount uint32) { - p.vtLock.Lock() - if p.sentReqs != nil { - p.sentReqs[id] = sentReqEntry{reqType, amount, mclock.Now()} - } - p.vtLock.Unlock() -} - -// answeredRequest marks a request answered at the current moment by this server. -func (p *serverPeer) answeredRequest(id uint64) { - p.vtLock.Lock() - if p.sentReqs == nil { - p.vtLock.Unlock() - return - } - e, ok := p.sentReqs[id] - delete(p.sentReqs, id) - nvt := p.nodeValueTracker - p.vtLock.Unlock() - if !ok { - return - } - var ( - vtReqs [2]vfc.ServedRequest - reqCount int - ) - m := requestMapping[e.reqType] - if m.rest == -1 || e.amount <= 1 { - reqCount = 1 - vtReqs[0] = vfc.ServedRequest{ReqType: uint32(m.first), Amount: e.amount} - } else { - reqCount = 2 - vtReqs[0] = vfc.ServedRequest{ReqType: uint32(m.first), Amount: 1} - vtReqs[1] = vfc.ServedRequest{ReqType: uint32(m.rest), Amount: e.amount - 1} - } - dt := time.Duration(mclock.Now() - e.at) - nvt.Served(vtReqs[:reqCount], dt) -} - -// clientPeer represents each node to which the les server is connected. -// The node here refers to the light client. -type clientPeer struct { - peerCommons - - // responseLock ensures that responses are queued in the same order as - // RequestProcessed is called - responseLock sync.Mutex - responseCount uint64 // Counter to generate an unique id for request processing. - - balance vfs.ConnectedBalance - - // invalidLock is used for protecting invalidCount. - invalidLock sync.RWMutex - invalidCount utils.LinearExpiredValue // Counter the invalid request the client peer has made. - - capacity uint64 - // lastAnnounce is the last broadcast created by the server; may be newer than the last head - // sent to the specific client (stored in headInfo) if capacity is zero. In this case the - // latest head is sent when the client gains non-zero capacity. - lastAnnounce announceData - - connectedAt mclock.AbsTime - server bool - errCh chan error - fcClient *flowcontrol.ClientNode // Server side mirror token bucket. -} - -func newClientPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *clientPeer { - return &clientPeer{ - peerCommons: peerCommons{ - Peer: p, - rw: rw, - id: p.ID().String(), - version: version, - network: network, - sendQueue: utils.NewExecQueue(100), - closeCh: make(chan struct{}), - }, - invalidCount: utils.LinearExpiredValue{Rate: mclock.AbsTime(time.Hour)}, - errCh: make(chan error, 1), - } -} - -// FreeClientId returns a string identifier for the peer. Multiple peers with -// the same identifier can not be connected in free mode simultaneously. -func (p *clientPeer) FreeClientId() string { - if addr, ok := p.RemoteAddr().(*net.TCPAddr); ok { - if addr.IP.IsLoopback() { - // using peer id instead of loopback ip address allows multiple free - // connections from local machine to own server - return p.id - } else { - return addr.IP.String() - } - } - return p.id -} - -// sendStop notifies the client about being in frozen state -func (p *clientPeer) sendStop() error { - return p2p.Send(p.rw, StopMsg, struct{}{}) -} - -// sendResume notifies the client about getting out of frozen state -func (p *clientPeer) sendResume(bv uint64) error { - return p2p.Send(p.rw, ResumeMsg, bv) -} - -// freeze temporarily puts the client in a frozen state which means all unprocessed -// and subsequent requests are dropped. Unfreezing happens automatically after a short -// time if the client's buffer value is at least in the slightly positive region. -// The client is also notified about being frozen/unfrozen with a Stop/Resume message. -func (p *clientPeer) freeze() { - if p.version < lpv3 { - // if Stop/Resume is not supported then just drop the peer after setting - // its frozen status permanently - p.frozen.Store(true) - p.Peer.Disconnect(p2p.DiscUselessPeer) - return - } - if !p.frozen.Swap(true) { - go func() { - p.sendStop() - time.Sleep(freezeTimeBase + time.Duration(rand.Int63n(int64(freezeTimeRandom)))) - for { - bufValue, bufLimit := p.fcClient.BufferStatus() - if bufLimit == 0 { - return - } - if bufValue <= bufLimit/8 { - time.Sleep(freezeCheckPeriod) - continue - } - p.frozen.Store(false) - p.sendResume(bufValue) - return - } - }() - } -} - -// reply struct represents a reply with the actual data already RLP encoded and -// only the bv (buffer value) missing. This allows the serving mechanism to -// calculate the bv value which depends on the data size before sending the reply. -type reply struct { - w p2p.MsgWriter - msgcode, reqID uint64 - data rlp.RawValue -} - -// send sends the reply with the calculated buffer value -func (r *reply) send(bv uint64) error { - type resp struct { - ReqID, BV uint64 - Data rlp.RawValue - } - return p2p.Send(r.w, r.msgcode, &resp{r.reqID, bv, r.data}) -} - -// size returns the RLP encoded size of the message data -func (r *reply) size() uint32 { - return uint32(len(r.data)) -} - -// replyBlockHeaders creates a reply with a batch of block headers -func (p *clientPeer) replyBlockHeaders(reqID uint64, headers []*types.Header) *reply { - data, _ := rlp.EncodeToBytes(headers) - return &reply{p.rw, BlockHeadersMsg, reqID, data} -} - -// replyBlockBodiesRLP creates a reply with a batch of block contents from -// an already RLP encoded format. -func (p *clientPeer) replyBlockBodiesRLP(reqID uint64, bodies []rlp.RawValue) *reply { - data, _ := rlp.EncodeToBytes(bodies) - return &reply{p.rw, BlockBodiesMsg, reqID, data} -} - -// replyCode creates a reply with a batch of arbitrary internal data, corresponding to the -// hashes requested. -func (p *clientPeer) replyCode(reqID uint64, codes [][]byte) *reply { - data, _ := rlp.EncodeToBytes(codes) - return &reply{p.rw, CodeMsg, reqID, data} -} - -// replyReceiptsRLP creates a reply with a batch of transaction receipts, corresponding to the -// ones requested from an already RLP encoded format. -func (p *clientPeer) replyReceiptsRLP(reqID uint64, receipts []rlp.RawValue) *reply { - data, _ := rlp.EncodeToBytes(receipts) - return &reply{p.rw, ReceiptsMsg, reqID, data} -} - -// 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 { - data, _ := rlp.EncodeToBytes(proofs) - return &reply{p.rw, ProofsV2Msg, reqID, data} -} - -// replyHelperTrieProofs creates a reply with a batch of HelperTrie proofs, corresponding to the ones requested. -func (p *clientPeer) replyHelperTrieProofs(reqID uint64, resp HelperTrieResps) *reply { - data, _ := rlp.EncodeToBytes(resp) - return &reply{p.rw, HelperTrieProofsMsg, reqID, data} -} - -// replyTxStatus creates a reply with a batch of transaction status records, corresponding to the ones requested. -func (p *clientPeer) replyTxStatus(reqID uint64, stats []light.TxStatus) *reply { - data, _ := rlp.EncodeToBytes(stats) - return &reply{p.rw, TxStatusMsg, reqID, data} -} - -// sendAnnounce announces the availability of a number of blocks through -// a hash notification. -func (p *clientPeer) sendAnnounce(request announceData) error { - return p2p.Send(p.rw, AnnounceMsg, request) -} - -// InactiveAllowance implements vfs.clientPeer -func (p *clientPeer) InactiveAllowance() time.Duration { - return 0 // will return more than zero for les/5 clients -} - -// getCapacity returns the current capacity of the peer -func (p *clientPeer) getCapacity() uint64 { - p.lock.RLock() - defer p.lock.RUnlock() - - return p.capacity -} - -// UpdateCapacity updates the request serving capacity assigned to a given client -// and also sends an announcement about the updated flow control parameters. -// Note: UpdateCapacity implements vfs.clientPeer and should not block. The requested -// parameter is true if the callback was initiated by ClientPool.SetCapacity on the given peer. -func (p *clientPeer) UpdateCapacity(newCap uint64, requested bool) { - p.lock.Lock() - defer p.lock.Unlock() - - if newCap != p.fcParams.MinRecharge { - p.fcParams = flowcontrol.ServerParams{MinRecharge: newCap, BufLimit: newCap * bufLimitRatio} - p.fcClient.UpdateParams(p.fcParams) - var kvList keyValueList - kvList = kvList.add("flowControl/MRR", newCap) - kvList = kvList.add("flowControl/BL", newCap*bufLimitRatio) - p.queueSend(func() { p.sendAnnounce(announceData{Update: kvList}) }) - } - - if p.capacity == 0 && newCap != 0 { - p.sendLastAnnounce() - } - p.capacity = newCap -} - -// announceOrStore sends the given head announcement to the client if the client is -// active (capacity != 0) and the same announcement hasn't been sent before. If the -// client is inactive the announcement is stored and sent later if the client is -// activated again. -func (p *clientPeer) announceOrStore(announce announceData) { - p.lock.Lock() - defer p.lock.Unlock() - - p.lastAnnounce = announce - if p.capacity != 0 { - p.sendLastAnnounce() - } -} - -// announce sends the given head announcement to the client if it hasn't been sent before -func (p *clientPeer) sendLastAnnounce() { - if p.lastAnnounce.Td == nil { - return - } - if p.headInfo.Td == nil || p.lastAnnounce.Td.Cmp(p.headInfo.Td) > 0 { - if !p.queueSend(func() { p.sendAnnounce(p.lastAnnounce) }) { - p.Log().Debug("Dropped announcement because queue is full", "number", p.lastAnnounce.Number, "hash", p.lastAnnounce.Hash) - } else { - p.Log().Debug("Sent announcement", "number", p.lastAnnounce.Number, "hash", p.lastAnnounce.Hash) - } - p.headInfo = blockInfo{Hash: p.lastAnnounce.Hash, Number: p.lastAnnounce.Number, Td: p.lastAnnounce.Td} - } -} - -// Handshake executes the les protocol handshake, negotiating version number, -// network IDs, difficulties, head and genesis blocks. -func (p *clientPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID, forkFilter forkid.Filter, server *LesServer) error { - recentTx := server.handler.blockchain.TxLookupLimit() - if recentTx != txIndexUnlimited { - if recentTx < blockSafetyMargin { - recentTx = txIndexDisabled - } else { - recentTx -= blockSafetyMargin - txIndexRecentOffset - } - } - if recentTx != txIndexUnlimited && p.version < lpv4 { - return errors.New("Cannot serve old clients without a complete tx index") - } - // Note: clientPeer.headInfo should contain the last head announced to the client by us. - // The values announced in the handshake are dummy values for compatibility reasons and should be ignored. - 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. - *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.TriesInMemory - 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) - } - *lists = (*lists).add("flowControl/BL", server.defParams.BufLimit) - *lists = (*lists).add("flowControl/MRR", server.defParams.MinRecharge) - - var costList RequestCostList - if server.costTracker.testCostList != nil { - costList = server.costTracker.testCostList - } else { - costList = server.costTracker.makeCostList(server.costTracker.globalFactor()) - } - *lists = (*lists).add("flowControl/MRC", costList) - p.fcCosts = costList.decode(ProtocolLengths[uint(p.version)]) - p.fcParams = server.defParams - }, func(recv keyValueMap) error { - p.server = recv.get("flowControl/MRR", nil) == nil - if p.server { - p.announceType = announceTypeNone // connected to another server, send no messages - } else { - if recv.get("announceType", &p.announceType) != nil { - // set default announceType on server side - p.announceType = announceTypeSimple - } - } - return nil - }) -} - -func (p *clientPeer) bumpInvalid() { - p.invalidLock.Lock() - p.invalidCount.Add(1, mclock.Now()) - p.invalidLock.Unlock() -} - -func (p *clientPeer) getInvalid() uint64 { - p.invalidLock.RLock() - defer p.invalidLock.RUnlock() - return p.invalidCount.Value(mclock.Now()) -} - -// Disconnect implements vfs.clientPeer -func (p *clientPeer) Disconnect() { - p.Peer.Disconnect(p2p.DiscRequested) -} - -// serverPeerSubscriber is an interface to notify services about added or -// removed server peers -type serverPeerSubscriber interface { - registerPeer(*serverPeer) - unregisterPeer(*serverPeer) -} - -// serverPeerSet represents the set of active server peers currently -// participating in the Light Ethereum sub-protocol. -type serverPeerSet struct { - peers map[string]*serverPeer - // subscribers is a batch of subscribers and peerset will notify - // these subscribers when the peerset changes(new server peer is - // added or removed) - subscribers []serverPeerSubscriber - closed bool - lock sync.RWMutex -} - -// newServerPeerSet creates a new peer set to track the active server peers. -func newServerPeerSet() *serverPeerSet { - return &serverPeerSet{peers: make(map[string]*serverPeer)} -} - -// subscribe adds a service to be notified about added or removed -// peers and also register all active peers into the given service. -func (ps *serverPeerSet) subscribe(sub serverPeerSubscriber) { - ps.lock.Lock() - defer ps.lock.Unlock() - - ps.subscribers = append(ps.subscribers, sub) - for _, p := range ps.peers { - sub.registerPeer(p) - } -} - -// register adds a new server peer into the set, or returns an error if the -// peer is already known. -func (ps *serverPeerSet) register(peer *serverPeer) error { - ps.lock.Lock() - defer ps.lock.Unlock() - - if ps.closed { - return errClosed - } - if _, exist := ps.peers[peer.id]; exist { - return errAlreadyRegistered - } - ps.peers[peer.id] = peer - for _, sub := range ps.subscribers { - sub.registerPeer(peer) - } - return nil -} - -// unregister removes a remote peer from the active set, disabling any further -// actions to/from that particular entity. It also initiates disconnection at -// the networking layer. -func (ps *serverPeerSet) unregister(id string) error { - ps.lock.Lock() - defer ps.lock.Unlock() - - p, ok := ps.peers[id] - if !ok { - return errNotRegistered - } - delete(ps.peers, id) - for _, sub := range ps.subscribers { - sub.unregisterPeer(p) - } - p.Peer.Disconnect(p2p.DiscRequested) - return nil -} - -// ids returns a list of all registered peer IDs -func (ps *serverPeerSet) ids() []string { - ps.lock.RLock() - defer ps.lock.RUnlock() - - var ids []string - for id := range ps.peers { - ids = append(ids, id) - } - return ids -} - -// peer retrieves the registered peer with the given id. -func (ps *serverPeerSet) peer(id string) *serverPeer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return ps.peers[id] -} - -// len returns if the current number of peers in the set. -func (ps *serverPeerSet) len() int { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return len(ps.peers) -} - -// allServerPeers returns all server peers in a list. -func (ps *serverPeerSet) allPeers() []*serverPeer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - list := make([]*serverPeer, 0, len(ps.peers)) - for _, p := range ps.peers { - list = append(list, p) - } - return list -} - -// close disconnects all peers. No new peers can be registered -// after close has returned. -func (ps *serverPeerSet) close() { - ps.lock.Lock() - defer ps.lock.Unlock() - - for _, p := range ps.peers { - p.Disconnect(p2p.DiscQuitting) - } - ps.closed = true -} - -// clientPeerSet represents the set of active client peers currently -// participating in the Light Ethereum sub-protocol. -type clientPeerSet struct { - peers map[enode.ID]*clientPeer - lock sync.RWMutex - closed bool - - privateKey *ecdsa.PrivateKey - lastAnnounce, signedAnnounce announceData -} - -// newClientPeerSet creates a new peer set to track the client peers. -func newClientPeerSet() *clientPeerSet { - return &clientPeerSet{peers: make(map[enode.ID]*clientPeer)} -} - -// register adds a new peer into the peer set, or returns an error if the -// peer is already known. -func (ps *clientPeerSet) register(peer *clientPeer) error { - ps.lock.Lock() - defer ps.lock.Unlock() - - if ps.closed { - return errClosed - } - if _, exist := ps.peers[peer.ID()]; exist { - return errAlreadyRegistered - } - ps.peers[peer.ID()] = peer - ps.announceOrStore(peer) - return nil -} - -// unregister removes a remote peer from the peer set, disabling any further -// actions to/from that particular entity. It also initiates disconnection -// at the networking layer. -func (ps *clientPeerSet) unregister(id enode.ID) error { - ps.lock.Lock() - defer ps.lock.Unlock() - - p, ok := ps.peers[id] - if !ok { - return errNotRegistered - } - delete(ps.peers, id) - p.Peer.Disconnect(p2p.DiscRequested) - return nil -} - -// ids returns a list of all registered peer IDs -func (ps *clientPeerSet) ids() []enode.ID { - ps.lock.RLock() - defer ps.lock.RUnlock() - - var ids []enode.ID - for id := range ps.peers { - ids = append(ids, id) - } - return ids -} - -// peer retrieves the registered peer with the given id. -func (ps *clientPeerSet) peer(id enode.ID) *clientPeer { - ps.lock.RLock() - defer ps.lock.RUnlock() - - return ps.peers[id] -} - -// setSignerKey sets the signer key for signed announcements. Should be called before -// starting the protocol handler. -func (ps *clientPeerSet) setSignerKey(privateKey *ecdsa.PrivateKey) { - ps.privateKey = privateKey -} - -// broadcast sends the given announcements to all active peers -func (ps *clientPeerSet) broadcast(announce announceData) { - ps.lock.Lock() - defer ps.lock.Unlock() - - ps.lastAnnounce = announce - for _, peer := range ps.peers { - ps.announceOrStore(peer) - } -} - -// announceOrStore sends the requested type of announcement to the given peer or stores -// it for later if the peer is inactive (capacity == 0). -func (ps *clientPeerSet) announceOrStore(p *clientPeer) { - if ps.lastAnnounce.Td == nil { - return - } - switch p.announceType { - case announceTypeSimple: - p.announceOrStore(ps.lastAnnounce) - case announceTypeSigned: - if ps.signedAnnounce.Hash != ps.lastAnnounce.Hash { - ps.signedAnnounce = ps.lastAnnounce - ps.signedAnnounce.sign(ps.privateKey) - } - p.announceOrStore(ps.signedAnnounce) - } -} - -// close disconnects all peers. No new peers can be registered -// after close has returned. -func (ps *clientPeerSet) close() { - ps.lock.Lock() - defer ps.lock.Unlock() - - for _, p := range ps.peers { - p.Peer.Disconnect(p2p.DiscQuitting) - } - ps.closed = true -} - -// serverSet is a special set which contains all connected les servers. -// Les servers will also be discovered by discovery protocol because they -// also run the LES protocol. We can't drop them although they are useless -// for us(server) but for other protocols(e.g. ETH) upon the devp2p they -// may be useful. -type serverSet struct { - lock sync.Mutex - set map[string]*clientPeer - closed bool -} - -func newServerSet() *serverSet { - return &serverSet{set: make(map[string]*clientPeer)} -} - -func (s *serverSet) register(peer *clientPeer) error { - s.lock.Lock() - defer s.lock.Unlock() - - if s.closed { - return errClosed - } - if _, exist := s.set[peer.id]; exist { - return errAlreadyRegistered - } - s.set[peer.id] = peer - return nil -} - -func (s *serverSet) unregister(peer *clientPeer) error { - s.lock.Lock() - defer s.lock.Unlock() - - if s.closed { - return errClosed - } - if _, exist := s.set[peer.id]; !exist { - return errNotRegistered - } - delete(s.set, peer.id) - peer.Peer.Disconnect(p2p.DiscQuitting) - return nil -} - -func (s *serverSet) close() { - s.lock.Lock() - defer s.lock.Unlock() - - for _, p := range s.set { - p.Peer.Disconnect(p2p.DiscQuitting) - } - s.closed = true -} diff --git a/les/peer_test.go b/les/peer_test.go deleted file mode 100644 index 021d5cb59..000000000 --- a/les/peer_test.go +++ /dev/null @@ -1,166 +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 ( - "crypto/rand" - "errors" - "math/big" - "reflect" - "sort" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/forkid" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/params" -) - -type testServerPeerSub struct { - regCh chan *serverPeer - unregCh chan *serverPeer -} - -func newTestServerPeerSub() *testServerPeerSub { - return &testServerPeerSub{ - regCh: make(chan *serverPeer, 1), - unregCh: make(chan *serverPeer, 1), - } -} - -func (t *testServerPeerSub) registerPeer(p *serverPeer) { t.regCh <- p } -func (t *testServerPeerSub) unregisterPeer(p *serverPeer) { t.unregCh <- p } - -func TestPeerSubscription(t *testing.T) { - peers := newServerPeerSet() - defer peers.close() - - checkIds := func(expect []string) { - given := peers.ids() - if len(given) == 0 && len(expect) == 0 { - return - } - sort.Strings(given) - sort.Strings(expect) - if !reflect.DeepEqual(given, expect) { - t.Fatalf("all peer ids mismatch, want %v, given %v", expect, given) - } - } - checkPeers := func(peerCh chan *serverPeer) { - select { - case <-peerCh: - case <-time.NewTimer(100 * time.Millisecond).C: - t.Fatalf("timeout, no event received") - } - select { - case <-peerCh: - t.Fatalf("unexpected event received") - case <-time.NewTimer(10 * time.Millisecond).C: - } - } - checkIds([]string{}) - - sub := newTestServerPeerSub() - peers.subscribe(sub) - - // Generate a random id and create the peer - var id enode.ID - rand.Read(id[:]) - peer := newServerPeer(2, NetworkId, false, p2p.NewPeer(id, "name", nil), nil) - peers.register(peer) - - checkIds([]string{peer.id}) - checkPeers(sub.regCh) - - peers.unregister(peer.id) - checkIds([]string{}) - checkPeers(sub.unregCh) -} - -type fakeChain struct{} - -func (f *fakeChain) Config() *params.ChainConfig { return params.MainnetChainConfig } -func (f *fakeChain) Genesis() *types.Block { - return core.DefaultGenesisBlock().ToBlock() -} -func (f *fakeChain) CurrentHeader() *types.Header { return &types.Header{Number: big.NewInt(10000000)} } - -func TestHandshake(t *testing.T) { - // Create a message pipe to communicate through - app, net := p2p.MsgPipe() - - // Generate a random id and create the peer - var id enode.ID - rand.Read(id[:]) - - peer1 := newClientPeer(2, NetworkId, p2p.NewPeer(id, "name", nil), net) - peer2 := newServerPeer(2, NetworkId, true, p2p.NewPeer(id, "name", nil), app) - - var ( - errCh1 = make(chan error, 1) - errCh2 = make(chan error, 1) - - td = big.NewInt(100) - head = common.HexToHash("deadbeef") - headNum = uint64(10) - 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) - filter1, filter2 = forkid.NewFilter(chain1), forkid.NewFilter(chain2) - ) - - go func() { - errCh1 <- peer1.handshake(td, head, headNum, genesis, forkID1, filter1, func(list *keyValueList) { - var announceType uint64 = announceTypeSigned - *list = (*list).add("announceType", announceType) - }, nil) - }() - go func() { - errCh2 <- peer2.handshake(td, head, headNum, genesis, forkID2, filter2, nil, func(recv keyValueMap) error { - var reqType uint64 - err := recv.get("announceType", &reqType) - if err != nil { - return err - } - if reqType != announceTypeSigned { - return errors.New("Expected announceTypeSigned") - } - return nil - }) - }() - - for i := 0; i < 2; i++ { - select { - case err := <-errCh1: - if err != nil { - t.Fatalf("handshake failed, %v", err) - } - case err := <-errCh2: - if err != nil { - t.Fatalf("handshake failed, %v", err) - } - case <-time.After(time.Second): - t.Fatalf("timeout") - } - } -} diff --git a/les/protocol.go b/les/protocol.go deleted file mode 100644 index cfebdbfb9..000000000 --- a/les/protocol.go +++ /dev/null @@ -1,327 +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 ( - "crypto/ecdsa" - "errors" - "fmt" - "io" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - vfc "github.com/ethereum/go-ethereum/les/vflux/client" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/rlp" -) - -// Constants to match up protocol versions and messages -const ( - lpv2 = 2 - lpv3 = 3 - lpv4 = 4 -) - -// Supported versions of the les protocol (first is primary) -var ( - ClientProtocolVersions = []uint{lpv2, lpv3, lpv4} - ServerProtocolVersions = []uint{lpv2, lpv3, lpv4} -) - -// ProtocolLengths is the number of implemented message corresponding to different protocol versions. -var ProtocolLengths = map[uint]uint64{lpv2: 22, lpv3: 24, lpv4: 24} - -const ( - NetworkId = 1 - ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message - blockSafetyMargin = 4 // safety margin applied to block ranges specified relative to head block - - txIndexUnlimited = 0 // this value in the "recentTxLookup" handshake field means the entire tx index history is served - txIndexDisabled = 1 // this value means tx index is not served at all - txIndexRecentOffset = 1 // txIndexRecentOffset + N in the handshake field means then tx index of the last N blocks is supported -) - -// les protocol message codes -const ( - // Protocol messages inherited from LPV1 - StatusMsg = 0x00 - AnnounceMsg = 0x01 - GetBlockHeadersMsg = 0x02 - BlockHeadersMsg = 0x03 - GetBlockBodiesMsg = 0x04 - BlockBodiesMsg = 0x05 - GetReceiptsMsg = 0x06 - ReceiptsMsg = 0x07 - GetCodeMsg = 0x0a - CodeMsg = 0x0b - // Protocol messages introduced in LPV2 - GetProofsV2Msg = 0x0f - ProofsV2Msg = 0x10 - GetHelperTrieProofsMsg = 0x11 - HelperTrieProofsMsg = 0x12 - SendTxV2Msg = 0x13 - GetTxStatusMsg = 0x14 - TxStatusMsg = 0x15 - // Protocol messages introduced in LPV3 - StopMsg = 0x16 - ResumeMsg = 0x17 -) - -// GetBlockHeadersData represents a block header query (the request ID is not included) -type GetBlockHeadersData 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) -} - -// GetBlockHeadersPacket represents a block header request -type GetBlockHeadersPacket struct { - ReqID uint64 - Query GetBlockHeadersData -} - -// GetBlockBodiesPacket represents a block body request -type GetBlockBodiesPacket struct { - ReqID uint64 - Hashes []common.Hash -} - -// GetCodePacket represents a contract code request -type GetCodePacket struct { - ReqID uint64 - Reqs []CodeReq -} - -// GetReceiptsPacket represents a block receipts request -type GetReceiptsPacket struct { - ReqID uint64 - Hashes []common.Hash -} - -// GetProofsPacket represents a proof request -type GetProofsPacket struct { - ReqID uint64 - Reqs []ProofReq -} - -// GetHelperTrieProofsPacket represents a helper trie proof request -type GetHelperTrieProofsPacket struct { - ReqID uint64 - Reqs []HelperTrieReq -} - -// SendTxPacket represents a transaction propagation request -type SendTxPacket struct { - ReqID uint64 - Txs []*types.Transaction -} - -// GetTxStatusPacket represents a transaction status query -type GetTxStatusPacket struct { - ReqID uint64 - Hashes []common.Hash -} - -type requestInfo struct { - name string - maxCount uint64 - refBasketFirst, refBasketRest float64 -} - -// reqMapping maps an LES request to one or two vflux service vector entries. -// If rest != -1 and the request type is used with amounts larger than one then the -// first one of the multi-request is mapped to first while the rest is mapped to rest. -type reqMapping struct { - first, rest int -} - -var ( - // requests describes the available LES request types and their initializing amounts - // in the vfc.ValueTracker reference basket. Initial values are estimates - // based on the same values as the server's default cost estimates (reqAvgTimeCost). - requests = map[uint64]requestInfo{ - GetBlockHeadersMsg: {"GetBlockHeaders", MaxHeaderFetch, 10, 1000}, - GetBlockBodiesMsg: {"GetBlockBodies", MaxBodyFetch, 1, 0}, - GetReceiptsMsg: {"GetReceipts", MaxReceiptFetch, 1, 0}, - GetCodeMsg: {"GetCode", MaxCodeFetch, 1, 0}, - GetProofsV2Msg: {"GetProofsV2", MaxProofsFetch, 10, 0}, - GetHelperTrieProofsMsg: {"GetHelperTrieProofs", MaxHelperTrieProofsFetch, 10, 100}, - SendTxV2Msg: {"SendTxV2", MaxTxSend, 1, 0}, - GetTxStatusMsg: {"GetTxStatus", MaxTxStatus, 10, 0}, - } - requestList []vfc.RequestInfo - requestMapping map[uint32]reqMapping -) - -// init creates a request list and mapping between protocol message codes and vflux -// service vector indices. -func init() { - requestMapping = make(map[uint32]reqMapping) - for code, req := range requests { - cost := reqAvgTimeCost[code] - rm := reqMapping{len(requestList), -1} - requestList = append(requestList, vfc.RequestInfo{ - Name: req.name + ".first", - InitAmount: req.refBasketFirst, - InitValue: float64(cost.baseCost + cost.reqCost), - }) - if req.refBasketRest != 0 { - rm.rest = len(requestList) - requestList = append(requestList, vfc.RequestInfo{ - Name: req.name + ".rest", - InitAmount: req.refBasketRest, - InitValue: float64(cost.reqCost), - }) - } - requestMapping[uint32(code)] = rm - } -} - -type errCode int - -const ( - ErrMsgTooLarge = iota - ErrDecode - ErrInvalidMsgCode - ErrProtocolVersionMismatch - ErrNetworkIdMismatch - ErrGenesisBlockMismatch - ErrNoStatusMsg - ErrExtraStatusMsg - ErrSuspendedPeer - ErrUselessPeer - ErrRequestRejected - ErrUnexpectedResponse - ErrInvalidResponse - ErrTooManyTimeouts - ErrMissingKey - ErrForkIDRejected -) - -func (e errCode) String() string { - return errorToString[int(e)] -} - -// XXX change once legacy code is out -var errorToString = map[int]string{ - ErrMsgTooLarge: "Message too long", - ErrDecode: "Invalid message", - ErrInvalidMsgCode: "Invalid message code", - ErrProtocolVersionMismatch: "Protocol version mismatch", - ErrNetworkIdMismatch: "NetworkId mismatch", - ErrGenesisBlockMismatch: "Genesis block mismatch", - ErrNoStatusMsg: "No status message", - ErrExtraStatusMsg: "Extra status message", - ErrSuspendedPeer: "Suspended peer", - ErrRequestRejected: "Request rejected", - ErrUnexpectedResponse: "Unexpected response", - ErrInvalidResponse: "Invalid response", - ErrTooManyTimeouts: "Too many request timeouts", - ErrMissingKey: "Key missing from list", - ErrForkIDRejected: "ForkID rejected", -} - -// announceData is the network packet for the block announcements. -type announceData struct { - Hash common.Hash // Hash of one particular block being announced - Number uint64 // Number of one particular block being announced - Td *big.Int // Total difficulty of one particular block being announced - ReorgDepth uint64 - Update keyValueList -} - -// sanityCheck verifies that the values are reasonable, as a DoS protection -func (a *announceData) sanityCheck() error { - if tdlen := a.Td.BitLen(); tdlen > 100 { - return fmt.Errorf("too large block TD: bitlen %d", tdlen) - } - return nil -} - -// sign adds a signature to the block announcement by the given privKey -func (a *announceData) sign(privKey *ecdsa.PrivateKey) { - rlp, _ := rlp.EncodeToBytes(blockInfo{a.Hash, a.Number, a.Td}) - sig, _ := crypto.Sign(crypto.Keccak256(rlp), privKey) - a.Update = a.Update.add("sign", sig) -} - -// checkSignature verifies if the block announcement has a valid signature by the given pubKey -func (a *announceData) checkSignature(id enode.ID, update keyValueMap) error { - var sig []byte - if err := update.get("sign", &sig); err != nil { - return err - } - rlp, _ := rlp.EncodeToBytes(blockInfo{a.Hash, a.Number, a.Td}) - recPubkey, err := crypto.SigToPub(crypto.Keccak256(rlp), sig) - if err != nil { - return err - } - if id == enode.PubkeyToIDV4(recPubkey) { - return nil - } - return errors.New("wrong signature") -} - -type blockInfo struct { - Hash common.Hash // Hash of one particular block being announced - Number uint64 // Number of one particular block being announced - Td *big.Int // Total difficulty of one particular block being announced -} - -// hashOrNumber is a combined field for specifying an origin block. -type hashOrNumber struct { - Hash common.Hash // Block hash from which to retrieve headers (excludes Number) - Number uint64 // Block hash from which to retrieve headers (excludes Hash) -} - -// EncodeRLP is a specialized encoder for hashOrNumber to encode only one of the -// two contained union fields. -func (hn *hashOrNumber) EncodeRLP(w io.Writer) error { - if hn.Hash == (common.Hash{}) { - return rlp.Encode(w, hn.Number) - } - if hn.Number != 0 { - return fmt.Errorf("both origin hash (%x) and number (%d) provided", hn.Hash, hn.Number) - } - return rlp.Encode(w, hn.Hash) -} - -// DecodeRLP is a specialized decoder for hashOrNumber to decode the contents -// into either a block hash or a block number. -func (hn *hashOrNumber) DecodeRLP(s *rlp.Stream) error { - _, size, err := s.Kind() - switch { - case err != nil: - return err - case size == 32: - hn.Number = 0 - return s.Decode(&hn.Hash) - case size <= 8: - hn.Hash = common.Hash{} - return s.Decode(&hn.Number) - default: - return fmt.Errorf("invalid input size %d for origin", size) - } -} - -// CodeData is the network response packet for a node data retrieval. -type CodeData []struct { - Value []byte -} diff --git a/les/request_test.go b/les/request_test.go deleted file mode 100644 index 5e354b7ef..000000000 --- a/les/request_test.go +++ /dev/null @@ -1,129 +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 - -// 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" - "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/ethdb" - "github.com/ethereum/go-ethereum/light" -) - -var testBankSecureTrieKey = secAddr(bankAddr) - -func secAddr(addr common.Address) []byte { - return crypto.Keccak256(addr[:]) -} - -type accessTestFn func(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest - -func TestBlockAccessLes2(t *testing.T) { testAccess(t, 2, tfBlockAccess) } -func TestBlockAccessLes3(t *testing.T) { testAccess(t, 3, tfBlockAccess) } -func TestBlockAccessLes4(t *testing.T) { testAccess(t, 4, tfBlockAccess) } - -func tfBlockAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { - return &light.BlockRequest{Hash: bhash, Number: number} -} - -func TestReceiptsAccessLes2(t *testing.T) { testAccess(t, 2, tfReceiptsAccess) } -func TestReceiptsAccessLes3(t *testing.T) { testAccess(t, 3, tfReceiptsAccess) } -func TestReceiptsAccessLes4(t *testing.T) { testAccess(t, 4, tfReceiptsAccess) } - -func tfReceiptsAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { - return &light.ReceiptsRequest{Hash: bhash, Number: number} -} - -func TestTrieEntryAccessLes2(t *testing.T) { testAccess(t, 2, tfTrieEntryAccess) } -func TestTrieEntryAccessLes3(t *testing.T) { testAccess(t, 3, tfTrieEntryAccess) } -func TestTrieEntryAccessLes4(t *testing.T) { testAccess(t, 4, tfTrieEntryAccess) } - -func tfTrieEntryAccess(db ethdb.Database, bhash common.Hash, number uint64) light.OdrRequest { - if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { - return &light.TrieRequest{Id: light.StateTrieID(rawdb.ReadHeader(db, bhash, *number)), Key: testBankSecureTrieKey} - } - return nil -} - -func TestCodeAccessLes2(t *testing.T) { testAccess(t, 2, tfCodeAccess) } -func TestCodeAccessLes3(t *testing.T) { testAccess(t, 3, tfCodeAccess) } -func TestCodeAccessLes4(t *testing.T) { testAccess(t, 4, tfCodeAccess) } - -func tfCodeAccess(db ethdb.Database, bhash common.Hash, num uint64) light.OdrRequest { - number := rawdb.ReadHeaderNumber(db, bhash) - if number != nil { - return nil - } - header := rawdb.ReadHeader(db, bhash, *number) - if header.Number.Uint64() < testContractDeployed { - return nil - } - sti := light.StateTrieID(header) - ci := light.StorageTrieID(sti, testContractAddr, types.EmptyRootHash) - return &light.CodeRequest{Id: ci, Hash: crypto.Keccak256Hash(testContractCodeDeployed)} -} - -func testAccess(t *testing.T, protocol int, fn accessTestFn) { - // Assemble the test environment - netconfig := testnetConfig{ - blocks: 4, - protocol: protocol, - indexFn: nil, - connect: true, - nopruning: true, - } - server, client, tearDown := newClientServerEnv(t, netconfig) - defer tearDown() - - // Ensure the client has synced all necessary data. - clientHead := client.handler.backend.blockchain.CurrentHeader() - if clientHead.Number.Uint64() != 4 { - t.Fatalf("Failed to sync the chain with server, head: %v", clientHead.Number.Uint64()) - } - - test := func(expFail uint64) { - for i := uint64(0); i <= server.handler.blockchain.CurrentHeader().Number.Uint64(); i++ { - bhash := rawdb.ReadCanonicalHash(server.db, i) - if req := fn(client.db, bhash, i); req != nil { - ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) - - err := client.handler.backend.odr.Retrieve(ctx, req) - cancel() - - got := err == nil - exp := i < expFail - if exp && !got { - t.Errorf("object retrieval failed") - } - if !exp && got { - t.Errorf("unexpected object retrieval success") - } - } - } - } - test(5) -} -*/ diff --git a/les/retrieve.go b/les/retrieve.go deleted file mode 100644 index 728f960a5..000000000 --- a/les/retrieve.go +++ /dev/null @@ -1,421 +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 les - -import ( - "context" - "errors" - "sync" - "time" - - "github.com/ethereum/go-ethereum/light" -) - -var ( - retryQueue = time.Millisecond * 100 - hardRequestTimeout = time.Second * 10 -) - -// retrieveManager is a layer on top of requestDistributor which takes care of -// matching replies by request ID and handles timeouts and resends if necessary. -type retrieveManager struct { - dist *requestDistributor - peers *serverPeerSet - softRequestTimeout func() time.Duration - - lock sync.RWMutex - sentReqs map[uint64]*sentReq -} - -// validatorFunc is a function that processes a reply message -type validatorFunc func(distPeer, *Msg) error - -// sentReq represents a request sent and tracked by retrieveManager -type sentReq struct { - rm *retrieveManager - req *distReq - id uint64 - validate validatorFunc - - eventsCh chan reqPeerEvent - stopCh chan struct{} - stopped bool - err error - - lock sync.RWMutex // protect access to sentTo map - sentTo map[distPeer]sentReqToPeer - - lastReqQueued bool // last request has been queued but not sent - lastReqSentTo distPeer // if not nil then last request has been sent to given peer but not timed out - reqSrtoCount int // number of requests that reached soft (but not hard) timeout -} - -// sentReqToPeer notifies the request-from-peer goroutine (tryRequest) about a response -// delivered by the given peer. Only one delivery is allowed per request per peer, -// after which delivered is set to true, the validity of the response is sent on the -// valid channel and no more responses are accepted. -type sentReqToPeer struct { - delivered, frozen bool - event chan int -} - -// reqPeerEvent is sent by the request-from-peer goroutine (tryRequest) to the -// request state machine (retrieveLoop) through the eventsCh channel. -type reqPeerEvent struct { - event int - peer distPeer -} - -const ( - rpSent = iota // if peer == nil, not sent (no suitable peers) - rpSoftTimeout - rpHardTimeout - rpDeliveredValid - rpDeliveredInvalid - rpNotDelivered -) - -// newRetrieveManager creates the retrieve manager -func newRetrieveManager(peers *serverPeerSet, dist *requestDistributor, srto func() time.Duration) *retrieveManager { - return &retrieveManager{ - peers: peers, - dist: dist, - sentReqs: make(map[uint64]*sentReq), - softRequestTimeout: srto, - } -} - -// retrieve sends a request (to multiple peers if necessary) and waits for an answer -// that is delivered through the deliver function and successfully validated by the -// validator callback. It returns when a valid answer is delivered or the context is -// cancelled. -func (rm *retrieveManager) retrieve(ctx context.Context, reqID uint64, req *distReq, val validatorFunc, shutdown chan struct{}) error { - sentReq := rm.sendReq(reqID, req, val) - select { - case <-sentReq.stopCh: - case <-ctx.Done(): - sentReq.stop(ctx.Err()) - case <-shutdown: - sentReq.stop(errors.New("client is shutting down")) - } - return sentReq.getError() -} - -// sendReq starts a process that keeps trying to retrieve a valid answer for a -// request from any suitable peers until stopped or succeeded. -func (rm *retrieveManager) sendReq(reqID uint64, req *distReq, val validatorFunc) *sentReq { - r := &sentReq{ - rm: rm, - req: req, - id: reqID, - sentTo: make(map[distPeer]sentReqToPeer), - stopCh: make(chan struct{}), - eventsCh: make(chan reqPeerEvent, 10), - validate: val, - } - - canSend := req.canSend - req.canSend = func(p distPeer) bool { - // add an extra check to canSend: the request has not been sent to the same peer before - r.lock.RLock() - _, sent := r.sentTo[p] - r.lock.RUnlock() - return !sent && canSend(p) - } - - request := req.request - req.request = func(p distPeer) func() { - // before actually sending the request, put an entry into the sentTo map - r.lock.Lock() - r.sentTo[p] = sentReqToPeer{delivered: false, frozen: false, event: make(chan int, 1)} - r.lock.Unlock() - return request(p) - } - rm.lock.Lock() - rm.sentReqs[reqID] = r - rm.lock.Unlock() - - go r.retrieveLoop() - return r -} - -// 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() - req, ok := rm.sentReqs[msg.ReqID] - rm.lock.RUnlock() - - if ok { - return req.deliver(peer, msg) - } - return errResp(ErrUnexpectedResponse, "reqID = %v", msg.ReqID) -} - -// frozen is called by the LES protocol manager when a server has suspended its service and we -// should not expect an answer for the requests already sent there -func (rm *retrieveManager) frozen(peer distPeer) { - rm.lock.RLock() - defer rm.lock.RUnlock() - - for _, req := range rm.sentReqs { - req.frozen(peer) - } -} - -// reqStateFn represents a state of the retrieve loop state machine -type reqStateFn func() reqStateFn - -// retrieveLoop is the retrieval state machine event loop -func (r *sentReq) retrieveLoop() { - go r.tryRequest() - r.lastReqQueued = true - state := r.stateRequesting - - for state != nil { - state = state() - } - - r.rm.lock.Lock() - delete(r.rm.sentReqs, r.id) - r.rm.lock.Unlock() -} - -// stateRequesting: a request has been queued or sent recently; when it reaches soft timeout, -// a new request is sent to a new peer -func (r *sentReq) stateRequesting() reqStateFn { - select { - case ev := <-r.eventsCh: - r.update(ev) - switch ev.event { - case rpSent: - if ev.peer == nil { - // request send failed, no more suitable peers - if r.waiting() { - // we are already waiting for sent requests which may succeed so keep waiting - return r.stateNoMorePeers - } - // nothing to wait for, no more peers to ask, return with error - r.stop(light.ErrNoPeers) - // no need to go to stopped state because waiting() already returned false - return nil - } - case rpSoftTimeout: - // last request timed out, try asking a new peer - go r.tryRequest() - r.lastReqQueued = true - return r.stateRequesting - case rpDeliveredInvalid, rpNotDelivered: - // if it was the last sent request (set to nil by update) then start a new one - if !r.lastReqQueued && r.lastReqSentTo == nil { - go r.tryRequest() - r.lastReqQueued = true - } - return r.stateRequesting - case rpDeliveredValid: - r.stop(nil) - return r.stateStopped - } - return r.stateRequesting - case <-r.stopCh: - return r.stateStopped - } -} - -// stateNoMorePeers: could not send more requests because no suitable peers are available. -// Peers may become suitable for a certain request later or new peers may appear so we -// keep trying. -func (r *sentReq) stateNoMorePeers() reqStateFn { - select { - case <-time.After(retryQueue): - go r.tryRequest() - r.lastReqQueued = true - return r.stateRequesting - case ev := <-r.eventsCh: - r.update(ev) - if ev.event == rpDeliveredValid { - r.stop(nil) - return r.stateStopped - } - if r.waiting() { - return r.stateNoMorePeers - } - r.stop(light.ErrNoPeers) - return nil - case <-r.stopCh: - return r.stateStopped - } -} - -// stateStopped: request succeeded or cancelled, just waiting for some peers -// to either answer or time out hard -func (r *sentReq) stateStopped() reqStateFn { - for r.waiting() { - r.update(<-r.eventsCh) - } - return nil -} - -// update updates the queued/sent flags and timed out peers counter according to the event -func (r *sentReq) update(ev reqPeerEvent) { - switch ev.event { - case rpSent: - r.lastReqQueued = false - r.lastReqSentTo = ev.peer - case rpSoftTimeout: - r.lastReqSentTo = nil - r.reqSrtoCount++ - case rpHardTimeout: - r.reqSrtoCount-- - case rpDeliveredValid, rpDeliveredInvalid, rpNotDelivered: - if ev.peer == r.lastReqSentTo { - r.lastReqSentTo = nil - } else { - r.reqSrtoCount-- - } - } -} - -// waiting returns true if the retrieval mechanism is waiting for an answer from -// any peer -func (r *sentReq) waiting() bool { - return r.lastReqQueued || r.lastReqSentTo != nil || r.reqSrtoCount > 0 -} - -// tryRequest tries to send the request to a new peer and waits for it to either -// succeed or time out if it has been sent. It also sends the appropriate reqPeerEvent -// messages to the request's event channel. -func (r *sentReq) tryRequest() { - sent := r.rm.dist.queue(r.req) - var p distPeer - select { - case p = <-sent: - case <-r.stopCh: - if r.rm.dist.cancel(r.req) { - p = nil - } else { - p = <-sent - } - } - - r.eventsCh <- reqPeerEvent{rpSent, p} - if p == nil { - return - } - - hrto := false - - r.lock.RLock() - s, ok := r.sentTo[p] - r.lock.RUnlock() - if !ok { - panic(nil) - } - - defer func() { - pp, ok := p.(*serverPeer) - if hrto && ok { - pp.Log().Debug("Request timed out hard") - if r.rm.peers != nil { - r.rm.peers.unregister(pp.id) - } - } - }() - - select { - case event := <-s.event: - if event == rpNotDelivered { - r.lock.Lock() - delete(r.sentTo, p) - r.lock.Unlock() - } - r.eventsCh <- reqPeerEvent{event, p} - return - case <-time.After(r.rm.softRequestTimeout()): - r.eventsCh <- reqPeerEvent{rpSoftTimeout, p} - } - - select { - case event := <-s.event: - if event == rpNotDelivered { - r.lock.Lock() - delete(r.sentTo, p) - r.lock.Unlock() - } - r.eventsCh <- reqPeerEvent{event, p} - case <-time.After(hardRequestTimeout): - hrto = true - r.eventsCh <- reqPeerEvent{rpHardTimeout, p} - } -} - -// deliver a reply belonging to this request -func (r *sentReq) deliver(peer distPeer, msg *Msg) error { - r.lock.Lock() - defer r.lock.Unlock() - - s, ok := r.sentTo[peer] - if !ok || s.delivered { - return errResp(ErrUnexpectedResponse, "reqID = %v", msg.ReqID) - } - if s.frozen { - return nil - } - valid := r.validate(peer, msg) == nil - r.sentTo[peer] = sentReqToPeer{delivered: true, frozen: false, event: s.event} - if valid { - s.event <- rpDeliveredValid - } else { - s.event <- rpDeliveredInvalid - } - if !valid { - return errResp(ErrInvalidResponse, "reqID = %v", msg.ReqID) - } - return nil -} - -// frozen sends a "not delivered" event to the peer event channel belonging to the -// given peer if the request has been sent there, causing the state machine to not -// expect an answer and potentially even send the request to the same peer again -// when canSend allows it. -func (r *sentReq) frozen(peer distPeer) { - r.lock.Lock() - defer r.lock.Unlock() - - s, ok := r.sentTo[peer] - if ok && !s.delivered && !s.frozen { - r.sentTo[peer] = sentReqToPeer{delivered: false, frozen: true, event: s.event} - s.event <- rpNotDelivered - } -} - -// stop stops the retrieval process and sets an error code that will be returned -// by getError -func (r *sentReq) stop(err error) { - r.lock.Lock() - if !r.stopped { - r.stopped = true - r.err = err - close(r.stopCh) - } - r.lock.Unlock() -} - -// getError returns any retrieval error (either internally generated or set by the -// stop function) after stopCh has been closed -func (r *sentReq) getError() error { - return r.err -} diff --git a/les/server.go b/les/server.go deleted file mode 100644 index d84856c7f..000000000 --- a/les/server.go +++ /dev/null @@ -1,281 +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 ( - "crypto/ecdsa" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/txpool" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/les/flowcontrol" - vfs "github.com/ethereum/go-ethereum/les/vflux/server" - "github.com/ethereum/go-ethereum/light" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" - "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/params" - "github.com/ethereum/go-ethereum/rpc" -) - -var ( - defaultPosFactors = vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1} - defaultNegFactors = vfs.PriceFactors{TimeFactor: 0, CapacityFactor: 1, RequestFactor: 1} -) - -const defaultConnectedBias = time.Minute * 3 - -type ethBackend interface { - ArchiveMode() bool - BlockChain() *core.BlockChain - BloomIndexer() *core.ChainIndexer - ChainDb() ethdb.Database - Synced() bool - TxPool() *txpool.TxPool -} - -type LesServer struct { - lesCommons - - archiveMode bool // Flag whether the ethereum node runs in archive mode. - handler *serverHandler - peers *clientPeerSet - serverset *serverSet - vfluxServer *vfs.Server - privateKey *ecdsa.PrivateKey - - // Flow control and capacity management - fcManager *flowcontrol.ClientManager - costTracker *costTracker - defParams flowcontrol.ServerParams - servingQueue *servingQueue - clientPool *vfs.ClientPool - - minCapacity, maxCapacity uint64 - threadsIdle int // Request serving threads count when system is idle. - threadsBusy int // Request serving threads count when system is busy(block insertion). - - p2pSrv *p2p.Server -} - -func NewLesServer(node *node.Node, e ethBackend, config *ethconfig.Config) (*LesServer, error) { - lesDb, err := node.OpenDatabase("les.server", 0, 0, "eth/db/lesserver/", false) - if err != nil { - return nil, err - } - // Calculate the number of threads used to service the light client - // requests based on the user-specified value. - threads := config.LightServ * 4 / 100 - if threads < 4 { - threads = 4 - } - srv := &LesServer{ - lesCommons: lesCommons{ - genesis: e.BlockChain().Genesis().Hash(), - config: config, - chainConfig: e.BlockChain().Config(), - iConfig: light.DefaultServerIndexerConfig, - chainDb: e.ChainDb(), - lesDb: lesDb, - chainReader: e.BlockChain(), - chtIndexer: light.NewChtIndexer(e.ChainDb(), nil, params.CHTFrequency, params.HelperTrieProcessConfirmations, true), - bloomTrieIndexer: light.NewBloomTrieIndexer(e.ChainDb(), nil, params.BloomBitsBlocks, params.BloomTrieFrequency, true), - closeCh: make(chan struct{}), - }, - archiveMode: e.ArchiveMode(), - peers: newClientPeerSet(), - serverset: newServerSet(), - vfluxServer: vfs.NewServer(time.Millisecond * 10), - fcManager: flowcontrol.NewClientManager(nil, &mclock.System{}), - servingQueue: newServingQueue(int64(time.Millisecond*10), float64(config.LightServ)/100), - threadsBusy: config.LightServ/100 + 1, - threadsIdle: threads, - p2pSrv: node.Server(), - } - issync := e.Synced - if config.LightNoSyncServe { - issync = func() bool { return true } - } - srv.handler = newServerHandler(srv, e.BlockChain(), e.ChainDb(), e.TxPool(), issync) - srv.costTracker, srv.minCapacity = newCostTracker(e.ChainDb(), config) - - // Initialize the bloom trie indexer. - e.BloomIndexer().AddChildIndexer(srv.bloomTrieIndexer) - - // Initialize server capacity management fields. - srv.defParams = flowcontrol.ServerParams{ - BufLimit: srv.minCapacity * bufLimitRatio, - MinRecharge: srv.minCapacity, - } - // LES flow control tries to more or less guarantee the possibility for the - // clients to send a certain amount of requests at any time and get a quick - // response. Most of the clients want this guarantee but don't actually need - // to send requests most of the time. Our goal is to serve as many clients as - // possible while the actually used server capacity does not exceed the limits - totalRecharge := srv.costTracker.totalRecharge() - srv.maxCapacity = srv.minCapacity * uint64(srv.config.LightPeers) - if totalRecharge > srv.maxCapacity { - srv.maxCapacity = totalRecharge - } - srv.fcManager.SetCapacityLimits(srv.minCapacity, srv.maxCapacity, srv.minCapacity*2) - srv.clientPool = vfs.NewClientPool(lesDb, srv.minCapacity, defaultConnectedBias, mclock.System{}, issync) - srv.clientPool.Start() - srv.clientPool.SetDefaultFactors(defaultPosFactors, defaultNegFactors) - srv.vfluxServer.Register(srv.clientPool, "les", "Ethereum light client service") - srv.chtIndexer.Start(e.BlockChain()) - - node.RegisterProtocols(srv.Protocols()) - node.RegisterAPIs(srv.APIs()) - node.RegisterLifecycle(srv) - return srv, nil -} - -func (s *LesServer) APIs() []rpc.API { - return []rpc.API{ - { - Namespace: "les", - Service: NewLightServerAPI(s), - }, - { - Namespace: "debug", - Service: NewDebugAPI(s), - }, - } -} - -func (s *LesServer) Protocols() []p2p.Protocol { - ps := s.makeProtocols(ServerProtocolVersions, s.handler.runPeer, func(id enode.ID) interface{} { - if p := s.peers.peer(id); p != nil { - return p.Info() - } - return nil - }, nil) - // Add "les" ENR entries. - for i := range ps { - ps[i].Attributes = []enr.Entry{&lesEntry{ - VfxVersion: 1, - }} - } - return ps -} - -// Start starts the LES server -func (s *LesServer) Start() error { - s.privateKey = s.p2pSrv.PrivateKey - s.peers.setSignerKey(s.privateKey) - s.handler.start() - s.wg.Add(1) - go s.capacityManagement() - if s.p2pSrv.DiscV5 != nil { - s.p2pSrv.DiscV5.RegisterTalkHandler("vfx", s.vfluxServer.ServeEncoded) - } - return nil -} - -// Stop stops the LES service -func (s *LesServer) Stop() error { - close(s.closeCh) - - s.clientPool.Stop() - if s.serverset != nil { - s.serverset.close() - } - s.peers.close() - s.fcManager.Stop() - s.costTracker.stop() - s.handler.stop() - s.servingQueue.stop() - if s.vfluxServer != nil { - s.vfluxServer.Stop() - } - - // Note, bloom trie indexer is closed by parent bloombits indexer. - if s.chtIndexer != nil { - s.chtIndexer.Close() - } - if s.lesDb != nil { - s.lesDb.Close() - } - s.wg.Wait() - log.Info("Les server stopped") - - return nil -} - -// capacityManagement starts an event handler loop that updates the recharge curve of -// the client manager and adjusts the client pool's size according to the total -// capacity updates coming from the client manager -func (s *LesServer) capacityManagement() { - defer s.wg.Done() - - processCh := make(chan bool, 100) - sub := s.handler.blockchain.SubscribeBlockProcessingEvent(processCh) - defer sub.Unsubscribe() - - totalRechargeCh := make(chan uint64, 100) - totalRecharge := s.costTracker.subscribeTotalRecharge(totalRechargeCh) - - totalCapacityCh := make(chan uint64, 100) - totalCapacity := s.fcManager.SubscribeTotalCapacity(totalCapacityCh) - s.clientPool.SetLimits(uint64(s.config.LightPeers), totalCapacity) - - var ( - busy bool - freePeers uint64 - blockProcess mclock.AbsTime - ) - updateRecharge := func() { - if busy { - s.servingQueue.setThreads(s.threadsBusy) - s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge, totalRecharge}}) - } else { - s.servingQueue.setThreads(s.threadsIdle) - s.fcManager.SetRechargeCurve(flowcontrol.PieceWiseLinear{{0, 0}, {totalRecharge / 10, totalRecharge}, {totalRecharge, totalRecharge}}) - } - } - updateRecharge() - - for { - select { - case busy = <-processCh: - if busy { - blockProcess = mclock.Now() - } else { - blockProcessingTimer.Update(time.Duration(mclock.Now() - blockProcess)) - } - updateRecharge() - case totalRecharge = <-totalRechargeCh: - totalRechargeGauge.Update(int64(totalRecharge)) - updateRecharge() - case totalCapacity = <-totalCapacityCh: - totalCapacityGauge.Update(int64(totalCapacity)) - newFreePeers := totalCapacity / s.minCapacity - if newFreePeers < freePeers && newFreePeers < uint64(s.config.LightPeers) { - log.Warn("Reduced free peer connections", "from", freePeers, "to", newFreePeers) - } - freePeers = newFreePeers - s.clientPool.SetLimits(uint64(s.config.LightPeers), totalCapacity) - case <-s.closeCh: - return - } - } -} diff --git a/les/server_handler.go b/les/server_handler.go deleted file mode 100644 index ad18d3f4e..000000000 --- a/les/server_handler.go +++ /dev/null @@ -1,435 +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" - "fmt" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/mclock" - "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/ethdb" - "github.com/ethereum/go-ethereum/les/flowcontrol" - "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/trie" -) - -const ( - softResponseLimit = 2 * 1024 * 1024 // Target maximum size of returned blocks, headers or node data. - estHeaderRlpSize = 500 // Approximate size of an RLP encoded block header - - MaxHeaderFetch = 192 // Amount of block headers to be fetched per retrieval request - MaxBodyFetch = 32 // Amount of block bodies to be fetched per retrieval request - MaxReceiptFetch = 128 // Amount of transaction receipts to allow fetching per request - MaxCodeFetch = 64 // Amount of contract codes to allow fetching per request - MaxProofsFetch = 64 // Amount of merkle proofs to be fetched per retrieval request - MaxHelperTrieProofsFetch = 64 // Amount of helper tries to be fetched per retrieval request - MaxTxSend = 64 // Amount of transactions to be send per request - MaxTxStatus = 256 // Amount of transactions to queried per request -) - -var ( - errTooManyInvalidRequest = errors.New("too many invalid requests made") -) - -// serverHandler is responsible for serving light client and process -// all incoming light requests. -type serverHandler struct { - forkFilter forkid.Filter - blockchain *core.BlockChain - chainDb ethdb.Database - txpool *txpool.TxPool - server *LesServer - - closeCh chan struct{} // Channel used to exit all background routines of handler. - wg sync.WaitGroup // WaitGroup used to track all background routines of handler. - synced func() bool // Callback function used to determine whether local node is synced. - - // Testing fields - addTxsSync bool -} - -func newServerHandler(server *LesServer, blockchain *core.BlockChain, chainDb ethdb.Database, txpool *txpool.TxPool, synced func() bool) *serverHandler { - handler := &serverHandler{ - forkFilter: forkid.NewFilter(blockchain), - server: server, - blockchain: blockchain, - chainDb: chainDb, - txpool: txpool, - closeCh: make(chan struct{}), - synced: synced, - } - return handler -} - -// start starts the server handler. -func (h *serverHandler) start() { - h.wg.Add(1) - go h.broadcastLoop() -} - -// stop stops the server handler. -func (h *serverHandler) stop() { - close(h.closeCh) - h.wg.Wait() -} - -// runPeer is the p2p protocol run function for the given version. -func (h *serverHandler) runPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error { - peer := newClientPeer(int(version), h.server.config.NetworkId, p, newMeteredMsgWriter(rw, int(version))) - defer peer.close() - h.wg.Add(1) - defer h.wg.Done() - return h.handle(peer) -} - -func (h *serverHandler) handle(p *clientPeer) error { - p.Log().Debug("Light Ethereum peer connected", "name", p.Name()) - - // Execute the LES handshake - var ( - head = h.blockchain.CurrentHeader() - 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) - ) - 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) - return err - } - // Connected to another server, no messages expected, just wait for disconnection - if p.server { - if err := h.server.serverset.register(p); err != nil { - return err - } - _, err := p.rw.ReadMsg() - h.server.serverset.unregister(p) - return err - } - // Setup flow control mechanism for the peer - p.fcClient = flowcontrol.NewClientNode(h.server.fcManager, p.fcParams) - defer p.fcClient.Disconnect() - - // Reject light clients if server is not synced. Put this checking here, so - // that "non-synced" les-server peers are still allowed to keep the connection. - if !h.synced() { - p.Log().Debug("Light server not synced, rejecting peer") - return p2p.DiscRequested - } - - // Register the peer into the peerset and clientpool - if err := h.server.peers.register(p); err != nil { - return err - } - if p.balance = h.server.clientPool.Register(p); p.balance == nil { - h.server.peers.unregister(p.ID()) - p.Log().Debug("Client pool already closed") - return p2p.DiscRequested - } - p.connectedAt = mclock.Now() - - var wg sync.WaitGroup // Wait group used to track all in-flight task routines. - defer func() { - wg.Wait() // Ensure all background task routines have exited. - h.server.clientPool.Unregister(p) - h.server.peers.unregister(p.ID()) - p.balance = nil - connectionTimer.Update(time.Duration(mclock.Now() - p.connectedAt)) - }() - - // Mark the peer as being served. - p.serving.Store(true) - defer p.serving.Store(false) - - // Spawn a main loop to handle all incoming messages. - for { - select { - case err := <-p.errCh: - p.Log().Debug("Failed to send light ethereum response", "err", err) - return err - default: - } - if err := h.handleMsg(p, &wg); err != nil { - p.Log().Debug("Light Ethereum message handling failed", "err", err) - return err - } - } -} - -// beforeHandle will do a series of prechecks before handling message. -func (h *serverHandler) beforeHandle(p *clientPeer, reqID, responseCount uint64, msg p2p.Msg, reqCnt uint64, maxCount uint64) (*servingTask, uint64) { - // Ensure that the request sent by client peer is valid - inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0) - if reqCnt == 0 || reqCnt > maxCount { - p.fcClient.OneTimeCost(inSizeCost) - return nil, 0 - } - // Ensure that the client peer complies with the flow control - // rules agreed by both sides. - if p.isFrozen() { - p.fcClient.OneTimeCost(inSizeCost) - return nil, 0 - } - maxCost := p.fcCosts.getMaxCost(msg.Code, reqCnt) - accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost) - if !accepted { - p.freeze() - p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge))) - p.fcClient.OneTimeCost(inSizeCost) - return nil, 0 - } - // Create a multi-stage task, estimate the time it takes for the task to - // execute, and cache it in the request service queue. - factor := h.server.costTracker.globalFactor() - if factor < 0.001 { - factor = 1 - p.Log().Error("Invalid global cost factor", "factor", factor) - } - maxTime := uint64(float64(maxCost) / factor) - task := h.server.servingQueue.newTask(p, maxTime, priority) - if !task.start() { - p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost) - return nil, 0 - } - return task, maxCost -} - -// Afterhandle will perform a series of operations after message handling, -// such as updating flow control data, sending reply, etc. -func (h *serverHandler) afterHandle(p *clientPeer, reqID, responseCount uint64, msg p2p.Msg, maxCost uint64, reqCnt uint64, task *servingTask, reply *reply) { - if reply != nil { - task.done() - } - p.responseLock.Lock() - defer p.responseLock.Unlock() - - // Short circuit if the client is already frozen. - if p.isFrozen() { - realCost := h.server.costTracker.realCost(task.servingTime, msg.Size, 0) - p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) - return - } - // Positive correction buffer value with real cost. - var replySize uint32 - if reply != nil { - replySize = reply.size() - } - var realCost uint64 - if h.server.costTracker.testing { - realCost = maxCost // Assign a fake cost for testing purpose - } else { - realCost = h.server.costTracker.realCost(task.servingTime, msg.Size, replySize) - if realCost > maxCost { - realCost = maxCost - } - } - bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost) - if reply != nil { - // Feed cost tracker request serving statistic. - h.server.costTracker.updateStats(msg.Code, reqCnt, task.servingTime, realCost) - // Reduce priority "balance" for the specific peer. - p.balance.RequestServed(realCost) - p.queueSend(func() { - if err := reply.send(bv); err != nil { - select { - case p.errCh <- err: - default: - } - } - }) - } -} - -// handleMsg is invoked whenever an inbound message is received from a remote -// peer. The remote connection is torn down upon returning any error. -func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error { - // Read the next message from the remote peer, and ensure it's fully consumed - msg, err := p.rw.ReadMsg() - if err != nil { - return err - } - p.Log().Trace("Light Ethereum message arrived", "code", msg.Code, "bytes", msg.Size) - - // Discard large message which exceeds the limitation. - if msg.Size > ProtocolMaxMsgSize { - clientErrorMeter.Mark(1) - return errResp(ErrMsgTooLarge, "%v > %v", msg.Size, ProtocolMaxMsgSize) - } - defer msg.Discard() - - // Lookup the request handler table, ensure it's supported - // message type by the protocol. - req, ok := Les3[msg.Code] - if !ok { - p.Log().Trace("Received invalid message", "code", msg.Code) - clientErrorMeter.Mark(1) - return errResp(ErrInvalidMsgCode, "%v", msg.Code) - } - p.Log().Trace("Received " + req.Name) - - // Decode the p2p message, resolve the concrete handler for it. - serve, reqID, reqCnt, err := req.Handle(msg) - if err != nil { - clientErrorMeter.Mark(1) - return errResp(ErrDecode, "%v: %v", msg, err) - } - if metrics.EnabledExpensive { - req.InPacketsMeter.Mark(1) - req.InTrafficMeter.Mark(int64(msg.Size)) - } - p.responseCount++ - responseCount := p.responseCount - - // First check this client message complies all rules before - // handling it and return a processor if all checks are passed. - task, maxCost := h.beforeHandle(p, reqID, responseCount, msg, reqCnt, req.MaxCount) - if task == nil { - return nil - } - wg.Add(1) - go func() { - defer wg.Done() - - reply := serve(h, p, task.waitOrStop) - h.afterHandle(p, reqID, responseCount, msg, maxCost, reqCnt, task, reply) - - if metrics.EnabledExpensive { - size := uint32(0) - if reply != nil { - size = reply.size() - } - req.OutPacketsMeter.Mark(1) - req.OutTrafficMeter.Mark(int64(size)) - req.ServingTimeMeter.Update(time.Duration(task.servingTime)) - } - }() - // If the client has made too much invalid request(e.g. request a non-existent data), - // reject them to prevent SPAM attack. - if p.getInvalid() > maxRequestErrors { - clientErrorMeter.Mark(1) - return errTooManyInvalidRequest - } - return nil -} - -// BlockChain implements serverBackend -func (h *serverHandler) BlockChain() *core.BlockChain { - return h.blockchain -} - -// TxPool implements serverBackend -func (h *serverHandler) TxPool() *txpool.TxPool { - return h.txpool -} - -// ArchiveMode implements serverBackend -func (h *serverHandler) ArchiveMode() bool { - return h.server.archiveMode -} - -// AddTxsSync implements serverBackend -func (h *serverHandler) AddTxsSync() bool { - return h.addTxsSync -} - -// getAccount retrieves an account from the state based on root. -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 - } - acc, err := trie.GetAccount(addr) - if err != nil { - return types.StateAccount{}, err - } - if acc == nil { - return types.StateAccount{}, fmt.Errorf("account %#x is not present", addr) - } - return *acc, nil -} - -// GetHelperTrie returns the post-processed trie root for the given trie ID and section index -func (h *serverHandler) GetHelperTrie(typ uint, index uint64) *trie.Trie { - var ( - root common.Hash - prefix string - ) - switch typ { - case htCanonical: - sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.ChtSize-1) - root, prefix = light.GetChtRoot(h.chainDb, index, sectionHead), string(rawdb.ChtTablePrefix) - case htBloomBits: - sectionHead := rawdb.ReadCanonicalHash(h.chainDb, (index+1)*h.server.iConfig.BloomTrieSize-1) - root, prefix = light.GetBloomTrieRoot(h.chainDb, index, sectionHead), string(rawdb.BloomTrieTablePrefix) - } - if root == (common.Hash{}) { - return nil - } - trie, _ := trie.New(trie.TrieID(root), trie.NewDatabase(rawdb.NewTable(h.chainDb, prefix))) - return trie -} - -// broadcastLoop broadcasts new block information to all connected light -// clients. According to the agreement between client and server, server should -// only broadcast new announcement if the total difficulty is higher than the -// last one. Besides server will add the signature if client requires. -func (h *serverHandler) broadcastLoop() { - defer h.wg.Done() - - headCh := make(chan core.ChainHeadEvent, 10) - headSub := h.blockchain.SubscribeChainHeadEvent(headCh) - defer headSub.Unsubscribe() - - var ( - lastHead = h.blockchain.CurrentHeader() - lastTd = common.Big0 - ) - for { - select { - case ev := <-headCh: - header := ev.Block.Header() - hash, number := header.Hash(), header.Number.Uint64() - td := h.blockchain.GetTd(hash, number) - if td == nil || td.Cmp(lastTd) <= 0 { - continue - } - var reorg uint64 - if lastHead != nil { - // If a setHead has been performed, the common ancestor can be nil. - if ancestor := rawdb.FindCommonAncestor(h.chainDb, header, lastHead); ancestor != nil { - reorg = lastHead.Number.Uint64() - ancestor.Number.Uint64() - } - } - lastHead, lastTd = header, td - log.Debug("Announcing block to peers", "number", number, "hash", hash, "td", td, "reorg", reorg) - h.server.peers.broadcast(announceData{Hash: hash, Number: number, Td: td, ReorgDepth: reorg}) - case <-h.closeCh: - return - } - } -} diff --git a/les/server_requests.go b/les/server_requests.go deleted file mode 100644 index 30ff2cd05..000000000 --- a/les/server_requests.go +++ /dev/null @@ -1,566 +0,0 @@ -// Copyright 2021 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 ( - "encoding/binary" - "encoding/json" - - "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/txpool" - "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/rlp" - "github.com/ethereum/go-ethereum/trie" -) - -// serverBackend defines the backend functions needed for serving LES requests -type serverBackend interface { - ArchiveMode() bool - AddTxsSync() bool - BlockChain() *core.BlockChain - TxPool() *txpool.TxPool - GetHelperTrie(typ uint, index uint64) *trie.Trie -} - -// Decoder is implemented by the messages passed to the handler functions -type Decoder interface { - Decode(val interface{}) error -} - -// RequestType is a static struct that describes an LES request type and references -// its handler function. -type RequestType struct { - Name string - MaxCount uint64 - InPacketsMeter, InTrafficMeter, OutPacketsMeter, OutTrafficMeter metrics.Meter - ServingTimeMeter metrics.Timer - Handle func(msg Decoder) (serve serveRequestFn, reqID, amount uint64, err error) -} - -// serveRequestFn is returned by the request handler functions after decoding the request. -// This function does the actual request serving using the supplied backend. waitOrStop is -// called between serving individual request items and may block if the serving process -// needs to be throttled. If it returns false then the process is terminated. -// The reply is not sent by this function yet. The flow control feedback value is supplied -// by the protocol handler when calling the send function of the returned reply struct. -type serveRequestFn func(backend serverBackend, peer *clientPeer, waitOrStop func() bool) *reply - -// Les3 contains the request types supported by les/2 and les/3 -var Les3 = map[uint64]RequestType{ - GetBlockHeadersMsg: { - Name: "block header request", - MaxCount: MaxHeaderFetch, - InPacketsMeter: miscInHeaderPacketsMeter, - InTrafficMeter: miscInHeaderTrafficMeter, - OutPacketsMeter: miscOutHeaderPacketsMeter, - OutTrafficMeter: miscOutHeaderTrafficMeter, - ServingTimeMeter: miscServingTimeHeaderTimer, - Handle: handleGetBlockHeaders, - }, - GetBlockBodiesMsg: { - Name: "block bodies request", - MaxCount: MaxBodyFetch, - InPacketsMeter: miscInBodyPacketsMeter, - InTrafficMeter: miscInBodyTrafficMeter, - OutPacketsMeter: miscOutBodyPacketsMeter, - OutTrafficMeter: miscOutBodyTrafficMeter, - ServingTimeMeter: miscServingTimeBodyTimer, - Handle: handleGetBlockBodies, - }, - GetCodeMsg: { - Name: "code request", - MaxCount: MaxCodeFetch, - InPacketsMeter: miscInCodePacketsMeter, - InTrafficMeter: miscInCodeTrafficMeter, - OutPacketsMeter: miscOutCodePacketsMeter, - OutTrafficMeter: miscOutCodeTrafficMeter, - ServingTimeMeter: miscServingTimeCodeTimer, - Handle: handleGetCode, - }, - GetReceiptsMsg: { - Name: "receipts request", - MaxCount: MaxReceiptFetch, - InPacketsMeter: miscInReceiptPacketsMeter, - InTrafficMeter: miscInReceiptTrafficMeter, - OutPacketsMeter: miscOutReceiptPacketsMeter, - OutTrafficMeter: miscOutReceiptTrafficMeter, - ServingTimeMeter: miscServingTimeReceiptTimer, - Handle: handleGetReceipts, - }, - GetProofsV2Msg: { - Name: "les/2 proofs request", - MaxCount: MaxProofsFetch, - InPacketsMeter: miscInTrieProofPacketsMeter, - InTrafficMeter: miscInTrieProofTrafficMeter, - OutPacketsMeter: miscOutTrieProofPacketsMeter, - OutTrafficMeter: miscOutTrieProofTrafficMeter, - ServingTimeMeter: miscServingTimeTrieProofTimer, - Handle: handleGetProofs, - }, - GetHelperTrieProofsMsg: { - Name: "helper trie proof request", - MaxCount: MaxHelperTrieProofsFetch, - InPacketsMeter: miscInHelperTriePacketsMeter, - InTrafficMeter: miscInHelperTrieTrafficMeter, - OutPacketsMeter: miscOutHelperTriePacketsMeter, - OutTrafficMeter: miscOutHelperTrieTrafficMeter, - ServingTimeMeter: miscServingTimeHelperTrieTimer, - Handle: handleGetHelperTrieProofs, - }, - SendTxV2Msg: { - Name: "new transactions", - MaxCount: MaxTxSend, - InPacketsMeter: miscInTxsPacketsMeter, - InTrafficMeter: miscInTxsTrafficMeter, - OutPacketsMeter: miscOutTxsPacketsMeter, - OutTrafficMeter: miscOutTxsTrafficMeter, - ServingTimeMeter: miscServingTimeTxTimer, - Handle: handleSendTx, - }, - GetTxStatusMsg: { - Name: "transaction status query request", - MaxCount: MaxTxStatus, - InPacketsMeter: miscInTxStatusPacketsMeter, - InTrafficMeter: miscInTxStatusTrafficMeter, - OutPacketsMeter: miscOutTxStatusPacketsMeter, - OutTrafficMeter: miscOutTxStatusTrafficMeter, - ServingTimeMeter: miscServingTimeTxStatusTimer, - Handle: handleGetTxStatus, - }, -} - -// handleGetBlockHeaders handles a block header request -func handleGetBlockHeaders(msg Decoder) (serveRequestFn, uint64, uint64, error) { - var r GetBlockHeadersPacket - if err := msg.Decode(&r); err != nil { - return nil, 0, 0, err - } - return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { - // Gather headers until the fetch or network limits is reached - var ( - bc = backend.BlockChain() - hashMode = r.Query.Origin.Hash != (common.Hash{}) - first = true - maxNonCanonical = uint64(100) - bytes common.StorageSize - headers []*types.Header - unknown bool - ) - for !unknown && len(headers) < int(r.Query.Amount) && bytes < softResponseLimit { - if !first && !waitOrStop() { - return nil - } - // Retrieve the next header satisfying the r - var origin *types.Header - if hashMode { - if first { - origin = bc.GetHeaderByHash(r.Query.Origin.Hash) - if origin != nil { - r.Query.Origin.Number = origin.Number.Uint64() - } - } else { - origin = bc.GetHeader(r.Query.Origin.Hash, r.Query.Origin.Number) - } - } else { - origin = bc.GetHeaderByNumber(r.Query.Origin.Number) - } - if origin == nil { - break - } - headers = append(headers, origin) - bytes += estHeaderRlpSize - - // Advance to the next header of the r - switch { - case hashMode && r.Query.Reverse: - // Hash based traversal towards the genesis block - ancestor := r.Query.Skip + 1 - if ancestor == 0 { - unknown = true - } else { - r.Query.Origin.Hash, r.Query.Origin.Number = bc.GetAncestor(r.Query.Origin.Hash, r.Query.Origin.Number, ancestor, &maxNonCanonical) - unknown = r.Query.Origin.Hash == common.Hash{} - } - case hashMode && !r.Query.Reverse: - // Hash based traversal towards the leaf block - var ( - current = origin.Number.Uint64() - next = current + r.Query.Skip + 1 - ) - if next <= current { - infos, _ := json.Marshal(p.Peer.Info()) - p.Log().Warn("GetBlockHeaders skip overflow attack", "current", current, "skip", r.Query.Skip, "next", next, "attacker", string(infos)) - unknown = true - } else { - if header := bc.GetHeaderByNumber(next); header != nil { - nextHash := header.Hash() - expOldHash, _ := bc.GetAncestor(nextHash, next, r.Query.Skip+1, &maxNonCanonical) - if expOldHash == r.Query.Origin.Hash { - r.Query.Origin.Hash, r.Query.Origin.Number = nextHash, next - } else { - unknown = true - } - } else { - unknown = true - } - } - case r.Query.Reverse: - // Number based traversal towards the genesis block - if r.Query.Origin.Number >= r.Query.Skip+1 { - r.Query.Origin.Number -= r.Query.Skip + 1 - } else { - unknown = true - } - - case !r.Query.Reverse: - // Number based traversal towards the leaf block - r.Query.Origin.Number += r.Query.Skip + 1 - } - first = false - } - return p.replyBlockHeaders(r.ReqID, headers) - }, r.ReqID, r.Query.Amount, nil -} - -// handleGetBlockBodies handles a block body request -func handleGetBlockBodies(msg Decoder) (serveRequestFn, uint64, uint64, error) { - var r GetBlockBodiesPacket - if err := msg.Decode(&r); err != nil { - return nil, 0, 0, err - } - return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { - var ( - bytes int - bodies []rlp.RawValue - ) - bc := backend.BlockChain() - for i, hash := range r.Hashes { - if i != 0 && !waitOrStop() { - return nil - } - if bytes >= softResponseLimit { - break - } - body := bc.GetBodyRLP(hash) - if body == nil { - p.bumpInvalid() - continue - } - bodies = append(bodies, body) - bytes += len(body) - } - return p.replyBlockBodiesRLP(r.ReqID, bodies) - }, r.ReqID, uint64(len(r.Hashes)), nil -} - -// handleGetCode handles a contract code request -func handleGetCode(msg Decoder) (serveRequestFn, uint64, uint64, error) { - var r GetCodePacket - if err := msg.Decode(&r); err != nil { - return nil, 0, 0, err - } - return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { - var ( - bytes int - data [][]byte - ) - bc := backend.BlockChain() - for i, request := range r.Reqs { - if i != 0 && !waitOrStop() { - return nil - } - // Look up the root hash belonging to the request - header := bc.GetHeaderByHash(request.BHash) - if header == nil { - p.Log().Warn("Failed to retrieve associate header for code", "hash", request.BHash) - p.bumpInvalid() - continue - } - // Refuse to search stale state data in the database since looking for - // a non-exist key is kind of expensive. - local := bc.CurrentHeader().Number.Uint64() - if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local { - p.Log().Debug("Reject stale code request", "number", header.Number.Uint64(), "head", local) - p.bumpInvalid() - continue - } - triedb := bc.StateCache().TrieDB() - address := common.BytesToAddress(request.AccountAddress) - account, err := getAccount(triedb, header.Root, address) - if err != nil { - 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(address, common.BytesToHash(account.CodeHash)) - if err != nil { - 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 - data = append(data, code) - if bytes += len(code); bytes >= softResponseLimit { - break - } - } - return p.replyCode(r.ReqID, data) - }, r.ReqID, uint64(len(r.Reqs)), nil -} - -// handleGetReceipts handles a block receipts request -func handleGetReceipts(msg Decoder) (serveRequestFn, uint64, uint64, error) { - var r GetReceiptsPacket - if err := msg.Decode(&r); err != nil { - return nil, 0, 0, err - } - return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { - var ( - bytes int - receipts []rlp.RawValue - ) - bc := backend.BlockChain() - for i, hash := range r.Hashes { - if i != 0 && !waitOrStop() { - return nil - } - if bytes >= softResponseLimit { - break - } - // Retrieve the requested block's receipts, skipping if unknown to us - results := bc.GetReceiptsByHash(hash) - if results == nil { - if header := bc.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyReceiptsHash { - p.bumpInvalid() - continue - } - } - // If known, encode and queue for response packet - if encoded, err := rlp.EncodeToBytes(results); err != nil { - log.Error("Failed to encode receipt", "err", err) - } else { - receipts = append(receipts, encoded) - bytes += len(encoded) - } - } - return p.replyReceiptsRLP(r.ReqID, receipts) - }, r.ReqID, uint64(len(r.Hashes)), nil -} - -// handleGetProofs handles a proof request -func handleGetProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { - var r GetProofsPacket - if err := msg.Decode(&r); err != nil { - return nil, 0, 0, err - } - return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { - var ( - lastBHash common.Hash - root common.Hash - header *types.Header - err error - ) - bc := backend.BlockChain() - nodes := light.NewNodeSet() - - for i, request := range r.Reqs { - if i != 0 && !waitOrStop() { - return nil - } - // Look up the root hash belonging to the request - if request.BHash != lastBHash { - root, lastBHash = common.Hash{}, request.BHash - - if header = bc.GetHeaderByHash(request.BHash); header == nil { - p.Log().Warn("Failed to retrieve header for proof", "hash", request.BHash) - p.bumpInvalid() - continue - } - // Refuse to search stale state data in the database since looking for - // a non-exist key is kind of expensive. - local := bc.CurrentHeader().Number.Uint64() - if !backend.ArchiveMode() && header.Number.Uint64()+core.TriesInMemory <= local { - p.Log().Debug("Reject stale trie request", "number", header.Number.Uint64(), "head", local) - p.bumpInvalid() - continue - } - root = header.Root - } - // If a header lookup failed (non existent), ignore subsequent requests for the same header - if root == (common.Hash{}) { - p.bumpInvalid() - continue - } - // Open the account or storage trie for the request - statedb := bc.StateCache() - - var trie state.Trie - switch len(request.AccountAddress) { - case 0: - // No account key specified, open an account trie - trie, err = statedb.OpenTrie(root) - if trie == nil || err != nil { - p.Log().Warn("Failed to open storage trie for proof", "block", header.Number, "hash", header.Hash(), "root", root, "err", err) - continue - } - default: - // Account key specified, open a storage trie - address := common.BytesToAddress(request.AccountAddress) - account, err := getAccount(statedb.TrieDB(), root, address) - if err != nil { - 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, 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", address, "root", account.Root, "err", err) - continue - } - } - // Prove the user's request from the account or storage trie - 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 - } - if nodes.DataSize() >= softResponseLimit { - break - } - } - return p.replyProofsV2(r.ReqID, nodes.NodeList()) - }, r.ReqID, uint64(len(r.Reqs)), nil -} - -// handleGetHelperTrieProofs handles a helper trie proof request -func handleGetHelperTrieProofs(msg Decoder) (serveRequestFn, uint64, uint64, error) { - var r GetHelperTrieProofsPacket - if err := msg.Decode(&r); err != nil { - return nil, 0, 0, err - } - return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { - var ( - lastIdx uint64 - lastType uint - auxTrie *trie.Trie - auxBytes int - auxData [][]byte - ) - bc := backend.BlockChain() - nodes := light.NewNodeSet() - for i, request := range r.Reqs { - if i != 0 && !waitOrStop() { - return nil - } - if auxTrie == nil || request.Type != lastType || request.TrieIdx != lastIdx { - lastType, lastIdx = request.Type, request.TrieIdx - auxTrie = backend.GetHelperTrie(request.Type, request.TrieIdx) - } - if auxTrie == nil { - return nil - } - // TODO(rjl493456442) short circuit if the proving is failed. - // The original client side code has a dirty hack to retrieve - // 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, nodes) - if p.version >= lpv4 && err != nil { - return nil - } - if request.Type == htCanonical && request.AuxReq == htAuxHeader && len(request.Key) == 8 { - header := bc.GetHeaderByNumber(binary.BigEndian.Uint64(request.Key)) - data, err := rlp.EncodeToBytes(header) - if err != nil { - log.Error("Failed to encode header", "err", err) - return nil - } - auxData = append(auxData, data) - auxBytes += len(data) - } - if nodes.DataSize()+auxBytes >= softResponseLimit { - break - } - } - return p.replyHelperTrieProofs(r.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData}) - }, r.ReqID, uint64(len(r.Reqs)), nil -} - -// handleSendTx handles a transaction propagation request -func handleSendTx(msg Decoder) (serveRequestFn, uint64, uint64, error) { - var r SendTxPacket - if err := msg.Decode(&r); err != nil { - return nil, 0, 0, err - } - amount := uint64(len(r.Txs)) - return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { - stats := make([]light.TxStatus, len(r.Txs)) - for i, tx := range r.Txs { - if i != 0 && !waitOrStop() { - return nil - } - hash := tx.Hash() - stats[i] = txStatus(backend, hash) - if stats[i].Status == txpool.TxStatusUnknown { - if errs := backend.TxPool().Add([]*txpool.Transaction{{Tx: tx}}, false, backend.AddTxsSync()); errs[0] != nil { - stats[i].Error = errs[0].Error() - continue - } - stats[i] = txStatus(backend, hash) - } - } - return p.replyTxStatus(r.ReqID, stats) - }, r.ReqID, amount, nil -} - -// handleGetTxStatus handles a transaction status query -func handleGetTxStatus(msg Decoder) (serveRequestFn, uint64, uint64, error) { - var r GetTxStatusPacket - if err := msg.Decode(&r); err != nil { - return nil, 0, 0, err - } - return func(backend serverBackend, p *clientPeer, waitOrStop func() bool) *reply { - stats := make([]light.TxStatus, len(r.Hashes)) - for i, hash := range r.Hashes { - if i != 0 && !waitOrStop() { - return nil - } - stats[i] = txStatus(backend, hash) - } - return p.replyTxStatus(r.ReqID, stats) - }, r.ReqID, uint64(len(r.Hashes)), nil -} - -// txStatus returns the status of a specified transaction. -func txStatus(b serverBackend, hash common.Hash) light.TxStatus { - var stat light.TxStatus - // Looking the transaction in txpool first. - stat.Status = b.TxPool().Status(hash) - - // If the transaction is unknown to the pool, try looking it up locally. - if stat.Status == txpool.TxStatusUnknown { - lookup := b.BlockChain().GetTransactionLookup(hash) - if lookup != nil { - stat.Status = txpool.TxStatusIncluded - stat.Lookup = lookup - } - } - return stat -} diff --git a/les/servingqueue.go b/les/servingqueue.go deleted file mode 100644 index 68cad9cb5..000000000 --- a/les/servingqueue.go +++ /dev/null @@ -1,364 +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 ( - "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 - - queueAddCh, queueBestCh chan *servingTask - stopThreadCh, quit chan struct{} - setThreadsCh chan int - - wg sync.WaitGroup - threadCount int // number of currently running threads - queue *prque.Prque[int64, *servingTask] // priority queue for waiting or suspended tasks - best *servingTask // the highest priority task (not included in the queue) - suspendBias int64 // priority bias against suspending an already running task -} - -// servingTask represents a request serving task. Tasks can be implemented to -// run in multiple steps, allowing the serving queue to suspend execution between -// steps if higher priority tasks are entered. The creator of the task should -// set the following fields: -// -// - priority: greater value means higher priority; values can wrap around the int64 range -// - run: execute a single step; return true if finished -// - after: executed after run finishes or returns an error, receives the total serving time -type servingTask struct { - sq *servingQueue - servingTime, timeAdded, maxTime, expTime uint64 - peer *clientPeer - priority int64 - biasAdded bool - token runToken - tokenCh chan runToken -} - -// runToken received by servingTask.start allows the task to run. Closing the -// channel by servingTask.stop signals the thread controller to allow a new task -// to start running. -type runToken chan struct{} - -// start blocks until the task can start and returns true if it is allowed to run. -// Returning false means that the task should be cancelled. -func (t *servingTask) start() bool { - if t.peer.isFrozen() { - return false - } - t.tokenCh = make(chan runToken, 1) - select { - case t.sq.queueAddCh <- t: - case <-t.sq.quit: - return false - } - select { - case t.token = <-t.tokenCh: - case <-t.sq.quit: - return false - } - if t.token == nil { - return false - } - t.servingTime -= uint64(mclock.Now()) - return true -} - -// done signals the thread controller about the task being finished and returns -// the total serving time of the task in nanoseconds. -func (t *servingTask) done() uint64 { - t.servingTime += uint64(mclock.Now()) - close(t.token) - diff := t.servingTime - t.timeAdded - t.timeAdded = t.servingTime - if t.expTime > diff { - t.expTime -= diff - atomic.AddUint64(&t.sq.servingTimeDiff, t.expTime) - } else { - t.expTime = 0 - } - return t.servingTime -} - -// waitOrStop can be called during the execution of the task. It blocks if there -// is a higher priority task waiting (a bias is applied in favor of the currently -// running task). Returning true means that the execution can be resumed. False -// means the task should be cancelled. -func (t *servingTask) waitOrStop() bool { - t.done() - if !t.biasAdded { - t.priority += t.sq.suspendBias - t.biasAdded = true - } - return t.start() -} - -// newServingQueue returns a new servingQueue -func newServingQueue(suspendBias int64, utilTarget float64) *servingQueue { - sq := &servingQueue{ - queue: prque.New[int64, *servingTask](nil), - suspendBias: suspendBias, - queueAddCh: make(chan *servingTask, 100), - queueBestCh: make(chan *servingTask), - stopThreadCh: make(chan struct{}), - quit: make(chan struct{}), - setThreadsCh: make(chan int, 10), - burstLimit: uint64(utilTarget * bufLimitRatio * 1200000), - burstDropLimit: uint64(utilTarget * bufLimitRatio * 1000000), - burstDecRate: utilTarget, - lastUpdate: mclock.Now(), - } - sq.wg.Add(2) - go sq.queueLoop() - go sq.threadCountLoop() - return sq -} - -// newTask creates a new task with the given priority -func (sq *servingQueue) newTask(peer *clientPeer, maxTime uint64, priority int64) *servingTask { - return &servingTask{ - sq: sq, - peer: peer, - maxTime: maxTime, - expTime: maxTime, - priority: priority, - } -} - -// threadController is started in multiple goroutines and controls the execution -// of tasks. The number of active thread controllers equals the allowed number of -// concurrently running threads. It tries to fetch the highest priority queued -// task first. If there are no queued tasks waiting then it can directly catch -// run tokens from the token channel and allow the corresponding tasks to run -// without entering the priority queue. -func (sq *servingQueue) threadController() { - defer sq.wg.Done() - for { - token := make(runToken) - select { - case best := <-sq.queueBestCh: - best.tokenCh <- token - case <-sq.stopThreadCh: - return - case <-sq.quit: - return - } - select { - case <-sq.stopThreadCh: - return - case <-sq.quit: - return - case <-token: - } - } -} - -// 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 []*peerTasks - if sq.best != nil { - sq.queue.Push(sq.best, sq.best.priority) - } - sq.best = nil - for sq.queue.Size() > 0 { - task := sq.queue.PopItem() - tasks := peerMap[task.peer] - if tasks == nil { - bufValue, bufLimit := task.peer.fcClient.BufferStatus() - if bufLimit < 1 { - bufLimit = 1 - } - tasks = &peerTasks{ - peer: task.peer, - priority: float64(bufValue) / float64(bufLimit), // lower value comes first - } - peerMap[task.peer] = tasks - peerList = append(peerList, tasks) - } - tasks.list = append(tasks.list, task) - tasks.sumTime += task.expTime - } - 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 { - tasks.peer.freeze() - tasks.peer.fcClient.Freeze() - sq.queuedTime -= tasks.sumTime - sqQueuedGauge.Update(int64(sq.queuedTime)) - clientFreezeMeter.Mark(1) - drop = sq.recentTime+sq.queuedTime > sq.burstDropLimit - for _, task := range tasks.list { - task.tokenCh <- nil - } - } else { - for _, task := range tasks.list { - sq.queue.Push(task, task.priority) - } - } - } - if sq.queue.Size() > 0 { - sq.best = sq.queue.PopItem() - } -} - -// updateRecentTime recalculates the recent serving time value -func (sq *servingQueue) updateRecentTime() { - subTime := atomic.SwapUint64(&sq.servingTimeDiff, 0) - now := mclock.Now() - dt := now - sq.lastUpdate - sq.lastUpdate = now - if dt > 0 { - subTime += uint64(float64(dt) * sq.burstDecRate) - } - if sq.recentTime > subTime { - sq.recentTime -= subTime - } else { - sq.recentTime = 0 - } -} - -// addTask inserts a task into the priority queue -func (sq *servingQueue) addTask(task *servingTask) { - if sq.best == nil { - sq.best = task - } else if task.priority-sq.best.priority > 0 { - sq.queue.Push(sq.best, sq.best.priority) - sq.best = task - } else { - sq.queue.Push(task, task.priority) - } - sq.updateRecentTime() - sq.queuedTime += task.expTime - sqServedGauge.Update(int64(sq.recentTime)) - sqQueuedGauge.Update(int64(sq.queuedTime)) - if sq.recentTime+sq.queuedTime > sq.burstLimit { - sq.freezePeers() - } -} - -// queueLoop is an event loop running in a goroutine. It receives tasks from queueAddCh -// and always tries to send the highest priority task to queueBestCh. Successfully sent -// tasks are removed from the queue. -func (sq *servingQueue) queueLoop() { - defer sq.wg.Done() - for { - if sq.best != nil { - expTime := sq.best.expTime - select { - case task := <-sq.queueAddCh: - sq.addTask(task) - case sq.queueBestCh <- sq.best: - sq.updateRecentTime() - sq.queuedTime -= expTime - sq.recentTime += expTime - sqServedGauge.Update(int64(sq.recentTime)) - sqQueuedGauge.Update(int64(sq.queuedTime)) - if sq.queue.Size() == 0 { - sq.best = nil - } else { - sq.best = sq.queue.PopItem() - } - case <-sq.quit: - return - } - } else { - select { - case task := <-sq.queueAddCh: - sq.addTask(task) - case <-sq.quit: - return - } - } - } -} - -// threadCountLoop is an event loop running in a goroutine. It adjusts the number -// of active thread controller goroutines. -func (sq *servingQueue) threadCountLoop() { - var threadCountTarget int - defer sq.wg.Done() - for { - for threadCountTarget > sq.threadCount { - sq.wg.Add(1) - go sq.threadController() - sq.threadCount++ - } - if threadCountTarget < sq.threadCount { - select { - case threadCountTarget = <-sq.setThreadsCh: - case sq.stopThreadCh <- struct{}{}: - sq.threadCount-- - case <-sq.quit: - return - } - } else { - select { - case threadCountTarget = <-sq.setThreadsCh: - case <-sq.quit: - return - } - } - } -} - -// setThreads sets the allowed processing thread count, suspending tasks as soon as -// possible if necessary. -func (sq *servingQueue) setThreads(threadCount int) { - select { - case sq.setThreadsCh <- threadCount: - case <-sq.quit: - return - } -} - -// stop stops task processing as soon as possible and shuts down the serving queue. -func (sq *servingQueue) stop() { - close(sq.quit) - sq.wg.Wait() -} diff --git a/les/state_accessor.go b/les/state_accessor.go deleted file mode 100644 index 9a8214ac2..000000000 --- a/les/state_accessor.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2021 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 ( - "context" - "errors" - "fmt" - - "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/core/vm" - "github.com/ethereum/go-ethereum/eth/tracers" - "github.com/ethereum/go-ethereum/light" -) - -// noopReleaser is returned in case there is no operation expected -// for releasing state. -var noopReleaser = tracers.StateReleaseFunc(func() {}) - -// stateAtBlock retrieves the state database associated with a certain block. -func (leth *LightEthereum) stateAtBlock(ctx context.Context, block *types.Block, reexec uint64) (*state.StateDB, tracers.StateReleaseFunc, error) { - return light.NewState(ctx, block.Header(), leth.odr), noopReleaser, nil -} - -// stateAtTransaction returns the execution environment of a certain transaction. -func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { - // Short circuit if it's genesis block. - if block.NumberU64() == 0 { - return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis") - } - // Create the parent state database - parent, err := leth.blockchain.GetBlock(ctx, block.ParentHash(), block.NumberU64()-1) - if err != nil { - return nil, vm.BlockContext{}, nil, nil, err - } - statedb, release, err := leth.stateAtBlock(ctx, parent, reexec) - if err != nil { - return nil, vm.BlockContext{}, nil, nil, err - } - if txIndex == 0 && len(block.Transactions()) == 0 { - return nil, vm.BlockContext{}, statedb, release, nil - } - // Recompute transactions up to the target index. - signer := types.MakeSigner(leth.blockchain.Config(), block.Number(), block.Time()) - for idx, tx := range block.Transactions() { - // Assemble the transaction call message and return if the requested offset - msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) - txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), leth.blockchain, nil) - statedb.SetTxContext(tx.Hash(), idx) - if idx == txIndex { - return msg, context, statedb, release, nil - } - // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, txContext, statedb, leth.blockchain.Config(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { - return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) - } - // Ensure any modifications are committed to the state - // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) - } - return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) -} diff --git a/les/test_helper.go b/les/test_helper.go deleted file mode 100644 index b03bca14b..000000000 --- a/les/test_helper.go +++ /dev/null @@ -1,625 +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 . - -// This file contains some shares testing functionality, common to multiple -// different files and modules being tested. Client based network and Server -// based network can be created easily with available APIs. - -package les - -import ( - "context" - "crypto/rand" - "fmt" - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/consensus" - "github.com/ethereum/go-ethereum/consensus/ethash" - "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/txpool/legacypool" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/ethconfig" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/les/flowcontrol" - vfs "github.com/ethereum/go-ethereum/les/vflux/server" - "github.com/ethereum/go-ethereum/light" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/params" -) - -var ( - bankKey, _ = crypto.GenerateKey() - bankAddr = crypto.PubkeyToAddress(bankKey.PublicKey) - bankFunds = big.NewInt(1_000_000_000_000_000_000) - - userKey1, _ = crypto.GenerateKey() - userKey2, _ = crypto.GenerateKey() - userAddr1 = crypto.PubkeyToAddress(userKey1.PublicKey) - userAddr2 = crypto.PubkeyToAddress(userKey2.PublicKey) - - testContractAddr common.Address - testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056") - testContractCodeDeployed = testContractCode[16:] - testContractDeployed = uint64(2) - - testEventEmitterCode = common.Hex2Bytes("60606040523415600e57600080fd5b7f57050ab73f6b9ebdd9f76b8d4997793f48cf956e965ee070551b9ca0bb71584e60405160405180910390a160358060476000396000f3006060604052600080fd00a165627a7a723058203f727efcad8b5811f8cb1fc2620ce5e8c63570d697aef968172de296ea3994140029") - - // Checkpoint oracle relative fields - signerKey, _ = crypto.GenerateKey() - signerAddr = crypto.PubkeyToAddress(signerKey.PublicKey) -) - -var ( - // The token bucket buffer limit for testing purpose. - testBufLimit = uint64(1000000) - - // The buffer recharging speed for testing purpose. - testBufRecharge = uint64(1000) -) - -/* -contract test { - - uint256[100] data; - - function Put(uint256 addr, uint256 value) { - data[addr] = value; - } - - function Get(uint256 addr) constant returns (uint256 value) { - return data[addr]; - } -} -*/ - -// prepare pre-commits specified number customized blocks into chain. -func prepare(n int, backend *backends.SimulatedBackend) { - var ( - ctx = context.Background() - signer = types.HomesteadSigner{} - ) - for i := 0; i < n; i++ { - switch i { - case 0: - // Builtin-block - // number: 1 - // txs: 1 - - // bankUser transfers some ether to user1 - nonce, _ := backend.PendingNonceAt(ctx, bankAddr) - tx, _ := types.SignTx(types.NewTransaction(nonce, userAddr1, big.NewInt(10_000_000_000_000_000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, bankKey) - backend.SendTransaction(ctx, tx) - case 1: - // Builtin-block - // number: 2 - // txs: 4 - - bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) - userNonce1, _ := backend.PendingNonceAt(ctx, userAddr1) - - // bankUser transfers more ether to user1 - tx1, _ := types.SignTx(types.NewTransaction(bankNonce, userAddr1, big.NewInt(1_000_000_000_000_000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, bankKey) - backend.SendTransaction(ctx, tx1) - - // user1 relays ether to user2 - tx2, _ := types.SignTx(types.NewTransaction(userNonce1, userAddr2, big.NewInt(1_000_000_000_000_000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, userKey1) - backend.SendTransaction(ctx, tx2) - - // user1 deploys a test contract - tx3, _ := types.SignTx(types.NewContractCreation(userNonce1+1, big.NewInt(0), 200000, big.NewInt(params.InitialBaseFee), testContractCode), signer, userKey1) - backend.SendTransaction(ctx, tx3) - testContractAddr = crypto.CreateAddress(userAddr1, userNonce1+1) - - // user1 deploys a event contract - tx4, _ := types.SignTx(types.NewContractCreation(userNonce1+2, big.NewInt(0), 200000, big.NewInt(params.InitialBaseFee), testEventEmitterCode), signer, userKey1) - backend.SendTransaction(ctx, tx4) - case 2: - // Builtin-block - // number: 3 - // txs: 2 - - // bankUser transfer some ether to signer - bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) - tx1, _ := types.SignTx(types.NewTransaction(bankNonce, signerAddr, big.NewInt(1000000000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), signer, bankKey) - backend.SendTransaction(ctx, tx1) - - // invoke test contract - data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001") - tx2, _ := types.SignTx(types.NewTransaction(bankNonce+1, testContractAddr, big.NewInt(0), 100000, big.NewInt(params.InitialBaseFee), data), signer, bankKey) - backend.SendTransaction(ctx, tx2) - case 3: - // Builtin-block - // number: 4 - // txs: 1 - - // invoke test contract - bankNonce, _ := backend.PendingNonceAt(ctx, bankAddr) - data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002") - tx, _ := types.SignTx(types.NewTransaction(bankNonce, testContractAddr, big.NewInt(0), 100000, big.NewInt(params.InitialBaseFee), data), signer, bankKey) - backend.SendTransaction(ctx, tx) - } - backend.Commit() - } -} - -// testIndexers creates a set of indexers with specified params for testing purpose. -func testIndexers(db ethdb.Database, odr light.OdrBackend, config *light.IndexerConfig, disablePruning bool) []*core.ChainIndexer { - var indexers [3]*core.ChainIndexer - indexers[0] = light.NewChtIndexer(db, odr, config.ChtSize, config.ChtConfirms, disablePruning) - indexers[1] = core.NewBloomIndexer(db, config.BloomSize, config.BloomConfirms) - indexers[2] = light.NewBloomTrieIndexer(db, odr, config.BloomSize, config.BloomTrieSize, disablePruning) - // make bloomTrieIndexer as a child indexer of bloom indexer. - indexers[1].AddChildIndexer(indexers[2]) - return indexers[:] -} - -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() - gspec = core.Genesis{ - Config: params.AllEthashProtocolChanges, - Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}}, - GasLimit: 100000000, - BaseFee: big.NewInt(params.InitialBaseFee), - } - ) - genesis := gspec.MustCommit(db) - chain, _ := light.NewLightChain(odr, gspec.Config, engine) - - client := &LightEthereum{ - lesCommons: lesCommons{ - genesis: genesis.Hash(), - config: ðconfig.Config{LightPeers: 100, NetworkId: NetworkId}, - chainConfig: params.AllEthashProtocolChanges, - iConfig: light.TestClientIndexerConfig, - chainDb: db, - chainReader: chain, - closeCh: make(chan struct{}), - }, - peers: peers, - reqDist: odr.retriever.dist, - retriever: odr.retriever, - odr: odr, - engine: engine, - blockchain: chain, - eventMux: evmux, - merger: consensus.NewMerger(rawdb.NewMemoryDatabase()), - } - client.handler = newClientHandler(client) - - return client.handler, func() { - client.handler.stop() - } -} - -func newTestServerHandler(blocks int, indexers []*core.ChainIndexer, db ethdb.Database, clock mclock.Clock) (*serverHandler, *backends.SimulatedBackend, func()) { - var ( - gspec = core.Genesis{ - Config: params.AllEthashProtocolChanges, - Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}}, - GasLimit: 100000000, - BaseFee: big.NewInt(params.InitialBaseFee), - } - ) - genesis := gspec.MustCommit(db) - - // create a simulation backend and pre-commit several customized block to the database. - simulation := backends.NewSimulatedBackendWithDatabase(db, gspec.Alloc, 100000000) - prepare(blocks, simulation) - - txpoolConfig := legacypool.DefaultConfig - txpoolConfig.Journal = "" - - pool := legacypool.New(txpoolConfig, simulation.Blockchain()) - txpool, _ := txpool.New(new(big.Int).SetUint64(txpoolConfig.PriceLimit), simulation.Blockchain(), []txpool.SubPool{pool}) - - server := &LesServer{ - lesCommons: lesCommons{ - genesis: genesis.Hash(), - config: ðconfig.Config{LightPeers: 100, NetworkId: NetworkId}, - chainConfig: params.AllEthashProtocolChanges, - iConfig: light.TestServerIndexerConfig, - chainDb: db, - chainReader: simulation.Blockchain(), - closeCh: make(chan struct{}), - }, - peers: newClientPeerSet(), - servingQueue: newServingQueue(int64(time.Millisecond*10), 1), - defParams: flowcontrol.ServerParams{ - BufLimit: testBufLimit, - MinRecharge: testBufRecharge, - }, - fcManager: flowcontrol.NewClientManager(nil, clock), - } - server.costTracker, server.minCapacity = newCostTracker(db, server.config) - server.costTracker.testCostList = testCostList(0) // Disable flow control mechanism. - server.clientPool = vfs.NewClientPool(db, testBufRecharge, defaultConnectedBias, clock, alwaysTrueFn) - server.clientPool.Start() - server.clientPool.SetLimits(10000, 10000) // Assign enough capacity for clientpool - server.handler = newServerHandler(server, simulation.Blockchain(), db, txpool, func() bool { return true }) - server.servingQueue.setThreads(4) - server.handler.start() - closer := func() { server.Stop() } - return server.handler, simulation, closer -} - -func alwaysTrueFn() bool { - return true -} - -// testPeer is a simulated peer to allow testing direct network calls. -type testPeer struct { - cpeer *clientPeer - speer *serverPeer - - net p2p.MsgReadWriter // Network layer reader/writer to simulate remote messaging - app *p2p.MsgPipeRW // Application layer reader/writer to simulate the local side -} - -// handshakeWithServer executes the handshake with the remote server peer. -func (p *testPeer) handshakeWithServer(t *testing.T, td *big.Int, head common.Hash, headNum uint64, genesis common.Hash, forkID forkid.ID) { - // It only works for the simulated client peer - if p.cpeer == nil { - t.Fatal("handshake for client peer only") - } - var sendList keyValueList - sendList = sendList.add("protocolVersion", uint64(p.cpeer.version)) - sendList = sendList.add("networkId", uint64(NetworkId)) - sendList = sendList.add("headTd", td) - sendList = sendList.add("headHash", head) - sendList = sendList.add("headNum", headNum) - sendList = sendList.add("genesisHash", genesis) - if p.cpeer.version >= lpv4 { - sendList = sendList.add("forkID", &forkID) - } - if err := p2p.ExpectMsg(p.app, StatusMsg, nil); err != nil { - t.Fatalf("status recv: %v", err) - } - if err := p2p.Send(p.app, StatusMsg, &sendList); err != nil { - t.Fatalf("status send: %v", err) - } -} - -// handshakeWithClient executes the handshake with the remote client peer. -// (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") - } - var sendList keyValueList - sendList = sendList.add("protocolVersion", uint64(p.speer.version)) - sendList = sendList.add("networkId", uint64(NetworkId)) - sendList = sendList.add("headTd", td) - sendList = sendList.add("headHash", head) - sendList = sendList.add("headNum", headNum) - sendList = sendList.add("genesisHash", genesis) - sendList = sendList.add("serveHeaders", nil) - sendList = sendList.add("serveChainSince", uint64(0)) - sendList = sendList.add("serveStateSince", uint64(0)) - sendList = sendList.add("serveRecentState", uint64(core.TriesInMemory-4)) - sendList = sendList.add("txRelay", nil) - sendList = sendList.add("flowControl/BL", testBufLimit) - sendList = sendList.add("flowControl/MRR", testBufRecharge) - sendList = sendList.add("flowControl/MRC", costList) - if p.speer.version >= lpv4 { - sendList = sendList.add("forkID", &forkID) - sendList = sendList.add("recentTxLookup", recentTxLookup) - } - if err := p2p.ExpectMsg(p.app, StatusMsg, nil); err != nil { - t.Fatalf("status recv: %v", err) - } - 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. -func (p *testPeer) close() { - p.app.Close() -} - -func newTestPeerPair(name string, version int, server *serverHandler, client *clientHandler, noInitAnnounce bool) (*testPeer, *testPeer, error) { - // Create a message pipe to communicate through - app, net := p2p.MsgPipe() - - // Generate a random id and create the peer - var id enode.ID - rand.Read(id[:]) - - peer1 := newClientPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) - peer2 := newServerPeer(version, NetworkId, false, p2p.NewPeer(id, name, nil), app) - - // Start the peer 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(peer1): - } - }() - go func() { - select { - case <-client.closeCh: - errc2 <- p2p.DiscQuitting - case errc2 <- client.handle(peer2, 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 &testPeer{cpeer: peer1, net: net, app: app}, &testPeer{speer: peer2, net: app, app: net}, nil -} - -type indexerCallback func(*core.ChainIndexer, *core.ChainIndexer, *core.ChainIndexer) - -// testClient represents a client object for testing with necessary auxiliary fields. -type testClient struct { - clock mclock.Clock - db ethdb.Database - peer *testPeer - handler *clientHandler - - chtIndexer *core.ChainIndexer - bloomIndexer *core.ChainIndexer - bloomTrieIndexer *core.ChainIndexer -} - -// newRawPeer creates a new server peer connects to the server and do the handshake. -// (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() - - // Generate a random id and create the peer - var id enode.ID - rand.Read(id[:]) - peer := newServerPeer(version, NetworkId, false, p2p.NewPeer(id, name, nil), net) - - // Start the peer on a new thread - errCh := make(chan error, 1) - go func() { - select { - case <-client.handler.closeCh: - errCh <- p2p.DiscQuitting - case errCh <- client.handler.handle(peer, false): - } - }() - tp := &testPeer{ - app: app, - net: net, - speer: peer, - } - var ( - genesis = client.handler.backend.blockchain.Genesis() - head = client.handler.backend.blockchain.CurrentHeader() - td = client.handler.backend.blockchain.GetTd(head.Hash(), head.Number.Uint64()) - ) - forkID := forkid.NewID(client.handler.backend.blockchain.Config(), genesis.Hash(), head.Number.Uint64(), head.Time) - tp.handshakeWithClient(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID, testCostList(0), recentTxLookup) // disable flow control by default - - // Ensure the connection is established or exits when any error occurs - for { - select { - case <-errCh: - return nil, nil, nil - default: - } - if peer.serving.Load() { - break - } - time.Sleep(50 * time.Millisecond) - } - closePeer := func() { - tp.speer.close() - tp.close() - } - return tp, closePeer, errCh -}*/ - -// testServer represents a server object for testing with necessary auxiliary fields. -type testServer struct { - clock mclock.Clock - backend *backends.SimulatedBackend - db ethdb.Database - peer *testPeer - handler *serverHandler - - chtIndexer *core.ChainIndexer - bloomIndexer *core.ChainIndexer - bloomTrieIndexer *core.ChainIndexer -} - -// newRawPeer creates a new client peer connects to the server and do the handshake. -func (server *testServer) newRawPeer(t *testing.T, name string, version int) (*testPeer, func(), <-chan error) { - // Create a message pipe to communicate through - app, net := p2p.MsgPipe() - - // Generate a random id and create the peer - var id enode.ID - rand.Read(id[:]) - peer := newClientPeer(version, NetworkId, p2p.NewPeer(id, name, nil), net) - - // Start the peer on a new thread - errCh := make(chan error, 1) - go func() { - select { - case <-server.handler.closeCh: - errCh <- p2p.DiscQuitting - case errCh <- server.handler.handle(peer): - } - }() - tp := &testPeer{ - app: app, - net: net, - cpeer: peer, - } - var ( - genesis = server.handler.blockchain.Genesis() - 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) - tp.handshakeWithServer(t, td, head.Hash(), head.Number.Uint64(), genesis.Hash(), forkID) - - // Ensure the connection is established or exits when any error occurs - for { - select { - case <-errCh: - return nil, nil, nil - default: - } - if peer.serving.Load() { - break - } - time.Sleep(50 * time.Millisecond) - } - closePeer := func() { - tp.cpeer.close() - tp.close() - } - return tp, closePeer, errCh -} - -// testnetConfig wraps all the configurations for testing network. -type testnetConfig struct { - blocks int - protocol int - indexFn indexerCallback - simClock bool - connect bool - nopruning bool -} - -func newClientServerEnv(t *testing.T, config testnetConfig) (*testServer, *testClient, func()) { - var ( - sdb = rawdb.NewMemoryDatabase() - cdb = rawdb.NewMemoryDatabase() - speers = newServerPeerSet() - ) - var clock mclock.Clock = &mclock.System{} - if config.simClock { - clock = &mclock.Simulated{} - } - dist := newRequestDistributor(speers, clock) - rm := newRetrieveManager(speers, dist, func() time.Duration { return time.Millisecond * 500 }) - odr := NewLesOdr(cdb, light.TestClientIndexerConfig, speers, rm) - - sindexers := testIndexers(sdb, nil, light.TestServerIndexerConfig, true) - cIndexers := testIndexers(cdb, odr, light.TestClientIndexerConfig, config.nopruning) - - scIndexer, sbIndexer, sbtIndexer := sindexers[0], sindexers[1], sindexers[2] - ccIndexer, cbIndexer, cbtIndexer := cIndexers[0], cIndexers[1], cIndexers[2] - odr.SetIndexers(ccIndexer, cbIndexer, cbtIndexer) - - server, b, serverClose := newTestServerHandler(config.blocks, sindexers, sdb, clock) - client, clientClose := newTestClientHandler(b, odr, cIndexers, cdb, speers) - - scIndexer.Start(server.blockchain) - sbIndexer.Start(server.blockchain) - ccIndexer.Start(client.backend.blockchain) - cbIndexer.Start(client.backend.blockchain) - - if config.indexFn != nil { - config.indexFn(scIndexer, sbIndexer, sbtIndexer) - } - var ( - err error - speer, cpeer *testPeer - ) - if config.connect { - done := make(chan struct{}) - cpeer, speer, err = newTestPeerPair("peer", config.protocol, server, client, false) - if err != nil { - t.Fatalf("Failed to connect testing peers %v", err) - } - select { - case <-done: - case <-time.After(10 * time.Second): - t.Fatal("test peer did not connect and sync within 3s") - } - } - s := &testServer{ - clock: clock, - backend: b, - db: sdb, - peer: cpeer, - handler: server, - chtIndexer: scIndexer, - bloomIndexer: sbIndexer, - bloomTrieIndexer: sbtIndexer, - } - c := &testClient{ - clock: clock, - db: cdb, - peer: speer, - handler: client, - chtIndexer: ccIndexer, - bloomIndexer: cbIndexer, - bloomTrieIndexer: cbtIndexer, - } - teardown := func() { - if config.connect { - speer.close() - cpeer.close() - cpeer.cpeer.close() - speer.speer.close() - } - ccIndexer.Close() - cbIndexer.Close() - scIndexer.Close() - sbIndexer.Close() - dist.close() - serverClose() - b.Close() - clientClose() - } - return s, c, teardown -} - -// NewFuzzerPeer creates a client peer for test purposes, and also returns -// a function to close the peer: this is needed to avoid goroutine leaks in the -// exec queue. -func NewFuzzerPeer(version int) (p *clientPeer, closer func()) { - p = newClientPeer(version, 0, p2p.NewPeer(enode.ID{}, "", nil), nil) - return p, func() { p.peerCommons.close() } -} diff --git a/les/txrelay.go b/les/txrelay.go deleted file mode 100644 index 40a51fb76..000000000 --- a/les/txrelay.go +++ /dev/null @@ -1,179 +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 ( - "context" - "math/rand" - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" -) - -type lesTxRelay struct { - txSent map[common.Hash]*types.Transaction - txPending map[common.Hash]struct{} - peerList []*serverPeer - peerStartPos int - lock sync.Mutex - stop chan struct{} - - retriever *retrieveManager -} - -func newLesTxRelay(ps *serverPeerSet, retriever *retrieveManager) *lesTxRelay { - r := &lesTxRelay{ - txSent: make(map[common.Hash]*types.Transaction), - txPending: make(map[common.Hash]struct{}), - retriever: retriever, - stop: make(chan struct{}), - } - ps.subscribe(r) - return r -} - -func (ltrx *lesTxRelay) Stop() { - close(ltrx.stop) -} - -func (ltrx *lesTxRelay) registerPeer(p *serverPeer) { - ltrx.lock.Lock() - defer ltrx.lock.Unlock() - - // Short circuit if the peer is announce only. - if p.onlyAnnounce { - return - } - ltrx.peerList = append(ltrx.peerList, p) -} - -func (ltrx *lesTxRelay) unregisterPeer(p *serverPeer) { - ltrx.lock.Lock() - defer ltrx.lock.Unlock() - - for i, peer := range ltrx.peerList { - if peer == p { - // Remove from the peer list - ltrx.peerList = append(ltrx.peerList[:i], ltrx.peerList[i+1:]...) - return - } - } -} - -// send sends a list of transactions to at most a given number of peers. -func (ltrx *lesTxRelay) send(txs types.Transactions, count int) { - sendTo := make(map[*serverPeer]types.Transactions) - - ltrx.peerStartPos++ // rotate the starting position of the peer list - if ltrx.peerStartPos >= len(ltrx.peerList) { - ltrx.peerStartPos = 0 - } - - for _, tx := range txs { - hash := tx.Hash() - _, ok := ltrx.txSent[hash] - if !ok { - ltrx.txSent[hash] = tx - ltrx.txPending[hash] = struct{}{} - } - if len(ltrx.peerList) > 0 { - cnt := count - pos := ltrx.peerStartPos - for { - peer := ltrx.peerList[pos] - sendTo[peer] = append(sendTo[peer], tx) - cnt-- - if cnt == 0 { - break // sent it to the desired number of peers - } - pos++ - if pos == len(ltrx.peerList) { - pos = 0 - } - if pos == ltrx.peerStartPos { - break // tried all available peers - } - } - } - } - - for p, list := range sendTo { - pp := p - ll := list - enc, _ := rlp.EncodeToBytes(ll) - - reqID := rand.Uint64() - rq := &distReq{ - getCost: func(dp distPeer) uint64 { - peer := dp.(*serverPeer) - return peer.getTxRelayCost(len(ll), len(enc)) - }, - canSend: func(dp distPeer) bool { - return !dp.(*serverPeer).onlyAnnounce && dp.(*serverPeer) == pp - }, - request: func(dp distPeer) func() { - peer := dp.(*serverPeer) - cost := peer.getTxRelayCost(len(ll), len(enc)) - peer.fcServer.QueuedRequest(reqID, cost) - return func() { peer.sendTxs(reqID, len(ll), enc) } - }, - } - go ltrx.retriever.retrieve(context.Background(), reqID, rq, func(p distPeer, msg *Msg) error { return nil }, ltrx.stop) - } -} - -func (ltrx *lesTxRelay) Send(txs types.Transactions) { - ltrx.lock.Lock() - defer ltrx.lock.Unlock() - - ltrx.send(txs, 3) -} - -func (ltrx *lesTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash) { - ltrx.lock.Lock() - defer ltrx.lock.Unlock() - - for _, hash := range mined { - delete(ltrx.txPending, hash) - } - - for _, hash := range rollback { - ltrx.txPending[hash] = struct{}{} - } - - if len(ltrx.txPending) > 0 { - txs := make(types.Transactions, len(ltrx.txPending)) - i := 0 - for hash := range ltrx.txPending { - txs[i] = ltrx.txSent[hash] - i++ - } - ltrx.send(txs, 1) - } -} - -func (ltrx *lesTxRelay) Discard(hashes []common.Hash) { - ltrx.lock.Lock() - defer ltrx.lock.Unlock() - - for _, hash := range hashes { - delete(ltrx.txSent, hash) - delete(ltrx.txPending, hash) - } -} diff --git a/les/utils/exec_queue.go b/les/utils/exec_queue.go deleted file mode 100644 index 5942b06ec..000000000 --- a/les/utils/exec_queue.go +++ /dev/null @@ -1,105 +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 utils - -import "sync" - -// ExecQueue implements a queue that executes function calls in a single thread, -// in the same order as they have been queued. -type ExecQueue struct { - mu sync.Mutex - cond *sync.Cond - funcs []func() - closeWait chan struct{} -} - -// NewExecQueue creates a new execution Queue. -func NewExecQueue(capacity int) *ExecQueue { - q := &ExecQueue{funcs: make([]func(), 0, capacity)} - q.cond = sync.NewCond(&q.mu) - go q.loop() - return q -} - -func (q *ExecQueue) loop() { - for f := q.waitNext(false); f != nil; f = q.waitNext(true) { - f() - } - close(q.closeWait) -} - -func (q *ExecQueue) waitNext(drop bool) (f func()) { - q.mu.Lock() - if drop && len(q.funcs) > 0 { - // Remove the function that just executed. We do this here instead of when - // dequeuing so len(q.funcs) includes the function that is running. - q.funcs = append(q.funcs[:0], q.funcs[1:]...) - } - for !q.isClosed() { - if len(q.funcs) > 0 { - f = q.funcs[0] - break - } - q.cond.Wait() - } - q.mu.Unlock() - return f -} - -func (q *ExecQueue) isClosed() bool { - return q.closeWait != nil -} - -// CanQueue returns true if more function calls can be added to the execution Queue. -func (q *ExecQueue) CanQueue() bool { - q.mu.Lock() - ok := !q.isClosed() && len(q.funcs) < cap(q.funcs) - q.mu.Unlock() - return ok -} - -// Queue adds a function call to the execution Queue. Returns true if successful. -func (q *ExecQueue) Queue(f func()) bool { - q.mu.Lock() - ok := !q.isClosed() && len(q.funcs) < cap(q.funcs) - if ok { - q.funcs = append(q.funcs, f) - q.cond.Signal() - } - q.mu.Unlock() - return ok -} - -// Clear drops all queued functions. -func (q *ExecQueue) Clear() { - q.mu.Lock() - q.funcs = q.funcs[:0] - q.mu.Unlock() -} - -// Quit stops the exec Queue. -// -// Quit waits for the current execution to finish before returning. -func (q *ExecQueue) Quit() { - q.mu.Lock() - if !q.isClosed() { - q.closeWait = make(chan struct{}) - q.cond.Signal() - } - q.mu.Unlock() - <-q.closeWait -} diff --git a/les/utils/exec_queue_test.go b/les/utils/exec_queue_test.go deleted file mode 100644 index 98601c448..000000000 --- a/les/utils/exec_queue_test.go +++ /dev/null @@ -1,60 +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 utils - -import "testing" - -func TestExecQueue(t *testing.T) { - var ( - N = 10000 - q = NewExecQueue(N) - counter int - execd = make(chan int) - testexit = make(chan struct{}) - ) - defer q.Quit() - defer close(testexit) - - check := func(state string, wantOK bool) { - c := counter - counter++ - qf := func() { - select { - case execd <- c: - case <-testexit: - } - } - if q.CanQueue() != wantOK { - t.Fatalf("CanQueue() == %t for %s", !wantOK, state) - } - if q.Queue(qf) != wantOK { - t.Fatalf("Queue() == %t for %s", !wantOK, state) - } - } - - for i := 0; i < N; i++ { - check("queue below cap", true) - } - check("full queue", false) - for i := 0; i < N; i++ { - if c := <-execd; c != i { - t.Fatal("execution out of order") - } - } - q.Quit() - check("closed queue", false) -} diff --git a/les/utils/expiredvalue.go b/les/utils/expiredvalue.go deleted file mode 100644 index 099b61d05..000000000 --- a/les/utils/expiredvalue.go +++ /dev/null @@ -1,270 +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 utils - -import ( - "math" - "sync" - - "github.com/ethereum/go-ethereum/common/mclock" -) - -// ExpiredValue is a scalar value that is continuously expired (decreased -// exponentially) based on the provided logarithmic expiration offset value. -// -// The formula for value calculation is: base*2^(exp-logOffset). In order to -// simplify the calculation of ExpiredValue, its value is expressed in the form -// of an exponent with a base of 2. -// -// Also here is a trick to reduce a lot of calculations. In theory, when a value X -// decays over time and then a new value Y is added, the final result should be -// X*2^(exp-logOffset)+Y. However it's very hard to represent in memory. -// So the trick is using the idea of inflation instead of exponential decay. At this -// moment the temporary value becomes: X*2^exp+Y*2^logOffset_1, apply the exponential -// decay when we actually want to calculate the value. -// -// e.g. -// t0: V = 100 -// t1: add 30, inflationary value is: 100 + 30/0.3, 0.3 is the decay coefficient -// t2: get value, decay coefficient is 0.2 now, final result is: 200*0.2 = 40 -type ExpiredValue struct { - Base, Exp uint64 // rlp encoding works by default -} - -// ExpirationFactor is calculated from logOffset. 1 <= Factor < 2 and Factor*2^Exp -// describes the multiplier applicable for additions and the divider for readouts. -// If logOffset changes slowly then it saves some expensive operations to not calculate -// them for each addition and readout but cache this intermediate form for some time. -// It is also useful for structures where multiple values are expired with the same -// Expirer. -type ExpirationFactor struct { - Exp uint64 - Factor float64 -} - -// ExpFactor calculates ExpirationFactor based on logOffset -func ExpFactor(logOffset Fixed64) ExpirationFactor { - return ExpirationFactor{Exp: logOffset.ToUint64(), Factor: logOffset.Fraction().Pow2()} -} - -// Value calculates the expired value based on a floating point base and integer -// power-of-2 exponent. This function should be used by multi-value expired structures. -func (e ExpirationFactor) Value(base float64, exp uint64) float64 { - return base / e.Factor * math.Pow(2, float64(int64(exp-e.Exp))) -} - -// Value calculates the value at the given moment. -func (e ExpiredValue) Value(logOffset Fixed64) uint64 { - offset := Uint64ToFixed64(e.Exp) - logOffset - return uint64(float64(e.Base) * offset.Pow2()) -} - -// Add adds a signed value at the given moment -func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 { - integer, frac := logOffset.ToUint64(), logOffset.Fraction() - factor := frac.Pow2() - base := factor * float64(amount) - if integer < e.Exp { - base /= math.Pow(2, float64(e.Exp-integer)) - } - if integer > e.Exp { - e.Base >>= (integer - e.Exp) - e.Exp = integer - } - if base >= 0 || uint64(-base) <= e.Base { - // The conversion from negative float64 to - // uint64 is undefined in golang, and doesn't - // work with ARMv8. More details at: - // https://github.com/golang/go/issues/43047 - if base >= 0 { - e.Base += uint64(base) - } else { - e.Base -= uint64(-base) - } - return amount - } - net := int64(-float64(e.Base) / factor) - e.Base = 0 - return net -} - -// AddExp adds another ExpiredValue -func (e *ExpiredValue) AddExp(a ExpiredValue) { - if e.Exp > a.Exp { - a.Base >>= (e.Exp - a.Exp) - } - if e.Exp < a.Exp { - e.Base >>= (a.Exp - e.Exp) - e.Exp = a.Exp - } - e.Base += a.Base -} - -// SubExp subtracts another ExpiredValue -func (e *ExpiredValue) SubExp(a ExpiredValue) { - if e.Exp > a.Exp { - a.Base >>= (e.Exp - a.Exp) - } - if e.Exp < a.Exp { - e.Base >>= (a.Exp - e.Exp) - e.Exp = a.Exp - } - if e.Base > a.Base { - e.Base -= a.Base - } else { - e.Base = 0 - } -} - -// IsZero returns true if the value is zero -func (e *ExpiredValue) IsZero() bool { - return e.Base == 0 -} - -// LinearExpiredValue is very similar with the expiredValue which the value -// will continuously expired. But the different part is it's expired linearly. -type LinearExpiredValue struct { - Offset uint64 // The latest time offset - Val uint64 // The remaining value, can never be negative - Rate mclock.AbsTime `rlp:"-"` // Expiration rate(by nanosecond), will ignored by RLP -} - -// Value calculates the value at the given moment. This function always has the -// assumption that the given timestamp shouldn't less than the recorded one. -func (e LinearExpiredValue) Value(now mclock.AbsTime) uint64 { - offset := uint64(now / e.Rate) - if e.Offset < offset { - diff := offset - e.Offset - if e.Val >= diff { - e.Val -= diff - } else { - e.Val = 0 - } - } - return e.Val -} - -// Add adds a signed value at the given moment. This function always has the -// assumption that the given timestamp shouldn't less than the recorded one. -func (e *LinearExpiredValue) Add(amount int64, now mclock.AbsTime) uint64 { - offset := uint64(now / e.Rate) - if e.Offset < offset { - diff := offset - e.Offset - if e.Val >= diff { - e.Val -= diff - } else { - e.Val = 0 - } - e.Offset = offset - } - if amount < 0 && uint64(-amount) > e.Val { - e.Val = 0 - } else { - e.Val = uint64(int64(e.Val) + amount) - } - return e.Val -} - -// ValueExpirer controls value expiration rate -type ValueExpirer interface { - SetRate(now mclock.AbsTime, rate float64) - SetLogOffset(now mclock.AbsTime, logOffset Fixed64) - LogOffset(now mclock.AbsTime) Fixed64 -} - -// Expirer changes logOffset with a linear rate which can be changed during operation. -// It is not thread safe, if access by multiple goroutines is needed then it should be -// encapsulated into a locked structure. -// Note that if neither SetRate nor SetLogOffset are used during operation then LogOffset -// is thread safe. -type Expirer struct { - lock sync.RWMutex - logOffset Fixed64 - rate float64 - lastUpdate mclock.AbsTime -} - -// SetRate changes the expiration rate which is the inverse of the time constant in -// nanoseconds. -func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) { - e.lock.Lock() - defer e.lock.Unlock() - - dt := now - e.lastUpdate - if dt > 0 { - e.logOffset += Fixed64(logToFixedFactor * float64(dt) * e.rate) - } - e.lastUpdate = now - e.rate = rate -} - -// SetLogOffset sets logOffset instantly. -func (e *Expirer) SetLogOffset(now mclock.AbsTime, logOffset Fixed64) { - e.lock.Lock() - defer e.lock.Unlock() - - e.lastUpdate = now - e.logOffset = logOffset -} - -// LogOffset returns the current logarithmic offset. -func (e *Expirer) LogOffset(now mclock.AbsTime) Fixed64 { - e.lock.RLock() - defer e.lock.RUnlock() - - dt := now - e.lastUpdate - if dt <= 0 { - return e.logOffset - } - return e.logOffset + Fixed64(logToFixedFactor*float64(dt)*e.rate) -} - -// fixedFactor is the fixed point multiplier factor used by Fixed64. -const fixedFactor = 0x1000000 - -// Fixed64 implements 64-bit fixed point arithmetic functions. -type Fixed64 int64 - -// Uint64ToFixed64 converts uint64 integer to Fixed64 format. -func Uint64ToFixed64(f uint64) Fixed64 { - return Fixed64(f * fixedFactor) -} - -// Float64ToFixed64 converts float64 to Fixed64 format. -func Float64ToFixed64(f float64) Fixed64 { - return Fixed64(f * fixedFactor) -} - -// ToUint64 converts Fixed64 format to uint64. -func (f64 Fixed64) ToUint64() uint64 { - return uint64(f64) / fixedFactor -} - -// Fraction returns the fractional part of a Fixed64 value. -func (f64 Fixed64) Fraction() Fixed64 { - return f64 % fixedFactor -} - -var ( - logToFixedFactor = float64(fixedFactor) / math.Log(2) - fixedToLogFactor = math.Log(2) / float64(fixedFactor) -) - -// Pow2 returns the base 2 power of the fixed point value. -func (f64 Fixed64) Pow2() float64 { - return math.Exp(float64(f64) * fixedToLogFactor) -} diff --git a/les/utils/expiredvalue_test.go b/les/utils/expiredvalue_test.go deleted file mode 100644 index 1c751d8cc..000000000 --- a/les/utils/expiredvalue_test.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 utils - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common/mclock" -) - -func TestValueExpiration(t *testing.T) { - var cases = []struct { - input ExpiredValue - timeOffset Fixed64 - expect uint64 - }{ - {ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 128}, - {ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(1), 64}, - {ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(2), 32}, - {ExpiredValue{Base: 128, Exp: 2}, Uint64ToFixed64(2), 128}, - {ExpiredValue{Base: 128, Exp: 2}, Uint64ToFixed64(3), 64}, - } - for _, c := range cases { - if got := c.input.Value(c.timeOffset); got != c.expect { - t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got) - } - } -} - -func TestValueAddition(t *testing.T) { - var cases = []struct { - input ExpiredValue - addend int64 - timeOffset Fixed64 - expect uint64 - expectNet int64 - }{ - // Addition - {ExpiredValue{Base: 128, Exp: 0}, 128, Uint64ToFixed64(0), 256, 128}, - {ExpiredValue{Base: 128, Exp: 2}, 128, Uint64ToFixed64(0), 640, 128}, - - // Addition with offset - {ExpiredValue{Base: 128, Exp: 0}, 128, Uint64ToFixed64(1), 192, 128}, - {ExpiredValue{Base: 128, Exp: 2}, 128, Uint64ToFixed64(1), 384, 128}, - {ExpiredValue{Base: 128, Exp: 2}, 128, Uint64ToFixed64(3), 192, 128}, - - // Subtraction - {ExpiredValue{Base: 128, Exp: 0}, -64, Uint64ToFixed64(0), 64, -64}, - {ExpiredValue{Base: 128, Exp: 0}, -128, Uint64ToFixed64(0), 0, -128}, - {ExpiredValue{Base: 128, Exp: 0}, -192, Uint64ToFixed64(0), 0, -128}, - - // Subtraction with offset - {ExpiredValue{Base: 128, Exp: 0}, -64, Uint64ToFixed64(1), 0, -64}, - {ExpiredValue{Base: 128, Exp: 0}, -128, Uint64ToFixed64(1), 0, -64}, - {ExpiredValue{Base: 128, Exp: 2}, -128, Uint64ToFixed64(1), 128, -128}, - {ExpiredValue{Base: 128, Exp: 2}, -128, Uint64ToFixed64(2), 0, -128}, - } - for _, c := range cases { - if net := c.input.Add(c.addend, c.timeOffset); net != c.expectNet { - t.Fatalf("Net amount mismatch, want=%d, got=%d", c.expectNet, net) - } - if got := c.input.Value(c.timeOffset); got != c.expect { - t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got) - } - } -} - -func TestExpiredValueAddition(t *testing.T) { - var cases = []struct { - input ExpiredValue - another ExpiredValue - timeOffset Fixed64 - expect uint64 - }{ - {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 256}, - {ExpiredValue{Base: 128, Exp: 1}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 384}, - {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 1}, Uint64ToFixed64(0), 384}, - {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(1), 128}, - } - for _, c := range cases { - c.input.AddExp(c.another) - if got := c.input.Value(c.timeOffset); got != c.expect { - t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got) - } - } -} - -func TestExpiredValueSubtraction(t *testing.T) { - var cases = []struct { - input ExpiredValue - another ExpiredValue - timeOffset Fixed64 - expect uint64 - }{ - {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 0}, - {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 1}, Uint64ToFixed64(0), 0}, - {ExpiredValue{Base: 128, Exp: 1}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 128}, - {ExpiredValue{Base: 128, Exp: 1}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(1), 64}, - } - for _, c := range cases { - c.input.SubExp(c.another) - if got := c.input.Value(c.timeOffset); got != c.expect { - t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got) - } - } -} - -func TestLinearExpiredValue(t *testing.T) { - var cases = []struct { - value LinearExpiredValue - now mclock.AbsTime - expect uint64 - }{ - {LinearExpiredValue{ - Offset: 0, - Val: 0, - Rate: mclock.AbsTime(1), - }, 0, 0}, - - {LinearExpiredValue{ - Offset: 1, - Val: 1, - Rate: mclock.AbsTime(1), - }, 0, 1}, - - {LinearExpiredValue{ - Offset: 1, - Val: 1, - Rate: mclock.AbsTime(1), - }, mclock.AbsTime(2), 0}, - - {LinearExpiredValue{ - Offset: 1, - Val: 1, - Rate: mclock.AbsTime(1), - }, mclock.AbsTime(3), 0}, - } - for _, c := range cases { - if value := c.value.Value(c.now); value != c.expect { - t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, value) - } - } -} - -func TestLinearExpiredAddition(t *testing.T) { - var cases = []struct { - value LinearExpiredValue - amount int64 - now mclock.AbsTime - expect uint64 - }{ - {LinearExpiredValue{ - Offset: 0, - Val: 0, - Rate: mclock.AbsTime(1), - }, -1, 0, 0}, - - {LinearExpiredValue{ - Offset: 1, - Val: 1, - Rate: mclock.AbsTime(1), - }, -1, 0, 0}, - - {LinearExpiredValue{ - Offset: 1, - Val: 2, - Rate: mclock.AbsTime(1), - }, -1, mclock.AbsTime(2), 0}, - - {LinearExpiredValue{ - Offset: 1, - Val: 2, - Rate: mclock.AbsTime(1), - }, -2, mclock.AbsTime(2), 0}, - } - for _, c := range cases { - if value := c.value.Add(c.amount, c.now); value != c.expect { - t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, value) - } - } -} diff --git a/les/utils/limiter.go b/les/utils/limiter.go deleted file mode 100644 index 70b7ff64f..000000000 --- a/les/utils/limiter.go +++ /dev/null @@ -1,398 +0,0 @@ -// Copyright 2021 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package utils - -import ( - "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 - -// Limiter protects a network request serving mechanism from denial-of-service attacks. -// It limits the total amount of resources used for serving requests while ensuring that -// the most valuable connections always have a reasonable chance of being served. -type Limiter struct { - lock sync.Mutex - cond *sync.Cond - quit bool - - nodes map[enode.ID]*nodeQueue - addresses map[string]*addressGroup - addressSelect, valueSelect *WeightedRandomSelect - maxValue float64 - maxCost, sumCost, sumCostLimit uint - selectAddressNext bool -} - -// nodeQueue represents queued requests coming from a single node ID -type nodeQueue struct { - queue []request // always nil if penaltyCost != 0 - id enode.ID - address string - value float64 - flatWeight, valueWeight uint64 // current selection weights in the address/value selectors - sumCost uint // summed cost of requests queued by the node - penaltyCost uint // cumulative cost of dropped requests since last processed request - groupIndex int -} - -// addressGroup is a group of node IDs that have sent their last requests from the same -// network address -type addressGroup struct { - nodes []*nodeQueue - nodeSelect *WeightedRandomSelect - sumFlatWeight, groupWeight uint64 -} - -// request represents an incoming request scheduled for processing -type request struct { - process chan chan struct{} - cost uint -} - -// flatWeight distributes weights equally between each active network address -func flatWeight(item interface{}) uint64 { return item.(*nodeQueue).flatWeight } - -// add adds the node queue to the address group. It is the caller's responsibility to -// add the address group to the address map and the address selector if it wasn't -// there before. -func (ag *addressGroup) add(nq *nodeQueue) { - if nq.groupIndex != -1 { - panic("added node queue is already in an address group") - } - l := len(ag.nodes) - nq.groupIndex = l - ag.nodes = append(ag.nodes, nq) - ag.sumFlatWeight += nq.flatWeight - ag.groupWeight = ag.sumFlatWeight / uint64(l+1) - ag.nodeSelect.Update(ag.nodes[l]) -} - -// update updates the selection weight of the node queue inside the address group. -// It is the caller's responsibility to update the group's selection weight in the -// address selector. -func (ag *addressGroup) update(nq *nodeQueue, weight uint64) { - if nq.groupIndex == -1 || nq.groupIndex >= len(ag.nodes) || ag.nodes[nq.groupIndex] != nq { - panic("updated node queue is not in this address group") - } - ag.sumFlatWeight += weight - nq.flatWeight - nq.flatWeight = weight - ag.groupWeight = ag.sumFlatWeight / uint64(len(ag.nodes)) - ag.nodeSelect.Update(nq) -} - -// remove removes the node queue from the address group. It is the caller's responsibility -// to remove the address group from the address map if it is empty. -func (ag *addressGroup) remove(nq *nodeQueue) { - if nq.groupIndex == -1 || nq.groupIndex >= len(ag.nodes) || ag.nodes[nq.groupIndex] != nq { - panic("removed node queue is not in this address group") - } - - l := len(ag.nodes) - 1 - if nq.groupIndex != l { - ag.nodes[nq.groupIndex] = ag.nodes[l] - ag.nodes[nq.groupIndex].groupIndex = nq.groupIndex - } - nq.groupIndex = -1 - ag.nodes = ag.nodes[:l] - ag.sumFlatWeight -= nq.flatWeight - if l >= 1 { - ag.groupWeight = ag.sumFlatWeight / uint64(l) - } else { - ag.groupWeight = 0 - } - ag.nodeSelect.Remove(nq) -} - -// choose selects one of the node queues belonging to the address group -func (ag *addressGroup) choose() *nodeQueue { - return ag.nodeSelect.Choose().(*nodeQueue) -} - -// NewLimiter creates a new Limiter -func NewLimiter(sumCostLimit uint) *Limiter { - l := &Limiter{ - addressSelect: NewWeightedRandomSelect(func(item interface{}) uint64 { return item.(*addressGroup).groupWeight }), - valueSelect: NewWeightedRandomSelect(func(item interface{}) uint64 { return item.(*nodeQueue).valueWeight }), - nodes: make(map[enode.ID]*nodeQueue), - addresses: make(map[string]*addressGroup), - sumCostLimit: sumCostLimit, - } - l.cond = sync.NewCond(&l.lock) - go l.processLoop() - return l -} - -// selectionWeights calculates the selection weights of a node for both the address and -// the value selector. The selection weight depends on the next request cost or the -// summed cost of recently dropped requests. -func (l *Limiter) selectionWeights(reqCost uint, value float64) (flatWeight, valueWeight uint64) { - if value > l.maxValue { - l.maxValue = value - } - if value > 0 { - // normalize value to <= 1 - value /= l.maxValue - } - if reqCost > l.maxCost { - l.maxCost = reqCost - } - relCost := float64(reqCost) / float64(l.maxCost) - var f float64 - if relCost <= 0.001 { - f = 1 - } else { - f = 0.001 / relCost - } - f *= maxSelectionWeight - flatWeight, valueWeight = uint64(f), uint64(f*value) - if flatWeight == 0 { - flatWeight = 1 - } - return -} - -// Add adds a new request to the node queue belonging to the given id. Value belongs -// to the requesting node. A higher value gives the request a higher chance of being -// served quickly in case of heavy load or a DDoS attack. Cost is a rough estimate -// of the serving cost of the request. A lower cost also gives the request a -// better chance. -func (l *Limiter) Add(id enode.ID, address string, value float64, reqCost uint) chan chan struct{} { - l.lock.Lock() - defer l.lock.Unlock() - - process := make(chan chan struct{}, 1) - if l.quit { - close(process) - return process - } - if reqCost == 0 { - reqCost = 1 - } - if nq, ok := l.nodes[id]; ok { - if nq.queue != nil { - nq.queue = append(nq.queue, request{process, reqCost}) - nq.sumCost += reqCost - nq.value = value - if address != nq.address { - // known id sending request from a new address, move to different address group - l.removeFromGroup(nq) - l.addToGroup(nq, address) - } - } else { - // already waiting on a penalty, just add to the penalty cost and drop the request - nq.penaltyCost += reqCost - l.update(nq) - close(process) - return process - } - } else { - nq := &nodeQueue{ - queue: []request{{process, reqCost}}, - id: id, - value: value, - sumCost: reqCost, - groupIndex: -1, - } - nq.flatWeight, nq.valueWeight = l.selectionWeights(reqCost, value) - if len(l.nodes) == 0 { - l.cond.Signal() - } - l.nodes[id] = nq - if nq.valueWeight != 0 { - l.valueSelect.Update(nq) - } - l.addToGroup(nq, address) - } - l.sumCost += reqCost - if l.sumCost > l.sumCostLimit { - l.dropRequests() - } - return process -} - -// update updates the selection weights of the node queue -func (l *Limiter) update(nq *nodeQueue) { - var cost uint - if nq.queue != nil { - cost = nq.queue[0].cost - } else { - cost = nq.penaltyCost - } - flatWeight, valueWeight := l.selectionWeights(cost, nq.value) - ag := l.addresses[nq.address] - ag.update(nq, flatWeight) - l.addressSelect.Update(ag) - nq.valueWeight = valueWeight - l.valueSelect.Update(nq) -} - -// addToGroup adds the node queue to the given address group. The group is created if -// it does not exist yet. -func (l *Limiter) addToGroup(nq *nodeQueue, address string) { - nq.address = address - ag := l.addresses[address] - if ag == nil { - ag = &addressGroup{nodeSelect: NewWeightedRandomSelect(flatWeight)} - l.addresses[address] = ag - } - ag.add(nq) - l.addressSelect.Update(ag) -} - -// removeFromGroup removes the node queue from its address group -func (l *Limiter) removeFromGroup(nq *nodeQueue) { - ag := l.addresses[nq.address] - ag.remove(nq) - if len(ag.nodes) == 0 { - delete(l.addresses, nq.address) - } - l.addressSelect.Update(ag) -} - -// remove removes the node queue from its address group, the nodes map and the value -// selector -func (l *Limiter) remove(nq *nodeQueue) { - l.removeFromGroup(nq) - if nq.valueWeight != 0 { - l.valueSelect.Remove(nq) - } - delete(l.nodes, nq.id) -} - -// choose selects the next node queue to process. -func (l *Limiter) choose() *nodeQueue { - if l.valueSelect.IsEmpty() || l.selectAddressNext { - if ag, ok := l.addressSelect.Choose().(*addressGroup); ok { - l.selectAddressNext = false - return ag.choose() - } - } - nq, _ := l.valueSelect.Choose().(*nodeQueue) - l.selectAddressNext = true - return nq -} - -// processLoop processes requests sequentially -func (l *Limiter) processLoop() { - l.lock.Lock() - defer l.lock.Unlock() - - for { - if l.quit { - for _, nq := range l.nodes { - for _, request := range nq.queue { - close(request.process) - } - } - return - } - nq := l.choose() - if nq == nil { - l.cond.Wait() - continue - } - if nq.queue != nil { - request := nq.queue[0] - nq.queue = nq.queue[1:] - nq.sumCost -= request.cost - l.sumCost -= request.cost - l.lock.Unlock() - ch := make(chan struct{}) - request.process <- ch - <-ch - l.lock.Lock() - if len(nq.queue) > 0 { - l.update(nq) - } else { - l.remove(nq) - } - } else { - // penalized queue removed, next request will be added to a clean queue - l.remove(nq) - } - } -} - -// Stop stops the processing loop. All queued and future requests are rejected. -func (l *Limiter) Stop() { - l.lock.Lock() - defer l.lock.Unlock() - - l.quit = true - l.cond.Signal() -} - -type dropListItem struct { - nq *nodeQueue - priority float64 -} - -// dropRequests selects the nodes with the highest queued request cost to selection -// weight ratio and drops their queued request. The empty node queues stay in the -// selectors with a low selection weight in order to penalize these nodes. -func (l *Limiter) dropRequests() { - var ( - sumValue float64 - list []dropListItem - ) - for _, nq := range l.nodes { - sumValue += nq.value - } - for _, nq := range l.nodes { - if nq.sumCost == 0 { - continue - } - w := 1 / float64(len(l.addresses)*len(l.addresses[nq.address].nodes)) - if sumValue > 0 { - w += nq.value / sumValue - } - list = append(list, dropListItem{ - nq: nq, - priority: w / float64(nq.sumCost), - }) - } - 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) - } - // make the queue penalized; no more requests are accepted until the node is - // selected based on the penalty cost which is the cumulative cost of all dropped - // requests. This ensures that sending excess requests is always penalized - // and incentivizes the sender to stop for a while if no replies are received. - item.nq.queue = nil - item.nq.penaltyCost = item.nq.sumCost - l.sumCost -= item.nq.sumCost // penalty costs are not counted in sumCost - item.nq.sumCost = 0 - l.update(item.nq) - if l.sumCost <= l.sumCostLimit/2 { - return - } - } -} diff --git a/les/utils/limiter_test.go b/les/utils/limiter_test.go deleted file mode 100644 index c031b21de..000000000 --- a/les/utils/limiter_test.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2021 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package utils - -import ( - "crypto/rand" - "testing" - - "github.com/ethereum/go-ethereum/p2p/enode" -) - -const ( - ltTolerance = 0.03 - ltRounds = 7 -) - -type ( - ltNode struct { - addr, id int - value, exp float64 - cost uint - reqRate float64 - reqMax, runCount int - lastTotalCost uint - - served, dropped int - } - - ltResult struct { - node *ltNode - ch chan struct{} - } - - limTest struct { - limiter *Limiter - results chan ltResult - runCount int - expCost, totalCost uint - } -) - -func (lt *limTest) request(n *ltNode) { - var ( - address string - id enode.ID - ) - if n.addr >= 0 { - address = string([]byte{byte(n.addr)}) - } else { - var b [32]byte - rand.Read(b[:]) - address = string(b[:]) - } - if n.id >= 0 { - id = enode.ID{byte(n.id)} - } else { - rand.Read(id[:]) - } - lt.runCount++ - n.runCount++ - cch := lt.limiter.Add(id, address, n.value, n.cost) - go func() { - lt.results <- ltResult{n, <-cch} - }() -} - -func (lt *limTest) moreRequests(n *ltNode) { - maxStart := int(float64(lt.totalCost-n.lastTotalCost) * n.reqRate) - if maxStart != 0 { - n.lastTotalCost = lt.totalCost - } - for n.reqMax > n.runCount && maxStart > 0 { - lt.request(n) - maxStart-- - } -} - -func (lt *limTest) process() { - res := <-lt.results - lt.runCount-- - res.node.runCount-- - if res.ch != nil { - res.node.served++ - if res.node.exp != 0 { - lt.expCost += res.node.cost - } - lt.totalCost += res.node.cost - close(res.ch) - } else { - res.node.dropped++ - } -} - -func TestLimiter(t *testing.T) { - limTests := [][]*ltNode{ - { // one id from an individual address and two ids from a shared address - {addr: 0, id: 0, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.5}, - {addr: 1, id: 1, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, - {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, - }, - { // varying request costs - {addr: 0, id: 0, value: 0, cost: 10, reqRate: 0.2, reqMax: 1, exp: 0.5}, - {addr: 1, id: 1, value: 0, cost: 3, reqRate: 0.5, reqMax: 1, exp: 0.25}, - {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, - }, - { // different request rate - {addr: 0, id: 0, value: 0, cost: 1, reqRate: 2, reqMax: 2, exp: 0.5}, - {addr: 1, id: 1, value: 0, cost: 1, reqRate: 10, reqMax: 10, exp: 0.25}, - {addr: 1, id: 2, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25}, - }, - { // adding value - {addr: 0, id: 0, value: 3, cost: 1, reqRate: 1, reqMax: 1, exp: (0.5 + 0.3) / 2}, - {addr: 1, id: 1, value: 0, cost: 1, reqRate: 1, reqMax: 1, exp: 0.25 / 2}, - {addr: 1, id: 2, value: 7, cost: 1, reqRate: 1, reqMax: 1, exp: (0.25 + 0.7) / 2}, - }, - { // DoS attack from a single address with a single id - {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, - {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, - {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, - {addr: 3, id: 3, value: 0, cost: 1, reqRate: 10, reqMax: 1000000000, exp: 0}, - }, - { // DoS attack from a single address with different ids - {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, - {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, - {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, - {addr: 3, id: -1, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0}, - }, - { // DDoS attack from different addresses with a single id - {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, - {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, - {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, - {addr: -1, id: 3, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0}, - }, - { // DDoS attack from different addresses with different ids - {addr: 0, id: 0, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, - {addr: 1, id: 1, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, - {addr: 2, id: 2, value: 1, cost: 1, reqRate: 1, reqMax: 1, exp: 0.3333}, - {addr: -1, id: -1, value: 0, cost: 1, reqRate: 1, reqMax: 1000000000, exp: 0}, - }, - } - - lt := &limTest{ - limiter: NewLimiter(100), - results: make(chan ltResult), - } - for _, test := range limTests { - lt.expCost, lt.totalCost = 0, 0 - iterCount := 10000 - for j := 0; j < ltRounds; j++ { - // try to reach expected target range in multiple rounds with increasing iteration counts - last := j == ltRounds-1 - for _, n := range test { - lt.request(n) - } - for i := 0; i < iterCount; i++ { - lt.process() - for _, n := range test { - lt.moreRequests(n) - } - } - for lt.runCount > 0 { - lt.process() - } - if spamRatio := 1 - float64(lt.expCost)/float64(lt.totalCost); spamRatio > 0.5*(1+ltTolerance) { - t.Errorf("Spam ratio too high (%f)", spamRatio) - } - fail, success := false, true - for _, n := range test { - if n.exp != 0 { - if n.dropped > 0 { - t.Errorf("Dropped %d requests of non-spam node", n.dropped) - fail = true - } - r := float64(n.served) * float64(n.cost) / float64(lt.expCost) - if r < n.exp*(1-ltTolerance) || r > n.exp*(1+ltTolerance) { - if last { - // print error only if the target is still not reached in the last round - t.Errorf("Request ratio (%f) does not match expected value (%f)", r, n.exp) - } - success = false - } - } - } - if fail || success { - break - } - // neither failed nor succeeded; try more iterations to reach probability targets - iterCount *= 2 - } - } - lt.limiter.Stop() -} diff --git a/les/utils/timeutils.go b/les/utils/timeutils.go deleted file mode 100644 index 62a4285d1..000000000 --- a/les/utils/timeutils.go +++ /dev/null @@ -1,69 +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 utils - -import ( - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" -) - -type UpdateTimer struct { - clock mclock.Clock - lock sync.Mutex - last mclock.AbsTime - threshold time.Duration -} - -func NewUpdateTimer(clock mclock.Clock, threshold time.Duration) *UpdateTimer { - // We don't accept the update threshold less than 0. - if threshold < 0 { - return nil - } - // Don't panic for lazy users - if clock == nil { - clock = mclock.System{} - } - return &UpdateTimer{ - clock: clock, - last: clock.Now(), - threshold: threshold, - } -} - -func (t *UpdateTimer) Update(callback func(diff time.Duration) bool) bool { - return t.UpdateAt(t.clock.Now(), callback) -} - -func (t *UpdateTimer) UpdateAt(at mclock.AbsTime, callback func(diff time.Duration) bool) bool { - t.lock.Lock() - defer t.lock.Unlock() - - diff := time.Duration(at - t.last) - if diff < 0 { - diff = 0 - } - if diff < t.threshold { - return false - } - if callback(diff) { - t.last = at - return true - } - return false -} diff --git a/les/utils/timeutils_test.go b/les/utils/timeutils_test.go deleted file mode 100644 index b219d0439..000000000 --- a/les/utils/timeutils_test.go +++ /dev/null @@ -1,47 +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 utils - -import ( - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" -) - -func TestUpdateTimer(t *testing.T) { - timer := NewUpdateTimer(mclock.System{}, -1) - if timer != nil { - t.Fatalf("Create update timer with negative threshold") - } - sim := &mclock.Simulated{} - timer = NewUpdateTimer(sim, time.Second) - if updated := timer.Update(func(diff time.Duration) bool { return true }); updated { - t.Fatalf("Update the clock without reaching the threshold") - } - sim.Run(time.Second) - if updated := timer.Update(func(diff time.Duration) bool { return true }); !updated { - t.Fatalf("Doesn't update the clock when reaching the threshold") - } - if updated := timer.UpdateAt(sim.Now().Add(time.Second), func(diff time.Duration) bool { return true }); !updated { - t.Fatalf("Doesn't update the clock when reaching the threshold") - } - timer = NewUpdateTimer(sim, 0) - if updated := timer.Update(func(diff time.Duration) bool { return true }); !updated { - t.Fatalf("Doesn't update the clock without threshold limitaion") - } -} diff --git a/les/utils/weighted_select.go b/les/utils/weighted_select.go deleted file mode 100644 index 486b00820..000000000 --- a/les/utils/weighted_select.go +++ /dev/null @@ -1,183 +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 utils - -import ( - "math" - "math/rand" - - "github.com/ethereum/go-ethereum/log" -) - -type ( - // WeightedRandomSelect is capable of weighted random selection from a set of items - WeightedRandomSelect struct { - root *wrsNode - idx map[WrsItem]int - wfn WeightFn - } - WrsItem interface{} - WeightFn func(interface{}) uint64 -) - -// NewWeightedRandomSelect returns a new WeightedRandomSelect structure -func NewWeightedRandomSelect(wfn WeightFn) *WeightedRandomSelect { - return &WeightedRandomSelect{root: &wrsNode{maxItems: wrsBranches}, idx: make(map[WrsItem]int), wfn: wfn} -} - -// Update updates an item's weight, adds it if it was non-existent or removes it if -// the new weight is zero. Note that explicitly updating decreasing weights is not necessary. -func (w *WeightedRandomSelect) Update(item WrsItem) { - w.setWeight(item, w.wfn(item)) -} - -// Remove removes an item from the set -func (w *WeightedRandomSelect) Remove(item WrsItem) { - w.setWeight(item, 0) -} - -// IsEmpty returns true if the set is empty -func (w *WeightedRandomSelect) IsEmpty() bool { - return w.root.sumCost == 0 -} - -// setWeight sets an item's weight to a specific value (removes it if zero) -func (w *WeightedRandomSelect) setWeight(item WrsItem, weight uint64) { - if weight > math.MaxInt64-w.root.sumCost { - // old weight is still included in sumCost, remove and check again - w.setWeight(item, 0) - if weight > math.MaxInt64-w.root.sumCost { - log.Error("WeightedRandomSelect overflow", "sumCost", w.root.sumCost, "new weight", weight) - weight = math.MaxInt64 - w.root.sumCost - } - } - idx, ok := w.idx[item] - if ok { - w.root.setWeight(idx, weight) - if weight == 0 { - delete(w.idx, item) - } - } else { - if weight != 0 { - if w.root.itemCnt == w.root.maxItems { - // add a new level - newRoot := &wrsNode{sumCost: w.root.sumCost, itemCnt: w.root.itemCnt, level: w.root.level + 1, maxItems: w.root.maxItems * wrsBranches} - newRoot.items[0] = w.root - newRoot.weights[0] = w.root.sumCost - w.root = newRoot - } - w.idx[item] = w.root.insert(item, weight) - } - } -} - -// Choose randomly selects an item from the set, with a chance proportional to its -// current weight. If the weight of the chosen element has been decreased since the -// last stored value, returns it with a newWeight/oldWeight chance, otherwise just -// updates its weight and selects another one -func (w *WeightedRandomSelect) Choose() WrsItem { - for { - if w.root.sumCost == 0 { - return nil - } - val := uint64(rand.Int63n(int64(w.root.sumCost))) - choice, lastWeight := w.root.choose(val) - weight := w.wfn(choice) - if weight != lastWeight { - w.setWeight(choice, weight) - } - if weight >= lastWeight || uint64(rand.Int63n(int64(lastWeight))) < weight { - return choice - } - } -} - -const wrsBranches = 8 // max number of branches in the wrsNode tree - -// wrsNode is a node of a tree structure that can store WrsItems or further wrsNodes. -type wrsNode struct { - items [wrsBranches]interface{} - weights [wrsBranches]uint64 - sumCost uint64 - level, itemCnt, maxItems int -} - -// insert recursively inserts a new item to the tree and returns the item index -func (n *wrsNode) insert(item WrsItem, weight uint64) int { - branch := 0 - for n.items[branch] != nil && (n.level == 0 || n.items[branch].(*wrsNode).itemCnt == n.items[branch].(*wrsNode).maxItems) { - branch++ - if branch == wrsBranches { - panic(nil) - } - } - n.itemCnt++ - n.sumCost += weight - n.weights[branch] += weight - if n.level == 0 { - n.items[branch] = item - return branch - } - var subNode *wrsNode - if n.items[branch] == nil { - subNode = &wrsNode{maxItems: n.maxItems / wrsBranches, level: n.level - 1} - n.items[branch] = subNode - } else { - subNode = n.items[branch].(*wrsNode) - } - subIdx := subNode.insert(item, weight) - return subNode.maxItems*branch + subIdx -} - -// setWeight updates the weight of a certain item (which should exist) and returns -// the change of the last weight value stored in the tree -func (n *wrsNode) setWeight(idx int, weight uint64) uint64 { - if n.level == 0 { - oldWeight := n.weights[idx] - n.weights[idx] = weight - diff := weight - oldWeight - n.sumCost += diff - if weight == 0 { - n.items[idx] = nil - n.itemCnt-- - } - return diff - } - branchItems := n.maxItems / wrsBranches - branch := idx / branchItems - diff := n.items[branch].(*wrsNode).setWeight(idx-branch*branchItems, weight) - n.weights[branch] += diff - n.sumCost += diff - if weight == 0 { - n.itemCnt-- - } - return diff -} - -// choose recursively selects an item from the tree and returns it along with its weight -func (n *wrsNode) choose(val uint64) (WrsItem, uint64) { - for i, w := range n.weights { - if val < w { - if n.level == 0 { - return n.items[i].(WrsItem), n.weights[i] - } - return n.items[i].(*wrsNode).choose(val) - } - val -= w - } - panic(nil) -} diff --git a/les/utils/weighted_select_test.go b/les/utils/weighted_select_test.go deleted file mode 100644 index 3e1c0ad98..000000000 --- a/les/utils/weighted_select_test.go +++ /dev/null @@ -1,68 +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 utils - -import ( - "math/rand" - "testing" -) - -type testWrsItem struct { - idx int - widx *int -} - -func testWeight(i interface{}) uint64 { - t := i.(*testWrsItem) - w := *t.widx - if w == -1 || w == t.idx { - return uint64(t.idx + 1) - } - return 0 -} - -func TestWeightedRandomSelect(t *testing.T) { - testFn := func(cnt int) { - s := NewWeightedRandomSelect(testWeight) - w := -1 - list := make([]testWrsItem, cnt) - for i := range list { - list[i] = testWrsItem{idx: i, widx: &w} - s.Update(&list[i]) - } - w = rand.Intn(cnt) - c := s.Choose() - if c == nil { - t.Errorf("expected item, got nil") - } else { - if c.(*testWrsItem).idx != w { - t.Errorf("expected another item") - } - } - w = -2 - if s.Choose() != nil { - t.Errorf("expected nil, got item") - } - } - testFn(1) - testFn(10) - testFn(100) - testFn(1000) - testFn(10000) - testFn(100000) - testFn(1000000) -} diff --git a/les/vflux/client/api.go b/les/vflux/client/api.go deleted file mode 100644 index 135273ef9..000000000 --- a/les/vflux/client/api.go +++ /dev/null @@ -1,107 +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 client - -import ( - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/p2p/enode" -) - -// PrivateClientAPI implements the vflux client side API -type PrivateClientAPI struct { - vt *ValueTracker -} - -// NewPrivateClientAPI creates a PrivateClientAPI -func NewPrivateClientAPI(vt *ValueTracker) *PrivateClientAPI { - return &PrivateClientAPI{vt} -} - -// parseNodeStr converts either an enode address or a plain hex node id to enode.ID -func parseNodeStr(nodeStr string) (enode.ID, error) { - if id, err := enode.ParseID(nodeStr); err == nil { - return id, nil - } - if node, err := enode.Parse(enode.ValidSchemes, nodeStr); err == nil { - return node.ID(), nil - } else { - return enode.ID{}, err - } -} - -// RequestStats returns the current contents of the reference request basket, with -// request values meaning average per request rather than total. -func (api *PrivateClientAPI) RequestStats() []RequestStatsItem { - return api.vt.RequestStats() -} - -// Distribution returns a distribution as a series of (X, Y) chart coordinates, -// where the X axis is the response time in seconds while the Y axis is the amount of -// service value received with a response time close to the X coordinate. -// The distribution is optionally normalized to a sum of 1. -// If nodeStr == "" then the global distribution is returned, otherwise the individual -// distribution of the specified server node. -func (api *PrivateClientAPI) Distribution(nodeStr string, normalized bool) (RtDistribution, error) { - var expFactor utils.ExpirationFactor - if !normalized { - expFactor = utils.ExpFactor(api.vt.StatsExpirer().LogOffset(mclock.Now())) - } - if nodeStr == "" { - return api.vt.RtStats().Distribution(normalized, expFactor), nil - } - if id, err := parseNodeStr(nodeStr); err == nil { - return api.vt.GetNode(id).RtStats().Distribution(normalized, expFactor), nil - } else { - return RtDistribution{}, err - } -} - -// Timeout suggests a timeout value based on either the global distribution or the -// distribution of the specified node. The parameter is the desired rate of timeouts -// assuming a similar distribution in the future. -// Note that the actual timeout should have a sensible minimum bound so that operating -// under ideal working conditions for a long time (for example, using a local server -// with very low response times) will not make it very hard for the system to accommodate -// longer response times in the future. -func (api *PrivateClientAPI) Timeout(nodeStr string, failRate float64) (float64, error) { - if nodeStr == "" { - return float64(api.vt.RtStats().Timeout(failRate)) / float64(time.Second), nil - } - if id, err := parseNodeStr(nodeStr); err == nil { - return float64(api.vt.GetNode(id).RtStats().Timeout(failRate)) / float64(time.Second), nil - } else { - return 0, err - } -} - -// Value calculates the total service value provided either globally or by the specified -// server node, using a weight function based on the given timeout. -func (api *PrivateClientAPI) Value(nodeStr string, timeout float64) (float64, error) { - wt := TimeoutWeights(time.Duration(timeout * float64(time.Second))) - expFactor := utils.ExpFactor(api.vt.StatsExpirer().LogOffset(mclock.Now())) - if nodeStr == "" { - return api.vt.RtStats().Value(wt, expFactor), nil - } - if id, err := parseNodeStr(nodeStr); err == nil { - return api.vt.GetNode(id).RtStats().Value(wt, expFactor), nil - } else { - return 0, err - } -} diff --git a/les/vflux/client/fillset.go b/les/vflux/client/fillset.go deleted file mode 100644 index 0da850bca..000000000 --- a/les/vflux/client/fillset.go +++ /dev/null @@ -1,107 +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 client - -import ( - "sync" - - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/nodestate" -) - -// FillSet tries to read nodes from an input iterator and add them to a node set by -// setting the specified node state flag(s) until the size of the set reaches the target. -// Note that other mechanisms (like other FillSet instances reading from different inputs) -// can also set the same flag(s) and FillSet will always care about the total number of -// nodes having those flags. -type FillSet struct { - lock sync.Mutex - cond *sync.Cond - ns *nodestate.NodeStateMachine - input enode.Iterator - closed bool - flags nodestate.Flags - count, target int -} - -// NewFillSet creates a new FillSet -func NewFillSet(ns *nodestate.NodeStateMachine, input enode.Iterator, flags nodestate.Flags) *FillSet { - fs := &FillSet{ - ns: ns, - input: input, - flags: flags, - } - fs.cond = sync.NewCond(&fs.lock) - - ns.SubscribeState(flags, func(n *enode.Node, oldState, newState nodestate.Flags) { - fs.lock.Lock() - if oldState.Equals(flags) { - fs.count-- - } - if newState.Equals(flags) { - fs.count++ - } - if fs.target > fs.count { - fs.cond.Signal() - } - fs.lock.Unlock() - }) - - go fs.readLoop() - return fs -} - -// readLoop keeps reading nodes from the input and setting the specified flags for them -// whenever the node set size is under the current target -func (fs *FillSet) readLoop() { - for { - fs.lock.Lock() - for fs.target <= fs.count && !fs.closed { - fs.cond.Wait() - } - - fs.lock.Unlock() - if !fs.input.Next() { - return - } - fs.ns.SetState(fs.input.Node(), fs.flags, nodestate.Flags{}, 0) - } -} - -// SetTarget sets the current target for node set size. If the previous target was not -// reached and FillSet was still waiting for the next node from the input then the next -// incoming node will be added to the set regardless of the target. This ensures that -// all nodes coming from the input are eventually added to the set. -func (fs *FillSet) SetTarget(target int) { - fs.lock.Lock() - defer fs.lock.Unlock() - - fs.target = target - if fs.target > fs.count { - fs.cond.Signal() - } -} - -// Close shuts FillSet down and closes the input iterator -func (fs *FillSet) Close() { - fs.lock.Lock() - defer fs.lock.Unlock() - - fs.closed = true - fs.input.Close() - fs.cond.Signal() -} diff --git a/les/vflux/client/fillset_test.go b/les/vflux/client/fillset_test.go deleted file mode 100644 index 652dcf9f6..000000000 --- a/les/vflux/client/fillset_test.go +++ /dev/null @@ -1,117 +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 client - -import ( - "crypto/rand" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" - "github.com/ethereum/go-ethereum/p2p/nodestate" -) - -type testIter struct { - waitCh chan struct{} - nodeCh chan *enode.Node - node *enode.Node -} - -func (i *testIter) Next() bool { - if _, ok := <-i.waitCh; !ok { - return false - } - i.node = <-i.nodeCh - return true -} - -func (i *testIter) Node() *enode.Node { - return i.node -} - -func (i *testIter) Close() { - close(i.waitCh) -} - -func (i *testIter) push() { - var id enode.ID - rand.Read(id[:]) - i.nodeCh <- enode.SignNull(new(enr.Record), id) -} - -func (i *testIter) waiting(timeout time.Duration) bool { - select { - case i.waitCh <- struct{}{}: - return true - case <-time.After(timeout): - return false - } -} - -func TestFillSet(t *testing.T) { - ns := nodestate.NewNodeStateMachine(nil, nil, &mclock.Simulated{}, testSetup) - iter := &testIter{ - waitCh: make(chan struct{}), - nodeCh: make(chan *enode.Node), - } - fs := NewFillSet(ns, iter, sfTest1) - ns.Start() - - expWaiting := func(i int, push bool) { - for ; i > 0; i-- { - if !iter.waiting(time.Second * 10) { - t.Fatalf("FillSet not waiting for new nodes") - } - if push { - iter.push() - } - } - } - - expNotWaiting := func() { - if iter.waiting(time.Millisecond * 100) { - t.Fatalf("FillSet unexpectedly waiting for new nodes") - } - } - - expNotWaiting() - fs.SetTarget(3) - expWaiting(3, true) - expNotWaiting() - fs.SetTarget(100) - expWaiting(2, true) - expWaiting(1, false) - // lower the target before the previous one has been filled up - fs.SetTarget(0) - iter.push() - expNotWaiting() - fs.SetTarget(10) - expWaiting(4, true) - expNotWaiting() - // remove all previously set flags - ns.ForEach(sfTest1, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - ns.SetState(node, nodestate.Flags{}, sfTest1, 0) - }) - // now expect FillSet to fill the set up again with 10 new nodes - expWaiting(10, true) - expNotWaiting() - - fs.Close() - ns.Stop() -} diff --git a/les/vflux/client/queueiterator.go b/les/vflux/client/queueiterator.go deleted file mode 100644 index ad3f8df5b..000000000 --- a/les/vflux/client/queueiterator.go +++ /dev/null @@ -1,123 +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 client - -import ( - "sync" - - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/nodestate" -) - -// QueueIterator returns nodes from the specified selectable set in the same order as -// they entered the set. -type QueueIterator struct { - lock sync.Mutex - cond *sync.Cond - - ns *nodestate.NodeStateMachine - queue []*enode.Node - nextNode *enode.Node - waitCallback func(bool) - fifo, closed bool -} - -// NewQueueIterator creates a new QueueIterator. Nodes are selectable if they have all the required -// and none of the disabled flags set. When a node is selected the selectedFlag is set which also -// disables further selectability until it is removed or times out. -func NewQueueIterator(ns *nodestate.NodeStateMachine, requireFlags, disableFlags nodestate.Flags, fifo bool, waitCallback func(bool)) *QueueIterator { - qi := &QueueIterator{ - ns: ns, - fifo: fifo, - waitCallback: waitCallback, - } - qi.cond = sync.NewCond(&qi.lock) - - ns.SubscribeState(requireFlags.Or(disableFlags), func(n *enode.Node, oldState, newState nodestate.Flags) { - oldMatch := oldState.HasAll(requireFlags) && oldState.HasNone(disableFlags) - newMatch := newState.HasAll(requireFlags) && newState.HasNone(disableFlags) - if newMatch == oldMatch { - return - } - - qi.lock.Lock() - defer qi.lock.Unlock() - - if newMatch { - qi.queue = append(qi.queue, n) - } else { - id := n.ID() - for i, qn := range qi.queue { - if qn.ID() == id { - copy(qi.queue[i:len(qi.queue)-1], qi.queue[i+1:]) - qi.queue = qi.queue[:len(qi.queue)-1] - break - } - } - } - qi.cond.Signal() - }) - return qi -} - -// Next moves to the next selectable node. -func (qi *QueueIterator) Next() bool { - qi.lock.Lock() - if !qi.closed && len(qi.queue) == 0 { - if qi.waitCallback != nil { - qi.waitCallback(true) - } - for !qi.closed && len(qi.queue) == 0 { - qi.cond.Wait() - } - if qi.waitCallback != nil { - qi.waitCallback(false) - } - } - if qi.closed { - qi.nextNode = nil - qi.lock.Unlock() - return false - } - // Move to the next node in queue. - if qi.fifo { - qi.nextNode = qi.queue[0] - copy(qi.queue[:len(qi.queue)-1], qi.queue[1:]) - qi.queue = qi.queue[:len(qi.queue)-1] - } else { - qi.nextNode = qi.queue[len(qi.queue)-1] - qi.queue = qi.queue[:len(qi.queue)-1] - } - qi.lock.Unlock() - return true -} - -// Close ends the iterator. -func (qi *QueueIterator) Close() { - qi.lock.Lock() - qi.closed = true - qi.lock.Unlock() - qi.cond.Signal() -} - -// Node returns the current node. -func (qi *QueueIterator) Node() *enode.Node { - qi.lock.Lock() - defer qi.lock.Unlock() - - return qi.nextNode -} diff --git a/les/vflux/client/queueiterator_test.go b/les/vflux/client/queueiterator_test.go deleted file mode 100644 index 400d978e1..000000000 --- a/les/vflux/client/queueiterator_test.go +++ /dev/null @@ -1,95 +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 client - -import ( - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" - "github.com/ethereum/go-ethereum/p2p/nodestate" -) - -func testNode(i int) *enode.Node { - return enode.SignNull(new(enr.Record), testNodeID(i)) -} - -func TestQueueIteratorFIFO(t *testing.T) { - testQueueIterator(t, true) -} - -func TestQueueIteratorLIFO(t *testing.T) { - testQueueIterator(t, false) -} - -func testQueueIterator(t *testing.T, fifo bool) { - ns := nodestate.NewNodeStateMachine(nil, nil, &mclock.Simulated{}, testSetup) - qi := NewQueueIterator(ns, sfTest2, sfTest3.Or(sfTest4), fifo, nil) - ns.Start() - for i := 1; i <= iterTestNodeCount; i++ { - ns.SetState(testNode(i), sfTest1, nodestate.Flags{}, 0) - } - next := func() int { - ch := make(chan struct{}) - go func() { - qi.Next() - close(ch) - }() - select { - case <-ch: - case <-time.After(time.Second * 5): - t.Fatalf("Iterator.Next() timeout") - } - node := qi.Node() - ns.SetState(node, sfTest4, nodestate.Flags{}, 0) - return testNodeIndex(node.ID()) - } - exp := func(i int) { - n := next() - if n != i { - t.Errorf("Wrong item returned by iterator (expected %d, got %d)", i, n) - } - } - explist := func(list []int) { - for i := range list { - if fifo { - exp(list[i]) - } else { - exp(list[len(list)-1-i]) - } - } - } - - ns.SetState(testNode(1), sfTest2, nodestate.Flags{}, 0) - ns.SetState(testNode(2), sfTest2, nodestate.Flags{}, 0) - ns.SetState(testNode(3), sfTest2, nodestate.Flags{}, 0) - explist([]int{1, 2, 3}) - ns.SetState(testNode(4), sfTest2, nodestate.Flags{}, 0) - ns.SetState(testNode(5), sfTest2, nodestate.Flags{}, 0) - ns.SetState(testNode(6), sfTest2, nodestate.Flags{}, 0) - ns.SetState(testNode(5), sfTest3, nodestate.Flags{}, 0) - explist([]int{4, 6}) - ns.SetState(testNode(1), nodestate.Flags{}, sfTest4, 0) - ns.SetState(testNode(2), nodestate.Flags{}, sfTest4, 0) - ns.SetState(testNode(3), nodestate.Flags{}, sfTest4, 0) - ns.SetState(testNode(2), sfTest3, nodestate.Flags{}, 0) - ns.SetState(testNode(2), nodestate.Flags{}, sfTest3, 0) - explist([]int{1, 3, 2}) - ns.Stop() -} diff --git a/les/vflux/client/requestbasket.go b/les/vflux/client/requestbasket.go deleted file mode 100644 index 55d4b165d..000000000 --- a/les/vflux/client/requestbasket.go +++ /dev/null @@ -1,285 +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 client - -import ( - "io" - - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/rlp" -) - -const basketFactor = 1000000 // reference basket amount and value scale factor - -// referenceBasket keeps track of global request usage statistics and the usual prices -// of each used request type relative to each other. The amounts in the basket are scaled -// up by basketFactor because of the exponential expiration of long-term statistical data. -// Values are scaled so that the sum of all amounts and the sum of all values are equal. -// -// reqValues represent the internal relative value estimates for each request type and are -// calculated as value / amount. The average reqValue of all used requests is 1. -// In other words: SUM(refBasket[type].amount * reqValue[type]) = SUM(refBasket[type].amount) -type referenceBasket struct { - basket requestBasket - reqValues []float64 // contents are read only, new slice is created for each update -} - -// serverBasket collects served request amount and value statistics for a single server. -// -// Values are gradually transferred to the global reference basket with a long time -// constant so that each server basket represents long term usage and price statistics. -// When the transferred part is added to the reference basket the values are scaled so -// that their sum equals the total value calculated according to the previous reqValues. -// The ratio of request values coming from the server basket represent the pricing of -// the specific server and modify the global estimates with a weight proportional to -// the amount of service provided by the server. -type serverBasket struct { - basket requestBasket - rvFactor float64 -} - -type ( - // requestBasket holds amounts and values for each request type. - // These values are exponentially expired (see utils.ExpiredValue). The power of 2 - // exponent is applicable to all values within. - requestBasket struct { - items []basketItem - exp uint64 - } - // basketItem holds amount and value for a single request type. Value is the total - // relative request value accumulated for served requests while amount is the counter - // for each request type. - // Note that these values are both scaled up by basketFactor because of the exponential - // expiration. - basketItem struct { - amount, value uint64 - } -) - -// setExp sets the power of 2 exponent of the structure, scaling base values (the amounts -// and request values) up or down if necessary. -func (b *requestBasket) setExp(exp uint64) { - if exp > b.exp { - shift := exp - b.exp - for i, item := range b.items { - item.amount >>= shift - item.value >>= shift - b.items[i] = item - } - b.exp = exp - } - if exp < b.exp { - shift := b.exp - exp - for i, item := range b.items { - item.amount <<= shift - item.value <<= shift - b.items[i] = item - } - b.exp = exp - } -} - -// init initializes a new server basket with the given service vector size (number of -// different request types) -func (s *serverBasket) init(size int) { - if s.basket.items == nil { - s.basket.items = make([]basketItem, size) - } -} - -// add adds the give type and amount of requests to the basket. Cost is calculated -// according to the server's own cost table. -func (s *serverBasket) add(reqType, reqAmount uint32, reqCost uint64, expFactor utils.ExpirationFactor) { - s.basket.setExp(expFactor.Exp) - i := &s.basket.items[reqType] - i.amount += uint64(float64(uint64(reqAmount)*basketFactor) * expFactor.Factor) - i.value += uint64(float64(reqCost) * s.rvFactor * expFactor.Factor) -} - -// updateRvFactor updates the request value factor that scales server costs into the -// local value dimensions. -func (s *serverBasket) updateRvFactor(rvFactor float64) { - s.rvFactor = rvFactor -} - -// transfer decreases amounts and values in the basket with the given ratio and -// moves the removed amounts into a new basket which is returned and can be added -// to the global reference basket. -func (s *serverBasket) transfer(ratio float64) requestBasket { - res := requestBasket{ - items: make([]basketItem, len(s.basket.items)), - exp: s.basket.exp, - } - for i, v := range s.basket.items { - ta := uint64(float64(v.amount) * ratio) - tv := uint64(float64(v.value) * ratio) - if ta > v.amount { - ta = v.amount - } - if tv > v.value { - tv = v.value - } - s.basket.items[i] = basketItem{v.amount - ta, v.value - tv} - res.items[i] = basketItem{ta, tv} - } - return res -} - -// init initializes the reference basket with the given service vector size (number of -// different request types) -func (r *referenceBasket) init(size int) { - r.reqValues = make([]float64, size) - r.normalize() - r.updateReqValues() -} - -// add adds the transferred part of a server basket to the reference basket while scaling -// value amounts so that their sum equals the total value calculated according to the -// previous reqValues. -func (r *referenceBasket) add(newBasket requestBasket) { - r.basket.setExp(newBasket.exp) - // scale newBasket to match service unit value - var ( - totalCost uint64 - totalValue float64 - ) - for i, v := range newBasket.items { - totalCost += v.value - totalValue += float64(v.amount) * r.reqValues[i] - } - if totalCost > 0 { - // add to reference with scaled values - scaleValues := totalValue / float64(totalCost) - for i, v := range newBasket.items { - r.basket.items[i].amount += v.amount - r.basket.items[i].value += uint64(float64(v.value) * scaleValues) - } - } - r.updateReqValues() -} - -// updateReqValues recalculates reqValues after adding transferred baskets. Note that -// values should be normalized first. -func (r *referenceBasket) updateReqValues() { - r.reqValues = make([]float64, len(r.reqValues)) - for i, b := range r.basket.items { - if b.amount > 0 { - r.reqValues[i] = float64(b.value) / float64(b.amount) - } else { - r.reqValues[i] = 0 - } - } -} - -// normalize ensures that the sum of values equal the sum of amounts in the basket. -func (r *referenceBasket) normalize() { - var sumAmount, sumValue uint64 - for _, b := range r.basket.items { - sumAmount += b.amount - sumValue += b.value - } - add := float64(int64(sumAmount-sumValue)) / float64(sumValue) - for i, b := range r.basket.items { - b.value += uint64(int64(float64(b.value) * add)) - r.basket.items[i] = b - } -} - -// reqValueFactor calculates the request value factor applicable to the server with -// the given announced request cost list -func (r *referenceBasket) reqValueFactor(costList []uint64) float64 { - var ( - totalCost float64 - totalValue uint64 - ) - for i, b := range r.basket.items { - totalCost += float64(costList[i]) * float64(b.amount) // use floats to avoid overflow - totalValue += b.value - } - if totalCost < 1 { - return 0 - } - return float64(totalValue) * basketFactor / totalCost -} - -// EncodeRLP implements rlp.Encoder -func (b *basketItem) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, []interface{}{b.amount, b.value}) -} - -// DecodeRLP implements rlp.Decoder -func (b *basketItem) DecodeRLP(s *rlp.Stream) error { - var item struct { - Amount, Value uint64 - } - if err := s.Decode(&item); err != nil { - return err - } - b.amount, b.value = item.Amount, item.Value - return nil -} - -// EncodeRLP implements rlp.Encoder -func (r *requestBasket) EncodeRLP(w io.Writer) error { - return rlp.Encode(w, []interface{}{r.items, r.exp}) -} - -// DecodeRLP implements rlp.Decoder -func (r *requestBasket) DecodeRLP(s *rlp.Stream) error { - var enc struct { - Items []basketItem - Exp uint64 - } - if err := s.Decode(&enc); err != nil { - return err - } - r.items, r.exp = enc.Items, enc.Exp - return nil -} - -// convertMapping converts a basket loaded from the database into the current format. -// If the available request types and their mapping into the service vector differ from -// the one used when saving the basket then this function reorders old fields and fills -// in previously unknown fields by scaling up amounts and values taken from the -// initialization basket. -func (r requestBasket) convertMapping(oldMapping, newMapping []string, initBasket requestBasket) requestBasket { - nameMap := make(map[string]int) - for i, name := range oldMapping { - nameMap[name] = i - } - rc := requestBasket{items: make([]basketItem, len(newMapping))} - var scale, oldScale, newScale float64 - for i, name := range newMapping { - if ii, ok := nameMap[name]; ok { - rc.items[i] = r.items[ii] - oldScale += float64(initBasket.items[i].amount) * float64(initBasket.items[i].amount) - newScale += float64(rc.items[i].amount) * float64(initBasket.items[i].amount) - } - } - if oldScale > 1e-10 { - scale = newScale / oldScale - } else { - scale = 1 - } - for i, name := range newMapping { - if _, ok := nameMap[name]; !ok { - rc.items[i].amount = uint64(float64(initBasket.items[i].amount) * scale) - rc.items[i].value = uint64(float64(initBasket.items[i].value) * scale) - } - } - return rc -} diff --git a/les/vflux/client/requestbasket_test.go b/les/vflux/client/requestbasket_test.go deleted file mode 100644 index 7c5f87c61..000000000 --- a/les/vflux/client/requestbasket_test.go +++ /dev/null @@ -1,161 +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 client - -import ( - "math/rand" - "testing" - - "github.com/ethereum/go-ethereum/les/utils" -) - -func checkU64(t *testing.T, name string, value, exp uint64) { - if value != exp { - t.Errorf("Incorrect value for %s: got %d, expected %d", name, value, exp) - } -} - -func checkF64(t *testing.T, name string, value, exp, tol float64) { - if value < exp-tol || value > exp+tol { - t.Errorf("Incorrect value for %s: got %f, expected %f", name, value, exp) - } -} - -func TestServerBasket(t *testing.T) { - var s serverBasket - s.init(2) - // add some requests with different request value factors - s.updateRvFactor(1) - noexp := utils.ExpirationFactor{Factor: 1} - s.add(0, 1000, 10000, noexp) - s.add(1, 3000, 60000, noexp) - s.updateRvFactor(10) - s.add(0, 4000, 4000, noexp) - s.add(1, 2000, 4000, noexp) - s.updateRvFactor(10) - // check basket contents directly - checkU64(t, "s.basket[0].amount", s.basket.items[0].amount, 5000*basketFactor) - checkU64(t, "s.basket[0].value", s.basket.items[0].value, 50000) - checkU64(t, "s.basket[1].amount", s.basket.items[1].amount, 5000*basketFactor) - checkU64(t, "s.basket[1].value", s.basket.items[1].value, 100000) - // transfer 50% of the contents of the basket - transfer1 := s.transfer(0.5) - checkU64(t, "transfer1[0].amount", transfer1.items[0].amount, 2500*basketFactor) - checkU64(t, "transfer1[0].value", transfer1.items[0].value, 25000) - checkU64(t, "transfer1[1].amount", transfer1.items[1].amount, 2500*basketFactor) - checkU64(t, "transfer1[1].value", transfer1.items[1].value, 50000) - // add more requests - s.updateRvFactor(100) - s.add(0, 1000, 100, noexp) - // transfer 25% of the contents of the basket - transfer2 := s.transfer(0.25) - checkU64(t, "transfer2[0].amount", transfer2.items[0].amount, (2500+1000)/4*basketFactor) - checkU64(t, "transfer2[0].value", transfer2.items[0].value, (25000+10000)/4) - checkU64(t, "transfer2[1].amount", transfer2.items[1].amount, 2500/4*basketFactor) - checkU64(t, "transfer2[1].value", transfer2.items[1].value, 50000/4) -} - -func TestConvertMapping(t *testing.T) { - b := requestBasket{items: []basketItem{{3, 3}, {1, 1}, {2, 2}}} - oldMap := []string{"req3", "req1", "req2"} - newMap := []string{"req1", "req2", "req3", "req4"} - init := requestBasket{items: []basketItem{{2, 2}, {4, 4}, {6, 6}, {8, 8}}} - bc := b.convertMapping(oldMap, newMap, init) - checkU64(t, "bc[0].amount", bc.items[0].amount, 1) - checkU64(t, "bc[1].amount", bc.items[1].amount, 2) - checkU64(t, "bc[2].amount", bc.items[2].amount, 3) - checkU64(t, "bc[3].amount", bc.items[3].amount, 4) // 8 should be scaled down to 4 -} - -func TestReqValueFactor(t *testing.T) { - var ref referenceBasket - ref.basket = requestBasket{items: make([]basketItem, 4)} - for i := range ref.basket.items { - ref.basket.items[i].amount = uint64(i+1) * basketFactor - ref.basket.items[i].value = uint64(i+1) * basketFactor - } - ref.init(4) - rvf := ref.reqValueFactor([]uint64{1000, 2000, 3000, 4000}) - // expected value is (1000000+2000000+3000000+4000000) / (1*1000+2*2000+3*3000+4*4000) = 10000000/30000 = 333.333 - checkF64(t, "reqValueFactor", rvf, 333.333, 1) -} - -func TestNormalize(t *testing.T) { - for cycle := 0; cycle < 100; cycle += 1 { - // Initialize data for testing - valueRange, lower := 1000000, 1000000 - ref := referenceBasket{basket: requestBasket{items: make([]basketItem, 10)}} - for i := 0; i < 10; i++ { - ref.basket.items[i].amount = uint64(rand.Intn(valueRange) + lower) - ref.basket.items[i].value = uint64(rand.Intn(valueRange) + lower) - } - ref.normalize() - - // Check whether SUM(amount) ~= SUM(value) - var sumAmount, sumValue uint64 - for i := 0; i < 10; i++ { - sumAmount += ref.basket.items[i].amount - sumValue += ref.basket.items[i].value - } - var epsilon = 0.01 - if float64(sumAmount)*(1+epsilon) < float64(sumValue) || float64(sumAmount)*(1-epsilon) > float64(sumValue) { - t.Fatalf("Failed to normalize sumAmount: %d sumValue: %d", sumAmount, sumValue) - } - } -} - -func TestReqValueAdjustment(t *testing.T) { - var s1, s2 serverBasket - s1.init(3) - s2.init(3) - cost1 := []uint64{30000, 60000, 90000} - cost2 := []uint64{100000, 200000, 300000} - var ref referenceBasket - ref.basket = requestBasket{items: make([]basketItem, 3)} - for i := range ref.basket.items { - ref.basket.items[i].amount = 123 * basketFactor - ref.basket.items[i].value = 123 * basketFactor - } - ref.init(3) - // initial reqValues are expected to be {1, 1, 1} - checkF64(t, "reqValues[0]", ref.reqValues[0], 1, 0.01) - checkF64(t, "reqValues[1]", ref.reqValues[1], 1, 0.01) - checkF64(t, "reqValues[2]", ref.reqValues[2], 1, 0.01) - var logOffset utils.Fixed64 - for period := 0; period < 1000; period++ { - exp := utils.ExpFactor(logOffset) - s1.updateRvFactor(ref.reqValueFactor(cost1)) - s2.updateRvFactor(ref.reqValueFactor(cost2)) - // throw in random requests into each basket using their internal pricing - for i := 0; i < 1000; i++ { - reqType, reqAmount := uint32(rand.Intn(3)), uint32(rand.Intn(10)+1) - reqCost := uint64(reqAmount) * cost1[reqType] - s1.add(reqType, reqAmount, reqCost, exp) - reqType, reqAmount = uint32(rand.Intn(3)), uint32(rand.Intn(10)+1) - reqCost = uint64(reqAmount) * cost2[reqType] - s2.add(reqType, reqAmount, reqCost, exp) - } - ref.add(s1.transfer(0.1)) - ref.add(s2.transfer(0.1)) - ref.normalize() - ref.updateReqValues() - logOffset += utils.Float64ToFixed64(0.1) - } - checkF64(t, "reqValues[0]", ref.reqValues[0], 0.5, 0.01) - checkF64(t, "reqValues[1]", ref.reqValues[1], 1, 0.01) - checkF64(t, "reqValues[2]", ref.reqValues[2], 1.5, 0.01) -} diff --git a/les/vflux/client/serverpool.go b/les/vflux/client/serverpool.go deleted file mode 100644 index 271d6e022..000000000 --- a/les/vflux/client/serverpool.go +++ /dev/null @@ -1,605 +0,0 @@ -// Copyright 2021 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package client - -import ( - "errors" - "math/rand" - "reflect" - "sync" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" - "github.com/ethereum/go-ethereum/p2p/nodestate" - "github.com/ethereum/go-ethereum/rlp" -) - -const ( - minTimeout = time.Millisecond * 500 // minimum request timeout suggested by the server pool - timeoutRefresh = time.Second * 5 // recalculate timeout if older than this - dialCost = 10000 // cost of a TCP dial (used for known node selection weight calculation) - dialWaitStep = 1.5 // exponential multiplier of redial wait time when no value was provided by the server - queryCost = 500 // cost of a UDP pre-negotiation query - queryWaitStep = 1.02 // exponential multiplier of redial wait time when no value was provided by the server - waitThreshold = time.Hour * 2000 // drop node if waiting time is over the threshold - nodeWeightMul = 1000000 // multiplier constant for node weight calculation - nodeWeightThreshold = 100 // minimum weight for keeping a node in the known (valuable) set - minRedialWait = 10 // minimum redial wait time in seconds - preNegLimit = 5 // maximum number of simultaneous pre-negotiation queries - warnQueryFails = 20 // number of consecutive UDP query failures before we print a warning - maxQueryFails = 100 // number of consecutive UDP query failures when then chance of skipping a query reaches 50% -) - -// ServerPool provides a node iterator for dial candidates. The output is a mix of newly discovered -// nodes, a weighted random selection of known (previously valuable) nodes and trusted/paid nodes. -type ServerPool struct { - clock mclock.Clock - unixTime func() int64 - db ethdb.KeyValueStore - - ns *nodestate.NodeStateMachine - vt *ValueTracker - mixer *enode.FairMix - mixSources []enode.Iterator - dialIterator enode.Iterator - validSchemes enr.IdentityScheme - trustedURLs []string - fillSet *FillSet - started, queryFails uint32 - - timeoutLock sync.RWMutex - timeout time.Duration - timeWeights ResponseTimeWeights - timeoutRefreshed mclock.AbsTime - - suggestedTimeoutGauge, totalValueGauge metrics.Gauge - sessionValueMeter metrics.Meter -} - -// nodeHistory keeps track of dial costs which determine node weight together with the -// service value calculated by ValueTracker. -type nodeHistory struct { - dialCost utils.ExpiredValue - redialWaitStart, redialWaitEnd int64 // unix time (seconds) -} - -type nodeHistoryEnc struct { - DialCost utils.ExpiredValue - RedialWaitStart, RedialWaitEnd uint64 -} - -// QueryFunc sends a pre-negotiation query and blocks until a response arrives or timeout occurs. -// It returns 1 if the remote node has confirmed that connection is possible, 0 if not -// possible and -1 if no response arrived (timeout). -type QueryFunc func(*enode.Node) int - -var ( - clientSetup = &nodestate.Setup{Version: 2} - sfHasValue = clientSetup.NewPersistentFlag("hasValue") - sfQuery = clientSetup.NewFlag("query") - sfCanDial = clientSetup.NewFlag("canDial") - sfDialing = clientSetup.NewFlag("dialed") - sfWaitDialTimeout = clientSetup.NewFlag("dialTimeout") - sfConnected = clientSetup.NewFlag("connected") - sfRedialWait = clientSetup.NewFlag("redialWait") - sfAlwaysConnect = clientSetup.NewFlag("alwaysConnect") - sfDialProcess = nodestate.MergeFlags(sfQuery, sfCanDial, sfDialing, sfConnected, sfRedialWait) - - sfiNodeHistory = clientSetup.NewPersistentField("nodeHistory", reflect.TypeOf(nodeHistory{}), - func(field interface{}) ([]byte, error) { - if n, ok := field.(nodeHistory); ok { - ne := nodeHistoryEnc{ - DialCost: n.dialCost, - RedialWaitStart: uint64(n.redialWaitStart), - RedialWaitEnd: uint64(n.redialWaitEnd), - } - enc, err := rlp.EncodeToBytes(&ne) - return enc, err - } - return nil, errors.New("invalid field type") - }, - func(enc []byte) (interface{}, error) { - var ne nodeHistoryEnc - err := rlp.DecodeBytes(enc, &ne) - n := nodeHistory{ - dialCost: ne.DialCost, - redialWaitStart: int64(ne.RedialWaitStart), - redialWaitEnd: int64(ne.RedialWaitEnd), - } - return n, err - }, - ) - sfiNodeWeight = clientSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0))) - sfiConnectedStats = clientSetup.NewField("connectedStats", reflect.TypeOf(ResponseTimeStats{})) - sfiLocalAddress = clientSetup.NewPersistentField("localAddress", reflect.TypeOf(&enr.Record{}), - func(field interface{}) ([]byte, error) { - if enr, ok := field.(*enr.Record); ok { - enc, err := rlp.EncodeToBytes(enr) - return enc, err - } - return nil, errors.New("invalid field type") - }, - func(enc []byte) (interface{}, error) { - var enr enr.Record - if err := rlp.DecodeBytes(enc, &enr); err != nil { - return nil, err - } - return &enr, nil - }, - ) -) - -// NewServerPool creates a new server pool -func NewServerPool(db ethdb.KeyValueStore, dbKey []byte, mixTimeout time.Duration, query QueryFunc, clock mclock.Clock, trustedURLs []string, requestList []RequestInfo) (*ServerPool, enode.Iterator) { - s := &ServerPool{ - db: db, - clock: clock, - unixTime: func() int64 { return time.Now().Unix() }, - validSchemes: enode.ValidSchemes, - trustedURLs: trustedURLs, - vt: NewValueTracker(db, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), - ns: nodestate.NewNodeStateMachine(db, []byte(string(dbKey)+"ns:"), clock, clientSetup), - } - s.recalTimeout() - s.mixer = enode.NewFairMix(mixTimeout) - knownSelector := NewWrsIterator(s.ns, sfHasValue, sfDialProcess, sfiNodeWeight) - alwaysConnect := NewQueueIterator(s.ns, sfAlwaysConnect, sfDialProcess, true, nil) - s.mixSources = append(s.mixSources, knownSelector) - s.mixSources = append(s.mixSources, alwaysConnect) - - s.dialIterator = s.mixer - if query != nil { - s.dialIterator = s.addPreNegFilter(s.dialIterator, query) - } - - s.ns.SubscribeState(nodestate.MergeFlags(sfWaitDialTimeout, sfConnected), func(n *enode.Node, oldState, newState nodestate.Flags) { - if oldState.Equals(sfWaitDialTimeout) && newState.IsEmpty() { - // dial timeout, no connection - s.setRedialWait(n, dialCost, dialWaitStep) - s.ns.SetStateSub(n, nodestate.Flags{}, sfDialing, 0) - } - }) - - return s, &serverPoolIterator{ - dialIterator: s.dialIterator, - nextFn: func(node *enode.Node) { - s.ns.Operation(func() { - s.ns.SetStateSub(node, sfDialing, sfCanDial, 0) - s.ns.SetStateSub(node, sfWaitDialTimeout, nodestate.Flags{}, time.Second*10) - }) - }, - nodeFn: s.DialNode, - } -} - -type serverPoolIterator struct { - dialIterator enode.Iterator - nextFn func(*enode.Node) - nodeFn func(*enode.Node) *enode.Node -} - -// Next implements enode.Iterator -func (s *serverPoolIterator) Next() bool { - if s.dialIterator.Next() { - s.nextFn(s.dialIterator.Node()) - return true - } - return false -} - -// Node implements enode.Iterator -func (s *serverPoolIterator) Node() *enode.Node { - return s.nodeFn(s.dialIterator.Node()) -} - -// Close implements enode.Iterator -func (s *serverPoolIterator) Close() { - s.dialIterator.Close() -} - -// AddMetrics adds metrics to the server pool. Should be called before Start(). -func (s *ServerPool) AddMetrics( - suggestedTimeoutGauge, totalValueGauge, serverSelectableGauge, serverConnectedGauge metrics.Gauge, - sessionValueMeter, serverDialedMeter metrics.Meter) { - s.suggestedTimeoutGauge = suggestedTimeoutGauge - s.totalValueGauge = totalValueGauge - s.sessionValueMeter = sessionValueMeter - if serverSelectableGauge != nil { - s.ns.AddLogMetrics(sfHasValue, sfDialProcess, "selectable", nil, nil, serverSelectableGauge) - } - if serverDialedMeter != nil { - s.ns.AddLogMetrics(sfDialing, nodestate.Flags{}, "dialed", serverDialedMeter, nil, nil) - } - if serverConnectedGauge != nil { - s.ns.AddLogMetrics(sfConnected, nodestate.Flags{}, "connected", nil, nil, serverConnectedGauge) - } -} - -// AddSource adds a node discovery source to the server pool (should be called before start) -func (s *ServerPool) AddSource(source enode.Iterator) { - if source != nil { - s.mixSources = append(s.mixSources, source) - } -} - -// addPreNegFilter installs a node filter mechanism that performs a pre-negotiation query. -// Nodes that are filtered out and does not appear on the output iterator are put back -// into redialWait state. -func (s *ServerPool) addPreNegFilter(input enode.Iterator, query QueryFunc) enode.Iterator { - s.fillSet = NewFillSet(s.ns, input, sfQuery) - s.ns.SubscribeState(sfDialProcess, func(n *enode.Node, oldState, newState nodestate.Flags) { - if !newState.Equals(sfQuery) { - if newState.HasAll(sfQuery) { - // remove query flag if the node is already somewhere in the dial process - s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0) - } - return - } - fails := atomic.LoadUint32(&s.queryFails) - failMax := fails - if failMax > maxQueryFails { - failMax = maxQueryFails - } - if rand.Intn(maxQueryFails*2) < int(failMax) { - // skip pre-negotiation with increasing chance, max 50% - // this ensures that the client can operate even if UDP is not working at all - s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10) - // set canDial before resetting queried so that FillSet will not read more - // candidates unnecessarily - s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0) - return - } - go func() { - q := query(n) - if q == -1 { - atomic.AddUint32(&s.queryFails, 1) - fails++ - if fails%warnQueryFails == 0 { - // warn if a large number of consecutive queries have failed - log.Warn("UDP connection queries failed", "count", fails) - } - } else { - atomic.StoreUint32(&s.queryFails, 0) - } - s.ns.Operation(func() { - // we are no longer running in the operation that the callback belongs to, start a new one because of setRedialWait - if q == 1 { - s.ns.SetStateSub(n, sfCanDial, nodestate.Flags{}, time.Second*10) - } else { - s.setRedialWait(n, queryCost, queryWaitStep) - } - s.ns.SetStateSub(n, nodestate.Flags{}, sfQuery, 0) - }) - }() - }) - return NewQueueIterator(s.ns, sfCanDial, nodestate.Flags{}, false, func(waiting bool) { - if waiting { - s.fillSet.SetTarget(preNegLimit) - } else { - s.fillSet.SetTarget(0) - } - }) -} - -// Start starts the server pool. Note that NodeStateMachine should be started first. -func (s *ServerPool) Start() { - s.ns.Start() - for _, iter := range s.mixSources { - // add sources to mixer at startup because the mixer instantly tries to read them - // which should only happen after NodeStateMachine has been started - s.mixer.AddSource(iter) - } - for _, url := range s.trustedURLs { - if node, err := enode.Parse(s.validSchemes, url); err == nil { - s.ns.SetState(node, sfAlwaysConnect, nodestate.Flags{}, 0) - } else { - log.Error("Invalid trusted server URL", "url", url, "error", err) - } - } - unixTime := s.unixTime() - s.ns.Operation(func() { - s.ns.ForEach(sfHasValue, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - s.calculateWeight(node) - if n, ok := s.ns.GetField(node, sfiNodeHistory).(nodeHistory); ok && n.redialWaitEnd > unixTime { - wait := n.redialWaitEnd - unixTime - lastWait := n.redialWaitEnd - n.redialWaitStart - if wait > lastWait { - // if the time until expiration is larger than the last suggested - // waiting time then the system clock was probably adjusted - wait = lastWait - } - s.ns.SetStateSub(node, sfRedialWait, nodestate.Flags{}, time.Duration(wait)*time.Second) - } - }) - }) - atomic.StoreUint32(&s.started, 1) -} - -// Stop stops the server pool -func (s *ServerPool) Stop() { - if s.fillSet != nil { - s.fillSet.Close() - } - s.ns.Operation(func() { - s.ns.ForEach(sfConnected, nodestate.Flags{}, func(n *enode.Node, state nodestate.Flags) { - // recalculate weight of connected nodes in order to update hasValue flag if necessary - s.calculateWeight(n) - }) - }) - s.ns.Stop() - s.vt.Stop() -} - -// RegisterNode implements serverPeerSubscriber -func (s *ServerPool) RegisterNode(node *enode.Node) (*NodeValueTracker, error) { - if atomic.LoadUint32(&s.started) == 0 { - return nil, errors.New("server pool not started yet") - } - nvt := s.vt.Register(node.ID()) - s.ns.Operation(func() { - s.ns.SetStateSub(node, sfConnected, sfDialing.Or(sfWaitDialTimeout), 0) - s.ns.SetFieldSub(node, sfiConnectedStats, nvt.RtStats()) - if node.IP().IsLoopback() { - s.ns.SetFieldSub(node, sfiLocalAddress, node.Record()) - } - }) - return nvt, nil -} - -// UnregisterNode implements serverPeerSubscriber -func (s *ServerPool) UnregisterNode(node *enode.Node) { - s.ns.Operation(func() { - s.setRedialWait(node, dialCost, dialWaitStep) - s.ns.SetStateSub(node, nodestate.Flags{}, sfConnected, 0) - s.ns.SetFieldSub(node, sfiConnectedStats, nil) - }) - s.vt.Unregister(node.ID()) -} - -// recalTimeout calculates the current recommended timeout. This value is used by -// the client as a "soft timeout" value. It also affects the service value calculation -// of individual nodes. -func (s *ServerPool) recalTimeout() { - // Use cached result if possible, avoid recalculating too frequently. - s.timeoutLock.RLock() - refreshed := s.timeoutRefreshed - s.timeoutLock.RUnlock() - now := s.clock.Now() - if refreshed != 0 && time.Duration(now-refreshed) < timeoutRefresh { - return - } - // Cached result is stale, recalculate a new one. - rts := s.vt.RtStats() - - // Add a fake statistic here. It is an easy way to initialize with some - // conservative values when the database is new. As soon as we have a - // considerable amount of real stats this small value won't matter. - rts.Add(time.Second*2, 10, s.vt.StatsExpFactor()) - - // Use either 10% failure rate timeout or twice the median response time - // as the recommended timeout. - timeout := minTimeout - if t := rts.Timeout(0.1); t > timeout { - timeout = t - } - if t := rts.Timeout(0.5) * 2; t > timeout { - timeout = t - } - s.timeoutLock.Lock() - if s.timeout != timeout { - s.timeout = timeout - s.timeWeights = TimeoutWeights(s.timeout) - - if s.suggestedTimeoutGauge != nil { - s.suggestedTimeoutGauge.Update(int64(s.timeout / time.Millisecond)) - } - if s.totalValueGauge != nil { - s.totalValueGauge.Update(int64(rts.Value(s.timeWeights, s.vt.StatsExpFactor()))) - } - } - s.timeoutRefreshed = now - s.timeoutLock.Unlock() -} - -// GetTimeout returns the recommended request timeout. -func (s *ServerPool) GetTimeout() time.Duration { - s.recalTimeout() - s.timeoutLock.RLock() - defer s.timeoutLock.RUnlock() - return s.timeout -} - -// getTimeoutAndWeight returns the recommended request timeout as well as the -// response time weight which is necessary to calculate service value. -func (s *ServerPool) getTimeoutAndWeight() (time.Duration, ResponseTimeWeights) { - s.recalTimeout() - s.timeoutLock.RLock() - defer s.timeoutLock.RUnlock() - return s.timeout, s.timeWeights -} - -// addDialCost adds the given amount of dial cost to the node history and returns the current -// amount of total dial cost -func (s *ServerPool) addDialCost(n *nodeHistory, amount int64) uint64 { - logOffset := s.vt.StatsExpirer().LogOffset(s.clock.Now()) - if amount > 0 { - n.dialCost.Add(amount, logOffset) - } - totalDialCost := n.dialCost.Value(logOffset) - if totalDialCost < dialCost { - totalDialCost = dialCost - } - return totalDialCost -} - -// serviceValue returns the service value accumulated in this session and in total -func (s *ServerPool) serviceValue(node *enode.Node) (sessionValue, totalValue float64) { - nvt := s.vt.GetNode(node.ID()) - if nvt == nil { - return 0, 0 - } - currentStats := nvt.RtStats() - _, timeWeights := s.getTimeoutAndWeight() - expFactor := s.vt.StatsExpFactor() - - totalValue = currentStats.Value(timeWeights, expFactor) - if connStats, ok := s.ns.GetField(node, sfiConnectedStats).(ResponseTimeStats); ok { - diff := currentStats - diff.SubStats(&connStats) - sessionValue = diff.Value(timeWeights, expFactor) - if s.sessionValueMeter != nil { - s.sessionValueMeter.Mark(int64(sessionValue)) - } - } - return -} - -// updateWeight calculates the node weight and updates the nodeWeight field and the -// hasValue flag. It also saves the node state if necessary. -// Note: this function should run inside a NodeStateMachine operation -func (s *ServerPool) updateWeight(node *enode.Node, totalValue float64, totalDialCost uint64) { - weight := uint64(totalValue * nodeWeightMul / float64(totalDialCost)) - if weight >= nodeWeightThreshold { - s.ns.SetStateSub(node, sfHasValue, nodestate.Flags{}, 0) - s.ns.SetFieldSub(node, sfiNodeWeight, weight) - } else { - s.ns.SetStateSub(node, nodestate.Flags{}, sfHasValue, 0) - s.ns.SetFieldSub(node, sfiNodeWeight, nil) - s.ns.SetFieldSub(node, sfiNodeHistory, nil) - s.ns.SetFieldSub(node, sfiLocalAddress, nil) - } - s.ns.Persist(node) // saved if node history or hasValue changed -} - -// setRedialWait calculates and sets the redialWait timeout based on the service value -// and dial cost accumulated during the last session/attempt and in total. -// The waiting time is raised exponentially if no service value has been received in order -// to prevent dialing an unresponsive node frequently for a very long time just because it -// was useful in the past. It can still be occasionally dialed though and once it provides -// a significant amount of service value again its waiting time is quickly reduced or reset -// to the minimum. -// Note: node weight is also recalculated and updated by this function. -// Note 2: this function should run inside a NodeStateMachine operation -func (s *ServerPool) setRedialWait(node *enode.Node, addDialCost int64, waitStep float64) { - n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory) - sessionValue, totalValue := s.serviceValue(node) - totalDialCost := s.addDialCost(&n, addDialCost) - - // if the current dial session has yielded at least the average value/dial cost ratio - // then the waiting time should be reset to the minimum. If the session value - // is below average but still positive then timeout is limited to the ratio of - // average / current service value multiplied by the minimum timeout. If the attempt - // was unsuccessful then timeout is raised exponentially without limitation. - // Note: dialCost is used in the formula below even if dial was not attempted at all - // because the pre-negotiation query did not return a positive result. In this case - // the ratio has no meaning anyway and waitFactor is always raised, though in smaller - // steps because queries are cheaper and therefore we can allow more failed attempts. - unixTime := s.unixTime() - plannedTimeout := float64(n.redialWaitEnd - n.redialWaitStart) // last planned redialWait timeout - var actualWait float64 // actual waiting time elapsed - if unixTime > n.redialWaitEnd { - // the planned timeout has elapsed - actualWait = plannedTimeout - } else { - // if the node was redialed earlier then we do not raise the planned timeout - // exponentially because that could lead to the timeout rising very high in - // a short amount of time - // Note that in case of an early redial actualWait also includes the dial - // timeout or connection time of the last attempt but it still serves its - // purpose of preventing the timeout rising quicker than linearly as a function - // of total time elapsed without a successful connection. - actualWait = float64(unixTime - n.redialWaitStart) - } - // raise timeout exponentially if the last planned timeout has elapsed - // (use at least the last planned timeout otherwise) - nextTimeout := actualWait * waitStep - if plannedTimeout > nextTimeout { - nextTimeout = plannedTimeout - } - // we reduce the waiting time if the server has provided service value during the - // connection (but never under the minimum) - a := totalValue * dialCost * float64(minRedialWait) - b := float64(totalDialCost) * sessionValue - if a < b*nextTimeout { - nextTimeout = a / b - } - if nextTimeout < minRedialWait { - nextTimeout = minRedialWait - } - wait := time.Duration(float64(time.Second) * nextTimeout) - if wait < waitThreshold { - n.redialWaitStart = unixTime - n.redialWaitEnd = unixTime + int64(nextTimeout) - s.ns.SetFieldSub(node, sfiNodeHistory, n) - s.ns.SetStateSub(node, sfRedialWait, nodestate.Flags{}, wait) - s.updateWeight(node, totalValue, totalDialCost) - } else { - // discard known node statistics if waiting time is very long because the node - // hasn't been responsive for a very long time - s.ns.SetFieldSub(node, sfiNodeHistory, nil) - s.ns.SetFieldSub(node, sfiNodeWeight, nil) - s.ns.SetStateSub(node, nodestate.Flags{}, sfHasValue, 0) - } -} - -// calculateWeight calculates and sets the node weight without altering the node history. -// This function should be called during startup and shutdown only, otherwise setRedialWait -// will keep the weights updated as the underlying statistics are adjusted. -// Note: this function should run inside a NodeStateMachine operation -func (s *ServerPool) calculateWeight(node *enode.Node) { - n, _ := s.ns.GetField(node, sfiNodeHistory).(nodeHistory) - _, totalValue := s.serviceValue(node) - totalDialCost := s.addDialCost(&n, 0) - s.updateWeight(node, totalValue, totalDialCost) -} - -// API returns the vflux client API -func (s *ServerPool) API() *PrivateClientAPI { - return NewPrivateClientAPI(s.vt) -} - -type dummyIdentity enode.ID - -func (id dummyIdentity) Verify(r *enr.Record, sig []byte) error { return nil } -func (id dummyIdentity) NodeAddr(r *enr.Record) []byte { return id[:] } - -// DialNode replaces the given enode with a locally generated one containing the ENR -// stored in the sfiLocalAddress field if present. This workaround ensures that nodes -// on the local network can be dialed at the local address if a connection has been -// successfully established previously. -// Note that NodeStateMachine always remembers the enode with the latest version of -// the remote signed ENR. ENR filtering should be performed on that version while -// dialNode should be used for dialing the node over TCP or UDP. -func (s *ServerPool) DialNode(n *enode.Node) *enode.Node { - if enr, ok := s.ns.GetField(n, sfiLocalAddress).(*enr.Record); ok { - n, _ := enode.New(dummyIdentity(n.ID()), enr) - return n - } - return n -} - -// Persist immediately stores the state of a node in the node database -func (s *ServerPool) Persist(n *enode.Node) { - s.ns.Persist(n) -} diff --git a/les/vflux/client/serverpool_test.go b/les/vflux/client/serverpool_test.go deleted file mode 100644 index f1fd987d7..000000000 --- a/les/vflux/client/serverpool_test.go +++ /dev/null @@ -1,392 +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 client - -import ( - "math/rand" - "strconv" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/ethdb/memorydb" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" -) - -const ( - spTestNodes = 1000 - spTestTarget = 5 - spTestLength = 10000 - spMinTotal = 40000 - spMaxTotal = 50000 -) - -func testNodeID(i int) enode.ID { - return enode.ID{42, byte(i % 256), byte(i / 256)} -} - -func testNodeIndex(id enode.ID) int { - if id[0] != 42 { - return -1 - } - return int(id[1]) + int(id[2])*256 -} - -type ServerPoolTest struct { - db ethdb.KeyValueStore - clock *mclock.Simulated - quit chan chan struct{} - preNeg, preNegFail bool - sp *ServerPool - spi enode.Iterator - input enode.Iterator - testNodes []spTestNode - trusted []string - waitCount, waitEnded int32 - - // preNegLock protects the cycle counter, testNodes list and its connected field - // (accessed from both the main thread and the preNeg callback) - preNegLock sync.Mutex - queryWg *sync.WaitGroup // a new wait group is created each time the simulation is started - stopping bool // stopping avoid calling queryWg.Add after queryWg.Wait - - cycle, conn, servedConn int - serviceCycles, dialCount int - disconnect map[int][]int -} - -type spTestNode struct { - connectCycles, waitCycles int - nextConnCycle, totalConn int - connected, service bool - node *enode.Node -} - -func newServerPoolTest(preNeg, preNegFail bool) *ServerPoolTest { - nodes := make([]*enode.Node, spTestNodes) - for i := range nodes { - nodes[i] = enode.SignNull(&enr.Record{}, testNodeID(i)) - } - return &ServerPoolTest{ - clock: &mclock.Simulated{}, - db: memorydb.New(), - input: enode.CycleNodes(nodes), - testNodes: make([]spTestNode, spTestNodes), - preNeg: preNeg, - preNegFail: preNegFail, - } -} - -func (s *ServerPoolTest) beginWait() { - // ensure that dialIterator and the maximal number of pre-neg queries are not all stuck in a waiting state - for atomic.AddInt32(&s.waitCount, 1) > preNegLimit { - atomic.AddInt32(&s.waitCount, -1) - s.clock.Run(time.Second) - } -} - -func (s *ServerPoolTest) endWait() { - atomic.AddInt32(&s.waitCount, -1) - atomic.AddInt32(&s.waitEnded, 1) -} - -func (s *ServerPoolTest) addTrusted(i int) { - s.trusted = append(s.trusted, enode.SignNull(&enr.Record{}, testNodeID(i)).String()) -} - -func (s *ServerPoolTest) start() { - var testQuery QueryFunc - s.queryWg = new(sync.WaitGroup) - if s.preNeg { - testQuery = func(node *enode.Node) int { - s.preNegLock.Lock() - if s.stopping { - s.preNegLock.Unlock() - return 0 - } - s.queryWg.Add(1) - idx := testNodeIndex(node.ID()) - n := &s.testNodes[idx] - canConnect := !n.connected && n.connectCycles != 0 && s.cycle >= n.nextConnCycle - s.preNegLock.Unlock() - defer s.queryWg.Done() - - if s.preNegFail { - // simulate a scenario where UDP queries never work - s.beginWait() - s.clock.Sleep(time.Second * 5) - s.endWait() - return -1 - } - switch idx % 3 { - case 0: - // pre-neg returns true only if connection is possible - if canConnect { - return 1 - } - return 0 - case 1: - // pre-neg returns true but connection might still fail - return 1 - case 2: - // pre-neg returns true if connection is possible, otherwise timeout (node unresponsive) - if canConnect { - return 1 - } - s.beginWait() - s.clock.Sleep(time.Second * 5) - s.endWait() - return -1 - } - return -1 - } - } - - requestList := make([]RequestInfo, testReqTypes) - for i := range requestList { - requestList[i] = RequestInfo{Name: "testreq" + strconv.Itoa(i), InitAmount: 1, InitValue: 1} - } - - s.sp, s.spi = NewServerPool(s.db, []byte("sp:"), 0, testQuery, s.clock, s.trusted, requestList) - s.sp.AddSource(s.input) - s.sp.validSchemes = enode.ValidSchemesForTesting - s.sp.unixTime = func() int64 { return int64(s.clock.Now()) / int64(time.Second) } - s.disconnect = make(map[int][]int) - s.sp.Start() - s.quit = make(chan chan struct{}) - go func() { - last := int32(-1) - for { - select { - case <-time.After(time.Millisecond * 100): - c := atomic.LoadInt32(&s.waitEnded) - if c == last { - // advance clock if test is stuck (might happen in rare cases) - s.clock.Run(time.Second) - } - last = c - case quit := <-s.quit: - close(quit) - return - } - } - }() -} - -func (s *ServerPoolTest) stop() { - // disable further queries and wait if one is currently running - s.preNegLock.Lock() - s.stopping = true - s.preNegLock.Unlock() - s.queryWg.Wait() - - quit := make(chan struct{}) - s.quit <- quit - <-quit - s.sp.Stop() - s.spi.Close() - s.preNegLock.Lock() - s.stopping = false - s.preNegLock.Unlock() - for i := range s.testNodes { - n := &s.testNodes[i] - if n.connected { - n.totalConn += s.cycle - } - n.connected = false - n.node = nil - n.nextConnCycle = 0 - } - s.conn, s.servedConn = 0, 0 -} - -func (s *ServerPoolTest) run() { - for count := spTestLength; count > 0; count-- { - if dcList := s.disconnect[s.cycle]; dcList != nil { - for _, idx := range dcList { - n := &s.testNodes[idx] - s.sp.UnregisterNode(n.node) - n.totalConn += s.cycle - s.preNegLock.Lock() - n.connected = false - s.preNegLock.Unlock() - n.node = nil - s.conn-- - if n.service { - s.servedConn-- - } - n.nextConnCycle = s.cycle + n.waitCycles - } - delete(s.disconnect, s.cycle) - } - if s.conn < spTestTarget { - s.dialCount++ - s.beginWait() - s.spi.Next() - s.endWait() - dial := s.spi.Node() - id := dial.ID() - idx := testNodeIndex(id) - n := &s.testNodes[idx] - if !n.connected && n.connectCycles != 0 && s.cycle >= n.nextConnCycle { - s.conn++ - if n.service { - s.servedConn++ - } - n.totalConn -= s.cycle - s.preNegLock.Lock() - n.connected = true - s.preNegLock.Unlock() - dc := s.cycle + n.connectCycles - s.disconnect[dc] = append(s.disconnect[dc], idx) - n.node = dial - nv, _ := s.sp.RegisterNode(n.node) - if n.service { - nv.Served([]ServedRequest{{ReqType: 0, Amount: 100}}, 0) - } - } - } - s.serviceCycles += s.servedConn - s.clock.Run(time.Second) - s.preNegLock.Lock() - s.cycle++ - s.preNegLock.Unlock() - } -} - -func (s *ServerPoolTest) setNodes(count, conn, wait int, service, trusted bool) (res []int) { - for ; count > 0; count-- { - idx := rand.Intn(spTestNodes) - for s.testNodes[idx].connectCycles != 0 || s.testNodes[idx].connected { - idx = rand.Intn(spTestNodes) - } - res = append(res, idx) - s.preNegLock.Lock() - s.testNodes[idx] = spTestNode{ - connectCycles: conn, - waitCycles: wait, - service: service, - } - s.preNegLock.Unlock() - if trusted { - s.addTrusted(idx) - } - } - return -} - -func (s *ServerPoolTest) resetNodes() { - for i, n := range s.testNodes { - if n.connected { - n.totalConn += s.cycle - s.sp.UnregisterNode(n.node) - } - s.preNegLock.Lock() - s.testNodes[i] = spTestNode{totalConn: n.totalConn} - s.preNegLock.Unlock() - } - s.conn, s.servedConn = 0, 0 - s.disconnect = make(map[int][]int) - s.trusted = nil -} - -func (s *ServerPoolTest) checkNodes(t *testing.T, nodes []int) { - var sum int - for _, idx := range nodes { - n := &s.testNodes[idx] - if n.connected { - n.totalConn += s.cycle - } - sum += n.totalConn - n.totalConn = 0 - if n.connected { - n.totalConn -= s.cycle - } - } - if sum < spMinTotal || sum > spMaxTotal { - t.Errorf("Total connection amount %d outside expected range %d to %d", sum, spMinTotal, spMaxTotal) - } -} - -func TestServerPool(t *testing.T) { testServerPool(t, false, false) } -func TestServerPoolWithPreNeg(t *testing.T) { testServerPool(t, true, false) } -func TestServerPoolWithPreNegFail(t *testing.T) { testServerPool(t, true, true) } -func testServerPool(t *testing.T, preNeg, fail bool) { - s := newServerPoolTest(preNeg, fail) - nodes := s.setNodes(100, 200, 200, true, false) - s.setNodes(100, 20, 20, false, false) - s.start() - s.run() - s.stop() - s.checkNodes(t, nodes) -} - -func TestServerPoolChangedNodes(t *testing.T) { testServerPoolChangedNodes(t, false) } -func TestServerPoolChangedNodesWithPreNeg(t *testing.T) { testServerPoolChangedNodes(t, true) } -func testServerPoolChangedNodes(t *testing.T, preNeg bool) { - s := newServerPoolTest(preNeg, false) - nodes := s.setNodes(100, 200, 200, true, false) - s.setNodes(100, 20, 20, false, false) - s.start() - s.run() - s.checkNodes(t, nodes) - for i := 0; i < 3; i++ { - s.resetNodes() - nodes := s.setNodes(100, 200, 200, true, false) - s.setNodes(100, 20, 20, false, false) - s.run() - s.checkNodes(t, nodes) - } - s.stop() -} - -func TestServerPoolRestartNoDiscovery(t *testing.T) { testServerPoolRestartNoDiscovery(t, false) } -func TestServerPoolRestartNoDiscoveryWithPreNeg(t *testing.T) { - testServerPoolRestartNoDiscovery(t, true) -} -func testServerPoolRestartNoDiscovery(t *testing.T, preNeg bool) { - s := newServerPoolTest(preNeg, false) - nodes := s.setNodes(100, 200, 200, true, false) - s.setNodes(100, 20, 20, false, false) - s.start() - s.run() - s.stop() - s.checkNodes(t, nodes) - s.input = nil - s.start() - s.run() - s.stop() - s.checkNodes(t, nodes) -} - -func TestServerPoolTrustedNoDiscovery(t *testing.T) { testServerPoolTrustedNoDiscovery(t, false) } -func TestServerPoolTrustedNoDiscoveryWithPreNeg(t *testing.T) { - testServerPoolTrustedNoDiscovery(t, true) -} -func testServerPoolTrustedNoDiscovery(t *testing.T, preNeg bool) { - s := newServerPoolTest(preNeg, false) - trusted := s.setNodes(200, 200, 200, true, true) - s.input = nil - s.start() - s.run() - s.stop() - s.checkNodes(t, trusted) -} diff --git a/les/vflux/client/timestats.go b/les/vflux/client/timestats.go deleted file mode 100644 index 7f1ffdbe2..000000000 --- a/les/vflux/client/timestats.go +++ /dev/null @@ -1,237 +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 client - -import ( - "io" - "math" - "time" - - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/rlp" -) - -const ( - minResponseTime = time.Millisecond * 50 - maxResponseTime = time.Second * 10 - timeStatLength = 32 - weightScaleFactor = 1000000 -) - -// ResponseTimeStats is the response time distribution of a set of answered requests, -// weighted with request value, either served by a single server or aggregated for -// multiple servers. -// It it a fixed length (timeStatLength) distribution vector with linear interpolation. -// The X axis (the time values) are not linear, they should be transformed with -// TimeToStatScale and StatScaleToTime. -type ( - ResponseTimeStats struct { - stats [timeStatLength]uint64 - exp uint64 - } - ResponseTimeWeights [timeStatLength]float64 -) - -var timeStatsLogFactor = (timeStatLength - 1) / (math.Log(float64(maxResponseTime)/float64(minResponseTime)) + 1) - -// TimeToStatScale converts a response time to a distribution vector index. The index -// is represented by a float64 so that linear interpolation can be applied. -func TimeToStatScale(d time.Duration) float64 { - if d < 0 { - return 0 - } - r := float64(d) / float64(minResponseTime) - if r > 1 { - r = math.Log(r) + 1 - } - r *= timeStatsLogFactor - if r > timeStatLength-1 { - return timeStatLength - 1 - } - return r -} - -// StatScaleToTime converts a distribution vector index to a response time. The index -// is represented by a float64 so that linear interpolation can be applied. -func StatScaleToTime(r float64) time.Duration { - r /= timeStatsLogFactor - if r > 1 { - r = math.Exp(r - 1) - } - return time.Duration(r * float64(minResponseTime)) -} - -// TimeoutWeights calculates the weight function used for calculating service value -// based on the response time distribution of the received service. -// It is based on the request timeout value of the system. It consists of a half cosine -// function starting with 1, crossing zero at timeout and reaching -1 at 2*timeout. -// After 2*timeout the weight is constant -1. -func TimeoutWeights(timeout time.Duration) (res ResponseTimeWeights) { - for i := range res { - t := StatScaleToTime(float64(i)) - if t < 2*timeout { - res[i] = math.Cos(math.Pi / 2 * float64(t) / float64(timeout)) - } else { - res[i] = -1 - } - } - return -} - -// EncodeRLP implements rlp.Encoder -func (rt *ResponseTimeStats) EncodeRLP(w io.Writer) error { - enc := struct { - Stats [timeStatLength]uint64 - Exp uint64 - }{rt.stats, rt.exp} - return rlp.Encode(w, &enc) -} - -// DecodeRLP implements rlp.Decoder -func (rt *ResponseTimeStats) DecodeRLP(s *rlp.Stream) error { - var enc struct { - Stats [timeStatLength]uint64 - Exp uint64 - } - if err := s.Decode(&enc); err != nil { - return err - } - rt.stats, rt.exp = enc.Stats, enc.Exp - return nil -} - -// Add adds a new response time with the given weight to the distribution. -func (rt *ResponseTimeStats) Add(respTime time.Duration, weight float64, expFactor utils.ExpirationFactor) { - rt.setExp(expFactor.Exp) - weight *= expFactor.Factor * weightScaleFactor - r := TimeToStatScale(respTime) - i := int(r) - r -= float64(i) - rt.stats[i] += uint64(weight * (1 - r)) - if i < timeStatLength-1 { - rt.stats[i+1] += uint64(weight * r) - } -} - -// setExp sets the power of 2 exponent of the structure, scaling base values (the vector -// itself) up or down if necessary. -func (rt *ResponseTimeStats) setExp(exp uint64) { - if exp > rt.exp { - shift := exp - rt.exp - for i, v := range rt.stats { - rt.stats[i] = v >> shift - } - rt.exp = exp - } - if exp < rt.exp { - shift := rt.exp - exp - for i, v := range rt.stats { - rt.stats[i] = v << shift - } - rt.exp = exp - } -} - -// Value calculates the total service value based on the given distribution, using the -// specified weight function. -func (rt ResponseTimeStats) Value(weights ResponseTimeWeights, expFactor utils.ExpirationFactor) float64 { - var v float64 - for i, s := range rt.stats { - v += float64(s) * weights[i] - } - if v < 0 { - return 0 - } - return expFactor.Value(v, rt.exp) / weightScaleFactor -} - -// AddStats adds the given ResponseTimeStats to the current one. -func (rt *ResponseTimeStats) AddStats(s *ResponseTimeStats) { - rt.setExp(s.exp) - for i, v := range s.stats { - rt.stats[i] += v - } -} - -// SubStats subtracts the given ResponseTimeStats from the current one. -func (rt *ResponseTimeStats) SubStats(s *ResponseTimeStats) { - rt.setExp(s.exp) - for i, v := range s.stats { - if v < rt.stats[i] { - rt.stats[i] -= v - } else { - rt.stats[i] = 0 - } - } -} - -// Timeout suggests a timeout value based on the previous distribution. The parameter -// is the desired rate of timeouts assuming a similar distribution in the future. -// Note that the actual timeout should have a sensible minimum bound so that operating -// under ideal working conditions for a long time (for example, using a local server -// with very low response times) will not make it very hard for the system to accommodate -// longer response times in the future. -func (rt ResponseTimeStats) Timeout(failRatio float64) time.Duration { - var sum uint64 - for _, v := range rt.stats { - sum += v - } - s := uint64(float64(sum) * failRatio) - i := timeStatLength - 1 - for i > 0 && s >= rt.stats[i] { - s -= rt.stats[i] - i-- - } - r := float64(i) + 0.5 - if rt.stats[i] > 0 { - r -= float64(s) / float64(rt.stats[i]) - } - if r < 0 { - r = 0 - } - th := StatScaleToTime(r) - if th > maxResponseTime { - th = maxResponseTime - } - return th -} - -// RtDistribution represents a distribution as a series of (X, Y) chart coordinates, -// where the X axis is the response time in seconds while the Y axis is the amount of -// service value received with a response time close to the X coordinate. -type RtDistribution [timeStatLength][2]float64 - -// Distribution returns a RtDistribution, optionally normalized to a sum of 1. -func (rt ResponseTimeStats) Distribution(normalized bool, expFactor utils.ExpirationFactor) (res RtDistribution) { - var mul float64 - if normalized { - var sum uint64 - for _, v := range rt.stats { - sum += v - } - if sum > 0 { - mul = 1 / float64(sum) - } - } else { - mul = expFactor.Value(float64(1)/weightScaleFactor, rt.exp) - } - for i, v := range rt.stats { - res[i][0] = float64(StatScaleToTime(float64(i))) / float64(time.Second) - res[i][1] = float64(v) * mul - } - return -} diff --git a/les/vflux/client/timestats_test.go b/les/vflux/client/timestats_test.go deleted file mode 100644 index a28460171..000000000 --- a/les/vflux/client/timestats_test.go +++ /dev/null @@ -1,137 +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 client - -import ( - "math" - "math/rand" - "testing" - "time" - - "github.com/ethereum/go-ethereum/les/utils" -) - -func TestTransition(t *testing.T) { - var epsilon = 0.01 - var cases = []time.Duration{ - time.Millisecond, minResponseTime, - time.Second, time.Second * 5, maxResponseTime, - } - for _, c := range cases { - got := StatScaleToTime(TimeToStatScale(c)) - if float64(got)*(1+epsilon) < float64(c) || float64(got)*(1-epsilon) > float64(c) { - t.Fatalf("Failed to transition back") - } - } - // If the time is too large(exceeds the max response time. - got := StatScaleToTime(TimeToStatScale(2 * maxResponseTime)) - if float64(got)*(1+epsilon) < float64(maxResponseTime) || float64(got)*(1-epsilon) > float64(maxResponseTime) { - t.Fatalf("Failed to transition back") - } -} - -var maxResponseWeights = TimeoutWeights(maxResponseTime) - -func TestValue(t *testing.T) { - noexp := utils.ExpirationFactor{Factor: 1} - for i := 0; i < 1000; i++ { - max := minResponseTime + time.Duration(rand.Int63n(int64(maxResponseTime-minResponseTime))) - min := minResponseTime + time.Duration(rand.Int63n(int64(max-minResponseTime))) - timeout := max/2 + time.Duration(rand.Int63n(int64(maxResponseTime-max/2))) - s := makeRangeStats(min, max, 1000, noexp) - value := s.Value(TimeoutWeights(timeout), noexp) - // calculate the average weight (the average of the given range of the half cosine - // weight function). - minx := math.Pi / 2 * float64(min) / float64(timeout) - maxx := math.Pi / 2 * float64(max) / float64(timeout) - avgWeight := (math.Sin(maxx) - math.Sin(minx)) / (maxx - minx) - expv := 1000 * avgWeight - if expv < 0 { - expv = 0 - } - if value < expv-10 || value > expv+10 { - t.Errorf("Value failed (expected %v, got %v)", expv, value) - } - } -} - -func TestAddSubExpire(t *testing.T) { - var ( - sum1, sum2 ResponseTimeStats - sum1ValueExp, sum2ValueExp float64 - logOffset utils.Fixed64 - ) - for i := 0; i < 1000; i++ { - exp := utils.ExpFactor(logOffset) - max := minResponseTime + time.Duration(rand.Int63n(int64(maxResponseTime-minResponseTime))) - min := minResponseTime + time.Duration(rand.Int63n(int64(max-minResponseTime))) - s := makeRangeStats(min, max, 1000, exp) - value := s.Value(maxResponseWeights, exp) - sum1.AddStats(&s) - sum1ValueExp += value - if rand.Intn(2) == 1 { - sum2.AddStats(&s) - sum2ValueExp += value - } - logOffset += utils.Float64ToFixed64(0.001 / math.Log(2)) - sum1ValueExp -= sum1ValueExp * 0.001 - sum2ValueExp -= sum2ValueExp * 0.001 - } - exp := utils.ExpFactor(logOffset) - sum1Value := sum1.Value(maxResponseWeights, exp) - if sum1Value < sum1ValueExp*0.99 || sum1Value > sum1ValueExp*1.01 { - t.Errorf("sum1Value failed (expected %v, got %v)", sum1ValueExp, sum1Value) - } - sum2Value := sum2.Value(maxResponseWeights, exp) - if sum2Value < sum2ValueExp*0.99 || sum2Value > sum2ValueExp*1.01 { - t.Errorf("sum2Value failed (expected %v, got %v)", sum2ValueExp, sum2Value) - } - diff := sum1 - diff.SubStats(&sum2) - diffValue := diff.Value(maxResponseWeights, exp) - diffValueExp := sum1ValueExp - sum2ValueExp - if diffValue < diffValueExp*0.99 || diffValue > diffValueExp*1.01 { - t.Errorf("diffValue failed (expected %v, got %v)", diffValueExp, diffValue) - } -} - -func TestTimeout(t *testing.T) { - testTimeoutRange(t, 0, time.Second) - testTimeoutRange(t, time.Second, time.Second*2) - testTimeoutRange(t, time.Second, maxResponseTime) -} - -func testTimeoutRange(t *testing.T, min, max time.Duration) { - s := makeRangeStats(min, max, 1000, utils.ExpirationFactor{Factor: 1}) - for i := 2; i < 9; i++ { - to := s.Timeout(float64(i) / 10) - exp := max - (max-min)*time.Duration(i)/10 - tol := (max - min) / 50 - if to < exp-tol || to > exp+tol { - t.Errorf("Timeout failed (expected %v, got %v)", exp, to) - } - } -} - -func makeRangeStats(min, max time.Duration, amount float64, exp utils.ExpirationFactor) ResponseTimeStats { - var s ResponseTimeStats - amount /= 1000 - for i := 0; i < 1000; i++ { - s.Add(min+(max-min)*time.Duration(i)/999, amount, exp) - } - return s -} diff --git a/les/vflux/client/valuetracker.go b/les/vflux/client/valuetracker.go deleted file mode 100644 index 806d0c7d7..000000000 --- a/les/vflux/client/valuetracker.go +++ /dev/null @@ -1,506 +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 client - -import ( - "bytes" - "fmt" - "math" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/rlp" -) - -const ( - vtVersion = 1 // database encoding format for ValueTracker - nvtVersion = 1 // database encoding format for NodeValueTracker -) - -var ( - vtKey = []byte("vt:") - vtNodeKey = []byte("vtNode:") -) - -// NodeValueTracker collects service value statistics for a specific server node -type NodeValueTracker struct { - lock sync.Mutex - - vt *ValueTracker - rtStats, lastRtStats ResponseTimeStats - lastTransfer mclock.AbsTime - basket serverBasket - reqCosts []uint64 - reqValues []float64 -} - -// UpdateCosts updates the node value tracker's request cost table -func (nv *NodeValueTracker) UpdateCosts(reqCosts []uint64) { - nv.vt.lock.Lock() - defer nv.vt.lock.Unlock() - - nv.updateCosts(reqCosts, nv.vt.refBasket.reqValues, nv.vt.refBasket.reqValueFactor(reqCosts)) -} - -// updateCosts updates the request cost table of the server. The request value factor -// is also updated based on the given cost table and the current reference basket. -// Note that the contents of the referenced reqValues slice will not change; a new -// reference is passed if the values are updated by ValueTracker. -func (nv *NodeValueTracker) updateCosts(reqCosts []uint64, reqValues []float64, rvFactor float64) { - nv.lock.Lock() - defer nv.lock.Unlock() - - nv.reqCosts = reqCosts - nv.reqValues = reqValues - nv.basket.updateRvFactor(rvFactor) -} - -// transferStats returns request basket and response time statistics that should be -// added to the global statistics. The contents of the server's own request basket are -// gradually transferred to the main reference basket and removed from the server basket -// with the specified transfer rate. -// The response time statistics are retained at both places and therefore the global -// distribution is always the sum of the individual server distributions. -func (nv *NodeValueTracker) transferStats(now mclock.AbsTime, transferRate float64) (requestBasket, ResponseTimeStats) { - nv.lock.Lock() - defer nv.lock.Unlock() - - dt := now - nv.lastTransfer - nv.lastTransfer = now - if dt < 0 { - dt = 0 - } - recentRtStats := nv.rtStats - recentRtStats.SubStats(&nv.lastRtStats) - nv.lastRtStats = nv.rtStats - return nv.basket.transfer(-math.Expm1(-transferRate * float64(dt))), recentRtStats -} - -type ServedRequest struct { - ReqType, Amount uint32 -} - -// Served adds a served request to the node's statistics. An actual request may be composed -// of one or more request types (service vector indices). -func (nv *NodeValueTracker) Served(reqs []ServedRequest, respTime time.Duration) { - nv.vt.statsExpLock.RLock() - expFactor := nv.vt.statsExpFactor - nv.vt.statsExpLock.RUnlock() - - nv.lock.Lock() - defer nv.lock.Unlock() - - var value float64 - for _, r := range reqs { - nv.basket.add(r.ReqType, r.Amount, nv.reqCosts[r.ReqType]*uint64(r.Amount), expFactor) - value += nv.reqValues[r.ReqType] * float64(r.Amount) - } - nv.rtStats.Add(respTime, value, expFactor) -} - -// RtStats returns the node's own response time distribution statistics -func (nv *NodeValueTracker) RtStats() ResponseTimeStats { - nv.lock.Lock() - defer nv.lock.Unlock() - - return nv.rtStats -} - -// ValueTracker coordinates service value calculation for individual servers and updates -// global statistics -type ValueTracker struct { - clock mclock.Clock - lock sync.Mutex - quit chan chan struct{} - db ethdb.KeyValueStore - connected map[enode.ID]*NodeValueTracker - reqTypeCount int - - refBasket referenceBasket - mappings [][]string - currentMapping int - initRefBasket requestBasket - rtStats ResponseTimeStats - - transferRate float64 - statsExpLock sync.RWMutex - statsExpRate, offlineExpRate float64 - statsExpirer utils.Expirer - statsExpFactor utils.ExpirationFactor -} - -type valueTrackerEncV1 struct { - Mappings [][]string - RefBasketMapping uint - RefBasket requestBasket - RtStats ResponseTimeStats - ExpOffset, SavedAt uint64 -} - -type nodeValueTrackerEncV1 struct { - RtStats ResponseTimeStats - ServerBasketMapping uint - ServerBasket requestBasket -} - -// RequestInfo is an initializer structure for the service vector. -type RequestInfo struct { - // Name identifies the request type and is used for re-mapping the service vector if necessary - Name string - // InitAmount and InitValue are used to initialize the reference basket - InitAmount, InitValue float64 -} - -// NewValueTracker creates a new ValueTracker and loads its previously saved state from -// the database if possible. -func NewValueTracker(db ethdb.KeyValueStore, clock mclock.Clock, reqInfo []RequestInfo, updatePeriod time.Duration, transferRate, statsExpRate, offlineExpRate float64) *ValueTracker { - now := clock.Now() - - initRefBasket := requestBasket{items: make([]basketItem, len(reqInfo))} - mapping := make([]string, len(reqInfo)) - - var sumAmount, sumValue float64 - for _, req := range reqInfo { - sumAmount += req.InitAmount - sumValue += req.InitAmount * req.InitValue - } - scaleValues := sumAmount * basketFactor / sumValue - for i, req := range reqInfo { - mapping[i] = req.Name - initRefBasket.items[i].amount = uint64(req.InitAmount * basketFactor) - initRefBasket.items[i].value = uint64(req.InitAmount * req.InitValue * scaleValues) - } - - vt := &ValueTracker{ - clock: clock, - connected: make(map[enode.ID]*NodeValueTracker), - quit: make(chan chan struct{}), - db: db, - reqTypeCount: len(initRefBasket.items), - initRefBasket: initRefBasket, - transferRate: transferRate, - statsExpRate: statsExpRate, - offlineExpRate: offlineExpRate, - } - if vt.loadFromDb(mapping) != nil { - // previous state not saved or invalid, init with default values - vt.refBasket.basket = initRefBasket - vt.mappings = [][]string{mapping} - vt.currentMapping = 0 - } - vt.statsExpirer.SetRate(now, statsExpRate) - vt.refBasket.init(vt.reqTypeCount) - vt.periodicUpdate() - - go func() { - for { - select { - case <-clock.After(updatePeriod): - vt.lock.Lock() - vt.periodicUpdate() - vt.lock.Unlock() - case quit := <-vt.quit: - close(quit) - return - } - } - }() - return vt -} - -// StatsExpirer returns the statistics expirer so that other values can be expired -// with the same rate as the service value statistics. -func (vt *ValueTracker) StatsExpirer() *utils.Expirer { - return &vt.statsExpirer -} - -// StatsExpFactor returns the current expiration factor so that other values can be expired -// with the same rate as the service value statistics. -func (vt *ValueTracker) StatsExpFactor() utils.ExpirationFactor { - vt.statsExpLock.RLock() - defer vt.statsExpLock.RUnlock() - - return vt.statsExpFactor -} - -// loadFromDb loads the value tracker's state from the database and converts saved -// request basket index mapping if it does not match the specified index to name mapping. -func (vt *ValueTracker) loadFromDb(mapping []string) error { - enc, err := vt.db.Get(vtKey) - if err != nil { - return err - } - r := bytes.NewReader(enc) - var version uint - if err := rlp.Decode(r, &version); err != nil { - log.Error("Decoding value tracker state failed", "err", err) - return err - } - if version != vtVersion { - log.Error("Unknown ValueTracker version", "stored", version, "current", nvtVersion) - return fmt.Errorf("Unknown ValueTracker version %d (current version is %d)", version, vtVersion) - } - var vte valueTrackerEncV1 - if err := rlp.Decode(r, &vte); err != nil { - log.Error("Decoding value tracker state failed", "err", err) - return err - } - logOffset := utils.Fixed64(vte.ExpOffset) - dt := time.Now().UnixNano() - int64(vte.SavedAt) - if dt > 0 { - logOffset += utils.Float64ToFixed64(float64(dt) * vt.offlineExpRate / math.Log(2)) - } - vt.statsExpirer.SetLogOffset(vt.clock.Now(), logOffset) - vt.rtStats = vte.RtStats - vt.mappings = vte.Mappings - vt.currentMapping = -1 -loop: - for i, m := range vt.mappings { - if len(m) != len(mapping) { - continue loop - } - for j, s := range mapping { - if m[j] != s { - continue loop - } - } - vt.currentMapping = i - break - } - if vt.currentMapping == -1 { - vt.currentMapping = len(vt.mappings) - vt.mappings = append(vt.mappings, mapping) - } - if int(vte.RefBasketMapping) == vt.currentMapping { - vt.refBasket.basket = vte.RefBasket - } else { - if vte.RefBasketMapping >= uint(len(vt.mappings)) { - log.Error("Unknown request basket mapping", "stored", vte.RefBasketMapping, "current", vt.currentMapping) - return fmt.Errorf("Unknown request basket mapping %d (current version is %d)", vte.RefBasketMapping, vt.currentMapping) - } - vt.refBasket.basket = vte.RefBasket.convertMapping(vt.mappings[vte.RefBasketMapping], mapping, vt.initRefBasket) - } - return nil -} - -// saveToDb saves the value tracker's state to the database -func (vt *ValueTracker) saveToDb() { - vte := valueTrackerEncV1{ - Mappings: vt.mappings, - RefBasketMapping: uint(vt.currentMapping), - RefBasket: vt.refBasket.basket, - RtStats: vt.rtStats, - ExpOffset: uint64(vt.statsExpirer.LogOffset(vt.clock.Now())), - SavedAt: uint64(time.Now().UnixNano()), - } - enc1, err := rlp.EncodeToBytes(uint(vtVersion)) - if err != nil { - log.Error("Encoding value tracker state failed", "err", err) - return - } - enc2, err := rlp.EncodeToBytes(&vte) - if err != nil { - log.Error("Encoding value tracker state failed", "err", err) - return - } - if err := vt.db.Put(vtKey, append(enc1, enc2...)); err != nil { - log.Error("Saving value tracker state failed", "err", err) - } -} - -// Stop saves the value tracker's state and each loaded node's individual state and -// returns after shutting the internal goroutines down. -func (vt *ValueTracker) Stop() { - quit := make(chan struct{}) - vt.quit <- quit - <-quit - vt.lock.Lock() - vt.periodicUpdate() - for id, nv := range vt.connected { - vt.saveNode(id, nv) - } - vt.connected = nil - vt.saveToDb() - vt.lock.Unlock() -} - -// Register adds a server node to the value tracker -func (vt *ValueTracker) Register(id enode.ID) *NodeValueTracker { - vt.lock.Lock() - defer vt.lock.Unlock() - - if vt.connected == nil { - // ValueTracker has already been stopped - return nil - } - nv := vt.loadOrNewNode(id) - reqTypeCount := len(vt.refBasket.reqValues) - nv.reqCosts = make([]uint64, reqTypeCount) - nv.lastTransfer = vt.clock.Now() - nv.reqValues = vt.refBasket.reqValues - nv.basket.init(reqTypeCount) - - vt.connected[id] = nv - return nv -} - -// Unregister removes a server node from the value tracker -func (vt *ValueTracker) Unregister(id enode.ID) { - vt.lock.Lock() - defer vt.lock.Unlock() - - if nv := vt.connected[id]; nv != nil { - vt.saveNode(id, nv) - delete(vt.connected, id) - } -} - -// GetNode returns an individual server node's value tracker. If it did not exist before -// then a new node is created. -func (vt *ValueTracker) GetNode(id enode.ID) *NodeValueTracker { - vt.lock.Lock() - defer vt.lock.Unlock() - - return vt.loadOrNewNode(id) -} - -// loadOrNewNode returns an individual server node's value tracker. If it did not exist before -// then a new node is created. -func (vt *ValueTracker) loadOrNewNode(id enode.ID) *NodeValueTracker { - if nv, ok := vt.connected[id]; ok { - return nv - } - nv := &NodeValueTracker{vt: vt, lastTransfer: vt.clock.Now()} - enc, err := vt.db.Get(append(vtNodeKey, id[:]...)) - if err != nil { - return nv - } - r := bytes.NewReader(enc) - var version uint - if err := rlp.Decode(r, &version); err != nil { - log.Error("Failed to decode node value tracker", "id", id, "err", err) - return nv - } - if version != nvtVersion { - log.Error("Unknown NodeValueTracker version", "stored", version, "current", nvtVersion) - return nv - } - var nve nodeValueTrackerEncV1 - if err := rlp.Decode(r, &nve); err != nil { - log.Error("Failed to decode node value tracker", "id", id, "err", err) - return nv - } - nv.rtStats = nve.RtStats - nv.lastRtStats = nve.RtStats - if int(nve.ServerBasketMapping) == vt.currentMapping { - nv.basket.basket = nve.ServerBasket - } else { - if nve.ServerBasketMapping >= uint(len(vt.mappings)) { - log.Error("Unknown request basket mapping", "stored", nve.ServerBasketMapping, "current", vt.currentMapping) - return nv - } - nv.basket.basket = nve.ServerBasket.convertMapping(vt.mappings[nve.ServerBasketMapping], vt.mappings[vt.currentMapping], vt.initRefBasket) - } - return nv -} - -// saveNode saves a server node's value tracker to the database -func (vt *ValueTracker) saveNode(id enode.ID, nv *NodeValueTracker) { - recentRtStats := nv.rtStats - recentRtStats.SubStats(&nv.lastRtStats) - vt.rtStats.AddStats(&recentRtStats) - nv.lastRtStats = nv.rtStats - - nve := nodeValueTrackerEncV1{ - RtStats: nv.rtStats, - ServerBasketMapping: uint(vt.currentMapping), - ServerBasket: nv.basket.basket, - } - enc1, err := rlp.EncodeToBytes(uint(nvtVersion)) - if err != nil { - log.Error("Failed to encode service value information", "id", id, "err", err) - return - } - enc2, err := rlp.EncodeToBytes(&nve) - if err != nil { - log.Error("Failed to encode service value information", "id", id, "err", err) - return - } - if err := vt.db.Put(append(vtNodeKey, id[:]...), append(enc1, enc2...)); err != nil { - log.Error("Failed to save service value information", "id", id, "err", err) - } -} - -// RtStats returns the global response time distribution statistics -func (vt *ValueTracker) RtStats() ResponseTimeStats { - vt.lock.Lock() - defer vt.lock.Unlock() - - vt.periodicUpdate() - return vt.rtStats -} - -// periodicUpdate transfers individual node data to the global statistics, normalizes -// the reference basket and updates request values. The global state is also saved to -// the database with each update. -func (vt *ValueTracker) periodicUpdate() { - now := vt.clock.Now() - vt.statsExpLock.Lock() - vt.statsExpFactor = utils.ExpFactor(vt.statsExpirer.LogOffset(now)) - vt.statsExpLock.Unlock() - - for _, nv := range vt.connected { - basket, rtStats := nv.transferStats(now, vt.transferRate) - vt.refBasket.add(basket) - vt.rtStats.AddStats(&rtStats) - } - vt.refBasket.normalize() - vt.refBasket.updateReqValues() - for _, nv := range vt.connected { - nv.updateCosts(nv.reqCosts, vt.refBasket.reqValues, vt.refBasket.reqValueFactor(nv.reqCosts)) - } - vt.saveToDb() -} - -type RequestStatsItem struct { - Name string - ReqAmount, ReqValue float64 -} - -// RequestStats returns the current contents of the reference request basket, with -// request values meaning average per request rather than total. -func (vt *ValueTracker) RequestStats() []RequestStatsItem { - vt.statsExpLock.RLock() - expFactor := vt.statsExpFactor - vt.statsExpLock.RUnlock() - vt.lock.Lock() - defer vt.lock.Unlock() - - vt.periodicUpdate() - res := make([]RequestStatsItem, len(vt.refBasket.basket.items)) - for i, item := range vt.refBasket.basket.items { - res[i].Name = vt.mappings[vt.currentMapping][i] - res[i].ReqAmount = expFactor.Value(float64(item.amount)/basketFactor, vt.refBasket.basket.exp) - res[i].ReqValue = vt.refBasket.reqValues[i] - } - return res -} diff --git a/les/vflux/client/valuetracker_test.go b/les/vflux/client/valuetracker_test.go deleted file mode 100644 index 87a337be8..000000000 --- a/les/vflux/client/valuetracker_test.go +++ /dev/null @@ -1,135 +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 client - -import ( - "math" - "math/rand" - "strconv" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/ethdb/memorydb" - "github.com/ethereum/go-ethereum/p2p/enode" - - "github.com/ethereum/go-ethereum/les/utils" -) - -const ( - testReqTypes = 3 - testNodeCount = 5 - testReqCount = 10000 - testRounds = 10 -) - -func TestValueTracker(t *testing.T) { - db := memorydb.New() - clock := &mclock.Simulated{} - requestList := make([]RequestInfo, testReqTypes) - relPrices := make([]float64, testReqTypes) - totalAmount := make([]uint64, testReqTypes) - for i := range requestList { - requestList[i] = RequestInfo{Name: "testreq" + strconv.Itoa(i), InitAmount: 1, InitValue: 1} - totalAmount[i] = 1 - relPrices[i] = rand.Float64() + 0.1 - } - nodes := make([]*NodeValueTracker, testNodeCount) - for round := 0; round < testRounds; round++ { - makeRequests := round < testRounds-2 - useExpiration := round == testRounds-1 - var expRate float64 - if useExpiration { - expRate = math.Log(2) / float64(time.Hour*100) - } - - vt := NewValueTracker(db, clock, requestList, time.Minute, 1/float64(time.Hour), expRate, expRate) - updateCosts := func(i int) { - costList := make([]uint64, testReqTypes) - baseCost := rand.Float64()*10000000 + 100000 - for j := range costList { - costList[j] = uint64(baseCost * relPrices[j]) - } - nodes[i].UpdateCosts(costList) - } - for i := range nodes { - nodes[i] = vt.Register(enode.ID{byte(i)}) - updateCosts(i) - } - if makeRequests { - for i := 0; i < testReqCount; i++ { - reqType := rand.Intn(testReqTypes) - reqAmount := rand.Intn(10) + 1 - node := rand.Intn(testNodeCount) - respTime := time.Duration((rand.Float64() + 1) * float64(time.Second) * float64(node+1) / testNodeCount) - totalAmount[reqType] += uint64(reqAmount) - nodes[node].Served([]ServedRequest{{uint32(reqType), uint32(reqAmount)}}, respTime) - clock.Run(time.Second) - } - } else { - clock.Run(time.Hour * 100) - if useExpiration { - for i, a := range totalAmount { - totalAmount[i] = a / 2 - } - } - } - vt.Stop() - var sumrp, sumrv float64 - for i, rp := range relPrices { - sumrp += rp - sumrv += vt.refBasket.reqValues[i] - } - for i, rp := range relPrices { - ratio := vt.refBasket.reqValues[i] * sumrp / (rp * sumrv) - if ratio < 0.99 || ratio > 1.01 { - t.Errorf("reqValues (%v) does not match relPrices (%v)", vt.refBasket.reqValues, relPrices) - break - } - } - exp := utils.ExpFactor(vt.StatsExpirer().LogOffset(clock.Now())) - basketAmount := make([]uint64, testReqTypes) - for i, bi := range vt.refBasket.basket.items { - basketAmount[i] += uint64(exp.Value(float64(bi.amount), vt.refBasket.basket.exp)) - } - if makeRequests { - // if we did not make requests in this round then we expect all amounts to be - // in the reference basket - for _, node := range nodes { - for i, bi := range node.basket.basket.items { - basketAmount[i] += uint64(exp.Value(float64(bi.amount), node.basket.basket.exp)) - } - } - } - for i, a := range basketAmount { - amount := a / basketFactor - if amount+10 < totalAmount[i] || amount > totalAmount[i]+10 { - t.Errorf("totalAmount[%d] mismatch in round %d (expected %d, got %d)", i, round, totalAmount[i], amount) - } - } - var sumValue float64 - for _, node := range nodes { - s := node.RtStats() - sumValue += s.Value(maxResponseWeights, exp) - } - s := vt.RtStats() - mainValue := s.Value(maxResponseWeights, exp) - if sumValue < mainValue-10 || sumValue > mainValue+10 { - t.Errorf("Main rtStats value does not match sum of node rtStats values in round %d (main %v, sum %v)", round, mainValue, sumValue) - } - } -} diff --git a/les/vflux/client/wrsiterator.go b/les/vflux/client/wrsiterator.go deleted file mode 100644 index 1b37cba6e..000000000 --- a/les/vflux/client/wrsiterator.go +++ /dev/null @@ -1,127 +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 client - -import ( - "sync" - - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/nodestate" -) - -// WrsIterator returns nodes from the specified selectable set with a weighted random -// selection. Selection weights are provided by a callback function. -type WrsIterator struct { - lock sync.Mutex - cond *sync.Cond - - ns *nodestate.NodeStateMachine - wrs *utils.WeightedRandomSelect - nextNode *enode.Node - closed bool -} - -// NewWrsIterator creates a new WrsIterator. Nodes are selectable if they have all the required -// and none of the disabled flags set. When a node is selected the selectedFlag is set which also -// disables further selectability until it is removed or times out. -func NewWrsIterator(ns *nodestate.NodeStateMachine, requireFlags, disableFlags nodestate.Flags, weightField nodestate.Field) *WrsIterator { - wfn := func(i interface{}) uint64 { - n := ns.GetNode(i.(enode.ID)) - if n == nil { - return 0 - } - wt, _ := ns.GetField(n, weightField).(uint64) - return wt - } - - w := &WrsIterator{ - ns: ns, - wrs: utils.NewWeightedRandomSelect(wfn), - } - w.cond = sync.NewCond(&w.lock) - - ns.SubscribeField(weightField, func(n *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { - if state.HasAll(requireFlags) && state.HasNone(disableFlags) { - w.lock.Lock() - w.wrs.Update(n.ID()) - w.lock.Unlock() - w.cond.Signal() - } - }) - - ns.SubscribeState(requireFlags.Or(disableFlags), func(n *enode.Node, oldState, newState nodestate.Flags) { - oldMatch := oldState.HasAll(requireFlags) && oldState.HasNone(disableFlags) - newMatch := newState.HasAll(requireFlags) && newState.HasNone(disableFlags) - if newMatch == oldMatch { - return - } - - w.lock.Lock() - if newMatch { - w.wrs.Update(n.ID()) - } else { - w.wrs.Remove(n.ID()) - } - w.lock.Unlock() - w.cond.Signal() - }) - return w -} - -// Next selects the next node. -func (w *WrsIterator) Next() bool { - w.nextNode = w.chooseNode() - return w.nextNode != nil -} - -func (w *WrsIterator) chooseNode() *enode.Node { - w.lock.Lock() - defer w.lock.Unlock() - - for { - for !w.closed && w.wrs.IsEmpty() { - w.cond.Wait() - } - if w.closed { - return nil - } - // Choose the next node at random. Even though w.wrs is guaranteed - // non-empty here, Choose might return nil if all items have weight - // zero. - if c := w.wrs.Choose(); c != nil { - id := c.(enode.ID) - w.wrs.Remove(id) - return w.ns.GetNode(id) - } - } -} - -// Close ends the iterator. -func (w *WrsIterator) Close() { - w.lock.Lock() - w.closed = true - w.lock.Unlock() - w.cond.Signal() -} - -// Node returns the current node. -func (w *WrsIterator) Node() *enode.Node { - w.lock.Lock() - defer w.lock.Unlock() - return w.nextNode -} diff --git a/les/vflux/client/wrsiterator_test.go b/les/vflux/client/wrsiterator_test.go deleted file mode 100644 index 77bb5ee0c..000000000 --- a/les/vflux/client/wrsiterator_test.go +++ /dev/null @@ -1,103 +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 client - -import ( - "reflect" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/p2p/nodestate" -) - -var ( - testSetup = &nodestate.Setup{} - sfTest1 = testSetup.NewFlag("test1") - sfTest2 = testSetup.NewFlag("test2") - sfTest3 = testSetup.NewFlag("test3") - sfTest4 = testSetup.NewFlag("test4") - sfiTestWeight = testSetup.NewField("nodeWeight", reflect.TypeOf(uint64(0))) -) - -const iterTestNodeCount = 6 - -func TestWrsIterator(t *testing.T) { - ns := nodestate.NewNodeStateMachine(nil, nil, &mclock.Simulated{}, testSetup) - w := NewWrsIterator(ns, sfTest2, sfTest3.Or(sfTest4), sfiTestWeight) - ns.Start() - for i := 1; i <= iterTestNodeCount; i++ { - ns.SetState(testNode(i), sfTest1, nodestate.Flags{}, 0) - ns.SetField(testNode(i), sfiTestWeight, uint64(1)) - } - next := func() int { - ch := make(chan struct{}) - go func() { - w.Next() - close(ch) - }() - select { - case <-ch: - case <-time.After(time.Second * 5): - t.Fatalf("Iterator.Next() timeout") - } - node := w.Node() - ns.SetState(node, sfTest4, nodestate.Flags{}, 0) - return testNodeIndex(node.ID()) - } - set := make(map[int]bool) - expset := func() { - for len(set) > 0 { - n := next() - if !set[n] { - t.Errorf("Item returned by iterator not in the expected set (got %d)", n) - } - delete(set, n) - } - } - - ns.SetState(testNode(1), sfTest2, nodestate.Flags{}, 0) - ns.SetState(testNode(2), sfTest2, nodestate.Flags{}, 0) - ns.SetState(testNode(3), sfTest2, nodestate.Flags{}, 0) - set[1] = true - set[2] = true - set[3] = true - expset() - ns.SetState(testNode(4), sfTest2, nodestate.Flags{}, 0) - ns.SetState(testNode(5), sfTest2.Or(sfTest3), nodestate.Flags{}, 0) - ns.SetState(testNode(6), sfTest2, nodestate.Flags{}, 0) - set[4] = true - set[6] = true - expset() - ns.SetField(testNode(2), sfiTestWeight, uint64(0)) - ns.SetState(testNode(1), nodestate.Flags{}, sfTest4, 0) - ns.SetState(testNode(2), nodestate.Flags{}, sfTest4, 0) - ns.SetState(testNode(3), nodestate.Flags{}, sfTest4, 0) - set[1] = true - set[3] = true - expset() - ns.SetField(testNode(2), sfiTestWeight, uint64(1)) - ns.SetState(testNode(2), nodestate.Flags{}, sfTest2, 0) - ns.SetState(testNode(1), nodestate.Flags{}, sfTest4, 0) - ns.SetState(testNode(2), sfTest2, sfTest4, 0) - ns.SetState(testNode(3), nodestate.Flags{}, sfTest4, 0) - set[1] = true - set[2] = true - set[3] = true - expset() - ns.Stop() -} diff --git a/les/vflux/requests.go b/les/vflux/requests.go deleted file mode 100644 index 5abae2f53..000000000 --- a/les/vflux/requests.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2021 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 vflux - -import ( - "errors" - "math" - "math/big" - - "github.com/ethereum/go-ethereum/rlp" -) - -var ErrNoReply = errors.New("no reply for given request") - -const ( - MaxRequestLength = 16 // max number of individual requests in a batch - CapacityQueryName = "cq" - CapacityQueryMaxLen = 16 -) - -type ( - // Request describes a single vflux request inside a batch. Service and request - // type are identified by strings, parameters are RLP encoded. - Request struct { - Service, Name string - Params []byte - } - // Requests are a batch of vflux requests - Requests []Request - - // Replies are the replies to a batch of requests - Replies [][]byte - - // CapacityQueryReq is the encoding format of the capacity query - CapacityQueryReq struct { - Bias uint64 // seconds - AddTokens []IntOrInf - } - // CapacityQueryReply is the encoding format of the response to the capacity query - CapacityQueryReply []uint64 -) - -// Add encodes and adds a new request to the batch -func (r *Requests) Add(service, name string, val interface{}) (int, error) { - enc, err := rlp.EncodeToBytes(val) - if err != nil { - return -1, err - } - *r = append(*r, Request{ - Service: service, - Name: name, - Params: enc, - }) - return len(*r) - 1, nil -} - -// Get decodes the reply to the i-th request in the batch -func (r Replies) Get(i int, val interface{}) error { - if i < 0 || i >= len(r) { - return ErrNoReply - } - return rlp.DecodeBytes(r[i], val) -} - -const ( - IntNonNegative = iota - IntNegative - IntPlusInf - IntMinusInf -) - -// IntOrInf is the encoding format for arbitrary length signed integers that can also -// hold the values of +Inf or -Inf -type IntOrInf struct { - Type uint8 - Value big.Int -} - -// BigInt returns the value as a big.Int or panics if the value is infinity -func (i *IntOrInf) BigInt() *big.Int { - switch i.Type { - case IntNonNegative: - return new(big.Int).Set(&i.Value) - case IntNegative: - return new(big.Int).Neg(&i.Value) - case IntPlusInf: - panic(nil) // caller should check Inf() before trying to convert to big.Int - case IntMinusInf: - panic(nil) - } - return &big.Int{} // invalid type decodes to 0 value -} - -// Inf returns 1 if the value is +Inf, -1 if it is -Inf, 0 otherwise -func (i *IntOrInf) Inf() int { - switch i.Type { - case IntPlusInf: - return 1 - case IntMinusInf: - return -1 - } - return 0 // invalid type decodes to 0 value -} - -// Int64 limits the value between MinInt64 and MaxInt64 (even if it is +-Inf) and returns an int64 type -func (i *IntOrInf) Int64() int64 { - switch i.Type { - case IntNonNegative: - if i.Value.IsInt64() { - return i.Value.Int64() - } else { - return math.MaxInt64 - } - case IntNegative: - if i.Value.IsInt64() { - return -i.Value.Int64() - } else { - return math.MinInt64 - } - case IntPlusInf: - return math.MaxInt64 - case IntMinusInf: - return math.MinInt64 - } - return 0 // invalid type decodes to 0 value -} - -// SetBigInt sets the value to the given big.Int -func (i *IntOrInf) SetBigInt(v *big.Int) { - if v.Sign() >= 0 { - i.Type = IntNonNegative - i.Value.Set(v) - } else { - i.Type = IntNegative - i.Value.Neg(v) - } -} - -// SetInt64 sets the value to the given int64. Note that MaxInt64 translates to +Inf -// while MinInt64 translates to -Inf. -func (i *IntOrInf) SetInt64(v int64) { - if v >= 0 { - if v == math.MaxInt64 { - i.Type = IntPlusInf - } else { - i.Type = IntNonNegative - i.Value.SetInt64(v) - } - } else { - if v == math.MinInt64 { - i.Type = IntMinusInf - } else { - i.Type = IntNegative - i.Value.SetInt64(-v) - } - } -} - -// SetInf sets the value to +Inf or -Inf -func (i *IntOrInf) SetInf(sign int) { - if sign == 1 { - i.Type = IntPlusInf - } else { - i.Type = IntMinusInf - } -} diff --git a/les/vflux/server/balance.go b/les/vflux/server/balance.go deleted file mode 100644 index b09f7bb50..000000000 --- a/les/vflux/server/balance.go +++ /dev/null @@ -1,693 +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 server - -import ( - "errors" - "math" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/nodestate" -) - -var errBalanceOverflow = errors.New("balance overflow") - -const maxBalance = math.MaxInt64 // maximum allowed balance value - -const ( - balanceCallbackUpdate = iota // called when priority drops below the last minimum estimate - balanceCallbackZero // called when priority drops to zero (positive balance exhausted) - balanceCallbackCount // total number of balance callbacks -) - -// PriceFactors determine the pricing policy (may apply either to positive or -// negative balances which may have different factors). -// - TimeFactor is cost unit per nanosecond of connection time -// - CapacityFactor is cost unit per nanosecond of connection time per 1000000 capacity -// - RequestFactor is cost unit per request "realCost" unit -type PriceFactors struct { - TimeFactor, CapacityFactor, RequestFactor float64 -} - -// connectionPrice returns the price of connection per nanosecond at the given capacity -// and the estimated average request cost. -func (p PriceFactors) connectionPrice(cap uint64, avgReqCost float64) float64 { - return p.TimeFactor + float64(cap)*p.CapacityFactor/1000000 + p.RequestFactor*avgReqCost -} - -type ( - // nodePriority interface provides current and estimated future priorities on demand - nodePriority interface { - // priority should return the current priority of the node (higher is better) - priority(cap uint64) int64 - // estimatePriority should return a lower estimate for the minimum of the node priority - // value starting from the current moment until the given time. If the priority goes - // under the returned estimate before the specified moment then it is the caller's - // responsibility to signal with updateFlag. - estimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64 - } - - // ReadOnlyBalance provides read-only operations on the node balance - ReadOnlyBalance interface { - nodePriority - GetBalance() (uint64, uint64) - GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue) - GetPriceFactors() (posFactor, negFactor PriceFactors) - } - - // ConnectedBalance provides operations permitted on connected nodes (non-read-only - // operations are not permitted inside a BalanceOperation) - ConnectedBalance interface { - ReadOnlyBalance - SetPriceFactors(posFactor, negFactor PriceFactors) - RequestServed(cost uint64) uint64 - } - - // AtomicBalanceOperator provides operations permitted in an atomic BalanceOperation - AtomicBalanceOperator interface { - ReadOnlyBalance - AddBalance(amount int64) (uint64, uint64, error) - SetBalance(pos, neg uint64) error - } -) - -// nodeBalance keeps track of the positive and negative balances of a connected -// client and calculates actual and projected future priority values. -// Implements nodePriority interface. -type nodeBalance struct { - bt *balanceTracker - lock sync.RWMutex - node *enode.Node - connAddress string - active, hasPriority, setFlags bool - capacity uint64 - balance balance - posFactor, negFactor PriceFactors - sumReqCost uint64 - lastUpdate, nextUpdate, initTime mclock.AbsTime - updateEvent mclock.Timer - // since only a limited and fixed number of callbacks are needed, they are - // stored in a fixed size array ordered by priority threshold. - callbacks [balanceCallbackCount]balanceCallback - // callbackIndex maps balanceCallback constants to callbacks array indexes (-1 if not active) - callbackIndex [balanceCallbackCount]int - callbackCount int // number of active callbacks -} - -// balance represents a pair of positive and negative balances -type balance struct { - pos, neg utils.ExpiredValue - posExp, negExp utils.ValueExpirer -} - -// posValue returns the value of positive balance at a given timestamp. -func (b balance) posValue(now mclock.AbsTime) uint64 { - return b.pos.Value(b.posExp.LogOffset(now)) -} - -// negValue returns the value of negative balance at a given timestamp. -func (b balance) negValue(now mclock.AbsTime) uint64 { - return b.neg.Value(b.negExp.LogOffset(now)) -} - -// addValue adds the value of a given amount to the balance. The original value and -// updated value will also be returned if the addition is successful. -// Returns the error if the given value is too large and the value overflows. -func (b *balance) addValue(now mclock.AbsTime, amount int64, pos bool, force bool) (uint64, uint64, int64, error) { - var ( - val utils.ExpiredValue - offset utils.Fixed64 - ) - if pos { - offset, val = b.posExp.LogOffset(now), b.pos - } else { - offset, val = b.negExp.LogOffset(now), b.neg - } - old := val.Value(offset) - if amount > 0 && (amount > maxBalance || old > maxBalance-uint64(amount)) { - if !force { - return old, 0, 0, errBalanceOverflow - } - val = utils.ExpiredValue{} - amount = maxBalance - } - net := val.Add(amount, offset) - if pos { - b.pos = val - } else { - b.neg = val - } - return old, val.Value(offset), net, nil -} - -// setValue sets the internal balance amount to the given values. Returns the -// error if the given value is too large. -func (b *balance) setValue(now mclock.AbsTime, pos uint64, neg uint64) error { - if pos > maxBalance || neg > maxBalance { - return errBalanceOverflow - } - var pb, nb utils.ExpiredValue - pb.Add(int64(pos), b.posExp.LogOffset(now)) - nb.Add(int64(neg), b.negExp.LogOffset(now)) - b.pos = pb - b.neg = nb - return nil -} - -// balanceCallback represents a single callback that is activated when client priority -// reaches the given threshold -type balanceCallback struct { - id int - threshold int64 - callback func() -} - -// GetBalance returns the current positive and negative balance. -func (n *nodeBalance) GetBalance() (uint64, uint64) { - n.lock.Lock() - defer n.lock.Unlock() - - now := n.bt.clock.Now() - n.updateBalance(now) - return n.balance.posValue(now), n.balance.negValue(now) -} - -// GetRawBalance returns the current positive and negative balance -// but in the raw(expired value) format. -func (n *nodeBalance) GetRawBalance() (utils.ExpiredValue, utils.ExpiredValue) { - n.lock.Lock() - defer n.lock.Unlock() - - now := n.bt.clock.Now() - n.updateBalance(now) - return n.balance.pos, n.balance.neg -} - -// AddBalance adds the given amount to the positive balance and returns the balance -// before and after the operation. Exceeding maxBalance results in an error (balance is -// unchanged) while adding a negative amount higher than the current balance results in -// zero balance. -// Note: this function should run inside a NodeStateMachine operation -func (n *nodeBalance) AddBalance(amount int64) (uint64, uint64, error) { - var ( - err error - old, new uint64 - now = n.bt.clock.Now() - callbacks []func() - setPriority bool - ) - // Operation with holding the lock - n.bt.updateTotalBalance(n, func() bool { - n.updateBalance(now) - if old, new, _, err = n.balance.addValue(now, amount, true, false); err != nil { - return false - } - callbacks, setPriority = n.checkCallbacks(now), n.checkPriorityStatus() - n.storeBalance(true, false) - return true - }) - if err != nil { - return old, old, err - } - // Operation without holding the lock - for _, cb := range callbacks { - cb() - } - if n.setFlags { - if setPriority { - n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0) - } - // Note: priority flag is automatically removed by the zero priority callback if necessary - n.signalPriorityUpdate() - } - return old, new, nil -} - -// SetBalance sets the positive and negative balance to the given values -// Note: this function should run inside a NodeStateMachine operation -func (n *nodeBalance) SetBalance(pos, neg uint64) error { - var ( - now = n.bt.clock.Now() - callbacks []func() - setPriority bool - ) - // Operation with holding the lock - n.bt.updateTotalBalance(n, func() bool { - n.updateBalance(now) - if err := n.balance.setValue(now, pos, neg); err != nil { - return false - } - callbacks, setPriority = n.checkCallbacks(now), n.checkPriorityStatus() - n.storeBalance(true, true) - return true - }) - // Operation without holding the lock - for _, cb := range callbacks { - cb() - } - if n.setFlags { - if setPriority { - n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0) - } - // Note: priority flag is automatically removed by the zero priority callback if necessary - n.signalPriorityUpdate() - } - return nil -} - -// RequestServed should be called after serving a request for the given peer -func (n *nodeBalance) RequestServed(cost uint64) (newBalance uint64) { - n.lock.Lock() - - var ( - check bool - fcost = float64(cost) - now = n.bt.clock.Now() - ) - n.updateBalance(now) - if !n.balance.pos.IsZero() { - posCost := -int64(fcost * n.posFactor.RequestFactor) - if posCost == 0 { - fcost = 0 - newBalance = n.balance.posValue(now) - } else { - var net int64 - _, newBalance, net, _ = n.balance.addValue(now, posCost, true, false) - if posCost == net { - fcost = 0 - } else { - fcost *= 1 - float64(net)/float64(posCost) - } - check = true - } - } - if fcost > 0 && n.negFactor.RequestFactor != 0 { - n.balance.addValue(now, int64(fcost*n.negFactor.RequestFactor), false, false) - check = true - } - n.sumReqCost += cost - - var callbacks []func() - if check { - callbacks = n.checkCallbacks(now) - } - n.lock.Unlock() - - if callbacks != nil { - n.bt.ns.Operation(func() { - for _, cb := range callbacks { - cb() - } - }) - } - return -} - -// priority returns the actual priority based on the current balance -func (n *nodeBalance) priority(capacity uint64) int64 { - n.lock.Lock() - defer n.lock.Unlock() - - now := n.bt.clock.Now() - n.updateBalance(now) - return n.balanceToPriority(now, n.balance, capacity) -} - -// EstMinPriority gives a lower estimate for the priority at a given time in the future. -// An average request cost per time is assumed that is twice the average cost per time -// in the current session. -// If update is true then a priority callback is added that turns updateFlag on and off -// in case the priority goes below the estimated minimum. -func (n *nodeBalance) estimatePriority(capacity uint64, addBalance int64, future, bias time.Duration, update bool) int64 { - n.lock.Lock() - defer n.lock.Unlock() - - now := n.bt.clock.Now() - n.updateBalance(now) - - b := n.balance // copy the balance - if addBalance != 0 { - b.addValue(now, addBalance, true, true) - } - if future > 0 { - var avgReqCost float64 - dt := time.Duration(n.lastUpdate - n.initTime) - if dt > time.Second { - avgReqCost = float64(n.sumReqCost) * 2 / float64(dt) - } - b = n.reducedBalance(b, now, future, capacity, avgReqCost) - } - if bias > 0 { - b = n.reducedBalance(b, now.Add(future), bias, capacity, 0) - } - pri := n.balanceToPriority(now, b, capacity) - // Ensure that biased estimates are always lower than actual priorities, even if - // the bias is very small. - // This ensures that two nodes will not ping-pong update signals forever if both of - // them have zero estimated priority drop in the projected future. - current := n.balanceToPriority(now, n.balance, capacity) - if pri >= current { - pri = current - 1 - } - if update { - n.addCallback(balanceCallbackUpdate, pri, n.signalPriorityUpdate) - } - return pri -} - -// SetPriceFactors sets the price factors. TimeFactor is the price of a nanosecond of -// connection while RequestFactor is the price of a request cost unit. -func (n *nodeBalance) SetPriceFactors(posFactor, negFactor PriceFactors) { - n.lock.Lock() - now := n.bt.clock.Now() - n.updateBalance(now) - n.posFactor, n.negFactor = posFactor, negFactor - callbacks := n.checkCallbacks(now) - n.lock.Unlock() - if callbacks != nil { - n.bt.ns.Operation(func() { - for _, cb := range callbacks { - cb() - } - }) - } -} - -// GetPriceFactors returns the price factors -func (n *nodeBalance) GetPriceFactors() (posFactor, negFactor PriceFactors) { - n.lock.Lock() - defer n.lock.Unlock() - - return n.posFactor, n.negFactor -} - -// activate starts time/capacity cost deduction. -func (n *nodeBalance) activate() { - n.bt.updateTotalBalance(n, func() bool { - if n.active { - return false - } - n.active = true - n.lastUpdate = n.bt.clock.Now() - return true - }) -} - -// deactivate stops time/capacity cost deduction and saves the balances in the database -func (n *nodeBalance) deactivate() { - n.bt.updateTotalBalance(n, func() bool { - if !n.active { - return false - } - n.updateBalance(n.bt.clock.Now()) - if n.updateEvent != nil { - n.updateEvent.Stop() - n.updateEvent = nil - } - n.storeBalance(true, true) - n.active = false - return true - }) -} - -// updateBalance updates balance based on the time factor -func (n *nodeBalance) updateBalance(now mclock.AbsTime) { - if n.active && now > n.lastUpdate { - n.balance = n.reducedBalance(n.balance, n.lastUpdate, time.Duration(now-n.lastUpdate), n.capacity, 0) - n.lastUpdate = now - } -} - -// storeBalance stores the positive and/or negative balance of the node in the database -func (n *nodeBalance) storeBalance(pos, neg bool) { - if pos { - n.bt.storeBalance(n.node.ID().Bytes(), false, n.balance.pos) - } - if neg { - n.bt.storeBalance([]byte(n.connAddress), true, n.balance.neg) - } -} - -// addCallback sets up a one-time callback to be called when priority reaches -// the threshold. If it has already reached the threshold the callback is called -// immediately. -// Note: should be called while n.lock is held -// Note 2: the callback function runs inside a NodeStateMachine operation -func (n *nodeBalance) addCallback(id int, threshold int64, callback func()) { - n.removeCallback(id) - idx := 0 - for idx < n.callbackCount && threshold > n.callbacks[idx].threshold { - idx++ - } - for i := n.callbackCount - 1; i >= idx; i-- { - n.callbackIndex[n.callbacks[i].id]++ - n.callbacks[i+1] = n.callbacks[i] - } - n.callbackCount++ - n.callbackIndex[id] = idx - n.callbacks[idx] = balanceCallback{id, threshold, callback} - now := n.bt.clock.Now() - n.updateBalance(now) - n.scheduleCheck(now) -} - -// removeCallback removes the given callback and returns true if it was active -// Note: should be called while n.lock is held -func (n *nodeBalance) removeCallback(id int) bool { - idx := n.callbackIndex[id] - if idx == -1 { - return false - } - n.callbackIndex[id] = -1 - for i := idx; i < n.callbackCount-1; i++ { - n.callbackIndex[n.callbacks[i+1].id]-- - n.callbacks[i] = n.callbacks[i+1] - } - n.callbackCount-- - return true -} - -// checkCallbacks checks whether the threshold of any of the active callbacks -// have been reached and returns triggered callbacks. -// Note: checkCallbacks assumes that the balance has been recently updated. -func (n *nodeBalance) checkCallbacks(now mclock.AbsTime) (callbacks []func()) { - if n.callbackCount == 0 || n.capacity == 0 { - return - } - pri := n.balanceToPriority(now, n.balance, n.capacity) - for n.callbackCount != 0 && n.callbacks[n.callbackCount-1].threshold >= pri { - n.callbackCount-- - n.callbackIndex[n.callbacks[n.callbackCount].id] = -1 - callbacks = append(callbacks, n.callbacks[n.callbackCount].callback) - } - n.scheduleCheck(now) - return -} - -// scheduleCheck sets up or updates a scheduled event to ensure that it will be called -// again just after the next threshold has been reached. -func (n *nodeBalance) scheduleCheck(now mclock.AbsTime) { - if n.callbackCount != 0 { - d, ok := n.timeUntil(n.callbacks[n.callbackCount-1].threshold) - if !ok { - n.nextUpdate = 0 - n.updateAfter(0) - return - } - if n.nextUpdate == 0 || n.nextUpdate > now.Add(d) { - if d > time.Second { - // Note: if the scheduled update is not in the very near future then we - // schedule the update a bit earlier. This way we do need to update a few - // extra times but don't need to reschedule every time a processed request - // brings the expected firing time a little bit closer. - d = ((d - time.Second) * 7 / 8) + time.Second - } - n.nextUpdate = now.Add(d) - n.updateAfter(d) - } - } else { - n.nextUpdate = 0 - n.updateAfter(0) - } -} - -// updateAfter schedules a balance update and callback check in the future -func (n *nodeBalance) updateAfter(dt time.Duration) { - if n.updateEvent == nil || n.updateEvent.Stop() { - if dt == 0 { - n.updateEvent = nil - } else { - n.updateEvent = n.bt.clock.AfterFunc(dt, func() { - var callbacks []func() - n.lock.Lock() - if n.callbackCount != 0 { - now := n.bt.clock.Now() - n.updateBalance(now) - callbacks = n.checkCallbacks(now) - } - n.lock.Unlock() - if callbacks != nil { - n.bt.ns.Operation(func() { - for _, cb := range callbacks { - cb() - } - }) - } - }) - } - } -} - -// balanceExhausted should be called when the positive balance is exhausted (priority goes to zero/negative) -// Note: this function should run inside a NodeStateMachine operation -func (n *nodeBalance) balanceExhausted() { - n.lock.Lock() - n.storeBalance(true, false) - n.hasPriority = false - n.lock.Unlock() - if n.setFlags { - n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.setup.priorityFlag, 0) - } -} - -// checkPriorityStatus checks whether the node has gained priority status and sets the priority -// callback and flag if necessary. It assumes that the balance has been recently updated. -// Note that the priority flag has to be set by the caller after the mutex has been released. -func (n *nodeBalance) checkPriorityStatus() bool { - if !n.hasPriority && !n.balance.pos.IsZero() { - n.hasPriority = true - n.addCallback(balanceCallbackZero, 0, func() { n.balanceExhausted() }) - return true - } - return false -} - -// signalPriorityUpdate signals that the priority fell below the previous minimum estimate -// Note: this function should run inside a NodeStateMachine operation -func (n *nodeBalance) signalPriorityUpdate() { - n.bt.ns.SetStateSub(n.node, n.bt.setup.updateFlag, nodestate.Flags{}, 0) - n.bt.ns.SetStateSub(n.node, nodestate.Flags{}, n.bt.setup.updateFlag, 0) -} - -// setCapacity updates the capacity value used for priority calculation -// Note: capacity should never be zero -// Note 2: this function should run inside a NodeStateMachine operation -func (n *nodeBalance) setCapacity(capacity uint64) { - n.lock.Lock() - now := n.bt.clock.Now() - n.updateBalance(now) - n.capacity = capacity - callbacks := n.checkCallbacks(now) - n.lock.Unlock() - for _, cb := range callbacks { - cb() - } -} - -// balanceToPriority converts a balance to a priority value. Lower priority means -// first to disconnect. Positive balance translates to positive priority. If positive -// balance is zero then negative balance translates to a negative priority. -func (n *nodeBalance) balanceToPriority(now mclock.AbsTime, b balance, capacity uint64) int64 { - pos := b.posValue(now) - if pos > 0 { - return int64(pos / capacity) - } - return -int64(b.negValue(now)) -} - -// priorityToBalance converts a target priority to a requested balance value. -// If the priority is negative, then minimal negative balance is returned; -// otherwise the minimal positive balance is returned. -func (n *nodeBalance) priorityToBalance(priority int64, capacity uint64) (uint64, uint64) { - if priority > 0 { - return uint64(priority) * n.capacity, 0 - } - return 0, uint64(-priority) -} - -// reducedBalance estimates the reduced balance at a given time in the future based -// on the given balance, the time factor and an estimated average request cost per time ratio -func (n *nodeBalance) reducedBalance(b balance, start mclock.AbsTime, dt time.Duration, capacity uint64, avgReqCost float64) balance { - // since the costs are applied continuously during the dt time period we calculate - // the expiration offset at the middle of the period - var ( - at = start.Add(dt / 2) - dtf = float64(dt) - ) - if !b.pos.IsZero() { - factor := n.posFactor.connectionPrice(capacity, avgReqCost) - diff := -int64(dtf * factor) - _, _, net, _ := b.addValue(at, diff, true, false) - if net == diff { - dtf = 0 - } else { - dtf += float64(net) / factor - } - } - if dtf > 0 { - factor := n.negFactor.connectionPrice(capacity, avgReqCost) - b.addValue(at, int64(dtf*factor), false, false) - } - return b -} - -// timeUntil calculates the remaining time needed to reach a given priority level -// assuming that no requests are processed until then. If the given level is never -// reached then (0, false) is returned. If it has already been reached then (0, true) -// is returned. -// Note: the function assumes that the balance has been recently updated and -// calculates the time starting from the last update. -func (n *nodeBalance) timeUntil(priority int64) (time.Duration, bool) { - var ( - now = n.bt.clock.Now() - pos = n.balance.posValue(now) - targetPos, targetNeg = n.priorityToBalance(priority, n.capacity) - diffTime float64 - ) - if pos > 0 { - timePrice := n.posFactor.connectionPrice(n.capacity, 0) - if timePrice < 1e-100 { - return 0, false - } - if targetPos > 0 { - if targetPos > pos { - return 0, true - } - diffTime = float64(pos-targetPos) / timePrice - return time.Duration(diffTime), true - } else { - diffTime = float64(pos) / timePrice - } - } else { - if targetPos > 0 { - return 0, true - } - } - neg := n.balance.negValue(now) - if targetNeg > neg { - timePrice := n.negFactor.connectionPrice(n.capacity, 0) - if timePrice < 1e-100 { - return 0, false - } - diffTime += float64(targetNeg-neg) / timePrice - } - return time.Duration(diffTime), true -} diff --git a/les/vflux/server/balance_test.go b/les/vflux/server/balance_test.go deleted file mode 100644 index 7c100aab5..000000000 --- a/les/vflux/server/balance_test.go +++ /dev/null @@ -1,439 +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 server - -import ( - "math" - "math/rand" - "reflect" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/ethdb/memorydb" - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" - "github.com/ethereum/go-ethereum/p2p/nodestate" -) - -type zeroExpirer struct{} - -func (z zeroExpirer) SetRate(now mclock.AbsTime, rate float64) {} -func (z zeroExpirer) SetLogOffset(now mclock.AbsTime, logOffset utils.Fixed64) {} -func (z zeroExpirer) LogOffset(now mclock.AbsTime) utils.Fixed64 { return 0 } - -type balanceTestClient struct{} - -func (client balanceTestClient) FreeClientId() string { return "" } - -type balanceTestSetup struct { - clock *mclock.Simulated - db ethdb.KeyValueStore - ns *nodestate.NodeStateMachine - setup *serverSetup - bt *balanceTracker -} - -func newBalanceTestSetup(db ethdb.KeyValueStore, posExp, negExp utils.ValueExpirer) *balanceTestSetup { - // Initialize and customize the setup for the balance testing - clock := &mclock.Simulated{} - setup := newServerSetup() - setup.clientField = setup.setup.NewField("balanceTestClient", reflect.TypeOf(balanceTestClient{})) - - ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup) - if posExp == nil { - posExp = zeroExpirer{} - } - if negExp == nil { - negExp = zeroExpirer{} - } - if db == nil { - db = memorydb.New() - } - bt := newBalanceTracker(ns, setup, db, clock, posExp, negExp) - ns.Start() - return &balanceTestSetup{ - clock: clock, - db: db, - ns: ns, - setup: setup, - bt: bt, - } -} - -func (b *balanceTestSetup) newNode(capacity uint64) *nodeBalance { - node := enode.SignNull(&enr.Record{}, enode.ID{}) - b.ns.SetField(node, b.setup.clientField, balanceTestClient{}) - if capacity != 0 { - b.ns.SetField(node, b.setup.capacityField, capacity) - } - n, _ := b.ns.GetField(node, b.setup.balanceField).(*nodeBalance) - return n -} - -func (b *balanceTestSetup) setBalance(node *nodeBalance, pos, neg uint64) (err error) { - b.bt.BalanceOperation(node.node.ID(), node.connAddress, func(balance AtomicBalanceOperator) { - err = balance.SetBalance(pos, neg) - }) - return -} - -func (b *balanceTestSetup) addBalance(node *nodeBalance, add int64) (old, new uint64, err error) { - b.bt.BalanceOperation(node.node.ID(), node.connAddress, func(balance AtomicBalanceOperator) { - old, new, err = balance.AddBalance(add) - }) - return -} - -func (b *balanceTestSetup) stop() { - b.bt.stop() - b.ns.Stop() -} - -func TestAddBalance(t *testing.T) { - b := newBalanceTestSetup(nil, nil, nil) - defer b.stop() - - node := b.newNode(1000) - var inputs = []struct { - delta int64 - expect [2]uint64 - total uint64 - expectErr bool - }{ - {100, [2]uint64{0, 100}, 100, false}, - {-100, [2]uint64{100, 0}, 0, false}, - {-100, [2]uint64{0, 0}, 0, false}, - {1, [2]uint64{0, 1}, 1, false}, - {maxBalance, [2]uint64{0, 0}, 0, true}, - } - for _, i := range inputs { - old, new, err := b.addBalance(node, i.delta) - if i.expectErr { - if err == nil { - t.Fatalf("Expect get error but nil") - } - continue - } else if err != nil { - t.Fatalf("Expect get no error but %v", err) - } - if old != i.expect[0] || new != i.expect[1] { - t.Fatalf("Positive balance mismatch, got %v -> %v", old, new) - } - if b.bt.TotalTokenAmount() != i.total { - t.Fatalf("Total positive balance mismatch, want %v, got %v", i.total, b.bt.TotalTokenAmount()) - } - } -} - -func TestSetBalance(t *testing.T) { - b := newBalanceTestSetup(nil, nil, nil) - defer b.stop() - node := b.newNode(1000) - - var inputs = []struct { - pos, neg uint64 - }{ - {1000, 0}, - {0, 1000}, - {1000, 1000}, - } - for _, i := range inputs { - b.setBalance(node, i.pos, i.neg) - pos, neg := node.GetBalance() - if pos != i.pos { - t.Fatalf("Positive balance mismatch, want %v, got %v", i.pos, pos) - } - if neg != i.neg { - t.Fatalf("Negative balance mismatch, want %v, got %v", i.neg, neg) - } - } -} - -func TestBalanceTimeCost(t *testing.T) { - b := newBalanceTestSetup(nil, nil, nil) - defer b.stop() - node := b.newNode(1000) - - node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) - b.setBalance(node, uint64(time.Minute), 0) // 1 minute time allowance - - var inputs = []struct { - runTime time.Duration - expPos uint64 - expNeg uint64 - }{ - {time.Second, uint64(time.Second * 59), 0}, - {0, uint64(time.Second * 59), 0}, - {time.Second * 59, 0, 0}, - {time.Second, 0, uint64(time.Second)}, - } - for _, i := range inputs { - b.clock.Run(i.runTime) - if pos, _ := node.GetBalance(); pos != i.expPos { - t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) - } - if _, neg := node.GetBalance(); neg != i.expNeg { - t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) - } - } - - b.setBalance(node, uint64(time.Minute), 0) // Refill 1 minute time allowance - for _, i := range inputs { - b.clock.Run(i.runTime) - if pos, _ := node.GetBalance(); pos != i.expPos { - t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) - } - if _, neg := node.GetBalance(); neg != i.expNeg { - t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) - } - } -} - -func TestBalanceReqCost(t *testing.T) { - b := newBalanceTestSetup(nil, nil, nil) - defer b.stop() - node := b.newNode(1000) - node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) - - b.setBalance(node, uint64(time.Minute), 0) // 1 minute time serving time allowance - var inputs = []struct { - reqCost uint64 - expPos uint64 - expNeg uint64 - }{ - {uint64(time.Second), uint64(time.Second * 59), 0}, - {0, uint64(time.Second * 59), 0}, - {uint64(time.Second * 59), 0, 0}, - {uint64(time.Second), 0, uint64(time.Second)}, - } - for _, i := range inputs { - node.RequestServed(i.reqCost) - if pos, _ := node.GetBalance(); pos != i.expPos { - t.Fatalf("Positive balance mismatch, want %v, got %v", i.expPos, pos) - } - if _, neg := node.GetBalance(); neg != i.expNeg { - t.Fatalf("Negative balance mismatch, want %v, got %v", i.expNeg, neg) - } - } -} - -func TestBalanceToPriority(t *testing.T) { - b := newBalanceTestSetup(nil, nil, nil) - defer b.stop() - node := b.newNode(1000) - node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) - - var inputs = []struct { - pos uint64 - neg uint64 - priority int64 - }{ - {1000, 0, 1}, - {2000, 0, 2}, // Higher balance, higher priority value - {0, 0, 0}, - {0, 1000, -1000}, - } - for _, i := range inputs { - b.setBalance(node, i.pos, i.neg) - priority := node.priority(1000) - if priority != i.priority { - t.Fatalf("priority mismatch, want %v, got %v", i.priority, priority) - } - } -} - -func TestEstimatedPriority(t *testing.T) { - b := newBalanceTestSetup(nil, nil, nil) - defer b.stop() - node := b.newNode(1000000000) - node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) - b.setBalance(node, uint64(time.Minute), 0) - var inputs = []struct { - runTime time.Duration // time cost - futureTime time.Duration // diff of future time - reqCost uint64 // single request cost - priority int64 // expected estimated priority - }{ - {time.Second, time.Second, 0, 58}, - {0, time.Second, 0, 58}, - - // 2 seconds time cost, 1 second estimated time cost, 10^9 request cost, - // 10^9 estimated request cost per second. - {time.Second, time.Second, 1000000000, 55}, - - // 3 seconds time cost, 3 second estimated time cost, 10^9*2 request cost, - // 4*10^9 estimated request cost. - {time.Second, 3 * time.Second, 1000000000, 48}, - - // All positive balance is used up - {time.Second * 55, 0, 0, -1}, - - // 1 minute estimated time cost, 4/58 * 10^9 estimated request cost per sec. - {0, time.Minute, 0, -int64(time.Minute) - int64(time.Second)*120/29}, - } - for _, i := range inputs { - b.clock.Run(i.runTime) - node.RequestServed(i.reqCost) - priority := node.estimatePriority(1000000000, 0, i.futureTime, 0, false) - if priority != i.priority { - t.Fatalf("Estimated priority mismatch, want %v, got %v", i.priority, priority) - } - } -} - -func TestPositiveBalanceCounting(t *testing.T) { - b := newBalanceTestSetup(nil, nil, nil) - defer b.stop() - - var nodes []*nodeBalance - for i := 0; i < 100; i += 1 { - node := b.newNode(1000000) - node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) - nodes = append(nodes, node) - } - - // Allocate service token - var sum uint64 - for i := 0; i < 100; i += 1 { - amount := int64(rand.Intn(100) + 100) - b.addBalance(nodes[i], amount) - sum += uint64(amount) - } - if b.bt.TotalTokenAmount() != sum { - t.Fatalf("Invalid token amount") - } - - // Change client status - for i := 0; i < 100; i += 1 { - if rand.Intn(2) == 0 { - b.ns.SetField(nodes[i].node, b.setup.capacityField, uint64(1)) - } - } - if b.bt.TotalTokenAmount() != sum { - t.Fatalf("Invalid token amount") - } - for i := 0; i < 100; i += 1 { - if rand.Intn(2) == 0 { - b.ns.SetField(nodes[i].node, b.setup.capacityField, uint64(1)) - } - } - if b.bt.TotalTokenAmount() != sum { - t.Fatalf("Invalid token amount") - } -} - -func TestCallbackChecking(t *testing.T) { - b := newBalanceTestSetup(nil, nil, nil) - defer b.stop() - node := b.newNode(1000000) - node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) - - var inputs = []struct { - priority int64 - expDiff time.Duration - }{ - {500, time.Millisecond * 500}, - {0, time.Second}, - {-int64(time.Second), 2 * time.Second}, - } - b.setBalance(node, uint64(time.Second), 0) - for _, i := range inputs { - diff, _ := node.timeUntil(i.priority) - if diff != i.expDiff { - t.Fatalf("Time difference mismatch, want %v, got %v", i.expDiff, diff) - } - } -} - -func TestCallback(t *testing.T) { - b := newBalanceTestSetup(nil, nil, nil) - defer b.stop() - node := b.newNode(1000) - node.SetPriceFactors(PriceFactors{1, 0, 1}, PriceFactors{1, 0, 1}) - - callCh := make(chan struct{}, 1) - b.setBalance(node, uint64(time.Minute), 0) - node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} }) - - b.clock.Run(time.Minute) - select { - case <-callCh: - case <-time.NewTimer(time.Second).C: - t.Fatalf("Callback hasn't been called yet") - } - - b.setBalance(node, uint64(time.Minute), 0) - node.addCallback(balanceCallbackZero, 0, func() { callCh <- struct{}{} }) - node.removeCallback(balanceCallbackZero) - - b.clock.Run(time.Minute) - select { - case <-callCh: - t.Fatalf("Callback shouldn't be called") - case <-time.NewTimer(time.Millisecond * 100).C: - } -} - -func TestBalancePersistence(t *testing.T) { - posExp := &utils.Expirer{} - negExp := &utils.Expirer{} - posExp.SetRate(0, math.Log(2)/float64(time.Hour*2)) // halves every two hours - negExp.SetRate(0, math.Log(2)/float64(time.Hour)) // halves every hour - setup := newBalanceTestSetup(nil, posExp, negExp) - - exp := func(balance *nodeBalance, expPos, expNeg uint64) { - pos, neg := balance.GetBalance() - if pos != expPos { - t.Fatalf("Positive balance incorrect, want %v, got %v", expPos, pos) - } - if neg != expNeg { - t.Fatalf("Positive balance incorrect, want %v, got %v", expPos, pos) - } - } - expTotal := func(expTotal uint64) { - total := setup.bt.TotalTokenAmount() - if total != expTotal { - t.Fatalf("Total token amount incorrect, want %v, got %v", expTotal, total) - } - } - - expTotal(0) - balance := setup.newNode(0) - expTotal(0) - setup.setBalance(balance, 16000000000, 16000000000) - exp(balance, 16000000000, 16000000000) - expTotal(16000000000) - - setup.clock.Run(time.Hour * 2) - exp(balance, 8000000000, 4000000000) - expTotal(8000000000) - setup.stop() - - // Test the functionalities after restart - setup = newBalanceTestSetup(setup.db, posExp, negExp) - expTotal(8000000000) - balance = setup.newNode(0) - exp(balance, 8000000000, 4000000000) - expTotal(8000000000) - setup.clock.Run(time.Hour * 2) - exp(balance, 4000000000, 1000000000) - expTotal(4000000000) - setup.stop() -} diff --git a/les/vflux/server/balance_tracker.go b/les/vflux/server/balance_tracker.go deleted file mode 100644 index 9695e7963..000000000 --- a/les/vflux/server/balance_tracker.go +++ /dev/null @@ -1,300 +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 server - -import ( - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" - "github.com/ethereum/go-ethereum/p2p/nodestate" -) - -const ( - posThreshold = 1000000 // minimum positive balance that is persisted in the database - negThreshold = 1000000 // minimum negative balance that is persisted in the database - persistExpirationRefresh = time.Minute * 5 // refresh period of the token expiration persistence -) - -// balanceTracker tracks positive and negative balances for connected nodes. -// After clientField is set externally, a nodeBalance is created and previous -// balance values are loaded from the database. Both balances are exponentially expired -// values. Costs are deducted from the positive balance if present, otherwise added to -// the negative balance. If the capacity is non-zero then a time cost is applied -// continuously while individual request costs are applied immediately. -// The two balances are translated into a single priority value that also depends -// on the actual capacity. -type balanceTracker struct { - setup *serverSetup - clock mclock.Clock - lock sync.Mutex - ns *nodestate.NodeStateMachine - ndb *nodeDB - posExp, negExp utils.ValueExpirer - - posExpTC, negExpTC uint64 - defaultPosFactors, defaultNegFactors PriceFactors - - active, inactive utils.ExpiredValue - balanceTimer *utils.UpdateTimer - quit chan struct{} -} - -// newBalanceTracker creates a new balanceTracker -func newBalanceTracker(ns *nodestate.NodeStateMachine, setup *serverSetup, db ethdb.KeyValueStore, clock mclock.Clock, posExp, negExp utils.ValueExpirer) *balanceTracker { - ndb := newNodeDB(db, clock) - bt := &balanceTracker{ - ns: ns, - setup: setup, - ndb: ndb, - clock: clock, - posExp: posExp, - negExp: negExp, - balanceTimer: utils.NewUpdateTimer(clock, time.Second*10), - quit: make(chan struct{}), - } - posOffset, negOffset := bt.ndb.getExpiration() - posExp.SetLogOffset(clock.Now(), posOffset) - negExp.SetLogOffset(clock.Now(), negOffset) - - // Load all persisted balance entries of priority nodes, - // calculate the total number of issued service tokens. - bt.ndb.forEachBalance(false, func(id enode.ID, balance utils.ExpiredValue) bool { - bt.inactive.AddExp(balance) - return true - }) - - ns.SubscribeField(bt.setup.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { - n, _ := ns.GetField(node, bt.setup.balanceField).(*nodeBalance) - if n == nil { - return - } - - ov, _ := oldValue.(uint64) - nv, _ := newValue.(uint64) - if ov == 0 && nv != 0 { - n.activate() - } - if nv != 0 { - n.setCapacity(nv) - } - if ov != 0 && nv == 0 { - n.deactivate() - } - }) - ns.SubscribeField(bt.setup.clientField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { - type peer interface { - FreeClientId() string - } - if newValue != nil { - n := bt.newNodeBalance(node, newValue.(peer).FreeClientId(), true) - bt.lock.Lock() - n.SetPriceFactors(bt.defaultPosFactors, bt.defaultNegFactors) - bt.lock.Unlock() - ns.SetFieldSub(node, bt.setup.balanceField, n) - } else { - ns.SetStateSub(node, nodestate.Flags{}, bt.setup.priorityFlag, 0) - if b, _ := ns.GetField(node, bt.setup.balanceField).(*nodeBalance); b != nil { - b.deactivate() - } - ns.SetFieldSub(node, bt.setup.balanceField, nil) - } - }) - - // The positive and negative balances of clients are stored in database - // and both of these decay exponentially over time. Delete them if the - // value is small enough. - bt.ndb.evictCallBack = bt.canDropBalance - - go func() { - for { - select { - case <-clock.After(persistExpirationRefresh): - now := clock.Now() - bt.ndb.setExpiration(posExp.LogOffset(now), negExp.LogOffset(now)) - case <-bt.quit: - return - } - } - }() - return bt -} - -// Stop saves expiration offset and unsaved node balances and shuts balanceTracker down -func (bt *balanceTracker) stop() { - now := bt.clock.Now() - bt.ndb.setExpiration(bt.posExp.LogOffset(now), bt.negExp.LogOffset(now)) - close(bt.quit) - bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - if n, ok := bt.ns.GetField(node, bt.setup.balanceField).(*nodeBalance); ok { - n.lock.Lock() - n.storeBalance(true, true) - n.lock.Unlock() - bt.ns.SetField(node, bt.setup.balanceField, nil) - } - }) - bt.ndb.close() -} - -// TotalTokenAmount returns the current total amount of service tokens in existence -func (bt *balanceTracker) TotalTokenAmount() uint64 { - bt.lock.Lock() - defer bt.lock.Unlock() - - bt.balanceTimer.Update(func(_ time.Duration) bool { - bt.active = utils.ExpiredValue{} - bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) { - if n, ok := bt.ns.GetField(node, bt.setup.balanceField).(*nodeBalance); ok && n.active { - pos, _ := n.GetRawBalance() - bt.active.AddExp(pos) - } - }) - return true - }) - total := bt.active - total.AddExp(bt.inactive) - return total.Value(bt.posExp.LogOffset(bt.clock.Now())) -} - -// GetPosBalanceIDs lists node IDs with an associated positive balance -func (bt *balanceTracker) GetPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) { - return bt.ndb.getPosBalanceIDs(start, stop, maxCount) -} - -// SetDefaultFactors sets the default price factors applied to subsequently connected clients -func (bt *balanceTracker) SetDefaultFactors(posFactors, negFactors PriceFactors) { - bt.lock.Lock() - bt.defaultPosFactors = posFactors - bt.defaultNegFactors = negFactors - bt.lock.Unlock() -} - -// SetExpirationTCs sets positive and negative token expiration time constants. -// Specified in seconds, 0 means infinite (no expiration). -func (bt *balanceTracker) SetExpirationTCs(pos, neg uint64) { - bt.lock.Lock() - defer bt.lock.Unlock() - - bt.posExpTC, bt.negExpTC = pos, neg - now := bt.clock.Now() - if pos > 0 { - bt.posExp.SetRate(now, 1/float64(pos*uint64(time.Second))) - } else { - bt.posExp.SetRate(now, 0) - } - if neg > 0 { - bt.negExp.SetRate(now, 1/float64(neg*uint64(time.Second))) - } else { - bt.negExp.SetRate(now, 0) - } -} - -// GetExpirationTCs returns the current positive and negative token expiration -// time constants -func (bt *balanceTracker) GetExpirationTCs() (pos, neg uint64) { - bt.lock.Lock() - defer bt.lock.Unlock() - - return bt.posExpTC, bt.negExpTC -} - -// BalanceOperation allows atomic operations on the balance of a node regardless of whether -// it is currently connected or not -func (bt *balanceTracker) BalanceOperation(id enode.ID, connAddress string, cb func(AtomicBalanceOperator)) { - bt.ns.Operation(func() { - var nb *nodeBalance - if node := bt.ns.GetNode(id); node != nil { - nb, _ = bt.ns.GetField(node, bt.setup.balanceField).(*nodeBalance) - } - if nb == nil { - node := enode.SignNull(&enr.Record{}, id) - nb = bt.newNodeBalance(node, connAddress, false) - } - cb(nb) - }) -} - -// newNodeBalance loads balances from the database and creates a nodeBalance instance -// for the given node. It also sets the priorityFlag and adds balanceCallbackZero if -// the node has a positive balance. -// Note: this function should run inside a NodeStateMachine operation -func (bt *balanceTracker) newNodeBalance(node *enode.Node, connAddress string, setFlags bool) *nodeBalance { - pb := bt.ndb.getOrNewBalance(node.ID().Bytes(), false) - nb := bt.ndb.getOrNewBalance([]byte(connAddress), true) - n := &nodeBalance{ - bt: bt, - node: node, - setFlags: setFlags, - connAddress: connAddress, - balance: balance{pos: pb, neg: nb, posExp: bt.posExp, negExp: bt.negExp}, - initTime: bt.clock.Now(), - lastUpdate: bt.clock.Now(), - } - for i := range n.callbackIndex { - n.callbackIndex[i] = -1 - } - if setFlags && n.checkPriorityStatus() { - n.bt.ns.SetStateSub(n.node, n.bt.setup.priorityFlag, nodestate.Flags{}, 0) - } - return n -} - -// storeBalance stores either a positive or a negative balance in the database -func (bt *balanceTracker) storeBalance(id []byte, neg bool, value utils.ExpiredValue) { - if bt.canDropBalance(bt.clock.Now(), neg, value) { - bt.ndb.delBalance(id, neg) // balance is small enough, drop it directly. - } else { - bt.ndb.setBalance(id, neg, value) - } -} - -// canDropBalance tells whether a positive or negative balance is below the threshold -// and therefore can be dropped from the database -func (bt *balanceTracker) canDropBalance(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool { - if neg { - return b.Value(bt.negExp.LogOffset(now)) <= negThreshold - } - return b.Value(bt.posExp.LogOffset(now)) <= posThreshold -} - -// updateTotalBalance adjusts the total balance after executing given callback. -func (bt *balanceTracker) updateTotalBalance(n *nodeBalance, callback func() bool) { - bt.lock.Lock() - defer bt.lock.Unlock() - - n.lock.Lock() - defer n.lock.Unlock() - - original, active := n.balance.pos, n.active - if !callback() { - return - } - if active { - bt.active.SubExp(original) - } else { - bt.inactive.SubExp(original) - } - if n.active { - bt.active.AddExp(n.balance.pos) - } else { - bt.inactive.AddExp(n.balance.pos) - } -} diff --git a/les/vflux/server/clientdb.go b/les/vflux/server/clientdb.go deleted file mode 100644 index a39cbec36..000000000 --- a/les/vflux/server/clientdb.go +++ /dev/null @@ -1,250 +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 server - -import ( - "bytes" - "encoding/binary" - "time" - - "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/ethdb" - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/rlp" -) - -const ( - balanceCacheLimit = 8192 // the maximum number of cached items in service token balance queue - - // nodeDBVersion is the version identifier of the node data in db - // - // Changelog: - // Version 0 => 1 - // * Replace `lastTotal` with `meta` in positive balance: version 0=>1 - // - // Version 1 => 2 - // * Positive Balance and negative balance is changed: - // * Cumulative time is replaced with expiration - nodeDBVersion = 2 - - // dbCleanupCycle is the cycle of db for useless data cleanup - dbCleanupCycle = time.Hour -) - -var ( - positiveBalancePrefix = []byte("pb:") // dbVersion(uint16 big endian) + positiveBalancePrefix + id -> balance - negativeBalancePrefix = []byte("nb:") // dbVersion(uint16 big endian) + negativeBalancePrefix + ip -> balance - expirationKey = []byte("expiration:") // dbVersion(uint16 big endian) + expirationKey -> posExp, negExp -) - -type nodeDB struct { - db ethdb.KeyValueStore - cache *lru.Cache[string, utils.ExpiredValue] - auxbuf []byte // 37-byte auxiliary buffer for key encoding - verbuf [2]byte // 2-byte auxiliary buffer for db version - evictCallBack func(mclock.AbsTime, bool, utils.ExpiredValue) bool // Callback to determine whether the balance can be evicted. - clock mclock.Clock - closeCh chan struct{} - cleanupHook func() // Test hook used for testing -} - -func newNodeDB(db ethdb.KeyValueStore, clock mclock.Clock) *nodeDB { - ndb := &nodeDB{ - db: db, - cache: lru.NewCache[string, utils.ExpiredValue](balanceCacheLimit), - auxbuf: make([]byte, 37), - clock: clock, - closeCh: make(chan struct{}), - } - binary.BigEndian.PutUint16(ndb.verbuf[:], uint16(nodeDBVersion)) - go ndb.expirer() - return ndb -} - -func (db *nodeDB) close() { - close(db.closeCh) -} - -func (db *nodeDB) getPrefix(neg bool) []byte { - prefix := positiveBalancePrefix - if neg { - prefix = negativeBalancePrefix - } - return append(db.verbuf[:], prefix...) -} - -func (db *nodeDB) key(id []byte, neg bool) []byte { - prefix := positiveBalancePrefix - if neg { - prefix = negativeBalancePrefix - } - if len(prefix)+len(db.verbuf)+len(id) > len(db.auxbuf) { - db.auxbuf = append(db.auxbuf, make([]byte, len(prefix)+len(db.verbuf)+len(id)-len(db.auxbuf))...) - } - copy(db.auxbuf[:len(db.verbuf)], db.verbuf[:]) - copy(db.auxbuf[len(db.verbuf):len(db.verbuf)+len(prefix)], prefix) - copy(db.auxbuf[len(prefix)+len(db.verbuf):len(prefix)+len(db.verbuf)+len(id)], id) - return db.auxbuf[:len(prefix)+len(db.verbuf)+len(id)] -} - -func (db *nodeDB) getExpiration() (utils.Fixed64, utils.Fixed64) { - blob, err := db.db.Get(append(db.verbuf[:], expirationKey...)) - if err != nil || len(blob) != 16 { - return 0, 0 - } - return utils.Fixed64(binary.BigEndian.Uint64(blob[:8])), utils.Fixed64(binary.BigEndian.Uint64(blob[8:16])) -} - -func (db *nodeDB) setExpiration(pos, neg utils.Fixed64) { - var buff [16]byte - binary.BigEndian.PutUint64(buff[:8], uint64(pos)) - binary.BigEndian.PutUint64(buff[8:16], uint64(neg)) - db.db.Put(append(db.verbuf[:], expirationKey...), buff[:16]) -} - -func (db *nodeDB) getOrNewBalance(id []byte, neg bool) utils.ExpiredValue { - key := db.key(id, neg) - item, exist := db.cache.Get(string(key)) - if exist { - return item - } - - var b utils.ExpiredValue - enc, err := db.db.Get(key) - if err != nil || len(enc) == 0 { - return b - } - if err := rlp.DecodeBytes(enc, &b); err != nil { - log.Crit("Failed to decode positive balance", "err", err) - } - db.cache.Add(string(key), b) - return b -} - -func (db *nodeDB) setBalance(id []byte, neg bool, b utils.ExpiredValue) { - key := db.key(id, neg) - enc, err := rlp.EncodeToBytes(&(b)) - if err != nil { - log.Crit("Failed to encode positive balance", "err", err) - } - db.db.Put(key, enc) - db.cache.Add(string(key), b) -} - -func (db *nodeDB) delBalance(id []byte, neg bool) { - key := db.key(id, neg) - db.db.Delete(key) - db.cache.Remove(string(key)) -} - -// getPosBalanceIDs returns a lexicographically ordered list of IDs of accounts -// with a positive balance -func (db *nodeDB) getPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) { - if maxCount <= 0 { - return - } - prefix := db.getPrefix(false) - keylen := len(prefix) + len(enode.ID{}) - - it := db.db.NewIterator(prefix, start.Bytes()) - defer it.Release() - - for it.Next() { - var id enode.ID - if len(it.Key()) != keylen { - return - } - copy(id[:], it.Key()[keylen-len(id):]) - if bytes.Compare(id.Bytes(), stop.Bytes()) >= 0 { - return - } - result = append(result, id) - if len(result) == maxCount { - return - } - } - return -} - -// forEachBalance iterates all balances and passes values to callback. -func (db *nodeDB) forEachBalance(neg bool, callback func(id enode.ID, balance utils.ExpiredValue) bool) { - prefix := db.getPrefix(neg) - keylen := len(prefix) + len(enode.ID{}) - - it := db.db.NewIterator(prefix, nil) - defer it.Release() - - for it.Next() { - var id enode.ID - if len(it.Key()) != keylen { - return - } - copy(id[:], it.Key()[keylen-len(id):]) - - var b utils.ExpiredValue - if err := rlp.DecodeBytes(it.Value(), &b); err != nil { - continue - } - if !callback(id, b) { - return - } - } -} - -func (db *nodeDB) expirer() { - for { - select { - case <-db.clock.After(dbCleanupCycle): - db.expireNodes() - case <-db.closeCh: - return - } - } -} - -// expireNodes iterates the whole node db and checks whether the -// token balances can be deleted. -func (db *nodeDB) expireNodes() { - var ( - visited int - deleted int - start = time.Now() - ) - for _, neg := range []bool{false, true} { - iter := db.db.NewIterator(db.getPrefix(neg), nil) - for iter.Next() { - visited++ - var balance utils.ExpiredValue - if err := rlp.DecodeBytes(iter.Value(), &balance); err != nil { - log.Crit("Failed to decode negative balance", "err", err) - } - if db.evictCallBack != nil && db.evictCallBack(db.clock.Now(), neg, balance) { - deleted++ - db.db.Delete(iter.Key()) - } - } - } - // Invoke testing hook if it's not nil. - if db.cleanupHook != nil { - db.cleanupHook() - } - log.Debug("Expire nodes", "visited", visited, "deleted", deleted, "elapsed", common.PrettyDuration(time.Since(start))) -} diff --git a/les/vflux/server/clientdb_test.go b/les/vflux/server/clientdb_test.go deleted file mode 100644 index 353d84aea..000000000 --- a/les/vflux/server/clientdb_test.go +++ /dev/null @@ -1,144 +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 server - -import ( - "reflect" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/p2p/enode" -) - -func expval(v uint64) utils.ExpiredValue { - return utils.ExpiredValue{Base: v} -} - -func TestNodeDB(t *testing.T) { - ndb := newNodeDB(rawdb.NewMemoryDatabase(), mclock.System{}) - defer ndb.close() - - var cases = []struct { - id enode.ID - ip string - balance utils.ExpiredValue - positive bool - }{ - {enode.ID{0x00, 0x01, 0x02}, "", expval(100), true}, - {enode.ID{0x00, 0x01, 0x02}, "", expval(200), true}, - {enode.ID{}, "127.0.0.1", expval(100), false}, - {enode.ID{}, "127.0.0.1", expval(200), false}, - } - for _, c := range cases { - if c.positive { - ndb.setBalance(c.id.Bytes(), false, c.balance) - if pb := ndb.getOrNewBalance(c.id.Bytes(), false); !reflect.DeepEqual(pb, c.balance) { - t.Fatalf("Positive balance mismatch, want %v, got %v", c.balance, pb) - } - } else { - ndb.setBalance([]byte(c.ip), true, c.balance) - if nb := ndb.getOrNewBalance([]byte(c.ip), true); !reflect.DeepEqual(nb, c.balance) { - t.Fatalf("Negative balance mismatch, want %v, got %v", c.balance, nb) - } - } - } - for _, c := range cases { - if c.positive { - ndb.delBalance(c.id.Bytes(), false) - if pb := ndb.getOrNewBalance(c.id.Bytes(), false); !reflect.DeepEqual(pb, utils.ExpiredValue{}) { - t.Fatalf("Positive balance mismatch, want %v, got %v", utils.ExpiredValue{}, pb) - } - } else { - ndb.delBalance([]byte(c.ip), true) - if nb := ndb.getOrNewBalance([]byte(c.ip), true); !reflect.DeepEqual(nb, utils.ExpiredValue{}) { - t.Fatalf("Negative balance mismatch, want %v, got %v", utils.ExpiredValue{}, nb) - } - } - } - posExp, negExp := utils.Fixed64(1000), utils.Fixed64(2000) - ndb.setExpiration(posExp, negExp) - if pos, neg := ndb.getExpiration(); pos != posExp || neg != negExp { - t.Fatalf("Expiration mismatch, want %v / %v, got %v / %v", posExp, negExp, pos, neg) - } - /* curBalance := currencyBalance{typ: "ETH", amount: 10000} - ndb.setCurrencyBalance(enode.ID{0x01, 0x02}, curBalance) - if got := ndb.getCurrencyBalance(enode.ID{0x01, 0x02}); !reflect.DeepEqual(got, curBalance) { - t.Fatalf("Currency balance mismatch, want %v, got %v", curBalance, got) - }*/ -} - -func TestNodeDBExpiration(t *testing.T) { - var ( - iterated int - done = make(chan struct{}, 1) - ) - callback := func(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool { - iterated += 1 - return true - } - clock := &mclock.Simulated{} - ndb := newNodeDB(rawdb.NewMemoryDatabase(), clock) - defer ndb.close() - ndb.evictCallBack = callback - ndb.cleanupHook = func() { done <- struct{}{} } - - var cases = []struct { - id []byte - neg bool - balance utils.ExpiredValue - }{ - {[]byte{0x01, 0x02}, false, expval(1)}, - {[]byte{0x03, 0x04}, false, expval(1)}, - {[]byte{0x05, 0x06}, false, expval(1)}, - {[]byte{0x07, 0x08}, false, expval(1)}, - - {[]byte("127.0.0.1"), true, expval(1)}, - {[]byte("127.0.0.2"), true, expval(1)}, - {[]byte("127.0.0.3"), true, expval(1)}, - {[]byte("127.0.0.4"), true, expval(1)}, - } - for _, c := range cases { - ndb.setBalance(c.id, c.neg, c.balance) - } - clock.WaitForTimers(1) - clock.Run(time.Hour + time.Minute) - select { - case <-done: - case <-time.NewTimer(time.Second).C: - t.Fatalf("timeout") - } - if iterated != 8 { - t.Fatalf("Failed to evict useless balances, want %v, got %d", 8, iterated) - } - - for _, c := range cases { - ndb.setBalance(c.id, c.neg, c.balance) - } - clock.WaitForTimers(1) - clock.Run(time.Hour + time.Minute) - select { - case <-done: - case <-time.NewTimer(time.Second).C: - t.Fatalf("timeout") - } - if iterated != 16 { - t.Fatalf("Failed to evict useless balances, want %v, got %d", 16, iterated) - } -} diff --git a/les/vflux/server/clientpool.go b/les/vflux/server/clientpool.go deleted file mode 100644 index a525f8636..000000000 --- a/les/vflux/server/clientpool.go +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2021 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 server - -import ( - "errors" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/les/vflux" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/nodestate" - "github.com/ethereum/go-ethereum/rlp" -) - -var ( - ErrNotConnected = errors.New("client not connected") - ErrNoPriority = errors.New("priority too low to raise capacity") - ErrCantFindMaximum = errors.New("unable to find maximum allowed capacity") -) - -// ClientPool implements a client database that assigns a priority to each client -// based on a positive and negative balance. Positive balance is externally assigned -// to prioritized clients and is decreased with connection time and processed -// requests (unless the price factors are zero). If the positive balance is zero -// then negative balance is accumulated. -// -// Balance tracking and priority calculation for connected clients is done by -// balanceTracker. PriorityQueue ensures that clients with the lowest positive or -// highest negative balance get evicted when the total capacity allowance is full -// and new clients with a better balance want to connect. -// -// Already connected nodes receive a small bias in their favor in order to avoid -// accepting and instantly kicking out clients. In theory, we try to ensure that -// each client can have several minutes of connection time. -// -// Balances of disconnected clients are stored in nodeDB including positive balance -// and negative balance. Both positive balance and negative balance will decrease -// exponentially. If the balance is low enough, then the record will be dropped. -type ClientPool struct { - *priorityPool - *balanceTracker - - setup *serverSetup - clock mclock.Clock - ns *nodestate.NodeStateMachine - synced func() bool - - lock sync.RWMutex - connectedBias time.Duration - - minCap uint64 // the minimal capacity value allowed for any client - capReqNode *enode.Node // node that is requesting capacity change; only used inside NSM operation -} - -// clientPeer represents a peer in the client pool. None of the callbacks should block. -type clientPeer interface { - Node() *enode.Node - FreeClientId() string // unique id for non-priority clients (typically a prefix of the network address) - InactiveAllowance() time.Duration // disconnection timeout for inactive non-priority peers - UpdateCapacity(newCap uint64, requested bool) // signals a capacity update (requested is true if it is a result of a SetCapacity call on the given peer - Disconnect() // initiates disconnection (Unregister should always be called) -} - -// NewClientPool creates a new client pool -func NewClientPool(balanceDb ethdb.KeyValueStore, minCap uint64, connectedBias time.Duration, clock mclock.Clock, synced func() bool) *ClientPool { - setup := newServerSetup() - ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup) - cp := &ClientPool{ - priorityPool: newPriorityPool(ns, setup, clock, minCap, connectedBias, 4, 100), - balanceTracker: newBalanceTracker(ns, setup, balanceDb, clock, &utils.Expirer{}, &utils.Expirer{}), - setup: setup, - ns: ns, - clock: clock, - minCap: minCap, - connectedBias: connectedBias, - synced: synced, - } - - ns.SubscribeState(nodestate.MergeFlags(setup.activeFlag, setup.inactiveFlag, setup.priorityFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { - if newState.Equals(setup.inactiveFlag) { - // set timeout for non-priority inactive client - var timeout time.Duration - if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok { - timeout = c.InactiveAllowance() - } - ns.AddTimeout(node, setup.inactiveFlag, timeout) - } - if oldState.Equals(setup.inactiveFlag) && newState.Equals(setup.inactiveFlag.Or(setup.priorityFlag)) { - ns.SetStateSub(node, setup.inactiveFlag, nodestate.Flags{}, 0) // priority gained; remove timeout - } - if newState.Equals(setup.activeFlag) { - // active with no priority; limit capacity to minCap - cap, _ := ns.GetField(node, setup.capacityField).(uint64) - if cap > minCap { - cp.requestCapacity(node, minCap, minCap, 0) - } - } - if newState.Equals(nodestate.Flags{}) { - if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok { - c.Disconnect() - } - } - }) - - ns.SubscribeField(setup.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { - if c, ok := ns.GetField(node, setup.clientField).(clientPeer); ok { - newCap, _ := newValue.(uint64) - c.UpdateCapacity(newCap, node == cp.capReqNode) - } - }) - - // add metrics - cp.ns.SubscribeState(nodestate.MergeFlags(cp.setup.activeFlag, cp.setup.inactiveFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { - if oldState.IsEmpty() && !newState.IsEmpty() { - clientConnectedMeter.Mark(1) - } - if !oldState.IsEmpty() && newState.IsEmpty() { - clientDisconnectedMeter.Mark(1) - } - if oldState.HasNone(cp.setup.activeFlag) && oldState.HasAll(cp.setup.activeFlag) { - clientActivatedMeter.Mark(1) - } - if oldState.HasAll(cp.setup.activeFlag) && oldState.HasNone(cp.setup.activeFlag) { - clientDeactivatedMeter.Mark(1) - } - activeCount, activeCap := cp.Active() - totalActiveCountGauge.Update(int64(activeCount)) - totalActiveCapacityGauge.Update(int64(activeCap)) - totalInactiveCountGauge.Update(int64(cp.Inactive())) - }) - return cp -} - -// Start starts the client pool. Should be called before Register/Unregister. -func (cp *ClientPool) Start() { - cp.ns.Start() -} - -// Stop shuts the client pool down. The clientPeer interface callbacks will not be called -// after Stop. Register calls will return nil. -func (cp *ClientPool) Stop() { - cp.balanceTracker.stop() - cp.ns.Stop() -} - -// Register registers the peer into the client pool. If the peer has insufficient -// priority and remains inactive for longer than the allowed timeout then it will be -// disconnected by calling the Disconnect function of the clientPeer interface. -func (cp *ClientPool) Register(peer clientPeer) ConnectedBalance { - cp.ns.SetField(peer.Node(), cp.setup.clientField, peerWrapper{peer}) - balance, _ := cp.ns.GetField(peer.Node(), cp.setup.balanceField).(*nodeBalance) - return balance -} - -// Unregister removes the peer from the client pool -func (cp *ClientPool) Unregister(peer clientPeer) { - cp.ns.SetField(peer.Node(), cp.setup.clientField, nil) -} - -// SetConnectedBias sets the connection bias, which is applied to already connected clients -// So that already connected client won't be kicked out very soon and we can ensure all -// connected clients can have enough time to request or sync some data. -func (cp *ClientPool) SetConnectedBias(bias time.Duration) { - cp.lock.Lock() - cp.connectedBias = bias - cp.setActiveBias(bias) - cp.lock.Unlock() -} - -// SetCapacity sets the assigned capacity of a connected client -func (cp *ClientPool) SetCapacity(node *enode.Node, reqCap uint64, bias time.Duration, requested bool) (capacity uint64, err error) { - cp.lock.RLock() - if cp.connectedBias > bias { - bias = cp.connectedBias - } - cp.lock.RUnlock() - - cp.ns.Operation(func() { - balance, _ := cp.ns.GetField(node, cp.setup.balanceField).(*nodeBalance) - if balance == nil { - err = ErrNotConnected - return - } - capacity, _ = cp.ns.GetField(node, cp.setup.capacityField).(uint64) - if capacity == 0 { - // if the client is inactive then it has insufficient priority for the minimal capacity - // (will be activated automatically with minCap when possible) - return - } - if reqCap < cp.minCap { - // can't request less than minCap; switching between 0 (inactive state) and minCap is - // performed by the server automatically as soon as necessary/possible - reqCap = cp.minCap - } - if reqCap > cp.minCap && cp.ns.GetState(node).HasNone(cp.setup.priorityFlag) { - err = ErrNoPriority - return - } - if reqCap == capacity { - return - } - if requested { - // mark the requested node so that the UpdateCapacity callback can signal - // whether the update is the direct result of a SetCapacity call on the given node - cp.capReqNode = node - defer func() { - cp.capReqNode = nil - }() - } - - var minTarget, maxTarget uint64 - if reqCap > capacity { - // Estimate maximum available capacity at the current priority level and request - // the estimated amount. - // Note: requestCapacity could find the highest available capacity between the - // current and the requested capacity but it could cost a lot of iterations with - // fine step adjustment if the requested capacity is very high. By doing a quick - // estimation of the maximum available capacity based on the capacity curve we - // can limit the number of required iterations. - curve := cp.getCapacityCurve().exclude(node.ID()) - maxTarget = curve.maxCapacity(func(capacity uint64) int64 { - return balance.estimatePriority(capacity, 0, 0, bias, false) - }) - if maxTarget < reqCap { - return - } - maxTarget = reqCap - - // Specify a narrow target range that allows a limited number of fine step - // iterations - minTarget = maxTarget - maxTarget/20 - if minTarget < capacity { - minTarget = capacity - } - } else { - minTarget, maxTarget = reqCap, reqCap - } - if newCap := cp.requestCapacity(node, minTarget, maxTarget, bias); newCap >= minTarget && newCap <= maxTarget { - capacity = newCap - return - } - // we should be able to find the maximum allowed capacity in a few iterations - log.Error("Unable to find maximum allowed capacity") - err = ErrCantFindMaximum - }) - return -} - -// serveCapQuery serves a vflux capacity query. It receives multiple token amount values -// and a bias time value. For each given token amount it calculates the maximum achievable -// capacity in case the amount is added to the balance. -func (cp *ClientPool) serveCapQuery(id enode.ID, freeID string, data []byte) []byte { - var req vflux.CapacityQueryReq - if rlp.DecodeBytes(data, &req) != nil { - return nil - } - if l := len(req.AddTokens); l == 0 || l > vflux.CapacityQueryMaxLen { - return nil - } - result := make(vflux.CapacityQueryReply, len(req.AddTokens)) - if !cp.synced() { - capacityQueryZeroMeter.Mark(1) - reply, _ := rlp.EncodeToBytes(&result) - return reply - } - - bias := time.Second * time.Duration(req.Bias) - cp.lock.RLock() - if cp.connectedBias > bias { - bias = cp.connectedBias - } - cp.lock.RUnlock() - - // use capacityCurve to answer request for multiple newly bought token amounts - curve := cp.getCapacityCurve().exclude(id) - cp.BalanceOperation(id, freeID, func(balance AtomicBalanceOperator) { - pb, _ := balance.GetBalance() - for i, addTokens := range req.AddTokens { - add := addTokens.Int64() - result[i] = curve.maxCapacity(func(capacity uint64) int64 { - return balance.estimatePriority(capacity, add, 0, bias, false) / int64(capacity) - }) - if add <= 0 && uint64(-add) >= pb && result[i] > cp.minCap { - result[i] = cp.minCap - } - if result[i] < cp.minCap { - result[i] = 0 - } - } - }) - // add first result to metrics (don't care about priority client multi-queries yet) - if result[0] == 0 { - capacityQueryZeroMeter.Mark(1) - } else { - capacityQueryNonZeroMeter.Mark(1) - } - reply, _ := rlp.EncodeToBytes(&result) - return reply -} - -// Handle implements Service -func (cp *ClientPool) Handle(id enode.ID, address string, name string, data []byte) []byte { - switch name { - case vflux.CapacityQueryName: - return cp.serveCapQuery(id, address, data) - default: - return nil - } -} diff --git a/les/vflux/server/clientpool_test.go b/les/vflux/server/clientpool_test.go deleted file mode 100644 index f75c70afc..000000000 --- a/les/vflux/server/clientpool_test.go +++ /dev/null @@ -1,606 +0,0 @@ -// Copyright 2021 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 server - -import ( - "fmt" - "math/rand" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" - "github.com/ethereum/go-ethereum/p2p/nodestate" -) - -const defaultConnectedBias = time.Minute * 3 - -func TestClientPoolL10C100Free(t *testing.T) { - testClientPool(t, 10, 100, 0, true) -} - -func TestClientPoolL40C200Free(t *testing.T) { - testClientPool(t, 40, 200, 0, true) -} - -func TestClientPoolL100C300Free(t *testing.T) { - testClientPool(t, 100, 300, 0, true) -} - -func TestClientPoolL10C100P4(t *testing.T) { - testClientPool(t, 10, 100, 4, false) -} - -func TestClientPoolL40C200P30(t *testing.T) { - testClientPool(t, 40, 200, 30, false) -} - -func TestClientPoolL100C300P20(t *testing.T) { - testClientPool(t, 100, 300, 20, false) -} - -const testClientPoolTicks = 100000 - -type poolTestPeer struct { - node *enode.Node - index int - disconnCh chan int - cap uint64 - inactiveAllowed bool -} - -func newPoolTestPeer(i int, disconnCh chan int) *poolTestPeer { - return &poolTestPeer{ - index: i, - disconnCh: disconnCh, - node: enode.SignNull(&enr.Record{}, enode.ID{byte(i % 256), byte(i >> 8)}), - } -} - -func (i *poolTestPeer) Node() *enode.Node { - return i.node -} - -func (i *poolTestPeer) FreeClientId() string { - return fmt.Sprintf("addr #%d", i.index) -} - -func (i *poolTestPeer) InactiveAllowance() time.Duration { - if i.inactiveAllowed { - return time.Second * 10 - } - return 0 -} - -func (i *poolTestPeer) UpdateCapacity(capacity uint64, requested bool) { - i.cap = capacity -} - -func (i *poolTestPeer) Disconnect() { - if i.disconnCh == nil { - return - } - id := i.node.ID() - i.disconnCh <- int(id[0]) + int(id[1])<<8 -} - -func getBalance(pool *ClientPool, p *poolTestPeer) (pos, neg uint64) { - pool.BalanceOperation(p.node.ID(), p.FreeClientId(), func(nb AtomicBalanceOperator) { - pos, neg = nb.GetBalance() - }) - return -} - -func addBalance(pool *ClientPool, id enode.ID, amount int64) { - pool.BalanceOperation(id, "", func(nb AtomicBalanceOperator) { - nb.AddBalance(amount) - }) -} - -func checkDiff(a, b uint64) bool { - maxDiff := (a + b) / 2000 - if maxDiff < 1 { - maxDiff = 1 - } - return a > b+maxDiff || b > a+maxDiff -} - -func connect(pool *ClientPool, peer *poolTestPeer) uint64 { - pool.Register(peer) - return peer.cap -} - -func disconnect(pool *ClientPool, peer *poolTestPeer) { - pool.Unregister(peer) -} - -func alwaysTrueFn() bool { - return true -} - -func testClientPool(t *testing.T, activeLimit, clientCount, paidCount int, randomDisconnect bool) { - var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - connected = make([]bool, clientCount) - connTicks = make([]int, clientCount) - disconnCh = make(chan int, clientCount) - pool = NewClientPool(db, 1, 0, &clock, alwaysTrueFn) - ) - pool.Start() - pool.SetExpirationTCs(0, 1000) - - pool.SetLimits(uint64(activeLimit), uint64(activeLimit)) - pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) - - // pool should accept new peers up to its connected limit - for i := 0; i < activeLimit; i++ { - if cap := connect(pool, newPoolTestPeer(i, disconnCh)); cap != 0 { - connected[i] = true - } else { - t.Fatalf("Test peer #%d rejected", i) - } - } - // randomly connect and disconnect peers, expect to have a similar total connection time at the end - for tickCounter := 0; tickCounter < testClientPoolTicks; tickCounter++ { - clock.Run(1 * time.Second) - - if tickCounter == testClientPoolTicks/4 { - // give a positive balance to some of the peers - amount := testClientPoolTicks / 2 * int64(time.Second) // enough for half of the simulation period - for i := 0; i < paidCount; i++ { - addBalance(pool, newPoolTestPeer(i, disconnCh).node.ID(), amount) - } - } - - i := rand.Intn(clientCount) - if connected[i] { - if randomDisconnect { - disconnect(pool, newPoolTestPeer(i, disconnCh)) - connected[i] = false - connTicks[i] += tickCounter - } - } else { - if cap := connect(pool, newPoolTestPeer(i, disconnCh)); cap != 0 { - connected[i] = true - connTicks[i] -= tickCounter - } else { - disconnect(pool, newPoolTestPeer(i, disconnCh)) - } - } - pollDisconnects: - for { - select { - case i := <-disconnCh: - disconnect(pool, newPoolTestPeer(i, disconnCh)) - if connected[i] { - connTicks[i] += tickCounter - connected[i] = false - } - default: - break pollDisconnects - } - } - } - - expTicks := testClientPoolTicks/2*activeLimit/clientCount + testClientPoolTicks/2*(activeLimit-paidCount)/(clientCount-paidCount) - expMin := expTicks - expTicks/5 - expMax := expTicks + expTicks/5 - paidTicks := testClientPoolTicks/2*activeLimit/clientCount + testClientPoolTicks/2 - paidMin := paidTicks - paidTicks/5 - paidMax := paidTicks + paidTicks/5 - - // check if the total connected time of peers are all in the expected range - for i, c := range connected { - if c { - connTicks[i] += testClientPoolTicks - } - min, max := expMin, expMax - if i < paidCount { - // expect a higher amount for clients with a positive balance - min, max = paidMin, paidMax - } - if connTicks[i] < min || connTicks[i] > max { - t.Errorf("Total connected time of test node #%d (%d) outside expected range (%d to %d)", i, connTicks[i], min, max) - } - } - pool.Stop() -} - -func testPriorityConnect(t *testing.T, pool *ClientPool, p *poolTestPeer, cap uint64, expSuccess bool) { - if cap := connect(pool, p); cap == 0 { - if expSuccess { - t.Fatalf("Failed to connect paid client") - } else { - return - } - } - if newCap, _ := pool.SetCapacity(p.node, cap, defaultConnectedBias, true); newCap != cap { - if expSuccess { - t.Fatalf("Failed to raise capacity of paid client") - } else { - return - } - } - if !expSuccess { - t.Fatalf("Should reject high capacity paid client") - } -} - -func TestConnectPaidClient(t *testing.T) { - var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - ) - pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) - pool.Start() - defer pool.Stop() - pool.SetLimits(10, uint64(10)) - pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) - - // Add balance for an external client and mark it as paid client - addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute)) - testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 10, true) -} - -func TestConnectPaidClientToSmallPool(t *testing.T) { - var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - ) - pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) - pool.Start() - defer pool.Stop() - pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 - pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) - - // Add balance for an external client and mark it as paid client - addBalance(pool, newPoolTestPeer(0, nil).node.ID(), int64(time.Minute)) - - // connect a fat paid client to pool, should reject it. - testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 100, false) -} - -func TestConnectPaidClientToFullPool(t *testing.T) { - var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - ) - pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) - pool.Start() - defer pool.Stop() - pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 - pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) - - for i := 0; i < 10; i++ { - addBalance(pool, newPoolTestPeer(i, nil).node.ID(), int64(time.Second*20)) - connect(pool, newPoolTestPeer(i, nil)) - } - addBalance(pool, newPoolTestPeer(11, nil).node.ID(), int64(time.Second*2)) // Add low balance to new paid client - if cap := connect(pool, newPoolTestPeer(11, nil)); cap != 0 { - t.Fatalf("Low balance paid client should be rejected") - } - clock.Run(time.Second) - addBalance(pool, newPoolTestPeer(12, nil).node.ID(), int64(time.Minute*5)) // Add high balance to new paid client - if cap := connect(pool, newPoolTestPeer(12, nil)); cap == 0 { - t.Fatalf("High balance paid client should be accepted") - } -} - -func TestPaidClientKickedOut(t *testing.T) { - var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - kickedCh = make(chan int, 100) - ) - pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) - pool.Start() - pool.SetExpirationTCs(0, 0) - defer pool.Stop() - pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 - pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) - - for i := 0; i < 10; i++ { - addBalance(pool, newPoolTestPeer(i, kickedCh).node.ID(), 10000000000) // 10 second allowance - connect(pool, newPoolTestPeer(i, kickedCh)) - clock.Run(time.Millisecond) - } - clock.Run(defaultConnectedBias + time.Second*11) - if cap := connect(pool, newPoolTestPeer(11, kickedCh)); cap == 0 { - t.Fatalf("Free client should be accepted") - } - clock.Run(0) - select { - case id := <-kickedCh: - if id != 0 { - t.Fatalf("Kicked client mismatch, want %v, got %v", 0, id) - } - default: - t.Fatalf("timeout") - } -} - -func TestConnectFreeClient(t *testing.T) { - var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - ) - pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) - pool.Start() - defer pool.Stop() - pool.SetLimits(10, uint64(10)) - pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) - if cap := connect(pool, newPoolTestPeer(0, nil)); cap == 0 { - t.Fatalf("Failed to connect free client") - } - testPriorityConnect(t, pool, newPoolTestPeer(0, nil), 2, false) -} - -func TestConnectFreeClientToFullPool(t *testing.T) { - var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - ) - pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) - pool.Start() - defer pool.Stop() - pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 - pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) - - for i := 0; i < 10; i++ { - connect(pool, newPoolTestPeer(i, nil)) - } - if cap := connect(pool, newPoolTestPeer(11, nil)); cap != 0 { - t.Fatalf("New free client should be rejected") - } - clock.Run(time.Minute) - if cap := connect(pool, newPoolTestPeer(12, nil)); cap != 0 { - t.Fatalf("New free client should be rejected") - } - clock.Run(time.Millisecond) - clock.Run(4 * time.Minute) - if cap := connect(pool, newPoolTestPeer(13, nil)); cap == 0 { - t.Fatalf("Old client connects more than 5min should be kicked") - } -} - -func TestFreeClientKickedOut(t *testing.T) { - var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - kicked = make(chan int, 100) - ) - pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) - pool.Start() - defer pool.Stop() - pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 - pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) - - for i := 0; i < 10; i++ { - connect(pool, newPoolTestPeer(i, kicked)) - clock.Run(time.Millisecond) - } - if cap := connect(pool, newPoolTestPeer(10, kicked)); cap != 0 { - t.Fatalf("New free client should be rejected") - } - clock.Run(0) - select { - case <-kicked: - default: - t.Fatalf("timeout") - } - disconnect(pool, newPoolTestPeer(10, kicked)) - clock.Run(5 * time.Minute) - for i := 0; i < 10; i++ { - connect(pool, newPoolTestPeer(i+10, kicked)) - } - clock.Run(0) - - for i := 0; i < 10; i++ { - select { - case id := <-kicked: - if id >= 10 { - t.Fatalf("Old client should be kicked, now got: %d", id) - } - default: - t.Fatalf("timeout") - } - } -} - -func TestPositiveBalanceCalculation(t *testing.T) { - var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - kicked = make(chan int, 10) - ) - pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) - pool.Start() - defer pool.Stop() - pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 - pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) - - addBalance(pool, newPoolTestPeer(0, kicked).node.ID(), int64(time.Minute*3)) - testPriorityConnect(t, pool, newPoolTestPeer(0, kicked), 10, true) - clock.Run(time.Minute) - - disconnect(pool, newPoolTestPeer(0, kicked)) - pb, _ := getBalance(pool, newPoolTestPeer(0, kicked)) - if checkDiff(pb, uint64(time.Minute*2)) { - t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute*2), pb) - } -} - -func TestDowngradePriorityClient(t *testing.T) { - var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - kicked = make(chan int, 10) - ) - pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) - pool.Start() - defer pool.Stop() - pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 - pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 1}) - - p := newPoolTestPeer(0, kicked) - addBalance(pool, p.node.ID(), int64(time.Minute)) - testPriorityConnect(t, pool, p, 10, true) - if p.cap != 10 { - t.Fatalf("The capacity of priority peer hasn't been updated, got: %d", p.cap) - } - - clock.Run(time.Minute) // All positive balance should be used up. - time.Sleep(300 * time.Millisecond) // Ensure the callback is called - if p.cap != 1 { - t.Fatalf("The capcacity of peer should be downgraded, got: %d", p.cap) - } - pb, _ := getBalance(pool, newPoolTestPeer(0, kicked)) - if pb != 0 { - t.Fatalf("Positive balance mismatch, want %v, got %v", 0, pb) - } - - addBalance(pool, newPoolTestPeer(0, kicked).node.ID(), int64(time.Minute)) - pb, _ = getBalance(pool, newPoolTestPeer(0, kicked)) - if checkDiff(pb, uint64(time.Minute)) { - t.Fatalf("Positive balance mismatch, want %v, got %v", uint64(time.Minute), pb) - } -} - -func TestNegativeBalanceCalculation(t *testing.T) { - var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - ) - pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) - pool.Start() - defer pool.Stop() - pool.SetExpirationTCs(0, 3600) - pool.SetLimits(10, uint64(10)) // Total capacity limit is 10 - pool.SetDefaultFactors(PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}, PriceFactors{TimeFactor: 1e-3, CapacityFactor: 0, RequestFactor: 1}) - - for i := 0; i < 10; i++ { - connect(pool, newPoolTestPeer(i, nil)) - } - clock.Run(time.Second) - - for i := 0; i < 10; i++ { - disconnect(pool, newPoolTestPeer(i, nil)) - _, nb := getBalance(pool, newPoolTestPeer(i, nil)) - if nb != 0 { - t.Fatalf("Short connection shouldn't be recorded") - } - } - for i := 0; i < 10; i++ { - connect(pool, newPoolTestPeer(i, nil)) - } - clock.Run(time.Minute) - for i := 0; i < 10; i++ { - disconnect(pool, newPoolTestPeer(i, nil)) - _, nb := getBalance(pool, newPoolTestPeer(i, nil)) - exp := uint64(time.Minute) / 1000 - exp -= exp / 120 // correct for negative balance expiration - if checkDiff(nb, exp) { - t.Fatalf("Negative balance mismatch, want %v, got %v", exp, nb) - } - } -} - -func TestInactiveClient(t *testing.T) { - var ( - clock mclock.Simulated - db = rawdb.NewMemoryDatabase() - ) - pool := NewClientPool(db, 1, defaultConnectedBias, &clock, alwaysTrueFn) - pool.Start() - defer pool.Stop() - pool.SetLimits(2, uint64(2)) - - p1 := newPoolTestPeer(1, nil) - p1.inactiveAllowed = true - p2 := newPoolTestPeer(2, nil) - p2.inactiveAllowed = true - p3 := newPoolTestPeer(3, nil) - p3.inactiveAllowed = true - addBalance(pool, p1.node.ID(), 1000*int64(time.Second)) - addBalance(pool, p3.node.ID(), 2000*int64(time.Second)) - // p1: 1000 p2: 0 p3: 2000 - p1.cap = connect(pool, p1) - if p1.cap != 1 { - t.Fatalf("Failed to connect peer #1") - } - p2.cap = connect(pool, p2) - if p2.cap != 1 { - t.Fatalf("Failed to connect peer #2") - } - p3.cap = connect(pool, p3) - if p3.cap != 1 { - t.Fatalf("Failed to connect peer #3") - } - if p2.cap != 0 { - t.Fatalf("Failed to deactivate peer #2") - } - addBalance(pool, p2.node.ID(), 3000*int64(time.Second)) - // p1: 1000 p2: 3000 p3: 2000 - if p2.cap != 1 { - t.Fatalf("Failed to activate peer #2") - } - if p1.cap != 0 { - t.Fatalf("Failed to deactivate peer #1") - } - addBalance(pool, p2.node.ID(), -2500*int64(time.Second)) - // p1: 1000 p2: 500 p3: 2000 - if p1.cap != 1 { - t.Fatalf("Failed to activate peer #1") - } - if p2.cap != 0 { - t.Fatalf("Failed to deactivate peer #2") - } - pool.SetDefaultFactors(PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}, PriceFactors{TimeFactor: 1, CapacityFactor: 0, RequestFactor: 0}) - p4 := newPoolTestPeer(4, nil) - addBalance(pool, p4.node.ID(), 1500*int64(time.Second)) - // p1: 1000 p2: 500 p3: 2000 p4: 1500 - p4.cap = connect(pool, p4) - if p4.cap != 1 { - t.Fatalf("Failed to activate peer #4") - } - if p1.cap != 0 { - t.Fatalf("Failed to deactivate peer #1") - } - clock.Run(time.Second * 600) - // manually trigger a check to avoid a long real-time wait - pool.ns.SetState(p1.node, pool.setup.updateFlag, nodestate.Flags{}, 0) - pool.ns.SetState(p1.node, nodestate.Flags{}, pool.setup.updateFlag, 0) - // p1: 1000 p2: 500 p3: 2000 p4: 900 - if p1.cap != 1 { - t.Fatalf("Failed to activate peer #1") - } - if p4.cap != 0 { - t.Fatalf("Failed to deactivate peer #4") - } - disconnect(pool, p2) - disconnect(pool, p4) - addBalance(pool, p1.node.ID(), -1000*int64(time.Second)) - if p1.cap != 1 { - t.Fatalf("Should not deactivate peer #1") - } - if p2.cap != 0 { - t.Fatalf("Should not activate peer #2") - } -} diff --git a/les/vflux/server/metrics.go b/les/vflux/server/metrics.go deleted file mode 100644 index 680aebe2e..000000000 --- a/les/vflux/server/metrics.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2021 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 server - -import ( - "github.com/ethereum/go-ethereum/metrics" -) - -var ( - totalActiveCapacityGauge = metrics.NewRegisteredGauge("vflux/server/active/capacity", nil) - totalActiveCountGauge = metrics.NewRegisteredGauge("vflux/server/active/count", nil) - totalInactiveCountGauge = metrics.NewRegisteredGauge("vflux/server/inactive/count", nil) - - clientConnectedMeter = metrics.NewRegisteredMeter("vflux/server/clientEvent/connected", nil) - clientActivatedMeter = metrics.NewRegisteredMeter("vflux/server/clientEvent/activated", nil) - clientDeactivatedMeter = metrics.NewRegisteredMeter("vflux/server/clientEvent/deactivated", nil) - clientDisconnectedMeter = metrics.NewRegisteredMeter("vflux/server/clientEvent/disconnected", nil) - - capacityQueryZeroMeter = metrics.NewRegisteredMeter("vflux/server/capQueryZero", nil) - capacityQueryNonZeroMeter = metrics.NewRegisteredMeter("vflux/server/capQueryNonZero", nil) -) diff --git a/les/vflux/server/prioritypool.go b/les/vflux/server/prioritypool.go deleted file mode 100644 index 766026a80..000000000 --- a/les/vflux/server/prioritypool.go +++ /dev/null @@ -1,695 +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 server - -import ( - "math" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/common/prque" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/nodestate" -) - -const ( - lazyQueueRefresh = time.Second * 10 // refresh period of the active queue -) - -// priorityPool handles a set of nodes where each node has a capacity (a scalar value) -// and a priority (which can change over time and can also depend on the capacity). -// A node is active if it has at least the necessary minimal amount of capacity while -// inactive nodes have 0 capacity (values between 0 and the minimum are not allowed). -// The pool ensures that the number and total capacity of all active nodes are limited -// and the highest priority nodes are active at all times (limits can be changed -// during operation with immediate effect). -// -// When activating clients a priority bias is applied in favor of the already active -// nodes in order to avoid nodes quickly alternating between active and inactive states -// when their priorities are close to each other. The bias is specified in terms of -// duration (time) because priorities are expected to usually get lower over time and -// therefore a future minimum prediction (see EstMinPriority) should monotonously -// decrease with the specified time parameter. -// This time bias can be interpreted as minimum expected active time at the given -// capacity (if the threshold priority stays the same). -// -// Nodes in the pool always have either inactiveFlag or activeFlag set. A new node is -// added to the pool by externally setting inactiveFlag. priorityPool can switch a node -// between inactiveFlag and activeFlag at any time. Nodes can be removed from the pool -// by externally resetting both flags. activeFlag should not be set externally. -// -// The highest priority nodes in "inactive" state are moved to "active" state as soon as -// the minimum capacity can be granted for them. The capacity of lower priority active -// nodes is reduced or they are demoted to "inactive" state if their priority is -// insufficient even at minimal capacity. -type priorityPool struct { - setup *serverSetup - ns *nodestate.NodeStateMachine - clock mclock.Clock - lock sync.Mutex - maxCount, maxCap uint64 - minCap uint64 - activeBias time.Duration - capacityStepDiv, fineStepDiv uint64 - - // The snapshot of priority pool for query. - cachedCurve *capacityCurve - ccUpdatedAt mclock.AbsTime - ccUpdateForced bool - - // Runtime status of prioritypool, represents the - // temporary state if tempState is not empty - tempState []*ppNodeInfo - activeCount, activeCap uint64 - activeQueue *prque.LazyQueue[int64, *ppNodeInfo] - inactiveQueue *prque.Prque[int64, *ppNodeInfo] -} - -// ppNodeInfo is the internal node descriptor of priorityPool -type ppNodeInfo struct { - nodePriority nodePriority - node *enode.Node - connected bool - capacity uint64 // only changed when temporary state is committed - activeIndex, inactiveIndex int - - tempState bool // should only be true while the priorityPool lock is held - tempCapacity uint64 // equals capacity when tempState is false - - // the following fields only affect the temporary state and they are set to their - // default value when leaving the temp state - minTarget, stepDiv uint64 - bias time.Duration -} - -// newPriorityPool creates a new priorityPool -func newPriorityPool(ns *nodestate.NodeStateMachine, setup *serverSetup, clock mclock.Clock, minCap uint64, activeBias time.Duration, capacityStepDiv, fineStepDiv uint64) *priorityPool { - pp := &priorityPool{ - setup: setup, - ns: ns, - clock: clock, - inactiveQueue: prque.New[int64, *ppNodeInfo](inactiveSetIndex), - minCap: minCap, - activeBias: activeBias, - capacityStepDiv: capacityStepDiv, - fineStepDiv: fineStepDiv, - } - if pp.activeBias < time.Duration(1) { - pp.activeBias = time.Duration(1) - } - pp.activeQueue = prque.NewLazyQueue(activeSetIndex, activePriority, pp.activeMaxPriority, clock, lazyQueueRefresh) - - ns.SubscribeField(pp.setup.balanceField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { - if newValue != nil { - c := &ppNodeInfo{ - node: node, - nodePriority: newValue.(nodePriority), - activeIndex: -1, - inactiveIndex: -1, - } - ns.SetFieldSub(node, pp.setup.queueField, c) - ns.SetStateSub(node, setup.inactiveFlag, nodestate.Flags{}, 0) - } else { - ns.SetStateSub(node, nodestate.Flags{}, pp.setup.activeFlag.Or(pp.setup.inactiveFlag), 0) - if n, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo); n != nil { - pp.disconnectNode(n) - } - ns.SetFieldSub(node, pp.setup.capacityField, nil) - ns.SetFieldSub(node, pp.setup.queueField, nil) - } - }) - ns.SubscribeState(pp.setup.activeFlag.Or(pp.setup.inactiveFlag), func(node *enode.Node, oldState, newState nodestate.Flags) { - if c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo); c != nil { - if oldState.IsEmpty() { - pp.connectNode(c) - } - if newState.IsEmpty() { - pp.disconnectNode(c) - } - } - }) - ns.SubscribeState(pp.setup.updateFlag, func(node *enode.Node, oldState, newState nodestate.Flags) { - if !newState.IsEmpty() { - pp.updatePriority(node) - } - }) - return pp -} - -// requestCapacity tries to set the capacity of a connected node to the highest possible -// value inside the given target range. If maxTarget is not reachable then the capacity is -// iteratively reduced in fine steps based on the fineStepDiv parameter until minTarget is reached. -// The function returns the new capacity if successful and the original capacity otherwise. -// Note: this function should run inside a NodeStateMachine operation -func (pp *priorityPool) requestCapacity(node *enode.Node, minTarget, maxTarget uint64, bias time.Duration) uint64 { - pp.lock.Lock() - pp.activeQueue.Refresh() - - if minTarget < pp.minCap { - minTarget = pp.minCap - } - if maxTarget < minTarget { - maxTarget = minTarget - } - if bias < pp.activeBias { - bias = pp.activeBias - } - c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo) - if c == nil { - log.Error("requestCapacity called for unknown node", "id", node.ID()) - pp.lock.Unlock() - return 0 - } - pp.setTempState(c) - if maxTarget > c.capacity { - pp.setTempStepDiv(c, pp.fineStepDiv) - pp.setTempBias(c, bias) - } - pp.setTempCapacity(c, maxTarget) - c.minTarget = minTarget - pp.removeFromQueues(c) - pp.activeQueue.Push(c) - pp.enforceLimits() - updates := pp.finalizeChanges(c.tempCapacity >= minTarget && c.tempCapacity <= maxTarget && c.tempCapacity != c.capacity) - pp.lock.Unlock() - pp.updateFlags(updates) - return c.capacity -} - -// SetLimits sets the maximum number and total capacity of simultaneously active nodes -func (pp *priorityPool) SetLimits(maxCount, maxCap uint64) { - pp.lock.Lock() - pp.activeQueue.Refresh() - inc := (maxCount > pp.maxCount) || (maxCap > pp.maxCap) - dec := (maxCount < pp.maxCount) || (maxCap < pp.maxCap) - pp.maxCount, pp.maxCap = maxCount, maxCap - - var updates []capUpdate - if dec { - pp.enforceLimits() - updates = pp.finalizeChanges(true) - } - if inc { - updates = append(updates, pp.tryActivate(false)...) - } - pp.lock.Unlock() - pp.ns.Operation(func() { pp.updateFlags(updates) }) -} - -// setActiveBias sets the bias applied when trying to activate inactive nodes -func (pp *priorityPool) setActiveBias(bias time.Duration) { - pp.lock.Lock() - pp.activeBias = bias - if pp.activeBias < time.Duration(1) { - pp.activeBias = time.Duration(1) - } - updates := pp.tryActivate(false) - pp.lock.Unlock() - pp.ns.Operation(func() { pp.updateFlags(updates) }) -} - -// Active returns the number and total capacity of currently active nodes -func (pp *priorityPool) Active() (uint64, uint64) { - pp.lock.Lock() - defer pp.lock.Unlock() - - return pp.activeCount, pp.activeCap -} - -// Inactive returns the number of currently inactive nodes -func (pp *priorityPool) Inactive() int { - pp.lock.Lock() - defer pp.lock.Unlock() - - return pp.inactiveQueue.Size() -} - -// Limits returns the maximum allowed number and total capacity of active nodes -func (pp *priorityPool) Limits() (uint64, uint64) { - pp.lock.Lock() - defer pp.lock.Unlock() - - return pp.maxCount, pp.maxCap -} - -// inactiveSetIndex callback updates ppNodeInfo item index in inactiveQueue -func inactiveSetIndex(a *ppNodeInfo, index int) { - a.inactiveIndex = index -} - -// activeSetIndex callback updates ppNodeInfo item index in activeQueue -func activeSetIndex(a *ppNodeInfo, index int) { - a.activeIndex = index -} - -// invertPriority inverts a priority value. The active queue uses inverted priorities -// because the node on the top is the first to be deactivated. -func invertPriority(p int64) int64 { - if p == math.MinInt64 { - return math.MaxInt64 - } - return -p -} - -// activePriority callback returns actual priority of ppNodeInfo item in activeQueue -func activePriority(c *ppNodeInfo) int64 { - if c.bias == 0 { - return invertPriority(c.nodePriority.priority(c.tempCapacity)) - } else { - return invertPriority(c.nodePriority.estimatePriority(c.tempCapacity, 0, 0, c.bias, true)) - } -} - -// activeMaxPriority callback returns estimated maximum priority of ppNodeInfo item in activeQueue -func (pp *priorityPool) activeMaxPriority(c *ppNodeInfo, until mclock.AbsTime) int64 { - future := time.Duration(until - pp.clock.Now()) - if future < 0 { - future = 0 - } - return invertPriority(c.nodePriority.estimatePriority(c.tempCapacity, 0, future, c.bias, false)) -} - -// inactivePriority callback returns actual priority of ppNodeInfo item in inactiveQueue -func (pp *priorityPool) inactivePriority(p *ppNodeInfo) int64 { - return p.nodePriority.priority(pp.minCap) -} - -// removeFromQueues removes the node from the active/inactive queues -func (pp *priorityPool) removeFromQueues(c *ppNodeInfo) { - if c.activeIndex >= 0 { - pp.activeQueue.Remove(c.activeIndex) - } - if c.inactiveIndex >= 0 { - pp.inactiveQueue.Remove(c.inactiveIndex) - } -} - -// connectNode is called when a new node has been added to the pool (inactiveFlag set) -// Note: this function should run inside a NodeStateMachine operation -func (pp *priorityPool) connectNode(c *ppNodeInfo) { - pp.lock.Lock() - pp.activeQueue.Refresh() - if c.connected { - pp.lock.Unlock() - return - } - c.connected = true - pp.inactiveQueue.Push(c, pp.inactivePriority(c)) - updates := pp.tryActivate(false) - pp.lock.Unlock() - pp.updateFlags(updates) -} - -// disconnectNode is called when a node has been removed from the pool (both inactiveFlag -// and activeFlag reset) -// Note: this function should run inside a NodeStateMachine operation -func (pp *priorityPool) disconnectNode(c *ppNodeInfo) { - pp.lock.Lock() - pp.activeQueue.Refresh() - if !c.connected { - pp.lock.Unlock() - return - } - c.connected = false - pp.removeFromQueues(c) - - var updates []capUpdate - if c.capacity != 0 { - pp.setTempState(c) - pp.setTempCapacity(c, 0) - updates = pp.tryActivate(true) - } - pp.lock.Unlock() - pp.updateFlags(updates) -} - -// setTempState internally puts a node in a temporary state that can either be reverted -// or confirmed later. This temporary state allows changing the capacity of a node and -// moving it between the active and inactive queue. activeFlag/inactiveFlag and -// capacityField are not changed while the changes are still temporary. -func (pp *priorityPool) setTempState(c *ppNodeInfo) { - if c.tempState { - return - } - c.tempState = true - if c.tempCapacity != c.capacity { // should never happen - log.Error("tempCapacity != capacity when entering tempState") - } - // Assign all the defaults to the temp state. - c.minTarget = pp.minCap - c.stepDiv = pp.capacityStepDiv - c.bias = 0 - pp.tempState = append(pp.tempState, c) -} - -// unsetTempState revokes the temp status of the node and reset all internal -// fields to the default value. -func (pp *priorityPool) unsetTempState(c *ppNodeInfo) { - if !c.tempState { - return - } - c.tempState = false - if c.tempCapacity != c.capacity { // should never happen - log.Error("tempCapacity != capacity when leaving tempState") - } - c.minTarget = pp.minCap - c.stepDiv = pp.capacityStepDiv - c.bias = 0 -} - -// setTempCapacity changes the capacity of a node in the temporary state and adjusts -// activeCap and activeCount accordingly. Since this change is performed in the temporary -// state it should be called after setTempState and before finalizeChanges. -func (pp *priorityPool) setTempCapacity(c *ppNodeInfo, cap uint64) { - if !c.tempState { // should never happen - log.Error("Node is not in temporary state") - return - } - pp.activeCap += cap - c.tempCapacity - if c.tempCapacity == 0 { - pp.activeCount++ - } - if cap == 0 { - pp.activeCount-- - } - c.tempCapacity = cap -} - -// setTempBias changes the connection bias of a node in the temporary state. -func (pp *priorityPool) setTempBias(c *ppNodeInfo, bias time.Duration) { - if !c.tempState { // should never happen - log.Error("Node is not in temporary state") - return - } - c.bias = bias -} - -// setTempStepDiv changes the capacity divisor of a node in the temporary state. -func (pp *priorityPool) setTempStepDiv(c *ppNodeInfo, stepDiv uint64) { - if !c.tempState { // should never happen - log.Error("Node is not in temporary state") - return - } - c.stepDiv = stepDiv -} - -// enforceLimits enforces active node count and total capacity limits. It returns the -// lowest active node priority. Note that this function is performed on the temporary -// internal state. -func (pp *priorityPool) enforceLimits() (*ppNodeInfo, int64) { - if pp.activeCap <= pp.maxCap && pp.activeCount <= pp.maxCount { - return nil, math.MinInt64 - } - var ( - lastNode *ppNodeInfo - maxActivePriority int64 - ) - pp.activeQueue.MultiPop(func(c *ppNodeInfo, priority int64) bool { - lastNode = c - pp.setTempState(c) - maxActivePriority = priority - if c.tempCapacity == c.minTarget || pp.activeCount > pp.maxCount { - pp.setTempCapacity(c, 0) - } else { - sub := c.tempCapacity / c.stepDiv - if sub == 0 { - sub = 1 - } - if c.tempCapacity-sub < c.minTarget { - sub = c.tempCapacity - c.minTarget - } - pp.setTempCapacity(c, c.tempCapacity-sub) - pp.activeQueue.Push(c) - } - return pp.activeCap > pp.maxCap || pp.activeCount > pp.maxCount - }) - return lastNode, invertPriority(maxActivePriority) -} - -// finalizeChanges either commits or reverts temporary changes. The necessary capacity -// field and according flag updates are not performed here but returned in a list because -// they should be performed while the mutex is not held. -func (pp *priorityPool) finalizeChanges(commit bool) (updates []capUpdate) { - for _, c := range pp.tempState { - // always remove and push back in order to update biased priority - pp.removeFromQueues(c) - oldCapacity := c.capacity - if commit { - c.capacity = c.tempCapacity - } else { - pp.setTempCapacity(c, c.capacity) // revert activeCount/activeCap - } - pp.unsetTempState(c) - - if c.connected { - if c.capacity != 0 { - pp.activeQueue.Push(c) - } else { - pp.inactiveQueue.Push(c, pp.inactivePriority(c)) - } - if c.capacity != oldCapacity { - updates = append(updates, capUpdate{c.node, oldCapacity, c.capacity}) - } - } - } - pp.tempState = nil - if commit { - pp.ccUpdateForced = true - } - return -} - -// capUpdate describes a capacityField and activeFlag/inactiveFlag update -type capUpdate struct { - node *enode.Node - oldCap, newCap uint64 -} - -// updateFlags performs capacityField and activeFlag/inactiveFlag updates while the -// pool mutex is not held -// Note: this function should run inside a NodeStateMachine operation -func (pp *priorityPool) updateFlags(updates []capUpdate) { - for _, f := range updates { - if f.oldCap == 0 { - pp.ns.SetStateSub(f.node, pp.setup.activeFlag, pp.setup.inactiveFlag, 0) - } - if f.newCap == 0 { - pp.ns.SetStateSub(f.node, pp.setup.inactiveFlag, pp.setup.activeFlag, 0) - pp.ns.SetFieldSub(f.node, pp.setup.capacityField, nil) - } else { - pp.ns.SetFieldSub(f.node, pp.setup.capacityField, f.newCap) - } - } -} - -// tryActivate tries to activate inactive nodes if possible -func (pp *priorityPool) tryActivate(commit bool) []capUpdate { - for pp.inactiveQueue.Size() > 0 { - c := pp.inactiveQueue.PopItem() - pp.setTempState(c) - pp.setTempBias(c, pp.activeBias) - pp.setTempCapacity(c, pp.minCap) - pp.activeQueue.Push(c) - pp.enforceLimits() - if c.tempCapacity > 0 { - commit = true - pp.setTempBias(c, 0) - } else { - break - } - } - pp.ccUpdateForced = true - return pp.finalizeChanges(commit) -} - -// updatePriority gets the current priority value of the given node from the nodePriority -// interface and performs the necessary changes. It is triggered by updateFlag. -// Note: this function should run inside a NodeStateMachine operation -func (pp *priorityPool) updatePriority(node *enode.Node) { - pp.lock.Lock() - pp.activeQueue.Refresh() - c, _ := pp.ns.GetField(node, pp.setup.queueField).(*ppNodeInfo) - if c == nil || !c.connected { - pp.lock.Unlock() - return - } - pp.removeFromQueues(c) - if c.capacity != 0 { - pp.activeQueue.Push(c) - } else { - pp.inactiveQueue.Push(c, pp.inactivePriority(c)) - } - updates := pp.tryActivate(false) - pp.lock.Unlock() - pp.updateFlags(updates) -} - -// capacityCurve is a snapshot of the priority pool contents in a format that can efficiently -// estimate how much capacity could be granted to a given node at a given priority level. -type capacityCurve struct { - points []curvePoint // curve points sorted in descending order of priority - index map[enode.ID][]int // curve point indexes belonging to each node - excludeList []int // curve point indexes of excluded node - excludeFirst bool // true if activeCount == maxCount -} - -type curvePoint struct { - freeCap uint64 // available capacity and node count at the current priority level - nextPri int64 // next priority level where more capacity will be available -} - -// getCapacityCurve returns a new or recently cached capacityCurve based on the contents of the pool -func (pp *priorityPool) getCapacityCurve() *capacityCurve { - pp.lock.Lock() - defer pp.lock.Unlock() - - now := pp.clock.Now() - dt := time.Duration(now - pp.ccUpdatedAt) - if !pp.ccUpdateForced && pp.cachedCurve != nil && dt < time.Second*10 { - return pp.cachedCurve - } - - pp.ccUpdateForced = false - pp.ccUpdatedAt = now - curve := &capacityCurve{ - index: make(map[enode.ID][]int), - } - pp.cachedCurve = curve - - var excludeID enode.ID - excludeFirst := pp.maxCount == pp.activeCount - // reduce node capacities or remove nodes until nothing is left in the queue; - // record the available capacity and the necessary priority after each step - lastPri := int64(math.MinInt64) - for pp.activeCap > 0 { - cp := curvePoint{} - if pp.activeCap > pp.maxCap { - log.Error("Active capacity is greater than allowed maximum", "active", pp.activeCap, "maximum", pp.maxCap) - } else { - cp.freeCap = pp.maxCap - pp.activeCap - } - // temporarily increase activeCap to enforce reducing or removing a node capacity - tempCap := cp.freeCap + 1 - pp.activeCap += tempCap - var next *ppNodeInfo - // enforceLimits removes the lowest priority node if it has minimal capacity, - // otherwise reduces its capacity - next, cp.nextPri = pp.enforceLimits() - if cp.nextPri < lastPri { - // enforce monotonicity which may be broken by continuously changing priorities - cp.nextPri = lastPri - } else { - lastPri = cp.nextPri - } - pp.activeCap -= tempCap - if next == nil { - log.Error("getCapacityCurve: cannot remove next element from the priority queue") - break - } - id := next.node.ID() - if excludeFirst { - // if the node count limit is already reached then mark the node with the - // lowest priority for exclusion - curve.excludeFirst = true - excludeID = id - excludeFirst = false - } - // multiple curve points and therefore multiple indexes may belong to a node - // if it was removed in multiple steps (if its capacity was more than the minimum) - curve.index[id] = append(curve.index[id], len(curve.points)) - curve.points = append(curve.points, cp) - } - // restore original state of the queue - pp.finalizeChanges(false) - curve.points = append(curve.points, curvePoint{ - freeCap: pp.maxCap, - nextPri: math.MaxInt64, - }) - if curve.excludeFirst { - curve.excludeList = curve.index[excludeID] - } - return curve -} - -// exclude returns a capacityCurve with the given node excluded from the original curve -func (cc *capacityCurve) exclude(id enode.ID) *capacityCurve { - if excludeList, ok := cc.index[id]; ok { - // return a new version of the curve (only one excluded node can be selected) - // Note: if the first node was excluded by default (excludeFirst == true) then - // we can forget about that and exclude the node with the given id instead. - return &capacityCurve{ - points: cc.points, - index: cc.index, - excludeList: excludeList, - } - } - return cc -} - -func (cc *capacityCurve) getPoint(i int) curvePoint { - cp := cc.points[i] - if i == 0 && cc.excludeFirst { - cp.freeCap = 0 - return cp - } - for ii := len(cc.excludeList) - 1; ii >= 0; ii-- { - ei := cc.excludeList[ii] - if ei < i { - break - } - e1, e2 := cc.points[ei], cc.points[ei+1] - cp.freeCap += e2.freeCap - e1.freeCap - } - return cp -} - -// maxCapacity calculates the maximum capacity available for a node with a given -// (monotonically decreasing) priority vs. capacity function. Note that if the requesting -// node is already in the pool then it should be excluded from the curve in order to get -// the correct result. -func (cc *capacityCurve) maxCapacity(priority func(cap uint64) int64) uint64 { - min, max := 0, len(cc.points)-1 // the curve always has at least one point - for min < max { - mid := (min + max) / 2 - cp := cc.getPoint(mid) - if cp.freeCap == 0 || priority(cp.freeCap) > cp.nextPri { - min = mid + 1 - } else { - max = mid - } - } - cp2 := cc.getPoint(min) - if cp2.freeCap == 0 || min == 0 { - return cp2.freeCap - } - cp1 := cc.getPoint(min - 1) - if priority(cp2.freeCap) > cp1.nextPri { - return cp2.freeCap - } - minc, maxc := cp1.freeCap, cp2.freeCap-1 - for minc < maxc { - midc := (minc + maxc + 1) / 2 - if midc == 0 || priority(midc) > cp1.nextPri { - minc = midc - } else { - maxc = midc - 1 - } - } - return maxc -} diff --git a/les/vflux/server/prioritypool_test.go b/les/vflux/server/prioritypool_test.go deleted file mode 100644 index 515231211..000000000 --- a/les/vflux/server/prioritypool_test.go +++ /dev/null @@ -1,233 +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 server - -import ( - "math/rand" - "reflect" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" - "github.com/ethereum/go-ethereum/p2p/nodestate" -) - -const ( - testCapacityStepDiv = 100 - testCapacityToleranceDiv = 10 - testMinCap = 100 -) - -type ppTestClient struct { - node *enode.Node - balance, cap uint64 -} - -func (c *ppTestClient) priority(cap uint64) int64 { - return int64(c.balance / cap) -} - -func (c *ppTestClient) estimatePriority(cap uint64, addBalance int64, future, bias time.Duration, update bool) int64 { - return int64(c.balance / cap) -} - -func TestPriorityPool(t *testing.T) { - clock := &mclock.Simulated{} - setup := newServerSetup() - setup.balanceField = setup.setup.NewField("ppTestClient", reflect.TypeOf(&ppTestClient{})) - ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup) - - ns.SubscribeField(setup.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) { - if n := ns.GetField(node, setup.balanceField); n != nil { - c := n.(*ppTestClient) - c.cap = newValue.(uint64) - } - }) - pp := newPriorityPool(ns, setup, clock, testMinCap, 0, testCapacityStepDiv, testCapacityStepDiv) - ns.Start() - pp.SetLimits(100, 1000000) - clients := make([]*ppTestClient, 100) - raise := func(c *ppTestClient) { - for { - var ok bool - ns.Operation(func() { - newCap := c.cap + c.cap/testCapacityStepDiv - ok = pp.requestCapacity(c.node, newCap, newCap, 0) == newCap - }) - if !ok { - return - } - } - } - var sumBalance uint64 - check := func(c *ppTestClient) { - expCap := 1000000 * c.balance / sumBalance - capTol := expCap / testCapacityToleranceDiv - if c.cap < expCap-capTol || c.cap > expCap+capTol { - t.Errorf("Wrong node capacity (expected %d, got %d)", expCap, c.cap) - } - } - - for i := range clients { - c := &ppTestClient{ - node: enode.SignNull(&enr.Record{}, enode.ID{byte(i)}), - balance: 100000000000, - cap: 1000, - } - sumBalance += c.balance - clients[i] = c - ns.SetField(c.node, setup.balanceField, c) - ns.SetState(c.node, setup.inactiveFlag, nodestate.Flags{}, 0) - raise(c) - check(c) - } - - for count := 0; count < 100; count++ { - c := clients[rand.Intn(len(clients))] - oldBalance := c.balance - c.balance = uint64(rand.Int63n(100000000000) + 100000000000) - sumBalance += c.balance - oldBalance - pp.ns.SetState(c.node, setup.updateFlag, nodestate.Flags{}, 0) - pp.ns.SetState(c.node, nodestate.Flags{}, setup.updateFlag, 0) - if c.balance > oldBalance { - raise(c) - } else { - for _, c := range clients { - raise(c) - } - } - // check whether capacities are proportional to balances - for _, c := range clients { - check(c) - } - if count%10 == 0 { - // test available capacity calculation with capacity curve - c = clients[rand.Intn(len(clients))] - curve := pp.getCapacityCurve().exclude(c.node.ID()) - - add := uint64(rand.Int63n(10000000000000)) - c.balance += add - sumBalance += add - expCap := curve.maxCapacity(func(cap uint64) int64 { - return int64(c.balance / cap) - }) - var ok bool - expFail := expCap + 10 - if expFail < testMinCap { - expFail = testMinCap - } - ns.Operation(func() { - ok = pp.requestCapacity(c.node, expFail, expFail, 0) == expFail - }) - if ok { - t.Errorf("Request for more than expected available capacity succeeded") - } - if expCap >= testMinCap { - ns.Operation(func() { - ok = pp.requestCapacity(c.node, expCap, expCap, 0) == expCap - }) - if !ok { - t.Errorf("Request for expected available capacity failed") - } - } - c.balance -= add - sumBalance -= add - pp.ns.SetState(c.node, setup.updateFlag, nodestate.Flags{}, 0) - pp.ns.SetState(c.node, nodestate.Flags{}, setup.updateFlag, 0) - for _, c := range clients { - raise(c) - } - } - } - - ns.Stop() -} - -func TestCapacityCurve(t *testing.T) { - clock := &mclock.Simulated{} - setup := newServerSetup() - setup.balanceField = setup.setup.NewField("ppTestClient", reflect.TypeOf(&ppTestClient{})) - ns := nodestate.NewNodeStateMachine(nil, nil, clock, setup.setup) - - pp := newPriorityPool(ns, setup, clock, 400000, 0, 2, 2) - ns.Start() - pp.SetLimits(10, 10000000) - clients := make([]*ppTestClient, 10) - - for i := range clients { - c := &ppTestClient{ - node: enode.SignNull(&enr.Record{}, enode.ID{byte(i)}), - balance: 100000000000 * uint64(i+1), - cap: 1000000, - } - clients[i] = c - ns.SetField(c.node, setup.balanceField, c) - ns.SetState(c.node, setup.inactiveFlag, nodestate.Flags{}, 0) - ns.Operation(func() { - pp.requestCapacity(c.node, c.cap, c.cap, 0) - }) - } - - curve := pp.getCapacityCurve() - check := func(balance, expCap uint64) { - cap := curve.maxCapacity(func(cap uint64) int64 { - return int64(balance / cap) - }) - var fail bool - if cap == 0 || expCap == 0 { - fail = cap != expCap - } else { - pri := balance / cap - expPri := balance / expCap - fail = pri != expPri && pri != expPri+1 - } - if fail { - t.Errorf("Incorrect capacity for %d balance (got %d, expected %d)", balance, cap, expCap) - } - } - - check(0, 0) - check(10000000000, 100000) - check(50000000000, 500000) - check(100000000000, 1000000) - check(200000000000, 1000000) - check(300000000000, 1500000) - check(450000000000, 1500000) - check(600000000000, 2000000) - check(800000000000, 2000000) - check(1000000000000, 2500000) - - pp.SetLimits(11, 10000000) - curve = pp.getCapacityCurve() - - check(0, 0) - check(10000000000, 100000) - check(50000000000, 500000) - check(150000000000, 750000) - check(200000000000, 1000000) - check(220000000000, 1100000) - check(275000000000, 1100000) - check(375000000000, 1500000) - check(450000000000, 1500000) - check(600000000000, 2000000) - check(800000000000, 2000000) - check(1000000000000, 2500000) - - ns.Stop() -} diff --git a/les/vflux/server/service.go b/les/vflux/server/service.go deleted file mode 100644 index 40515f072..000000000 --- a/les/vflux/server/service.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2021 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 server - -import ( - "net" - "strings" - "sync" - "time" - - "github.com/ethereum/go-ethereum/les/utils" - "github.com/ethereum/go-ethereum/les/vflux" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/rlp" -) - -type ( - // Server serves vflux requests - Server struct { - limiter *utils.Limiter - lock sync.Mutex - services map[string]*serviceEntry - delayPerRequest time.Duration - } - - // Service is a service registered at the Server and identified by a string id - Service interface { - Handle(id enode.ID, address string, name string, data []byte) []byte // never called concurrently - } - - serviceEntry struct { - id, desc string - backend Service - } -) - -// NewServer creates a new Server -func NewServer(delayPerRequest time.Duration) *Server { - return &Server{ - limiter: utils.NewLimiter(1000), - delayPerRequest: delayPerRequest, - services: make(map[string]*serviceEntry), - } -} - -// Register registers a Service -func (s *Server) Register(b Service, id, desc string) { - srv := &serviceEntry{backend: b, id: id, desc: desc} - if strings.Contains(srv.id, ":") { - // srv.id + ":" will be used as a service database prefix - log.Error("Service ID contains ':'", "id", srv.id) - return - } - s.lock.Lock() - s.services[srv.id] = srv - s.lock.Unlock() -} - -// Serve serves a vflux request batch -// Note: requests are served by the Handle functions of the registered services. Serve -// may be called concurrently but the Handle functions are called sequentially and -// therefore thread safety is guaranteed. -func (s *Server) Serve(id enode.ID, address string, requests vflux.Requests) vflux.Replies { - reqLen := uint(len(requests)) - if reqLen == 0 || reqLen > vflux.MaxRequestLength { - return nil - } - // Note: the value parameter will be supplied by the token sale module (total amount paid) - ch := <-s.limiter.Add(id, address, 0, reqLen) - if ch == nil { - return nil - } - // Note: the limiter ensures that the following section is not running concurrently, - // the lock only protects against contention caused by new service registration - s.lock.Lock() - results := make(vflux.Replies, len(requests)) - for i, req := range requests { - if service := s.services[req.Service]; service != nil { - results[i] = service.backend.Handle(id, address, req.Name, req.Params) - } - } - s.lock.Unlock() - time.Sleep(s.delayPerRequest * time.Duration(reqLen)) - close(ch) - return results -} - -// ServeEncoded serves an encoded vflux request batch and returns the encoded replies -func (s *Server) ServeEncoded(id enode.ID, addr *net.UDPAddr, req []byte) []byte { - var requests vflux.Requests - if err := rlp.DecodeBytes(req, &requests); err != nil { - return nil - } - results := s.Serve(id, addr.String(), requests) - if results == nil { - return nil - } - res, _ := rlp.EncodeToBytes(&results) - return res -} - -// Stop shuts down the server -func (s *Server) Stop() { - s.limiter.Stop() -} diff --git a/les/vflux/server/status.go b/les/vflux/server/status.go deleted file mode 100644 index 2d7e25b68..000000000 --- a/les/vflux/server/status.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2021 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 server - -import ( - "reflect" - - "github.com/ethereum/go-ethereum/p2p/nodestate" -) - -type peerWrapper struct{ clientPeer } // the NodeStateMachine type system needs this wrapper - -// serverSetup is a wrapper of the node state machine setup, which contains -// all the created flags and fields used in the vflux server side. -type serverSetup struct { - setup *nodestate.Setup - clientField nodestate.Field // Field contains the client peer handler - - // Flags and fields controlled by balance tracker. BalanceTracker - // is responsible for setting/deleting these flags or fields. - priorityFlag nodestate.Flags // Flag is set if the node has a positive balance - updateFlag nodestate.Flags // Flag is set whenever the node balance is changed(priority changed) - balanceField nodestate.Field // Field contains the client balance for priority calculation - - // Flags and fields controlled by priority queue. Priority queue - // is responsible for setting/deleting these flags or fields. - activeFlag nodestate.Flags // Flag is set if the node is active - inactiveFlag nodestate.Flags // Flag is set if the node is inactive - capacityField nodestate.Field // Field contains the capacity of the node - queueField nodestate.Field // Field contains the information in the priority queue -} - -// newServerSetup initializes the setup for state machine and returns the flags/fields group. -func newServerSetup() *serverSetup { - setup := &serverSetup{setup: &nodestate.Setup{}} - setup.clientField = setup.setup.NewField("client", reflect.TypeOf(peerWrapper{})) - setup.priorityFlag = setup.setup.NewFlag("priority") - setup.updateFlag = setup.setup.NewFlag("update") - setup.balanceField = setup.setup.NewField("balance", reflect.TypeOf(&nodeBalance{})) - setup.activeFlag = setup.setup.NewFlag("active") - setup.inactiveFlag = setup.setup.NewFlag("inactive") - setup.capacityField = setup.setup.NewField("capacity", reflect.TypeOf(uint64(0))) - setup.queueField = setup.setup.NewField("queue", reflect.TypeOf(&ppNodeInfo{})) - return setup -} diff --git a/light/lightchain.go b/light/lightchain.go deleted file mode 100644 index 617658b85..000000000 --- a/light/lightchain.go +++ /dev/null @@ -1,531 +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 light implements on-demand retrieval capable state and chain objects -// for the Ethereum Light Client. -package light - -import ( - "context" - "errors" - "math/big" - "sync" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/lru" - "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/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" -) - -var ( - bodyCacheLimit = 256 - blockCacheLimit = 256 -) - -// LightChain represents a canonical chain that by default only handles block -// headers, downloading block bodies and receipts on demand through an ODR -// interface. It only does header validation during chain insertion. -type LightChain struct { - hc *core.HeaderChain - indexerConfig *IndexerConfig - chainDb ethdb.Database - engine consensus.Engine - odr OdrBackend - chainFeed event.Feed - chainSideFeed event.Feed - chainHeadFeed event.Feed - scope event.SubscriptionScope - genesisBlock *types.Block - forker *core.ForkChoice - - bodyCache *lru.Cache[common.Hash, *types.Body] - bodyRLPCache *lru.Cache[common.Hash, rlp.RawValue] - blockCache *lru.Cache[common.Hash, *types.Block] - - chainmu sync.RWMutex // protects header inserts - quit chan struct{} - wg sync.WaitGroup - - // Atomic boolean switches: - stopped atomic.Bool // whether LightChain is stopped or running - procInterrupt atomic.Bool // interrupts chain insert -} - -// NewLightChain returns a fully initialised light chain using information -// available in the database. It initialises the default Ethereum header -// validator. -func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.Engine) (*LightChain, error) { - bc := &LightChain{ - chainDb: odr.Database(), - indexerConfig: odr.IndexerConfig(), - odr: odr, - quit: make(chan struct{}), - bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit), - bodyRLPCache: lru.NewCache[common.Hash, rlp.RawValue](bodyCacheLimit), - blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit), - engine: engine, - } - bc.forker = core.NewForkChoice(bc, nil) - var err error - bc.hc, err = core.NewHeaderChain(odr.Database(), config, bc.engine, bc.getProcInterrupt) - if err != nil { - return nil, err - } - bc.genesisBlock, _ = bc.GetBlockByNumber(NoOdr, 0) - if bc.genesisBlock == nil { - return nil, core.ErrNoGenesis - } - if err := bc.loadLastState(); err != nil { - return nil, err - } - // Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain - for hash := range core.BadHashes { - if header := bc.GetHeaderByHash(hash); header != nil { - log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash) - bc.SetHead(header.Number.Uint64() - 1) - log.Info("Chain rewind was successful, resuming normal operation") - } - } - return bc, nil -} - -func (lc *LightChain) getProcInterrupt() bool { - return lc.procInterrupt.Load() -} - -// Odr returns the ODR backend of the chain -func (lc *LightChain) Odr() OdrBackend { - return lc.odr -} - -// HeaderChain returns the underlying header chain. -func (lc *LightChain) HeaderChain() *core.HeaderChain { - return lc.hc -} - -// loadLastState loads the last known chain state from the database. This method -// assumes that the chain manager mutex is held. -func (lc *LightChain) loadLastState() error { - if head := rawdb.ReadHeadHeaderHash(lc.chainDb); head == (common.Hash{}) { - // Corrupt or empty database, init from scratch - lc.Reset() - } else { - header := lc.GetHeaderByHash(head) - if header == nil { - // Corrupt or empty database, init from scratch - lc.Reset() - } else { - lc.hc.SetCurrentHeader(header) - } - } - // Issue a status log and return - header := lc.hc.CurrentHeader() - headerTd := lc.GetTd(header.Hash(), header.Number.Uint64()) - log.Info("Loaded most recent local header", "number", header.Number, "hash", header.Hash(), "td", headerTd, "age", common.PrettyAge(time.Unix(int64(header.Time), 0))) - return nil -} - -// SetHead rewinds the local chain to a new head. Everything above the new -// head will be deleted and the new one set. -func (lc *LightChain) SetHead(head uint64) error { - lc.chainmu.Lock() - defer lc.chainmu.Unlock() - - lc.hc.SetHead(head, nil, nil) - return lc.loadLastState() -} - -// SetHeadWithTimestamp rewinds the local chain to a new head that has at max -// the given timestamp. Everything above the new head will be deleted and the -// new one set. -func (lc *LightChain) SetHeadWithTimestamp(timestamp uint64) error { - lc.chainmu.Lock() - defer lc.chainmu.Unlock() - - lc.hc.SetHeadWithTimestamp(timestamp, nil, nil) - return lc.loadLastState() -} - -// GasLimit returns the gas limit of the current HEAD block. -func (lc *LightChain) GasLimit() uint64 { - return lc.hc.CurrentHeader().GasLimit -} - -// Reset purges the entire blockchain, restoring it to its genesis state. -func (lc *LightChain) Reset() { - lc.ResetWithGenesisBlock(lc.genesisBlock) -} - -// ResetWithGenesisBlock purges the entire blockchain, restoring it to the -// specified genesis state. -func (lc *LightChain) ResetWithGenesisBlock(genesis *types.Block) { - // Dump the entire block chain and purge the caches - lc.SetHead(0) - - lc.chainmu.Lock() - defer lc.chainmu.Unlock() - - // Prepare the genesis block and reinitialise the chain - batch := lc.chainDb.NewBatch() - rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()) - rawdb.WriteBlock(batch, genesis) - rawdb.WriteHeadHeaderHash(batch, genesis.Hash()) - if err := batch.Write(); err != nil { - log.Crit("Failed to reset genesis block", "err", err) - } - lc.genesisBlock = genesis - lc.hc.SetGenesis(lc.genesisBlock.Header()) - lc.hc.SetCurrentHeader(lc.genesisBlock.Header()) -} - -// Accessors - -// Engine retrieves the light chain's consensus engine. -func (lc *LightChain) Engine() consensus.Engine { return lc.engine } - -// Genesis returns the genesis block -func (lc *LightChain) Genesis() *types.Block { - return lc.genesisBlock -} - -func (lc *LightChain) StateCache() state.Database { - panic("not implemented") -} - -// GetBody retrieves a block body (transactions and uncles) from the database -// or ODR service by hash, caching it if found. -func (lc *LightChain) GetBody(ctx context.Context, hash common.Hash) (*types.Body, error) { - // Short circuit if the body's already in the cache, retrieve otherwise - if cached, ok := lc.bodyCache.Get(hash); ok { - return cached, nil - } - number := lc.hc.GetBlockNumber(hash) - if number == nil { - return nil, errors.New("unknown block") - } - body, err := GetBody(ctx, lc.odr, hash, *number) - if err != nil { - return nil, err - } - // Cache the found body for next time and return - lc.bodyCache.Add(hash, body) - return body, nil -} - -// GetBodyRLP retrieves a block body in RLP encoding from the database or -// ODR service by hash, caching it if found. -func (lc *LightChain) GetBodyRLP(ctx context.Context, hash common.Hash) (rlp.RawValue, error) { - // Short circuit if the body's already in the cache, retrieve otherwise - if cached, ok := lc.bodyRLPCache.Get(hash); ok { - return cached, nil - } - number := lc.hc.GetBlockNumber(hash) - if number == nil { - return nil, errors.New("unknown block") - } - body, err := GetBodyRLP(ctx, lc.odr, hash, *number) - if err != nil { - return nil, err - } - // Cache the found body for next time and return - lc.bodyRLPCache.Add(hash, body) - return body, nil -} - -// HasBlock checks if a block is fully present in the database or not, caching -// it if present. -func (lc *LightChain) HasBlock(hash common.Hash, number uint64) bool { - blk, _ := lc.GetBlock(NoOdr, hash, number) - return blk != nil -} - -// GetBlock retrieves a block from the database or ODR service by hash and number, -// caching it if found. -func (lc *LightChain) GetBlock(ctx context.Context, hash common.Hash, number uint64) (*types.Block, error) { - // Short circuit if the block's already in the cache, retrieve otherwise - if block, ok := lc.blockCache.Get(hash); ok { - return block, nil - } - block, err := GetBlock(ctx, lc.odr, hash, number) - if err != nil { - return nil, err - } - // Cache the found block for next time and return - lc.blockCache.Add(block.Hash(), block) - return block, nil -} - -// GetBlockByHash retrieves a block from the database or ODR service by hash, -// caching it if found. -func (lc *LightChain) GetBlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - number := lc.hc.GetBlockNumber(hash) - if number == nil { - return nil, errors.New("unknown block") - } - return lc.GetBlock(ctx, hash, *number) -} - -// GetBlockByNumber retrieves a block from the database or ODR service by -// number, caching it (associated with its hash) if found. -func (lc *LightChain) GetBlockByNumber(ctx context.Context, number uint64) (*types.Block, error) { - hash, err := GetCanonicalHash(ctx, lc.odr, number) - if hash == (common.Hash{}) || err != nil { - return nil, err - } - return lc.GetBlock(ctx, hash, number) -} - -// Stop stops the blockchain service. If any imports are currently in progress -// it will abort them using the procInterrupt. -func (lc *LightChain) Stop() { - if !lc.stopped.CompareAndSwap(false, true) { - return - } - close(lc.quit) - lc.StopInsert() - lc.wg.Wait() - log.Info("Blockchain stopped") -} - -// StopInsert interrupts all insertion methods, causing them to return -// errInsertionInterrupted as soon as possible. Insertion is permanently disabled after -// calling this method. -func (lc *LightChain) StopInsert() { - lc.procInterrupt.Store(true) -} - -// Rollback is designed to remove a chain of links from the database that aren't -// certain enough to be valid. -func (lc *LightChain) Rollback(chain []common.Hash) { - lc.chainmu.Lock() - defer lc.chainmu.Unlock() - - batch := lc.chainDb.NewBatch() - for i := len(chain) - 1; i >= 0; i-- { - hash := chain[i] - - // Degrade the chain markers if they are explicitly reverted. - // In theory we should update all in-memory markers in the - // last step, however the direction of rollback is from high - // to low, so it's safe the update in-memory markers directly. - if head := lc.hc.CurrentHeader(); head.Hash() == hash { - rawdb.WriteHeadHeaderHash(batch, head.ParentHash) - lc.hc.SetCurrentHeader(lc.GetHeader(head.ParentHash, head.Number.Uint64()-1)) - } - } - if err := batch.Write(); err != nil { - log.Crit("Failed to rollback light chain", "error", err) - } -} - -func (lc *LightChain) InsertHeader(header *types.Header) error { - // Verify the header first before obtaining the lock - headers := []*types.Header{header} - if _, err := lc.hc.ValidateHeaderChain(headers); err != nil { - return err - } - // Make sure only one thread manipulates the chain at once - lc.chainmu.Lock() - defer lc.chainmu.Unlock() - - lc.wg.Add(1) - defer lc.wg.Done() - - _, err := lc.hc.WriteHeaders(headers) - log.Info("Inserted header", "number", header.Number, "hash", header.Hash()) - return err -} - -func (lc *LightChain) SetCanonical(header *types.Header) error { - lc.chainmu.Lock() - defer lc.chainmu.Unlock() - - lc.wg.Add(1) - defer lc.wg.Done() - - if err := lc.hc.Reorg([]*types.Header{header}); err != nil { - return err - } - // Emit events - block := types.NewBlockWithHeader(header) - lc.chainFeed.Send(core.ChainEvent{Block: block, Hash: block.Hash()}) - lc.chainHeadFeed.Send(core.ChainHeadEvent{Block: block}) - log.Info("Set the chain head", "number", block.Number(), "hash", block.Hash()) - return nil -} - -// InsertHeaderChain attempts to insert the given header chain in to the local -// chain, possibly creating a reorg. If an error is returned, it will return the -// index number of the failing header as well an error describing what went wrong. - -// In the case of a light chain, InsertHeaderChain also creates and posts light -// chain events when necessary. -func (lc *LightChain) InsertHeaderChain(chain []*types.Header) (int, error) { - if len(chain) == 0 { - return 0, nil - } - start := time.Now() - if i, err := lc.hc.ValidateHeaderChain(chain); err != nil { - return i, err - } - - // Make sure only one thread manipulates the chain at once - lc.chainmu.Lock() - defer lc.chainmu.Unlock() - - lc.wg.Add(1) - defer lc.wg.Done() - - status, err := lc.hc.InsertHeaderChain(chain, start, lc.forker) - if err != nil || len(chain) == 0 { - return 0, err - } - - // Create chain event for the new head block of this insertion. - var ( - lastHeader = chain[len(chain)-1] - block = types.NewBlockWithHeader(lastHeader) - ) - switch status { - case core.CanonStatTy: - lc.chainFeed.Send(core.ChainEvent{Block: block, Hash: block.Hash()}) - lc.chainHeadFeed.Send(core.ChainHeadEvent{Block: block}) - case core.SideStatTy: - lc.chainSideFeed.Send(core.ChainSideEvent{Block: block}) - } - return 0, err -} - -// CurrentHeader retrieves the current head header of the canonical chain. The -// header is retrieved from the HeaderChain's internal cache. -func (lc *LightChain) CurrentHeader() *types.Header { - return lc.hc.CurrentHeader() -} - -// GetTd retrieves a block's total difficulty in the canonical chain from the -// database by hash and number, caching it if found. -func (lc *LightChain) GetTd(hash common.Hash, number uint64) *big.Int { - return lc.hc.GetTd(hash, number) -} - -// GetTdOdr retrieves the total difficult from the database or -// network by hash and number, caching it (associated with its hash) if found. -func (lc *LightChain) GetTdOdr(ctx context.Context, hash common.Hash, number uint64) *big.Int { - td := lc.GetTd(hash, number) - if td != nil { - return td - } - td, _ = GetTd(ctx, lc.odr, hash, number) - return td -} - -// GetHeader retrieves a block header from the database by hash and number, -// caching it if found. -func (lc *LightChain) GetHeader(hash common.Hash, number uint64) *types.Header { - return lc.hc.GetHeader(hash, number) -} - -// GetHeaderByHash retrieves a block header from the database by hash, caching it if -// found. -func (lc *LightChain) GetHeaderByHash(hash common.Hash) *types.Header { - return lc.hc.GetHeaderByHash(hash) -} - -// HasHeader checks if a block header is present in the database or not, caching -// it if present. -func (lc *LightChain) HasHeader(hash common.Hash, number uint64) bool { - return lc.hc.HasHeader(hash, number) -} - -// GetCanonicalHash returns the canonical hash for a given block number -func (bc *LightChain) GetCanonicalHash(number uint64) common.Hash { - return bc.hc.GetCanonicalHash(number) -} - -// GetAncestor retrieves the Nth ancestor of a given block. It assumes that either the given block or -// a close ancestor of it is canonical. maxNonCanonical points to a downwards counter limiting the -// number of blocks to be individually checked before we reach the canonical chain. -// -// Note: ancestor == 0 returns the same block, 1 returns its parent and so on. -func (lc *LightChain) GetAncestor(hash common.Hash, number, ancestor uint64, maxNonCanonical *uint64) (common.Hash, uint64) { - return lc.hc.GetAncestor(hash, number, ancestor, maxNonCanonical) -} - -// GetHeaderByNumber retrieves a block header from the database by number, -// caching it (associated with its hash) if found. -func (lc *LightChain) GetHeaderByNumber(number uint64) *types.Header { - return lc.hc.GetHeaderByNumber(number) -} - -// GetHeaderByNumberOdr retrieves a block header from the database or network -// by number, caching it (associated with its hash) if found. -func (lc *LightChain) GetHeaderByNumberOdr(ctx context.Context, number uint64) (*types.Header, error) { - if header := lc.hc.GetHeaderByNumber(number); header != nil { - return header, nil - } - return GetHeaderByNumber(ctx, lc.odr, number) -} - -// Config retrieves the header chain's chain configuration. -func (lc *LightChain) Config() *params.ChainConfig { return lc.hc.Config() } - -// LockChain locks the chain mutex for reading so that multiple canonical hashes can be -// retrieved while it is guaranteed that they belong to the same version of the chain -func (lc *LightChain) LockChain() { - lc.chainmu.RLock() -} - -// UnlockChain unlocks the chain mutex -func (lc *LightChain) UnlockChain() { - lc.chainmu.RUnlock() -} - -// SubscribeChainEvent registers a subscription of ChainEvent. -func (lc *LightChain) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { - return lc.scope.Track(lc.chainFeed.Subscribe(ch)) -} - -// SubscribeChainHeadEvent registers a subscription of ChainHeadEvent. -func (lc *LightChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { - return lc.scope.Track(lc.chainHeadFeed.Subscribe(ch)) -} - -// SubscribeChainSideEvent registers a subscription of ChainSideEvent. -func (lc *LightChain) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { - return lc.scope.Track(lc.chainSideFeed.Subscribe(ch)) -} - -// SubscribeLogsEvent implements the interface of filters.Backend -// LightChain does not send logs events, so return an empty subscription. -func (lc *LightChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { - return lc.scope.Track(new(event.Feed).Subscribe(ch)) -} - -// SubscribeRemovedLogsEvent implements the interface of filters.Backend -// LightChain does not send core.RemovedLogsEvent, so return an empty subscription. -func (lc *LightChain) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { - return lc.scope.Track(new(event.Feed).Subscribe(ch)) -} diff --git a/light/lightchain_test.go b/light/lightchain_test.go deleted file mode 100644 index e3d756f80..000000000 --- a/light/lightchain_test.go +++ /dev/null @@ -1,357 +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 light - -import ( - "context" - "errors" - "math/big" - "testing" - - "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/ethdb" - "github.com/ethereum/go-ethereum/params" -) - -// So we can deterministically seed different blockchains -var ( - canonicalSeed = 1 - forkSeed = 2 -) - -// makeHeaderChain creates a deterministic chain of headers rooted at parent. -func makeHeaderChain(parent *types.Header, n int, db ethdb.Database, seed int) []*types.Header { - blocks, _ := core.GenerateChain(params.TestChainConfig, types.NewBlockWithHeader(parent), ethash.NewFaker(), db, n, func(i int, b *core.BlockGen) { - b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)}) - }) - headers := make([]*types.Header, len(blocks)) - for i, block := range blocks { - headers[i] = block.Header() - } - return headers -} - -// newCanonical creates a chain database, and injects a deterministic canonical -// chain. Depending on the full flag, if creates either a full block chain or a -// header only chain. -func newCanonical(n int) (ethdb.Database, *LightChain, error) { - db := rawdb.NewMemoryDatabase() - gspec := core.Genesis{Config: params.TestChainConfig} - genesis := gspec.MustCommit(db) - blockchain, _ := NewLightChain(&dummyOdr{db: db, indexerConfig: TestClientIndexerConfig}, gspec.Config, ethash.NewFaker()) - - // Create and inject the requested chain - if n == 0 { - return db, blockchain, nil - } - // Header-only chain requested - headers := makeHeaderChain(genesis.Header(), n, db, canonicalSeed) - _, err := blockchain.InsertHeaderChain(headers) - return db, blockchain, err -} - -// newTestLightChain creates a LightChain that doesn't validate anything. -func newTestLightChain() *LightChain { - db := rawdb.NewMemoryDatabase() - gspec := &core.Genesis{ - Difficulty: big.NewInt(1), - Config: params.TestChainConfig, - } - gspec.MustCommit(db) - lc, err := NewLightChain(&dummyOdr{db: db}, gspec.Config, ethash.NewFullFaker()) - if err != nil { - panic(err) - } - return lc -} - -// Test fork of length N starting from block i -func testFork(t *testing.T, LightChain *LightChain, i, n int, comparator func(td1, td2 *big.Int)) { - // Copy old chain up to #i into a new db - db, LightChain2, err := newCanonical(i) - if err != nil { - t.Fatal("could not make new canonical in testFork", err) - } - // Assert the chains have the same header/block at #i - var hash1, hash2 common.Hash - hash1 = LightChain.GetHeaderByNumber(uint64(i)).Hash() - hash2 = LightChain2.GetHeaderByNumber(uint64(i)).Hash() - if hash1 != hash2 { - t.Errorf("chain content mismatch at %d: have hash %v, want hash %v", i, hash2, hash1) - } - // Extend the newly created chain - headerChainB := makeHeaderChain(LightChain2.CurrentHeader(), n, db, forkSeed) - if _, err := LightChain2.InsertHeaderChain(headerChainB); err != nil { - t.Fatalf("failed to insert forking chain: %v", err) - } - // Sanity check that the forked chain can be imported into the original - var tdPre, tdPost *big.Int - cur := LightChain.CurrentHeader() - tdPre = LightChain.GetTd(cur.Hash(), cur.Number.Uint64()) - if err := testHeaderChainImport(headerChainB, LightChain); err != nil { - t.Fatalf("failed to import forked header chain: %v", err) - } - last := headerChainB[len(headerChainB)-1] - tdPost = LightChain.GetTd(last.Hash(), last.Number.Uint64()) - // Compare the total difficulties of the chains - comparator(tdPre, tdPost) -} - -// testHeaderChainImport tries to process a chain of header, writing them into -// the database if successful. -func testHeaderChainImport(chain []*types.Header, lightchain *LightChain) error { - for _, header := range chain { - // Try and validate the header - if err := lightchain.engine.VerifyHeader(lightchain.hc, header); err != nil { - return err - } - // Manually insert the header into the database, but don't reorganize (allows subsequent testing) - lightchain.chainmu.Lock() - rawdb.WriteTd(lightchain.chainDb, header.Hash(), header.Number.Uint64(), - new(big.Int).Add(header.Difficulty, lightchain.GetTd(header.ParentHash, header.Number.Uint64()-1))) - rawdb.WriteHeader(lightchain.chainDb, header) - lightchain.chainmu.Unlock() - } - return nil -} - -// Tests that given a starting canonical chain of a given size, it can be extended -// with various length chains. -func TestExtendCanonicalHeaders(t *testing.T) { - length := 5 - - // Make first chain starting from genesis - _, processor, err := newCanonical(length) - if err != nil { - t.Fatalf("failed to make new canonical chain: %v", err) - } - // Define the difficulty comparator - better := func(td1, td2 *big.Int) { - if td2.Cmp(td1) <= 0 { - t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1) - } - } - // Start fork from current height - testFork(t, processor, length, 1, better) - testFork(t, processor, length, 2, better) - testFork(t, processor, length, 5, better) - testFork(t, processor, length, 10, better) -} - -// Tests that given a starting canonical chain of a given size, creating shorter -// forks do not take canonical ownership. -func TestShorterForkHeaders(t *testing.T) { - length := 10 - - // Make first chain starting from genesis - _, processor, err := newCanonical(length) - if err != nil { - t.Fatalf("failed to make new canonical chain: %v", err) - } - // Define the difficulty comparator - worse := func(td1, td2 *big.Int) { - if td2.Cmp(td1) >= 0 { - t.Errorf("total difficulty mismatch: have %v, expected less than %v", td2, td1) - } - } - // Sum of numbers must be less than `length` for this to be a shorter fork - testFork(t, processor, 0, 3, worse) - testFork(t, processor, 0, 7, worse) - testFork(t, processor, 1, 1, worse) - testFork(t, processor, 1, 7, worse) - testFork(t, processor, 5, 3, worse) - testFork(t, processor, 5, 4, worse) -} - -// Tests that given a starting canonical chain of a given size, creating longer -// forks do take canonical ownership. -func TestLongerForkHeaders(t *testing.T) { - length := 10 - - // Make first chain starting from genesis - _, processor, err := newCanonical(length) - if err != nil { - t.Fatalf("failed to make new canonical chain: %v", err) - } - // Define the difficulty comparator - better := func(td1, td2 *big.Int) { - if td2.Cmp(td1) <= 0 { - t.Errorf("total difficulty mismatch: have %v, expected more than %v", td2, td1) - } - } - // Sum of numbers must be greater than `length` for this to be a longer fork - testFork(t, processor, 0, 11, better) - testFork(t, processor, 0, 15, better) - testFork(t, processor, 1, 10, better) - testFork(t, processor, 1, 12, better) - testFork(t, processor, 5, 6, better) - testFork(t, processor, 5, 8, better) -} - -// Tests that given a starting canonical chain of a given size, creating equal -// forks do take canonical ownership. -func TestEqualForkHeaders(t *testing.T) { - length := 10 - - // Make first chain starting from genesis - _, processor, err := newCanonical(length) - if err != nil { - t.Fatalf("failed to make new canonical chain: %v", err) - } - // Define the difficulty comparator - equal := func(td1, td2 *big.Int) { - if td2.Cmp(td1) != 0 { - t.Errorf("total difficulty mismatch: have %v, want %v", td2, td1) - } - } - // Sum of numbers must be equal to `length` for this to be an equal fork - testFork(t, processor, 0, 10, equal) - testFork(t, processor, 1, 9, equal) - testFork(t, processor, 2, 8, equal) - testFork(t, processor, 5, 5, equal) - testFork(t, processor, 6, 4, equal) - testFork(t, processor, 9, 1, equal) -} - -// Tests that chains missing links do not get accepted by the processor. -func TestBrokenHeaderChain(t *testing.T) { - // Make chain starting from genesis - db, LightChain, err := newCanonical(10) - if err != nil { - t.Fatalf("failed to make new canonical chain: %v", err) - } - // Create a forked chain, and try to insert with a missing link - chain := makeHeaderChain(LightChain.CurrentHeader(), 5, db, forkSeed)[1:] - if err := testHeaderChainImport(chain, LightChain); err == nil { - t.Errorf("broken header chain not reported") - } -} - -func makeHeaderChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Header { - var chain []*types.Header - for i, difficulty := range d { - header := &types.Header{ - Coinbase: common.Address{seed}, - Number: big.NewInt(int64(i + 1)), - Difficulty: big.NewInt(int64(difficulty)), - UncleHash: types.EmptyUncleHash, - TxHash: types.EmptyTxsHash, - ReceiptHash: types.EmptyReceiptsHash, - } - if i == 0 { - header.ParentHash = genesis.Hash() - } else { - header.ParentHash = chain[i-1].Hash() - } - chain = append(chain, types.CopyHeader(header)) - } - return chain -} - -type dummyOdr struct { - OdrBackend - db ethdb.Database - indexerConfig *IndexerConfig -} - -func (odr *dummyOdr) Database() ethdb.Database { - return odr.db -} - -func (odr *dummyOdr) Retrieve(ctx context.Context, req OdrRequest) error { - return nil -} - -func (odr *dummyOdr) IndexerConfig() *IndexerConfig { - return odr.indexerConfig -} - -// Tests that reorganizing a long difficult chain after a short easy one -// overwrites the canonical numbers and links in the database. -func TestReorgLongHeaders(t *testing.T) { - testReorg(t, []int{1, 2, 4}, []int{1, 2, 3, 4}, 10) -} - -// Tests that reorganizing a short difficult chain after a long easy one -// overwrites the canonical numbers and links in the database. -func TestReorgShortHeaders(t *testing.T) { - testReorg(t, []int{1, 2, 3, 4}, []int{1, 10}, 11) -} - -func testReorg(t *testing.T, first, second []int, td int64) { - bc := newTestLightChain() - - // Insert an easy and a difficult chain afterwards - bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, first, 11)) - bc.InsertHeaderChain(makeHeaderChainWithDiff(bc.genesisBlock, second, 22)) - // Check that the chain is valid number and link wise - prev := bc.CurrentHeader() - for header := bc.GetHeaderByNumber(bc.CurrentHeader().Number.Uint64() - 1); header.Number.Uint64() != 0; prev, header = header, bc.GetHeaderByNumber(header.Number.Uint64()-1) { - if prev.ParentHash != header.Hash() { - t.Errorf("parent header hash mismatch: have %x, want %x", prev.ParentHash, header.Hash()) - } - } - // Make sure the chain total difficulty is the correct one - want := new(big.Int).Add(bc.genesisBlock.Difficulty(), big.NewInt(td)) - if have := bc.GetTd(bc.CurrentHeader().Hash(), bc.CurrentHeader().Number.Uint64()); have.Cmp(want) != 0 { - t.Errorf("total difficulty mismatch: have %v, want %v", have, want) - } -} - -// Tests that the insertion functions detect banned hashes. -func TestBadHeaderHashes(t *testing.T) { - bc := newTestLightChain() - - // Create a chain, ban a hash and try to import - var err error - headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 4}, 10) - core.BadHashes[headers[2].Hash()] = true - if _, err = bc.InsertHeaderChain(headers); !errors.Is(err, core.ErrBannedHash) { - t.Errorf("error mismatch: have: %v, want %v", err, core.ErrBannedHash) - } -} - -// Tests that bad hashes are detected on boot, and the chan rolled back to a -// good state prior to the bad hash. -func TestReorgBadHeaderHashes(t *testing.T) { - bc := newTestLightChain() - - // Create a chain, import and ban afterwards - headers := makeHeaderChainWithDiff(bc.genesisBlock, []int{1, 2, 3, 4}, 10) - - if _, err := bc.InsertHeaderChain(headers); err != nil { - t.Fatalf("failed to import headers: %v", err) - } - if bc.CurrentHeader().Hash() != headers[3].Hash() { - t.Errorf("last header hash mismatch: have: %x, want %x", bc.CurrentHeader().Hash(), headers[3].Hash()) - } - core.BadHashes[headers[3].Hash()] = true - defer func() { delete(core.BadHashes, headers[3].Hash()) }() - - // Create a new LightChain and check that it rolled back the state. - ncm, err := NewLightChain(&dummyOdr{db: bc.chainDb}, params.TestChainConfig, ethash.NewFaker()) - if err != nil { - t.Fatalf("failed to create new chain manager: %v", err) - } - if ncm.CurrentHeader().Hash() != headers[2].Hash() { - t.Errorf("last header hash mismatch: have: %x, want %x", ncm.CurrentHeader().Hash(), headers[2].Hash()) - } -} diff --git a/light/odr.go b/light/odr.go deleted file mode 100644 index 259702743..000000000 --- a/light/odr.go +++ /dev/null @@ -1,195 +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 light - -import ( - "context" - "errors" - "math/big" - - "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/txpool" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" -) - -// NoOdr is the default context passed to an ODR capable function when the ODR -// service is not required. -var NoOdr = context.Background() - -// ErrNoPeers is returned if no peers capable of serving a queued request are available -var ErrNoPeers = errors.New("no suitable peers available") - -// OdrBackend is an interface to a backend service that handles ODR retrievals type -type OdrBackend interface { - Database() ethdb.Database - ChtIndexer() *core.ChainIndexer - BloomTrieIndexer() *core.ChainIndexer - BloomIndexer() *core.ChainIndexer - Retrieve(ctx context.Context, req OdrRequest) error - RetrieveTxStatus(ctx context.Context, req *TxStatusRequest) error - IndexerConfig() *IndexerConfig -} - -// OdrRequest is an interface for retrieval requests -type OdrRequest interface { - StoreResult(db ethdb.Database) -} - -// TrieID identifies a state or account storage trie -type TrieID struct { - 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, - 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, address common.Address, root common.Hash) *TrieID { - return &TrieID{ - BlockHash: state.BlockHash, - BlockNumber: state.BlockNumber, - StateRoot: state.StateRoot, - AccountAddress: address[:], - Root: root, - } -} - -// TrieRequest is the ODR request type for state/storage trie entries -type TrieRequest struct { - Id *TrieID - Key []byte - Proof *NodeSet -} - -// StoreResult stores the retrieved data in local database -func (req *TrieRequest) StoreResult(db ethdb.Database) { - req.Proof.Store(db) -} - -// CodeRequest is the ODR request type for retrieving contract code -type CodeRequest struct { - Id *TrieID // references storage trie of the account - Hash common.Hash - Data []byte -} - -// StoreResult stores the retrieved data in local database -func (req *CodeRequest) StoreResult(db ethdb.Database) { - rawdb.WriteCode(db, req.Hash, req.Data) -} - -// BlockRequest is the ODR request type for retrieving block bodies -type BlockRequest struct { - Hash common.Hash - Number uint64 - Header *types.Header - Rlp []byte -} - -// StoreResult stores the retrieved data in local database -func (req *BlockRequest) StoreResult(db ethdb.Database) { - rawdb.WriteBodyRLP(db, req.Hash, req.Number, req.Rlp) -} - -// ReceiptsRequest is the ODR request type for retrieving receipts. -type ReceiptsRequest struct { - Hash common.Hash - Number uint64 - Header *types.Header - Receipts types.Receipts -} - -// StoreResult stores the retrieved data in local database -func (req *ReceiptsRequest) StoreResult(db ethdb.Database) { - rawdb.WriteReceipts(db, req.Hash, req.Number, req.Receipts) -} - -// ChtRequest is the ODR request type for retrieving header by Canonical Hash Trie -type ChtRequest struct { - Config *IndexerConfig - ChtNum, BlockNum uint64 - ChtRoot common.Hash - Header *types.Header - Td *big.Int - Proof *NodeSet -} - -// StoreResult stores the retrieved data in local database -func (req *ChtRequest) StoreResult(db ethdb.Database) { - hash, num := req.Header.Hash(), req.Header.Number.Uint64() - rawdb.WriteHeader(db, req.Header) - rawdb.WriteTd(db, hash, num, req.Td) - rawdb.WriteCanonicalHash(db, hash, num) -} - -// BloomRequest is the ODR request type for retrieving bloom filters from a CHT structure -type BloomRequest struct { - OdrRequest - Config *IndexerConfig - BloomTrieNum uint64 - BitIdx uint - SectionIndexList []uint64 - BloomTrieRoot common.Hash - BloomBits [][]byte - Proofs *NodeSet -} - -// StoreResult stores the retrieved data in local database -func (req *BloomRequest) StoreResult(db ethdb.Database) { - for i, sectionIdx := range req.SectionIndexList { - sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*req.Config.BloomTrieSize-1) - // if we don't have the canonical hash stored for this section head number, we'll still store it under - // a key with a zero sectionHead. GetBloomBits will look there too if we still don't have the canonical - // hash. In the unlikely case we've retrieved the section head hash since then, we'll just retrieve the - // bit vector again from the network. - rawdb.WriteBloomBits(db, req.BitIdx, sectionIdx, sectionHead, req.BloomBits[i]) - } -} - -// TxStatus describes the status of a transaction -type TxStatus struct { - Status txpool.TxStatus - Lookup *rawdb.LegacyTxLookupEntry `rlp:"nil"` - Error string -} - -// TxStatusRequest is the ODR request type for retrieving transaction status -type TxStatusRequest struct { - Hashes []common.Hash - Status []TxStatus -} - -// StoreResult stores the retrieved data in local database -func (req *TxStatusRequest) StoreResult(db ethdb.Database) {} diff --git a/light/odr_test.go b/light/odr_test.go deleted file mode 100644 index 79f901bbd..000000000 --- a/light/odr_test.go +++ /dev/null @@ -1,337 +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 light - -import ( - "bytes" - "context" - "errors" - "math/big" - "testing" - "time" - - "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/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/crypto" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" -) - -var ( - testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) - testBankFunds = big.NewInt(1_000_000_000_000_000_000) - - acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") - acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") - acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey) - acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey) - - testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056") - testContractAddr common.Address -) - -type testOdr struct { - OdrBackend - indexerConfig *IndexerConfig - sdb, ldb ethdb.Database - serverState state.Database - disable bool -} - -func (odr *testOdr) Database() ethdb.Database { - return odr.ldb -} - -var ErrOdrDisabled = errors.New("ODR disabled") - -func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error { - if odr.disable { - return ErrOdrDisabled - } - switch req := req.(type) { - case *BlockRequest: - number := rawdb.ReadHeaderNumber(odr.sdb, req.Hash) - if number != nil { - req.Rlp = rawdb.ReadBodyRLP(odr.sdb, req.Hash, *number) - } - case *ReceiptsRequest: - number := rawdb.ReadHeaderNumber(odr.sdb, req.Hash) - if number != nil { - req.Receipts = rawdb.ReadRawReceipts(odr.sdb, req.Hash, *number) - } - case *TrieRequest: - var ( - err error - t state.Trie - ) - 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, nodes) - req.Proof = nodes - case *CodeRequest: - req.Data = rawdb.ReadCode(odr.sdb, req.Hash) - } - req.StoreResult(odr.ldb) - return nil -} - -func (odr *testOdr) IndexerConfig() *IndexerConfig { - return odr.indexerConfig -} - -type odrTestFn func(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) - -func TestOdrGetBlockLes2(t *testing.T) { testChainOdr(t, 1, odrGetBlock) } - -func odrGetBlock(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { - var block *types.Block - if bc != nil { - block = bc.GetBlockByHash(bhash) - } else { - block, _ = lc.GetBlockByHash(ctx, bhash) - } - if block == nil { - return nil, nil - } - rlp, _ := rlp.EncodeToBytes(block) - return rlp, nil -} - -func TestOdrGetReceiptsLes2(t *testing.T) { testChainOdr(t, 1, odrGetReceipts) } - -func odrGetReceipts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { - var receipts types.Receipts - if bc != nil { - if number := rawdb.ReadHeaderNumber(db, bhash); number != nil { - if header := rawdb.ReadHeader(db, bhash, *number); header != nil { - receipts = rawdb.ReadReceipts(db, bhash, *number, header.Time, bc.Config()) - } - } - } else { - number := rawdb.ReadHeaderNumber(db, bhash) - if number != nil { - receipts, _ = GetBlockReceipts(ctx, lc.Odr(), bhash, *number) - } - } - if receipts == nil { - return nil, nil - } - rlp, _ := rlp.EncodeToBytes(receipts) - return rlp, nil -} - -func TestOdrAccountsLes2(t *testing.T) { testChainOdr(t, 1, odrAccounts) } - -func odrAccounts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { - dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678") - acc := []common.Address{testBankAddress, acc1Addr, acc2Addr, dummyAddr} - - var st *state.StateDB - if bc == nil { - header := lc.GetHeaderByHash(bhash) - st = NewState(ctx, header, lc.Odr()) - } else { - header := bc.GetHeaderByHash(bhash) - st, _ = state.New(header.Root, bc.StateCache(), nil) - } - - var res []byte - for _, addr := range acc { - bal := st.GetBalance(addr) - rlp, _ := rlp.EncodeToBytes(bal) - res = append(res, rlp...) - } - return res, st.Error() -} - -func TestOdrContractCallLes2(t *testing.T) { testChainOdr(t, 1, odrContractCall) } - -func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { - data := common.Hex2Bytes("60CD26850000000000000000000000000000000000000000000000000000000000000000") - config := params.TestChainConfig - - var res []byte - for i := 0; i < 3; i++ { - data[35] = byte(i) - - var ( - st *state.StateDB - header *types.Header - chain core.ChainContext - ) - if bc == nil { - chain = lc - header = lc.GetHeaderByHash(bhash) - st = NewState(ctx, header, lc.Odr()) - } else { - chain = bc - header = bc.GetHeaderByHash(bhash) - st, _ = state.New(header.Root, bc.StateCache(), nil) - } - - // Perform read-only call. - st.SetBalance(testBankAddress, math.MaxBig256) - msg := &core.Message{ - From: testBankAddress, - To: &testContractAddr, - Value: new(big.Int), - GasLimit: 1000000, - GasPrice: big.NewInt(params.InitialBaseFee), - GasFeeCap: big.NewInt(params.InitialBaseFee), - GasTipCap: new(big.Int), - Data: data, - SkipAccountChecks: true, - } - txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(header, chain, nil) - vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true}) - gp := new(core.GasPool).AddGas(math.MaxUint64) - result, _ := core.ApplyMessage(vmenv, msg, gp) - res = append(res, result.Return()...) - if st.Error() != nil { - return res, st.Error() - } - } - return res, nil -} - -func testChainGen(i int, block *core.BlockGen) { - signer := types.HomesteadSigner{} - switch i { - case 0: - // In block 1, the test bank sends account #1 some ether. - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(10_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, testBankKey) - 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. - // acc1Addr creates a test contract. - tx1, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), acc1Addr, big.NewInt(1_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, testBankKey) - nonce := block.TxNonce(acc1Addr) - tx2, _ := types.SignTx(types.NewTransaction(nonce, acc2Addr, big.NewInt(1_000_000_000_000_000), params.TxGas, block.BaseFee(), nil), signer, acc1Key) - nonce++ - tx3, _ := types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 1000000, block.BaseFee(), testContractCode), signer, acc1Key) - testContractAddr = crypto.CreateAddress(acc1Addr, nonce) - block.AddTx(tx1) - block.AddTx(tx2) - block.AddTx(tx3) - case 2: - // Block 3 is empty but was mined by account #2. - block.SetCoinbase(acc2Addr) - block.SetExtra([]byte("yeehaw")) - data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001") - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, block.BaseFee(), data), signer, testBankKey) - block.AddTx(tx) - 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) - data := common.Hex2Bytes("C16431B900000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002") - tx, _ := types.SignTx(types.NewTransaction(block.TxNonce(testBankAddress), testContractAddr, big.NewInt(0), 100000, block.BaseFee(), data), signer, testBankKey) - block.AddTx(tx) - } -} - -func testChainOdr(t *testing.T, protocol int, fn odrTestFn) { - var ( - sdb = rawdb.NewMemoryDatabase() - ldb = rawdb.NewMemoryDatabase() - gspec = &core.Genesis{ - Config: params.TestChainConfig, - Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, - BaseFee: big.NewInt(params.InitialBaseFee), - } - ) - // Assemble the test environment - blockchain, _ := core.NewBlockChain(sdb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil) - _, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 4, testChainGen) - if _, err := blockchain.InsertChain(gchain); err != nil { - t.Fatal(err) - } - - gspec.MustCommit(ldb) - odr := &testOdr{sdb: sdb, ldb: ldb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig} - lightchain, err := NewLightChain(odr, gspec.Config, ethash.NewFullFaker()) - if err != nil { - t.Fatal(err) - } - headers := make([]*types.Header, len(gchain)) - for i, block := range gchain { - headers[i] = block.Header() - } - if _, err := lightchain.InsertHeaderChain(headers); err != nil { - t.Fatal(err) - } - - test := func(expFail int) { - for i := uint64(0); i <= blockchain.CurrentHeader().Number.Uint64(); i++ { - bhash := rawdb.ReadCanonicalHash(sdb, i) - b1, err := fn(NoOdr, sdb, blockchain, nil, bhash) - if err != nil { - t.Fatalf("error in full-node test for block %d: %v", i, err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) - defer cancel() - - exp := i < uint64(expFail) - b2, err := fn(ctx, ldb, nil, lightchain, bhash) - if err != nil && exp { - t.Errorf("error in ODR test for block %d: %v", i, err) - } - - eq := bytes.Equal(b1, b2) - if exp && !eq { - t.Errorf("ODR test output for block %d doesn't match full node", i) - } - } - } - - // expect retrievals to fail (except genesis block) without a les peer - t.Log("checking without ODR") - odr.disable = true - test(1) - - // expect all retrievals to pass with ODR enabled - t.Log("checking with ODR") - odr.disable = false - test(len(gchain)) - - // still expect all retrievals to pass, now data should be cached locally - t.Log("checking without ODR, should be cached") - odr.disable = true - test(len(gchain)) -} diff --git a/light/odr_util.go b/light/odr_util.go deleted file mode 100644 index 02379ce5f..000000000 --- a/light/odr_util.go +++ /dev/null @@ -1,276 +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 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" - "github.com/ethereum/go-ethereum/rlp" -) - -// errNonCanonicalHash is returned if the requested chain data doesn't belong -// to the canonical chain. ODR can only retrieve the canonical chain data covered -// by the CHT or Bloom trie for verification. -var errNonCanonicalHash = errors.New("hash is not currently canonical") - -// GetHeaderByNumber retrieves the canonical block header corresponding to the -// given number. The returned header is proven by local CHT. -func GetHeaderByNumber(ctx context.Context, odr OdrBackend, number uint64) (*types.Header, error) { - // Try to find it in the local database first. - db := odr.Database() - hash := rawdb.ReadCanonicalHash(db, number) - - // If there is a canonical hash, there should have a header too. - // But if it's pruned, re-fetch from network again. - if (hash != common.Hash{}) { - if header := rawdb.ReadHeader(db, hash, number); header != nil { - return header, nil - } - } - // Retrieve the header via ODR, ensure the requested header is covered - // by local trusted CHT. - chts, _, chtHead := odr.ChtIndexer().Sections() - if number >= chts*odr.IndexerConfig().ChtSize { - return nil, errNoTrustedCht - } - r := &ChtRequest{ - ChtRoot: GetChtRoot(db, chts-1, chtHead), - ChtNum: chts - 1, - BlockNum: number, - Config: odr.IndexerConfig(), - } - if err := odr.Retrieve(ctx, r); err != nil { - return nil, err - } - return r.Header, nil -} - -// GetCanonicalHash retrieves the canonical block hash corresponding to the number. -func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (common.Hash, error) { - hash := rawdb.ReadCanonicalHash(odr.Database(), number) - if hash != (common.Hash{}) { - return hash, nil - } - header, err := GetHeaderByNumber(ctx, odr, number) - if err != nil { - return common.Hash{}, err - } - // number -> canonical mapping already be stored in db, get it. - return header.Hash(), nil -} - -// GetTd retrieves the total difficulty corresponding to the number and hash. -func GetTd(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*big.Int, error) { - td := rawdb.ReadTd(odr.Database(), hash, number) - if td != nil { - return td, nil - } - header, err := GetHeaderByNumber(ctx, odr, number) - if err != nil { - return nil, err - } - if header.Hash() != hash { - return nil, errNonCanonicalHash - } - // -> td mapping already be stored in db, get it. - return rawdb.ReadTd(odr.Database(), hash, number), nil -} - -// GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding. -func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (rlp.RawValue, error) { - if data := rawdb.ReadBodyRLP(odr.Database(), hash, number); data != nil { - return data, nil - } - // Retrieve the block header first and pass it for verification. - header, err := GetHeaderByNumber(ctx, odr, number) - if err != nil { - return nil, errNoHeader - } - if header.Hash() != hash { - return nil, errNonCanonicalHash - } - r := &BlockRequest{Hash: hash, Number: number, Header: header} - if err := odr.Retrieve(ctx, r); err != nil { - return nil, err - } - return r.Rlp, nil -} - -// GetBody retrieves the block body (transactions, uncles) corresponding to the -// hash. -func GetBody(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Body, error) { - data, err := GetBodyRLP(ctx, odr, hash, number) - if err != nil { - return nil, err - } - body := new(types.Body) - if err := rlp.Decode(bytes.NewReader(data), body); err != nil { - return nil, err - } - return body, nil -} - -// GetBlock retrieves an entire block corresponding to the hash, assembling it -// back from the stored header and body. -func GetBlock(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (*types.Block, error) { - // Retrieve the block header and body contents - header, err := GetHeaderByNumber(ctx, odr, number) - if err != nil { - return nil, errNoHeader - } - body, err := GetBody(ctx, odr, hash, number) - if err != nil { - return nil, err - } - // Reassemble the block and return - return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles), nil -} - -// GetBlockReceipts retrieves the receipts generated by the transactions included -// in a block given by its hash. Receipts will be filled in with context data. -func GetBlockReceipts(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (types.Receipts, error) { - // Assume receipts are already stored locally and attempt to retrieve. - receipts := rawdb.ReadRawReceipts(odr.Database(), hash, number) - if receipts == nil { - header, err := GetHeaderByNumber(ctx, odr, number) - if err != nil { - return nil, errNoHeader - } - if header.Hash() != hash { - return nil, errNonCanonicalHash - } - r := &ReceiptsRequest{Hash: hash, Number: number, Header: header} - if err := odr.Retrieve(ctx, r); err != nil { - return nil, err - } - receipts = r.Receipts - } - // If the receipts are incomplete, fill the derived fields - if len(receipts) > 0 && receipts[0].TxHash == (common.Hash{}) { - block, err := GetBlock(ctx, odr, hash, number) - if err != nil { - return nil, err - } - genesis := rawdb.ReadCanonicalHash(odr.Database(), 0) - config := rawdb.ReadChainConfig(odr.Database(), genesis) - - 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) - } - return receipts, nil -} - -// GetBlockLogs retrieves the logs generated by the transactions included in a -// block given by its hash. Logs will be filled in with context data. -func GetBlockLogs(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) ([][]*types.Log, error) { - receipts, err := GetBlockReceipts(ctx, odr, hash, number) - if err != nil { - return nil, err - } - logs := make([][]*types.Log, len(receipts)) - for i, receipt := range receipts { - logs[i] = receipt.Logs - } - return logs, nil -} - -// GetBloomBits retrieves a batch of compressed bloomBits vectors belonging to -// the given bit index and section indexes. -func GetBloomBits(ctx context.Context, odr OdrBackend, bit uint, sections []uint64) ([][]byte, error) { - var ( - reqIndex []int - reqSections []uint64 - db = odr.Database() - result = make([][]byte, len(sections)) - ) - blooms, _, sectionHead := odr.BloomTrieIndexer().Sections() - for i, section := range sections { - sectionHead := rawdb.ReadCanonicalHash(db, (section+1)*odr.IndexerConfig().BloomSize-1) - // If we don't have the canonical hash stored for this section head number, - // we'll still look for an entry with a zero sectionHead (we store it with - // zero section head too if we don't know it at the time of the retrieval) - if bloomBits, _ := rawdb.ReadBloomBits(db, bit, section, sectionHead); len(bloomBits) != 0 { - result[i] = bloomBits - continue - } - // TODO(rjl493456442) Convert sectionIndex to BloomTrie relative index - if section >= blooms { - return nil, errNoTrustedBloomTrie - } - reqSections = append(reqSections, section) - reqIndex = append(reqIndex, i) - } - // Find all bloombits in database, nothing to query via odr, return. - if reqSections == nil { - return result, nil - } - // Send odr request to retrieve missing bloombits. - r := &BloomRequest{ - BloomTrieRoot: GetBloomTrieRoot(db, blooms-1, sectionHead), - BloomTrieNum: blooms - 1, - BitIdx: bit, - SectionIndexList: reqSections, - Config: odr.IndexerConfig(), - } - if err := odr.Retrieve(ctx, r); err != nil { - return nil, err - } - for i, idx := range reqIndex { - result[idx] = r.BloomBits[i] - } - return result, nil -} - -// GetTransaction retrieves a canonical transaction by hash and also returns -// its position in the chain. There is no guarantee in the LES protocol that -// the mined transaction will be retrieved back for sure because of different -// reasons(the transaction is unindexed, the malicious server doesn't reply it -// deliberately, etc). Therefore, unretrieved transactions will receive a certain -// number of retries, thus giving a weak guarantee. -func GetTransaction(ctx context.Context, odr OdrBackend, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { - r := &TxStatusRequest{Hashes: []common.Hash{txHash}} - if err := odr.RetrieveTxStatus(ctx, r); err != nil || r.Status[0].Status != txpool.TxStatusIncluded { - return nil, common.Hash{}, 0, 0, err - } - pos := r.Status[0].Lookup - // first ensure that we have the header, otherwise block body retrieval will fail - // also verify if this is a canonical block by getting the header by number and checking its hash - if header, err := GetHeaderByNumber(ctx, odr, pos.BlockIndex); err != nil || header.Hash() != pos.BlockHash { - return nil, common.Hash{}, 0, 0, err - } - body, err := GetBody(ctx, odr, pos.BlockHash, pos.BlockIndex) - if err != nil || uint64(len(body.Transactions)) <= pos.Index || body.Transactions[pos.Index].Hash() != txHash { - return nil, common.Hash{}, 0, 0, err - } - return body.Transactions[pos.Index], pos.BlockHash, pos.BlockIndex, pos.Index, nil -} diff --git a/light/postprocess.go b/light/postprocess.go deleted file mode 100644 index 567814e2b..000000000 --- a/light/postprocess.go +++ /dev/null @@ -1,538 +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 light - -import ( - "bytes" - "context" - "encoding/binary" - "errors" - "fmt" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/bitutil" - "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/log" - "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" -) - -// IndexerConfig includes a set of configs for chain indexers. -type IndexerConfig struct { - // The block frequency for creating CHTs. - ChtSize uint64 - - // The number of confirmations needed to generate/accept a canonical hash help trie. - ChtConfirms uint64 - - // The block frequency for creating new bloom bits. - BloomSize uint64 - - // The number of confirmation needed before a bloom section is considered probably final and its rotated bits - // are calculated. - BloomConfirms uint64 - - // The block frequency for creating BloomTrie. - BloomTrieSize uint64 - - // The number of confirmations needed to generate/accept a bloom trie. - BloomTrieConfirms uint64 -} - -var ( - // DefaultServerIndexerConfig wraps a set of configs as a default indexer config for server side. - DefaultServerIndexerConfig = &IndexerConfig{ - ChtSize: params.CHTFrequency, - ChtConfirms: params.HelperTrieProcessConfirmations, - BloomSize: params.BloomBitsBlocks, - BloomConfirms: params.BloomConfirms, - BloomTrieSize: params.BloomTrieFrequency, - BloomTrieConfirms: params.HelperTrieProcessConfirmations, - } - // DefaultClientIndexerConfig wraps a set of configs as a default indexer config for client side. - DefaultClientIndexerConfig = &IndexerConfig{ - ChtSize: params.CHTFrequency, - ChtConfirms: params.HelperTrieConfirmations, - BloomSize: params.BloomBitsBlocksClient, - BloomConfirms: params.HelperTrieConfirmations, - BloomTrieSize: params.BloomTrieFrequency, - BloomTrieConfirms: params.HelperTrieConfirmations, - } - // TestServerIndexerConfig wraps a set of configs as a test indexer config for server side. - TestServerIndexerConfig = &IndexerConfig{ - ChtSize: 128, - ChtConfirms: 1, - BloomSize: 16, - BloomConfirms: 1, - BloomTrieSize: 128, - BloomTrieConfirms: 1, - } - // TestClientIndexerConfig wraps a set of configs as a test indexer config for client side. - TestClientIndexerConfig = &IndexerConfig{ - ChtSize: 128, - ChtConfirms: 8, - BloomSize: 128, - BloomConfirms: 8, - BloomTrieSize: 128, - BloomTrieConfirms: 8, - } -) - -var ( - errNoTrustedCht = errors.New("no trusted canonical hash trie") - errNoTrustedBloomTrie = errors.New("no trusted bloom trie") - errNoHeader = errors.New("header not found") -) - -// ChtNode structures are stored in the Canonical Hash Trie in an RLP encoded format -type ChtNode struct { - Hash common.Hash - Td *big.Int -} - -// GetChtRoot reads the CHT root associated to the given section from the database -func GetChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], sectionIdx) - data, _ := db.Get(append(append(rawdb.ChtPrefix, encNumber[:]...), sectionHead.Bytes()...)) - return common.BytesToHash(data) -} - -// StoreChtRoot writes the CHT root associated to the given section into the database -func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) { - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], sectionIdx) - db.Put(append(append(rawdb.ChtPrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes()) -} - -// ChtIndexerBackend implements core.ChainIndexerBackend. -type ChtIndexerBackend struct { - disablePruning bool - diskdb, trieTable ethdb.Database - odr OdrBackend - triedb *trie.Database - section, sectionSize uint64 - lastHash common.Hash - trie *trie.Trie - originRoot common.Hash -} - -// NewChtIndexer creates a Cht chain indexer -func NewChtIndexer(db ethdb.Database, odr OdrBackend, size, confirms uint64, disablePruning bool) *core.ChainIndexer { - trieTable := rawdb.NewTable(db, string(rawdb.ChtTablePrefix)) - backend := &ChtIndexerBackend{ - diskdb: db, - odr: odr, - trieTable: trieTable, - triedb: trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down - sectionSize: size, - disablePruning: disablePruning, - } - return core.NewChainIndexer(db, rawdb.NewTable(db, string(rawdb.ChtIndexTablePrefix)), backend, size, confirms, time.Millisecond*100, "cht") -} - -// fetchMissingNodes tries to retrieve the last entry of the latest trusted CHT from the -// ODR backend in order to be able to add new entries and calculate subsequent root hashes -func (c *ChtIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error { - batch := c.trieTable.NewBatch() - r := &ChtRequest{ChtRoot: root, ChtNum: section - 1, BlockNum: section*c.sectionSize - 1, Config: c.odr.IndexerConfig()} - for { - err := c.odr.Retrieve(ctx, r) - switch err { - case nil: - r.Proof.Store(batch) - return batch.Write() - case ErrNoPeers: - // if there are no peers to serve, retry later - select { - case <-ctx.Done(): - return ctx.Err() - case <-time.After(time.Second * 10): - // stay in the loop and try again - } - default: - return err - } - } -} - -// Reset implements core.ChainIndexerBackend -func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error { - root := types.EmptyRootHash - if section > 0 { - root = GetChtRoot(c.diskdb, section-1, lastSectionHead) - } - var err error - c.trie, err = trie.New(trie.TrieID(root), c.triedb) - - if err != nil && c.odr != nil { - err = c.fetchMissingNodes(ctx, section, root) - if err == nil { - c.trie, err = trie.New(trie.TrieID(root), c.triedb) - } - } - c.section = section - c.originRoot = root - return err -} - -// Process implements core.ChainIndexerBackend -func (c *ChtIndexerBackend) Process(ctx context.Context, header *types.Header) error { - hash, num := header.Hash(), header.Number.Uint64() - c.lastHash = hash - - td := rawdb.ReadTd(c.diskdb, hash, num) - if td == nil { - panic(nil) - } - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], num) - data, _ := rlp.EncodeToBytes(ChtNode{hash, td}) - return c.trie.Update(encNumber[:], data) -} - -// Commit implements core.ChainIndexerBackend -func (c *ChtIndexerBackend) Commit() error { - 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, 0, trienode.NewWithNodeSet(nodes), nil); err != nil { - return err - } - if err := c.triedb.Commit(root, false); err != nil { - return err - } - } - // Re-create trie with newly generated root and updated database. - c.trie, err = trie.New(trie.TrieID(root), c.triedb) - if err != nil { - return err - } - // Pruning historical trie nodes if necessary. - if !c.disablePruning { - it := c.trieTable.NewIterator(nil, nil) - defer it.Release() - - var ( - deleted int - batch = c.trieTable.NewBatch() - t = time.Now() - ) - hashes := make(map[common.Hash]struct{}) - if nodes != nil { - for _, hash := range nodes.Hashes() { - hashes[hash] = struct{}{} - } - } - for it.Next() { - trimmed := bytes.TrimPrefix(it.Key(), rawdb.ChtTablePrefix) - if len(trimmed) == common.HashLength { - if _, ok := hashes[common.BytesToHash(trimmed)]; !ok { - batch.Delete(trimmed) - deleted += 1 - } - } - } - if err := batch.Write(); err != nil { - return err - } - log.Debug("Prune historical CHT trie nodes", "deleted", deleted, "remaining", len(hashes), "elapsed", common.PrettyDuration(time.Since(t))) - } - log.Info("Storing CHT", "section", c.section, "head", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root)) - StoreChtRoot(c.diskdb, c.section, c.lastHash, root) - return nil -} - -// Prune implements core.ChainIndexerBackend which deletes all chain data -// (except hash<->number mappings) older than the specified threshold. -func (c *ChtIndexerBackend) Prune(threshold uint64) error { - // Short circuit if the light pruning is disabled. - if c.disablePruning { - return nil - } - t := time.Now() - // Always keep genesis header in database. - start, end := uint64(1), (threshold+1)*c.sectionSize - - var batch = c.diskdb.NewBatch() - for { - numbers, hashes := rawdb.ReadAllCanonicalHashes(c.diskdb, start, end, 10240) - if len(numbers) == 0 { - break - } - for i := 0; i < len(numbers); i++ { - // Keep hash<->number mapping in database otherwise the hash based - // API(e.g. GetReceipt, GetLogs) will be broken. - // - // Storage size wise, the size of a mapping is ~41bytes. For one - // section is about 1.3MB which is acceptable. - // - // In order to totally get rid of this index, we need an additional - // flag to specify how many historical data light client can serve. - rawdb.DeleteCanonicalHash(batch, numbers[i]) - rawdb.DeleteBlockWithoutNumber(batch, hashes[i], numbers[i]) - } - if batch.ValueSize() > ethdb.IdealBatchSize { - if err := batch.Write(); err != nil { - return err - } - batch.Reset() - } - start = numbers[len(numbers)-1] + 1 - } - if err := batch.Write(); err != nil { - return err - } - log.Debug("Prune history headers", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(t))) - return nil -} - -// GetBloomTrieRoot reads the BloomTrie root associated to the given section from the database -func GetBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead common.Hash) common.Hash { - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], sectionIdx) - data, _ := db.Get(append(append(rawdb.BloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...)) - return common.BytesToHash(data) -} - -// StoreBloomTrieRoot writes the BloomTrie root associated to the given section into the database -func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common.Hash) { - var encNumber [8]byte - binary.BigEndian.PutUint64(encNumber[:], sectionIdx) - db.Put(append(append(rawdb.BloomTriePrefix, encNumber[:]...), sectionHead.Bytes()...), root.Bytes()) -} - -// BloomTrieIndexerBackend implements core.ChainIndexerBackend -type BloomTrieIndexerBackend struct { - disablePruning bool - diskdb, trieTable ethdb.Database - triedb *trie.Database - odr OdrBackend - section uint64 - parentSize uint64 - size uint64 - bloomTrieRatio uint64 - trie *trie.Trie - originRoot common.Hash - sectionHeads []common.Hash -} - -// NewBloomTrieIndexer creates a BloomTrie chain indexer -func NewBloomTrieIndexer(db ethdb.Database, odr OdrBackend, parentSize, size uint64, disablePruning bool) *core.ChainIndexer { - trieTable := rawdb.NewTable(db, string(rawdb.BloomTrieTablePrefix)) - backend := &BloomTrieIndexerBackend{ - diskdb: db, - odr: odr, - trieTable: trieTable, - triedb: trie.NewDatabaseWithConfig(trieTable, &trie.Config{Cache: 1}), // Use a tiny cache only to keep memory down - parentSize: parentSize, - size: size, - disablePruning: disablePruning, - } - backend.bloomTrieRatio = size / parentSize - backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio) - return core.NewChainIndexer(db, rawdb.NewTable(db, string(rawdb.BloomTrieIndexPrefix)), backend, size, 0, time.Millisecond*100, "bloomtrie") -} - -// fetchMissingNodes tries to retrieve the last entries of the latest trusted bloom trie from the -// ODR backend in order to be able to add new entries and calculate subsequent root hashes -func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error { - indexCh := make(chan uint, types.BloomBitLength) - type res struct { - nodes *NodeSet - err error - } - resCh := make(chan res, types.BloomBitLength) - for i := 0; i < 20; i++ { - go func() { - for bitIndex := range indexCh { - r := &BloomRequest{BloomTrieRoot: root, BloomTrieNum: section - 1, BitIdx: bitIndex, SectionIndexList: []uint64{section - 1}, Config: b.odr.IndexerConfig()} - for { - if err := b.odr.Retrieve(ctx, r); err == ErrNoPeers { - // if there are no peers to serve, retry later - select { - case <-ctx.Done(): - resCh <- res{nil, ctx.Err()} - return - case <-time.After(time.Second * 10): - // stay in the loop and try again - } - } else { - resCh <- res{r.Proofs, err} - break - } - } - } - }() - } - for i := uint(0); i < types.BloomBitLength; i++ { - indexCh <- i - } - close(indexCh) - batch := b.trieTable.NewBatch() - for i := uint(0); i < types.BloomBitLength; i++ { - res := <-resCh - if res.err != nil { - return res.err - } - res.nodes.Store(batch) - } - return batch.Write() -} - -// Reset implements core.ChainIndexerBackend -func (b *BloomTrieIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error { - root := types.EmptyRootHash - if section > 0 { - root = GetBloomTrieRoot(b.diskdb, section-1, lastSectionHead) - } - var err error - b.trie, err = trie.New(trie.TrieID(root), b.triedb) - if err != nil && b.odr != nil { - err = b.fetchMissingNodes(ctx, section, root) - if err == nil { - b.trie, err = trie.New(trie.TrieID(root), b.triedb) - } - } - b.section = section - b.originRoot = root - return err -} - -// Process implements core.ChainIndexerBackend -func (b *BloomTrieIndexerBackend) Process(ctx context.Context, header *types.Header) error { - num := header.Number.Uint64() - b.section*b.size - if (num+1)%b.parentSize == 0 { - b.sectionHeads[num/b.parentSize] = header.Hash() - } - return nil -} - -// Commit implements core.ChainIndexerBackend -func (b *BloomTrieIndexerBackend) Commit() error { - var compSize, decompSize uint64 - - for i := uint(0); i < types.BloomBitLength; i++ { - var encKey [10]byte - binary.BigEndian.PutUint16(encKey[0:2], uint16(i)) - binary.BigEndian.PutUint64(encKey[2:10], b.section) - var decomp []byte - for j := uint64(0); j < b.bloomTrieRatio; j++ { - data, err := rawdb.ReadBloomBits(b.diskdb, i, b.section*b.bloomTrieRatio+j, b.sectionHeads[j]) - if err != nil { - return err - } - decompData, err2 := bitutil.DecompressBytes(data, int(b.parentSize/8)) - if err2 != nil { - return err2 - } - decomp = append(decomp, decompData...) - } - comp := bitutil.CompressBytes(decomp) - - decompSize += uint64(len(decomp)) - compSize += uint64(len(comp)) - - var terr error - if len(comp) > 0 { - terr = b.trie.Update(encKey[:], comp) - } else { - terr = b.trie.Delete(encKey[:]) - } - if terr != nil { - return terr - } - } - 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, 0, trienode.NewWithNodeSet(nodes), nil); err != nil { - return err - } - if err := b.triedb.Commit(root, false); err != nil { - return err - } - } - // Re-create trie with newly generated root and updated database. - b.trie, err = trie.New(trie.TrieID(root), b.triedb) - if err != nil { - return err - } - // Pruning historical trie nodes if necessary. - if !b.disablePruning { - it := b.trieTable.NewIterator(nil, nil) - defer it.Release() - - var ( - deleted int - batch = b.trieTable.NewBatch() - t = time.Now() - ) - hashes := make(map[common.Hash]struct{}) - if nodes != nil { - for _, hash := range nodes.Hashes() { - hashes[hash] = struct{}{} - } - } - for it.Next() { - trimmed := bytes.TrimPrefix(it.Key(), rawdb.BloomTrieTablePrefix) - if len(trimmed) == common.HashLength { - if _, ok := hashes[common.BytesToHash(trimmed)]; !ok { - batch.Delete(trimmed) - deleted += 1 - } - } - } - if err := batch.Write(); err != nil { - return err - } - log.Debug("Prune historical bloom trie nodes", "deleted", deleted, "remaining", len(hashes), "elapsed", common.PrettyDuration(time.Since(t))) - } - sectionHead := b.sectionHeads[b.bloomTrieRatio-1] - StoreBloomTrieRoot(b.diskdb, b.section, sectionHead, root) - log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize)) - - return nil -} - -// Prune implements core.ChainIndexerBackend which deletes all -// bloombits which older than the specified threshold. -func (b *BloomTrieIndexerBackend) Prune(threshold uint64) error { - // Short circuit if the light pruning is disabled. - if b.disablePruning { - return nil - } - start := time.Now() - for i := uint(0); i < types.BloomBitLength; i++ { - rawdb.DeleteBloombits(b.diskdb, i, 0, threshold*b.bloomTrieRatio+b.bloomTrieRatio) - } - log.Debug("Prune history bloombits", "threshold", threshold, "elapsed", common.PrettyDuration(time.Since(start))) - return nil -} diff --git a/light/trie.go b/light/trie.go deleted file mode 100644 index 4967cc74e..000000000 --- a/light/trie.go +++ /dev/null @@ -1,317 +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 light - -import ( - "context" - "errors" - "fmt" - - "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/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie" - "github.com/ethereum/go-ethereum/trie/trienode" -) - -var ( - sha3Nil = crypto.Keccak256Hash(nil) -) - -func NewState(ctx context.Context, head *types.Header, odr OdrBackend) *state.StateDB { - state, _ := state.New(head.Root, NewStateDatabase(ctx, head, odr), nil) - return state -} - -func NewStateDatabase(ctx context.Context, head *types.Header, odr OdrBackend) state.Database { - return &odrDatabase{ctx, StateTrieID(head), odr} -} - -type odrDatabase struct { - ctx context.Context - id *TrieID - backend OdrBackend -} - -func (db *odrDatabase) OpenTrie(root common.Hash) (state.Trie, error) { - return &odrTrie{db: db, id: db.id}, 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 { - switch t := t.(type) { - case *odrTrie: - cpy := &odrTrie{db: t.db, id: t.id} - if t.trie != nil { - cpy.trie = t.trie.Copy() - } - return cpy - default: - panic(fmt.Errorf("unknown trie type %T", t)) - } -} - -func (db *odrDatabase) ContractCode(addr common.Address, codeHash common.Hash) ([]byte, error) { - if codeHash == sha3Nil { - return nil, nil - } - code := rawdb.ReadCode(db.backend.Database(), codeHash) - if len(code) != 0 { - return code, nil - } - id := *db.id - id.AccountAddress = addr[:] - req := &CodeRequest{Id: &id, Hash: codeHash} - err := db.backend.Retrieve(db.ctx, req) - return req.Data, err -} - -func (db *odrDatabase) ContractCodeSize(addr common.Address, codeHash common.Hash) (int, error) { - code, err := db.ContractCode(addr, codeHash) - return len(code), err -} - -func (db *odrDatabase) TrieDB() *trie.Database { - return nil -} - -func (db *odrDatabase) DiskDB() ethdb.KeyValueStore { - panic("not implemented") -} - -type odrTrie struct { - db *odrDatabase - id *TrieID - trie *trie.Trie -} - -func (t *odrTrie) GetStorage(_ common.Address, key []byte) ([]byte, error) { - key = crypto.Keccak256(key) - var enc []byte - err := t.do(key, func() (err error) { - enc, err = t.trie.Get(key) - return 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 ( - enc []byte - key = crypto.Keccak256(address.Bytes()) - ) - err := t.do(key, func() (err error) { - enc, err = t.trie.Get(key) - return 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 { - key := crypto.Keccak256(address.Bytes()) - value, err := rlp.EncodeToBytes(acc) - if err != nil { - return fmt.Errorf("decoding error in account update: %w", err) - } - return t.do(key, func() error { - return t.trie.Update(key, value) - }) -} - -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, v) - }) -} - -func (t *odrTrie) DeleteStorage(_ common.Address, key []byte) error { - key = crypto.Keccak256(key) - return t.do(key, func() error { - return t.trie.Delete(key) - }) -} - -// DeleteAccount abstracts an account deletion from the trie. -func (t *odrTrie) DeleteAccount(address common.Address) error { - key := crypto.Keccak256(address.Bytes()) - return t.do(key, func() error { - return t.trie.Delete(key) - }) -} - -func (t *odrTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) { - if t.trie == nil { - return t.id.Root, nil, nil - } - return t.trie.Commit(collectLeaf) -} - -func (t *odrTrie) Hash() common.Hash { - if t.trie == nil { - return t.id.Root - } - return t.trie.Hash() -} - -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, proofDb ethdb.KeyValueWriter) error { - return errors.New("not implemented, needs client/server interface split") -} - -// do tries and retries to execute a function until it returns with no error or -// an error type other than MissingNodeError -func (t *odrTrie) do(key []byte, fn func() error) error { - for { - var err error - if t.trie == nil { - var id *trie.ID - 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())) - } - if err == nil { - err = fn() - } - if _, ok := err.(*trie.MissingNodeError); !ok { - return err - } - r := &TrieRequest{Id: t.id, Key: key} - if err := t.db.backend.Retrieve(t.db.ctx, r); err != nil { - return err - } - } -} - -type nodeIterator struct { - trie.NodeIterator - t *odrTrie - err error -} - -func newNodeIterator(t *odrTrie, startkey []byte) trie.NodeIterator { - it := &nodeIterator{t: t} - // Open the actual non-ODR trie if that hasn't happened yet. - if t.trie == nil { - it.do(func() error { - var id *trie.ID - 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())) - if err == nil { - it.t.trie = t - } - return err - }) - } - it.do(func() error { - var err error - it.NodeIterator, err = it.t.trie.NodeIterator(startkey) - if err != nil { - return err - } - return it.NodeIterator.Error() - }) - return it -} - -func (it *nodeIterator) Next(descend bool) bool { - var ok bool - it.do(func() error { - ok = it.NodeIterator.Next(descend) - return it.NodeIterator.Error() - }) - return ok -} - -// do runs fn and attempts to fill in missing nodes by retrieving. -func (it *nodeIterator) do(fn func() error) { - var lasthash common.Hash - for { - it.err = fn() - missing, ok := it.err.(*trie.MissingNodeError) - if !ok { - return - } - if missing.NodeHash == lasthash { - it.err = fmt.Errorf("retrieve loop for trie node %x", missing.NodeHash) - return - } - lasthash = missing.NodeHash - r := &TrieRequest{Id: it.t.id, Key: nibblesToKey(missing.Path)} - if it.err = it.t.db.backend.Retrieve(it.t.db.ctx, r); it.err != nil { - return - } - } -} - -func (it *nodeIterator) Error() error { - if it.err != nil { - return it.err - } - return it.NodeIterator.Error() -} - -func nibblesToKey(nib []byte) []byte { - if len(nib) > 0 && nib[len(nib)-1] == 0x10 { - nib = nib[:len(nib)-1] // drop terminator - } - if len(nib)&1 == 1 { - nib = append(nib, 0) // make even - } - key := make([]byte, len(nib)/2) - for bi, ni := 0, 0; ni < len(nib); bi, ni = bi+1, ni+2 { - key[bi] = nib[ni]<<4 | nib[ni+1] - } - return key -} diff --git a/light/trie_test.go b/light/trie_test.go deleted file mode 100644 index ad7d769c8..000000000 --- a/light/trie_test.go +++ /dev/null @@ -1,95 +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 light - -import ( - "bytes" - "context" - "errors" - "fmt" - "math/big" - "testing" - - "github.com/davecgh/go-spew/spew" - "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/vm" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/trie" -) - -func TestNodeIterator(t *testing.T) { - var ( - fulldb = rawdb.NewMemoryDatabase() - lightdb = rawdb.NewMemoryDatabase() - gspec = &core.Genesis{ - Config: params.TestChainConfig, - Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, - BaseFee: big.NewInt(params.InitialBaseFee), - } - ) - blockchain, _ := core.NewBlockChain(fulldb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil) - _, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 4, testChainGen) - if _, err := blockchain.InsertChain(gchain); err != nil { - panic(err) - } - - gspec.MustCommit(lightdb) - ctx := context.Background() - odr := &testOdr{sdb: fulldb, ldb: lightdb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig} - head := blockchain.CurrentHeader() - lightTrie, _ := NewStateDatabase(ctx, head, odr).OpenTrie(head.Root) - fullTrie, _ := blockchain.StateCache().OpenTrie(head.Root) - if err := diffTries(fullTrie, lightTrie); err != nil { - t.Fatal(err) - } -} - -func diffTries(t1, t2 state.Trie) error { - 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) - return fmt.Errorf("tries have different keys %x, %x", i1.Key, i2.Key) - } - if !bytes.Equal(i1.Value, i2.Value) { - return fmt.Errorf("tries differ at key %x", i1.Key) - } - } - switch { - case i1.Err != nil: - return fmt.Errorf("full trie iterator error: %v", i1.Err) - case i2.Err != nil: - return fmt.Errorf("light trie iterator error: %v", i2.Err) - case i1.Next(): - return errors.New("full trie iterator has more k/v pairs") - case i2.Next(): - return errors.New("light trie iterator has more k/v pairs") - } - return nil -} diff --git a/light/txpool.go b/light/txpool.go deleted file mode 100644 index b792d70b1..000000000 --- a/light/txpool.go +++ /dev/null @@ -1,556 +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 light - -import ( - "context" - "fmt" - "math/big" - "sync" - "time" - - "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/txpool" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" -) - -const ( - // chainHeadChanSize is the size of channel listening to ChainHeadEvent. - chainHeadChanSize = 10 -) - -// txPermanent is the number of mined blocks after a mined transaction is -// considered permanent and no rollback is expected -var txPermanent = uint64(500) - -// TxPool implements the transaction pool for light clients, which keeps track -// of the status of locally created transactions, detecting if they are included -// in a block (mined) or rolled back. There are no queued transactions since we -// always receive all locally signed transactions in the same order as they are -// created. -type TxPool struct { - config *params.ChainConfig - signer types.Signer - quit chan bool - txFeed event.Feed - scope event.SubscriptionScope - chainHeadCh chan core.ChainHeadEvent - chainHeadSub event.Subscription - mu sync.RWMutex - chain *LightChain - odr OdrBackend - chainDb ethdb.Database - relay TxRelayBackend - head common.Hash - nonce map[common.Address]uint64 // "pending" nonce - pending map[common.Hash]*types.Transaction // pending transactions by tx hash - mined map[common.Hash][]*types.Transaction // mined transactions by block hash - clearIdx uint64 // earliest block nr that can contain mined tx info - - istanbul bool // Fork indicator whether we are in the istanbul stage. - eip2718 bool // Fork indicator whether we are in the eip2718 stage. - shanghai bool // Fork indicator whether we are in the shanghai stage. -} - -// TxRelayBackend provides an interface to the mechanism that forwards transactions to the -// ETH network. The implementations of the functions should be non-blocking. -// -// Send instructs backend to forward new transactions NewHead notifies backend about a new -// head after processed by the tx pool, including mined and rolled back transactions since -// the last event. -// -// Discard notifies backend about transactions that should be discarded either because -// they have been replaced by a re-send or because they have been mined long ago and no -// rollback is expected. -type TxRelayBackend interface { - Send(txs types.Transactions) - NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash) - Discard(hashes []common.Hash) -} - -// NewTxPool creates a new light transaction pool -func NewTxPool(config *params.ChainConfig, chain *LightChain, relay TxRelayBackend) *TxPool { - pool := &TxPool{ - config: config, - signer: types.LatestSigner(config), - nonce: make(map[common.Address]uint64), - pending: make(map[common.Hash]*types.Transaction), - mined: make(map[common.Hash][]*types.Transaction), - quit: make(chan bool), - chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), - chain: chain, - relay: relay, - odr: chain.Odr(), - chainDb: chain.Odr().Database(), - head: chain.CurrentHeader().Hash(), - clearIdx: chain.CurrentHeader().Number.Uint64(), - } - // Subscribe events from blockchain - pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh) - go pool.eventLoop() - - return pool -} - -// currentState returns the light state of the current head header -func (pool *TxPool) currentState(ctx context.Context) *state.StateDB { - return NewState(ctx, pool.chain.CurrentHeader(), pool.odr) -} - -// GetNonce returns the "pending" nonce of a given address. It always queries -// the nonce belonging to the latest header too in order to detect if another -// client using the same key sent a transaction. -func (pool *TxPool) GetNonce(ctx context.Context, addr common.Address) (uint64, error) { - state := pool.currentState(ctx) - nonce := state.GetNonce(addr) - if state.Error() != nil { - return 0, state.Error() - } - sn, ok := pool.nonce[addr] - if ok && sn > nonce { - nonce = sn - } - if !ok || sn < nonce { - pool.nonce[addr] = nonce - } - return nonce, nil -} - -// txStateChanges stores the recent changes between pending/mined states of -// transactions. True means mined, false means rolled back, no entry means no change -type txStateChanges map[common.Hash]bool - -// setState sets the status of a tx to either recently mined or recently rolled back -func (txc txStateChanges) setState(txHash common.Hash, mined bool) { - val, ent := txc[txHash] - if ent && (val != mined) { - delete(txc, txHash) - } else { - txc[txHash] = mined - } -} - -// getLists creates lists of mined and rolled back tx hashes -func (txc txStateChanges) getLists() (mined []common.Hash, rollback []common.Hash) { - for hash, val := range txc { - if val { - mined = append(mined, hash) - } else { - rollback = append(rollback, hash) - } - } - return -} - -// checkMinedTxs checks newly added blocks for the currently pending transactions -// and marks them as mined if necessary. It also stores block position in the db -// and adds them to the received txStateChanges map. -func (pool *TxPool) checkMinedTxs(ctx context.Context, hash common.Hash, number uint64, txc txStateChanges) error { - // If no transactions are pending, we don't care about anything - if len(pool.pending) == 0 { - return nil - } - block, err := GetBlock(ctx, pool.odr, hash, number) - if err != nil { - return err - } - // Gather all the local transaction mined in this block - list := pool.mined[hash] - for _, tx := range block.Transactions() { - if _, ok := pool.pending[tx.Hash()]; ok { - list = append(list, tx) - } - } - // If some transactions have been mined, write the needed data to disk and update - if list != nil { - // Retrieve all the receipts belonging to this block and write the lookup table - if _, err := GetBlockReceipts(ctx, pool.odr, hash, number); err != nil { // ODR caches, ignore results - return err - } - rawdb.WriteTxLookupEntriesByBlock(pool.chainDb, block) - - // Update the transaction pool's state - for _, tx := range list { - delete(pool.pending, tx.Hash()) - txc.setState(tx.Hash(), true) - } - pool.mined[hash] = list - } - return nil -} - -// rollbackTxs marks the transactions contained in recently rolled back blocks -// as rolled back. It also removes any positional lookup entries. -func (pool *TxPool) rollbackTxs(hash common.Hash, txc txStateChanges) { - batch := pool.chainDb.NewBatch() - if list, ok := pool.mined[hash]; ok { - for _, tx := range list { - txHash := tx.Hash() - rawdb.DeleteTxLookupEntry(batch, txHash) - pool.pending[txHash] = tx - txc.setState(txHash, false) - } - delete(pool.mined, hash) - } - batch.Write() -} - -// reorgOnNewHead sets a new head header, processing (and rolling back if necessary) -// the blocks since the last known head and returns a txStateChanges map containing -// the recently mined and rolled back transaction hashes. If an error (context -// timeout) occurs during checking new blocks, it leaves the locally known head -// at the latest checked block and still returns a valid txStateChanges, making it -// possible to continue checking the missing blocks at the next chain head event -func (pool *TxPool) reorgOnNewHead(ctx context.Context, newHeader *types.Header) (txStateChanges, error) { - txc := make(txStateChanges) - oldh := pool.chain.GetHeaderByHash(pool.head) - newh := newHeader - // find common ancestor, create list of rolled back and new block hashes - var oldHashes, newHashes []common.Hash - for oldh.Hash() != newh.Hash() { - if oldh.Number.Uint64() >= newh.Number.Uint64() { - oldHashes = append(oldHashes, oldh.Hash()) - oldh = pool.chain.GetHeader(oldh.ParentHash, oldh.Number.Uint64()-1) - } - if oldh.Number.Uint64() < newh.Number.Uint64() { - newHashes = append(newHashes, newh.Hash()) - newh = pool.chain.GetHeader(newh.ParentHash, newh.Number.Uint64()-1) - if newh == nil { - // happens when CHT syncing, nothing to do - newh = oldh - } - } - } - if oldh.Number.Uint64() < pool.clearIdx { - pool.clearIdx = oldh.Number.Uint64() - } - // roll back old blocks - for _, hash := range oldHashes { - pool.rollbackTxs(hash, txc) - } - pool.head = oldh.Hash() - // check mined txs of new blocks (array is in reversed order) - for i := len(newHashes) - 1; i >= 0; i-- { - hash := newHashes[i] - if err := pool.checkMinedTxs(ctx, hash, newHeader.Number.Uint64()-uint64(i), txc); err != nil { - return txc, err - } - pool.head = hash - } - - // clear old mined tx entries of old blocks - if idx := newHeader.Number.Uint64(); idx > pool.clearIdx+txPermanent { - idx2 := idx - txPermanent - if len(pool.mined) > 0 { - for i := pool.clearIdx; i < idx2; i++ { - hash := rawdb.ReadCanonicalHash(pool.chainDb, i) - if list, ok := pool.mined[hash]; ok { - hashes := make([]common.Hash, len(list)) - for i, tx := range list { - hashes[i] = tx.Hash() - } - pool.relay.Discard(hashes) - delete(pool.mined, hash) - } - } - } - pool.clearIdx = idx2 - } - - return txc, nil -} - -// blockCheckTimeout is the time limit for checking new blocks for mined -// transactions. Checking resumes at the next chain head event if timed out. -const blockCheckTimeout = time.Second * 3 - -// eventLoop processes chain head events and also notifies the tx relay backend -// about the new head hash and tx state changes -func (pool *TxPool) eventLoop() { - for { - select { - case ev := <-pool.chainHeadCh: - pool.setNewHead(ev.Block.Header()) - // hack in order to avoid hogging the lock; this part will - // be replaced by a subsequent PR. - time.Sleep(time.Millisecond) - - // System stopped - case <-pool.chainHeadSub.Err(): - return - } - } -} - -func (pool *TxPool) setNewHead(head *types.Header) { - pool.mu.Lock() - defer pool.mu.Unlock() - - ctx, cancel := context.WithTimeout(context.Background(), blockCheckTimeout) - defer cancel() - - txc, _ := pool.reorgOnNewHead(ctx, head) - m, r := txc.getLists() - pool.relay.NewHead(pool.head, m, r) - - // Update fork indicator by next pending block number - next := new(big.Int).Add(head.Number, big.NewInt(1)) - pool.istanbul = pool.config.IsIstanbul(next) - pool.eip2718 = pool.config.IsBerlin(next) - pool.shanghai = pool.config.IsShanghai(next, uint64(time.Now().Unix())) -} - -// Stop stops the light transaction pool -func (pool *TxPool) Stop() { - // Unsubscribe all subscriptions registered from txpool - pool.scope.Close() - // Unsubscribe subscriptions registered from blockchain - pool.chainHeadSub.Unsubscribe() - close(pool.quit) - log.Info("Transaction pool stopped") -} - -// SubscribeNewTxsEvent registers a subscription of core.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)) -} - -// Stats returns the number of currently pending (locally created) transactions -func (pool *TxPool) Stats() (pending int) { - pool.mu.RLock() - defer pool.mu.RUnlock() - - pending = len(pool.pending) - return -} - -// validateTx checks whether a transaction is valid according to the consensus rules. -func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error { - // Validate sender - var ( - from common.Address - err error - ) - - // Validate the transaction sender and it's sig. Throw - // if the from fields is invalid. - if from, err = types.Sender(pool.signer, tx); err != nil { - return txpool.ErrInvalidSender - } - // Last but not least check for nonce errors - currentState := pool.currentState(ctx) - if n := currentState.GetNonce(from); n > tx.Nonce() { - return core.ErrNonceTooLow - } - - // Check the transaction doesn't exceed the current - // block limit gas. - header := pool.chain.GetHeaderByHash(pool.head) - if header.GasLimit < tx.Gas() { - return txpool.ErrGasLimit - } - - // Transactions can't be negative. This may never happen - // using RLP decoded transactions but may occur if you create - // a transaction using the RPC for example. - if tx.Value().Sign() < 0 { - return txpool.ErrNegativeValue - } - - // Transactor should have enough funds to cover the costs - // cost == V + GP * GL - if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 { - return core.ErrInsufficientFunds - } - - // Should supply enough intrinsic gas - gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, true, pool.istanbul, pool.shanghai) - if err != nil { - return err - } - if tx.Gas() < gas { - return core.ErrIntrinsicGas - } - return currentState.Error() -} - -// add validates a new transaction and sets its state pending if processable. -// It also updates the locally stored nonce if necessary. -func (pool *TxPool) add(ctx context.Context, tx *types.Transaction) error { - hash := tx.Hash() - - if pool.pending[hash] != nil { - return fmt.Errorf("known transaction (%x)", hash[:4]) - } - err := pool.validateTx(ctx, tx) - if err != nil { - return err - } - - if _, ok := pool.pending[hash]; !ok { - pool.pending[hash] = tx - - nonce := tx.Nonce() + 1 - - addr, _ := types.Sender(pool.signer, tx) - if nonce > pool.nonce[addr] { - pool.nonce[addr] = nonce - } - - // Notify the subscribers. This event is posted in a goroutine - // because it's possible that somewhere during the post "Remove transaction" - // gets called which will then wait for the global tx pool lock and deadlock. - go pool.txFeed.Send(core.NewTxsEvent{Txs: types.Transactions{tx}}) - } - - // Print a log message if low enough level is set - log.Debug("Pooled new transaction", "hash", hash, "from", log.Lazy{Fn: func() common.Address { from, _ := types.Sender(pool.signer, tx); return from }}, "to", tx.To()) - return nil -} - -// Add adds a transaction to the pool if valid and passes it to the tx relay -// backend -func (pool *TxPool) Add(ctx context.Context, tx *types.Transaction) error { - pool.mu.Lock() - defer pool.mu.Unlock() - data, err := tx.MarshalBinary() - if err != nil { - return err - } - - if err := pool.add(ctx, tx); err != nil { - return err - } - //fmt.Println("Send", tx.Hash()) - pool.relay.Send(types.Transactions{tx}) - - pool.chainDb.Put(tx.Hash().Bytes(), data) - return nil -} - -// AddBatch adds all valid transactions to the pool and passes them to -// the tx relay backend -func (pool *TxPool) AddBatch(ctx context.Context, txs []*types.Transaction) { - pool.mu.Lock() - defer pool.mu.Unlock() - var sendTx types.Transactions - - for _, tx := range txs { - if err := pool.add(ctx, tx); err == nil { - sendTx = append(sendTx, tx) - } - } - if len(sendTx) > 0 { - pool.relay.Send(sendTx) - } -} - -// GetTransaction returns a transaction if it is contained in the pool -// and nil otherwise. -func (pool *TxPool) GetTransaction(hash common.Hash) *types.Transaction { - // check the txs first - if tx, ok := pool.pending[hash]; ok { - return tx - } - return nil -} - -// GetTransactions returns all currently processable transactions. -// The returned slice may be modified by the caller. -func (pool *TxPool) GetTransactions() (txs types.Transactions, err error) { - pool.mu.RLock() - defer pool.mu.RUnlock() - - txs = make(types.Transactions, len(pool.pending)) - i := 0 - for _, tx := range pool.pending { - txs[i] = tx - i++ - } - return txs, nil -} - -// 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.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.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.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.Transaction, []*types.Transaction) { - pool.mu.RLock() - defer pool.mu.RUnlock() - - // Retrieve the pending transactions and sort by nonce - var pending []*types.Transaction - for _, tx := range pool.pending { - account, _ := types.Sender(pool.signer, tx) - if account != addr { - continue - } - pending = append(pending, tx) - } - // There are no queued transactions in a light pool, just return an empty map - return pending, []*types.Transaction{} -} - -// RemoveTransactions removes all given transactions from the pool. -func (pool *TxPool) RemoveTransactions(txs types.Transactions) { - pool.mu.Lock() - defer pool.mu.Unlock() - - var hashes []common.Hash - batch := pool.chainDb.NewBatch() - for _, tx := range txs { - hash := tx.Hash() - delete(pool.pending, hash) - batch.Delete(hash.Bytes()) - hashes = append(hashes, hash) - } - batch.Write() - pool.relay.Discard(hashes) -} - -// RemoveTx removes the transaction with the given hash from the pool. -func (pool *TxPool) RemoveTx(hash common.Hash) { - pool.mu.Lock() - defer pool.mu.Unlock() - // delete from pending pool - delete(pool.pending, hash) - pool.chainDb.Delete(hash[:]) - pool.relay.Discard([]common.Hash{hash}) -} diff --git a/light/txpool_test.go b/light/txpool_test.go deleted file mode 100644 index 1181e3889..000000000 --- a/light/txpool_test.go +++ /dev/null @@ -1,146 +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 light - -import ( - "context" - "math" - "math/big" - "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/core/vm" - "github.com/ethereum/go-ethereum/params" -) - -type testTxRelay struct { - send, discard, mined chan int -} - -func (r *testTxRelay) Send(txs types.Transactions) { - r.send <- len(txs) -} - -func (r *testTxRelay) NewHead(head common.Hash, mined []common.Hash, rollback []common.Hash) { - m := len(mined) - if m != 0 { - r.mined <- m - } -} - -func (r *testTxRelay) Discard(hashes []common.Hash) { - r.discard <- len(hashes) -} - -const poolTestTxs = 1000 -const poolTestBlocks = 100 - -// test tx 0..n-1 -var testTx [poolTestTxs]*types.Transaction - -// txs sent before block i -func sentTx(i int) int { - return int(math.Pow(float64(i)/float64(poolTestBlocks), 0.9) * poolTestTxs) -} - -// txs included in block i or before that (minedTx(i) <= sentTx(i)) -func minedTx(i int) int { - return int(math.Pow(float64(i)/float64(poolTestBlocks), 1.1) * poolTestTxs) -} - -func txPoolTestChainGen(i int, block *core.BlockGen) { - s := minedTx(i) - e := minedTx(i + 1) - for i := s; i < e; i++ { - block.AddTx(testTx[i]) - } -} - -func TestTxPool(t *testing.T) { - for i := range testTx { - testTx[i], _ = types.SignTx(types.NewTransaction(uint64(i), acc1Addr, big.NewInt(10000), params.TxGas, big.NewInt(params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey) - } - - var ( - sdb = rawdb.NewMemoryDatabase() - ldb = rawdb.NewMemoryDatabase() - gspec = &core.Genesis{ - Config: params.TestChainConfig, - Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, - BaseFee: big.NewInt(params.InitialBaseFee), - } - ) - // Assemble the test environment - blockchain, _ := core.NewBlockChain(sdb, nil, gspec, nil, ethash.NewFullFaker(), vm.Config{}, nil, nil) - _, gchain, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), poolTestBlocks, txPoolTestChainGen) - if _, err := blockchain.InsertChain(gchain); err != nil { - panic(err) - } - - gspec.MustCommit(ldb) - odr := &testOdr{sdb: sdb, ldb: ldb, serverState: blockchain.StateCache(), indexerConfig: TestClientIndexerConfig} - relay := &testTxRelay{ - send: make(chan int, 1), - discard: make(chan int, 1), - mined: make(chan int, 1), - } - lightchain, _ := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker()) - txPermanent = 50 - pool := NewTxPool(params.TestChainConfig, lightchain, relay) - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - - for ii, block := range gchain { - i := ii + 1 - s := sentTx(i - 1) - e := sentTx(i) - for i := s; i < e; i++ { - pool.Add(ctx, testTx[i]) - got := <-relay.send - exp := 1 - if got != exp { - t.Errorf("relay.Send expected len = %d, got %d", exp, got) - } - } - - if _, err := lightchain.InsertHeaderChain([]*types.Header{block.Header()}); err != nil { - panic(err) - } - - got := <-relay.mined - exp := minedTx(i) - minedTx(i-1) - if got != exp { - t.Errorf("relay.NewHead expected len(mined) = %d, got %d", exp, got) - } - - exp = 0 - if i > int(txPermanent)+1 { - exp = minedTx(i-int(txPermanent)-1) - minedTx(i-int(txPermanent)-2) - } - if exp != 0 { - got = <-relay.discard - if got != exp { - t.Errorf("relay.Discard expected len = %d, got %d", exp, got) - } - } - } -} diff --git a/log/CONTRIBUTORS b/log/CONTRIBUTORS deleted file mode 100644 index a0866713b..000000000 --- a/log/CONTRIBUTORS +++ /dev/null @@ -1,11 +0,0 @@ -Contributors to log15: - -- Aaron L -- Alan Shreve -- Chris Hines -- Ciaran Downey -- Dmitry Chestnykh -- Evan Shaw -- Péter Szilágyi -- Trevor Gattis -- Vincent Vanackere diff --git a/log/LICENSE b/log/LICENSE deleted file mode 100644 index 5f0d1fb6a..000000000 --- a/log/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2014 Alan Shreve - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/log/README.md b/log/README.md deleted file mode 100644 index 47426806d..000000000 --- a/log/README.md +++ /dev/null @@ -1,77 +0,0 @@ -![obligatory xkcd](https://imgs.xkcd.com/comics/standards.png) - -# log15 [![godoc reference](https://godoc.org/github.com/inconshreveable/log15?status.png)](https://godoc.org/github.com/inconshreveable/log15) [![Build Status](https://travis-ci.org/inconshreveable/log15.svg?branch=master)](https://travis-ci.org/inconshreveable/log15) - -Package log15 provides an opinionated, simple toolkit for best-practice logging in Go (golang) that is both human and machine readable. It is modeled after the Go standard library's [`io`](https://golang.org/pkg/io/) and [`net/http`](https://golang.org/pkg/net/http/) packages and is an alternative to the standard library's [`log`](https://golang.org/pkg/log/) package. - -## Features -- A simple, easy-to-understand API -- Promotes structured logging by encouraging use of key/value pairs -- Child loggers which inherit and add their own private context -- Lazy evaluation of expensive operations -- Simple Handler interface allowing for construction of flexible, custom logging configurations with a tiny API. -- Color terminal support -- Built-in support for logging to files, streams, syslog, and the network -- Support for forking records to multiple handlers, buffering records for output, failing over from failed handler writes, + more - -## Versioning -The API of the master branch of log15 should always be considered unstable. If you want to rely on a stable API, -you must vendor the library. - -## Importing - -```go -import log "github.com/inconshreveable/log15" -``` - -## Examples - -```go -// all loggers can have key/value context -srvlog := log.New("module", "app/server") - -// all log messages can have key/value context -srvlog.Warn("abnormal conn rate", "rate", curRate, "low", lowRate, "high", highRate) - -// child loggers with inherited context -connlog := srvlog.New("raddr", c.RemoteAddr()) -connlog.Info("connection open") - -// lazy evaluation -connlog.Debug("ping remote", "latency", log.Lazy{pingRemote}) - -// flexible configuration -srvlog.SetHandler(log.MultiHandler( - log.StreamHandler(os.Stderr, log.LogfmtFormat()), - log.LvlFilterHandler( - log.LvlError, - log.Must.FileHandler("errors.json", log.JSONFormat())))) -``` - -Will result in output that looks like this: - -``` -WARN[06-17|21:58:10] abnormal conn rate module=app/server rate=0.500 low=0.100 high=0.800 -INFO[06-17|21:58:10] connection open module=app/server raddr=10.0.0.1 -``` - -## Breaking API Changes -The following commits broke API stability. This reference is intended to help you understand the consequences of updating to a newer version -of log15. - -- 57a084d014d4150152b19e4e531399a7145d1540 - Added a `Get()` method to the `Logger` interface to retrieve the current handler -- 93404652ee366648fa622b64d1e2b67d75a3094a - `Record` field `Call` changed to `stack.Call` with switch to `github.com/go-stack/stack` -- a5e7613673c73281f58e15a87d2cf0cf111e8152 - Restored `syslog.Priority` argument to the `SyslogXxx` handler constructors - -## FAQ - -### The varargs style is brittle and error prone! Can I have type safety please? -Yes. Use `log.Ctx`: - -```go -srvlog := log.New(log.Ctx{"module": "app/server"}) -srvlog.Warn("abnormal conn rate", log.Ctx{"rate": curRate, "low": lowRate, "high": highRate}) -``` - -## License -Apache diff --git a/log/README_ETHEREUM.md b/log/README_ETHEREUM.md deleted file mode 100644 index f6c42ccc0..000000000 --- a/log/README_ETHEREUM.md +++ /dev/null @@ -1,5 +0,0 @@ -This package is a fork of https://github.com/inconshreveable/log15, with some -minor modifications required by the go-ethereum codebase: - - * Support for log level `trace` - * Modified behavior to exit on `critical` failure diff --git a/log/doc.go b/log/doc.go deleted file mode 100644 index d2e15140e..000000000 --- a/log/doc.go +++ /dev/null @@ -1,327 +0,0 @@ -/* -Package log15 provides an opinionated, simple toolkit for best-practice logging that is -both human and machine readable. It is modeled after the standard library's io and net/http -packages. - -This package enforces you to only log key/value pairs. Keys must be strings. Values may be -any type that you like. The default output format is logfmt, but you may also choose to use -JSON instead if that suits you. Here's how you log: - - log.Info("page accessed", "path", r.URL.Path, "user_id", user.id) - -This will output a line that looks like: - - lvl=info t=2014-05-02T16:07:23-0700 msg="page accessed" path=/org/71/profile user_id=9 - -# Getting Started - -To get started, you'll want to import the library: - - import log "github.com/inconshreveable/log15" - -Now you're ready to start logging: - - func main() { - log.Info("Program starting", "args", os.Args()) - } - -# Convention - -Because recording a human-meaningful message is common and good practice, the first argument to every -logging method is the value to the *implicit* key 'msg'. - -Additionally, the level you choose for a message will be automatically added with the key 'lvl', and so -will the current timestamp with key 't'. - -You may supply any additional context as a set of key/value pairs to the logging function. log15 allows -you to favor terseness, ordering, and speed over safety. This is a reasonable tradeoff for -logging functions. You don't need to explicitly state keys/values, log15 understands that they alternate -in the variadic argument list: - - log.Warn("size out of bounds", "low", lowBound, "high", highBound, "val", val) - -If you really do favor your type-safety, you may choose to pass a log.Ctx instead: - - log.Warn("size out of bounds", log.Ctx{"low": lowBound, "high": highBound, "val": val}) - -# Context loggers - -Frequently, you want to add context to a logger so that you can track actions associated with it. An http -request is a good example. You can easily create new loggers that have context that is automatically included -with each log line: - - requestlogger := log.New("path", r.URL.Path) - - // later - requestlogger.Debug("db txn commit", "duration", txnTimer.Finish()) - -This will output a log line that includes the path context that is attached to the logger: - - lvl=dbug t=2014-05-02T16:07:23-0700 path=/repo/12/add_hook msg="db txn commit" duration=0.12 - -# Handlers - -The Handler interface defines where log lines are printed to and how they are formatted. Handler is a -single interface that is inspired by net/http's handler interface: - - type Handler interface { - Log(r *Record) error - } - -Handlers can filter records, format them, or dispatch to multiple other Handlers. -This package implements a number of Handlers for common logging patterns that are -easily composed to create flexible, custom logging structures. - -Here's an example handler that prints logfmt output to Stdout: - - handler := log.StreamHandler(os.Stdout, log.LogfmtFormat()) - -Here's an example handler that defers to two other handlers. One handler only prints records -from the rpc package in logfmt to standard out. The other prints records at Error level -or above in JSON formatted output to the file /var/log/service.json - - handler := log.MultiHandler( - log.LvlFilterHandler(log.LvlError, log.Must.FileHandler("/var/log/service.json", log.JSONFormat())), - log.MatchFilterHandler("pkg", "app/rpc" log.StdoutHandler()) - ) - -# Logging File Names and Line Numbers - -This package implements three Handlers that add debugging information to the -context, CallerFileHandler, CallerFuncHandler and CallerStackHandler. Here's -an example that adds the source file and line number of each logging call to -the context. - - h := log.CallerFileHandler(log.StdoutHandler) - log.Root().SetHandler(h) - ... - log.Error("open file", "err", err) - -This will output a line that looks like: - - lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" caller=data.go:42 - -Here's an example that logs the call stack rather than just the call site. - - h := log.CallerStackHandler("%+v", log.StdoutHandler) - log.Root().SetHandler(h) - ... - log.Error("open file", "err", err) - -This will output a line that looks like: - - lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" stack="[pkg/data.go:42 pkg/cmd/main.go]" - -The "%+v" format instructs the handler to include the path of the source file -relative to the compile time GOPATH. The github.com/go-stack/stack package -documents the full list of formatting verbs and modifiers available. - -# Custom Handlers - -The Handler interface is so simple that it's also trivial to write your own. Let's create an -example handler which tries to write to one handler, but if that fails it falls back to -writing to another handler and includes the error that it encountered when trying to write -to the primary. This might be useful when trying to log over a network socket, but if that -fails you want to log those records to a file on disk. - - type BackupHandler struct { - Primary Handler - Secondary Handler - } - - func (h *BackupHandler) Log (r *Record) error { - err := h.Primary.Log(r) - if err != nil { - r.Ctx = append(ctx, "primary_err", err) - return h.Secondary.Log(r) - } - return nil - } - -This pattern is so useful that a generic version that handles an arbitrary number of Handlers -is included as part of this library called FailoverHandler. - -# Logging Expensive Operations - -Sometimes, you want to log values that are extremely expensive to compute, but you don't want to pay -the price of computing them if you haven't turned up your logging level to a high level of detail. - -This package provides a simple type to annotate a logging operation that you want to be evaluated -lazily, just when it is about to be logged, so that it would not be evaluated if an upstream Handler -filters it out. Just wrap any function which takes no arguments with the log.Lazy type. For example: - - func factorRSAKey() (factors []int) { - // return the factors of a very large number - } - - log.Debug("factors", log.Lazy{factorRSAKey}) - -If this message is not logged for any reason (like logging at the Error level), then -factorRSAKey is never evaluated. - -# Dynamic context values - -The same log.Lazy mechanism can be used to attach context to a logger which you want to be -evaluated when the message is logged, but not when the logger is created. For example, let's imagine -a game where you have Player objects: - - type Player struct { - name string - alive bool - log.Logger - } - -You always want to log a player's name and whether they're alive or dead, so when you create the player -object, you might do: - - p := &Player{name: name, alive: true} - p.Logger = log.New("name", p.name, "alive", p.alive) - -Only now, even after a player has died, the logger will still report they are alive because the logging -context is evaluated when the logger was created. By using the Lazy wrapper, we can defer the evaluation -of whether the player is alive or not to each log message, so that the log records will reflect the player's -current state no matter when the log message is written: - - p := &Player{name: name, alive: true} - isAlive := func() bool { return p.alive } - player.Logger = log.New("name", p.name, "alive", log.Lazy{isAlive}) - -# Terminal Format - -If log15 detects that stdout is a terminal, it will configure the default -handler for it (which is log.StdoutHandler) to use TerminalFormat. This format -logs records nicely for your terminal, including color-coded output based -on log level. - -# Error Handling - -Becasuse log15 allows you to step around the type system, there are a few ways you can specify -invalid arguments to the logging functions. You could, for example, wrap something that is not -a zero-argument function with log.Lazy or pass a context key that is not a string. Since logging libraries -are typically the mechanism by which errors are reported, it would be onerous for the logging functions -to return errors. Instead, log15 handles errors by making these guarantees to you: - -- Any log record containing an error will still be printed with the error explained to you as part of the log record. - -- Any log record containing an error will include the context key LOG15_ERROR, enabling you to easily -(and if you like, automatically) detect if any of your logging calls are passing bad values. - -Understanding this, you might wonder why the Handler interface can return an error value in its Log method. Handlers -are encouraged to return errors only if they fail to write their log records out to an external source like if the -syslog daemon is not responding. This allows the construction of useful handlers which cope with those failures -like the FailoverHandler. - -# Library Use - -log15 is intended to be useful for library authors as a way to provide configurable logging to -users of their library. Best practice for use in a library is to always disable all output for your logger -by default and to provide a public Logger instance that consumers of your library can configure. Like so: - - package yourlib - - import "github.com/inconshreveable/log15" - - var Log = log.New() - - func init() { - Log.SetHandler(log.DiscardHandler()) - } - -Users of your library may then enable it if they like: - - import "github.com/inconshreveable/log15" - import "example.com/yourlib" - - func main() { - handler := // custom handler setup - yourlib.Log.SetHandler(handler) - } - -# Best practices attaching logger context - -The ability to attach context to a logger is a powerful one. Where should you do it and why? -I favor embedding a Logger directly into any persistent object in my application and adding -unique, tracing context keys to it. For instance, imagine I am writing a web browser: - - type Tab struct { - url string - render *RenderingContext - // ... - - Logger - } - - func NewTab(url string) *Tab { - return &Tab { - // ... - url: url, - - Logger: log.New("url", url), - } - } - -When a new tab is created, I assign a logger to it with the url of -the tab as context so it can easily be traced through the logs. -Now, whenever we perform any operation with the tab, we'll log with its -embedded logger and it will include the tab title automatically: - - tab.Debug("moved position", "idx", tab.idx) - -There's only one problem. What if the tab url changes? We could -use log.Lazy to make sure the current url is always written, but that -would mean that we couldn't trace a tab's full lifetime through our -logs after the user navigate to a new URL. - -Instead, think about what values to attach to your loggers the -same way you think about what to use as a key in a SQL database schema. -If it's possible to use a natural key that is unique for the lifetime of the -object, do so. But otherwise, log15's ext package has a handy RandId -function to let you generate what you might call "surrogate keys" -They're just random hex identifiers to use for tracing. Back to our -Tab example, we would prefer to set up our Logger like so: - - import logext "github.com/inconshreveable/log15/ext" - - t := &Tab { - // ... - url: url, - } - - t.Logger = log.New("id", logext.RandId(8), "url", log.Lazy{t.getUrl}) - return t - -Now we'll have a unique traceable identifier even across loading new urls, but -we'll still be able to see the tab's current url in the log messages. - -# Must - -For all Handler functions which can return an error, there is a version of that -function which will return no error but panics on failure. They are all available -on the Must object. For example: - - log.Must.FileHandler("/path", log.JSONFormat) - log.Must.NetHandler("tcp", ":1234", log.JSONFormat) - -# Inspiration and Credit - -All of the following excellent projects inspired the design of this library: - -code.google.com/p/log4go - -github.com/op/go-logging - -github.com/technoweenie/grohl - -github.com/Sirupsen/logrus - -github.com/kr/logfmt - -github.com/spacemonkeygo/spacelog - -golang's stdlib, notably io and net/http - -# The Name - -https://xkcd.com/927/ -*/ -package log diff --git a/log/format.go b/log/format.go index 6a03013b8..6447f3c1f 100644 --- a/log/format.go +++ b/log/format.go @@ -2,69 +2,26 @@ package log import ( "bytes" - "encoding/json" "fmt" "math/big" "reflect" "strconv" - "strings" - "sync" - "sync/atomic" "time" "unicode/utf8" "github.com/holiman/uint256" + "golang.org/x/exp/slog" ) const ( timeFormat = "2006-01-02T15:04:05-0700" - termTimeFormat = "01-02|15:04:05.000" floatFormat = 'f' termMsgJust = 40 termCtxMaxPadding = 40 ) -// locationTrims are trimmed for display to avoid unwieldy log lines. -var locationTrims = []string{ - "github.com/ethereum/go-ethereum/", -} - -// PrintOrigins sets or unsets log location (file:line) printing for terminal -// format output. -func PrintOrigins(print bool) { - locationEnabled.Store(print) -} - -// locationEnabled is an atomic flag controlling whether the terminal formatter -// should append the log locations too when printing entries. -var locationEnabled atomic.Bool - -// locationLength is the maxmimum path length encountered, which all logs are -// padded to to aid in alignment. -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. -var fieldPadding = make(map[string]int) - -// fieldPaddingLock is a global mutex protecting the field padding map. -var fieldPaddingLock sync.RWMutex - -type Format interface { - Format(r *Record) []byte -} - -// FormatFunc returns a new Format object which uses -// the given function to perform record formatting. -func FormatFunc(f func(*Record) []byte) Format { - return formatFunc(f) -} - -type formatFunc func(*Record) []byte - -func (f formatFunc) Format(r *Record) []byte { - return f(r) -} +// 40 spaces +var spaces = []byte(" ") // TerminalStringer is an analogous interface to the stdlib stringer, allowing // own types to have custom shortened serialization formats when printed to the @@ -73,348 +30,172 @@ type TerminalStringer interface { TerminalString() string } -// TerminalFormat formats log records optimized for human readability on -// a terminal with color-coded level output and terser human friendly timestamp. -// This format should only be used for interactive programs or while developing. -// -// [LEVEL] [TIME] MESSAGE key=value key=value ... -// -// Example: -// -// [DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002 -func TerminalFormat(usecolor bool) Format { - return FormatFunc(func(r *Record) []byte { - msg := escapeMessage(r.Msg) - var color = 0 - if usecolor { - switch r.Lvl { - case LvlCrit: - color = 35 - case LvlError: - color = 31 - case LvlWarn: - color = 33 - case LvlInfo: - color = 32 - case LvlDebug: - color = 36 - case LvlTrace: - color = 34 - } - } - - b := &bytes.Buffer{} - lvl := r.Lvl.AlignedString() - 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(locationLength.Load()) - if align < len(location) { - align = len(location) - locationLength.Store(uint32(align)) - } - padding := strings.Repeat(" ", align-len(location)) - - // Assemble and print the log heading - if color > 0 { - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s|%s]%s %s ", color, lvl, r.Time.Format(termTimeFormat), location, padding, msg) - } else { - fmt.Fprintf(b, "%s[%s|%s]%s %s ", lvl, r.Time.Format(termTimeFormat), location, padding, msg) - } - } else { - if color > 0 { - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), msg) - } else { - fmt.Fprintf(b, "%s[%s] %s ", lvl, r.Time.Format(termTimeFormat), msg) - } +func (h *TerminalHandler) format(buf []byte, r slog.Record, usecolor bool) []byte { + msg := escapeMessage(r.Message) + var color = "" + if usecolor { + switch r.Level { + case LevelCrit: + color = "\x1b[35m" + case slog.LevelError: + color = "\x1b[31m" + case slog.LevelWarn: + color = "\x1b[33m" + case slog.LevelInfo: + color = "\x1b[32m" + case slog.LevelDebug: + color = "\x1b[36m" + case LevelTrace: + color = "\x1b[34m" } - // try to justify the log output for short messages - length := utf8.RuneCountInString(msg) - if len(r.Ctx) > 0 && length < termMsgJust { - b.Write(bytes.Repeat([]byte{' '}, termMsgJust-length)) - } - // print the keys logfmt style - logfmt(b, r.Ctx, color, true) - return b.Bytes() - }) -} + } + if buf == nil { + buf = make([]byte, 0, 30+termMsgJust) + } + b := bytes.NewBuffer(buf) + + if color != "" { // Start color + b.WriteString(color) + b.WriteString(LevelAlignedString(r.Level)) + b.WriteString("\x1b[0m") + } else { + b.WriteString(LevelAlignedString(r.Level)) + } + b.WriteString("[") + writeTimeTermFormat(b, r.Time) + b.WriteString("] ") + b.WriteString(msg) + + // try to justify the log output for short messages + //length := utf8.RuneCountInString(msg) + length := len(msg) + if (r.NumAttrs()+len(h.attrs)) > 0 && length < termMsgJust { + b.Write(spaces[:termMsgJust-length]) + } + // print the attributes + h.formatAttributes(b, r, color) -// LogfmtFormat prints records in logfmt format, an easy machine-parseable but human-readable -// format for key/value pairs. -// -// For more details see: http://godoc.org/github.com/kr/logfmt -func LogfmtFormat() Format { - return FormatFunc(func(r *Record) []byte { - common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg} - buf := &bytes.Buffer{} - logfmt(buf, append(common, r.Ctx...), 0, false) - return buf.Bytes() - }) + return b.Bytes() } -func logfmt(buf *bytes.Buffer, ctx []interface{}, color int, term bool) { - for i := 0; i < len(ctx); i += 2 { - if i != 0 { - buf.WriteByte(' ') - } - - k, ok := ctx[i].(string) - v := formatLogfmtValue(ctx[i+1], term) - if !ok { - k, v = errorKey, fmt.Sprintf("%+T is not a string key", ctx[i]) +func (h *TerminalHandler) formatAttributes(buf *bytes.Buffer, r slog.Record, color string) { + // tmp is a temporary buffer we use, until bytes.Buffer.AvailableBuffer() (1.21) + // can be used. + var tmp = make([]byte, 40) + writeAttr := func(attr slog.Attr, first, last bool) { + buf.WriteByte(' ') + + if color != "" { + buf.WriteString(color) + //buf.Write(appendEscapeString(buf.AvailableBuffer(), attr.Key)) + buf.Write(appendEscapeString(tmp[:0], attr.Key)) + buf.WriteString("\x1b[0m=") } else { - k = escapeString(k) + //buf.Write(appendEscapeString(buf.AvailableBuffer(), attr.Key)) + buf.Write(appendEscapeString(tmp[:0], attr.Key)) + buf.WriteByte('=') } + //val := FormatSlogValue(attr.Value, true, buf.AvailableBuffer()) + val := FormatSlogValue(attr.Value, tmp[:0]) - // XXX: we should probably check that all of your key bytes aren't invalid - fieldPaddingLock.RLock() - padding := fieldPadding[k] - fieldPaddingLock.RUnlock() + padding := h.fieldPadding[attr.Key] - length := utf8.RuneCountInString(v) + length := utf8.RuneCount(val) if padding < length && length <= termCtxMaxPadding { padding = length - - fieldPaddingLock.Lock() - fieldPadding[k] = padding - fieldPaddingLock.Unlock() + h.fieldPadding[attr.Key] = padding } - if color > 0 { - fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=", color, k) - } else { - buf.WriteString(k) - buf.WriteByte('=') - } - buf.WriteString(v) - if i < len(ctx)-2 && padding > length { - buf.Write(bytes.Repeat([]byte{' '}, padding-length)) + buf.Write(val) + if !last && padding > length { + buf.Write(spaces[:padding-length]) } } - buf.WriteByte('\n') -} - -// JSONFormat formats log records as JSON objects separated by newlines. -// It is the equivalent of JSONFormatEx(false, true). -func JSONFormat() Format { - return JSONFormatEx(false, true) -} - -// JSONFormatOrderedEx formats log records as JSON arrays. If pretty is true, -// records will be pretty-printed. If lineSeparated is true, records -// will be logged with a new line between each record. -func JSONFormatOrderedEx(pretty, lineSeparated bool) Format { - jsonMarshal := json.Marshal - if pretty { - jsonMarshal = func(v interface{}) ([]byte, error) { - return json.MarshalIndent(v, "", " ") - } + var n = 0 + var nAttrs = len(h.attrs) + r.NumAttrs() + for _, attr := range h.attrs { + writeAttr(attr, n == 0, n == nAttrs-1) + n++ } - return FormatFunc(func(r *Record) []byte { - props := map[string]interface{}{ - r.KeyNames.Time: r.Time, - r.KeyNames.Lvl: r.Lvl.String(), - r.KeyNames.Msg: r.Msg, - } - - ctx := make([]string, len(r.Ctx)) - for i := 0; i < len(r.Ctx); i += 2 { - if k, ok := r.Ctx[i].(string); ok { - ctx[i] = k - ctx[i+1] = formatLogfmtValue(r.Ctx[i+1], true) - } else { - props[errorKey] = fmt.Sprintf("%+T is not a string key,", r.Ctx[i]) - } - } - props[r.KeyNames.Ctx] = ctx - - b, err := jsonMarshal(props) - if err != nil { - b, _ = jsonMarshal(map[string]string{ - errorKey: err.Error(), - }) - return b - } - if lineSeparated { - b = append(b, '\n') - } - return b - }) -} - -// JSONFormatEx formats log records as JSON objects. If pretty is true, -// records will be pretty-printed. If lineSeparated is true, records -// will be logged with a new line between each record. -func JSONFormatEx(pretty, lineSeparated bool) Format { - jsonMarshal := json.Marshal - if pretty { - jsonMarshal = func(v interface{}) ([]byte, error) { - return json.MarshalIndent(v, "", " ") - } - } - - return FormatFunc(func(r *Record) []byte { - props := map[string]interface{}{ - r.KeyNames.Time: r.Time, - r.KeyNames.Lvl: r.Lvl.String(), - r.KeyNames.Msg: r.Msg, - } - - for i := 0; i < len(r.Ctx); i += 2 { - k, ok := r.Ctx[i].(string) - if !ok { - props[errorKey] = fmt.Sprintf("%+T is not a string key", r.Ctx[i]) - } else { - props[k] = formatJSONValue(r.Ctx[i+1]) - } - } - - b, err := jsonMarshal(props) - if err != nil { - b, _ = jsonMarshal(map[string]string{ - errorKey: err.Error(), - }) - return b - } - - if lineSeparated { - b = append(b, '\n') - } - - return b + r.Attrs(func(attr slog.Attr) bool { + writeAttr(attr, n == 0, n == nAttrs-1) + n++ + return true }) + buf.WriteByte('\n') } -func formatShared(value interface{}) (result interface{}) { +// FormatSlogValue formats a slog.Value for serialization to terminal. +func FormatSlogValue(v slog.Value, tmp []byte) (result []byte) { + var value any defer func() { if err := recover(); err != nil { if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() { - result = "nil" + result = []byte("") } else { panic(err) } } }() - switch v := value.(type) { - case time.Time: - return v.Format(timeFormat) - - case error: - return v.Error() - - case fmt.Stringer: - return v.String() - - default: - return v - } -} - -func formatJSONValue(value interface{}) interface{} { - value = formatShared(value) - switch value.(type) { - case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string: - return value - default: - return fmt.Sprintf("%+v", value) - } -} - -// formatValue formats a value for serialization -func formatLogfmtValue(value interface{}, term bool) string { - if value == nil { - return "nil" - } - - switch v := value.(type) { - case time.Time: + switch v.Kind() { + case slog.KindString: + return appendEscapeString(tmp, v.String()) + case slog.KindInt64: // All int-types (int8, int16 etc) wind up here + return appendInt64(tmp, v.Int64()) + case slog.KindUint64: // All uint-types (uint8, uint16 etc) wind up here + return appendUint64(tmp, v.Uint64(), false) + case slog.KindFloat64: + return strconv.AppendFloat(tmp, v.Float64(), floatFormat, 3, 64) + case slog.KindBool: + return strconv.AppendBool(tmp, v.Bool()) + case slog.KindDuration: + value = v.Duration() + case slog.KindTime: // Performance optimization: No need for escaping since the provided // timeFormat doesn't have any escape characters, and escaping is // expensive. - return v.Format(timeFormat) - - case *big.Int: - // Big ints get consumed by the Stringer clause, so we need to handle - // them earlier on. - if v == nil { - return "" - } - return formatLogfmtBigInt(v) - - case *uint256.Int: - // Uint256s get consumed by the Stringer clause, so we need to handle - // them earlier on. - if v == nil { - return "" - } - return formatLogfmtUint256(v) + return v.Time().AppendFormat(tmp, timeFormat) + default: + value = v.Any() } - if term { - if s, ok := value.(TerminalStringer); ok { - // Custom terminal stringer provided, use that - return escapeString(s.TerminalString()) - } + if value == nil { + return []byte("") } - value = formatShared(value) switch v := value.(type) { - case bool: - return strconv.FormatBool(v) - case float32: - return strconv.FormatFloat(float64(v), floatFormat, 3, 64) - case float64: - return strconv.FormatFloat(v, floatFormat, 3, 64) - case int8: - return strconv.FormatInt(int64(v), 10) - case uint8: - return strconv.FormatInt(int64(v), 10) - case int16: - return strconv.FormatInt(int64(v), 10) - case uint16: - return strconv.FormatInt(int64(v), 10) - // Larger integers get thousands separators. - case int: - return FormatLogfmtInt64(int64(v)) - case int32: - return FormatLogfmtInt64(int64(v)) - case int64: - return FormatLogfmtInt64(v) - case uint: - return FormatLogfmtUint64(uint64(v)) - case uint32: - return FormatLogfmtUint64(uint64(v)) - case uint64: - return FormatLogfmtUint64(v) - case string: - return escapeString(v) - default: - return escapeString(fmt.Sprintf("%+v", value)) + case *big.Int: // Need to be before fmt.Stringer-clause + return appendBigInt(tmp, v) + case *uint256.Int: // Need to be before fmt.Stringer-clause + return appendU256(tmp, v) + case error: + return appendEscapeString(tmp, v.Error()) + case TerminalStringer: + return appendEscapeString(tmp, v.TerminalString()) + case fmt.Stringer: + return appendEscapeString(tmp, v.String()) } + + // We can use the 'tmp' as a scratch-buffer, to first format the + // value, and in a second step do escaping. + internal := fmt.Appendf(tmp, "%+v", value) + return appendEscapeString(tmp, string(internal)) } -// FormatLogfmtInt64 formats n with thousand separators. -func FormatLogfmtInt64(n int64) string { +// appendInt64 formats n with thousand separators and writes into buffer dst. +func appendInt64(dst []byte, n int64) []byte { if n < 0 { - return formatLogfmtUint64(uint64(-n), true) + return appendUint64(dst, uint64(-n), true) } - return formatLogfmtUint64(uint64(n), false) -} - -// FormatLogfmtUint64 formats n with thousand separators. -func FormatLogfmtUint64(n uint64) string { - return formatLogfmtUint64(n, false) + return appendUint64(dst, uint64(n), false) } -func formatLogfmtUint64(n uint64, neg bool) string { +// appendUint64 formats n with thousand separators and writes into buffer dst. +func appendUint64(dst []byte, n uint64, neg bool) []byte { // Small numbers are fine as is if n < 100000 { if neg { - return strconv.Itoa(-int(n)) + return strconv.AppendInt(dst, -int64(n), 10) } else { - return strconv.Itoa(int(n)) + return strconv.AppendInt(dst, int64(n), 10) } } // Large numbers should be split @@ -439,16 +220,21 @@ func formatLogfmtUint64(n uint64, neg bool) string { out[i] = '-' i-- } - return string(out[i+1:]) + return append(dst, out[i+1:]...) +} + +// FormatLogfmtUint64 formats n with thousand separators. +func FormatLogfmtUint64(n uint64) string { + return string(appendUint64(nil, n, false)) } -// formatLogfmtBigInt formats n with thousand separators. -func formatLogfmtBigInt(n *big.Int) string { +// appendBigInt formats n with thousand separators and writes to dst. +func appendBigInt(dst []byte, n *big.Int) []byte { if n.IsUint64() { - return FormatLogfmtUint64(n.Uint64()) + return appendUint64(dst, n.Uint64(), false) } if n.IsInt64() { - return FormatLogfmtInt64(n.Int64()) + return appendInt64(dst, n.Int64()) } var ( @@ -473,54 +259,48 @@ func formatLogfmtBigInt(n *big.Int) string { comma++ } } - return string(buf[i+1:]) + return append(dst, buf[i+1:]...) } -// formatLogfmtUint256 formats n with thousand separators. -func formatLogfmtUint256(n *uint256.Int) string { +// appendU256 formats n with thousand separators. +func appendU256(dst []byte, n *uint256.Int) []byte { if n.IsUint64() { - return FormatLogfmtUint64(n.Uint64()) + return appendUint64(dst, n.Uint64(), false) } - var ( - text = n.Dec() - buf = make([]byte, len(text)+len(text)/3) - comma = 0 - i = len(buf) - 1 - ) - for j := len(text) - 1; j >= 0; j, i = j-1, i-1 { - c := text[j] - - switch { - case c == '-': - buf[i] = c - case comma == 3: - buf[i] = ',' - i-- - comma = 0 - fallthrough - default: - buf[i] = c - comma++ - } - } - return string(buf[i+1:]) + res := []byte(n.PrettyDec(',')) + return append(dst, res...) } -// escapeString checks if the provided string needs escaping/quoting, and -// calls strconv.Quote if needed -func escapeString(s string) string { +// appendEscapeString writes the string s to the given writer, with +// escaping/quoting if needed. +func appendEscapeString(dst []byte, s string) []byte { needsQuoting := false + needsEscaping := false for _, r := range s { - // We quote everything below " (0x22) and above~ (0x7E), plus equal-sign - if r <= '"' || r > '~' || r == '=' { + // If it contains spaces or equal-sign, we need to quote it. + if r == ' ' || r == '=' { needsQuoting = true + continue + } + // We need to escape it, if it contains + // - character " (0x22) and lower (except space) + // - characters above ~ (0x7E), plus equal-sign + if r <= '"' || r > '~' { + needsEscaping = true break } } - if !needsQuoting { - return s + if needsEscaping { + return strconv.AppendQuote(dst, s) } - return strconv.Quote(s) + // No escaping needed, but we might have to place within quote-marks, in case + // it contained a space + if needsQuoting { + dst = append(dst, '"') + dst = append(dst, []byte(s)...) + return append(dst, '"') + } + return append(dst, []byte(s)...) } // escapeMessage checks if the provided string needs escaping/quoting, similarly @@ -545,3 +325,45 @@ func escapeMessage(s string) string { } return strconv.Quote(s) } + +// writeTimeTermFormat writes on the format "01-02|15:04:05.000" +func writeTimeTermFormat(buf *bytes.Buffer, t time.Time) { + _, month, day := t.Date() + writePosIntWidth(buf, int(month), 2) + buf.WriteByte('-') + writePosIntWidth(buf, day, 2) + buf.WriteByte('|') + hour, min, sec := t.Clock() + writePosIntWidth(buf, hour, 2) + buf.WriteByte(':') + writePosIntWidth(buf, min, 2) + buf.WriteByte(':') + writePosIntWidth(buf, sec, 2) + ns := t.Nanosecond() + buf.WriteByte('.') + writePosIntWidth(buf, ns/1e6, 3) +} + +// writePosIntWidth writes non-negative integer i to the buffer, padded on the left +// by zeroes to the given width. Use a width of 0 to omit padding. +// Adapted from golang.org/x/exp/slog/internal/buffer/buffer.go +func writePosIntWidth(b *bytes.Buffer, i, width int) { + // Cheap integer to fixed-width decimal ASCII. + // Copied from log/log.go. + if i < 0 { + panic("negative int") + } + // Assemble decimal in reverse order. + var bb [20]byte + bp := len(bb) - 1 + for i >= 10 || width > 1 { + width-- + q := i / 10 + bb[bp] = byte('0' + i - q*10) + bp-- + i = q + } + // i < 10 + bb[bp] = byte('0' + i) + b.Write(bb[bp:]) +} diff --git a/log/format_test.go b/log/format_test.go index e08c1d1a4..d4c1df4ab 100644 --- a/log/format_test.go +++ b/log/format_test.go @@ -1,161 +1,24 @@ package log import ( - "fmt" - "math" - "math/big" "math/rand" - "strings" "testing" - - "github.com/holiman/uint256" ) -func TestPrettyInt64(t *testing.T) { - tests := []struct { - n int64 - s string - }{ - {0, "0"}, - {10, "10"}, - {-10, "-10"}, - {100, "100"}, - {-100, "-100"}, - {1000, "1000"}, - {-1000, "-1000"}, - {10000, "10000"}, - {-10000, "-10000"}, - {99999, "99999"}, - {-99999, "-99999"}, - {100000, "100,000"}, - {-100000, "-100,000"}, - {1000000, "1,000,000"}, - {-1000000, "-1,000,000"}, - {math.MaxInt64, "9,223,372,036,854,775,807"}, - {math.MinInt64, "-9,223,372,036,854,775,808"}, - } - for i, tt := range tests { - if have := FormatLogfmtInt64(tt.n); have != tt.s { - t.Errorf("test %d: format mismatch: have %s, want %s", i, have, tt.s) - } - } -} - -func TestPrettyUint64(t *testing.T) { - tests := []struct { - n uint64 - s string - }{ - {0, "0"}, - {10, "10"}, - {100, "100"}, - {1000, "1000"}, - {10000, "10000"}, - {99999, "99999"}, - {100000, "100,000"}, - {1000000, "1,000,000"}, - {math.MaxUint64, "18,446,744,073,709,551,615"}, - } - for i, tt := range tests { - if have := FormatLogfmtUint64(tt.n); have != tt.s { - t.Errorf("test %d: format mismatch: have %s, want %s", i, have, tt.s) - } - } -} - -func TestPrettyBigInt(t *testing.T) { - tests := []struct { - int string - s string - }{ - {"111222333444555678999", "111,222,333,444,555,678,999"}, - {"-111222333444555678999", "-111,222,333,444,555,678,999"}, - {"11122233344455567899900", "11,122,233,344,455,567,899,900"}, - {"-11122233344455567899900", "-11,122,233,344,455,567,899,900"}, - } - - for _, tt := range tests { - v, _ := new(big.Int).SetString(tt.int, 10) - if have := formatLogfmtBigInt(v); have != tt.s { - t.Errorf("invalid output %s, want %s", have, tt.s) - } - } -} - -func TestPrettyUint256(t *testing.T) { - tests := []struct { - int string - s string - }{ - {"111222333444555678999", "111,222,333,444,555,678,999"}, - {"11122233344455567899900", "11,122,233,344,455,567,899,900"}, - } - - for _, tt := range tests { - v := new(uint256.Int) - v.SetFromDecimal(tt.int) - if have := formatLogfmtUint256(v); have != tt.s { - t.Errorf("invalid output %s, want %s", have, tt.s) - } - } -} - -var sink string +var sink []byte func BenchmarkPrettyInt64Logfmt(b *testing.B) { + buf := make([]byte, 100) b.ReportAllocs() for i := 0; i < b.N; i++ { - sink = FormatLogfmtInt64(rand.Int63()) + sink = appendInt64(buf, rand.Int63()) } } func BenchmarkPrettyUint64Logfmt(b *testing.B) { + buf := make([]byte, 100) b.ReportAllocs() for i := 0; i < b.N; i++ { - sink = FormatLogfmtUint64(rand.Uint64()) - } -} - -func TestSanitation(t *testing.T) { - msg := "\u001b[1G\u001b[K\u001b[1A" - msg2 := "\u001b \u0000" - msg3 := "NiceMessage" - msg4 := "Space Message" - msg5 := "Enter\nMessage" - - for i, tt := range []struct { - msg string - want string - }{ - { - msg: msg, - want: fmt.Sprintf("] %q %q=%q\n", msg, msg, msg), - }, - { - msg: msg2, - want: fmt.Sprintf("] %q %q=%q\n", msg2, msg2, msg2), - }, - { - msg: msg3, - want: fmt.Sprintf("] %s %s=%s\n", msg3, msg3, msg3), - }, - { - msg: msg4, - want: fmt.Sprintf("] %s %q=%q\n", msg4, msg4, msg4), - }, - { - msg: msg5, - want: fmt.Sprintf("] %s %q=%q\n", msg5, msg5, msg5), - }, - } { - var ( - logger = New() - out = new(strings.Builder) - ) - logger.SetHandler(LvlFilterHandler(LvlInfo, StreamHandler(out, TerminalFormat(false)))) - logger.Info(tt.msg, tt.msg, tt.msg) - if have := out.String()[24:]; tt.want != have { - t.Fatalf("test %d: want / have: \n%v\n%v", i, tt.want, have) - } + sink = appendUint64(buf, rand.Uint64(), false) } } diff --git a/log/handler.go b/log/handler.go index 892cfcc3e..7459aad89 100644 --- a/log/handler.go +++ b/log/handler.go @@ -1,356 +1,192 @@ package log import ( + "context" "fmt" "io" - "net" - "os" + "math/big" "reflect" "sync" + "time" - "github.com/go-stack/stack" + "github.com/holiman/uint256" + "golang.org/x/exp/slog" ) -// Handler defines where and how log records are written. -// A Logger prints its log records by writing to a Handler. -// Handlers are composable, providing you great flexibility in combining -// them to achieve the logging structure that suits your applications. -type Handler interface { - Log(r *Record) error -} +type discardHandler struct{} -// FuncHandler returns a Handler that logs records with the given -// function. -func FuncHandler(fn func(r *Record) error) Handler { - return funcHandler(fn) +// DiscardHandler returns a no-op handler +func DiscardHandler() slog.Handler { + return &discardHandler{} } -type funcHandler func(r *Record) error - -func (h funcHandler) Log(r *Record) error { - return h(r) +func (h *discardHandler) Handle(_ context.Context, r slog.Record) error { + return nil } -// StreamHandler writes log records to an io.Writer -// with the given format. StreamHandler can be used -// to easily begin writing log records to other -// outputs. -// -// StreamHandler wraps itself with LazyHandler and SyncHandler -// to evaluate Lazy objects and perform safe concurrent writes. -func StreamHandler(wr io.Writer, fmtr Format) Handler { - h := FuncHandler(func(r *Record) error { - _, err := wr.Write(fmtr.Format(r)) - return err - }) - return LazyHandler(SyncHandler(h)) +func (h *discardHandler) Enabled(_ context.Context, level slog.Level) bool { + return false } -// SyncHandler can be wrapped around a handler to guarantee that -// only a single Log operation can proceed at a time. It's necessary -// for thread-safe concurrent writes. -func SyncHandler(h Handler) Handler { - var mu sync.Mutex - return FuncHandler(func(r *Record) error { - mu.Lock() - defer mu.Unlock() - - return h.Log(r) - }) +func (h *discardHandler) WithGroup(name string) slog.Handler { + panic("not implemented") } -// FileHandler returns a handler which writes log records to the give file -// using the given format. If the path -// already exists, FileHandler will append to the given file. If it does not, -// FileHandler will create the file with mode 0644. -func FileHandler(path string, fmtr Format) (Handler, error) { - f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return nil, err - } - return closingHandler{f, StreamHandler(f, fmtr)}, nil +func (h *discardHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return &discardHandler{} } -// NetHandler opens a socket to the given address and writes records -// over the connection. -func NetHandler(network, addr string, fmtr Format) (Handler, error) { - conn, err := net.Dial(network, addr) - if err != nil { - return nil, err - } - - return closingHandler{conn, StreamHandler(conn, fmtr)}, nil -} +type TerminalHandler struct { + mu sync.Mutex + wr io.Writer + lvl slog.Level + useColor bool + attrs []slog.Attr + // fieldPadding is a map with maximum field value lengths seen until now + // to allow padding log contexts in a bit smarter way. + fieldPadding map[string]int -// XXX: closingHandler is essentially unused at the moment -// it's meant for a future time when the Handler interface supports -// a possible Close() operation -type closingHandler struct { - io.WriteCloser - Handler + buf []byte } -func (h *closingHandler) Close() error { - return h.WriteCloser.Close() +// NewTerminalHandler returns a handler which formats log records at all levels optimized for human readability on +// a terminal with color-coded level output and terser human friendly timestamp. +// This format should only be used for interactive programs or while developing. +// +// [LEVEL] [TIME] MESSAGE key=value key=value ... +// +// Example: +// +// [DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002 +func NewTerminalHandler(wr io.Writer, useColor bool) *TerminalHandler { + return NewTerminalHandlerWithLevel(wr, levelMaxVerbosity, useColor) +} + +// NewTerminalHandlerWithLevel returns the same handler as NewTerminalHandler but only outputs +// records which are less than or equal to the specified verbosity level. +func NewTerminalHandlerWithLevel(wr io.Writer, lvl slog.Level, useColor bool) *TerminalHandler { + return &TerminalHandler{ + wr: wr, + lvl: lvl, + useColor: useColor, + fieldPadding: make(map[string]int), + } } -// CallerFileHandler returns a Handler that adds the line number and file of -// the calling function to the context with key "caller". -func CallerFileHandler(h Handler) Handler { - return FuncHandler(func(r *Record) error { - r.Ctx = append(r.Ctx, "caller", fmt.Sprint(r.Call)) - return h.Log(r) - }) +func (h *TerminalHandler) Handle(_ context.Context, r slog.Record) error { + h.mu.Lock() + defer h.mu.Unlock() + buf := h.format(h.buf, r, h.useColor) + h.wr.Write(buf) + h.buf = buf[:0] + return nil } -// CallerFuncHandler returns a Handler that adds the calling function name to -// the context with key "fn". -func CallerFuncHandler(h Handler) Handler { - return FuncHandler(func(r *Record) error { - r.Ctx = append(r.Ctx, "fn", formatCall("%+n", r.Call)) - return h.Log(r) - }) +func (h *TerminalHandler) Enabled(_ context.Context, level slog.Level) bool { + return level >= h.lvl } -// This function is here to please go vet on Go < 1.8. -func formatCall(format string, c stack.Call) string { - return fmt.Sprintf(format, c) +func (h *TerminalHandler) WithGroup(name string) slog.Handler { + panic("not implemented") } -// CallerStackHandler returns a Handler that adds a stack trace to the context -// with key "stack". The stack trace is formatted as a space separated list of -// call sites inside matching []'s. The most recent call site is listed first. -// Each call site is formatted according to format. See the documentation of -// package github.com/go-stack/stack for the list of supported formats. -func CallerStackHandler(format string, h Handler) Handler { - return FuncHandler(func(r *Record) error { - s := stack.Trace().TrimBelow(r.Call).TrimRuntime() - if len(s) > 0 { - r.Ctx = append(r.Ctx, "stack", fmt.Sprintf(format, s)) - } - return h.Log(r) - }) +func (h *TerminalHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return &TerminalHandler{ + wr: h.wr, + lvl: h.lvl, + useColor: h.useColor, + attrs: append(h.attrs, attrs...), + fieldPadding: make(map[string]int), + } } -// FilterHandler returns a Handler that only writes records to the -// wrapped Handler if the given function evaluates true. For example, -// to only log records where the 'err' key is not nil: -// -// logger.SetHandler(FilterHandler(func(r *Record) bool { -// for i := 0; i < len(r.Ctx); i += 2 { -// if r.Ctx[i] == "err" { -// return r.Ctx[i+1] != nil -// } -// } -// return false -// }, h)) -func FilterHandler(fn func(r *Record) bool, h Handler) Handler { - return FuncHandler(func(r *Record) error { - if fn(r) { - return h.Log(r) - } - return nil - }) +// ResetFieldPadding zeroes the field-padding for all attribute pairs. +func (t *TerminalHandler) ResetFieldPadding() { + t.mu.Lock() + t.fieldPadding = make(map[string]int) + t.mu.Unlock() } -// MatchFilterHandler returns a Handler that only writes records -// to the wrapped Handler if the given key in the logged -// context matches the value. For example, to only log records -// from your ui package: -// -// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler) -func MatchFilterHandler(key string, value interface{}, h Handler) Handler { - return FilterHandler(func(r *Record) (pass bool) { - switch key { - case r.KeyNames.Lvl: - return r.Lvl == value - case r.KeyNames.Time: - return r.Time == value - case r.KeyNames.Msg: - return r.Msg == value - } +type leveler struct{ minLevel slog.Level } - for i := 0; i < len(r.Ctx); i += 2 { - if r.Ctx[i] == key { - return r.Ctx[i+1] == value - } - } - return false - }, h) +func (l *leveler) Level() slog.Level { + return l.minLevel } -// LvlFilterHandler returns a Handler that only writes -// records which are less than the given verbosity -// level to the wrapped Handler. For example, to only -// log Error/Crit records: -// -// log.LvlFilterHandler(log.LvlError, log.StdoutHandler) -func LvlFilterHandler(maxLvl Lvl, h Handler) Handler { - return FilterHandler(func(r *Record) (pass bool) { - return r.Lvl <= maxLvl - }, h) +// JSONHandler returns a handler which prints records in JSON format. +func JSONHandler(wr io.Writer) slog.Handler { + return slog.NewJSONHandler(wr, &slog.HandlerOptions{ + ReplaceAttr: builtinReplaceJSON, + }) } -// MultiHandler dispatches any write to each of its handlers. -// This is useful for writing different types of log information -// to different locations. For example, to log to a file and -// standard error: +// LogfmtHandler returns a handler which prints records in logfmt format, an easy machine-parseable but human-readable +// format for key/value pairs. // -// log.MultiHandler( -// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()), -// log.StderrHandler) -func MultiHandler(hs ...Handler) Handler { - return FuncHandler(func(r *Record) error { - for _, h := range hs { - // what to do about failures? - h.Log(r) - } - return nil +// For more details see: http://godoc.org/github.com/kr/logfmt +func LogfmtHandler(wr io.Writer) slog.Handler { + return slog.NewTextHandler(wr, &slog.HandlerOptions{ + ReplaceAttr: builtinReplaceLogfmt, }) } -// FailoverHandler writes all log records to the first handler -// specified, but will failover and write to the second handler if -// the first handler has failed, and so on for all handlers specified. -// For example you might want to log to a network socket, but failover -// to writing to a file if the network fails, and then to -// standard out if the file write fails: -// -// log.FailoverHandler( -// log.Must.NetHandler("tcp", ":9090", log.JSONFormat()), -// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()), -// log.StdoutHandler) -// -// All writes that do not go to the first handler will add context with keys of -// the form "failover_err_{idx}" which explain the error encountered while -// trying to write to the handlers before them in the list. -func FailoverHandler(hs ...Handler) Handler { - return FuncHandler(func(r *Record) error { - var err error - for i, h := range hs { - err = h.Log(r) - if err == nil { - return nil - } - r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err) - } - - return err +// LogfmtHandlerWithLevel returns the same handler as LogfmtHandler but it only outputs +// records which are less than or equal to the specified verbosity level. +func LogfmtHandlerWithLevel(wr io.Writer, level slog.Level) slog.Handler { + return slog.NewTextHandler(wr, &slog.HandlerOptions{ + ReplaceAttr: builtinReplaceLogfmt, + Level: &leveler{level}, }) } -// ChannelHandler writes all records to the given channel. -// It blocks if the channel is full. Useful for async processing -// of log messages, it's used by BufferedHandler. -func ChannelHandler(recs chan<- *Record) Handler { - return FuncHandler(func(r *Record) error { - recs <- r - return nil - }) +func builtinReplaceLogfmt(_ []string, attr slog.Attr) slog.Attr { + return builtinReplace(nil, attr, true) } -// BufferedHandler writes all records to a buffered -// channel of the given size which flushes into the wrapped -// handler whenever it is available for writing. Since these -// writes happen asynchronously, all writes to a BufferedHandler -// never return an error and any errors from the wrapped handler are ignored. -func BufferedHandler(bufSize int, h Handler) Handler { - recs := make(chan *Record, bufSize) - go func() { - for m := range recs { - _ = h.Log(m) - } - }() - return ChannelHandler(recs) +func builtinReplaceJSON(_ []string, attr slog.Attr) slog.Attr { + return builtinReplace(nil, attr, false) } -// LazyHandler writes all values to the wrapped handler after evaluating -// any lazy functions in the record's context. It is already wrapped -// around StreamHandler and SyslogHandler in this library, you'll only need -// it if you write your own Handler. -func LazyHandler(h Handler) Handler { - return FuncHandler(func(r *Record) error { - // go through the values (odd indices) and reassign - // the values of any lazy fn to the result of its execution - hadErr := false - for i := 1; i < len(r.Ctx); i += 2 { - lz, ok := r.Ctx[i].(Lazy) - if ok { - v, err := evaluateLazy(lz) - if err != nil { - hadErr = true - r.Ctx[i] = err - } else { - if cs, ok := v.(stack.CallStack); ok { - v = cs.TrimBelow(r.Call).TrimRuntime() - } - r.Ctx[i] = v - } +func builtinReplace(_ []string, attr slog.Attr, logfmt bool) slog.Attr { + switch attr.Key { + case slog.TimeKey: + if attr.Value.Kind() == slog.KindTime { + if logfmt { + return slog.String("t", attr.Value.Time().Format(timeFormat)) + } else { + return slog.Attr{Key: "t", Value: attr.Value} } } - - if hadErr { - r.Ctx = append(r.Ctx, errorKey, "bad lazy") + case slog.LevelKey: + if l, ok := attr.Value.Any().(slog.Level); ok { + attr = slog.Any("lvl", LevelString(l)) + return attr } - - return h.Log(r) - }) -} - -func evaluateLazy(lz Lazy) (interface{}, error) { - t := reflect.TypeOf(lz.Fn) - - if t.Kind() != reflect.Func { - return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn) - } - - if t.NumIn() > 0 { - return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn) } - if t.NumOut() == 0 { - return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn) - } - - value := reflect.ValueOf(lz.Fn) - results := value.Call([]reflect.Value{}) - if len(results) == 1 { - return results[0].Interface(), nil - } - values := make([]interface{}, len(results)) - for i, v := range results { - values[i] = v.Interface() - } - return values, nil -} - -// DiscardHandler reports success for all writes but does nothing. -// It is useful for dynamically disabling logging at runtime via -// a Logger's SetHandler method. -func DiscardHandler() Handler { - return FuncHandler(func(r *Record) error { - return nil - }) -} - -// Must provides the following Handler creation functions -// which instead of returning an error parameter only return a Handler -// and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler -var Must muster - -func must(h Handler, err error) Handler { - if err != nil { - panic(err) + switch v := attr.Value.Any().(type) { + case time.Time: + if logfmt { + attr = slog.String(attr.Key, v.Format(timeFormat)) + } + case *big.Int: + if v == nil { + attr.Value = slog.StringValue("") + } else { + attr.Value = slog.StringValue(v.String()) + } + case *uint256.Int: + if v == nil { + attr.Value = slog.StringValue("") + } else { + attr.Value = slog.StringValue(v.Dec()) + } + case fmt.Stringer: + if v == nil || (reflect.ValueOf(v).Kind() == reflect.Pointer && reflect.ValueOf(v).IsNil()) { + attr.Value = slog.StringValue("") + } else { + attr.Value = slog.StringValue(v.String()) + } } - return h -} - -type muster struct{} - -func (m muster) FileHandler(path string, fmtr Format) Handler { - return must(FileHandler(path, fmtr)) -} - -func (m muster) NetHandler(network, addr string, fmtr Format) Handler { - return must(NetHandler(network, addr, fmtr)) + return attr } diff --git a/log/handler_glog.go b/log/handler_glog.go index 6db5f1a4c..fb1e03c5b 100644 --- a/log/handler_glog.go +++ b/log/handler_glog.go @@ -17,6 +17,7 @@ package log import ( + "context" "errors" "fmt" "regexp" @@ -25,54 +26,47 @@ import ( "strings" "sync" "sync/atomic" + + "golang.org/x/exp/slog" ) // errVmoduleSyntax is returned when a user vmodule pattern is invalid. var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N") -// errTraceSyntax is returned when a user backtrace pattern is invalid. -var errTraceSyntax = errors.New("expect file.go:234") - // GlogHandler is a log handler that mimics the filtering features of Google's // glog logger: setting global log levels; overriding with callsite pattern // matches; and requesting backtraces at certain positions. type GlogHandler struct { - origin Handler // The origin handler this wraps + origin slog.Handler // The origin handler this wraps - 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 + level atomic.Int32 // Current log level, atomically accessible + override atomic.Bool // Flag whether overrides are used, atomically accessible - patterns []pattern // Current list of patterns to override with - siteCache map[uintptr]Lvl // Cache of callsite pattern evaluations - location string // file:line location where to do a stackdump at - lock sync.RWMutex // Lock protecting the override pattern list + patterns []pattern // Current list of patterns to override with + siteCache map[uintptr]slog.Level // Cache of callsite pattern evaluations + location string // file:line location where to do a stackdump at + lock sync.RWMutex // Lock protecting the override pattern list } // NewGlogHandler creates a new log handler with filtering functionality similar // to Google's glog logger. The returned handler implements Handler. -func NewGlogHandler(h Handler) *GlogHandler { +func NewGlogHandler(h slog.Handler) *GlogHandler { return &GlogHandler{ origin: h, } } -// SetHandler updates the handler to write records to the specified sub-handler. -func (h *GlogHandler) SetHandler(nh Handler) { - h.origin = nh -} - // pattern contains a filter for the Vmodule option, holding a verbosity level // and a file pattern to match. type pattern struct { pattern *regexp.Regexp - level Lvl + level slog.Level } // 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) { - h.level.Store(uint32(level)) +func (h *GlogHandler) Verbosity(level slog.Level) { + h.level.Store(int32(level)) } // Vmodule sets the glog verbosity pattern. @@ -108,11 +102,13 @@ func (h *GlogHandler) Vmodule(ruleset string) error { return errVmoduleSyntax } // Parse the level and if correct, assemble the filter rule - level, err := strconv.Atoi(parts[1]) + l, err := strconv.Atoi(parts[1]) if err != nil { return errVmoduleSyntax } - if level <= 0 { + level := FromLegacyLevel(l) + + if level == LevelCrit { continue // Ignore. It's harmless but no point in paying the overhead. } // Compile the rule pattern into a regular expression @@ -130,103 +126,84 @@ func (h *GlogHandler) Vmodule(ruleset string) error { matcher = matcher + "$" re, _ := regexp.Compile(matcher) - filter = append(filter, pattern{re, Lvl(level)}) + filter = append(filter, pattern{re, level}) } // Swap out the vmodule pattern for the new filter system h.lock.Lock() defer h.lock.Unlock() h.patterns = filter - h.siteCache = make(map[uintptr]Lvl) + h.siteCache = make(map[uintptr]slog.Level) h.override.Store(len(filter) != 0) return nil } -// BacktraceAt sets the glog backtrace location. When set to a file and line -// number holding a logging statement, a stack trace will be written to the Info -// log whenever execution hits that statement. -// -// Unlike with Vmodule, the ".go" must be present. -func (h *GlogHandler) BacktraceAt(location string) error { - // Ensure the backtrace location contains two non-empty elements - parts := strings.Split(location, ":") - if len(parts) != 2 { - return errTraceSyntax - } - parts[0] = strings.TrimSpace(parts[0]) - parts[1] = strings.TrimSpace(parts[1]) - if len(parts[0]) == 0 || len(parts[1]) == 0 { - return errTraceSyntax - } - // Ensure the .go prefix is present and the line is valid - if !strings.HasSuffix(parts[0], ".go") { - return errTraceSyntax +func (h *GlogHandler) Enabled(ctx context.Context, lvl slog.Level) bool { + // fast-track skipping logging if override not enabled and the provided verbosity is above configured + return h.override.Load() || slog.Level(h.level.Load()) <= lvl +} + +func (h *GlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + h.lock.RLock() + siteCache := make(map[uintptr]slog.Level) + for k, v := range h.siteCache { + siteCache[k] = v } - if _, err := strconv.Atoi(parts[1]); err != nil { - return errTraceSyntax + h.lock.RUnlock() + + patterns := []pattern{} + patterns = append(patterns, h.patterns...) + + res := GlogHandler{ + origin: h.origin.WithAttrs(attrs), + patterns: patterns, + siteCache: siteCache, + location: h.location, } - // All seems valid - h.lock.Lock() - defer h.lock.Unlock() - h.location = location - h.backtrace.Store(len(location) > 0) + res.level.Store(h.level.Load()) + res.override.Store(h.override.Load()) + return &res +} - return nil +func (h *GlogHandler) WithGroup(name string) slog.Handler { + panic("not implemented") } // Log implements Handler.Log, filtering a log record through the global, local // 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 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. - h.lock.RLock() - match := h.location == r.Call.String() - h.lock.RUnlock() - - if match { - // Callsite matched, raise the log level to info and gather the stacks - r.Lvl = LvlInfo - - buf := make([]byte, 1024*1024) - buf = buf[:runtime.Stack(buf, true)] - r.Msg += "\n\n" + string(buf) - } - } +func (h *GlogHandler) Handle(_ context.Context, r slog.Record) error { // If the global log level allows, fast track logging - if h.level.Load() >= uint32(r.Lvl) { - return h.origin.Log(r) - } - // If no local overrides are present, fast track skipping - if !h.override.Load() { - return nil + if slog.Level(h.level.Load()) <= r.Level { + return h.origin.Handle(context.Background(), r) } + // Check callsite cache for previously calculated log levels h.lock.RLock() - lvl, ok := h.siteCache[r.Call.Frame().PC] + lvl, ok := h.siteCache[r.PC] h.lock.RUnlock() // If we didn't cache the callsite yet, calculate it if !ok { h.lock.Lock() + + fs := runtime.CallersFrames([]uintptr{r.PC}) + frame, _ := fs.Next() + for _, rule := range h.patterns { - if rule.pattern.MatchString(fmt.Sprintf("%+s", r.Call)) { - h.siteCache[r.Call.Frame().PC], lvl, ok = rule.level, rule.level, true - break + if rule.pattern.MatchString(fmt.Sprintf("%+s", frame.File)) { + h.siteCache[r.PC], lvl, ok = rule.level, rule.level, true } } // If no rule matched, remember to drop log the next time if !ok { - h.siteCache[r.Call.Frame().PC] = 0 + h.siteCache[r.PC] = 0 } h.lock.Unlock() } - if lvl >= r.Lvl { - return h.origin.Log(r) + if lvl <= r.Level { + return h.origin.Handle(context.Background(), r) } return nil } diff --git a/log/handler_go13.go b/log/handler_go13.go deleted file mode 100644 index 4df694deb..000000000 --- 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 d0cb14aa0..000000000 --- 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 4e471a22d..75e364304 100644 --- a/log/logger.go +++ b/log/logger.go @@ -1,291 +1,210 @@ package log import ( - "fmt" + "context" + "math" "os" + "runtime" "time" - "github.com/go-stack/stack" + "golang.org/x/exp/slog" ) -const timeKey = "t" -const lvlKey = "lvl" -const msgKey = "msg" -const ctxKey = "ctx" -const errorKey = "LOG15_ERROR" -const skipLevel = 2 +const errorKey = "LOG_ERROR" -type Lvl int +const ( + legacyLevelCrit = iota + legacyLevelError + legacyLevelWarn + legacyLevelInfo + legacyLevelDebug + legacyLevelTrace +) const ( - LvlCrit Lvl = iota - LvlError - LvlWarn - LvlInfo - LvlDebug - LvlTrace + levelMaxVerbosity slog.Level = math.MinInt + LevelTrace slog.Level = -8 + LevelDebug = slog.LevelDebug + LevelInfo = slog.LevelInfo + LevelWarn = slog.LevelWarn + LevelError = slog.LevelError + LevelCrit slog.Level = 12 + + // for backward-compatibility + LvlTrace = LevelTrace + LvlInfo = LevelInfo + LvlDebug = LevelDebug ) -// AlignedString returns a 5-character string containing the name of a Lvl. -func (l Lvl) AlignedString() string { +// convert from old Geth verbosity level constants +// to levels defined by slog +func FromLegacyLevel(lvl int) slog.Level { + switch lvl { + case legacyLevelCrit: + return LevelCrit + case legacyLevelError: + return slog.LevelError + case legacyLevelWarn: + return slog.LevelWarn + case legacyLevelInfo: + return slog.LevelInfo + case legacyLevelDebug: + return slog.LevelDebug + case legacyLevelTrace: + return LevelTrace + default: + break + } + + // TODO: should we allow use of custom levels or force them to match existing max/min if they fall outside the range as I am doing here? + if lvl > legacyLevelTrace { + return LevelTrace + } + return LevelCrit +} + +// LevelAlignedString returns a 5-character string containing the name of a Lvl. +func LevelAlignedString(l slog.Level) string { switch l { - case LvlTrace: + case LevelTrace: return "TRACE" - case LvlDebug: + case slog.LevelDebug: return "DEBUG" - case LvlInfo: + case slog.LevelInfo: return "INFO " - case LvlWarn: + case slog.LevelWarn: return "WARN " - case LvlError: + case slog.LevelError: return "ERROR" - case LvlCrit: + case LevelCrit: return "CRIT " default: - panic("bad level") + return "unknown level" } } -// String returns the name of a Lvl. -func (l Lvl) String() string { +// LevelString returns a string containing the name of a Lvl. +func LevelString(l slog.Level) string { switch l { - case LvlTrace: - return "trce" - case LvlDebug: - return "dbug" - case LvlInfo: + case LevelTrace: + return "trace" + case slog.LevelDebug: + return "debug" + case slog.LevelInfo: return "info" - case LvlWarn: + case slog.LevelWarn: return "warn" - case LvlError: - return "eror" - case LvlCrit: + case slog.LevelError: + return "error" + case LevelCrit: return "crit" default: - panic("bad level") - } -} - -// LvlFromString returns the appropriate Lvl from a string name. -// Useful for parsing command line args and configuration files. -func LvlFromString(lvlString string) (Lvl, error) { - switch lvlString { - case "trace", "trce": - return LvlTrace, nil - case "debug", "dbug": - return LvlDebug, nil - case "info": - return LvlInfo, nil - case "warn": - return LvlWarn, nil - case "error", "eror": - return LvlError, nil - case "crit": - return LvlCrit, nil - default: - return LvlDebug, fmt.Errorf("unknown level: %v", lvlString) + return "unknown" } } -// A Record is what a Logger asks its handler to write -type Record struct { - Time time.Time - Lvl Lvl - Msg string - Ctx []interface{} - Call stack.Call - KeyNames RecordKeyNames -} - -// RecordKeyNames gets stored in a Record when the write function is executed. -type RecordKeyNames struct { - Time string - Msg string - Lvl string - Ctx string -} - // A Logger writes key/value pairs to a Handler type Logger interface { - // New returns a new Logger that has this logger's context plus the given context - New(ctx ...interface{}) Logger + // With returns a new Logger that has this logger's attributes plus the given attributes + With(ctx ...interface{}) Logger - // GetHandler gets the handler associated with the logger. - GetHandler() Handler + // With returns a new Logger that has this logger's attributes plus the given attributes. Identical to 'With'. + New(ctx ...interface{}) Logger - // SetHandler updates the logger to write records to the specified handler. - SetHandler(h Handler) + // Log logs a message at the specified level with context key/value pairs + Log(level slog.Level, msg string, ctx ...interface{}) - // Log a message at the trace level with context key/value pairs - // - // # Usage - // - // log.Trace("msg") - // log.Trace("msg", "key1", val1) - // log.Trace("msg", "key1", val1, "key2", val2) + // Trace log a message at the trace level with context key/value pairs Trace(msg string, ctx ...interface{}) - // Log a message at the debug level with context key/value pairs - // - // # Usage Examples - // - // log.Debug("msg") - // log.Debug("msg", "key1", val1) - // log.Debug("msg", "key1", val1, "key2", val2) + // Debug logs a message at the debug level with context key/value pairs Debug(msg string, ctx ...interface{}) - // Log a message at the info level with context key/value pairs - // - // # Usage Examples - // - // log.Info("msg") - // log.Info("msg", "key1", val1) - // log.Info("msg", "key1", val1, "key2", val2) + // Info logs a message at the info level with context key/value pairs Info(msg string, ctx ...interface{}) - // Log a message at the warn level with context key/value pairs - // - // # Usage Examples - // - // log.Warn("msg") - // log.Warn("msg", "key1", val1) - // log.Warn("msg", "key1", val1, "key2", val2) + // Warn logs a message at the warn level with context key/value pairs Warn(msg string, ctx ...interface{}) - // Log a message at the error level with context key/value pairs - // - // # Usage Examples - // - // log.Error("msg") - // log.Error("msg", "key1", val1) - // log.Error("msg", "key1", val1, "key2", val2) + // Error logs a message at the error level with context key/value pairs Error(msg string, ctx ...interface{}) - // Log a message at the crit level with context key/value pairs, and then exit. - // - // # Usage Examples - // - // log.Crit("msg") - // log.Crit("msg", "key1", val1) - // log.Crit("msg", "key1", val1, "key2", val2) + // Crit logs a message at the crit level with context key/value pairs, and exits Crit(msg string, ctx ...interface{}) + + // Write logs a message at the specified level + Write(level slog.Level, msg string, attrs ...any) + + // Enabled reports whether l emits log records at the given context and level. + Enabled(ctx context.Context, level slog.Level) bool } type logger struct { - ctx []interface{} - h *swapHandler + inner *slog.Logger } -func (l *logger) write(msg string, lvl Lvl, ctx []interface{}, skip int) { - l.h.Log(&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, - }, - }) +// NewLogger returns a logger with the specified handler set +func NewLogger(h slog.Handler) Logger { + return &logger{ + slog.New(h), + } } -func (l *logger) New(ctx ...interface{}) Logger { - child := &logger{newContext(l.ctx, ctx), new(swapHandler)} - child.SetHandler(l.h) - return child -} +// write logs a message at the specified level: +func (l *logger) Write(level slog.Level, msg string, attrs ...any) { + if !l.inner.Enabled(context.Background(), level) { + return + } -func newContext(prefix []interface{}, suffix []interface{}) []interface{} { - normalizedSuffix := normalize(suffix) - newCtx := make([]interface{}, len(prefix)+len(normalizedSuffix)) - n := copy(newCtx, prefix) - copy(newCtx[n:], normalizedSuffix) - return newCtx -} + var pcs [1]uintptr + runtime.Callers(3, pcs[:]) -func (l *logger) Trace(msg string, ctx ...interface{}) { - l.write(msg, LvlTrace, ctx, skipLevel) + if len(attrs)%2 != 0 { + attrs = append(attrs, nil, errorKey, "Normalized odd number of arguments by adding nil") + } + r := slog.NewRecord(time.Now(), level, msg, pcs[0]) + r.Add(attrs...) + l.inner.Handler().Handle(context.Background(), r) } -func (l *logger) Debug(msg string, ctx ...interface{}) { - l.write(msg, LvlDebug, ctx, skipLevel) +func (l *logger) Log(level slog.Level, msg string, attrs ...any) { + l.Write(level, msg, attrs...) } -func (l *logger) Info(msg string, ctx ...interface{}) { - l.write(msg, LvlInfo, ctx, skipLevel) +func (l *logger) With(ctx ...interface{}) Logger { + return &logger{l.inner.With(ctx...)} } -func (l *logger) Warn(msg string, ctx ...interface{}) { - l.write(msg, LvlWarn, ctx, skipLevel) +func (l *logger) New(ctx ...interface{}) Logger { + return l.With(ctx...) } -func (l *logger) Error(msg string, ctx ...interface{}) { - l.write(msg, LvlError, ctx, skipLevel) +// Enabled reports whether l emits log records at the given context and level. +func (l *logger) Enabled(ctx context.Context, level slog.Level) bool { + return l.inner.Enabled(ctx, level) } -func (l *logger) Crit(msg string, ctx ...interface{}) { - l.write(msg, LvlCrit, ctx, skipLevel) - os.Exit(1) +func (l *logger) Trace(msg string, ctx ...interface{}) { + l.Write(LevelTrace, msg, ctx...) } -func (l *logger) GetHandler() Handler { - return l.h.Get() +func (l *logger) Debug(msg string, ctx ...interface{}) { + l.Write(slog.LevelDebug, msg, ctx...) } -func (l *logger) SetHandler(h Handler) { - l.h.Swap(h) +func (l *logger) Info(msg string, ctx ...interface{}) { + l.Write(slog.LevelInfo, msg, ctx...) } -func normalize(ctx []interface{}) []interface{} { - // if the caller passed a Ctx object, then expand it - if len(ctx) == 1 { - if ctxMap, ok := ctx[0].(Ctx); ok { - ctx = ctxMap.toArray() - } - } - - // ctx needs to be even because it's a series of key/value pairs - // no one wants to check for errors on logging functions, - // so instead of erroring on bad input, we'll just make sure - // that things are the right length and users can fix bugs - // when they see the output looks wrong - if len(ctx)%2 != 0 { - ctx = append(ctx, nil, errorKey, "Normalized odd number of arguments by adding nil") - } - - return ctx +func (l *logger) Warn(msg string, ctx ...any) { + l.Write(slog.LevelWarn, msg, ctx...) } -// Lazy allows you to defer calculation of a logged value that is expensive -// to compute until it is certain that it must be evaluated with the given filters. -// -// Lazy may also be used in conjunction with a Logger's New() function -// to generate a child logger which always reports the current value of changing -// state. -// -// You may wrap any function which takes no arguments to Lazy. It may return any -// number of values of any type. -type Lazy struct { - Fn interface{} +func (l *logger) Error(msg string, ctx ...interface{}) { + l.Write(slog.LevelError, msg, ctx...) } -// Ctx is a map of key/value pairs to pass as context to a log function -// Use this only if you really need greater safety around the arguments you pass -// to the logging functions. -type Ctx map[string]interface{} - -func (c Ctx) toArray() []interface{} { - arr := make([]interface{}, len(c)*2) - - i := 0 - for k, v := range c { - arr[i] = k - arr[i+1] = v - i += 2 - } - - return arr +func (l *logger) Crit(msg string, ctx ...interface{}) { + l.Write(LevelCrit, msg, ctx...) + os.Exit(1) } diff --git a/log/logger_test.go b/log/logger_test.go new file mode 100644 index 000000000..a633f5ad7 --- /dev/null +++ b/log/logger_test.go @@ -0,0 +1,172 @@ +package log + +import ( + "bytes" + "fmt" + "io" + "math/big" + "os" + "strings" + "testing" + "time" + + "github.com/holiman/uint256" + "golang.org/x/exp/slog" +) + +// TestLoggingWithVmodule checks that vmodule works. +func TestLoggingWithVmodule(t *testing.T) { + out := new(bytes.Buffer) + glog := NewGlogHandler(NewTerminalHandlerWithLevel(out, LevelTrace, false)) + glog.Verbosity(LevelCrit) + logger := NewLogger(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 TestTerminalHandlerWithAttrs(t *testing.T) { + out := new(bytes.Buffer) + glog := NewGlogHandler(NewTerminalHandlerWithLevel(out, LevelTrace, false).WithAttrs([]slog.Attr{slog.String("baz", "bat")})) + glog.Verbosity(LevelTrace) + logger := NewLogger(glog) + 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 baz=bat foo=bar\n" + if have != want { + t.Errorf("\nhave: %q\nwant: %q\n", have, want) + } +} + +func BenchmarkTraceLogging(b *testing.B) { + SetDefault(NewLogger(NewTerminalHandler(os.Stderr, true))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Trace("a message", "v", i) + } +} + +func BenchmarkTerminalHandler(b *testing.B) { + l := NewLogger(NewTerminalHandler(io.Discard, false)) + benchmarkLogger(b, l) +} +func BenchmarkLogfmtHandler(b *testing.B) { + l := NewLogger(LogfmtHandler(io.Discard)) + benchmarkLogger(b, l) +} + +func BenchmarkJSONHandler(b *testing.B) { + l := NewLogger(JSONHandler(io.Discard)) + benchmarkLogger(b, l) +} + +func benchmarkLogger(b *testing.B, l Logger) { + var ( + bb = make([]byte, 10) + tt = time.Now() + bigint = big.NewInt(100) + nilbig *big.Int + err = fmt.Errorf("Oh nooes it's crap") + ) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + l.Info("This is a message", + "foo", int16(i), + "bytes", bb, + "bonk", "a string with text", + "time", tt, + "bigint", bigint, + "nilbig", nilbig, + "err", err) + } + b.StopTimer() +} + +func TestLoggerOutput(t *testing.T) { + type custom struct { + A string + B int8 + } + var ( + customA = custom{"Foo", 12} + customB = custom{"Foo\nLinebreak", 122} + bb = make([]byte, 10) + tt = time.Time{} + bigint = big.NewInt(100) + nilbig *big.Int + err = fmt.Errorf("Oh nooes it's crap") + smallUint = uint256.NewInt(500_000) + bigUint = &uint256.Int{0xff, 0xff, 0xff, 0xff} + ) + + out := new(bytes.Buffer) + glogHandler := NewGlogHandler(NewTerminalHandler(out, false)) + glogHandler.Verbosity(LevelInfo) + NewLogger(glogHandler).Info("This is a message", + "foo", int16(123), + "bytes", bb, + "bonk", "a string with text", + "time", tt, + "bigint", bigint, + "nilbig", nilbig, + "err", err, + "struct", customA, + "struct", customB, + "ptrstruct", &customA, + "smalluint", smallUint, + "bigUint", bigUint) + + have := out.String() + t.Logf("output %v", out.String()) + want := `INFO [11-07|19:14:33.821] This is a message foo=123 bytes="[0 0 0 0 0 0 0 0 0 0]" bonk="a string with text" time=0001-01-01T00:00:00+0000 bigint=100 nilbig= err="Oh nooes it's crap" struct="{A:Foo B:12}" struct="{A:Foo\nLinebreak B:122}" ptrstruct="&{A:Foo B:12}" smalluint=500,000 bigUint=1,600,660,942,523,603,594,864,898,306,482,794,244,293,965,082,972,225,630,372,095 +` + if !bytes.Equal([]byte(have)[25:], []byte(want)[25:]) { + t.Errorf("Error\nhave: %q\nwant: %q", have, want) + } +} + +const termTimeFormat = "01-02|15:04:05.000" + +func BenchmarkAppendFormat(b *testing.B) { + var now = time.Now() + b.Run("fmt time.Format", func(b *testing.B) { + for i := 0; i < b.N; i++ { + fmt.Fprintf(io.Discard, "%s", now.Format(termTimeFormat)) + } + }) + b.Run("time.AppendFormat", func(b *testing.B) { + for i := 0; i < b.N; i++ { + now.AppendFormat(nil, termTimeFormat) + } + }) + var buf = new(bytes.Buffer) + b.Run("time.Custom", func(b *testing.B) { + for i := 0; i < b.N; i++ { + writeTimeTermFormat(buf, now) + buf.Reset() + } + }) +} + +func TestTermTimeFormat(t *testing.T) { + var now = time.Now() + want := now.AppendFormat(nil, termTimeFormat) + var b = new(bytes.Buffer) + writeTimeTermFormat(b, now) + have := b.Bytes() + if !bytes.Equal(have, want) { + t.Errorf("have != want\nhave: %q\nwant: %q\n", have, want) + } +} diff --git a/log/root.go b/log/root.go index 5a41723c3..8662d8706 100644 --- a/log/root.go +++ b/log/root.go @@ -2,31 +2,32 @@ package log import ( "os" -) + "sync/atomic" -var ( - root = &logger{[]interface{}{}, new(swapHandler)} - StdoutHandler = StreamHandler(os.Stdout, LogfmtFormat()) - StderrHandler = StreamHandler(os.Stderr, LogfmtFormat()) + "golang.org/x/exp/slog" ) +var root atomic.Value + func init() { - root.SetHandler(DiscardHandler()) + root.Store(&logger{slog.New(DiscardHandler())}) } -// New returns a new logger with the given context. -// New is a convenient alias for Root().New -func New(ctx ...interface{}) Logger { - return root.New(ctx...) +// SetDefault sets the default global logger +func SetDefault(l Logger) { + root.Store(l) + if lg, ok := l.(*logger); ok { + slog.SetDefault(lg.inner) + } } // Root returns the root logger func Root() Logger { - return root + return root.Load().(Logger) } // The following functions bypass the exported logger methods (logger.Debug, -// etc.) to keep the call depth the same for all paths to logger.write so +// etc.) to keep the call depth the same for all paths to logger.Write so // runtime.Caller(2) always refers to the call site in client code. // Trace is a convenient alias for Root().Trace @@ -39,7 +40,7 @@ func Root() Logger { // log.Trace("msg", "key1", val1) // log.Trace("msg", "key1", val1, "key2", val2) func Trace(msg string, ctx ...interface{}) { - root.write(msg, LvlTrace, ctx, skipLevel) + Root().Write(LevelTrace, msg, ctx...) } // Debug is a convenient alias for Root().Debug @@ -52,7 +53,7 @@ func Trace(msg string, ctx ...interface{}) { // log.Debug("msg", "key1", val1) // log.Debug("msg", "key1", val1, "key2", val2) func Debug(msg string, ctx ...interface{}) { - root.write(msg, LvlDebug, ctx, skipLevel) + Root().Write(slog.LevelDebug, msg, ctx...) } // Info is a convenient alias for Root().Info @@ -65,7 +66,7 @@ func Debug(msg string, ctx ...interface{}) { // log.Info("msg", "key1", val1) // log.Info("msg", "key1", val1, "key2", val2) func Info(msg string, ctx ...interface{}) { - root.write(msg, LvlInfo, ctx, skipLevel) + Root().Write(slog.LevelInfo, msg, ctx...) } // Warn is a convenient alias for Root().Warn @@ -78,7 +79,7 @@ func Info(msg string, ctx ...interface{}) { // log.Warn("msg", "key1", val1) // log.Warn("msg", "key1", val1, "key2", val2) func Warn(msg string, ctx ...interface{}) { - root.write(msg, LvlWarn, ctx, skipLevel) + Root().Write(slog.LevelWarn, msg, ctx...) } // Error is a convenient alias for Root().Error @@ -91,7 +92,7 @@ func Warn(msg string, ctx ...interface{}) { // log.Error("msg", "key1", val1) // log.Error("msg", "key1", val1, "key2", val2) func Error(msg string, ctx ...interface{}) { - root.write(msg, LvlError, ctx, skipLevel) + Root().Write(slog.LevelError, msg, ctx...) } // Crit is a convenient alias for Root().Crit @@ -104,15 +105,12 @@ func Error(msg string, ctx ...interface{}) { // log.Crit("msg", "key1", val1) // log.Crit("msg", "key1", val1, "key2", val2) func Crit(msg string, ctx ...interface{}) { - root.write(msg, LvlCrit, ctx, skipLevel) + Root().Write(LevelCrit, msg, ctx...) os.Exit(1) } -// Output is a convenient alias for write, allowing for the modification of -// the calldepth (number of stack frames to skip). -// calldepth influences the reported line number of the log message. -// A calldepth of zero reports the immediate caller of Output. -// Non-zero calldepth skips as many stack frames. -func Output(msg string, lvl Lvl, calldepth int, ctx ...interface{}) { - root.write(msg, lvl, ctx, calldepth+skipLevel) +// New returns a new logger with the given context. +// New is a convenient alias for Root().New +func New(ctx ...interface{}) Logger { + return Root().With(ctx...) } diff --git a/log/syslog.go b/log/syslog.go deleted file mode 100644 index 451d831b6..000000000 --- a/log/syslog.go +++ /dev/null @@ -1,58 +0,0 @@ -//go:build !windows && !plan9 -// +build !windows,!plan9 - -package log - -import ( - "log/syslog" - "strings" -) - -// SyslogHandler opens a connection to the system syslog daemon by calling -// syslog.New and writes all records to it. -func SyslogHandler(priority syslog.Priority, tag string, fmtr Format) (Handler, error) { - wr, err := syslog.New(priority, tag) - return sharedSyslog(fmtr, wr, err) -} - -// SyslogNetHandler opens a connection to a log daemon over the network and writes -// all log records to it. -func SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) (Handler, error) { - wr, err := syslog.Dial(net, addr, priority, tag) - return sharedSyslog(fmtr, wr, err) -} - -func sharedSyslog(fmtr Format, sysWr *syslog.Writer, err error) (Handler, error) { - if err != nil { - return nil, err - } - h := FuncHandler(func(r *Record) error { - var syslogFn = sysWr.Info - switch r.Lvl { - case LvlCrit: - syslogFn = sysWr.Crit - case LvlError: - syslogFn = sysWr.Err - case LvlWarn: - syslogFn = sysWr.Warning - case LvlInfo: - syslogFn = sysWr.Info - case LvlDebug: - syslogFn = sysWr.Debug - case LvlTrace: - syslogFn = func(m string) error { return nil } // There's no syslog level for trace - } - - s := strings.TrimSpace(string(fmtr.Format(r))) - return syslogFn(s) - }) - return LazyHandler(&closingHandler{sysWr, h}), nil -} - -func (m muster) SyslogHandler(priority syslog.Priority, tag string, fmtr Format) Handler { - return must(SyslogHandler(priority, tag, fmtr)) -} - -func (m muster) SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) Handler { - return must(SyslogNetHandler(net, addr, priority, tag, fmtr)) -} diff --git a/metrics/counter.go b/metrics/counter.go index 55e1c5954..cb81599c2 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 d1197bb8e..15c81494e 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 f17aca330..c21bd3307 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 af26ef154..1b15b23f2 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/disk_nop.go b/metrics/disk_nop.go index 58fa4e02f..41bbe9adb 100644 --- a/metrics/disk_nop.go +++ b/metrics/disk_nop.go @@ -23,5 +23,5 @@ import "errors" // ReadDiskStats retrieves the disk IO stats belonging to the current process. func ReadDiskStats(stats *DiskStats) error { - return errors.New("Not implemented") + return errors.New("not implemented") } diff --git a/metrics/doc.go b/metrics/doc.go deleted file mode 100644 index 13f429c16..000000000 --- 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 ed95cba19..1d7a4f00c 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 5b2441916..9a91b43db 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 2b04eeab2..7e3f82a07 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 81137d7f7..68f8f11ab 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 237ff8036..967f2bc60 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 647d09000..194a18821 100644 --- a/metrics/gauge_float64_test.go +++ b/metrics/gauge_float64_test.go @@ -26,25 +26,17 @@ 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) snapshot := g.Snapshot() g.Update(float64(0)) - if v := snapshot.Value(); 47.0 != v { + if v := snapshot.Value(); v != 47.0 { t.Errorf("g.Value(): 47.0 != %v\n", v) } } @@ -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(); g.Value() != 47.0 { t.Fatal(g) } } diff --git a/metrics/gauge_info.go b/metrics/gauge_info.go new file mode 100644 index 000000000..c44b2d85f --- /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 000000000..319afbf92 --- /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 a98fe985d..f2ba930bc 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 29f72b0c4..aba752e0e 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 2c54ce8b4..44de588bc 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 7c9f42fce..22fc5468b 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 000000000..1f47f0210 --- /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 5dfbbab3e..bbc4fc024 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 000000000..c6f2eeac6 --- /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 f65d30ef9..ac5828080 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 7984898f3..0be5137d5 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 000000000..9443faedc --- /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 000000000..9443faedc --- /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 000000000..de9b207b6 --- /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 000000000..001329940 --- /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 3d45f4c7b..a86f75863 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 d1ce627a8..3b9773faa 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 e8564d6a5..22475ef6e 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 b3f6cb8c0..019c4d765 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 c206f1692..9ca8f115c 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 534c44139..2861d5f2c 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 c9fd2e75d..e81690f94 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 c43728960..4548309f9 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 2bd9bf22c..25b258d56 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 ff87c8e76..ea17aac45 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 d966fa9a8..dbdeae6c7 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 000000000..861c5f5cf --- /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 ec6e37c54..8bfbc0804 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 7cc5cf14f..75012dd4a 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 43c1129cd..c38ffcd3e 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 8e23c8eee..6802e3fce 100644 --- a/metrics/resetting_timer.go +++ b/metrics/resetting_timer.go @@ -1,22 +1,24 @@ package metrics import ( - "math" "sync" "time" - - "golang.org/x/exp/slices" ) // 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) @@ -52,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(f func()) { f() } - -// 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. @@ -130,106 +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) { - slices.Sort(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) + } + return t.max +} + +// 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 +} - t.calculated = true +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 77c49dc38..4571fc8eb 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 c68939af1..92fcbcc28 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 d53a01438..cf7e36420 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 252a878f5..5398dd42d 100644 --- a/metrics/sample.go +++ b/metrics/sample.go @@ -11,10 +11,7 @@ import ( 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 @@ -22,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) +} + // ExpDecaySample is an exponentially-decaying sample using a forward-decaying // priority reservoir. See Cormode et al's "Forward Decay: A Practical Time // Decay Model for Streaming Systems". @@ -77,72 +79,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 { +func (s *ExpDecaySample) Snapshot() SampleSnapshot { 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, + 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. @@ -150,23 +109,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) { @@ -202,207 +144,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 []int64, p float64) float64 { - return SamplePercentiles(values, []float64{p})[0] + return CalculatePercentiles(values, []float64{p})[0] } -// SamplePercentiles returns a slice of arbitrary percentiles of the slice of -// int64. -func SamplePercentiles(values []int64, 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 { - 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) - } + 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)) @@ -445,83 +340,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. @@ -544,22 +370,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 diff --git a/metrics/sample_test.go b/metrics/sample_test.go index 3ae128d56..796735705 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) { @@ -79,65 +87,42 @@ func BenchmarkUniformSample1028(b *testing.B) { benchmarkSample(b, NewUniformSample(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) + } } } } @@ -147,15 +132,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]) @@ -194,24 +180,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) } @@ -219,12 +208,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++ { @@ -250,7 +240,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) { @@ -267,7 +257,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) } @@ -295,7 +285,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) } @@ -349,8 +339,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/syslog.go b/metrics/syslog.go index f23b07e19..fd856d697 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 000000000..43fe1b2ac --- /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 2e1a9be47..bb8def82f 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(f func()) { f() } - -// 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() @@ -225,102 +106,77 @@ func (t *StandardTimer) Time(f func()) { t.Update(time.Since(ts)) } -// Record the duration of an event. +// Record the duration of an event, in nanoseconds. func (t *StandardTimer) Update(d time.Duration) { t.mutex.Lock() defer t.mutex.Unlock() - t.histogram.Update(int64(d)) + t.histogram.Update(d.Nanoseconds()) t.meter.Mark(1) } // Record the duration of an event that started at a time and ends now. +// The record uses nanoseconds. func (t *StandardTimer) UpdateSince(ts time.Time) { - t.mutex.Lock() - defer t.mutex.Unlock() - t.histogram.Update(int64(time.Since(ts))) - t.meter.Mark(1) -} - -// Variance returns the variance of the values in the sample. -func (t *StandardTimer) Variance() float64 { - return t.histogram.Variance() + t.Update(time.Since(ts)) } -// 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 903e8e8d4..f10de16c9 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 82434e9d1..098da45c2 100644 --- a/metrics/writer.go +++ b/metrics/writer.go @@ -29,16 +29,19 @@ func WriteOnce(r Registry, w io.Writer) { 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) diff --git a/miner/miner_test.go b/miner/miner_test.go index 21db1ce4a..411d6026c 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -64,6 +64,7 @@ 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 @@ -89,11 +90,16 @@ 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) } func TestMiner(t *testing.T) { + t.Parallel() miner, mux, cleanup := createMiner(t) defer cleanup(false) @@ -123,6 +129,7 @@ func TestMiner(t *testing.T) { // An initial FailedEvent should allow mining to stop on a subsequent // downloader StartEvent. func TestMinerDownloaderFirstFails(t *testing.T) { + t.Parallel() miner, mux, cleanup := createMiner(t) defer cleanup(false) @@ -156,6 +163,7 @@ func TestMinerDownloaderFirstFails(t *testing.T) { } func TestMinerStartStopAfterDownloaderEvents(t *testing.T) { + t.Parallel() miner, mux, cleanup := createMiner(t) defer cleanup(false) @@ -180,6 +188,7 @@ func TestMinerStartStopAfterDownloaderEvents(t *testing.T) { } func TestStartWhileDownload(t *testing.T) { + t.Parallel() miner, mux, cleanup := createMiner(t) defer cleanup(false) waitForMiningState(t, miner, false) @@ -194,6 +203,7 @@ func TestStartWhileDownload(t *testing.T) { } func TestStartStopMiner(t *testing.T) { + t.Parallel() miner, _, cleanup := createMiner(t) defer cleanup(false) waitForMiningState(t, miner, false) @@ -204,6 +214,7 @@ func TestStartStopMiner(t *testing.T) { } func TestCloseMiner(t *testing.T) { + t.Parallel() miner, _, cleanup := createMiner(t) defer cleanup(true) waitForMiningState(t, miner, false) @@ -217,6 +228,7 @@ func TestCloseMiner(t *testing.T) { // TestMinerSetEtherbase checks that etherbase becomes set even if mining isn't // possible at the moment func TestMinerSetEtherbase(t *testing.T) { + t.Parallel() miner, mux, cleanup := createMiner(t) defer cleanup(false) miner.Start() @@ -288,8 +300,9 @@ func createMiner(t *testing.T) (*Miner, *event.TypeMux, func(skipMiner bool)) { } // Create chainConfig chainDB := rawdb.NewMemoryDatabase() + triedb := trie.NewDatabase(chainDB, nil) genesis := minerTestGenesisBlock(15, 11_500_000, common.HexToAddress("12345")) - chainConfig, _, err := core.SetupGenesisBlock(chainDB, trie.NewDatabase(chainDB), genesis) + chainConfig, _, err := core.SetupGenesisBlock(chainDB, triedb, genesis) if err != nil { t.Fatalf("can't create new chain config: %v", err) } @@ -300,8 +313,8 @@ 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{chainConfig, 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}) diff --git a/miner/ordering_test.go b/miner/ordering_test.go index 589633e0b..e5868d7a0 100644 --- a/miner/ordering_test.go +++ b/miner/ordering_test.go @@ -30,10 +30,12 @@ import ( ) func TestTransactionPriceNonceSortLegacy(t *testing.T) { + t.Parallel() testTransactionPriceNonceSort(t, nil) } func TestTransactionPriceNonceSort1559(t *testing.T) { + t.Parallel() testTransactionPriceNonceSort(t, big.NewInt(0)) testTransactionPriceNonceSort(t, big.NewInt(5)) testTransactionPriceNonceSort(t, big.NewInt(50)) @@ -88,10 +90,12 @@ func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) { } groups[addr] = append(groups[addr], &txpool.LazyTransaction{ Hash: tx.Hash(), - Tx: &txpool.Transaction{Tx: tx}, + Tx: tx, Time: tx.Time(), GasFeeCap: tx.GasFeeCap(), GasTipCap: tx.GasTipCap(), + Gas: tx.Gas(), + BlobGas: tx.BlobGas(), }) } expectedCount += count @@ -101,7 +105,7 @@ func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) { txs := types.Transactions{} for tx := txset.Peek(); tx != nil; tx = txset.Peek() { - txs = append(txs, tx.Tx.Tx) + txs = append(txs, tx.Tx) txset.Shift() } if len(txs) != expectedCount { @@ -136,6 +140,7 @@ func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) { // 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) { + t.Parallel() // Generate a batch of accounts to start with keys := make([]*ecdsa.PrivateKey, 5) for i := 0; i < len(keys); i++ { @@ -153,10 +158,12 @@ func TestTransactionTimeSort(t *testing.T) { groups[addr] = append(groups[addr], &txpool.LazyTransaction{ Hash: tx.Hash(), - Tx: &txpool.Transaction{Tx: tx}, + 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 @@ -164,7 +171,7 @@ func TestTransactionTimeSort(t *testing.T) { txs := types.Transactions{} for tx := txset.Peek(); tx != nil; tx = txset.Peek() { - txs = append(txs, tx.Tx.Tx) + txs = append(txs, tx.Tx) txset.Shift() } if len(txs) != len(keys) { diff --git a/miner/payload_building.go b/miner/payload_building.go index 299196a3c..69ffab75b 100644 --- a/miner/payload_building.go +++ b/miner/payload_building.go @@ -33,13 +33,14 @@ import ( // BuildPayloadArgs contains the provided parameters for building payload. // Check engine-api specification for more details. -// https://github.com/ethereum/execution-apis/blob/main/src/engine/specification.md#payloadattributesv1 +// https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#payloadattributesv3 type BuildPayloadArgs struct { Parent common.Hash // The parent block to build payload on top Timestamp uint64 // The provided timestamp of generated payload 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()), "withdrawals", len(block.Withdrawals()), "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, nil, nil, nil) + return engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars) } - return engine.BlockToExecutableData(payload.empty, big.NewInt(0), nil, nil, nil) + return engine.BlockToExecutableData(payload.empty, big.NewInt(0), nil) } // ResolveEmpty is basically identical to Resolve, but it expects empty block only. @@ -131,7 +145,7 @@ func (payload *Payload) ResolveEmpty() *engine.ExecutionPayloadEnvelope { payload.lock.Lock() defer payload.lock.Unlock() - return engine.BlockToExecutableData(payload.empty, big.NewInt(0), nil, nil, nil) + return engine.BlockToExecutableData(payload.empty, big.NewInt(0), nil) } // ResolveFull is basically identical to Resolve, but it expects full block only. @@ -157,7 +171,7 @@ func (payload *Payload) ResolveFull() *engine.ExecutionPayloadEnvelope { default: close(payload.stop) } - return engine.BlockToExecutableData(payload.full, payload.fullFees, nil, nil, nil) + return engine.BlockToExecutableData(payload.full, payload.fullFees, payload.sidecars) } // buildPayload builds the payload according to the provided parameters. @@ -165,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. @@ -185,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 6f5736344..928363522 100644 --- a/miner/payload_building_test.go +++ b/miner/payload_building_test.go @@ -30,6 +30,7 @@ import ( ) func TestBuildPayload(t *testing.T) { + t.Parallel() var ( db = rawdb.NewMemoryDatabase() recipient = common.HexToAddress("0xdeadbeef") @@ -82,6 +83,7 @@ func TestBuildPayload(t *testing.T) { } func TestPayloadId(t *testing.T) { + t.Parallel() ids := make(map[string]int) for i, tt := range []*BuildPayloadArgs{ { diff --git a/miner/stress/clique/main.go b/miner/stress/clique/main.go index 53ff2450c..13336cd83 100644 --- a/miner/stress/clique/main.go +++ b/miner/stress/clique/main.go @@ -30,7 +30,6 @@ 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" @@ -46,7 +45,7 @@ import ( ) func main() { - log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true))) fdlimit.Raise(2048) // Generate a batch of accounts to seal and fund with @@ -133,7 +132,7 @@ func main() { if err != nil { panic(err) } - if err := backend.TxPool().Add([]*txpool.Transaction{{Tx: tx}}, true, false); err != nil { + if err := backend.TxPool().Add([]*types.Transaction{tx}, true, false); err != nil { panic(err) } nonces[index]++ @@ -148,7 +147,7 @@ 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 Seplia config + // Create a Clique network based off of the Sepolia config genesis := core.DefaultSepoliaGenesisBlock() genesis.GasLimit = 25000000 diff --git a/miner/worker.go b/miner/worker.go index 97967ea2f..2ed91cc18 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -27,10 +27,12 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "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" @@ -89,6 +91,8 @@ type environment struct { header *types.Header txs []*types.Transaction receipts []*types.Receipt + sidecars []*types.BlobTxSidecar + blobs int } // copy creates a deep copy of environment. @@ -107,6 +111,10 @@ func (env *environment) copy() *environment { } cpy.txs = make([]*types.Transaction, len(env.txs)) copy(cpy.txs, env.txs) + + cpy.sidecars = make([]*types.BlobTxSidecar, len(env.sidecars)) + copy(cpy.sidecars, env.sidecars) + return cpy } @@ -141,11 +149,12 @@ type newWorkReq struct { 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. @@ -254,8 +263,8 @@ 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) @@ -516,12 +525,7 @@ func (w *worker) mainLoop() { 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, - } + req.result <- w.generateWork(req.params) case ev := <-w.txsCh: // Apply transactions to the pending state if we're not sealing @@ -538,11 +542,14 @@ func (w *worker) mainLoop() { for _, tx := range ev.Txs { acc, _ := types.Sender(w.current.signer, 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: &txpool.Transaction{Tx: tx}, + 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 := newTransactionsByPriceAndNonce(w.current.signer, txs, w.current.header.BaseFee) @@ -734,21 +741,55 @@ func (w *worker) updateSnapshot(env *environment) { w.snapshotState = env.state.Copy() } -func (w *worker) commitTransaction(env *environment, tx *txpool.Transaction) ([]*types.Log, error) { +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() ) - receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx.Tx, &env.header.GasUsed, *w.chain.GetVMConfig()) + receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig()) if err != nil { env.state.RevertToSnapshot(snap) env.gasPool.SetGas(gp) - return nil, err } - env.txs = append(env.txs, tx.Tx) - env.receipts = append(env.receipts, receipt) - - return receipt.Logs, nil + return receipt, err } func (w *worker) commitTransactions(env *environment, txs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error { @@ -775,33 +816,43 @@ func (w *worker) commitTransactions(env *environment, txs *transactionsByPriceAn 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.Warn("Ignoring evicted transaction") - + 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.Tx) + from, _ := types.Sender(env.signer, tx) // 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.Tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) { - log.Trace("Ignoring reply protected transaction", "hash", tx.Tx.Hash(), "eip155", w.chainConfig.EIP155Block) - + if tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) { + log.Trace("Ignoring replay protected transaction", "hash", ltx.Hash, "eip155", w.chainConfig.EIP155Block) txs.Pop() continue } // Start executing the transaction - env.state.SetTxContext(tx.Tx.Hash(), env.tcount) + env.state.SetTxContext(tx.Hash(), env.tcount) logs, err := w.commitTransaction(env, tx) 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.Tx.Nonce()) + log.Trace("Skipping transaction with low nonce", "hash", ltx.Hash, "sender", from, "nonce", tx.Nonce()) txs.Shift() case errors.Is(err, nil): @@ -813,7 +864,7 @@ func (w *worker) commitTransactions(env *environment, txs *transactionsByPriceAn 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.Tx.Hash(), "err", err) + log.Debug("Transaction failed, account skipped", "hash", ltx.Hash, "err", err) txs.Pop() } } @@ -843,6 +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. + beaconRoot *common.Hash // The beacon root (cancun field). noTxs bool // Flag whether an empty block without any transaction is expected } @@ -895,6 +947,19 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { header.GasLimit = core.CalcGasLimit(parentGasLimit, w.config.GasCeil) } } + // Apply EIP-4844, EIP-4788. + if w.chainConfig.IsCancun(header.Number, header.Time) { + var excessBlobGas uint64 + if w.chainConfig.IsCancun(parent.Number, parent.Time) { + 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) @@ -908,6 +973,11 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { log.Error("Failed to create sealing context", "err", err) return nil, err } + 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 } @@ -915,10 +985,9 @@ 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) + // 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 { @@ -926,6 +995,8 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err localTxs[account] = txs } } + + // Fill the block with all available pending transactions. if len(localTxs) > 0 { txs := newTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee) if err := w.commitTransactions(env, txs, interrupt); err != nil { @@ -942,10 +1013,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() @@ -963,9 +1034,13 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, e } 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 @@ -999,7 +1074,7 @@ func (w *worker) commitWork(interrupt *atomic.Int32, timestamp int64) { case err == nil: // The entire block is filled, decrease resubmit interval in case // of current interval is larger than the user-specified one. - w.resubmitAdjustCh <- &intervalAdjust{inc: false} + w.adjustResubmitInterval(&intervalAdjust{inc: false}) case errors.Is(err, errBlockInterruptedByRecommit): // Notify resubmit loop to increase resubmitting interval if the @@ -1009,10 +1084,10 @@ func (w *worker) commitWork(interrupt *atomic.Int32, timestamp int64) { if ratio < 0.1 { ratio = 0.1 } - w.resubmitAdjustCh <- &intervalAdjust{ + w.adjustResubmitInterval(&intervalAdjust{ ratio: ratio, inc: true, - } + }) case errors.Is(err, errBlockInterruptedByNewHead): // If the block building is interrupted by newhead event, discard it @@ -1074,28 +1149,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, - 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")} } } @@ -1106,6 +1169,15 @@ func (w *worker) isTTDReached(header *types.Header) bool { return td != nil && ttd != nil && td.Cmp(ttd) >= 0 } +// adjustResubmitInterval adjusts the resubmit interval. +func (w *worker) adjustResubmitInterval(message *intervalAdjust) { + select { + case w.resubmitAdjustCh <- message: + default: + log.Warn("the resubmitAdjustCh is full, discard the message") + } +} + // copyReceipts makes a deep copy of the given receipts. func copyReceipts(receipts []*types.Receipt) []*types.Receipt { result := make([]*types.Receipt, len(receipts)) diff --git a/miner/worker_test.go b/miner/worker_test.go index 80557d99b..59fbbbcdc 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -63,7 +63,7 @@ var ( testUserAddress = crypto.PubkeyToAddress(testUserKey.PublicKey) // Test transactions - pendingTxs []*txpool.Transaction + pendingTxs []*types.Transaction newTxs []*types.Transaction testConfig = &Config{ @@ -93,7 +93,7 @@ func init() { Gas: params.TxGas, GasPrice: big.NewInt(params.InitialBaseFee), }) - pendingTxs = append(pendingTxs, &txpool.Transaction{Tx: tx1}) + pendingTxs = append(pendingTxs, tx1) tx2 := types.MustSignNewTx(testBankKey, signer, &types.LegacyTx{ Nonce: 1, @@ -167,6 +167,7 @@ func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consens } func TestGenerateAndImportBlock(t *testing.T) { + t.Parallel() var ( db = rawdb.NewMemoryDatabase() config = *params.AllCliqueProtocolChanges @@ -194,8 +195,8 @@ func TestGenerateAndImportBlock(t *testing.T) { w.start() for i := 0; i < 5; i++ { - b.txPool.Add([]*txpool.Transaction{{Tx: b.newRandomTx(true)}}, true, false) - b.txPool.Add([]*txpool.Transaction{{Tx: b.newRandomTx(false)}}, true, false) + 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(): @@ -210,9 +211,11 @@ func TestGenerateAndImportBlock(t *testing.T) { } func TestEmptyWorkEthash(t *testing.T) { + t.Parallel() testEmptyWork(t, ethashChainConfig, ethash.NewFaker()) } func TestEmptyWorkClique(t *testing.T) { + t.Parallel() testEmptyWork(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase())) } @@ -252,10 +255,12 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens } func TestAdjustIntervalEthash(t *testing.T) { + t.Parallel() testAdjustInterval(t, ethashChainConfig, ethash.NewFaker()) } func TestAdjustIntervalClique(t *testing.T) { + t.Parallel() testAdjustInterval(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase())) } @@ -346,14 +351,17 @@ func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine co } func TestGetSealingWorkEthash(t *testing.T) { + t.Parallel() testGetSealingWork(t, ethashChainConfig, ethash.NewFaker()) } func TestGetSealingWorkClique(t *testing.T) { + t.Parallel() testGetSealingWork(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, rawdb.NewMemoryDatabase())) } func TestGetSealingWorkPostMerge(t *testing.T) { + t.Parallel() local := new(params.ChainConfig) *local = *ethashChainConfig local.TerminalTotalDifficulty = big.NewInt(0) @@ -452,32 +460,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/defaults.go b/node/defaults.go index d8f718121..42d9d4cde 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 diff --git a/node/node.go b/node/node.go index da41169c5..41c9971fe 100644 --- a/node/node.go +++ b/node/node.go @@ -449,8 +449,11 @@ func (n *Node) startRPC() error { if err := server.setListenAddr(n.config.AuthAddr, port); err != nil { return err } - sharedConfig := rpcConfig - sharedConfig.jwtSecret = secret + sharedConfig := rpcEndpointConfig{ + jwtSecret: secret, + batchItemLimit: engineAPIBatchItemLimit, + batchResponseSizeLimit: engineAPIBatchResponseSizeLimit, + } if err := server.enableRPC(allAPIs, httpConfig{ CorsAllowedOrigins: DefaultAuthCors, Vhosts: n.config.AuthVirtualHosts, diff --git a/oss-fuzz.sh b/oss-fuzz.sh index 745a5ba7c..8978de70d 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -1,5 +1,5 @@ -#/bin/bash -eu -# Copyright 2020 Google Inc. +#!/bin/bash -eu +# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,17 +15,6 @@ # ################################################################################ -# This file is for integration with Google OSS-Fuzz. -# The following ENV variables are available when executing on OSS-fuzz: -# -# /out/ $OUT Directory to store build artifacts (fuzz targets, dictionaries, options files, seed corpus archives). -# /src/ $SRC Directory to checkout source files. -# /work/ $WORK Directory to store intermediate files. -# -# $CC, $CXX, $CCC The C and C++ compiler binaries. -# $CFLAGS, $CXXFLAGS C and C++ compiler flags. -# $LIB_FUZZING_ENGINE C++ compiler argument to link fuzz target against the prebuilt engine library (e.g. libFuzzer). - # This sets the -coverpgk for the coverage report when the corpus is executed through go test coverpkg="github.com/ethereum/go-ethereum/..." @@ -59,26 +48,27 @@ DOG cd - } -function compile_fuzzer { - # Inputs: - # $1: The package to fuzz, within go-ethereum - # $2: The name of the fuzzing function - # $3: The name to give to the final fuzzing-binary - - path=$GOPATH/src/github.com/ethereum/go-ethereum/$1 - func=$2 +function compile_fuzzer() { + package=$1 + function=$2 fuzzer=$3 + file=$4 + + path=$GOPATH/src/$package echo "Building $fuzzer" + cd $path - # Do a coverage-build or a regular build - if [[ $SANITIZER = *coverage* ]]; then - coverbuild $path $func $fuzzer $coverpkg - else - (cd $path && \ - go-fuzz -func $func -o $WORK/$fuzzer.a . && \ - $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $WORK/$fuzzer.a -o $OUT/$fuzzer) - fi + # Install build dependencies + go mod tidy + go get github.com/holiman/gofuzz-shim/testing + + if [[ $SANITIZER == *coverage* ]]; then + coverbuild $path $function $fuzzer $coverpkg + else + gofuzz-shim --func $function --package $package -f $file -o $fuzzer.a + $CXX $CXXFLAGS $LIB_FUZZING_ENGINE $fuzzer.a -o $OUT/$fuzzer + fi ## Check if there exists a seed corpus file corpusfile="${path}/testdata/${fuzzer}_seed_corpus.zip" @@ -87,43 +77,143 @@ function compile_fuzzer { cp $corpusfile $OUT/ echo "Found seed corpus: $corpusfile" fi + cd - } -compile_fuzzer tests/fuzzers/bitutil Fuzz fuzzBitutilCompress -compile_fuzzer tests/fuzzers/bn256 FuzzAdd fuzzBn256Add -compile_fuzzer tests/fuzzers/bn256 FuzzMul fuzzBn256Mul -compile_fuzzer tests/fuzzers/bn256 FuzzPair fuzzBn256Pair -compile_fuzzer tests/fuzzers/runtime Fuzz fuzzVmRuntime -compile_fuzzer tests/fuzzers/keystore Fuzz fuzzKeystore -compile_fuzzer tests/fuzzers/txfetcher Fuzz fuzzTxfetcher -compile_fuzzer tests/fuzzers/rlp Fuzz fuzzRlp -compile_fuzzer tests/fuzzers/trie Fuzz fuzzTrie -compile_fuzzer tests/fuzzers/stacktrie Fuzz fuzzStackTrie -compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty -compile_fuzzer tests/fuzzers/abi Fuzz fuzzAbi -compile_fuzzer tests/fuzzers/les Fuzz fuzzLes -compile_fuzzer tests/fuzzers/secp256k1 Fuzz fuzzSecp256k1 -compile_fuzzer tests/fuzzers/vflux FuzzClientPool fuzzClientPool - -compile_fuzzer tests/fuzzers/bls12381 FuzzG1Add fuzz_g1_add -compile_fuzzer tests/fuzzers/bls12381 FuzzG1Mul fuzz_g1_mul -compile_fuzzer tests/fuzzers/bls12381 FuzzG1MultiExp fuzz_g1_multiexp -compile_fuzzer tests/fuzzers/bls12381 FuzzG2Add fuzz_g2_add -compile_fuzzer tests/fuzzers/bls12381 FuzzG2Mul fuzz_g2_mul -compile_fuzzer tests/fuzzers/bls12381 FuzzG2MultiExp fuzz_g2_multiexp -compile_fuzzer tests/fuzzers/bls12381 FuzzPairing fuzz_pairing -compile_fuzzer tests/fuzzers/bls12381 FuzzMapG1 fuzz_map_g1 -compile_fuzzer tests/fuzzers/bls12381 FuzzMapG2 fuzz_map_g2 - -compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG1Add fuzz_cross_g1_add -compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG1MultiExp fuzz_cross_g1_multiexp -compile_fuzzer tests/fuzzers/bls12381 FuzzCrossG2Add fuzz_cross_g2_add -compile_fuzzer tests/fuzzers/bls12381 FuzzCrossPairing fuzz_cross_pairing - -compile_fuzzer tests/fuzzers/snap FuzzARange fuzz_account_range -compile_fuzzer tests/fuzzers/snap FuzzSRange fuzz_storage_range -compile_fuzzer tests/fuzzers/snap FuzzByteCodes fuzz_byte_codes -compile_fuzzer tests/fuzzers/snap FuzzTrieNodes fuzz_trie_nodes - -#TODO: move this to tests/fuzzers, if possible -compile_fuzzer crypto/blake2b Fuzz fuzzBlake2b +go install github.com/holiman/gofuzz-shim@latest +repo=$GOPATH/src/github.com/ethereum/go-ethereum +compile_fuzzer github.com/ethereum/go-ethereum/accounts/abi \ + FuzzABI fuzzAbi \ + $repo/accounts/abi/abifuzzer_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/common/bitutil \ + FuzzEncoder fuzzBitutilEncoder \ + $repo/common/bitutil/compress_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/common/bitutil \ + FuzzDecoder fuzzBitutilDecoder \ + $repo/common/bitutil/compress_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/core/vm/runtime \ + FuzzVmRuntime fuzzVmRuntime\ + $repo/core/vm/runtime/runtime_fuzz_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/core/vm \ + FuzzPrecompiledContracts fuzzPrecompiledContracts\ + $repo/core/vm/contracts_fuzz_test.go,$repo/core/vm/contracts_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/core/types \ + FuzzRLP fuzzRlp \ + $repo/core/types/rlp_fuzzer_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/crypto/blake2b \ + Fuzz fuzzBlake2b \ + $repo/crypto/blake2b/blake2b_f_fuzz_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/accounts/keystore \ + FuzzPassword fuzzKeystore \ + $repo/accounts/keystore/keystore_fuzzing_test.go + +pkg=$repo/trie/ +compile_fuzzer github.com/ethereum/go-ethereum/trie \ + FuzzTrie fuzzTrie \ + $pkg/trie_test.go,$pkg/database_test.go,$pkg/tracer_test.go,$pkg/proof_test.go,$pkg/iterator_test.go,$pkg/sync_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/trie \ + FuzzStackTrie fuzzStackTrie \ + $pkg/stacktrie_fuzzer_test.go,$pkg/iterator_test.go,$pkg/trie_test.go,$pkg/database_test.go,$pkg/tracer_test.go,$pkg/proof_test.go,$pkg/sync_test.go + +#compile_fuzzer tests/fuzzers/snap FuzzARange fuzz_account_range +compile_fuzzer github.com/ethereum/go-ethereum/eth/protocols/snap \ + FuzzARange fuzz_account_range \ + $repo/eth/protocols/snap/handler_fuzzing_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/eth/protocols/snap \ + FuzzSRange fuzz_storage_range \ + $repo/eth/protocols/snap/handler_fuzzing_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/eth/protocols/snap \ + FuzzByteCodes fuzz_byte_codes \ + $repo/eth/protocols/snap/handler_fuzzing_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/eth/protocols/snap \ + FuzzTrieNodes fuzz_trie_nodes\ + $repo/eth/protocols/snap/handler_fuzzing_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bn256 \ + FuzzAdd fuzzBn256Add\ + $repo/tests/fuzzers/bn256/bn256_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bn256 \ + FuzzMul fuzzBn256Mul \ + $repo/tests/fuzzers/bn256/bn256_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bn256 \ + FuzzPair fuzzBn256Pair \ + $repo/tests/fuzzers/bn256/bn256_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/txfetcher \ + Fuzz fuzzTxfetcher \ + $repo/tests/fuzzers/txfetcher/txfetcher_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzG1Add fuzz_g1_add\ + $repo/tests/fuzzers/bls12381/bls12381_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzG1Mul fuzz_g1_mul\ + $repo/tests/fuzzers/bls12381/bls12381_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzG1MultiExp fuzz_g1_multiexp \ + $repo/tests/fuzzers/bls12381/bls12381_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzG2Add fuzz_g2_add \ + $repo/tests/fuzzers/bls12381/bls12381_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzG2Mul fuzz_g2_mul\ + $repo/tests/fuzzers/bls12381/bls12381_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzG2MultiExp fuzz_g2_multiexp \ + $repo/tests/fuzzers/bls12381/bls12381_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzPairing fuzz_pairing \ + $repo/tests/fuzzers/bls12381/bls12381_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzMapG1 fuzz_map_g1\ + $repo/tests/fuzzers/bls12381/bls12381_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzMapG2 fuzz_map_g2 \ + $repo/tests/fuzzers/bls12381/bls12381_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzCrossG1Add fuzz_cross_g1_add \ + $repo/tests/fuzzers/bls12381/bls12381_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzCrossG1MultiExp fuzz_cross_g1_multiexp \ + $repo/tests/fuzzers/bls12381/bls12381_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzCrossG2Add fuzz_cross_g2_add \ + $repo/tests/fuzzers/bls12381/bls12381_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzCrossPairing fuzz_cross_pairing\ + $repo/tests/fuzzers/bls12381/bls12381_test.go + +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/secp256k1 \ + Fuzz fuzzSecp256k1\ + $repo/tests/fuzzers/secp256k1/secp_test.go + + +#compile_fuzzer tests/fuzzers/vflux FuzzClientPool fuzzClientPool +#compile_fuzzer tests/fuzzers/difficulty Fuzz fuzzDifficulty +#compile_fuzzer tests/fuzzers/les Fuzz fuzzLes + diff --git a/p2p/discover/table.go b/p2p/discover/table.go index f476d2079..2b7a28708 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -23,6 +23,7 @@ package discover import ( + "context" crand "crypto/rand" "encoding/binary" "fmt" @@ -330,8 +331,10 @@ func (tab *Table) loadSeedNodes() { seeds = append(seeds, tab.nursery...) for i := range seeds { seed := seeds[i] - age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP())) }} - tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age) + if tab.log.Enabled(context.Background(), log.LevelTrace) { + age := time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP())) + tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age) + } tab.addSeenNode(seed) } } @@ -456,6 +459,26 @@ func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) * return nodes } +// appendLiveNodes adds nodes at the given distance to the result slice. +func (tab *Table) appendLiveNodes(dist uint, result []*enode.Node) []*enode.Node { + if dist > 256 { + return result + } + if dist == 0 { + return append(result, tab.self()) + } + + tab.mutex.Lock() + defer tab.mutex.Unlock() + for _, n := range tab.bucketAtDistance(int(dist)).entries { + if n.livenessChecks >= 1 { + node := n.Node // avoid handing out pointer to struct field + result = append(result, &node) + } + } + return result +} + // len returns the number of nodes in the table. func (tab *Table) len() (n int) { tab.mutex.Lock() diff --git a/p2p/discover/table_test.go b/p2p/discover/table_test.go index 2781dd422..3ba342225 100644 --- a/p2p/discover/table_test.go +++ b/p2p/discover/table_test.go @@ -199,7 +199,7 @@ func TestTable_findnodeByID(t *testing.T) { tab, db := newTestTable(transport) defer db.Close() defer tab.close() - fillTable(tab, test.All) + fillTable(tab, test.All, true) // check that closest(Target, N) returns nodes result := tab.findnodeByID(test.Target, test.N, false).entries diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go index 8f3813bdc..d6309dfd6 100644 --- a/p2p/discover/table_util_test.go +++ b/p2p/discover/table_util_test.go @@ -109,8 +109,11 @@ func fillBucket(tab *Table, n *node) (last *node) { // fillTable adds nodes the table to the end of their corresponding bucket // if the bucket is not full. The caller must not hold tab.mutex. -func fillTable(tab *Table, nodes []*node) { +func fillTable(tab *Table, nodes []*node, setLive bool) { for _, n := range nodes { + if setLive { + n.livenessChecks = 1 + } tab.addSeenNode(n) } } diff --git a/p2p/discover/v4_lookup_test.go b/p2p/discover/v4_lookup_test.go index 1f9ad69d0..8867a5a8a 100644 --- a/p2p/discover/v4_lookup_test.go +++ b/p2p/discover/v4_lookup_test.go @@ -40,7 +40,7 @@ func TestUDPv4_Lookup(t *testing.T) { } // Seed table with initial node. - fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))}) + fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))}, true) // Start the lookup. resultC := make(chan []*enode.Node, 1) @@ -74,7 +74,7 @@ func TestUDPv4_LookupIterator(t *testing.T) { for i := range lookupTestnet.dists[256] { bootnodes[i] = wrapNode(lookupTestnet.node(256, i)) } - fillTable(test.table, bootnodes) + fillTable(test.table, bootnodes, true) go serveTestnet(test, lookupTestnet) // Create the iterator and collect the nodes it yields. @@ -109,7 +109,7 @@ func TestUDPv4_LookupIteratorClose(t *testing.T) { for i := range lookupTestnet.dists[256] { bootnodes[i] = wrapNode(lookupTestnet.node(256, i)) } - fillTable(test.table, bootnodes) + fillTable(test.table, bootnodes, true) go serveTestnet(test, lookupTestnet) it := test.udp.RandomNodes() diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index 5add9cefa..361e37962 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -269,7 +269,7 @@ func TestUDPv4_findnode(t *testing.T) { } nodes.push(n, numCandidates) } - fillTable(test.table, nodes.entries) + fillTable(test.table, nodes.entries, false) // ensure there's a bond with the test node, // findnode won't be accepted otherwise. @@ -557,12 +557,7 @@ func startLocalhostV4(t *testing.T, cfg Config) *UDPv4 { // Prefix logs with node ID. lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString()) - lfmt := log.TerminalFormat(false) - cfg.Log = testlog.Logger(t, log.LvlTrace) - cfg.Log.SetHandler(log.FuncHandler(func(r *log.Record) error { - t.Logf("%s %s", lprefix, lfmt.Format(r)) - return nil - })) + cfg.Log = testlog.Logger(t, log.LevelTrace).With("node-id", lprefix) // Listen. socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}}) diff --git a/p2p/discover/v4wire/v4wire.go b/p2p/discover/v4wire/v4wire.go index 3935068cd..9c59359fb 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 6ba7a9061..8b3e33d37 100644 --- a/p2p/discover/v5_udp.go +++ b/p2p/discover/v5_udp.go @@ -851,6 +851,7 @@ func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *ne // collectTableNodes creates a FINDNODE result set for the given distances. func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node { + var bn []*enode.Node var nodes []*enode.Node var processed = make(map[uint]struct{}) for _, dist := range distances { @@ -859,21 +860,11 @@ func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*en if seen || dist > 256 { continue } - - // Get the nodes. - var bn []*enode.Node - if dist == 0 { - bn = []*enode.Node{t.Self()} - } else if dist <= 256 { - t.tab.mutex.Lock() - bn = unwrapNodes(t.tab.bucketAtDistance(int(dist)).entries) - t.tab.mutex.Unlock() - } processed[dist] = struct{}{} - // Apply some pre-checks to avoid sending invalid nodes. - for _, n := range bn { - // TODO livenessChecks > 1 + for _, n := range t.tab.appendLiveNodes(dist, bn[:0]) { + // Apply some pre-checks to avoid sending invalid nodes. + // Note liveness is checked by appendLiveNodes. if netutil.CheckRelayIP(rip, n.IP()) != nil { continue } diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go index 880b71a99..eaa969ea8 100644 --- a/p2p/discover/v5_udp_test.go +++ b/p2p/discover/v5_udp_test.go @@ -79,12 +79,7 @@ func startLocalhostV5(t *testing.T, cfg Config) *UDPv5 { // Prefix logs with node ID. lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString()) - lfmt := log.TerminalFormat(false) - cfg.Log = testlog.Logger(t, log.LvlTrace) - cfg.Log.SetHandler(log.FuncHandler(func(r *log.Record) error { - t.Logf("%s %s", lprefix, lfmt.Format(r)) - return nil - })) + cfg.Log = testlog.Logger(t, log.LevelTrace).With("node-id", lprefix) // Listen. socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}}) @@ -164,9 +159,9 @@ func TestUDPv5_findnodeHandling(t *testing.T) { nodes253 := nodesAtDistance(test.table.self().ID(), 253, 16) nodes249 := nodesAtDistance(test.table.self().ID(), 249, 4) nodes248 := nodesAtDistance(test.table.self().ID(), 248, 10) - fillTable(test.table, wrapNodes(nodes253)) - fillTable(test.table, wrapNodes(nodes249)) - fillTable(test.table, wrapNodes(nodes248)) + fillTable(test.table, wrapNodes(nodes253), true) + fillTable(test.table, wrapNodes(nodes249), true) + fillTable(test.table, wrapNodes(nodes248), true) // Requesting with distance zero should return the node's own record. test.packetIn(&v5wire.Findnode{ReqID: []byte{0}, Distances: []uint{0}}) @@ -594,7 +589,7 @@ func TestUDPv5_lookup(t *testing.T) { // Seed table with initial node. initialNode := lookupTestnet.node(256, 0) - fillTable(test.table, []*node{wrapNode(initialNode)}) + fillTable(test.table, []*node{wrapNode(initialNode)}, true) // Start the lookup. resultC := make(chan []*enode.Node, 1) diff --git a/p2p/msgrate/msgrate.go b/p2p/msgrate/msgrate.go index 4f0879224..de1a3177d 100644 --- a/p2p/msgrate/msgrate.go +++ b/p2p/msgrate/msgrate.go @@ -18,6 +18,7 @@ package msgrate import ( + "context" "errors" "math" "sort" @@ -410,7 +411,9 @@ func (t *Trackers) tune() { t.tuned = time.Now() t.log.Debug("Recalculated msgrate QoS values", "rtt", t.roundtrip, "confidence", t.confidence, "ttl", t.targetTimeout(), "next", t.tuned.Add(t.roundtrip)) - t.log.Trace("Debug dump of mean capacities", "caps", log.Lazy{Fn: t.meanCapacities}) + if t.log.Enabled(context.Background(), log.LevelTrace) { + t.log.Trace("Debug dump of mean capacities", "caps", t.meanCapacities()) + } } // detune reduces the tracker's confidence in order to make fresh measurements diff --git a/p2p/nat/nat.go b/p2p/nat/nat.go index 61b692298..2aa1f8558 100644 --- a/p2p/nat/nat.go +++ b/p2p/nat/nat.go @@ -61,12 +61,12 @@ type Interface interface { // "pmp:192.168.0.1" uses NAT-PMP with the given gateway address func Parse(spec string) (Interface, error) { var ( - parts = strings.SplitN(spec, ":", 2) - mech = strings.ToLower(parts[0]) - ip net.IP + before, after, found = strings.Cut(spec, ":") + mech = strings.ToLower(before) + ip net.IP ) - if len(parts) > 1 { - ip = net.ParseIP(parts[1]) + if found { + ip = net.ParseIP(after) if ip == nil { return nil, errors.New("invalid IP address") } @@ -86,7 +86,7 @@ func Parse(spec string) (Interface, error) { case "pmp", "natpmp", "nat-pmp": return PMP(ip), nil default: - return nil, fmt.Errorf("unknown mechanism %q", parts[0]) + return nil, fmt.Errorf("unknown mechanism %q", before) } } diff --git a/p2p/rlpx/rlpx_test.go b/p2p/rlpx/rlpx_test.go index 28759f2b4..136cb1b5b 100644 --- a/p2p/rlpx/rlpx_test.go +++ b/p2p/rlpx/rlpx_test.go @@ -421,7 +421,7 @@ func BenchmarkThroughput(b *testing.B) { } conn2.SetSnappy(true) if err := <-handshakeDone; err != nil { - b.Fatal("server hanshake error:", err) + b.Fatal("server handshake error:", err) } // Read N messages. diff --git a/p2p/simulations/adapters/exec.go b/p2p/simulations/adapters/exec.go index 1d812514d..63cc4936c 100644 --- a/p2p/simulations/adapters/exec.go +++ b/p2p/simulations/adapters/exec.go @@ -34,13 +34,14 @@ import ( "syscall" "time" - "github.com/docker/docker/pkg/reexec" + "github.com/ethereum/go-ethereum/internal/reexec" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rpc" "github.com/gorilla/websocket" + "golang.org/x/exp/slog" ) func init() { @@ -375,9 +376,11 @@ type execNodeConfig struct { func initLogging() { // Initialize the logging by default first. - glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat())) - glogger.Verbosity(log.LvlInfo) - log.Root().SetHandler(glogger) + var innerHandler slog.Handler + innerHandler = slog.NewTextHandler(os.Stderr, nil) + glogger := log.NewGlogHandler(innerHandler) + glogger.Verbosity(log.LevelInfo) + log.SetDefault(log.NewLogger(glogger)) confEnv := os.Getenv(envNodeConfig) if confEnv == "" { @@ -395,14 +398,15 @@ func initLogging() { } writer = logWriter } - var verbosity = log.LvlInfo - if conf.Node.LogVerbosity <= log.LvlTrace && conf.Node.LogVerbosity >= log.LvlCrit { - verbosity = conf.Node.LogVerbosity + var verbosity = log.LevelInfo + if conf.Node.LogVerbosity <= log.LevelTrace && conf.Node.LogVerbosity >= log.LevelCrit { + verbosity = log.FromLegacyLevel(int(conf.Node.LogVerbosity)) } // Reinitialize the logger - glogger = log.NewGlogHandler(log.StreamHandler(writer, log.TerminalFormat(true))) + innerHandler = log.NewTerminalHandler(writer, true) + glogger = log.NewGlogHandler(innerHandler) glogger.Verbosity(verbosity) - log.Root().SetHandler(glogger) + log.SetDefault(log.NewLogger(glogger)) } // execP2PNode starts a simulation node when the current binary is executed with diff --git a/p2p/simulations/adapters/types.go b/p2p/simulations/adapters/types.go index 3b4e05a90..fb8463d22 100644 --- a/p2p/simulations/adapters/types.go +++ b/p2p/simulations/adapters/types.go @@ -25,8 +25,8 @@ import ( "os" "strconv" - "github.com/docker/docker/pkg/reexec" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/internal/reexec" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/rpc" "github.com/gorilla/websocket" + "golang.org/x/exp/slog" ) // Node represents a node in a simulation network which is created by a @@ -129,7 +130,7 @@ type NodeConfig struct { // LogVerbosity is the log verbosity of the p2p node at runtime. // // The default verbosity is INFO. - LogVerbosity log.Lvl + LogVerbosity slog.Level } // nodeConfigJSON is used to encode and decode NodeConfig as JSON by encoding @@ -197,7 +198,7 @@ func (n *NodeConfig) UnmarshalJSON(data []byte) error { n.Port = confJSON.Port n.EnableMsgEvents = confJSON.EnableMsgEvents n.LogFile = confJSON.LogFile - n.LogVerbosity = log.Lvl(confJSON.LogVerbosity) + n.LogVerbosity = slog.Level(confJSON.LogVerbosity) return nil } diff --git a/p2p/simulations/examples/ping-pong.go b/p2p/simulations/examples/ping-pong.go index f6cf5113a..70b35ad77 100644 --- a/p2p/simulations/examples/ping-pong.go +++ b/p2p/simulations/examples/ping-pong.go @@ -41,7 +41,7 @@ func main() { flag.Parse() // set the log level to Trace - log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, false))) // register a single ping-pong service services := map[string]adapters.LifecycleConstructor{ diff --git a/p2p/simulations/http.go b/p2p/simulations/http.go index 7a4f70e9b..34521b477 100644 --- a/p2p/simulations/http.go +++ b/p2p/simulations/http.go @@ -479,12 +479,12 @@ func (s *Server) StreamNetworkEvents(w http.ResponseWriter, req *http.Request) { func NewMsgFilters(filterParam string) (MsgFilters, error) { filters := make(MsgFilters) for _, filter := range strings.Split(filterParam, "-") { - protoCodes := strings.SplitN(filter, ":", 2) - if len(protoCodes) != 2 || protoCodes[0] == "" || protoCodes[1] == "" { + proto, codes, found := strings.Cut(filter, ":") + if !found || proto == "" || codes == "" { return nil, fmt.Errorf("invalid message filter: %s", filter) } - proto := protoCodes[0] - for _, code := range strings.Split(protoCodes[1], ",") { + + for _, code := range strings.Split(codes, ",") { if code == "*" || code == "-1" { filters[MsgFilter{Proto: proto, Code: -1}] = struct{}{} continue diff --git a/p2p/simulations/http_test.go b/p2p/simulations/http_test.go index 05e43238a..c53a49797 100644 --- a/p2p/simulations/http_test.go +++ b/p2p/simulations/http_test.go @@ -37,14 +37,14 @@ import ( "github.com/ethereum/go-ethereum/p2p/simulations/adapters" "github.com/ethereum/go-ethereum/rpc" "github.com/mattn/go-colorable" + "golang.org/x/exp/slog" ) func TestMain(m *testing.M) { loglevel := flag.Int("loglevel", 2, "verbosity of logs") flag.Parse() - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), slog.Level(*loglevel), true))) os.Exit(m.Run()) } diff --git a/p2p/simulations/network_test.go b/p2p/simulations/network_test.go index ab8cf1946..4ed1e4e6c 100644 --- a/p2p/simulations/network_test.go +++ b/p2p/simulations/network_test.go @@ -683,7 +683,7 @@ func triggerChecks(ctx context.Context, ids []enode.ID, trigger chan enode.ID, i } } -// \todo: refactor to implement shapshots +// \todo: refactor to implement snapshots // and connect configuration methods once these are moved from // swarm/network/simulations/connect.go func BenchmarkMinimalService(b *testing.B) { diff --git a/params/bootnodes.go b/params/bootnodes.go index edc6e06cb..5e2c7c218 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{ @@ -58,20 +66,25 @@ var GoerliBootnodes = []string{ var V5Bootnodes = []string{ // Teku team's bootnode - "enr:-KG4QOtcP9X1FbIMOe17QNMKqDxCpm14jcX5tiOE4_TyMrFqbmhPZHK_ZPG2Gxb1GE2xdtodOfx9-cgvNtxnRyHEmC0ghGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNlY3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA", - "enr:-KG4QDyytgmE4f7AnvW-ZaUOIi9i79qX4JwjRAiXBZCU65wOfBu-3Nb5I7b_Rmg3KCOcZM_C3y5pg7EBU5XGrcLTduQEhGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQ2_DUbiXNlY3AyNTZrMaEDKnz_-ps3UUOfHWVYaskI5kWYO_vtYMGYCQRAR3gHDouDdGNwgiMog3VkcIIjKA", + "enr:-KG4QMOEswP62yzDjSwWS4YEjtTZ5PO6r65CPqYBkgTTkrpaedQ8uEUo1uMALtJIvb2w_WWEVmg5yt1UAuK1ftxUU7QDhGV0aDKQu6TalgMAAAD__________4JpZIJ2NIJpcIQEnfA2iXNlY3AyNTZrMaEDfol8oLr6XJ7FsdAYE7lpJhKMls4G_v6qQOGKJUWGb_uDdGNwgiMog3VkcIIjKA", // # 4.157.240.54 | azure-us-east-virginia + "enr:-KG4QF4B5WrlFcRhUU6dZETwY5ZzAXnA0vGC__L1Kdw602nDZwXSTs5RFXFIFUnbQJmhNGVU6OIX7KVrCSTODsz1tK4DhGV0aDKQu6TalgMAAAD__________4JpZIJ2NIJpcIQExNYEiXNlY3AyNTZrMaECQmM9vp7KhaXhI-nqL_R0ovULLCFSFTa9CPPSdb1zPX6DdGNwgiMog3VkcIIjKA", // 4.196.214.4 | azure-au-east-sydney // Prylab team's bootnodes - "enr:-Ku4QImhMc1z8yCiNJ1TyUxdcfNucje3BGwEHzodEZUan8PherEo4sF7pPHPSIB1NNuSg5fZy7qFsjmUKs2ea1Whi0EBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQOVphkDqal4QzPMksc5wnpuC3gvSC8AfbFOnZY_On34wIN1ZHCCIyg", - "enr:-Ku4QP2xDnEtUXIjzJ_DhlCRN9SN99RYQPJL92TMlSv7U5C1YnYLjwOQHgZIUXw6c-BvRg2Yc2QsZxxoS_pPRVe0yK8Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMeFF5GrS7UZpAH2Ly84aLK-TyvH-dRo0JM1i8yygH50YN1ZHCCJxA", - "enr:-Ku4QPp9z1W4tAO8Ber_NQierYaOStqhDqQdOPY3bB3jDgkjcbk6YrEnVYIiCBbTxuar3CzS528d2iE7TdJsrL-dEKoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMw5fqqkw2hHC4F5HZZDPsNmPdB1Gi8JPQK7pRc9XHh-oN1ZHCCKvg", + "enr:-Ku4QImhMc1z8yCiNJ1TyUxdcfNucje3BGwEHzodEZUan8PherEo4sF7pPHPSIB1NNuSg5fZy7qFsjmUKs2ea1Whi0EBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQOVphkDqal4QzPMksc5wnpuC3gvSC8AfbFOnZY_On34wIN1ZHCCIyg", // 18.223.219.100 | aws-us-east-2-ohio + "enr:-Ku4QP2xDnEtUXIjzJ_DhlCRN9SN99RYQPJL92TMlSv7U5C1YnYLjwOQHgZIUXw6c-BvRg2Yc2QsZxxoS_pPRVe0yK8Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMeFF5GrS7UZpAH2Ly84aLK-TyvH-dRo0JM1i8yygH50YN1ZHCCJxA", // 18.223.219.100 | aws-us-east-2-ohio + "enr:-Ku4QPp9z1W4tAO8Ber_NQierYaOStqhDqQdOPY3bB3jDgkjcbk6YrEnVYIiCBbTxuar3CzS528d2iE7TdJsrL-dEKoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMw5fqqkw2hHC4F5HZZDPsNmPdB1Gi8JPQK7pRc9XHh-oN1ZHCCKvg", // 18.223.219.100 | aws-us-east-2-ohio // Lighthouse team's bootnodes - "enr:-IS4QLkKqDMy_ExrpOEWa59NiClemOnor-krjp4qoeZwIw2QduPC-q7Kz4u1IOWf3DDbdxqQIgC4fejavBOuUPy-HE4BgmlkgnY0gmlwhCLzAHqJc2VjcDI1NmsxoQLQSJfEAHZApkm5edTCZ_4qps_1k_ub2CxHFxi-gr2JMIN1ZHCCIyg", - "enr:-IS4QDAyibHCzYZmIYZCjXwU9BqpotWmv2BsFlIq1V31BwDDMJPFEbox1ijT5c2Ou3kvieOKejxuaCqIcjxBjJ_3j_cBgmlkgnY0gmlwhAMaHiCJc2VjcDI1NmsxoQJIdpj_foZ02MXz4It8xKD7yUHTBx7lVFn3oeRP21KRV4N1ZHCCIyg", + "enr:-Le4QPUXJS2BTORXxyx2Ia-9ae4YqA_JWX3ssj4E_J-3z1A-HmFGrU8BpvpqhNabayXeOZ2Nq_sbeDgtzMJpLLnXFgAChGV0aDKQtTA_KgEAAAAAIgEAAAAAAIJpZIJ2NIJpcISsaa0Zg2lwNpAkAIkHAAAAAPA8kv_-awoTiXNlY3AyNTZrMaEDHAD2JKYevx89W0CcFJFiskdcEzkH_Wdv9iW42qLK79ODdWRwgiMohHVkcDaCI4I", // 172.105.173.25 | linode-au-sydney + "enr:-Le4QLHZDSvkLfqgEo8IWGG96h6mxwe_PsggC20CL3neLBjfXLGAQFOPSltZ7oP6ol54OvaNqO02Rnvb8YmDR274uq8ChGV0aDKQtTA_KgEAAAAAIgEAAAAAAIJpZIJ2NIJpcISLosQxg2lwNpAqAX4AAAAAAPA8kv_-ax65iXNlY3AyNTZrMaEDBJj7_dLFACaxBfaI8KZTh_SSJUjhyAyfshimvSqo22WDdWRwgiMohHVkcDaCI4I", // 139.162.196.49 | linode-uk-london + "enr:-Le4QH6LQrusDbAHPjU_HcKOuMeXfdEB5NJyXgHWFadfHgiySqeDyusQMvfphdYWOzuSZO9Uq2AMRJR5O4ip7OvVma8BhGV0aDKQtTA_KgEAAAAAIgEAAAAAAIJpZIJ2NIJpcISLY9ncg2lwNpAkAh8AgQIBAAAAAAAAAAmXiXNlY3AyNTZrMaECDYCZTZEksF-kmgPholqgVt8IXr-8L7Nu7YrZ7HUpgxmDdWRwgiMohHVkcDaCI4I", // 139.99.217.220 | ovh-au-sydney + "enr:-Le4QIqLuWybHNONr933Lk0dcMmAB5WgvGKRyDihy1wHDIVlNuuztX62W51voT4I8qD34GcTEOTmag1bcdZ_8aaT4NUBhGV0aDKQtTA_KgEAAAAAIgEAAAAAAIJpZIJ2NIJpcISLY04ng2lwNpAkAh8AgAIBAAAAAAAAAA-fiXNlY3AyNTZrMaEDscnRV6n1m-D9ID5UsURk0jsoKNXt1TIrj8uKOGW6iluDdWRwgiMohHVkcDaCI4I", // 139.99.78.39 | ovh-singapore // EF bootnodes - "enr:-Ku4QHqVeJ8PPICcWk1vSn_XcSkjOkNiTg6Fmii5j6vUQgvzMc9L1goFnLKgXqBJspJjIsB91LTOleFmyWWrFVATGngBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAMRHkWJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyg", - "enr:-Ku4QG-2_Md3sZIAUebGYT6g0SMskIml77l6yR-M_JXc-UdNHCmHQeOiMLbylPejyJsdAPsTHJyjJB2sYGDLe0dn8uYBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhBLY-NyJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyg", - "enr:-Ku4QPn5eVhcoF1opaFEvg1b6JNFD2rqVkHQ8HApOKK61OIcIXD127bKWgAtbwI7pnxx6cDyk_nI88TrZKQaGMZj0q0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDayLMaJc2VjcDI1NmsxoQK2sBOLGcUb4AwuYzFuAVCaNHA-dy24UuEKkeFNgCVCsIN1ZHCCIyg", - "enr:-Ku4QEWzdnVtXc2Q0ZVigfCGggOVB2Vc1ZCPEc6j21NIFLODSJbvNaef1g4PxhPwl_3kax86YPheFUSLXPRs98vvYsoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDZBrP2Jc2VjcDI1NmsxoQM6jr8Rb1ktLEsVcKAPa08wCsKUmvoQ8khiOl_SLozf9IN1ZHCCIyg", + "enr:-Ku4QHqVeJ8PPICcWk1vSn_XcSkjOkNiTg6Fmii5j6vUQgvzMc9L1goFnLKgXqBJspJjIsB91LTOleFmyWWrFVATGngBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAMRHkWJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyg", // 3.17.30.69 | aws-us-east-2-ohio + "enr:-Ku4QG-2_Md3sZIAUebGYT6g0SMskIml77l6yR-M_JXc-UdNHCmHQeOiMLbylPejyJsdAPsTHJyjJB2sYGDLe0dn8uYBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhBLY-NyJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyg", // 18.216.248.220 | aws-us-east-2-ohio + "enr:-Ku4QPn5eVhcoF1opaFEvg1b6JNFD2rqVkHQ8HApOKK61OIcIXD127bKWgAtbwI7pnxx6cDyk_nI88TrZKQaGMZj0q0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDayLMaJc2VjcDI1NmsxoQK2sBOLGcUb4AwuYzFuAVCaNHA-dy24UuEKkeFNgCVCsIN1ZHCCIyg", // 54.178.44.198 | aws-ap-northeast-1-tokyo + "enr:-Ku4QEWzdnVtXc2Q0ZVigfCGggOVB2Vc1ZCPEc6j21NIFLODSJbvNaef1g4PxhPwl_3kax86YPheFUSLXPRs98vvYsoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDZBrP2Jc2VjcDI1NmsxoQM6jr8Rb1ktLEsVcKAPa08wCsKUmvoQ8khiOl_SLozf9IN1ZHCCIyg", // 54.65.172.253 | aws-ap-northeast-1-tokyo + // Nimbus team's bootnodes + "enr:-LK4QA8FfhaAjlb_BXsXxSfiysR7R52Nhi9JBt4F8SPssu8hdE1BXQQEtVDC3qStCW60LSO7hEsVHv5zm8_6Vnjhcn0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAN4aBKJc2VjcDI1NmsxoQJerDhsJ-KxZ8sHySMOCmTO6sHM3iCFQ6VMvLTe948MyYN0Y3CCI4yDdWRwgiOM", // 3.120.104.18 | aws-eu-central-1-frankfurt + "enr:-LK4QKWrXTpV9T78hNG6s8AM6IO4XH9kFT91uZtFg1GcsJ6dKovDOr1jtAAFPnS2lvNltkOGA9k29BUN7lFh_sjuc9QBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhANAdd-Jc2VjcDI1NmsxoQLQa6ai7y9PMN5hpLe5HmiJSlYzMuzP7ZhwRiwHvqNXdoN0Y3CCI4yDdWRwgiOM", // 3.64.117.223 | aws-eu-central-1-frankfurt} } const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@" @@ -88,6 +101,8 @@ func KnownDNSNetwork(genesis common.Hash, protocol string) string { net = "goerli" case SepoliaGenesisHash: net = "sepolia" + case HoleskyGenesisHash: + net = "holesky" default: return "" } diff --git a/params/config.go b/params/config.go index 75c8fd89d..7e8dfc812 100644 --- a/params/config.go +++ b/params/config.go @@ -26,6 +26,7 @@ import ( // Genesis hashes to enforce below configs on. var ( MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") + HoleskyGenesisHash = common.HexToHash("0xb5f7f912443c940f21fd611f12828d75b534364ed9e95ca4e307729a4661bde4") SepoliaGenesisHash = common.HexToHash("0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9") GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a") ) @@ -58,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), @@ -74,6 +99,8 @@ 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), @@ -100,6 +127,7 @@ var ( TerminalTotalDifficulty: big.NewInt(10_790_000), TerminalTotalDifficultyPassed: true, ShanghaiTime: newUint64(1678832736), + CancunTime: newUint64(1705473120), Clique: &CliqueConfig{ Period: 15, Epoch: 30000, @@ -153,7 +181,6 @@ var ( ShanghaiTime: newUint64(0), TerminalTotalDifficulty: big.NewInt(0), TerminalTotalDifficultyPassed: true, - IsDevMode: true, } // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced @@ -187,7 +214,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), @@ -253,6 +280,7 @@ var NetworkNames = map[string]string{ MainnetChainConfig.ChainID.String(): "mainnet", GoerliChainConfig.ChainID.String(): "goerli", SepoliaChainConfig.ChainID.String(): "sepolia", + HoleskyChainConfig.ChainID.String(): "holesky", } // ChainConfig is the core config which determines the blockchain settings. @@ -301,9 +329,8 @@ type ChainConfig struct { TerminalTotalDifficultyPassed bool `json:"terminalTotalDifficultyPassed,omitempty"` // Various consensus engines - Ethash *EthashConfig `json:"ethash,omitempty"` - Clique *CliqueConfig `json:"clique,omitempty"` - IsDevMode bool `json:"isDev,omitempty"` + Ethash *EthashConfig `json:"ethash,omitempty"` + Clique *CliqueConfig `json:"clique,omitempty"` } // EthashConfig is the consensus engine configs for proof-of-work based sealing. @@ -329,7 +356,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" diff --git a/params/protocol_params.go b/params/protocol_params.go index a407ed147..8a5c01184 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. @@ -163,12 +167,13 @@ const ( 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 - BlobTxMaxBlobGasPerBlock = 1 << 19 // Maximum consumable blob gas for data blobs per block - BlobTxTargetBlobGasPerBlock = 1 << 18 // Target consumable blob gas for data blobs per block (for 1559-like pricing) BlobTxBlobGasPerBlob = 1 << 17 // Gas consumption of a single data blob (== blob byte size) BlobTxMinBlobGasprice = 1 // Minimum gas price for data blobs - BlobTxBlobGaspriceUpdateFraction = 2225652 // Controls the maximum rate of change for blob gas price + 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 @@ -179,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 607deea23..e92ad50fa 100644 --- a/params/version.go +++ b/params/version.go @@ -22,14 +22,14 @@ import ( const ( VersionMajor = 1 // Major version component of the current release - VersionMinor = 12 // Minor version component of the current release - VersionPatch = 2 // Patch version component of the current release + VersionMinor = 13 // Minor version component of the current release + VersionPatch = 10 // Patch version component of the current release VersionMeta = "stable" // Version metadata to append to the version string ) const ( BspVersionMajor = 1 // Major version component of the current release - BspVersionMinor = 5 // Minor version component of the current release + BspVersionMinor = 6 // Minor version component of the current release BspVersionPatch = 0 // Patch version component of the current release ) diff --git a/rlp/decode.go b/rlp/decode.go index c9b50e8c1..9b17d2d81 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/rlpgen/main.go b/rlp/rlpgen/main.go index 25d4393cc..b3a74b9df 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_opt.go b/rpc/client_opt.go index 5bef08cca..3fa045a9b 100644 --- a/rpc/client_opt.go +++ b/rpc/client_opt.go @@ -34,7 +34,8 @@ type clientConfig struct { httpAuth HTTPAuth // WebSocket options - wsDialer *websocket.Dialer + wsDialer *websocket.Dialer + wsMessageSizeLimit *int64 // wsMessageSizeLimit nil = default, 0 = no limit // RPC handler options idgen func() ID @@ -66,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 { diff --git a/rpc/client_test.go b/rpc/client_test.go index 7c96b2d66..ac02ad33c 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -595,7 +595,7 @@ func TestClientSubscriptionChannelClose(t *testing.T) { for i := 0; i < 100; i++ { ch := make(chan int, 100) - sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", maxClientSubscriptionBuffer-1, 1) + sub, err := client.Subscribe(context.Background(), "nftest", ch, "someSubscription", 100, 1) if err != nil { t.Fatal(err) } diff --git a/rpc/ipc_windows.go b/rpc/ipc_windows.go index adb1826f0..efec38cf3 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/json.go b/rpc/json.go index 8a3b162ca..5557a8076 100644 --- a/rpc/json.go +++ b/rpc/json.go @@ -46,6 +46,17 @@ type subscriptionResult struct { Result json.RawMessage `json:"result,omitempty"` } +type subscriptionResultEnc struct { + ID string `json:"subscription"` + Result any `json:"result"` +} + +type jsonrpcSubscriptionNotification struct { + Version string `json:"jsonrpc"` + Method string `json:"method"` + Params subscriptionResultEnc `json:"params"` +} + // A value of this type can a JSON-RPC request, notification, successful response or // error response. Which one it is depends on the fields. type jsonrpcMessage struct { @@ -86,8 +97,8 @@ func (msg *jsonrpcMessage) isUnsubscribe() bool { } func (msg *jsonrpcMessage) namespace() string { - elem := strings.SplitN(msg.Method, serviceMethodSeparator, 2) - return elem[0] + before, _, _ := strings.Cut(msg.Method, serviceMethodSeparator) + return before } func (msg *jsonrpcMessage) String() string { diff --git a/rpc/metrics.go b/rpc/metrics.go index b1f128453..ef7449ce0 100644 --- a/rpc/metrics.go +++ b/rpc/metrics.go @@ -46,5 +46,5 @@ func updateServeTimeHistogram(method string, success bool, elapsed time.Duration metrics.NewExpDecaySample(1028, 0.015), ) } - metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(elapsed.Microseconds()) + metrics.GetOrRegisterHistogramLazy(h, nil, sampler).Update(elapsed.Nanoseconds()) } diff --git a/rpc/server_test.go b/rpc/server_test.go index 5d3929dfd..9d1c7fb5f 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)) } diff --git a/rpc/service.go b/rpc/service.go index 8485cab3a..a180b8db9 100644 --- a/rpc/service.go +++ b/rpc/service.go @@ -93,13 +93,13 @@ func (r *serviceRegistry) registerName(name string, rcvr interface{}) error { // callback returns the callback corresponding to the given RPC method name. func (r *serviceRegistry) callback(method string) *callback { - elem := strings.SplitN(method, serviceMethodSeparator, 2) - if len(elem) != 2 { + before, after, found := strings.Cut(method, serviceMethodSeparator) + if !found { return nil } r.mu.Lock() defer r.mu.Unlock() - return r.services[elem[0]].callbacks[elem[1]] + return r.services[before].callbacks[after] } // subscription returns a subscription callback in the given service. diff --git a/rpc/subscription.go b/rpc/subscription.go index 3231c2cee..9cb072754 100644 --- a/rpc/subscription.go +++ b/rpc/subscription.go @@ -105,7 +105,7 @@ type Notifier struct { mu sync.Mutex sub *Subscription - buffer []json.RawMessage + buffer []any callReturned bool activated bool } @@ -129,12 +129,7 @@ func (n *Notifier) CreateSubscription() *Subscription { // Notify sends a notification to the client with the given data as payload. // If an error occurs the RPC connection is closed and the error is returned. -func (n *Notifier) Notify(id ID, data interface{}) error { - enc, err := json.Marshal(data) - if err != nil { - return err - } - +func (n *Notifier) Notify(id ID, data any) error { n.mu.Lock() defer n.mu.Unlock() @@ -144,9 +139,9 @@ func (n *Notifier) Notify(id ID, data interface{}) error { panic("Notify with wrong ID") } if n.activated { - return n.send(n.sub, enc) + return n.send(n.sub, data) } - n.buffer = append(n.buffer, enc) + n.buffer = append(n.buffer, data) return nil } @@ -181,16 +176,16 @@ func (n *Notifier) activate() error { return nil } -func (n *Notifier) send(sub *Subscription, data json.RawMessage) error { - params, _ := json.Marshal(&subscriptionResult{ID: string(sub.ID), Result: data}) - ctx := context.Background() - - msg := &jsonrpcMessage{ +func (n *Notifier) send(sub *Subscription, data any) error { + msg := jsonrpcSubscriptionNotification{ Version: vsn, Method: n.namespace + notificationMethodSuffix, - Params: params, + Params: subscriptionResultEnc{ + ID: string(sub.ID), + Result: data, + }, } - return n.h.conn.writeJSON(ctx, msg, false) + return n.h.conn.writeJSON(context.Background(), &msg, false) } // A Subscription is created by a notifier and tied to that notifier. The client can use diff --git a/rpc/subscription_test.go b/rpc/subscription_test.go index b27045782..3a131c8e6 100644 --- a/rpc/subscription_test.go +++ b/rpc/subscription_test.go @@ -17,12 +17,19 @@ package rpc import ( + "bytes" + "context" "encoding/json" "fmt" + "io" + "math/big" "net" "strings" "testing" "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" ) func TestNewID(t *testing.T) { @@ -218,3 +225,56 @@ func readAndValidateMessage(in *json.Decoder) (*subConfirmation, *subscriptionRe return nil, nil, fmt.Errorf("unrecognized message: %v", msg) } } + +type mockConn struct { + enc *json.Encoder +} + +// writeJSON writes a message to the connection. +func (c *mockConn) writeJSON(ctx context.Context, msg interface{}, isError bool) error { + return c.enc.Encode(msg) +} + +// Closed returns a channel which is closed when the connection is closed. +func (c *mockConn) closed() <-chan interface{} { return nil } + +// RemoteAddr returns the peer address of the connection. +func (c *mockConn) remoteAddr() string { return "" } + +// BenchmarkNotify benchmarks the performance of notifying a subscription. +func BenchmarkNotify(b *testing.B) { + id := ID("test") + notifier := &Notifier{ + h: &handler{conn: &mockConn{json.NewEncoder(io.Discard)}}, + sub: &Subscription{ID: id}, + activated: true, + } + msg := &types.Header{ + ParentHash: common.HexToHash("0x01"), + Number: big.NewInt(100), + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + notifier.Notify(id, msg) + } +} + +func TestNotify(t *testing.T) { + out := new(bytes.Buffer) + id := ID("test") + notifier := &Notifier{ + h: &handler{conn: &mockConn{json.NewEncoder(out)}}, + sub: &Subscription{ID: id}, + activated: true, + } + msg := &types.Header{ + ParentHash: common.HexToHash("0x01"), + Number: big.NewInt(100), + } + notifier.Notify(id, msg) + have := strings.TrimSpace(out.String()) + want := `{"jsonrpc":"2.0","method":"_subscription","params":{"subscription":"test","result":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000001","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":null,"number":"0x64","gasLimit":"0x0","gasUsed":"0x0","timestamp":"0x0","extraData":"0x","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":null,"withdrawalsRoot":null,"blobGasUsed":null,"excessBlobGas":null,"parentBeaconBlockRoot":null,"hash":"0xe5fb877dde471b45b9742bb4bb4b3d74a761e2fb7cb849a3d2b687eed90fb604"}}}` + if have != want { + t.Errorf("have:\n%v\nwant:\n%v\n", have, want) + } +} diff --git a/rpc/testservice_test.go b/rpc/testservice_test.go index eab67f1dd..7d873af66 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/types.go b/rpc/types.go index 34a1451de..f88c37c59 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -21,7 +21,6 @@ import ( "encoding/json" "fmt" "math" - "strconv" "strings" "github.com/ethereum/go-ethereum/common" @@ -221,7 +220,7 @@ func (bnh *BlockNumberOrHash) Number() (BlockNumber, bool) { func (bnh *BlockNumberOrHash) String() string { if bnh.BlockNumber != nil { - return strconv.Itoa(int(*bnh.BlockNumber)) + return bnh.BlockNumber.String() } if bnh.BlockHash != nil { return bnh.BlockHash.String() diff --git a/rpc/types_test.go b/rpc/types_test.go index f110dee7c..617f441d9 100644 --- a/rpc/types_test.go +++ b/rpc/types_test.go @@ -153,3 +153,24 @@ func TestBlockNumberOrHash_WithNumber_MarshalAndUnmarshal(t *testing.T) { }) } } + +func TestBlockNumberOrHash_StringAndUnmarshal(t *testing.T) { + tests := []BlockNumberOrHash{ + BlockNumberOrHashWithNumber(math.MaxInt64), + BlockNumberOrHashWithNumber(PendingBlockNumber), + BlockNumberOrHashWithNumber(LatestBlockNumber), + BlockNumberOrHashWithNumber(EarliestBlockNumber), + BlockNumberOrHashWithNumber(32), + BlockNumberOrHashWithHash(common.Hash{0xaa}, false), + } + for _, want := range tests { + marshalled, _ := json.Marshal(want.String()) + var have BlockNumberOrHash + if err := json.Unmarshal(marshalled, &have); err != nil { + t.Fatalf("cannot unmarshal (%v): %v", string(marshalled), err) + } + if !reflect.DeepEqual(want, have) { + t.Fatalf("wrong result: have %v, want %v", have, want) + } + } +} diff --git a/rpc/websocket.go b/rpc/websocket.go index b1213fdfa..538e53a31 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) }) } @@ -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 fb9357605..d3e15d94c 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() @@ -155,7 +215,7 @@ func TestClientWebsocketPing(t *testing.T) { var ( sendPing = make(chan struct{}) server = wsPingTestServer(t, sendPing) - ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second) + ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second) ) defer cancel() defer server.Shutdown(ctx) @@ -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 43eb89ee0..ef8c13662 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -65,7 +65,7 @@ type ExternalAPI interface { EcRecover(ctx context.Context, data hexutil.Bytes, sig hexutil.Bytes) (common.Address, error) // Version info about the APIs Version(ctx context.Context) (string, error) - // SignGnosisSafeTransaction signs/confirms a gnosis-safe multisig transaction + // SignGnosisSafeTx signs/confirms a gnosis-safe multisig transaction SignGnosisSafeTx(ctx context.Context, signerAddress common.MixedcaseAddress, gnosisTx GnosisSafeTx, methodSelector *string) (*GnosisSafeTx, error) } diff --git a/signer/core/api_test.go b/signer/core/api_test.go index 9bb55bddc..69229dada 100644 --- a/signer/core/api_test.go +++ b/signer/core/api_test.go @@ -169,6 +169,7 @@ func list(ui *headlessUi, api *core.SignerAPI, t *testing.T) ([]common.Address, } func TestNewAcc(t *testing.T) { + t.Parallel() api, control := setup(t) verifyNum := func(num int) { list, err := list(control, api, t) @@ -235,6 +236,7 @@ func mkTestTx(from common.MixedcaseAddress) apitypes.SendTxArgs { } func TestSignTx(t *testing.T) { + t.Parallel() var ( list []common.Address res, res2 *ethapi.SignTransactionResult @@ -282,7 +284,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 +310,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/signer/core/apitypes/signed_data_internal_test.go b/signer/core/apitypes/signed_data_internal_test.go index af7fc93ed..8067893c2 100644 --- a/signer/core/apitypes/signed_data_internal_test.go +++ b/signer/core/apitypes/signed_data_internal_test.go @@ -27,6 +27,7 @@ import ( ) func TestBytesPadding(t *testing.T) { + t.Parallel() tests := []struct { Type string Input []byte @@ -87,6 +88,7 @@ func TestBytesPadding(t *testing.T) { } func TestParseAddress(t *testing.T) { + t.Parallel() tests := []struct { Input interface{} Output []byte // nil => error @@ -136,6 +138,7 @@ func TestParseAddress(t *testing.T) { } func TestParseBytes(t *testing.T) { + t.Parallel() for i, tt := range []struct { v interface{} exp []byte @@ -170,6 +173,7 @@ func TestParseBytes(t *testing.T) { } func TestParseInteger(t *testing.T) { + t.Parallel() for i, tt := range []struct { t string v interface{} @@ -200,6 +204,7 @@ func TestParseInteger(t *testing.T) { } func TestConvertStringDataToSlice(t *testing.T) { + t.Parallel() slice := []string{"a", "b", "c"} var it interface{} = slice _, err := convertDataToSlice(it) @@ -209,6 +214,7 @@ func TestConvertStringDataToSlice(t *testing.T) { } func TestConvertUint256DataToSlice(t *testing.T) { + t.Parallel() slice := []*math.HexOrDecimal256{ math.NewHexOrDecimal256(1), math.NewHexOrDecimal256(2), @@ -222,6 +228,7 @@ func TestConvertUint256DataToSlice(t *testing.T) { } func TestConvertAddressDataToSlice(t *testing.T) { + t.Parallel() slice := []common.Address{ common.HexToAddress("0x0000000000000000000000000000000000000001"), common.HexToAddress("0x0000000000000000000000000000000000000002"), diff --git a/signer/core/apitypes/types.go b/signer/core/apitypes/types.go index 8218e754d..6bfcd2a72 100644 --- a/signer/core/apitypes/types.go +++ b/signer/core/apitypes/types.go @@ -62,7 +62,7 @@ func (vs *ValidationMessages) Info(msg string) { vs.Messages = append(vs.Messages, ValidationInfo{INFO, msg}) } -// getWarnings returns an error with all messages of type WARN of above, or nil if no warnings were present +// GetWarnings returns an error with all messages of type WARN of above, or nil if no warnings were present func (v *ValidationMessages) GetWarnings() error { var messages []string for _, msg := range v.Messages { diff --git a/signer/core/apitypes/types_test.go b/signer/core/apitypes/types_test.go index eef3cae00..b5aa3d1e9 100644 --- a/signer/core/apitypes/types_test.go +++ b/signer/core/apitypes/types_test.go @@ -19,6 +19,7 @@ package apitypes import "testing" func TestIsPrimitive(t *testing.T) { + t.Parallel() // Expected positives for i, tc := range []string{ "int24", "int24[]", "uint88", "uint88[]", "uint", "uint[]", "int256", "int256[]", diff --git a/signer/core/auditlog.go b/signer/core/auditlog.go index a0b292bf7..d2207c9eb 100644 --- a/signer/core/auditlog.go +++ b/signer/core/auditlog.go @@ -19,12 +19,14 @@ package core import ( "context" "encoding/json" + "os" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/signer/core/apitypes" + "golang.org/x/exp/slog" ) type AuditLogger struct { @@ -113,12 +115,13 @@ func (l *AuditLogger) Version(ctx context.Context) (string, error) { } func NewAuditLogger(path string, api ExternalAPI) (*AuditLogger, error) { - l := log.New("api", "signer") - handler, err := log.FileHandler(path, log.LogfmtFormat()) + f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) if err != nil { return nil, err } - l.SetHandler(handler) + + handler := slog.NewTextHandler(f, nil) + l := log.NewLogger(handler).With("api", "signer") l.Info("Configured", "audit log", path) return &AuditLogger{l, api}, nil } diff --git a/signer/core/signed_data_test.go b/signer/core/signed_data_test.go index 3e3837cae..1cf8b4bf3 100644 --- a/signer/core/signed_data_test.go +++ b/signer/core/signed_data_test.go @@ -183,6 +183,7 @@ var typedData = apitypes.TypedData{ } func TestSignData(t *testing.T) { + t.Parallel() api, control := setup(t) //Create two accounts createAccount(control, api, t) @@ -248,6 +249,7 @@ func TestSignData(t *testing.T) { } func TestDomainChainId(t *testing.T) { + t.Parallel() withoutChainID := apitypes.TypedData{ Types: apitypes.Types{ "EIP712Domain": []apitypes.Type{ @@ -289,6 +291,7 @@ func TestDomainChainId(t *testing.T) { } func TestHashStruct(t *testing.T) { + t.Parallel() hash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) if err != nil { t.Fatal(err) @@ -309,6 +312,7 @@ func TestHashStruct(t *testing.T) { } func TestEncodeType(t *testing.T) { + t.Parallel() domainTypeEncoding := string(typedData.EncodeType("EIP712Domain")) if domainTypeEncoding != "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" { t.Errorf("Expected different encodeType result (got %s)", domainTypeEncoding) @@ -321,6 +325,7 @@ func TestEncodeType(t *testing.T) { } func TestTypeHash(t *testing.T) { + t.Parallel() mailTypeHash := fmt.Sprintf("0x%s", common.Bytes2Hex(typedData.TypeHash(typedData.PrimaryType))) if mailTypeHash != "0xa0cedeb2dc280ba39b857546d74f5549c3a1d7bdc2dd96bf881f76108e23dac2" { t.Errorf("Expected different typeHash result (got %s)", mailTypeHash) @@ -328,6 +333,7 @@ func TestTypeHash(t *testing.T) { } func TestEncodeData(t *testing.T) { + t.Parallel() hash, err := typedData.EncodeData(typedData.PrimaryType, typedData.Message, 0) if err != nil { t.Fatal(err) @@ -339,6 +345,7 @@ func TestEncodeData(t *testing.T) { } func TestFormatter(t *testing.T) { + t.Parallel() var d apitypes.TypedData err := json.Unmarshal([]byte(jsonTypedData), &d) if err != nil { @@ -368,6 +375,7 @@ func sign(typedData apitypes.TypedData) ([]byte, []byte, error) { } func TestJsonFiles(t *testing.T) { + t.Parallel() testfiles, err := os.ReadDir("testdata/") if err != nil { t.Fatalf("failed reading files: %v", err) @@ -402,6 +410,7 @@ func TestJsonFiles(t *testing.T) { // TestFuzzerFiles tests some files that have been found by fuzzing to cause // crashes or hangs. func TestFuzzerFiles(t *testing.T) { + t.Parallel() corpusdir := path.Join("testdata", "fuzzing") testfiles, err := os.ReadDir(corpusdir) if err != nil { @@ -514,6 +523,7 @@ var gnosisTx = ` // TestGnosisTypedData tests the scenario where a user submits a full EIP-712 // struct without using the gnosis-specific endpoint func TestGnosisTypedData(t *testing.T) { + t.Parallel() var td apitypes.TypedData err := json.Unmarshal([]byte(gnosisTypedData), &td) if err != nil { @@ -532,6 +542,7 @@ func TestGnosisTypedData(t *testing.T) { // TestGnosisCustomData tests the scenario where a user submits only the gnosis-safe // specific data, and we fill the TypedData struct on our side func TestGnosisCustomData(t *testing.T) { + t.Parallel() var tx core.GnosisSafeTx err := json.Unmarshal([]byte(gnosisTx), &tx) if err != nil { @@ -644,6 +655,7 @@ var gnosisTxWithChainId = ` ` func TestGnosisTypedDataWithChainId(t *testing.T) { + t.Parallel() var td apitypes.TypedData err := json.Unmarshal([]byte(gnosisTypedDataWithChainId), &td) if err != nil { @@ -662,6 +674,7 @@ func TestGnosisTypedDataWithChainId(t *testing.T) { // TestGnosisCustomData tests the scenario where a user submits only the gnosis-safe // specific data, and we fill the TypedData struct on our side func TestGnosisCustomDataWithChainId(t *testing.T) { + t.Parallel() var tx core.GnosisSafeTx err := json.Unmarshal([]byte(gnosisTxWithChainId), &tx) if err != nil { @@ -813,6 +826,7 @@ var complexTypedData = ` ` func TestComplexTypedData(t *testing.T) { + t.Parallel() var td apitypes.TypedData err := json.Unmarshal([]byte(complexTypedData), &td) if err != nil { @@ -829,6 +843,7 @@ func TestComplexTypedData(t *testing.T) { } func TestGnosisSafe(t *testing.T) { + t.Parallel() // json missing chain id js := "{\n \"safe\": \"0x899FcB1437DE65DC6315f5a69C017dd3F2837557\",\n \"to\": \"0x899FcB1437DE65DC6315f5a69C017dd3F2837557\",\n \"value\": \"0\",\n \"data\": \"0x0d582f13000000000000000000000000d3ed2b8756b942c98c851722f3bd507a17b4745f0000000000000000000000000000000000000000000000000000000000000005\",\n \"operation\": 0,\n \"gasToken\": \"0x0000000000000000000000000000000000000000\",\n \"safeTxGas\": 0,\n \"baseGas\": 0,\n \"gasPrice\": \"0\",\n \"refundReceiver\": \"0x0000000000000000000000000000000000000000\",\n \"nonce\": 0,\n \"executionDate\": null,\n \"submissionDate\": \"2022-02-23T14:09:00.018475Z\",\n \"modified\": \"2022-12-01T15:52:21.214357Z\",\n \"blockNumber\": null,\n \"transactionHash\": null,\n \"safeTxHash\": \"0x6f0f5cffee69087c9d2471e477a63cab2ae171cf433e754315d558d8836274f4\",\n \"executor\": null,\n \"isExecuted\": false,\n \"isSuccessful\": null,\n \"ethGasPrice\": null,\n \"maxFeePerGas\": null,\n \"maxPriorityFeePerGas\": null,\n \"gasUsed\": null,\n \"fee\": null,\n \"origin\": \"https://gnosis-safe.io\",\n \"dataDecoded\": {\n \"method\": \"addOwnerWithThreshold\",\n \"parameters\": [\n {\n \"name\": \"owner\",\n \"type\": \"address\",\n \"value\": \"0xD3Ed2b8756b942c98c851722F3bd507a17B4745F\"\n },\n {\n \"name\": \"_threshold\",\n \"type\": \"uint256\",\n \"value\": \"5\"\n }\n ]\n },\n \"confirmationsRequired\": 4,\n \"confirmations\": [\n {\n \"owner\": \"0x30B714E065B879F5c042A75Bb40a220A0BE27966\",\n \"submissionDate\": \"2022-03-01T14:56:22Z\",\n \"transactionHash\": \"0x6d0a9c83ac7578ef3be1f2afce089fb83b619583dfa779b82f4422fd64ff3ee9\",\n \"signature\": \"0x00000000000000000000000030b714e065b879f5c042a75bb40a220a0be27966000000000000000000000000000000000000000000000000000000000000000001\",\n \"signatureType\": \"APPROVED_HASH\"\n },\n {\n \"owner\": \"0x8300dFEa25Da0eb744fC0D98c23283F86AB8c10C\",\n \"submissionDate\": \"2022-12-01T15:52:21.214357Z\",\n \"transactionHash\": null,\n \"signature\": \"0xbce73de4cc6ee208e933a93c794dcb8ba1810f9848d1eec416b7be4dae9854c07dbf1720e60bbd310d2159197a380c941cfdb55b3ce58f9dd69efd395d7bef881b\",\n \"signatureType\": \"EOA\"\n }\n ],\n \"trusted\": true,\n \"signatures\": null\n}\n" var gnosisTx core.GnosisSafeTx @@ -984,6 +999,7 @@ var complexTypedDataLCRefType = ` ` func TestComplexTypedDataWithLowercaseReftype(t *testing.T) { + t.Parallel() var td apitypes.TypedData err := json.Unmarshal([]byte(complexTypedDataLCRefType), &td) if err != nil { diff --git a/signer/core/uiapi.go b/signer/core/uiapi.go index 4a060147a..b8c3acfb4 100644 --- a/signer/core/uiapi.go +++ b/signer/core/uiapi.go @@ -31,7 +31,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -// SignerUIAPI implements methods Clef provides for a UI to query, in the bidirectional communication +// UIServerAPI implements methods Clef provides for a UI to query, in the bidirectional communication // channel. // This API is considered secure, since a request can only // ever arrive from the UI -- and the UI is capable of approving any action, thus we can consider these diff --git a/signer/core/validation_test.go b/signer/core/validation_test.go index 6adaa21af..7f733b0bb 100644 --- a/signer/core/validation_test.go +++ b/signer/core/validation_test.go @@ -19,6 +19,7 @@ package core import "testing" func TestPasswordValidation(t *testing.T) { + t.Parallel() testcases := []struct { pw string shouldFail bool diff --git a/signer/fourbyte/abi_test.go b/signer/fourbyte/abi_test.go index 68c027ece..9656732df 100644 --- a/signer/fourbyte/abi_test.go +++ b/signer/fourbyte/abi_test.go @@ -52,6 +52,7 @@ func verify(t *testing.T, jsondata, calldata string, exp []interface{}) { } func TestNewUnpacker(t *testing.T) { + t.Parallel() type unpackTest struct { jsondata string calldata string @@ -97,6 +98,7 @@ func TestNewUnpacker(t *testing.T) { } func TestCalldataDecoding(t *testing.T) { + t.Parallel() // send(uint256) : a52c101e // compareAndApprove(address,uint256,uint256) : 751e1079 // issue(address[],uint256) : 42958b54 @@ -159,6 +161,7 @@ func TestCalldataDecoding(t *testing.T) { } func TestMaliciousABIStrings(t *testing.T) { + t.Parallel() tests := []string{ "func(uint256,uint256,[]uint256)", "func(uint256,uint256,uint256,)", diff --git a/signer/fourbyte/fourbyte_test.go b/signer/fourbyte/fourbyte_test.go index 017001f97..a3dc3b511 100644 --- a/signer/fourbyte/fourbyte_test.go +++ b/signer/fourbyte/fourbyte_test.go @@ -17,8 +17,8 @@ package fourbyte import ( + "encoding/json" "fmt" - "strings" "testing" "github.com/ethereum/go-ethereum/accounts/abi" @@ -27,18 +27,19 @@ import ( // Tests that all the selectors contained in the 4byte database are valid. func TestEmbeddedDatabase(t *testing.T) { + t.Parallel() db, err := New() if err != nil { t.Fatal(err) } + var abistruct abi.ABI for id, selector := range db.embedded { abistring, err := parseSelector(selector) if err != nil { t.Errorf("Failed to convert selector to ABI: %v", err) continue } - abistruct, err := abi.JSON(strings.NewReader(string(abistring))) - if err != nil { + if err := json.Unmarshal(abistring, &abistruct); err != nil { t.Errorf("Failed to parse ABI: %v", err) continue } @@ -55,6 +56,7 @@ func TestEmbeddedDatabase(t *testing.T) { // Tests that custom 4byte datasets can be handled too. func TestCustomDatabase(t *testing.T) { + t.Parallel() // Create a new custom 4byte database with no embedded component tmpdir := t.TempDir() filename := fmt.Sprintf("%s/4byte_custom.json", tmpdir) diff --git a/signer/fourbyte/validation_test.go b/signer/fourbyte/validation_test.go index 1b0ab507a..74fed9fe0 100644 --- a/signer/fourbyte/validation_test.go +++ b/signer/fourbyte/validation_test.go @@ -73,6 +73,7 @@ type txtestcase struct { } func TestTransactionValidation(t *testing.T) { + t.Parallel() var ( // use empty db, there are other tests for the abi-specific stuff db = newEmpty() diff --git a/signer/rules/rules_test.go b/signer/rules/rules_test.go index c35da8ecc..d27de22b2 100644 --- a/signer/rules/rules_test.go +++ b/signer/rules/rules_test.go @@ -124,6 +124,7 @@ func initRuleEngine(js string) (*rulesetUI, error) { } func TestListRequest(t *testing.T) { + t.Parallel() accs := make([]accounts.Account, 5) for i := range accs { @@ -152,6 +153,7 @@ func TestListRequest(t *testing.T) { } func TestSignTxRequest(t *testing.T) { + t.Parallel() js := ` function ApproveTx(r){ console.log("transaction.from", r.transaction.from); @@ -244,6 +246,7 @@ func (d *dummyUI) OnSignerStartup(info core.StartupInfo) { // TestForwarding tests that the rule-engine correctly dispatches requests to the next caller func TestForwarding(t *testing.T) { + t.Parallel() js := "" ui := &dummyUI{make([]string, 0)} jsBackend := storage.NewEphemeralStorage() @@ -271,6 +274,7 @@ func TestForwarding(t *testing.T) { } func TestMissingFunc(t *testing.T) { + t.Parallel() r, err := initRuleEngine(JS) if err != nil { t.Errorf("Couldn't create evaluator %v", err) @@ -293,6 +297,7 @@ func TestMissingFunc(t *testing.T) { t.Logf("Err %v", err) } func TestStorage(t *testing.T) { + t.Parallel() js := ` function testStorage(){ storage.put("mykey", "myvalue") @@ -455,6 +460,7 @@ func dummySigned(value *big.Int) *types.Transaction { } func TestLimitWindow(t *testing.T) { + t.Parallel() r, err := initRuleEngine(ExampleTxWindow) if err != nil { t.Errorf("Couldn't create evaluator %v", err) @@ -540,6 +546,7 @@ func (d *dontCallMe) OnApprovedTx(tx ethapi.SignTransactionResult) { // if it does, that would be bad since developers may rely on that to store data, // instead of using the disk-based data storage func TestContextIsCleared(t *testing.T) { + t.Parallel() js := ` function ApproveTx(){ if (typeof foobar == 'undefined') { @@ -571,6 +578,7 @@ func TestContextIsCleared(t *testing.T) { } func TestSignData(t *testing.T) { + t.Parallel() js := `function ApproveListing(){ return "Approve" } diff --git a/signer/storage/aes_gcm_storage_test.go b/signer/storage/aes_gcm_storage_test.go index e1fea5928..a223b1a6b 100644 --- a/signer/storage/aes_gcm_storage_test.go +++ b/signer/storage/aes_gcm_storage_test.go @@ -26,9 +26,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/mattn/go-colorable" + "golang.org/x/exp/slog" ) func TestEncryption(t *testing.T) { + t.Parallel() // key := []byte("AES256Key-32Characters1234567890") // plaintext := []byte(value) key := []byte("AES256Key-32Characters1234567890") @@ -51,6 +53,7 @@ func TestEncryption(t *testing.T) { } func TestFileStorage(t *testing.T) { + t.Parallel() a := map[string]storedCredential{ "secret": { Iv: common.Hex2Bytes("cdb30036279601aeee60f16b"), @@ -89,7 +92,8 @@ func TestFileStorage(t *testing.T) { } } func TestEnd2End(t *testing.T) { - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(3), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) + t.Parallel() + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), slog.LevelInfo, true))) d := t.TempDir() @@ -109,9 +113,10 @@ func TestEnd2End(t *testing.T) { } func TestSwappedKeys(t *testing.T) { + t.Parallel() // It should not be possible to swap the keys/values, so that // K1:V1, K2:V2 can be swapped into K1:V2, K2:V1 - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(3), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) + log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(colorable.NewColorableStderr(), slog.LevelInfo, true))) d := t.TempDir() diff --git a/tests/block_test.go b/tests/block_test.go index 2405da1cc..aa6f27b8f 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -17,12 +17,15 @@ package tests import ( + "math/rand" + "runtime" "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 +51,43 @@ 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, nil)); err != nil { - t.Errorf("test without snapshotter failed: %v", err) - } - if err := bt.checkFailure(t, test.Run(true, nil)); err != nil { - t.Errorf("test with snapshotter failed: %v", err) + if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 { + t.Skip("test (randomly) skipped on 32-bit windows") } + 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) + + 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, nil)); err != nil { + t.Errorf("test in hash mode without snapshotter failed: %v", err) + return + } + if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil)); err != nil { + t.Errorf("test in hash mode with snapshotter failed: %v", err) + return + } + if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil)); err != nil { + t.Errorf("test in path mode without snapshotter failed: %v", err) + return + } + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil)); err != nil { + t.Errorf("test in path mode with snapshotter failed: %v", err) + return + } +} diff --git a/tests/block_test_util.go b/tests/block_test_util.go index d3e525a38..e0130be48 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,36 @@ type btHeaderMarshaling struct { GasUsed math.HexOrDecimal64 Timestamp math.HexOrDecimal64 BaseFeePerGas *math.HexOrDecimal256 + BlobGasUsed *math.HexOrDecimal64 + ExcessBlobGas *math.HexOrDecimal64 } -func (t *BlockTest) Run(snapshotter bool, tracer vm.EVMLogger) error { +func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *core.BlockChain)) (result error) { config, ok := Forks[t.json.Network] if !ok { return UnsupportedForkError{t.json.Network} } - // import pre accounts & construct test genesis block & state root - db := rawdb.NewMemoryDatabase() + var ( + db = rawdb.NewMemoryDatabase() + tconf = &trie.Config{ + Preimages: true, + } + ) + 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,7 +143,7 @@ func (t *BlockTest) Run(snapshotter bool, tracer vm.EVMLogger) error { // Wrap the original engine within the beacon-engine engine := beacon.New(ethash.NewFaker()) - cache := &core.CacheConfig{TrieCleanLimit: 0} + cache := &core.CacheConfig{TrieCleanLimit: 0, StateScheme: scheme, Preimages: true} if snapshotter { cache.SnapshotLimit = 1 cache.SnapshotWait = true @@ -136,6 +160,11 @@ func (t *BlockTest) Run(snapshotter bool, tracer vm.EVMLogger) error { if err != nil { return err } + // Import succeeded: regardless of whether the _test_ succeeds or not, schedule + // the post-check to run + if postCheck != nil { + defer postCheck(result, chain) + } cmlast := chain.CurrentBlock().Hash() if common.Hash(t.json.BestBlock) != cmlast { return fmt.Errorf("last block hash validation mismatch: want: %x, have: %x", t.json.BestBlock, cmlast) @@ -158,18 +187,20 @@ func (t *BlockTest) Run(snapshotter bool, tracer vm.EVMLogger) 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, } } @@ -278,6 +309,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 } @@ -297,6 +337,12 @@ func (t *BlockTest) validatePostState(statedb *state.StateDB) error { if nonce2 != acct.Nonce { return fmt.Errorf("account nonce mismatch for addr: %s want: %d have: %d", addr, acct.Nonce, nonce2) } + for k, v := range acct.Storage { + v2 := statedb.GetState(addr, k) + if v2 != v { + return fmt.Errorf("account storage mismatch for addr: %s, slot: %x, want: %x, have: %x", addr, k, v, v2) + } + } } return nil } diff --git a/tests/fuzzers/abi/abifuzzer_test.go b/tests/fuzzers/abi/abifuzzer_test.go deleted file mode 100644 index c66399e1b..000000000 --- a/tests/fuzzers/abi/abifuzzer_test.go +++ /dev/null @@ -1,43 +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 abi - -import ( - "testing" -) - -// TestReplicate can be used to replicate crashers from the fuzzing tests. -// Just replace testString with the data in .quoted -func TestReplicate(t *testing.T) { - testString := "\x20\x20\x20\x20\x20\x20\x20\x20\x80\x00\x00\x00\x20\x20\x20\x20\x00" - data := []byte(testString) - runFuzzer(data) -} - -// TestGenerateCorpus can be used to add corpus for the fuzzer. -// Just replace corpusHex with the hexEncoded output you want to add to the fuzzer. -func TestGenerateCorpus(t *testing.T) { - /* - corpusHex := "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - data := common.FromHex(corpusHex) - checksum := sha1.Sum(data) - outf := fmt.Sprintf("corpus/%x", checksum) - if err := os.WriteFile(outf, data, 0777); err != nil { - panic(err) - } - */ -} diff --git a/tests/fuzzers/bitutil/compress_fuzz.go b/tests/fuzzers/bitutil/compress_fuzz.go deleted file mode 100644 index 5903cf2f9..000000000 --- a/tests/fuzzers/bitutil/compress_fuzz.go +++ /dev/null @@ -1,70 +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 bitutil - -import ( - "bytes" - - "github.com/ethereum/go-ethereum/common/bitutil" -) - -// Fuzz implements a go-fuzz fuzzer method to test various encoding method -// invocations. -func Fuzz(data []byte) int { - if len(data) == 0 { - return 0 - } - if data[0]%2 == 0 { - return fuzzEncode(data[1:]) - } - return fuzzDecode(data[1:]) -} - -// fuzzEncode implements a go-fuzz fuzzer method to test the bitset encoding and -// decoding algorithm. -func fuzzEncode(data []byte) int { - proc, _ := bitutil.DecompressBytes(bitutil.CompressBytes(data), len(data)) - if !bytes.Equal(data, proc) { - panic("content mismatch") - } - return 1 -} - -// fuzzDecode implements a go-fuzz fuzzer method to test the bit decoding and -// reencoding algorithm. -func fuzzDecode(data []byte) int { - blob, err := bitutil.DecompressBytes(data, 1024) - if err != nil { - return 0 - } - // re-compress it (it's OK if the re-compressed differs from the - // original - the first input may not have been compressed at all) - comp := bitutil.CompressBytes(blob) - if len(comp) > len(blob) { - // After compression, it must be smaller or equal - panic("bad compression") - } - // But decompressing it once again should work - decomp, err := bitutil.DecompressBytes(data, 1024) - if err != nil { - panic(err) - } - if !bytes.Equal(decomp, blob) { - panic("content mismatch") - } - return 1 -} diff --git a/tests/fuzzers/bls12381/bls12381_fuzz.go b/tests/fuzzers/bls12381/bls12381_fuzz.go index ced87dd41..9a5c56654 100644 --- a/tests/fuzzers/bls12381/bls12381_fuzz.go +++ b/tests/fuzzers/bls12381/bls12381_fuzz.go @@ -14,8 +14,8 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -//go:build gofuzz -// +build gofuzz +//go:build cgo +// +build cgo package bls @@ -35,7 +35,7 @@ import ( blst "github.com/supranational/blst/bindings/go" ) -func FuzzCrossPairing(data []byte) int { +func fuzzCrossPairing(data []byte) int { input := bytes.NewReader(data) // get random G1 points @@ -101,7 +101,7 @@ func massageBLST(in []byte) []byte { return out } -func FuzzCrossG1Add(data []byte) int { +func fuzzCrossG1Add(data []byte) int { input := bytes.NewReader(data) // get random G1 points @@ -139,7 +139,7 @@ func FuzzCrossG1Add(data []byte) int { return 1 } -func FuzzCrossG2Add(data []byte) int { +func fuzzCrossG2Add(data []byte) int { input := bytes.NewReader(data) // get random G2 points @@ -177,7 +177,7 @@ func FuzzCrossG2Add(data []byte) int { return 1 } -func FuzzCrossG1MultiExp(data []byte) int { +func fuzzCrossG1MultiExp(data []byte) int { var ( input = bytes.NewReader(data) gethScalars []*big.Int diff --git a/tests/fuzzers/bls12381/bls12381_test.go b/tests/fuzzers/bls12381/bls12381_test.go new file mode 100644 index 000000000..3e88979d1 --- /dev/null +++ b/tests/fuzzers/bls12381/bls12381_test.go @@ -0,0 +1,100 @@ +// 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 . + +//go:build cgo +// +build cgo + +package bls + +import "testing" + +func FuzzCrossPairing(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzzCrossPairing(data) + }) +} + +func FuzzCrossG1Add(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzzCrossG1Add(data) + }) +} + +func FuzzCrossG2Add(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzzCrossG2Add(data) + }) +} + +func FuzzCrossG1MultiExp(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzzCrossG1MultiExp(data) + }) +} + +func FuzzG1Add(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(blsG1Add, data) + }) +} + +func FuzzG1Mul(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(blsG1Mul, data) + }) +} + +func FuzzG1MultiExp(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(blsG1MultiExp, data) + }) +} + +func FuzzG2Add(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(blsG2Add, data) + }) +} + +func FuzzG2Mul(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(blsG2Mul, data) + }) +} + +func FuzzG2MultiExp(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(blsG2MultiExp, data) + }) +} + +func FuzzPairing(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(blsPairing, data) + }) +} + +func FuzzMapG1(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(blsMapG1, data) + }) +} + +func FuzzMapG2(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(blsMapG2, data) + }) +} diff --git a/tests/fuzzers/bls12381/precompile_fuzzer.go b/tests/fuzzers/bls12381/precompile_fuzzer.go index cab2bcba3..763ed56e9 100644 --- a/tests/fuzzers/bls12381/precompile_fuzzer.go +++ b/tests/fuzzers/bls12381/precompile_fuzzer.go @@ -36,16 +36,6 @@ const ( blsMapG2 = byte(18) ) -func FuzzG1Add(data []byte) int { return fuzz(blsG1Add, data) } -func FuzzG1Mul(data []byte) int { return fuzz(blsG1Mul, data) } -func FuzzG1MultiExp(data []byte) int { return fuzz(blsG1MultiExp, data) } -func FuzzG2Add(data []byte) int { return fuzz(blsG2Add, data) } -func FuzzG2Mul(data []byte) int { return fuzz(blsG2Mul, data) } -func FuzzG2MultiExp(data []byte) int { return fuzz(blsG2MultiExp, data) } -func FuzzPairing(data []byte) int { return fuzz(blsPairing, data) } -func FuzzMapG1(data []byte) int { return fuzz(blsMapG1, data) } -func FuzzMapG2(data []byte) int { return fuzz(blsMapG2, data) } - func checkInput(id byte, inputLen int) bool { switch id { case blsG1Add: diff --git a/tests/fuzzers/bn256/bn256_fuzz.go b/tests/fuzzers/bn256/bn256_fuzz.go index abf1b8861..75f7d59de 100644 --- a/tests/fuzzers/bn256/bn256_fuzz.go +++ b/tests/fuzzers/bn256/bn256_fuzz.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 gofuzz -// +build gofuzz - package bn256 import ( @@ -64,8 +61,8 @@ func getG2Points(input io.Reader) (*cloudflare.G2, *google.G2, *bn254.G2Affine) return xc, xg, xs } -// FuzzAdd fuzzez bn256 addition between the Google and Cloudflare libraries. -func FuzzAdd(data []byte) int { +// fuzzAdd fuzzez bn256 addition between the Google and Cloudflare libraries. +func fuzzAdd(data []byte) int { input := bytes.NewReader(data) xc, xg, xs := getG1Points(input) if xc == nil { @@ -97,9 +94,9 @@ func FuzzAdd(data []byte) int { return 1 } -// FuzzMul fuzzez bn256 scalar multiplication between the Google and Cloudflare +// fuzzMul fuzzez bn256 scalar multiplication between the Google and Cloudflare // libraries. -func FuzzMul(data []byte) int { +func fuzzMul(data []byte) int { input := bytes.NewReader(data) pc, pg, ps := getG1Points(input) if pc == nil { @@ -139,7 +136,7 @@ func FuzzMul(data []byte) int { return 1 } -func FuzzPair(data []byte) int { +func fuzzPair(data []byte) int { input := bytes.NewReader(data) pc, pg, ps := getG1Points(input) if pc == nil { diff --git a/internal/debug/trace_fallback.go b/tests/fuzzers/bn256/bn256_test.go similarity index 67% rename from internal/debug/trace_fallback.go rename to tests/fuzzers/bn256/bn256_test.go index ec07d991e..8b2f96228 100644 --- a/internal/debug/trace_fallback.go +++ b/tests/fuzzers/bn256/bn256_test.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,24 @@ // 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 bn256 -// no-op implementation of tracing methods for Go < 1.5. +import "testing" -package debug - -import "errors" +func FuzzAdd(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzzAdd(data) + }) +} -func (*HandlerT) StartGoTrace(string) error { - return errors.New("tracing is not supported on Go < 1.5") +func FuzzMul(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzzMul(data) + }) } -func (*HandlerT) StopGoTrace() error { - return errors.New("tracing is not supported on Go < 1.5") +func FuzzPair(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzzPair(data) + }) } diff --git a/tests/fuzzers/difficulty/debug/main.go b/tests/fuzzers/difficulty/debug/main.go deleted file mode 100644 index 70cf09256..000000000 --- a/tests/fuzzers/difficulty/debug/main.go +++ /dev/null @@ -1,38 +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 main - -import ( - "fmt" - "os" - - "github.com/ethereum/go-ethereum/tests/fuzzers/difficulty" -) - -func main() { - if len(os.Args) != 2 { - fmt.Fprintf(os.Stderr, "Usage: debug ") - os.Exit(1) - } - crasher := os.Args[1] - data, err := os.ReadFile(crasher) - if err != nil { - fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) - os.Exit(1) - } - difficulty.Fuzz(data) -} diff --git a/tests/fuzzers/difficulty/difficulty-fuzz.go b/tests/fuzzers/difficulty/difficulty-fuzz.go index e8753bb62..fbbd7f687 100644 --- a/tests/fuzzers/difficulty/difficulty-fuzz.go +++ b/tests/fuzzers/difficulty/difficulty-fuzz.go @@ -75,7 +75,7 @@ func (f *fuzzer) readBool() bool { // - 0 otherwise // // other values are reserved for future use. -func Fuzz(data []byte) int { +func fuzz(data []byte) int { f := fuzzer{ input: bytes.NewReader(data), exhausted: false, diff --git a/internal/debug/loudpanic_fallback.go b/tests/fuzzers/difficulty/difficulty_test.go similarity index 78% rename from internal/debug/loudpanic_fallback.go rename to tests/fuzzers/difficulty/difficulty_test.go index 377490e5b..49beedb48 100644 --- a/internal/debug/loudpanic_fallback.go +++ b/tests/fuzzers/difficulty/difficulty_test.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,12 +14,12 @@ // 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 difficulty -package debug +import "testing" -// LoudPanic panics in a way that gets all goroutine stacks printed on stderr. -func LoudPanic(x interface{}) { - panic(x) +func Fuzz(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(data) + }) } diff --git a/tests/fuzzers/keystore/corpus/0176eaf52ed014ec5c91cf4afa070dd3fd469077-1 b/tests/fuzzers/keystore/corpus/0176eaf52ed014ec5c91cf4afa070dd3fd469077-1 deleted file mode 100644 index 1c0ecf525..000000000 --- a/tests/fuzzers/keystore/corpus/0176eaf52ed014ec5c91cf4afa070dd3fd469077-1 +++ /dev/null @@ -1 +0,0 @@ -ns©›,²Ô \ No newline at end of file diff --git a/tests/fuzzers/les/debug/main.go b/tests/fuzzers/les/debug/main.go deleted file mode 100644 index 77a612703..000000000 --- a/tests/fuzzers/les/debug/main.go +++ /dev/null @@ -1,40 +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 main - -import ( - "fmt" - "os" - - "github.com/ethereum/go-ethereum/tests/fuzzers/les" -) - -func main() { - if len(os.Args) != 2 { - fmt.Fprintf(os.Stderr, "Usage: debug \n") - fmt.Fprintf(os.Stderr, "Example\n") - fmt.Fprintf(os.Stderr, " $ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n") - os.Exit(1) - } - crasher := os.Args[1] - data, err := os.ReadFile(crasher) - if err != nil { - fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) - os.Exit(1) - } - les.Fuzz(data) -} diff --git a/tests/fuzzers/les/les-fuzzer.go b/tests/fuzzers/les/les-fuzzer.go deleted file mode 100644 index c62253a72..000000000 --- a/tests/fuzzers/les/les-fuzzer.go +++ /dev/null @@ -1,411 +0,0 @@ -// Copyright 2021 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" - "encoding/binary" - "io" - "math/big" - - "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/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" - l "github.com/ethereum/go-ethereum/les" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/trie" -) - -var ( - bankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - bankAddr = crypto.PubkeyToAddress(bankKey.PublicKey) - bankFunds = new(big.Int).Mul(big.NewInt(100), big.NewInt(params.Ether)) - - testChainLen = 256 - testContractCode = common.Hex2Bytes("606060405260cc8060106000396000f360606040526000357c01000000000000000000000000000000000000000000000000000000009004806360cd2685146041578063c16431b914606b57603f565b005b6055600480803590602001909190505060a9565b6040518082815260200191505060405180910390f35b60886004808035906020019091908035906020019091905050608a565b005b80600060005083606481101560025790900160005b50819055505b5050565b6000600060005082606481101560025790900160005b5054905060c7565b91905056") - - chain *core.BlockChain - addresses []common.Address - txHashes []common.Hash - - chtTrie *trie.Trie - bloomTrie *trie.Trie - chtKeys [][]byte - bloomKeys [][]byte -) - -func makechain() (bc *core.BlockChain, addresses []common.Address, txHashes []common.Hash) { - gspec := &core.Genesis{ - Config: params.TestChainConfig, - Alloc: core.GenesisAlloc{bankAddr: {Balance: bankFunds}}, - GasLimit: 100000000, - } - signer := types.HomesteadSigner{} - _, blocks, _ := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), testChainLen, - func(i int, gen *core.BlockGen) { - var ( - tx *types.Transaction - addr common.Address - ) - nonce := uint64(i) - if i%4 == 0 { - tx, _ = types.SignTx(types.NewContractCreation(nonce, big.NewInt(0), 200000, big.NewInt(0), testContractCode), signer, bankKey) - addr = crypto.CreateAddress(bankAddr, nonce) - } else { - addr = common.BigToAddress(big.NewInt(int64(i))) - tx, _ = types.SignTx(types.NewTransaction(nonce, addr, big.NewInt(10000), params.TxGas, big.NewInt(params.GWei), nil), signer, bankKey) - } - gen.AddTx(tx) - addresses = append(addresses, addr) - txHashes = append(txHashes, tx.Hash()) - }) - bc, _ = core.NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) - if _, err := bc.InsertChain(blocks); err != nil { - panic(err) - } - return -} - -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())) - for i := 0; i < testChainLen; i++ { - // The element in CHT is -> - key := make([]byte, 8) - binary.BigEndian.PutUint64(key, uint64(i+1)) - chtTrie.MustUpdate(key, []byte{0x1, 0xf}) - chtKeys = append(chtKeys, key) - - // The element in Bloom trie is <2 byte bit index> + -> bloom - key2 := make([]byte, 10) - binary.BigEndian.PutUint64(key2[2:], uint64(i+1)) - bloomTrie.MustUpdate(key2, []byte{0x2, 0xe}) - bloomKeys = append(bloomKeys, key2) - } - return -} - -func init() { - chain, addresses, txHashes = makechain() - chtTrie, bloomTrie, chtKeys, bloomKeys = makeTries() -} - -type fuzzer struct { - chain *core.BlockChain - pool *txpool.TxPool - - chainLen int - addresses []common.Address - txs []common.Hash - nonce uint64 - - chtKeys [][]byte - bloomKeys [][]byte - chtTrie *trie.Trie - bloomTrie *trie.Trie - - input io.Reader - exhausted bool -} - -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, - addresses: addresses, - txs: txHashes, - chtTrie: chtTrie, - bloomTrie: bloomTrie, - chtKeys: chtKeys, - bloomKeys: bloomKeys, - nonce: uint64(len(txHashes)), - pool: txpool, - input: bytes.NewReader(input), - } -} - -func (f *fuzzer) read(size int) []byte { - out := make([]byte, size) - if _, err := f.input.Read(out); err != nil { - f.exhausted = true - } - return out -} - -func (f *fuzzer) randomByte() byte { - d := f.read(1) - return d[0] -} - -func (f *fuzzer) randomBool() bool { - d := f.read(1) - return d[0]&1 == 1 -} - -func (f *fuzzer) randomInt(max int) int { - if max == 0 { - return 0 - } - if max <= 256 { - return int(f.randomByte()) % max - } - var a uint16 - if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil { - f.exhausted = true - } - return int(a % uint16(max)) -} - -func (f *fuzzer) randomX(max int) uint64 { - var a uint16 - if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil { - f.exhausted = true - } - if a < 0x8000 { - return uint64(a%uint16(max+1)) - 1 - } - return (uint64(1)<<(a%64+1) - 1) & (uint64(a) * 343897772345826595) -} - -func (f *fuzzer) randomBlockHash() common.Hash { - h := f.chain.GetCanonicalHash(uint64(f.randomInt(3 * f.chainLen))) - if h != (common.Hash{}) { - return h - } - return common.BytesToHash(f.read(common.HashLength)) -} - -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.AddressLength) -} - -func (f *fuzzer) randomCHTTrieKey() []byte { - i := f.randomInt(3 * len(f.chtKeys)) - if i < len(f.chtKeys) { - return f.chtKeys[i] - } - return f.read(8) -} - -func (f *fuzzer) randomBloomTrieKey() []byte { - i := f.randomInt(3 * len(f.bloomKeys)) - if i < len(f.bloomKeys) { - return f.bloomKeys[i] - } - return f.read(10) -} - -func (f *fuzzer) randomTxHash() common.Hash { - i := f.randomInt(3 * len(f.txs)) - if i < len(f.txs) { - return f.txs[i] - } - return common.BytesToHash(f.read(common.HashLength)) -} - -func (f *fuzzer) BlockChain() *core.BlockChain { - return f.chain -} - -func (f *fuzzer) TxPool() *txpool.TxPool { - return f.pool -} - -func (f *fuzzer) ArchiveMode() bool { - return false -} - -func (f *fuzzer) AddTxsSync() bool { - return false -} - -func (f *fuzzer) GetHelperTrie(typ uint, index uint64) *trie.Trie { - if typ == 0 { - return f.chtTrie - } else if typ == 1 { - return f.bloomTrie - } - return nil -} - -type dummyMsg struct { - data []byte -} - -func (d dummyMsg) Decode(val interface{}) error { - return rlp.DecodeBytes(d.data, val) -} - -func (f *fuzzer) doFuzz(msgCode uint64, packet interface{}) { - enc, err := rlp.EncodeToBytes(packet) - if err != nil { - panic(err) - } - version := f.randomInt(3) + 2 // [LES2, LES3, LES4] - peer, closeFn := l.NewFuzzerPeer(version) - defer closeFn() - fn, _, _, err := l.Les3[msgCode].Handle(dummyMsg{enc}) - if err != nil { - panic(err) - } - fn(f, peer, func() bool { return true }) -} - -func Fuzz(input []byte) int { - // We expect some large inputs - if len(input) < 100 { - return -1 - } - f := newFuzzer(input) - if f.exhausted { - return -1 - } - for !f.exhausted { - switch f.randomInt(8) { - case 0: - req := &l.GetBlockHeadersPacket{ - Query: l.GetBlockHeadersData{ - Amount: f.randomX(l.MaxHeaderFetch + 1), - Skip: f.randomX(10), - Reverse: f.randomBool(), - }, - } - if f.randomBool() { - req.Query.Origin.Hash = f.randomBlockHash() - } else { - req.Query.Origin.Number = uint64(f.randomInt(f.chainLen * 2)) - } - f.doFuzz(l.GetBlockHeadersMsg, req) - - case 1: - req := &l.GetBlockBodiesPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxBodyFetch+1))} - for i := range req.Hashes { - req.Hashes[i] = f.randomBlockHash() - } - f.doFuzz(l.GetBlockBodiesMsg, req) - - case 2: - 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(), - AccountAddress: f.randomAddress(), - } - } - f.doFuzz(l.GetCodeMsg, req) - - case 3: - req := &l.GetReceiptsPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxReceiptFetch+1))} - for i := range req.Hashes { - req.Hashes[i] = f.randomBlockHash() - } - f.doFuzz(l.GetReceiptsMsg, req) - - case 4: - req := &l.GetProofsPacket{Reqs: make([]l.ProofReq, f.randomInt(l.MaxProofsFetch+1))} - for i := range req.Reqs { - if f.randomBool() { - req.Reqs[i] = l.ProofReq{ - 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.randomAddress(), - FromLevel: uint(f.randomX(3)), - } - } - } - f.doFuzz(l.GetProofsV2Msg, req) - - case 5: - req := &l.GetHelperTrieProofsPacket{Reqs: make([]l.HelperTrieReq, f.randomInt(l.MaxHelperTrieProofsFetch+1))} - for i := range req.Reqs { - switch f.randomInt(3) { - case 0: - // Canonical hash trie - req.Reqs[i] = l.HelperTrieReq{ - Type: 0, - TrieIdx: f.randomX(3), - Key: f.randomCHTTrieKey(), - FromLevel: uint(f.randomX(3)), - AuxReq: uint(2), - } - case 1: - // Bloom trie - req.Reqs[i] = l.HelperTrieReq{ - Type: 1, - TrieIdx: f.randomX(3), - Key: f.randomBloomTrieKey(), - FromLevel: uint(f.randomX(3)), - AuxReq: 0, - } - default: - // Random trie - req.Reqs[i] = l.HelperTrieReq{ - Type: 2, - TrieIdx: f.randomX(3), - Key: f.randomCHTTrieKey(), - FromLevel: uint(f.randomX(3)), - AuxReq: 0, - } - } - } - f.doFuzz(l.GetHelperTrieProofsMsg, req) - - case 6: - req := &l.SendTxPacket{Txs: make([]*types.Transaction, f.randomInt(l.MaxTxSend+1))} - signer := types.HomesteadSigner{} - for i := range req.Txs { - var nonce uint64 - if f.randomBool() { - nonce = uint64(f.randomByte()) - } else { - nonce = f.nonce - f.nonce += 1 - } - req.Txs[i], _ = types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(10000), params.TxGas, big.NewInt(1000000000*int64(f.randomByte())), nil), signer, bankKey) - } - f.doFuzz(l.SendTxV2Msg, req) - - case 7: - req := &l.GetTxStatusPacket{Hashes: make([]common.Hash, f.randomInt(l.MaxTxStatus+1))} - for i := range req.Hashes { - req.Hashes[i] = f.randomTxHash() - } - f.doFuzz(l.GetTxStatusMsg, req) - } - } - return 0 -} diff --git a/tests/fuzzers/rangeproof/debug/main.go b/tests/fuzzers/rangeproof/debug/main.go deleted file mode 100644 index d4cab8ec4..000000000 --- a/tests/fuzzers/rangeproof/debug/main.go +++ /dev/null @@ -1,40 +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 main - -import ( - "fmt" - "os" - - "github.com/ethereum/go-ethereum/tests/fuzzers/rangeproof" -) - -func main() { - if len(os.Args) != 2 { - fmt.Fprintf(os.Stderr, "Usage: debug \n") - fmt.Fprintf(os.Stderr, "Example\n") - fmt.Fprintf(os.Stderr, " $ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n") - os.Exit(1) - } - crasher := os.Args[1] - data, err := os.ReadFile(crasher) - if err != nil { - fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) - os.Exit(1) - } - rangeproof.Fuzz(data) -} diff --git a/tests/fuzzers/rangeproof/rangeproof-fuzzer.go b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go index aa81e5c9d..6b5ca9088 100644 --- a/tests/fuzzers/rangeproof/rangeproof-fuzzer.go +++ b/tests/fuzzers/rangeproof/rangeproof-fuzzer.go @@ -56,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 @@ -128,7 +128,7 @@ func (f *fuzzer) fuzz() int { if len(keys) == 0 { return 0 } - var first, last = keys[0], keys[len(keys)-1] + var first = keys[0] testcase %= 6 switch testcase { case 0: @@ -165,7 +165,7 @@ func (f *fuzzer) fuzz() int { } ok = 1 //nodes, subtrie - hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, last, keys, vals, proof) + hasMore, err := trie.VerifyRangeProof(tr.Hash(), first, keys, vals, proof) if err != nil { if hasMore { panic("err != nil && hasMore == true") @@ -185,7 +185,7 @@ func (f *fuzzer) fuzz() int { // - 0 otherwise // // other values are reserved for future use. -func Fuzz(input []byte) int { +func fuzz(input []byte) int { if len(input) < 100 { return 0 } diff --git a/tests/fuzzers/rangeproof/rangeproof_test.go b/tests/fuzzers/rangeproof/rangeproof_test.go new file mode 100644 index 000000000..bc7badc5b --- /dev/null +++ b/tests/fuzzers/rangeproof/rangeproof_test.go @@ -0,0 +1,25 @@ +// 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 rangeproof + +import "testing" + +func Fuzz(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(data) + }) +} diff --git a/tests/fuzzers/rlp/corpus/block_with_uncle.rlp b/tests/fuzzers/rlp/corpus/block_with_uncle.rlp deleted file mode 100644 index 1b49fe6a0..000000000 Binary files a/tests/fuzzers/rlp/corpus/block_with_uncle.rlp and /dev/null differ diff --git a/tests/fuzzers/rlp/corpus/r.bin b/tests/fuzzers/rlp/corpus/r.bin deleted file mode 100644 index cb98a76a8..000000000 --- a/tests/fuzzers/rlp/corpus/r.bin +++ /dev/null @@ -1 +0,0 @@ -Ë€€€À€ÀÃÀÀÀÀ \ No newline at end of file diff --git a/tests/fuzzers/rlp/corpus/transaction.rlp b/tests/fuzzers/rlp/corpus/transaction.rlp deleted file mode 100644 index 80eea1aec..000000000 --- a/tests/fuzzers/rlp/corpus/transaction.rlp +++ /dev/null @@ -1,2 +0,0 @@ -øNƒ“à€€€‚ -• aùËåÀP?-'´{ÏЋDY¯³fÆj\ÿE÷ ~ì•ÒçF?1(íij6@Év ±LÀÝÚ‘‘ \ No newline at end of file diff --git a/tests/fuzzers/rlp/rlp_fuzzer.go b/tests/fuzzers/rlp/rlp_fuzzer.go deleted file mode 100644 index 9fcdb5776..000000000 --- a/tests/fuzzers/rlp/rlp_fuzzer.go +++ /dev/null @@ -1,143 +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 rlp - -import ( - "bytes" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" - "github.com/holiman/uint256" -) - -func decodeEncode(input []byte, val interface{}, i int) { - if err := rlp.DecodeBytes(input, val); err == nil { - output, err := rlp.EncodeToBytes(val) - if err != nil { - panic(err) - } - if !bytes.Equal(input, output) { - panic(fmt.Sprintf("case %d: encode-decode is not equal, \ninput : %x\noutput: %x", i, input, output)) - } - } -} - -func Fuzz(input []byte) int { - if len(input) == 0 { - return 0 - } - if len(input) > 500*1024 { - return 0 - } - - var i int - { - rlp.Split(input) - } - { - if elems, _, err := rlp.SplitList(input); err == nil { - rlp.CountValues(elems) - } - } - - { - rlp.NewStream(bytes.NewReader(input), 0).Decode(new(interface{})) - } - - { - decodeEncode(input, new(interface{}), i) - i++ - } - { - var v struct { - Int uint - String string - Bytes []byte - } - decodeEncode(input, &v, i) - i++ - } - - { - type Types struct { - Bool bool - Raw rlp.RawValue - Slice []*Types - Iface []interface{} - } - var v Types - decodeEncode(input, &v, i) - i++ - } - { - type AllTypes struct { - Int uint - String string - Bytes []byte - Bool bool - Raw rlp.RawValue - Slice []*AllTypes - Array [3]*AllTypes - Iface []interface{} - } - var v AllTypes - decodeEncode(input, &v, i) - i++ - } - { - decodeEncode(input, [10]byte{}, i) - i++ - } - { - var v struct { - Byte [10]byte - Rool [10]bool - } - decodeEncode(input, &v, i) - i++ - } - { - var h types.Header - decodeEncode(input, &h, i) - i++ - var b types.Block - decodeEncode(input, &b, i) - i++ - var t types.Transaction - decodeEncode(input, &t, i) - i++ - var txs types.Transactions - decodeEncode(input, &txs, i) - i++ - var rs types.Receipts - decodeEncode(input, &rs, i) - } - { - i++ - var v struct { - AnIntPtr *big.Int - AnInt big.Int - AnU256Ptr *uint256.Int - AnU256 uint256.Int - NotAnU256 [4]uint64 - } - decodeEncode(input, &v, i) - } - return 1 -} diff --git a/tests/fuzzers/runtime/runtime_fuzz.go b/tests/fuzzers/runtime/runtime_fuzz.go deleted file mode 100644 index b30e9243d..000000000 --- a/tests/fuzzers/runtime/runtime_fuzz.go +++ /dev/null @@ -1,36 +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 runtime - -import ( - "github.com/ethereum/go-ethereum/core/vm/runtime" -) - -// Fuzz is the basic entry point for the go-fuzz tool -// -// This returns 1 for valid parse:able/runnable code, 0 -// for invalid opcode. -func Fuzz(input []byte) int { - _, _, err := runtime.Execute(input, input, &runtime.Config{ - GasLimit: 12000000, - }) - // invalid opcode - if err != nil && len(err.Error()) > 6 && err.Error()[:7] == "invalid" { - return 0 - } - return 1 -} diff --git a/tests/fuzzers/secp256k1/secp_fuzzer.go b/tests/fuzzers/secp256k1/secp_fuzzer.go deleted file mode 100644 index 47083d5fe..000000000 --- a/tests/fuzzers/secp256k1/secp_fuzzer.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2021 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// build +gofuzz - -package secp256k1 - -import ( - "fmt" - - "github.com/btcsuite/btcd/btcec/v2" - "github.com/ethereum/go-ethereum/crypto/secp256k1" - fuzz "github.com/google/gofuzz" -) - -func Fuzz(input []byte) int { - var ( - fuzzer = fuzz.NewFromGoFuzz(input) - curveA = secp256k1.S256() - curveB = btcec.S256() - dataP1 []byte - dataP2 []byte - ) - // first point - fuzzer.Fuzz(&dataP1) - x1, y1 := curveB.ScalarBaseMult(dataP1) - // second point - fuzzer.Fuzz(&dataP2) - x2, y2 := curveB.ScalarBaseMult(dataP2) - resAX, resAY := curveA.Add(x1, y1, x2, y2) - resBX, resBY := curveB.Add(x1, y1, x2, y2) - if resAX.Cmp(resBX) != 0 || resAY.Cmp(resBY) != 0 { - fmt.Printf("%s %s %s %s\n", x1, y1, x2, y2) - panic(fmt.Sprintf("Addition failed: geth: %s %s btcd: %s %s", resAX, resAY, resBX, resBY)) - } - return 0 -} diff --git a/tests/fuzzers/secp256k1/secp_test.go b/tests/fuzzers/secp256k1/secp_test.go index 0ca16cb9b..ca3039764 100644 --- a/tests/fuzzers/secp256k1/secp_test.go +++ b/tests/fuzzers/secp256k1/secp_test.go @@ -16,9 +16,38 @@ package secp256k1 -import "testing" +import ( + "fmt" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/ethereum/go-ethereum/crypto/secp256k1" +) func TestFuzzer(t *testing.T) { - test := "00000000N0000000/R00000000000000000U0000S0000000mkhP000000000000000U" - Fuzz([]byte(test)) + a, b := "00000000N0000000/R0000000000000000", "0U0000S0000000mkhP000000000000000U" + fuzz([]byte(a), []byte(b)) +} + +func Fuzz(f *testing.F) { + f.Fuzz(func(t *testing.T, a, b []byte) { + fuzz(a, b) + }) +} + +func fuzz(dataP1, dataP2 []byte) { + var ( + curveA = secp256k1.S256() + curveB = btcec.S256() + ) + // first point + x1, y1 := curveB.ScalarBaseMult(dataP1) + // second points + x2, y2 := curveB.ScalarBaseMult(dataP2) + resAX, resAY := curveA.Add(x1, y1, x2, y2) + resBX, resBY := curveB.Add(x1, y1, x2, y2) + if resAX.Cmp(resBX) != 0 || resAY.Cmp(resBY) != 0 { + fmt.Printf("%s %s %s %s\n", x1, y1, x2, y2) + panic(fmt.Sprintf("Addition failed: geth: %s %s btcd: %s %s", resAX, resAY, resBX, resBY)) + } } diff --git a/tests/fuzzers/stacktrie/debug/main.go b/tests/fuzzers/stacktrie/debug/main.go deleted file mode 100644 index 6b634f05c..000000000 --- a/tests/fuzzers/stacktrie/debug/main.go +++ /dev/null @@ -1,38 +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 main - -import ( - "fmt" - "os" - - "github.com/ethereum/go-ethereum/tests/fuzzers/stacktrie" -) - -func main() { - if len(os.Args) != 2 { - fmt.Fprintf(os.Stderr, "Usage: debug ") - os.Exit(1) - } - crasher := os.Args[1] - data, err := os.ReadFile(crasher) - if err != nil { - fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) - os.Exit(1) - } - stacktrie.Debug(data) -} diff --git a/tests/fuzzers/stacktrie/trie_fuzzer.go b/tests/fuzzers/stacktrie/trie_fuzzer.go deleted file mode 100644 index 391bdf300..000000000 --- a/tests/fuzzers/stacktrie/trie_fuzzer.go +++ /dev/null @@ -1,248 +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 stacktrie - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "hash" - "io" - - "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/trie" - "github.com/ethereum/go-ethereum/trie/trienode" - "golang.org/x/crypto/sha3" - "golang.org/x/exp/slices" -) - -type fuzzer struct { - input io.Reader - exhausted bool - debugging bool -} - -func (f *fuzzer) read(size int) []byte { - out := make([]byte, size) - if _, err := f.input.Read(out); err != nil { - f.exhausted = true - } - return out -} - -func (f *fuzzer) readSlice(min, max int) []byte { - var a uint16 - binary.Read(f.input, binary.LittleEndian, &a) - size := min + int(a)%(max-min) - out := make([]byte, size) - if _, err := f.input.Read(out); err != nil { - f.exhausted = true - } - return out -} - -// spongeDb is a dummy db backend which accumulates writes in a sponge -type spongeDb struct { - sponge hash.Hash - debug bool -} - -func (s *spongeDb) Has(key []byte) (bool, error) { panic("implement me") } -func (s *spongeDb) Get(key []byte) ([]byte, error) { return nil, errors.New("no such elem") } -func (s *spongeDb) Delete(key []byte) error { panic("implement me") } -func (s *spongeDb) NewBatch() ethdb.Batch { return &spongeBatch{s} } -func (s *spongeDb) NewBatchWithSize(size int) ethdb.Batch { return &spongeBatch{s} } -func (s *spongeDb) NewSnapshot() (ethdb.Snapshot, error) { panic("implement me") } -func (s *spongeDb) Stat(property string) (string, error) { panic("implement me") } -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 { - if s.debug { - fmt.Printf("db.Put %x : %x\n", key, value) - } - s.sponge.Write(key) - s.sponge.Write(value) - return nil -} -func (s *spongeDb) NewIterator(prefix []byte, start []byte) ethdb.Iterator { panic("implement me") } - -// spongeBatch is a dummy batch which immediately writes to the underlying spongedb -type spongeBatch struct { - db *spongeDb -} - -func (b *spongeBatch) Put(key, value []byte) error { - b.db.Put(key, value) - return nil -} -func (b *spongeBatch) Delete(key []byte) error { panic("implement me") } -func (b *spongeBatch) ValueSize() int { return 100 } -func (b *spongeBatch) Write() error { return nil } -func (b *spongeBatch) Reset() {} -func (b *spongeBatch) Replay(w ethdb.KeyValueWriter) error { return nil } - -type kv struct { - k, v []byte -} - -// Fuzz is the fuzzing entry-point. -// The function must return -// -// - 1 if the fuzzer should increase priority of the -// given input during subsequent fuzzing (for example, the input is lexically -// correct and was parsed successfully); -// - -1 if the input must not be added to corpus even if gives new coverage; and -// - 0 otherwise -// -// other values are reserved for future use. -func Fuzz(data []byte) int { - f := fuzzer{ - input: bytes.NewReader(data), - exhausted: false, - } - return f.fuzz() -} - -func Debug(data []byte) int { - f := fuzzer{ - input: bytes.NewReader(data), - exhausted: false, - debugging: true, - } - return f.fuzz() -} - -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)) - 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()) - }) - vals []kv - useful bool - maxElements = 10000 - // operate on unique keys only - keys = make(map[string]struct{}) - ) - // Fill the trie with elements - for i := 0; !f.exhausted && i < maxElements; i++ { - k := f.read(32) - v := f.readSlice(1, 500) - if f.exhausted { - // If it was exhausted while reading, the value may be all zeroes, - // thus 'deletion' which is not supported on stacktrie - break - } - if _, present := keys[string(k)]; present { - // This key is a duplicate, ignore it - continue - } - keys[string(k)] = struct{}{} - vals = append(vals, kv{k: k, v: v}) - trieA.MustUpdate(k, v) - useful = true - } - if !useful { - return 0 - } - // Flush trie -> database - rootA, nodes, err := trieA.Commit(false) - if err != nil { - panic(err) - } - if nodes != nil { - dbA.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) - } - // Flush memdb -> disk (sponge) - dbA.Commit(rootA, false) - - // Stacktrie requires sorted insertion - 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) - } - trieB.MustUpdate(kv.k, kv.v) - } - rootB := trieB.Hash() - trieB.Commit() - if rootA != rootB { - panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootB)) - } - sumA := spongeA.sponge.Sum(nil) - sumB := spongeB.sponge.Sum(nil) - if !bytes.Equal(sumA, sumB) { - panic(fmt.Sprintf("sequence differ: (trie) %x != %x (stacktrie)", sumA, sumB)) - } - - // 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) { - 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 - ) - for _, kv := range vals { - trieC.MustUpdate(kv.k, kv.v) - } - rootC, _ := trieC.Commit() - if rootA != rootC { - panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootC)) - } - trieA, _ = trie.New(trie.TrieID(rootA), dbA) - iterA := trieA.MustNodeIterator(nil) - for iterA.Next(true) { - if iterA.Hash() == (common.Hash{}) { - if _, present := nodeset[string(iterA.Path())]; present { - panic("unexpected tiny node") - } - continue - } - nodeBlob, present := nodeset[string(iterA.Path())] - if !present { - panic("missing node") - } - if !bytes.Equal(nodeBlob, iterA.NodeBlob()) { - panic("node blob is not matched") - } - checked += 1 - } - if checked != len(nodeset) { - panic("node number is not matched") - } - return 1 -} diff --git a/tests/fuzzers/trie/corpus/data b/tests/fuzzers/trie/corpus/data deleted file mode 100644 index c4a4839cb..000000000 --- a/tests/fuzzers/trie/corpus/data +++ /dev/null @@ -1 +0,0 @@ -asdlfkjasf23oiejfasdfadkfqlkjfasdlkfjalwk4jfalsdkfjawlefkjsadlfkjasldkfjwalefkjasdlfkjM \ No newline at end of file diff --git a/tests/fuzzers/trie/trie-fuzzer.go b/tests/fuzzers/trie/trie-fuzzer.go deleted file mode 100644 index fe9bf3d0f..000000000 --- a/tests/fuzzers/trie/trie-fuzzer.go +++ /dev/null @@ -1,201 +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 trie - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/trie" - "github.com/ethereum/go-ethereum/trie/trienode" -) - -// randTest performs random trie operations. -// Instances of this test are created by Generate. -type randTest []randTestStep - -type randTestStep struct { - op int - key []byte // for opUpdate, opDelete, opGet - value []byte // for opUpdate - err error // for debugging -} - -type proofDb struct{} - -func (proofDb) Put(key []byte, value []byte) error { - return nil -} - -func (proofDb) Delete(key []byte) error { - return nil -} - -const ( - opUpdate = iota - opDelete - opGet - opHash - opCommit - opItercheckhash - opProve - opMax // boundary value, not an actual op -) - -type dataSource struct { - input []byte - reader *bytes.Reader -} - -func newDataSource(input []byte) *dataSource { - return &dataSource{ - input, bytes.NewReader(input), - } -} -func (ds *dataSource) readByte() byte { - if b, err := ds.reader.ReadByte(); err != nil { - return 0 - } else { - return b - } -} -func (ds *dataSource) Read(buf []byte) (int, error) { - return ds.reader.Read(buf) -} -func (ds *dataSource) Ended() bool { - return ds.reader.Len() == 0 -} - -func Generate(input []byte) randTest { - var allKeys [][]byte - r := newDataSource(input) - genKey := func() []byte { - if len(allKeys) < 2 || r.readByte() < 0x0f { - // new key - key := make([]byte, r.readByte()%50) - r.Read(key) - allKeys = append(allKeys, key) - return key - } - // use existing key - return allKeys[int(r.readByte())%len(allKeys)] - } - - var steps randTest - - for i := 0; !r.Ended(); i++ { - step := randTestStep{op: int(r.readByte()) % opMax} - switch step.op { - case opUpdate: - step.key = genKey() - step.value = make([]byte, 8) - binary.BigEndian.PutUint64(step.value, uint64(i)) - case opGet, opDelete, opProve: - step.key = genKey() - } - steps = append(steps, step) - if len(steps) > 500 { - break - } - } - - return steps -} - -// Fuzz is the fuzzing entry-point. -// The function must return -// -// - 1 if the fuzzer should increase priority of the -// given input during subsequent fuzzing (for example, the input is lexically -// correct and was parsed successfully); -// - -1 if the input must not be added to corpus even if gives new coverage; and -// - 0 otherwise -// -// other values are reserved for future use. -func Fuzz(input []byte) int { - program := Generate(input) - if len(program) == 0 { - return 0 - } - if err := runRandTest(program); err != nil { - panic(err) - } - return 1 -} - -func runRandTest(rt randTest) error { - var ( - triedb = trie.NewDatabase(rawdb.NewMemoryDatabase()) - tr = trie.NewEmpty(triedb) - origin = types.EmptyRootHash - values = make(map[string]string) // tracks content of the trie - ) - for i, step := range rt { - switch step.op { - case opUpdate: - tr.MustUpdate(step.key, step.value) - values[string(step.key)] = string(step.value) - case opDelete: - tr.MustDelete(step.key) - delete(values, string(step.key)) - case opGet: - v := tr.MustGet(step.key) - want := values[string(step.key)] - if string(v) != want { - rt[i].err = fmt.Errorf("mismatch for key %#x, got %#x want %#x", step.key, v, want) - } - case opHash: - tr.Hash() - case opCommit: - hash, nodes, err := tr.Commit(false) - if err != nil { - return err - } - if nodes != nil { - if err := triedb.Update(hash, origin, 0, trienode.NewWithNodeSet(nodes), nil); err != nil { - return err - } - } - newtr, err := trie.New(trie.TrieID(hash), triedb) - if err != nil { - return err - } - tr = newtr - origin = hash - case opItercheckhash: - checktr := trie.NewEmpty(triedb) - it := trie.NewIterator(tr.MustNodeIterator(nil)) - for it.Next() { - checktr.MustUpdate(it.Key, it.Value) - } - if tr.Hash() != checktr.Hash() { - return errors.New("hash mismatch in opItercheckhash") - } - case opProve: - rt[i].err = tr.Prove(step.key, proofDb{}) - } - // Abort the test on error. - if rt[i].err != nil { - return rt[i].err - } - } - return nil -} diff --git a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go index 56b6b1e64..51f2fc3b4 100644 --- a/tests/fuzzers/txfetcher/txfetcher_fuzzer.go +++ b/tests/fuzzers/txfetcher/txfetcher_fuzzer.go @@ -25,7 +25,6 @@ import ( "github.com/ethereum/go-ethereum/common" "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/eth/fetcher" ) @@ -49,7 +48,7 @@ func init() { } } -func Fuzz(input []byte) int { +func fuzz(input []byte) int { // Don't generate insanely large test cases, not much value in them if len(input) > 16*1024 { return 0 @@ -80,10 +79,11 @@ func Fuzz(input []byte) int { f := fetcher.NewTxFetcherForTests( func(common.Hash) bool { return false }, - func(txs []*txpool.Transaction) []error { + func(txs []*types.Transaction) []error { return make([]error, len(txs)) }, func(string, []common.Hash) error { return nil }, + nil, clock, rand, ) f.Start() @@ -117,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) @@ -125,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/fuzzers/txfetcher/txfetcher_test.go b/tests/fuzzers/txfetcher/txfetcher_test.go new file mode 100644 index 000000000..ac2e6b1c6 --- /dev/null +++ b/tests/fuzzers/txfetcher/txfetcher_test.go @@ -0,0 +1,25 @@ +// 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 txfetcher + +import "testing" + +func Fuzz(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(data) + }) +} diff --git a/tests/fuzzers/vflux/clientpool-fuzzer.go b/tests/fuzzers/vflux/clientpool-fuzzer.go deleted file mode 100644 index b3b523cc8..000000000 --- a/tests/fuzzers/vflux/clientpool-fuzzer.go +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2021 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 vflux - -import ( - "bytes" - "encoding/binary" - "io" - "math" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/common/mclock" - "github.com/ethereum/go-ethereum/ethdb/memorydb" - "github.com/ethereum/go-ethereum/les/vflux" - vfs "github.com/ethereum/go-ethereum/les/vflux/server" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/enr" - "github.com/ethereum/go-ethereum/rlp" -) - -var ( - debugMode = false - doLog = func(msg string, ctx ...interface{}) { - if !debugMode { - return - } - log.Info(msg, ctx...) - } -) - -type fuzzer struct { - peers [256]*clientPeer - disconnectList []*clientPeer - input io.Reader - exhausted bool - activeCount, activeCap uint64 - maxCount, maxCap uint64 -} - -type clientPeer struct { - fuzzer *fuzzer - node *enode.Node - freeID string - timeout time.Duration - - balance vfs.ConnectedBalance - capacity uint64 -} - -func (p *clientPeer) Node() *enode.Node { - return p.node -} - -func (p *clientPeer) FreeClientId() string { - return p.freeID -} - -func (p *clientPeer) InactiveAllowance() time.Duration { - return p.timeout -} - -func (p *clientPeer) UpdateCapacity(newCap uint64, requested bool) { - origin, originTotal := p.capacity, p.fuzzer.activeCap - p.fuzzer.activeCap -= p.capacity - if p.capacity != 0 { - p.fuzzer.activeCount-- - } - p.capacity = newCap - p.fuzzer.activeCap += p.capacity - if p.capacity != 0 { - p.fuzzer.activeCount++ - } - doLog("Update capacity", "peer", p.node.ID(), "origin", origin, "cap", newCap, "origintotal", originTotal, "total", p.fuzzer.activeCap, "requested", requested) -} - -func (p *clientPeer) Disconnect() { - origin, originTotal := p.capacity, p.fuzzer.activeCap - p.fuzzer.disconnectList = append(p.fuzzer.disconnectList, p) - p.fuzzer.activeCap -= p.capacity - if p.capacity != 0 { - p.fuzzer.activeCount-- - } - p.capacity = 0 - p.balance = nil - doLog("Disconnect", "peer", p.node.ID(), "origin", origin, "origintotal", originTotal, "total", p.fuzzer.activeCap) -} - -func newFuzzer(input []byte) *fuzzer { - f := &fuzzer{ - input: bytes.NewReader(input), - } - for i := range f.peers { - f.peers[i] = &clientPeer{ - fuzzer: f, - node: enode.SignNull(new(enr.Record), enode.ID{byte(i)}), - freeID: string([]byte{byte(i)}), - timeout: f.randomDelay(), - } - } - return f -} - -func (f *fuzzer) read(size int) []byte { - out := make([]byte, size) - if _, err := f.input.Read(out); err != nil { - f.exhausted = true - } - return out -} - -func (f *fuzzer) randomByte() byte { - d := f.read(1) - return d[0] -} - -func (f *fuzzer) randomBool() bool { - d := f.read(1) - return d[0]&1 == 1 -} - -func (f *fuzzer) randomInt(max int) int { - if max == 0 { - return 0 - } - if max <= 256 { - return int(f.randomByte()) % max - } - var a uint16 - if err := binary.Read(f.input, binary.LittleEndian, &a); err != nil { - f.exhausted = true - } - return int(a % uint16(max)) -} - -func (f *fuzzer) randomTokenAmount(signed bool) int64 { - x := uint64(f.randomInt(65000)) - x = x * x * x * x - - if signed && (x&1) == 1 { - if x <= math.MaxInt64 { - return -int64(x) - } - return math.MinInt64 - } - if x <= math.MaxInt64 { - return int64(x) - } - return math.MaxInt64 -} - -func (f *fuzzer) randomDelay() time.Duration { - delay := f.randomByte() - if delay < 128 { - return time.Duration(delay) * time.Second - } - return 0 -} - -func (f *fuzzer) randomFactors() vfs.PriceFactors { - return vfs.PriceFactors{ - TimeFactor: float64(f.randomByte()) / 25500, - CapacityFactor: float64(f.randomByte()) / 255, - RequestFactor: float64(f.randomByte()) / 255, - } -} - -func (f *fuzzer) connectedBalanceOp(balance vfs.ConnectedBalance, id enode.ID) { - switch f.randomInt(3) { - case 0: - cost := uint64(f.randomTokenAmount(false)) - balance.RequestServed(cost) - doLog("Serve request cost", "id", id, "amount", cost) - case 1: - posFactor, negFactor := f.randomFactors(), f.randomFactors() - balance.SetPriceFactors(posFactor, negFactor) - doLog("Set price factor", "pos", posFactor, "neg", negFactor) - case 2: - balance.GetBalance() - balance.GetRawBalance() - balance.GetPriceFactors() - } -} - -func (f *fuzzer) atomicBalanceOp(balance vfs.AtomicBalanceOperator, id enode.ID) { - switch f.randomInt(3) { - case 0: - amount := f.randomTokenAmount(true) - balance.AddBalance(amount) - doLog("Add balance", "id", id, "amount", amount) - case 1: - pos, neg := uint64(f.randomTokenAmount(false)), uint64(f.randomTokenAmount(false)) - balance.SetBalance(pos, neg) - doLog("Set balance", "id", id, "pos", pos, "neg", neg) - case 2: - balance.GetBalance() - balance.GetRawBalance() - balance.GetPriceFactors() - } -} - -func FuzzClientPool(input []byte) int { - if len(input) > 10000 { - return -1 - } - f := newFuzzer(input) - if f.exhausted { - return 0 - } - clock := &mclock.Simulated{} - db := memorydb.New() - pool := vfs.NewClientPool(db, 10, f.randomDelay(), clock, func() bool { return true }) - pool.Start() - defer pool.Stop() - - count := 0 - for !f.exhausted && count < 1000 { - count++ - switch f.randomInt(11) { - case 0: - i := int(f.randomByte()) - f.peers[i].balance = pool.Register(f.peers[i]) - doLog("Register peer", "id", f.peers[i].node.ID()) - case 1: - i := int(f.randomByte()) - f.peers[i].Disconnect() - doLog("Disconnect peer", "id", f.peers[i].node.ID()) - case 2: - f.maxCount = uint64(f.randomByte()) - f.maxCap = uint64(f.randomByte()) - f.maxCap *= f.maxCap - - count, cap := pool.Limits() - pool.SetLimits(f.maxCount, f.maxCap) - doLog("Set limits", "maxcount", f.maxCount, "maxcap", f.maxCap, "origincount", count, "oricap", cap) - case 3: - bias := f.randomDelay() - pool.SetConnectedBias(f.randomDelay()) - doLog("Set connection bias", "bias", bias) - case 4: - pos, neg := f.randomFactors(), f.randomFactors() - pool.SetDefaultFactors(pos, neg) - doLog("Set default factors", "pos", pos, "neg", neg) - case 5: - pos, neg := uint64(f.randomInt(50000)), uint64(f.randomInt(50000)) - pool.SetExpirationTCs(pos, neg) - doLog("Set expiration constants", "pos", pos, "neg", neg) - case 6: - var ( - index = f.randomByte() - reqCap = uint64(f.randomByte()) - bias = f.randomDelay() - requested = f.randomBool() - ) - pool.SetCapacity(f.peers[index].node, reqCap, bias, requested) - doLog("Set capacity", "id", f.peers[index].node.ID(), "reqcap", reqCap, "bias", bias, "requested", requested) - case 7: - index := f.randomByte() - if balance := f.peers[index].balance; balance != nil { - f.connectedBalanceOp(balance, f.peers[index].node.ID()) - } - case 8: - index := f.randomByte() - pool.BalanceOperation(f.peers[index].node.ID(), f.peers[index].freeID, func(balance vfs.AtomicBalanceOperator) { - count := f.randomInt(4) - for i := 0; i < count; i++ { - f.atomicBalanceOp(balance, f.peers[index].node.ID()) - } - }) - case 9: - pool.TotalTokenAmount() - pool.GetExpirationTCs() - pool.Active() - pool.Limits() - pool.GetPosBalanceIDs(f.peers[f.randomByte()].node.ID(), f.peers[f.randomByte()].node.ID(), f.randomInt(100)) - case 10: - req := vflux.CapacityQueryReq{ - Bias: uint64(f.randomByte()), - AddTokens: make([]vflux.IntOrInf, f.randomInt(vflux.CapacityQueryMaxLen+1)), - } - for i := range req.AddTokens { - v := vflux.IntOrInf{Type: uint8(f.randomInt(4))} - if v.Type < 2 { - v.Value = *big.NewInt(f.randomTokenAmount(false)) - } - req.AddTokens[i] = v - } - reqEnc, err := rlp.EncodeToBytes(&req) - if err != nil { - panic(err) - } - p := int(f.randomByte()) - if p < len(reqEnc) { - reqEnc[p] = f.randomByte() - } - pool.Handle(f.peers[f.randomByte()].node.ID(), f.peers[f.randomByte()].freeID, vflux.CapacityQueryName, reqEnc) - } - - for _, peer := range f.disconnectList { - pool.Unregister(peer) - doLog("Unregister peer", "id", peer.node.ID()) - } - f.disconnectList = nil - if d := f.randomDelay(); d > 0 { - clock.Run(d) - } - doLog("Clientpool stats in fuzzer", "count", f.activeCap, "maxcount", f.maxCount, "cap", f.activeCap, "maxcap", f.maxCap) - activeCount, activeCap := pool.Active() - doLog("Clientpool stats in pool", "count", activeCount, "cap", activeCap) - if activeCount != f.activeCount || activeCap != f.activeCap { - panic(nil) - } - if f.activeCount > f.maxCount || f.activeCap > f.maxCap { - panic(nil) - } - } - return 0 -} diff --git a/tests/fuzzers/vflux/debug/main.go b/tests/fuzzers/vflux/debug/main.go deleted file mode 100644 index e6cec0460..000000000 --- a/tests/fuzzers/vflux/debug/main.go +++ /dev/null @@ -1,43 +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 main - -import ( - "fmt" - "os" - - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/tests/fuzzers/vflux" -) - -func main() { - log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) - - if len(os.Args) != 2 { - fmt.Fprintf(os.Stderr, "Usage: debug \n") - fmt.Fprintf(os.Stderr, "Example\n") - fmt.Fprintf(os.Stderr, " $ debug ../crashers/4bbef6857c733a87ecf6fd8b9e7238f65eb9862a\n") - os.Exit(1) - } - crasher := os.Args[1] - data, err := os.ReadFile(crasher) - if err != nil { - fmt.Fprintf(os.Stderr, "error loading crasher %v: %v", crasher, err) - os.Exit(1) - } - vflux.FuzzClientPool(data) -} diff --git a/tests/gen_btheader.go b/tests/gen_btheader.go index 985ea692d..80ad89e03 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_stenv.go b/tests/gen_stenv.go index 71f006317..a5bd0d5fc 100644 --- a/tests/gen_stenv.go +++ b/tests/gen_stenv.go @@ -16,13 +16,14 @@ 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" gencodec:"optional"` - Random *math.HexOrDecimal256 `json:"currentRandom" gencodec:"optional"` - GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` - Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` - Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` - BaseFee *math.HexOrDecimal256 `json:"currentBaseFee" gencodec:"optional"` + Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"optional"` + Random *math.HexOrDecimal256 `json:"currentRandom" gencodec:"optional"` + GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` + Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` + Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` + BaseFee *math.HexOrDecimal256 `json:"currentBaseFee" gencodec:"optional"` + ExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas" gencodec:"optional"` } var enc stEnv enc.Coinbase = common.UnprefixedAddress(s.Coinbase) @@ -32,19 +33,21 @@ func (s stEnv) MarshalJSON() ([]byte, error) { enc.Number = math.HexOrDecimal64(s.Number) enc.Timestamp = math.HexOrDecimal64(s.Timestamp) enc.BaseFee = (*math.HexOrDecimal256)(s.BaseFee) + enc.ExcessBlobGas = (*math.HexOrDecimal64)(s.ExcessBlobGas) 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" gencodec:"optional"` - Random *math.HexOrDecimal256 `json:"currentRandom" gencodec:"optional"` - GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` - Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` - Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` - BaseFee *math.HexOrDecimal256 `json:"currentBaseFee" gencodec:"optional"` + Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"optional"` + Random *math.HexOrDecimal256 `json:"currentRandom" gencodec:"optional"` + GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` + Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` + Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` + BaseFee *math.HexOrDecimal256 `json:"currentBaseFee" gencodec:"optional"` + ExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas" gencodec:"optional"` } var dec stEnv if err := json.Unmarshal(input, &dec); err != nil { @@ -75,5 +78,8 @@ func (s *stEnv) UnmarshalJSON(input []byte) error { if dec.BaseFee != nil { s.BaseFee = (*big.Int)(dec.BaseFee) } + if dec.ExcessBlobGas != nil { + s.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) + } return nil } diff --git a/tests/gen_sttransaction.go b/tests/gen_sttransaction.go index f47da494f..9b5aecbfe 100644 --- a/tests/gen_sttransaction.go +++ b/tests/gen_sttransaction.go @@ -27,6 +27,7 @@ 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"` } @@ -46,6 +47,7 @@ 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) @@ -64,6 +66,7 @@ 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"` } @@ -104,6 +107,9 @@ 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 } diff --git a/tests/init.go b/tests/init.go index a04e227dc..99b7e4d33 100644 --- a/tests/init.go +++ b/tests/init.go @@ -318,6 +318,25 @@ var Forks = map[string]*params.ChainConfig{ 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 7d8743efc..3ab15e765 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 782f1b0b4..ae78a53a7 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -21,15 +21,19 @@ import ( "bytes" "fmt" "math/big" + "math/rand" "os" "path/filepath" "reflect" + "runtime" "strings" "testing" "time" "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" @@ -74,25 +78,60 @@ func TestState(t *testing.T) { benchmarksDir, } { st.walk(t, dir, func(t *testing.T, name string, test *StateTest) { + if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 { + t.Skip("test (randomly) skipped on 32-bit windows") + return + } for _, subtest := range test.Subtests() { 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 }) }) } @@ -190,7 +229,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 42f0c662e..19387b539 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -28,6 +28,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/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" @@ -39,6 +40,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" ) @@ -81,23 +84,25 @@ type stPostState 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" gencodec:"optional"` - Random *big.Int `json:"currentRandom" gencodec:"optional"` - GasLimit uint64 `json:"currentGasLimit" gencodec:"required"` - Number uint64 `json:"currentNumber" gencodec:"required"` - Timestamp uint64 `json:"currentTimestamp" gencodec:"required"` - BaseFee *big.Int `json:"currentBaseFee" gencodec:"optional"` + Coinbase common.Address `json:"currentCoinbase" gencodec:"required"` + Difficulty *big.Int `json:"currentDifficulty" gencodec:"optional"` + Random *big.Int `json:"currentRandom" gencodec:"optional"` + GasLimit uint64 `json:"currentGasLimit" gencodec:"required"` + Number uint64 `json:"currentNumber" gencodec:"required"` + Timestamp uint64 `json:"currentTimestamp" gencodec:"required"` + BaseFee *big.Int `json:"currentBaseFee" gencodec:"optional"` + ExcessBlobGas *uint64 `json:"currentExcessBlobGas" gencodec:"optional"` } type stEnvMarshaling struct { - Coinbase common.UnprefixedAddress - Difficulty *math.HexOrDecimal256 - Random *math.HexOrDecimal256 - GasLimit math.HexOrDecimal64 - Number math.HexOrDecimal64 - Timestamp math.HexOrDecimal64 - BaseFee *math.HexOrDecimal256 + Coinbase common.UnprefixedAddress + Difficulty *math.HexOrDecimal256 + Random *math.HexOrDecimal256 + GasLimit math.HexOrDecimal64 + Number math.HexOrDecimal64 + Timestamp math.HexOrDecimal64 + BaseFee *math.HexOrDecimal256 + ExcessBlobGas *math.HexOrDecimal64 } //go:generate go run github.com/fjl/gencodec -type stTransaction -field-override stTransactionMarshaling -out gen_sttransaction.go @@ -113,6 +118,7 @@ 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"` } @@ -187,43 +193,53 @@ 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() + } + if snaps != nil { + snaps.Release() + } + }() + 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 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) - } - // Re-init the post-state instance for further operation - statedb, err = state.New(root, statedb.Database(), snaps) - if err != nil { - return nil, nil, err + 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)) { @@ -237,7 +253,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 @@ -245,11 +262,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 } } @@ -267,7 +286,11 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh context.Random = &rnd context.Difficulty = big.NewInt(0) } + if config.IsCancun(new(big.Int), block.Time()) && t.json.Env.ExcessBlobGas != nil { + context.BlobBaseFee = eip4844.CalcBlobFee(*t.json.Env.ExcessBlobGas) + } evm := vm.NewEVM(context, txContext, statedb, config, vmconfig) + // Execute the message. snapshot := statedb.Snapshot() gaspool := new(core.GasPool) @@ -282,17 +305,25 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh // - 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 + + // Commit state mutations into database. root, _ := statedb.Commit(block.NumberU64(), config.IsEIP158(block.Number())) - return snaps, statedb, root, err + 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) @@ -313,10 +344,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 { @@ -338,9 +369,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) diff --git a/trie/database.go b/trie/database.go index 49a884fd7..e20f7ef90 100644 --- a/trie/database.go +++ b/trie/database.go @@ -21,6 +21,7 @@ import ( "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" @@ -29,12 +30,17 @@ import ( // Config defines all necessary options for database. type Config struct { - Cache int // Memory allowance (MB) to use for caching trie nodes in memory - Preimages bool // Flag whether the preimage of trie key is recorded - PathDB *pathdb.Config // Configs for experimental path-based scheme, not used yet. + Preimages bool // Flag whether the preimage of node key is recorded + IsVerkle bool // Flag whether the db is holding a verkle tree + HashDB *hashdb.Config // Configs for hash-based scheme + PathDB *pathdb.Config // Configs for experimental path-based scheme +} - // Testing hooks - OnCommit func(states *triestate.Set) // Hook invoked when commit is performed +// 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 @@ -47,9 +53,12 @@ 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 @@ -77,36 +86,30 @@ type Database struct { 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 { +// 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, 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 { - var cleans int - if config != nil && config.Cache != 0 { - cleans = config.Cache * 1024 * 1024 + 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{}) } - db := prepare(diskdb, config) - db.backend = hashdb.New(diskdb, cleans, mptResolver{}) return db } @@ -130,9 +133,6 @@ func (db *Database) Reader(blockRoot common.Hash) (Reader, 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.config != nil && db.config.OnCommit != nil { - db.config.OnCommit(states) - } if db.preimages != nil { db.preimages.commit(false) } @@ -149,18 +149,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 @@ -189,6 +190,15 @@ func (db *Database) WritePreimages() { } } +// 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 // memory usage goes below the given threshold. The held pre-images accumulated // up to this point will be flushed in case the size exceeds the threshold. @@ -230,13 +240,76 @@ func (db *Database) Dereference(root common.Hash) error { return nil } -// Node retrieves the rlp-encoded node blob with provided node hash. It's -// only supported by hash-based database and will return an error for others. -// Note, this function should be deprecated once ETH66 is deprecated. -func (db *Database) Node(hash common.Hash) ([]byte, error) { - hdb, ok := db.backend.(*hashdb.Database) +// 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 nil, errors.New("not supported") + return errors.New("not supported") } - return hdb.Node(hash) + 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) +} + +// IsVerkle returns the indicator if the database is holding a verkle tree. +func (db *Database) IsVerkle() bool { + return db.config.IsVerkle } diff --git a/trie/database_test.go b/trie/database_test.go index ed43a81e5..d508c6553 100644 --- a/trie/database_test.go +++ b/trie/database_test.go @@ -25,11 +25,16 @@ import ( // 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, 0, mptResolver{}) + config.HashDB = &hashdb.Config{ + CleanCacheSize: 0, + } // disable clean cache } else { - db.backend = pathdb.New(diskdb, &pathdb.Config{}) // disable clean/dirty cache + config.PathDB = &pathdb.Config{ + CleanCacheSize: 0, + DirtyCacheSize: 0, + } // disable clean/dirty cache } - return db + return NewDatabase(diskdb, config) } diff --git a/trie/encoding.go b/trie/encoding.go index 8ee0022ef..3284d3f8f 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 d16d25c35..ac50b5d02 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/hasher.go b/trie/hasher.go index e594d6d6b..1e063d802 100644 --- a/trie/hasher.go +++ b/trie/hasher.go @@ -84,20 +84,19 @@ func (h *hasher) hash(n node, force bool) (hashed node, cached node) { } return hashed, cached default: - // Value and hash nodes don't have children so they're left as were + // Value and hash nodes don't have children, so they're left as were return n, n } } // hashShortNodeChildren collapses the short node. The returned collapsed node // holds a live reference to the Key, and must not be modified. -// The cached func (h *hasher) hashShortNodeChildren(n *shortNode) (collapsed, cached *shortNode) { // Hash the short node's child, caching the newly hashed subtree collapsed, cached = n.copy(), n.copy() // Previously, we did copy this one. We don't seem to need to actually // do that, since we don't overwrite/reuse keys - //cached.Key = common.CopyBytes(n.Key) + // cached.Key = common.CopyBytes(n.Key) collapsed.Key = hexToCompact(n.Key) // Unless the child is a valuenode or hashnode, hash it switch n.Val.(type) { @@ -153,7 +152,7 @@ func (h *hasher) shortnodeToHash(n *shortNode, force bool) node { return h.hashData(enc) } -// shortnodeToHash is used to creates a hashNode from a set of hashNodes, (which +// fullnodeToHash is used to create a hashNode from a fullNode, (which // may contain nil values) func (h *hasher) fullnodeToHash(n *fullNode, force bool) node { n.encode(h.encbuf) @@ -203,7 +202,7 @@ func (h *hasher) proofHash(original node) (collapsed, hashed node) { fn, _ := h.hashFullNodeChildren(n) return fn, h.fullnodeToHash(fn, false) default: - // Value and hash nodes don't have children so they're left as were + // Value and hash nodes don't have children, so they're left as were return n, n } } diff --git a/trie/iterator.go b/trie/iterator.go index 6f054a724..83ccc0740 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -144,7 +144,8 @@ type nodeIterator struct { path []byte // Path to the current node err error // Failure set in case of an internal error in the iterator - resolver NodeResolver // optional node resolver for avoiding disk hits + resolver NodeResolver // optional node resolver for avoiding disk hits + pool []*nodeIteratorState // local pool for iteratorstates } // errIteratorEnd is stored in nodeIterator.err when iteration is done. @@ -172,6 +173,24 @@ func newNodeIterator(trie *Trie, start []byte) NodeIterator { return it } +func (it *nodeIterator) putInPool(item *nodeIteratorState) { + if len(it.pool) < 40 { + item.node = nil + it.pool = append(it.pool, item) + } +} + +func (it *nodeIterator) getFromPool() *nodeIteratorState { + idx := len(it.pool) - 1 + if idx < 0 { + return new(nodeIteratorState) + } + el := it.pool[idx] + it.pool[idx] = nil + it.pool = it.pool[:idx] + return el +} + func (it *nodeIterator) AddResolver(resolver NodeResolver) { it.resolver = resolver } @@ -423,8 +442,9 @@ func (st *nodeIteratorState) resolve(it *nodeIterator, path []byte) error { return nil } -func findChild(n *fullNode, index int, path []byte, ancestor common.Hash) (node, *nodeIteratorState, []byte, int) { +func (it *nodeIterator) findChild(n *fullNode, index int, ancestor common.Hash) (node, *nodeIteratorState, []byte, int) { var ( + path = it.path child node state *nodeIteratorState childPath []byte @@ -433,13 +453,12 @@ func findChild(n *fullNode, index int, path []byte, ancestor common.Hash) (node, if n.Children[index] != nil { child = n.Children[index] hash, _ := child.cache() - state = &nodeIteratorState{ - hash: common.BytesToHash(hash), - node: child, - parent: ancestor, - index: -1, - pathlen: len(path), - } + state = it.getFromPool() + state.hash = common.BytesToHash(hash) + state.node = child + state.parent = ancestor + state.index = -1 + state.pathlen = len(path) childPath = append(childPath, path...) childPath = append(childPath, byte(index)) return child, state, childPath, index @@ -452,7 +471,7 @@ func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Has switch node := parent.node.(type) { case *fullNode: // Full node, move to the first non-nil child. - if child, state, path, index := findChild(node, parent.index+1, it.path, ancestor); child != nil { + if child, state, path, index := it.findChild(node, parent.index+1, ancestor); child != nil { parent.index = index - 1 return state, path, true } @@ -460,13 +479,12 @@ func (it *nodeIterator) nextChild(parent *nodeIteratorState, ancestor common.Has // Short node, return the pointer singleton child if parent.index < 0 { hash, _ := node.Val.cache() - state := &nodeIteratorState{ - hash: common.BytesToHash(hash), - node: node.Val, - parent: ancestor, - index: -1, - pathlen: len(it.path), - } + state := it.getFromPool() + state.hash = common.BytesToHash(hash) + state.node = node.Val + state.parent = ancestor + state.index = -1 + state.pathlen = len(it.path) path := append(it.path, node.Key...) return state, path, true } @@ -480,7 +498,7 @@ func (it *nodeIterator) nextChildAt(parent *nodeIteratorState, ancestor common.H switch n := parent.node.(type) { case *fullNode: // Full node, move to the first non-nil child before the desired key position - child, state, path, index := findChild(n, parent.index+1, it.path, ancestor) + child, state, path, index := it.findChild(n, parent.index+1, ancestor) if child == nil { // No more children in this fullnode return parent, it.path, false @@ -492,7 +510,7 @@ func (it *nodeIterator) nextChildAt(parent *nodeIteratorState, ancestor common.H } // The child is before the seek position. Try advancing for { - nextChild, nextState, nextPath, nextIndex := findChild(n, index+1, it.path, ancestor) + nextChild, nextState, nextPath, nextIndex := it.findChild(n, index+1, ancestor) // If we run out of children, or skipped past the target, return the // previous one if nextChild == nil || bytes.Compare(nextPath, key) >= 0 { @@ -506,13 +524,12 @@ func (it *nodeIterator) nextChildAt(parent *nodeIteratorState, ancestor common.H // Short node, return the pointer singleton child if parent.index < 0 { hash, _ := n.Val.cache() - state := &nodeIteratorState{ - hash: common.BytesToHash(hash), - node: n.Val, - parent: ancestor, - index: -1, - pathlen: len(it.path), - } + state := it.getFromPool() + state.hash = common.BytesToHash(hash) + state.node = n.Val + state.parent = ancestor + state.index = -1 + state.pathlen = len(it.path) path := append(it.path, n.Key...) return state, path, true } @@ -533,6 +550,8 @@ func (it *nodeIterator) pop() { it.path = it.path[:last.pathlen] it.stack[len(it.stack)-1] = nil it.stack = it.stack[:len(it.stack)-1] + // last is now unused + it.putInPool(last) } func compareNodes(a, b NodeIterator) int { diff --git a/trie/iterator_test.go b/trie/iterator_test.go index e711ffb81..9679b49ca 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,13 +26,11 @@ 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())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) iter := trie.MustNodeIterator(nil) seen := make(map[string]struct{}) @@ -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"}, @@ -89,7 +86,7 @@ func (k *kv) cmp(other *kv) int { } 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++ { @@ -208,7 +205,7 @@ 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)) } @@ -249,7 +246,7 @@ 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)) @@ -258,7 +255,7 @@ func TestDifferenceIterator(t *testing.T) { 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)) @@ -291,7 +288,7 @@ 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)) @@ -300,7 +297,7 @@ func TestUnionIterator(t *testing.T) { 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)) @@ -344,7 +341,7 @@ 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)) } @@ -537,96 +534,6 @@ func TestIteratorNodeBlob(t *testing.T) { 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, 0, trienode.NewWithNodeSet(nodes), nil) - triedb.Commit(root, false) - - // Return the generated trie - trie, _ = NewStateTrie(TrieID(root), triedb) - 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: 6 get operations - if have, want := logDb.getCount, uint64(6); have != want { - t.Fatalf("Too many lookups during seek, have %d want %d", have, want) - } -} - func testIteratorNodeBlob(t *testing.T, scheme string) { var ( db = rawdb.NewMemoryDatabase() @@ -700,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{} } @@ -709,3 +616,15 @@ func isTrieNode(scheme string, key, val []byte) (bool, []byte, common.Hash) { } return true, path, hash } + +func BenchmarkIterator(b *testing.B) { + diskDb, srcDb, tr, _ := makeTestTrie(rawdb.HashScheme) + root := tr.Hash() + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := checkTrieConsistency(diskDb, srcDb.Scheme(), root, false); err != nil { + b.Fatal(err) + } + } +} diff --git a/trie/proof.go b/trie/proof.go index a463c80b4..a526a5340 100644 --- a/trie/proof.go +++ b/trie/proof.go @@ -481,7 +481,7 @@ func hasRightElement(node node, key []byte) bool { // Note: This method does not verify that the proof is of minimal form. If the input // proofs are 'bloated' with neighbour leaves or random data, aside from the 'useful' // data, then the proof will still be accepted. -func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (bool, error) { +func VerifyRangeProof(rootHash common.Hash, firstKey []byte, keys [][]byte, values [][]byte, proof ethdb.KeyValueReader) (bool, error) { if len(keys) != len(values) { return false, fmt.Errorf("inconsistent proof data, keys: %d, values: %d", len(keys), len(values)) } @@ -520,6 +520,7 @@ func VerifyRangeProof(rootHash common.Hash, firstKey []byte, lastKey []byte, key } return false, nil } + var lastKey = keys[len(keys)-1] // Special case, there is only one element and two edge keys are same. // In this case, we can't construct two edge paths. So handle it here. if len(keys) == 1 && bytes.Equal(firstKey, lastKey) { diff --git a/trie/proof_test.go b/trie/proof_test.go index e8ea116c8..59ae201ce 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -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,7 +145,7 @@ 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"} { @@ -191,7 +191,7 @@ func TestRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, proof) if err != nil { t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } @@ -221,19 +221,10 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { if bytes.Compare(first, entries[start].k) > 0 { continue } - // Short circuit if the increased key is same with the next key - last := increaseKey(common.CopyBytes(entries[end-1].k)) - if end != len(entries) && bytes.Equal(last, entries[end].k) { - continue - } - // Short circuit if the increased key is overflow - if bytes.Compare(last, entries[end-1].k) < 0 { - continue - } if err := trie.Prove(first, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(last, 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 @@ -242,36 +233,15 @@ func TestRangeProofWithNonExistentProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) + _, err := VerifyRangeProof(trie.Hash(), first, keys, vals, proof) if err != nil { t.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } } - // Special case, two edge proofs for two edge key. - proof := memorydb.New() - first := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes() - last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes() - if err := trie.Prove(first, proof); err != nil { - t.Fatalf("Failed to prove the first node %v", err) - } - if err := trie.Prove(last, proof); err != nil { - t.Fatalf("Failed to prove the last node %v", err) - } - var k [][]byte - var v [][]byte - for i := 0; i < len(entries); i++ { - k = append(k, entries[i].k) - v = append(v, entries[i].v) - } - _, err := VerifyRangeProof(trie.Hash(), first, last, k, v, proof) - if err != nil { - t.Fatal("Failed to verify whole rang with non-existent edges") - } } // TestRangeProofWithInvalidNonExistentProof tests such scenarios: // - There exists a gap between the first element and the left edge proof -// - There exists a gap between the last element and the right edge proof func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { trie, vals := randomTrie(4096) var entries []*kv @@ -298,29 +268,7 @@ func TestRangeProofWithInvalidNonExistentProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, err := VerifyRangeProof(trie.Hash(), first, k[len(k)-1], k, v, proof) - if err == nil { - t.Fatalf("Expected to detect the error, got nil") - } - - // Case 2 - start, end = 100, 200 - last := increaseKey(common.CopyBytes(entries[end-1].k)) - proof = memorydb.New() - 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, proof); err != nil { - t.Fatalf("Failed to prove the last node %v", err) - } - end = 195 // Capped slice - k = make([][]byte, 0) - v = make([][]byte, 0) - for i := start; i < end; i++ { - k = append(k, entries[i].k) - v = append(v, entries[i].v) - } - _, err = VerifyRangeProof(trie.Hash(), k[0], last, k, v, proof) + _, err := VerifyRangeProof(trie.Hash(), first, k, v, proof) if err == nil { t.Fatalf("Expected to detect the error, got nil") } @@ -344,7 +292,7 @@ func TestOneElementRangeProof(t *testing.T) { 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) + _, err := VerifyRangeProof(trie.Hash(), entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -359,7 +307,7 @@ func TestOneElementRangeProof(t *testing.T) { 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) + _, err = VerifyRangeProof(trie.Hash(), first, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -374,7 +322,7 @@ func TestOneElementRangeProof(t *testing.T) { 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) + _, err = VerifyRangeProof(trie.Hash(), entries[start].k, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -389,13 +337,13 @@ func TestOneElementRangeProof(t *testing.T) { 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) + _, err = VerifyRangeProof(trie.Hash(), first, [][]byte{entries[start].k}, [][]byte{entries[start].v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } // 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) @@ -408,7 +356,7 @@ func TestOneElementRangeProof(t *testing.T) { 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) + _, err = VerifyRangeProof(tinyTrie.Hash(), first, [][]byte{entry.k}, [][]byte{entry.v}, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -430,7 +378,7 @@ func TestAllElementsProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, err := VerifyRangeProof(trie.Hash(), nil, nil, k, v, nil) + _, err := VerifyRangeProof(trie.Hash(), nil, k, v, nil) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -443,7 +391,7 @@ func TestAllElementsProof(t *testing.T) { 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) + _, err = VerifyRangeProof(trie.Hash(), k[0], k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -451,14 +399,13 @@ func TestAllElementsProof(t *testing.T) { // Even with non-existent edge proofs, it should still work. proof = memorydb.New() first := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000").Bytes() - last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").Bytes() if err := trie.Prove(first, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(last, 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(), first, last, k, v, proof) + _, err = VerifyRangeProof(trie.Hash(), first, k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -467,7 +414,7 @@ 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())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) var entries []*kv for i := 0; i < 4096; i++ { value := &kv{randBytes(32), randBytes(20), false} @@ -491,43 +438,7 @@ func TestSingleSideRangeProof(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - _, err := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k[len(k)-1], k, v, proof) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - } - } -} - -// 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 []*kv - for i := 0; i < 4096; i++ { - value := &kv{randBytes(32), randBytes(20), false} - trie.MustUpdate(value.k, value.v) - entries = append(entries, value) - } - 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, proof); err != nil { - t.Fatalf("Failed to prove the first node %v", err) - } - last := common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") - if err := trie.Prove(last.Bytes(), proof); err != nil { - t.Fatalf("Failed to prove the last node %v", err) - } - k := make([][]byte, 0) - v := make([][]byte, 0) - for i := pos; i < len(entries); i++ { - k = append(k, entries[i].k) - v = append(v, entries[i].v) - } - _, err := VerifyRangeProof(trie.Hash(), k[0], last.Bytes(), k, v, proof) + _, err := VerifyRangeProof(trie.Hash(), common.Hash{}.Bytes(), k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -561,7 +472,7 @@ func TestBadRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - var first, last = keys[0], keys[len(keys)-1] + var first = keys[0] testcase := mrand.Intn(6) var index int switch testcase { @@ -576,7 +487,7 @@ func TestBadRangeProof(t *testing.T) { case 2: // Gapped entry slice index = mrand.Intn(end - start) - if (index == 0 && start < 100) || (index == end-start-1 && end <= 100) { + if (index == 0 && start < 100) || (index == end-start-1) { continue } keys = append(keys[:index], keys[index+1:]...) @@ -599,7 +510,7 @@ func TestBadRangeProof(t *testing.T) { index = mrand.Intn(end - start) vals[index] = nil } - _, err := VerifyRangeProof(trie.Hash(), first, last, keys, vals, proof) + _, err := VerifyRangeProof(trie.Hash(), first, keys, vals, proof) if err == nil { t.Fatalf("%d Case %d index %d range: (%d->%d) expect error, got nil", i, testcase, index, start, end-1) } @@ -609,7 +520,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} @@ -633,7 +544,7 @@ func TestGappedRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, proof) if err == nil { t.Fatal("expect error, got nil") } @@ -649,24 +560,22 @@ func TestSameSideProofs(t *testing.T) { slices.SortFunc(entries, (*kv).cmp) pos := 1000 - first := decreaseKey(common.CopyBytes(entries[pos].k)) - first = decreaseKey(first) - last := decreaseKey(common.CopyBytes(entries[pos].k)) + first := common.CopyBytes(entries[0].k) proof := memorydb.New() if err := trie.Prove(first, proof); err != nil { t.Fatalf("Failed to prove the first node %v", err) } - if err := trie.Prove(last, proof); err != nil { - t.Fatalf("Failed to prove the last node %v", err) + if err := trie.Prove(entries[2000].k, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) } - _, err := VerifyRangeProof(trie.Hash(), first, last, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) + _, err := VerifyRangeProof(trie.Hash(), first, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) if err == nil { t.Fatalf("Expected error, got nil") } first = increaseKey(common.CopyBytes(entries[pos].k)) - last = increaseKey(common.CopyBytes(entries[pos].k)) + last := increaseKey(common.CopyBytes(entries[pos].k)) last = increaseKey(last) proof = memorydb.New() @@ -676,14 +585,14 @@ func TestSameSideProofs(t *testing.T) { 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) + _, err = VerifyRangeProof(trie.Hash(), first, [][]byte{entries[pos].k}, [][]byte{entries[pos].v}, proof) if err == nil { t.Fatalf("Expected error, got nil") } } func TestHasRightElement(t *testing.T) { - trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) + trie := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) var entries []*kv for i := 0; i < 4096; i++ { value := &kv{randBytes(32), randBytes(20), false} @@ -703,15 +612,12 @@ func TestHasRightElement(t *testing.T) { {50, 100, true}, {50, len(entries), false}, // No more element expected {len(entries) - 1, len(entries), false}, // Single last element with two existent proofs(point to same key) - {len(entries) - 1, -1, false}, // Single last element with non-existent right proof {0, len(entries), false}, // The whole set with existent left proof {-1, len(entries), false}, // The whole set with non-existent left proof - {-1, -1, false}, // The whole set with non-existent left/right proof } for _, c := range cases { var ( firstKey []byte - lastKey []byte start = c.start end = c.end proof = memorydb.New() @@ -727,16 +633,8 @@ func TestHasRightElement(t *testing.T) { 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, 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, proof); err != nil { - t.Fatalf("Failed to prove the first node %v", err) - } + if err := trie.Prove(entries[c.end-1].k, proof); err != nil { + t.Fatalf("Failed to prove the first node %v", err) } k := make([][]byte, 0) v := make([][]byte, 0) @@ -744,7 +642,7 @@ func TestHasRightElement(t *testing.T) { k = append(k, entries[i].k) v = append(v, entries[i].v) } - hasMore, err := VerifyRangeProof(trie.Hash(), firstKey, lastKey, k, v, proof) + hasMore, err := VerifyRangeProof(trie.Hash(), firstKey, k, v, proof) if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -777,7 +675,7 @@ func TestEmptyRangeProof(t *testing.T) { 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) + _, err := VerifyRangeProof(trie.Hash(), first, nil, nil, proof) if c.err && err == nil { t.Fatalf("Expected error, got nil") } @@ -817,7 +715,7 @@ func TestBloatedProof(t *testing.T) { 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 { + if _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, proof); err != nil { t.Fatalf("expected bloated proof to succeed, got %v", err) } } @@ -860,7 +758,7 @@ func TestEmptyValueRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, vals, proof) + _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, vals, proof) if err == nil { t.Fatalf("Expected failure on noop entry") } @@ -895,7 +793,7 @@ func TestAllElementsEmptyValueRangeProof(t *testing.T) { keys = append(keys, entries[i].k) vals = append(vals, entries[i].v) } - _, err := VerifyRangeProof(trie.Hash(), nil, nil, keys, vals, nil) + _, err := VerifyRangeProof(trie.Hash(), nil, keys, vals, nil) if err == nil { t.Fatalf("Expected failure on noop entry") } @@ -1001,7 +899,7 @@ func benchmarkVerifyRangeProof(b *testing.B, size int) { b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, proof) + _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, values, proof) if err != nil { b.Fatalf("Case %d(%d->%d) expect no error, got %v", i, start, end-1, err) } @@ -1028,7 +926,7 @@ func benchmarkVerifyRangeNoProof(b *testing.B, size int) { } b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := VerifyRangeProof(trie.Hash(), keys[0], keys[len(keys)-1], keys, values, nil) + _, err := VerifyRangeProof(trie.Hash(), keys[0], keys, values, nil) if err != nil { b.Fatalf("Expected no error, got %v", err) } @@ -1036,7 +934,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} @@ -1055,7 +953,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++ { @@ -1080,22 +978,21 @@ 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]) } root := trie.Hash() proof := memorydb.New() start := common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000") - end := common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") if err := trie.Prove(start, proof); err != nil { t.Fatalf("failed to prove start: %v", err) } - if err := trie.Prove(end, proof); err != nil { + if err := trie.Prove(keys[len(keys)-1], proof); err != nil { t.Fatalf("failed to prove end: %v", err) } - more, err := VerifyRangeProof(root, start, end, keys, vals, proof) + more, err := VerifyRangeProof(root, start, keys, vals, proof) if err != nil { t.Fatalf("failed to verify range proof: %v", err) } diff --git a/trie/secure_trie_test.go b/trie/secure_trie_test.go index a610ca2fd..2087866d3 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 diff --git a/trie/stacktrie.go b/trie/stacktrie.go index ee1ce2829..f2f5355c4 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -17,183 +17,149 @@ package trie import ( - "bufio" "bytes" - "encoding/gob" "errors" - "io" "sync" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" ) -var ErrCommitDisabled = errors.New("no database for committing") +var ( + stPool = sync.Pool{New: func() any { return new(stNode) }} + _ = types.TrieHasher((*StackTrie)(nil)) +) + +// StackTrieOptions contains the configured options for manipulating the stackTrie. +type StackTrieOptions struct { + Writer func(path []byte, hash common.Hash, blob []byte) // The function to commit the dirty nodes + Cleaner func(path []byte) // The function to clean up dangling nodes -var stPool = sync.Pool{ - New: func() interface{} { - return NewStackTrie(nil) - }, + SkipLeftBoundary bool // Flag whether the nodes on the left boundary are skipped for committing + SkipRightBoundary bool // Flag whether the nodes on the right boundary are skipped for committing + boundaryGauge metrics.Gauge // Gauge to track how many boundary nodes are met } -// 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) +// NewStackTrieOptions initializes an empty options for stackTrie. +func NewStackTrieOptions() *StackTrieOptions { return &StackTrieOptions{} } -func stackTrieFromPool(writeFn NodeWriteFunc, owner common.Hash) *StackTrie { - st := stPool.Get().(*StackTrie) - st.owner = owner - st.writeFn = writeFn - return st +// WithWriter configures trie node writer within the options. +func (o *StackTrieOptions) WithWriter(writer func(path []byte, hash common.Hash, blob []byte)) *StackTrieOptions { + o.Writer = writer + return o +} + +// WithCleaner configures the cleaner in the option for removing dangling nodes. +func (o *StackTrieOptions) WithCleaner(cleaner func(path []byte)) *StackTrieOptions { + o.Cleaner = cleaner + return o } -func returnToPool(st *StackTrie) { - st.Reset() - stPool.Put(st) +// WithSkipBoundary configures whether the left and right boundary nodes are +// filtered for committing, along with a gauge metrics to track how many +// boundary nodes are met. +func (o *StackTrieOptions) WithSkipBoundary(skipLeft, skipRight bool, gauge metrics.Gauge) *StackTrieOptions { + o.SkipLeftBoundary = skipLeft + o.SkipRightBoundary = skipRight + o.boundaryGauge = gauge + return o } // 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 + options *StackTrieOptions + root *stNode + h *hasher + + first []byte // The (hex-encoded without terminator) key of first inserted entry, tracked as left boundary. + last []byte // The (hex-encoded without terminator) key of last inserted entry, tracked as right boundary. } // NewStackTrie allocates and initializes an empty trie. -func NewStackTrie(writeFn NodeWriteFunc) *StackTrie { - return &StackTrie{ - nodeType: emptyNode, - writeFn: writeFn, +func NewStackTrie(options *StackTrieOptions) *StackTrie { + if options == nil { + options = NewStackTrieOptions() } -} - -// 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, + options: options, + root: stPool.Get().(*stNode), + h: newHasher(false), } } -// 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 +// Update inserts a (key, value) pair into the stack trie. +func (t *StackTrie) Update(key, value []byte) error { + if len(value) == 0 { + return errors.New("trying to insert empty (deletion)") } - // If a database is used, we need to recursively add it to every child - if writeFn != nil { - st.setWriter(writeFn) + k := keybytesToHex(key) + k = k[:len(k)-1] // chop the termination flag + if bytes.Compare(t.last, k) >= 0 { + return errors.New("non-ascending key order") } - 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 + // track the first and last inserted entries. + if t.first == nil { + t.first = append([]byte{}, k...) } - 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) - } + if t.last == nil { + t.last = append([]byte{}, k...) // allocate key slice + } else { + t.last = append(t.last[:0], k...) // reuse key slice } - w.Flush() - return b.Bytes(), nil + t.insert(t.root, k, value, nil) + return nil } -// UnmarshalBinary implements encoding.BinaryUnmarshaler -func (st *StackTrie) UnmarshalBinary(data []byte) error { - r := bytes.NewReader(data) - return st.unmarshalBinary(r) +// 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) + } } -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 +// Reset resets the stack trie object to empty state. +func (t *StackTrie) Reset() { + t.options = NewStackTrieOptions() + t.root = stPool.Get().(*stNode) + t.first = nil + t.last = nil } -func (st *StackTrie) setWriter(writeFn NodeWriteFunc) { - st.writeFn = writeFn - for _, child := range st.children { - if child != nil { - child.setWriter(writeFn) - } - } +// 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 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 +168,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") - } - 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 +func (n *stNode) reset() *stNode { + n.key = n.key[:0] + n.val = nil + for i := range n.children { + n.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, path []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(path, byte(i))) } break } @@ -262,9 +209,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(path, key[0])) } case extNode: /* Ext */ @@ -279,46 +226,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(path, 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(path, 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(path, 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 +291,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 +310,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(path, 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 +322,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 +345,19 @@ 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) { - // The switch below sets this to the RLP-encoding of this node. - var encodedNode []byte - - switch st.nodeType { +func (t *StackTrie) hash(st *stNode, path []byte) { + var ( + blob []byte // RLP-encoded node blob + internal [][]byte // List of node paths covered by the extension node + ) + 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,109 +367,113 @@ 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) + blob = t.h.encodedBytes() case extNode: - st.children[0].hashRec(hasher, append(path, st.key...)) - - n := shortNode{Key: hexToCompact(st.key)} + // recursively hash and commit child as the first step + t.hash(st.children[0], append(path, st.key...)) + + // Collect the path of internal nodes between shortNode and its **in disk** + // child. This is essential in the case of path mode scheme to avoid leaving + // danging nodes within the range of this internal path on disk, which would + // break the guarantee for state healing. + if len(st.children[0].val) >= 32 && t.options.Cleaner != nil { + for i := 1; i < len(st.key); i++ { + internal = append(internal, append(path, st.key[:i]...)) + } + } + // encode the extension node + 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) + blob = 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) + blob = 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) + + // Skip committing the non-root node if the size is smaller than 32 bytes. + if len(blob) < 32 && len(path) > 0 { + st.val = common.CopyBytes(blob) return } - // 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) - } -} - -// Hash returns the hash of the current node. -func (st *StackTrie) Hash() (h common.Hash) { - hasher := newHasher(false) - defer returnHasherToPool(hasher) + // input values. + st.val = t.h.hashData(blob) - st.hashRec(hasher, nil) - if len(st.val) == 32 { - copy(h[:], st.val) - return h + // Short circuit if the stack trie is not configured for writing. + if t.options.Writer == nil { + return } - // 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[:]) - return h + // Skip committing if the node is on the left boundary and stackTrie is + // configured to filter the boundary. + if t.options.SkipLeftBoundary && bytes.HasPrefix(t.first, path) { + if t.options.boundaryGauge != nil { + t.options.boundaryGauge.Inc(1) + } + return + } + // Skip committing if the node is on the right boundary and stackTrie is + // configured to filter the boundary. + if t.options.SkipRightBoundary && bytes.HasPrefix(t.last, path) { + if t.options.boundaryGauge != nil { + t.options.boundaryGauge.Inc(1) + } + return + } + // Clean up the internal dangling nodes covered by the extension node. + // This should be done before writing the node to adhere to the committing + // order from bottom to top. + for _, path := range internal { + t.options.Cleaner(path) + } + t.options.Writer(path, common.BytesToHash(st.val), blob) } -// Commit will firstly hash the entire trie if it's still not hashed -// and then commit all nodes to the associated database. Actually most -// of the trie nodes MAY have been committed already. The main purpose -// here is to commit the root node. +// Hash will firstly hash the entire trie if it's still not hashed and then commit +// all nodes to the associated database. Actually most of the trie nodes have been +// committed already. The main purpose here is to commit the nodes on right boundary. // -// 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 { - return common.Hash{}, ErrCommitDisabled - } - hasher := newHasher(false) - defer returnHasherToPool(hasher) +// For stack trie, Hash and Commit are functionally identical. +func (t *StackTrie) Hash() common.Hash { + n := t.root + t.hash(n, nil) + return common.BytesToHash(n.val) +} - st.hashRec(hasher, nil) - if len(st.val) == 32 { - copy(h[:], st.val) - return h, nil - } - // 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[:]) - - st.writeFn(st.owner, nil, h, st.val) - return h, nil +// Commit will firstly hash the entire trie if it's still not hashed and then commit +// all nodes to the associated database. Actually most of the trie nodes have been +// committed already. The main purpose here is to commit the nodes on right boundary. +// +// For stack trie, Hash and Commit are functionally identical. +func (t *StackTrie) Commit() common.Hash { + return t.Hash() } diff --git a/trie/stacktrie_fuzzer_test.go b/trie/stacktrie_fuzzer_test.go new file mode 100644 index 000000000..1b3f9dbe9 --- /dev/null +++ b/trie/stacktrie_fuzzer_test.go @@ -0,0 +1,155 @@ +// 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 trie + +import ( + "bytes" + "encoding/binary" + "fmt" + "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/trie/trienode" + "golang.org/x/crypto/sha3" + "golang.org/x/exp/slices" +) + +func FuzzStackTrie(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzz(data, false) + }) +} + +func fuzz(data []byte, debugging bool) { + // This spongeDb is used to check the sequence of disk-db-writes + var ( + input = bytes.NewReader(data) + spongeA = &spongeDb{sponge: sha3.NewLegacyKeccak256()} + dbA = NewDatabase(rawdb.NewDatabase(spongeA), nil) + trieA = NewEmpty(dbA) + spongeB = &spongeDb{sponge: sha3.NewLegacyKeccak256()} + dbB = NewDatabase(rawdb.NewDatabase(spongeB), nil) + + options = NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(spongeB, common.Hash{}, path, hash, blob, dbB.Scheme()) + }) + trieB = NewStackTrie(options) + vals []*kv + maxElements = 10000 + // operate on unique keys only + keys = make(map[string]struct{}) + ) + // Fill the trie with elements + for i := 0; input.Len() > 0 && i < maxElements; i++ { + k := make([]byte, 32) + input.Read(k) + var a uint16 + binary.Read(input, binary.LittleEndian, &a) + a = 1 + a%100 + v := make([]byte, a) + input.Read(v) + if input.Len() == 0 { + // If it was exhausted while reading, the value may be all zeroes, + // thus 'deletion' which is not supported on stacktrie + break + } + if _, present := keys[string(k)]; present { + // This key is a duplicate, ignore it + continue + } + keys[string(k)] = struct{}{} + vals = append(vals, &kv{k: k, v: v}) + trieA.MustUpdate(k, v) + } + if len(vals) == 0 { + return + } + // Flush trie -> database + rootA, nodes, err := trieA.Commit(false) + if err != nil { + panic(err) + } + if nodes != nil { + dbA.Update(rootA, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) + } + // Flush memdb -> disk (sponge) + dbA.Commit(rootA, false) + + // Stacktrie requires sorted insertion + slices.SortFunc(vals, (*kv).cmp) + + for _, kv := range vals { + if debugging { + fmt.Printf("{\"%#x\" , \"%#x\"} // stacktrie.Update\n", kv.k, kv.v) + } + trieB.MustUpdate(kv.k, kv.v) + } + rootB := trieB.Hash() + trieB.Commit() + if rootA != rootB { + panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootB)) + } + sumA := spongeA.sponge.Sum(nil) + sumB := spongeB.sponge.Sum(nil) + if !bytes.Equal(sumA, sumB) { + panic(fmt.Sprintf("sequence differ: (trie) %x != %x (stacktrie)", sumA, sumB)) + } + + // Ensure all the nodes are persisted correctly + var ( + nodeset = make(map[string][]byte) // path -> blob + optionsC = NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) { + if crypto.Keccak256Hash(blob) != hash { + panic("invalid node blob") + } + nodeset[string(path)] = common.CopyBytes(blob) + }) + trieC = NewStackTrie(optionsC) + checked int + ) + for _, kv := range vals { + trieC.MustUpdate(kv.k, kv.v) + } + rootC := trieC.Commit() + if rootA != rootC { + panic(fmt.Sprintf("roots differ: (trie) %x != %x (stacktrie)", rootA, rootC)) + } + trieA, _ = New(TrieID(rootA), dbA) + iterA := trieA.MustNodeIterator(nil) + for iterA.Next(true) { + if iterA.Hash() == (common.Hash{}) { + if _, present := nodeset[string(iterA.Path())]; present { + panic("unexpected tiny node") + } + continue + } + nodeBlob, present := nodeset[string(iterA.Path())] + if !present { + panic("missing node") + } + if !bytes.Equal(nodeBlob, iterA.NodeBlob()) { + panic("node blob is not matched") + } + checked += 1 + } + if checked != len(nodeset) { + panic("node number is not matched") + } +} diff --git a/trie/stacktrie_test.go b/trie/stacktrie_test.go index ea3eef788..909a77062 100644 --- a/trie/stacktrie_test.go +++ b/trie/stacktrie_test.go @@ -19,11 +19,15 @@ package trie import ( "bytes" "math/big" + "math/rand" "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/trie/testutil" + "github.com/stretchr/testify/assert" + "golang.org/x/exp/slices" ) func TestStackTrieInsertAndHash(t *testing.T) { @@ -165,13 +169,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 +223,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 +238,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 +264,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 +289,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 +317,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 @@ -346,47 +381,107 @@ func TestStacktrieNotModifyValues(t *testing.T) { } } -// TestStacktrieSerialization tests that the stacktrie works well if we -// serialize/unserialize it a lot -func TestStacktrieSerialization(t *testing.T) { +func buildPartialTree(entries []*kv, t *testing.T) map[string]common.Hash { var ( - st = NewStackTrie(nil) - nt = NewEmpty(NewDatabase(rawdb.NewMemoryDatabase())) - keyB = big.NewInt(1) - keyDelta = big.NewInt(1) - vals [][]byte - keys [][]byte + options = NewStackTrieOptions() + nodes = make(map[string]common.Hash) ) - 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() + var ( + first int + last = len(entries) - 1 + + noLeft bool + noRight bool + ) + // Enter split mode if there are at least two elements + if rand.Intn(5) != 0 { + for { + first = rand.Intn(len(entries)) + last = rand.Intn(len(entries)) + if first <= last { + break + } + } + if first != 0 { + noLeft = true + } + if last != len(entries)-1 { + noRight = true } } - 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])) + options = options.WithSkipBoundary(noLeft, noRight, nil) + options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) { + nodes[string(path)] = hash + }) + tr := NewStackTrie(options) + + for i := first; i <= last; i++ { + tr.MustUpdate(entries[i].k, entries[i].v) } + tr.Commit() + return nodes +} + +func TestPartialStackTrie(t *testing.T) { + for round := 0; round < 100; round++ { + var ( + n = rand.Intn(100) + 1 + entries []*kv + ) + for i := 0; i < n; i++ { + var val []byte + if rand.Intn(3) == 0 { + val = testutil.RandBytes(3) + } else { + val = testutil.RandBytes(32) + } + entries = append(entries, &kv{ + k: testutil.RandBytes(32), + v: val, + }) + } + slices.SortFunc(entries, (*kv).cmp) - for i, k := range keys { - blob, err := st.MarshalBinary() - if err != nil { - t.Fatal(err) + var ( + nodes = make(map[string]common.Hash) + options = NewStackTrieOptions().WithWriter(func(path []byte, hash common.Hash, blob []byte) { + nodes[string(path)] = hash + }) + ) + tr := NewStackTrie(options) + + for i := 0; i < len(entries); i++ { + tr.MustUpdate(entries[i].k, entries[i].v) } - newSt, err := NewFromBinary(blob, nil) - if err != nil { - t.Fatal(err) + tr.Commit() + + for j := 0; j < 100; j++ { + for path, hash := range buildPartialTree(entries, t) { + if nodes[path] != hash { + t.Errorf("%v, want %x, got %x", []byte(path), nodes[path], hash) + } + } } - 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) +} + +func TestStackTrieErrors(t *testing.T) { + s := NewStackTrie(nil) + // Deletion + if err := s.Update(nil, nil); err == nil { + t.Fatal("expected error") + } + if err := s.Update(nil, []byte{}); err == nil { + t.Fatal("expected error") + } + if err := s.Update([]byte{0xa}, []byte{}); err == nil { + t.Fatal("expected error") } + // Non-ascending keys (going backwards or repeating) + assert.Nil(t, s.Update([]byte{0xaa}, []byte{0xa})) + assert.NotNil(t, s.Update([]byte{0xaa}, []byte{0xa}), "repeat insert same key") + assert.NotNil(t, s.Update([]byte{0xaa}, []byte{0xb}), "repeat insert same key") + assert.Nil(t, s.Update([]byte{0xab}, []byte{0xa})) + assert.NotNil(t, s.Update([]byte{0x10}, []byte{0xb}), "out of order insert") + assert.NotNil(t, s.Update([]byte{0xaa}, []byte{0xb}), "repeat insert same key") } diff --git a/trie/sync.go b/trie/sync.go index 4f5584599..589d28364 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,28 @@ 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) + + // accountNodeSyncedGauge is the metric to track how many account trie + // node are written during the sync. + accountNodeSyncedGauge = metrics.NewRegisteredGauge("trie/sync/nodes/account", nil) + + // storageNodeSyncedGauge is the metric to track how many account trie + // node are written during the sync. + storageNodeSyncedGauge = metrics.NewRegisteredGauge("trie/sync/nodes/storage", nil) + + // codeSyncedGauge is the metric to track how many contract codes are + // written during the sync. + codeSyncedGauge = metrics.NewRegisteredGauge("trie/sync/codes", nil) +) + // SyncPath is a path tuple identifying a particular trie node either in a single // trie (account) or a layered trie (account -> storage). // @@ -122,36 +145,85 @@ type CodeSyncResult struct { Data []byte // Data content of the retrieved bytecode } +// nodeOp represents an operation upon the trie node. It can either represent a +// deletion to the specific node or a node write for persisting retrieved node. +type nodeOp struct { + owner common.Hash // identifier of the trie (empty for account trie) + path []byte // path from the root to the specified node. + blob []byte // the content of the node (nil for deletion) + hash common.Hash // hash of the node content (empty for node deletion) +} + +// isDelete indicates if the operation is a database deletion. +func (op *nodeOp) isDelete() bool { + return len(op.blob) == 0 +} + // 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 + scheme string // State scheme identifier + codes map[common.Hash][]byte // In-memory batch of recently completed codes + nodes []nodeOp // In-memory batch of recently completed/deleted nodes size uint64 // Estimated batch-size of in-memory data. } // newSyncMemBatch allocates a new memory-buffer for not-yet persisted trie nodes. -func newSyncMemBatch() *syncMemBatch { +func newSyncMemBatch(scheme string) *syncMemBatch { return &syncMemBatch{ - nodes: make(map[string][]byte), - hashes: make(map[string]common.Hash), + scheme: scheme, codes: make(map[common.Hash][]byte), } } -// hasNode reports the trie node with specific path is already cached. -func (batch *syncMemBatch) hasNode(path []byte) bool { - _, ok := batch.nodes[string(path)] - return ok -} - // hasCode reports the contract code with specific hash is already cached. func (batch *syncMemBatch) hasCode(hash common.Hash) bool { _, ok := batch.codes[hash] return ok } +// addCode caches a contract code database write operation. +func (batch *syncMemBatch) addCode(hash common.Hash, code []byte) { + batch.codes[hash] = code + batch.size += common.HashLength + uint64(len(code)) +} + +// addNode caches a node database write operation. +func (batch *syncMemBatch) addNode(owner common.Hash, path []byte, blob []byte, hash common.Hash) { + if batch.scheme == rawdb.PathScheme { + if owner == (common.Hash{}) { + batch.size += uint64(len(path) + len(blob)) + } else { + batch.size += common.HashLength + uint64(len(path)+len(blob)) + } + } else { + batch.size += common.HashLength + uint64(len(blob)) + } + batch.nodes = append(batch.nodes, nodeOp{ + owner: owner, + path: path, + blob: blob, + hash: hash, + }) +} + +// delNode caches a node database delete operation. +func (batch *syncMemBatch) delNode(owner common.Hash, path []byte) { + if batch.scheme != rawdb.PathScheme { + log.Error("Unexpected node deletion", "owner", owner, "path", path, "scheme", batch.scheme) + return // deletion is not supported in hash mode. + } + if owner == (common.Hash{}) { + batch.size += uint64(len(path)) + } else { + batch.size += common.HashLength + uint64(len(path)) + } + batch.nodes = append(batch.nodes, nodeOp{ + owner: owner, + path: path, + }) +} + // Sync is the main state trie synchronisation scheduler, which provides yet // unknown trie hashes to retrieve, accepts node data associated with said hashes // and reconstructs the trie step by step until all is done. @@ -170,7 +242,7 @@ func NewSync(root common.Hash, database ethdb.KeyValueReader, callback LeafCallb ts := &Sync{ scheme: scheme, database: database, - membatch: newSyncMemBatch(), + membatch: newSyncMemBatch(scheme), nodeReqs: make(map[string]*nodeRequest), codeReqs: make(map[common.Hash]*codeRequest), queue: prque.New[int64, any](nil), // Ugh, can contain both string and hash, whyyy @@ -184,16 +256,17 @@ func NewSync(root common.Hash, database ethdb.KeyValueReader, callback LeafCallb // parent for completion tracking. The given path is a unique node path in // hex format and contain all the parent path if it's layered trie node. func (s *Sync) AddSubTrie(root common.Hash, path []byte, parent common.Hash, parentPath []byte, callback LeafCallback) { - // Short circuit if the trie is empty or already known if root == types.EmptyRootHash { return } - if s.membatch.hasNode(path) { - return - } owner, inner := ResolvePath(path) - if rawdb.HasTrieNode(s.database, owner, inner, root, s.scheme) { + exist, inconsistent := s.hasNode(owner, inner, root) + if exist { + // The entire subtrie is already present in the database. return + } else if inconsistent { + // There is a pre-existing node with the wrong hash in DB, remove it. + s.membatch.delNode(owner, inner) } // Assemble the new sub-trie sync request req := &nodeRequest{ @@ -288,7 +361,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 @@ -345,18 +418,42 @@ func (s *Sync) ProcessNode(result NodeSyncResult) error { } // Commit flushes the data stored in the internal membatch out to persistent -// storage, returning any occurred error. +// storage, returning any occurred error. The whole data set will be flushed +// in an atomic database batch. func (s *Sync) Commit(dbw ethdb.Batch) error { - // Dump the membatch into a database dbw - 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 writes into database batch. + var ( + account int + storage int + ) + for _, op := range s.membatch.nodes { + if op.isDelete() { + // node deletion is only supported in path mode. + if op.owner == (common.Hash{}) { + rawdb.DeleteAccountTrieNode(dbw, op.path) + } else { + rawdb.DeleteStorageTrieNode(dbw, op.owner, op.path) + } + deletionGauge.Inc(1) + } else { + if op.owner == (common.Hash{}) { + account += 1 + } else { + storage += 1 + } + rawdb.WriteTrieNode(dbw, op.owner, op.path, op.hash, op.blob, s.scheme) + } } + accountNodeSyncedGauge.Inc(int64(account)) + storageNodeSyncedGauge.Inc(int64(storage)) + + // 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() + codeSyncedGauge.Inc(int64(len(s.membatch.codes))) + + s.membatch = newSyncMemBatch(s.scheme) // reset the batch return nil } @@ -370,7 +467,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 +482,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 +522,41 @@ 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 and the persistent state + // ends up with a very weird situation that nodes on the same path + // are not inconsistent while they all present in disk. This property + // 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 are not accessible with the new shortNode, and + // also the whole sub-trie is still 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 { + s.membatch.delNode(owner, append(inner, key[:i]...)) + 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 { @@ -441,6 +573,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { var ( missing = make(chan *nodeRequest, len(children)) pending sync.WaitGroup + batchMu sync.Mutex ) for _, child := range children { // Notify any external watcher of a new key/value node @@ -458,34 +591,32 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) { } } } - // If the child references another node, resolve or schedule + // If the child references another node, resolve or schedule. + // We check all children concurrently. if node, ok := (child.node).(hashNode); ok { - // Try to resolve the node from the local database - if s.membatch.hasNode(child.path) { - continue - } - // Check the presence of children concurrently + path := child.path + hash := common.BytesToHash(node) pending.Add(1) - go func(child childNode) { + go func() { defer pending.Done() - - // If database says duplicate, then at least the trie node is present - // and we hold the assumption that it's NOT legacy contract code. - var ( - chash = common.BytesToHash(node) - owner, inner = ResolvePath(child.path) - ) - if rawdb.HasTrieNode(s.database, owner, inner, chash, s.scheme) { + owner, inner := ResolvePath(path) + exist, inconsistent := s.hasNode(owner, inner, hash) + if exist { return + } else if inconsistent { + // There is a pre-existing node with the wrong hash in DB, remove it. + batchMu.Lock() + s.membatch.delNode(owner, inner) + batchMu.Unlock() } // Locally unknown node, schedule for retrieval missing <- &nodeRequest{ - path: child.path, - hash: chash, + path: path, + hash: hash, parent: req, callback: req.callback, } - }(child) + }() } } pending.Wait() @@ -502,17 +633,15 @@ 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)) + owner, path := ResolvePath(req.path) + s.membatch.addNode(owner, path, req.data, req.hash) + + // Removed the completed node request delete(s.nodeReqs, string(req.path)) s.fetches[len(req.path)]-- @@ -528,13 +657,14 @@ 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 { // Write the node content to the membatch - s.membatch.codes[req.hash] = req.data - s.membatch.size += common.HashLength + uint64(len(req.data)) + s.membatch.addCode(req.hash, req.data) + + // Removed the completed code request delete(s.codeReqs, req.hash) s.fetches[len(req.path)]-- @@ -550,6 +680,28 @@ func (s *Sync) commitCodeRequest(req *codeRequest) error { return nil } +// hasNode reports whether the specified trie node is present in the database. +// 'exists' is true when the node exists in the database and matches the given root +// hash. The 'inconsistent' return value is true when the node exists but does not +// match the expected hash. +func (s *Sync) hasNode(owner common.Hash, path []byte, hash common.Hash) (exists bool, inconsistent bool) { + // If node is running with hash scheme, check the presence with node hash. + if s.scheme == rawdb.HashScheme { + return rawdb.HasLegacyTrieNode(s.database, hash), false + } + // If node is running with path scheme, check the presence with node path. + var blob []byte + var dbHash common.Hash + if owner == (common.Hash{}) { + blob, dbHash = rawdb.ReadAccountTrieNode(s.database, path) + } else { + blob, dbHash = rawdb.ReadStorageTrieNode(s.database, owner, path) + } + exists = hash == dbHash + inconsistent = !exists && len(blob) != 0 + return exists, inconsistent +} + // ResolvePath resolves the provided composite node path by separating the // path in account trie if it's existent. func ResolvePath(path []byte) (common.Hash, []byte) { diff --git a/trie/sync_test.go b/trie/sync_test.go index b6fe8d84a..585181b48 100644 --- a/trie/sync_test.go +++ b/trie/sync_test.go @@ -19,6 +19,7 @@ package trie import ( "bytes" "fmt" + "math/rand" "testing" "github.com/ethereum/go-ethereum/common" @@ -70,31 +71,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.MustNodeIterator(nil) for it.Next(true) { } return it.Error() @@ -109,8 +132,8 @@ 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()) + dbA := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) + dbB := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) dbC := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) dbD := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.PathScheme) @@ -205,7 +228,7 @@ 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 @@ -271,7 +294,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, @@ -341,7 +364,7 @@ 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 @@ -413,7 +436,7 @@ 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 @@ -484,7 +507,7 @@ 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 @@ -549,7 +572,7 @@ func testIncompleteSync(t *testing.T, scheme string) { hash := crypto.Keccak256Hash(result.Data) if hash != root { addedKeys = append(addedKeys, result.Path) - addedHashes = append(addedHashes, crypto.Keccak256Hash(result.Data)) + addedHashes = append(addedHashes, hash) } } // Fetch the next batch to retrieve @@ -565,11 +588,15 @@ func testIncompleteSync(t *testing.T, scheme string) { } // Sanity check that removing any node from the database is detected for i, path := range addedKeys { + if rand.Int31n(100) > 5 { + // Only check 5 percent of added keys as a sanity check + continue + } owner, inner := ResolvePath([]byte(path)) 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) @@ -643,7 +670,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++ { @@ -657,14 +684,17 @@ func testSyncOrdering(t *testing.T, scheme string) { } } } - func syncWith(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database) { + syncWithHookWriter(t, root, db, srcDb, nil) +} + +func syncWithHookWriter(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database, hookWriter ethdb.KeyValueWriter) { // Create a destination trie and sync with the scheduler sched := NewSync(root, db, nil, srcDb.Scheme()) // 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{ @@ -696,9 +726,12 @@ func syncWith(t *testing.T, root common.Hash, db ethdb.Database, srcDb *Database if err := sched.Commit(batch); err != nil { t.Fatalf("failed to commit data: %v", err) } - batch.Write() - - paths, nodes, _ = sched.Missing(1) + if hookWriter != nil { + batch.Replay(hookWriter) + } else { + batch.Write() + } + paths, nodes, _ = sched.Missing(0) elements = elements[:0] for i := 0; i < len(paths); i++ { elements = append(elements, trieElement{ @@ -724,7 +757,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) @@ -748,7 +781,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) @@ -772,5 +805,211 @@ 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) +} + +func TestSyncAbort(t *testing.T) { + testSyncAbort(t, rawdb.PathScheme) + testSyncAbort(t, rawdb.HashScheme) +} + +type hookWriter struct { + db ethdb.KeyValueStore + filter func(key []byte, value []byte) bool +} + +// Put inserts the given value into the key-value data store. +func (w *hookWriter) Put(key []byte, value []byte) error { + if w.filter != nil && w.filter(key, value) { + return nil + } + return w.db.Put(key, value) +} + +// Delete removes the key from the key-value data store. +func (w *hookWriter) Delete(key []byte) error { + return w.db.Delete(key) +} + +func testSyncAbort(t *testing.T, scheme string) { + 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 { + 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 + } + ) + var ( + stateA = make(map[string][]byte) + key = randBytes(32) + val = randBytes(32) + ) + for i := 0; i < 256; i++ { + writeFn(randBytes(32), nil, srcTrie, stateA) + } + writeFn(key, val, 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 the element from the trie + stateB := copyStates(stateA) + srcTrie, _ = New(TrieID(rootA), srcTrieDB) + deleteFn(key, 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) + } + + // Sync the new state, but never persist the new root node. Before the + // fix #28595, the original old root node will still be left in database + // which breaks the next healing cycle. + syncWithHookWriter(t, rootB, destDisk, srcTrieDB, &hookWriter{db: destDisk, filter: func(key []byte, value []byte) bool { + if scheme == rawdb.HashScheme { + return false + } + if len(value) == 0 { + return false + } + ok, path := rawdb.ResolveAccountTrieNodeKey(key) + return ok && len(path) == 0 + }}) + + // Add elements to expand trie + stateC := copyStates(stateB) + srcTrie, _ = New(TrieID(rootB), srcTrieDB) + + writeFn(key, val, 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/tracer_test.go b/trie/tracer_test.go index 86daec6d2..acb8c2f6b 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 @@ -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() ) @@ -211,7 +211,7 @@ 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 @@ -262,7 +262,7 @@ 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 { diff --git a/trie/trie_reader.go b/trie/trie_reader.go index 1c63ff454..421596455 100644 --- a/trie/trie_reader.go +++ b/trie/trie_reader.go @@ -20,6 +20,7 @@ import ( "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. @@ -83,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 3cb21c195..c5bd3faf5 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "hash" + "io" "math/big" "math/rand" "reflect" @@ -45,7 +46,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 +55,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 +65,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") } @@ -161,7 +167,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,7 +179,7 @@ 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") @@ -184,7 +190,7 @@ func TestInsert(t *testing.T) { } 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") @@ -209,7 +215,7 @@ func TestGet(t *testing.T) { } 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 +242,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 +266,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"}, @@ -321,7 +327,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() @@ -357,13 +363,18 @@ func TestRandomCases(t *testing.T) { {op: 1, key: common.Hex2Bytes("980c393656413a15c8da01978ed9f89feb80b502f58f2d640e3a2f5f7a99a7018f1b573befd92053ac6f78fca4a87268"), value: common.Hex2Bytes("")}, // step 24 {op: 1, key: common.Hex2Bytes("fd"), value: common.Hex2Bytes("")}, // step 25 } - runRandTest(rt) + if err := runRandTest(rt); err != nil { + t.Fatal(err) + } } // randTest performs random trie operations. // Instances of this test are created by Generate. type randTest []randTestStep +// compile-time interface check +var _ quick.Generator = (randTest)(nil) + type randTestStep struct { op int key []byte // for opUpdate, opDelete, opGet @@ -384,33 +395,45 @@ const ( ) func (randTest) Generate(r *rand.Rand, size int) reflect.Value { + var finishedFn = func() bool { + size-- + return size == 0 + } + return reflect.ValueOf(generateSteps(finishedFn, r)) +} + +func generateSteps(finished func() bool, r io.Reader) randTest { var allKeys [][]byte + var one = []byte{0} genKey := func() []byte { - if len(allKeys) < 2 || r.Intn(100) < 10 { + r.Read(one) + if len(allKeys) < 2 || one[0]%100 > 90 { // new key - key := make([]byte, r.Intn(50)) + size := one[0] % 50 + key := make([]byte, size) r.Read(key) allKeys = append(allKeys, key) return key } // use existing key - return allKeys[r.Intn(len(allKeys))] + idx := int(one[0]) % len(allKeys) + return allKeys[idx] } - var steps randTest - for i := 0; i < size; i++ { - step := randTestStep{op: r.Intn(opMax)} + for !finished() { + r.Read(one) + step := randTestStep{op: int(one[0]) % opMax} switch step.op { case opUpdate: step.key = genKey() step.value = make([]byte, 8) - binary.BigEndian.PutUint64(step.value, uint64(i)) + binary.BigEndian.PutUint64(step.value, uint64(len(steps))) case opGet, opDelete, opProve: step.key = genKey() } steps = append(steps, step) } - return reflect.ValueOf(steps) + return steps } func verifyAccessList(old *Trie, new *Trie, set *trienode.NodeSet) error { @@ -455,7 +478,12 @@ func verifyAccessList(old *Trie, new *Trie, set *trienode.NodeSet) error { return nil } -func runRandTest(rt randTest) bool { +// runRandTestBool coerces error to boolean, for use in quick.Check +func runRandTestBool(rt randTest) bool { + return runRandTest(rt) == nil +} + +func runRandTest(rt randTest) error { var scheme = rawdb.HashScheme if rand.Intn(2) == 0 { scheme = rawdb.PathScheme @@ -508,12 +536,12 @@ func runRandTest(rt randTest) bool { newtr, err := New(TrieID(root), triedb) if err != nil { rt[i].err = err - return false + return err } if nodes != nil { if err := verifyAccessList(origTrie, newtr, nodes); err != nil { rt[i].err = err - return false + return err } } tr = newtr @@ -582,14 +610,14 @@ func runRandTest(rt randTest) bool { } // Abort the test on error. if rt[i].err != nil { - return false + return rt[i].err } } - return true + return nil } func TestRandom(t *testing.T) { - if err := quick.Check(runRandTest, nil); err != nil { + if err := quick.Check(runRandTestBool, nil); err != nil { if cerr, ok := err.(*quick.CheckError); ok { t.Fatalf("random test iteration %d failed: %s", cerr.Count, spew.Sdump(cerr.In)) } @@ -604,12 +632,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 +651,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 +683,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 +714,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 +728,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,7 +741,7 @@ 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())) + checktr := NewEmpty(NewDatabase(rawdb.NewMemoryDatabase(), nil)) it := NewIterator(trie.MustNodeIterator(nil)) for it.Next() { checktr.MustUpdate(it.Key, it.Value) @@ -722,7 +754,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]) } @@ -788,11 +820,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,7 +868,7 @@ 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++ { @@ -861,7 +899,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++ { @@ -893,13 +931,16 @@ 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()) + + options := NewStackTrieOptions() + options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(stackTrieSponge, common.Hash{}, path, hash, blob, db.Scheme()) }) + stTrie := NewStackTrie(options) // Fill the trie with elements for i := 0; i < count; i++ { // For the stack trie, we need to do inserts in proper order @@ -922,10 +963,7 @@ func TestCommitSequenceStackTrie(t *testing.T) { db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) db.Commit(root, false) // And flush stacktrie -> disk - stRoot, err := stTrie.Commit() - if err != nil { - t.Fatalf("Failed to commit stack trie %v", err) - } + stRoot := stTrie.Commit() if stRoot != root { t.Fatalf("root wrong, got %x exp %x", stRoot, root) } @@ -952,13 +990,16 @@ 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()) + + options := NewStackTrieOptions() + options = options.WithWriter(func(path []byte, hash common.Hash, blob []byte) { + rawdb.WriteTrieNode(stackTrieSponge, common.Hash{}, path, hash, blob, db.Scheme()) }) + stTrie := NewStackTrie(options) // Add a single small-element to the trie(s) key := make([]byte, 5) key[0] = 1 @@ -970,10 +1011,7 @@ func TestCommitSequenceSmallRoot(t *testing.T) { db.Update(root, types.EmptyRootHash, 0, trienode.NewWithNodeSet(nodes), nil) db.Commit(root, false) // And flush stacktrie -> disk - stRoot, err := stTrie.Commit() - if err != nil { - t.Fatalf("Failed to commit stack trie %v", err) - } + stRoot := stTrie.Commit() if stRoot != root { t.Fatalf("root wrong, got %x exp %x", stRoot, root) } @@ -1029,7 +1067,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 +1118,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,7 +1170,7 @@ 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]) @@ -1170,3 +1208,17 @@ func TestDecodeNode(t *testing.T) { decodeNode(hash, elems) } } + +func FuzzTrie(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + var steps = 500 + var input = bytes.NewReader(data) + var finishedFn = func() bool { + steps-- + return steps < 0 || input.Len() == 0 + } + if err := runRandTest(generateSteps(finishedFn, input)); err != nil { + t.Fatal(err) + } + }) +} diff --git a/trie/triedb/hashdb/database.go b/trie/triedb/hashdb/database.go index 4441f2a38..e45ccdba3 100644 --- a/trie/triedb/hashdb/database.go +++ b/trie/triedb/hashdb/database.go @@ -65,14 +65,23 @@ 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. -// -// Note, the trie Database is **not** thread safe in its mutations, but it **is** -// thread safe in providing individual, independent node access. The rationale -// behind this split design is to provide read access to RPC handlers and sync -// servers even while the trie is executing expensive garbage collection. type Database struct { diskdb ethdb.Database // Persistent storage for matured trie nodes resolver ChildResolver // The handler to resolve children of nodes @@ -99,7 +108,7 @@ type Database struct { // cachedNode is all the information we know about a single cached trie node // in the memory database write layer. type cachedNode struct { - node []byte // Encoded node blob + node []byte // Encoded node blob, immutable parents uint32 // Number of live nodes referencing this one external map[common.Hash]struct{} // The set of external children flushPrev common.Hash // Previous node in the flush-list @@ -122,12 +131,13 @@ func (n *cachedNode) forChildren(resolver ChildResolver, onChild func(hash commo } // New initializes the hash-based node database. -func New(diskdb ethdb.Database, size int, resolver ChildResolver) *Database { - // Initialize the clean cache if the specified cache allowance - // is non-zero. Note, the size is in bytes. +func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Database { + if config == nil { + config = Defaults + } var cleans *fastcache.Cache - if size > 0 { - cleans = fastcache.New(size) + if config.CleanCacheSize > 0 { + cleans = fastcache.New(config.CleanCacheSize) } return &Database{ diskdb: diskdb, @@ -137,9 +147,9 @@ func New(diskdb ethdb.Database, size int, resolver ChildResolver) *Database { } } -// insert inserts a simplified trie node into the memory database. -// All nodes inserted by this function will be reference tracked -// and in theory should only used for **trie nodes** insertion. +// insert inserts a trie node into the memory database. All nodes inserted by +// this function will be reference tracked. This function assumes the lock is +// already held. func (db *Database) insert(hash common.Hash, node []byte) { // If the node's already cached, skip if _, ok := db.dirties[hash]; ok { @@ -168,9 +178,9 @@ func (db *Database) insert(hash common.Hash, node []byte) { db.dirtiesSize += common.StorageSize(common.HashLength + len(node)) } -// Node retrieves an encoded cached trie node from memory. If it cannot be found +// node retrieves an encoded cached trie node from memory. If it cannot be found // cached, the method queries the persistent database for the content. -func (db *Database) Node(hash common.Hash) ([]byte, error) { +func (db *Database) node(hash common.Hash) ([]byte, error) { // It doesn't make sense to retrieve the metaroot if hash == (common.Hash{}) { return nil, errors.New("not found") @@ -183,11 +193,14 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) { return enc, nil } } - // Retrieve the node from the dirty cache if available + // Retrieve the node from the dirty cache if available. db.lock.RLock() dirty := db.dirties[hash] db.lock.RUnlock() + // Return the cached node if it's found in the dirty set. + // The dirty.node field is immutable and safe to read it + // even without lock guard. if dirty != nil { memcacheDirtyHitMeter.Mark(1) memcacheDirtyReadMeter.Mark(int64(len(dirty.node))) @@ -208,20 +221,6 @@ func (db *Database) Node(hash common.Hash) ([]byte, error) { return nil, errors.New("not found") } -// Nodes retrieves the hashes of all the nodes cached within the memory database. -// This method is extremely expensive and should only be used to validate internal -// states in test code. -func (db *Database) Nodes() []common.Hash { - db.lock.RLock() - defer db.lock.RUnlock() - - var hashes = make([]common.Hash, 0, len(db.dirties)) - for hash := range db.dirties { - hashes = append(hashes, hash) - } - return hashes -} - // Reference adds a new reference from a parent node to a child node. // This function is used to add reference between internal trie node // and external node(e.g. storage trie root), all internal trie nodes @@ -329,16 +328,16 @@ func (db *Database) dereference(hash common.Hash) { // Cap iteratively flushes old but still referenced trie nodes until the total // memory usage goes below the given threshold. -// -// Note, this method is a non-synchronized mutator. It is unsafe to call this -// concurrently with other mutators. func (db *Database) Cap(limit common.StorageSize) error { + db.lock.Lock() + defer db.lock.Unlock() + // Create a database batch to flush persistent data out. It is important that // 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() batch := db.diskdb.NewBatch() + nodes, storage, start := len(db.dirties), db.dirtiesSize, time.Now() // 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 @@ -376,9 +375,6 @@ func (db *Database) Cap(limit common.StorageSize) error { return err } // Write successful, clear out the flushed data - db.lock.Lock() - defer db.lock.Unlock() - for db.oldest != oldest { node := db.dirties[db.oldest] delete(db.dirties, db.oldest) @@ -409,10 +405,10 @@ func (db *Database) Cap(limit common.StorageSize) error { // Commit iterates over all the children of a particular node, writes them out // to disk, forcefully tearing down all references in both directions. As a side // effect, all pre-images accumulated up to this point are also written. -// -// Note, this method is a non-synchronized mutator. It is unsafe to call this -// concurrently with other mutators. func (db *Database) Commit(node common.Hash, report bool) error { + db.lock.Lock() + defer db.lock.Unlock() + // Create a database batch to flush persistent data out. It is important that // 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 @@ -434,8 +430,6 @@ func (db *Database) Commit(node common.Hash, report bool) error { return err } // Uncache any leftovers in the last batch - db.lock.Lock() - defer db.lock.Unlock() if err := batch.Replay(uncacher); err != nil { return err } @@ -484,13 +478,11 @@ func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleane if err := batch.Write(); err != nil { return err } - db.lock.Lock() err := batch.Replay(uncacher) - batch.Reset() - db.lock.Unlock() if err != nil { return err } + batch.Reset() } return nil } @@ -559,7 +551,7 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool { 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 { + if blob, _ := db.node(parent); len(blob) == 0 { log.Error("parent state is not present") } } @@ -609,7 +601,10 @@ func (db *Database) Update(root common.Hash, parent common.Hash, block uint64, n // 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() @@ -617,11 +612,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 { @@ -631,7 +632,7 @@ func (db *Database) Scheme() string { // Reader retrieves a node reader belonging to the given state root. // 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 { + if _, err := db.node(root); err != nil { return nil, fmt.Errorf("state %#x is not available, %v", root, err) } return &reader{db: db}, nil @@ -642,9 +643,9 @@ type reader struct { db *Database } -// Node retrieves the trie node with the given node hash. -// No error will be returned if the node is not found. +// Node retrieves the trie node with the given node hash. No error will be +// returned if the node is not found. func (reader *reader) Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error) { - blob, _ := reader.db.Node(hash) + blob, _ := reader.db.node(hash) return blob, nil } diff --git a/trie/triedb/pathdb/database.go b/trie/triedb/pathdb/database.go index 29f6b5e10..f2d6cea63 100644 --- a/trie/triedb/pathdb/database.go +++ b/trie/triedb/pathdb/database.go @@ -33,8 +33,26 @@ import ( "github.com/ethereum/go-ethereum/trie/triestate" ) -// maxDiffLayers is the maximum diff layers allowed in the layer tree. -const maxDiffLayers = 128 +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. @@ -68,30 +86,33 @@ type layer interface { // Config contains the settings for database. type Config struct { - StateLimit uint64 // Number of recent blocks to maintain state history for - CleanSize int // Maximum memory allowance (in bytes) for caching clean nodes - DirtySize int // Maximum memory allowance (in bytes) for caching dirty nodes - ReadOnly bool // Flag whether the database is opened in read only mode. + 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. } -var ( - // defaultCleanSize is the default memory allowance of clean cache. - defaultCleanSize = 16 * 1024 * 1024 - - // defaultBufferSize is the default memory allowance of node buffer - // that aggregates the writes from above until it's flushed into the - // disk. Do not increase the buffer size arbitrarily, otherwise the - // system pause time will increase when the database writes happen. - defaultBufferSize = 128 * 1024 * 1024 -) +// 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{ - StateLimit: params.FullImmutabilityThreshold, - CleanSize: defaultCleanSize, - DirtySize: defaultBufferSize, + 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 @@ -107,7 +128,8 @@ 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 // Indicator if database is opened in read only mode + 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 @@ -123,9 +145,11 @@ func New(diskdb ethdb.Database, config *Config) *Database { if config == nil { config = Defaults } + config = config.sanitize() + db := &Database{ readOnly: config.ReadOnly, - bufferSize: config.DirtySize, + bufferSize: config.DirtyCacheSize, config: config, diskdb: diskdb, } @@ -140,20 +164,43 @@ func New(diskdb ethdb.Database, config *Config) *Database { // 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.NewStateHistoryFreezer(ancient, false) + 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) + diskLayerID := db.tree.bottom().stateID() + if diskLayerID == 0 { + // Reset the entire state histories in case the trie database is + // not initialized yet, as these state histories are not expected. + frozen, err := db.freezer.Ancients() + if err != nil { + log.Crit("Failed to retrieve head of state history", "err", err) + } + if frozen != 0 { + err := db.freezer.Reset() + if err != nil { + log.Crit("Failed to reset state histories", "err", err) + } + log.Info("Truncated extraneous state history") + } + } else { + // Truncate the extra state histories above in freezer in case + // it's not aligned with the disk layer. + pruned, err := truncateFromHead(db.diskdb, freezer, diskLayerID) + if err != nil { + log.Crit("Failed to truncate extra state histories", "err", err) + } + if pruned != 0 { + log.Warn("Truncated extra state histories", "number", pruned) + } } - 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") @@ -181,9 +228,9 @@ func (db *Database) Update(root common.Hash, parentRoot common.Hash, block uint6 db.lock.Lock() defer db.lock.Unlock() - // Short circuit if the database is in read only mode. - if db.readOnly { - return errSnapshotReadOnly + // 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 @@ -204,45 +251,59 @@ func (db *Database) Commit(root common.Hash, report bool) error { db.lock.Lock() defer db.lock.Unlock() - // Short circuit if the database is in read only mode. - if db.readOnly { - return errSnapshotReadOnly + // Short circuit if the mutation is not allowed. + if err := db.modifyAllowed(); err != nil { + return err } return db.tree.cap(root, 0) } -// Reset rebuilds the database with the specified state as the base. -// -// - if target state is empty, clear the stored state and all layers on top -// - if target state is non-empty, ensure the stored state matches with it -// and clear all other layers on top. -func (db *Database) Reset(root common.Hash) error { +// 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 errSnapshotReadOnly + return errDatabaseReadOnly } - batch := db.diskdb.NewBatch() - root = types.TrieRootHash(root) - if root == types.EmptyRootHash { - // Empty state is requested as the target, nuke out - // the root node and leave all others as dangling. - rawdb.DeleteAccountTrieNode(batch, nil) - } else { - // Ensure the requested state is existent before any - // action is applied. - _, hash := rawdb.ReadAccountTrieNode(db.diskdb, nil) - if hash != root { - return fmt.Errorf("state is mismatched, local: %x, target: %x", hash, root) - } + // Prevent duplicated disable operation. + if db.waitSync { + log.Error("Reject duplicated disable operation") + return nil } - // Mark the disk layer as stale before applying any mutation. + 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 { @@ -259,8 +320,11 @@ func (db *Database) Reset(root common.Hash) error { } // Re-construct a new disk layer backed by persistent state // with **empty clean cache and node buffer**. - dl := newDiskLayer(root, 0, db, nil, newNodeBuffer(db.bufferSize, nil, 0)) - db.tree.reset(dl) + 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 } @@ -273,7 +337,10 @@ func (db *Database) Recover(root common.Hash, loader triestate.TrieLoader) error defer db.lock.Unlock() // Short circuit if rollback operation is not supported. - if db.readOnly || db.freezer == nil { + 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. @@ -344,7 +411,14 @@ 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 } @@ -353,16 +427,16 @@ func (db *Database) Close() error { // Size returns the current storage size of the memory cache in front of the // persistent database layer. -func (db *Database) Size() (size common.StorageSize) { +func (db *Database) Size() (diffs common.StorageSize, nodes common.StorageSize) { db.tree.forEach(func(layer layer) { if diff, ok := layer.(*diffLayer); ok { - size += common.StorageSize(diff.memory) + diffs += common.StorageSize(diff.memory) } if disk, ok := layer.(*diskLayer); ok { - size += disk.size() + nodes += disk.size() } }) - return size + return diffs, nodes } // Initialized returns an indicator if the state data is already @@ -374,6 +448,9 @@ func (db *Database) Initialized(genesisRoot common.Hash) bool { inited = true } }) + if !inited { + inited = rawdb.ReadSnapSyncStatusFlag(db.diskdb) != rawdb.StateSyncUnknown + } return inited } @@ -382,6 +459,10 @@ 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) } @@ -390,3 +471,15 @@ func (db *Database) SetBufferSize(size int) error { 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 index bcc37e59c..5509682c3 100644 --- a/trie/triedb/pathdb/database_test.go +++ b/trie/triedb/pathdb/database_test.go @@ -46,7 +46,8 @@ func updateTrie(addrHash common.Hash, root common.Hash, dirties, cleans map[comm h.Update(key.Bytes(), val) } } - return h.Commit(false) + root, nodes, _ := h.Commit(false) + return root, nodes } func generateAccount(storageRoot common.Hash) types.StateAccount { @@ -95,11 +96,15 @@ type tester struct { snapStorages map[common.Hash]map[common.Hash]map[common.Hash][]byte } -func newTester(t *testing.T) *tester { +func newTester(t *testing.T, historyLimit uint64) *tester { var ( disk, _ = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false) - db = New(disk, &Config{CleanSize: 256 * 1024, DirtySize: 256 * 1024}) - obj = &tester{ + db = New(disk, &Config{ + StateHistory: historyLimit, + CleanCacheSize: 256 * 1024, + DirtyCacheSize: 256 * 1024, + }) + obj = &tester{ db: db, preimages: make(map[common.Hash]common.Address), accounts: make(map[common.Hash][]byte), @@ -375,7 +380,7 @@ func (t *tester) bottomIndex() int { func TestDatabaseRollback(t *testing.T) { // Verify state histories - tester := newTester(t) + tester := newTester(t, 0) defer tester.release() if err := tester.verifyHistory(); err != nil { @@ -401,7 +406,7 @@ func TestDatabaseRollback(t *testing.T) { func TestDatabaseRecoverable(t *testing.T) { var ( - tester = newTester(t) + tester = newTester(t, 0) index = tester.bottomIndex() ) defer tester.release() @@ -438,43 +443,44 @@ func TestDatabaseRecoverable(t *testing.T) { } } -func TestReset(t *testing.T) { - var ( - tester = newTester(t) - index = tester.bottomIndex() - ) +func TestDisable(t *testing.T) { + tester := newTester(t, 0) defer tester.release() - // Reset database to unknown target, should reject it - if err := tester.db.Reset(testutil.RandomHash()); err == nil { - t.Fatal("Failed to reject invalid reset") + _, stored := rawdb.ReadAccountTrieNode(tester.db.diskdb, nil) + if err := tester.db.Disable(); err != nil { + t.Fatal("Failed to deactivate database") } - // Reset database to state persisted in the disk - if err := tester.db.Reset(types.EmptyRootHash); err != nil { - t.Fatalf("Failed to reset database %v", err) + 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 - for i := 0; i <= index; i++ { - _, err := readHistory(tester.db.freezer, uint64(i+1)) - if err == nil { - t.Fatalf("Failed to clean state history, index %d", i+1) - } + 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() != types.EmptyRootHash { - t.Fatalf("Root hash is not matched exp %x got %x", types.EmptyRootHash, tester.db.tree.bottom().rootHash()) + 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) + tester := newTester(t, 0) defer tester.release() if err := tester.db.Commit(tester.lastHash(), false); err != nil { @@ -498,7 +504,7 @@ func TestCommit(t *testing.T) { } func TestJournal(t *testing.T) { - tester := newTester(t) + tester := newTester(t, 0) defer tester.release() if err := tester.db.Journal(tester.lastHash()); err != nil { @@ -522,7 +528,7 @@ func TestJournal(t *testing.T) { } func TestCorruptedJournal(t *testing.T) { - tester := newTester(t) + tester := newTester(t, 0) defer tester.release() if err := tester.db.Journal(tester.lastHash()); err != nil { @@ -551,6 +557,35 @@ func TestCorruptedJournal(t *testing.T) { } } +// TestTailTruncateHistory function is designed to test a specific edge case where, +// when history objects are removed from the end, it should trigger a state flush +// if the ID of the new tail object is even higher than the persisted state ID. +// +// For example, let's say the ID of the persistent state is 10, and the current +// history objects range from ID(5) to ID(15). As we accumulate six more objects, +// the history will expand to cover ID(11) to ID(21). ID(11) then becomes the +// oldest history object, and its ID is even higher than the stored state. +// +// In this scenario, it is mandatory to update the persistent state before +// truncating the tail histories. This ensures that the ID of the persistent state +// always falls within the range of [oldest-history-id, latest-history-id]. +func TestTailTruncateHistory(t *testing.T) { + tester := newTester(t, 10) + defer tester.release() + + tester.db.Close() + tester.db = New(tester.db.diskdb, &Config{StateHistory: 10}) + + head, err := tester.db.freezer.Ancients() + if err != nil { + t.Fatalf("Failed to obtain freezer head") + } + stored := rawdb.ReadPersistentStateID(tester.db.diskdb) + if head != stored { + t.Fatalf("Failed to truncate excess history object above, stored: %d, head: %d", stored, head) + } +} + // 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)) diff --git a/trie/triedb/pathdb/difflayer.go b/trie/triedb/pathdb/difflayer.go index d25ac1c60..10567715d 100644 --- a/trie/triedb/pathdb/difflayer.go +++ b/trie/triedb/pathdb/difflayer.go @@ -114,7 +114,7 @@ func (dl *diffLayer) node(owner common.Hash, path []byte, hash common.Hash, dept 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) + return nil, newUnexpectedNodeError("diff", hash, n.Hash, owner, path, n.Blob) } dirtyHitMeter.Mark(1) dirtyNodeHitDepthHist.Update(int64(depth)) diff --git a/trie/triedb/pathdb/difflayer_test.go b/trie/triedb/pathdb/difflayer_test.go index 77c4cd572..9b5907c3c 100644 --- a/trie/triedb/pathdb/difflayer_test.go +++ b/trie/triedb/pathdb/difflayer_test.go @@ -29,7 +29,7 @@ import ( func emptyLayer() *diskLayer { return &diskLayer{ db: New(rawdb.NewMemoryDatabase(), nil), - buffer: newNodeBuffer(defaultBufferSize, nil, 0), + buffer: newNodeBuffer(DefaultBufferSize, nil, 0), } } diff --git a/trie/triedb/pathdb/disklayer.go b/trie/triedb/pathdb/disklayer.go index b526b3b7d..ef697cbce 100644 --- a/trie/triedb/pathdb/disklayer.go +++ b/trie/triedb/pathdb/disklayer.go @@ -47,8 +47,8 @@ func newDiskLayer(root common.Hash, id uint64, db *Database, cleans *fastcache.C // 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.CleanSize != 0 { - cleans = fastcache.New(db.config.CleanSize) + if cleans == nil && db.config.CleanCacheSize != 0 { + cleans = fastcache.New(db.config.CleanCacheSize) } return &diskLayer{ root: root, @@ -150,7 +150,7 @@ func (dl *diskLayer) Node(owner common.Hash, path []byte, hash common.Hash) ([]b 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) + return nil, newUnexpectedNodeError("disk", hash, nHash, owner, path, nBlob) } if dl.cleans != nil && len(nBlob) > 0 { dl.cleans.Set(key, nBlob) @@ -172,37 +172,65 @@ 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. + // 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 from head in the next restart. + var ( + overflow bool + oldest uint64 + ) if dl.db.freezer != nil { - err := writeHistory(dl.db.diskdb, dl.db.freezer, bottom, dl.db.config.StateLimit) + err := writeHistory(dl.db.freezer, bottom) + if err != nil { + return nil, err + } + // Determine if the persisted history object has exceeded the configured + // limitation, set the overflow as true if so. + tail, err := dl.db.freezer.Tail() if err != nil { return nil, err } + limit := dl.db.config.StateHistory + if limit != 0 && bottom.stateID()-tail > limit { + overflow = true + oldest = bottom.stateID() - limit + 1 // track the id of history **after truncation** + } } // 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. + // 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. + // 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. 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 { + + // In a unique scenario where the ID of the oldest history object (after tail + // truncation) surpasses the persisted state ID, we take the necessary action + // of forcibly committing the cached dirty nodes to ensure that the persisted + // state ID remains higher. + if !force && rawdb.ReadPersistentStateID(dl.db.diskdb) < oldest { + force = true + } + if err := ndl.buffer.flush(ndl.db.diskdb, ndl.cleans, ndl.id, force); err != nil { return nil, err } + // To remove outdated history objects from the end, we set the 'tail' parameter + // to 'oldest-1' due to the offset between the freezer index and the history ID. + if overflow { + pruned, err := truncateFromTail(ndl.db.diskdb, ndl.db.freezer, oldest-1) + if err != nil { + return nil, err + } + log.Debug("Pruned state history", "items", pruned, "tailid", oldest) + } return ndl, nil } @@ -276,6 +304,20 @@ func (dl *diskLayer) size() common.StorageSize { 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 } diff --git a/trie/triedb/pathdb/errors.go b/trie/triedb/pathdb/errors.go index f503a9c49..78ee4459f 100644 --- a/trie/triedb/pathdb/errors.go +++ b/trie/triedb/pathdb/errors.go @@ -21,12 +21,17 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" ) var ( - // errSnapshotReadOnly is returned if the database is opened in read only mode - // and mutation is requested. - errSnapshotReadOnly = errors.New("read only") + // 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 @@ -46,6 +51,10 @@ var ( errUnexpectedNode = errors.New("unexpected node") ) -func newUnexpectedNodeError(loc string, expHash common.Hash, gotHash common.Hash, owner common.Hash, path []byte) error { - return fmt.Errorf("%w, loc: %s, node: (%x %v), %x!=%x", errUnexpectedNode, loc, owner, path, expHash, gotHash) +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 index ce8253250..6e3f3faae 100644 --- a/trie/triedb/pathdb/history.go +++ b/trie/triedb/pathdb/history.go @@ -512,38 +512,28 @@ func readHistory(freezer *rawdb.ResettableFreezer, id uint64) (*history, error) 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 { +// writeHistory persists the state history with the provided state set. +func writeHistory(freezer *rawdb.ResettableFreezer, dl *diffLayer) 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) + start = time.Now() + history = newHistory(dl.rootHash(), dl.parentLayer().rootHash(), dl.block, dl.states) ) - accountData, storageData, accountIndex, storageIndex := h.encode() + accountData, storageData, accountIndex, storageIndex := history.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) + rawdb.WriteStateHistory(freezer, dl.stateID(), history.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))) + log.Debug("Stored state history", "id", dl.stateID(), "block", dl.block, "data", dataSize, "index", indexSize, "elapsed", common.PrettyDuration(time.Since(start))) + return nil } @@ -581,7 +571,16 @@ func truncateFromHead(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, nhead if err != nil { return 0, err } - if ohead <= nhead { + otail, err := freezer.Tail() + if err != nil { + return 0, err + } + // Ensure that the truncation target falls within the specified range. + if ohead < nhead || nhead < otail { + return 0, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", otail, ohead, nhead) + } + // Short circuit if nothing to truncate. + if ohead == nhead { return 0, nil } // Load the meta objects in range [nhead+1, ohead] @@ -610,11 +609,20 @@ func truncateFromHead(db ethdb.Batcher, freezer *rawdb.ResettableFreezer, nhead // 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) { + ohead, err := freezer.Ancients() + if err != nil { + return 0, err + } otail, err := freezer.Tail() if err != nil { return 0, err } - if otail >= ntail { + // Ensure that the truncation target falls within the specified range. + if otail > ntail || ntail > ohead { + return 0, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", otail, ohead, ntail) + } + // Short circuit if nothing to truncate. + if otail == ntail { return 0, nil } // Load the meta objects in range [otail+1, ntail] diff --git a/trie/triedb/pathdb/history_test.go b/trie/triedb/pathdb/history_test.go index 6c250c259..a3257441d 100644 --- a/trie/triedb/pathdb/history_test.go +++ b/trie/triedb/pathdb/history_test.go @@ -224,9 +224,53 @@ func TestTruncateTailHistories(t *testing.T) { } } +func TestTruncateOutOfRange(t *testing.T) { + var ( + 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)) + } + truncateFromTail(db, freezer, uint64(len(hs)/2)) + + // Ensure of-out-range truncations are rejected correctly. + head, _ := freezer.Ancients() + tail, _ := freezer.Tail() + + cases := []struct { + mode int + target uint64 + expErr error + }{ + {0, head, nil}, // nothing to delete + {0, head + 1, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", tail, head, head+1)}, + {0, tail - 1, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", tail, head, tail-1)}, + {1, tail, nil}, // nothing to delete + {1, head + 1, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", tail, head, head+1)}, + {1, tail - 1, fmt.Errorf("out of range, tail: %d, head: %d, target: %d", tail, head, tail-1)}, + } + for _, c := range cases { + var gotErr error + if c.mode == 0 { + _, gotErr = truncateFromHead(db, freezer, c.target) + } else { + _, gotErr = truncateFromTail(db, freezer, c.target) + } + if !reflect.DeepEqual(gotErr, c.expErr) { + t.Errorf("Unexpected error, want: %v, got: %v", c.expErr, gotErr) + } + } +} + // openFreezer initializes the freezer instance for storing state histories. func openFreezer(datadir string, readOnly bool) (*rawdb.ResettableFreezer, error) { - return rawdb.NewStateHistoryFreezer(datadir, readOnly) + return rawdb.NewStateFreezer(datadir, readOnly) } func compareSet[k comparable](a, b map[k][]byte) bool { diff --git a/trie/triedb/pathdb/journal.go b/trie/triedb/pathdb/journal.go index d8c7d39fb..ac770763e 100644 --- a/trie/triedb/pathdb/journal.go +++ b/trie/triedb/pathdb/journal.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "io" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" @@ -341,13 +342,21 @@ func (db *Database) Journal(root common.Hash) error { 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 errSnapshotReadOnly + return errDatabaseReadOnly } // Firstly write out the metadata of journal journal := new(bytes.Buffer) @@ -373,6 +382,6 @@ func (db *Database) Journal(root common.Hash) error { // Set the db in read only mode to reject all following mutations db.readOnly = true - log.Info("Stored journal in triedb", "disk", diskroot, "size", common.StorageSize(journal.Len())) + 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/nodebuffer.go b/trie/triedb/pathdb/nodebuffer.go index 67de225b0..4a7d328b9 100644 --- a/trie/triedb/pathdb/nodebuffer.go +++ b/trie/triedb/pathdb/nodebuffer.go @@ -71,7 +71,7 @@ func (b *nodebuffer) node(owner common.Hash, path []byte, hash common.Hash) (*tr 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) + return nil, newUnexpectedNodeError("dirty", hash, n.Hash, owner, path, n.Blob) } return n, nil } diff --git a/trie/triedb/pathdb/testutils.go b/trie/triedb/pathdb/testutils.go index 4406dbc52..d6fdacb42 100644 --- a/trie/triedb/pathdb/testutils.go +++ b/trie/triedb/pathdb/testutils.go @@ -80,7 +80,7 @@ func (h *testHasher) Delete(key []byte) error { // 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) { +func (h *testHasher) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) { var ( nodes = make(map[common.Hash][]byte) set = trienode.NewNodeSet(h.owner) @@ -108,7 +108,7 @@ func (h *testHasher) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) { if root == types.EmptyRootHash && h.root != types.EmptyRootHash { set.AddNode(nil, trienode.NewDeleted()) } - return root, set + return root, set, nil } // hash performs the hash computation upon the provided states. diff --git a/trie/trienode/node.go b/trie/trienode/node.go index 98d5588b6..95315c2e9 100644 --- a/trie/trienode/node.go +++ b/trie/trienode/node.go @@ -39,7 +39,7 @@ func (n *Node) Size() int { // IsDeleted returns the indicator if the node is marked as deleted. func (n *Node) IsDeleted() bool { - return n.Hash == (common.Hash{}) + return len(n.Blob) == 0 } // New constructs a node with provided node information. 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 366259678..012f0087d 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 index cb3611baf..4c47e9c39 100644 --- a/trie/triestate/state.go +++ b/trie/triestate/state.go @@ -43,7 +43,7 @@ type Trie interface { // Commit the trie and returns a set of dirty nodes generated along with // the new root hash. - Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) + Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet, error) } // TrieLoader wraps functions to load tries. @@ -129,7 +129,10 @@ func Apply(prevRoot common.Hash, postRoot common.Hash, accounts map[common.Addre return nil, fmt.Errorf("failed to revert state, err: %w", err) } } - root, result := tr.Commit(false) + 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) } @@ -181,7 +184,10 @@ func updateAccount(ctx *context, loader TrieLoader, addr common.Address) error { return err } } - root, result := st.Commit(false) + root, result, err := st.Commit(false) + if err != nil { + return err + } if root != prev.Root { return errors.New("failed to reset storage trie") } @@ -232,7 +238,10 @@ func deleteAccount(ctx *context, loader TrieLoader, addr common.Address) error { return err } } - root, result := st.Commit(false) + root, result, err := st.Commit(false) + if err != nil { + return err + } if root != types.EmptyRootHash { return errors.New("failed to clear storage trie") } diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go new file mode 100644 index 000000000..ce059edc6 --- /dev/null +++ b/trie/utils/verkle.go @@ -0,0 +1,342 @@ +// Copyright 2023 go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package utils + +import ( + "encoding/binary" + "sync" + + "github.com/crate-crypto/go-ipa/bandersnatch/fr" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/metrics" + "github.com/gballet/go-verkle" + "github.com/holiman/uint256" +) + +const ( + // The spec of verkle key encoding can be found here. + // https://notes.ethereum.org/@vbuterin/verkle_tree_eip#Tree-embedding + VersionLeafKey = 0 + BalanceLeafKey = 1 + NonceLeafKey = 2 + CodeKeccakLeafKey = 3 + CodeSizeLeafKey = 4 +) + +var ( + zero = uint256.NewInt(0) + verkleNodeWidthLog2 = 8 + headerStorageOffset = uint256.NewInt(64) + mainStorageOffsetLshVerkleNodeWidth = new(uint256.Int).Lsh(uint256.NewInt(256), 31-uint(verkleNodeWidthLog2)) + codeOffset = uint256.NewInt(128) + verkleNodeWidth = uint256.NewInt(256) + codeStorageDelta = uint256.NewInt(0).Sub(codeOffset, headerStorageOffset) + + index0Point *verkle.Point // pre-computed commitment of polynomial [2+256*64] + + // cacheHitGauge is the metric to track how many cache hit occurred. + cacheHitGauge = metrics.NewRegisteredGauge("trie/verkle/cache/hit", nil) + + // cacheMissGauge is the metric to track how many cache miss occurred. + cacheMissGauge = metrics.NewRegisteredGauge("trie/verkle/cache/miss", nil) +) + +func init() { + // The byte array is the Marshalled output of the point computed as such: + // + // var ( + // config = verkle.GetConfig() + // fr verkle.Fr + // ) + // verkle.FromLEBytes(&fr, []byte{2, 64}) + // point := config.CommitToPoly([]verkle.Fr{fr}, 1) + index0Point = new(verkle.Point) + err := index0Point.SetBytes([]byte{34, 25, 109, 242, 193, 5, 144, 224, 76, 52, 189, 92, 197, 126, 9, 145, 27, 152, 199, 130, 165, 3, 210, 27, 193, 131, 142, 28, 110, 26, 16, 191}) + if err != nil { + panic(err) + } +} + +// PointCache is the LRU cache for storing evaluated address commitment. +type PointCache struct { + lru lru.BasicLRU[string, *verkle.Point] + lock sync.RWMutex +} + +// NewPointCache returns the cache with specified size. +func NewPointCache(maxItems int) *PointCache { + return &PointCache{ + lru: lru.NewBasicLRU[string, *verkle.Point](maxItems), + } +} + +// Get returns the cached commitment for the specified address, or computing +// it on the flight. +func (c *PointCache) Get(addr []byte) *verkle.Point { + c.lock.Lock() + defer c.lock.Unlock() + + p, ok := c.lru.Get(string(addr)) + if ok { + cacheHitGauge.Inc(1) + return p + } + cacheMissGauge.Inc(1) + p = evaluateAddressPoint(addr) + c.lru.Add(string(addr), p) + return p +} + +// GetStem returns the first 31 bytes of the tree key as the tree stem. It only +// works for the account metadata whose treeIndex is 0. +func (c *PointCache) GetStem(addr []byte) []byte { + p := c.Get(addr) + return pointToHash(p, 0)[:31] +} + +// GetTreeKey performs both the work of the spec's get_tree_key function, and that +// of pedersen_hash: it builds the polynomial in pedersen_hash without having to +// create a mostly zero-filled buffer and "type cast" it to a 128-long 16-byte +// array. Since at most the first 5 coefficients of the polynomial will be non-zero, +// these 5 coefficients are created directly. +func GetTreeKey(address []byte, treeIndex *uint256.Int, subIndex byte) []byte { + if len(address) < 32 { + var aligned [32]byte + address = append(aligned[:32-len(address)], address...) + } + // poly = [2+256*64, address_le_low, address_le_high, tree_index_le_low, tree_index_le_high] + var poly [5]fr.Element + + // 32-byte address, interpreted as two little endian + // 16-byte numbers. + verkle.FromLEBytes(&poly[1], address[:16]) + verkle.FromLEBytes(&poly[2], address[16:]) + + // treeIndex must be interpreted as a 32-byte aligned little-endian integer. + // e.g: if treeIndex is 0xAABBCC, we need the byte representation to be 0xCCBBAA00...00. + // poly[3] = LE({CC,BB,AA,00...0}) (16 bytes), poly[4]=LE({00,00,...}) (16 bytes). + // + // To avoid unnecessary endianness conversions for go-ipa, we do some trick: + // - poly[3]'s byte representation is the same as the *top* 16 bytes (trieIndexBytes[16:]) of + // 32-byte aligned big-endian representation (BE({00,...,AA,BB,CC})). + // - poly[4]'s byte representation is the same as the *low* 16 bytes (trieIndexBytes[:16]) of + // the 32-byte aligned big-endian representation (BE({00,00,...}). + trieIndexBytes := treeIndex.Bytes32() + verkle.FromBytes(&poly[3], trieIndexBytes[16:]) + verkle.FromBytes(&poly[4], trieIndexBytes[:16]) + + cfg := verkle.GetConfig() + ret := cfg.CommitToPoly(poly[:], 0) + + // add a constant point corresponding to poly[0]=[2+256*64]. + ret.Add(ret, index0Point) + + return pointToHash(ret, subIndex) +} + +// GetTreeKeyWithEvaluatedAddress is basically identical to GetTreeKey, the only +// difference is a part of polynomial is already evaluated. +// +// Specifically, poly = [2+256*64, address_le_low, address_le_high] is already +// evaluated. +func GetTreeKeyWithEvaluatedAddress(evaluated *verkle.Point, treeIndex *uint256.Int, subIndex byte) []byte { + var poly [5]fr.Element + + poly[0].SetZero() + poly[1].SetZero() + poly[2].SetZero() + + // little-endian, 32-byte aligned treeIndex + var index [32]byte + for i := 0; i < len(treeIndex); i++ { + binary.LittleEndian.PutUint64(index[i*8:(i+1)*8], treeIndex[i]) + } + verkle.FromLEBytes(&poly[3], index[:16]) + verkle.FromLEBytes(&poly[4], index[16:]) + + cfg := verkle.GetConfig() + ret := cfg.CommitToPoly(poly[:], 0) + + // add the pre-evaluated address + ret.Add(ret, evaluated) + + return pointToHash(ret, subIndex) +} + +// VersionKey returns the verkle tree key of the version field for the specified account. +func VersionKey(address []byte) []byte { + return GetTreeKey(address, zero, VersionLeafKey) +} + +// BalanceKey returns the verkle tree key of the balance field for the specified account. +func BalanceKey(address []byte) []byte { + return GetTreeKey(address, zero, BalanceLeafKey) +} + +// NonceKey returns the verkle tree key of the nonce field for the specified account. +func NonceKey(address []byte) []byte { + return GetTreeKey(address, zero, NonceLeafKey) +} + +// CodeKeccakKey returns the verkle tree key of the code keccak field for +// the specified account. +func CodeKeccakKey(address []byte) []byte { + return GetTreeKey(address, zero, CodeKeccakLeafKey) +} + +// CodeSizeKey returns the verkle tree key of the code size field for the +// specified account. +func CodeSizeKey(address []byte) []byte { + return GetTreeKey(address, zero, CodeSizeLeafKey) +} + +func codeChunkIndex(chunk *uint256.Int) (*uint256.Int, byte) { + var ( + chunkOffset = new(uint256.Int).Add(codeOffset, chunk) + treeIndex = new(uint256.Int).Div(chunkOffset, verkleNodeWidth) + subIndexMod = new(uint256.Int).Mod(chunkOffset, verkleNodeWidth) + ) + var subIndex byte + if len(subIndexMod) != 0 { + subIndex = byte(subIndexMod[0]) + } + return treeIndex, subIndex +} + +// CodeChunkKey returns the verkle tree key of the code chunk for the +// specified account. +func CodeChunkKey(address []byte, chunk *uint256.Int) []byte { + treeIndex, subIndex := codeChunkIndex(chunk) + return GetTreeKey(address, treeIndex, subIndex) +} + +func storageIndex(bytes []byte) (*uint256.Int, byte) { + // If the storage slot is in the header, we need to add the header offset. + var key uint256.Int + key.SetBytes(bytes) + if key.Cmp(codeStorageDelta) < 0 { + // This addition is always safe; it can't ever overflow since pos + +package utils + +import ( + "bytes" + "testing" + + "github.com/gballet/go-verkle" + "github.com/holiman/uint256" +) + +func TestTreeKey(t *testing.T) { + var ( + address = []byte{0x01} + addressEval = evaluateAddressPoint(address) + smallIndex = uint256.NewInt(1) + largeIndex = uint256.NewInt(10000) + smallStorage = []byte{0x1} + largeStorage = bytes.Repeat([]byte{0xff}, 16) + ) + if !bytes.Equal(VersionKey(address), VersionKeyWithEvaluatedAddress(addressEval)) { + t.Fatal("Unmatched version key") + } + if !bytes.Equal(BalanceKey(address), BalanceKeyWithEvaluatedAddress(addressEval)) { + t.Fatal("Unmatched balance key") + } + if !bytes.Equal(NonceKey(address), NonceKeyWithEvaluatedAddress(addressEval)) { + t.Fatal("Unmatched nonce key") + } + if !bytes.Equal(CodeKeccakKey(address), CodeKeccakKeyWithEvaluatedAddress(addressEval)) { + t.Fatal("Unmatched code keccak key") + } + if !bytes.Equal(CodeSizeKey(address), CodeSizeKeyWithEvaluatedAddress(addressEval)) { + t.Fatal("Unmatched code size key") + } + if !bytes.Equal(CodeChunkKey(address, smallIndex), CodeChunkKeyWithEvaluatedAddress(addressEval, smallIndex)) { + t.Fatal("Unmatched code chunk key") + } + if !bytes.Equal(CodeChunkKey(address, largeIndex), CodeChunkKeyWithEvaluatedAddress(addressEval, largeIndex)) { + t.Fatal("Unmatched code chunk key") + } + if !bytes.Equal(StorageSlotKey(address, smallStorage), StorageSlotKeyWithEvaluatedAddress(addressEval, smallStorage)) { + t.Fatal("Unmatched storage slot key") + } + if !bytes.Equal(StorageSlotKey(address, largeStorage), StorageSlotKeyWithEvaluatedAddress(addressEval, largeStorage)) { + t.Fatal("Unmatched storage slot key") + } +} + +// goos: darwin +// goarch: amd64 +// pkg: github.com/ethereum/go-ethereum/trie/utils +// cpu: VirtualApple @ 2.50GHz +// BenchmarkTreeKey +// BenchmarkTreeKey-8 398731 2961 ns/op 32 B/op 1 allocs/op +func BenchmarkTreeKey(b *testing.B) { + // Initialize the IPA settings which can be pretty expensive. + verkle.GetConfig() + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + BalanceKey([]byte{0x01}) + } +} + +// goos: darwin +// goarch: amd64 +// pkg: github.com/ethereum/go-ethereum/trie/utils +// cpu: VirtualApple @ 2.50GHz +// BenchmarkTreeKeyWithEvaluation +// BenchmarkTreeKeyWithEvaluation-8 513855 2324 ns/op 32 B/op 1 allocs/op +func BenchmarkTreeKeyWithEvaluation(b *testing.B) { + // Initialize the IPA settings which can be pretty expensive. + verkle.GetConfig() + + addr := []byte{0x01} + eval := evaluateAddressPoint(addr) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + BalanceKeyWithEvaluatedAddress(eval) + } +} + +// goos: darwin +// goarch: amd64 +// pkg: github.com/ethereum/go-ethereum/trie/utils +// cpu: VirtualApple @ 2.50GHz +// BenchmarkStorageKey +// BenchmarkStorageKey-8 230516 4584 ns/op 96 B/op 3 allocs/op +func BenchmarkStorageKey(b *testing.B) { + // Initialize the IPA settings which can be pretty expensive. + verkle.GetConfig() + + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + StorageSlotKey([]byte{0x01}, bytes.Repeat([]byte{0xff}, 32)) + } +} + +// goos: darwin +// goarch: amd64 +// pkg: github.com/ethereum/go-ethereum/trie/utils +// cpu: VirtualApple @ 2.50GHz +// BenchmarkStorageKeyWithEvaluation +// BenchmarkStorageKeyWithEvaluation-8 320125 3753 ns/op 96 B/op 3 allocs/op +func BenchmarkStorageKeyWithEvaluation(b *testing.B) { + // Initialize the IPA settings which can be pretty expensive. + verkle.GetConfig() + + addr := []byte{0x01} + eval := evaluateAddressPoint(addr) + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + StorageSlotKeyWithEvaluatedAddress(eval, bytes.Repeat([]byte{0xff}, 32)) + } +} diff --git a/trie/verkle.go b/trie/verkle.go new file mode 100644 index 000000000..89e2e5340 --- /dev/null +++ b/trie/verkle.go @@ -0,0 +1,375 @@ +// Copyright 2023 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 trie + +import ( + "encoding/binary" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/ethereum/go-ethereum/trie/utils" + "github.com/gballet/go-verkle" + "github.com/holiman/uint256" +) + +var ( + zero [32]byte + errInvalidRootType = errors.New("invalid node type for root") +) + +// VerkleTrie is a wrapper around VerkleNode that implements the trie.Trie +// interface so that Verkle trees can be reused verbatim. +type VerkleTrie struct { + root verkle.VerkleNode + db *Database + cache *utils.PointCache + reader *trieReader +} + +// NewVerkleTrie constructs a verkle tree based on the specified root hash. +func NewVerkleTrie(root common.Hash, db *Database, cache *utils.PointCache) (*VerkleTrie, error) { + reader, err := newTrieReader(root, common.Hash{}, db) + if err != nil { + return nil, err + } + // Parse the root verkle node if it's not empty. + node := verkle.New() + if root != types.EmptyVerkleHash && root != types.EmptyRootHash { + blob, err := reader.node(nil, common.Hash{}) + if err != nil { + return nil, err + } + node, err = verkle.ParseNode(blob, 0) + if err != nil { + return nil, err + } + } + return &VerkleTrie{ + root: node, + db: db, + cache: cache, + reader: reader, + }, nil +} + +// GetKey returns the sha3 preimage of a hashed key that was previously used +// to store a value. +func (t *VerkleTrie) GetKey(key []byte) []byte { + return key +} + +// GetAccount implements state.Trie, retrieving the account with the specified +// account address. If the specified account is not in the verkle tree, nil will +// be returned. If the tree is corrupted, an error will be returned. +func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error) { + var ( + acc = &types.StateAccount{} + values [][]byte + err error + ) + switch n := t.root.(type) { + case *verkle.InternalNode: + values, err = n.GetValuesAtStem(t.cache.GetStem(addr[:]), t.nodeResolver) + if err != nil { + return nil, fmt.Errorf("GetAccount (%x) error: %v", addr, err) + } + default: + return nil, errInvalidRootType + } + if values == nil { + return nil, nil + } + // Decode nonce in little-endian + if len(values[utils.NonceLeafKey]) > 0 { + acc.Nonce = binary.LittleEndian.Uint64(values[utils.NonceLeafKey]) + } + // Decode balance in little-endian + var balance [32]byte + copy(balance[:], values[utils.BalanceLeafKey]) + for i := 0; i < len(balance)/2; i++ { + balance[len(balance)-i-1], balance[i] = balance[i], balance[len(balance)-i-1] + } + acc.Balance = new(big.Int).SetBytes(balance[:]) + + // Decode codehash + acc.CodeHash = values[utils.CodeKeccakLeafKey] + + // TODO account.Root is leave as empty. How should we handle the legacy account? + return acc, nil +} + +// GetStorage implements state.Trie, retrieving the storage slot with the specified +// account address and storage key. If the specified slot is not in the verkle tree, +// nil will be returned. If the tree is corrupted, an error will be returned. +func (t *VerkleTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) { + k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key) + val, err := t.root.Get(k, t.nodeResolver) + if err != nil { + return nil, err + } + return common.TrimLeftZeroes(val), nil +} + +// UpdateAccount implements state.Trie, writing the provided account into the tree. +// If the tree is corrupted, an error will be returned. +func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount) error { + var ( + err error + nonce, balance [32]byte + values = make([][]byte, verkle.NodeWidth) + ) + values[utils.VersionLeafKey] = zero[:] + values[utils.CodeKeccakLeafKey] = acc.CodeHash[:] + + // Encode nonce in little-endian + binary.LittleEndian.PutUint64(nonce[:], acc.Nonce) + values[utils.NonceLeafKey] = nonce[:] + + // Encode balance in little-endian + bytes := acc.Balance.Bytes() + if len(bytes) > 0 { + for i, b := range bytes { + balance[len(bytes)-i-1] = b + } + } + values[utils.BalanceLeafKey] = balance[:] + + switch n := t.root.(type) { + case *verkle.InternalNode: + err = n.InsertValuesAtStem(t.cache.GetStem(addr[:]), values, t.nodeResolver) + if err != nil { + return fmt.Errorf("UpdateAccount (%x) error: %v", addr, err) + } + default: + return errInvalidRootType + } + // TODO figure out if the code size needs to be updated, too + return nil +} + +// UpdateStorage implements state.Trie, writing the provided storage slot into +// the tree. If the tree is corrupted, an error will be returned. +func (t *VerkleTrie) UpdateStorage(address common.Address, key, value []byte) error { + // Left padding the slot value to 32 bytes. + var v [32]byte + if len(value) >= 32 { + copy(v[:], value[:32]) + } else { + copy(v[32-len(value):], value[:]) + } + k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(address.Bytes()), key) + return t.root.Insert(k, v[:], t.nodeResolver) +} + +// DeleteAccount implements state.Trie, deleting the specified account from the +// trie. If the account was not existent in the trie, no error will be returned. +// If the trie is corrupted, an error will be returned. +func (t *VerkleTrie) DeleteAccount(addr common.Address) error { + var ( + err error + values = make([][]byte, verkle.NodeWidth) + ) + for i := 0; i < verkle.NodeWidth; i++ { + values[i] = zero[:] + } + switch n := t.root.(type) { + case *verkle.InternalNode: + err = n.InsertValuesAtStem(t.cache.GetStem(addr.Bytes()), values, t.nodeResolver) + if err != nil { + return fmt.Errorf("DeleteAccount (%x) error: %v", addr, err) + } + default: + return errInvalidRootType + } + return nil +} + +// DeleteStorage implements state.Trie, deleting the specified storage slot from +// the trie. If the storage slot was not existent in the trie, no error will be +// returned. If the trie is corrupted, an error will be returned. +func (t *VerkleTrie) DeleteStorage(addr common.Address, key []byte) error { + var zero [32]byte + k := utils.StorageSlotKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), key) + return t.root.Insert(k, zero[:], t.nodeResolver) +} + +// Hash returns the root hash of the tree. It does not write to the database and +// can be used even if the tree doesn't have one. +func (t *VerkleTrie) Hash() common.Hash { + return t.root.Commit().Bytes() +} + +// Commit writes all nodes to the tree's memory database. +func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet, error) { + root, ok := t.root.(*verkle.InternalNode) + if !ok { + return common.Hash{}, nil, errors.New("unexpected root node type") + } + nodes, err := root.BatchSerialize() + if err != nil { + return common.Hash{}, nil, fmt.Errorf("serializing tree nodes: %s", err) + } + nodeset := trienode.NewNodeSet(common.Hash{}) + for _, node := range nodes { + // hash parameter is not used in pathdb + nodeset.AddNode(node.Path, trienode.New(common.Hash{}, node.SerializedBytes)) + } + // Serialize root commitment form + return t.Hash(), nodeset, nil +} + +// NodeIterator implements state.Trie, returning an iterator that returns +// nodes of the trie. Iteration starts at the key after the given start key. +// +// TODO(gballet, rjl493456442) implement it. +func (t *VerkleTrie) NodeIterator(startKey []byte) (NodeIterator, error) { + panic("not implemented") +} + +// Prove implements state.Trie, constructing 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 node and can be retrieved by verifying the proof. +// +// 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. +// +// TODO(gballet, rjl493456442) implement it. +func (t *VerkleTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error { + panic("not implemented") +} + +// Copy returns a deep-copied verkle tree. +func (t *VerkleTrie) Copy() *VerkleTrie { + return &VerkleTrie{ + root: t.root.Copy(), + db: t.db, + cache: t.cache, + reader: t.reader, + } +} + +// IsVerkle indicates if the trie is a Verkle trie. +func (t *VerkleTrie) IsVerkle() bool { + return true +} + +// ChunkedCode represents a sequence of 32-bytes chunks of code (31 bytes of which +// are actual code, and 1 byte is the pushdata offset). +type ChunkedCode []byte + +// Copy the values here so as to avoid an import cycle +const ( + PUSH1 = byte(0x60) + PUSH32 = byte(0x7f) +) + +// ChunkifyCode generates the chunked version of an array representing EVM bytecode +func ChunkifyCode(code []byte) ChunkedCode { + var ( + chunkOffset = 0 // offset in the chunk + chunkCount = len(code) / 31 + codeOffset = 0 // offset in the code + ) + if len(code)%31 != 0 { + chunkCount++ + } + chunks := make([]byte, chunkCount*32) + for i := 0; i < chunkCount; i++ { + // number of bytes to copy, 31 unless the end of the code has been reached. + end := 31 * (i + 1) + if len(code) < end { + end = len(code) + } + copy(chunks[i*32+1:], code[31*i:end]) // copy the code itself + + // chunk offset = taken from the last chunk. + if chunkOffset > 31 { + // skip offset calculation if push data covers the whole chunk + chunks[i*32] = 31 + chunkOffset = 1 + continue + } + chunks[32*i] = byte(chunkOffset) + chunkOffset = 0 + + // Check each instruction and update the offset it should be 0 unless + // a PUSH-N overflows. + for ; codeOffset < end; codeOffset++ { + if code[codeOffset] >= PUSH1 && code[codeOffset] <= PUSH32 { + codeOffset += int(code[codeOffset] - PUSH1 + 1) + if codeOffset+1 >= 31*(i+1) { + codeOffset++ + chunkOffset = codeOffset - 31*(i+1) + break + } + } + } + } + return chunks +} + +// UpdateContractCode implements state.Trie, writing the provided contract code +// into the trie. +func (t *VerkleTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error { + var ( + chunks = ChunkifyCode(code) + values [][]byte + key []byte + err error + ) + for i, chunknr := 0, uint64(0); i < len(chunks); i, chunknr = i+32, chunknr+1 { + groupOffset := (chunknr + 128) % 256 + if groupOffset == 0 /* start of new group */ || chunknr == 0 /* first chunk in header group */ { + values = make([][]byte, verkle.NodeWidth) + key = utils.CodeChunkKeyWithEvaluatedAddress(t.cache.Get(addr.Bytes()), uint256.NewInt(chunknr)) + } + values[groupOffset] = chunks[i : i+32] + + // Reuse the calculated key to also update the code size. + if i == 0 { + cs := make([]byte, 32) + binary.LittleEndian.PutUint64(cs, uint64(len(code))) + values[utils.CodeSizeLeafKey] = cs + } + if groupOffset == 255 || len(chunks)-i <= 32 { + switch root := t.root.(type) { + case *verkle.InternalNode: + err = root.InsertValuesAtStem(key[:31], values, t.nodeResolver) + if err != nil { + return fmt.Errorf("UpdateContractCode (addr=%x) error: %w", addr[:], err) + } + default: + return errInvalidRootType + } + } + } + return nil +} + +func (t *VerkleTrie) ToDot() string { + return verkle.ToDot(t.root) +} + +func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) { + return t.reader.node(path, common.Hash{}) +} diff --git a/trie/verkle_test.go b/trie/verkle_test.go new file mode 100644 index 000000000..bd31ea387 --- /dev/null +++ b/trie/verkle_test.go @@ -0,0 +1,97 @@ +// Copyright 2023 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 trie + +import ( + "bytes" + "math/big" + "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/trie/triedb/pathdb" + "github.com/ethereum/go-ethereum/trie/utils" +) + +var ( + accounts = map[common.Address]*types.StateAccount{ + {1}: { + Nonce: 100, + Balance: big.NewInt(100), + CodeHash: common.Hash{0x1}.Bytes(), + }, + {2}: { + Nonce: 200, + Balance: big.NewInt(200), + CodeHash: common.Hash{0x2}.Bytes(), + }, + } + storages = map[common.Address]map[common.Hash][]byte{ + {1}: { + common.Hash{10}: []byte{10}, + common.Hash{11}: []byte{11}, + common.MaxHash: []byte{0xff}, + }, + {2}: { + common.Hash{20}: []byte{20}, + common.Hash{21}: []byte{21}, + common.MaxHash: []byte{0xff}, + }, + } +) + +func TestVerkleTreeReadWrite(t *testing.T) { + db := NewDatabase(rawdb.NewMemoryDatabase(), &Config{ + IsVerkle: true, + PathDB: pathdb.Defaults, + }) + defer db.Close() + + tr, _ := NewVerkleTrie(types.EmptyVerkleHash, db, utils.NewPointCache(100)) + + for addr, acct := range accounts { + if err := tr.UpdateAccount(addr, acct); err != nil { + t.Fatalf("Failed to update account, %v", err) + } + for key, val := range storages[addr] { + if err := tr.UpdateStorage(addr, key.Bytes(), val); err != nil { + t.Fatalf("Failed to update account, %v", err) + } + } + } + + for addr, acct := range accounts { + stored, err := tr.GetAccount(addr) + if err != nil { + t.Fatalf("Failed to get account, %v", err) + } + if !reflect.DeepEqual(stored, acct) { + t.Fatal("account is not matched") + } + for key, val := range storages[addr] { + stored, err := tr.GetStorage(addr, key.Bytes()) + if err != nil { + t.Fatalf("Failed to get storage, %v", err) + } + if !bytes.Equal(stored, val) { + t.Fatal("storage is not matched") + } + } + } +}